1// Copyright 2025 The BoringSSL Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package runner
16
17import (
18	"crypto"
19	"crypto/ecdsa"
20	"crypto/ed25519"
21	"crypto/rand"
22	"crypto/rsa"
23	"crypto/x509"
24	"crypto/x509/pkix"
25	"encoding/asn1"
26	"encoding/pem"
27	"errors"
28	"fmt"
29	"math/bits"
30	"os"
31	"sync/atomic"
32	"time"
33
34	"golang.org/x/crypto/cryptobyte"
35	cbasn1 "golang.org/x/crypto/cryptobyte/asn1"
36)
37
38// A custom X.509 certificate generator. This file exists both to give more
39// convenient ways to generate X.509 certificates, as well as add support for
40// key types that upstream Go does not support. As a result, it does not reuse
41// the x509.Certificate encoder.
42
43var (
44	oidSHA256WithRSAEncryption = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11}
45	oidECDSAWithSHA256         = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2}
46	oidEd25519                 = asn1.ObjectIdentifier{1, 3, 101, 112}
47	oidPSS                     = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10}
48
49	oidSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1}
50
51	oidMGF1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 8}
52
53	oidSubjectKeyID     = []int{2, 5, 29, 14}
54	oidKeyUsage         = []int{2, 5, 29, 15}
55	oidSubjectAltName   = []int{2, 5, 29, 17}
56	oidBasicConstraints = []int{2, 5, 29, 19}
57	oidAuthorityKeyID   = []int{2, 5, 29, 35}
58
59	lastSerial atomic.Uint64
60
61	tmpDir string
62)
63
64type X509SignatureAlgorithm int
65
66const (
67	X509SignDefault X509SignatureAlgorithm = iota
68	X509SignRSAWithSHA256
69	X509SignECDSAWithSHA256
70	X509SignEd25519
71)
72
73func (alg X509SignatureAlgorithm) Marshal(bb *cryptobyte.Builder) error {
74	switch alg {
75	case X509SignRSAWithSHA256:
76		bb.AddASN1(cbasn1.SEQUENCE, func(algID *cryptobyte.Builder) {
77			algID.AddASN1ObjectIdentifier(oidSHA256WithRSAEncryption)
78			algID.AddASN1NULL()
79		})
80	case X509SignECDSAWithSHA256:
81		bb.AddASN1(cbasn1.SEQUENCE, func(algID *cryptobyte.Builder) {
82			algID.AddASN1ObjectIdentifier(oidECDSAWithSHA256)
83		})
84	case X509SignEd25519:
85		bb.AddASN1(cbasn1.SEQUENCE, func(algID *cryptobyte.Builder) {
86			algID.AddASN1ObjectIdentifier(oidEd25519)
87		})
88	default:
89		return errors.New("unknown algorithm")
90	}
91	return nil
92}
93
94func chooseDefaultX509SignatureAlgorithm(signer crypto.Signer) (X509SignatureAlgorithm, error) {
95	if _, ok := signer.(*rsa.PrivateKey); ok {
96		return X509SignRSAWithSHA256, nil
97	}
98	if _, ok := signer.(*ecdsa.PrivateKey); ok {
99		return X509SignECDSAWithSHA256, nil
100	}
101	if _, ok := signer.(ed25519.PrivateKey); ok {
102		return X509SignEd25519, nil
103	}
104	return 0, fmt.Errorf("unsupported key type: %T", signer)
105}
106
107func signX509(signer crypto.Signer, alg X509SignatureAlgorithm, in []byte) ([]byte, error) {
108	var opts crypto.SignerOpts
109	if _, ok := signer.(*rsa.PrivateKey); ok {
110		switch alg {
111		case X509SignRSAWithSHA256:
112			opts = crypto.SHA256
113		default:
114			return nil, errors.New("unknown algorithm")
115		}
116	} else if _, ok := signer.(*ecdsa.PrivateKey); ok {
117		switch alg {
118		case X509SignECDSAWithSHA256:
119			opts = crypto.SHA256
120		default:
121			return nil, errors.New("unknown algorithm")
122		}
123	} else if _, ok := signer.(ed25519.PrivateKey); ok {
124		switch alg {
125		case X509SignEd25519:
126			opts = crypto.Hash(0)
127		default:
128			return nil, errors.New("unknown algorithm")
129		}
130	} else {
131		return nil, fmt.Errorf("unsupported key type: %T", signer)
132	}
133
134	digest := in
135	if hash := opts.HashFunc(); hash != crypto.Hash(0) {
136		h := hash.New()
137		h.Write(in)
138		digest = h.Sum(nil)
139	}
140	return signer.Sign(rand.Reader, digest, opts)
141}
142
143func addX509Time(bb *cryptobyte.Builder, t time.Time) {
144	t = t.UTC()
145	if y := t.Year(); 1950 <= y && y <= 2049 {
146		bb.AddASN1UTCTime(t)
147	} else {
148		bb.AddASN1GeneralizedTime(t)
149	}
150}
151
152func addASN1ImplicitString(bb *cryptobyte.Builder, tag cbasn1.Tag, b []byte) {
153	bb.AddASN1(tag, func(child *cryptobyte.Builder) { child.AddBytes(b) })
154}
155
156func addASN1ExplicitTag(bb *cryptobyte.Builder, outerTag, innerTag cbasn1.Tag, cb func(*cryptobyte.Builder)) {
157	bb.AddASN1(outerTag.Constructed().ContextSpecific(), func(child *cryptobyte.Builder) {
158		child.AddASN1(innerTag, cb)
159	})
160}
161
162func addRSAPSSSubjectPublicKeyInfo(bb *cryptobyte.Builder, key *rsa.PublicKey) {
163	bb.AddASN1(cbasn1.SEQUENCE, func(spki *cryptobyte.Builder) {
164		spki.AddASN1(cbasn1.SEQUENCE, func(algID *cryptobyte.Builder) {
165			algID.AddASN1ObjectIdentifier(oidPSS)
166			algID.AddASN1(cbasn1.SEQUENCE, func(params *cryptobyte.Builder) {
167				addASN1ExplicitTag(params, 0, cbasn1.SEQUENCE, func(hash *cryptobyte.Builder) {
168					hash.AddASN1ObjectIdentifier(oidSHA256)
169					hash.AddASN1NULL()
170				})
171				addASN1ExplicitTag(params, 1, cbasn1.SEQUENCE, func(mgf *cryptobyte.Builder) {
172					mgf.AddASN1ObjectIdentifier(oidMGF1)
173					mgf.AddASN1(cbasn1.SEQUENCE, func(hash *cryptobyte.Builder) {
174						hash.AddASN1ObjectIdentifier(oidSHA256)
175						hash.AddASN1NULL()
176					})
177				})
178				params.AddASN1(cbasn1.Tag(2).Constructed().ContextSpecific(), func(saltLen *cryptobyte.Builder) {
179					saltLen.AddASN1Uint64(32)
180				})
181			})
182		})
183		spki.AddASN1BitString(x509.MarshalPKCS1PublicKey(key))
184	})
185}
186
187type X509Info struct {
188	PrivateKey         crypto.Signer
189	Name               pkix.Name
190	DNSNames           []string
191	IsCA               bool
192	SubjectKeyID       []byte
193	KeyUsage           x509.KeyUsage
194	SignatureAlgorithm X509SignatureAlgorithm
195	// EncodeSPKIAsRSAPSS, if true, causes the subjectPublicKeyInfo field to be
196	// encoded as id-RSASSA-PSS with SHA-256 parameters, instead of
197	// id-rsaEncryption. This is sufficient for our purposes because we do not
198	// need real id-RSASSA-PSS support in the test runner. If we ever to, we
199	// can replace this with a real PSSPrivateKey type.
200	EncodeSPKIAsRSAPSS bool
201}
202
203type X509ChainBuilder struct {
204	privateKey   crypto.Signer
205	name         pkix.Name
206	subjectKeyID []byte
207	rootCert     []byte
208	rootPath     string
209	chain        [][]byte
210}
211
212func x509ChainBuilderFromInfo(info X509Info) *X509ChainBuilder {
213	return &X509ChainBuilder{
214		privateKey:   info.PrivateKey,
215		name:         info.Name,
216		subjectKeyID: info.SubjectKeyID,
217	}
218}
219
220func NewX509Root(root X509Info) *X509ChainBuilder {
221	ret := x509ChainBuilderFromInfo(root).Issue(root)
222	ret.rootCert = ret.chain[0]
223	ret.rootPath = writeTempCertFile([][]byte{ret.rootCert})
224	ret.chain = nil
225	return ret
226}
227
228func (issuer *X509ChainBuilder) Issue(subject X509Info) *X509ChainBuilder {
229	serial := lastSerial.Add(1)
230
231	sigAlg := subject.SignatureAlgorithm
232	if sigAlg == X509SignDefault {
233		var err error
234		sigAlg, err = chooseDefaultX509SignatureAlgorithm(issuer.privateKey)
235		if err != nil {
236			panic(err)
237		}
238	}
239
240	notBefore := time.Now().Add(-time.Hour)
241	notAfter := time.Now().Add(time.Hour)
242
243	bb := cryptobyte.NewBuilder(nil)
244	bb.AddASN1(cbasn1.SEQUENCE, func(tbs *cryptobyte.Builder) {
245		tbs.AddASN1(cbasn1.Tag(0).Constructed().ContextSpecific(), func(vers *cryptobyte.Builder) {
246			vers.AddASN1Uint64(2) // v3
247		})
248		tbs.AddASN1Uint64(serial)
249		tbs.AddValue(sigAlg)
250		issuerDER, err := asn1.Marshal(issuer.name.ToRDNSequence())
251		if err != nil {
252			tbs.SetError(err)
253			return
254		}
255		tbs.AddBytes(issuerDER)
256		tbs.AddASN1(cbasn1.SEQUENCE, func(val *cryptobyte.Builder) {
257			addX509Time(val, notBefore)
258			addX509Time(val, notAfter)
259		})
260		subjectDER, err := asn1.Marshal(subject.Name.ToRDNSequence())
261		if err != nil {
262			tbs.SetError(err)
263			return
264		}
265		tbs.AddBytes(subjectDER)
266		if subject.EncodeSPKIAsRSAPSS {
267			addRSAPSSSubjectPublicKeyInfo(tbs, subject.PrivateKey.Public().(*rsa.PublicKey))
268		} else {
269			spki, err := x509.MarshalPKIXPublicKey(subject.PrivateKey.Public())
270			if err != nil {
271				tbs.SetError(err)
272				return
273			}
274			tbs.AddBytes(spki)
275		}
276		addASN1ExplicitTag(tbs, 3, cbasn1.SEQUENCE, func(exts *cryptobyte.Builder) {
277			if len(issuer.subjectKeyID) != 0 {
278				exts.AddASN1(cbasn1.SEQUENCE, func(ext *cryptobyte.Builder) {
279					ext.AddASN1ObjectIdentifier(oidAuthorityKeyID)
280					ext.AddASN1(cbasn1.OCTET_STRING, func(extVal *cryptobyte.Builder) {
281						extVal.AddASN1(cbasn1.SEQUENCE, func(akid *cryptobyte.Builder) {
282							addASN1ImplicitString(akid, cbasn1.Tag(0).ContextSpecific(), issuer.subjectKeyID)
283						})
284					})
285				})
286			}
287
288			if subject.KeyUsage != 0 {
289				exts.AddASN1(cbasn1.SEQUENCE, func(ext *cryptobyte.Builder) {
290					ext.AddASN1ObjectIdentifier(oidKeyUsage)
291					ext.AddASN1Boolean(true) // critical
292					ext.AddASN1(cbasn1.OCTET_STRING, func(extVal *cryptobyte.Builder) {
293						var b [2]byte
294						// DER orders the bits from most to least significant.
295						b[0] = bits.Reverse8(byte(subject.KeyUsage))
296						b[1] = bits.Reverse8(byte(subject.KeyUsage >> 8))
297						// If the final byte is all zeros, skip it.
298						var ku asn1.BitString
299						if b[1] == 0 {
300							ku.Bytes = b[:1]
301						} else {
302							ku.Bytes = b[:]
303						}
304						ku.BitLength = bits.Len16(uint16(subject.KeyUsage))
305						der, err := asn1.Marshal(ku)
306						if err != nil {
307							extVal.SetError(err)
308						} else {
309							extVal.AddBytes(der)
310						}
311					})
312				})
313			}
314
315			if len(subject.DNSNames) != 0 {
316				exts.AddASN1(cbasn1.SEQUENCE, func(ext *cryptobyte.Builder) {
317					ext.AddASN1ObjectIdentifier(oidSubjectAltName)
318					ext.AddASN1(cbasn1.OCTET_STRING, func(extVal *cryptobyte.Builder) {
319						extVal.AddASN1(cbasn1.SEQUENCE, func(names *cryptobyte.Builder) {
320							for _, dns := range subject.DNSNames {
321								addASN1ImplicitString(names, cbasn1.Tag(2).ContextSpecific(), []byte(dns))
322							}
323						})
324					})
325				})
326			}
327
328			if subject.IsCA {
329				exts.AddASN1(cbasn1.SEQUENCE, func(ext *cryptobyte.Builder) {
330					ext.AddASN1ObjectIdentifier(oidBasicConstraints)
331					ext.AddASN1Boolean(true) // critical
332					ext.AddASN1(cbasn1.OCTET_STRING, func(extVal *cryptobyte.Builder) {
333						extVal.AddASN1(cbasn1.SEQUENCE, func(bcons *cryptobyte.Builder) {
334							bcons.AddASN1Boolean(true)
335						})
336					})
337				})
338			}
339
340			if len(subject.SubjectKeyID) != 0 {
341				exts.AddASN1(cbasn1.SEQUENCE, func(ext *cryptobyte.Builder) {
342					ext.AddASN1ObjectIdentifier(oidSubjectKeyID)
343					ext.AddASN1(cbasn1.OCTET_STRING, func(extVal *cryptobyte.Builder) {
344						extVal.AddASN1OctetString(subject.SubjectKeyID)
345					})
346				})
347			}
348		})
349	})
350
351	tbs := bb.BytesOrPanic()
352	sig, err := signX509(issuer.privateKey, sigAlg, tbs)
353	if err != nil {
354		panic(err)
355	}
356
357	bb = cryptobyte.NewBuilder(nil)
358	bb.AddASN1(cbasn1.SEQUENCE, func(cert *cryptobyte.Builder) {
359		cert.AddBytes(tbs)
360		cert.AddValue(sigAlg)
361		cert.AddASN1BitString(sig)
362	})
363	cert := bb.BytesOrPanic()
364
365	ret := x509ChainBuilderFromInfo(subject)
366	ret.rootCert = issuer.rootCert
367	ret.rootPath = issuer.rootPath
368	ret.chain = make([][]byte, len(issuer.chain)+1)
369	copy(ret.chain, issuer.chain)
370	ret.chain[len(ret.chain)-1] = cert
371	return ret
372}
373
374func (b *X509ChainBuilder) ToCredential() Credential {
375	return Credential{
376		Certificate:     b.chain,
377		ChainPath:       writeTempCertFile(b.chain),
378		PrivateKey:      b.privateKey,
379		KeyPath:         writeTempKeyFile(b.privateKey),
380		RootCertificate: b.rootCert,
381		RootPath:        b.rootPath,
382	}
383}
384
385func writeTempCertFile(certs [][]byte) string {
386	f, err := os.CreateTemp(tmpDir, "test-cert")
387	if err != nil {
388		panic(fmt.Sprintf("failed to create temp file: %s", err))
389	}
390	for _, cert := range certs {
391		if err := pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: cert}); err != nil {
392			panic(fmt.Sprintf("failed to write test certificate: %s", err))
393		}
394	}
395	tmpCertPath := f.Name()
396	if err := f.Close(); err != nil {
397		panic(fmt.Sprintf("failed to close test certificate temp file: %s", err))
398	}
399	return tmpCertPath
400}
401
402func writeTempKeyFile(privKey crypto.PrivateKey) string {
403	f, err := os.CreateTemp(tmpDir, "test-key")
404	if err != nil {
405		panic(fmt.Sprintf("failed to create temp file: %s", err))
406	}
407	keyDER, err := x509.MarshalPKCS8PrivateKey(privKey)
408	if err != nil {
409		panic(fmt.Sprintf("failed to marshal test key: %s", err))
410	}
411	if err := pem.Encode(f, &pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}); err != nil {
412		panic(fmt.Sprintf("failed to write test key: %s", err))
413	}
414	tmpKeyPath := f.Name()
415	if err := f.Close(); err != nil {
416		panic(fmt.Sprintf("failed to close test key temp file: %s", err))
417	}
418	return tmpKeyPath
419}
420