blob: b3a6aad821caedcfe21a31407bd83fc9fc8b4a48 [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 BasicLock implements LockInterface {
// Layout: LockValue (byte)
private byte[] storage;
private short storageOffset;
private OwnerInterface globalState;
private boolean onlyInBootloader;
private boolean onlyInHLOS;
private boolean needMetadata;
private short metadataSize;
private LockInterface[] requiredLocks;
/**
* Initializes the instance.
*
* @param maxMetadataSize length of the metadata
* @param requiredLocks specify the number of locks that unlocking
* will depend on.
*
*/
public BasicLock(short maxMetadataSize, short requiredLockNum) {
onlyInBootloader = false;
onlyInHLOS = false;
needMetadata = false;
metadataSize = maxMetadataSize;
requiredLocks = new LockInterface[requiredLockNum];
}
/**
* {@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;
}
/**
* Indicates that it is required that the {@link #globalState} is
* in the bootloader when the lock is changed.
*
* @param inBootloader true if changes can only happen in the bootloader.
*/
public void requireBootloader(boolean inBootloader) {
onlyInBootloader = inBootloader;
}
/**
* Indicates that it is required that the {@link #globalState} is
* NOT in the bootloader when the lock is changed.
*
* @param inHLOS true if changes can only happen in the bootloader.
*/
public void requireHLOS(boolean inHLOS) {
onlyInHLOS = inHLOS;
}
/**
* Indicates that metadata must be supplied when locking.
*
* @param atLock true if metadata must be supplied.
*/
public void requireMetadata(boolean atLock) {
needMetadata = atLock;
}
/**
* Adds a lock that must be unlocked to enable
* this lock to toggle.
*
* @param lock lock to depend on
* @return true on success or false on no space.
*/
public boolean addRequiredLock(LockInterface lock) {
for (short i = 0; i < (short) requiredLocks.length; ++i) {
if (requiredLocks[i] == null) {
requiredLocks[i] = lock;
return true;
}
}
return false;
}
/**
* {@inheritDoc}
*
* Return the error states useful for diagnostics.
*/
@Override
public short initialized() {
if (storage == null) {
return 1;
}
if (globalState == null) {
return 2;
}
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public short getStorageNeeded() {
return (short)(1 + metadataLength());
}
/**
* Sets the backing store to use for 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 lockOffset) {
if (storage == null) {
return 0x0001;
}
try {
Util.arrayCopy(storage, storageOffset,
lockOut, lockOffset, (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}
*
*/
@Override
public short metadataOffset() {
if (storage == null) {
return (short) 0xffff;
}
return (short)(lockOffset() + 1);
}
/**
* {@inheritDoc}
*
* @return length of metadata.
*/
public short metadataLength() {
return metadataSize;
}
/**
* Ensures any requiredLocks are unlocked.
* @return true if allowed or false if not.
*/
public boolean prerequisitesMet() {
if (requiredLocks.length != 0) {
byte[] temp = new byte[1];
short resp = 0;
for (short l = 0; l < requiredLocks.length; ++l) {
resp = requiredLocks[l].get(temp, (short) 0);
// On error or not cleared, fail.
if (resp != 0 || temp[0] != (byte) 0x0) {
return false;
}
}
}
return true;
}
/**
* {@inheritDoc}
*
* Returns 0x0 on success.
*/
@Override
public short set(byte val) {
if (storage == null) {
return 0x0001;
}
// Do not require meta on unlock.
if (val != 0) {
// While an invalid combo, we can just make the require flag
// pointless if metadataLength == 0.
if (needMetadata == true && metadataLength() > 0) {
return 0x0002;
}
}
// To relock, the lock must be unlocked, then relocked.
if (val != (byte)0 && storage[lockOffset()] != (byte)0) {
return 0x0005;
}
if (globalState.production() == true) {
// Enforce only when in production.
if (onlyInBootloader == true) {
// If onlyInBootloader is false, we allow toggling regardless.
if (globalState.inBootloader() == false) {
return 0x0003;
}
}
if (onlyInHLOS == true) {
// If onlyInHLOS is false, we allow toggling regardless.
if (globalState.inBootloader() == true) {
return 0x0003;
}
}
}
if (prerequisitesMet() == false) {
return 0x0a00;
}
try {
storage[storageOffset] = val;
} catch (CardRuntimeException e) {
return 0x0004;
}
return 0;
}
/**
* {@inheritDoc}
*
* If configured with {@link #requiredMetadata}, will populate the
* metadata. Otherwise, it will just call {@link #set}.
*
*/
@Override
public short setWithMetadata(byte lockValue, byte[] lockMeta,
short lockMetaOffset, short lockMetaLength) {
if (storage == null) {
return 0x0001;
}
// No overruns, please.
if (lockMetaLength > metadataLength()) {
return 0x0002;
}
// To relock, the lock must be unlocked, then relocked.
// This ensures that a lock like LOCK_OWNER cannot have its key value
// changed without first having the permission to unlock and lock again.
if (lockValue != (byte)0 && storage[lockOffset()] != (byte)0) {
return 0x0005;
}
if (metadataLength() == 0) {
return set(lockValue);
}
// Before copying, ensure changing the lock state is currently permitted.
if (prerequisitesMet() == false) {
return 0x0a00;
}
try {
// When unlocking, do so before clearing the metadata.
if (lockValue == (byte) 0) {
JCSystem.beginTransaction();
storage[lockOffset()] = lockValue;
JCSystem.commitTransaction();
}
if (lockMetaLength == 0) {
// An empty lockMeta will clear the value.
Util.arrayFillNonAtomic(storage, metadataOffset(),
metadataLength(), (byte) 0x00);
} else {
Util.arrayCopyNonAtomic(lockMeta, lockMetaOffset,
storage, metadataOffset(),
lockMetaLength);
}
// When locking, do so after the copy as interrupting it will
// not impact its use in a locked state.
if (lockValue != (byte) 0) {
JCSystem.beginTransaction();
storage[lockOffset()] = lockValue;
JCSystem.commitTransaction();
}
} catch (CardRuntimeException e) {
return 0x0004;
}
return 0;
}
}