From 8ff3569a11a9c1583d472de3620e539feb384ebf Mon Sep 17 00:00:00 2001 From: Tim Trippel Date: Mon, 7 Oct 2024 14:44:14 -0700 Subject: [PATCH] [se] add function to generate symmetric keys This adds a wrapper function to the Secure Element (SE) interface to derive symmetric keys from pre-loaded secret seeds on the SE. This function will back the DerriveSymmetricKey RPC available from the ProvisioningAppliance. This partially addresses #4. Signed-off-by: Tim Trippel --- src/spm/services/se.go | 24 +++++++ src/spm/services/se_pk11.go | 73 ++++++++++++++++++++- src/spm/services/se_pk11_test.go | 106 +++++++++++++++++++++++++++++-- 3 files changed, 193 insertions(+), 10 deletions(-) diff --git a/src/spm/services/se.go b/src/spm/services/se.go index e44c772..5396f04 100644 --- a/src/spm/services/se.go +++ b/src/spm/services/se.go @@ -8,6 +8,7 @@ package se import ( "crypto/x509" + "github.com/lowRISC/opentitan-provisioning/src/pk11" ) // Parameters for generating an RSA keypair. @@ -29,6 +30,20 @@ type CertInfo struct { WrappedKey, Iv, Cert []byte } +const ( + SymmetricKeyTypeRaw = iota + SymmetricKeyTypeHashedOtLcToken +) + +// Parameters for GenerateSymmetricKey(). +type SymmetricKeygenParams struct { + UseHighSecuritySeed bool + KeyType uint + SizeInBits uint + Sku string + Diversifier string +} + // SE is an interface representing a secure element, which may be implemented // by various hardware modules under the hood. // @@ -50,6 +65,15 @@ type SE interface { // successfully generated up until that point. GenerateKeyPairAndCert(caCert *x509.Certificate, params []SigningParams) ([]CertInfo, error) + // Generates symmetric keys. + // + // These keys are generated via the HKDF mechanism and may be used as: + // - Wafer Authentication Secrets, or + // - Lifecycle Tokens. + // + // Returns: slice of AESKey objects. + GenerateSymmetricKey(params []*SymmetricKeygenParams) ([]pk11.AESKey, error) + // GenerateRandom returns random data extracted from the HSM. GenerateRandom(length int) ([]byte, error) diff --git a/src/spm/services/se_pk11.go b/src/spm/services/se_pk11.go index 3500ffd..420f81c 100644 --- a/src/spm/services/se_pk11.go +++ b/src/spm/services/se_pk11.go @@ -53,8 +53,8 @@ func (q *sessionQueue) insert(s *pk11.Session) error { // getHandle returns a session from the queue and a release function to // get the session back into the queue. Recommended use: // -// session, release := s.getHandle() -// defer release() +// session, release := s.getHandle() +// defer release() // // Note: failing to call the release function can result into deadlocks // if the queue remains empty after calling the `insert` function. @@ -87,6 +87,12 @@ type HSMConfig struct { // KcaName is the KCA key label used to find the key in the HSM. KcaName string + // KHsksName is the HighSecKdfSeed key label used to find the key in the HSM. + KHsksName string + + // KLsksName is the LowSecKdfSeed key label used to find the key in the HSM. + KLsksName string + // hsmType contains the type of the HSM (SoftHSM or NetworkHSM) HSMType pk11.HSMType } @@ -101,7 +107,7 @@ type HSM struct { // // May be nil if those keys are not present and not used by any of the called // methods. - KG, KT, Kca []byte + KG, KT, Kca, KHsks, KLsks []byte // The PKCS#11 session we're working with. sessions *sessionQueue @@ -169,6 +175,18 @@ func NewHSM(cfg HSMConfig) (*HSM, error) { return nil, status.Errorf(codes.Internal, "fail to find KG key ID: %q, error: %v", cfg.KGName, err) } } + if cfg.KHsksName != "" { + hsm.KHsks, err = hsm.getKeyIDByLabel(session, pk11.ClassSecretKey, cfg.KHsksName) + if err != nil { + return nil, status.Errorf(codes.Internal, "fail to find KHsks key ID: %q, error: %v", cfg.KHsksName, err) + } + } + if cfg.KLsksName != "" { + hsm.KLsks, err = hsm.getKeyIDByLabel(session, pk11.ClassSecretKey, cfg.KLsksName) + if err != nil { + return nil, status.Errorf(codes.Internal, "fail to find KLsks key ID: %q, error: %v", cfg.KLsksName, err) + } + } return hsm, nil } @@ -314,3 +332,52 @@ func (h *HSM) GenerateKeyPairAndCert(caCert *x509.Certificate, params []SigningP return certs, nil } + +// GenerateSymmetricKey generates a symmetric key. +func (h *HSM) GenerateSymmetricKey(params []*SymmetricKeygenParams) ([]pk11.AESKey, error) { + session, release := h.sessions.getHandle() + defer release() + var symmetricKeys []pk11.AESKey + + for _, p := range params { + // Select the seed asset to use (High or Low security seed). + var seed pk11.SecretKey + var err error + if p.UseHighSecuritySeed { + seed, err = session.FindSecretKey(h.KHsks) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to get KHsks key object: %v", err) + } + } else { + seed, err = session.FindSecretKey(h.KLsks) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to get KLsks key object: %v", err) + } + } + + // Generate key from seed and extract. + seKey, err := seed.HKDFDeriveAES(crypto.SHA256, []byte(p.Sku), + []byte(p.Diversifier), p.SizeInBits, &pk11.KeyOptions{Extractable: true}) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed HKDFDeriveAES: %v", err) + } + + // Extract the key from the SE. + exportedKey, err := seKey.ExportKey() + if err != nil { + return nil, status.Errorf(codes.Internal, + "failed to extract symmetric key: %v", err) + } + + // Parse and format the key bytes. + keyBytes, ok := exportedKey.(pk11.AESKey) + if !ok { + return nil, status.Errorf(codes.Internal, + "failed to parse extracted symmetric key: %v", err) + } + // TODO: format it based on type (i.e. LC token or RAW) + symmetricKeys = append(symmetricKeys, keyBytes) + } + + return symmetricKeys, nil +} diff --git a/src/spm/services/se_pk11_test.go b/src/spm/services/se_pk11_test.go index 06bc6f7..45c279d 100644 --- a/src/spm/services/se_pk11_test.go +++ b/src/spm/services/se_pk11_test.go @@ -11,6 +11,9 @@ import ( "crypto/elliptic" "crypto/x509" "crypto/x509/pkix" + "encoding/hex" + "io" + "log" "math/rand" "testing" "time" @@ -30,34 +33,123 @@ import ( // // Returns the hsm, the (host-side) bytes of the KG, and the (host-side) bytes // of the KT. -func MakeHSM(t *testing.T) (*HSM, []byte, []byte) { +func MakeHSM(t *testing.T) (*HSM, []byte, []byte, []byte, []byte) { t.Helper() s := ts.GetSession(t) ts.Check(t, s.Login(pk11.NormalUser, ts.UserPin)) + // Initialize HSM with KG. global, err := s.GenerateAES(256, &pk11.KeyOptions{Extractable: true}) ts.Check(t, err) gUID, err := global.UID() ts.Check(t, err) - globalBytes, err := global.ExportKey() + globalKeyBytes, err := global.ExportKey() ts.Check(t, err) - secret := []byte("this is secret data for generating keys from") - transport, err := s.ImportKeyMaterial(secret, &pk11.KeyOptions{Extractable: true}) + // Initialize HSM with KT. + transportKeySeed := []byte("this is secret data for generating keys from") + transport, err := s.ImportKeyMaterial(transportKeySeed, &pk11.KeyOptions{Extractable: true}) ts.Check(t, err) tUID, err := transport.UID() ts.Check(t, err) + // Initialize HSM with KHsks. + hsKeySeed := []byte("high security KDF seed") + hsks, err := s.ImportKeyMaterial(hsKeySeed, &pk11.KeyOptions{Extractable: false}) + ts.Check(t, err) + hsksUID, err := hsks.UID() + ts.Check(t, err) + + // Initialize HSM with KLsks. + lsKeySeed := []byte("low security KDF seed") + lsks, err := s.ImportKeyMaterial(lsKeySeed, &pk11.KeyOptions{Extractable: false}) + ts.Check(t, err) + lsksUID, err := lsks.UID() + ts.Check(t, err) + + // Initialize session queue. numSessions := 1 sessions := newSessionQueue(numSessions) err = sessions.insert(s) ts.Check(t, err) - return &HSM{KG: gUID, KT: tUID, sessions: sessions}, []byte(globalBytes.(pk11.AESKey)), secret + return &HSM{KG: gUID, KT: tUID, KHsks: hsksUID, KLsks: lsksUID, sessions: sessions}, + []byte(globalKeyBytes.(pk11.AESKey)), + transportKeySeed, + hsKeySeed, + lsKeySeed +} + +func TestGenerateSymmKey(t *testing.T) { + hsm, _, _, hsKeySeed, lsKeySeed := MakeHSM(t) + + // Symmetric keygen parameters. + // test unlock token + testUnlockTokenParams := SymmetricKeygenParams{ + UseHighSecuritySeed: false, + KeyType: SymmetricKeyTypeRaw, + SizeInBits: 128, + Sku: "test sku", + Diversifier: "test_unlock", + } + // test exit token + testExitTokenParams := SymmetricKeygenParams{ + UseHighSecuritySeed: false, + KeyType: SymmetricKeyTypeRaw, + SizeInBits: 128, + Sku: "test sku", + Diversifier: "test_exit", + } + // RMA token + rmaTokenParams := SymmetricKeygenParams{ + UseHighSecuritySeed: true, + KeyType: SymmetricKeyTypeRaw, + SizeInBits: 128, + Sku: "test sku", + Diversifier: "rma: device_id", + } + // wafer authentication secret + wasParams := SymmetricKeygenParams{ + UseHighSecuritySeed: true, + KeyType: SymmetricKeyTypeRaw, + SizeInBits: 256, + Sku: "test sku", + Diversifier: "was", + } + params := []*SymmetricKeygenParams{ + &testUnlockTokenParams, + &testExitTokenParams, + &rmaTokenParams, + &wasParams, + } + + // Generate the actual keys (using the HSM). + keys, err := hsm.GenerateSymmetricKey(params) + ts.Check(t, err) + + // Check actual keys match those generated using the go crypto package. + for i, p := range params { + // Generate expected key. + var keyGenerator io.Reader + if p.UseHighSecuritySeed { + keyGenerator = hkdf.New(crypto.SHA256.New, hsKeySeed, []byte(p.Sku), []byte(p.Diversifier)) + } else { + keyGenerator = hkdf.New(crypto.SHA256.New, lsKeySeed, []byte(p.Sku), []byte(p.Diversifier)) + } + expected_key := make([]byte, len(keys[i])) + keyGenerator.Read(expected_key) + + // Check the actual and expected keys are equal. + log.Printf("Actual Key: %q", hex.EncodeToString(keys[i])) + log.Printf("Expected Key: %q", hex.EncodeToString(expected_key)) + if !bytes.Equal(keys[i], expected_key) { + t.Fatal("symmetric keygen failed") + } + } } func TestTransport(t *testing.T) { - hsm, kg, kt := MakeHSM(t) + hsm, kg, kt, _, _ := MakeHSM(t) key, err := hsm.DeriveAndWrapTransportSecret([]byte("my device id")) ts.Check(t, err) @@ -85,7 +177,7 @@ func CreateCAKeys(t *testing.T, hsm *HSM) (pk11.KeyPair, error) { } func TestGenerateCert(t *testing.T) { - hsm, kg, _ := MakeHSM(t) + hsm, kg, _, _, _ := MakeHSM(t) ca, err := CreateCAKeys(t, hsm) ts.Check(t, err)