Last active
August 1, 2019 15:14
-
-
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.
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
// 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