1use crate::{Component, Error, FieldSection, Message, SecretKey, VerifyingKey, fields};
4use alloc::{
5 borrow::ToOwned,
6 format,
7 string::{String, ToString},
8 vec,
9 vec::Vec,
10};
11use base64::{Engine as _, engine::general_purpose::STANDARD};
12use http::{HeaderMap, HeaderValue};
13use sfv::{BareItem, FieldType, InnerList, Item, ListEntry};
14
15pub const REQUEST_SIGNATURE_TAG: &str = "asimov-remote-request-v1";
17
18pub fn key_id(key: &VerifyingKey) -> String {
20 format!("ed25519:{}", bs58::encode(key.as_bytes()).into_string())
21}
22
23#[derive(Clone, Debug)]
28pub struct SignatureParams {
29 pub created: i64,
31 pub expires: Option<i64>,
33 pub key_id: String,
35 pub nonce: Option<String>,
37 pub tag: Option<String>,
39}
40
41impl SignatureParams {
42 pub fn fresh(key: &VerifyingKey, tag: &str) -> Result<Self, Error> {
45 let created = Self::now()?;
46 let mut nonce = [0; 32];
47 getrandom::fill(&mut nonce).map_err(|error| Error::Random(error.to_string()))?;
48 Ok(Self {
49 created,
50 expires: Some(created.checked_add(60).ok_or(Error::Clock)?),
51 key_id: key_id(key),
52 nonce: Some(STANDARD.encode(nonce)),
53 tag: Some(tag.into()),
54 })
55 }
56
57 pub fn now() -> Result<i64, Error> {
59 std::time::SystemTime::now()
60 .duration_since(std::time::UNIX_EPOCH)
61 .map_err(|_| Error::Clock)?
62 .as_secs()
63 .try_into()
64 .map_err(|_| Error::Clock)
65 }
66}
67
68#[derive(Clone, Debug)]
75pub struct SignatureInput {
76 inner: InnerList,
77 components: Vec<Component>,
78}
79
80impl SignatureInput {
81 pub fn new(components: Vec<Component>, params: SignatureParams) -> Result<Self, Error> {
83 let mut inner = InnerList::new(components.iter().map(|c| c.0.clone()).collect());
84 let integer = |value| {
85 sfv::Integer::try_from(value)
86 .map(BareItem::Integer)
87 .map_err(Error::from)
88 };
89 inner
90 .params
91 .insert(sfv::key_ref("created").to_owned(), integer(params.created)?);
92 if let Some(expires) = params.expires {
93 inner
94 .params
95 .insert(sfv::key_ref("expires").to_owned(), integer(expires)?);
96 }
97 for (name, value) in [
98 ("nonce", params.nonce.as_deref()),
99 ("alg", Some("ed25519")),
100 ("keyid", Some(params.key_id.as_str())),
101 ("tag", params.tag.as_deref()),
102 ] {
103 if let Some(value) = value {
104 inner.params.insert(
105 sfv::key_ref(name).to_owned(),
106 sfv::StringRef::from_str(value)?.into(),
107 );
108 }
109 }
110 Self::from_inner(inner)
111 }
112
113 pub fn parse(value: &str) -> Result<Self, Error> {
115 let mut list: sfv::List = fields::parser(value)?.parse()?;
116 if list.len() != 1 {
117 return Err(Error::InvalidField("expected one signature inner list"));
118 }
119 match list.remove(0) {
120 ListEntry::InnerList(inner) => Self::from_inner(inner),
121 _ => Err(Error::InvalidField("signature input must be an inner list")),
122 }
123 }
124
125 fn from_inner(inner: InnerList) -> Result<Self, Error> {
126 if inner.items.len() > fields::MAX_COMPONENTS {
127 return Err(Error::Limit);
128 }
129 let mut components = Vec::new();
130 for item in &inner.items {
131 let component = Component::from_item(item.clone())?;
132 if components.contains(&component) {
133 return Err(Error::DuplicateComponent);
134 }
135 components.push(component);
136 }
137 for (name, value) in &inner.params {
138 match name.as_str() {
139 "created" | "expires" => {
140 if value.as_integer().is_none_or(|n| i64::from(n) < 0) {
141 return Err(Error::InvalidField("invalid signature timestamp"));
142 }
143 },
144 "alg" | "keyid" | "nonce" | "tag" if value.as_string().is_none() => {
145 return Err(Error::InvalidField("signature parameter must be a string"));
146 },
147 _ => (), }
149 }
150 let result = Self { inner, components };
151 if result.parameter("alg").is_some_and(|alg| alg != "ed25519") {
152 return Err(Error::Algorithm);
153 }
154 if result.to_field_value().len() > fields::MAX_FIELD_BYTES {
155 return Err(Error::Limit);
156 }
157 Ok(result)
158 }
159
160 pub fn from_message(message: &Message, label: &str) -> Result<Self, Error> {
163 match selected(message, "signature-input", label)? {
164 ListEntry::InnerList(inner) => Self::from_inner(inner),
165 _ => Err(Error::InvalidField("signature input must be an inner list")),
166 }
167 }
168
169 pub fn components(&self) -> &[Component] {
171 &self.components
172 }
173
174 pub fn parameter(&self, name: &str) -> Option<&str> {
176 self.inner
177 .params
178 .get(name)?
179 .as_string()
180 .map(|value| value.as_str())
181 }
182
183 pub fn integer_parameter(&self, name: &str) -> Option<i64> {
185 self.inner.params.get(name)?.as_integer().map(i64::from)
186 }
187
188 pub fn to_field_value(&self) -> String {
190 vec![ListEntry::InnerList(self.inner.clone())]
191 .serialize()
192 .expect("one inner list")
193 }
194
195 pub fn signature_base(&self, message: &Message) -> Result<Vec<u8>, Error> {
198 let mut base = String::new();
199 for component in &self.components {
200 let value = message.value(component)?;
201 base.push_str(&component.to_field_value());
202 base.push_str(": ");
203 base.push_str(&value);
204 base.push('\n');
205 if base.len() > fields::MAX_FIELD_BYTES {
206 return Err(Error::Limit);
207 }
208 }
209 base.push_str("\"@signature-params\": ");
210 base.push_str(&self.to_field_value());
211 if base.len() > fields::MAX_FIELD_BYTES {
212 return Err(Error::Limit);
213 }
214 Ok(base.into_bytes())
215 }
216
217 pub(crate) fn preflight(&self, message: &Message) -> Result<(), Error> {
219 for component in &self.components {
220 if !component.flag("tr") || component.flag("req") {
221 message.value(component)?;
222 }
223 }
224 Ok(())
225 }
226
227 pub(crate) fn covers_digest(&self, section: FieldSection) -> bool {
228 self.components.iter().any(|component| {
229 component.name() == "content-digest"
230 && !component.flag("req")
231 && component.flag("tr") == (section == FieldSection::Trailers)
232 && component
233 .string_param("key")
234 .is_none_or(|key| key == "sha-256")
235 })
236 }
237
238 pub fn verify(
242 &self,
243 message: &Message,
244 signature: &[u8],
245 key: &VerifyingKey,
246 ) -> Result<(), Error> {
247 let signature = iroh_base::Signature::try_from(signature).map_err(|_| Error::Signature)?;
248 key.verify(&self.signature_base(message)?, &signature)
249 .map_err(|_| Error::Signature)
250 }
251}
252
253#[derive(Clone, Debug)]
255pub struct SignatureFields {
256 pub signature_input: HeaderValue,
258 pub signature: HeaderValue,
260 label: String,
261}
262
263impl SignatureFields {
264 pub fn append_to(&self, fields: &mut HeaderMap) -> Result<(), Error> {
267 for name in ["signature-input", "signature"] {
268 if let Some(value) = fields::combined(&[fields], name)?
269 && fields::dictionary(&value)?.contains_key(self.label.as_str())
270 {
271 return Err(Error::DuplicateLabel);
272 }
273 }
274 fields.append("signature-input", self.signature_input.clone());
275 fields.append("signature", self.signature.clone());
276 Ok(())
277 }
278}
279
280#[derive(Clone, Debug)]
285pub struct Signer {
286 key: SecretKey,
287 label: String,
288 input: SignatureInput,
289}
290
291impl Signer {
292 pub fn new(key: SecretKey, label: &str, input: SignatureInput) -> Result<Self, Error> {
294 sfv::KeyRef::from_str(label)?;
295 Ok(Self {
296 key,
297 label: label.into(),
298 input,
299 })
300 }
301
302 pub fn input(&self) -> &SignatureInput {
304 &self.input
305 }
306
307 pub fn label(&self) -> &str {
309 &self.label
310 }
311
312 pub fn sign(&self, message: &Message) -> Result<SignatureFields, Error> {
314 let signature = self
315 .key
316 .sign(&self.input.signature_base(message)?)
317 .to_bytes();
318 Ok(SignatureFields {
319 signature_input: self.input_header()?,
320 signature: fields::header(format!(
321 "{}={}",
322 self.label,
323 Item::new(signature.as_slice()).serialize()
324 ))?,
325 label: self.label.clone(),
326 })
327 }
328
329 pub(crate) fn input_header(&self) -> Result<HeaderValue, Error> {
330 fields::header(format!("{}={}", self.label, self.input.to_field_value()))
331 }
332}
333
334#[derive(Clone, Debug)]
341pub struct VerificationPolicy {
342 pub required_components: Vec<Component>,
376 pub required_tag: Option<String>,
378 pub max_age: u64,
380 pub clock_skew: u64,
382 pub require_nonce: bool,
384}
385
386impl VerificationPolicy {
387 pub fn request() -> Self {
390 let components = [
391 "@method",
392 "@target-uri",
393 "authorization",
394 "accept",
395 "content-type",
396 "content-digest",
397 ]
398 .into_iter()
399 .map(|name| Component::new(name).expect("constant component identifier"))
400 .collect();
401 let mut policy = Self::new(components);
402 policy.required_tag = Some(REQUEST_SIGNATURE_TAG.into());
403 policy
404 }
405
406 pub fn new(required_components: Vec<Component>) -> Self {
409 Self {
410 required_components,
411 required_tag: None,
412 max_age: 60,
413 clock_skew: 30,
414 require_nonce: true,
415 }
416 }
417
418 fn check(&self, input: &SignatureInput, now: i64) -> Result<(), Error> {
419 let created = input
420 .integer_parameter("created")
421 .ok_or(Error::Policy("missing created"))? as i128;
422 let expires = input
423 .integer_parameter("expires")
424 .ok_or(Error::Policy("missing expires"))? as i128;
425 let now = i128::from(now);
426 let skew = i128::from(self.clock_skew);
427 let age = i128::from(self.max_age);
428 if now < 0
429 || created > now + skew
430 || created < now - age - skew
431 || expires <= created
432 || expires > created + age
433 || now >= expires + skew
434 {
435 return Err(Error::Policy("signature validity window"));
436 }
437 if self
438 .required_tag
439 .as_deref()
440 .is_some_and(|tag| input.parameter("tag") != Some(tag))
441 {
442 return Err(Error::Policy("signature tag"));
443 }
444 if self.require_nonce {
445 let nonce = input
446 .parameter("nonce")
447 .ok_or(Error::Policy("missing nonce"))?;
448 if STANDARD
449 .decode(nonce)
450 .map_or(true, |bytes| bytes.len() < 16)
451 {
452 return Err(Error::Policy("nonce must contain at least 128 bits"));
453 }
454 }
455 if !self
456 .required_components
457 .iter()
458 .all(|c| input.components.contains(c))
459 {
460 return Err(Error::Policy("required signature coverage"));
461 }
462 Ok(())
463 }
464}
465
466#[derive(Clone, Debug)]
469pub struct VerifiedSignature {
470 pub label: String,
472 pub input: SignatureInput,
474 pub key: VerifyingKey,
476}
477
478#[derive(Clone, Debug)]
480pub struct Verifier {
481 key: VerifyingKey,
482 key_id: String,
483 label: String,
484 policy: VerificationPolicy,
485}
486
487impl Verifier {
488 pub fn new(
490 key: VerifyingKey,
491 key_id: &str,
492 label: &str,
493 policy: VerificationPolicy,
494 ) -> Result<Self, Error> {
495 sfv::KeyRef::from_str(label)?;
496 sfv::StringRef::from_str(key_id)?;
497 Ok(Self {
498 key,
499 key_id: key_id.into(),
500 label: label.into(),
501 policy,
502 })
503 }
504
505 pub fn verify(&self, message: &Message, now: i64) -> Result<VerifiedSignature, Error> {
508 let input = SignatureInput::from_message(message, &self.label)?;
509 if input.parameter("keyid") != Some(self.key_id.as_str()) {
510 return Err(Error::Policy("key ID"));
511 }
512 self.policy.check(&input, now)?;
513 let ListEntry::Item(item) = selected(message, "signature", &self.label)? else {
514 return Err(Error::InvalidField("signature must be a byte sequence"));
515 };
516 let bytes = item
517 .bare_item
518 .as_byte_sequence()
519 .ok_or(Error::InvalidField("signature byte sequence"))?;
520 input.verify(message, bytes, &self.key)?;
521 Ok(VerifiedSignature {
522 label: self.label.clone(),
523 input,
524 key: self.key,
525 })
526 }
527}
528
529fn selected(message: &Message, name: &str, label: &str) -> Result<ListEntry, Error> {
530 let mut selected = None;
531 for section in [FieldSection::Headers, FieldSection::Trailers] {
532 if let Some(value) = fields::combined(&[message.fields(section)], name)? {
533 let mut dictionary = fields::dictionary(&value)?;
534 if dictionary.len() > fields::MAX_COMPONENTS {
535 return Err(Error::Limit);
536 }
537 for entry in dictionary.values() {
538 let valid = match (name, entry) {
539 ("signature-input", ListEntry::InnerList(_)) => true,
540 ("signature", ListEntry::Item(item)) => {
541 item.bare_item.as_byte_sequence().is_some()
542 },
543 _ => false,
544 };
545 if !valid {
546 return Err(Error::InvalidField("signature dictionary member type"));
547 }
548 }
549 if let Some(entry) = dictionary.shift_remove(label) {
550 if selected.is_some() {
551 return Err(Error::DuplicateLabel);
552 }
553 selected = Some(entry);
554 }
555 }
556 }
557 selected.ok_or_else(|| Error::MissingComponent(format!("{name};key={label}")))
558}