Prepare Apple App Store distribution (#59)
This commit is contained in:
1 parent
b50e47ab73
commit
f894d43eb6
46 files changed
+20694
-166
No files matched your search
@@ -181,7 +181,7 @@ impl MobileKeyTransferService {
|
||||
}
|
||||
|
||||
pub struct MobileKeyTransferImport {
|
||||
config: Config,
|
||||
destination: MobileKeyTransferDestination,
|
||||
digest: Option<String>,
|
||||
total: Option<usize>,
|
||||
chunks: BTreeMap<usize, Vec<u8>>,
|
||||
@@ -189,10 +189,26 @@ pub struct MobileKeyTransferImport {
|
||||
key: Option<MobileKeyTransferKey>,
|
||||
}
|
||||
|
||||
enum MobileKeyTransferDestination {
|
||||
Configured(Box<Config>),
|
||||
LocalSetup,
|
||||
}
|
||||
|
||||
impl MobileKeyTransferImport {
|
||||
fn new(config: Config) -> Self {
|
||||
Self {
|
||||
config,
|
||||
destination: MobileKeyTransferDestination::Configured(Box::new(config)),
|
||||
digest: None,
|
||||
total: None,
|
||||
chunks: BTreeMap::new(),
|
||||
complete: None,
|
||||
key: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn local_setup() -> Self {
|
||||
Self {
|
||||
destination: MobileKeyTransferDestination::LocalSetup,
|
||||
digest: None,
|
||||
total: None,
|
||||
chunks: BTreeMap::new(),
|
||||
@@ -280,6 +296,7 @@ impl MobileKeyTransferImport {
|
||||
&mut self,
|
||||
passphrase: Option<SecretBytes>,
|
||||
make_default: bool,
|
||||
overwrite: bool,
|
||||
) -> Result<MobileKeyTransferOutcome, MobileKeyTransferError> {
|
||||
let armor = self
|
||||
.complete
|
||||
@@ -298,20 +315,46 @@ impl MobileKeyTransferImport {
|
||||
return Err(MobileKeyTransferError::PublicDefault);
|
||||
}
|
||||
|
||||
let existing = KeyStore::load(self.config.key_material())?;
|
||||
if matches!(self.destination, MobileKeyTransferDestination::LocalSetup) {
|
||||
if key.kind != MobileKeyTransferKind::Private || !make_default {
|
||||
return Err(MobileKeyTransferError::PublicDefault);
|
||||
}
|
||||
let outcome = crate::mobile_onboarding::MobileOnboardingOperation::default()
|
||||
.setup_local_imported(
|
||||
SecretBytes::new(armor.expose().to_vec()),
|
||||
passphrase,
|
||||
overwrite,
|
||||
)
|
||||
.map_err(MobileKeyTransferError::LocalSetup)?;
|
||||
return Ok(MobileKeyTransferOutcome {
|
||||
title: outcome.title().to_owned(),
|
||||
detail: outcome.detail().to_owned(),
|
||||
});
|
||||
}
|
||||
let MobileKeyTransferDestination::Configured(config) = &self.destination else {
|
||||
unreachable!("local setup returned above")
|
||||
};
|
||||
let existing = KeyStore::load(config.key_material())?;
|
||||
if let Some(found) = existing
|
||||
.infos()
|
||||
.find(|info| info.fingerprint().as_str() == key.fingerprint)
|
||||
&& (found.has_secret() || key.kind == MobileKeyTransferKind::Public)
|
||||
&& !overwrite
|
||||
{
|
||||
return Err(MobileKeyTransferError::DuplicateKey);
|
||||
return Err(MobileKeyTransferError::ExistingKey);
|
||||
}
|
||||
|
||||
persist_key_material(self.config.key_material(), key, armor.expose())?;
|
||||
persist_armored_key(
|
||||
config.key_material(),
|
||||
&key.fingerprint,
|
||||
key.kind == MobileKeyTransferKind::Private,
|
||||
armor.expose(),
|
||||
overwrite,
|
||||
)?;
|
||||
if make_default {
|
||||
let mut settings = self.config.settings();
|
||||
let mut settings = config.settings();
|
||||
settings.set_default_key(key.fingerprint.clone());
|
||||
self.config.with_settings(settings)?.persist()?;
|
||||
config.with_settings(settings)?.persist()?;
|
||||
}
|
||||
let kind = if key.kind == MobileKeyTransferKind::Private {
|
||||
"private"
|
||||
@@ -357,12 +400,13 @@ pub enum MobileKeyTransferError {
|
||||
AlreadyComplete,
|
||||
ChecksumMismatch,
|
||||
MismatchedKeys,
|
||||
DuplicateKey,
|
||||
ExistingKey,
|
||||
PublicDefault,
|
||||
InvalidKeyMaterial,
|
||||
IncorrectPassphrase,
|
||||
QrPayload,
|
||||
Write,
|
||||
LocalSetup(crate::mobile_onboarding::MobileOnboardingError),
|
||||
}
|
||||
|
||||
impl fmt::Display for MobileKeyTransferError {
|
||||
@@ -381,12 +425,15 @@ impl fmt::Display for MobileKeyTransferError {
|
||||
"the reconstructed key transfer did not pass its integrity check"
|
||||
}
|
||||
Self::MismatchedKeys => "the transfer contains multiple or mismatched GPG keys",
|
||||
Self::DuplicateKey => "this GPG key is already installed",
|
||||
Self::ExistingKey => {
|
||||
"this GPG key is already installed; confirm replacement to continue"
|
||||
}
|
||||
Self::PublicDefault => "a public-only key cannot become the default GPG key",
|
||||
Self::InvalidKeyMaterial => "the GPG key material is invalid",
|
||||
Self::IncorrectPassphrase => "the GPG key passphrase is incorrect",
|
||||
Self::QrPayload => "the GPG key could not be represented as QR codes",
|
||||
Self::Write => "the GPG key could not be stored safely",
|
||||
Self::LocalSetup(error) => return error.fmt(formatter),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -499,7 +546,7 @@ fn parse_positive(value: Option<&str>) -> Result<usize, MobileKeyTransferError>
|
||||
.ok_or(MobileKeyTransferError::InvalidFrame)
|
||||
}
|
||||
|
||||
fn validate_transfer_armor(bytes: &[u8]) -> Result<(), MobileKeyTransferError> {
|
||||
pub(crate) fn validate_transfer_armor(bytes: &[u8]) -> Result<(), MobileKeyTransferError> {
|
||||
let text =
|
||||
std::str::from_utf8(bytes).map_err(|_| MobileKeyTransferError::InvalidKeyMaterial)?;
|
||||
let end = if text.starts_with("-----BEGIN PGP PUBLIC KEY BLOCK-----") {
|
||||
@@ -522,18 +569,19 @@ fn validate_transfer_armor(bytes: &[u8]) -> Result<(), MobileKeyTransferError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn persist_key_material(
|
||||
pub(crate) fn persist_armored_key(
|
||||
configured: &Path,
|
||||
key: &MobileKeyTransferKey,
|
||||
fingerprint: &str,
|
||||
secret: bool,
|
||||
armor: &[u8],
|
||||
overwrite: bool,
|
||||
) -> Result<(), MobileKeyTransferError> {
|
||||
if configured.is_dir() {
|
||||
let suffix = if key.kind == MobileKeyTransferKind::Private {
|
||||
"secret"
|
||||
} else {
|
||||
"public"
|
||||
};
|
||||
let name = format!("{}-{suffix}.asc", key.fingerprint.to_ascii_lowercase());
|
||||
let suffix = if secret { "secret" } else { "public" };
|
||||
let name = format!("{}-{suffix}.asc", fingerprint.to_ascii_lowercase());
|
||||
if configured.join(&name).exists() && !overwrite {
|
||||
return Err(MobileKeyTransferError::ExistingKey);
|
||||
}
|
||||
atomic_replace(configured, Path::new(&name), armor)
|
||||
} else {
|
||||
let existing = fs::read(configured).map_err(|_| MobileKeyTransferError::Write)?;
|
||||
@@ -689,7 +737,7 @@ mod tests {
|
||||
);
|
||||
assert!(progress.received() < progress.total());
|
||||
assert_eq!(
|
||||
importer.import(None, false),
|
||||
importer.import(None, false, false),
|
||||
Err(MobileKeyTransferError::IncompleteTransfer)
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -706,13 +754,14 @@ mod tests {
|
||||
let mut importer = MobileKeyTransferImport::new(fixture.config.clone());
|
||||
importer.add_frame(SecretBytes::new(ALICE_SECRET.to_vec()))?;
|
||||
assert_eq!(
|
||||
importer.import(Some(SecretBytes::new(b"wrong".to_vec())), true),
|
||||
importer.import(Some(SecretBytes::new(b"wrong".to_vec())), true, false),
|
||||
Err(MobileKeyTransferError::IncorrectPassphrase)
|
||||
);
|
||||
assert_eq!(std::fs::read_dir(&fixture.key_path)?.count(), 1);
|
||||
importer.import(
|
||||
Some(SecretBytes::new(b"fixture-alice-passphrase".to_vec())),
|
||||
true,
|
||||
false,
|
||||
)?;
|
||||
let keys = KeyStore::load(&fixture.key_path)?;
|
||||
assert!(keys.infos().any(|key| key.has_secret()));
|
||||
@@ -722,6 +771,32 @@ mod tests {
|
||||
.as_str(),
|
||||
ALICE_FINGERPRINT
|
||||
);
|
||||
let before = std::fs::read(fixture.key_path.join(format!(
|
||||
"{}-secret.asc",
|
||||
ALICE_FINGERPRINT.to_ascii_lowercase()
|
||||
)))?;
|
||||
let mut replacement = MobileKeyTransferImport::new(fixture.config);
|
||||
replacement.add_frame(SecretBytes::new(ALICE_SECRET.to_vec()))?;
|
||||
assert_eq!(
|
||||
replacement.import(
|
||||
Some(SecretBytes::new(b"fixture-alice-passphrase".to_vec())),
|
||||
true,
|
||||
false,
|
||||
),
|
||||
Err(MobileKeyTransferError::ExistingKey)
|
||||
);
|
||||
assert_eq!(
|
||||
std::fs::read(fixture.key_path.join(format!(
|
||||
"{}-secret.asc",
|
||||
ALICE_FINGERPRINT.to_ascii_lowercase()
|
||||
)))?,
|
||||
before
|
||||
);
|
||||
replacement.import(
|
||||
Some(SecretBytes::new(b"fixture-alice-passphrase".to_vec())),
|
||||
true,
|
||||
true,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user