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