• 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 
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