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

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