commit 60bffa22c8d435cb1426a8460dc7cfddbf6eea2f
parent 8c75251a3ebbbc07f99504b09583892b36906e1c
Author: triesap <tyson@radroots.org>
Date: Wed, 5 Aug 2026 22:11:40 +0000
refactor: adopt context-bound envelopes
Diffstat:
4 files changed, 283 insertions(+), 50 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -1719,7 +1719,7 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "radroots_blossom"
version = "0.1.0-alpha"
-source = "git+https://github.com/radrootslabs/lib.git?rev=74f5c91cbc80248df1f51f644004daaf8ae8e9be#74f5c91cbc80248df1f51f644004daaf8ae8e9be"
+source = "git+https://github.com/radrootslabs/lib.git?rev=f7f456d906d91aadbb9fac4850afaa8c5723c3e9#f7f456d906d91aadbb9fac4850afaa8c5723c3e9"
dependencies = [
"mediatype",
"serde",
@@ -1731,7 +1731,7 @@ dependencies = [
[[package]]
name = "radroots_core"
version = "0.1.0-alpha"
-source = "git+https://github.com/radrootslabs/lib.git?rev=74f5c91cbc80248df1f51f644004daaf8ae8e9be#74f5c91cbc80248df1f51f644004daaf8ae8e9be"
+source = "git+https://github.com/radrootslabs/lib.git?rev=f7f456d906d91aadbb9fac4850afaa8c5723c3e9#f7f456d906d91aadbb9fac4850afaa8c5723c3e9"
dependencies = [
"rust_decimal",
"serde",
@@ -1740,7 +1740,7 @@ dependencies = [
[[package]]
name = "radroots_event"
version = "0.1.0-alpha"
-source = "git+https://github.com/radrootslabs/lib.git?rev=74f5c91cbc80248df1f51f644004daaf8ae8e9be#74f5c91cbc80248df1f51f644004daaf8ae8e9be"
+source = "git+https://github.com/radrootslabs/lib.git?rev=f7f456d906d91aadbb9fac4850afaa8c5723c3e9#f7f456d906d91aadbb9fac4850afaa8c5723c3e9"
dependencies = [
"hex",
"jiff-tzdb",
@@ -1758,8 +1758,9 @@ dependencies = [
[[package]]
name = "radroots_event_codec"
version = "0.1.0-alpha"
-source = "git+https://github.com/radrootslabs/lib.git?rev=74f5c91cbc80248df1f51f644004daaf8ae8e9be#74f5c91cbc80248df1f51f644004daaf8ae8e9be"
+source = "git+https://github.com/radrootslabs/lib.git?rev=f7f456d906d91aadbb9fac4850afaa8c5723c3e9#f7f456d906d91aadbb9fac4850afaa8c5723c3e9"
dependencies = [
+ "hex",
"radroots_blossom",
"radroots_core",
"radroots_event",
@@ -1768,12 +1769,13 @@ dependencies = [
"secp256k1",
"serde",
"serde_json",
+ "sha2",
]
[[package]]
name = "radroots_identity"
version = "0.1.0-alpha"
-source = "git+https://github.com/radrootslabs/lib.git?rev=74f5c91cbc80248df1f51f644004daaf8ae8e9be#74f5c91cbc80248df1f51f644004daaf8ae8e9be"
+source = "git+https://github.com/radrootslabs/lib.git?rev=f7f456d906d91aadbb9fac4850afaa8c5723c3e9#f7f456d906d91aadbb9fac4850afaa8c5723c3e9"
dependencies = [
"k256",
"serde",
@@ -1783,7 +1785,7 @@ dependencies = [
[[package]]
name = "radroots_nostr"
version = "0.1.0-alpha"
-source = "git+https://github.com/radrootslabs/lib.git?rev=74f5c91cbc80248df1f51f644004daaf8ae8e9be#74f5c91cbc80248df1f51f644004daaf8ae8e9be"
+source = "git+https://github.com/radrootslabs/lib.git?rev=f7f456d906d91aadbb9fac4850afaa8c5723c3e9#f7f456d906d91aadbb9fac4850afaa8c5723c3e9"
dependencies = [
"nostr",
"radroots_event",
@@ -1797,7 +1799,7 @@ dependencies = [
[[package]]
name = "radroots_nostr_connect"
version = "0.1.0-alpha"
-source = "git+https://github.com/radrootslabs/lib.git?rev=74f5c91cbc80248df1f51f644004daaf8ae8e9be#74f5c91cbc80248df1f51f644004daaf8ae8e9be"
+source = "git+https://github.com/radrootslabs/lib.git?rev=f7f456d906d91aadbb9fac4850afaa8c5723c3e9#f7f456d906d91aadbb9fac4850afaa8c5723c3e9"
dependencies = [
"nostr",
"radroots_event",
@@ -1813,7 +1815,7 @@ dependencies = [
[[package]]
name = "radroots_protocol"
version = "0.1.0-alpha"
-source = "git+https://github.com/radrootslabs/lib.git?rev=74f5c91cbc80248df1f51f644004daaf8ae8e9be#74f5c91cbc80248df1f51f644004daaf8ae8e9be"
+source = "git+https://github.com/radrootslabs/lib.git?rev=f7f456d906d91aadbb9fac4850afaa8c5723c3e9#f7f456d906d91aadbb9fac4850afaa8c5723c3e9"
dependencies = [
"serde",
]
@@ -1821,23 +1823,28 @@ dependencies = [
[[package]]
name = "radroots_secrets"
version = "0.1.0-alpha"
-source = "git+https://github.com/radrootslabs/lib.git?rev=74f5c91cbc80248df1f51f644004daaf8ae8e9be#74f5c91cbc80248df1f51f644004daaf8ae8e9be"
+source = "git+https://github.com/radrootslabs/lib.git?rev=f7f456d906d91aadbb9fac4850afaa8c5723c3e9#f7f456d906d91aadbb9fac4850afaa8c5723c3e9"
dependencies = [
"chacha20poly1305",
"keyring",
"serde",
+ "sha2",
+ "subtle",
"zeroize",
]
[[package]]
name = "radroots_signing"
version = "0.1.0-alpha"
-source = "git+https://github.com/radrootslabs/lib.git?rev=74f5c91cbc80248df1f51f644004daaf8ae8e9be#74f5c91cbc80248df1f51f644004daaf8ae8e9be"
+source = "git+https://github.com/radrootslabs/lib.git?rev=f7f456d906d91aadbb9fac4850afaa8c5723c3e9#f7f456d906d91aadbb9fac4850afaa8c5723c3e9"
dependencies = [
+ "hex",
"radroots_event",
+ "radroots_event_codec",
"radroots_identity",
"radroots_protocol",
"serde",
+ "sha2",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
@@ -23,12 +23,12 @@ hex = "0.4"
keyring = { version = "3.6", features = ["apple-native", "windows-native", "sync-secret-service"] }
nostr = { version = "0.44.2", features = ["nip04", "nip44", "nip46", "nip49"] }
nostr-sdk = { version = "0.44.1" }
-radroots_identity = { git = "https://github.com/radrootslabs/lib.git", rev = "74f5c91cbc80248df1f51f644004daaf8ae8e9be", version = "=0.1.0-alpha" }
-radroots_event = { git = "https://github.com/radrootslabs/lib.git", rev = "74f5c91cbc80248df1f51f644004daaf8ae8e9be", version = "=0.1.0-alpha", features = ["serde"] }
-radroots_nostr = { git = "https://github.com/radrootslabs/lib.git", rev = "74f5c91cbc80248df1f51f644004daaf8ae8e9be", version = "=0.1.0-alpha", features = ["events"] }
-radroots_nostr_connect = { git = "https://github.com/radrootslabs/lib.git", rev = "74f5c91cbc80248df1f51f644004daaf8ae8e9be", version = "=0.1.0-alpha" }
-radroots_secrets = { git = "https://github.com/radrootslabs/lib.git", rev = "74f5c91cbc80248df1f51f644004daaf8ae8e9be", version = "=0.1.0-alpha", features = ["std", "keyring"] }
-radroots_signing = { git = "https://github.com/radrootslabs/lib.git", rev = "74f5c91cbc80248df1f51f644004daaf8ae8e9be", version = "=0.1.0-alpha", features = ["std"] }
+radroots_identity = { git = "https://github.com/radrootslabs/lib.git", rev = "f7f456d906d91aadbb9fac4850afaa8c5723c3e9", version = "=0.1.0-alpha" }
+radroots_event = { git = "https://github.com/radrootslabs/lib.git", rev = "f7f456d906d91aadbb9fac4850afaa8c5723c3e9", version = "=0.1.0-alpha", features = ["serde"] }
+radroots_nostr = { git = "https://github.com/radrootslabs/lib.git", rev = "f7f456d906d91aadbb9fac4850afaa8c5723c3e9", version = "=0.1.0-alpha", features = ["events"] }
+radroots_nostr_connect = { git = "https://github.com/radrootslabs/lib.git", rev = "f7f456d906d91aadbb9fac4850afaa8c5723c3e9", version = "=0.1.0-alpha" }
+radroots_secrets = { git = "https://github.com/radrootslabs/lib.git", rev = "f7f456d906d91aadbb9fac4850afaa8c5723c3e9", version = "=0.1.0-alpha", features = ["std", "keyring"] }
+radroots_signing = { git = "https://github.com/radrootslabs/lib.git", rev = "f7f456d906d91aadbb9fac4850afaa8c5723c3e9", version = "=0.1.0-alpha", features = ["std"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sha2 = "0.10"
diff --git a/src/identity_files.rs b/src/identity_files.rs
@@ -5,11 +5,17 @@ use std::path::{Path, PathBuf};
use chacha20poly1305::aead::{Aead, KeyInit, Payload};
use chacha20poly1305::{Key, XChaCha20Poly1305, XNonce};
-use radroots_secrets::envelope::{Nonce, SealMaterial, SealRequest};
+use radroots_secrets::context::{
+ EnvelopeContext, EnvelopePurpose, EnvelopeSubject, PayloadSchemaId,
+};
+use radroots_secrets::envelope::{
+ ENVELOPE_VERSION, LEGACY_ENVELOPE_VERSION, LegacyV1ResealAuthority, Nonce, SealMaterial,
+ SealRequest,
+};
use radroots_secrets::error::Operation;
use radroots_secrets::id::{BackendKind, KeyVersion};
use radroots_secrets::wrapping::{
- BoxFuture, SecretMaterial, UnwrapRequest, WrapRequest, WrappedSecret,
+ BoxFuture, LegacyV1UnwrapRequest, SecretMaterial, UnwrapRequest, WrapRequest, WrappedSecret,
};
use radroots_secrets::{EncryptedEnvelope, KeyWrapping, SecretId, SecretRef};
use zeroize::Zeroize;
@@ -21,7 +27,9 @@ use crate::host_identity::{
const MYC_IDENTITY_KEY_SLOT: &str = "myc_identity";
const WRAPPING_KEY_BYTES: usize = 32;
const WRAPPING_NONCE_BYTES: usize = 24;
-const WRAPPED_KEY_VERSION: u8 = 1;
+const LEGACY_WRAPPED_KEY_VERSION: u8 = 1;
+const WRAPPED_KEY_VERSION: u8 = 2;
+const WRAPPING_AAD_DOMAIN: &[u8] = b"myc.wrapped_data_key.v2";
struct MycFileKeyWrapping {
key_path: PathBuf,
@@ -81,14 +89,16 @@ impl KeyWrapping for MycFileKeyWrapping {
request: WrapRequest<'a>,
) -> BoxFuture<'a, Result<WrappedSecret, radroots_secrets::Error>> {
Box::pin(async move {
+ validate_identity_reference(request.reference(), Operation::Wrap)?;
let mut key = self.load_or_create_key()?;
let nonce: [u8; WRAPPING_NONCE_BYTES] = rand::random();
+ let aad = wrapping_aad(request.reference(), request.context());
let ciphertext = request.plaintext().expose_secret(|plaintext| {
XChaCha20Poly1305::new(Key::from_slice(&key)).encrypt(
XNonce::from_slice(&nonce),
Payload {
msg: plaintext,
- aad: request.reference().id().as_str().as_bytes(),
+ aad: aad.as_slice(),
},
)
});
@@ -107,26 +117,81 @@ impl KeyWrapping for MycFileKeyWrapping {
request: UnwrapRequest<'a>,
) -> BoxFuture<'a, Result<SecretMaterial, radroots_secrets::Error>> {
Box::pin(async move {
- let wrapped = request.wrapped().as_bytes();
- if wrapped.len() <= 1 + WRAPPING_NONCE_BYTES || wrapped[0] != WRAPPED_KEY_VERSION {
- return Err(secret_backend_failure(Operation::Unwrap));
- }
- let mut key = self.load_key()?;
- let plaintext = XChaCha20Poly1305::new(Key::from_slice(&key)).decrypt(
- XNonce::from_slice(&wrapped[1..1 + WRAPPING_NONCE_BYTES]),
- Payload {
- msg: &wrapped[1 + WRAPPING_NONCE_BYTES..],
- aad: request.reference().id().as_str().as_bytes(),
- },
- );
- key.zeroize();
- SecretMaterial::from_slice(
- &plaintext.map_err(|_| secret_backend_failure(Operation::Unwrap))?,
+ validate_identity_reference(request.reference(), Operation::Unwrap)?;
+ let aad = wrapping_aad(request.reference(), request.context());
+ self.unwrap_with_aad(request.wrapped(), WRAPPED_KEY_VERSION, aad.as_slice())
+ })
+ }
+
+ fn unwrap_legacy_v1<'a>(
+ &'a self,
+ request: LegacyV1UnwrapRequest<'a>,
+ ) -> BoxFuture<'a, Result<SecretMaterial, radroots_secrets::Error>> {
+ Box::pin(async move {
+ validate_identity_reference(request.reference(), Operation::Unwrap)?;
+ self.unwrap_with_aad(
+ request.wrapped(),
+ LEGACY_WRAPPED_KEY_VERSION,
+ request.reference().id().as_str().as_bytes(),
)
})
}
}
+impl MycFileKeyWrapping {
+ fn unwrap_with_aad(
+ &self,
+ wrapped: &WrappedSecret,
+ expected_version: u8,
+ aad: &[u8],
+ ) -> Result<SecretMaterial, radroots_secrets::Error> {
+ let wrapped = wrapped.as_bytes();
+ if wrapped.len() <= 1 + WRAPPING_NONCE_BYTES || wrapped[0] != expected_version {
+ return Err(secret_backend_failure(Operation::Unwrap));
+ }
+ let mut key = self.load_key()?;
+ let plaintext = XChaCha20Poly1305::new(Key::from_slice(&key)).decrypt(
+ XNonce::from_slice(&wrapped[1..1 + WRAPPING_NONCE_BYTES]),
+ Payload {
+ msg: &wrapped[1 + WRAPPING_NONCE_BYTES..],
+ aad,
+ },
+ );
+ key.zeroize();
+ SecretMaterial::from_slice(
+ &plaintext.map_err(|_| secret_backend_failure(Operation::Unwrap))?,
+ )
+ }
+}
+
+fn wrapping_aad(reference: &SecretRef, context: &EnvelopeContext) -> Vec<u8> {
+ let id = reference.id().as_str().as_bytes();
+ let mut aad = Vec::with_capacity(WRAPPING_AAD_DOMAIN.len() + 2 + id.len() + 4 + 32);
+ aad.extend_from_slice(WRAPPING_AAD_DOMAIN);
+ aad.extend_from_slice(
+ &u16::try_from(id.len())
+ .expect("validated secret identifier length fits u16")
+ .to_be_bytes(),
+ );
+ aad.extend_from_slice(id);
+ aad.extend_from_slice(&reference.key_version().get().to_be_bytes());
+ aad.extend_from_slice(&context.authentication_digest());
+ aad
+}
+
+fn validate_identity_reference(
+ reference: &SecretRef,
+ operation: Operation,
+) -> Result<(), radroots_secrets::Error> {
+ if reference.backend() != BackendKind::External
+ || reference.key_version().get() != 1
+ || reference.id().as_str() != MYC_IDENTITY_KEY_SLOT
+ {
+ return Err(secret_backend_failure(operation));
+ }
+ Ok(())
+}
+
fn identity_secret_ref() -> Result<SecretRef, radroots_secrets::Error> {
Ok(SecretRef::new(
SecretId::parse(MYC_IDENTITY_KEY_SLOT)?,
@@ -135,6 +200,14 @@ fn identity_secret_ref() -> Result<SecretRef, radroots_secrets::Error> {
))
}
+fn identity_envelope_context() -> Result<EnvelopeContext, radroots_secrets::Error> {
+ Ok(EnvelopeContext::new(
+ EnvelopePurpose::parse("radroots.service_identity")?,
+ EnvelopeSubject::parse("service", "myc")?,
+ PayloadSchemaId::parse("radroots.myc_identity.v1")?,
+ ))
+}
+
fn secret_backend_failure(operation: Operation) -> radroots_secrets::Error {
radroots_secrets::Error::BackendFailure {
backend: BackendKind::External,
@@ -175,10 +248,13 @@ pub fn store_encrypted_identity(
let data_key = SecretMaterial::from_slice(&rand::random::<[u8; 32]>())
.map_err(|_| storage_error(path, "validate identity data key"))?;
let wrapping = MycFileKeyWrapping::new(path);
+ let context =
+ identity_envelope_context().map_err(|_| storage_error(path, "build identity context"))?;
let envelope = futures_executor::block_on(EncryptedEnvelope::seal(
&wrapping,
SealRequest::new(
identity_secret_ref().map_err(|_| storage_error(path, "build identity reference"))?,
+ context,
&plaintext,
SealMaterial::new(data_key, Nonce::new(rand::random())),
),
@@ -202,7 +278,27 @@ pub fn load_encrypted_identity(path: impl AsRef<Path>) -> Result<RadrootsIdentit
let envelope = EncryptedEnvelope::decode(encoded.as_slice())
.map_err(|_| storage_error(path, "decode encrypted identity"))?;
let wrapping = MycFileKeyWrapping::new(path);
- let payload = futures_executor::block_on(envelope.open(&wrapping))
+ let context =
+ identity_envelope_context().map_err(|_| storage_error(path, "build identity context"))?;
+ if envelope.version() == LEGACY_ENVELOPE_VERSION {
+ return migrate_legacy_identity(path, envelope, &wrapping, context);
+ }
+ if envelope.version() != ENVELOPE_VERSION {
+ return Err(storage_error(
+ path,
+ "unsupported encrypted identity version",
+ ));
+ }
+ open_identity(path, &envelope, &wrapping, &context)
+}
+
+fn open_identity(
+ path: &Path,
+ envelope: &EncryptedEnvelope,
+ wrapping: &MycFileKeyWrapping,
+ context: &EnvelopeContext,
+) -> Result<RadrootsIdentity, IdentityError> {
+ let payload = futures_executor::block_on(envelope.open(wrapping, context))
.map_err(|_| storage_error(path, "open encrypted identity"))?;
let file: RadrootsIdentityFile = payload
.expose_secret(|bytes| serde_json::from_slice(bytes))
@@ -210,6 +306,42 @@ pub fn load_encrypted_identity(path: impl AsRef<Path>) -> Result<RadrootsIdentit
RadrootsIdentity::try_from(file)
}
+fn migrate_legacy_identity(
+ path: &Path,
+ envelope: EncryptedEnvelope,
+ wrapping: &MycFileKeyWrapping,
+ context: EnvelopeContext,
+) -> Result<RadrootsIdentity, IdentityError> {
+ let expected_reference =
+ identity_secret_ref().map_err(|_| storage_error(path, "build identity reference"))?;
+ let data_key = SecretMaterial::from_slice(&rand::random::<[u8; 32]>())
+ .map_err(|_| storage_error(path, "validate identity data key"))?;
+ let resealed = futures_executor::block_on(envelope.reseal_legacy_v1(
+ wrapping,
+ &LegacyV1ResealAuthority::new(),
+ &expected_reference,
+ identity_secret_ref().map_err(|_| storage_error(path, "build identity reference"))?,
+ context.clone(),
+ &valid_identity_payload,
+ SealMaterial::new(data_key, Nonce::new(rand::random())),
+ ))
+ .map_err(|_| storage_error(path, "migrate legacy encrypted identity"))?;
+ let envelope = resealed.into_envelope();
+ let identity = open_identity(path, &envelope, wrapping, &context)?;
+ let encoded = envelope
+ .encode()
+ .map_err(|_| storage_error(path, "encode migrated identity"))?;
+ atomic_write(path, encoded.as_slice())?;
+ Ok(identity)
+}
+
+fn valid_identity_payload(bytes: &[u8]) -> bool {
+ serde_json::from_slice::<RadrootsIdentityFile>(bytes)
+ .ok()
+ .and_then(|file| RadrootsIdentity::try_from(file).ok())
+ .is_some()
+}
+
pub fn rotate_encrypted_identity(path: impl AsRef<Path>) -> Result<(), IdentityError> {
let path = path.as_ref();
let identity = load_encrypted_identity(path)?;
@@ -325,6 +457,36 @@ mod tests {
load_encrypted_identity(&path).expect("load").id(),
identity.id()
);
+ let envelope =
+ EncryptedEnvelope::decode(&fs::read(&path).expect("read encrypted identity"))
+ .expect("decode encrypted identity");
+ assert_eq!(envelope.version(), ENVELOPE_VERSION);
+ assert_eq!(
+ envelope.context(),
+ Some(&identity_envelope_context().expect("identity context"))
+ );
+ }
+
+ #[test]
+ fn encrypted_identity_migrates_legacy_v1() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let path = temp.path().join("identity.enc");
+ let identity = identity();
+ store_legacy_identity(&path, &identity);
+
+ assert_eq!(
+ load_encrypted_identity(&path)
+ .expect("migrate legacy identity")
+ .id(),
+ identity.id()
+ );
+ let envelope = EncryptedEnvelope::decode(&fs::read(&path).expect("read migrated identity"))
+ .expect("decode migrated identity");
+ assert_eq!(envelope.version(), ENVELOPE_VERSION);
+ assert_eq!(
+ envelope.context(),
+ Some(&identity_envelope_context().expect("identity context"))
+ );
}
#[test]
@@ -338,4 +500,64 @@ mod tests {
identity.id()
);
}
+
+ fn store_legacy_identity(path: &Path, identity: &RadrootsIdentity) {
+ const NONCE_BYTES: usize = 24;
+ const TAG_BYTES: usize = 16;
+
+ let wrapping_key = [0x11; 32];
+ let data_key = [0x22; 32];
+ let wrapping_nonce = [0x33; NONCE_BYTES];
+ let envelope_nonce = [0x44; NONCE_BYTES];
+ let payload = serde_json::to_vec(&identity.to_file()).expect("encode identity payload");
+ let wrapped_ciphertext = XChaCha20Poly1305::new(Key::from_slice(&wrapping_key))
+ .encrypt(
+ XNonce::from_slice(&wrapping_nonce),
+ Payload {
+ msg: &data_key,
+ aad: MYC_IDENTITY_KEY_SLOT.as_bytes(),
+ },
+ )
+ .expect("wrap legacy data key");
+ let mut wrapped = Vec::with_capacity(1 + NONCE_BYTES + wrapped_ciphertext.len());
+ wrapped.push(LEGACY_WRAPPED_KEY_VERSION);
+ wrapped.extend_from_slice(&wrapping_nonce);
+ wrapped.extend_from_slice(&wrapped_ciphertext);
+
+ let id = MYC_IDENTITY_KEY_SLOT.as_bytes();
+ let mut encoded = Vec::new();
+ encoded.extend_from_slice(b"RRS1");
+ encoded.extend_from_slice(&LEGACY_ENVELOPE_VERSION.to_be_bytes());
+ encoded.extend_from_slice(&[1, 1, 4]);
+ encoded.extend_from_slice(&1_u32.to_be_bytes());
+ encoded.extend_from_slice(&u16::try_from(id.len()).expect("id length").to_be_bytes());
+ encoded.extend_from_slice(id);
+ encoded.extend_from_slice(&envelope_nonce);
+ encoded.extend_from_slice(
+ &u32::try_from(wrapped.len())
+ .expect("wrapped length")
+ .to_be_bytes(),
+ );
+ encoded.extend_from_slice(&wrapped);
+ encoded.extend_from_slice(
+ &u32::try_from(payload.len() + TAG_BYTES)
+ .expect("ciphertext length")
+ .to_be_bytes(),
+ );
+ let ciphertext = XChaCha20Poly1305::new(Key::from_slice(&data_key))
+ .encrypt(
+ XNonce::from_slice(&envelope_nonce),
+ Payload {
+ msg: &payload,
+ aad: &encoded,
+ },
+ )
+ .expect("encrypt legacy payload");
+ encoded.extend_from_slice(&ciphertext);
+
+ fs::write(path, encoded).expect("write legacy envelope");
+ let key_path = encrypted_identity_wrapping_key_path(path);
+ fs::write(&key_path, wrapping_key).expect("write wrapping key");
+ set_secret_permissions(&key_path).expect("secure wrapping key");
+ }
}
diff --git a/src/signing_adapter.rs b/src/signing_adapter.rs
@@ -6,6 +6,7 @@ use nostr::{EventBuilder, JsonUtil, Kind, Tag, Timestamp};
use radroots_event::{SignedEvent, wire::v1::Nip01EventWire};
use radroots_signing::capability::{CancellationSupport, SignerCapability, SignerKind};
use radroots_signing::error::Kind as SigningErrorKind;
+use radroots_signing::recovery::ReplayCapability;
use radroots_signing::status::{SignProgress, SignProgressStage, SignerAvailability};
use radroots_signing::{Error, SignReceipt, SignRequest, Signer, SignerStatus};
@@ -18,6 +19,7 @@ impl Signer for MycActiveIdentity {
SignerAvailability::Ready,
vec![SignerCapability::new(
SignerKind::HostMediated,
+ ReplayCapability::LocalReplaySafe,
CancellationSupport::BeforePublication,
true,
true,
@@ -32,27 +34,27 @@ impl Signer for MycActiveIdentity {
request: SignRequest,
) -> radroots_signing::signer::BoxFuture<'_, Result<SignReceipt, Error>> {
Box::pin(async move {
- let now = now_unix_secs();
- if now > request.policy().deadline_unix() {
- return Err(Error::new(SigningErrorKind::DeadlineExceeded));
- }
- if request.draft().expected_pubkey() != &self.public_identity().public_key() {
+ let now = now_unix_ms();
+ request.ensure_active(now)?;
+ let plan = request.plan();
+ if plan.author() != &self.public_identity().public_key() {
return Err(Error::new(SigningErrorKind::AuthorizationDenied));
}
report_progress(&request, SignProgressStage::Validating)?;
- let kind = u16::try_from(request.draft().kind_u32())
+ let kind = u16::try_from(plan.body().kind())
.map_err(|_| Error::new(SigningErrorKind::InvalidArgument))?;
- let tags = request
- .draft()
- .tags_as_vec()
- .into_iter()
+ let tags = plan
+ .body()
+ .tags()
+ .iter()
+ .cloned()
.map(Tag::parse)
.collect::<Result<Vec<_>, _>>()
.map_err(|source| Error::with_source(SigningErrorKind::InvalidArgument, source))?;
- let unsigned = EventBuilder::new(Kind::Custom(kind), request.draft().content())
+ let unsigned = EventBuilder::new(Kind::Custom(kind), plan.body().content())
.tags(tags)
- .custom_created_at(Timestamp::from(request.draft().created_at_u64()))
+ .custom_created_at(Timestamp::from(plan.created_at()))
.build(self.public_key());
let event = self
.sign_unsigned_event(unsigned, "final signing request")
@@ -79,8 +81,10 @@ fn report_progress(request: &SignRequest, stage: SignProgressStage) -> Result<()
Ok(())
}
-fn now_unix_secs() -> u64 {
+fn now_unix_ms() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
- .map_or(0, |duration| duration.as_secs())
+ .map_or(0, |duration| {
+ u64::try_from(duration.as_millis()).unwrap_or(u64::MAX)
+ })
}