identity.rs (5239B)
1 //! Stable signing operation, artifact, and signer-request identities. 2 3 use core::fmt; 4 use radroots_event_codec::authoring::PlanDigest; 5 use sha2::{Digest, Sha256}; 6 7 use crate::{Error, error::Kind}; 8 9 const REQUEST_ID_DOMAIN: &[u8] = b"radroots.signer_request.v1"; 10 11 macro_rules! nonzero_id { 12 ($name:ident, $label:literal) => { 13 #[cfg_attr(feature = "serde", derive(serde::Serialize))] 14 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 15 pub struct $name([u8; 16]); 16 17 impl $name { 18 pub fn new(bytes: [u8; 16]) -> Result<Self, Error> { 19 if bytes == [0; 16] { 20 return Err(Error::new(Kind::InvalidArgument)); 21 } 22 Ok(Self(bytes)) 23 } 24 25 #[must_use] 26 pub const fn as_bytes(&self) -> &[u8; 16] { 27 &self.0 28 } 29 30 #[must_use] 31 pub fn to_hex(self) -> alloc_or_std::String { 32 hex::encode(self.0) 33 } 34 } 35 36 impl fmt::Debug for $name { 37 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 38 formatter.debug_tuple($label).field(&self.to_hex()).finish() 39 } 40 } 41 }; 42 } 43 44 #[cfg(not(feature = "std"))] 45 mod alloc_or_std { 46 pub use alloc::string::String; 47 } 48 #[cfg(feature = "std")] 49 mod alloc_or_std { 50 pub use std::string::String; 51 } 52 53 nonzero_id!(SigningOperationId, "SigningOperationId"); 54 nonzero_id!(AuthoredArtifactId, "AuthoredArtifactId"); 55 56 /// Stable parent/child identity for one authored artifact signing operation. 57 #[cfg_attr(feature = "serde", derive(serde::Serialize))] 58 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 59 pub struct SigningIntentId { 60 operation_id: SigningOperationId, 61 artifact_id: AuthoredArtifactId, 62 } 63 64 impl SigningIntentId { 65 #[must_use] 66 pub const fn new(operation_id: SigningOperationId, artifact_id: AuthoredArtifactId) -> Self { 67 Self { 68 operation_id, 69 artifact_id, 70 } 71 } 72 73 #[must_use] 74 pub const fn operation_id(self) -> SigningOperationId { 75 self.operation_id 76 } 77 78 #[must_use] 79 pub const fn artifact_id(self) -> AuthoredArtifactId { 80 self.artifact_id 81 } 82 } 83 84 /// Deterministic identity a replay-capable remote signer must deduplicate. 85 #[cfg_attr(feature = "serde", derive(serde::Serialize))] 86 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 87 pub struct SignerRequestId([u8; 32]); 88 89 impl SignerRequestId { 90 #[must_use] 91 pub fn derive(artifact_id: AuthoredArtifactId, plan_digest: PlanDigest) -> Self { 92 let mut digest = Sha256::new(); 93 digest.update(REQUEST_ID_DOMAIN); 94 digest.update(artifact_id.as_bytes()); 95 digest.update(plan_digest.as_bytes()); 96 Self(digest.finalize().into()) 97 } 98 99 #[must_use] 100 pub const fn as_bytes(&self) -> &[u8; 32] { 101 &self.0 102 } 103 104 #[must_use] 105 pub fn to_hex(self) -> alloc_or_std::String { 106 hex::encode(self.0) 107 } 108 } 109 110 impl fmt::Debug for SignerRequestId { 111 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 112 formatter 113 .debug_tuple("SignerRequestId") 114 .field(&self.to_hex()) 115 .finish() 116 } 117 } 118 119 #[cfg(test)] 120 mod tests { 121 use super::*; 122 #[cfg(not(feature = "std"))] 123 use alloc::format; 124 125 #[test] 126 fn stable_identities_reject_zero_and_expose_exact_bytes() { 127 assert_eq!( 128 SigningOperationId::new([0; 16]) 129 .expect_err("zero operation ID must fail") 130 .kind(), 131 Kind::InvalidArgument 132 ); 133 assert_eq!( 134 AuthoredArtifactId::new([0; 16]) 135 .expect_err("zero artifact ID must fail") 136 .kind(), 137 Kind::InvalidArgument 138 ); 139 140 let operation = SigningOperationId::new([1; 16]).expect("operation ID"); 141 let artifact = AuthoredArtifactId::new([2; 16]).expect("artifact ID"); 142 assert_eq!(operation.as_bytes(), &[1; 16]); 143 assert_eq!(artifact.as_bytes(), &[2; 16]); 144 assert_eq!(operation.to_hex(), "01".repeat(16)); 145 assert_eq!(artifact.to_hex(), "02".repeat(16)); 146 assert_eq!( 147 format!("{operation:?}"), 148 format!("SigningOperationId(\"{}\")", "01".repeat(16)) 149 ); 150 assert_eq!( 151 format!("{artifact:?}"), 152 format!("AuthoredArtifactId(\"{}\")", "02".repeat(16)) 153 ); 154 155 let intent = SigningIntentId::new(operation, artifact); 156 assert_eq!(intent.operation_id(), operation); 157 assert_eq!(intent.artifact_id(), artifact); 158 } 159 160 #[test] 161 fn signer_request_identity_is_deterministic_and_domain_separated() { 162 let artifact = AuthoredArtifactId::new([3; 16]).expect("artifact ID"); 163 let digest = PlanDigest::from_bytes([4; 32]); 164 let request = SignerRequestId::derive(artifact, digest); 165 assert_eq!(request.as_bytes().len(), 32); 166 assert_eq!(request.to_hex().len(), 64); 167 assert_eq!(request, SignerRequestId::derive(artifact, digest)); 168 assert_eq!( 169 format!("{request:?}"), 170 format!("SignerRequestId(\"{}\")", request.to_hex()) 171 ); 172 } 173 }