/* * * * Copyright (C) 2014 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * / */ package org.anhonesteffort.flock.crypto; import android.content.Context; import android.util.Log; import org.anhonesteffort.flock.util.guava.Optional; import org.anhonesteffort.flock.util.Base64; import org.anhonesteffort.flock.util.Util; import java.io.IOException; import java.security.GeneralSecurityException; import java.util.Arrays; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; /** * Programmer: rhodey */ public class KeyHelper { private static final String TAG = "org.anhonesteffort.flock.crypto.KeyHelper"; public static void generateAndSaveSaltAndKeyMaterial(Context context) throws IOException, GeneralSecurityException { Log.d(TAG, "GENERATING SALT AND KEY MATERIAL!"); byte[] cipherKey = KeyUtil.generateCipherKey(); byte[] macKey = KeyUtil.generateMacKey(); byte[] salt = KeyUtil.generateSalt(); Log.d(TAG, "SAVING SALT AND KEY MATERIAL!"); KeyStore.saveCipherKey( context, cipherKey); KeyStore.saveMacKey( context, macKey); KeyStore.saveKeyMaterialSalt(context, salt); Optional<String> encryptedKeyMaterial = buildEncryptedKeyMaterial(context); if (encryptedKeyMaterial.isPresent()) KeyStore.saveEncryptedKeyMaterial(context, encryptedKeyMaterial.get()); } public static Optional<MasterCipher> getMasterCipher(Context context) throws IOException { Optional<byte[]> cipherKeyBytes = KeyStore.getCipherKey(context); Optional<byte[]> macKeyBytes = KeyStore.getMacKey(context); if (!cipherKeyBytes.isPresent() || !macKeyBytes.isPresent()) return Optional.absent(); SecretKey cipherKey = new SecretKeySpec(cipherKeyBytes.get(), "AES"); SecretKey macKey = new SecretKeySpec(macKeyBytes.get(), "SHA256"); return Optional.of(new MasterCipher(cipherKey, macKey)); } public static Optional<String> buildEncodedSalt(Context context) throws IOException { Optional<byte[]> salt = KeyStore.getKeyMaterialSalt(context); if (!salt.isPresent()) return Optional.absent(); return Optional.of(Base64.encodeBytes(salt.get())); } public static Optional<String> buildEncryptedKeyMaterial(Context context) throws IOException, GeneralSecurityException { Optional<byte[]> cipherKey = KeyStore.getCipherKey(context); Optional<byte[]> macKey = KeyStore.getMacKey(context); Optional<byte[]> salt = KeyStore.getKeyMaterialSalt(context); Optional<String> masterPassphrase = KeyStore.getMasterPassphrase(context); if (!masterPassphrase.isPresent() || !cipherKey.isPresent() || !macKey.isPresent() || !salt.isPresent()) return Optional.absent(); SecretKey[] masterKeys = KeyUtil.getCipherAndMacKeysForPassphrase(salt.get(), masterPassphrase.get()); SecretKey masterCipherKey = masterKeys[0]; SecretKey masterMacKey = masterKeys[1]; MasterCipher masterCipher = new MasterCipher(masterCipherKey, masterMacKey); byte[] keyMaterial = Util.combine(cipherKey.get(), macKey.get()); byte[] encryptedKeyMaterial = masterCipher.encryptAndEncode(keyMaterial); return Optional.of(new String(encryptedKeyMaterial)); } public static void importSaltAndEncryptedKeyMaterial(Context context, String[] saltAndEncryptedKeyMaterial) throws GeneralSecurityException, InvalidMacException, IOException { Log.d(TAG, "IMPORTING ENCRYPTED KEY MATERIAL!"); Optional<String> masterPassphrase = KeyStore.getMasterPassphrase(context); if (!masterPassphrase.isPresent()) throw new InvalidMacException("Passphrase unavailable."); byte[] salt = Base64.decode(saltAndEncryptedKeyMaterial[0]); SecretKey[] masterKeys = KeyUtil.getCipherAndMacKeysForPassphrase(salt, masterPassphrase.get()); SecretKey masterCipherKey = masterKeys[0]; SecretKey masterMacKey = masterKeys[1]; MasterCipher masterCipher = new MasterCipher(masterCipherKey, masterMacKey); byte[] plaintextKeyMaterial = masterCipher.decodeAndDecrypt(saltAndEncryptedKeyMaterial[1].getBytes()); boolean saltLengthValid = salt.length == KeyUtil.SALT_LENGTH_BYTES; boolean keyMaterialLengthValid = plaintextKeyMaterial.length == (KeyUtil.CIPHER_KEY_LENGTH_BYTES + KeyUtil.MAC_KEY_LENGTH_BYTES); if (!saltLengthValid || !keyMaterialLengthValid) throw new GeneralSecurityException("invalid length on salt or key material >> " + saltLengthValid + " " + keyMaterialLengthValid); byte[] plaintextCipherKey = Arrays.copyOfRange(plaintextKeyMaterial, 0, KeyUtil.CIPHER_KEY_LENGTH_BYTES); byte[] plaintextMacKey = Arrays.copyOfRange(plaintextKeyMaterial, KeyUtil.CIPHER_KEY_LENGTH_BYTES, KeyUtil.CIPHER_KEY_LENGTH_BYTES + KeyUtil.MAC_KEY_LENGTH_BYTES); KeyStore.saveEncryptedKeyMaterial(context, saltAndEncryptedKeyMaterial[1]); KeyStore.saveKeyMaterialSalt( context, salt); KeyStore.saveCipherKey( context, plaintextCipherKey); KeyStore.saveMacKey( context, plaintextMacKey); } public static boolean masterPassphraseIsValid(Context context) throws GeneralSecurityException, IOException { Optional<String> masterPassphrase = KeyStore.getMasterPassphrase(context); if (!masterPassphrase.isPresent()) return false; Optional<String> encryptedKeyMaterial = KeyStore.getEncryptedKeyMaterial(context); if (!encryptedKeyMaterial.isPresent()) throw new GeneralSecurityException("Where did my key material go! XXX!!!!"); Optional<byte[]> salt = KeyStore.getKeyMaterialSalt(context); if (!salt.isPresent()) throw new GeneralSecurityException("Where did my salt go! XXX!!!!"); SecretKey[] masterKeys = KeyUtil.getCipherAndMacKeysForPassphrase(salt.get(), masterPassphrase.get()); SecretKey masterCipherKey = masterKeys[0]; SecretKey masterMacKey = masterKeys[1]; MasterCipher masterCipher = new MasterCipher(masterCipherKey, masterMacKey); try { masterCipher.decodeAndDecrypt(encryptedKeyMaterial.get()); } catch (InvalidMacException e) { return false; } return true; } }