| // 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", |
| }) |
| } |