mirror of
https://github.com/google/go-attestation.git
synced 2025-04-14 14:36:44 +00:00
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
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:
parent
c5d6b1e758
commit
ae4b8b8d16
@ -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.
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user