Skip to content

Instantly share code, notes, and snippets.

@ww24
Last active August 13, 2023 16:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ww24/3e2af7f82aaf1b986a130bce78bfdebd to your computer and use it in GitHub Desktop.
Save ww24/3e2af7f82aaf1b986a130bce78bfdebd to your computer and use it in GitHub Desktop.
OCSP verifier implemented in Go
module sandbox.example.com/crl-ocsp
go 1.21
toolchain go1.21.0
require golang.org/x/crypto v0.12.0
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
package main
import (
"bytes"
"context"
"crypto"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"
"io"
"log"
"net/http"
"time"
"golang.org/x/crypto/ocsp"
)
const (
rootCAG3URL = "https://www.apple.com/certificateauthority/AppleRootCA-G3.cer"
intermediateWWDRCAG6URL = "https://www.apple.com/certificateauthority/AppleWWDRCAG6.cer"
)
var (
// see https://oidref.com/1.3.6.1.5.5.7.48.1.5
oidExtensionNameOCSPNoCheck = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 48, 1, 5}
)
func main() {
httpClient := &http.Client{Timeout: 5 * time.Second}
rootCACert, err := fetchCACert(httpClient, rootCAG3URL)
if err != nil {
panic(err)
}
intermediateCACert, err := fetchCACert(httpClient, intermediateWWDRCAG6URL)
if err != nil {
panic(err)
}
log.Println("Root CA Subject:", rootCACert.Subject)
log.Println("Intermediate CA Subject:", intermediateCACert.Subject)
ctx := context.Background()
verifier := newOCSPVerifier(httpClient)
if err := verifier.verify(ctx, intermediateCACert, rootCACert, time.Now()); err != nil {
panic(err)
}
log.Println("success")
}
func fetchCACert(httpClient *http.Client, url string) (*x509.Certificate, error) {
resp, err := httpClient.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
buf, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
cert, err := x509.ParseCertificate(buf)
if err != nil {
return nil, err
}
if !cert.IsCA {
return nil, errors.New("not a CA certificate")
}
return cert, nil
}
type ocspVerifier struct {
client *http.Client
}
func newOCSPVerifier(httpClient *http.Client) *ocspVerifier {
return &ocspVerifier{
client: httpClient,
}
}
func (c *ocspVerifier) verify(ctx context.Context, cert, issuer *x509.Certificate, now time.Time) error {
if len(cert.OCSPServer) == 0 {
return errors.New("no OCSPServer")
}
ocspServer := cert.OCSPServer[0]
log.Println("OCSP Server:", ocspServer)
ocspRequest, err := ocsp.CreateRequest(cert, issuer, &ocsp.RequestOptions{Hash: crypto.SHA256})
if err != nil {
return err
}
log.Println("OCSP Request Size:", len(ocspRequest))
req, err := http.NewRequest(http.MethodPost, ocspServer, bytes.NewReader(ocspRequest))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/ocsp-request")
req.Header.Set("Accept", "application/ocsp-response")
resp, err := c.client.Do(req.WithContext(ctx))
if err != nil {
return err
}
defer resp.Body.Close()
log.Println("OCSP Response Status Code:", resp.StatusCode)
if retryAfter := resp.Header.Get("Retry-After"); retryAfter != "" {
log.Println("OCSP Response Retry-After:", retryAfter)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
ocspResponse, err := ocsp.ParseResponseForCert(body, cert, issuer)
if err != nil {
var responseErr ocsp.ResponseError
if errors.As(err, &responseErr) {
return fmt.Errorf("ocsp status: %s", responseErr.Status.String())
}
return err
}
if ocspResponse.Certificate != nil {
log.Println("OCSP Response Certificate Subject:", ocspResponse.Certificate.Subject)
log.Println("OCSP Response Certificate Issuer Common Name:", ocspResponse.Certificate.Issuer.CommonName)
log.Println("OCSP Response Certificate Key Usage:", ocspResponse.Certificate.KeyUsage)
if ocspResponse.Certificate.IsCA {
return errors.New("ocsp response certificate should be end-entity certificate")
}
if ocspResponse.Certificate.KeyUsage&x509.KeyUsageDigitalSignature == 0 {
return errors.New("ocsp response certificate has no digital signature key usage")
}
if !hasExtension(ocspResponse.Certificate.Extensions, oidExtensionNameOCSPNoCheck) {
return errors.New("no id-pkix-ocsp-nocheck extension")
}
// verify OCSP Responder Certificate
pool := x509.NewCertPool()
pool.AddCert(issuer)
opts := x509.VerifyOptions{
Roots: pool,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageOCSPSigning},
}
if _, err := ocspResponse.Certificate.Verify(opts); err != nil {
return err
}
} else {
if issuer.KeyUsage&x509.KeyUsageDigitalSignature == 0 {
return errors.New("certificate has no digital signature key usage")
}
}
// check OCSP Response
if ocspResponse.ThisUpdate.After(now) {
return fmt.Errorf("ocsp response thisUpdate(%s) is in the future", ocspResponse.ThisUpdate.Format(time.RFC3339))
}
if !ocspResponse.NextUpdate.IsZero() && ocspResponse.NextUpdate.Before(now) {
return fmt.Errorf("ocsp response nextUpdate(%s) is in the past", ocspResponse.NextUpdate.Format(time.RFC3339))
}
log.Println("OCSP Response Status:", ocspResponse.Status)
if ocspResponse.Status != ocsp.Good {
return errors.New("ocsp response status is not good")
}
return nil
}
func hasExtension(ext []pkix.Extension, oid asn1.ObjectIdentifier) bool {
for _, ext := range ext {
if ext.Id.Equal(oid) {
return true
}
}
return false
}
@ww24
Copy link
Author

ww24 commented Aug 13, 2023

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment