blob: 038ad0e4c7bd7dc60ce633d3d6cb6084e9494349 [file] [log] [blame]
// 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/elliptic"
"crypto/rand"
"crypto/x509"
"fmt"
"time"
"golang.org/x/crypto/cryptobyte"
)
// delegatedCredentialConfig specifies the shape of a delegated credential, not
// including the keys themselves.
type delegatedCredentialConfig struct {
// lifetime is the amount of time, from the notBefore of the parent
// certificate, that the delegated credential is valid for. If zero, then 24
// hours is assumed.
lifetime time.Duration
// dcAlgo is the signature scheme that should be used with this delegated
// credential. If zero, ECDSA with P-256 is assumed.
dcAlgo signatureAlgorithm
// algo is the signature algorithm that the delegated credential itself is
// signed with. Cannot be zero.
algo signatureAlgorithm
}
func createDelegatedCredential(parent *Credential, config delegatedCredentialConfig) *Credential {
if parent.Type != CredentialTypeX509 {
panic("delegated credentials must be issued by X.509 credentials")
}
dcAlgo := config.dcAlgo
if dcAlgo == 0 {
dcAlgo = signatureECDSAWithP256AndSHA256
}
var dcPriv crypto.Signer
switch dcAlgo {
case signatureRSAPKCS1WithMD5, signatureRSAPKCS1WithSHA1, signatureRSAPKCS1WithSHA256, signatureRSAPKCS1WithSHA384, signatureRSAPKCS1WithSHA512, signatureRSAPSSWithSHA256, signatureRSAPSSWithSHA384, signatureRSAPSSWithSHA512:
dcPriv = &rsa2048Key
case signatureECDSAWithSHA1, signatureECDSAWithP256AndSHA256, signatureECDSAWithP384AndSHA384, signatureECDSAWithP521AndSHA512:
var curve elliptic.Curve
switch dcAlgo {
case signatureECDSAWithSHA1, signatureECDSAWithP256AndSHA256:
curve = elliptic.P256()
case signatureECDSAWithP384AndSHA384:
curve = elliptic.P384()
case signatureECDSAWithP521AndSHA512:
curve = elliptic.P521()
default:
panic("internal error")
}
priv, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
panic(err)
}
dcPriv = priv
default:
panic(fmt.Errorf("unsupported DC signature algorithm: %x", dcAlgo))
}
lifetime := config.lifetime
if lifetime == 0 {
lifetime = 24 * time.Hour
}
lifetimeSecs := int64(lifetime.Seconds())
if lifetimeSecs < 0 || lifetimeSecs > 1<<32 {
panic(fmt.Errorf("lifetime %s is too long to be expressed", lifetime))
}
// https://www.rfc-editor.org/rfc/rfc9345.html#section-4
dc := cryptobyte.NewBuilder(nil)
dc.AddUint32(uint32(lifetimeSecs))
dc.AddUint16(uint16(dcAlgo))
pubBytes, err := x509.MarshalPKIXPublicKey(dcPriv.Public())
if err != nil {
panic(err)
}
addUint24LengthPrefixedBytes(dc, pubBytes)
var dummyConfig Config
msg := delegatedCredentialSignedMessage(dc.BytesOrPanic(), config.algo, parent.Certificate[0])
parentSignature, err := signMessage(false /* server */, VersionTLS13, parent.PrivateKey, &dummyConfig, config.algo, msg)
if err != nil {
panic(err)
}
dc.AddUint16(uint16(config.algo))
addUint16LengthPrefixedBytes(dc, parentSignature)
dcCred := *parent
dcCred.Type = CredentialTypeDelegated
dcCred.DelegatedCredential = dc.BytesOrPanic()
dcCred.PrivateKey = dcPriv
dcCred.KeyPath = writeTempKeyFile(dcPriv)
return &dcCred
}
func addDelegatedCredentialTests() {
p256DC := createDelegatedCredential(&rsaCertificate, delegatedCredentialConfig{
dcAlgo: signatureECDSAWithP256AndSHA256,
algo: signatureRSAPSSWithSHA256,
})
p256DCFromECDSA := createDelegatedCredential(&ecdsaP256Certificate, delegatedCredentialConfig{
dcAlgo: signatureECDSAWithP256AndSHA256,
algo: signatureECDSAWithP256AndSHA256,
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "DelegatedCredentials-NoClientSupport",
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
},
shimCredentials: []*Credential{p256DC, &rsaCertificate},
flags: []string{"-expect-selected-credential", "1"},
expectations: connectionExpectations{
peerCertificate: &rsaCertificate,
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "DelegatedCredentials-Basic",
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
},
shimCredentials: []*Credential{p256DC, &rsaCertificate},
flags: []string{"-expect-selected-credential", "0"},
expectations: connectionExpectations{
peerCertificate: p256DC,
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "DelegatedCredentials-ExactAlgorithmMatch",
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
// Test that the server doesn't mix up the two signature algorithm
// fields. These options are a match because the signature_algorithms
// extension matches against the signature on the delegated
// credential, while the delegated_credential extension matches
// against the signature made by the delegated credential.
VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
},
shimCredentials: []*Credential{p256DC, &rsaCertificate},
flags: []string{"-expect-selected-credential", "0"},
expectations: connectionExpectations{
peerCertificate: p256DC,
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "DelegatedCredentials-SigAlgoMissing",
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
// If the client doesn't support the signature in the delegated credential,
// the server should not use delegated credentials.
VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA384},
DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
},
shimCredentials: []*Credential{p256DC, &rsaCertificate},
flags: []string{"-expect-selected-credential", "1"},
expectations: connectionExpectations{
peerCertificate: &rsaCertificate,
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "DelegatedCredentials-CertVerifySigAlgoMissing",
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
// If the client doesn't support the delegated credential's
// CertificateVerify algorithm, the server should not use delegated
// credentials.
VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP384AndSHA384},
},
shimCredentials: []*Credential{p256DC, &rsaCertificate},
flags: []string{"-expect-selected-credential", "1"},
expectations: connectionExpectations{
peerCertificate: &rsaCertificate,
},
})
// Delegated credentials are not supported at TLS 1.2, even if the client
// sends the extension.
testCases = append(testCases, testCase{
testType: serverTest,
name: "DelegatedCredentials-TLS12-Forbidden",
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS12,
DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
},
shimCredentials: []*Credential{p256DC, &rsaCertificate},
flags: []string{"-expect-selected-credential", "1"},
expectations: connectionExpectations{
peerCertificate: &rsaCertificate,
},
})
// Generate another delegated credential, so we can get the keys out of sync.
dcWrongKey := createDelegatedCredential(&rsaCertificate, delegatedCredentialConfig{
algo: signatureRSAPSSWithSHA256,
})
dcWrongKey.DelegatedCredential = p256DC.DelegatedCredential
testCases = append(testCases, testCase{
testType: serverTest,
name: "DelegatedCredentials-KeyMismatch",
// The handshake hints version of the test will, as a side effect, use a
// custom private key. Custom private keys can't be checked for key
// mismatches.
skipHints: true,
shimCredentials: []*Credential{dcWrongKey},
shouldFail: true,
expectedError: ":KEY_VALUES_MISMATCH:",
})
// RSA delegated credentials should be rejected at configuration time.
rsaDC := createDelegatedCredential(&rsaCertificate, delegatedCredentialConfig{
algo: signatureRSAPSSWithSHA256,
dcAlgo: signatureRSAPSSWithSHA256,
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "DelegatedCredentials-NoRSA",
shimCredentials: []*Credential{rsaDC},
shouldFail: true,
expectedError: ":INVALID_SIGNATURE_ALGORITHM:",
})
// If configured with multiple delegated credentials, the server can cleanly
// select the first one that works.
p384DC := createDelegatedCredential(&rsaCertificate, delegatedCredentialConfig{
dcAlgo: signatureECDSAWithP384AndSHA384,
algo: signatureRSAPSSWithSHA256,
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "DelegatedCredentials-Multiple",
config: Config{
DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP384AndSHA384},
},
shimCredentials: []*Credential{p256DC, p384DC},
flags: []string{"-expect-selected-credential", "1"},
expectations: connectionExpectations{
peerCertificate: p384DC,
},
})
// Delegated credentials participate in issuer-based certificate selection.
testCases = append(testCases, testCase{
testType: serverTest,
name: "DelegatedCredentials-MatchIssuer",
config: Config{
DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
// The client requested p256DCFromECDSA's issuer.
RootCAs: makeCertPoolFromRoots(p256DCFromECDSA),
SendRootCAs: true,
},
shimCredentials: []*Credential{
p256DC.WithMustMatchIssuer(true), p256DCFromECDSA.WithMustMatchIssuer(true)},
flags: []string{"-expect-selected-credential", "1"},
expectations: connectionExpectations{
peerCertificate: p256DCFromECDSA,
},
})
// Delegated credentials participate in trust anchor IDs.
id1 := []byte{1, 1}
id2 := []byte{2, 2, 2}
testCases = append(testCases, testCase{
testType: serverTest,
name: "DelegatedCredentials-TrustAnchorIDs",
config: Config{
RequestTrustAnchors: [][]byte{id2},
DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
Bugs: ProtocolBugs{
ExpectPeerAvailableTrustAnchors: [][]byte{id1, id2},
},
},
shimCredentials: []*Credential{
p256DC.WithTrustAnchorID(id1), p256DCFromECDSA.WithTrustAnchorID(id2)},
flags: []string{"-expect-selected-credential", "1"},
expectations: connectionExpectations{
peerCertificate: p256DCFromECDSA,
},
})
}