Skip to content

Commit

Permalink
Merge pull request #113 from subrahmanyaman/hmac_sign_len_validation
Browse files Browse the repository at this point in the history
Optimizations
  • Loading branch information
mdwivedi authored Apr 19, 2022
2 parents 0d842b6 + cf7dd4a commit 772720f
Show file tree
Hide file tree
Showing 16 changed files with 442 additions and 395 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,9 @@ public void setAttestationIds(short attIdVals) {
KMException.throwIt(KMError.INVALID_ARGUMENT);
}
obj = KMByteTag.cast(obj).getValue();
if (KMByteBlob.cast(obj).length() > KMConfigurations.MAX_ATTESTATION_IDS_SIZE) {
KMException.throwIt(KMError.INVALID_INPUT_LENGTH);
}
kmDataStore.setAttestationId(key, KMByteBlob.cast(obj).getBuffer(),
KMByteBlob.cast(obj).getStartOff(), KMByteBlob.cast(obj).length());
index++;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public class KMAttestationCertImpl implements KMAttestationCert {
KMType.ATTESTATION_ID_SERIAL, KMType.ATTESTATION_ID_PRODUCT,
KMType.ATTESTATION_ID_DEVICE, KMType.ATTESTATION_ID_BRAND,
KMType.OS_PATCH_LEVEL, KMType.OS_VERSION, KMType.ROOT_OF_TRUST,
KMType.ORIGIN, KMType.UNLOCKED_DEVICE_REQUIRED,
KMType.ORIGIN, KMType.UNLOCKED_DEVICE_REQUIRED,
KMType.TRUSTED_CONFIRMATION_REQUIRED,
KMType.AUTH_TIMEOUT, KMType.USER_AUTH_TYPE,
KMType.NO_AUTH_REQUIRED, KMType.EARLY_BOOT_ONLY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ public class KMConfigurations {
public static final byte LITTLE_ENDIAN = 0x00;
public static final byte BIG_ENDIAN = 0x01;
public static final byte TEE_MACHINE_TYPE = LITTLE_ENDIAN;
// If the size of the attestation ids is known and lesser than 64
// then reduce the size here. It reduces the heap memory usage.
public static final byte MAX_ATTESTATION_IDS_SIZE = 64;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ public class KMConfigurations {
public static final byte LITTLE_ENDIAN = 0x00;
public static final byte BIG_ENDIAN = 0x01;
public static final byte TEE_MACHINE_TYPE = LITTLE_ENDIAN;
// If the size of the attestation ids is known and lesser than 64
// then reduce the size here. It reduces the heap memory usage.
public static final byte MAX_ATTESTATION_IDS_SIZE = 64;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,22 @@
import com.android.javacard.seprovider.KMException;
import javacard.framework.Util;

public class KMPKCS8Decoder {
public class KMAsn1Parser {
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[] EC_CURVE = {
0x06,0x08,0x2a,(byte)0x86,0x48,(byte)0xce,0x3d,0x03,
0x01,0x07
Expand All @@ -23,12 +32,55 @@ public class KMPKCS8Decoder {
0x3d,0x02,0x01,0x06,0x08,0x2a,(byte)0x86,0x48,
(byte)0xce,0x3d,0x03,0x01,0x07
};

//https://datatracker.ietf.org/doc/html/rfc5280, RFC 5280, Page 21
// 2.5.4
public byte[] COMMON_OID = new byte[] {
0x06, 0x03, 0x55, 0x04
};
// 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 */
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.
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 */
0x10, /* 1-16 givenName */
0x05, /* 1-5 initials */
0x03, /* 1-3 gen qualifier */
0x40, /* 1-64 dn-qualifier */
(byte) 0x80 /* 1-128 pseudonym */
};
private byte[] data;
private short start;
private short length;
private short cur;
private static KMPKCS8Decoder inst;
private KMPKCS8Decoder(){
private static KMAsn1Parser inst;
private KMAsn1Parser(){
start = 0;
length = 0;
cur = 0;
Expand All @@ -45,6 +97,18 @@ public short decodeEc(short blob){
decodeCommon((short)0, EC_ALGORITHM);
return decodeEcPrivateKey((short)1);
}

public void validateDerSubject(short blob) {
init(blob);
header(ASN1_SEQUENCE);
while (cur < ((short) (start + length))) {
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);
Expand Down Expand Up @@ -183,6 +247,46 @@ private void validateTag0IfPresent(){
if(Util.arrayCompare(data, cur, EC_CURVE, (short)0, len) != 0) KMException.throwIt(KMError.UNKNOWN_ERROR);
incrementCursor(len);
}

private void validateAttributeTypeAndValue() {
short start = cur;
if (getByte() != OBJECT_IDENTIFIER) {
KMException.throwIt(KMError.UNKNOWN_ERROR);
}
short length = getLength();
if (length != 3) {
KMException.throwIt(KMError.UNKNOWN_ERROR);
}
cur = start;
boolean found = false;
for(short i = 0; i < (short) attributeOIds.length; i++) {
if ((Util.arrayCompare(data, cur, COMMON_OID, (short)0, (short) COMMON_OID.length) == 0) &&
(attributeOIds[i] == data[(short)(cur + 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);
}
length = getLength();
if (length <= 0 && length > attributeValueMaxLen[i]) {
KMException.throwIt(KMError.UNKNOWN_ERROR);
}
incrementCursor(length);
found = true;
break;
}
}
if (!found) {
// None of the attributes matches.
KMException.throwIt(KMError.UNKNOWN_ERROR);
}
}

private short header(short tag){
short t = getByte();
if(t != tag) KMException.throwIt(KMError.UNKNOWN_ERROR);
Expand Down Expand Up @@ -222,9 +326,9 @@ private short getLength(){
else KMException.throwIt(KMError.UNKNOWN_ERROR);
return KMType.INVALID_VALUE; //should not come here
}
public static KMPKCS8Decoder instance() {
public static KMAsn1Parser instance() {
if (inst == null) {
inst = new KMPKCS8Decoder();
inst = new KMAsn1Parser();
}
return inst;
}
Expand Down
41 changes: 26 additions & 15 deletions Applet/src/com/android/javacard/keymaster/KMByteTag.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,23 +98,19 @@ public short length() {
private static boolean validateKey(short key, short byteBlob) {
short valueLen = KMByteBlob.cast(byteBlob).length();
switch (key) {
case ROOT_OF_TRUST:
case UNIQUE_ID:
case ATTESTATION_APPLICATION_ID:
case ATTESTATION_ID_BRAND:
case ATTESTATION_ID_DEVICE:
case ATTESTATION_ID_PRODUCT:
case ATTESTATION_ID_SERIAL:
case ATTESTATION_ID_IMEI:
case ATTESTATION_ID_MEID:
case ATTESTATION_ID_MANUFACTURER:
case ATTESTATION_ID_MODEL:
case ASSOCIATED_DATA:
case NONCE:
case CONFIRMATION_TOKEN:
case VERIFIED_BOOT_KEY:
case VERIFIED_BOOT_HASH:
if (valueLen > MAX_ATTESTATION_APP_ID_SIZE) {
return false;
}
break;
case CERTIFICATE_SUBJECT_NAME:
{
if (valueLen > MAX_SUBJECT_DER_LEN) {
return false;
}
KMAsn1Parser asn1Decoder = KMAsn1Parser.instance();
asn1Decoder.validateDerSubject(byteBlob);
}
break;
case APPLICATION_ID:
case APPLICATION_DATA:
Expand All @@ -127,6 +123,21 @@ private static boolean validateKey(short key, short byteBlob) {
return false;
}
break;
case ATTESTATION_ID_BRAND:
case ATTESTATION_ID_DEVICE:
case ATTESTATION_ID_PRODUCT:
case ATTESTATION_ID_SERIAL:
case ATTESTATION_ID_IMEI:
case ATTESTATION_ID_MEID:
case ATTESTATION_ID_MANUFACTURER:
case ATTESTATION_ID_MODEL:
if (valueLen > KMConfigurations.MAX_ATTESTATION_IDS_SIZE) {
return false;
}
break;
case ROOT_OF_TRUST:
case NONCE:
break;
default:
return false;
}
Expand Down
4 changes: 2 additions & 2 deletions Applet/src/com/android/javacard/keymaster/KMCoseMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ private static boolean compareAndSwap(short ptr, short index) {
firstKey = KMMap.cast(ptr).getKey(index);
secondKey = KMMap.cast(ptr).getKey((short) (index + 1));
}
firstKeyLen = KMKeymasterApplet.encoder.encode(firstKey, scratchpad, (short) 0);
secondKeyLen = KMKeymasterApplet.encoder.encode(secondKey, scratchpad, firstKeyLen);
firstKeyLen = KMKeymasterApplet.encoder.encode(firstKey, scratchpad, (short) 0, (short) scratchpad.length);
secondKeyLen = KMKeymasterApplet.encoder.encode(secondKey, scratchpad, firstKeyLen, (short) scratchpad.length);
if ((firstKeyLen > secondKeyLen) ||
((firstKeyLen == secondKeyLen) &&
(0 < Util.arrayCompare(scratchpad, (short) 0, scratchpad, firstKeyLen, firstKeyLen)))) {
Expand Down
11 changes: 7 additions & 4 deletions Applet/src/com/android/javacard/keymaster/KMDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -363,10 +363,13 @@ private short decodeKeyParam(short exp) {
obj = decode(tagClass);
KMArray.cast(vals).add(arrPos++, obj);
break;
}catch(KMException e){
if(KMException.reason() == KMError.INVALID_TAG &&
!ignoreInvalidTags){
KMException.throwIt(KMError.INVALID_TAG);
} catch(KMException e){
if (KMException.reason() == KMError.INVALID_TAG) {
if(!ignoreInvalidTags){
KMException.throwIt(KMError.INVALID_TAG);
}
}else {
KMException.throwIt(KMException.reason());
}
break;
}
Expand Down
68 changes: 27 additions & 41 deletions Applet/src/com/android/javacard/keymaster/KMEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,70 +77,56 @@ private void encode(short obj) {
push(obj);
}

public short encode(short object, byte[] buffer, short startOff) {
/**
* This functions encodes the given object into the provider buffer space
* in cbor format.
*
* @param object Object to be encoded into cbor data.
* @param buffer Output where cbor data is copied.
* @param startOff is the start offset of the buffer.
* @param bufLen length of the buffer
* @param encoderOutLimitLen excepted encoded output length.
* @return length of the encoded buffer.
*/
public short encode(short object, byte[] buffer, short startOff, short bufLen,
short encoderOutLimitLen) {
scratchBuf[STACK_PTR_OFFSET] = 0;
bufferRef[0] = buffer;
scratchBuf[START_OFFSET] = startOff;
short len = (short) (buffer.length - startOff);
if ((len < 0) || len > KMRepository.HEAP_SIZE) {
scratchBuf[LEN_OFFSET] = KMRepository.HEAP_SIZE;
} else {
scratchBuf[LEN_OFFSET] = (short) buffer.length;
if ((short) (startOff + encoderOutLimitLen) > bufLen) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
//this.length = (short)(startOff + length);
scratchBuf[LEN_OFFSET] = (short) (startOff + encoderOutLimitLen);
push(object);
encode();
return (short) (scratchBuf[START_OFFSET] - startOff);
}

// array{KMError.OK,Array{KMByteBlobs}}
public void encodeCertChain(byte[] buffer, short offset, short length, short errInt32Ptr) {
bufferRef[0] = buffer;
scratchBuf[START_OFFSET] = offset;
scratchBuf[LEN_OFFSET] = (short) (offset + 1);
//Total length is ArrayHeader + [UIntHeader + length(errInt32Ptr)]
scratchBuf[LEN_OFFSET] += (short) (1 + getEncodedIntegerLength(errInt32Ptr));

writeMajorTypeWithLength(ARRAY_TYPE, (short) 2); // Array of 2 elements
encodeUnsignedInteger(errInt32Ptr);
public short encode(short object, byte[] buffer, short startOff, short bufLen) {
return encode(object, buffer, startOff, bufLen, (short) (bufLen - startOff));
}

//array{KMError.OK,Array{KMByteBlobs}}
public short encodeCert(byte[] certBuffer, short bufferStart, short certStart, short certLength, short errInt32Ptr) {
public short encodeCert(byte[] certBuffer, short bufferStart, short certStart, short certLength) {
if (bufferStart > certStart) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
bufferRef[0] = certBuffer;
scratchBuf[START_OFFSET] = certStart;
scratchBuf[LEN_OFFSET] = (short) (certStart + 1);
//Array header - 2 elements i.e. 1 byte
scratchBuf[START_OFFSET]--;
// errInt32Ptr - PowerResetStatus + ErrorCode - 4 bytes
// Integer header - 1 byte
scratchBuf[START_OFFSET] -= getEncodedIntegerLength(errInt32Ptr);
//Array header - 2 elements i.e. 1 byte
// Byte Header + cert length
scratchBuf[START_OFFSET] -= getEncodedBytesLength(certLength);
//Array header - 1 elements i.e. 1 byte
scratchBuf[START_OFFSET]--;
// Cert Byte blob - typically 2 bytes length i.e. 3 bytes header
scratchBuf[START_OFFSET] -= 2;
if (certLength >= SHORT_PAYLOAD) {
scratchBuf[START_OFFSET]--;
}
if (scratchBuf[START_OFFSET] < bufferStart) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
bufferStart = scratchBuf[START_OFFSET];
writeMajorTypeWithLength(ARRAY_TYPE, (short) 2); // Array of 2 elements
encodeUnsignedInteger(errInt32Ptr); //PowerResetStatus + ErrorCode
writeMajorTypeWithLength(ARRAY_TYPE, (short) 1); // Array of 1 element
writeMajorTypeWithLength(ARRAY_TYPE, (short) 1); // Array of 1 elements
writeMajorTypeWithLength(BYTES_TYPE, certLength); // Cert Byte Blob of length
return bufferStart;
}

public short encodeError(short errInt32Ptr, byte[] buffer, short startOff, short length) {
bufferRef[0] = buffer;
scratchBuf[START_OFFSET] = startOff;
scratchBuf[LEN_OFFSET] = (short) (startOff + length + 1);
encodeUnsignedInteger(errInt32Ptr);
return (short) (scratchBuf[START_OFFSET] - startOff);
}

private void encode() {
while (scratchBuf[STACK_PTR_OFFSET] > 0) {
short exp = pop();
Expand Down Expand Up @@ -637,7 +623,7 @@ private short getEncodedArrayLen(short obj) {
return len;
}

private short getEncodedBytesLength(short len) {
public short getEncodedBytesLength(short len) {
short ret = 0;
if (len < KMEncoder.UINT8_LENGTH && len >= 0) {
ret = 1;
Expand Down
Loading

0 comments on commit 772720f

Please sign in to comment.