Skip to content

Instantly share code, notes, and snippets.

@vanbroup
Last active January 20, 2024 10:54
Show Gist options
  • Save vanbroup/84859cd10479ed95c64abe6fcdbdf83d to your computer and use it in GitHub Desktop.
Save vanbroup/84859cd10479ed95c64abe6fcdbdf83d to your computer and use it in GitHub Desktop.
Script to create a CA hierarchy with delegated OCSP responder certificates to test the effects on different combinations of OCSP Signing EKU settings
// certutil -urlcache * delete
// certutil -verify -user -urlfetch "Server Certificate.cer"
package main
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"log"
"math/big"
"net/http"
"net/url"
"path"
"time"
"golang.org/x/crypto/ocsp"
)
func main() {
// Self signed root CA
rootCATemplate := &x509.Certificate{
SerialNumber: big.NewInt(time.Now().UnixNano()),
Subject: pkix.Name{
CommonName: "Root",
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(25, 0, 0),
IsCA: true,
BasicConstraintsValid: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
}
rootKey, rootCert, err := createAndIssue(rootCATemplate, nil, nil)
if err != nil {
panic(err)
}
// ICA
iCATemplate := &x509.Certificate{
SerialNumber: big.NewInt(time.Now().UnixNano()),
Subject: pkix.Name{
CommonName: "ICA",
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
IsCA: true,
BasicConstraintsValid: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
OCSPServer: []string{"http://localhost:8080"},
}
icaKey, icaCert, err := createAndIssue(iCATemplate, rootCert, rootKey)
if err != nil {
panic(err)
}
icaOCSPResponse, err := createOCSP(rootKey, rootCert, icaCert)
if err != nil {
panic(err)
}
// ICA2
iCA2Template := &x509.Certificate{
SerialNumber: big.NewInt(time.Now().UnixNano()),
Subject: pkix.Name{
CommonName: "ICA 2",
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
IsCA: true,
BasicConstraintsValid: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageOCSPSigning},
OCSPServer: []string{"http://localhost:8080"},
}
ica2Key, ica2Cert, err := createAndIssue(iCA2Template, rootCert, rootKey)
if err != nil {
panic(err)
}
ica2OCSPResponse, err := createOCSP(rootKey, rootCert, ica2Cert)
if err != nil {
panic(err)
}
// overrull the OCSP response
icaOCSPResponse, err = ocsp.CreateResponse(rootCert, ica2Cert, ocsp.Response{
Status: ocsp.Revoked,
SerialNumber: icaCert.SerialNumber,
ThisUpdate: time.Now(),
NextUpdate: time.Now().AddDate(1, 0, 0),
RevokedAt: time.Now(),
RevocationReason: ocsp.KeyCompromise,
Certificate: ica2Cert,
}, ica2Key)
if err != nil {
panic(err)
}
// Server certificate
serverTemplate := &x509.Certificate{
SerialNumber: big.NewInt(time.Now().UnixNano()),
Subject: pkix.Name{
CommonName: "Server Certificate",
},
DNSNames: []string{"localhost"},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(0, 3, 0),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
OCSPServer: []string{"http://localhost:8080"},
}
serverKey, serverCert, err := createAndIssue(serverTemplate, icaCert, icaKey)
if err != nil {
panic(err)
}
serverOCSPResponse, err := createOCSP(icaKey, icaCert, serverCert)
if err != nil {
panic(err)
}
ocspResponses := make(map[string][]byte)
ocspResponses[icaCert.SerialNumber.String()] = icaOCSPResponse
ocspResponses[ica2Cert.SerialNumber.String()] = ica2OCSPResponse
ocspResponses[serverCert.SerialNumber.String()] = serverOCSPResponse
// Verify
rootPool := x509.NewCertPool()
rootPool.AddCert(rootCert)
icaPool := x509.NewCertPool()
icaPool.AddCert(icaCert)
_, err = icaCert.Verify(x509.VerifyOptions{
Roots: rootPool,
Intermediates: icaPool,
CurrentTime: time.Now(),
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageOCSPSigning},
})
if err != nil {
log.Println("Verify error:", err)
} else {
log.Println("Verify OK")
}
go startWebserver(serverKey, serverCert, icaCert)
startOCSPServer(ocspResponses)
}
func createOCSP(icaKey crypto.Signer, icaCert, cert *x509.Certificate) ([]byte, error) {
// OCSP Signing Certificate
ocspTemplate := &x509.Certificate{
SerialNumber: big.NewInt(3),
Subject: pkix.Name{
CommonName: "Delegated OCSP signing certificate for " + icaCert.Subject.CommonName,
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(0, 3, 0),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageOCSPSigning},
}
ocspTemplate.ExtraExtensions = append(ocspTemplate.ExtraExtensions, pkix.Extension{
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 48, 1, 5},
Value: []byte{asn1.TagNull, 0},
Critical: false,
})
ocspKey, ocspCert, err := createAndIssue(ocspTemplate, icaCert, icaKey)
if err != nil {
return nil, err
}
// Create OCSP response
ocspResponse, err := ocsp.CreateResponse(icaCert, ocspCert, ocsp.Response{
Status: ocsp.Good,
SerialNumber: cert.SerialNumber,
ThisUpdate: time.Now(),
NextUpdate: time.Now().AddDate(1, 0, 0),
Certificate: ocspCert,
}, ocspKey)
if err != nil {
return nil, err
}
return ocspResponse, nil
}
func startWebserver(key crypto.PrivateKey, cert, issuer *x509.Certificate) {
serverHandler := func(w http.ResponseWriter, req *http.Request) {
log.Printf("HTTPS | %s [%s] %s\n", req.UserAgent(), req.Method, req.RequestURI)
io.WriteString(w, "Hello, world!\n")
}
mux := http.NewServeMux()
mux.HandleFunc("/", serverHandler)
cfg := &tls.Config{Certificates: []tls.Certificate{{
Certificate: [][]byte{cert.Raw, issuer.Raw},
PrivateKey: key,
}}}
srv := &http.Server{
Addr: ":8443",
TLSConfig: cfg,
ReadTimeout: time.Minute,
WriteTimeout: time.Minute,
Handler: mux,
}
log.Println("Starting webserver on port 8443")
log.Fatal(srv.ListenAndServeTLS("", ""))
}
func startOCSPServer(response map[string][]byte) {
ocspHandler := func(w http.ResponseWriter, req *http.Request) {
log.Printf("OCSP | %s [%s] %s\n", req.UserAgent(), req.Method, req.RequestURI)
var err error
var request []byte
switch req.Method {
case http.MethodPost:
request, err = ioutil.ReadAll(req.Body)
if err != nil {
log.Println(err)
return
}
case http.MethodGet:
requestBase64, err := url.QueryUnescape(path.Base(req.URL.RawPath))
if err != nil {
log.Println(err)
return
}
request, err = base64.StdEncoding.DecodeString(requestBase64)
if err != nil {
log.Println(err)
return
}
}
or, err := ocsp.ParseRequest(request)
if err != nil {
log.Println(err)
return
}
if _, ok := response[or.SerialNumber.String()]; !ok {
log.Println("OCSP | Unkown serial number:", or.SerialNumber)
return
}
w.Header().Set("Content-Type", "application/ocsp-response")
w.Write(response[or.SerialNumber.String()])
log.Println("OCSP | Request served for serial number:", or.SerialNumber.String())
}
mux := http.NewServeMux()
mux.HandleFunc("/", ocspHandler)
srv := &http.Server{
Addr: ":8080",
ReadTimeout: time.Minute,
WriteTimeout: time.Minute,
Handler: mux,
}
log.Fatal(srv.ListenAndServe())
}
func createAndIssue(template, issuerCert *x509.Certificate, issuerKey crypto.Signer) (crypto.Signer, *x509.Certificate, error) {
private, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
panic(err)
}
if issuerKey == nil {
issuerKey = private
issuerCert = template
}
rawCert, err := x509.CreateCertificate(rand.Reader, template, issuerCert, private.Public(), issuerKey)
if err != nil {
fmt.Println(err)
return nil, nil, err
}
cert, err := x509.ParseCertificate(rawCert)
if err != nil {
return nil, nil, err
}
ioutil.WriteFile(cert.Subject.CommonName+".cer", rawCert, 0644)
fmt.Printf("Issued certificate:\n\tSubject: %s\n\tIssuer: %s\n\n", cert.Subject, cert.Issuer)
return private, cert, nil
}
@vanbroup
Copy link
Author

