blob: f3f84622528fdc77956fd576181b7dd6e5a411a4 [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 "golang.org/x/crypto/cryptobyte"
func trustAnchorListFlagValue(ids ...[]byte) string {
b := cryptobyte.NewBuilder(nil)
for _, id := range ids {
addUint8LengthPrefixedBytes(b, id)
}
return base64FlagValue(b.BytesOrPanic())
}
func addTrustAnchorTests() {
id1 := []byte{1}
id2 := []byte{2, 2}
id3 := []byte{3, 3, 3}
id4 := []byte{4, 4, 4, 4}
// Unsolicited trust_anchors extensions should be rejected.
testCases = append(testCases, testCase{
name: "TrustAnchors-Unsolicited-Certificate",
config: Config{
MinVersion: VersionTLS13,
Bugs: ProtocolBugs{
AlwaysMatchTrustAnchorID: true,
},
},
shouldFail: true,
expectedLocalError: "remote error: unsupported extension",
expectedError: ":UNEXPECTED_EXTENSION:",
})
testCases = append(testCases, testCase{
name: "TrustAnchors-Unsolicited-EncryptedExtensions",
config: Config{
MinVersion: VersionTLS13,
AvailableTrustAnchors: [][]byte{id1, id2},
Bugs: ProtocolBugs{
AlwaysSendAvailableTrustAnchors: true,
},
},
shouldFail: true,
expectedLocalError: "remote error: unsupported extension",
expectedError: ":UNEXPECTED_EXTENSION:",
})
// Test that the client sends trust anchors when configured, and correctly
// reports the server's response.
testCases = append(testCases, testCase{
name: "TrustAnchors-ClientRequest-Match",
config: Config{
MinVersion: VersionTLS13,
AvailableTrustAnchors: [][]byte{id1, id2},
Credential: rsaChainCertificate.WithTrustAnchorID(id1),
Bugs: ProtocolBugs{
ExpectPeerRequestedTrustAnchors: [][]byte{id1, id3},
},
},
flags: []string{
"-requested-trust-anchors", trustAnchorListFlagValue(id1, id3),
"-expect-peer-match-trust-anchor",
"-expect-peer-available-trust-anchors", trustAnchorListFlagValue(id1, id2),
},
})
// The client should not like it if the server indicates the match with a non-empty
// extension.
testCases = append(testCases, testCase{
name: "TrustAnchors-ClientRequest-Match-Non-Empty-Extension",
config: Config{
MinVersion: VersionTLS13,
AvailableTrustAnchors: [][]byte{id1, id2},
Credential: rsaChainCertificate.WithTrustAnchorID(id1),
Bugs: ProtocolBugs{
SendNonEmptyTrustAnchorMatch: true,
ExpectPeerRequestedTrustAnchors: [][]byte{id1, id3},
},
},
flags: []string{
"-requested-trust-anchors", trustAnchorListFlagValue(id1, id3),
},
shouldFail: true,
expectedLocalError: "remote error: error decoding message",
expectedError: ":ERROR_PARSING_EXTENSION:",
})
// The client should not like it if the server indicates the match on the incorrect
// certificate in the Certificate message.
testCases = append(testCases, testCase{
name: "TrustAnchors-ClientRequest-Match-On-Incorrect-Certificate",
config: Config{
MinVersion: VersionTLS13,
AvailableTrustAnchors: [][]byte{id1, id2},
Credential: rsaChainCertificate.WithTrustAnchorID(id1),
Bugs: ProtocolBugs{
SendTrustAnchorWrongCertificate: true,
ExpectPeerRequestedTrustAnchors: [][]byte{id1, id3},
},
},
flags: []string{
"-requested-trust-anchors", trustAnchorListFlagValue(id1, id3),
},
shouldFail: true,
expectedLocalError: "remote error: unsupported extension",
expectedError: ":UNEXPECTED_EXTENSION:",
})
testCases = append(testCases, testCase{
name: "TrustAnchors-ClientRequest-NoMatch",
config: Config{
MinVersion: VersionTLS13,
AvailableTrustAnchors: [][]byte{id1, id2},
Bugs: ProtocolBugs{
ExpectPeerRequestedTrustAnchors: [][]byte{id3},
},
},
flags: []string{
"-requested-trust-anchors", trustAnchorListFlagValue(id3),
"-expect-no-peer-match-trust-anchor",
"-expect-peer-available-trust-anchors", trustAnchorListFlagValue(id1, id2),
},
})
// An empty trust anchor ID is a syntax error, so most be rejected in both
// ClientHello and EncryptedExtensions.
testCases = append(testCases, testCase{
testType: serverTest,
name: "TrustAnchors-EmptyID-ClientHello",
config: Config{
MinVersion: VersionTLS13,
RequestTrustAnchors: [][]byte{{}},
},
shouldFail: true,
expectedError: ":DECODE_ERROR:",
})
testCases = append(testCases, testCase{
name: "TrustAnchors-EmptyID-EncryptedExtensions",
config: Config{
MinVersion: VersionTLS13,
AvailableTrustAnchors: [][]byte{{}},
},
flags: []string{"-requested-trust-anchors", trustAnchorListFlagValue(id1)},
shouldFail: true,
expectedError: ":DECODE_ERROR:",
})
// Test the server selection logic, as well as whether it correctly reports
// available trust anchors and the match status. (The general selection flow
// is covered in addCertificateSelectionTests.)
testCases = append(testCases, testCase{
testType: serverTest,
name: "TrustAnchors-ServerSelect-Match",
config: Config{
MinVersion: VersionTLS13,
RequestTrustAnchors: [][]byte{id2},
Bugs: ProtocolBugs{
ExpectPeerAvailableTrustAnchors: [][]byte{id1, id2},
ExpectPeerMatchTrustAnchor: ptrTo(true),
},
},
shimCredentials: []*Credential{
rsaCertificate.WithTrustAnchorID(id1),
rsaCertificate.WithTrustAnchorID(id2),
},
flags: []string{"-expect-selected-credential", "1"},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "TrustAnchors-ServerSelect-None",
config: Config{
MinVersion: VersionTLS13,
RequestTrustAnchors: [][]byte{id1},
},
shimCredentials: []*Credential{
rsaCertificate.WithTrustAnchorID(id2),
rsaCertificate.WithTrustAnchorID(id3),
},
shouldFail: true,
expectedError: ":NO_MATCHING_ISSUER:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "TrustAnchors-ServerSelect-Fallback",
config: Config{
MinVersion: VersionTLS13,
RequestTrustAnchors: [][]byte{id1},
Bugs: ProtocolBugs{
ExpectPeerAvailableTrustAnchors: [][]byte{id2, id3},
ExpectPeerMatchTrustAnchor: ptrTo(false),
},
},
shimCredentials: []*Credential{
rsaCertificate.WithTrustAnchorID(id2),
rsaCertificate.WithTrustAnchorID(id3),
&rsaCertificate,
},
flags: []string{"-expect-selected-credential", "2"},
})
// When the server sends available trust anchors, it should filter the list
// by whether the credential would be usable with the connection at all.
p256DC := createDelegatedCredential(&rsaCertificate, delegatedCredentialConfig{
dcAlgo: signatureECDSAWithP256AndSHA256,
algo: signatureRSAPSSWithSHA256,
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "TrustAnchors-Server-FilterAvailable",
config: Config{
MinVersion: VersionTLS13,
RequestTrustAnchors: [][]byte{id1},
VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256, signatureECDSAWithP256AndSHA256},
Bugs: ProtocolBugs{
ExpectPeerAvailableTrustAnchors: [][]byte{id2},
ExpectPeerMatchTrustAnchor: ptrTo(false),
},
},
shimCredentials: []*Credential{
rsaCertificate.WithTrustAnchorID(id2),
// Ineligible because of signature algorithms.
rsaCertificate.WithTrustAnchorID(id3).WithSignatureAlgorithms(signatureRSAPSSWithSHA384),
// Ineligible because of credential type.
p256DC.WithTrustAnchorID(id4),
&rsaCertificate,
},
flags: []string{"-expect-selected-credential", "3"},
})
// After filtering, the list of available trust anchors may even be empty.
testCases = append(testCases, testCase{
testType: serverTest,
name: "TrustAnchors-Server-FilterAvailable-Empty",
config: Config{
MinVersion: VersionTLS13,
RequestTrustAnchors: [][]byte{id1},
VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256, signatureECDSAWithP256AndSHA256},
Bugs: ProtocolBugs{
ExpectPeerAvailableTrustAnchors: [][]byte{},
ExpectPeerMatchTrustAnchor: ptrTo(false),
},
},
shimCredentials: []*Credential{
// Ineligible because of signature algorithms.
rsaCertificate.WithTrustAnchorID(id2).WithSignatureAlgorithms(signatureRSAPSSWithSHA384),
// Ineligible because of credential type.
p256DC.WithTrustAnchorID(id4),
&rsaCertificate,
},
flags: []string{"-expect-selected-credential", "2"},
})
// The ClientHello list may be empty. The client must be able to send it and
// receive available trust anchors.
testCases = append(testCases, testCase{
name: "TrustAnchors-ClientRequestEmpty",
config: Config{
MinVersion: VersionTLS13,
AvailableTrustAnchors: [][]byte{id1, id2},
Bugs: ProtocolBugs{
ExpectPeerRequestedTrustAnchors: [][]byte{},
},
},
flags: []string{
"-requested-trust-anchors", trustAnchorListFlagValue(),
"-expect-peer-available-trust-anchors", trustAnchorListFlagValue(id1, id2),
},
})
// The server must be able to process it, and send available trust anchors.
testCases = append(testCases, testCase{
testType: serverTest,
name: "TrustAnchors-ServerReceiveEmptyRequest",
config: Config{
MinVersion: VersionTLS13,
RequestTrustAnchors: [][]byte{},
Bugs: ProtocolBugs{
ExpectPeerAvailableTrustAnchors: [][]byte{id1, id2},
ExpectPeerMatchTrustAnchor: ptrTo(false),
},
},
shimCredentials: []*Credential{
rsaCertificate.WithTrustAnchorID(id1),
rsaCertificate.WithTrustAnchorID(id2),
&rsaCertificate,
},
flags: []string{"-expect-selected-credential", "2"},
})
// This extension requires TLS 1.3. If a server receives this and negotiates
// TLS 1.2, it should ignore the extension and not accidentally send
// something in ServerHello (implicitly checked by runner).
testCases = append(testCases, testCase{
testType: serverTest,
name: "TrustAnchors-TLS12-Server",
config: Config{
MaxVersion: VersionTLS12,
RequestTrustAnchors: [][]byte{id1},
},
shimCredentials: []*Credential{
rsaCertificate.WithTrustAnchorID(id1),
&rsaCertificate,
},
// The first credential is skipped because the extension is ignored.
flags: []string{"-expect-selected-credential", "1"},
})
// The client should reject the extension in TLS 1.2 ServerHello.
testCases = append(testCases, testCase{
name: "TrustAnchors-TLS12-Client",
config: Config{
MaxVersion: VersionTLS12,
AvailableTrustAnchors: [][]byte{id1},
Bugs: ProtocolBugs{
AlwaysSendAvailableTrustAnchors: true,
},
},
flags: []string{"-requested-trust-anchors", trustAnchorListFlagValue(id1)},
shouldFail: true,
expectedError: ":UNEXPECTED_EXTENSION:",
expectedLocalError: "remote error: unsupported extension",
})
}