mod java;
use core::fmt;
use jni::JNIEnv;
use jni::objects::{JObject, JValue};
use crate::keystore::encoding::{sec1_from_coordinates, signature_from_der};
use crate::keystore::traits::{
Backend, DIGEST_LEN, DeviceKeyStore, DeviceSigner, DirectMasterKeyStore, MASTER_KEY_LEN,
MasterKey, WrappedMasterKey, WrappedMasterKeyStore, secrets_equal,
};
use crate::{DevicePublicKey, DeviceSignature, Error, Result};
const KEYSTORE: &str = "AndroidKeyStore";
const ALGORITHM: &str = "EC";
const CURVE: &str = "secp256r1";
const PURPOSE_SIGN: i32 = 4;
const PURPOSE_ENCRYPT_OR_DECRYPT: i32 = 1 | 2;
const DIGESTS: [&str; 2] = ["NONE", "SHA-256"];
const SIGNATURE: &str = "NONEwithECDSA";
const AES: &str = "AES";
const TRANSFORMATION: &str = "AES/GCM/NoPadding";
const AES_KEY_BITS: i32 = 256;
const GCM_TAG_BITS: i32 = 128;
const GCM_IV_BYTES: usize = 12;
const WRAPPED_MASTER_KEY_LEN: usize = GCM_IV_BYTES + MASTER_KEY_LEN + 16;
const MASTER_KEY_ALIAS_PREFIX: &str = "Qualia2FA-MasterKey-";
const MASTER_KEK_ALIAS_PREFIX: &str = "Qualia2FA-MasterKek-";
const MASTER_PROBE_ALIAS: &str = "Qualia2FA-MasterProbe";
const MAX_LABEL_LEN: usize = 128;
const BLOCK_MODE: &str = "GCM";
const PADDING: &str = "NoPadding";
const CIPHER_ENCRYPT_MODE: i32 = 1;
const CIPHER_DECRYPT_MODE: i32 = 2;
#[derive(Debug)]
pub struct AndroidKeyStore;
impl AndroidKeyStore {
pub fn open_best_available() -> Result<Self> {
java::with_env("KeyStore.getInstance", |env| open_keystore(env).map(|_| ()))?;
Ok(Self)
}
}
impl DeviceKeyStore for AndroidKeyStore {
fn backend(&self) -> Backend {
Backend::AndroidKeystore
}
fn open(&self, label: &str) -> Result<Box<dyn DeviceSigner>> {
check_label(label)?;
java::with_env("KeyStore.getCertificate", |env| {
let store = open_keystore(env)?;
let public_key = certificate_public_key(env, &store, label)?;
let hardware = signer_is_hardware_backed(env, &store, label)?;
Ok(signer(label, public_key, hardware))
})
}
fn create(&self, label: &str) -> Result<Box<dyn DeviceSigner>> {
check_label(label)?;
java::with_env("KeyPairGenerator.generateKeyPair", |env| {
let store = open_keystore(env)?;
if contains_alias(env, &store, label)? {
return Err(Error::KeyAlreadyExists {
label: label.to_owned(),
});
}
generate(env, label)?;
let public_key = certificate_public_key(env, &store, label)?;
let hardware = signer_is_hardware_backed(env, &store, label)?;
Ok(signer(label, public_key, hardware))
})
}
fn delete(&self, label: &str) -> Result<()> {
check_label(label)?;
java::with_env("KeyStore.deleteEntry", |env| {
let store = open_keystore(env)?;
if !contains_alias(env, &store, label)? {
return Err(Error::KeyNotFound {
label: label.to_owned(),
});
}
let alias = java::string(env, label, "KeyStore.deleteEntry")?;
java::call_void(
env,
&store,
"deleteEntry",
"(Ljava/lang/String;)V",
&[JValue::Object(&alias.into())],
"KeyStore.deleteEntry",
)
})
}
}
pub struct AndroidSigner {
label: String,
backend: Backend,
public_key: DevicePublicKey,
}
impl fmt::Debug for AndroidSigner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AndroidSigner")
.field("label", &self.label)
.field("backend", &self.backend)
.field("device_id", &self.public_key.device_id())
.finish_non_exhaustive()
}
}
impl DeviceSigner for AndroidSigner {
fn backend(&self) -> Backend {
self.backend
}
fn public_key(&self) -> &DevicePublicKey {
&self.public_key
}
fn sign_digest(&self, digest: &[u8]) -> Result<DeviceSignature> {
if digest.len() != DIGEST_LEN {
return Err(Error::InvalidDigestLength {
expected: DIGEST_LEN,
actual: digest.len(),
});
}
java::with_env("Signature.sign", |env| {
let store = open_keystore(env)?;
let key = private_key(env, &store, &self.label)?;
let der = sign_digest_with(env, &key, digest)?;
signature_from_der(&der)
})
}
}
fn signer(label: &str, public_key: DevicePublicKey, hardware: bool) -> Box<dyn DeviceSigner> {
let backend = if hardware {
Backend::AndroidKeystore
} else {
Backend::Software
};
Box::new(AndroidSigner {
label: label.to_owned(),
backend,
public_key,
})
}
fn check_label(label: &str) -> Result<()> {
if label.is_empty() {
return Err(Error::InvalidKeyLabel {
reason: "the label is empty",
});
}
Ok(())
}
fn open_keystore<'local>(env: &mut JNIEnv<'local>) -> Result<JObject<'local>> {
let operation = "KeyStore.getInstance";
let class = java::class(env, "java/security/KeyStore", operation)?;
let name = JObject::from(java::string(env, KEYSTORE, operation)?);
let store = java::static_object(
env,
&class,
"getInstance",
"(Ljava/lang/String;)Ljava/security/KeyStore;",
&[JValue::Object(&name)],
operation,
)?;
let parameter = JObject::null();
java::call_void(
env,
&store,
"load",
"(Ljava/security/KeyStore$LoadStoreParameter;)V",
&[JValue::Object(¶meter)],
operation,
)?;
Ok(store)
}
fn contains_alias<'local>(
env: &mut JNIEnv<'local>,
store: &JObject<'local>,
label: &str,
) -> Result<bool> {
contains_entry(env, store, label)
}
fn private_key<'local>(
env: &mut JNIEnv<'local>,
store: &JObject<'local>,
label: &str,
) -> Result<JObject<'local>> {
keystore_entry(env, store, label, label)
}
fn certificate_public_key<'local>(
env: &mut JNIEnv<'local>,
store: &JObject<'local>,
label: &str,
) -> Result<DevicePublicKey> {
let operation = "KeyStore.getCertificate";
let alias = JObject::from(java::string(env, label, operation)?);
let certificate = java::object(
env,
store,
"getCertificate",
"(Ljava/lang/String;)Ljava/security/cert/Certificate;",
&[JValue::Object(&alias)],
operation,
)?;
if certificate.is_null() {
return Err(Error::KeyNotFound {
label: label.to_owned(),
});
}
let key = java::object(
env,
&certificate,
"getPublicKey",
"()Ljava/security/PublicKey;",
&[],
operation,
)?;
coordinates(env, &key)
}
fn coordinates<'local>(env: &mut JNIEnv<'local>, key: &JObject<'local>) -> Result<DevicePublicKey> {
let operation = "ECPublicKey.getW";
let point = java::object(
env,
key,
"getW",
"()Ljava/security/spec/ECPoint;",
&[],
operation,
)?;
let x = integer_bytes(env, &point, "getAffineX", operation)?;
let y = integer_bytes(env, &point, "getAffineY", operation)?;
sec1_from_coordinates(&x, &y)
}
fn integer_bytes<'local>(
env: &mut JNIEnv<'local>,
point: &JObject<'local>,
name: &str,
operation: &'static str,
) -> Result<Vec<u8>> {
let value = java::object(env, point, name, "()Ljava/math/BigInteger;", &[], operation)?;
let bytes = java::object(env, &value, "toByteArray", "()[B", &[], operation)?;
java::object_bytes(env, bytes, operation)
}
fn signer_is_hardware_backed<'local>(
env: &mut JNIEnv<'local>,
store: &JObject<'local>,
label: &str,
) -> Result<bool> {
let key = private_key(env, store, label)?;
let operation = "KeyFactory.getKeySpec";
let class = java::class(env, "java/security/KeyFactory", operation)?;
let algorithm = java::object(
env,
&key,
"getAlgorithm",
"()Ljava/lang/String;",
&[],
operation,
)?;
let provider = JObject::from(java::string(env, KEYSTORE, operation)?);
let factory = java::static_object(
env,
&class,
"getInstance",
"(Ljava/lang/String;Ljava/lang/String;)Ljava/security/KeyFactory;",
&[JValue::Object(&algorithm), JValue::Object(&provider)],
operation,
)?;
let info = JObject::from(java::class(
env,
"android/security/keystore/KeyInfo",
operation,
)?);
let spec = java::object(
env,
&factory,
"getKeySpec",
"(Ljava/security/Key;Ljava/lang/Class;)Ljava/security/spec/KeySpec;",
&[JValue::Object(&key), JValue::Object(&info)],
operation,
)?;
java::boolean(env, &spec, "isInsideSecureHardware", "()Z", &[], operation)
}
fn generate(env: &mut JNIEnv<'_>, label: &str) -> Result<()> {
match generate_in(env, label, true) {
Ok(()) => Ok(()),
Err(_) => generate_in(env, label, false),
}
}
fn generate_in(env: &mut JNIEnv<'_>, label: &str, strong_box: bool) -> Result<()> {
let operation = "KeyPairGenerator.generateKeyPair";
let class = java::class(env, "java/security/KeyPairGenerator", operation)?;
let algorithm = JObject::from(java::string(env, ALGORITHM, operation)?);
let provider = JObject::from(java::string(env, KEYSTORE, operation)?);
let generator = java::static_object(
env,
&class,
"getInstance",
"(Ljava/lang/String;Ljava/lang/String;)Ljava/security/KeyPairGenerator;",
&[JValue::Object(&algorithm), JValue::Object(&provider)],
operation,
)?;
let spec = key_parameter_spec(env, label, strong_box)?;
java::call_void(
env,
&generator,
"initialize",
"(Ljava/security/spec/AlgorithmParameterSpec;)V",
&[JValue::Object(&spec)],
operation,
)?;
java::object(
env,
&generator,
"generateKeyPair",
"()Ljava/security/KeyPair;",
&[],
operation,
)?;
Ok(())
}
fn key_parameter_spec<'local>(
env: &mut JNIEnv<'local>,
label: &str,
strong_box: bool,
) -> Result<JObject<'local>> {
let operation = "KeyGenParameterSpec.Builder";
let class = java::class(
env,
"android/security/keystore/KeyGenParameterSpec$Builder",
operation,
)?;
let alias = JObject::from(java::string(env, label, operation)?);
let builder = java::construct(
env,
&class,
"(Ljava/lang/String;I)V",
&[JValue::Object(&alias), JValue::Int(PURPOSE_SIGN)],
operation,
)?;
let curve_class = java::class(env, "java/security/spec/ECGenParameterSpec", operation)?;
let curve_name = JObject::from(java::string(env, CURVE, operation)?);
let curve = java::construct(
env,
&curve_class,
"(Ljava/lang/String;)V",
&[JValue::Object(&curve_name)],
operation,
)?;
let builder = java::object(
env,
&builder,
"setAlgorithmParameterSpec",
"(Ljava/security/spec/AlgorithmParameterSpec;)Landroid/security/keystore/KeyGenParameterSpec$Builder;",
&[JValue::Object(&curve)],
operation,
)?;
let digests = JObject::from(java::string_array(env, &DIGESTS, operation)?);
let builder = java::object(
env,
&builder,
"setDigests",
"([Ljava/lang/String;)Landroid/security/keystore/KeyGenParameterSpec$Builder;",
&[JValue::Object(&digests)],
operation,
)?;
let builder = if strong_box {
java::object(
env,
&builder,
"setIsStrongBoxBacked",
"(Z)Landroid/security/keystore/KeyGenParameterSpec$Builder;",
&[JValue::Bool(1)],
operation,
)?
} else {
builder
};
java::object(
env,
&builder,
"build",
"()Landroid/security/keystore/KeyGenParameterSpec;",
&[],
operation,
)
}
fn sign_digest_with<'local>(
env: &mut JNIEnv<'local>,
key: &JObject<'local>,
digest: &[u8],
) -> Result<Vec<u8>> {
let operation = "Signature.sign";
let class = java::class(env, "java/security/Signature", operation)?;
let algorithm = JObject::from(java::string(env, SIGNATURE, operation)?);
let signature = java::static_object(
env,
&class,
"getInstance",
"(Ljava/lang/String;)Ljava/security/Signature;",
&[JValue::Object(&algorithm)],
operation,
)?;
java::call_void(
env,
&signature,
"initSign",
"(Ljava/security/PrivateKey;)V",
&[JValue::Object(key)],
operation,
)?;
let bytes = JObject::from(java::bytes(env, digest, operation)?);
java::call_void(
env,
&signature,
"update",
"([B)V",
&[JValue::Object(&bytes)],
operation,
)?;
java::byte_array(env, &signature, "sign", "()[B", &[], operation)
}
fn master_alias(prefix: &str, label: &str) -> Result<String> {
check_label(label)?;
if label.len() > MAX_LABEL_LEN {
return Err(Error::InvalidKeyLabel {
reason: "the label is too long (limit is 128 bytes)",
});
}
Ok(format!("{prefix}{label}"))
}
fn contains_entry<'local>(
env: &mut JNIEnv<'local>,
store: &JObject<'local>,
alias: &str,
) -> Result<bool> {
let operation = "KeyStore.containsAlias";
let alias = JObject::from(java::string(env, alias, operation)?);
java::boolean(
env,
store,
"containsAlias",
"(Ljava/lang/String;)Z",
&[JValue::Object(&alias)],
operation,
)
}
fn delete_entry<'local>(
env: &mut JNIEnv<'local>,
store: &JObject<'local>,
alias: &str,
label: &str,
) -> Result<()> {
if !contains_entry(env, store, alias)? {
return Err(Error::KeyNotFound {
label: label.to_owned(),
});
}
let operation = "KeyStore.deleteEntry";
let alias = JObject::from(java::string(env, alias, operation)?);
java::call_void(
env,
store,
"deleteEntry",
"(Ljava/lang/String;)V",
&[JValue::Object(&alias)],
operation,
)
}
fn keystore_entry<'local>(
env: &mut JNIEnv<'local>,
store: &JObject<'local>,
alias: &str,
label: &str,
) -> Result<JObject<'local>> {
let operation = "KeyStore.getKey";
let alias = JObject::from(java::string(env, alias, operation)?);
let password = JObject::null();
let key = java::object(
env,
store,
"getKey",
"(Ljava/lang/String;[C)Ljava/security/Key;",
&[JValue::Object(&alias), JValue::Object(&password)],
operation,
)?;
if key.is_null() {
return Err(Error::KeyNotFound {
label: label.to_owned(),
});
}
Ok(key)
}
fn entry_material<'local>(
env: &mut JNIEnv<'local>,
store: &JObject<'local>,
alias: &str,
label: &str,
) -> Result<MasterKey> {
let operation = "Key.getEncoded";
let key = keystore_entry(env, store, alias, label)?;
let encoded = java::object(env, &key, "getEncoded", "()[B", &[], operation)?;
if encoded.is_null() {
return Err(Error::KeyNotExportable {
reason: "the Android keystore holds the entry but returns null for the encoded key, so the material cannot be read back",
});
}
let bytes = java::object_bytes(env, encoded, operation)?;
if bytes.len() != MASTER_KEY_LEN {
return Err(Error::InvalidMasterKey {
reason: "the Android keystore returned a master key of the wrong length",
});
}
let mut material = [0_u8; MASTER_KEY_LEN];
material.copy_from_slice(&bytes);
Ok(MasterKey::from_bytes(material))
}
fn import_material<'local>(
env: &mut JNIEnv<'local>,
store: &JObject<'local>,
alias: &str,
material: &[u8],
) -> Result<()> {
let operation = "KeyStore.setEntry";
let algorithm = JObject::from(java::string(env, AES, operation)?);
let bytes = JObject::from(java::bytes(env, material, operation)?);
let spec_class = java::class(env, "javax/crypto/spec/SecretKeySpec", operation)?;
let spec = java::construct(
env,
&spec_class,
"([BLjava/lang/String;)V",
&[JValue::Object(&bytes), JValue::Object(&algorithm)],
operation,
)?;
let entry_class = java::class(env, "java/security/KeyStore$SecretKeyEntry", operation)?;
let entry = java::construct(
env,
&entry_class,
"(Ljavax/crypto/SecretKey;)V",
&[JValue::Object(&spec)],
operation,
)?;
let protection = key_protection(env)?;
let alias = JObject::from(java::string(env, alias, operation)?);
java::call_void(
env,
store,
"setEntry",
"(Ljava/lang/String;Ljava/security/KeyStore$Entry;Ljava/security/KeyStore$ProtectionParameter;)V",
&[
JValue::Object(&alias),
JValue::Object(&entry),
JValue::Object(&protection),
],
operation,
)
}
fn key_protection<'local>(env: &mut JNIEnv<'local>) -> Result<JObject<'local>> {
let operation = "KeyProtection.Builder";
let class = java::class(
env,
"android/security/keystore/KeyProtection$Builder",
operation,
)?;
let builder = java::construct(
env,
&class,
"(I)V",
&[JValue::Int(PURPOSE_ENCRYPT_OR_DECRYPT)],
operation,
)?;
let modes = JObject::from(java::string_array(env, &[BLOCK_MODE], operation)?);
let builder = java::object(
env,
&builder,
"setBlockModes",
"([Ljava/lang/String;)Landroid/security/keystore/KeyProtection$Builder;",
&[JValue::Object(&modes)],
operation,
)?;
let paddings = JObject::from(java::string_array(env, &[PADDING], operation)?);
let builder = java::object(
env,
&builder,
"setEncryptionPaddings",
"([Ljava/lang/String;)Landroid/security/keystore/KeyProtection$Builder;",
&[JValue::Object(&paddings)],
operation,
)?;
java::object(
env,
&builder,
"build",
"()Landroid/security/keystore/KeyProtection;",
&[],
operation,
)
}
fn generate_wrapping_key<'local>(env: &mut JNIEnv<'local>, alias: &str) -> Result<()> {
match generate_wrapping_key_in(env, alias, true) {
Ok(()) => Ok(()),
Err(_) => generate_wrapping_key_in(env, alias, false),
}
}
fn generate_wrapping_key_in<'local>(
env: &mut JNIEnv<'local>,
alias: &str,
strong_box: bool,
) -> Result<()> {
let operation = "KeyGenerator.generateKey";
let class = java::class(env, "javax/crypto/KeyGenerator", operation)?;
let algorithm = JObject::from(java::string(env, AES, operation)?);
let provider = JObject::from(java::string(env, KEYSTORE, operation)?);
let generator = java::static_object(
env,
&class,
"getInstance",
"(Ljava/lang/String;Ljava/lang/String;)Ljavax/crypto/KeyGenerator;",
&[JValue::Object(&algorithm), JValue::Object(&provider)],
operation,
)?;
let spec = wrapping_key_spec(env, alias, strong_box)?;
java::call_void(
env,
&generator,
"init",
"(Ljava/security/spec/AlgorithmParameterSpec;)V",
&[JValue::Object(&spec)],
operation,
)?;
java::object(
env,
&generator,
"generateKey",
"()Ljavax/crypto/SecretKey;",
&[],
operation,
)?;
Ok(())
}
fn wrapping_key_spec<'local>(
env: &mut JNIEnv<'local>,
alias: &str,
strong_box: bool,
) -> Result<JObject<'local>> {
let operation = "KeyGenParameterSpec.Builder";
let class = java::class(
env,
"android/security/keystore/KeyGenParameterSpec$Builder",
operation,
)?;
let alias = JObject::from(java::string(env, alias, operation)?);
let builder = java::construct(
env,
&class,
"(Ljava/lang/String;I)V",
&[
JValue::Object(&alias),
JValue::Int(PURPOSE_ENCRYPT_OR_DECRYPT),
],
operation,
)?;
let modes = JObject::from(java::string_array(env, &[BLOCK_MODE], operation)?);
let builder = java::object(
env,
&builder,
"setBlockModes",
"([Ljava/lang/String;)Landroid/security/keystore/KeyGenParameterSpec$Builder;",
&[JValue::Object(&modes)],
operation,
)?;
let paddings = JObject::from(java::string_array(env, &[PADDING], operation)?);
let builder = java::object(
env,
&builder,
"setEncryptionPaddings",
"([Ljava/lang/String;)Landroid/security/keystore/KeyGenParameterSpec$Builder;",
&[JValue::Object(&paddings)],
operation,
)?;
let builder = java::object(
env,
&builder,
"setKeySize",
"(I)Landroid/security/keystore/KeyGenParameterSpec$Builder;",
&[JValue::Int(AES_KEY_BITS)],
operation,
)?;
let builder = java::object(
env,
&builder,
"setRandomizedEncryptionRequired",
"(Z)Landroid/security/keystore/KeyGenParameterSpec$Builder;",
&[JValue::Bool(1)],
operation,
)?;
let builder = if strong_box {
java::object(
env,
&builder,
"setIsStrongBoxBacked",
"(Z)Landroid/security/keystore/KeyGenParameterSpec$Builder;",
&[JValue::Bool(1)],
operation,
)?
} else {
builder
};
java::object(
env,
&builder,
"build",
"()Landroid/security/keystore/KeyGenParameterSpec;",
&[],
operation,
)
}
fn wrap_with_kek<'local>(
env: &mut JNIEnv<'local>,
kek: &JObject<'local>,
material: &[u8; MASTER_KEY_LEN],
) -> Result<WrappedMasterKey> {
let operation = "Cipher.doFinal";
let cipher = cipher(env)?;
java::call_void(
env,
&cipher,
"init",
"(ILjava/security/Key;)V",
&[JValue::Int(CIPHER_ENCRYPT_MODE), JValue::Object(kek)],
operation,
)?;
let iv = java::object(env, &cipher, "getIV", "()[B", &[], operation)?;
if iv.is_null() {
return Err(Error::InvalidWrappedKey {
reason: "the platform produced no initialization vector for the wrapping key",
code: 0,
});
}
let iv = java::object_bytes(env, iv, operation)?;
if iv.len() != GCM_IV_BYTES {
return Err(Error::InvalidWrappedKey {
reason: "the platform produced an initialization vector of an unexpected length",
code: 0,
});
}
let bytes = JObject::from(java::bytes(env, material, operation)?);
let sealed = java::byte_array(
env,
&cipher,
"doFinal",
"([B)[B",
&[JValue::Object(&bytes)],
operation,
)?;
if sealed.len() != MASTER_KEY_LEN + 16 {
return Err(Error::InvalidWrappedKey {
reason: "the platform sealed the master key into an unexpected number of bytes",
code: 0,
});
}
let mut wrapped = Vec::with_capacity(WRAPPED_MASTER_KEY_LEN);
wrapped.extend_from_slice(&iv);
wrapped.extend_from_slice(&sealed);
Ok(WrappedMasterKey::from_bytes(wrapped))
}
fn unwrap_with_kek<'local>(
env: &mut JNIEnv<'local>,
kek: &JObject<'local>,
wrapped: &[u8],
) -> Result<MasterKey> {
let (iv, sealed) = wrapped.split_at(GCM_IV_BYTES);
let operation = "Cipher.doFinal";
let cipher = cipher(env)?;
let spec_class = java::class(env, "javax/crypto/spec/GCMParameterSpec", operation)?;
let iv = JObject::from(java::bytes(env, iv, operation)?);
let spec = java::construct(
env,
&spec_class,
"(I[B)V",
&[JValue::Int(GCM_TAG_BITS), JValue::Object(&iv)],
operation,
)?;
java::call_void(
env,
&cipher,
"init",
"(ILjava/security/Key;Ljava/security/spec/AlgorithmParameterSpec;)V",
&[
JValue::Int(CIPHER_DECRYPT_MODE),
JValue::Object(kek),
JValue::Object(&spec),
],
operation,
)?;
let sealed = JObject::from(java::bytes(env, sealed, operation)?);
let plain = java::byte_array_authenticated(
env,
&cipher,
"doFinal",
"([B)[B",
&[JValue::Object(&sealed)],
operation,
)?;
if plain.len() != MASTER_KEY_LEN {
return Err(Error::InvalidWrappedKey {
reason: "the wrapped bytes do not decrypt to a 32-byte master key",
code: 0,
});
}
let mut material = [0_u8; MASTER_KEY_LEN];
material.copy_from_slice(&plain);
Ok(MasterKey::from_bytes(material))
}
fn cipher<'local>(env: &mut JNIEnv<'local>) -> Result<JObject<'local>> {
let operation = "Cipher.getInstance";
let class = java::class(env, "javax/crypto/Cipher", operation)?;
let transformation = JObject::from(java::string(env, TRANSFORMATION, operation)?);
java::static_object(
env,
&class,
"getInstance",
"(Ljava/lang/String;)Ljavax/crypto/Cipher;",
&[JValue::Object(&transformation)],
operation,
)
}
fn secret_key_is_hardware_backed<'local>(
env: &mut JNIEnv<'local>,
key: &JObject<'local>,
) -> Result<bool> {
let operation = "SecretKeyFactory.getKeySpec";
let class = java::class(env, "javax/crypto/SecretKeyFactory", operation)?;
let algorithm = java::object(
env,
key,
"getAlgorithm",
"()Ljava/lang/String;",
&[],
operation,
)?;
let provider = JObject::from(java::string(env, KEYSTORE, operation)?);
let factory = java::static_object(
env,
&class,
"getInstance",
"(Ljava/lang/String;Ljava/lang/String;)Ljavax/crypto/SecretKeyFactory;",
&[JValue::Object(&algorithm), JValue::Object(&provider)],
operation,
)?;
let info = JObject::from(java::class(
env,
"android/security/keystore/KeyInfo",
operation,
)?);
let spec = java::object(
env,
&factory,
"getKeySpec",
"(Ljavax/crypto/SecretKey;Ljava/lang/Class;)Ljava/security/spec/KeySpec;",
&[JValue::Object(key), JValue::Object(&info)],
operation,
)?;
java::boolean(env, &spec, "isInsideSecureHardware", "()Z", &[], operation)
}
#[derive(Debug)]
pub struct AndroidDirectMasterKeyStore {
backend: Backend,
}
impl AndroidDirectMasterKeyStore {
pub fn open_best_available() -> Result<Self> {
java::with_env("KeyStore.getInstance", |env| open_keystore(env).map(|_| ()))?;
Ok(Self {
backend: Backend::Software,
})
}
}
impl DirectMasterKeyStore for AndroidDirectMasterKeyStore {
fn backend(&self) -> Backend {
self.backend
}
fn store(&self, label: &str, key: &MasterKey) -> Result<()> {
let alias = master_alias(MASTER_KEY_ALIAS_PREFIX, label)?;
java::with_env("KeyStore.setEntry", |env| {
let store = open_keystore(env)?;
if contains_entry(env, &store, &alias)? {
delete_entry(env, &store, &alias, label)?;
}
import_material(env, &store, &alias, key.as_bytes())?;
let read_back = entry_material(env, &store, &alias, label)?;
if !secrets_equal(read_back.as_bytes(), key.as_bytes()) {
let _ = delete_entry(env, &store, &alias, label);
return Err(Error::InvalidMasterKey {
reason: "the Android keystore returned material that differs from what was imported",
});
}
Ok(())
})
}
fn load(&self, label: &str) -> Result<MasterKey> {
let alias = master_alias(MASTER_KEY_ALIAS_PREFIX, label)?;
java::with_env("KeyStore.getKey", |env| {
let store = open_keystore(env)?;
entry_material(env, &store, &alias, label)
})
}
fn delete(&self, label: &str) -> Result<()> {
let alias = master_alias(MASTER_KEY_ALIAS_PREFIX, label)?;
java::with_env("KeyStore.deleteEntry", |env| {
let store = open_keystore(env)?;
delete_entry(env, &store, &alias, label)
})
}
}
#[derive(Debug)]
pub struct AndroidWrappedMasterKeyStore {
backend: Backend,
}
impl AndroidWrappedMasterKeyStore {
pub fn open_best_available() -> Result<Self> {
let backend = java::with_env("KeyGenerator.generateKey", |env| {
let store = open_keystore(env)?;
let key = match keystore_entry(env, &store, MASTER_PROBE_ALIAS, MASTER_PROBE_ALIAS) {
Ok(key) => key,
Err(Error::KeyNotFound { .. }) => {
generate_wrapping_key(env, MASTER_PROBE_ALIAS)?;
keystore_entry(env, &store, MASTER_PROBE_ALIAS, MASTER_PROBE_ALIAS)?
}
Err(other) => return Err(other),
};
let hardware = secret_key_is_hardware_backed(env, &key)?;
Ok(if hardware {
Backend::AndroidKeystore
} else {
Backend::Software
})
})?;
Ok(Self { backend })
}
}
impl WrappedMasterKeyStore for AndroidWrappedMasterKeyStore {
fn backend(&self) -> Backend {
self.backend
}
fn store(&self, label: &str, key: &MasterKey) -> Result<WrappedMasterKey> {
let alias = master_alias(MASTER_KEK_ALIAS_PREFIX, label)?;
java::with_env("Cipher.doFinal", |env| {
let store = open_keystore(env)?;
if !contains_entry(env, &store, &alias)? {
generate_wrapping_key(env, &alias)?;
}
let kek = keystore_entry(env, &store, &alias, label)?;
wrap_with_kek(env, &kek, key.as_bytes())
})
}
fn load(&self, label: &str, wrapped: &WrappedMasterKey) -> Result<MasterKey> {
let alias = master_alias(MASTER_KEK_ALIAS_PREFIX, label)?;
if wrapped.as_bytes().len() != WRAPPED_MASTER_KEY_LEN {
return Err(Error::InvalidWrappedKey {
reason: "the wrapped master key is not the 60 bytes an AES-GCM key-encryption key produces",
code: 0,
});
}
java::with_env("Cipher.doFinal", |env| {
let store = open_keystore(env)?;
let kek = keystore_entry(env, &store, &alias, label)?;
unwrap_with_kek(env, &kek, wrapped.as_bytes())
})
}
fn delete(&self, label: &str) -> Result<()> {
let alias = master_alias(MASTER_KEK_ALIAS_PREFIX, label)?;
java::with_env("KeyStore.deleteEntry", |env| {
let store = open_keystore(env)?;
delete_entry(env, &store, &alias, label)
})
}
}
const _: () = assert!(PURPOSE_SIGN == 4, "KeyProperties.PURPOSE_SIGN is 4");