Last active
September 23, 2019 17:22
-
-
Save B3nac/10dc3786937383437935b5483d1e4827 to your computer and use it in GitHub Desktop.
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" | |
"github.com/cloudflare/cfssl/helpers" | |
"github.com/cloudflare/cfssl/log" | |
) | |
// 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 { | |
log.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) { | |
log.Infof("skipping LDAP CRL: %s", url) | |
continue | |
} | |
if revoked, ok, err := certIsRevokedCRL(cert, url); !ok { | |
log.Warning("error checking revocation via CRL") | |
if HardFail { | |
return true, false, err | |
} | |
return false, false, err | |
} else if revoked { | |
log.Info("certificate is revoked via CRL") | |
return true, true, err | |
} | |
} | |
if revoked, ok, err := certIsRevokedOCSP(cert, HardFail); !ok { | |
log.Warning("error checking revocation via OCSP") | |
if HardFail { | |
return true, false, err | |
} | |
return false, false, err | |
} else if revoked { | |
log.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 { | |
log.Warningf("failed to fetch CRL: %v", err) | |
return false, false, err | |
} | |
// check CRL signature | |
if issuer != nil { | |
err = issuer.CheckCRLSignature(crl) | |
if err != nil { | |
log.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 { | |
log.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) | |
log.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) | |
log.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) | |
log.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) | |
log.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) | |
log.Info(msg) | |
} | |
if !cert.IsCA { | |
msg := fmt.Sprintf("Certificate is not a CA! Exiting. %s\n", cert.IsCA) | |
log.Info(msg) | |
os.Exit(3) | |
} | |
if cert.MaxPathLen > 5 { | |
msg := fmt.Sprintf("Max path length exceeded! Exiting. %s\n", cert.MaxPathLen) | |
log.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) | |
log.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) | |
log.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 helpers.ParseCertificatePEM(in) | |
} | |
return x509.ParseCertificate(in) | |
} | |
var ocspOpts = ocsp.RequestOptions{ | |
Hash: crypto.SHA1, | |
} | |
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