blob: 93ff64a33cded68f00ed4fedc74a7be3a4cf0ed8 [file] [log] [blame]
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);
}
}
}