Last active
August 13, 2023 16:16
-
-
Save ww24/3e2af7f82aaf1b986a130bce78bfdebd to your computer and use it in GitHub Desktop.
OCSP verifier implemented in Go
This file contains hidden or 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
module sandbox.example.com/crl-ocsp | |
go 1.21 | |
toolchain go1.21.0 | |
require golang.org/x/crypto v0.12.0 |
This file contains hidden or 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
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= | |
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= |
This file contains hidden or 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 ( | |
"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 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
記事: https://zenn.dev/ww24/articles/16091296c52ff4