// // 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; } }