Support ECDSA based AK's on Windows (#415)
Some checks failed
CodeQL / Analyze (go) (push) Has been cancelled
Test / test-linux (1.24.x) (push) Has been cancelled
Test / test-linux-tpm12 (1.24.x) (push) Has been cancelled
Test / test-macos (1.24.x) (push) Has been cancelled
Test / test-windows (1.24.x) (push) Has been cancelled

This commit is contained in:
Nithin Sade 2025-03-27 10:15:13 -07:00 committed by GitHub
parent c5d6b1e758
commit ae4b8b8d16
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 139 additions and 14 deletions

View File

@ -3,6 +3,7 @@ package attest
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"errors"
@ -20,6 +21,8 @@ import (
const (
// minRSABits is the minimum accepted bit size of an RSA key.
minRSABits = 2048
// minECCBits is the minimum accepted bit size of an ECC key.
minECCBits = 256
// activationSecretLen is the size in bytes of the generated secret
// which is generated for credential activation.
activationSecretLen = 32
@ -115,6 +118,12 @@ func (p *ActivationParameters) checkTPM20AKParameters() error {
if pub.RSAParameters.KeyBits < minRSABits {
return fmt.Errorf("attestation key too small: must be at least %d bits but was %d bits", minRSABits, pub.RSAParameters.KeyBits)
}
case tpm2.AlgECC:
if len(pub.ECCParameters.Point.XRaw)*8 < minECCBits {
return fmt.Errorf("attestation key too small: must be at least %d bits but was %d bits", minECCBits, len(pub.ECCParameters.Point.XRaw)*8)
} else if len(pub.ECCParameters.Point.YRaw)*8 < minECCBits {
return fmt.Errorf("attestation key too small: must be at least %d bits but was %d bits", minECCBits, len(pub.ECCParameters.Point.YRaw)*8)
}
default:
return fmt.Errorf("public key of alg 0x%x not supported", pub.Type)
}
@ -160,6 +169,17 @@ func (p *ActivationParameters) checkTPM20AKParameters() error {
}
// Check the signature over the attestation data verifies correctly.
switch pub.Type {
case tpm2.AlgRSA:
return verifyRSASignature(pub, p)
case tpm2.AlgECC:
return verifyECDSASignature(pub, p)
default:
return fmt.Errorf("public key of alg 0x%x not supported", pub.Type)
}
}
func verifyRSASignature(pub tpm2.Public, p *ActivationParameters) error {
pk := rsa.PublicKey{E: int(pub.RSAParameters.Exponent()), N: pub.RSAParameters.Modulus()}
signHash, err := pub.RSAParameters.Sign.Hash.Hash()
if err != nil {
@ -188,6 +208,40 @@ func (p *ActivationParameters) checkTPM20AKParameters() error {
return nil
}
func verifyECDSASignature(pub tpm2.Public, p *ActivationParameters) error {
key, err := pub.Key()
if err != nil {
return nil
}
pk, ok := key.(*ecdsa.PublicKey)
if !ok {
return fmt.Errorf("expected *ecdsa.PublicKey, got %T", key)
}
signHash, err := pub.ECCParameters.Sign.Hash.Hash()
if err != nil {
return err
}
hsh := signHash.New()
_, err = hsh.Write(p.AK.CreateAttestation)
if err != nil {
return err
}
if len(p.AK.CreateSignature) < 8 {
return fmt.Errorf("signature invalid: length of %d is shorter than 8", len(p.AK.CreateSignature))
}
sig, err := tpm2.DecodeSignature(bytes.NewBuffer(p.AK.CreateSignature))
if err != nil {
return fmt.Errorf("DecodeSignature() failed: %v", err)
}
if !ecdsa.Verify(pk, hsh.Sum(nil), sig.ECC.R, sig.ECC.S) {
return fmt.Errorf("unable to verify attestation for ecdsa credential")
}
return nil
}
// Generate returns a credential activation challenge, which can be provided
// to the TPM to verify the AK parameters given are authentic & the AK
// is present on the same TPM as the EK.

View File

@ -67,3 +67,33 @@ func TestActivationTPM20(t *testing.T) {
t.Fatalf("secret = %v, want %v", got, want)
}
}
func TestECCActivationTPM20(t *testing.T) {
priv := ekCertSigner(t)
rand := rand.New(rand.NewSource(123456))
// These parameters represent an ECC P256 AK generated on a real-world,
// Google vTPM.
params := ActivationParameters{
TPMVersion: TPMVersion20,
AK: AttestationParameters{
Public: decodeBase64("ACMACwAFBHIAIJ3/y/NsODrmmfuYaNxty4nXFTiEvigDkiwSQVi/rSKuABAAGAAEAAMAEAAgydcfFWW6O7PjW9vnOE4IDx3545TxylD1iVHP8MIFI78AIJuD/QM9EbqM+3SEl7PgiXlWV1NhmvnmE2AHiEfI/hUn", t),
CreateData: decodeBase64("AAAAAAAg47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUBAAsAIgALLEyPGewwEIKqPNw9Lx7QXsfp0MsOZFt4EzHFT4tSXekAIgALf7O+OxqTNzTuIhi1YGQoulPZoyxsNKHpBgT4dHqT3+UAAA==", t),
CreateAttestation: decodeBase64("/1RDR4AaACIAC8Uqr5OAkfcyLJ2gLU2oSvIoPi8XdoLHGdpS5h/JJz8aAAAAAAAEXezQqarG49k44bBnAUna81DZr1xhACIAC3AoqIzDrusUtdH3uAwqbqUrybtu35H1XPQDyLBHVGF+ACCKEKpwL1LFbw/IG+vtJ6CtXHIYBhVWyrkAqYLkHleYMw==", t),
CreateSignature: decodeBase64("ABgABAAgbTEBcZvjb9uEEYZCiSqPh/XO0BZQS+egnJ8WKtpSbmkAIDNgvF9iyiOCvd5480hOCzjRj7GP3YZ3XqjEVvN3Q3Ca", t),
},
EK: &rsa.PublicKey{
E: priv.E,
N: priv.N,
},
Rand: rand,
}
secret, _, err := params.Generate()
if err != nil {
t.Fatalf("Generate() returned err: %v", err)
}
if got, want := secret, decodeBase64("0vhS7HtORX9uf/iyQ8Sf9WkpJuoJ1olCfTjSZuyNNxY=", t); !bytes.Equal(got, want) {
t.Fatalf("secret = %v, want %v", got, want)
}
}

View File

@ -63,6 +63,13 @@ type Algorithm string
const (
ECDSA Algorithm = "ECDSA"
RSA Algorithm = "RSA"
// Windows specific ECDSA CNG algorithm identifiers.
// NOTE: Using ECDSA will default to ECDSA_P256.
// Ref: https://learn.microsoft.com/en-us/windows/win32/SecCNG/cng-algorithm-identifiers
ECDSA_P256 Algorithm = "ECDSA_P256"
ECDSA_P384 Algorithm = "ECDSA_P384"
ECDSA_P521 Algorithm = "ECDSA_P521"
)
// KeyConfig encapsulates parameters for minting keys.
@ -88,6 +95,24 @@ var defaultConfig = &KeyConfig{
Size: 256,
}
// Size returns the bit size associated with an algorithm.
func (a Algorithm) Size() int {
switch a {
case RSA:
return 2048
case ECDSA:
return 256
case ECDSA_P256:
return 256
case ECDSA_P384:
return 384
case ECDSA_P521:
return 521
default:
return 0
}
}
// Public returns the public key corresponding to the private key.
func (k *Key) Public() crypto.PublicKey {
return k.pub

View File

@ -190,6 +190,8 @@ func (k *AK) Certify(tpm *TPM, handle interface{}) (*CertificationParameters, er
// AKConfig encapsulates parameters for minting keys.
type AKConfig struct {
// Optionally set unique name for AK on Windows.
Name string
// Parent describes the Storage Root Key that will be used as a parent.
// If nil, the default SRK (i.e. RSA with handle 0x81000001) is assumed.
// Supported only by TPM 2.0 on Linux.

View File

@ -131,12 +131,13 @@ func TestAKCreateAndLoad(t *testing.T) {
}
defer loaded.Close(tpm)
k1, k2 := ak.ak.(*wrappedKey20), loaded.ak.(*wrappedKey20)
k1 := ak.ak.attestationParameters()
k2 := loaded.ak.attestationParameters()
if !bytes.Equal(k1.public, k2.public) {
if !bytes.Equal(k1.Public, k2.Public) {
t.Error("Original & loaded AK public blobs did not match.")
t.Logf("Original = %v", k1.public)
t.Logf("Loaded = %v", k2.public)
t.Logf("Original = %v", k1.Public)
t.Logf("Loaded = %v", k2.Public)
}
})
}

View File

@ -453,19 +453,19 @@ func getPCPCerts(hProv uintptr, propertyName string) ([][]byte, error) {
}
// NewAK creates a persistent attestation key of the specified name.
func (h *winPCP) NewAK(name string) (uintptr, error) {
func (h *winPCP) NewAK(name string, alg Algorithm) (uintptr, error) {
var kh uintptr
utf16Name, err := windows.UTF16FromString(name)
if err != nil {
return 0, err
}
utf16RSA, err := windows.UTF16FromString("RSA")
utf16Alg, err := windows.UTF16FromString(string(alg))
if err != nil {
return 0, err
}
// Create a persistent RSA key of the specified name.
r, _, msg := nCryptCreatePersistedKey.Call(h.hProv, uintptr(unsafe.Pointer(&kh)), uintptr(unsafe.Pointer(&utf16RSA[0])), uintptr(unsafe.Pointer(&utf16Name[0])), 0, 0)
r, _, msg := nCryptCreatePersistedKey.Call(h.hProv, uintptr(unsafe.Pointer(&kh)), uintptr(unsafe.Pointer(&utf16Alg[0])), uintptr(unsafe.Pointer(&utf16Name[0])), 0, 0)
if r != 0 {
if tpmErr := maybeWinErr(r); tpmErr != nil {
msg = tpmErr
@ -477,7 +477,7 @@ func (h *winPCP) NewAK(name string) (uintptr, error) {
if err != nil {
return 0, err
}
var length uint32 = 2048
var length uint32 = uint32(alg.Size())
r, _, msg = nCryptSetProperty.Call(kh, uintptr(unsafe.Pointer(&utf16Length[0])), uintptr(unsafe.Pointer(&length)), unsafe.Sizeof(length), 0)
if r != 0 {
if tpmErr := maybeWinErr(r); tpmErr != nil {

View File

@ -287,13 +287,26 @@ func decryptCredential(secretKey, blob []byte) ([]byte, error) {
}
func (t *windowsTPM) newAK(opts *AKConfig) (*AK, error) {
nameHex := make([]byte, 5)
if n, err := rand.Read(nameHex); err != nil || n != len(nameHex) {
return nil, fmt.Errorf("rand.Read() failed with %d/%d bytes read and error: %v", n, len(nameHex), err)
}
name := fmt.Sprintf("ak-%x", nameHex)
var name string
var alg Algorithm
kh, err := t.pcp.NewAK(name)
if opts != nil && opts.Name != "" {
name = opts.Name
} else {
nameHex := make([]byte, 5)
if n, err := rand.Read(nameHex); err != nil || n != len(nameHex) {
return nil, fmt.Errorf("rand.Read() failed with %d/%d bytes read and error: %v", n, len(nameHex), err)
}
name = fmt.Sprintf("ak-%x", nameHex)
}
if opts != nil && opts.Algorithm != "" {
alg = opts.Algorithm
} else {
// Default to RSA based AK.
alg = RSA
}
kh, err := t.pcp.NewAK(name, alg)
if err != nil {
return nil, fmt.Errorf("pcp failed to mint attestation key: %v", err)
}