diff --git a/verification/certifyKey.go b/verification/certifyKey.go index 126478a3..15e07b3c 100755 --- a/verification/certifyKey.go +++ b/verification/certifyKey.go @@ -4,11 +4,14 @@ package verification import ( "bytes" + "crypto/ecdsa" + "crypto/elliptic" "crypto/x509" "encoding/asn1" "encoding/binary" "encoding/pem" "fmt" + "math/big" "reflect" "testing" "time" @@ -117,18 +120,23 @@ const ( type TcgMultiTcbInfo = []DiceTcbInfo +type CertifyKeyParams struct { + Label []byte + Flags CertifyKeyFlags +} + func TestCertifyKey(d TestDPEInstance, c DPEClient, t *testing.T) { testCertifyKey(d, c, t, false) } -func TestCertifyKey_SimulationMode(d TestDPEInstance, c DPEClient, t *testing.T) { +func TestCertifyKeySimulation(d TestDPEInstance, c DPEClient, t *testing.T) { testCertifyKey(d, c, t, true) } // Ignores critical extensions that are unknown to x509 package // but atleast defined in DPE certificate profile specification. // UnhandledCriticalExtensions may have only custom extensions mentioned in spec -// unknownExtnMap collects extensions unknown to both x59 and the DICE certificate profiles 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() @@ -158,6 +166,11 @@ func removeTcgDiceCriticalExtensions(t *testing.T, certs []*x509.Certificate) { } } +// Ignores extended key usages that are unknown to x509 package +// but atleast 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{} @@ -218,7 +231,7 @@ func checkCertifyKeyTcgUeidExtension(t *testing.T, c *x509.Certificate, label [] } } -// Check whether certificate extended key usage is as per spec +// 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 @@ -274,7 +287,7 @@ func checkCertifyKeyExtendedKeyUsages(t *testing.T, c *x509.Certificate) (*TcgMu return multiTcbInfo, err } -// Check for KeyUsage Extension as per spec +// 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, c *x509.Certificate) { @@ -296,25 +309,25 @@ func checkCertifyKeyExtensions(t *testing.T, c *x509.Certificate) { } -// Validate basic constraints in certificate returned by CertifyKey command +// 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, c *x509.Certificate, flags uint32) { +func checkCertifyKeyBasicConstraints(t *testing.T, c *x509.Certificate, flags CertifyKeyFlags) { t.Helper() flagsBuf := &bytes.Buffer{} binary.Write(flagsBuf, binary.LittleEndian, flags) - flagIsCA := uint32(CertifyAddIsCA)&flags != 0 + flagIsCA := CertifyAddIsCA&flags != 0 if flagIsCA != c.IsCA { t.Errorf("[ERROR]: ADD_IS_CA is set to %v but the basic constraint IsCA is set to %v", flagIsCA, c.IsCA) } } -// Validate X509 fields in certificate returned by CertifyKey command. -func validateCertifyKeyCert(t *testing.T, c *x509.Certificate, flags uint32, label []byte) { +// Validates X509 fields in certificate returned by CertifyKey command. +func validateCertifyKeyCert(t *testing.T, c *x509.Certificate, flags CertifyKeyFlags, label []byte) { t.Helper() // Check for basic constraints extension @@ -337,6 +350,7 @@ func validateCertifyKeyCert(t *testing.T, c *x509.Certificate, flags uint32, lab } } +// Parses X509 certificate func checkCertificateStructure(t *testing.T, certBytes []byte) *x509.Certificate { t.Helper() failed := false @@ -411,44 +425,48 @@ func checkCertificateStructure(t *testing.T, certBytes []byte) *x509.Certificate return x509Cert } -func testCertifyKey(d TestDPEInstance, client DPEClient, t *testing.T, simulation bool) { - ctx := getInitialContextHandle(d, client, t, simulation) +func testCertifyKey(d TestDPEInstance, c DPEClient, t *testing.T, simulation bool) { + handle := getInitialContextHandle(d, c, t, simulation) defer func() { if simulation { - client.DestroyContext(ctx, DestroyDescendants) + c.DestroyContext(handle, DestroyDescendants) } }() - type Params struct { - Label []byte - Flags CertifyKeyFlags - } - profile, err := GetTransportProfile(d) if err != nil { t.Fatalf("Could not get profile: %v", err) } digestLen := profile.GetDigestSize() + var hashAlg asn1.ObjectIdentifier + if digestLen == 32 { + hashAlg = OidSHA256 + } else if digestLen == 48 { + hashAlg = OidSHA384 + } else { + t.Fatal("Unknown Hash Algorithm") + } + seqLabel := make([]byte, digestLen) for i := range seqLabel { seqLabel[i] = byte(i) } - certifyKeyParams := []Params{ + certifyKeyParams := []CertifyKeyParams{ {Label: make([]byte, digestLen), Flags: CertifyKeyFlags(0)}, {Label: seqLabel, Flags: CertifyKeyFlags(0)}, } for _, params := range certifyKeyParams { // Get DPE leaf certificate from CertifyKey - certifyKeyResp, err := client.CertifyKey(ctx, params.Label, CertifyKeyX509, params.Flags) + 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 := client.GetCertificateChain() + certChainBytes, err := c.GetCertificateChain() if err != nil { t.Fatalf("[FATAL]: Could not get Certificate Chain: %v", err) } @@ -459,8 +477,14 @@ func testCertifyKey(d TestDPEInstance, client DPEClient, t *testing.T, simulatio leafCert := checkCertificateStructure(t, leafCertBytes) certChain := checkCertificateChain(t, certChainBytes) + // Check default context handle is unchanged + checkCertifyKeyRespHandle(*certifyKeyResp, t, handle) + + // Check key returned in command response against certificate + checkCertifyKeyResponse(t, leafCert, *certifyKeyResp, hashAlg) + // Validate that all X.509 fields conform with the format defined in the DPE iRoT profile - validateCertifyKeyCert(t, leafCert, uint32(params.Flags), params.Label) + validateCertifyKeyCert(t, leafCert, params.Flags, params.Label) // Ensure full certificate chain has valid signatures // This also checks certificate lifetime, signatures as part of cert chain validation @@ -469,13 +493,12 @@ func testCertifyKey(d TestDPEInstance, client DPEClient, t *testing.T, simulatio // Reassign handle for simulation mode. // However, this does not impact in default mode because // same default context handle is returned in default mode. - ctx = &certifyKeyResp.Handle - - // TODO: When DeriveChild is implemented, call it here to add more TCIs and call CertifyKey again. + handle = &certifyKeyResp.Handle } + // TODO: When DeriveChild is implemented, call it here to add more TCIs and call CertifyKey again. } -// Build certificate chain and calls to validateSignature on each chain. +// Builds and verifies certificate chain. func validateLeafCertChain(t *testing.T, certChain []*x509.Certificate, leafCert *x509.Certificate) { t.Helper() certsToProcess := []*x509.Certificate{leafCert} @@ -502,6 +525,7 @@ func validateLeafCertChain(t *testing.T, certChain []*x509.Certificate, leafCert } } +// Builds Certificate chain verifier parameters. func buildVerifyOptions(t *testing.T, certChain []*x509.Certificate) x509.VerifyOptions { roots := x509.NewCertPool() intermediates := x509.NewCertPool() @@ -526,6 +550,7 @@ func buildVerifyOptions(t *testing.T, certChain []*x509.Certificate) x509.Verify return opts } +// Gets KeyUsage bitmap and returns as list of KeyUsage name strings. func getKeyUsageNames(keyUsage x509.KeyUsage) []string { keyUsageNames := []string{} @@ -567,3 +592,58 @@ func getKeyUsageNames(keyUsage x509.KeyUsage) []string { return keyUsageNames } + +// Checks CertifyKey command response against public key extracted from certificate returned in response +func checkCertifyKeyResponse(t *testing.T, x509Cert *x509.Certificate, response CertifiedKey, hashAlg asn1.ObjectIdentifier) { + var err error + + publicKeyDer, err := x509.MarshalPKIXPublicKey(x509Cert.PublicKey) + if err != nil { + t.Fatalf("[FATAL]: Could not marshal pub key: %v", err) + } + + // Parse the DER-encoded public key + pubKeyInCert, err := x509.ParsePKIXPublicKey(publicKeyDer) + if err != nil { + t.Fatalf("[FATAL]: Failed to parse DER-encoded public key: %v", err) + } + + if _, ok := pubKeyInCert.(*ecdsa.PublicKey); !ok { + t.Fatal("[FATAL]: Public key is not a ecdsa key") + } + + var pubKeyInResponse ecdsa.PublicKey + + if hashAlg.Equal(OidSHA384) { + pubKeyInResponse = ecdsa.PublicKey{ + Curve: elliptic.P384(), + X: new(big.Int).SetBytes(response.Pub.X), + Y: new(big.Int).SetBytes(response.Pub.Y), + } + } else if hashAlg.Equal(OidSHA256) { + pubKeyInResponse = ecdsa.PublicKey{ + Curve: elliptic.P256(), + X: new(big.Int).SetBytes(response.Pub.X), + Y: new(big.Int).SetBytes(response.Pub.Y), + } + } else { + t.Errorf("[ERROR]: Unsupported hash algorithm.") + return + } + + if !(pubKeyInResponse.Equal(pubKeyInCert)) { + 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 CertifiedKey, t *testing.T, handle *ContextHandle) { + if *handle != 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) + } +} diff --git a/verification/sign.go b/verification/sign.go new file mode 100644 index 00000000..eb127ee4 --- /dev/null +++ b/verification/sign.go @@ -0,0 +1,170 @@ +// Licensed under the Apache-2.0 license + +package verification + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/x509" + "errors" + "math/big" + "testing" +) + +// Obtain and validate signature of asymmetric signing. +// Check whether the digital signature returned by Sign command can be verified +// using public key in signing key certificate returned by CertifyKey command. +// Inspite of the DPE profile supporting symmetric key, for symmetric signing it must be enabled +// explicitly in Sign command flags. Else asymmetric signing is used as default. +func TestAsymmetricSigning(d TestDPEInstance, c DPEClient, t *testing.T) { + useSimulation := false + handle := getInitialContextHandle(d, c, t, useSimulation) + // Get digest size + profile, err := GetTransportProfile(d) + if err != nil { + t.Fatalf("Could not get profile: %v", err) + } + + digestLen := profile.GetDigestSize() + + // Validate asymmetric signature generated + flags := SignFlags(0) + + seqLabel := make([]byte, digestLen) + for i := range seqLabel { + seqLabel[i] = byte(i) + } + + tbs := make([]byte, digestLen) + for i := range tbs { + tbs[i] = byte(i) + } + + signResp, err := c.Sign(handle, seqLabel, flags, tbs) + if err != nil { + t.Fatalf("[FATAL]: Error while signing %v", err) + } + + // Get signing key certificate using CertifyKey command + certifiedKey, err := c.CertifyKey(handle, seqLabel, CertifyKeyX509, CertifyKeyFlags(0)) + if err != nil { + t.Fatalf("[FATAL]: Could not CertifyKey: %v", err) + } + + // Check certificate structure + if _, err := x509.ParseCertificate(certifiedKey.Certificate); err != nil { + t.Fatalf("[FATAL]: Could not parse certificate using crypto/x509: %v", err) + } + + // Read public key + var ec elliptic.Curve + x := new(big.Int).SetBytes(certifiedKey.Pub.X) + y := new(big.Int).SetBytes(certifiedKey.Pub.Y) + + if digestLen == 32 { + ec = elliptic.P256() + } else if digestLen == 48 { + ec = elliptic.P384() + } + + publicKey := ecdsa.PublicKey{Curve: ec, X: x, Y: y} + + // Build Signature from bytes + r := new(big.Int).SetBytes(signResp.HmacOrSignatureR) + s := new(big.Int).SetBytes(signResp.SignatureS) + + // Verify Signature + valid := ecdsa.Verify(&publicKey, tbs, r, s) + if !valid { + t.Error("Signature Verification failed") + } +} + +// Check command fails in simulated context because this context does not allow signing. +// This is because simulation context does not allow using context's private key. +func TestSignSimulation(d TestDPEInstance, c DPEClient, t *testing.T) { + useSimulation := true + handle := getInitialContextHandle(d, c, t, useSimulation) + defer func() { + c.DestroyContext(handle, DestroyDescendants) + }() + + // Get digest size + profile, err := GetTransportProfile(d) + if err != nil { + t.Fatalf("Could not get profile: %v", err) + } + + digestLen := profile.GetDigestSize() + + if _, err := c.Sign(handle, make([]byte, digestLen), SignFlags(IsSymmetric), make([]byte, digestLen)); err == nil { + t.Fatalf("[FATAL]: Should return %q, but returned no error", StatusInvalidArgument) + } else if !errors.Is(err, StatusInvalidArgument) { + t.Fatalf("[FATAL]: Incorrect error type. Should return %q, but returned %q", StatusInvalidArgument, err) + } + + if _, err := c.Sign(handle, make([]byte, digestLen), SignFlags(0), make([]byte, digestLen)); err == nil { + t.Fatalf("[FATAL]: Should return %q, but returned no error", StatusInvalidArgument) + } else if !errors.Is(err, StatusInvalidArgument) { + t.Fatalf("[FATAL]: Incorrect error type. Should return %q, but returned %q", StatusInvalidArgument, err) + } +} + +// Obtain HMAC (symmetric signature) generated and compare for varying label inputs. +// Signature created is deterministic and depends on label passed to command. +// This is because label is used by DPE in symmetric key derivation. +// Invoking Sign command multiple times with same label and same content (TBS) should return same signature +// but it should return different signatures for different labels despite having the same content (To Be Signed content). +func TestSymmetricSigning(d TestDPEInstance, c DPEClient, t *testing.T) { + useSimulation := false + handle := getInitialContextHandle(d, c, t, useSimulation) + + // Get digest size + profile, err := GetTransportProfile(d) + if err != nil { + t.Fatalf("Could not get profile: %v", err) + } + + digestLen := profile.GetDigestSize() + label := make([]byte, digestLen) + for i := range label { + label[i] = byte(i) + } + + tbs := make([]byte, digestLen) + for i := range tbs { + tbs[i] = byte(i) + } + + signedData, err := c.Sign(handle, label, SignFlags(IsSymmetric), tbs) + if err != nil { + t.Fatalf("[FATAL]: Error while signing %v", err) + } + + // Rerun with same label and compare signature emitted. + signedDataWithSameLabel, err := c.Sign(handle, label, SignFlags(IsSymmetric), tbs) + if err != nil { + t.Fatalf("[FATAL]: Error while signing %v", err) + } + + // Symmetric sign only populates HmacOrSignatureR. SignatureS is all zeroes. + if !bytes.Equal(signedDataWithSameLabel.HmacOrSignatureR, signedData.HmacOrSignatureR) { + t.Errorf("[ERROR]: Signature varies for same label, want %v but got %v", signedData.HmacOrSignatureR, signedDataWithSameLabel.HmacOrSignatureR) + } + + // Rerun with different label, signature must change this time + newLabel := make([]byte, digestLen) + for i := range newLabel { + newLabel[i] = byte(0) + } + + signedDataWithDiffLabel, err := c.Sign(handle, newLabel, SignFlags(IsSymmetric), tbs) + if err != nil { + t.Fatalf("[FATAL]: Error while signing %v", err) + } + + if bytes.Equal(signedDataWithDiffLabel.HmacOrSignatureR, signedData.HmacOrSignatureR) { + t.Errorf("[ERROR]: Signature must vary for different label despite having same toBeSigned content, want new signature but got old %v", signedData.HmacOrSignatureR) + } +} diff --git a/verification/simulator.go b/verification/simulator.go index f86d6747..becb62ce 100644 --- a/verification/simulator.go +++ b/verification/simulator.go @@ -231,7 +231,7 @@ func GetSimulatorTargets() []TestTarget { }, { "DefaultSupport", - getTestTarget([]string{"AutoInit", "Simulation", "X509", "IsCA", "RotateContext", "ExtendTci"}), + getTestTarget([]string{"AutoInit", "Simulation", "X509", "IsCA", "RotateContext", "ExtendTci", "IsSymmetric"}), AllTestCases, }, { diff --git a/verification/verification.go b/verification/verification.go index 7b83fb46..5be5da0f 100644 --- a/verification/verification.go +++ b/verification/verification.go @@ -30,7 +30,7 @@ var CertifyKeyTestCase = TestCase{ "CertifyKey", TestCertifyKey, []string{"AutoInit", "X509", "IsCA"}, } var CertifyKeySimulationTestCase = TestCase{ - "CertifyKeySimulation", TestCertifyKey_SimulationMode, []string{"AutoInit", "Simulation", "X509", "IsCA"}, + "CertifyKeySimulation", TestCertifyKeySimulation, []string{"AutoInit", "Simulation", "X509", "IsCA"}, } var GetCertificateChainTestCase = TestCase{ "GetCertificateChain", TestGetCertificateChain, []string{"AutoInit", "X509"}, @@ -56,6 +56,15 @@ var UnsupportedCommand = TestCase{ var UnsupportedCommandFlag = TestCase{ "CheckSupportForCommmandFlag", TestUnsupportedCommandFlag, []string{"AutoInit", "RotateContext", "ExtendTci"}, } +var SignAsymmetricTestCase = TestCase{ + "Sign", TestAsymmetricSigning, []string{"AutoInit", "X509"}, +} +var SignSymmetricTestCase = TestCase{ + "SignSymmetric", TestSymmetricSigning, []string{"AutoInit", "IsSymmetric"}, +} +var SignSimulationTestCase = TestCase{ + "SignSimulation", TestSignSimulation, []string{"Simulation"}, +} var AllTestCases = []TestCase{ CertifyKeyTestCase, @@ -63,6 +72,9 @@ var AllTestCases = []TestCase{ GetCertificateChainTestCase, ExtendTCITestCase, ExtendDerivedTciTestCase, + SignAsymmetricTestCase, + SignSymmetricTestCase, + SignSimulationTestCase, GetProfileTestCase, InitializeContextTestCase, InitializeContextSimulationTestCase,