• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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