1 package didx509resolver
2
3 import (
4 "crypto/sha256"
5 "crypto/sha512"
6 "crypto/x509"
7 "encoding/asn1"
8 "encoding/base64"
9 "encoding/json"
10 "encoding/pem"
11 "errors"
12 "fmt"
13 "net/url"
14 "strconv"
15 "strings"
16
17 "github.com/lestrrat-go/jwx/jwk"
18 )
19
20 func VerifyCertificateChain(chain []*x509.Certificate, trustedRoots []*x509.Certificate, ignoreTime bool) ([][]*x509.Certificate, error) {
21 roots := x509.NewCertPool()
22 for _, cert := range trustedRoots {
23 roots.AddCert(cert)
24 }
25
26 intermediates := x509.NewCertPool()
27
28 for _, c := range chain[1 : len(chain)-1] {
29 intermediates.AddCert(c)
30 }
31
32 opts := x509.VerifyOptions{
33 Roots: roots,
34 Intermediates: intermediates,
35 KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
36 CurrentTime: chain[0].NotAfter,
37 }
38
39 return chain[0].Verify(opts)
40 }
41
42 func checkFingerprint(chain []*x509.Certificate, caFingerprintAlg, caFingerprint string) error {
43
44 expectedCaFingerprints := make(map[string]struct{})
45 for _, cert := range chain[1:] {
46 var n struct{}
47 var hash []byte
48 switch caFingerprintAlg {
49 case "sha256":
50 x := sha256.Sum256(cert.Raw)
51 hash = x[:]
52 case "sha384":
53 x := sha512.Sum384(cert.Raw)
54 hash = x[:]
55 case "sha512":
56 x := sha512.Sum512(cert.Raw)
57 hash = x[:]
58 default:
59 return errors.New("unsupported hash algorithm")
60 }
61 hashStr := base64.RawURLEncoding.EncodeToString(hash)
62 expectedCaFingerprints[hashStr] = n
63 }
64
65 if _, found := expectedCaFingerprints[caFingerprint]; !found {
66 return errors.New("unexpected certificate fingerprint")
67 }
68
69 return nil
70 }
71
72 func oidFromString(s string) (*asn1.ObjectIdentifier, error) {
73 tokens := strings.Split(s, ".")
74 var ints []int
75 for _, x := range tokens {
76 i, err := strconv.Atoi(x)
77 if err != nil {
78 return nil, errors.New("invalid OID")
79 }
80 ints = append(ints, i)
81 }
82 result := asn1.ObjectIdentifier(ints)
83 return &result, nil
84 }
85
86 func checkHasSan(sanType string, value string, cert *x509.Certificate) error {
87 switch sanType {
88 case "dns":
89 for _, name := range cert.DNSNames {
90 if name == value {
91 return nil
92 }
93 }
94 case "email":
95 for _, email := range cert.EmailAddresses {
96 if email == value {
97 return nil
98 }
99 }
100 case "ipaddress":
101 for _, ip := range cert.IPAddresses {
102 if ip.String() == value {
103 return nil
104 }
105 }
106 case "uri":
107 for _, uri := range cert.URIs {
108 if uri.String() == value {
109 return nil
110 }
111 }
112 default:
113 return fmt.Errorf("unknown SAN type: %s", sanType)
114 }
115 return fmt.Errorf("SAN not found: %s", value)
116 }
117
118
119
120
121
122 var (
123 oidExtKeyUsageAny = asn1.ObjectIdentifier{2, 5, 29, 37, 0}
124 oidExtKeyUsageServerAuth = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 1}
125 oidExtKeyUsageClientAuth = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 2}
126 oidExtKeyUsageCodeSigning = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 3}
127 oidExtKeyUsageEmailProtection = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 4}
128 oidExtKeyUsageIPSECEndSystem = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 5}
129 oidExtKeyUsageIPSECTunnel = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 6}
130 oidExtKeyUsageIPSECUser = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 7}
131 oidExtKeyUsageTimeStamping = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 8}
132 oidExtKeyUsageOCSPSigning = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 9}
133 oidExtKeyUsageMicrosoftServerGatedCrypto = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 10, 3, 3}
134 oidExtKeyUsageNetscapeServerGatedCrypto = asn1.ObjectIdentifier{2, 16, 840, 1, 113730, 4, 1}
135 oidExtKeyUsageMicrosoftCommercialCodeSigning = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 2, 1, 22}
136 oidExtKeyUsageMicrosoftKernelCodeSigning = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 61, 1, 1}
137 )
138
139 var extKeyUsageOIDs = []struct {
140 extKeyUsage x509.ExtKeyUsage
141 oid asn1.ObjectIdentifier
142 }{
143 {x509.ExtKeyUsageAny, oidExtKeyUsageAny},
144 {x509.ExtKeyUsageServerAuth, oidExtKeyUsageServerAuth},
145 {x509.ExtKeyUsageClientAuth, oidExtKeyUsageClientAuth},
146 {x509.ExtKeyUsageCodeSigning, oidExtKeyUsageCodeSigning},
147 {x509.ExtKeyUsageEmailProtection, oidExtKeyUsageEmailProtection},
148 {x509.ExtKeyUsageIPSECEndSystem, oidExtKeyUsageIPSECEndSystem},
149 {x509.ExtKeyUsageIPSECTunnel, oidExtKeyUsageIPSECTunnel},
150 {x509.ExtKeyUsageIPSECUser, oidExtKeyUsageIPSECUser},
151 {x509.ExtKeyUsageTimeStamping, oidExtKeyUsageTimeStamping},
152 {x509.ExtKeyUsageOCSPSigning, oidExtKeyUsageOCSPSigning},
153 {x509.ExtKeyUsageMicrosoftServerGatedCrypto, oidExtKeyUsageMicrosoftServerGatedCrypto},
154 {x509.ExtKeyUsageNetscapeServerGatedCrypto, oidExtKeyUsageNetscapeServerGatedCrypto},
155 {x509.ExtKeyUsageMicrosoftCommercialCodeSigning, oidExtKeyUsageMicrosoftCommercialCodeSigning},
156 {x509.ExtKeyUsageMicrosoftKernelCodeSigning, oidExtKeyUsageMicrosoftKernelCodeSigning},
157 }
158
159 func OidFromExtKeyUsage(eku x509.ExtKeyUsage) (oid asn1.ObjectIdentifier, ok bool) {
160 for _, pair := range extKeyUsageOIDs {
161 if eku == pair.extKeyUsage {
162 return pair.oid, true
163 }
164 }
165 return
166 }
167
168
169
170
171
172
173
174
175
176 func verifyDid(chain []*x509.Certificate, did string) error {
177 var topTokens = strings.Split(did, "::")
178
179 if len(topTokens) <= 1 {
180 return errors.New("invalid DID string")
181 }
182
183 var pretokens = strings.Split(topTokens[0], ":")
184
185 if len(pretokens) < 5 || pretokens[0] != "did" || pretokens[1] != "x509" {
186 return errors.New("unsupported method/prefix")
187 }
188
189 if pretokens[2] != "0" {
190 return errors.New("unsupported did:x509 version")
191 }
192
193 caFingerprintAlg := pretokens[3]
194 caFingerprint := pretokens[4]
195
196 policies := topTokens[1:]
197
198 if len(chain) < 2 {
199 return errors.New("certificate chain too short")
200 }
201
202 err := checkFingerprint(chain, caFingerprintAlg, caFingerprint)
203 if err != nil {
204 return err
205 }
206
207 for _, policy := range policies {
208 parts := strings.Split(policy, ":")
209
210 if len(parts) < 2 {
211 return errors.New("invalid policy")
212 }
213
214 policyName, args := parts[0], parts[1:]
215 switch policyName {
216 case "subject":
217 if len(args) == 0 || len(args)%2 != 0 {
218 return errors.New("key-value pairs required")
219 }
220
221 if len(args) < 2 {
222 return errors.New("at least one key-value pair is required")
223 }
224
225
226
227 var seenFields []string
228 for i := 0; i < len(args); i += 2 {
229 k := strings.ToUpper(args[i])
230 v, err := url.QueryUnescape(args[i+1])
231
232 if err != nil {
233 return fmt.Errorf("urlUnescape failed: %w", err)
234 }
235
236 for _, sk := range seenFields {
237 if sk == k {
238 return fmt.Errorf("duplicate field '%s'", k)
239 }
240 }
241 seenFields = append(seenFields, k)
242
243 leaf := chain[0]
244 var fieldValues []string
245 switch k {
246 case "C":
247 fieldValues = leaf.Subject.Country
248 case "O":
249 fieldValues = leaf.Subject.Organization
250 case "OU":
251 fieldValues = leaf.Subject.OrganizationalUnit
252 case "L":
253 fieldValues = leaf.Subject.Locality
254 case "S":
255 fieldValues = leaf.Subject.Province
256 case "STREET":
257 fieldValues = leaf.Subject.StreetAddress
258 case "POSTALCODE":
259 fieldValues = leaf.Subject.PostalCode
260 case "SERIALNUMBER":
261 fieldValues = []string{leaf.Subject.SerialNumber}
262 case "CN":
263 fieldValues = []string{leaf.Subject.CommonName}
264 default:
265 for _, aav := range leaf.Subject.Names {
266 if aav.Type.String() == k {
267 fieldValues = []string{aav.Value.(string)}
268 break
269 }
270 }
271 if len(fieldValues) == 0 {
272 return fmt.Errorf("unsupported subject key: %s", k)
273 }
274 }
275 found := false
276 for _, fv := range fieldValues {
277 if fv == v {
278 found = true
279 break
280 }
281 }
282 if !found {
283 return fmt.Errorf("invalid subject value: %s=%s", k, v)
284 }
285 }
286 case "san":
287 if len(args) != 2 {
288 return fmt.Errorf("exactly one SAN type and value required")
289 }
290 sanType := args[0]
291 sanValue, err := url.QueryUnescape(args[1])
292 if err != nil {
293 return fmt.Errorf("url.QueryUnescape failed: %w", err)
294 }
295 err = checkHasSan(sanType, sanValue, chain[0])
296 if err != nil {
297 return err
298 }
299 case "eku":
300 if len(args) != 1 {
301 return errors.New("exactly one EKU required")
302 }
303
304 ekuOid, err := oidFromString(args[0])
305 if err != nil {
306 return fmt.Errorf("oidFromString failed: %w", err)
307 }
308
309 if len(chain[0].UnknownExtKeyUsage) == 0 {
310 return errors.New("no EKU extension in certificate")
311 }
312
313 foundEku := false
314 for _, certEku := range chain[0].ExtKeyUsage {
315 certEkuOid, ok := OidFromExtKeyUsage(certEku)
316 if ok && certEkuOid.Equal(*ekuOid) {
317 foundEku = true
318 break
319 }
320 }
321 for _, certEkuOid := range chain[0].UnknownExtKeyUsage {
322 if certEkuOid.Equal(*ekuOid) {
323 foundEku = true
324 break
325 }
326 }
327
328 if !foundEku {
329 return fmt.Errorf("EKU not found: %s", ekuOid)
330 }
331 case "fulcio-issuer":
332 if len(args) != 1 {
333 return errors.New("excessive arguments to fulcio-issuer")
334 }
335 decodedArg, err := url.QueryUnescape(args[0])
336 if err != nil {
337 return fmt.Errorf("urlUnescape failed: %w", err)
338 }
339 fulcioIssuer := "https://" + decodedArg
340 fulcioIssuerOid, err := oidFromString("1.3.6.1.4.1.57264.1.1")
341 if err != nil {
342 return fmt.Errorf("oidFromString failed: %w", err)
343 }
344 found := false
345 for _, ext := range chain[0].Extensions {
346 if ext.Id.Equal(*fulcioIssuerOid) {
347 if string(ext.Value) == fulcioIssuer {
348 found = true
349 break
350 }
351 }
352 }
353 if !found {
354 return fmt.Errorf("invalid fulcio-issuer: %s", fulcioIssuer)
355 }
356 default:
357 return fmt.Errorf("unsupported did:x509 policy name '%s'", policyName)
358 }
359 }
360
361 return nil
362 }
363
364 func createDidDocument(did string, chain []*x509.Certificate) (string, error) {
365 format := `
366 {
367 "@context": "https://www.w3.org/ns/did/v1",
368 "id": "%s",
369 "verificationMethod": [{
370 "id": "%[1]s#key-1",
371 "type": "JsonWebKey2020",
372 "controller": "%[1]s",
373 "publicKeyJwk": %s,
374 }]
375 %s
376 %s
377 }`
378
379 includeAssertionMethod := chain[0].KeyUsage == 0 || (chain[0].KeyUsage&x509.KeyUsageDigitalSignature) != 0
380 includeKeyAgreement := chain[0].KeyUsage == 0 || (chain[0].KeyUsage&x509.KeyUsageKeyAgreement) != 0
381
382 if !includeAssertionMethod && !includeKeyAgreement {
383 return "", errors.New("leaf certificate key usage must include digital signature or key agreement")
384 }
385
386 am := ""
387 ka := ""
388 if includeAssertionMethod {
389 am = fmt.Sprintf(",\"assertionMethod\": \"%s#key-1\"", did)
390 }
391 if includeKeyAgreement {
392 ka = fmt.Sprintf(",\"keyAgreement\": \"%s#key-1\"", did)
393 }
394
395 leaf, err := jwk.New(chain[0].PublicKey)
396 if err != nil {
397 return "", err
398 }
399 jleaf, err := json.Marshal(leaf)
400 if err != nil {
401 return "", err
402 }
403 doc := fmt.Sprintf(format, did, jleaf, am, ka)
404 return doc, nil
405 }
406
407 func parsePemChain(chainPem string) ([]*x509.Certificate, error) {
408 var chain = []*x509.Certificate{}
409
410 bs := []byte(chainPem)
411 for block, rest := pem.Decode(bs); block != nil; block, rest = pem.Decode(rest) {
412 if block.Type == "CERTIFICATE" {
413 cert, err := x509.ParseCertificate(block.Bytes)
414 if err != nil {
415 return []*x509.Certificate{}, fmt.Errorf("certificate parser failed: %w", err)
416 }
417 chain = append(chain, cert)
418 }
419 }
420
421 return chain, nil
422 }
423
424 func Resolve(chainPem string, did string, ignoreTime bool) (string, error) {
425 chain, err := parsePemChain(chainPem)
426
427 if err != nil {
428 return "", err
429 }
430
431 if len(chain) == 0 {
432 return "", errors.New("no certificate chain")
433 }
434
435
436 roots := []*x509.Certificate{chain[len(chain)-1]}
437
438 chains, err := VerifyCertificateChain(chain, roots, ignoreTime)
439
440 if err != nil {
441 return "", fmt.Errorf("certificate chain verification failed: %w", err)
442 }
443
444 for _, chain := range chains {
445 err = verifyDid(chain, did)
446 if err != nil {
447 return "", fmt.Errorf("DID verification failed: %w", err)
448 }
449 }
450
451 doc, err := createDidDocument(did, chain)
452
453 if err != nil {
454 return "", fmt.Errorf("DID document creation failed: %w", err)
455 }
456
457 return doc, nil
458 }
459
View as plain text