Skip to content

Commit 58d753d

Browse files
committed
cose: support low-level encrytion for message forwarding
1 parent 659ceb9 commit 58d753d

File tree

1 file changed

+82
-58
lines changed

1 file changed

+82
-58
lines changed

src/cose/mod.rs

Lines changed: 82 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,30 @@ pub fn seal_at<E: Encode, A: Encode>(
461461
domain,
462462
timestamp,
463463
);
464+
// Encrypt the signed message to the recipient
465+
encrypt(&signed, Raw(msg_to_auth), recipient, domain)
466+
}
467+
468+
/// encrypt encrypts an already-signed COSE_Sign1 to a recipient.
469+
///
470+
/// For most use cases, prefer [`seal`] which signs and encrypts in one step.
471+
/// Use this only when re-encrypting a message (from [`decrypt`]) to a different
472+
/// recipient without access to the original signer's key.
473+
///
474+
/// - `sign1`: The COSE_Sign1 structure (e.g., from [`decrypt`])
475+
/// - `msg_to_auth`: The same additional authenticated data used during sealing
476+
/// - `recipient`: The xHPKE public key to encrypt to
477+
/// - `domain`: Application domain for HPKE key derivation
478+
///
479+
/// Returns the serialized COSE_Encrypt0 structure.
480+
pub fn encrypt<A: Encode>(
481+
sign1: &[u8],
482+
msg_to_auth: A,
483+
recipient: &xhpke::PublicKey,
484+
domain: &[u8],
485+
) -> Result<Vec<u8>, Error> {
486+
// Pre-encode for EncStructure (which needs raw bytes for external_aad)
487+
let msg_to_auth = cbor::encode(msg_to_auth);
464488

465489
// Build protected header with recipient's fingerprint
466490
let protected = cbor::encode(&EncProtectedHeader {
@@ -473,7 +497,7 @@ pub fn seal_at<E: Encode, A: Encode>(
473497
// Build and seal Enc_structure
474498
let (encap_key, ciphertext) = recipient
475499
.seal(
476-
&signed,
500+
sign1,
477501
&EncStructure {
478502
context: "Encrypt0",
479503
protected: &protected,
@@ -494,63 +518,6 @@ pub fn seal_at<E: Encode, A: Encode>(
494518
}))
495519
}
496520

497-
/// decrypt decrypts a sealed message without verifying the signature.
498-
///
499-
/// This allows inspecting the signer before verification. Use [`signer`] to
500-
/// extract the signer's fingerprint, then [`verify`] or [`verify_at`] to verify.
501-
///
502-
/// - `msg_to_open`: The serialized COSE_Encrypt0 structure
503-
/// - `msg_to_auth`: The same additional authenticated data used during sealing
504-
/// - `recipient`: The xHPKE secret key to decrypt with
505-
/// - `domain`: Application domain for HPKE key derivation
506-
///
507-
/// Returns the decrypted COSE_Sign1 structure (not yet verified).
508-
pub fn decrypt<A: Encode>(
509-
msg_to_open: &[u8],
510-
msg_to_auth: A,
511-
recipient: &xhpke::SecretKey,
512-
domain: &[u8],
513-
) -> Result<Vec<u8>, Error> {
514-
// Pre-encode for EncStructure (which needs raw bytes for external_aad)
515-
let msg_to_auth = cbor::encode(msg_to_auth);
516-
517-
// Restrict the user's domain to the context of this library
518-
let info = [DOMAIN_PREFIX, domain].concat();
519-
520-
// Parse COSE_Encrypt0
521-
let encrypt0: CoseEncrypt0 = cbor::decode(msg_to_open)?;
522-
523-
// Verify protected header
524-
verify_enc_protected_header(&encrypt0.protected, ALGORITHM_ID_XHPKE, recipient)?;
525-
526-
// Extract encapsulated key from the unprotected headers
527-
let encap_key: &[u8; xhpke::ENCAP_KEY_SIZE] = encrypt0
528-
.unprotected
529-
.encap_key
530-
.as_slice()
531-
.try_into()
532-
.map_err(|_| {
533-
Error::InvalidEncapKeySize(encrypt0.unprotected.encap_key.len(), xhpke::ENCAP_KEY_SIZE)
534-
})?;
535-
536-
// Rebuild and open Enc_structure
537-
let decrypted = recipient
538-
.open(
539-
encap_key,
540-
&encrypt0.ciphertext,
541-
&EncStructure {
542-
context: "Encrypt0",
543-
protected: &encrypt0.protected,
544-
external_aad: &msg_to_auth,
545-
}
546-
.encode_cbor(),
547-
&info,
548-
)
549-
.map_err(|e| Error::DecryptionFailed(e.to_string()))?;
550-
551-
Ok(decrypted)
552-
}
553-
554521
/// open decrypts and verifies a sealed message.
555522
///
556523
/// Uses the current system time for drift checking. For testing or custom
@@ -616,6 +583,63 @@ pub fn open_at<E: Decode, A: Encode + Clone>(
616583
Ok(cbor::decode(&raw.0)?)
617584
}
618585

586+
/// decrypt decrypts a sealed message without verifying the signature.
587+
///
588+
/// This allows inspecting the signer before verification. Use [`signer`] to
589+
/// extract the signer's fingerprint, then [`verify`] or [`verify_at`] to verify.
590+
///
591+
/// - `msg_to_open`: The serialized COSE_Encrypt0 structure
592+
/// - `msg_to_auth`: The same additional authenticated data used during sealing
593+
/// - `recipient`: The xHPKE secret key to decrypt with
594+
/// - `domain`: Application domain for HPKE key derivation
595+
///
596+
/// Returns the decrypted COSE_Sign1 structure (not yet verified).
597+
pub fn decrypt<A: Encode>(
598+
msg_to_open: &[u8],
599+
msg_to_auth: A,
600+
recipient: &xhpke::SecretKey,
601+
domain: &[u8],
602+
) -> Result<Vec<u8>, Error> {
603+
// Pre-encode for EncStructure (which needs raw bytes for external_aad)
604+
let msg_to_auth = cbor::encode(msg_to_auth);
605+
606+
// Restrict the user's domain to the context of this library
607+
let info = [DOMAIN_PREFIX, domain].concat();
608+
609+
// Parse COSE_Encrypt0
610+
let encrypt0: CoseEncrypt0 = cbor::decode(msg_to_open)?;
611+
612+
// Verify protected header
613+
verify_enc_protected_header(&encrypt0.protected, ALGORITHM_ID_XHPKE, recipient)?;
614+
615+
// Extract encapsulated key from the unprotected headers
616+
let encap_key: &[u8; xhpke::ENCAP_KEY_SIZE] = encrypt0
617+
.unprotected
618+
.encap_key
619+
.as_slice()
620+
.try_into()
621+
.map_err(|_| {
622+
Error::InvalidEncapKeySize(encrypt0.unprotected.encap_key.len(), xhpke::ENCAP_KEY_SIZE)
623+
})?;
624+
625+
// Rebuild and open Enc_structure
626+
let decrypted = recipient
627+
.open(
628+
encap_key,
629+
&encrypt0.ciphertext,
630+
&EncStructure {
631+
context: "Encrypt0",
632+
protected: &encrypt0.protected,
633+
external_aad: &msg_to_auth,
634+
}
635+
.encode_cbor(),
636+
&info,
637+
)
638+
.map_err(|e| Error::DecryptionFailed(e.to_string()))?;
639+
640+
Ok(decrypted)
641+
}
642+
619643
/// recipient extracts the recipient's fingerprint from a COSE_Encrypt0 message
620644
/// without decrypting it.
621645
///

0 commit comments

Comments
 (0)