vanbroup commented Jul 6, 2020

OpenSSL accepts the response:

openssl ocsp -CAfile root.pem -issuer root.pem -cert ca.pem -no_nonce -text -url http://localhost:8080
OCSP Request Data:
    Version: 1 (0x0)
    Requestor List:
        Certificate ID:
          Hash Algorithm: sha1
          Issuer Name Hash: BB14FA9683082119DC1C1FBE0D6D312AD95A7D65
          Issuer Key Hash: 99334693D848FE1E0F93B80EB31A15068A388874
          Serial Number: 161F21532EA4BEDB
OCSP Response Data:
    OCSP Response Status: successful (0x0)
    Response Type: Basic OCSP Response
    Version: 1 (0x0)
    Responder Id: CN = ICA 2
    Produced At: Jul  6 09:57:00 2020 GMT
    Responses:
    Certificate ID:
      Hash Algorithm: sha1
      Issuer Name Hash: BB14FA9683082119DC1C1FBE0D6D312AD95A7D65
      Issuer Key Hash: 99334693D848FE1E0F93B80EB31A15068A388874
      Serial Number: 161F21532EA4BEDB
    Cert Status: revoked
    Revocation Time: Jul  6 09:57:15 2020 GMT
    Revocation Reason: keyCompromise (0x1)
    This Update: Jul  6 09:57:15 2020 GMT
    Next Update: Jul  6 09:57:15 2021 GMT

    Signature Algorithm: sha256WithRSAEncryption
         72:91:eb:d0:4b:1b:82:34:b9:8b:23:e1:0f:38:b4:ea:4b:3c:
         68:6a:ac:08:08:1b:6e:9b:ff:46:c2:9d:98:42:43:d0:16:b4:
         a6:04:57:02:6d:d7:c5:38:4c:d2:4b:07:89:25:18:11:84:01:
         ad:f9:ff:05:9a:cc:91:ce:f0:03:bc:8f:ec:b7:09:fd:b6:d5:
         8a:a8:bd:07:5d:df:24:ae:0f:2e:05:1d:4a:ab:de:62:14:b0:
         98:a7:8b:06:17:c9:5b:a7:79:2a:57:ce:f1:8c:90:df:54:82:
         46:24:22:8d:94:ac:ce:03:e2:08:d5:58:de:b9:68:ca:ed:d1:
         74:21:35:eb:8a:4f:d0:54:9f:c7:7e:32:e9:97:9e:54:01:9a:
         e3:cc:06:a2:4b:55:84:93:74:5c:c8:bf:3f:12:e2:b8:08:46:
         11:6b:86:90:87:e8:41:fe:44:3e:13:6f:2a:f9:4f:f5:ca:7a:
         54:56:8a:77:75:c6:fc:c1:5c:84:95:c2:0e:d6:a8:56:af:1a:
         bc:91:73:67:b9:97:61:2e:59:df:41:61:9f:5e:2e:25:d2:95:
         2b:1a:3d:0f:13:1f:58:27:e5:68:15:ff:bc:f6:ea:08:49:e8:
         cb:e7:3f:5d:e0:ef:25:3b:36:c0:e7:31:ca:79:13:f8:e8:38:
         6e:af:1a:0e
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 1594029434537339075 (0x161f21533f1d68c3)
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=Root
        Validity
            Not Before: Jul  6 09:57:14 2020 GMT
            Not After : Jul  6 09:57:14 2030 GMT
        Subject: CN=ICA 2
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:98:c1:2c:70:a8:70:0b:5b:dd:5d:45:d3:9a:de:
                    c8:b3:11:b2:5d:7a:69:0e:c9:44:58:5d:64:9f:c0:
                    6a:23:64:e9:f4:ce:11:db:4e:0f:02:5f:f7:35:90:
                    e2:96:31:ce:5b:c4:d9:b7:b2:5c:57:2e:91:19:00:
                    af:f5:17:e5:27:1c:80:98:46:20:ee:5b:3d:82:3d:
                    f9:e5:50:ce:91:45:45:ae:25:1d:0f:ef:8e:d8:14:
                    7d:6d:cd:95:56:1f:d9:9b:98:17:29:b2:17:2d:36:
                    95:d3:dd:2f:21:a8:b5:be:ec:70:a4:09:31:cb:f1:
                    16:a3:62:84:4c:c0:90:37:6b:af:14:15:cd:45:2e:
                    26:63:2a:2c:53:72:af:a1:c6:b9:00:b3:99:61:bb:
                    26:25:aa:b8:d8:78:fe:89:d8:53:65:0f:02:66:87:
                    89:2a:03:31:e6:99:24:ed:c4:d7:04:11:f8:60:d1:
                    31:18:96:3f:a0:ad:fa:f1:a5:bb:16:5c:5f:e2:3f:
                    6a:e0:58:4a:d4:45:d2:a4:88:ff:f3:32:7e:85:84:
                    73:7a:ca:cd:22:bc:4d:33:4c:6d:08:01:18:df:8f:
                    b3:1a:3e:f0:e0:d2:bd:45:03:62:98:7f:99:fe:be:
                    eb:3d:88:2c:f3:36:8f:86:0b:e3:b3:d7:83:52:fa:
                    b2:49
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Certificate Sign, CRL Sign
            X509v3 Extended Key Usage: 
                TLS Web Client Authentication, TLS Web Server Authentication, OCSP Signing
            X509v3 Basic Constraints: critical
                CA:TRUE
            Authority Information Access: 
                OCSP - URI:http://localhost:8080

    Signature Algorithm: sha256WithRSAEncryption
         2f:f7:4b:ed:38:0f:64:c0:34:87:88:a0:9c:55:98:9d:26:67:
         5b:1d:1c:f6:ba:9c:2c:85:95:4d:81:9d:74:aa:50:d4:55:e9:
         5e:90:c5:55:dc:63:e9:d0:56:56:22:b2:54:83:95:b8:5e:f5:
         f6:00:5f:80:db:fd:b7:d0:d5:e4:1e:ff:81:f9:55:ee:3c:71:
         12:c1:d7:4d:aa:c6:4d:59:56:38:76:8b:08:0b:cf:60:2c:3d:
         c4:38:ba:f8:b1:66:99:36:d6:b9:26:f7:0f:4e:de:1a:eb:e2:
         1e:98:38:9b:e4:d4:c9:c6:37:79:f8:50:ea:0d:17:e1:e9:f5:
         da:d7:1f:79:c4:c0:22:9f:eb:74:71:de:5b:82:83:7b:7e:43:
         98:fc:f9:65:b2:7f:fe:fe:47:0c:d4:b8:05:73:7a:c1:6b:4a:
         60:35:de:56:41:8b:f6:be:0c:a4:98:af:50:be:7f:da:1b:e2:
         2a:9f:4f:10:10:64:cf:83:92:21:65:5c:0b:d5:bd:24:35:e1:
         9b:0d:7b:de:98:91:35:c4:7c:fb:04:aa:f2:ae:01:57:1f:62:
         67:6f:92:7a:88:95:73:76:c3:a7:d9:17:b2:6c:26:b5:a4:43:
         18:56:51:12:fe:4f:b4:ad:86:be:85:da:76:91:4c:2d:f9:dd:
         20:f1:c2:0c
