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