mirror of
https://github.com/google/go-attestation.git
synced 2025-06-22 16:39:06 +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:
@ -3,6 +3,7 @@ package attest
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"errors"
|
"errors"
|
||||||
@ -20,6 +21,8 @@ import (
|
|||||||
const (
|
const (
|
||||||
// minRSABits is the minimum accepted bit size of an RSA key.
|
// minRSABits is the minimum accepted bit size of an RSA key.
|
||||||
minRSABits = 2048
|
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
|
// activationSecretLen is the size in bytes of the generated secret
|
||||||
// which is generated for credential activation.
|
// which is generated for credential activation.
|
||||||
activationSecretLen = 32
|
activationSecretLen = 32
|
||||||
@ -115,6 +118,12 @@ func (p *ActivationParameters) checkTPM20AKParameters() error {
|
|||||||
if pub.RSAParameters.KeyBits < minRSABits {
|
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)
|
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:
|
default:
|
||||||
return fmt.Errorf("public key of alg 0x%x not supported", pub.Type)
|
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.
|
// 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()}
|
pk := rsa.PublicKey{E: int(pub.RSAParameters.Exponent()), N: pub.RSAParameters.Modulus()}
|
||||||
signHash, err := pub.RSAParameters.Sign.Hash.Hash()
|
signHash, err := pub.RSAParameters.Sign.Hash.Hash()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -188,6 +208,40 @@ func (p *ActivationParameters) checkTPM20AKParameters() error {
|
|||||||
return nil
|
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
|
// Generate returns a credential activation challenge, which can be provided
|
||||||
// to the TPM to verify the AK parameters given are authentic & the AK
|
// to the TPM to verify the AK parameters given are authentic & the AK
|
||||||
// is present on the same TPM as the EK.
|
// 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)
|
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 (
|
const (
|
||||||
ECDSA Algorithm = "ECDSA"
|
ECDSA Algorithm = "ECDSA"
|
||||||
RSA Algorithm = "RSA"
|
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.
|
// KeyConfig encapsulates parameters for minting keys.
|
||||||
@ -88,6 +95,24 @@ var defaultConfig = &KeyConfig{
|
|||||||
Size: 256,
|
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.
|
// Public returns the public key corresponding to the private key.
|
||||||
func (k *Key) Public() crypto.PublicKey {
|
func (k *Key) Public() crypto.PublicKey {
|
||||||
return k.pub
|
return k.pub
|
||||||
|
@ -190,6 +190,8 @@ func (k *AK) Certify(tpm *TPM, handle interface{}) (*CertificationParameters, er
|
|||||||
|
|
||||||
// AKConfig encapsulates parameters for minting keys.
|
// AKConfig encapsulates parameters for minting keys.
|
||||||
type AKConfig struct {
|
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.
|
// 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.
|
// If nil, the default SRK (i.e. RSA with handle 0x81000001) is assumed.
|
||||||
// Supported only by TPM 2.0 on Linux.
|
// Supported only by TPM 2.0 on Linux.
|
||||||
|
@ -131,12 +131,13 @@ func TestAKCreateAndLoad(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer loaded.Close(tpm)
|
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.Error("Original & loaded AK public blobs did not match.")
|
||||||
t.Logf("Original = %v", k1.public)
|
t.Logf("Original = %v", k1.Public)
|
||||||
t.Logf("Loaded = %v", k2.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.
|
// 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
|
var kh uintptr
|
||||||
utf16Name, err := windows.UTF16FromString(name)
|
utf16Name, err := windows.UTF16FromString(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
utf16RSA, err := windows.UTF16FromString("RSA")
|
utf16Alg, err := windows.UTF16FromString(string(alg))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a persistent RSA key of the specified name.
|
// 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 r != 0 {
|
||||||
if tpmErr := maybeWinErr(r); tpmErr != nil {
|
if tpmErr := maybeWinErr(r); tpmErr != nil {
|
||||||
msg = tpmErr
|
msg = tpmErr
|
||||||
@ -477,7 +477,7 @@ func (h *winPCP) NewAK(name string) (uintptr, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
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)
|
r, _, msg = nCryptSetProperty.Call(kh, uintptr(unsafe.Pointer(&utf16Length[0])), uintptr(unsafe.Pointer(&length)), unsafe.Sizeof(length), 0)
|
||||||
if r != 0 {
|
if r != 0 {
|
||||||
if tpmErr := maybeWinErr(r); tpmErr != nil {
|
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) {
|
func (t *windowsTPM) newAK(opts *AKConfig) (*AK, error) {
|
||||||
nameHex := make([]byte, 5)
|
var name string
|
||||||
if n, err := rand.Read(nameHex); err != nil || n != len(nameHex) {
|
var alg Algorithm
|
||||||
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)
|
|
||||||
|
|
||||||
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("pcp failed to mint attestation key: %v", err)
|
return nil, fmt.Errorf("pcp failed to mint attestation key: %v", err)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user