283 lines
10 KiB
Rust
283 lines
10 KiB
Rust
//! Android OS calls; all envelopes, policy and lifecycle remain in storage.
|
|
use crate::{MobileSecretBackend, MobileSecretError, MobileSecretLocator, MobileSecretProtection};
|
|
use ironstorage::{
|
|
repository::SecretBytes,
|
|
secret_store::{
|
|
SecretLocator, SecretProtection, SecretStoreBackend, SecretStoreError, android as os,
|
|
},
|
|
};
|
|
use std::{fmt, sync::Arc};
|
|
|
|
#[derive(Clone, Copy, uniffi::Record)]
|
|
pub struct MobileAndroidDeviceState {
|
|
pub api: u32,
|
|
pub locked: bool,
|
|
pub secure: bool,
|
|
pub strong_biometric: bool,
|
|
pub strongbox: bool,
|
|
}
|
|
#[derive(Clone, Copy, uniffi::Enum)]
|
|
pub enum MobileAndroidAuthorization {
|
|
None,
|
|
StrongBiometric,
|
|
UserPresence,
|
|
}
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileAndroidKeySpecification {
|
|
pub alias: String,
|
|
pub authorization: MobileAndroidAuthorization,
|
|
pub unlocked_device: bool,
|
|
pub invalidate_on_enrollment: bool,
|
|
pub strongbox: bool,
|
|
}
|
|
#[derive(Clone, uniffi::Record)]
|
|
pub struct MobileAndroidCiphertext {
|
|
pub nonce: Vec<u8>,
|
|
pub bytes: Vec<u8>,
|
|
}
|
|
#[derive(Clone, Copy, Debug, uniffi::Error)]
|
|
pub enum MobileAndroidKeystoreError {
|
|
MissingKey,
|
|
Invalidated,
|
|
Lockout,
|
|
Locked,
|
|
Denied,
|
|
Cancelled,
|
|
Corrupted,
|
|
UnsupportedProtection,
|
|
StrongboxUnavailable,
|
|
AlreadyExists,
|
|
Unavailable,
|
|
}
|
|
impl fmt::Display for MobileAndroidKeystoreError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.write_str("Android Keystore operation failed")
|
|
}
|
|
}
|
|
impl std::error::Error for MobileAndroidKeystoreError {}
|
|
#[uniffi::export(with_foreign)]
|
|
pub trait MobileAndroidKeystore: Send + Sync {
|
|
fn device_state(&self) -> Result<MobileAndroidDeviceState, MobileAndroidKeystoreError>;
|
|
fn create_key(
|
|
&self,
|
|
specification: MobileAndroidKeySpecification,
|
|
) -> Result<(), MobileAndroidKeystoreError>;
|
|
fn encrypt(
|
|
&self,
|
|
alias: String,
|
|
authorization: MobileAndroidAuthorization,
|
|
aad: Vec<u8>,
|
|
value: Vec<u8>,
|
|
) -> Result<MobileAndroidCiphertext, MobileAndroidKeystoreError>;
|
|
fn decrypt(
|
|
&self,
|
|
alias: String,
|
|
authorization: MobileAndroidAuthorization,
|
|
aad: Vec<u8>,
|
|
value: MobileAndroidCiphertext,
|
|
) -> Result<Vec<u8>, MobileAndroidKeystoreError>;
|
|
fn delete_key(&self, alias: String) -> Result<(), MobileAndroidKeystoreError>;
|
|
fn aliases(&self) -> Result<Vec<String>, MobileAndroidKeystoreError>;
|
|
fn cancel_pending(&self);
|
|
}
|
|
pub(crate) struct KeystoreAdapter(pub Arc<dyn MobileAndroidKeystore>);
|
|
impl From<os::AndroidAuthorization> for MobileAndroidAuthorization {
|
|
fn from(value: os::AndroidAuthorization) -> Self {
|
|
match value {
|
|
os::AndroidAuthorization::None => Self::None,
|
|
os::AndroidAuthorization::StrongBiometric => Self::StrongBiometric,
|
|
os::AndroidAuthorization::UserPresence => Self::UserPresence,
|
|
}
|
|
}
|
|
}
|
|
impl From<MobileAndroidKeystoreError> for os::AndroidKeystoreError {
|
|
fn from(value: MobileAndroidKeystoreError) -> Self {
|
|
match value {
|
|
MobileAndroidKeystoreError::MissingKey => Self::MissingKey,
|
|
MobileAndroidKeystoreError::Invalidated => Self::Invalidated,
|
|
MobileAndroidKeystoreError::Lockout => Self::Lockout,
|
|
MobileAndroidKeystoreError::Locked => Self::Locked,
|
|
MobileAndroidKeystoreError::Denied => Self::Denied,
|
|
MobileAndroidKeystoreError::Cancelled => Self::Cancelled,
|
|
MobileAndroidKeystoreError::Corrupted => Self::Corrupted,
|
|
MobileAndroidKeystoreError::UnsupportedProtection => Self::UnsupportedProtection,
|
|
MobileAndroidKeystoreError::StrongboxUnavailable => Self::StrongboxUnavailable,
|
|
MobileAndroidKeystoreError::AlreadyExists => Self::AlreadyExists,
|
|
MobileAndroidKeystoreError::Unavailable => Self::Unavailable,
|
|
}
|
|
}
|
|
}
|
|
impl os::AndroidKeystore for KeystoreAdapter {
|
|
fn device_state(&self) -> Result<os::AndroidDeviceState, os::AndroidKeystoreError> {
|
|
self.0
|
|
.device_state()
|
|
.map(|s| os::AndroidDeviceState {
|
|
api: s.api,
|
|
locked: s.locked,
|
|
secure: s.secure,
|
|
strong_biometric: s.strong_biometric,
|
|
strongbox: s.strongbox,
|
|
})
|
|
.map_err(Into::into)
|
|
}
|
|
fn create_key(&self, s: &os::AndroidKeySpecification) -> Result<(), os::AndroidKeystoreError> {
|
|
self.0
|
|
.create_key(MobileAndroidKeySpecification {
|
|
alias: s.alias.clone(),
|
|
authorization: s.authorization.into(),
|
|
unlocked_device: s.unlocked_device,
|
|
invalidate_on_enrollment: s.invalidate_on_enrollment,
|
|
strongbox: s.strongbox,
|
|
})
|
|
.map_err(Into::into)
|
|
}
|
|
fn encrypt(
|
|
&self,
|
|
alias: &str,
|
|
authorization: os::AndroidAuthorization,
|
|
aad: &[u8],
|
|
value: &SecretBytes,
|
|
) -> Result<os::AndroidCiphertext, os::AndroidKeystoreError> {
|
|
self.0
|
|
.encrypt(
|
|
alias.into(),
|
|
authorization.into(),
|
|
aad.to_vec(),
|
|
value.expose().to_vec(),
|
|
)
|
|
.map(|v| os::AndroidCiphertext {
|
|
nonce: v.nonce,
|
|
bytes: v.bytes,
|
|
})
|
|
.map_err(Into::into)
|
|
}
|
|
fn decrypt(
|
|
&self,
|
|
alias: &str,
|
|
authorization: os::AndroidAuthorization,
|
|
aad: &[u8],
|
|
value: &os::AndroidCiphertext,
|
|
) -> Result<SecretBytes, os::AndroidKeystoreError> {
|
|
self.0
|
|
.decrypt(
|
|
alias.into(),
|
|
authorization.into(),
|
|
aad.to_vec(),
|
|
MobileAndroidCiphertext {
|
|
nonce: value.nonce.clone(),
|
|
bytes: value.bytes.clone(),
|
|
},
|
|
)
|
|
.map(SecretBytes::new)
|
|
.map_err(Into::into)
|
|
}
|
|
fn delete_key(&self, alias: &str) -> Result<(), os::AndroidKeystoreError> {
|
|
self.0.delete_key(alias.into()).map_err(Into::into)
|
|
}
|
|
fn aliases(&self) -> Result<Vec<String>, os::AndroidKeystoreError> {
|
|
self.0.aliases().map_err(Into::into)
|
|
}
|
|
fn cancel_pending(&self) {
|
|
self.0.cancel_pending();
|
|
}
|
|
}
|
|
pub(crate) struct RustSecretBackend(pub Arc<os::AndroidSecretBackend>);
|
|
impl TryFrom<MobileSecretLocator> for SecretLocator {
|
|
type Error = MobileSecretError;
|
|
fn try_from(value: MobileSecretLocator) -> Result<Self, Self::Error> {
|
|
Ok(match value {
|
|
MobileSecretLocator::ApplicationBinding => Self::ApplicationBinding,
|
|
MobileSecretLocator::OpenPgpPassphrase { fingerprint } => {
|
|
Self::OpenPgpPassphrase { fingerprint }
|
|
}
|
|
MobileSecretLocator::HttpsGitCredential {
|
|
server_id,
|
|
application_id,
|
|
} => Self::HttpsGitCredential {
|
|
server_id,
|
|
application_id,
|
|
},
|
|
MobileSecretLocator::SshKeyPassphrase { fingerprint } => Self::SshKeyPassphrase {
|
|
fingerprint: ironstorage::config::SshFingerprint::parse(&fingerprint)
|
|
.map_err(|_| MobileSecretError::Corrupted)?,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
impl From<MobileSecretProtection> for SecretProtection {
|
|
fn from(value: MobileSecretProtection) -> Self {
|
|
match value {
|
|
MobileSecretProtection::DeviceUnlocked => Self::DeviceUnlocked,
|
|
MobileSecretProtection::RequireUserPresence => Self::RequireUserPresence,
|
|
MobileSecretProtection::BiometryCurrentSet => Self::BiometryCurrentSet,
|
|
}
|
|
}
|
|
}
|
|
impl From<SecretStoreError> for MobileSecretError {
|
|
fn from(value: SecretStoreError) -> Self {
|
|
match value {
|
|
SecretStoreError::AlreadyExists => Self::AlreadyExists,
|
|
SecretStoreError::Missing => Self::Missing,
|
|
SecretStoreError::Locked => Self::Locked,
|
|
SecretStoreError::Denied => Self::Denied,
|
|
SecretStoreError::Cancelled => Self::Cancelled,
|
|
SecretStoreError::Corrupted => Self::Corrupted,
|
|
SecretStoreError::UnsupportedProtection => Self::UnsupportedProtection,
|
|
SecretStoreError::Unavailable => Self::Unavailable,
|
|
SecretStoreError::DeviceKeyMissing => Self::DeviceKeyMissing,
|
|
SecretStoreError::Invalidated => Self::Invalidated,
|
|
SecretStoreError::Lockout => Self::Lockout,
|
|
_ => Self::Corrupted,
|
|
}
|
|
}
|
|
}
|
|
impl MobileSecretBackend for RustSecretBackend {
|
|
fn create(
|
|
&self,
|
|
locator: MobileSecretLocator,
|
|
protection: MobileSecretProtection,
|
|
value: Vec<u8>,
|
|
) -> Result<(), MobileSecretError> {
|
|
let secret = SecretBytes::new(value);
|
|
self.0
|
|
.create(&locator.try_into()?, protection.into(), secret.expose())
|
|
.map_err(Into::into)
|
|
}
|
|
fn replace(
|
|
&self,
|
|
locator: MobileSecretLocator,
|
|
protection: MobileSecretProtection,
|
|
value: Vec<u8>,
|
|
) -> Result<(), MobileSecretError> {
|
|
let secret = SecretBytes::new(value);
|
|
self.0
|
|
.replace(&locator.try_into()?, protection.into(), secret.expose())
|
|
.map_err(Into::into)
|
|
}
|
|
fn retrieve(
|
|
&self,
|
|
locator: MobileSecretLocator,
|
|
protection: MobileSecretProtection,
|
|
) -> Result<Vec<u8>, MobileSecretError> {
|
|
self.0
|
|
.retrieve(&locator.try_into()?, protection.into())
|
|
.map(|v| v.expose().to_vec())
|
|
.map_err(Into::into)
|
|
}
|
|
fn delete(
|
|
&self,
|
|
locator: MobileSecretLocator,
|
|
protection: MobileSecretProtection,
|
|
) -> Result<(), MobileSecretError> {
|
|
self.0
|
|
.delete(&locator.try_into()?, protection.into())
|
|
.map_err(Into::into)
|
|
}
|
|
fn lock(&self) -> Result<(), MobileSecretError> {
|
|
self.0.lock().map_err(Into::into)
|
|
}
|
|
fn unlock(&self) -> Result<(), MobileSecretError> {
|
|
self.0.unlock().map_err(Into::into)
|
|
}
|
|
}
|