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