-----BEGIN CERTIFICATE-----
MIIDIDCCAgigAwIBAgIIFh8hUz8daMMwDQYJKoZIhvcNAQELBQAwDzENMAsGA1UE
AxMEUm9vdDAeFw0yMDA3MDYwOTU3MTRaFw0zMDA3MDYwOTU3MTRaMBAxDjAMBgNV
BAMTBUlDQSAyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmMEscKhw
C1vdXUXTmt7IsxGyXXppDslEWF1kn8BqI2Tp9M4R204PAl/3NZDiljHOW8TZt7Jc
Vy6RGQCv9RflJxyAmEYg7ls9gj355VDOkUVFriUdD++O2BR9bc2VVh/Zm5gXKbIX
LTaV090vIai1vuxwpAkxy/EWo2KETMCQN2uvFBXNRS4mYyosU3Kvoca5ALOZYbsm
Jaq42Hj+idhTZQ8CZoeJKgMx5pkk7cTXBBH4YNExGJY/oK368aW7Flxf4j9q4FhK
1EXSpIj/8zJ+hYRzesrNIrxNM0xtCAEY34+zGj7w4NK9RQNimH+Z/r7rPYgs8zaP
hgvjs9eDUvqySQIDAQABo38wfTAOBgNVHQ8BAf8EBAMCAQYwJwYDVR0lBCAwHgYI
KwYBBQUHAwIGCCsGAQUFBwMBBggrBgEFBQcDCTAPBgNVHRMBAf8EBTADAQH/MDEG
CCsGAQUFBwEBBCUwIzAhBggrBgEFBQcwAYYVaHR0cDovL2xvY2FsaG9zdDo4MDgw
MA0GCSqGSIb3DQEBCwUAA4IBAQAv90vtOA9kwDSHiKCcVZidJmdbHRz2upwshZVN
gZ10qlDUVelekMVV3GPp0FZWIrJUg5W4XvX2AF+A2/230NXkHv+B+VXuPHESwddN
qsZNWVY4dosIC89gLD3EOLr4sWaZNta5JvcPTt4a6+IemDib5NTJxjd5+FDqDRfh
6fXa1x95xMAin+t0cd5bgoN7fkOY/Pllsn/+/kcM1LgFc3rBa0pgNd5WQYv2vgyk
mK9Qvn/aG+Iqn08QEGTPg5IhZVwL1b0kNeGbDXvemJE1xHz7BKryrgFXH2Jnb5J6
iJVzdsOn2ReybCa1pEMYVlES/k+0rYa+hdp2kUwt+d0g8cIM
-----END CERTIFICATE-----
Response verify OK
ca.pem: revoked
        This Update: Jul  6 09:57:15 2020 GMT
        Next Update: Jul  6 09:57:15 2021 GMT
        Reason: keyCompromise
        Revocation Time: Jul  6 09:57:15 2020 GMT

