| /* | |
| * Copyright (C) 2013 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.example.android.vault; | |
| import android.content.Context; | |
| import android.security.KeyPairGeneratorSpec; | |
| import java.io.IOException; | |
| import java.math.BigInteger; | |
| import java.security.GeneralSecurityException; | |
| import java.security.KeyPair; | |
| import java.security.KeyPairGenerator; | |
| import java.security.KeyStore; | |
| import java.util.Calendar; | |
| import java.util.GregorianCalendar; | |
| import javax.crypto.Cipher; | |
| import javax.crypto.SecretKey; | |
| import javax.security.auth.x500.X500Principal; | |
| /** | |
| * Wraps {@link SecretKey} instances using a public/private key pair stored in | |
| * the platform {@link KeyStore}. This allows us to protect symmetric keys with | |
| * hardware-backed crypto, if provided by the device. | |
| * <p> | |
| * See <a href="http://en.wikipedia.org/wiki/Key_Wrap">key wrapping</a> for more | |
| * details. | |
| * <p> | |
| * Not inherently thread safe. | |
| */ | |
| public class SecretKeyWrapper { | |
| private final Cipher mCipher; | |
| private final KeyPair mPair; | |
| /** | |
| * Create a wrapper using the public/private key pair with the given alias. | |
| * If no pair with that alias exists, it will be generated. | |
| */ | |
| public SecretKeyWrapper(Context context, String alias) | |
| throws GeneralSecurityException, IOException { | |
| mCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); | |
| final KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); | |
| keyStore.load(null); | |
| if (!keyStore.containsAlias(alias)) { | |
| generateKeyPair(context, alias); | |
| } | |
| // Even if we just generated the key, always read it back to ensure we | |
| // can read it successfully. | |
| final KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry) keyStore.getEntry( | |
| alias, null); | |
| mPair = new KeyPair(entry.getCertificate().getPublicKey(), entry.getPrivateKey()); | |
| } | |
| private static void generateKeyPair(Context context, String alias) | |
| throws GeneralSecurityException { | |
| final Calendar start = new GregorianCalendar(); | |
| final Calendar end = new GregorianCalendar(); | |
| end.add(Calendar.YEAR, 100); | |
| final KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context) | |
| .setAlias(alias) | |
| .setSubject(new X500Principal("CN=" + alias)) | |
| .setSerialNumber(BigInteger.ONE) | |
| .setStartDate(start.getTime()) | |
| .setEndDate(end.getTime()) | |
| .build(); | |
| final KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore"); | |
| gen.initialize(spec); | |
| gen.generateKeyPair(); | |
| } | |
| /** | |
| * Wrap a {@link SecretKey} using the public key assigned to this wrapper. | |
| * Use {@link #unwrap(byte[])} to later recover the original | |
| * {@link SecretKey}. | |
| * | |
| * @return a wrapped version of the given {@link SecretKey} that can be | |
| * safely stored on untrusted storage. | |
| */ | |
| public byte[] wrap(SecretKey key) throws GeneralSecurityException { | |
| mCipher.init(Cipher.WRAP_MODE, mPair.getPublic()); | |
| return mCipher.wrap(key); | |
| } | |
| /** | |
| * Unwrap a {@link SecretKey} using the private key assigned to this | |
| * wrapper. | |
| * | |
| * @param blob a wrapped {@link SecretKey} as previously returned by | |
| * {@link #wrap(SecretKey)}. | |
| */ | |
| public SecretKey unwrap(byte[] blob) throws GeneralSecurityException { | |
| mCipher.init(Cipher.UNWRAP_MODE, mPair.getPrivate()); | |
| return (SecretKey) mCipher.unwrap(blob, "AES", Cipher.SECRET_KEY); | |
| } | |
| } |