1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.weaver.core; 18 19 import javacard.framework.ISO7816; 20 import javacard.framework.ISOException; 21 import javacard.framework.JCSystem; 22 import javacard.framework.Util; 23 24 import com.android.weaver.Consts; 25 import com.android.weaver.Slots; 26 27 import com.nxp.id.jcopx.util.DSTimer; 28 29 class CoreSlots implements Slots { 30 static final byte NUM_SLOTS = 64; 31 32 private Slot[] mSlots; 33 CoreSlots()34 CoreSlots() { 35 // Allocate all memory up front 36 mSlots = new Slot[NUM_SLOTS]; 37 for (short i = 0; i < NUM_SLOTS; ++i) { 38 mSlots[i] = new Slot(); 39 } 40 41 // Make the same size as the value so the whole buffer can be copied in read() so there is 42 // no time difference between success and failure. 43 Slot.sRemainingBackoff = JCSystem.makeTransientByteArray( 44 Consts.SLOT_VALUE_BYTES, JCSystem.CLEAR_ON_RESET); 45 } 46 47 @Override getNumSlots()48 public short getNumSlots() { 49 return NUM_SLOTS; 50 } 51 52 @Override write(short rawSlotId, byte[] key, short keyOffset, byte[] value, short valueOffset)53 public void write(short rawSlotId, byte[] key, short keyOffset, 54 byte[] value, short valueOffset) { 55 final short slotId = validateSlotId(rawSlotId); 56 mSlots[slotId].write(key, keyOffset, value, valueOffset); 57 } 58 59 @Override read(short rawSlotId, byte[] key, short keyOffset, byte[] outValue, short outOffset)60 public byte read(short rawSlotId, byte[] key, short keyOffset, 61 byte[] outValue, short outOffset) { 62 final short slotId = validateSlotId(rawSlotId); 63 return mSlots[slotId].read(key, keyOffset, outValue, outOffset); 64 } 65 66 @Override eraseValue(short rawSlotId)67 public void eraseValue(short rawSlotId) { 68 final short slotId = validateSlotId(rawSlotId); 69 mSlots[slotId].eraseValue(); 70 } 71 72 @Override eraseAll()73 public void eraseAll() { 74 for (short i = 0; i < NUM_SLOTS; ++i) { 75 mSlots[i].erase(); 76 } 77 } 78 79 /** 80 * Check the slot ID is within range and convert it to a short. 81 */ validateSlotId(short slotId)82 private short validateSlotId(short slotId) { 83 // slotId is unsigned so if the signed version is negative then it is far too big 84 if (slotId < 0 || slotId >= NUM_SLOTS) { 85 ISOException.throwIt(Consts.SW_INVALID_SLOT_ID); 86 } 87 return slotId; 88 } 89 90 private static class Slot { 91 private static byte[] sRemainingBackoff; 92 93 private byte[] mKey = new byte[Consts.SLOT_KEY_BYTES]; 94 private byte[] mValue = new byte[Consts.SLOT_VALUE_BYTES]; 95 private short mFailureCount; 96 private DSTimer mBackoffTimer; 97 98 /** 99 * Transactionally reset the slot with a new key and value. 100 * 101 * @param keyBuffer the buffer containing the key data 102 * @param keyOffset the offset of the key in its buffer 103 * @param valueBuffer the buffer containing the value data 104 * @param valueOffset the offset of the value in its buffer 105 */ write( byte[] keyBuffer, short keyOffset, byte[] valueBuffer, short valueOffset)106 public void write( 107 byte[] keyBuffer, short keyOffset, byte[] valueBuffer, short valueOffset) { 108 JCSystem.beginTransaction(); 109 Util.arrayCopy(keyBuffer, keyOffset, mKey, (short) 0, Consts.SLOT_KEY_BYTES); 110 Util.arrayCopy(valueBuffer, valueOffset, mValue, (short) 0, Consts.SLOT_VALUE_BYTES); 111 mFailureCount = 0; 112 mBackoffTimer = DSTimer.getInstance(); 113 JCSystem.commitTransaction(); 114 } 115 116 /** 117 * Clear the slot's value. 118 */ eraseValue()119 public void eraseValue() { 120 // This is intended to be destructive so a partial update is not a problem 121 Util.arrayFillNonAtomic(mValue, (short) 0, Consts.SLOT_VALUE_BYTES, (byte) 0); 122 } 123 124 /** 125 * Transactionally clear the slot. 126 */ erase()127 public void erase() { 128 JCSystem.beginTransaction(); 129 arrayFill(mKey, (short) 0, Consts.SLOT_KEY_BYTES, (byte) 0); 130 arrayFill(mValue, (short) 0, Consts.SLOT_VALUE_BYTES, (byte) 0); 131 mFailureCount = 0; 132 mBackoffTimer.stopTimer(); 133 JCSystem.commitTransaction(); 134 } 135 136 /** 137 * Copy the slot's value to the buffer if the provided key matches the slot's key. 138 * 139 * @param keyBuffer the buffer containing the key 140 * @param keyOffset the offset of the key in its buffer 141 * @param outBuffer the buffer to copy the value or backoff time into 142 * @param outOffset the offset into the output buffer 143 * @return status code 144 */ read(byte[] keyBuffer, short keyOffset, byte[] outBuffer, short outOffset)145 public byte read(byte[] keyBuffer, short keyOffset, byte[] outBuffer, short outOffset) { 146 // Check timeout has expired or hasn't been started 147 mBackoffTimer.getRemainingTime(sRemainingBackoff, (short) 0); 148 if (sRemainingBackoff[0] != 0 || sRemainingBackoff[1] != 0 || 149 sRemainingBackoff[2] != 0 || sRemainingBackoff[3] != 0) { 150 Util.arrayCopyNonAtomic( 151 sRemainingBackoff, (short) 0, outBuffer, outOffset, (byte) 4); 152 return Consts.READ_BACK_OFF; 153 } 154 155 // Check the key matches in constant time and copy out the value if it does 156 final byte result = (Util.arrayCompare( 157 keyBuffer, keyOffset, mKey, (short) 0, Consts.SLOT_KEY_BYTES) == 0) ? 158 Consts.READ_SUCCESS : Consts.READ_WRONG_KEY; 159 160 // Keep track of the number of failures 161 if (result == Consts.READ_WRONG_KEY) { 162 if (mFailureCount != 0x7fff) { 163 mFailureCount += 1; 164 } 165 } else { 166 // This read was successful so reset the failures 167 if (mFailureCount != 0) { // attempt to maintain constant time 168 mFailureCount = 0; 169 } 170 } 171 172 // Start the timer on a failure 173 if (throttle(sRemainingBackoff, (short) 0, mFailureCount)) { 174 mBackoffTimer.startTimer( 175 sRemainingBackoff, (short) 0, DSTimer.DST_POWEROFFMODE_FALLBACK); 176 } else { 177 mBackoffTimer.stopTimer(); 178 } 179 180 final byte[] data = (result == Consts.READ_SUCCESS) ? mValue : sRemainingBackoff; 181 Util.arrayCopyNonAtomic(data, (short) 0, outBuffer, outOffset, Consts.SLOT_VALUE_BYTES); 182 183 return result; 184 } 185 186 /** 187 * 3.0.3 does not offer Util.arrayFill 188 */ arrayFill(byte[] bArray, short bOff, short bLen, byte bValue)189 private static void arrayFill(byte[] bArray, short bOff, short bLen, byte bValue) { 190 for (short i = 0; i < bLen; ++i) { 191 bArray[(short) (bOff + i)] = bValue; 192 } 193 } 194 195 /** 196 * Calculates the timeout in seconds as a function of the failure 197 * counter 'x' as follows: 198 * 199 * [0, 5) -> 0 200 * 5 -> 30 201 * [6, 10) -> 0 202 * [11, 30) -> 30 203 * [30, 140) -> 30 * (2^((x - 30)/10)) 204 * [140, inf) -> 1 day 205 * 206 * The 32-bit timeout in seconds is written to the array. 207 * 208 * @return Whether there is any throttle time. 209 */ throttle(byte[] bArray, short bOff, short failureCount)210 private static boolean throttle(byte[] bArray, short bOff, short failureCount) { 211 short highWord = 0; 212 short lowWord = 0; 213 214 final short thirtySeconds = 30; 215 if (failureCount == 0) { 216 // 0s 217 } else if (failureCount > 0 && failureCount <= 10) { 218 if (failureCount % 5 == 0) { 219 // 30s 220 lowWord = thirtySeconds; 221 } else { 222 // 0s 223 } 224 } else if (failureCount < 30) { 225 // 30s 226 lowWord = thirtySeconds; 227 } else if (failureCount < 140) { 228 // 30 * (2^((x - 30)/10)) 229 final short shift = (short) ((short) (failureCount - 30) / 10); 230 lowWord = (short) (thirtySeconds << shift); 231 } else { 232 // 1 day in seconds = 24 * 60 * 60 = 0x1 5180 233 highWord = 0x1; 234 lowWord = 0x5180; 235 } 236 237 // Write the value to the buffer 238 Util.setShort(bArray, bOff, highWord); 239 Util.setShort(bArray, (short) (bOff + 2), lowWord); 240 241 return highWord != 0 || lowWord != 0; 242 } 243 } 244 } 245