diff --git a/dpe/fuzz/Cargo.lock b/dpe/fuzz/Cargo.lock index 83059472..f4b163fc 100644 --- a/dpe/fuzz/Cargo.lock +++ b/dpe/fuzz/Cargo.lock @@ -80,6 +80,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bitflags" version = "1.3.2" @@ -156,6 +162,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "constant_time_eq" version = "0.3.0" @@ -192,6 +204,30 @@ dependencies = [ "typenum", ] +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "der_derive", + "flagset", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fe87ce4529967e0ba1dcf8450bab64d97dfd5010a6256187ffe2e43e6f0e049" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + [[package]] name = "deranged" version = "0.3.8" @@ -262,6 +298,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +[[package]] +name = "flagset" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a7e408202050813e6f1d9addadcaafef3dca7530c7ddfb005d4081cce6779" + [[package]] name = "foreign-types" version = "0.3.2" @@ -417,6 +459,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "pkg-config" version = "0.3.27" @@ -427,8 +478,10 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" name = "platform" version = "0.1.0" dependencies = [ + "cfg-if", "openssl", "ufmt", + "x509-cert", ] [[package]] @@ -528,6 +581,16 @@ dependencies = [ "time", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "strsim" version = "0.10.0" @@ -765,6 +828,17 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "x509-cert" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25eefca1d99701da3a57feb07e5079fc62abba059fc139e98c13bbb250f3ef29" +dependencies = [ + "const-oid", + "der", + "spki", +] + [[package]] name = "xdg" version = "2.5.2" diff --git a/verification/abi.go b/verification/abi.go index a78ad636..bd991b25 100755 --- a/verification/abi.go +++ b/verification/abi.go @@ -9,6 +9,7 @@ import ( // DefaultContextHandle is the default DPE context handle var DefaultContextHandle = ContextHandle{0} +var InvalidatedContextHandle = ContextHandle{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255} // Profile-defined constants const ( diff --git a/verification/certifyKey.go b/verification/certifyKey.go index 7d09c0ee..7f038d82 100755 --- a/verification/certifyKey.go +++ b/verification/certifyKey.go @@ -15,106 +15,14 @@ import ( "math/big" "reflect" "testing" - "time" "go.mozilla.org/pkcs7" 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 CertifyKeyFlags @@ -130,6 +38,14 @@ func TestCertifyKeySimulation(d TestDPEInstance, c DPEClient, t *testing.T) { testCertifyKey(d, c, t, true) } +func TestDiceTcbInfo(d TestDPEInstance, c DPEClient, t *testing.T) { + testDiceTcbInfo(d, c, t, false) +} + +func TestDiceTcbInfoSimulation(d TestDPEInstance, c DPEClient, t *testing.T) { + testDiceTcbInfo(d, c, t, true) +} + // TestCertifyKeyCsr tests calling CeritifyKey with type = CSR func TestCertifyKeyCsr(d TestDPEInstance, c DPEClient, t *testing.T) { ctx := getInitialContextHandle(d, c, t, false) @@ -207,197 +123,236 @@ func TestCertifyKeyCsr(d TestDPEInstance, c DPEClient, t *testing.T) { } } -// 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()) - } +func testCertifyKey(d TestDPEInstance, c DPEClient, t *testing.T, simulation bool) { + handle := getInitialContextHandle(d, c, t, simulation) + if simulation { + // Clean up contexts + defer func() { + err := c.DestroyContext(handle, DestroyDescendants) + if err != nil { + t.Errorf("[ERROR]: Error while cleaning contexts, this may cause failure in subsequent tests: %s", err) } + }() + } - if len(unknownExtnMap) == 0 { - cert.UnhandledCriticalExtensions = []asn1.ObjectIdentifier{} - } else { - unknownExtnMap[cert.Subject.String()] = unknownExtns - } - } + profile, err := GetTransportProfile(d) + if err != nil { + t.Fatalf("Could not get profile: %v", err) } - // 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") + digestLen := profile.GetDigestSize() + seqLabel := make([]byte, digestLen) + for i := range seqLabel { + seqLabel[i] = byte(i) } -} -// 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()) - } - } + certifyKeyParams := []CertifyKeyParams{ + {Label: make([]byte, digestLen), Flags: CertifyKeyFlags(0)}, + {Label: make([]byte, digestLen), Flags: CertifyKeyFlags(CertifyAddIsCA)}, + {Label: seqLabel, Flags: CertifyKeyFlags(0)}, + } - if len(unknownKeyUsagesMap) == 0 { - cert.UnknownExtKeyUsage = []asn1.ObjectIdentifier{} - } else { - unknownKeyUsagesMap[cert.Subject.String()] = unknownKeyUsages - } + for _, params := range certifyKeyParams { + // Get DPE leaf certificate from CertifyKey + certifyKeyResp, err := c.CertifyKey(handle, params.Label, CertifyKeyX509, params.Flags) + if err != nil { + t.Fatalf("[FATAL]: Could not certify key: %v", err) } - } - // 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) + + // 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) + + // Ensure full certificate chain has valid signatures + // This also checks certificate lifetime, signatures as part of cert chain validation + if err = validateLeafCertChain(certChain, leafCert); err != nil { + t.Errorf("[ERROR]: %v", err) + } + + _, err = getMultiTcbInfo(leafCert.Extensions) + if err != nil { + t.Errorf("[ERROR]: Could not parse multi TCB information: extension %s", err) } - t.Errorf("[ERROR]: Certificate chain validation will fail with non-empty unknown extended key usages list") + + // 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 } } -// 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() +// Checks Multi Tcb Info for context derived from non-simulation mode by adding more TCIs by DeriveChild command. +// MultiTcbInfo extension has a DiceTcbInfo block for each TCI node. +// In a DiceTcbInfo block of a given TCI node, +// - the "type" field must contain 4-byte tciType is provided by a client to DeriveChild. +// - the "fwid" field must contain cumulative TCI measurement. +func testDiceTcbInfo(d TestDPEInstance, c DPEClient, t *testing.T, simulation bool) { + handle := getInitialContextHandle(d, c, t, simulation) + if simulation { + // Clean up contexts + defer func() { + err := c.DestroyContext(handle, DestroyDescendants) + if err != nil { + t.Errorf("[ERROR]: Error while cleaning contexts, this may cause failure in subsequent tests: %s", err) + } + }() + } - ueid, err := getUeid(extensions) + profile, err := GetTransportProfile(d) if err != nil { - t.Errorf("[ERROR]: tcg-dice-Ueid extension is missing: %v", err) + t.Fatalf("Could not get profile: %v", err) } + digestLen := profile.GetDigestSize() - 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") + var hashAlg asn1.ObjectIdentifier + if digestLen == 32 { + hashAlg = OidSHA256 + } else if digestLen == 48 { + hashAlg = OidSHA384 } -} -// 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() + childTCI1 := make([]byte, digestLen) + for i := range childTCI1 { + childTCI1[i] = byte(i + 1) + } + + // Set tciType to verify in UEID extension + tciType := uint32(2) + + // Derive Child context with input data, tag it and check TCI_CUMULATIVE + childCtx, err := c.DeriveChild(handle, + childTCI1, + DeriveChildFlags(InputAllowX509), + tciType, + 0) - extKeyUsage, err := getExtendedKeyUsages(extensions) if err != nil { - t.Errorf("[ERROR]: ExtKeyUsage extension is missing: %v", err) + t.Fatalf("[FATAL]: Error while creating child handle: %s", err) } - if len(extKeyUsage) == 0 { - t.Errorf("[ERROR]: The Extended Key Usage extension is empty") + handle = &childCtx.NewContextHandle + + var childTcbInfo DiceTcbInfo + handle, childTcbInfo, err = getTcbInfoForHandle(c, handle) + if err != nil { + t.Fatalf("[FATAL]: Could not get TcbInfo: %v", err) } - // 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" + // Check vendorInfo field in multitcb + if err = checkDiceTcbVendorInfo(childTcbInfo, d.GetLocality()); err != nil { + t.Errorf("[ERROR]: %v", err) } - for _, oid := range extKeyUsage { - if oid.Equal(expectedKeyUsage) { - isExtendedKeyUsageValid = true - break - } + // Check tci type field in multitcb + if err = checkCurrentDiceTcbTciType(childTcbInfo, tciType); err != nil { + t.Errorf("[ERROR]: %v", err) } - if !isExtendedKeyUsageValid { - t.Errorf("[ERROR]: Certificate has IsCA: %v and does not contain specified key usage: %s", ca, expectedKeyUsageName) + + // Check hash algorithm field in multitcb + if err = checkDiceTcbHashAlgorithm(childTcbInfo, hashAlg); err != nil { + t.Errorf("[ERROR]: %v", err) } -} -// 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 CertifyKeyFlags, label []byte) { - t.Helper() + // Extend TCI support is mandatory for this validation + if !d.GetSupport().ExtendTci { + t.Errorf("ExtendTCI is unsupported by profile, unable to run tests to verify TCI_CUMULATIVE measurement") + return + } + + // Check dice tcb measurements of derived children + // Add one more child context + childTCI2 := make([]byte, digestLen) + for i := range childTCI2 { + childTCI2[i] = byte(i + 2) + } + + childCtx, err = c.DeriveChild(handle, + childTCI2, + DeriveChildFlags(InputAllowX509), + tciType, + 0) - bc, err := getBasicConstraints(extensions) if err != nil { - t.Error(err) + t.Fatalf("[FATAL]: Error while creating child handle: %s", err) } - checkCertifyKeyBasicConstraints(t, extensions, flags) - checkCertifyKeyExtendedKeyUsages(t, extensions, bc.IsCA) - checkCertifyKeyTcgUeidExtension(t, extensions, label) + handle = &childCtx.NewContextHandle - // Check MultiTcbInfo Extension structure - _, err = getMultiTcbInfo(extensions) + // Get latest TCB information + certifiedKey, err := c.CertifyKey(handle, childTCI2, CertifyKeyX509, 0) if err != nil { - t.Error(err) + t.Fatalf("[FATAL]: Could not certify key: %s", err) } - //Check for keyusage extension - var allowedKeyUsages x509.KeyUsage + handle = &certifiedKey.Handle + leafCertBytes := certifiedKey.Certificate - if bc.IsCA { - allowedKeyUsages = x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign - } else { - allowedKeyUsages = x509.KeyUsageDigitalSignature + // Build list of tci_current for validation and use it for validating TCI measurements + currentTCIs := [][]byte{childTCI2, childTCI1} + if err = validateDiceTcbFwids(leafCertBytes, currentTCIs, digestLen); err != nil { + t.Errorf("[ERROR]: %v", err) } +} - usage, err := getKeyUsage(extensions) - if err != nil { - t.Error(err) +func checkPubKey(t *testing.T, p Profile, pubkey any, response CertifiedKey) { + var pubKeyInResponse ecdsa.PublicKey + switch p { + case ProfileP256SHA256: + pubKeyInResponse = ecdsa.PublicKey{ + Curve: elliptic.P256(), + X: new(big.Int).SetBytes(response.Pub.X), + Y: new(big.Int).SetBytes(response.Pub.Y), + } + case 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) } - certKeyUsageList := getKeyUsageNames(usage) - allowedKeyUsageList := getKeyUsageNames(allowedKeyUsages) - if usage != allowedKeyUsages { - t.Errorf("[ERROR]: Certificate KeyUsage got %v but want %v ", certKeyUsageList, allowedKeyUsageList) + 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.") + } } -// 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 CertifyKeyFlags) { - t.Helper() - - flagsBuf := &bytes.Buffer{} - binary.Write(flagsBuf, binary.LittleEndian, flags) - - bc, err := getBasicConstraints(extensions) - if err != nil { - t.Error(err) +// Checks whether the context handle is unchanged after certifyKey command when default context handle is used. +func checkCertifyKeyRespHandle(res CertifiedKey, t *testing.T, handle *ContextHandle) { + if *handle != DefaultContextHandle { + t.Logf("[LOG]: Handle is not default context, skipping check...") + return } - flagIsCA := 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) + if res.Handle != *handle { + t.Errorf("[ERROR]: Handle must be unchanged by CertifyKey, want original handle %v but got %v", handle, res.Handle) } } // Parses X509 certificate func checkCertificateStructure(t *testing.T, certBytes []byte) *x509.Certificate { - t.Helper() failed := false var x509Cert *x509.Certificate @@ -405,13 +360,13 @@ func checkCertificateStructure(t *testing.T, certBytes []byte) *x509.Certificate // 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) + t.Fatalf("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) + t.Errorf("Could not parse certificate using zcrypto/x509: %v", err) failed = true } @@ -470,197 +425,124 @@ func checkCertificateStructure(t *testing.T, certBytes []byte) *x509.Certificate return x509Cert } -func testCertifyKey(d TestDPEInstance, c DPEClient, t *testing.T, simulation bool) { - handle := getInitialContextHandle(d, c, t, simulation) - defer func() { - if simulation { - c.DestroyContext(handle, DestroyDescendants) - } - }() +// 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() - profile, err := GetTransportProfile(d) + ueid, err := getUeid(extensions) 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: CertifyKeyFlags(0)}, - {Label: seqLabel, Flags: CertifyKeyFlags(0)}, + t.Errorf("[ERROR]: tcg-dice-Ueid extension is missing: %v", err) } - for _, params := range certifyKeyParams { - // Get DPE leaf certificate from CertifyKey - certifyKeyResp, err := c.CertifyKey(handle, params.Label, 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) - - // 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 + 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") } - // TODO: When DeriveChild 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) { +// 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() - 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) + extKeyUsage, err := getExtendedKeyUsages(extensions) if err != nil { - t.Errorf("[ERROR]: Error verifying DPE leaf: %s", err.Error()) + t.Errorf("[ERROR]: ExtKeyUsage extension is missing: %v", err) } - // Log certificate chains linked to leaf - if len(chains) != 1 { - t.Errorf("[ERROR]: Unexpected number of cert chains: %d", len(chains)) + if len(extKeyUsage) == 0 { + t.Errorf("[ERROR]: The Extended Key Usage extension is empty") } -} -// 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]) + // 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 _, 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 + for _, oid := range extKeyUsage { + if oid.Equal(expectedKeyUsage) { + isExtendedKeyUsageValid = true + break } - intermediates.AddCert(cert) } - opts := x509.VerifyOptions{ - Roots: roots, - Intermediates: intermediates, - CurrentTime: time.Now().UTC(), - KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, + if !isExtendedKeyUsageValid { + t.Errorf("[ERROR]: Certificate has IsCA: %v and does not contain specified key usage: %s", ca, expectedKeyUsageName) } - - 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") - } +// // 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 CertifyKeyFlags, label []byte) { + t.Helper() - if keyUsage&x509.KeyUsageKeyEncipherment != 0 { - keyUsageNames = append(keyUsageNames, "KeyEncipherment") + bc, err := getBasicConstraints(extensions) + if err != nil { + t.Error(err) } - if keyUsage&x509.KeyUsageDataEncipherment != 0 { - keyUsageNames = append(keyUsageNames, "DataEncipherment") - } + checkCertifyKeyBasicConstraints(t, extensions, flags) + checkCertifyKeyExtendedKeyUsages(t, extensions, bc.IsCA) + checkCertifyKeyTcgUeidExtension(t, extensions, label) - if keyUsage&x509.KeyUsageKeyAgreement != 0 { - keyUsageNames = append(keyUsageNames, "KeyAgreement") + // Check MultiTcbInfo Extension structure + _, err = getMultiTcbInfo(extensions) + if err != nil { + t.Error(err) } - if keyUsage&x509.KeyUsageCertSign != 0 { - keyUsageNames = append(keyUsageNames, "CertSign") - } + //Check for keyusage extension + var allowedKeyUsages x509.KeyUsage - if keyUsage&x509.KeyUsageCRLSign != 0 { - keyUsageNames = append(keyUsageNames, "CRLSign") + if bc.IsCA { + allowedKeyUsages = x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign + } else { + allowedKeyUsages = x509.KeyUsageDigitalSignature } - if keyUsage&x509.KeyUsageEncipherOnly != 0 { - keyUsageNames = append(keyUsageNames, "EncipherOnly") + usage, err := getKeyUsage(extensions) + if err != nil { + t.Error(err) } - if keyUsage&x509.KeyUsageDecipherOnly != 0 { - keyUsageNames = append(keyUsageNames, "DecipherOnly") + certKeyUsageList := getKeyUsageNames(usage) + allowedKeyUsageList := getKeyUsageNames(allowedKeyUsages) + if usage != allowedKeyUsages { + t.Errorf("[ERROR]: Certificate KeyUsage got %v but want %v ", certKeyUsageList, allowedKeyUsageList) } - return keyUsageNames } -func checkPubKey(t *testing.T, p Profile, pubkey any, response CertifiedKey) { - var pubKeyInResponse ecdsa.PublicKey - switch p { - case ProfileP256SHA256: - pubKeyInResponse = ecdsa.PublicKey{ - Curve: elliptic.P256(), - X: new(big.Int).SetBytes(response.Pub.X), - Y: new(big.Int).SetBytes(response.Pub.Y), - } - case 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") - } +// 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 CertifyKeyFlags) { + t.Helper() - if !(pubKeyInResponse.Equal(ecdsaPub)) { - t.Errorf("[ERROR]: Public key returned in response must match the Public Key Info in the certificate.") - } -} + flagsBuf := &bytes.Buffer{} + binary.Write(flagsBuf, binary.LittleEndian, flags) -// Checks whether the context handle is unchanged after certifyKey command when default context handle is used. -func checkCertifyKeyRespHandle(res CertifiedKey, t *testing.T, handle *ContextHandle) { - if *handle != DefaultContextHandle { - t.Logf("[LOG]: Handle is not default context, skipping check...") - return + bc, err := getBasicConstraints(extensions) + if err != nil { + t.Error(err) } - if res.Handle != *handle { - t.Errorf("[ERROR]: Handle must be unchanged by CertifyKey, want original handle %v but got %v", handle, res.Handle) + flagIsCA := 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) } } diff --git a/verification/certs.go b/verification/certs.go index c754b824..f2991e11 100644 --- a/verification/certs.go +++ b/verification/certs.go @@ -3,13 +3,17 @@ package verification import ( + "bytes" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" + "encoding/binary" "fmt" + "time" + + "golang.org/x/exp/slices" ) -// This file is used to test the certify key command. var ( OidExtensionKeyUsage = asn1.ObjectIdentifier{2, 5, 29, 15} OidExtensionAuthorityKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 35} @@ -28,9 +32,88 @@ var ( OidSHA384 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2} ) +var TcgDiceCriticalExtensions = [...]string{ + OidExtensionTcgDiceMultiTcbInfo.String(), + OidExtensionTcgDiceUeid.String(), +} + +var TcgDiceExtendedKeyUsages = [...]string{ + OidExtensionTcgDiceKpIdentityLoc.String(), + OidExtensionTcgDiceKpAttestLoc.String(), +} + +// tcg-dice-Ueid OBJECT IDENTIFIER ::= {tcg-dice 4} +// +// TcgUeid ::== SEQUENCE { +// ueid OCTET STRING +// } +type TcgUeidExtension struct { + Ueid []uint8 `asn1:"ueid,implicit"` +} + +// 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 Fwid struct { + HashAlg asn1.ObjectIdentifier + Digest []byte +} + +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"` +} + +type OperationalFlag int + +const ( + NotConfigured OperationalFlag = iota + NotSecure + Debug + Recovery +) + +type TcgMultiTcbInfo = []DiceTcbInfo + // BasicConstraints represents an X.509 BasicConstraints extension type BasicConstraints struct { - IsCA bool `asn1` + IsCA bool `asn1:"boolean"` PathLenConstraint int `asn1:"optional"` } @@ -41,11 +124,11 @@ func getMultiTcbInfo(extensions []pkix.Extension) (TcgMultiTcbInfo, error) { for _, ext := range extensions { if ext.Id.Equal(OidExtensionTcgDiceMultiTcbInfo) { if !ext.Critical { - return multiTcbInfo, fmt.Errorf("[ERROR]: TCG DICE MultiTcbInfo extension is not marked as CRITICAL") + return multiTcbInfo, fmt.Errorf("TCG DICE MultiTcbInfo extension is not marked as CRITICAL") } _, err := asn1.Unmarshal(ext.Value, &multiTcbInfo) if err != nil { - return multiTcbInfo, fmt.Errorf("[ERROR]: Failed to unmarshal MultiTcbInfo field: %v", err) + return multiTcbInfo, fmt.Errorf("Failed to unmarshal MultiTcbInfo field: %v", err) } break } @@ -58,11 +141,11 @@ func getBasicConstraints(extensions []pkix.Extension) (BasicConstraints, error) for _, ext := range extensions { if ext.Id.Equal(OidExtensionBasicConstraints) { if !ext.Critical { - return bc, fmt.Errorf("[ERROR]: BasicConstraints extension is not marked as CRITICAL") + return bc, fmt.Errorf("BasicConstraints extension is not marked as CRITICAL") } _, err := asn1.Unmarshal(ext.Value, &bc) if err != nil { - return bc, fmt.Errorf("[ERROR]: Failed to unmarshal BasicConstraints extension: %v", err) + return bc, fmt.Errorf("Failed to unmarshal BasicConstraints extension: %v", err) } break } @@ -75,11 +158,11 @@ func getUeid(extensions []pkix.Extension) (TcgUeidExtension, error) { for _, ext := range extensions { if ext.Id.Equal(OidExtensionTcgDiceUeid) { if !ext.Critical { - return ueid, fmt.Errorf("[ERROR]: UEID extension is not marked as CRITICAL") + return ueid, fmt.Errorf("UEID extension is not marked as CRITICAL") } _, err := asn1.Unmarshal(ext.Value, &ueid) if err != nil { - return ueid, fmt.Errorf("[ERROR]: Failed to unmarshal UEID extension: %v", err) + return ueid, fmt.Errorf("Failed to unmarshal UEID extension: %v", err) } break } @@ -92,11 +175,11 @@ func getExtendedKeyUsages(extensions []pkix.Extension) ([]asn1.ObjectIdentifier, for _, ext := range extensions { if ext.Id.Equal(OidExtensionExtKeyUsage) { if !ext.Critical { - return eku, fmt.Errorf("[ERROR]: ExtKeyUsage extension is not marked as CRITICAL") + return eku, fmt.Errorf("ExtKeyUsage extension is not marked as CRITICAL") } _, err := asn1.Unmarshal(ext.Value, &eku) if err != nil { - return eku, fmt.Errorf("[ERROR]: Failed to unmarshal ExtKeyUsage extension: %v", err) + return eku, fmt.Errorf("Failed to unmarshal ExtKeyUsage extension: %v", err) } break } @@ -109,11 +192,11 @@ func getKeyUsage(extensions []pkix.Extension) (x509.KeyUsage, error) { for _, ext := range extensions { if ext.Id.Equal(OidExtensionKeyUsage) { if !ext.Critical { - return x509.KeyUsage(0), fmt.Errorf("[ERROR]: KeyUsage extension is not marked as CRITICAL") + return x509.KeyUsage(0), fmt.Errorf("KeyUsage extension is not marked as CRITICAL") } _, err := asn1.Unmarshal(ext.Value, &usageBits) if err != nil { - return x509.KeyUsage(0), fmt.Errorf("[ERROR]: Failed to unmarshal KeyUsage extension: %v", err) + return x509.KeyUsage(0), fmt.Errorf("Failed to unmarshal KeyUsage extension: %v", err) } break } @@ -167,3 +250,274 @@ func getTcbInfoForHandle(c DPEClient, handle *ContextHandle) (*ContextHandle, Di return outHandle, multiTcbInfo[0], nil } + +// Removes the critical extensions that are unknown to x509 package +// but defined in DPE certificate profile specification for cert chain validation +// 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(certs []*x509.Certificate) error { + 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 + msg := "" + if len(unknownExtnMap) > 0 { + for certSubject, ext := range unknownExtnMap { + msg += fmt.Errorf("Certificate \"%s\" has unhandled critical extension \"%s\"", certSubject, ext).Error() + } + return fmt.Errorf("%s", msg) + } + return nil +} + +// Ignores extended key usages that are unknown to x509 package +// but atleast defined in DPE certificate profile specification for cert chain validation +// 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(certs []*x509.Certificate) error { + 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 + msg := "" + if len(unknownKeyUsagesMap) > 0 { + for certSubject, ext := range unknownKeyUsagesMap { + msg += fmt.Errorf("Certificate \"%s\" has unhandled critical extension \"%s\"", certSubject, ext).Error() + } + return fmt.Errorf("%s", msg) + } + return nil +} + +// Checks whether the VendorInfo is 4-bytes TARGET_LOCALITY parameter +func checkDiceTcbVendorInfo(currentTcbInfo DiceTcbInfo, targetLocality uint32) error { + var err error + expectedVendorInfo := make([]byte, 4) + binary.BigEndian.PutUint32(expectedVendorInfo, targetLocality) + if !bytes.Equal(currentTcbInfo.VendorInfo, expectedVendorInfo) { + err = fmt.Errorf("Unexpected VendorInfo for current DICE TCB block, want %v but got %v", expectedVendorInfo, currentTcbInfo.VendorInfo) + } + return err +} + +// Checks whether INPUT_TYPE passed to a deriveChild Request +// populates the "type" field in the DiceTcbInfo extension. +func checkCurrentDiceTcbTciType(currentTcbInfo DiceTcbInfo, expectedTciType uint32) error { + var err error + expectedTciTypeBytes := make([]byte, 4) + binary.BigEndian.PutUint32(expectedTciTypeBytes, expectedTciType) + if !bytes.Equal(currentTcbInfo.Type, expectedTciTypeBytes) { + err = fmt.Errorf("Unexpected TCI type for current DICE TCB block, want %v but got %v", expectedTciTypeBytes, currentTcbInfo.Type) + } + return err +} + +// Checks whether the Hash Algorithm field in FWID block is correct +func checkDiceTcbHashAlgorithm(currentTcbInfo DiceTcbInfo, hashAlg asn1.ObjectIdentifier) error { + for _, fwid := range currentTcbInfo.Fwids { + if !fwid.HashAlg.Equal(hashAlg) { + return fmt.Errorf("Unexpected hash algorithm in FWID block, expected %s but got %s", hashAlg, fwid.HashAlg) + } + } + return nil +} + +// Builds and verifies certificate chain. +func validateLeafCertChain(certChain []*x509.Certificate, leafCert *x509.Certificate) error { + var err error + certsToProcess := []*x509.Certificate{leafCert} + + // Remove unhandled critical extensions reported by x509 but defined in spec + if err = removeTcgDiceCriticalExtensions(certsToProcess); err != nil { + return err + } + + // Remove unhandled extended key usages reported by x509 but defined in spec + if err = removeTcgDiceExtendedKeyUsages(certsToProcess); err != nil { + return err + } + + // Build verify options + var opts *x509.VerifyOptions + if opts, err = buildVerifyOptions(certChain); err != nil { + return err + } + + // Certificate chain validation for leaf + chains, err := leafCert.Verify(*opts) + if err != nil { + // Unable to build certificate chain from leaf to root + return fmt.Errorf("Error verifying DPE leaf: %s", err.Error()) + } + + // Log certificate chains linked to leaf + if len(chains) != 1 { + return fmt.Errorf("Unexpected number of cert chains: %d", len(chains)) + } + return nil +} + +// Builds Certificate chain verifier parameters. +func buildVerifyOptions(certChain []*x509.Certificate) (*x509.VerifyOptions, error) { + var err error + 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() { + return nil, fmt.Errorf("Found a self-signed certificate in middle of certificate chain returned by GetCertificateChain") + } + intermediates.AddCert(cert) + } + opts := x509.VerifyOptions{ + Roots: roots, + Intermediates: intermediates, + CurrentTime: time.Now().UTC(), + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, + } + + return &opts, err +} + +// 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 +} + +// Verifies the TCI_Current and TCI_Cumulative of dice tcb information blocks +func verifyDiceTcbDigest(tcbInfo DiceTcbInfo, wantCurrentTCI []byte, lastCumulativeTCI []byte) error { + var err error + + // Check TCI_CURRENT + currentTCI := tcbInfo.Fwids[0].Digest + if !bytes.Equal(currentTCI, wantCurrentTCI) { + err = fmt.Errorf("Unexpected TCI_CURRENT digest, want %v but got %v", wantCurrentTCI, currentTCI) + } + + // Check TCI_CUMULATIVE against expected cumulative TCI + wantCumulativeTCI := computeExpectedCumulative(lastCumulativeTCI, currentTCI) + cumulativeTCI := tcbInfo.Fwids[1].Digest + if !bytes.Equal(cumulativeTCI, wantCumulativeTCI) { + err = fmt.Errorf("Unexpected TCI_CUMULATIVE value, want %v but got %v", wantCumulativeTCI, cumulativeTCI) + } + return err +} + +// Checks the FWID block's Digest. +// FWID at index 0 has the TCI_CURRENT as digest +// FWID at index 1 has the TCI_CUMULATIVE as digest +// FWID array always has two digest/hashAlg blocks when "ExtendTci" is supported by DPE profile. +func validateDiceTcbFwids(leafCertBytes []byte, currentTcis [][]byte, digestLen int) error { + var leafCert *x509.Certificate + var err error + + // Check whether certificate is DER encoded. + if leafCert, err = x509.ParseCertificate(leafCertBytes); err != nil { + return err + } + + // Get DICE information from MultiTcbInfo Extension + var multiTcbInfo []DiceTcbInfo + if multiTcbInfo, err = getMultiTcbInfo(leafCert.Extensions); err != nil { + return err + } + + if len(multiTcbInfo) == 0 { + return fmt.Errorf("Certificate MutliTcbInfo is empty") + } + + // Calculate expected cumulative value + defaultTci := make([]byte, digestLen) + + // Check cumulative, current TCI at the last index of multitcb info + // It must have default TCI value + lastIndex := len(multiTcbInfo) - 1 + if !bytes.Equal(multiTcbInfo[lastIndex].Fwids[0].Digest, defaultTci) { + return fmt.Errorf("Current TCI value for first TCB block, want %v but got %v", defaultTci, multiTcbInfo[lastIndex].Fwids[0].Digest) + } + + if !bytes.Equal(multiTcbInfo[lastIndex].Fwids[1].Digest, defaultTci) { + return fmt.Errorf("Cumulative TCI value for first TCB block, want %v but got %v", defaultTci, multiTcbInfo[lastIndex].Fwids[1].Digest) + } + + // Check cumulative, current TCI of other indices if any + lastCumulativeTCI := defaultTci + multiTcbInfo = multiTcbInfo[:lastIndex] + + for i, tcbinfo := range multiTcbInfo { + wantCurrentTci := currentTcis[i] + verifyDiceTcbDigest(tcbinfo, wantCurrentTci, lastCumulativeTCI) + } + return err +} diff --git a/verification/extendTCI.go b/verification/extendTCI.go index 7d8cb6e0..5f1d9e66 100644 --- a/verification/extendTCI.go +++ b/verification/extendTCI.go @@ -36,7 +36,7 @@ func TestExtendTCI(d TestDPEInstance, c DPEClient, t *testing.T) { if err != nil { t.Fatal(err) } - lastCumulative := tcbInfo.Fwids[1].Digest + lastCumulativeTCI := tcbInfo.Fwids[1].Digest // Set current TCI value _, err = c.ExtendTCI(handle, tciValue) @@ -44,9 +44,17 @@ func TestExtendTCI(d TestDPEInstance, c DPEClient, t *testing.T) { t.Fatalf("[FATAL]: Could not extend TCI: %v", err) } - // Check current and cumulative measurement by CertifyKey - expectedCumulative := computeExpectedCumulative(lastCumulative, tciValue) - verifyMeasurements(c, t, handle, tciValue, expectedCumulative) + // Refresh TCB info + _, tcbInfo, err = getTcbInfoForHandle(c, handle) + if err != nil { + t.Fatal(err) + } + + // Check current and cumulative measurement in DiceTcb info block + wantCurrentTCI := tciValue + if err = verifyDiceTcbDigest(tcbInfo, wantCurrentTCI, lastCumulativeTCI); err != nil { + t.Errorf("[ERROR]: %v", err) + } } func computeExpectedCumulative(lastCumulative []byte, tciValue []byte) []byte { @@ -155,21 +163,3 @@ func TestExtendTciOnDerivedContexts(d TestDPEInstance, c DPEClient, t *testing.T t.Errorf("[ERROR]: Child node's cumulative TCI %x, expected %x", childTcbInfo.Fwids[1].Digest, wantCumulativeTCI) } } - -func verifyMeasurements(c DPEClient, t *testing.T, handle *ContextHandle, expectedCurrent []byte, expectedCumulative []byte) { - handle, tcbInfo, err := getTcbInfoForHandle(c, handle) - if err != nil { - t.Fatal(err) - } - - // Check that the last TcbInfo current/cumulative are as expected - current := tcbInfo.Fwids[0].Digest - cumulative := tcbInfo.Fwids[1].Digest - if !bytes.Equal(current, expectedCurrent) { - t.Errorf("[ERROR]: Unexpected TCI_CURRENT digest, want %v but got %v", expectedCurrent, current) - } - - if !bytes.Equal(cumulative, expectedCumulative) { - t.Errorf("[ERROR]: Unexpected cumulative TCI value, want %v but got %v", expectedCumulative, cumulative) - } -} diff --git a/verification/getCertificateChain.go b/verification/getCertificateChain.go index aa008462..99784d8d 100644 --- a/verification/getCertificateChain.go +++ b/verification/getCertificateChain.go @@ -103,21 +103,29 @@ func checkCertificateChain(t *testing.T, certData []byte) []*x509.Certificate { // Build certificate chain and calls to validateSignature on each chain. func validateCertChain(t *testing.T, certChain []*x509.Certificate) { t.Helper() + var err error certsToProcess := certChain // Remove unhandled critical extensions reported by x509 but defined in spec - removeTcgDiceCriticalExtensions(t, certsToProcess) + if err = removeTcgDiceCriticalExtensions(certsToProcess); err != nil { + t.Errorf("[ERROR]: %v", err) + } // Remove unhandled extended key usages reported by x509 but defined in spec - removeTcgDiceExtendedKeyUsages(t, certsToProcess) + if err = removeTcgDiceExtendedKeyUsages(certsToProcess); err != nil { + t.Errorf("[ERROR]: %v", err) + } // Build verify options - opts := buildVerifyOptions(t, certChain) + opts, err := buildVerifyOptions(certChain) + if err != nil { + t.Errorf("[ERROR]: %v", err) + } // Certificate chain validation for each intermediate certificate for _, cert := range certChain { - chains, err := cert.Verify(opts) + chains, err := cert.Verify(*opts) if err != nil { t.Errorf("[ERROR]: Error in Certificate Chain of %s: %s", cert.Subject, err.Error()) } diff --git a/verification/simulator.go b/verification/simulator.go index 1e3c47f6..9933835a 100644 --- a/verification/simulator.go +++ b/verification/simulator.go @@ -253,6 +253,11 @@ func GetSimulatorTargets() []TestTarget { getTestTarget([]string{"AutoInit", "Simulation", "X509", "Csr", "IsCA", "RotateContext", "ExtendTci", "IsSymmetric"}), AllTestCases, }, + { + "CertifyKey_TcbValidation", + getTestTarget([]string{"AutoInit", "Simulation", "X509", "Csr", "IsCA", "RotateContext", "ExtendTci"}), + []TestCase{DiceTcbValidationTestCase, DiceTcbValidationSimulationTestCase}, + }, { "GetProfile_Simulation", getTestTarget([]string{"Simulation"}), diff --git a/verification/verification.go b/verification/verification.go index 0ccea29e..c002798a 100644 --- a/verification/verification.go +++ b/verification/verification.go @@ -44,14 +44,22 @@ var CertifyKeyTestCase = TestCase{ "CertifyKey", TestCertifyKey, []string{"AutoInit", "X509", "IsCA"}, } +// CertifyKeySimulationTestCase tests CertifyKey on Simulation mode contexts +var CertifyKeySimulationTestCase = TestCase{ + "CertifyKeySimulation", TestCertifyKeySimulation, []string{"AutoInit", "Simulation", "X509", "IsCA"}, +} + // CertifyKeyCsrTestCase tests CertifyKey with type = CSR var CertifyKeyCsrTestCase = TestCase{ "CertifyKeyCsr", TestCertifyKeyCsr, []string{"AutoInit", "Csr", "IsCA"}, } -// CertifyKeySimulationTestCase tests CertifyKey on Simulation mode contexts -var CertifyKeySimulationTestCase = TestCase{ - "CertifyKeySimulation", TestCertifyKeySimulation, []string{"AutoInit", "Simulation", "X509", "IsCA"}, +var DiceTcbValidationTestCase = TestCase{ + "CheckDiceTcbInfo", TestDiceTcbInfo, []string{"AutoInit", "X509", "IsCA", "RotateContext", "ExtendTci"}, +} + +var DiceTcbValidationSimulationTestCase = TestCase{ + "CheckDiceTcbInfoInSimulationMode", TestDiceTcbInfoSimulation, []string{"AutoInit", "Simulation", "X509", "IsCA", "RotateContext", "ExtendTci"}, } // GetCertificateChainTestCase tests GetCertificateChain