|
| 1 | +package com.google.u2f.server.impl.androidattestation; |
| 2 | + |
| 3 | +import org.bouncycastle.asn1.ASN1Encodable; |
| 4 | +import org.bouncycastle.asn1.ASN1InputStream; |
| 5 | +import org.bouncycastle.asn1.ASN1Integer; |
| 6 | +import org.bouncycastle.asn1.ASN1Object; |
| 7 | +import org.bouncycastle.asn1.ASN1Primitive; |
| 8 | +import org.bouncycastle.asn1.ASN1Sequence; |
| 9 | +import org.bouncycastle.asn1.DEROctetString; |
| 10 | +import org.bouncycastle.asn1.DERSet; |
| 11 | +import org.bouncycastle.asn1.DERTaggedObject; |
| 12 | +import org.bouncycastle.asn1.DLSequence; |
| 13 | + |
| 14 | +import java.io.IOException; |
| 15 | +import java.math.BigInteger; |
| 16 | +import java.security.cert.CertificateParsingException; |
| 17 | +import java.security.cert.X509Certificate; |
| 18 | +import java.util.ArrayList; |
| 19 | +import java.util.HashMap; |
| 20 | +import java.util.List; |
| 21 | + |
| 22 | +/** |
| 23 | + * Parses and contains an Android KeyStore attestation. |
| 24 | + */ |
| 25 | +public class AndroidKeyStoreAttestation { |
| 26 | + private static final String KEY_DESCRIPTION_OID = "1.3.6.1.4.1.11129.2.1.17"; |
| 27 | + |
| 28 | + // Indexes for data in KeyDescription sequence |
| 29 | + private static final int DESCRIPTION_LENGTH_MIN = 4; |
| 30 | + private static final int DESCRIPTION_LENGTH_MAX = 5; |
| 31 | + private static final int DESCRIPTION_VERSION_INDEX = 0; |
| 32 | + private static final int DESCRIPTION_CHALLENGE_INDEX = 1; |
| 33 | + private static final int DESCRIPTION_SOFTWARE_ENFORCED_INDEX = 2; |
| 34 | + private static final int DESCRIPTION_TEE_ENFORCED_INDEX = 3; |
| 35 | + private static final int DESCRIPTION_UNIQUE_INDEX = 4; |
| 36 | + |
| 37 | + // Don't expect more than 32 bits for any INTEGER |
| 38 | + private static final int MAX_INTEGER_BITS = 32; |
| 39 | + |
| 40 | + // Tags for Authorization List |
| 41 | + private static final int AUTHZ_PURPOSE_TAG = 1; |
| 42 | + private static final int AUTHZ_ALGORITHM_TAG = 2; |
| 43 | + |
| 44 | + private final Integer keymasterVersion; |
| 45 | + private final byte[] attestationChallenge; |
| 46 | + private final AuthorizationList softwareAuthorizationList; |
| 47 | + private final byte[] uniqueId; |
| 48 | + |
| 49 | + private AndroidKeyStoreAttestation(Integer keymasterVersion, byte[] attestationChallenge, |
| 50 | + AuthorizationList softwareAuthorizationList, byte[] uniqueId) { |
| 51 | + this.keymasterVersion = keymasterVersion; |
| 52 | + this.attestationChallenge = attestationChallenge; |
| 53 | + this.softwareAuthorizationList = softwareAuthorizationList; |
| 54 | + this.uniqueId = uniqueId; |
| 55 | + } |
| 56 | + |
| 57 | + /** |
| 58 | + * Parses the key description extension. Expected format is: |
| 59 | + * KeyDescription ::= SEQUENCE { |
| 60 | + * keymasterVersion INTEGER, |
| 61 | + * attestationChallenge OCTET_STRING, |
| 62 | + * softwareEnforced AuthorizationList, |
| 63 | + * teeEnforced AuthorizationList, |
| 64 | + * uniqueId OCTET_STRING OPTIONAL, |
| 65 | + * } |
| 66 | + * |
| 67 | + * AuthorizationList ::= SEQUENCE { |
| 68 | + * -- See keymaster_purpose_t for purpose values. |
| 69 | + * purpose [1] IMPLICIT SET OF INTEGER OPTIONAL, |
| 70 | + * -- See keymaster_algorithm_t for algorithm values. |
| 71 | + * algorithm [2] IMPLICIT INTEGER OPTIONAL, |
| 72 | + * -- keySize is measured in bits, not bytes, and the value must be |
| 73 | + * -- positive and less than than 2^32, though realistic values are |
| 74 | + * -- much smaller. |
| 75 | + * keySize [3] IMPLICIT INTEGER OPTIONAL, |
| 76 | + * -- See keymaster_block_mode_t for blockMode values. |
| 77 | + * blockMode [4] IMPLICIT SET OF INTEGER OPTIONAL, |
| 78 | + * -- See keymaster_digest_t for digest values. |
| 79 | + * digest [5] IMPLICIT SET OF INTEGER OPTIONAL, |
| 80 | + * -- See keymaster_padding_t for padding values. |
| 81 | + * padding [6] IMPLICIT SET OF INTEGER OPTIONAL, |
| 82 | + * callerNonce [7] IMPLICIT NULL OPTIONAL, |
| 83 | + * -- minMacLength values must be positive and less than 2^32. |
| 84 | + * minMacLength [8] IMPLICIT INTEGER OPTIONAL, |
| 85 | + * -- See keymaster_kdf_t for kdf values. |
| 86 | + * kdf [9] IMPLICIT SEQUENCE OF INTEGER OPTIONAL, |
| 87 | + * -- See keymaster_ec_curve_t for ecCurve values |
| 88 | + * ecCurve [10] IMPLICIT INTEGER OPTIONAL, |
| 89 | + * -- rsaPublicExponent must be a valid RSA public exponent less |
| 90 | + * -- than 2^64. |
| 91 | + * rsaPublicExponent [200] IMPLICIT INTEGER OPTIONAL, |
| 92 | + * eciesSingleHashMode [201] IMPLICIT NULL OPTIONAL, |
| 93 | + * includeUniqueId [202] IMPLICIT NULL OPTIONAL, |
| 94 | + * -- See keymaster_key_blob_usage_requirements for |
| 95 | + * -- blobUsageRequirement values. |
| 96 | + * blobUsageRequirement [301] IMPLICIT INTEGER OPTIONAL, |
| 97 | + * bootloaderOnly [302] IMPLICIT NULL OPTIONAL, |
| 98 | + * -- activeDateTime must be a 64-bit Java date/time value. |
| 99 | + * activeDateTime [400] IMPLICIT INTEGER OPTIONAL |
| 100 | + * -- originationExpireDateTime must be a 64-bit Java date/time |
| 101 | + * -- value. |
| 102 | + * originationExpireDateTime [401] IMPLICIT INTEGER OPTIONAL |
| 103 | + * -- usageExpireDateTime must be a 64-bit Java date/time value. |
| 104 | + * usageExpireDateTime [402] IMPLICIT INTEGER OPTIONAL |
| 105 | + * -- minSecondsBetweenOps must be non-negative and less than 2^32. |
| 106 | + * minSecondsBetweenOps [403] IMPLICIT INTEGER OPTIONAL, |
| 107 | + * -- maxUsesPerBoot must be positive and less than 2^32. |
| 108 | + * maxUsesPerBoot [404] IMPLICIT INTEGER OPTIONAL, |
| 109 | + * noAuthRequired [503] IMPLICIT NULL OPTIONAL, |
| 110 | + * -- See hw_authenticator_type_t for userAuthType values. Note |
| 111 | + * -- this field is a bitmask; multiple authenticator types may be |
| 112 | + * -- ORed together. |
| 113 | + * userAuthType [504] IMPLICIT INTEGER OPTIONAL, |
| 114 | + * -- authTimeout, if present, must be positive and less than 2^32. |
| 115 | + * authTimeout [505] IMPLICIT INTEGER OPTIONAL, |
| 116 | + * allApplications [600] IMPLICIT NULL OPTIONAL, |
| 117 | + * applicationId [601] IMPLICIT OCTET_STRING OPTIONAL, |
| 118 | + * applicationData [700] IMPLICIT OCTET_STRING OPTIONAL, |
| 119 | + * -- creationDateTime must be a 64-bit Java date/time value. |
| 120 | + * creationDateTime [701] IMPLICIT INTEGER OPTIONAL, |
| 121 | + * -- See keymaster_origin_t for origin values. |
| 122 | + * origin [702] IMPLICIT INTEGER OPTIONAL, |
| 123 | + * rollbackResistant [703] IMPLICIT NULL OPTIONAL, |
| 124 | + * -- rootOfTrust is included only if bootloader is not locked. |
| 125 | + * rootOfTrust [704] IMPLICIT RootOfTrust OPTIONAL |
| 126 | + * osVersion [705] IMPLICIT INTEGER OPTIONAL, |
| 127 | + * patchLevel [706] IMPLICIT INTEGER OPTIONAL, |
| 128 | + * uniqueId [707] IMPLICIT NULL OPTIONAL, |
| 129 | + * } |
| 130 | + * |
| 131 | + * RootOfTrust ::= SEQUENCE { |
| 132 | + * verifiedBootKey OCTET_STRING, |
| 133 | + * osVersion INTEGER, |
| 134 | + * patchMonthYear INTEGER, |
| 135 | + * } |
| 136 | + */ |
| 137 | + public static AndroidKeyStoreAttestation Parse(X509Certificate cert) |
| 138 | + throws CertificateParsingException { |
| 139 | + // Extract the extension from the certificate |
| 140 | + byte[] extensionValue = extractExtensionValue(cert); |
| 141 | + |
| 142 | + // Get the KeyDescription sequence |
| 143 | + DLSequence keyDescriptionSequence = getKeyDescriptionSequence(extensionValue); |
| 144 | + |
| 145 | + // Extract version |
| 146 | + Integer keymasterVersion = getKeymasterVersion(keyDescriptionSequence); |
| 147 | + |
| 148 | + // Extract challenge |
| 149 | + byte[] challenge = getAttestationChallenge(keyDescriptionSequence); |
| 150 | + |
| 151 | + // Extract the software authorization list |
| 152 | + DLSequence softwareEnforcedSequence = getSoftwareEncodedSequence(keyDescriptionSequence); |
| 153 | + AuthorizationList softwareAuthorizationList = |
| 154 | + extractAuthorizationList(softwareEnforcedSequence); |
| 155 | + |
| 156 | + // Get the unique id |
| 157 | + byte[] uniqueId = null; |
| 158 | + if (keyDescriptionSequence.size() == DESCRIPTION_LENGTH_MAX) { |
| 159 | + uniqueId = getUniqueId(keyDescriptionSequence); |
| 160 | + } |
| 161 | + |
| 162 | + return new AndroidKeyStoreAttestation( |
| 163 | + keymasterVersion, challenge, softwareAuthorizationList, uniqueId); |
| 164 | + } |
| 165 | + |
| 166 | + /** |
| 167 | + * @return parsed keymaster version |
| 168 | + */ |
| 169 | + public Integer getKeyMasterVersion() { |
| 170 | + return keymasterVersion; |
| 171 | + } |
| 172 | + |
| 173 | + /** |
| 174 | + * @return parsed software authorization list |
| 175 | + */ |
| 176 | + public AuthorizationList getSoftwareAuthorizationList() { |
| 177 | + return softwareAuthorizationList; |
| 178 | + } |
| 179 | + |
| 180 | + /** |
| 181 | + * @return the parsed unique id or {@code null} if none was included |
| 182 | + */ |
| 183 | + public byte[] getUniqueId() { |
| 184 | + return uniqueId; |
| 185 | + } |
| 186 | + |
| 187 | + /** |
| 188 | + * @return the parsed attestation challenge |
| 189 | + */ |
| 190 | + public byte[] getAttestationChallenge() { |
| 191 | + return attestationChallenge; |
| 192 | + } |
| 193 | + |
| 194 | + private static byte[] extractExtensionValue(X509Certificate cert) |
| 195 | + throws CertificateParsingException { |
| 196 | + byte[] extensionValue = cert.getExtensionValue(KEY_DESCRIPTION_OID); |
| 197 | + |
| 198 | + if (extensionValue == null || extensionValue.length == 0) { |
| 199 | + throw new CertificateParsingException( |
| 200 | + "Did not find KeyDescription extension with OID " + KEY_DESCRIPTION_OID); |
| 201 | + } |
| 202 | + |
| 203 | + return extensionValue; |
| 204 | + } |
| 205 | + |
| 206 | + private static DLSequence getKeyDescriptionSequence(byte[] extensionValue) |
| 207 | + throws CertificateParsingException { |
| 208 | + ASN1InputStream ais = new ASN1InputStream(extensionValue); |
| 209 | + ASN1Object asn1Object; |
| 210 | + |
| 211 | + // Read the key description octet string |
| 212 | + try { |
| 213 | + asn1Object = ais.readObject(); |
| 214 | + ais.close(); |
| 215 | + } catch (IOException e) { |
| 216 | + throw new CertificateParsingException("Not able to read KeyDescription ASN.1 object", e); |
| 217 | + } |
| 218 | + if (asn1Object == null || !(asn1Object instanceof DEROctetString)) { |
| 219 | + throw new CertificateParsingException("Expected KeyDescription Octet String."); |
| 220 | + } |
| 221 | + DEROctetString octet = (DEROctetString) asn1Object; |
| 222 | + |
| 223 | + // Read out the Sequence |
| 224 | + ais = new ASN1InputStream(octet.getOctets()); |
| 225 | + try { |
| 226 | + asn1Object = ais.readObject(); |
| 227 | + ais.close(); |
| 228 | + } catch (IOException e) { |
| 229 | + throw new CertificateParsingException("Not able to read KeyDescription Octet String.", e); |
| 230 | + } |
| 231 | + if (asn1Object == null || !(asn1Object instanceof DLSequence)) { |
| 232 | + throw new CertificateParsingException("Expected KeyDescription Sequence."); |
| 233 | + } |
| 234 | + DLSequence sequence = (DLSequence) asn1Object; |
| 235 | + |
| 236 | + if (sequence.size() < DESCRIPTION_LENGTH_MIN || sequence.size() > DESCRIPTION_LENGTH_MAX) { |
| 237 | + throw new CertificateParsingException("KeyDescription Sequence has " + sequence.size() |
| 238 | + + " elements. Expected length between " + DESCRIPTION_LENGTH_MIN + " and " |
| 239 | + + DESCRIPTION_LENGTH_MAX); |
| 240 | + } |
| 241 | + |
| 242 | + return sequence; |
| 243 | + } |
| 244 | + |
| 245 | + private static DLSequence getSoftwareEncodedSequence(DLSequence keyDescriptionSequence) |
| 246 | + throws CertificateParsingException { |
| 247 | + ASN1Encodable asn1Encodable = |
| 248 | + keyDescriptionSequence.getObjectAt(DESCRIPTION_SOFTWARE_ENFORCED_INDEX); |
| 249 | + if (asn1Encodable == null || !(asn1Encodable instanceof DLSequence)) { |
| 250 | + throw new CertificateParsingException("Expected softwareEnforced DLSequence."); |
| 251 | + } |
| 252 | + return (DLSequence) asn1Encodable; |
| 253 | + } |
| 254 | + |
| 255 | + private static int getIntFromAsn1Encodable(ASN1Encodable asn1Encodable) |
| 256 | + throws CertificateParsingException { |
| 257 | + if (asn1Encodable == null || !(asn1Encodable instanceof ASN1Integer)) { |
| 258 | + throw new CertificateParsingException("Expected INTEGER type."); |
| 259 | + } |
| 260 | + ASN1Integer asn1Integer = (ASN1Integer) asn1Encodable; |
| 261 | + BigInteger bigInt = asn1Integer.getPositiveValue(); |
| 262 | + if (bigInt.bitLength() > MAX_INTEGER_BITS) { |
| 263 | + throw new CertificateParsingException("INTEGER too big"); |
| 264 | + } |
| 265 | + return bigInt.intValue(); |
| 266 | + } |
| 267 | + |
| 268 | + private static byte[] getByteArrayFromAsn1Encodable(ASN1Encodable asn1Encodable) |
| 269 | + throws CertificateParsingException { |
| 270 | + if (asn1Encodable == null || !(asn1Encodable instanceof DEROctetString)) { |
| 271 | + throw new CertificateParsingException("Expected DEROctetString"); |
| 272 | + } |
| 273 | + DEROctetString derOctectString = (DEROctetString) asn1Encodable; |
| 274 | + return derOctectString.getOctets(); |
| 275 | + } |
| 276 | + |
| 277 | + private static int checkValidTag(int tag) { |
| 278 | + // TODO(aczeskis): implement |
| 279 | + return tag; |
| 280 | + } |
| 281 | + |
| 282 | + private static HashMap<Integer, ASN1Primitive> extractTaggedObjects(DLSequence dlSequence) |
| 283 | + throws CertificateParsingException { |
| 284 | + HashMap<Integer, ASN1Primitive> taggedObjects = new HashMap<Integer, ASN1Primitive>(); |
| 285 | + |
| 286 | + for (ASN1Encodable asn1EncodablePurpose : dlSequence.toArray()) { |
| 287 | + if (asn1EncodablePurpose == null || !(asn1EncodablePurpose instanceof DERTaggedObject)) { |
| 288 | + throw new CertificateParsingException("Expected DERTagged object"); |
| 289 | + } |
| 290 | + DERTaggedObject derTaggedObject = (DERTaggedObject) asn1EncodablePurpose; |
| 291 | + taggedObjects.put( |
| 292 | + Integer.valueOf(checkValidTag(derTaggedObject.getTagNo())), derTaggedObject.getObject()); |
| 293 | + } |
| 294 | + |
| 295 | + return taggedObjects; |
| 296 | + } |
| 297 | + |
| 298 | + private static int getKeymasterVersion(DLSequence keyDescriptionSequence) |
| 299 | + throws CertificateParsingException { |
| 300 | + ASN1Encodable asn1Encodable = keyDescriptionSequence.getObjectAt(DESCRIPTION_VERSION_INDEX); |
| 301 | + return getIntFromAsn1Encodable(asn1Encodable); |
| 302 | + } |
| 303 | + |
| 304 | + private static byte[] getAttestationChallenge(DLSequence keyDescriptionSequence) |
| 305 | + throws CertificateParsingException { |
| 306 | + ASN1Encodable asn1Encodable = keyDescriptionSequence.getObjectAt(DESCRIPTION_CHALLENGE_INDEX); |
| 307 | + return getByteArrayFromAsn1Encodable(asn1Encodable); |
| 308 | + } |
| 309 | + |
| 310 | + private static byte[] getUniqueId(DLSequence keyDescriptionSequence) |
| 311 | + throws CertificateParsingException { |
| 312 | + ASN1Encodable asn1Encodable = keyDescriptionSequence.getObjectAt(DESCRIPTION_UNIQUE_INDEX); |
| 313 | + return getByteArrayFromAsn1Encodable(asn1Encodable); |
| 314 | + } |
| 315 | + |
| 316 | + private static List<Purpose> getPurpose( |
| 317 | + HashMap<Integer, ASN1Primitive> softwareEnforcedTaggedObjects) |
| 318 | + throws CertificateParsingException { |
| 319 | + ASN1Primitive asn1Primitive = softwareEnforcedTaggedObjects.get(AUTHZ_PURPOSE_TAG); |
| 320 | + if (!(asn1Primitive instanceof DERSet)) { |
| 321 | + throw new CertificateParsingException("Expected DERSet"); |
| 322 | + } |
| 323 | + |
| 324 | + DERSet set = (DERSet) asn1Primitive; |
| 325 | + List<Purpose> purpose = new ArrayList<Purpose>(); |
| 326 | + for (ASN1Encodable asn1Encodable : set.toArray()) { |
| 327 | + purpose.add(Purpose.fromValue(getIntFromAsn1Encodable(asn1Encodable))); |
| 328 | + } |
| 329 | + |
| 330 | + return purpose; |
| 331 | + } |
| 332 | + |
| 333 | + private static Algorithm getAlgorithm( |
| 334 | + HashMap<Integer, ASN1Primitive> softwareEnforcedTaggedObjects) |
| 335 | + throws CertificateParsingException { |
| 336 | + ASN1Primitive asn1Primitive = softwareEnforcedTaggedObjects.get(AUTHZ_ALGORITHM_TAG); |
| 337 | + return Algorithm.fromValue(getIntFromAsn1Encodable(asn1Primitive)); |
| 338 | + } |
| 339 | + |
| 340 | + private static AuthorizationList extractAuthorizationList(DLSequence authorizationSequence) |
| 341 | + throws CertificateParsingException { |
| 342 | + HashMap<Integer, ASN1Primitive> softwareEnforcedTaggedObjects = |
| 343 | + extractTaggedObjects(authorizationSequence); |
| 344 | + |
| 345 | + return new AuthorizationList.Builder() |
| 346 | + .setPurpose(getPurpose(softwareEnforcedTaggedObjects)) |
| 347 | + .setAlgorithm(getAlgorithm(softwareEnforcedTaggedObjects)) |
| 348 | + .build(); |
| 349 | + } |
| 350 | +} |
0 commit comments