Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use sha2::{Digest, Sha256};
use subtle::ConstantTimeEq;
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};

use crate::error::Unsupported;
use crate::trace;

const SYMMETRIC_MAGIC: &[u8; 8] = b"E2EES05\0";
Expand Down Expand Up @@ -122,7 +123,10 @@ impl KeyFile {
let bytes = fs::read(path).with_context(|| format!("read key file {}", path.display()))?;
let key: Self = serde_json::from_slice(&bytes).context("parse key file")?;
if key.format_version != 3 {
bail!("unsupported key file format {}", key.format_version)
bail!(Unsupported(format!(
"unsupported key file format {}",
key.format_version
)))
}
validate_root(&key.repository_root)?;
key.signing_key()?;
Expand Down Expand Up @@ -562,7 +566,7 @@ pub fn open_pack_stream(
.expect("fixed chunk size field"),
) as usize;
if chunk_size != PACK_STREAM_CHUNK_SIZE {
bail!("unsupported pack stream chunk size")
bail!(Unsupported("unsupported pack stream chunk size".to_owned()))
}
let segments = plaintext_size.div_ceil(PACK_STREAM_CHUNK_SIZE as u64);
if segments == 0 || segments >= u32::MAX as u64 {
Expand Down
43 changes: 43 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//! Failures that recovery classifies. Recovery inspects these by type, never
//! by message text, because messages can carry storage-controlled strings.

use std::io;

use thiserror::Error;

/// A required storage object is missing or could not be read.
#[derive(Debug, Error)]
#[error("{storage} object {id} is unavailable")]
pub struct ObjectUnavailable {
pub storage: &'static str,
pub id: String,
#[source]
pub source: Option<io::Error>,
}

/// Storage holds no HEAD at all.
#[derive(Debug, Error)]
#[error("encrypted repository has no storage HEAD")]
pub struct MissingStorageHead;

/// A format version or transition this implementation does not support.
#[derive(Debug, Error)]
#[error("{0}")]
pub struct Unsupported(pub String);

/// No generation-key envelope opens for this device.
#[derive(Debug, Error)]
#[error(
"device is not an active reader of the current generation (revoked, never added, or envelope malformed)"
)]
pub struct NotActiveReader;

/// The local key file and the manifest name different repository roots.
#[derive(Debug, Error)]
#[error("key file belongs to a different repository")]
pub struct ForeignRepository;

/// A fixed recovery budget ran out; recovery fails closed.
#[derive(Debug, Error)]
#[error("{0}")]
pub struct BudgetExhausted(pub String);
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod crypto;
pub mod error;
pub mod git;
pub mod manifest;
mod persist;
Expand Down
24 changes: 16 additions & 8 deletions src/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::crypto::{
derive_subkey, dummy_generation_envelope, open_with_key, random_seed, seal_with_key,
verify_domain, verify_key_commitment, wrap_generation_key_with_seed,
};
use crate::error::{ForeignRepository, NotActiveReader, Unsupported};
use crate::policy::PolicyState;

pub const FORMAT_VERSION: u32 = 5;
Expand Down Expand Up @@ -143,7 +144,9 @@ impl Manifest {
}
}
ManifestAuthorization::Checkpoint => {
bail!("checkpoint transitions are reserved but not implemented")
bail!(Unsupported(
"checkpoint transitions are reserved but not implemented".to_owned()
))
}
}
Ok(())
Expand Down Expand Up @@ -248,7 +251,9 @@ pub fn seal_manifest(
validate_introduced_policy(&manifest, policy, true)?
}
ManifestAuthorization::Checkpoint => {
bail!("checkpoint transitions are reserved but not implemented")
bail!(Unsupported(
"checkpoint transitions are reserved but not implemented".to_owned()
))
}
}
validate_pack_delta(&manifest)?;
Expand Down Expand Up @@ -337,7 +342,7 @@ pub fn peek_manifest_header(encrypted: &[u8]) -> Result<ManifestHeader> {

pub fn unwrap_generation_key(header: &ManifestHeader, key: &KeyFile) -> Result<SecretKey> {
if key.repository_root != header.repository_root {
bail!("key file belongs to a different repository")
bail!(ForeignRepository)
}
validate_public_header(header)?;
let device_id = key.device_id()?;
Expand All @@ -360,9 +365,7 @@ pub fn unwrap_generation_key(header: &ManifestHeader, key: &KeyFile) -> Result<S
return Ok(generation_key);
}
}
bail!(
"device is not an active reader of the current generation (revoked, never added, or envelope malformed)"
)
bail!(NotActiveReader)
}

pub fn open_manifest(encrypted: &[u8], generation_key: &[u8; 32]) -> Result<OpenedManifest> {
Expand Down Expand Up @@ -508,7 +511,9 @@ pub fn verify_manifest(
.context("policy transition manifest was not signed by a parent administrator")?
}
ManifestAuthorization::Checkpoint => {
bail!("checkpoint transitions are reserved but not implemented")
bail!(Unsupported(
"checkpoint transitions are reserved but not implemented".to_owned()
))
}
};
verify_domain(
Expand Down Expand Up @@ -764,7 +769,10 @@ fn validate_introduced_policy(

fn validate_public_header(header: &ManifestHeader) -> Result<()> {
if header.format_version != FORMAT_VERSION {
bail!("unsupported manifest format {}", header.format_version)
bail!(Unsupported(format!(
"unsupported manifest format {}",
header.format_version
)))
}
if header.repository_root.len() != 64
|| !header
Expand Down
6 changes: 5 additions & 1 deletion src/policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::crypto::{
KeyFile, PublicDevice, object_id, repository_root_for_device, validate_public_device,
verify_domain,
};
use crate::error::Unsupported;

pub const POLICY_FORMAT_VERSION: u32 = 6;
const POLICY_SIGNATURE_DOMAIN: &[u8] = b"git-remote-e2ee policy v6\0";
Expand Down Expand Up @@ -160,7 +161,10 @@ impl PolicyState {
let exact_body = BASE64.decode(object.body).context("decode policy body")?;
let body: PolicyBody = serde_json::from_slice(&exact_body).context("parse policy body")?;
if body.format_version != POLICY_FORMAT_VERSION {
bail!("unsupported policy format {}", body.format_version)
bail!(Unsupported(format!(
"unsupported policy format {}",
body.format_version
)))
}
validate_devices(&body.devices)?;
validate_device_roots(&body)?;
Expand Down
Loading
Loading