Skip to content

Instantly share code, notes, and snippets.

@B3nac
Last active September 23, 2019 23:54
Show Gist options
  • Save B3nac/209eb462f86fd05eda9e349fdf23b42d to your computer and use it in GitHub Desktop.
Save B3nac/209eb462f86fd05eda9e349fdf23b42d to your computer and use it in GitHub Desktop.

Revisions

  1. B3nac revised this gist Sep 23, 2019. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions revoke.go
    Original file line number Diff line number Diff line change
    @@ -19,9 +19,9 @@ import (
    neturl "net/url"
    "sync"
    "time"
    "reflect"
    "reflect"
    "golang.org/x/crypto/ocsp"
    "pkcs7"
    "pkcs7"
    "logwrapper"
    )

  2. B3nac created this gist Sep 23, 2019.
    162 changes: 162 additions & 0 deletions logwrapper.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,162 @@
    // Package logwrapper implements a wrapper around the Go standard library's
    // logging package. Clients should set the current log level; only
    // messages below that level will actually be logged. For example, if
    // Level is set to LevelWarning, only log messages at the Warning,
    // Error, and Critical levels will be logged.
    package logwrapper

    import (
    "fmt"
    "log"
    "os"
    )

    // The following constants represent logging levels in increasing levels of seriousness.
    const (
    // LevelDebug is the log level for Debug statements.
    LevelDebug = iota
    // LevelInfo is the log level for Info statements.
    LevelInfo
    // LevelWarning is the log level for Warning statements.
    LevelWarning
    // LevelError is the log level for Error statements.
    LevelError
    // LevelCritical is the log level for Critical statements.
    LevelCritical
    // LevelFatal is the log level for Fatal statements.
    LevelFatal
    )

    var levelPrefix = [...]string{
    LevelDebug: "DEBUG",
    LevelInfo: "INFO",
    LevelWarning: "WARNING",
    LevelError: "ERROR",
    LevelCritical: "CRITICAL",
    LevelFatal: "FATAL",
    }

    // Level stores the current logging level.
    var Level = LevelInfo

    // SyslogWriter specifies the necessary methods for an alternate output
    // destination passed in via SetLogger.
    //
    // SyslogWriter is satisfied by *syslog.Writer.
    type SyslogWriter interface {
    Debug(string)
    Info(string)
    Warning(string)
    Err(string)
    Crit(string)
    Emerg(string)
    }

    // syslogWriter stores the SetLogger() parameter.
    var syslogWriter SyslogWriter

    // SetLogger sets the output used for output by this package.
    // A *syslog.Writer is a good choice for the logger parameter.
    // Call with a nil parameter to revert to default behavior.
    func SetLogger(logger SyslogWriter) {
    syslogWriter = logger
    }

    func print(l int, msg string) {
    if l >= Level {
    if syslogWriter != nil {
    switch l {
    case LevelDebug:
    syslogWriter.Debug(msg)
    case LevelInfo:
    syslogWriter.Info(msg)
    case LevelWarning:
    syslogWriter.Warning(msg)
    case LevelError:
    syslogWriter.Err(msg)
    case LevelCritical:
    syslogWriter.Crit(msg)
    case LevelFatal:
    syslogWriter.Emerg(msg)
    }
    } else {
    log.Printf("[%s] %s", levelPrefix[l], msg)
    }
    }
    }

    func outputf(l int, format string, v []interface{}) {
    print(l, fmt.Sprintf(format, v...))
    }

    func output(l int, v []interface{}) {
    print(l, fmt.Sprint(v...))
    }

    // Fatalf logs a formatted message at the "fatal" level and then exits. The
    // arguments are handled in the same manner as fmt.Printf.
    func Fatalf(format string, v ...interface{}) {
    outputf(LevelFatal, format, v)
    os.Exit(1)
    }

    // Fatal logs its arguments at the "fatal" level and then exits.
    func Fatal(v ...interface{}) {
    output(LevelFatal, v)
    os.Exit(1)
    }

    // Criticalf logs a formatted message at the "critical" level. The
    // arguments are handled in the same manner as fmt.Printf.
    func Criticalf(format string, v ...interface{}) {
    outputf(LevelCritical, format, v)
    }

    // Critical logs its arguments at the "critical" level.
    func Critical(v ...interface{}) {
    output(LevelCritical, v)
    }

    // Errorf logs a formatted message at the "error" level. The arguments
    // are handled in the same manner as fmt.Printf.
    func Errorf(format string, v ...interface{}) {
    outputf(LevelError, format, v)
    }

    // Error logs its arguments at the "error" level.
    func Error(v ...interface{}) {
    output(LevelError, v)
    }

    // Warningf logs a formatted message at the "warning" level. The
    // arguments are handled in the same manner as fmt.Printf.
    func Warningf(format string, v ...interface{}) {
    outputf(LevelWarning, format, v)
    }

    // Warning logs its arguments at the "warning" level.
    func Warning(v ...interface{}) {
    output(LevelWarning, v)
    }

    // Infof logs a formatted message at the "info" level. The arguments
    // are handled in the same manner as fmt.Printf.
    func Infof(format string, v ...interface{}) {
    outputf(LevelInfo, format, v)
    }

    // Info logs its arguments at the "info" level.
    func Info(v ...interface{}) {
    output(LevelInfo, v)
    }

    // Debugf logs a formatted message at the "debug" level. The arguments
    // are handled in the same manner as fmt.Printf.
    func Debugf(format string, v ...interface{}) {
    outputf(LevelDebug, format, v)
    }

    // Debug logs its arguments at the "debug" level.
    func Debug(v ...interface{}) {
    output(LevelDebug, v)
    }
    186 changes: 186 additions & 0 deletions pkcs7.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,186 @@
    // Package pkcs7 implements the subset of the CMS PKCS #7 datatype that is typically
    // used to package certificates and CRLs. Using openssl, every certificate converted
    // to PKCS #7 format from another encoding such as PEM conforms to this implementation.
    // reference: https://www.openssl.org/docs/man1.1.0/apps/crl2pkcs7.html
    //
    // PKCS #7 Data type, reference: https://tools.ietf.org/html/rfc2315
    //
    // The full pkcs#7 cryptographic message syntax allows for cryptographic enhancements,
    // for example data can be encrypted and signed and then packaged through pkcs#7 to be
    // sent over a network and then verified and decrypted. It is asn1, and the type of
    // PKCS #7 ContentInfo, which comprises the PKCS #7 structure, is:
    //
    // ContentInfo ::= SEQUENCE {
    // contentType ContentType,
    // content [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL
    // }
    //
    // There are 6 possible ContentTypes, data, signedData, envelopedData,
    // signedAndEnvelopedData, digestedData, and encryptedData. Here signedData, Data, and encrypted
    // Data are implemented, as the degenerate case of signedData without a signature is the typical
    // format for transferring certificates and CRLS, and Data and encryptedData are used in PKCS #12
    // formats.
    // The ContentType signedData has the form:
    //
    //
    // signedData ::= SEQUENCE {
    // version Version,
    // digestAlgorithms DigestAlgorithmIdentifiers,
    // contentInfo ContentInfo,
    // certificates [0] IMPLICIT ExtendedCertificatesAndCertificates OPTIONAL
    // crls [1] IMPLICIT CertificateRevocationLists OPTIONAL,
    // signerInfos SignerInfos
    // }
    //
    // As of yet signerInfos and digestAlgorithms are not parsed, as they are not relevant to
    // this system's use of PKCS #7 data. Version is an integer type, note that PKCS #7 is
    // recursive, this second layer of ContentInfo is similar ignored for our degenerate
    // usage. The ExtendedCertificatesAndCertificates type consists of a sequence of choices
    // between PKCS #6 extended certificates and x509 certificates. Any sequence consisting
    // of any number of extended certificates is not yet supported in this implementation.
    //
    // The ContentType Data is simply a raw octet string and is parsed directly into a Go []byte slice.
    //
    // The ContentType encryptedData is the most complicated and its form can be gathered by
    // the go type below. It essentially contains a raw octet string of encrypted data and an
    // algorithm identifier for use in decrypting this data.
    package pkcs7

    import (
    "crypto/x509"
    "crypto/x509/pkix"
    "encoding/asn1"
    "fmt"
    )

    // Types used for asn1 Unmarshaling.

    type signedData struct {
    Version int
    DigestAlgorithms asn1.RawValue
    ContentInfo asn1.RawValue
    Certificates asn1.RawValue `asn1:"optional" asn1:"tag:0"`
    Crls asn1.RawValue `asn1:"optional"`
    SignerInfos asn1.RawValue
    }

    type initPKCS7 struct {
    Raw asn1.RawContent
    ContentType asn1.ObjectIdentifier
    Content asn1.RawValue `asn1:"tag:0,explicit,optional"`
    }

    // Object identifier strings of the three implemented PKCS7 types.
    const (
    ObjIDData = "1.2.840.113549.1.7.1"
    ObjIDSignedData = "1.2.840.113549.1.7.2"
    ObjIDEncryptedData = "1.2.840.113549.1.7.6"
    )

    // PKCS7 represents the ASN1 PKCS #7 Content type. It contains one of three
    // possible types of Content objects, as denoted by the object identifier in
    // the ContentInfo field, the other two being nil. SignedData
    // is the degenerate SignedData Content info without signature used
    // to hold certificates and crls. Data is raw bytes, and EncryptedData
    // is as defined in PKCS #7 standard.
    type PKCS7 struct {
    Raw asn1.RawContent
    ContentInfo string
    Content Content
    }

    // Content implements three of the six possible PKCS7 data types. Only one is non-nil.
    type Content struct {
    Data []byte
    SignedData SignedData
    EncryptedData EncryptedData
    }

    // SignedData defines the typical carrier of certificates and crls.
    type SignedData struct {
    Raw asn1.RawContent
    Version int
    Certificates []*x509.Certificate
    Crl *pkix.CertificateList
    }

    // Data contains raw bytes. Used as a subtype in PKCS12.
    type Data struct {
    Bytes []byte
    }

    // EncryptedData contains encrypted data. Used as a subtype in PKCS12.
    type EncryptedData struct {
    Raw asn1.RawContent
    Version int
    EncryptedContentInfo EncryptedContentInfo
    }

    // EncryptedContentInfo is a subtype of PKCS7EncryptedData.
    type EncryptedContentInfo struct {
    Raw asn1.RawContent
    ContentType asn1.ObjectIdentifier
    ContentEncryptionAlgorithm pkix.AlgorithmIdentifier
    EncryptedContent []byte `asn1:"tag:0,optional"`
    }

    // ParsePKCS7 attempts to parse the DER encoded bytes of a
    // PKCS7 structure.
    func ParsePKCS7(raw []byte) (msg *PKCS7, err error) {

    var pkcs7 initPKCS7
    _, err = asn1.Unmarshal(raw, &pkcs7)
    if err != nil {
    return nil, fmt.Errorf("Certificate error, parse error", err)
    }

    msg = new(PKCS7)
    msg.Raw = pkcs7.Raw
    msg.ContentInfo = pkcs7.ContentType.String()
    switch {
    case msg.ContentInfo == ObjIDData:
    msg.ContentInfo = "Data"
    _, err = asn1.Unmarshal(pkcs7.Content.Bytes, &msg.Content.Data)
    if err != nil {
    return nil, fmt.Errorf("Certificate error, parse error", err)
    }
    case msg.ContentInfo == ObjIDSignedData:
    msg.ContentInfo = "SignedData"
    var signedData signedData
    _, err = asn1.Unmarshal(pkcs7.Content.Bytes, &signedData)
    if err != nil {
    return nil, fmt.Errorf("Certificate error, parse error", err)
    }
    if len(signedData.Certificates.Bytes) != 0 {
    msg.Content.SignedData.Certificates, err = x509.ParseCertificates(signedData.Certificates.Bytes)
    if err != nil {
    return nil, fmt.Errorf("Certificate error, parse error", err)
    }
    }
    if len(signedData.Crls.Bytes) != 0 {
    msg.Content.SignedData.Crl, err = x509.ParseDERCRL(signedData.Crls.Bytes)
    if err != nil {
    return nil, fmt.Errorf("Certificate error, parse error", err)
    }
    }
    msg.Content.SignedData.Version = signedData.Version
    msg.Content.SignedData.Raw = pkcs7.Content.Bytes
    case msg.ContentInfo == ObjIDEncryptedData:
    msg.ContentInfo = "EncryptedData"
    var encryptedData EncryptedData
    _, err = asn1.Unmarshal(pkcs7.Content.Bytes, &encryptedData)
    if err != nil {
    return nil, fmt.Errorf("Certificate error, parse error", err)
    }
    if encryptedData.Version != 0 {
    return nil, fmt.Errorf("Certificate error, parse error", err)
    }
    msg.Content.EncryptedData = encryptedData

    default:
    return nil, fmt.Errorf("Certificate error, parse error", err)
    }

    return msg, nil

    }
    69 changes: 69 additions & 0 deletions remote-cert-test.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,69 @@
    package main

    import (
    "fmt"
    "crypto/x509"
    "io/ioutil"
    "encoding/pem"
    "revoke"
    "os"
    "crypto/tls"
    "bytes"
    )

    var remoteRead = ioutil.ReadAll

    var SkipVerify = false

    var TimeoutSeconds = 3

    const defaultPort = "443"

    func GetCertificatesPEM(address string) (string, error) {
    conn, err := tls.Dial("tcp", address, &tls.Config{
    InsecureSkipVerify: true,
    })
    if err != nil {
    return "", err
    }
    defer conn.Close()
    var b bytes.Buffer
    for _, cert := range conn.ConnectionState().PeerCertificates {
    err := pem.Encode(&b, &pem.Block{
    Type: "CERTIFICATE",
    Bytes: cert.Raw,
    })
    if err != nil {
    return "", err
    }
    }
    return b.String(), nil
    }

    func mustParse(pemData string) *x509.Certificate {
    block, _ := pem.Decode([]byte(pemData))
    if block == nil {
    panic("Invalid PEM data.")
    } else if block.Type != "CERTIFICATE" {
    panic("Invalid PEM type.")
    }

    cert, err := x509.ParseCertificate([]byte(block.Bytes))
    if err != nil {
    panic(err.Error())
    }
    return cert
    }

    func main() {

    arg := os.Args[1]

    cert, _ := GetCertificatesPEM(arg)

    revoked, _ := revoke.VerifyCertificate(mustParse(cert))

    fmt.Printf("\n\n")

    fmt.Printf("Certificate has been revoked: ", revoked)
    }
    413 changes: 413 additions & 0 deletions revoke.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,413 @@
    // Package revoke provides functionality for checking the validity of
    // a cert. Specifically, the temporal validity of the certificate is
    // checked first, then any CRL and OCSP url in the cert is checked.
    package revoke

    import (
    "bytes"
    "crypto"
    "crypto/x509"
    "crypto/x509/pkix"
    "encoding/base64"
    "encoding/pem"
    "errors"
    "fmt"
    "io"
    "os"
    "io/ioutil"
    "net/http"
    neturl "net/url"
    "sync"
    "time"
    "reflect"
    "golang.org/x/crypto/ocsp"
    "pkcs7"
    "logwrapper"
    )

    // HardFail determines whether the failure to check the revocation
    // status of a certificate (i.e. due to network failure) causes
    // verification to fail (a hard failure).
    var HardFail = false

    // CRLSet associates a PKIX certificate list with the URL the CRL is
    // fetched from.
    var CRLSet = map[string]*pkix.CertificateList{}
    var crlLock = new(sync.Mutex)

    // We can't handle LDAP certificates, so this checks to see if the
    // URL string points to an LDAP resource so that we can ignore it.
    func ldapURL(url string) bool {
    u, err := neturl.Parse(url)
    if err != nil {
    logwrapper.Warningf("error parsing url %s: %v", url, err)
    return false
    }
    if u.Scheme == "ldap" {
    return true
    }
    return false
    }

    // revCheck should check the certificate for any revocations. It
    // returns a pair of booleans: the first indicates whether the certificate
    // is revoked, the second indicates whether the revocations were
    // successfully checked.. This leads to the following combinations:
    //
    // false, false: an error was encountered while checking revocations.
    //
    // false, true: the certificate was checked successfully and
    // it is not revoked.
    //
    // true, true: the certificate was checked successfully and
    // it is revoked.
    //
    // true, false: failure to check revocation status causes
    // verification to fail
    func revCheck(cert *x509.Certificate) (revoked, ok bool, err error) {
    for _, url := range cert.CRLDistributionPoints {
    if ldapURL(url) {
    logwrapper.Infof("skipping LDAP CRL: %s", url)
    continue
    }

    if revoked, ok, err := certIsRevokedCRL(cert, url); !ok {
    logwrapper.Warning("error checking revocation via CRL")
    if HardFail {
    return true, false, err
    }
    return false, false, err
    } else if revoked {
    logwrapper.Info("certificate is revoked via CRL")
    return true, true, err
    }
    }

    if revoked, ok, err := certIsRevokedOCSP(cert, HardFail); !ok {
    logwrapper.Warning("error checking revocation via OCSP")
    if HardFail {
    return true, false, err
    }
    return false, false, err
    } else if revoked {
    logwrapper.Info("certificate is revoked via OCSP")
    return true, true, err
    }

    return false, true, nil
    }

    // fetchCRL fetches and parses a CRL.
    func fetchCRL(url string) (*pkix.CertificateList, error) {
    resp, err := http.Get(url)
    if err != nil {
    return nil, err
    } else if resp.StatusCode >= 300 {
    return nil, errors.New("failed to retrieve CRL")
    }

    body, err := crlRead(resp.Body)
    if err != nil {
    return nil, err
    }
    resp.Body.Close()

    return x509.ParseCRL(body)
    }

    func getIssuer(cert *x509.Certificate) *x509.Certificate {
    var issuer *x509.Certificate
    var err error
    for _, issuingCert := range cert.IssuingCertificateURL {
    issuer, err = FetchRemote(issuingCert)
    if err != nil {
    continue
    }
    break
    }

    return issuer

    }

    // check a cert against a specific CRL. Returns the same bool pair
    // as revCheck, plus an error if one occurred.
    func certIsRevokedCRL(cert *x509.Certificate, url string) (revoked, ok bool, err error) {
    crl, ok := CRLSet[url]
    if ok && crl == nil {
    ok = false
    crlLock.Lock()
    delete(CRLSet, url)
    crlLock.Unlock()
    }

    var shouldFetchCRL = true
    if ok {
    if !crl.HasExpired(time.Now()) {
    shouldFetchCRL = false
    }
    }

    issuer := getIssuer(cert)

    if shouldFetchCRL {
    var err error
    crl, err = fetchCRL(url)
    if err != nil {
    logwrapper.Warningf("failed to fetch CRL: %v", err)
    return false, false, err
    }

    // check CRL signature
    if issuer != nil {
    err = issuer.CheckCRLSignature(crl)
    if err != nil {
    logwrapper.Warningf("failed to verify CRL: %v", err)
    return false, false, err
    }
    }

    crlLock.Lock()
    CRLSet[url] = crl
    crlLock.Unlock()
    }

    for _, revoked := range crl.TBSCertList.RevokedCertificates {
    if cert.SerialNumber.Cmp(revoked.SerialNumber) == 0 {
    logwrapper.Info("Serial number match: intermediate is revoked.")
    return true, true, err
    }
    }

    return false, true, err
    }

    // VerifyCertificate ensures that the certificate passed in hasn't
    // expired and checks the CRL for the server.

    func VerifyCertificate(cert *x509.Certificate) (revoked, ok bool) {

    msg := fmt.Sprintf("Extended Key Usage: %s\n", cert.ExtKeyUsage, cert.MaxPathLen)
    logwrapper.Info(msg)

    if reflect.DeepEqual(cert.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}) == true {
    msg := fmt.Sprintf("The certificate has ServerAuth key usage %s\n", cert.ExtKeyUsage)
    logwrapper.Info(msg)
    }

    if reflect.DeepEqual(cert.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}) == true {
    msg := fmt.Sprintf("The certificate has ClientAuth key usage %s\n", cert.ExtKeyUsage)
    logwrapper.Info(msg)
    }

    if reflect.DeepEqual(cert.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) == true {
    msg := fmt.Sprintf("The certificate has ClientAuth and ServerAuth key usage %s\n", cert.ExtKeyUsage)
    logwrapper.Info(msg)
    }

    if reflect.DeepEqual(cert.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning, x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageEmailProtection, x509.ExtKeyUsageOCSPSigning}) == true {
    msg := fmt.Sprintf("The certificate has ClientAuth, ServerAuth, CodeSigning, OCSPSigning, and EmailProtection usage %s\n", cert.ExtKeyUsage)
    logwrapper.Info(msg)
    }

    if !cert.IsCA {
    msg := fmt.Sprintf("Certificate is not a CA! Exiting. %s\n", cert.IsCA)
    logwrapper.Info(msg)
    os.Exit(3)
    }

    if cert.MaxPathLen > 5 {
    msg := fmt.Sprintf("Max path length exceeded! Exiting. %s\n", cert.MaxPathLen)
    logwrapper.Info(msg)
    os.Exit(3)
    }
    revoked, ok, _ = VerifyCertificateError(cert)
    return revoked, ok
    }

    // VerifyCertificateError ensures that the certificate passed in hasn't
    // expired and checks the CRL for the server.
    func VerifyCertificateError(cert *x509.Certificate) (revoked, ok bool, err error) {

    if !time.Now().Before(cert.NotAfter) {
    msg := fmt.Sprintf("Certificate expired %s\n", cert.NotAfter)
    logwrapper.Info(msg)
    return true, true, fmt.Errorf(msg)
    } else if !time.Now().After(cert.NotBefore) {
    msg := fmt.Sprintf("Certificate isn't valid until %s\n", cert.NotBefore)
    logwrapper.Info(msg)
    return true, true, fmt.Errorf(msg)
    }
    return revCheck(cert)
    }

    func FetchRemote(url string) (*x509.Certificate, error) {
    resp, err := http.Get(url)

    if err != nil {
    return nil, err
    }

    in, err := remoteRead(resp.Body)

    p, _ := pem.Decode(in)

    if p != nil {
    return ParseCertificatePEM(in)
    }
    return x509.ParseCertificate(in)
    }

    var ocspOpts = ocsp.RequestOptions{
    Hash: crypto.SHA1,
    }

    func ParseCertificatePEM(certPEM []byte) (*x509.Certificate, error) {
    certPEM = bytes.TrimSpace(certPEM)
    cert, rest, err := ParseOneCertificateFromPEM(certPEM)
    if err != nil {
    // Log the actual parsing error but throw a default parse error message.
    logwrapper.Debugf("Certificate parsing error: %v", err)
    return nil, fmt.Errorf("Error parsing certificate!")
    } else if cert == nil {
    return nil, fmt.Errorf("Error parsing certificate!")
    } else if len(rest) > 0 {
    return nil, fmt.Errorf("the PEM file should contain only one object")
    } else if len(cert) > 1 {
    return nil, fmt.Errorf("the PKCS7 object in the PEM file should contain only one certificate")
    }
    return cert[0], nil
    }

    func ParseOneCertificateFromPEM(certsPEM []byte) ([]*x509.Certificate, []byte, error) {

    block, rest := pem.Decode(certsPEM)
    if block == nil {
    return nil, rest, nil
    }

    cert, err := x509.ParseCertificate(block.Bytes)
    if err != nil {
    pkcs7data, err := pkcs7.ParsePKCS7(block.Bytes)
    if err != nil {
    return nil, rest, err
    }
    if pkcs7data.ContentInfo != "SignedData" {
    return nil, rest, fmt.Errorf("only PKCS #7 Signed Data Content Info supported for certificate parsing")
    }
    certs := pkcs7data.Content.SignedData.Certificates
    if certs == nil {
    return nil, rest, fmt.Errorf("PKCS #7 structure contains no certificates")
    }
    return certs, rest, nil
    }
    var certs = []*x509.Certificate{cert}
    return certs, rest, nil
    }

    func certIsRevokedOCSP(leaf *x509.Certificate, strict bool) (revoked, ok bool, e error) {
    var err error

    ocspURLs := leaf.OCSPServer
    if len(ocspURLs) == 0 {
    // OCSP not enabled for this certificate.
    return false, true, nil
    }

    issuer := getIssuer(leaf)

    if issuer == nil {
    return false, false, nil
    }

    ocspRequest, err := ocsp.CreateRequest(leaf, issuer, &ocspOpts)
    if err != nil {
    return revoked, ok, err
    }

    for _, server := range ocspURLs {
    resp, err := sendOCSPRequest(server, ocspRequest, leaf, issuer)
    if err != nil {
    if strict {
    return revoked, ok, err
    }
    continue
    }

    // There wasn't an error fetching the OCSP status.
    ok = true

    if resp.Status != ocsp.Good {
    // The certificate was revoked.
    revoked = true
    }

    return revoked, ok, err
    }
    return revoked, ok, err
    }

    // sendOCSPRequest attempts to request an OCSP response from the
    // server. The error only indicates a failure to *fetch* the
    // certificate, and *does not* mean the certificate is valid.
    func sendOCSPRequest(server string, req []byte, leaf, issuer *x509.Certificate) (*ocsp.Response, error) {
    var resp *http.Response
    var err error
    if len(req) > 256 {
    buf := bytes.NewBuffer(req)
    resp, err = http.Post(server, "application/ocsp-request", buf)
    } else {
    reqURL := server + "/" + neturl.QueryEscape(base64.StdEncoding.EncodeToString(req))
    resp, err = http.Get(reqURL)
    }

    if err != nil {
    return nil, err
    }

    if resp.StatusCode != http.StatusOK {
    return nil, errors.New("failed to retrieve OSCP")
    }

    body, err := ocspRead(resp.Body)
    if err != nil {
    return nil, err
    }
    resp.Body.Close()

    switch {
    case bytes.Equal(body, ocsp.UnauthorizedErrorResponse):
    return nil, errors.New("OSCP unauthorized")
    case bytes.Equal(body, ocsp.MalformedRequestErrorResponse):
    return nil, errors.New("OSCP malformed")
    case bytes.Equal(body, ocsp.InternalErrorErrorResponse):
    return nil, errors.New("OSCP internal error")
    case bytes.Equal(body, ocsp.TryLaterErrorResponse):
    return nil, errors.New("OSCP try later")
    case bytes.Equal(body, ocsp.SigRequredErrorResponse):
    return nil, errors.New("OSCP signature required")
    }

    return ocsp.ParseResponseForCert(body, leaf, issuer)
    }

    var crlRead = ioutil.ReadAll

    // SetCRLFetcher sets the function to use to read from the http response body
    func SetCRLFetcher(fn func(io.Reader) ([]byte, error)) {
    crlRead = fn
    }

    var remoteRead = ioutil.ReadAll

    // SetRemoteFetcher sets the function to use to read from the http response body
    func SetRemoteFetcher(fn func(io.Reader) ([]byte, error)) {
    remoteRead = fn
    }

    var ocspRead = ioutil.ReadAll

    // SetOCSPFetcher sets the function to use to read from the http response body
    func SetOCSPFetcher(fn func(io.Reader) ([]byte, error)) {
    ocspRead = fn
    }