Skip to content

Commit

Permalink
[se] add function to generate symmetric keys
Browse files Browse the repository at this point in the history
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 lowRISC#4.

Signed-off-by: Tim Trippel <[email protected]>
  • Loading branch information
timothytrippel committed Oct 7, 2024
1 parent 6adf4fd commit 8ff3569
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 10 deletions.
24 changes: 24 additions & 0 deletions src/spm/services/se.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package se

import (
"crypto/x509"
"github.com/lowRISC/opentitan-provisioning/src/pk11"
)

// Parameters for generating an RSA keypair.
Expand All @@ -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.
//
Expand All @@ -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)

Expand Down
73 changes: 70 additions & 3 deletions src/spm/services/se_pk11.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
106 changes: 99 additions & 7 deletions src/spm/services/se_pk11_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import (
"crypto/elliptic"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex"
"io"
"log"
"math/rand"
"testing"
"time"
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 8ff3569

Please sign in to comment.