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

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