blob: 16d9710d9c4aaaf0bdd689c76c50af960ac1b916 [file] [log] [blame]
// Copyright 2017 Google Inc.
//
// 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.google.crypto.tink.integration.android;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import com.google.crypto.tink.Aead;
import com.google.crypto.tink.KmsClient;
import com.google.crypto.tink.subtle.Validators;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import javax.crypto.KeyGenerator;
/**
* An implementation of {@link KmsClient} for <a
* href="https://developer.android.com/training/articles/keystore.html">Android Keystore</a>.
*
* <p>This class requires Android M (API level 23) or newer.
*
* @since 1.0.0
*/
@TargetApi(VERSION_CODES.M)
public final class AndroidKeystoreKmsClient implements KmsClient {
/** The prefix of all keys stored in Android Keystore. */
public static final String PREFIX = "android-keystore://";
private String keyUri;
public AndroidKeystoreKmsClient() throws GeneralSecurityException {
if (!isAtLeastM()) {
throw new GeneralSecurityException(
"needs Android Keystore on Android M or newer");
}
}
/**
* Constructs an {@link AndroidKeystoreKmsClient} that is bound to a single key identified by
* {@code uri}.
*/
public AndroidKeystoreKmsClient(String uri) {
if (!uri.toLowerCase().startsWith(PREFIX)) {
throw new IllegalArgumentException("key URI must starts with " + PREFIX);
}
this.keyUri = uri;
}
/**
* @return true either if {@link AndroidKeystoreKmsClient#keyUri} is not null and equal to {@code
* uri}, or {@link AndroidKeystoreKmsClient#keyUri} is null and {@code uri} starts with {@link
* AndroidKeystoreKmsClient#PREFIX}.
*/
@Override
public boolean doesSupport(String uri) {
if (this.keyUri != null && this.keyUri.equals(uri)) {
return true;
}
return this.keyUri == null && uri.toLowerCase().startsWith(PREFIX);
}
/**
* Initializes a {@link KmsClient} for Android Keystore.
*
* <p>Note that Android Keystore doesn't need credentials, thus the credential path is unused.
*/
@Override
public KmsClient withCredentials(String unused) throws GeneralSecurityException {
return new AndroidKeystoreKmsClient();
}
/**
* Initializes a {@code KmsClient} for Android Keystore.
*
* <p>Note that Android Keystore does not use credentials.
*/
@Override
public KmsClient withDefaultCredentials() throws GeneralSecurityException {
return new AndroidKeystoreKmsClient();
}
private boolean isAtLeastM() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
}
@Override
public Aead getAead(String uri) throws GeneralSecurityException {
if (this.keyUri != null && !this.keyUri.equals(uri)) {
throw new GeneralSecurityException(
String.format("this client is bound to %s, cannot load keys bound to %s",
this.keyUri, uri));
}
try {
return new AndroidKeystoreAesGcm(
Validators.validateKmsKeyUriAndRemovePrefix(PREFIX, uri));
} catch (IOException e) {
throw new GeneralSecurityException(e);
}
}
/**
* Generates a new key in Android Keystore, if it doesn't exist.
*
* <p>At the moment it can generate only AES256-GCM keys.
*/
public static Aead getOrGenerateNewAeadKey(String keyUri)
throws GeneralSecurityException, IOException {
String keyId = Validators.validateKmsKeyUriAndRemovePrefix(PREFIX, keyUri);
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null /* param */);
if (!keyStore.containsAlias(keyId)) {
generateNewAeadKey(keyUri);
}
return new AndroidKeystoreKmsClient().getAead(keyUri);
}
/**
* Generates a new key in Android Keystore.
*
* <p>At the moment it can generate only AES256-GCM keys.
*/
public static void generateNewAeadKey(String keyUri)
throws GeneralSecurityException {
String keyId = Validators.validateKmsKeyUriAndRemovePrefix(PREFIX, keyUri);
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
KeyGenParameterSpec spec =
new KeyGenParameterSpec.Builder(keyId,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setKeySize(256)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build();
keyGenerator.init(spec);
keyGenerator.generateKey();
}
}