/
certifyKey.go
730 lines (630 loc) · 23.6 KB
/
certifyKey.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
// Licensed under the Apache-2.0 license
package verification
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/binary"
"encoding/pem"
"fmt"
"hash"
"math/big"
"reflect"
"testing"
"time"
cms "github.com/github/smimesign/ietf-cms"
"github.com/chipsalliance/caliptra-dpe/verification/client"
zx509 "github.com/zmap/zcrypto/x509"
zlint "github.com/zmap/zlint/v3"
"github.com/zmap/zlint/v3/lint"
"golang.org/x/exp/slices"
)
// TcgDiceCriticalExtensions are the OIDs of DICE extensions which must be
// marked as critical
var TcgDiceCriticalExtensions = [...]string{
OidExtensionTcgDiceMultiTcbInfo.String(),
OidExtensionTcgDiceUeid.String(),
}
// TcgDiceExtendedKeyUsages are the DICE OIDs expected to be present in the DPE
// leaf EKU extension
var TcgDiceExtendedKeyUsages = [...]string{
OidExtensionTcgDiceKpIdentityLoc.String(),
OidExtensionTcgDiceKpAttestLoc.String(),
}
// TcgUeidExtension is tcg-dice-Ueid OBJECT IDENTIFIER ::= {tcg-dice 4}
//
// TcgUeid ::== SEQUENCE {
// ueid OCTET STRING
// }
type TcgUeidExtension struct {
Ueid []uint8 `asn1:"ueid,implicit"`
}
// Fwid represents a TCG DICE FWID structure
type Fwid struct {
HashAlg asn1.ObjectIdentifier
Digest []byte
}
// DiceTcbInfo represents the following ASN.1 structures
// tcg-dice-MultiTcbInfo OBJECT IDENTIFIER ::= {tcg-dice 5}
// DiceTcbInfoSeq ::= SEQUENCE SIZE (1..MAX) OF DiceTcbInfo
//
// tcg-dice-TcbInfo OBJECT IDENTIFIER ::= {tcg-dice 1}
//
// DiceTcbInfo ::== SEQUENCE {
// vendor [0] IMPLICIT UTF8String OPTIONAL,
// model [1] IMPLICIT UTF8String OPTIONAL,
// version [2] IMPLICIT UTF8String OPTIONAL,
// svn [3] IMPLICIT INTEGER OPTIONAL,
// layer [4] IMPLICIT INTEGER OPTIONAL,
// index [5] IMPLICIT INTEGER OPTIONAL,
// fwids [6] IMPLICIT FWIDLIST OPTIONAL,
// flags [7] IMPLICIT OperationalFlags OPTIONAL,
// vendorInfo [8] IMPLICIT OCTET STRING OPTIONAL,
// type [9] IMPLICIT OCTET STRING OPTIONAL,
// }
//
// FWIDLIST ::== SEQUENCE SIZE (1..MAX) OF FWID
//
// FWID ::== SEQUENCE {
// hashAlg OBJECT IDENTIFIER,
// digest OCTET STRING
// }
//
// OperationalFlags ::= BIT STRING {
// notConfigured (0),
// notSecure (1),
// recovery (2),
// debug (3)
// }
type DiceTcbInfo struct {
Vendor string `asn1:"optional,tag:0,utf8"`
Model string `asn1:"optional,tag:1,utf8"`
Version string `asn1:"optional,tag:2,utf8"`
SVN int `asn1:"optional,tag:3"`
Layer int `asn1:"optional,tag:4"`
Index int `asn1:"optional,tag:5"`
Fwids []Fwid `asn1:"optional,tag:6"`
Flags OperationalFlag `asn1:"optional,tag:7"`
VendorInfo []byte `asn1:"optional,tag:8"`
Type []byte `asn1:"optional,tag:9"`
}
// OperationalFlag represents the TCBInfo Operational Flags field
type OperationalFlag int
// TCG spec-defined operational flags
const (
NotConfigured OperationalFlag = iota
NotSecure
Debug
Recovery
)
// TcgMultiTcbInfo represents a sequence of TCBInfos
type TcgMultiTcbInfo = []DiceTcbInfo
// CertifyKeyParams holds configurable parameters to CertifyKey for test-cases
type CertifyKeyParams struct {
Label []byte
Flags client.CertifyKeyFlags
}
// TestCertifyKey tests calling CertifyKey
func TestCertifyKey(d client.TestDPEInstance, c client.DPEClient, t *testing.T) {
testCertifyKey(d, c, t, false)
}
// TestCertifyKeySimulation tests calling CertifyKey on simulation contexts
func TestCertifyKeySimulation(d client.TestDPEInstance, c client.DPEClient, t *testing.T) {
testCertifyKey(d, c, t, true)
}
// Testclient.CertifyKeyCsr tests calling CeritifyKey with type = CSR
func TestCertifyKeyCsr(d client.TestDPEInstance, c client.DPEClient, t *testing.T) {
ctx := getInitialContextHandle(d, c, t, false)
profile, err := client.GetTransportProfile(d)
if err != nil {
t.Fatalf("Could not get profile: %v", err)
}
digestLen := profile.GetDigestSize()
flags := client.CertifyKeyFlags(client.CertifyAddIsCA)
label := make([]byte, digestLen)
// Get DPE leaf certificate from CertifyKey
certifyKeyResp, err := c.CertifyKey(ctx, label, client.CertifyKeyCsr, flags)
if err != nil {
t.Fatalf("[FATAL]: Could not certify key: %v", err)
}
wrappedCSR, err := cms.ParseSignedData(certifyKeyResp.Certificate)
if err != nil {
t.Fatalf("[FATAL]: Could not unmarshal CSR CMS message: %v", err)
}
// Check signature on the CMS message
certChainBytes, err := c.GetCertificateChain()
if err != nil {
t.Fatalf("[FATAL]: Could not get Certificate Chain: %v", err)
}
certChain := checkCertificateChain(t, certChainBytes)
// This library expects the cert to be in the Certificates field but DPE
// does not populate it. Add it so Verify succeeds.
//
// It is still compliant that DPE does not produce the certificate chain.
// From PKCS#7 section 9.1:
// There may also be fewer certificates than necessary, if it is expected that
// those verifying the signatures have an alternate means of
// obtaining necessary certificates (e.g., from a previous set
// of certificates).
err = wrappedCSR.SetCertificates(certChain)
if err != nil {
t.Errorf("[ERROR]: Failed to add certificates to SignedData: %v", err)
}
certPool := x509.NewCertPool()
for _, cert := range certChain {
certPool.AddCert(cert)
}
_, err = wrappedCSR.Verify(x509.VerifyOptions{Roots: certPool})
if err != nil {
t.Errorf("[ERROR]: Failed to verify CMS wrapper signature: %v", err)
}
content, err := wrappedCSR.GetData()
if err != nil {
t.Fatalf("[FATAL]: Could not get CMS content: %v", err)
}
csr, err := x509.ParseCertificateRequest(content)
if err != nil {
t.Fatalf("[FATAL]: Could not parse CSR: %v", err)
}
// Check fields and extensions in the CSR
checkCertifyKeyExtensions(t, csr.Extensions, flags, label, certifyKeyResp.Pub, false, certChain[len(certChain)-1].SubjectKeyId)
checkPubKey(t, profile, csr.PublicKey, *certifyKeyResp)
// Check that CSR is self-signed
err = csr.CheckSignature()
if err != nil {
t.Errorf("[ERROR] CSR is not self-signed: %v", err)
}
}
// Ignores critical extensions that are unknown to x509 package
// but at least defined in DPE certificate profile specification.
// UnhandledCriticalExtensions may have only custom extensions mentioned in spec
// unknownExtnMap collects extensions unknown to both x509 and the DICE certificate profiles spec.
// positive case expects the unknownExtnMap to be empty.
func removeTcgDiceCriticalExtensions(t *testing.T, certs []*x509.Certificate) {
t.Helper()
unknownExtnMap := map[string][]string{}
for _, cert := range certs {
if len(cert.UnhandledCriticalExtensions) > 0 {
unknownExtns := []string{}
for _, extn := range cert.UnhandledCriticalExtensions {
if !slices.Contains(TcgDiceCriticalExtensions[:], extn.String()) {
unknownExtns = append(unknownExtns, extn.String())
}
}
if len(unknownExtnMap) == 0 {
cert.UnhandledCriticalExtensions = []asn1.ObjectIdentifier{}
} else {
unknownExtnMap[cert.Subject.String()] = unknownExtns
}
}
}
// The error details in this map will be logged
if len(unknownExtnMap) > 0 {
for certSubject, ext := range unknownExtnMap {
t.Errorf("[ERROR]: Certificate \"%s\" has unhandled critical extension \"%s\"", certSubject, ext)
}
t.Errorf("[ERROR]: Certificate chain validation will fail with non-empty unhandled critical extensions list")
}
}
// Ignores extended key usages that are unknown to x509 package
// but at least defined in DPE certificate profile specification.
// UnhandledExtendedKeyUsages may have only custom key usages mentioned in spec
// unknownKeyUsagesMap collects keyusages unknown to both x509 and the DICE certificate profiles spec.
// positive case expects the unknownKeyUsagesMap to be empty.
func removeTcgDiceExtendedKeyUsages(t *testing.T, certs []*x509.Certificate) {
t.Helper()
unknownKeyUsagesMap := map[string][]string{}
for _, cert := range certs {
if len(cert.UnknownExtKeyUsage) > 0 {
unknownKeyUsages := []string{}
for _, eku := range cert.UnknownExtKeyUsage {
if !slices.Contains(TcgDiceExtendedKeyUsages[:], eku.String()) {
unknownKeyUsages = append(unknownKeyUsages, eku.String())
}
}
if len(unknownKeyUsagesMap) == 0 {
cert.UnknownExtKeyUsage = []asn1.ObjectIdentifier{}
} else {
unknownKeyUsagesMap[cert.Subject.String()] = unknownKeyUsages
}
}
}
// The error details in this map will be logged
if len(unknownKeyUsagesMap) > 0 {
for certSubject, ext := range unknownKeyUsagesMap {
t.Errorf("[ERROR]: Certificate \"%s\" has unknown extended key usages \"%s\"", certSubject, ext)
}
t.Errorf("[ERROR]: Certificate chain validation will fail with non-empty unknown extended key usages list")
}
}
// A tcg-dice-Ueid extension MUST be added
// This SHALL be populated by the LABEL input parameter to CertifyKey
// The extension SHOULD be marked as critical
func checkCertifyKeyTcgUeidExtension(t *testing.T, extensions []pkix.Extension, label []byte) {
t.Helper()
ueid, err := getUeid(extensions)
if err != nil {
t.Errorf("[ERROR]: tcg-dice-Ueid extension is missing: %v", err)
}
if !reflect.DeepEqual(ueid.Ueid, label) {
// Ueid extn value doen not match the label
t.Errorf("[ERROR]: tcg-dice-Ueid value does not match with the \"Label\" passed in CertifyKeyRequest")
}
}
// Checks whether certificate extended key usage is as per spec
// OID for ExtendedKeyUsage Extension: 2.5.29.37
// The ExtendedKeyUsage extension SHOULD be marked as critical
// If IsCA = true, the extension SHOULD contain tcg-dice-kp-eca
// If IsCA = false, the extension SHOULD contain tcg-dice-kp-attestLoc
func checkCertifyKeyExtendedKeyUsages(t *testing.T, extensions []pkix.Extension, ca bool) {
t.Helper()
extKeyUsage, err := getExtendedKeyUsages(extensions)
if err != nil {
t.Errorf("[ERROR]: ExtKeyUsage extension is missing: %v", err)
}
if len(extKeyUsage) == 0 {
t.Errorf("[ERROR]: The Extended Key Usage extension is empty")
}
// Iterate over the OIDs in the ExtKeyUsage extension
isExtendedKeyUsageValid := false
var expectedKeyUsage asn1.ObjectIdentifier
expectedKeyUsageName := ""
if ca {
expectedKeyUsage = OidExtensionTcgDiceKpEca
expectedKeyUsageName = "tcg-dice-kp-eca"
} else {
expectedKeyUsage = OidExtensionTcgDiceKpAttestLoc
expectedKeyUsageName = "tcg-dice-kp-attest-loc"
}
for _, oid := range extKeyUsage {
if oid.Equal(expectedKeyUsage) {
isExtendedKeyUsageValid = true
break
}
}
if !isExtendedKeyUsageValid {
t.Errorf("[ERROR]: Certificate has IsCA: %v and does not contain specified key usage: %s", ca, expectedKeyUsageName)
}
}
// Checks for KeyUsage Extension as per spec
// If IsCA = true, KeyUsage extension MUST contain DigitalSignature and KeyCertSign
// If IsCA = false, KeyUsage extension MUST contain only DigitalSignature
func checkCertifyKeyExtensions(t *testing.T, extensions []pkix.Extension, flags client.CertifyKeyFlags, label []byte, pubkey client.DPEPubKey, IsX509 bool, IssuerSki []byte) {
t.Helper()
bc, err := getBasicConstraints(extensions)
if err != nil {
t.Error(err)
}
checkCertifyKeyBasicConstraints(t, extensions, flags)
checkCertifyKeyExtendedKeyUsages(t, extensions, bc.IsCA)
checkCertifyKeyTcgUeidExtension(t, extensions, label)
if IsX509 {
checkCertifyKeySubjectKeyIdentifierExtension(t, extensions, bc.IsCA, pubkey)
checkCertifyKeyAuthorityKeyIdentifierExtension(t, extensions, bc.IsCA, IssuerSki)
}
// Check MultiTcbInfo Extension structure
_, err = getMultiTcbInfo(extensions)
if err != nil {
t.Error(err)
}
//Check for keyusage extension
var allowedKeyUsages x509.KeyUsage
if bc.IsCA {
allowedKeyUsages = x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign
} else {
allowedKeyUsages = x509.KeyUsageDigitalSignature
}
usage, err := getKeyUsage(extensions)
if err != nil {
t.Error(err)
}
certKeyUsageList := getKeyUsageNames(usage)
allowedKeyUsageList := getKeyUsageNames(allowedKeyUsages)
if usage != allowedKeyUsages {
t.Errorf("[ERROR]: Certificate KeyUsage got %v but want %v ", certKeyUsageList, allowedKeyUsageList)
}
}
// Validates SubjectKeyIdentifier in certificate returned by CertifyKey command
// against the isCa flag set in the BasicConstraints extension
// The SubjectKeyIdentifier extension MUST be included if isCA is true
// and MUST be excluded if isCA is false.
func checkCertifyKeySubjectKeyIdentifierExtension(t *testing.T, extensions []pkix.Extension, ca bool, pubkey client.DPEPubKey) {
t.Helper()
ski, err := getSubjectKeyIdentifier(extensions)
if err != nil {
t.Errorf("[ERROR]: Failed to retrieve SubjectKeyIdentifier extension: %v", err)
}
if ca {
if ski == nil {
t.Errorf("[ERROR]: The certificate is a CA but the SubjectKeyIdentifier extension is not present.")
}
var hasher hash.Hash
if len(pubkey.X) == 32 {
hasher = sha256.New()
} else {
hasher = sha512.New384()
}
hasher.Write([]byte{0x04})
hasher.Write(pubkey.X)
hasher.Write(pubkey.Y)
expectedKeyIdentifier := hasher.Sum(nil)[:20]
if !reflect.DeepEqual(ski, expectedKeyIdentifier) {
t.Errorf("[ERROR]: The value of the subject key identifier %v is not equal to the hash of the public key %v", ski, expectedKeyIdentifier)
}
} else if !ca && ski != nil {
t.Errorf("[ERROR]: The certificate is not a CA but the SubjectKeyIdentifier extension is present.")
}
}
// Validates AuthorityKeyIdentifier in certificate returned by CertifyKey command
// against the isCa flag set in the BasicConstraints extension
// The AuthorityKeyIdentifier extension MUST be included if isCA is true
// and MUST be excluded if isCA is false.
func checkCertifyKeyAuthorityKeyIdentifierExtension(t *testing.T, extensions []pkix.Extension, ca bool, IssuerSki []byte) {
t.Helper()
aki, err := getAuthorityKeyIdentifier(extensions)
if err != nil {
t.Errorf("[ERROR]: Failed to retrieve AuthorityKeyIdentifier extension: %v", err)
}
if ca {
if aki.KeyIdentifier == nil {
t.Fatal("[ERROR]: The certificate is a CA but the AuthorityKeyIdentifier extension is not present.")
}
// skip first two bytes - first byte is 0x04 der encoding byte and second byte is size byte
if !reflect.DeepEqual(aki.KeyIdentifier[2:], IssuerSki) {
t.Errorf("[ERROR]: The value of the authority key identifier %v is not equal to the issuer's subject key identifier %v", aki, IssuerSki)
}
} else if !ca && aki.KeyIdentifier != nil {
t.Errorf("[ERROR]: The certificate is not a CA but the AuthorityKeyIdentifier extension is present.")
}
}
// Validates basic constraints in certificate returned by CertifyKey command
// against the flag set for input parameter.
// The BasicConstraints extension MUST be included
// If CertifyKey AddIsCA is set, IsCA MUST be set to true.
// If CertifyKey AddIsCA is NOT set, IsCA MUST be set to false
func checkCertifyKeyBasicConstraints(t *testing.T, extensions []pkix.Extension, flags client.CertifyKeyFlags) {
t.Helper()
flagsBuf := &bytes.Buffer{}
binary.Write(flagsBuf, binary.LittleEndian, flags)
bc, err := getBasicConstraints(extensions)
if err != nil {
t.Error(err)
}
flagIsCA := client.CertifyAddIsCA&flags != 0
if flagIsCA != bc.IsCA {
t.Errorf("[ERROR]: ADD_IS_CA is set to %v but the basic constraint IsCA is set to %v", flagIsCA, bc.IsCA)
}
}
// Parses X509 certificate
func checkCertificateStructure(t *testing.T, certBytes []byte) *x509.Certificate {
t.Helper()
failed := false
var x509Cert *x509.Certificate
var err error
// Check whether certificate is DER encoded.
if x509Cert, err = x509.ParseCertificate(certBytes); err != nil {
t.Fatalf("[FATAL]: Could not parse certificate using crypto/x509: %v", err)
}
// Parse the cert with zcrypto so we can lint it.
cert, err := zx509.ParseCertificate(certBytes)
if err != nil {
t.Errorf("[ERROR]: Could not parse certificate using zcrypto/x509: %v", err)
failed = true
}
// zlint provides a lot of linter sources. Limit results to just the relevant RFCs.
// For a full listing of supported linter sources, see https://github.com/zmap/zlint/blob/master/v3/lint/source.go
registry, err := lint.GlobalRegistry().Filter(lint.FilterOptions{
IncludeSources: lint.SourceList{
lint.RFC3279,
lint.RFC5280,
lint.RFC5480,
lint.RFC5891,
lint.RFC8813,
},
ExcludeNames: []string{
// It is fine for cert chains to always use GeneralizedTime, UTCTime is
// strictly worse and mixing the two formats does not lend itself well
// to fixed-sized X.509 templating.
"e_wrong_time_format_pre2050",
// Certs in the Caliptra cert chain fail this lint currently.
// We will need to truncate the serial numbers for those certs and
// then enable this lint.
"e_subject_dn_serial_number_max_length",
},
})
if err != nil {
t.Fatalf("[FATAL]: Could not set up zlint registry: %v", err)
}
results := zlint.LintCertificateEx(cert, registry)
for id, result := range results.Results {
var level string
switch result.Status {
case lint.Error:
level = "ERROR"
case lint.Warn:
level = "WARN"
default:
continue
}
details := result.Details
if details != "" {
details = fmt.Sprintf("%s. ", details)
}
l := registry.ByName(id)
t.Logf("[LINT %s] %s: %s%s (%s)", level, l.Source, details, l.Description, l.Citation)
failed = true
}
if failed {
// Dump the cert in PEM and hex for use with various tools
t.Logf("[LINT]: Offending certificate: %s\n", cert.Subject.String())
t.Logf("[LINT]: Offending certificate (PEM):\n%s", (string)(pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
})))
t.Fatalf("Certificate lint failed!")
}
return x509Cert
}
func testCertifyKey(d client.TestDPEInstance, c client.DPEClient, t *testing.T, simulation bool) {
handle := getInitialContextHandle(d, c, t, simulation)
defer func() {
if simulation {
c.DestroyContext(handle)
}
}()
profile, err := client.GetTransportProfile(d)
if err != nil {
t.Fatalf("Could not get profile: %v", err)
}
digestLen := profile.GetDigestSize()
seqLabel := make([]byte, digestLen)
for i := range seqLabel {
seqLabel[i] = byte(i)
}
certifyKeyParams := []CertifyKeyParams{
{Label: make([]byte, digestLen), Flags: client.CertifyKeyFlags(client.CertifyAddIsCA)},
{Label: seqLabel, Flags: client.CertifyKeyFlags(client.CertifyAddIsCA)},
}
for _, params := range certifyKeyParams {
// Get DPE leaf certificate from CertifyKey
certifyKeyResp, err := c.CertifyKey(handle, params.Label, client.CertifyKeyX509, params.Flags)
if err != nil {
t.Fatalf("[FATAL]: Could not certify key: %v", err)
}
// Get root and intermediate certificates to validate certificate chain of leaf cert
certChainBytes, err := c.GetCertificateChain()
if err != nil {
t.Fatalf("[FATAL]: Could not get Certificate Chain: %v", err)
}
leafCertBytes := certifyKeyResp.Certificate
// Run X.509 linter on full certificate chain and file issues for errors
leafCert := checkCertificateStructure(t, leafCertBytes)
certChain := checkCertificateChain(t, certChainBytes)
// Check default context handle is unchanged
checkCertifyKeyRespHandle(*certifyKeyResp, t, handle)
// Check public key and algorithm parameters are correct
checkPubKey(t, profile, leafCert.PublicKey, *certifyKeyResp)
// Check all extensions
checkCertifyKeyExtensions(t, leafCert.Extensions, params.Flags, params.Label, certifyKeyResp.Pub, true, certChain[len(certChain)-1].SubjectKeyId)
// Ensure full certificate chain has valid signatures
// This also checks certificate lifetime, signatures as part of cert chain validation
validateLeafCertChain(t, certChain, leafCert)
// Reassign handle for simulation mode.
// However, this does not impact in default mode because
// same default context handle is returned in default mode.
handle = &certifyKeyResp.Handle
}
// TODO: When DeriveContext is implemented, call it here to add more TCIs and call CertifyKey again.
}
// Builds and verifies certificate chain.
func validateLeafCertChain(t *testing.T, certChain []*x509.Certificate, leafCert *x509.Certificate) {
t.Helper()
certsToProcess := []*x509.Certificate{leafCert}
// Remove unhandled critical extensions and EKUs by x509 but defined in spec
removeTcgDiceCriticalExtensions(t, certsToProcess)
removeTcgDiceExtendedKeyUsages(t, certsToProcess)
// Certificate chain validation for leaf
opts := buildVerifyOptions(t, certChain)
chains, err := leafCert.Verify(opts)
if err != nil {
t.Errorf("[ERROR]: Error verifying DPE leaf: %s", err.Error())
}
// Log certificate chains linked to leaf
if len(chains) != 1 {
t.Errorf("[ERROR]: Unexpected number of cert chains: %d", len(chains))
}
}
// Builds Certificate chain verifier parameters.
func buildVerifyOptions(t *testing.T, certChain []*x509.Certificate) x509.VerifyOptions {
roots := x509.NewCertPool()
intermediates := x509.NewCertPool()
// Root certificate is expected to be in the beginning of the chain, the rest are expected to be intermediates.
roots.AddCert(certChain[0])
for _, cert := range certChain[1:] {
if cert.Subject.String() == cert.Issuer.String() {
t.Errorf("[ERROR]: Found a self-signed certificate in middle of certificate chain returned by GetCertificateChain.")
continue
}
intermediates.AddCert(cert)
}
opts := x509.VerifyOptions{
Roots: roots,
Intermediates: intermediates,
CurrentTime: time.Now().UTC(),
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
}
return opts
}
// Gets KeyUsage bitmap and returns as list of KeyUsage name strings.
func getKeyUsageNames(keyUsage x509.KeyUsage) []string {
keyUsageNames := []string{}
if keyUsage&x509.KeyUsageDigitalSignature != 0 {
keyUsageNames = append(keyUsageNames, "DigitalSignature")
}
if keyUsage&x509.KeyUsageContentCommitment != 0 {
keyUsageNames = append(keyUsageNames, "ContentCommitment")
}
if keyUsage&x509.KeyUsageKeyEncipherment != 0 {
keyUsageNames = append(keyUsageNames, "KeyEncipherment")
}
if keyUsage&x509.KeyUsageDataEncipherment != 0 {
keyUsageNames = append(keyUsageNames, "DataEncipherment")
}
if keyUsage&x509.KeyUsageKeyAgreement != 0 {
keyUsageNames = append(keyUsageNames, "KeyAgreement")
}
if keyUsage&x509.KeyUsageCertSign != 0 {
keyUsageNames = append(keyUsageNames, "CertSign")
}
if keyUsage&x509.KeyUsageCRLSign != 0 {
keyUsageNames = append(keyUsageNames, "CRLSign")
}
if keyUsage&x509.KeyUsageEncipherOnly != 0 {
keyUsageNames = append(keyUsageNames, "EncipherOnly")
}
if keyUsage&x509.KeyUsageDecipherOnly != 0 {
keyUsageNames = append(keyUsageNames, "DecipherOnly")
}
return keyUsageNames
}
func checkPubKey(t *testing.T, p client.Profile, pubkey any, response client.CertifiedKey) {
var pubKeyInResponse ecdsa.PublicKey
switch p {
case client.ProfileP256SHA256:
pubKeyInResponse = ecdsa.PublicKey{
Curve: elliptic.P256(),
X: new(big.Int).SetBytes(response.Pub.X),
Y: new(big.Int).SetBytes(response.Pub.Y),
}
case client.ProfileP384SHA384:
pubKeyInResponse = ecdsa.PublicKey{
Curve: elliptic.P384(),
X: new(big.Int).SetBytes(response.Pub.X),
Y: new(big.Int).SetBytes(response.Pub.Y),
}
default:
t.Errorf("[ERROR]: Unsupported profile %v", p)
}
ecdsaPub, ok := pubkey.(*ecdsa.PublicKey)
if !ok {
t.Fatal("[FATAL]: Public key is not a ecdsa key")
}
if !(pubKeyInResponse.Equal(ecdsaPub)) {
t.Errorf("[ERROR]: Public key returned in response must match the Public Key Info in the certificate.")
}
}
// Checks whether the context handle is unchanged after certifyKey command when default context handle is used.
func checkCertifyKeyRespHandle(res client.CertifiedKey, t *testing.T, handle *client.ContextHandle) {
if *handle != client.DefaultContextHandle {
t.Logf("[LOG]: Handle is not default context, skipping check...")
return
}
if res.Handle != *handle {
t.Errorf("[ERROR]: Handle must be unchanged by CertifyKey, want original handle %v but got %v", handle, res.Handle)
}
}