| // Copyright 2025 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. | 
 |  | 
 | package runner | 
 |  | 
 | import ( | 
 | 	"crypto" | 
 | 	"crypto/ecdsa" | 
 | 	"crypto/ed25519" | 
 | 	"crypto/rand" | 
 | 	"crypto/rsa" | 
 | 	"crypto/x509" | 
 | 	"crypto/x509/pkix" | 
 | 	"encoding/asn1" | 
 | 	"encoding/pem" | 
 | 	"errors" | 
 | 	"fmt" | 
 | 	"math/bits" | 
 | 	"os" | 
 | 	"sync/atomic" | 
 | 	"time" | 
 |  | 
 | 	"golang.org/x/crypto/cryptobyte" | 
 | 	cbasn1 "golang.org/x/crypto/cryptobyte/asn1" | 
 | ) | 
 |  | 
 | // A custom X.509 certificate generator. This file exists both to give more | 
 | // convenient ways to generate X.509 certificates, as well as add support for | 
 | // key types that upstream Go does not support. As a result, it does not reuse | 
 | // the x509.Certificate encoder. | 
 |  | 
 | var ( | 
 | 	oidSHA256WithRSAEncryption = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11} | 
 | 	oidECDSAWithSHA256         = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2} | 
 | 	oidEd25519                 = asn1.ObjectIdentifier{1, 3, 101, 112} | 
 | 	oidPSS                     = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10} | 
 |  | 
 | 	oidSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1} | 
 |  | 
 | 	oidMGF1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 8} | 
 |  | 
 | 	oidSubjectKeyID     = []int{2, 5, 29, 14} | 
 | 	oidKeyUsage         = []int{2, 5, 29, 15} | 
 | 	oidSubjectAltName   = []int{2, 5, 29, 17} | 
 | 	oidBasicConstraints = []int{2, 5, 29, 19} | 
 | 	oidAuthorityKeyID   = []int{2, 5, 29, 35} | 
 |  | 
 | 	lastSerial atomic.Uint64 | 
 |  | 
 | 	tmpDir string | 
 | ) | 
 |  | 
 | type X509SignatureAlgorithm int | 
 |  | 
 | const ( | 
 | 	X509SignDefault X509SignatureAlgorithm = iota | 
 | 	X509SignRSAWithSHA256 | 
 | 	X509SignECDSAWithSHA256 | 
 | 	X509SignEd25519 | 
 | ) | 
 |  | 
 | func (alg X509SignatureAlgorithm) Marshal(bb *cryptobyte.Builder) error { | 
 | 	switch alg { | 
 | 	case X509SignRSAWithSHA256: | 
 | 		bb.AddASN1(cbasn1.SEQUENCE, func(algID *cryptobyte.Builder) { | 
 | 			algID.AddASN1ObjectIdentifier(oidSHA256WithRSAEncryption) | 
 | 			algID.AddASN1NULL() | 
 | 		}) | 
 | 	case X509SignECDSAWithSHA256: | 
 | 		bb.AddASN1(cbasn1.SEQUENCE, func(algID *cryptobyte.Builder) { | 
 | 			algID.AddASN1ObjectIdentifier(oidECDSAWithSHA256) | 
 | 		}) | 
 | 	case X509SignEd25519: | 
 | 		bb.AddASN1(cbasn1.SEQUENCE, func(algID *cryptobyte.Builder) { | 
 | 			algID.AddASN1ObjectIdentifier(oidEd25519) | 
 | 		}) | 
 | 	default: | 
 | 		return errors.New("unknown algorithm") | 
 | 	} | 
 | 	return nil | 
 | } | 
 |  | 
 | func chooseDefaultX509SignatureAlgorithm(signer crypto.Signer) (X509SignatureAlgorithm, error) { | 
 | 	if _, ok := signer.(*rsa.PrivateKey); ok { | 
 | 		return X509SignRSAWithSHA256, nil | 
 | 	} | 
 | 	if _, ok := signer.(*ecdsa.PrivateKey); ok { | 
 | 		return X509SignECDSAWithSHA256, nil | 
 | 	} | 
 | 	if _, ok := signer.(ed25519.PrivateKey); ok { | 
 | 		return X509SignEd25519, nil | 
 | 	} | 
 | 	return 0, fmt.Errorf("unsupported key type: %T", signer) | 
 | } | 
 |  | 
 | func signX509(signer crypto.Signer, alg X509SignatureAlgorithm, in []byte) ([]byte, error) { | 
 | 	var opts crypto.SignerOpts | 
 | 	if _, ok := signer.(*rsa.PrivateKey); ok { | 
 | 		switch alg { | 
 | 		case X509SignRSAWithSHA256: | 
 | 			opts = crypto.SHA256 | 
 | 		default: | 
 | 			return nil, errors.New("unknown algorithm") | 
 | 		} | 
 | 	} else if _, ok := signer.(*ecdsa.PrivateKey); ok { | 
 | 		switch alg { | 
 | 		case X509SignECDSAWithSHA256: | 
 | 			opts = crypto.SHA256 | 
 | 		default: | 
 | 			return nil, errors.New("unknown algorithm") | 
 | 		} | 
 | 	} else if _, ok := signer.(ed25519.PrivateKey); ok { | 
 | 		switch alg { | 
 | 		case X509SignEd25519: | 
 | 			opts = crypto.Hash(0) | 
 | 		default: | 
 | 			return nil, errors.New("unknown algorithm") | 
 | 		} | 
 | 	} else { | 
 | 		return nil, fmt.Errorf("unsupported key type: %T", signer) | 
 | 	} | 
 |  | 
 | 	digest := in | 
 | 	if hash := opts.HashFunc(); hash != crypto.Hash(0) { | 
 | 		h := hash.New() | 
 | 		h.Write(in) | 
 | 		digest = h.Sum(nil) | 
 | 	} | 
 | 	return signer.Sign(rand.Reader, digest, opts) | 
 | } | 
 |  | 
 | func addX509Time(bb *cryptobyte.Builder, t time.Time) { | 
 | 	t = t.UTC() | 
 | 	if y := t.Year(); 1950 <= y && y <= 2049 { | 
 | 		bb.AddASN1UTCTime(t) | 
 | 	} else { | 
 | 		bb.AddASN1GeneralizedTime(t) | 
 | 	} | 
 | } | 
 |  | 
 | func addASN1ImplicitString(bb *cryptobyte.Builder, tag cbasn1.Tag, b []byte) { | 
 | 	bb.AddASN1(tag, func(child *cryptobyte.Builder) { child.AddBytes(b) }) | 
 | } | 
 |  | 
 | func addASN1ExplicitTag(bb *cryptobyte.Builder, outerTag, innerTag cbasn1.Tag, cb func(*cryptobyte.Builder)) { | 
 | 	bb.AddASN1(outerTag.Constructed().ContextSpecific(), func(child *cryptobyte.Builder) { | 
 | 		child.AddASN1(innerTag, cb) | 
 | 	}) | 
 | } | 
 |  | 
 | func addRSAPSSSubjectPublicKeyInfo(bb *cryptobyte.Builder, key *rsa.PublicKey) { | 
 | 	bb.AddASN1(cbasn1.SEQUENCE, func(spki *cryptobyte.Builder) { | 
 | 		spki.AddASN1(cbasn1.SEQUENCE, func(algID *cryptobyte.Builder) { | 
 | 			algID.AddASN1ObjectIdentifier(oidPSS) | 
 | 			algID.AddASN1(cbasn1.SEQUENCE, func(params *cryptobyte.Builder) { | 
 | 				addASN1ExplicitTag(params, 0, cbasn1.SEQUENCE, func(hash *cryptobyte.Builder) { | 
 | 					hash.AddASN1ObjectIdentifier(oidSHA256) | 
 | 					hash.AddASN1NULL() | 
 | 				}) | 
 | 				addASN1ExplicitTag(params, 1, cbasn1.SEQUENCE, func(mgf *cryptobyte.Builder) { | 
 | 					mgf.AddASN1ObjectIdentifier(oidMGF1) | 
 | 					mgf.AddASN1(cbasn1.SEQUENCE, func(hash *cryptobyte.Builder) { | 
 | 						hash.AddASN1ObjectIdentifier(oidSHA256) | 
 | 						hash.AddASN1NULL() | 
 | 					}) | 
 | 				}) | 
 | 				params.AddASN1(cbasn1.Tag(2).Constructed().ContextSpecific(), func(saltLen *cryptobyte.Builder) { | 
 | 					saltLen.AddASN1Uint64(32) | 
 | 				}) | 
 | 			}) | 
 | 		}) | 
 | 		spki.AddASN1BitString(x509.MarshalPKCS1PublicKey(key)) | 
 | 	}) | 
 | } | 
 |  | 
 | type X509Info struct { | 
 | 	PrivateKey         crypto.Signer | 
 | 	Name               pkix.Name | 
 | 	DNSNames           []string | 
 | 	IsCA               bool | 
 | 	SubjectKeyID       []byte | 
 | 	KeyUsage           x509.KeyUsage | 
 | 	SignatureAlgorithm X509SignatureAlgorithm | 
 | 	// EncodeSPKIAsRSAPSS, if true, causes the subjectPublicKeyInfo field to be | 
 | 	// encoded as id-RSASSA-PSS with SHA-256 parameters, instead of | 
 | 	// id-rsaEncryption. This is sufficient for our purposes because we do not | 
 | 	// need real id-RSASSA-PSS support in the test runner. If we ever to, we | 
 | 	// can replace this with a real PSSPrivateKey type. | 
 | 	EncodeSPKIAsRSAPSS bool | 
 | } | 
 |  | 
 | type X509ChainBuilder struct { | 
 | 	privateKey   crypto.Signer | 
 | 	name         pkix.Name | 
 | 	subjectKeyID []byte | 
 | 	rootCert     []byte | 
 | 	rootPath     string | 
 | 	chain        [][]byte | 
 | } | 
 |  | 
 | func x509ChainBuilderFromInfo(info X509Info) *X509ChainBuilder { | 
 | 	return &X509ChainBuilder{ | 
 | 		privateKey:   info.PrivateKey, | 
 | 		name:         info.Name, | 
 | 		subjectKeyID: info.SubjectKeyID, | 
 | 	} | 
 | } | 
 |  | 
 | func NewX509Root(root X509Info) *X509ChainBuilder { | 
 | 	ret := x509ChainBuilderFromInfo(root).Issue(root) | 
 | 	ret.rootCert = ret.chain[0] | 
 | 	ret.rootPath = writeTempCertFile([][]byte{ret.rootCert}) | 
 | 	ret.chain = nil | 
 | 	return ret | 
 | } | 
 |  | 
 | func (issuer *X509ChainBuilder) Issue(subject X509Info) *X509ChainBuilder { | 
 | 	serial := lastSerial.Add(1) | 
 |  | 
 | 	sigAlg := subject.SignatureAlgorithm | 
 | 	if sigAlg == X509SignDefault { | 
 | 		var err error | 
 | 		sigAlg, err = chooseDefaultX509SignatureAlgorithm(issuer.privateKey) | 
 | 		if err != nil { | 
 | 			panic(err) | 
 | 		} | 
 | 	} | 
 |  | 
 | 	notBefore := time.Now().Add(-time.Hour) | 
 | 	notAfter := time.Now().Add(time.Hour) | 
 |  | 
 | 	bb := cryptobyte.NewBuilder(nil) | 
 | 	bb.AddASN1(cbasn1.SEQUENCE, func(tbs *cryptobyte.Builder) { | 
 | 		tbs.AddASN1(cbasn1.Tag(0).Constructed().ContextSpecific(), func(vers *cryptobyte.Builder) { | 
 | 			vers.AddASN1Uint64(2) // v3 | 
 | 		}) | 
 | 		tbs.AddASN1Uint64(serial) | 
 | 		tbs.AddValue(sigAlg) | 
 | 		issuerDER, err := asn1.Marshal(issuer.name.ToRDNSequence()) | 
 | 		if err != nil { | 
 | 			tbs.SetError(err) | 
 | 			return | 
 | 		} | 
 | 		tbs.AddBytes(issuerDER) | 
 | 		tbs.AddASN1(cbasn1.SEQUENCE, func(val *cryptobyte.Builder) { | 
 | 			addX509Time(val, notBefore) | 
 | 			addX509Time(val, notAfter) | 
 | 		}) | 
 | 		subjectDER, err := asn1.Marshal(subject.Name.ToRDNSequence()) | 
 | 		if err != nil { | 
 | 			tbs.SetError(err) | 
 | 			return | 
 | 		} | 
 | 		tbs.AddBytes(subjectDER) | 
 | 		if subject.EncodeSPKIAsRSAPSS { | 
 | 			addRSAPSSSubjectPublicKeyInfo(tbs, subject.PrivateKey.Public().(*rsa.PublicKey)) | 
 | 		} else { | 
 | 			spki, err := x509.MarshalPKIXPublicKey(subject.PrivateKey.Public()) | 
 | 			if err != nil { | 
 | 				tbs.SetError(err) | 
 | 				return | 
 | 			} | 
 | 			tbs.AddBytes(spki) | 
 | 		} | 
 | 		addASN1ExplicitTag(tbs, 3, cbasn1.SEQUENCE, func(exts *cryptobyte.Builder) { | 
 | 			if len(issuer.subjectKeyID) != 0 { | 
 | 				exts.AddASN1(cbasn1.SEQUENCE, func(ext *cryptobyte.Builder) { | 
 | 					ext.AddASN1ObjectIdentifier(oidAuthorityKeyID) | 
 | 					ext.AddASN1(cbasn1.OCTET_STRING, func(extVal *cryptobyte.Builder) { | 
 | 						extVal.AddASN1(cbasn1.SEQUENCE, func(akid *cryptobyte.Builder) { | 
 | 							addASN1ImplicitString(akid, cbasn1.Tag(0).ContextSpecific(), issuer.subjectKeyID) | 
 | 						}) | 
 | 					}) | 
 | 				}) | 
 | 			} | 
 |  | 
 | 			if subject.KeyUsage != 0 { | 
 | 				exts.AddASN1(cbasn1.SEQUENCE, func(ext *cryptobyte.Builder) { | 
 | 					ext.AddASN1ObjectIdentifier(oidKeyUsage) | 
 | 					ext.AddASN1Boolean(true) // critical | 
 | 					ext.AddASN1(cbasn1.OCTET_STRING, func(extVal *cryptobyte.Builder) { | 
 | 						var b [2]byte | 
 | 						// DER orders the bits from most to least significant. | 
 | 						b[0] = bits.Reverse8(byte(subject.KeyUsage)) | 
 | 						b[1] = bits.Reverse8(byte(subject.KeyUsage >> 8)) | 
 | 						// If the final byte is all zeros, skip it. | 
 | 						var ku asn1.BitString | 
 | 						if b[1] == 0 { | 
 | 							ku.Bytes = b[:1] | 
 | 						} else { | 
 | 							ku.Bytes = b[:] | 
 | 						} | 
 | 						ku.BitLength = bits.Len16(uint16(subject.KeyUsage)) | 
 | 						der, err := asn1.Marshal(ku) | 
 | 						if err != nil { | 
 | 							extVal.SetError(err) | 
 | 						} else { | 
 | 							extVal.AddBytes(der) | 
 | 						} | 
 | 					}) | 
 | 				}) | 
 | 			} | 
 |  | 
 | 			if len(subject.DNSNames) != 0 { | 
 | 				exts.AddASN1(cbasn1.SEQUENCE, func(ext *cryptobyte.Builder) { | 
 | 					ext.AddASN1ObjectIdentifier(oidSubjectAltName) | 
 | 					ext.AddASN1(cbasn1.OCTET_STRING, func(extVal *cryptobyte.Builder) { | 
 | 						extVal.AddASN1(cbasn1.SEQUENCE, func(names *cryptobyte.Builder) { | 
 | 							for _, dns := range subject.DNSNames { | 
 | 								addASN1ImplicitString(names, cbasn1.Tag(2).ContextSpecific(), []byte(dns)) | 
 | 							} | 
 | 						}) | 
 | 					}) | 
 | 				}) | 
 | 			} | 
 |  | 
 | 			if subject.IsCA { | 
 | 				exts.AddASN1(cbasn1.SEQUENCE, func(ext *cryptobyte.Builder) { | 
 | 					ext.AddASN1ObjectIdentifier(oidBasicConstraints) | 
 | 					ext.AddASN1Boolean(true) // critical | 
 | 					ext.AddASN1(cbasn1.OCTET_STRING, func(extVal *cryptobyte.Builder) { | 
 | 						extVal.AddASN1(cbasn1.SEQUENCE, func(bcons *cryptobyte.Builder) { | 
 | 							bcons.AddASN1Boolean(true) | 
 | 						}) | 
 | 					}) | 
 | 				}) | 
 | 			} | 
 |  | 
 | 			if len(subject.SubjectKeyID) != 0 { | 
 | 				exts.AddASN1(cbasn1.SEQUENCE, func(ext *cryptobyte.Builder) { | 
 | 					ext.AddASN1ObjectIdentifier(oidSubjectKeyID) | 
 | 					ext.AddASN1(cbasn1.OCTET_STRING, func(extVal *cryptobyte.Builder) { | 
 | 						extVal.AddASN1OctetString(subject.SubjectKeyID) | 
 | 					}) | 
 | 				}) | 
 | 			} | 
 | 		}) | 
 | 	}) | 
 |  | 
 | 	tbs := bb.BytesOrPanic() | 
 | 	sig, err := signX509(issuer.privateKey, sigAlg, tbs) | 
 | 	if err != nil { | 
 | 		panic(err) | 
 | 	} | 
 |  | 
 | 	bb = cryptobyte.NewBuilder(nil) | 
 | 	bb.AddASN1(cbasn1.SEQUENCE, func(cert *cryptobyte.Builder) { | 
 | 		cert.AddBytes(tbs) | 
 | 		cert.AddValue(sigAlg) | 
 | 		cert.AddASN1BitString(sig) | 
 | 	}) | 
 | 	cert := bb.BytesOrPanic() | 
 |  | 
 | 	ret := x509ChainBuilderFromInfo(subject) | 
 | 	ret.rootCert = issuer.rootCert | 
 | 	ret.rootPath = issuer.rootPath | 
 | 	ret.chain = make([][]byte, len(issuer.chain)+1) | 
 | 	copy(ret.chain, issuer.chain) | 
 | 	ret.chain[len(ret.chain)-1] = cert | 
 | 	return ret | 
 | } | 
 |  | 
 | func (b *X509ChainBuilder) ToCredential() Credential { | 
 | 	return Credential{ | 
 | 		Certificate:     b.chain, | 
 | 		ChainPath:       writeTempCertFile(b.chain), | 
 | 		PrivateKey:      b.privateKey, | 
 | 		KeyPath:         writeTempKeyFile(b.privateKey), | 
 | 		RootCertificate: b.rootCert, | 
 | 		RootPath:        b.rootPath, | 
 | 	} | 
 | } | 
 |  | 
 | func writeTempCertFile(certs [][]byte) string { | 
 | 	f, err := os.CreateTemp(tmpDir, "test-cert") | 
 | 	if err != nil { | 
 | 		panic(fmt.Sprintf("failed to create temp file: %s", err)) | 
 | 	} | 
 | 	for _, cert := range certs { | 
 | 		if err := pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: cert}); err != nil { | 
 | 			panic(fmt.Sprintf("failed to write test certificate: %s", err)) | 
 | 		} | 
 | 	} | 
 | 	tmpCertPath := f.Name() | 
 | 	if err := f.Close(); err != nil { | 
 | 		panic(fmt.Sprintf("failed to close test certificate temp file: %s", err)) | 
 | 	} | 
 | 	return tmpCertPath | 
 | } | 
 |  | 
 | func writeTempKeyFile(privKey crypto.PrivateKey) string { | 
 | 	f, err := os.CreateTemp(tmpDir, "test-key") | 
 | 	if err != nil { | 
 | 		panic(fmt.Sprintf("failed to create temp file: %s", err)) | 
 | 	} | 
 | 	keyDER, err := x509.MarshalPKCS8PrivateKey(privKey) | 
 | 	if err != nil { | 
 | 		panic(fmt.Sprintf("failed to marshal test key: %s", err)) | 
 | 	} | 
 | 	if err := pem.Encode(f, &pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}); err != nil { | 
 | 		panic(fmt.Sprintf("failed to write test key: %s", err)) | 
 | 	} | 
 | 	tmpKeyPath := f.Name() | 
 | 	if err := f.Close(); err != nil { | 
 | 		panic(fmt.Sprintf("failed to close test key temp file: %s", err)) | 
 | 	} | 
 | 	return tmpKeyPath | 
 | } |