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 hasRemainingBackOff()136 private boolean hasRemainingBackOff() { 137 return ((0 != Util.getShort(sRemainingBackoff, (short) 0)) || 138 (0 != Util.getShort(sRemainingBackoff, (short) 2))); 139 } 140 141 /** 142 * Copy the slot's value to the buffer if the provided key matches the slot's key. 143 * 144 * @param keyBuffer the buffer containing the key 145 * @param keyOffset the offset of the key in its buffer 146 * @param outBuffer the buffer to copy the value or backoff time into 147 * @param outOffset the offset into the output buffer 148 * @return status code 149 */ read(byte[] keyBuffer, short keyOffset, byte[] outBuffer, short outOffset)150 public byte read(byte[] keyBuffer, short keyOffset, byte[] outBuffer, short outOffset) { 151 // Check timeout has expired or hasn't been started 152 mBackoffTimer.getRemainingTime(sRemainingBackoff, (short) 0); 153 if (hasRemainingBackOff()) { 154 Util.arrayCopyNonAtomic( 155 sRemainingBackoff, (short) 0, outBuffer, outOffset, (byte) 4); 156 return Consts.READ_BACK_OFF; 157 } 158 159 // Assume this read will fail 160 if (mFailureCount != 0x7fff) { 161 mFailureCount += 1; 162 } 163 byte result = Consts.READ_WRONG_KEY; 164 165 // Start the timer on a failure 166 if (throttle(sRemainingBackoff, (short) 0, mFailureCount)) { 167 mBackoffTimer.startTimer( 168 sRemainingBackoff, (short) 0, DSTimer.DST_POWEROFFMODE_FALLBACK); 169 result = Consts.READ_BACK_OFF; 170 } else { 171 mBackoffTimer.stopTimer(); 172 } 173 174 // Check the key matches in constant time and copy out the value if it does 175 result = (Util.arrayCompare( 176 keyBuffer, keyOffset, mKey, (short) 0, Consts.SLOT_KEY_BYTES) == 0) ? 177 Consts.READ_SUCCESS : result; 178 179 // Keep track of the number of failures 180 if (result == Consts.READ_SUCCESS) { 181 // This read was successful so reset the failures 182 mFailureCount = 0; 183 mBackoffTimer.stopTimer(); 184 } 185 186 final byte[] data = (result == Consts.READ_SUCCESS) ? mValue : sRemainingBackoff; 187 Util.arrayCopyNonAtomic(data, (short) 0, outBuffer, outOffset, Consts.SLOT_VALUE_BYTES); 188 189 return result; 190 } 191 192 /** 193 * 3.0.3 does not offer Util.arrayFill 194 */ arrayFill(byte[] bArray, short bOff, short bLen, byte bValue)195 private static void arrayFill(byte[] bArray, short bOff, short bLen, byte bValue) { 196 for (short i = 0; i < bLen; ++i) { 197 bArray[(short) (bOff + i)] = bValue; 198 } 199 } 200 201 /** 202 * Calculates the timeout in seconds as a function of the failure 203 * counter 'x' as follows: 204 * 205 * [0, 5) -> 0 206 * 5 -> 30 207 * [6, 10) -> 0 208 * [11, 30) -> 30 209 * [30, 140) -> 30 * (2^((x - 30)/10)) 210 * [140, inf) -> 1 day 211 * 212 * The 32-bit timeout in seconds is written to the array. 213 * 214 * @return Whether there is any throttle time. 215 */ throttle(byte[] bArray, short bOff, short failureCount)216 private static boolean throttle(byte[] bArray, short bOff, short failureCount) { 217 short highWord = 0; 218 short lowWord = 0; 219 220 final short thirtySeconds = 30; 221 if (failureCount == 0) { 222 // 0s 223 } else if (failureCount > 0 && failureCount <= 10) { 224 if (failureCount % 5 == 0) { 225 // 30s 226 lowWord = thirtySeconds; 227 } else { 228 // 0s 229 } 230 } else if (failureCount < 30) { 231 // 30s 232 lowWord = thirtySeconds; 233 } else if (failureCount < 140) { 234 // 30 * (2^((x - 30)/10)) 235 final short shift = (short) ((short) (failureCount - 30) / 10); 236 lowWord = (short) (thirtySeconds << shift); 237 } else { 238 // 1 day in seconds = 24 * 60 * 60 = 0x1 5180 239 highWord = 0x1; 240 lowWord = 0x5180; 241 } 242 243 // Write the value to the buffer 244 Util.setShort(bArray, bOff, highWord); 245 Util.setShort(bArray, (short) (bOff + 2), lowWord); 246 247 return highWord != 0 || lowWord != 0; 248 } 249 } 250 } 251