Last active
September 23, 2019 23:54
-
-
Save B3nac/209eb462f86fd05eda9e349fdf23b42d to your computer and use it in GitHub Desktop.
Test certs remotely before connecting with client
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment