Skip to content

Instantly share code, notes, and snippets.

@cpu
Last active August 1, 2019 15:14
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 cpu/6d26b2718f29e184ff88a90f02d7cbcb to your computer and use it in GitHub Desktop.
Save cpu/6d26b2718f29e184ff88a90f02d7cbcb to your computer and use it in GitHub Desktop.
A small program written to generate test x509 subscriber certificates with mock embedded Certificate Transparency SCTs.
// sctTestCerts is a small program written to generate test subscriber
// certificates for the zlint `sct_policy_count_unsatisified.go` unit tests. It
// depends on `github.com/google/certificate-transparency-go` for providing
// needed SCT types.
//
// Usage:
// 1) go get github.com/google/certificate-transparency-go
// 2) go run sctTestCerts.go -differentLogs=[true/false] -lifetime [int] -scts [int]
//
// -differentLogs
// Use a different Log ID per SCT (default true)
// -lifetime int
// generated certificate lifetime (months) (default 3)
// -scts int
// number of embedded mock SCTs
// -poison bool
// if true, add the CT poison extension to issue a precertificate
//
// Example:
// go run sctTestCerts.go -lifetime 24 -scts 2
// - generate a test certificate with a lifetime of 24 months that has two
// embedded SCTs with unique log IDs.
//
// go run sctTestCerts.go -lifetime 39 -scts 4 -differentLogs=false
// - generate a test certificate with a lifetime of 39 months that has four
// embedded SCTs with the same log ID.
package main
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"flag"
"fmt"
"math"
"math/big"
"time"
ct "github.com/google/certificate-transparency-go"
cttls "github.com/google/certificate-transparency-go/tls"
ctx509 "github.com/google/certificate-transparency-go/x509"
)
var (
// oidExtensionCTPoison is defined in RFC 6962 s3.1.
// The `ctx509` package exports this but we're using the normal `x509` package
// to create the cert so to avoid an incompatible type we redefine the OID
// here.
oidExtensionCTPoison = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3}
)
func mustRandomKey() *rsa.PrivateKey {
randKey, err := rsa.GenerateKey(rand.Reader, 512)
if err != nil {
panic(err)
}
return randKey
}
func mustRandomSerial() *big.Int {
randSerial, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
if err != nil {
panic(err)
}
return randSerial
}
func sctTestCert(months int, numScts int, differentLogs bool, poison bool) *x509.Certificate {
template := &x509.Certificate{
Subject: pkix.Name{
CommonName: "lint_ct_sct_policy_count_unsatisified_test CA",
},
SerialNumber: mustRandomSerial(),
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(30, 0, 0),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
IsCA: true,
}
issuerKey := mustRandomKey()
// Create a self-signed issuer certificate to use
issuerDer, err := x509.CreateCertificate(rand.Reader, template, template, issuerKey.Public(), issuerKey)
if err != nil {
panic(err)
}
issuerCert, err := x509.ParseCertificate(issuerDer)
if err != nil {
panic(err)
}
// Reconfigure the template for a subscriber cert
subjectKey := mustRandomKey()
template.SerialNumber = mustRandomSerial()
template.Subject.CommonName = "zmap.io"
template.DNSNames = []string{"zmap.io"}
template.NotAfter = time.Now().AddDate(0, months, 0)
template.KeyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment
template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
template.IsCA = false
// Generate and embed an SCT list extension if numScts > 0
sctExt := sctsListExtension(numScts, differentLogs)
if sctExt != nil {
template.ExtraExtensions = []pkix.Extension{*sctExt}
}
// Add the precertificate poison extension if indicated
if poison {
template.ExtraExtensions = append(template.ExtraExtensions,
pkix.Extension{
Id: oidExtensionCTPoison,
Critical: true,
Value: []byte{0x05, 0x00}, // ASN.1 null value
})
}
// Issue the certificate with the self-signed issuer
subjectCertDer, err := x509.CreateCertificate(rand.Reader, template, issuerCert, subjectKey.Public(), issuerKey)
if err != nil {
panic(err)
}
subjectCert, err := x509.ParseCertificate(subjectCertDer)
if err != nil {
panic(err)
}
return subjectCert
}
func logID(num int) ct.LogID {
input := fmt.Sprintf("zlint-test-log %d", num)
sum := sha256.Sum256([]byte(input))
return ct.LogID{
KeyID: sum,
}
}
func sctsListExtension(numScts int, differentLogs bool) *pkix.Extension {
if numScts == 0 {
return nil
}
// Create a list of marshalled test SCTs
sctList := ctx509.SignedCertificateTimestampList{}
for i := 0; i < numScts; i++ {
id := logID(i)
if !differentLogs {
id = logID(0)
}
sigBytes := sha256.Sum256([]byte("cool sct"))
// Create a mock SCT
sct := ct.SignedCertificateTimestamp{
SCTVersion: ct.V1,
LogID: id,
Timestamp: uint64(time.Now().UnixNano() / int64(time.Millisecond)),
Extensions: ct.CTExtensions{},
Signature: ct.DigitallySigned{
Algorithm: cttls.SignatureAndHashAlgorithm{
Hash: cttls.SHA256,
Signature: cttls.ECDSA,
},
Signature: sigBytes[:],
},
}
// Marshal the SCT and add it to the list
sctBytes, err := cttls.Marshal(sct)
if err != nil {
panic(err)
}
sctList.SCTList = append(sctList.SCTList, ctx509.SerializedSCT{Val: sctBytes})
}
// Marshal the list of marshalled scts
sctListBytes, err := cttls.Marshal(sctList)
// Serialize the marshalled list to ASN1 for the cert extension.
asn1ListBytes, err := asn1.Marshal(sctListBytes)
if err != nil {
panic(err)
}
return &pkix.Extension{
// See https://tools.ietf.org/html/rfc6962 Section 3.3
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 2},
Value: asn1ListBytes,
}
}
func main() {
months := flag.Int("lifetime", 3, "generated certificate lifetime (months)")
sctCount := flag.Int("scts", 0, "number of embedded mock SCTs")
differentLogs := flag.Bool("differentLogs", true, "Use a different Log ID per SCT")
poison := flag.Bool("poison", false, "Add CT Poison extension to make a precertificate")
flag.Parse()
cert := sctTestCert(*months, *sctCount, *differentLogs, *poison)
var buf bytes.Buffer
err := pem.Encode(&buf, &pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Raw,
})
if err != nil {
panic(err)
}
fmt.Printf("%s", string(buf.Bytes()))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment