Source file
src/crypto/x509/x509limbo_test.go
1
2
3
4
5 package x509
6
7 import (
8 "crypto/internal/cryptotest"
9 "crypto/internal/cryptotest/x509limbo"
10 "encoding/json"
11 "encoding/pem"
12 "flag"
13 "fmt"
14 "internal/testenv"
15 "os"
16 "path/filepath"
17 "slices"
18 "strings"
19 "testing"
20 "time"
21 )
22
23 var limboCases = flag.String("limbo_cases", "", "comma-separated limbo case ids to run; if empty, all cases run")
24
25
26
27 var allowedUnexpectedVerifications = map[string]string{
28
29 "rfc5280::san::noncritical-with-empty-subject": "TODO(#79741)",
30 "webpki::san::san-critical-with-nonempty-subject": "TODO(#79741)",
31 "rfc5280::nc::not-allowed-in-ee-noncritical": "TODO(#79742)",
32 "rfc5280::nc::not-allowed-in-ee-critical": "TODO(#79742)",
33 "rfc5280::eku::ee-eku-empty": "TODO(#79743)",
34 "rfc5280::ca-empty-subject": "TODO(#79744)",
35
36
37
38 "rfc5280::san::underscore-dns": "TODO(#75835)",
39
40
41 "webpki::forbidden-dsa-leaf": "Go doesn't enforce CABF key strength policies",
42 "webpki::forbidden-weak-rsa-key-in-root": "Go doesn't enforce CABF key strength policies",
43 "webpki::forbidden-weak-rsa-in-leaf": "Go doesn't enforce CABF key strength policies",
44 "webpki::forbidden-rsa-not-divisible-by-8-in-root": "Go doesn't enforce CABF key strength policies",
45 "webpki::forbidden-rsa-key-not-divisible-by-8-in-leaf": "Go doesn't enforce CABF key strength policies",
46
47
48
49 "webpki::san::public-suffix-wildcard-san": "Go doesn't include the PSL in its stdlib",
50 "webpki::san::public-suffix-multi-label-wildcard-san": "Go doesn't include the PSL in its stdlib",
51 "webpki::san::public-suffix-private-namespace-wildcard-san": "Go doesn't include the PSL in its stdlib",
52
53
54
55 "rfc5280::root-non-critical-basic-constraints": "Go only considers BC on intermediates",
56
57
58 "rfc5280::root-inconsistent-ca-extensions": "Go ignores KU, only considers BC on intermediates",
59 "rfc5280::leaf-ku-keycertsign": "Go ignores KU, only considers BC on intermediates",
60
61
62 "rfc9881::ml-dsa-44-key-encipherment": "Go ignores KU",
63 "rfc9881::ml-dsa-44-key-agreement": "Go ignores KU",
64
65
66
67
68 "webpki::ee-basicconstraints-ca": "Go ignores KU",
69 "webpki::ca-as-leaf": "Go ignores KU",
70
71
72
73 "rfc5280::nc::invalid-dnsname-leading-period": "Go accepts leading period",
74
75
76
77 "rfc5280::aki::cross-signed-root-missing-aki": "Go only uses AKI for ordering hint, not a verification requirement",
78 "rfc5280::aki::leaf-missing-aki": "Go only uses AKI for ordering hint, not a verification requirement",
79 "webpki::aki::root-with-aki-missing-keyidentifier": "Go does not enforce CABF requirement that root AKI contain a keyIdentifier field",
80 "webpki::aki::root-with-aki-authoritycertissuer": "Go does not enforce CABF prohibition on authorityCertIssuer in root AKI",
81 "webpki::aki::root-with-aki-authoritycertserialnumber": "Go does not enforce CABF prohibition on authorityCertSerialNumber in root AKI",
82 "webpki::aki::root-with-aki-all-fields": "Go does not enforce CABF restrictions on AKI field composition in roots",
83 "webpki::aki::root-with-aki-ski-mismatch": "Go does not enforce CABF requirement that a self-signed root's AKI keyIdentifier match its SKI",
84
85
86
87
88 "webpki::eku::ee-critical-eku": "Go doesn't reject this extension when marked critical",
89 "rfc5280::nc::permitted-dns-match-noncritical": "Go doesn't require this extension to be critical",
90 "rfc5280::pc::ica-noncritical-pc": "Go doesn't require this extension to be critical",
91
92
93
94
95 "rfc5280::serial::too-long": "Causes significant breakage of real-world private PKIs",
96 "rfc5280::serial::zero": "RFC 5280 says certificate users SHOULD gracefully handle zero",
97
98
99
100 "rfc5280::ski::root-missing-ski": "would break various trusted Verisign roots",
101 "rfc5280::ski::intermediate-missing-ski": "would break various trusted intermediates",
102 "rfc5280::aki::intermediate-missing-aki": "would break real world certificates",
103
104
105
106
107 "webpki::eku::ee-anyeku": "Go treats anyExtendedKeyUsage as overriding any other key usage.",
108 "webpki::eku::ee-without-eku": "Go skips certs with no EKU when checking chain usage.",
109 "webpki::eku::root-has-eku": "Go allows a root to have an EKU as a downward constraint",
110
111
112
113
114
115
116
117 "pathological::nc-dos-1": "standards compliant; upstream rejects due to quadratic DoS limit",
118 "pathological::nc-dos-2": "standards compliant; upstream rejects due to quadratic DoS limit",
119
120
121
122
123
124
125 "webpki::cn::case-mismatch": "Go ignores legacy CN",
126 "webpki::cn::ipv4-hex-mismatch": "Go ignores legacy CN",
127 "webpki::cn::ipv4-leading-zeros-mismatch": "Go ignores legacy CN",
128 "webpki::cn::ipv6-non-rfc5952-mismatch": "Go ignores legacy CN",
129 "webpki::cn::ipv6-uncompressed-mismatch": "Go ignores legacy CN",
130 "webpki::cn::ipv6-uppercase-mismatch": "Go ignores legacy CN",
131 "webpki::cn::not-in-san": "Go ignores legacy CN",
132 "webpki::cn::punycode-not-in-san": "Go ignores legacy CN",
133 "webpki::cn::utf8-vs-punycode-mismatch": "Go ignores legacy CN",
134 }
135
136
137
138 var allowedUnexpectedFailures = map[string]string{
139
140
141 "rfc5280::nc::permitted-self-issued": "TODO(#79746)",
142
143
144
145
146
147
148 "pathlen::self-issued-certs-pathlen": "Go prefers a stricter pathen implementation",
149
150
151
152
153
154 "rfc5280::nc::nc-forbids-othername-noop": "Go rejects critical NC with GeneralName types it doesn't implement",
155
156
157
158
159 "rfc5280::validity::notafter-fractional": "Go uses instantaneous time comparisons",
160 }
161
162 var extKeyUsagesMap = map[x509limbo.KnownEKUs]ExtKeyUsage{
163 x509limbo.KnownEKUsAnyExtendedKeyUsage: ExtKeyUsageAny,
164 x509limbo.KnownEKUsClientAuth: ExtKeyUsageClientAuth,
165 x509limbo.KnownEKUsCodeSigning: ExtKeyUsageCodeSigning,
166 x509limbo.KnownEKUsEmailProtection: ExtKeyUsageEmailProtection,
167 x509limbo.KnownEKUsOCSPSigning: ExtKeyUsageOCSPSigning,
168 x509limbo.KnownEKUsServerAuth: ExtKeyUsageServerAuth,
169 x509limbo.KnownEKUsTimeStamping: ExtKeyUsageTimeStamping,
170 }
171
172
173 func TestX509Limbo(t *testing.T) {
174 testenv.SkipIfShortAndSlow(t)
175
176 limboDir := cryptotest.FetchModule(t, x509limbo.X509LimboModule, x509limbo.X509LimboVersion)
177
178 limboJson, err := os.ReadFile(filepath.Join(limboDir, "limbo.json"))
179 if err != nil {
180 t.Fatalf("error reading limbo.json: %v", err)
181 }
182
183 var limbo x509limbo.Limbo
184 if err := json.Unmarshal(limboJson, &limbo); err != nil {
185 t.Fatalf("failed to unmarshal limbo.json: %v", err)
186 }
187
188 for _, tc := range limbo.Testcases {
189 t.Run(tc.Id, func(t *testing.T) {
190 t.Parallel()
191
192 if *limboCases != "" && !slices.Contains(strings.Split(*limboCases, ","), tc.Id) {
193 t.Skip("filtered out by -limbo_cases")
194 }
195
196 if slices.Contains(tc.Features, x509limbo.FeatureHasCrl) {
197 t.Skipf("CRL revocation checking not supported")
198 }
199
200 if slices.Contains(tc.Features, x509limbo.FeatureMaxChainDepth) {
201 t.Skipf("customizable max chain depth not supported")
202 }
203
204 if slices.Contains(tc.Features, x509limbo.FeatureNameConstraintDn) {
205 t.Skipf("name constraints for DirectoryNames are not supported")
206 }
207
208 if len(tc.SignatureAlgorithms) != 0 {
209
210
211 t.Skipf("signature algorithms are not customizable through the x509 interface")
212 }
213
214 if len(tc.KeyUsage) != 0 &&
215 !slices.Contains(tc.KeyUsage, x509limbo.KeyUsageDigitalSignature) {
216
217
218 t.Skipf("key usage checks other than Digital Signature are not supported")
219 }
220
221
222
223 var verifyDnsName string
224 if tc.ExpectedPeerName != nil && tc.ValidationKind == x509limbo.ValidationKindSERVER {
225 switch tc.ExpectedPeerName.Kind {
226 case x509limbo.PeerKindDNS:
227 verifyDnsName = tc.ExpectedPeerName.Value
228 case x509limbo.PeerKindIP:
229 verifyDnsName = fmt.Sprintf("[%s]", tc.ExpectedPeerName.Value)
230 default:
231 t.Skipf("unsupported peer name kind: %v", tc.ExpectedPeerName.Kind)
232 }
233 }
234
235 roots, intermediates := NewCertPool(), NewCertPool()
236 for _, rootPem := range tc.TrustedCerts {
237 roots.AppendCertsFromPEM([]byte(rootPem))
238 }
239 for _, intermediatePem := range tc.UntrustedIntermediates {
240 intermediates.AppendCertsFromPEM([]byte(intermediatePem))
241 }
242
243 block, rest := pem.Decode([]byte(tc.PeerCertificate))
244 if block == nil {
245 t.Fatalf("unable to PEM decode peer certificate")
246 } else if block.Type != "CERTIFICATE" {
247 t.Fatalf("unexpected data, expected cert: %+#v", *block)
248 } else if len(rest) > 0 {
249 t.Fatalf("peer certificate has %d trailing bytes", len(rest))
250 }
251
252 peer, parseErr := ParseCertificate(block.Bytes)
253 if parseErr != nil {
254 if tc.ExpectedResult == x509limbo.ExpectedResultFAILURE {
255
256
257 return
258 }
259 printChainDetails(t, tc, parseErr)
260 t.Errorf("expected success, parsing peer certificate failed: %v", parseErr)
261 return
262 }
263
264 validationTime := time.Now()
265 if tc.ValidationTime != nil {
266 vtStr, ok := tc.ValidationTime.(string)
267 if !ok {
268 t.Fatalf("validation time is not a string: %T %v", tc.ValidationTime, tc.ValidationTime)
269 }
270 parsed, err := time.Parse(time.RFC3339, vtStr)
271 if err != nil {
272 t.Fatalf("invalid validation time %q: %v", vtStr, err)
273 }
274 validationTime = parsed
275 }
276
277 var ekus []ExtKeyUsage
278 for _, elem := range tc.ExtendedKeyUsage {
279 eku, ok := extKeyUsagesMap[elem]
280 if !ok {
281 t.Skipf("unsupported extended key usage: %v", elem)
282 }
283 ekus = append(ekus, eku)
284 }
285
286 _, err := peer.Verify(VerifyOptions{
287 DNSName: verifyDnsName,
288 Intermediates: intermediates,
289 Roots: roots,
290 CurrentTime: validationTime,
291 KeyUsages: ekus,
292 })
293 if err == nil && tc.ExpectedResult == x509limbo.ExpectedResultFAILURE {
294 if _, allowed := allowedUnexpectedVerifications[tc.Id]; !allowed {
295 printChainDetails(t, tc, nil)
296 t.Errorf("expected failure, built chain without error")
297 }
298 } else if err != nil && tc.ExpectedResult == x509limbo.ExpectedResultSUCCESS {
299 if _, allowed := allowedUnexpectedFailures[tc.Id]; !allowed {
300 printChainDetails(t, tc, err)
301 t.Errorf("expected success, built chain with error: %v", err)
302 }
303 }
304
305
306
307
308 if tc.ValidationKind == x509limbo.ValidationKindCLIENT {
309 for _, name := range tc.ExpectedPeerNames {
310 if name.Kind != x509limbo.PeerKindIP && name.Kind != x509limbo.PeerKindDNS {
311
312 t.Skipf("unsupported peer name kind: %v", name.Kind)
313 }
314 err = peer.VerifyHostname(name.Value)
315
316
317
318 if err == nil && tc.ExpectedResult == x509limbo.ExpectedResultFAILURE {
319 printChainDetails(t, tc, nil)
320 t.Errorf("expected failure, built chain without error")
321 } else if err != nil && tc.ExpectedResult == x509limbo.ExpectedResultSUCCESS {
322 printChainDetails(t, tc, err)
323 t.Errorf("expected success, built chain with error: %v", err)
324 }
325 }
326 }
327 })
328 }
329 }
330
331 func printChainDetails(t *testing.T, tc x509limbo.Testcase, actualResult error) {
332 t.Log("----")
333 t.Logf("testcase: %q expected result: %v actual result: %v", tc.Id, tc.ExpectedResult, actualResult)
334 t.Log("trust anchor PEM:")
335 for _, root := range tc.TrustedCerts {
336 t.Log(root)
337 }
338 t.Log("intermediates PEM:")
339 for _, intermediate := range tc.UntrustedIntermediates {
340 t.Log(intermediate)
341 }
342 t.Log("end entity PEM:")
343 t.Log(tc.PeerCertificate)
344 t.Log("----")
345 }
346
View as plain text