| // |
| // Copyright (C) 2017 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| |
| package com.android.verifiedboot.storage; |
| |
| import javacard.framework.CardRuntimeException; |
| import javacard.framework.JCSystem; |
| import javacard.framework.Util; |
| |
| import javacard.security.KeyBuilder; |
| import javacard.security.MessageDigest; |
| import javacard.security.RSAPublicKey; |
| import javacard.security.Signature; |
| |
| import com.android.verifiedboot.storage.LockInterface; |
| import com.android.verifiedboot.globalstate.owner.OwnerInterface; |
| |
| class CarrierLock implements LockInterface, BackupInterface { |
| private final static byte VERSION = (byte) 1; |
| private final static byte VERSION_SIZE = (byte) 8; |
| private final static byte NONCE_SIZE = (byte) 8; |
| private final static byte DEVICE_DATA_SIZE = (byte) (256 / 8); |
| |
| private final static byte[] PK_EXP = { (byte) 0x01, (byte) 0x00, (byte) 0x01 }; /* 65537 */ |
| // XXX Development key. Must be updated. |
| private final static byte[] PK_MOD = { |
| (byte) 0xAE, (byte) 0x14, (byte) 0xA4, (byte) 0x91, (byte) 0xA6, |
| (byte) 0xC8, (byte) 0x2E, (byte) 0x4D, (byte) 0x6B, (byte) 0xB3, |
| (byte) 0x4E, (byte) 0x23, (byte) 0x96, (byte) 0x57, (byte) 0x7C, |
| (byte) 0x2C, (byte) 0x7E, (byte) 0x69, (byte) 0xE6, (byte) 0xBF, |
| (byte) 0x5A, (byte) 0x9C, (byte) 0xD7, (byte) 0xA8, (byte) 0x38, |
| (byte) 0x0C, (byte) 0x9A, (byte) 0x54, (byte) 0x43, (byte) 0x4C, |
| (byte) 0x3C, (byte) 0xDA, (byte) 0xC5, (byte) 0xB1, (byte) 0x58, |
| (byte) 0x56, (byte) 0x9B, (byte) 0x5A, (byte) 0x05, (byte) 0xBA, |
| (byte) 0x2C, (byte) 0xAB, (byte) 0xC6, (byte) 0x50, (byte) 0x34, |
| (byte) 0x3C, (byte) 0x3B, (byte) 0x8E, (byte) 0xD8, (byte) 0x55, |
| (byte) 0xEB, (byte) 0xFA, (byte) 0x4F, (byte) 0x72, (byte) 0x81, |
| (byte) 0xA3, (byte) 0x8F, (byte) 0xDD, (byte) 0x8E, (byte) 0x0E, |
| (byte) 0xF2, (byte) 0xF6, (byte) 0xEF, (byte) 0x18, (byte) 0x95, |
| (byte) 0xCF, (byte) 0x71, (byte) 0x7D, (byte) 0x33, (byte) 0xA1, |
| (byte) 0xAE, (byte) 0xBE, (byte) 0x8C, (byte) 0xA5, (byte) 0x50, |
| (byte) 0x4C, (byte) 0xF2, (byte) 0xDC, (byte) 0x7B, (byte) 0x6C, |
| (byte) 0xAE, (byte) 0x14, (byte) 0x95, (byte) 0xB7, (byte) 0xE7, |
| (byte) 0xCA, (byte) 0xEB, (byte) 0xB0, (byte) 0x24, (byte) 0x5B, |
| (byte) 0xC9, (byte) 0x24, (byte) 0x2B, (byte) 0xC6, (byte) 0x96, |
| (byte) 0x99, (byte) 0xE9, (byte) 0x8B, (byte) 0x10, (byte) 0xCA, |
| (byte) 0x34, (byte) 0x2D, (byte) 0x84, (byte) 0x57, (byte) 0x09, |
| (byte) 0x4C, (byte) 0x32, (byte) 0x35, (byte) 0x68, (byte) 0x37, |
| (byte) 0x53, (byte) 0x0E, (byte) 0xF6, (byte) 0x93, (byte) 0x6C, |
| (byte) 0x86, (byte) 0x84, (byte) 0xC1, (byte) 0x44, (byte) 0x70, |
| (byte) 0x4A, (byte) 0x12, (byte) 0xAA, (byte) 0xC2, (byte) 0x9F, |
| (byte) 0x68, (byte) 0x5C, (byte) 0x42, (byte) 0xC8, (byte) 0xEB, |
| (byte) 0xD3, (byte) 0xAF, (byte) 0xD6, (byte) 0x34, (byte) 0x7F, |
| (byte) 0x9D, (byte) 0xC9, (byte) 0xE8, (byte) 0x81, (byte) 0x4A, |
| (byte) 0x5C, (byte) 0xDA, (byte) 0x36, (byte) 0x33, (byte) 0xFD, |
| (byte) 0x5C, (byte) 0x67, (byte) 0xBB, (byte) 0x91, (byte) 0x1C, |
| (byte) 0xF5, (byte) 0x21, (byte) 0xC0, (byte) 0x4E, (byte) 0x64, |
| (byte) 0x87, (byte) 0x89, (byte) 0xB6, (byte) 0x8B, (byte) 0xFD, |
| (byte) 0xDA, (byte) 0x30, (byte) 0x74, (byte) 0x1E, (byte) 0x00, |
| (byte) 0x57, (byte) 0xE1, (byte) 0x5C, (byte) 0xC4, (byte) 0xF2, |
| (byte) 0xEE, (byte) 0xF7, (byte) 0x05, (byte) 0x1C, (byte) 0xCE, |
| (byte) 0xF1, (byte) 0xCA, (byte) 0x88, (byte) 0xA0, (byte) 0x28, |
| (byte) 0x53, (byte) 0x2C, (byte) 0x84, (byte) 0xCD, (byte) 0xA3, |
| (byte) 0x6C, (byte) 0x1D, (byte) 0x15, (byte) 0x00, (byte) 0x5A, |
| (byte) 0x5D, (byte) 0x80, (byte) 0x40, (byte) 0x59, (byte) 0xE5, |
| (byte) 0xEA, (byte) 0xD1, (byte) 0x2A, (byte) 0xD6, (byte) 0x5A, |
| (byte) 0xE0, (byte) 0xE6, (byte) 0x9C, (byte) 0xEB, (byte) 0x23, |
| (byte) 0x4D, (byte) 0xD0, (byte) 0xB1, (byte) 0x27, (byte) 0xEE, |
| (byte) 0x41, (byte) 0x0D, (byte) 0xAA, (byte) 0x25, (byte) 0xBD, |
| (byte) 0xA0, (byte) 0xD0, (byte) 0x20, (byte) 0x00, (byte) 0x16, |
| (byte) 0x1F, (byte) 0x54, (byte) 0xC6, (byte) 0x4A, (byte) 0xDD, |
| (byte) 0x2A, (byte) 0x7E, (byte) 0x32, (byte) 0x43, (byte) 0x7F, |
| (byte) 0xD8, (byte) 0x74, (byte) 0x0F, (byte) 0x94, (byte) 0x88, |
| (byte) 0x3F, (byte) 0x26, (byte) 0x27, (byte) 0x54, (byte) 0x5D, |
| (byte) 0x01, (byte) 0x83, (byte) 0xAE, (byte) 0x47, (byte) 0x37, |
| (byte) 0x03, (byte) 0x6C, (byte) 0x80, (byte) 0xFD, (byte) 0x6E, |
| (byte) 0x08, (byte) 0xEB, (byte) 0xB4, (byte) 0x55, (byte) 0x81, |
| (byte) 0x13, |
| }; |
| |
| // Layout: |
| // LockValue (byte) || lastNonce (byte[8]) || deviceDataHash (byte[32]) |
| private byte[] storage; |
| private short storageOffset; |
| RSAPublicKey verifyingKey; |
| Signature verifier; |
| MessageDigest md_sha256; /* For creating the lock data hash from the input. */ |
| OwnerInterface globalState; |
| |
| /** |
| * Initializes the instance objects. |
| */ |
| public CarrierLock() { |
| try { |
| verifyingKey = (RSAPublicKey)KeyBuilder.buildKey( |
| KeyBuilder.TYPE_RSA_PUBLIC, KeyBuilder.LENGTH_RSA_2048, false); |
| verifyingKey.setExponent(PK_EXP, (short)0, (short)PK_EXP.length); |
| verifyingKey.setModulus(PK_MOD, (short)0, (short)PK_MOD.length); |
| } catch (CardRuntimeException e) { |
| verifyingKey = null; |
| } |
| |
| try { |
| verifier = Signature.getInstance(Signature.ALG_RSA_SHA_256_PKCS1, false); |
| verifier.init(verifyingKey, Signature.MODE_VERIFY); |
| } catch (CardRuntimeException e) { |
| verifier = null; |
| } |
| md_sha256 = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * Return the error states useful for diagnostics. |
| */ |
| @Override |
| public short initialized() { |
| if (storage == null) { |
| return 1; |
| } |
| if (verifyingKey == null) { |
| return 2; |
| } |
| if (verifier == null) { |
| return 3; |
| } |
| return 0; |
| } |
| /** |
| * {@inheritDoc} |
| * |
| */ |
| @Override |
| public short getStorageNeeded() { |
| return NONCE_SIZE + DEVICE_DATA_SIZE + 1; |
| } |
| |
| /** |
| * Sets the backing store to use for state. |
| * |
| * @param globalStateOwner interface for querying global state |
| * @param extStorage external array to use for storage |
| * @param extStorageOffset where to begin storing data |
| * |
| * This should be called before use. |
| */ |
| @Override |
| public void initialize(OwnerInterface globalStateOwner, byte[] extStorage, |
| short extStorageOffset) { |
| globalState = globalStateOwner; |
| // Zero it first (in case we are interrupted). |
| Util.arrayFillNonAtomic(extStorage, extStorageOffset, |
| getStorageNeeded(), (byte) 0x00); |
| storage = extStorage; |
| storageOffset = extStorageOffset; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public short get(byte[] lockOut, short outOffset) { |
| if (storage == null) { |
| return 0x0001; |
| } |
| try { |
| Util.arrayCopy(storage, lockOffset(), |
| lockOut, outOffset, (short) 1); |
| } catch (CardRuntimeException e) { |
| return 0x0002; |
| } |
| return 0; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * Returns 0xffff if {@link #initialize()} has not yet been called. |
| */ |
| @Override |
| public short lockOffset() { |
| if (storage == null) { |
| return (short) 0xffff; |
| } |
| return storageOffset; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| * |
| * Returns 0xffff if {@link #initialize()} has not yet been called. |
| */ |
| @Override |
| public short metadataOffset() { |
| if (storage == null) { |
| return (short) 0xffff; |
| } |
| return (short)(storageOffset + NONCE_SIZE + 1); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * Returns length of metadata. |
| * |
| * @return length of metadata. |
| */ |
| public short metadataLength() { |
| return (short) DEVICE_DATA_SIZE; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * Always returns false. Locking CarrierLock requires |
| * device data and unlocking (val=0x0) requires a signed |
| * assertion. |
| */ |
| @Override |
| public short set(byte val) { |
| return (short)0xffff; // Not implemented. |
| } |
| |
| /** |
| * Performs the verification of the incoming unlock token. |
| * |
| * This will check the version code, the nonce value, and the signature. |
| * |
| * |
| * @param deviceData |
| * @param deviceDataOffset |
| * @param lastNonce |
| * @param lastNonceOffset |
| * @param unlockToken |
| * @param unlockTokenOffset |
| * @param unlockTokenLength |
| * @return 0x0 on verified and an error code if not. |
| */ |
| private short verifyUnlock(byte[] deviceData, short deviceDataOffset, |
| byte[] lastNonce, short lastNonceOffset, |
| byte[] unlockToken, short unlockTokenOffset, |
| short unlockTokenLength) { |
| if (unlockTokenLength < (short)(VERSION_SIZE + NONCE_SIZE + PK_MOD.length)) { |
| return 0x0002; |
| } |
| // Only supported version is the uint64le_t 1 |
| if (unlockToken[unlockTokenOffset] != VERSION) { |
| return 0x0003; |
| } |
| |
| byte[] message = JCSystem.makeTransientByteArray( |
| (short)(NONCE_SIZE + DEVICE_DATA_SIZE), JCSystem.CLEAR_ON_DESELECT); |
| // Collect the incoming nonce. |
| Util.arrayCopy(unlockToken, (short)(unlockTokenOffset + VERSION_SIZE), |
| message, (short) 0x0, (short) NONCE_SIZE); |
| // Append the internallty stored device data. |
| Util.arrayCopy(deviceData, deviceDataOffset, |
| message, (short)NONCE_SIZE, (short) DEVICE_DATA_SIZE); |
| |
| // Verify it against the incoming signature. |
| if (verifier.verify( |
| message, (short) 0, (short) message.length, unlockToken, |
| (short)(unlockTokenOffset + VERSION_SIZE + NONCE_SIZE), |
| (short)(unlockTokenLength - (VERSION_SIZE + NONCE_SIZE))) == |
| false) { |
| return 0x0004; |
| } |
| if (littleEndianUnsignedGreaterThan(NONCE_SIZE, |
| unlockToken, (short)(unlockTokenOffset + VERSION_SIZE), |
| lastNonce, lastNonceOffset) == true) { |
| return 0; |
| } |
| return 0x0005; |
| } |
| |
| /** |
| * Compares two little endian byte streams and returns |
| * true if lhs is greater than rhs. |
| * |
| * @param len number of bytes to compare |
| * @param lhs left hand size buffer |
| * @param lhsBase starting offset |
| * @param rhs right hand size buffer |
| * @param rhsBase starting offset |
| */ |
| private boolean littleEndianUnsignedGreaterThan( |
| byte len, |
| byte[] lhs, short lhsBase, |
| byte[] rhs, short rhsBase) { |
| // Start with the most significant byte. |
| short i = len; |
| do { |
| i -= 1; |
| if (lhs[(short)(lhsBase +i)] > rhs[(short)(rhsBase + i)]) { |
| return true; |
| } |
| if (lhs[(short)(lhsBase +i)] < rhs[(short)(rhsBase + i)]) { |
| return false; |
| } |
| // Only proceed if the current bytes are equal. |
| } while (i > 0); |
| return false; |
| } |
| |
| |
| |
| /** |
| * {@inheritDoc} |
| * Returns true if the lock is changed with associated metadata. |
| * |
| * If |lockValue| is non-zero, then |lockMeta| should contain a series of |
| * ASCII (or hex) values separated by short lengths. These will be SHA256 |
| * hashed together to create a "device data hash". Its use is covered next. |
| * |
| * If |lockValue| is zero, then |lockMeta| should contain the following |
| * (all little endian): 8-bit version tag (0x01) || byte[8] "64-bit nonce" |
| * || byte[] Signature(nonce||deviceDataHash) The signature is using the |
| * embedded key {@link #pkModulus} and the format is ALG_RSA_SHA_256_PKCS1. |
| * If the signature verifies using the internally stored deviceDataHash and |
| * the provided nonce, then one last check is applied. If the nonce, |
| * treated as a little endian uint64_t, is greater than the stored nonce then |
| * it will be rejected. Note, once unlocked, the device data hash is deleted |
| * and the CarrierLock cannot be reapplied unless the device is taken out |
| * of production mode (bootloader RMA path). |
| * |
| * If {@link #globalState} indicates that the device is not yet in production |
| * mode, then the lock values can be toggled arbitrarily. |
| * The lock values may also be changed in the bootloader or in the HLOS as |
| * the transitions are either one-way (lock) or authenticated. It is required |
| * that the lock state is assigned prior to transitioning to production as that |
| * ensures that an unlocked device cannot be re-locked maliciously from the HLOS. |
| */ |
| @Override |
| public short setWithMetadata(byte lockValue, byte[] lockMeta, |
| short lockMetaOffset, short lockMetaLength) { |
| if (storage == null) { |
| // TODO: move to constants. |
| return 0x0001; |
| } |
| // Ensure we don't update the nonce if we didn't go through verify. |
| short resp = (short) 0xffff; |
| if (lockValue == LOCK_UNLOCKED) { // SHUT IT DOWN. |
| // If we're already unlocked, allow another call to make sure all the |
| // data is cleared. |
| if (storage[storageOffset] != LOCK_UNLOCKED && |
| globalState.production() == true) { |
| // RSA PKCS#1 signature should be the same length as the modulus but we'll allow it to |
| // be larger because ???. XXX TODO |
| resp = verifyUnlock(storage, (short)(storageOffset + 1 + NONCE_SIZE), |
| storage, (short)(storageOffset + 1), |
| lockMeta, lockMetaOffset, lockMetaLength); |
| if (resp != (short) 0) { |
| return resp; |
| } |
| } |
| JCSystem.beginTransaction(); |
| storage[storageOffset] = lockValue; |
| // Update the monotonically increasing "nonce" value. |
| // Note that the nonce is only ever updated if a signed value |
| // was seen or if we're not production() to assure it doesn't get |
| // rolled forward. |
| if (resp == 0) { |
| Util.arrayCopy(lockMeta, (short)1, |
| storage, (short)(1 + storageOffset), |
| (short)NONCE_SIZE); |
| } |
| // Delete the device-unique data. |
| Util.arrayFillNonAtomic(storage, (short)(NONCE_SIZE + 1 + storageOffset), |
| (short)DEVICE_DATA_SIZE, (byte)0x00); |
| JCSystem.commitTransaction(); |
| } else { // Locking. Expect a lockMeta of the device data. |
| if (globalState.production() == true) { |
| // Locking can only be done prior to production. |
| return 0x0006; |
| } |
| md_sha256.reset(); |
| JCSystem.beginTransaction(); |
| // Hash all the input data and store the result as the device data |
| // digest. |
| md_sha256.doFinal(lockMeta, lockMetaOffset, lockMetaLength, |
| storage, metadataOffset()); |
| // Note that we never clear or overwrite the nonce. |
| storage[storageOffset] = lockValue; |
| JCSystem.commitTransaction(); |
| } |
| return 0x0000; |
| } |
| |
| /** |
| * Given all the data, tests if the key actually works. |
| * |
| * buffer should contain: |
| * fakeLastNonce | fakeDeviceData | version (8) | testNonce | signature |
| * |
| * @param buffer Array with the test data. |
| * @param offset offset into the buffer. |
| * @param length total length from offset. |
| * @return 0x0 on verify and an error code otherwise. |
| */ |
| public short testVector(byte[] buffer, short offset, short length) { |
| return verifyUnlock(buffer, (short)(offset + NONCE_SIZE), // device data |
| buffer, offset, // fake last nonce. |
| // unlock data |
| buffer, (short)(offset + NONCE_SIZE + DEVICE_DATA_SIZE), |
| (short)(length - (NONCE_SIZE + DEVICE_DATA_SIZE))); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public short backupSize() { |
| return getStorageNeeded(); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public short backup(byte[] outBytes, short outBytesOffset) { |
| Util.arrayCopy(storage, storageOffset, |
| outBytes, outBytesOffset, |
| backupSize()); |
| return backupSize(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean restore(byte[] inBytes, short inBytesOffset, |
| short inBytesLength) { |
| if (inBytesLength > backupSize() || inBytesLength == (short)0) { |
| return false; |
| } |
| Util.arrayCopy(inBytes, inBytesOffset, |
| storage, storageOffset, |
| inBytesLength); |
| return true; |
| } |
| |
| |
| } |