| // Copyright 2026 The BoringSSL Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| //go:build ignore |
| |
| // This script is called without any arguments to re-generate all of the *.pem |
| // files in the script's directory. |
| package main |
| |
| import ( |
| "bytes" |
| "crypto" |
| "crypto/rand" |
| "crypto/rsa" |
| "crypto/sha1" |
| "crypto/sha256" |
| "crypto/x509" |
| "crypto/x509/pkix" |
| "encoding/asn1" |
| "encoding/pem" |
| "fmt" |
| "math/big" |
| "os" |
| "os/exec" |
| "time" |
| |
| "golang.org/x/crypto/cryptobyte" |
| cbasn1 "golang.org/x/crypto/cryptobyte/asn1" |
| ) |
| |
| var ( |
| nextSerial int64 = 1 |
| |
| certDate = time.Date(2017, 1, 1, 0, 0, 0, 0, time.UTC) |
| certExpire = time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC) |
| revokeDate = time.Date(2017, 2, 1, 0, 0, 0, 0, time.UTC) |
| thisDate = time.Date(2017, 3, 1, 0, 0, 0, 0, time.UTC) |
| producedDate = time.Date(2017, 3, 2, 0, 0, 0, 0, time.UTC) |
| verifyDate = time.Date(2017, 3, 5, 0, 0, 0, 0, time.UTC) |
| nextDate = time.Date(2017, 6, 1, 0, 0, 0, 0, time.UTC) |
| |
| oidSHA1 = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26} |
| oidSHA1WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5} |
| oidSHA256WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11} |
| oidBasicOCSPResponse = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 48, 1, 1} |
| oidCommonName = asn1.ObjectIdentifier{2, 5, 4, 3} |
| ) |
| |
| type certInfo struct { |
| der []byte |
| cert *x509.Certificate |
| key *rsa.PrivateKey |
| issuer *certInfo |
| serialBytes []byte |
| } |
| |
| func makeNameDER(commonName string) []byte { |
| var b cryptobyte.Builder |
| b.AddASN1(cbasn1.SEQUENCE, func(b *cryptobyte.Builder) { |
| b.AddASN1(cbasn1.SET, func(b *cryptobyte.Builder) { |
| b.AddASN1(cbasn1.SEQUENCE, func(b *cryptobyte.Builder) { |
| b.AddASN1ObjectIdentifier(oidCommonName) |
| b.AddASN1(cbasn1.UTF8String, func(b *cryptobyte.Builder) { |
| b.AddBytes([]byte(commonName)) |
| }) |
| }) |
| }) |
| }) |
| return b.BytesOrPanic() |
| } |
| |
| func loadKey(path string) *rsa.PrivateKey { |
| data, err := os.ReadFile(path) |
| if err != nil { |
| panic(err) |
| } |
| block, _ := pem.Decode(data) |
| if block == nil || block.Type != "PRIVATE KEY" { |
| panic("failed to decode PEM key: " + path) |
| } |
| key, err := x509.ParsePKCS8PrivateKey(block.Bytes) |
| if err != nil { |
| panic(err) |
| } |
| return key.(*rsa.PrivateKey) |
| } |
| |
| type certOptions struct { |
| name string |
| keyPath string |
| signer *certInfo |
| ocsp bool |
| extraExtensions []pkix.Extension |
| notBefore time.Time |
| notAfter time.Time |
| } |
| |
| func createCertWithOptions(opts certOptions) *certInfo { |
| privKey := loadKey(opts.keyPath) |
| subjectDER := makeNameDER(opts.name) |
| |
| serial := nextSerial |
| nextSerial++ |
| |
| notBefore := opts.notBefore |
| if notBefore.IsZero() { |
| notBefore = certDate |
| } |
| notAfter := opts.notAfter |
| if notAfter.IsZero() { |
| notAfter = certExpire |
| } |
| |
| tmpl := &x509.Certificate{ |
| SerialNumber: big.NewInt(serial), |
| RawSubject: subjectDER, |
| NotBefore: notBefore, |
| NotAfter: notAfter, |
| SignatureAlgorithm: x509.SHA256WithRSA, |
| ExtraExtensions: opts.extraExtensions, |
| } |
| if opts.ocsp { |
| tmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageOCSPSigning} |
| } |
| |
| var issuerTmpl *x509.Certificate |
| var issuerKey *rsa.PrivateKey |
| if opts.signer != nil { |
| issuerTmpl = opts.signer.cert |
| issuerKey = opts.signer.key |
| } else { |
| issuerTmpl = tmpl |
| issuerKey = privKey |
| } |
| |
| derBytes, err := x509.CreateCertificate(rand.Reader, tmpl, issuerTmpl, &privKey.PublicKey, issuerKey) |
| if err != nil { |
| panic(err) |
| } |
| parsedCert, err := x509.ParseCertificate(derBytes) |
| if err != nil { |
| panic(err) |
| } |
| |
| info := &certInfo{ |
| der: derBytes, |
| cert: parsedCert, |
| key: privKey, |
| serialBytes: []byte{byte(serial)}, |
| } |
| if opts.signer != nil { |
| info.issuer = opts.signer |
| } else { |
| info.issuer = info |
| } |
| return info |
| } |
| |
| func createCert(name, keyPath string, signer *certInfo, ocsp bool) *certInfo { |
| return createCertWithOptions(certOptions{ |
| name: name, |
| keyPath: keyPath, |
| signer: signer, |
| ocsp: ocsp, |
| }) |
| } |
| |
| // createCertWithRawSerial creates a copy of baseCert with its serialNumber INTEGER |
| // replaced by rawSerialBytes, and re-signs the certificate with its issuer's key. |
| func createCertWithRawSerial(baseCert *certInfo, rawSerialBytes []byte) *certInfo { |
| s := cryptobyte.String(baseCert.der) |
| var certSeq, tbsSeq cryptobyte.String |
| if !s.ReadASN1(&certSeq, cbasn1.SEQUENCE) || |
| !certSeq.ReadASN1(&tbsSeq, cbasn1.SEQUENCE) { |
| panic("failed to parse Certificate DER") |
| } |
| |
| // Read version [0] EXPLICIT TLV raw bytes |
| var versionTLV cryptobyte.String |
| if !tbsSeq.ReadASN1Element(&versionTLV, cbasn1.Tag(0).ContextSpecific().Constructed()) { |
| panic("failed to read version from tbsCertificate") |
| } |
| // Skip original serialNumber INTEGER |
| var oldSerial cryptobyte.String |
| if !tbsSeq.ReadASN1(&oldSerial, cbasn1.INTEGER) { |
| panic("failed to read serialNumber from tbsCertificate") |
| } |
| restOfTBS := []byte(tbsSeq) |
| |
| var newTBSBuilder cryptobyte.Builder |
| newTBSBuilder.AddASN1(cbasn1.SEQUENCE, func(b *cryptobyte.Builder) { |
| b.AddBytes(versionTLV) |
| b.AddASN1(cbasn1.INTEGER, func(b *cryptobyte.Builder) { |
| b.AddBytes(rawSerialBytes) |
| }) |
| b.AddBytes(restOfTBS) |
| }) |
| newTBS := newTBSBuilder.BytesOrPanic() |
| |
| digest := sha256.Sum256(newTBS) |
| sig, err := rsa.SignPKCS1v15(nil, baseCert.issuer.key, crypto.SHA256, digest[:]) |
| if err != nil { |
| panic(err) |
| } |
| |
| var newCertBuilder cryptobyte.Builder |
| newCertBuilder.AddASN1(cbasn1.SEQUENCE, func(b *cryptobyte.Builder) { |
| b.AddBytes(newTBS) |
| b.AddASN1(cbasn1.SEQUENCE, func(b *cryptobyte.Builder) { |
| b.AddASN1ObjectIdentifier(oidSHA256WithRSA) |
| b.AddASN1NULL() |
| }) |
| b.AddASN1BitString(sig) |
| }) |
| newDER := newCertBuilder.BytesOrPanic() |
| |
| return &certInfo{ |
| der: newDER, |
| cert: baseCert.cert, |
| key: baseCert.key, |
| issuer: baseCert.issuer, |
| serialBytes: rawSerialBytes, |
| } |
| } |
| |
| func extractSPKBitStringBytes(spkiDER []byte) []byte { |
| s := cryptobyte.String(spkiDER) |
| var seq, alg cryptobyte.String |
| var spk asn1.BitString |
| if !s.ReadASN1(&seq, cbasn1.SEQUENCE) || |
| !seq.ReadASN1(&alg, cbasn1.SEQUENCE) || |
| !seq.ReadASN1BitString(&spk) { |
| panic("failed to parse SubjectPublicKeyInfo") |
| } |
| return spk.Bytes |
| } |
| |
| type responderID struct { |
| byName []byte |
| byKey []byte |
| } |
| |
| func getNameResponderID(c *certInfo) responderID { |
| return responderID{byName: c.cert.RawSubject} |
| } |
| |
| func getKeyHashResponderID(c *certInfo) responderID { |
| spkBytes := extractSPKBitStringBytes(c.cert.RawSubjectPublicKeyInfo) |
| keyHash := sha1.Sum(spkBytes) |
| return responderID{byKey: keyHash[:]} |
| } |
| |
| func addExtensions(b *cryptobyte.Builder, tag uint8, exts []pkix.Extension) { |
| if len(exts) == 0 { |
| return |
| } |
| b.AddASN1(cbasn1.Tag(tag).ContextSpecific().Constructed(), func(child *cryptobyte.Builder) { |
| child.AddASN1(cbasn1.SEQUENCE, func(extsB *cryptobyte.Builder) { |
| for _, ext := range exts { |
| extsB.AddASN1(cbasn1.SEQUENCE, func(extB *cryptobyte.Builder) { |
| extB.AddASN1ObjectIdentifier(ext.Id) |
| if ext.Critical { |
| extB.AddASN1(cbasn1.BOOLEAN, func(crit *cryptobyte.Builder) { |
| crit.AddUint8(0xff) |
| }) |
| } |
| extB.AddASN1(cbasn1.OCTET_STRING, func(value *cryptobyte.Builder) { |
| value.AddBytes(ext.Value) |
| }) |
| }) |
| } |
| }) |
| }) |
| } |
| |
| type certStatus int |
| |
| const ( |
| statusGood certStatus = 0 |
| statusRevoked certStatus = 1 |
| statusUnknown certStatus = 2 |
| ) |
| |
| type singleResponse struct { |
| cert *certInfo |
| status certStatus |
| reason *int |
| thisUpdate time.Time |
| nextUpdate *time.Time |
| revokeTime *time.Time |
| extensions []pkix.Extension |
| // trailingDataInStatus causes the CertStatus to include extra data |
| // in its contents. |
| trailingDataInStatus bool |
| } |
| |
| func marshalSingleResponse(b *cryptobyte.Builder, resp singleResponse) { |
| issuer := resp.cert.issuer |
| nameHash := sha1.Sum(issuer.cert.RawSubject) |
| keyHash := sha1.Sum(extractSPKBitStringBytes(issuer.cert.RawSubjectPublicKeyInfo)) |
| |
| b.AddASN1(cbasn1.SEQUENCE, func(respB *cryptobyte.Builder) { |
| respB.AddASN1(cbasn1.SEQUENCE, func(certID *cryptobyte.Builder) { |
| certID.AddASN1(cbasn1.SEQUENCE, func(b *cryptobyte.Builder) { |
| b.AddASN1ObjectIdentifier(oidSHA1) |
| }) |
| certID.AddASN1(cbasn1.OCTET_STRING, func(b *cryptobyte.Builder) { |
| b.AddBytes(nameHash[:]) |
| }) |
| certID.AddASN1(cbasn1.OCTET_STRING, func(b *cryptobyte.Builder) { |
| b.AddBytes(keyHash[:]) |
| }) |
| certID.AddASN1(cbasn1.INTEGER, func(b *cryptobyte.Builder) { |
| b.AddBytes(resp.cert.serialBytes) |
| }) |
| }) |
| |
| switch resp.status { |
| case statusGood: |
| respB.AddASN1(cbasn1.Tag(0).ContextSpecific(), func(b *cryptobyte.Builder) { |
| if resp.trailingDataInStatus { |
| b.AddBytes([]byte("invalid")) |
| } |
| }) |
| case statusRevoked: |
| revTime := revokeDate |
| if resp.revokeTime != nil { |
| revTime = *resp.revokeTime |
| } |
| respB.AddASN1(cbasn1.Tag(1).ContextSpecific().Constructed(), func(revInfo *cryptobyte.Builder) { |
| revInfo.AddASN1GeneralizedTime(revTime) |
| if resp.reason != nil { |
| revInfo.AddASN1(cbasn1.Tag(0).ContextSpecific().Constructed(), func(child *cryptobyte.Builder) { |
| child.AddASN1Enum(int64(*resp.reason)) |
| }) |
| } |
| if resp.trailingDataInStatus { |
| revInfo.AddBytes([]byte("invalid")) |
| } |
| }) |
| case statusUnknown: |
| respB.AddASN1(cbasn1.Tag(2).ContextSpecific(), func(b *cryptobyte.Builder) { |
| if resp.trailingDataInStatus { |
| b.AddBytes([]byte("invalid")) |
| } |
| }) |
| } |
| |
| thisUpdate := resp.thisUpdate |
| if thisUpdate.IsZero() { |
| thisUpdate = thisDate |
| } |
| respB.AddASN1GeneralizedTime(thisUpdate) |
| |
| // nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL |
| if resp.nextUpdate != nil { |
| respB.AddASN1(cbasn1.Tag(0).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) { |
| b.AddASN1GeneralizedTime(*resp.nextUpdate) |
| }) |
| } |
| |
| // singleExtensions [1] EXPLICIT Extensions OPTIONAL |
| addExtensions(respB, 1, resp.extensions) |
| }) |
| } |
| |
| const ( |
| responseStatusSuccessful = 0 |
| responseStatusMalformedRequest = 1 |
| responseStatusInternalError = 2 |
| responseStatusTryLater = 3 |
| // 4 is not allocated. |
| responseStatusSigRequired = 5 |
| responseStatusUnauthorized = 6 |
| ) |
| |
| type ocspResponse struct { |
| signer *certInfo |
| responseStatus int |
| // responseType, if set, overrides the responseType from id-pkix-ocsp-basic |
| responseType asn1.ObjectIdentifier |
| // version, if set, explicitly encodes a version number. |
| version *int |
| responderID responderID |
| producedAt time.Time |
| responses []singleResponse |
| extensions []pkix.Extension |
| sigAlg x509.SignatureAlgorithm |
| // signature, if set, overrides the signature. |
| signature []byte |
| certs []*certInfo |
| // invalidResponseData, if true, causes the tbsResponseData to be invalid. |
| invalidResponseData bool |
| } |
| |
| func marshalBasicOCSPResponse(b *cryptobyte.Builder, resp *ocspResponse) { |
| sigAlg := resp.sigAlg |
| if sigAlg == 0 { |
| sigAlg = x509.SHA1WithRSA |
| } |
| |
| var sigAlgOID asn1.ObjectIdentifier |
| var hashFunc crypto.Hash |
| switch sigAlg { |
| case x509.SHA1WithRSA: |
| sigAlgOID = oidSHA1WithRSA |
| hashFunc = crypto.SHA1 |
| case x509.SHA256WithRSA: |
| sigAlgOID = oidSHA256WithRSA |
| hashFunc = crypto.SHA256 |
| default: |
| panic(fmt.Sprintf("unrecognized signature algorithm: %s", sigAlg)) |
| } |
| |
| var tbsBuilder cryptobyte.Builder |
| if resp.invalidResponseData { |
| tbsBuilder.AddASN1(cbasn1.OCTET_STRING, func(b *cryptobyte.Builder) { |
| b.AddBytes([]byte("invalid")) |
| }) |
| } else { |
| tbsBuilder.AddASN1(cbasn1.SEQUENCE, func(respData *cryptobyte.Builder) { |
| if resp.version != nil { |
| respData.AddASN1(cbasn1.Tag(0).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) { |
| b.AddASN1Int64(int64(*resp.version)) |
| }) |
| } |
| responderID := resp.responderID |
| if responderID.byKey != nil { |
| respData.AddASN1(cbasn1.Tag(2).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) { |
| b.AddASN1(cbasn1.OCTET_STRING, func(b *cryptobyte.Builder) { |
| b.AddBytes(responderID.byKey) |
| }) |
| }) |
| } else { |
| respData.AddASN1(cbasn1.Tag(1).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) { |
| if responderID.byName != nil { |
| b.AddBytes(responderID.byName) |
| } else { |
| // Default to the signer. |
| b.AddBytes(resp.signer.cert.RawSubject) |
| } |
| }) |
| } |
| |
| producedAt := resp.producedAt |
| if producedAt.IsZero() { |
| producedAt = producedDate |
| } |
| respData.AddASN1GeneralizedTime(producedAt) |
| |
| respData.AddASN1(cbasn1.SEQUENCE, func(b *cryptobyte.Builder) { |
| for _, r := range resp.responses { |
| marshalSingleResponse(b, r) |
| } |
| }) |
| |
| addExtensions(respData, 1, resp.extensions) |
| }) |
| } |
| tbsBytes := tbsBuilder.BytesOrPanic() |
| |
| b.AddASN1(cbasn1.SEQUENCE, func(basicResp *cryptobyte.Builder) { |
| basicResp.AddBytes(tbsBytes) |
| basicResp.AddASN1(cbasn1.SEQUENCE, func(b *cryptobyte.Builder) { |
| b.AddASN1ObjectIdentifier(sigAlgOID) |
| b.AddASN1NULL() |
| }) |
| |
| signature := resp.signature |
| if signature == nil { |
| h := hashFunc.New() |
| h.Write(tbsBytes) |
| var err error |
| signature, err = rsa.SignPKCS1v15(nil, resp.signer.key, hashFunc, h.Sum(nil)) |
| if err != nil { |
| panic(err) |
| } |
| } |
| basicResp.AddASN1BitString(signature) |
| if len(resp.certs) > 0 { |
| basicResp.AddASN1(cbasn1.Tag(0).ContextSpecific().Constructed(), func(certsWrapper *cryptobyte.Builder) { |
| certsWrapper.AddASN1(cbasn1.SEQUENCE, func(certs *cryptobyte.Builder) { |
| for _, c := range resp.certs { |
| certs.AddBytes(c.der) |
| } |
| }) |
| }) |
| } |
| }) |
| } |
| |
| func createOCSPResponse(resp *ocspResponse) []byte { |
| var b cryptobyte.Builder |
| b.AddASN1(cbasn1.SEQUENCE, func(respB *cryptobyte.Builder) { |
| respB.AddASN1Enum(int64(resp.responseStatus)) |
| if resp.responseStatus != responseStatusSuccessful { |
| return |
| } |
| respB.AddASN1(cbasn1.Tag(0).ContextSpecific().Constructed(), func(child *cryptobyte.Builder) { |
| child.AddASN1(cbasn1.SEQUENCE, func(respBytes *cryptobyte.Builder) { |
| responseType := resp.responseType |
| if len(responseType) == 0 { |
| responseType = oidBasicOCSPResponse |
| } |
| respBytes.AddASN1ObjectIdentifier(responseType) |
| respBytes.AddASN1(cbasn1.OCTET_STRING, func(respWrapper *cryptobyte.Builder) { |
| marshalBasicOCSPResponse(respWrapper, resp) |
| }) |
| }) |
| }) |
| }) |
| return b.BytesOrPanic() |
| } |
| |
| func createOCSPRequest(issuer, cert *certInfo) []byte { |
| nameHash := sha1.Sum(issuer.cert.RawSubject) |
| keyHash := sha1.Sum(extractSPKBitStringBytes(issuer.cert.RawSubjectPublicKeyInfo)) |
| |
| var b cryptobyte.Builder |
| b.AddASN1(cbasn1.SEQUENCE, func(ocspReq *cryptobyte.Builder) { |
| ocspReq.AddASN1(cbasn1.SEQUENCE, func(tbsReq *cryptobyte.Builder) { |
| tbsReq.AddASN1(cbasn1.SEQUENCE, func(reqList *cryptobyte.Builder) { |
| reqList.AddASN1(cbasn1.SEQUENCE, func(req *cryptobyte.Builder) { |
| req.AddASN1(cbasn1.SEQUENCE, func(reqCert *cryptobyte.Builder) { |
| reqCert.AddASN1(cbasn1.SEQUENCE, func(hashAlg *cryptobyte.Builder) { |
| hashAlg.AddASN1ObjectIdentifier(oidSHA1) |
| hashAlg.AddASN1NULL() |
| }) |
| reqCert.AddASN1(cbasn1.OCTET_STRING, func(child *cryptobyte.Builder) { |
| child.AddBytes(nameHash[:]) |
| }) |
| reqCert.AddASN1(cbasn1.OCTET_STRING, func(child *cryptobyte.Builder) { |
| child.AddBytes(keyHash[:]) |
| }) |
| reqCert.AddASN1(cbasn1.INTEGER, func(child *cryptobyte.Builder) { |
| child.AddBytes(cert.serialBytes) |
| }) |
| }) |
| }) |
| }) |
| }) |
| }) |
| return b.BytesOrPanic() |
| } |
| |
| func store(filename, description string, ca, cert *certInfo, dataDER []byte) { |
| ocspRequestDER := createOCSPRequest(ca, cert) |
| |
| var buf bytes.Buffer |
| buf.WriteString(description) |
| buf.WriteString("\n") |
| if err := pem.Encode(&buf, &pem.Block{Type: "OCSP RESPONSE", Bytes: dataDER}); err != nil { |
| panic(err) |
| } |
| if err := pem.Encode(&buf, &pem.Block{Type: "CA CERTIFICATE", Bytes: ca.der}); err != nil { |
| panic(err) |
| } |
| buf.WriteString("\n") |
| if err := pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: cert.der}); err != nil { |
| panic(err) |
| } |
| buf.WriteString("\n") |
| if err := pem.Encode(&buf, &pem.Block{Type: "OCSP REQUEST", Bytes: ocspRequestDER}); err != nil { |
| panic(err) |
| } |
| |
| if err := os.WriteFile(filename+".pem", buf.Bytes(), 0644); err != nil { |
| panic(err) |
| } |
| } |
| |
| func main() { |
| rootCA := createCert("Test CA", "root.key", nil, false) |
| ca := createCert("Test Intermediate CA", "intermediate.key", rootCA, false) |
| caLink := createCert("Test OCSP Signer", "ocsp_signer.key", ca, true) |
| caBadLink := createCert("Test False OCSP Signer", "bad_ocsp_signer.key", ca, false) |
| cert := createCert("Test Cert", "cert.key", ca, false) |
| junkCert := createCert("Random Cert", "cert2.key", nil, false) |
| caCriticalExtLink := createCertWithOptions(certOptions{ |
| name: "Test OCSP Signer", |
| keyPath: "ocsp_signer.key", |
| signer: ca, |
| ocsp: true, |
| extraExtensions: []pkix.Extension{ |
| {Id: asn1.ObjectIdentifier{1, 2, 3, 4}, Critical: true, Value: []byte("DEADBEEF")}, |
| }, |
| }) |
| ca2 := createCert("Test Intermediate CA 2", "intermediate.key", rootCA, false) |
| ca2Link := createCert("Test OCSP Signer", "ocsp_signer.key", ca2, true) |
| caExpiredLink := createCertWithOptions(certOptions{ |
| name: "Test OCSP Signer", |
| keyPath: "ocsp_signer.key", |
| signer: ca, |
| ocsp: true, |
| notBefore: certDate, |
| notAfter: verifyDate.Add(-1 * 24 * time.Hour), |
| }) |
| |
| create := func(resp ocspResponse) []byte { |
| if resp.signer == nil { |
| resp.signer = ca |
| } |
| if resp.responses == nil { |
| resp.responses = []singleResponse{{ |
| cert: cert, |
| status: statusGood, |
| }} |
| } |
| return createOCSPResponse(&resp) |
| } |
| |
| store( |
| "no_response", |
| "No SingleResponses attached to the response", |
| ca, cert, |
| create(ocspResponse{responses: []singleResponse{}}), |
| ) |
| |
| store( |
| "malformed_request", |
| "Has a status of MALFORMED_REQUEST", |
| ca, cert, |
| create(ocspResponse{responseStatus: responseStatusMalformedRequest}), |
| ) |
| |
| store( |
| "bad_status", |
| "Has an invalid status larger than the defined Status enumeration", |
| ca, cert, |
| create(ocspResponse{responseStatus: 17}), |
| ) |
| |
| store( |
| "bad_ocsp_type", |
| "Has an invalid OCSP OID", |
| ca, cert, |
| create(ocspResponse{responseType: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 48, 1, 2}}), |
| ) |
| |
| store( |
| "bad_signature", |
| "Has an invalid signature", |
| ca, cert, |
| create(ocspResponse{signature: []byte{0xde, 0xad, 0xbe, 0xef}}), |
| ) |
| |
| store( |
| "ocsp_sign_direct", |
| "Signed directly by the issuer", |
| ca, cert, |
| create(ocspResponse{signer: ca, certs: []*certInfo{}}), |
| ) |
| |
| store( |
| "ocsp_sign_indirect", |
| "Signed indirectly through an intermediate", |
| ca, cert, |
| create(ocspResponse{signer: caLink, certs: []*certInfo{caLink}}), |
| ) |
| |
| store( |
| "ocsp_sign_indirect_missing", |
| "Signed indirectly through a missing intermediate", |
| ca, cert, |
| create(ocspResponse{signer: caLink, certs: []*certInfo{}}), |
| ) |
| |
| store( |
| "ocsp_sign_bad_indirect", |
| "Signed through an intermediate without the correct key usage", |
| ca, cert, |
| create(ocspResponse{signer: caBadLink, certs: []*certInfo{caBadLink}}), |
| ) |
| |
| store( |
| "ocsp_sign_bad_indirect_critical_extension", |
| "Signed through an intermediate with an unknown critical extension", |
| ca, cert, |
| create(ocspResponse{signer: caCriticalExtLink, certs: []*certInfo{caCriticalExtLink}}), |
| ) |
| |
| store( |
| "ocsp_sign_bad_indirect_wrong_issuer", |
| "Signed through an intermediate issued by a differently named CA with the same key", |
| ca, cert, |
| create(ocspResponse{signer: ca2Link, certs: []*certInfo{ca2Link}}), |
| ) |
| |
| store( |
| "ocsp_sign_bad_indirect_expired", |
| "Signed through an intermediate that is not valid at the verification time", |
| ca, cert, |
| create(ocspResponse{signer: caExpiredLink, certs: []*certInfo{caExpiredLink}}), |
| ) |
| |
| store( |
| "ocsp_extra_certs", |
| "Includes extra certs", |
| ca, cert, |
| create(ocspResponse{signer: ca, certs: []*certInfo{ca, caLink}}), |
| ) |
| |
| store( |
| "has_version", |
| "Includes a default version V1", |
| ca, cert, |
| // v1 is encoded with value zero. |
| create(ocspResponse{version: new(0)}), |
| ) |
| |
| store( |
| "responder_name", |
| "Uses byName to identify the signer", |
| ca, cert, |
| create(ocspResponse{responderID: getNameResponderID(ca)}), |
| ) |
| |
| store( |
| "responder_id", |
| "Uses byKey to identify the signer", |
| ca, cert, |
| create(ocspResponse{responderID: getKeyHashResponderID(ca)}), |
| ) |
| |
| store( |
| "has_extension", |
| "Includes an x509v3 extension", |
| ca, cert, |
| create(ocspResponse{ |
| extensions: []pkix.Extension{ |
| {Id: asn1.ObjectIdentifier{1, 2, 3, 4}, Value: []byte("DEADBEEF")}, |
| }, |
| }), |
| ) |
| |
| store( |
| "good_response", |
| "Is a valid response for the cert", |
| ca, cert, |
| create(ocspResponse{ |
| responses: []singleResponse{ |
| {cert: cert, status: statusGood}, |
| }, |
| }), |
| ) |
| |
| store( |
| "good_response_sha256", |
| "Is a valid response for the cert with a SHA256 signature", |
| ca, cert, |
| create(ocspResponse{ |
| responses: []singleResponse{ |
| {cert: cert, status: statusGood}, |
| }, |
| sigAlg: x509.SHA256WithRSA, |
| }), |
| ) |
| |
| store( |
| "good_response_next_update", |
| "Is a valid response for the cert until nextUpdate", |
| ca, cert, |
| create(ocspResponse{ |
| responses: []singleResponse{ |
| { |
| cert: cert, |
| status: statusGood, |
| nextUpdate: new(nextDate), |
| }, |
| }, |
| }), |
| ) |
| |
| store( |
| "revoke_response", |
| "Is a REVOKE response for the cert", |
| ca, cert, |
| create(ocspResponse{ |
| responses: []singleResponse{ |
| {cert: cert, status: statusRevoked}, |
| }, |
| }), |
| ) |
| |
| store( |
| "revoke_response_reason", |
| "Is a REVOKE response for the cert with a reason", |
| ca, cert, |
| create(ocspResponse{ |
| responses: []singleResponse{ |
| { |
| cert: cert, |
| status: statusRevoked, |
| revokeTime: new(revokeDate), |
| reason: new(1), // keyCompromise |
| }, |
| }, |
| }), |
| ) |
| |
| store( |
| "unknown_response", |
| "Is an UNKNOWN response for the cert", |
| ca, cert, |
| create(ocspResponse{ |
| responses: []singleResponse{ |
| {cert: cert, status: statusUnknown}, |
| }, |
| }), |
| ) |
| |
| store( |
| "multiple_response", |
| "Has multiple responses for the cert", |
| ca, cert, |
| create(ocspResponse{ |
| responses: []singleResponse{ |
| {cert: cert, status: statusGood}, |
| {cert: cert, status: statusUnknown}, |
| }, |
| }), |
| ) |
| |
| store( |
| "other_response", |
| "Is a response for a different cert", |
| ca, cert, |
| create(ocspResponse{ |
| responses: []singleResponse{ |
| {cert: junkCert, status: statusGood}, |
| {cert: junkCert, status: statusRevoked}, |
| }, |
| }), |
| ) |
| |
| store( |
| "has_single_extension", |
| "Has an extension in the SingleResponse", |
| ca, cert, |
| create(ocspResponse{ |
| responses: []singleResponse{ |
| { |
| cert: cert, |
| status: statusGood, |
| extensions: []pkix.Extension{ |
| {Id: asn1.ObjectIdentifier{1, 2, 3, 4}, Value: []byte("DEADBEEF")}, |
| }, |
| }, |
| }, |
| }), |
| ) |
| |
| store( |
| "has_critical_single_extension", |
| "Has a critical extension in the SingleResponse", |
| ca, cert, |
| create(ocspResponse{ |
| responses: []singleResponse{ |
| { |
| cert: cert, |
| status: statusGood, |
| extensions: []pkix.Extension{ |
| {Id: asn1.ObjectIdentifier{1, 2, 3, 4}, Critical: true, Value: []byte("DEADBEEF")}, |
| }, |
| }, |
| }, |
| }), |
| ) |
| |
| store( |
| "has_critical_response_extension", |
| "Has a critical extension in the ResponseData", |
| ca, cert, |
| create(ocspResponse{ |
| responses: []singleResponse{ |
| {cert: cert, status: statusGood}, |
| }, |
| extensions: []pkix.Extension{ |
| {Id: asn1.ObjectIdentifier{1, 2, 3, 4}, Critical: true, Value: []byte("DEADBEEF")}, |
| }, |
| }), |
| ) |
| |
| store( |
| "has_critical_ct_extension", |
| "Has a critical CT extension in the SingleResponse", |
| ca, cert, |
| create(ocspResponse{ |
| responses: []singleResponse{ |
| { |
| cert: cert, |
| status: statusGood, |
| extensions: []pkix.Extension{ |
| {Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 5}, Critical: true, Value: []byte("DEADBEEF")}, |
| }, |
| }, |
| }, |
| }), |
| ) |
| |
| store( |
| "missing_response", |
| "Missing a response for the cert", |
| ca, cert, |
| create(ocspResponse{responseStatus: responseStatusSuccessful, responses: []singleResponse{}}), |
| ) |
| |
| store( |
| "stale_response", |
| "nextUpdate is before the current time", |
| ca, cert, |
| create(ocspResponse{ |
| responses: []singleResponse{ |
| { |
| cert: cert, |
| status: statusGood, |
| thisUpdate: verifyDate.Add(-2 * 24 * time.Hour), |
| nextUpdate: new(verifyDate.Add(-1 * 24 * time.Hour)), |
| }, |
| }, |
| }), |
| ) |
| |
| store( |
| "future_response", |
| "thisUpdate is after the current time", |
| ca, cert, |
| create(ocspResponse{ |
| responses: []singleResponse{ |
| { |
| cert: cert, |
| status: statusGood, |
| thisUpdate: verifyDate.Add(1 * 24 * time.Hour), |
| }, |
| }, |
| }), |
| ) |
| |
| store( |
| "old_response", |
| "thisUpdate is over a week before the current time", |
| ca, cert, |
| create(ocspResponse{ |
| responses: []singleResponse{ |
| { |
| cert: cert, |
| status: statusGood, |
| thisUpdate: verifyDate.Add(-8 * 24 * time.Hour), |
| }, |
| }, |
| }), |
| ) |
| |
| store( |
| "produced_early_response", |
| "producedAt is before the cert's notBefore", |
| ca, cert, |
| create(ocspResponse{ |
| responses: []singleResponse{ |
| {cert: cert, status: statusGood}, |
| }, |
| producedAt: certDate.Add(-1 * 24 * time.Hour), |
| }), |
| ) |
| |
| store( |
| "produced_late_response", |
| "producedAt is after the cert's notAfter", |
| ca, cert, |
| create(ocspResponse{ |
| responses: []singleResponse{ |
| {cert: cert, status: statusGood}, |
| }, |
| producedAt: certExpire.Add(1 * 24 * time.Hour), |
| }), |
| ) |
| |
| store( |
| "invalid_response", |
| "OCSPResponse cannot be parsed", |
| ca, cert, |
| []byte("invalid"), |
| ) |
| |
| store( |
| "invalid_response_data", |
| "ResponseData cannot be parsed", |
| ca, cert, |
| create(ocspResponse{invalidResponseData: true}), |
| ) |
| |
| store( |
| "multiple_response_good_revoked", |
| "Has both a good and a revoked response for the cert", |
| ca, cert, |
| create(ocspResponse{ |
| responses: []singleResponse{ |
| {cert: cert, status: statusGood}, |
| {cert: cert, status: statusRevoked}, |
| }, |
| }), |
| ) |
| |
| certInvalidSerial := createCertWithRawSerial(cert, []byte{0x00, 0x05}) |
| store( |
| "good_response_invalid_serial", |
| `Is a valid response for the cert, but the cert has an incorrectly encoded |
| serial number. Until we fix the cert parser (https://crbug.com/533048005), it |
| is prudent (though not critical) for every acceptable serial number to be |
| representable in OCSP.`, |
| ca, certInvalidSerial, |
| create(ocspResponse{ |
| responses: []singleResponse{ |
| {cert: certInvalidSerial, status: statusGood}, |
| }, |
| }), |
| ) |
| |
| store( |
| "good_response_invalid_status", |
| "Is a good response but the status is encoded wrong", |
| ca, cert, |
| create(ocspResponse{ |
| responses: []singleResponse{ |
| {cert: cert, status: statusGood, trailingDataInStatus: true}, |
| }, |
| }), |
| ) |
| store( |
| "revoke_response_invalid_status", |
| "Is a revoked response but the status is encoded wrong", |
| ca, cert, |
| create(ocspResponse{ |
| responses: []singleResponse{ |
| {cert: cert, status: statusRevoked, trailingDataInStatus: true}, |
| }, |
| }), |
| ) |
| store( |
| "unknown_response_invalid_status", |
| "Is an unknown response but the status is encoded wrong", |
| ca, cert, |
| create(ocspResponse{ |
| responses: []singleResponse{ |
| {cert: cert, status: statusUnknown, trailingDataInStatus: true}, |
| }, |
| }), |
| ) |
| |
| cmd := exec.Command("python3", "annotate_test_data.py") |
| cmd.Stdout = os.Stdout |
| cmd.Stderr = os.Stderr |
| if err := cmd.Run(); err != nil { |
| panic(err) |
| } |
| } |