diff --git a/attestation.go b/attestation.go index e759f96..3545224 100644 --- a/attestation.go +++ b/attestation.go @@ -2,6 +2,7 @@ package attestation import ( "encoding/asn1" + "fmt" ) // OIDKeyAttestationExtension is the key attestation extension. @@ -10,7 +11,7 @@ var OIDKeyAttestationExtension = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, // keyDescription reflects the ASN.1 data structure for KeyDescription. // // keyDescription ::= SEQUENCE { -// attestationVersion INTEGER, # KM2 value is 1. KM3 value is 2. KM4 value is 3. +// attestationVersion INTEGER, # Keymaster 2.0 = 1, 3.0 = 2, 4.0 = 3, 4.1 = 4; KeyMint 1.0 = 100, 2.0 = 200, 3.0 = 300, 4.0 = 400, 5.0 = 500. // attestationSecurityLevel SecurityLevel, // keymasterVersion INTEGER, // keymasterSecurityLevel SecurityLevel, @@ -67,8 +68,12 @@ func (v AttestationVersion) String() string { return "KeyMint version 2.0" case KAKeyMintVersion3: return "KeyMint version 3.0" + case KAKeyMintVersion4: + return "KeyMint version 4.0" + case KAKeyMintVersion5: + return "KeyMint version 5.0" default: - return "" + return fmt.Sprintf("unknown attestation version (%d)", uint(v)) } } @@ -80,6 +85,8 @@ const ( KAKeyMintVersion1 = 100 // KeyMint version 1.0 KAKeyMintVersion2 = 200 // KeyMint version 2.0 KAKeyMintVersion3 = 300 // KeyMint version 3.0 + KAKeyMintVersion4 = 400 // KeyMint version 4.0 + KAKeyMintVersion5 = 500 // KeyMint version 5.0 ) // KeymasterVersion is the version of the Keymaster or KeyMint hardware abstraction layer. @@ -106,8 +113,12 @@ func (v KeymasterVersion) String() string { return "KeyMint version 2.0" case KeyMintVersion3: return "KeyMint version 3.0" + case KeyMintVersion4: + return "KeyMint version 4.0" + case KeyMintVersion5: + return "KeyMint version 5.0" default: - return "" + return fmt.Sprintf("unknown keymaster version (%d)", uint(v)) } } @@ -121,6 +132,8 @@ const ( KeyMintVersion1 = 100 // KeyMint version 1.0 KeyMintVersion2 = 200 // KeyMint version 2.0 KeyMintVersion3 = 300 // KeyMint version 3.0 + KeyMintVersion4 = 400 // KeyMint version 4.0 + KeyMintVersion5 = 500 // KeyMint version 5.0 ) // SecurityLevel reflects the ASN.1 data structure for SecurityLevel. @@ -157,14 +170,19 @@ const ( // purpose [1] EXPLICIT SET OF INTEGER OPTIONAL, // algorithm [2] EXPLICIT INTEGER OPTIONAL, // keySize [3] EXPLICIT INTEGER OPTIONAL. +// blockMode [4] EXPLICIT SET OF INTEGER OPTIONAL, // digest [5] EXPLICIT SET OF INTEGER OPTIONAL, // padding [6] EXPLICIT SET OF INTEGER OPTIONAL, // ecCurve [10] EXPLICIT INTEGER OPTIONAL, +// mlDsaVariant [11] EXPLICIT INTEGER OPTIONAL, # KM5 // rsaPublicExponent [200] EXPLICIT INTEGER OPTIONAL, +// mgfDigest [203] EXPLICIT SET OF INTEGER OPTIONAL, // rollbackResistance [303] EXPLICIT NULL OPTIONAL, # KM4 +// earlyBootOnly [305] EXPLICIT NULL OPTIONAL, # KM4 // activeDateTime [400] EXPLICIT INTEGER OPTIONAL // originationExpireDateTime [401] EXPLICIT INTEGER OPTIONAL // usageExpireDateTime [402] EXPLICIT INTEGER OPTIONAL +// usageCountLimit [405] EXPLICIT INTEGER OPTIONAL, # KM4 // noAuthRequired [503] EXPLICIT NULL OPTIONAL, // userAuthType [504] EXPLICIT INTEGER OPTIONAL, // authTimeout [505] EXPLICIT INTEGER OPTIONAL, @@ -191,46 +209,57 @@ const ( // attestationIdModel [717] EXPLICIT OCTET_STRING OPTIONAL, # KM3 // vendorPatchLevel [718] EXPLICIT INTEGER OPTIONAL, # KM4 // bootPatchLevel [719] EXPLICIT INTEGER OPTIONAL, # KM4 +// deviceUniqueAttestation [720] EXPLICIT NULL OPTIONAL, # KM4 +// attestationIdSecondImei [723] EXPLICIT OCTET_STRING OPTIONAL, # KM3 +// moduleHash [724] EXPLICIT OCTET_STRING OPTIONAL, # KM4 // } type authorizationList struct { Raw asn1.RawContent - Purpose []int32 `asn1:"explicit,optional,omitempty,set,tag:1"` // [1] EXPLICIT SET OF INTEGER OPTIONAL, - Algorithm asn1.RawValue `asn1:"explicit,optional,tag:2"` // [2] EXPLICIT INTEGER OPTIONAL, - KeySize asn1.RawValue `asn1:"explicit,optional,tag:3"` // [3] EXPLICIT INTEGER OPTIONAL. - Digest []int `asn1:"explicit,optional,omitempty,set,tag:5"` // [5] EXPLICIT SET OF INTEGER OPTIONAL, - Padding []int `asn1:"explicit,optional,omitempty,set,tag:6"` // [6] EXPLICIT SET OF INTEGER OPTIONAL, - EcCurve asn1.RawValue `asn1:"explicit,optional,tag:10"` // [10] EXPLICIT INTEGER OPTIONAL, - RsaPublicExponent asn1.RawValue `asn1:"explicit,optional,tag:200"` // [200] EXPLICIT INTEGER OPTIONAL, - RollbackResistance asn1.RawValue `asn1:"explicit,optional,tag:303"` // [303] EXPLICIT NULL OPTIONAL, # KM4 - ActiveDateTime asn1.RawValue `asn1:"explicit,optional,tag:400"` // [400] EXPLICIT INTEGER OPTIONAL - OriginationExpireDateTime asn1.RawValue `asn1:"explicit,optional,tag:401"` // [401] EXPLICIT INTEGER OPTIONAL - UsageExpireDateTime asn1.RawValue `asn1:"explicit,optional,tag:402"` // [402] EXPLICIT INTEGER OPTIONAL - NoAuthRequired asn1.RawValue `asn1:"explicit,optional,tag:503"` // [503] EXPLICIT NULL OPTIONAL, - UserAuthType asn1.RawValue `asn1:"explicit,optional,tag:504"` // [504] EXPLICIT INTEGER OPTIONAL, - AuthTimeout asn1.RawValue `asn1:"explicit,optional,tag:505"` // [505] EXPLICIT INTEGER OPTIONAL, - AllowWhileOnBody asn1.RawValue `asn1:"explicit,optional,tag:506"` // [506] EXPLICIT NULL OPTIONAL, - TrustedUserPresenceRequired asn1.RawValue `asn1:"explicit,optional,tag:507"` // [507] EXPLICIT NULL OPTIONAL, # KM4 - TrustedConfirmationRequired asn1.RawValue `asn1:"explicit,optional,tag:508"` // [508] EXPLICIT NULL OPTIONAL, # KM4 - UnlockedDeviceRequired asn1.RawValue `asn1:"explicit,optional,tag:509"` // [509] EXPLICIT NULL OPTIONAL, # KM4 - AllApplications asn1.RawValue `asn1:"explicit,optional,tag:600"` // [600] EXPLICIT NULL OPTIONAL, - ApplicationId []byte `asn1:"explicit,optional,omitempty,tag:601"` // [601] EXPLICIT OCTET_STRING OPTIONAL, - CreationDateTime asn1.RawValue `asn1:"explicit,optional,tag:701"` // [701] EXPLICIT INTEGER OPTIONAL, - Origin asn1.RawValue `asn1:"explicit,optional,tag:702"` // [702] EXPLICIT INTEGER OPTIONAL, - RollbackResistant asn1.RawValue `asn1:"explicit,optional,tag:703"` // [703] EXPLICIT NULL OPTIONAL, # KM2 and KM3 only. - RootOfTrust asn1.RawValue `asn1:"explicit,optional,tag:704"` // [704] EXPLICIT RootOfTrust OPTIONAL, - OsVersion asn1.RawValue `asn1:"explicit,optional,tag:705"` // [705] EXPLICIT INTEGER OPTIONAL, - OsPatchLevel asn1.RawValue `asn1:"explicit,optional,tag:706"` // [706] EXPLICIT INTEGER OPTIONAL, - AttestationApplicationId asn1.RawContent `asn1:"explicit,optional,tag:709"` // [709] EXPLICIT OCTET_STRING OPTIONAL, # KM3 - AttestationIdBrand []byte `asn1:"explicit,optional,omitempty,tag:710"` // [710] EXPLICIT OCTET_STRING OPTIONAL, # KM3 - AttestationIdDevice []byte `asn1:"explicit,optional,omitempty,tag:711"` // [711] EXPLICIT OCTET_STRING OPTIONAL, # KM3 - AttestationIdProduct []byte `asn1:"explicit,optional,omitempty,tag:712"` // [712] EXPLICIT OCTET_STRING OPTIONAL, # KM3 - AttestationIdSerial []byte `asn1:"explicit,optional,omitempty,tag:713"` // [713] EXPLICIT OCTET_STRING OPTIONAL, # KM3 - AttestationIdImei []byte `asn1:"explicit,optional,omitempty,tag:714"` // [714] EXPLICIT OCTET_STRING OPTIONAL, # KM3 - AttestationIdMeid []byte `asn1:"explicit,optional,omitempty,tag:715"` // [715] EXPLICIT OCTET_STRING OPTIONAL, # KM3 - AttestationIdManufacturer []byte `asn1:"explicit,optional,omitempty,tag:716"` // [716] EXPLICIT OCTET_STRING OPTIONAL, # KM3 - AttestationIdModel []byte `asn1:"explicit,optional,omitempty,tag:717"` // [717] EXPLICIT OCTET_STRING OPTIONAL, # KM3 - VendorPatchLevel asn1.RawValue `asn1:"explicit,optional,tag:718"` // [718] EXPLICIT INTEGER OPTIONAL, # KM4 - BootPatchLevel asn1.RawValue `asn1:"explicit,optional,tag:719"` // [719] EXPLICIT INTEGER OPTIONAL, # KM4 + Purpose []int32 `asn1:"explicit,optional,omitempty,set,tag:1"` // [1] EXPLICIT SET OF INTEGER OPTIONAL, + Algorithm asn1.RawValue `asn1:"explicit,optional,tag:2"` // [2] EXPLICIT INTEGER OPTIONAL, + KeySize asn1.RawValue `asn1:"explicit,optional,tag:3"` // [3] EXPLICIT INTEGER OPTIONAL. + BlockMode []int `asn1:"explicit,optional,omitempty,set,tag:4"` // [4] EXPLICIT SET OF INTEGER OPTIONAL, + Digest []int `asn1:"explicit,optional,omitempty,set,tag:5"` // [5] EXPLICIT SET OF INTEGER OPTIONAL, + Padding []int `asn1:"explicit,optional,omitempty,set,tag:6"` // [6] EXPLICIT SET OF INTEGER OPTIONAL, + EcCurve asn1.RawValue `asn1:"explicit,optional,tag:10"` // [10] EXPLICIT INTEGER OPTIONAL, + MlDsaVariant asn1.RawValue `asn1:"explicit,optional,tag:11"` // [11] EXPLICIT INTEGER OPTIONAL, # KM5 + RsaPublicExponent asn1.RawValue `asn1:"explicit,optional,tag:200"` // [200] EXPLICIT INTEGER OPTIONAL, + MgfDigest []int `asn1:"explicit,optional,omitempty,set,tag:203"` // [203] EXPLICIT SET OF INTEGER OPTIONAL, + RollbackResistance asn1.RawValue `asn1:"explicit,optional,tag:303"` // [303] EXPLICIT NULL OPTIONAL, # KM4 + EarlyBootOnly asn1.RawValue `asn1:"explicit,optional,tag:305"` // [305] EXPLICIT NULL OPTIONAL, # KM4 + ActiveDateTime asn1.RawValue `asn1:"explicit,optional,tag:400"` // [400] EXPLICIT INTEGER OPTIONAL + OriginationExpireDateTime asn1.RawValue `asn1:"explicit,optional,tag:401"` // [401] EXPLICIT INTEGER OPTIONAL + UsageExpireDateTime asn1.RawValue `asn1:"explicit,optional,tag:402"` // [402] EXPLICIT INTEGER OPTIONAL + UsageCountLimit asn1.RawValue `asn1:"explicit,optional,tag:405"` // [405] EXPLICIT INTEGER OPTIONAL, # KM4 + NoAuthRequired asn1.RawValue `asn1:"explicit,optional,tag:503"` // [503] EXPLICIT NULL OPTIONAL, + UserAuthType asn1.RawValue `asn1:"explicit,optional,tag:504"` // [504] EXPLICIT INTEGER OPTIONAL, + AuthTimeout asn1.RawValue `asn1:"explicit,optional,tag:505"` // [505] EXPLICIT INTEGER OPTIONAL, + AllowWhileOnBody asn1.RawValue `asn1:"explicit,optional,tag:506"` // [506] EXPLICIT NULL OPTIONAL, + TrustedUserPresenceRequired asn1.RawValue `asn1:"explicit,optional,tag:507"` // [507] EXPLICIT NULL OPTIONAL, # KM4 + TrustedConfirmationRequired asn1.RawValue `asn1:"explicit,optional,tag:508"` // [508] EXPLICIT NULL OPTIONAL, # KM4 + UnlockedDeviceRequired asn1.RawValue `asn1:"explicit,optional,tag:509"` // [509] EXPLICIT NULL OPTIONAL, # KM4 + AllApplications asn1.RawValue `asn1:"explicit,optional,tag:600"` // [600] EXPLICIT NULL OPTIONAL, + ApplicationId []byte `asn1:"explicit,optional,omitempty,tag:601"` // [601] EXPLICIT OCTET_STRING OPTIONAL, + CreationDateTime asn1.RawValue `asn1:"explicit,optional,tag:701"` // [701] EXPLICIT INTEGER OPTIONAL, + Origin asn1.RawValue `asn1:"explicit,optional,tag:702"` // [702] EXPLICIT INTEGER OPTIONAL, + RollbackResistant asn1.RawValue `asn1:"explicit,optional,tag:703"` // [703] EXPLICIT NULL OPTIONAL, # KM2 and KM3 only. + RootOfTrust asn1.RawValue `asn1:"explicit,optional,tag:704"` // [704] EXPLICIT RootOfTrust OPTIONAL, + OsVersion asn1.RawValue `asn1:"explicit,optional,tag:705"` // [705] EXPLICIT INTEGER OPTIONAL, + OsPatchLevel asn1.RawValue `asn1:"explicit,optional,tag:706"` // [706] EXPLICIT INTEGER OPTIONAL, + AttestationApplicationId asn1.RawContent `asn1:"explicit,optional,tag:709"` // [709] EXPLICIT OCTET_STRING OPTIONAL, # KM3 + AttestationIdBrand []byte `asn1:"explicit,optional,omitempty,tag:710"` // [710] EXPLICIT OCTET_STRING OPTIONAL, # KM3 + AttestationIdDevice []byte `asn1:"explicit,optional,omitempty,tag:711"` // [711] EXPLICIT OCTET_STRING OPTIONAL, # KM3 + AttestationIdProduct []byte `asn1:"explicit,optional,omitempty,tag:712"` // [712] EXPLICIT OCTET_STRING OPTIONAL, # KM3 + AttestationIdSerial []byte `asn1:"explicit,optional,omitempty,tag:713"` // [713] EXPLICIT OCTET_STRING OPTIONAL, # KM3 + AttestationIdImei []byte `asn1:"explicit,optional,omitempty,tag:714"` // [714] EXPLICIT OCTET_STRING OPTIONAL, # KM3 + AttestationIdMeid []byte `asn1:"explicit,optional,omitempty,tag:715"` // [715] EXPLICIT OCTET_STRING OPTIONAL, # KM3 + AttestationIdManufacturer []byte `asn1:"explicit,optional,omitempty,tag:716"` // [716] EXPLICIT OCTET_STRING OPTIONAL, # KM3 + AttestationIdModel []byte `asn1:"explicit,optional,omitempty,tag:717"` // [717] EXPLICIT OCTET_STRING OPTIONAL, # KM3 + VendorPatchLevel asn1.RawValue `asn1:"explicit,optional,tag:718"` // [718] EXPLICIT INTEGER OPTIONAL, # KM4 + BootPatchLevel asn1.RawValue `asn1:"explicit,optional,tag:719"` // [719] EXPLICIT INTEGER OPTIONAL, # KM4 + DeviceUniqueAttestation asn1.RawValue `asn1:"explicit,optional,tag:720"` // [720] EXPLICIT NULL OPTIONAL, # KM4 + AttestationIdSecondImei []byte `asn1:"explicit,optional,omitempty,tag:723"` // [723] EXPLICIT OCTET_STRING OPTIONAL, # KM3 + ModuleHash []byte `asn1:"explicit,optional,omitempty,tag:724"` // [724] EXPLICIT OCTET_STRING OPTIONAL, # KM4 } // AuthorizationList reflects the key pair's properties as defined in the Keymaster or KeyMint @@ -240,14 +269,19 @@ type AuthorizationList struct { Purpose []KeyPurpose Algorithm *Algorithm KeySize *int + BlockMode []BlockMode Digest []Digest Padding []PaddingMode EcCurve *EcCurve + MlDsaVariant *int RsaPublicExponent *int64 + MgfDigest []Digest RollbackResistance bool + EarlyBootOnly bool ActiveDateTime *int64 OriginationExpireDateTime *int UsageExpireDateTime *int64 + UsageCountLimit *int NoAuthRequired bool UserAuthType *HardwareAuthenticatorType AuthTimeout *int32 @@ -274,6 +308,9 @@ type AuthorizationList struct { AttestationIdModel []byte VendorPatchLevel *int BootPatchLevel *int + DeviceUniqueAttestation bool + AttestationIdSecondImei []byte + ModuleHash []byte } // RootOfTrust reflects the ASN.1 data structure for RootOfTrust. diff --git a/cmd/attestation-cli/main.go b/cmd/attestation-cli/main.go index 96c7b98..d54704a 100644 --- a/cmd/attestation-cli/main.go +++ b/cmd/attestation-cli/main.go @@ -258,21 +258,33 @@ func printAuthorizationList(printer *printer, in attestation.AuthorizationList) if v, ok := isNotEmpty(in.KeySize); ok { printer.Printf("KeySize: %v\n", v) } + if _, ok := isNotEmpty(in.BlockMode); ok { + printer.Printf("BlockMode: %v\n", in.BlockMode) + } if _, ok := isNotEmpty(in.Digest); ok { printer.Printf("Digest: %v\n", in.Digest) } + if _, ok := isNotEmpty(in.MgfDigest); ok { + printer.Printf("MgfDigest: %v\n", in.MgfDigest) + } if _, ok := isNotEmpty(in.Padding); ok { printer.Printf("Padding: %v\n", in.Padding) } if v, ok := isNotEmpty(in.EcCurve); ok { printer.Printf("EcCurve: %v (%d)\n", v, v) } + if v, ok := isNotEmpty(in.MlDsaVariant); ok { + printer.Printf("MlDsaVariant: %v\n", v) + } if v, ok := isNotEmpty(in.RsaPublicExponent); ok { printer.Printf("RsaPublicExponent: %v\n", v) } if _, ok := isNotEmpty(in.RollbackResistance); ok { printer.Printf("RollbackResistance: %t\n", in.RollbackResistance) } + if _, ok := isNotEmpty(in.EarlyBootOnly); ok { + printer.Printf("EarlyBootOnly: %t\n", in.EarlyBootOnly) + } if _, ok := isNotEmpty(in.ActiveDateTime); ok { printer.Printf("ActiveDateTime: %v\n", in.ActiveDateTime) } @@ -282,6 +294,9 @@ func printAuthorizationList(printer *printer, in attestation.AuthorizationList) if _, ok := isNotEmpty(in.UsageExpireDateTime); ok { printer.Printf("UsageExpireDateTime: %v\n", in.UsageExpireDateTime) } + if v, ok := isNotEmpty(in.UsageCountLimit); ok { + printer.Printf("UsageCountLimit: %v\n", v) + } if _, ok := isNotEmpty(in.NoAuthRequired); ok { printer.Printf("NoAuthRequired: %t\n", in.NoAuthRequired) } @@ -366,6 +381,15 @@ func printAuthorizationList(printer *printer, in attestation.AuthorizationList) if v, ok := isNotEmpty(in.BootPatchLevel); ok { printer.Printf("BootPatchLevel: %v\n", v) } + if _, ok := isNotEmpty(in.DeviceUniqueAttestation); ok { + printer.Printf("DeviceUniqueAttestation: %t\n", in.DeviceUniqueAttestation) + } + if _, ok := isNotEmpty(in.AttestationIdSecondImei); ok { + printer.Printf("AttestationIdSecondImei: %s\n", in.AttestationIdSecondImei) + } + if _, ok := isNotEmpty(in.ModuleHash); ok { + printer.Printf("ModuleHash: %x\n", in.ModuleHash) + } } func printRootOfTrust(printer *printer, rot *attestation.RootOfTrust) { diff --git a/extension.go b/extension.go index 4d8edc0..dcdb7dd 100644 --- a/extension.go +++ b/extension.go @@ -141,12 +141,29 @@ func createAuthorizationList(authList *AuthorizationList) (*authorizationList, e return nil, err } + var blockModes []int + for _, blockMode := range authList.BlockMode { + blockModes = append(blockModes, int(blockMode)) + } + al.BlockMode = blockModes + + al.MlDsaVariant, err = newIntRawValue(authList.MlDsaVariant, TagMlDsaVariant) + if err != nil { + return nil, err + } + var digests []int for _, digest := range authList.Digest { digests = append(digests, int(digest)) } al.Digest = digests + var mgfDigests []int + for _, digest := range authList.MgfDigest { + mgfDigests = append(mgfDigests, int(digest)) + } + al.MgfDigest = mgfDigests + var paddings []int for _, padding := range authList.Padding { paddings = append(paddings, int(padding)) @@ -154,6 +171,7 @@ func createAuthorizationList(authList *AuthorizationList) (*authorizationList, e al.Padding = paddings al.RollbackResistance = newBoolRawValue(authList.RollbackResistance, TagRollbackResistance) + al.EarlyBootOnly = newBoolRawValue(authList.EarlyBootOnly, TagEarlyBootOnly) al.ActiveDateTime, err = newInt64RawValue(authList.ActiveDateTime, TagActiveDateTime) if err != nil { return nil, err @@ -166,6 +184,10 @@ func createAuthorizationList(authList *AuthorizationList) (*authorizationList, e if err != nil { return nil, err } + al.UsageCountLimit, err = newIntRawValue(authList.UsageCountLimit, TagUsageCountLimit) + if err != nil { + return nil, err + } al.NoAuthRequired = newBoolRawValue(authList.NoAuthRequired, TagNoAuthRequired) if t := authList.UserAuthType; t != nil { @@ -244,6 +266,10 @@ func createAuthorizationList(authList *AuthorizationList) (*authorizationList, e return nil, err } + al.DeviceUniqueAttestation = newBoolRawValue(authList.DeviceUniqueAttestation, TagDeviceUniqueAttestation) + al.AttestationIdSecondImei = authList.AttestationIdSecondImei + al.ModuleHash = authList.ModuleHash + return &al, nil } @@ -359,6 +385,11 @@ func newAuthorizationList(in *authorizationList) (*AuthorizationList, error) { out.EcCurve = v } + out.MlDsaVariant, err = newOptionnalInt(in.MlDsaVariant) + if err != nil { + return nil, err + } + out.RsaPublicExponent, err = newOptionnalInt64(in.RsaPublicExponent) if err != nil { return nil, err @@ -368,10 +399,18 @@ func newAuthorizationList(in *authorizationList) (*AuthorizationList, error) { return nil, err } + for _, b := range in.BlockMode { + out.BlockMode = append(out.BlockMode, BlockMode(b)) + } + for _, d := range in.Digest { out.Digest = append(out.Digest, Digest(d)) } + for _, d := range in.MgfDigest { + out.MgfDigest = append(out.MgfDigest, Digest(d)) + } + for _, m := range in.Padding { out.Padding = append(out.Padding, PaddingMode(m)) } @@ -381,6 +420,7 @@ func newAuthorizationList(in *authorizationList) (*AuthorizationList, error) { } out.RollbackResistance = isNullType(in.RollbackResistance) + out.EarlyBootOnly = isNullType(in.EarlyBootOnly) out.ActiveDateTime, err = newOptionnalInt64(in.ActiveDateTime) if err != nil { return nil, err @@ -393,6 +433,10 @@ func newAuthorizationList(in *authorizationList) (*AuthorizationList, error) { if err != nil { return nil, err } + out.UsageCountLimit, err = newOptionnalInt(in.UsageCountLimit) + if err != nil { + return nil, err + } out.NoAuthRequired = isNullType(in.NoAuthRequired) if t, err := newOptionnalInt32(in.UserAuthType); err != nil { @@ -483,6 +527,10 @@ func newAuthorizationList(in *authorizationList) (*AuthorizationList, error) { return nil, err } + out.DeviceUniqueAttestation = isNullType(in.DeviceUniqueAttestation) + out.AttestationIdSecondImei = in.AttestationIdSecondImei + out.ModuleHash = in.ModuleHash + return out, nil } diff --git a/key.go b/key.go index a533c0b..f7f3598 100644 --- a/key.go +++ b/key.go @@ -8,6 +8,8 @@ func (a Algorithm) String() string { return "RSA" case AlgoEC: return "EC" + case AlgoMLDSA: + return "ML_DSA" case AlgoAES: return "AES" case AlgoHMAC: @@ -18,10 +20,11 @@ func (a Algorithm) String() string { } const ( - AlgoRSA Algorithm = 1 - AlgoEC Algorithm = 3 - AlgoAES Algorithm = 32 - AlgoHMAC Algorithm = 128 + AlgoRSA Algorithm = 1 + AlgoEC Algorithm = 3 + AlgoMLDSA Algorithm = 4 + AlgoAES Algorithm = 32 + AlgoHMAC Algorithm = 128 ) // KeyBlobUsageRequirements specifies the necessary system environment conditions for the generated key to be used. diff --git a/keymint_test.go b/keymint_test.go new file mode 100644 index 0000000..20eefe0 --- /dev/null +++ b/keymint_test.go @@ -0,0 +1,219 @@ +package attestation + +import ( + "bytes" + "crypto/x509" + "crypto/x509/pkix" + "reflect" + "testing" +) + +func intPtr(v int) *int { return &v } +func int64Ptr(v int64) *int64 { return &v } +func algoPtr(v Algorithm) *Algorithm { return &v } +func ecCurvePtr(v EcCurve) *EcCurve { return &v } + +func TestAttestationVersion_String(t *testing.T) { + tests := []struct { + version AttestationVersion + want string + }{ + {KAKeymasterVersion2, "Keymaster version 2.0"}, + {KAKeymasterVersion3, "Keymaster version 3.0"}, + {KAKeymasterVersion4, "Keymaster version 4.0"}, + {KAKeymasterVersion41, "Keymaster version 4.1"}, + {KAKeyMintVersion1, "KeyMint version 1.0"}, + {KAKeyMintVersion2, "KeyMint version 2.0"}, + {KAKeyMintVersion3, "KeyMint version 3.0"}, + {KAKeyMintVersion4, "KeyMint version 4.0"}, + {KAKeyMintVersion5, "KeyMint version 5.0"}, + {AttestationVersion(999), "unknown attestation version (999)"}, + } + for _, tt := range tests { + if got := tt.version.String(); got != tt.want { + t.Errorf("AttestationVersion(%d).String() = %q, want %q", uint(tt.version), got, tt.want) + } + } +} + +func TestKeymasterVersion_String(t *testing.T) { + tests := []struct { + version KeymasterVersion + want string + }{ + {KeymasterVersion0, "Keymaster version 0"}, + {KeymasterVersion4, "Keymaster version 4"}, + {KeymasterVersion41, "Keymaster version 4.1"}, + {KeyMintVersion1, "KeyMint version 1.0"}, + {KeyMintVersion3, "KeyMint version 3.0"}, + {KeyMintVersion4, "KeyMint version 4.0"}, + {KeyMintVersion5, "KeyMint version 5.0"}, + {KeymasterVersion(999), "unknown keymaster version (999)"}, + } + for _, tt := range tests { + if got := tt.version.String(); got != tt.want { + t.Errorf("KeymasterVersion(%d).String() = %q, want %q", uint(tt.version), got, tt.want) + } + } +} + +// TestKeyMintRoundTrip verifies that a KeyDescription carrying the fields +// introduced with KeyMint 4.0 and 5.0 survives a Create -> Parse round trip. +func TestKeyMintRoundTrip(t *testing.T) { + template := &KeyDescription{ + AttestationVersion: KAKeyMintVersion5, + AttestationSecurityLevel: StrongBox, + KeymasterVersion: KeyMintVersion5, + KeymasterSecurityLevel: StrongBox, + AttestationChallenge: []byte("challenge"), + UniqueId: []byte("unique"), + TeeEnforced: AuthorizationList{ + Purpose: []KeyPurpose{PurposeSign, PurposeVerify}, + Algorithm: algoPtr(AlgoEC), + KeySize: intPtr(256), + BlockMode: []BlockMode{BlockModeECB, BlockModeGCM}, + Digest: []Digest{DigestSHA_2_256}, + MgfDigest: []Digest{DigestSHA_2_256, DigestSHA_2_512}, + Padding: []PaddingMode{PaddingRSA_PSS}, + EcCurve: ecCurvePtr(CurveP256), + MlDsaVariant: intPtr(65), + RsaPublicExponent: int64Ptr(65537), + RollbackResistance: true, + EarlyBootOnly: true, + UsageCountLimit: intPtr(1), + NoAuthRequired: true, + DeviceUniqueAttestation: true, + OsVersion: intPtr(160000), + OsPatchLevel: intPtr(202601), + VendorPatchLevel: intPtr(20260105), + BootPatchLevel: intPtr(20260105), + AttestationIdSecondImei: []byte("356938035643810"), + ModuleHash: []byte{0xde, 0xad, 0xbe, 0xef}, + RootOfTrust: &RootOfTrust{ + VerifiedBootKey: []byte("bootkey"), + DeviceLocked: true, + VerifiedBootState: Verified, + VerifiedBootHash: []byte("boothash"), + }, + }, + } + + der, err := CreateKeyDescription(template) + if err != nil { + t.Fatalf("CreateKeyDescription() error = %v", err) + } + + got, err := ParseExtension(der) + if err != nil { + t.Fatalf("ParseExtension() error = %v", err) + } + + if got.AttestationVersion != KAKeyMintVersion5 { + t.Errorf("AttestationVersion = %d, want %d", got.AttestationVersion, KAKeyMintVersion5) + } + if got.KeymasterVersion != KeyMintVersion5 { + t.Errorf("KeymasterVersion = %d, want %d", got.KeymasterVersion, KeyMintVersion5) + } + + te := got.TeeEnforced + want := template.TeeEnforced + + if !reflect.DeepEqual(te.BlockMode, want.BlockMode) { + t.Errorf("BlockMode = %v, want %v", te.BlockMode, want.BlockMode) + } + if !reflect.DeepEqual(te.MgfDigest, want.MgfDigest) { + t.Errorf("MgfDigest = %v, want %v", te.MgfDigest, want.MgfDigest) + } + if te.MlDsaVariant == nil || *te.MlDsaVariant != 65 { + t.Errorf("MlDsaVariant = %v, want 65", te.MlDsaVariant) + } + if !te.EarlyBootOnly { + t.Error("EarlyBootOnly = false, want true") + } + if te.UsageCountLimit == nil || *te.UsageCountLimit != 1 { + t.Errorf("UsageCountLimit = %v, want 1", te.UsageCountLimit) + } + if !te.DeviceUniqueAttestation { + t.Error("DeviceUniqueAttestation = false, want true") + } + if !bytes.Equal(te.AttestationIdSecondImei, want.AttestationIdSecondImei) { + t.Errorf("AttestationIdSecondImei = %q, want %q", te.AttestationIdSecondImei, want.AttestationIdSecondImei) + } + if !bytes.Equal(te.ModuleHash, want.ModuleHash) { + t.Errorf("ModuleHash = %x, want %x", te.ModuleHash, want.ModuleHash) + } + + // Sanity check that pre-existing fields still round trip alongside the new ones. + if !te.RollbackResistance || !te.NoAuthRequired { + t.Error("expected RollbackResistance and NoAuthRequired to be true") + } + if te.RootOfTrust == nil || !te.RootOfTrust.DeviceLocked { + t.Error("expected RootOfTrust with DeviceLocked = true") + } + if !bytes.Equal(te.RootOfTrust.VerifiedBootHash, want.RootOfTrust.VerifiedBootHash) { + t.Errorf("VerifiedBootHash = %q, want %q", te.RootOfTrust.VerifiedBootHash, want.RootOfTrust.VerifiedBootHash) + } +} + +// TestKeyMintCreateOptionalOmitted verifies that optional KeyMint fields left +// unset are omitted from the encoding and parse back to their zero values. +func TestKeyMintCreateOptionalOmitted(t *testing.T) { + template := &KeyDescription{ + AttestationVersion: KAKeyMintVersion4, + KeymasterVersion: KeyMintVersion4, + } + + der, err := CreateKeyDescription(template) + if err != nil { + t.Fatalf("CreateKeyDescription() error = %v", err) + } + + got, err := ParseExtension(der) + if err != nil { + t.Fatalf("ParseExtension() error = %v", err) + } + + te := got.TeeEnforced + if te.MlDsaVariant != nil { + t.Errorf("MlDsaVariant = %v, want nil", te.MlDsaVariant) + } + if te.UsageCountLimit != nil { + t.Errorf("UsageCountLimit = %v, want nil", te.UsageCountLimit) + } + if te.EarlyBootOnly { + t.Error("EarlyBootOnly = true, want false") + } + if te.DeviceUniqueAttestation { + t.Error("DeviceUniqueAttestation = true, want false") + } + if len(te.BlockMode) != 0 { + t.Errorf("BlockMode = %v, want empty", te.BlockMode) + } + if len(te.MgfDigest) != 0 { + t.Errorf("MgfDigest = %v, want empty", te.MgfDigest) + } + if te.AttestationIdSecondImei != nil { + t.Errorf("AttestationIdSecondImei = %q, want nil", te.AttestationIdSecondImei) + } + if te.ModuleHash != nil { + t.Errorf("ModuleHash = %x, want nil", te.ModuleHash) + } +} + +func Test_GetKeyExtension(t *testing.T) { + ext := pkix.Extension{Id: OIDKeyAttestationExtension, Value: []byte{0x01}} + crt := &x509.Certificate{Extensions: []pkix.Extension{ext}} + + got := GetKeyExtension(crt) + if got == nil { + t.Fatal("GetKeyExtension() = nil, want extension") + } + if !reflect.DeepEqual(*got, ext) { + t.Fatalf("GetKeyExtension() = %+v, want %+v", *got, ext) + } + + missing := &x509.Certificate{Extensions: []pkix.Extension{{Id: []int{1, 2, 3}}}} + if got := GetKeyExtension(missing); got != nil { + t.Fatalf("GetKeyExtension() = %+v, want nil", got) + } +} diff --git a/tag.go b/tag.go index 7c747b5..3d84101 100644 --- a/tag.go +++ b/tag.go @@ -6,9 +6,11 @@ const ( TagPurpose = 1 // Corresponds to the Tag::PURPOSE authorization tag, which uses a tag ID value of 1. TagAlgorithm = 2 // Corresponds to the Tag::ALGORITHM authorization tag, which uses a tag ID value of 2. // In an attestation AuthorizationList object, the algorithm value is always RSA or EC. TagKeySize = 3 // Corresponds to the Tag::KEY_SIZE authorization tag, which uses a tag ID value of 3. + TagBlockMode = 4 // Corresponds to the Tag::BLOCK_MODE authorization tag, which uses a tag ID value of 4. // The set of block cipher modes with which the key may be used. Only relevant to AES keys. TagDigest = 5 // Corresponds to the Tag::DIGEST authorization tag, which uses a tag ID value of 5. TagPadding = 6 // Corresponds to the Tag::PADDING authorization tag, which uses a tag ID value of 6. TagEcCurve = 10 // Corresponds to the Tag::EC_CURVE authorization tag, which uses a tag ID value of 10. // The set of parameters used to generate an elliptic curve (EC) key pair, which uses ECDSA for signing and verification, within the Android system keystore. + TagMlDsaVariant = 11 // Present only in key attestation versions >= 500. // Corresponds to the Tag::ML_DSA_VARIANT KeyMint authorization tag, which uses a tag ID value of 11. // The ML-DSA (post-quantum) parameter set used to generate the key. TagRsaPublicExponent = 200 // Corresponds to the Tag::RSA_PUBLIC_EXPONENT authorization tag, which uses a tag ID value of 200. TagMgfDigest = 203 // Present only in key attestation version >= 100. // Corresponds to the Tag::RSA_OAEP_MGF_DIGEST KeyMint authorization tag, which uses a tag ID value of 203. TagRollbackResistance = 303 // Present only in key attestation version >= 3. // Corresponds to the Tag::ROLLBACK_RESISTANT authorization tag, which uses a tag ID value of 303. @@ -44,6 +46,8 @@ const ( TagVendorPatchLevel = 718 // Present only in key attestation versions >= 3. // Corresponds to the Tag::VENDOR_PATCHLEVEL authorization tag, which uses a tag ID value of 718. // Specifies the vendor image security patch level that must be installed on the device for this key to be used. The value appears in the form YYYYMMDD, representing the date of the vendor security patch. For example, if a key were generated on an Android device with the vendor's August 1, 2018 security patch installed, this value would be 20180801. TagBootPatchLevel = 719 // Present only in key attestation versions >= 3. // Corresponds to the Tag::BOOT_PATCHLEVEL authorization tag, which uses a tag ID value of 719. // Specifies the kernel image security patch level that must be installed on the device for this key to be used. The value appears in the form YYYYMMDD, representing the date of the system security patch. For example, if a key were generated on an Android device with the system's August 5, 2018 security patch installed, this value would be 20180805. TagDeviceUniqueAttestation = 720 // Present only in key attestation versions >= 4. // Corresponds to the Tag::DEVICE_UNIQUE_ATTESTATION authorization tag, which uses a tag ID value of 720. + TagAttestationIdSecondImei = 723 // Present only in key attestation versions >= 300. // Corresponds to the Tag::ATTESTATION_ID_SECOND_IMEI authorization tag, which uses a tag ID value of 723. // The second IMEI of the device, for devices with more than one radio. + TagModuleHash = 724 // Present only in key attestation versions >= 400. // Corresponds to the Tag::MODULE_HASH authorization tag, which uses a tag ID value of 724. // A hash over the set of APEX modules and their versions installed on the device. ) // RootOfTrust