@vanbroup
Copy link
Author

vanbroup commented Jul 6, 2020

Like OpenSSL, ocspcheck on MacOS accepts the OCSP response issued by the ICA 2 sibling CA.

ocspcheck -vvvvvvvv -N -C root.pem chain.pem ; echo $?
Built an 75 byte ocsp request
Using http to host localhost, port 80, path /
DNS returns ::1 for localhost
Server at localhost returns:
	  [Body]=[1289 bytes]
OCSP response signature validated from localhost
OCSP response status 0 from host localhost
ocspcheck: Invalid OCSP reply: certificate is revoked
ocspcheck: Certificate revoked at: Mon Jul  6 13:02:49 2020

1

@egberts
Copy link

egberts commented Apr 11, 2022

Using your Digitorus TLS Connection online tester, I just noticed that LetsEncrypt R3 OSCP Responder is not responding (in a positive way) to the third unknown HTTP method (in 5-byte DER format, as opposed to HTTP GET or POST method).

Digitorus reported to that OSCP Responder unknown HTTP method as “unauthorized”.

Is this more a legacy issue and can be safely ignored, or not?

@vanbroup
Copy link
Author

unauthorized is a server status that is used instead of unknown, mostly for pre-produced responses as an unknown response is signed and an unauthorized status not.

@egberts
Copy link

egberts commented Apr 11, 2022

This is good to know. Thank you and keep up the good work.

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