blob: 13e81873d2949aa26c0d48e4b2f0d8a2fe1d05a9 [file] [log] [blame]
//
// 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;
}
}