aboutsummaryrefslogtreecommitdiff
path: root/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java
diff options
context:
space:
mode:
Diffstat (limited to 'ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java')
-rw-r--r--ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java471
1 files changed, 471 insertions, 0 deletions
diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java
new file mode 100644
index 0000000..22a16a3
--- /dev/null
+++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java
@@ -0,0 +1,471 @@
+package com.android.javacard.keymaster;
+
+import com.android.javacard.seprovider.KMException;
+import javacard.framework.JCSystem;
+import javacard.framework.Util;
+
+/**
+ * This is a utility class that helps in parsing the PKCS8 encoded RSA and EC keys, certificate
+ * subject, subjectPublicKey info and ECDSA signatures.
+ */
+public class KMAsn1Parser {
+
+ // Below are the ASN.1 tag types
+ public static final byte ASN1_OCTET_STRING = 0x04;
+ public static final byte ASN1_SEQUENCE = 0x30;
+ public static final byte ASN1_SET = 0x31;
+ public static final byte ASN1_INTEGER = 0x02;
+ public static final byte OBJECT_IDENTIFIER = 0x06;
+ public static final byte ASN1_A0_TAG = (byte) 0xA0;
+ public static final byte ASN1_A1_TAG = (byte) 0xA1;
+ public static final byte ASN1_BIT_STRING = 0x03;
+
+ public static final byte ASN1_UTF8_STRING = 0x0C;
+ public static final byte ASN1_TELETEX_STRING = 0x14;
+ public static final byte ASN1_PRINTABLE_STRING = 0x13;
+ public static final byte ASN1_UNIVERSAL_STRING = 0x1C;
+ public static final byte ASN1_BMP_STRING = 0x1E;
+ public static final byte IA5_STRING = 0x16;
+ // OID of the EC P256 curve 1.2.840.10045.3.1.7
+ public static final byte[] EC_CURVE = {
+ 0x06, 0x08, 0x2a, (byte) 0x86, 0x48, (byte) 0xce, 0x3d, 0x03, 0x01, 0x07
+ };
+ // Constant for rsaEncryption pkcs#1 (1.2.840.113549.1.1.1) and NULL
+ public static final byte[] RSA_ALGORITHM = {
+ 0x06,
+ 0x09,
+ 0x2A,
+ (byte) 0x86,
+ 0x48,
+ (byte) 0x86,
+ (byte) 0xF7,
+ 0x0D,
+ 0x01,
+ 0x01,
+ 0x01,
+ 0x05,
+ 0x00
+ };
+ // Constant for ecPublicKey (1.2.840.10045.2.1) and prime256v1 (1.2.840.10045.3.1.7)
+ public static final byte[] EC_ALGORITHM = {
+ 0x06,
+ 0x07,
+ 0x2a,
+ (byte) 0x86,
+ 0x48,
+ (byte) 0xce,
+ 0x3d,
+ 0x02,
+ 0x01,
+ 0x06,
+ 0x08,
+ 0x2a,
+ (byte) 0x86,
+ 0x48,
+ (byte) 0xce,
+ 0x3d,
+ 0x03,
+ 0x01,
+ 0x07
+ };
+ // The maximum length of email id attribute.
+ public static final short MAX_EMAIL_ADD_LEN = 255;
+ // Datatable offsets.
+ private static final byte DATA_START_OFFSET = 0;
+ private static final byte DATA_LENGTH_OFFSET = 1;
+ private static final byte DATA_CURSOR_OFFSET = 2;
+ // This array contains the last byte of OID for each oid type.
+ // The first 4 bytes are common as shown above in COMMON_OID
+ private static final byte[] attributeOIds = {
+ 0x03, /* commonName COMMON_OID.3 */ 0x04, /* surName COMMON_OID.4*/
+ 0x05, /* serialNumber COMMON_OID.5 */ 0x06, /* countryName COMMON_OID.6 */
+ 0x07, /* locality COMMON_OID.7 */ 0x08, /* stateOrProviince COMMON_OID.8 */
+ 0x0A, /* organizationName COMMON_OID.10 */ 0x0B, /* organizationalUnitName COMMON_OID.11 */
+ 0x0C, /* title COMMON_OID.10 */ 0x29, /* name COMMON_OID.41 */
+ 0x2A, /* givenName COMMON_OID.42 */ 0x2B, /* initials COMMON_OID.43 */
+ 0x2C, /* generationQualifier COMMON_OID.44 */ 0x2E, /* dnQualifer COMMON_OID.46 */
+ 0x41, /* pseudonym COMMON_OID.65 */
+ };
+ // https://datatracker.ietf.org/doc/html/rfc5280, RFC 5280, Page 124
+ // TODO Specification does not mention about the DN_QUALIFIER_OID max length.
+ // So the max limit is set at 64.
+ // For name the RFC 5280 supports up to 32768, as Javacard doesn't support
+ // that much length, the max limit for name is set to 128.
+ private static final byte[] attributeValueMaxLen = {
+ 0x40, /* 1-64 commonName */
+ 0x28, /* 1-40 surname */
+ 0x40, /* 1-64 serial */
+ 0x02, /* 1-2 country */
+ (byte) 0x80, /* 1-128 locality */
+ (byte) 0x80, /* 1-128 state */
+ 0x40, /* 1-64 organization */
+ 0x40, /* 1-64 organization unit*/
+ 0x40, /* 1-64 title */
+ 0x29, /* 1-128 name */
+ 0x10, /* 1-16 givenName */
+ 0x05, /* 1-5 initials */
+ 0x03, /* 1-3 gen qualifier */
+ 0x40, /* 1-64 dn-qualifier */
+ (byte) 0x80 /* 1-128 pseudonym */
+ };
+ private static KMAsn1Parser inst;
+ // https://datatracker.ietf.org/doc/html/rfc5280, RFC 5280, Page 21
+ // 2.5.4
+ public byte[] COMMON_OID = new byte[] {0x06, 0x03, 0x55, 0x04};
+ public byte[] EMAIL_ADDRESS_OID =
+ new byte[] {
+ 0x06, 0x09, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0x0D, 0x01, 0x09, 0x01
+ };
+ private byte[] data;
+ private short[] dataInfo;
+
+ private KMAsn1Parser() {
+ dataInfo = JCSystem.makeTransientShortArray((short) 3, JCSystem.CLEAR_ON_RESET);
+ dataInfo[DATA_START_OFFSET] = 0;
+ dataInfo[DATA_LENGTH_OFFSET] = 0;
+ dataInfo[DATA_CURSOR_OFFSET] = 0;
+ }
+
+ public static KMAsn1Parser instance() {
+ if (inst == null) {
+ inst = new KMAsn1Parser();
+ }
+ return inst;
+ }
+
+ public short decodeRsa(short blob) {
+ init(blob);
+ decodeCommon((short) 0, RSA_ALGORITHM);
+ return decodeRsaPrivateKey((short) 0);
+ }
+
+ public short decodeEc(short blob) {
+ init(blob);
+ decodeCommon((short) 0, EC_ALGORITHM);
+ return decodeEcPrivateKey((short) 1);
+ }
+
+ /*
+ Name ::= CHOICE { -- only one possibility for now --
+ rdnSequence RDNSequence }
+ RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+ RelativeDistinguishedName ::=
+ SET SIZE (1..MAX) OF AttributeTypeAndValue
+ AttributeTypeAndValue ::= SEQUENCE {
+ type AttributeType,
+ value AttributeValue }
+ AttributeType ::= OBJECT IDENTIFIER
+ AttributeValue ::= ANY -- DEFINED BY AttributeType
+ */
+ public void validateDerSubject(short blob) {
+ init(blob);
+ header(ASN1_SEQUENCE);
+ while (dataInfo[DATA_CURSOR_OFFSET]
+ < ((short) (dataInfo[DATA_START_OFFSET] + dataInfo[DATA_LENGTH_OFFSET]))) {
+ header(ASN1_SET);
+ header(ASN1_SEQUENCE);
+ // Parse and validate OBJECT-IDENTIFIER and Value fields
+ // Cursor is incremented in validateAttributeTypeAndValue.
+ validateAttributeTypeAndValue();
+ }
+ }
+
+ public short decodeEcSubjectPublicKeyInfo(short blob) {
+ init(blob);
+ header(ASN1_SEQUENCE);
+ short len = header(ASN1_SEQUENCE);
+ short ecPublicInfo = KMByteBlob.instance(len);
+ getBytes(ecPublicInfo);
+ if (Util.arrayCompare(
+ KMByteBlob.cast(ecPublicInfo).getBuffer(),
+ KMByteBlob.cast(ecPublicInfo).getStartOff(),
+ EC_ALGORITHM,
+ (short) 0,
+ KMByteBlob.cast(ecPublicInfo).length())
+ != 0) {
+ KMException.throwIt(KMError.UNKNOWN_ERROR);
+ }
+ len = header(ASN1_BIT_STRING);
+ if (len < 1) {
+ KMException.throwIt(KMError.UNKNOWN_ERROR);
+ }
+ // TODO need to handle if unused bits are not zero
+ byte unusedBits = getByte();
+ if (unusedBits != 0) {
+ KMException.throwIt(KMError.UNIMPLEMENTED);
+ }
+ short pubKey = KMByteBlob.instance((short) (len - 1));
+ getBytes(pubKey);
+ return pubKey;
+ }
+
+ // Seq[Int,Int,Int,Int,<ignore rest>]
+ public short decodeRsaPrivateKey(short version) {
+ short resp = KMArray.instance((short) 3);
+ header(ASN1_OCTET_STRING);
+ header(ASN1_SEQUENCE);
+ short len = header(ASN1_INTEGER);
+ if (len != 1) {
+ KMException.throwIt(KMError.UNKNOWN_ERROR);
+ }
+ short ver = getByte();
+ if (ver != version) {
+ KMException.throwIt(KMError.UNKNOWN_ERROR);
+ }
+ len = header(ASN1_INTEGER);
+ short modulus = KMByteBlob.instance(len);
+ getBytes(modulus);
+ updateRsaKeyBuffer(modulus);
+ len = header(ASN1_INTEGER);
+ short pubKey = KMByteBlob.instance(len);
+ getBytes(pubKey);
+ len = header(ASN1_INTEGER);
+ short privKey = KMByteBlob.instance(len);
+ getBytes(privKey);
+ updateRsaKeyBuffer(privKey);
+ KMArray.cast(resp).add((short) 0, modulus);
+ KMArray.cast(resp).add((short) 1, pubKey);
+ KMArray.cast(resp).add((short) 2, privKey);
+ return resp;
+ }
+
+ private void updateRsaKeyBuffer(short blob) {
+ byte[] buffer = KMByteBlob.cast(blob).getBuffer();
+ short startOff = KMByteBlob.cast(blob).getStartOff();
+ short len = KMByteBlob.cast(blob).length();
+ if (0 == buffer[startOff] && len > 256) {
+ KMByteBlob.cast(blob).setStartOff(++startOff);
+ KMByteBlob.cast(blob).setLength(--len);
+ }
+ }
+
+ private short readEcdsa256SigIntegerHeader() {
+ short len = header(ASN1_INTEGER);
+ if (len == 33) {
+ if (0 != getByte()) {
+ KMException.throwIt(KMError.INVALID_DATA);
+ }
+ len--;
+ } else if (len > 33) {
+ KMException.throwIt(KMError.INVALID_DATA);
+ }
+ return len;
+ }
+
+ // Seq [Int, Int]
+ public short decodeEcdsa256Signature(short blob, byte[] scratchPad, short scratchPadOff) {
+ init(blob);
+ short len = header(ASN1_SEQUENCE);
+ len = readEcdsa256SigIntegerHeader();
+ // concatenate r and s in the buffer (r||s)
+ Util.arrayFillNonAtomic(scratchPad, scratchPadOff, (short) 64, (byte) 0);
+ // read r
+ getBytes(scratchPad, (short) (scratchPadOff + 32 - len), len);
+ len = readEcdsa256SigIntegerHeader();
+ // read s
+ getBytes(scratchPad, (short) (scratchPadOff + 64 - len), len);
+ return (short) 64;
+ }
+
+ // Seq [Int, Blob]
+ public void decodeCommon(short version, byte[] alg) {
+ short len = header(ASN1_SEQUENCE);
+ len = header(ASN1_INTEGER);
+ if (len != 1) {
+ KMException.throwIt(KMError.UNKNOWN_ERROR);
+ }
+ short ver = getByte();
+ if (ver != version) {
+ KMException.throwIt(KMError.UNKNOWN_ERROR);
+ }
+ len = header(ASN1_SEQUENCE);
+ short blob = KMByteBlob.instance(len);
+ getBytes(blob);
+ if (Util.arrayCompare(
+ KMByteBlob.cast(blob).getBuffer(),
+ KMByteBlob.cast(blob).getStartOff(),
+ alg,
+ (short) 0,
+ KMByteBlob.cast(blob).length())
+ != 0) {
+ KMException.throwIt(KMError.UNKNOWN_ERROR);
+ }
+ }
+
+ // Seq[Int,blob,blob]
+ public short decodeEcPrivateKey(short version) {
+ short resp = KMArray.instance((short) 2);
+ header(ASN1_OCTET_STRING);
+ header(ASN1_SEQUENCE);
+ short len = header(ASN1_INTEGER);
+ if (len != 1) {
+ KMException.throwIt(KMError.UNKNOWN_ERROR);
+ }
+ short ver = getByte();
+ if (ver != version) {
+ KMException.throwIt(KMError.UNKNOWN_ERROR);
+ }
+ len = header(ASN1_OCTET_STRING);
+ short privKey = KMByteBlob.instance(len);
+ getBytes(privKey);
+ validateTag0IfPresent();
+ header(ASN1_A1_TAG);
+ len = header(ASN1_BIT_STRING);
+ if (len < 1) {
+ KMException.throwIt(KMError.UNKNOWN_ERROR);
+ }
+ // TODO need to handle if unused bits are not zero
+ byte unusedBits = getByte();
+ if (unusedBits != 0) {
+ KMException.throwIt(KMError.UNIMPLEMENTED);
+ }
+ short pubKey = KMByteBlob.instance((short) (len - 1));
+ getBytes(pubKey);
+ KMArray.cast(resp).add((short) 0, pubKey);
+ KMArray.cast(resp).add((short) 1, privKey);
+ return resp;
+ }
+
+ private void validateTag0IfPresent() {
+ if (data[dataInfo[DATA_CURSOR_OFFSET]] != ASN1_A0_TAG) {
+ return;
+ }
+ ;
+ short len = header(ASN1_A0_TAG);
+ if (len != EC_CURVE.length) {
+ KMException.throwIt(KMError.UNKNOWN_ERROR);
+ }
+ if (Util.arrayCompare(data, dataInfo[DATA_CURSOR_OFFSET], EC_CURVE, (short) 0, len) != 0) {
+ KMException.throwIt(KMError.UNKNOWN_ERROR);
+ }
+ incrementCursor(len);
+ }
+
+ private void validateAttributeTypeAndValue() {
+ // First byte should be OBJECT_IDENTIFIER, otherwise it is not well-formed DER Subject.
+ if (data[dataInfo[DATA_CURSOR_OFFSET]] != OBJECT_IDENTIFIER) {
+ KMException.throwIt(KMError.UNKNOWN_ERROR);
+ }
+ // Check if the OID matches the email address
+ if ((Util.arrayCompare(
+ data,
+ dataInfo[DATA_CURSOR_OFFSET],
+ EMAIL_ADDRESS_OID,
+ (short) 0,
+ (short) EMAIL_ADDRESS_OID.length)
+ == 0)) {
+ incrementCursor((short) EMAIL_ADDRESS_OID.length);
+ // Validate the length of the attribute value.
+ if (getByte() != IA5_STRING) {
+ KMException.throwIt(KMError.UNKNOWN_ERROR);
+ }
+ short emailLength = getLength();
+ if (emailLength <= 0 && emailLength > MAX_EMAIL_ADD_LEN) {
+ KMException.throwIt(KMError.UNKNOWN_ERROR);
+ }
+ incrementCursor(emailLength);
+ return;
+ }
+ // Check other OIDs.
+ for (short i = 0; i < (short) attributeOIds.length; i++) {
+ if ((Util.arrayCompare(
+ data,
+ dataInfo[DATA_CURSOR_OFFSET],
+ COMMON_OID,
+ (short) 0,
+ (short) COMMON_OID.length)
+ == 0)
+ && (attributeOIds[i]
+ == data[(short) (dataInfo[DATA_CURSOR_OFFSET] + COMMON_OID.length)])) {
+ incrementCursor((short) (COMMON_OID.length + 1));
+ // Validate the length of the attribute value.
+ short tag = getByte();
+ if (tag != ASN1_UTF8_STRING
+ && tag != ASN1_TELETEX_STRING
+ && tag != ASN1_PRINTABLE_STRING
+ && tag != ASN1_UNIVERSAL_STRING
+ && tag != ASN1_BMP_STRING) {
+ KMException.throwIt(KMError.UNKNOWN_ERROR);
+ }
+ short attrValueLength = getLength();
+ if (attrValueLength <= 0 && attrValueLength > attributeValueMaxLen[i]) {
+ KMException.throwIt(KMError.UNKNOWN_ERROR);
+ }
+ incrementCursor(attrValueLength);
+ return;
+ }
+ }
+ // If no match is found above then move the cursor to next element.
+ getByte(); // Move Cursor by one byte (OID)
+ incrementCursor(getLength()); // Move cursor to AtrributeTag
+ getByte(); // Move cursor to AttributeValue
+ incrementCursor(getLength()); // Move cursor to next SET element
+ }
+
+ private short header(short tag) {
+ short t = getByte();
+ if (t != tag) {
+ KMException.throwIt(KMError.UNKNOWN_ERROR);
+ }
+ return getLength();
+ }
+
+ private byte getByte() {
+ byte d = data[dataInfo[DATA_CURSOR_OFFSET]];
+ incrementCursor((short) 1);
+ return d;
+ }
+
+ private short getShort() {
+ short d = Util.getShort(data, dataInfo[DATA_CURSOR_OFFSET]);
+ incrementCursor((short) 2);
+ return d;
+ }
+
+ private void getBytes(short blob) {
+ short len = KMByteBlob.cast(blob).length();
+ Util.arrayCopyNonAtomic(
+ data,
+ dataInfo[DATA_CURSOR_OFFSET],
+ KMByteBlob.cast(blob).getBuffer(),
+ KMByteBlob.cast(blob).getStartOff(),
+ len);
+ incrementCursor(len);
+ }
+
+ private void getBytes(byte[] buffer, short offset, short len) {
+ Util.arrayCopyNonAtomic(data, dataInfo[DATA_CURSOR_OFFSET], buffer, offset, len);
+ incrementCursor(len);
+ }
+
+ private short getLength() {
+ byte len = getByte();
+ if (len >= 0) {
+ return len;
+ }
+ len = (byte) (len & 0x7F);
+ if (len == 1) {
+ return (short) (getByte() & 0xFF);
+ } else if (len == 2) {
+ return getShort();
+ } else {
+ KMException.throwIt(KMError.UNKNOWN_ERROR);
+ }
+ return KMType.INVALID_VALUE; // should not come here
+ }
+
+ public void init(short blob) {
+ data = KMByteBlob.cast(blob).getBuffer();
+ dataInfo[DATA_START_OFFSET] = KMByteBlob.cast(blob).getStartOff();
+ dataInfo[DATA_LENGTH_OFFSET] = KMByteBlob.cast(blob).length();
+ dataInfo[DATA_CURSOR_OFFSET] = dataInfo[DATA_START_OFFSET];
+ }
+
+ public void incrementCursor(short n) {
+ dataInfo[DATA_CURSOR_OFFSET] += n;
+ if (dataInfo[DATA_CURSOR_OFFSET]
+ > ((short) (dataInfo[DATA_START_OFFSET] + dataInfo[DATA_LENGTH_OFFSET]))) {
+ KMException.throwIt(KMError.UNKNOWN_ERROR);
+ }
+ }
+}