blob: e4297d72af247b3c07038edbfd6dc9b8853b678c [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.security.identity.cts;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.Optional;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1Boolean;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1Enumerated;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DEROctetString;
// This code is loosely based on https://github.com/google/android-key-attestation
class ParsedAttestationRecord {
private static final String KEY_DESCRIPTION_OID = "1.3.6.1.4.1.11129.2.1.17";
enum SecurityLevel {
SOFTWARE,
TRUSTED_ENVIRONMENT,
STRONG_BOX
}
private static final int ATTESTATION_VERSION_INDEX = 0;
private static final int ATTESTATION_SECURITY_LEVEL_INDEX = 1;
private static final int KEYMASTER_VERSION_INDEX = 2;
private static final int KEYMASTER_SECURITY_LEVEL_INDEX = 3;
private static final int ATTESTATION_CHALLENGE_INDEX = 4;
private static final int UNIQUE_ID_INDEX = 5;
private static final int SW_ENFORCED_INDEX = 6;
private static final int TEE_ENFORCED_INDEX = 7;
// Some security values. The complete list is in this AOSP file:
// hardware/libhardware/include/hardware/keymaster_defs.h
private static final int KM_SECURITY_LEVEL_SOFTWARE = 0;
private static final int KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT = 1;
private static final int KM_SECURITY_LEVEL_STRONG_BOX = 2;
private int attestationVersion;
private SecurityLevel attestationSecurityLevel;
private int keymasterVersion;
private SecurityLevel keymasterSecurityLevel;
private byte[] attestationChallenge;
private byte[] uniqueId;
private Map<Integer, ASN1Primitive> softwareEnforcedAuthorizations;
private Map<Integer, ASN1Primitive> teeEnforcedAuthorizations;
public int getAttestationVersion() {
return attestationVersion;
}
public SecurityLevel getAttestationSecurityLevel() {
return attestationSecurityLevel;
}
public int getKeymasterVersion() {
return keymasterVersion;
}
public SecurityLevel getKeymasterSecurityLevel() {
return attestationSecurityLevel;
}
public byte[] getAttestationChallenge() {
return attestationChallenge;
}
public byte[] getUniqueId() {
return uniqueId;
}
public Set<Integer> getSoftwareEnforcedAuthorizationTags() {
return softwareEnforcedAuthorizations.keySet();
}
public Set<Integer> getTeeEnforcedAuthorizationTags() {
return teeEnforcedAuthorizations.keySet();
}
private static ASN1Primitive findAuthorizationListEntry(
Map<Integer, ASN1Primitive> authorizationMap, int tag) {
return authorizationMap.getOrDefault(tag, null);
}
public Optional<Integer> getSoftwareAuthorizationInteger(int tag) {
ASN1Primitive entry = findAuthorizationListEntry(softwareEnforcedAuthorizations, tag);
return Optional.ofNullable(entry).map(ParsedAttestationRecord::getIntegerFromAsn1);
}
public Optional<Integer> getTeeAuthorizationInteger(int tag) {
ASN1Primitive entry = findAuthorizationListEntry(teeEnforcedAuthorizations, tag);
return Optional.ofNullable(entry).map(ParsedAttestationRecord::getIntegerFromAsn1);
}
public boolean getSoftwareAuthorizationBoolean(int tag) {
ASN1Primitive entry = findAuthorizationListEntry(softwareEnforcedAuthorizations, tag);
return entry != null;
}
public boolean getTeeAuthorizationBoolean(int tag) {
ASN1Primitive entry = findAuthorizationListEntry(teeEnforcedAuthorizations, tag);
return entry != null;
}
public Optional<byte[]> getSoftwareAuthorizationByteString(int tag) {
ASN1OctetString entry = (ASN1OctetString) findAuthorizationListEntry(softwareEnforcedAuthorizations, tag);
return Optional.ofNullable(entry).map(ASN1OctetString::getOctets);
}
public Optional<byte[]> getTeeAuthorizationByteString(int tag) {
ASN1OctetString entry = (ASN1OctetString) findAuthorizationListEntry(teeEnforcedAuthorizations, tag);
return Optional.ofNullable(entry).map(ASN1OctetString::getOctets);
}
private static boolean getBooleanFromAsn1(ASN1Encodable asn1Value) {
if (asn1Value instanceof ASN1Boolean) {
return ((ASN1Boolean) asn1Value).isTrue();
} else {
throw new RuntimeException(
"Boolean value expected; found " + asn1Value.getClass().getName() + " instead.");
}
}
private static int getIntegerFromAsn1(ASN1Encodable asn1Value) {
if (asn1Value instanceof ASN1Integer) {
return ((ASN1Integer) asn1Value).getValue().intValue();
} else if (asn1Value instanceof ASN1Enumerated) {
return ((ASN1Enumerated) asn1Value).getValue().intValue();
} else {
throw new IllegalArgumentException(
"Integer value expected; found " + asn1Value.getClass().getName() + " instead.");
}
}
private static Map<Integer, ASN1Primitive> getAuthorizationMap(
ASN1Encodable[] authorizationList) {
Map<Integer, ASN1Primitive> authorizationMap = new HashMap<>();
for (ASN1Encodable entry : authorizationList) {
ASN1TaggedObject taggedEntry = (ASN1TaggedObject) entry;
authorizationMap.put(taggedEntry.getTagNo(), taggedEntry.getObject());
}
return authorizationMap;
}
public ParsedAttestationRecord(X509Certificate cert) throws IOException {
byte[] attestationExtensionBytes = cert.getExtensionValue(KEY_DESCRIPTION_OID);
if (attestationExtensionBytes == null || attestationExtensionBytes.length == 0) {
throw new IllegalArgumentException("Couldn't find keystore attestation extension.");
}
ASN1Sequence seq;
try (ASN1InputStream asn1InputStream = new ASN1InputStream(attestationExtensionBytes)) {
// The extension contains one object, a sequence, in the
// Distinguished Encoding Rules (DER)-encoded form. Get the DER
// bytes.
byte[] derSequenceBytes = ((ASN1OctetString) asn1InputStream.readObject()).getOctets();
// Decode the bytes as an ASN1 sequence object.
try (ASN1InputStream seqInputStream = new ASN1InputStream(derSequenceBytes)) {
seq = (ASN1Sequence) seqInputStream.readObject();
}
}
this.attestationVersion = getIntegerFromAsn1(seq.getObjectAt(ATTESTATION_VERSION_INDEX));
this.attestationSecurityLevel =
securityLevelToEnum(getIntegerFromAsn1(
seq.getObjectAt(ATTESTATION_SECURITY_LEVEL_INDEX)));
this.keymasterVersion = getIntegerFromAsn1(seq.getObjectAt(KEYMASTER_VERSION_INDEX));
this.keymasterSecurityLevel = securityLevelToEnum(
getIntegerFromAsn1(seq.getObjectAt(KEYMASTER_SECURITY_LEVEL_INDEX)));
this.attestationChallenge =
((ASN1OctetString) seq.getObjectAt(ATTESTATION_CHALLENGE_INDEX)).getOctets();
this.uniqueId = ((ASN1OctetString) seq.getObjectAt(UNIQUE_ID_INDEX)).getOctets();
this.softwareEnforcedAuthorizations = getAuthorizationMap(
((ASN1Sequence) seq.getObjectAt(SW_ENFORCED_INDEX)).toArray());
this.teeEnforcedAuthorizations = getAuthorizationMap(
((ASN1Sequence) seq.getObjectAt(TEE_ENFORCED_INDEX)).toArray());
}
private static SecurityLevel securityLevelToEnum(int securityLevel) {
switch (securityLevel) {
case KM_SECURITY_LEVEL_SOFTWARE:
return SecurityLevel.SOFTWARE;
case KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT:
return SecurityLevel.TRUSTED_ENVIRONMENT;
case KM_SECURITY_LEVEL_STRONG_BOX:
return SecurityLevel.STRONG_BOX;
default:
throw new IllegalArgumentException("Invalid security level.");
}
}
}