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 "golang.org/x/crypto/cryptobyte"
18
19func trustAnchorListFlagValue(ids ...[]byte) string {
20	b := cryptobyte.NewBuilder(nil)
21	for _, id := range ids {
22		addUint8LengthPrefixedBytes(b, id)
23	}
24	return base64FlagValue(b.BytesOrPanic())
25}
26
27func addTrustAnchorTests() {
28	id1 := []byte{1}
29	id2 := []byte{2, 2}
30	id3 := []byte{3, 3, 3}
31	id4 := []byte{4, 4, 4, 4}
32
33	// Unsolicited trust_anchors extensions should be rejected.
34	testCases = append(testCases, testCase{
35		name: "TrustAnchors-Unsolicited-Certificate",
36		config: Config{
37			MinVersion: VersionTLS13,
38			Bugs: ProtocolBugs{
39				AlwaysMatchTrustAnchorID: true,
40			},
41		},
42		shouldFail:         true,
43		expectedLocalError: "remote error: unsupported extension",
44		expectedError:      ":UNEXPECTED_EXTENSION:",
45	})
46	testCases = append(testCases, testCase{
47		name: "TrustAnchors-Unsolicited-EncryptedExtensions",
48		config: Config{
49			MinVersion:            VersionTLS13,
50			AvailableTrustAnchors: [][]byte{id1, id2},
51			Bugs: ProtocolBugs{
52				AlwaysSendAvailableTrustAnchors: true,
53			},
54		},
55		shouldFail:         true,
56		expectedLocalError: "remote error: unsupported extension",
57		expectedError:      ":UNEXPECTED_EXTENSION:",
58	})
59
60	// Test that the client sends trust anchors when configured, and correctly
61	// reports the server's response.
62	testCases = append(testCases, testCase{
63		name: "TrustAnchors-ClientRequest-Match",
64		config: Config{
65			MinVersion:            VersionTLS13,
66			AvailableTrustAnchors: [][]byte{id1, id2},
67			Credential:            rsaChainCertificate.WithTrustAnchorID(id1),
68			Bugs: ProtocolBugs{
69				ExpectPeerRequestedTrustAnchors: [][]byte{id1, id3},
70			},
71		},
72		flags: []string{
73			"-requested-trust-anchors", trustAnchorListFlagValue(id1, id3),
74			"-expect-peer-match-trust-anchor",
75			"-expect-peer-available-trust-anchors", trustAnchorListFlagValue(id1, id2),
76		},
77	})
78	// The client should not like it if the server indicates the match with a non-empty
79	// extension.
80	testCases = append(testCases, testCase{
81		name: "TrustAnchors-ClientRequest-Match-Non-Empty-Extension",
82		config: Config{
83			MinVersion:            VersionTLS13,
84			AvailableTrustAnchors: [][]byte{id1, id2},
85			Credential:            rsaChainCertificate.WithTrustAnchorID(id1),
86			Bugs: ProtocolBugs{
87				SendNonEmptyTrustAnchorMatch:    true,
88				ExpectPeerRequestedTrustAnchors: [][]byte{id1, id3},
89			},
90		},
91		flags: []string{
92			"-requested-trust-anchors", trustAnchorListFlagValue(id1, id3),
93		},
94		shouldFail:         true,
95		expectedLocalError: "remote error: error decoding message",
96		expectedError:      ":ERROR_PARSING_EXTENSION:",
97	})
98	// The client should not like it if the server indicates the match on the incorrect
99	// certificate in the Certificate message.
100	testCases = append(testCases, testCase{
101		name: "TrustAnchors-ClientRequest-Match-On-Incorrect-Certificate",
102		config: Config{
103			MinVersion:            VersionTLS13,
104			AvailableTrustAnchors: [][]byte{id1, id2},
105			Credential:            rsaChainCertificate.WithTrustAnchorID(id1),
106			Bugs: ProtocolBugs{
107				SendTrustAnchorWrongCertificate: true,
108				ExpectPeerRequestedTrustAnchors: [][]byte{id1, id3},
109			},
110		},
111		flags: []string{
112			"-requested-trust-anchors", trustAnchorListFlagValue(id1, id3),
113		},
114		shouldFail:         true,
115		expectedLocalError: "remote error: unsupported extension",
116		expectedError:      ":UNEXPECTED_EXTENSION:",
117	})
118	testCases = append(testCases, testCase{
119		name: "TrustAnchors-ClientRequest-NoMatch",
120		config: Config{
121			MinVersion:            VersionTLS13,
122			AvailableTrustAnchors: [][]byte{id1, id2},
123			Bugs: ProtocolBugs{
124				ExpectPeerRequestedTrustAnchors: [][]byte{id3},
125			},
126		},
127		flags: []string{
128			"-requested-trust-anchors", trustAnchorListFlagValue(id3),
129			"-expect-no-peer-match-trust-anchor",
130			"-expect-peer-available-trust-anchors", trustAnchorListFlagValue(id1, id2),
131		},
132	})
133
134	// An empty trust anchor ID is a syntax error, so most be rejected in both
135	// ClientHello and EncryptedExtensions.
136	testCases = append(testCases, testCase{
137		testType: serverTest,
138		name:     "TrustAnchors-EmptyID-ClientHello",
139		config: Config{
140			MinVersion:          VersionTLS13,
141			RequestTrustAnchors: [][]byte{{}},
142		},
143		shouldFail:    true,
144		expectedError: ":DECODE_ERROR:",
145	})
146	testCases = append(testCases, testCase{
147		name: "TrustAnchors-EmptyID-EncryptedExtensions",
148		config: Config{
149			MinVersion:            VersionTLS13,
150			AvailableTrustAnchors: [][]byte{{}},
151		},
152		flags:         []string{"-requested-trust-anchors", trustAnchorListFlagValue(id1)},
153		shouldFail:    true,
154		expectedError: ":DECODE_ERROR:",
155	})
156
157	// Test the server selection logic, as well as whether it correctly reports
158	// available trust anchors and the match status. (The general selection flow
159	// is covered in addCertificateSelectionTests.)
160	testCases = append(testCases, testCase{
161		testType: serverTest,
162		name:     "TrustAnchors-ServerSelect-Match",
163		config: Config{
164			MinVersion:          VersionTLS13,
165			RequestTrustAnchors: [][]byte{id2},
166			Bugs: ProtocolBugs{
167				ExpectPeerAvailableTrustAnchors: [][]byte{id1, id2},
168				ExpectPeerMatchTrustAnchor:      ptrTo(true),
169			},
170		},
171		shimCredentials: []*Credential{
172			rsaCertificate.WithTrustAnchorID(id1),
173			rsaCertificate.WithTrustAnchorID(id2),
174		},
175		flags: []string{"-expect-selected-credential", "1"},
176	})
177	testCases = append(testCases, testCase{
178		testType: serverTest,
179		name:     "TrustAnchors-ServerSelect-None",
180		config: Config{
181			MinVersion:          VersionTLS13,
182			RequestTrustAnchors: [][]byte{id1},
183		},
184		shimCredentials: []*Credential{
185			rsaCertificate.WithTrustAnchorID(id2),
186			rsaCertificate.WithTrustAnchorID(id3),
187		},
188		shouldFail:    true,
189		expectedError: ":NO_MATCHING_ISSUER:",
190	})
191	testCases = append(testCases, testCase{
192		testType: serverTest,
193		name:     "TrustAnchors-ServerSelect-Fallback",
194		config: Config{
195			MinVersion:          VersionTLS13,
196			RequestTrustAnchors: [][]byte{id1},
197			Bugs: ProtocolBugs{
198				ExpectPeerAvailableTrustAnchors: [][]byte{id2, id3},
199				ExpectPeerMatchTrustAnchor:      ptrTo(false),
200			},
201		},
202		shimCredentials: []*Credential{
203			rsaCertificate.WithTrustAnchorID(id2),
204			rsaCertificate.WithTrustAnchorID(id3),
205			&rsaCertificate,
206		},
207		flags: []string{"-expect-selected-credential", "2"},
208	})
209
210	// When the server sends available trust anchors, it should filter the list
211	// by whether the credential would be usable with the connection at all.
212	p256DC := createDelegatedCredential(&rsaCertificate, delegatedCredentialConfig{
213		dcAlgo: signatureECDSAWithP256AndSHA256,
214		algo:   signatureRSAPSSWithSHA256,
215	})
216	testCases = append(testCases, testCase{
217		testType: serverTest,
218		name:     "TrustAnchors-Server-FilterAvailable",
219		config: Config{
220			MinVersion:                VersionTLS13,
221			RequestTrustAnchors:       [][]byte{id1},
222			VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256, signatureECDSAWithP256AndSHA256},
223			Bugs: ProtocolBugs{
224				ExpectPeerAvailableTrustAnchors: [][]byte{id2},
225				ExpectPeerMatchTrustAnchor:      ptrTo(false),
226			},
227		},
228		shimCredentials: []*Credential{
229			rsaCertificate.WithTrustAnchorID(id2),
230			// Ineligible because of signature algorithms.
231			rsaCertificate.WithTrustAnchorID(id3).WithSignatureAlgorithms(signatureRSAPSSWithSHA384),
232			// Ineligible because of credential type.
233			p256DC.WithTrustAnchorID(id4),
234			&rsaCertificate,
235		},
236		flags: []string{"-expect-selected-credential", "3"},
237	})
238
239	// After filtering, the list of available trust anchors may even be empty.
240	testCases = append(testCases, testCase{
241		testType: serverTest,
242		name:     "TrustAnchors-Server-FilterAvailable-Empty",
243		config: Config{
244			MinVersion:                VersionTLS13,
245			RequestTrustAnchors:       [][]byte{id1},
246			VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256, signatureECDSAWithP256AndSHA256},
247			Bugs: ProtocolBugs{
248				ExpectPeerAvailableTrustAnchors: [][]byte{},
249				ExpectPeerMatchTrustAnchor:      ptrTo(false),
250			},
251		},
252		shimCredentials: []*Credential{
253			// Ineligible because of signature algorithms.
254			rsaCertificate.WithTrustAnchorID(id2).WithSignatureAlgorithms(signatureRSAPSSWithSHA384),
255			// Ineligible because of credential type.
256			p256DC.WithTrustAnchorID(id4),
257			&rsaCertificate,
258		},
259		flags: []string{"-expect-selected-credential", "2"},
260	})
261
262	// The ClientHello list may be empty. The client must be able to send it and
263	// receive available trust anchors.
264	testCases = append(testCases, testCase{
265		name: "TrustAnchors-ClientRequestEmpty",
266		config: Config{
267			MinVersion:            VersionTLS13,
268			AvailableTrustAnchors: [][]byte{id1, id2},
269			Bugs: ProtocolBugs{
270				ExpectPeerRequestedTrustAnchors: [][]byte{},
271			},
272		},
273		flags: []string{
274			"-requested-trust-anchors", trustAnchorListFlagValue(),
275			"-expect-peer-available-trust-anchors", trustAnchorListFlagValue(id1, id2),
276		},
277	})
278	// The server must be able to process it, and send available trust anchors.
279	testCases = append(testCases, testCase{
280		testType: serverTest,
281		name:     "TrustAnchors-ServerReceiveEmptyRequest",
282		config: Config{
283			MinVersion:          VersionTLS13,
284			RequestTrustAnchors: [][]byte{},
285			Bugs: ProtocolBugs{
286				ExpectPeerAvailableTrustAnchors: [][]byte{id1, id2},
287				ExpectPeerMatchTrustAnchor:      ptrTo(false),
288			},
289		},
290		shimCredentials: []*Credential{
291			rsaCertificate.WithTrustAnchorID(id1),
292			rsaCertificate.WithTrustAnchorID(id2),
293			&rsaCertificate,
294		},
295		flags: []string{"-expect-selected-credential", "2"},
296	})
297
298	// This extension requires TLS 1.3. If a server receives this and negotiates
299	// TLS 1.2, it should ignore the extension and not accidentally send
300	// something in ServerHello (implicitly checked by runner).
301	testCases = append(testCases, testCase{
302		testType: serverTest,
303		name:     "TrustAnchors-TLS12-Server",
304		config: Config{
305			MaxVersion:          VersionTLS12,
306			RequestTrustAnchors: [][]byte{id1},
307		},
308		shimCredentials: []*Credential{
309			rsaCertificate.WithTrustAnchorID(id1),
310			&rsaCertificate,
311		},
312		// The first credential is skipped because the extension is ignored.
313		flags: []string{"-expect-selected-credential", "1"},
314	})
315	// The client should reject the extension in TLS 1.2 ServerHello.
316	testCases = append(testCases, testCase{
317		name: "TrustAnchors-TLS12-Client",
318		config: Config{
319			MaxVersion:            VersionTLS12,
320			AvailableTrustAnchors: [][]byte{id1},
321			Bugs: ProtocolBugs{
322				AlwaysSendAvailableTrustAnchors: true,
323			},
324		},
325		flags:              []string{"-requested-trust-anchors", trustAnchorListFlagValue(id1)},
326		shouldFail:         true,
327		expectedError:      ":UNEXPECTED_EXTENSION:",
328		expectedLocalError: "remote error: unsupported extension",
329	})
330}
331