1 /* 2 * Copyright (C) 2019 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.server.locksettings; 18 19 import com.android.internal.util.Preconditions; 20 21 import java.io.ByteArrayInputStream; 22 import java.io.ByteArrayOutputStream; 23 import java.io.DataInputStream; 24 import java.io.DataOutputStream; 25 import java.io.IOException; 26 import java.security.InvalidAlgorithmParameterException; 27 import java.security.InvalidKeyException; 28 import java.security.NoSuchAlgorithmException; 29 30 import javax.crypto.BadPaddingException; 31 import javax.crypto.Cipher; 32 import javax.crypto.IllegalBlockSizeException; 33 import javax.crypto.NoSuchPaddingException; 34 import javax.crypto.spec.IvParameterSpec; 35 36 /** 37 * Holds the data necessary to complete a reboot escrow of the Synthetic Password. 38 */ 39 class RebootEscrowData { 40 /** 41 * This is the current version of the escrow data format. This should be incremented if the 42 * format on disk is changed. 43 */ 44 private static final int CURRENT_VERSION = 1; 45 46 /** The algorithm used for the encryption of the key blob. */ 47 private static final String CIPHER_ALGO = "AES/GCM/NoPadding"; 48 RebootEscrowData(byte spVersion, byte[] iv, byte[] syntheticPassword, byte[] blob, RebootEscrowKey key)49 private RebootEscrowData(byte spVersion, byte[] iv, byte[] syntheticPassword, byte[] blob, 50 RebootEscrowKey key) { 51 mSpVersion = spVersion; 52 mIv = iv; 53 mSyntheticPassword = syntheticPassword; 54 mBlob = blob; 55 mKey = key; 56 } 57 58 private final byte mSpVersion; 59 private final byte[] mIv; 60 private final byte[] mSyntheticPassword; 61 private final byte[] mBlob; 62 private final RebootEscrowKey mKey; 63 getSpVersion()64 public byte getSpVersion() { 65 return mSpVersion; 66 } 67 getIv()68 public byte[] getIv() { 69 return mIv; 70 } 71 getSyntheticPassword()72 public byte[] getSyntheticPassword() { 73 return mSyntheticPassword; 74 } 75 getBlob()76 public byte[] getBlob() { 77 return mBlob; 78 } 79 getKey()80 public RebootEscrowKey getKey() { 81 return mKey; 82 } 83 fromEncryptedData(RebootEscrowKey key, byte[] blob)84 static RebootEscrowData fromEncryptedData(RebootEscrowKey key, byte[] blob) 85 throws IOException { 86 Preconditions.checkNotNull(key); 87 Preconditions.checkNotNull(blob); 88 89 DataInputStream dis = new DataInputStream(new ByteArrayInputStream(blob)); 90 int version = dis.readInt(); 91 if (version != CURRENT_VERSION) { 92 throw new IOException("Unsupported version " + version); 93 } 94 95 byte spVersion = dis.readByte(); 96 97 int ivSize = dis.readInt(); 98 if (ivSize < 0 || ivSize > 32) { 99 throw new IOException("IV out of range: " + ivSize); 100 } 101 byte[] iv = new byte[ivSize]; 102 dis.readFully(iv); 103 104 int cipherTextSize = dis.readInt(); 105 if (cipherTextSize < 0) { 106 throw new IOException("Invalid cipher text size: " + cipherTextSize); 107 } 108 109 byte[] cipherText = new byte[cipherTextSize]; 110 dis.readFully(cipherText); 111 112 final byte[] syntheticPassword; 113 try { 114 Cipher c = Cipher.getInstance(CIPHER_ALGO); 115 c.init(Cipher.DECRYPT_MODE, key.getKey(), new IvParameterSpec(iv)); 116 syntheticPassword = c.doFinal(cipherText); 117 } catch (NoSuchAlgorithmException | InvalidKeyException | BadPaddingException 118 | IllegalBlockSizeException | NoSuchPaddingException 119 | InvalidAlgorithmParameterException e) { 120 throw new IOException("Could not decrypt ciphertext", e); 121 } 122 123 return new RebootEscrowData(spVersion, iv, syntheticPassword, blob, key); 124 } 125 fromSyntheticPassword(RebootEscrowKey key, byte spVersion, byte[] syntheticPassword)126 static RebootEscrowData fromSyntheticPassword(RebootEscrowKey key, byte spVersion, 127 byte[] syntheticPassword) 128 throws IOException { 129 Preconditions.checkNotNull(syntheticPassword); 130 131 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 132 DataOutputStream dos = new DataOutputStream(bos); 133 134 final byte[] cipherText; 135 final byte[] iv; 136 try { 137 Cipher cipher = Cipher.getInstance(CIPHER_ALGO); 138 cipher.init(Cipher.ENCRYPT_MODE, key.getKey()); 139 cipherText = cipher.doFinal(syntheticPassword); 140 iv = cipher.getIV(); 141 } catch (NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException 142 | NoSuchPaddingException | InvalidKeyException e) { 143 throw new IOException("Could not encrypt reboot escrow data", e); 144 } 145 146 dos.writeInt(CURRENT_VERSION); 147 dos.writeByte(spVersion); 148 dos.writeInt(iv.length); 149 dos.write(iv); 150 dos.writeInt(cipherText.length); 151 dos.write(cipherText); 152 153 return new RebootEscrowData(spVersion, iv, syntheticPassword, bos.toByteArray(), 154 key); 155 } 156 } 157