1use std::fmt::Write as _;
7
8use base64::Engine as _;
9use base64::engine::general_purpose::URL_SAFE_NO_PAD;
10use bytes::Bytes;
11use p256::ecdsa::signature::{Signer as _, Verifier as _};
12use p256::ecdsa::{Signature, SigningKey, VerifyingKey};
13use p256::pkcs8::{DecodePrivateKey as _, DecodePublicKey as _};
14use serde_json::Value;
15use thiserror::Error;
16
17use crate::error::HeaderError;
18use crate::headers::grammar::{is_token_char, trim};
19use crate::headers::{Date, From, To};
20use crate::{HeaderName, Request, Scheme, TypedHeader, Uri};
21
22const IDENTITY: &str = "Identity";
23const ES256: &str = "ES256";
24
25#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct IdentityHeader {
28 pub digest: String,
30 pub info: String,
32 pub algorithm: String,
34 pub passport_type: Option<String>,
36 pub extensions: Vec<(String, Option<String>)>,
38}
39
40impl IdentityHeader {
41 #[must_use]
43 pub fn to_bytes(&self) -> Bytes {
44 let mut out = format!("{};info=<{}>", self.digest, self.info);
45 if self.algorithm != ES256 {
46 let _ = write!(out, ";alg={}", self.algorithm);
47 }
48 if let Some(passport_type) = &self.passport_type {
49 let _ = write!(out, ";ppt={passport_type}");
50 }
51 for (name, value) in &self.extensions {
52 out.push(';');
53 out.push_str(name);
54 if let Some(value) = value {
55 out.push('=');
56 out.push_str(value);
57 }
58 }
59 Bytes::from(out)
60 }
61}
62
63impl TypedHeader for IdentityHeader {
64 const NAME: HeaderName = HeaderName::Identity;
65
66 fn decode(value: &[u8]) -> Result<Self, HeaderError> {
67 parse_identity(value).ok_or(HeaderError::Syntax { header: IDENTITY })
68 }
69}
70
71fn parse_identity(value: &[u8]) -> Option<IdentityHeader> {
72 let text = std::str::from_utf8(trim(value)).ok()?;
73 let parts = split_identity_parts(text)?;
74 let digest = parts.first()?.trim();
75 if !valid_digest(digest) {
76 return None;
77 }
78
79 let mut info = None;
80 let mut algorithm = None;
81 let mut passport_type = None;
82 let mut extensions = Vec::new();
83 for (index, raw) in parts.iter().skip(1).enumerate() {
84 let raw = raw.trim();
85 let (name, value) = raw
86 .split_once('=')
87 .map_or((raw, None), |(n, v)| (n, Some(v)));
88 if name.is_empty() || !name.as_bytes().iter().all(|&b| is_token_char(b)) {
89 return None;
90 }
91 match name.to_ascii_lowercase().as_str() {
92 "info" => {
93 if info.is_some() {
94 return None;
95 }
96 let value = value?;
97 let uri = value.strip_prefix('<')?.strip_suffix('>')?;
98 Uri::parse(Bytes::copy_from_slice(uri.as_bytes())).ok()?;
99 info = Some(uri.to_owned());
100 }
101 "alg" => {
102 if index == 0 {
103 return None;
104 }
105 if algorithm.is_some() {
106 return None;
107 }
108 let value = value?;
109 if value.is_empty() || !value.as_bytes().iter().all(|&b| is_token_char(b)) {
110 return None;
111 }
112 algorithm = Some(value.to_owned());
113 }
114 "ppt" => {
115 if index == 0 {
116 return None;
117 }
118 if passport_type.is_some() {
119 return None;
120 }
121 let value = value?;
122 if value.is_empty() || !value.as_bytes().iter().all(|&b| is_token_char(b)) {
123 return None;
124 }
125 passport_type = Some(value.to_owned());
126 }
127 _ => {
128 if index == 0 {
129 return None;
130 }
131 extensions.push((name.to_owned(), value.map(str::to_owned)));
132 }
133 }
134 }
135 Some(IdentityHeader {
136 digest: digest.to_owned(),
137 info: info?,
138 algorithm: algorithm.unwrap_or_else(|| ES256.to_owned()),
139 passport_type,
140 extensions,
141 })
142}
143
144fn split_identity_parts(value: &str) -> Option<Vec<&str>> {
145 let mut parts = Vec::new();
146 let mut start = 0usize;
147 let mut in_angle = false;
148 for (at, byte) in value.bytes().enumerate() {
149 match byte {
150 b'<' if !in_angle => in_angle = true,
151 b'>' if in_angle => in_angle = false,
152 b';' if !in_angle => {
153 parts.push(value.get(start..at)?);
154 start = at.saturating_add(1);
155 }
156 _ => {}
157 }
158 }
159 if in_angle {
160 return None;
161 }
162 parts.push(value.get(start..)?);
163 Some(parts)
164}
165
166fn valid_digest(value: &str) -> bool {
167 let segments: Vec<_> = value.split('.').collect();
168 if segments.len() != 3 {
169 return false;
170 }
171 let full = segments.first().is_some_and(|part| !part.is_empty())
172 && segments.get(1).is_some_and(|part| !part.is_empty());
173 let compact = segments.first().is_some_and(|part| part.is_empty())
174 && segments.get(1).is_some_and(|part| part.is_empty());
175 (full || compact)
176 && segments.get(2).is_some_and(|part| !part.is_empty())
177 && value
178 .bytes()
179 .all(|b| b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_' | b'+' | b'/' | b'.'))
180}
181
182#[derive(Debug, Clone, PartialEq, Eq, Hash)]
184pub enum CanonicalIdentity {
185 TelephoneNumber(String),
187 Uri(String),
189}
190
191impl CanonicalIdentity {
192 pub fn from_uri(uri: &Uri) -> Result<Self, IdentityError> {
194 match uri.scheme() {
195 Scheme::Tel => canonical_number(uri.opaque().unwrap_or_default()),
196 Scheme::Sip | Scheme::Sips => {
197 let telephone = uri
198 .params()
199 .and_then(|params| params.value("user"))
200 .is_some_and(|value| value.eq_ignore_ascii_case(b"phone"));
201 if telephone {
202 return canonical_number(&uri.decoded_user().ok_or(IdentityError::Identity)?);
203 }
204 let user = uri.user().ok_or(IdentityError::Identity)?;
205 let user = normalized_user(user)?;
206 let host = uri.host().ok_or(IdentityError::Identity)?.to_string();
207 let scheme = if matches!(uri.scheme(), Scheme::Sips) {
208 "sips"
209 } else {
210 "sip"
211 };
212 Ok(Self::Uri(format!(
213 "{scheme}:{}@{}",
214 user.to_ascii_lowercase(),
215 host.to_ascii_lowercase()
216 )))
217 }
218 Scheme::Other(_) => Err(IdentityError::Identity),
219 }
220 }
221
222 fn kind(&self) -> &'static str {
223 match self {
224 Self::TelephoneNumber(_) => "tn",
225 Self::Uri(_) => "uri",
226 }
227 }
228
229 fn value(&self) -> &str {
230 match self {
231 Self::TelephoneNumber(value) | Self::Uri(value) => value,
232 }
233 }
234}
235
236fn canonical_number(raw: &[u8]) -> Result<CanonicalIdentity, IdentityError> {
237 let before_params = raw.split(|&b| b == b';').next().unwrap_or_default();
238 let number: String = before_params
239 .iter()
240 .copied()
241 .filter(|b| b.is_ascii_digit() || matches!(b, b'*' | b'#'))
242 .map(char::from)
243 .collect();
244 if number.is_empty() {
245 Err(IdentityError::Identity)
246 } else {
247 Ok(CanonicalIdentity::TelephoneNumber(number))
248 }
249}
250
251fn normalized_user(raw: &[u8]) -> Result<String, IdentityError> {
252 let mut out = String::new();
253 let mut at = 0usize;
254 while let Some(&byte) = raw.get(at) {
255 if byte == b'%' {
256 let hi = raw.get(at.saturating_add(1)).copied().and_then(hex_value);
257 let lo = raw.get(at.saturating_add(2)).copied().and_then(hex_value);
258 let decoded = hi
259 .zip(lo)
260 .map(|(hi, lo)| hi * 16 + lo)
261 .ok_or(IdentityError::Identity)?;
262 if decoded.is_ascii_alphanumeric() || matches!(decoded, b'-' | b'.' | b'_' | b'~') {
263 out.push(char::from(decoded));
264 } else {
265 let _ = write!(out, "%{decoded:02X}");
266 }
267 at = at.saturating_add(3);
268 } else {
269 if !byte.is_ascii() {
270 return Err(IdentityError::Identity);
271 }
272 out.push(char::from(byte));
273 at = at.saturating_add(1);
274 }
275 }
276 if out.is_empty() {
277 Err(IdentityError::Identity)
278 } else {
279 Ok(out)
280 }
281}
282
283fn hex_value(byte: u8) -> Option<u8> {
284 match byte {
285 b'0'..=b'9' => Some(byte - b'0'),
286 b'a'..=b'f' => Some(byte - b'a' + 10),
287 b'A'..=b'F' => Some(byte - b'A' + 10),
288 _ => None,
289 }
290}
291
292pub struct Es256SigningKey(SigningKey);
294
295impl std::fmt::Debug for Es256SigningKey {
296 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297 formatter
298 .debug_struct("Es256SigningKey")
299 .finish_non_exhaustive()
300 }
301}
302
303impl Es256SigningKey {
304 pub fn from_pkcs8_pem(pem: &str) -> Result<Self, IdentityError> {
306 SigningKey::from_pkcs8_pem(pem)
307 .map(Self)
308 .map_err(|_| IdentityError::Credential)
309 }
310
311 #[must_use]
313 pub fn verifying_key(&self) -> Es256VerifyingKey {
314 Es256VerifyingKey(*self.0.verifying_key())
315 }
316}
317
318#[derive(Clone)]
320pub struct Es256VerifyingKey(VerifyingKey);
321
322impl std::fmt::Debug for Es256VerifyingKey {
323 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
324 formatter
325 .debug_struct("Es256VerifyingKey")
326 .finish_non_exhaustive()
327 }
328}
329
330impl Es256VerifyingKey {
331 pub fn from_public_key_pem(pem: &str) -> Result<Self, IdentityError> {
333 VerifyingKey::from_public_key_pem(pem)
334 .map(Self)
335 .map_err(|_| IdentityError::Credential)
336 }
337}
338
339#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
341#[non_exhaustive]
342pub enum IdentityError {
343 #[error("invalid SIP identity")]
345 Identity,
346 #[error("invalid PASSporT")]
348 Passport,
349 #[error("unsupported PASSporT algorithm")]
351 Algorithm,
352 #[error("unsupported PASSporT type")]
354 PassportType,
355 #[error("unsupported ES256 credential")]
357 Credential,
358 #[error("invalid PASSporT signature")]
360 Signature,
361 #[error("invalid SIP Date")]
363 Date,
364}
365
366pub fn request_identities(
368 request: &Request,
369) -> Result<(CanonicalIdentity, CanonicalIdentity), IdentityError> {
370 let from = request
371 .headers
372 .typed::<From>()
373 .ok_or(IdentityError::Identity)?
374 .map_err(|_| IdentityError::Identity)?;
375 let to = request
376 .headers
377 .typed::<To>()
378 .ok_or(IdentityError::Identity)?
379 .map_err(|_| IdentityError::Identity)?;
380 Ok((
381 CanonicalIdentity::from_uri(&from.uri)?,
382 CanonicalIdentity::from_uri(&to.uri)?,
383 ))
384}
385
386pub fn sign_passport(
388 key: &Es256SigningKey,
389 origin: &CanonicalIdentity,
390 destination: &CanonicalIdentity,
391 issued_at: i64,
392 info: &str,
393) -> IdentityHeader {
394 let protected = protected_json(info);
395 let payload = payload_json(origin, destination, issued_at);
396 let protected = URL_SAFE_NO_PAD.encode(protected.as_bytes());
397 let payload = URL_SAFE_NO_PAD.encode(payload.as_bytes());
398 let signing_input = format!("{protected}.{payload}");
399 let signature: Signature = key.0.sign(signing_input.as_bytes());
400 let signature = URL_SAFE_NO_PAD.encode(signature.to_bytes());
401 IdentityHeader {
402 digest: format!("{signing_input}.{signature}"),
403 info: info.to_owned(),
404 algorithm: ES256.to_owned(),
405 passport_type: None,
406 extensions: Vec::new(),
407 }
408}
409
410pub fn verify_passport(
412 header: &IdentityHeader,
413 key: &Es256VerifyingKey,
414 origin: &CanonicalIdentity,
415 destination: &CanonicalIdentity,
416 issued_at: i64,
417) -> Result<(), IdentityError> {
418 if header.passport_type.is_some() {
419 return Err(IdentityError::PassportType);
420 }
421 if header.algorithm != ES256 {
422 return Err(IdentityError::Algorithm);
423 }
424 let mut parts = header.digest.split('.');
425 let encoded_header = parts.next().ok_or(IdentityError::Passport)?;
426 let encoded_payload = parts.next().ok_or(IdentityError::Passport)?;
427 let encoded_signature = parts.next().ok_or(IdentityError::Passport)?;
428 if parts.next().is_some() || encoded_signature.is_empty() {
429 return Err(IdentityError::Passport);
430 }
431
432 let expected_header = protected_json(&header.info);
433 let expected_payload = payload_json(origin, destination, issued_at);
434 let (protected, payload) = if encoded_header.is_empty() && encoded_payload.is_empty() {
435 (
436 URL_SAFE_NO_PAD.encode(expected_header.as_bytes()),
437 URL_SAFE_NO_PAD.encode(expected_payload.as_bytes()),
438 )
439 } else if !encoded_header.is_empty() && !encoded_payload.is_empty() {
440 validate_full_json(
441 encoded_header,
442 encoded_payload,
443 &expected_header,
444 &expected_payload,
445 )?;
446 (encoded_header.to_owned(), encoded_payload.to_owned())
447 } else {
448 return Err(IdentityError::Passport);
449 };
450
451 let signature = URL_SAFE_NO_PAD
452 .decode(encoded_signature)
453 .map_err(|_| IdentityError::Passport)?;
454 let signature = Signature::from_slice(&signature).map_err(|_| IdentityError::Passport)?;
455 let signing_input = format!("{protected}.{payload}");
456 verify_es256(&key.0, signing_input.as_bytes(), &signature)
457}
458
459pub fn passport_issued_at(header: &IdentityHeader) -> Result<Option<i64>, IdentityError> {
461 let mut parts = header.digest.split('.');
462 let encoded_header = parts.next().ok_or(IdentityError::Passport)?;
463 let encoded_payload = parts.next().ok_or(IdentityError::Passport)?;
464 let signature = parts.next().ok_or(IdentityError::Passport)?;
465 if parts.next().is_some() || signature.is_empty() {
466 return Err(IdentityError::Passport);
467 }
468 if encoded_header.is_empty() && encoded_payload.is_empty() {
469 return Ok(None);
470 }
471 if encoded_header.is_empty() || encoded_payload.is_empty() {
472 return Err(IdentityError::Passport);
473 }
474 let payload = URL_SAFE_NO_PAD
475 .decode(encoded_payload)
476 .map_err(|_| IdentityError::Passport)?;
477 let value: Value = serde_json::from_slice(&payload).map_err(|_| IdentityError::Passport)?;
478 value
479 .as_object()
480 .and_then(|object| object.get("iat"))
481 .and_then(Value::as_i64)
482 .map(Some)
483 .ok_or(IdentityError::Passport)
484}
485
486fn verify_es256(
487 key: &VerifyingKey,
488 signing_input: &[u8],
489 signature: &Signature,
490) -> Result<(), IdentityError> {
491 if key.verify(signing_input, signature).is_ok() {
492 return Ok(());
493 }
494 signature
498 .normalize_s()
499 .ok_or(IdentityError::Signature)
500 .and_then(|normalized| {
501 key.verify(signing_input, &normalized)
502 .map_err(|_| IdentityError::Signature)
503 })
504}
505
506fn validate_full_json(
507 encoded_header: &str,
508 encoded_payload: &str,
509 expected_header: &str,
510 expected_payload: &str,
511) -> Result<(), IdentityError> {
512 let header = URL_SAFE_NO_PAD
513 .decode(encoded_header)
514 .map_err(|_| IdentityError::Passport)?;
515 let payload = URL_SAFE_NO_PAD
516 .decode(encoded_payload)
517 .map_err(|_| IdentityError::Passport)?;
518 let header_value: Value =
519 serde_json::from_slice(&header).map_err(|_| IdentityError::Passport)?;
520 let payload_value: Value =
521 serde_json::from_slice(&payload).map_err(|_| IdentityError::Passport)?;
522 let expected_header_value: Value =
523 serde_json::from_str(expected_header).map_err(|_| IdentityError::Passport)?;
524 let expected_payload_value: Value =
525 serde_json::from_str(expected_payload).map_err(|_| IdentityError::Passport)?;
526 if header_value != expected_header_value || payload_value != expected_payload_value {
527 return Err(IdentityError::Passport);
528 }
529 if header.as_slice() != expected_header.as_bytes()
531 || payload.as_slice() != expected_payload.as_bytes()
532 {
533 return Err(IdentityError::Passport);
534 }
535 Ok(())
536}
537
538fn protected_json(info: &str) -> String {
539 let info = serde_json::to_string(info).unwrap_or_else(|_| "\"\"".to_owned());
540 format!(r#"{{"alg":"ES256","typ":"passport","x5u":{info}}}"#)
541}
542
543fn payload_json(
544 origin: &CanonicalIdentity,
545 destination: &CanonicalIdentity,
546 issued_at: i64,
547) -> String {
548 let origin_value = serde_json::to_string(origin.value()).unwrap_or_else(|_| "\"\"".to_owned());
549 let destination_value =
550 serde_json::to_string(destination.value()).unwrap_or_else(|_| "\"\"".to_owned());
551 format!(
552 r#"{{"dest":{{"{}":[{}]}},"iat":{},"orig":{{"{}":{}}}}}"#,
553 destination.kind(),
554 destination_value,
555 issued_at,
556 origin.kind(),
557 origin_value
558 )
559}
560
561pub fn date_timestamp(date: &Date) -> Result<i64, IdentityError> {
563 let value = date.0.as_slice();
564 if value.len() != 29 {
565 return Err(IdentityError::Date);
566 }
567 let number = |from: usize, to: usize| -> Result<i64, IdentityError> {
568 let bytes = value.get(from..to).ok_or(IdentityError::Date)?;
569 let text = std::str::from_utf8(bytes).map_err(|_| IdentityError::Date)?;
570 text.parse().map_err(|_| IdentityError::Date)
571 };
572 let year = number(12, 16)?;
573 let month = month_number(value.get(8..11).ok_or(IdentityError::Date)?)?;
574 let day = number(5, 7)?;
575 let hour = number(17, 19)?;
576 let minute = number(20, 22)?;
577 let second = number(23, 25)?;
578 if !(1..=9999).contains(&year)
579 || !(1..=days_in_month(year, month)).contains(&day)
580 || hour > 23
581 || minute > 59
582 || second > 59
583 {
584 return Err(IdentityError::Date);
585 }
586 let days =
587 days_before_year(year) + days_before_month(year, month) + day - 1 - days_before_year(1970);
588 let weekday = weekday_name(days);
589 if value.get(0..3) != Some(weekday.as_bytes()) {
590 return Err(IdentityError::Date);
591 }
592 Ok(days * 86_400 + hour * 3_600 + minute * 60 + second)
593}
594
595pub fn date_from_timestamp(timestamp: i64) -> Result<Date, IdentityError> {
597 const MIN: i64 = -62_135_596_800; const MAX: i64 = 253_402_300_799; if !(MIN..=MAX).contains(×tamp) {
600 return Err(IdentityError::Date);
601 }
602 let days = timestamp.div_euclid(86_400);
603 let seconds = timestamp.rem_euclid(86_400);
604 let mut year = 1970i64;
605 let mut day_of_year = days;
606 if day_of_year >= 0 {
607 while day_of_year >= days_in_year(year) {
608 day_of_year -= days_in_year(year);
609 year += 1;
610 }
611 } else {
612 while day_of_year < 0 {
613 year -= 1;
614 day_of_year += days_in_year(year);
615 }
616 }
617 let mut month = 1i64;
618 while day_of_year >= days_in_month(year, month) {
619 day_of_year -= days_in_month(year, month);
620 month += 1;
621 }
622 let day = day_of_year + 1;
623 let hour = seconds / 3_600;
624 let minute = (seconds % 3_600) / 60;
625 let second = seconds % 60;
626 let value = format!(
627 "{}, {day:02} {} {year:04} {hour:02}:{minute:02}:{second:02} GMT",
628 weekday_name(days),
629 month_name(month)?
630 );
631 Ok(Date(value.into_bytes()))
632}
633
634fn month_number(month: &[u8]) -> Result<i64, IdentityError> {
635 const MONTHS: [&[u8]; 12] = [
636 b"Jan", b"Feb", b"Mar", b"Apr", b"May", b"Jun", b"Jul", b"Aug", b"Sep", b"Oct", b"Nov",
637 b"Dec",
638 ];
639 MONTHS
640 .iter()
641 .position(|candidate| *candidate == month)
642 .and_then(|index| i64::try_from(index).ok())
643 .map(|index| index + 1)
644 .ok_or(IdentityError::Date)
645}
646
647fn month_name(month: i64) -> Result<&'static str, IdentityError> {
648 const MONTHS: [&str; 12] = [
649 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
650 ];
651 usize::try_from(month - 1)
652 .ok()
653 .and_then(|index| MONTHS.get(index).copied())
654 .ok_or(IdentityError::Date)
655}
656
657fn weekday_name(days_since_epoch: i64) -> &'static str {
658 const DAYS: [&str; 7] = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
659 usize::try_from((days_since_epoch + 3).rem_euclid(7))
660 .ok()
661 .and_then(|index| DAYS.get(index).copied())
662 .unwrap_or("Thu")
663}
664
665fn leap(year: i64) -> bool {
666 year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
667}
668
669fn days_in_year(year: i64) -> i64 {
670 if leap(year) { 366 } else { 365 }
671}
672
673fn days_before_year(year: i64) -> i64 {
674 let previous = year - 1;
675 previous * 365 + previous / 4 - previous / 100 + previous / 400
676}
677
678fn days_before_month(year: i64, month: i64) -> i64 {
679 (1..month)
680 .map(|candidate| days_in_month(year, candidate))
681 .sum()
682}
683
684fn days_in_month(year: i64, month: i64) -> i64 {
685 match month {
686 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
687 4 | 6 | 9 | 11 => 30,
688 2 if leap(year) => 29,
689 2 => 28,
690 _ => 0,
691 }
692}
693
694#[cfg(test)]
695#[allow(
696 clippy::unwrap_used,
697 clippy::expect_used,
698 clippy::panic,
699 clippy::indexing_slicing
700)]
701mod tests {
702 use super::*;
703
704 const PRIVATE: &str = "-----BEGIN PRIVATE KEY-----\n\
705MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgi7q2TZvN9VDFg8Vy\n\
706qCP06bETrR2v8MRvr89rn4i+UAahRANCAAQWfaj1HUETpoNCrOtp9KA8o0V79IuW\n\
707ARKt9C1cFPkyd3FBP4SeiNZxQhDrD0tdBHls3/wFe8++K2FrPyQF9vuh\n\
708-----END PRIVATE KEY-----";
709 const PUBLIC: &str = "-----BEGIN PUBLIC KEY-----\n\
710MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8HNbQd/TmvCKwPKHkMF9fScavGeH\n\
71178YTU8qLS8I5HLHSSmlATLcslQMhNC/OhlWBYC626nIlo7XeebYS7Sb37g==\n\
712-----END PUBLIC KEY-----";
713 const RFC_TOKEN: &str = concat!(
714 "eyJhbGciOiJFUzI1NiIsInR5cCI6InBhc3Nwb3J0IiwieDV1IjoiaHR0cHM6Ly9j",
715 "ZXJ0LmV4YW1wbGUub3JnL3Bhc3Nwb3J0LmNlciJ9.",
716 "eyJkZXN0Ijp7InVyaSI6WyJzaXA6YWxpY2VAZXhhbXBsZS5jb20iXX0sImlhdCI",
717 "6MTQ3MTM3NTQxOCwib3JpZyI6eyJ0biI6IjEyMTU1NTUxMjEyIn19.",
718 "VLBCIVDCaeK6M4hLJb6SHQvacAQVvoiiEOWQ_iUkqk79UD81fHQ0E1b3_GluIkb",
719 "a7UWYRM47ZbNFdOJquE35cw"
720 );
721
722 #[test]
723 fn rfc8225_appendix_a_is_the_signing_oracle() {
724 let key = Es256SigningKey::from_pkcs8_pem(PRIVATE).unwrap();
725 let origin = CanonicalIdentity::TelephoneNumber("12155551212".to_owned());
726 let destination = CanonicalIdentity::Uri("sip:alice@example.com".to_owned());
727 let identity = sign_passport(
728 &key,
729 &origin,
730 &destination,
731 1_471_375_418,
732 "https://cert.example.org/passport.cer",
733 );
734 let repeated = sign_passport(
735 &key,
736 &origin,
737 &destination,
738 1_471_375_418,
739 "https://cert.example.org/passport.cer",
740 );
741 assert_eq!(identity.digest, repeated.digest);
742 let expected_signing_input = RFC_TOKEN.rsplit_once('.').unwrap().0;
743 assert_eq!(
744 identity.digest.rsplit_once('.').unwrap().0,
745 expected_signing_input
746 );
747 verify_passport(
748 &identity,
749 &key.verifying_key(),
750 &origin,
751 &destination,
752 1_471_375_418,
753 )
754 .unwrap();
755 }
756
757 #[test]
758 fn a_valid_signature_over_differently_ordered_json_is_rejected() {
759 let key = Es256SigningKey::from_pkcs8_pem(PRIVATE).unwrap();
760 let origin = CanonicalIdentity::TelephoneNumber("12155551212".to_owned());
761 let destination = CanonicalIdentity::Uri("sip:alice@example.com".to_owned());
762 let mut identity = sign_passport(
763 &key,
764 &origin,
765 &destination,
766 1_471_375_418,
767 "https://cert.example.org/passport.cer",
768 );
769 let mut parts = identity.digest.split('.');
770 let _original_header = parts.next().unwrap();
771 let payload = parts.next().unwrap();
772 let _original_signature = parts.next().unwrap();
773 let reordered_header = URL_SAFE_NO_PAD.encode(
774 br#"{"typ":"passport","alg":"ES256","x5u":"https://cert.example.org/passport.cer"}"#,
775 );
776 let signing_input = format!("{reordered_header}.{payload}");
777 let signature: Signature = key.0.sign(signing_input.as_bytes());
778 identity.digest = format!(
779 "{signing_input}.{}",
780 URL_SAFE_NO_PAD.encode(signature.to_bytes())
781 );
782 assert_eq!(
783 verify_passport(
784 &identity,
785 &key.verifying_key(),
786 &origin,
787 &destination,
788 1_471_375_418,
789 ),
790 Err(IdentityError::Passport)
791 );
792 }
793
794 #[test]
795 fn rfc7515_appendix_a3_is_the_es256_verification_oracle() {
796 let x = URL_SAFE_NO_PAD
797 .decode("f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU")
798 .unwrap();
799 let y = URL_SAFE_NO_PAD
800 .decode("x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0")
801 .unwrap();
802 let mut point = Vec::with_capacity(65);
803 point.push(4);
804 point.extend_from_slice(&x);
805 point.extend_from_slice(&y);
806 let key = VerifyingKey::from_sec1_bytes(&point).unwrap();
807 let signature = URL_SAFE_NO_PAD
808 .decode(concat!(
809 "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSA",
810 "pmWQxfKTUJqPP3-Kg6NU1Q"
811 ))
812 .unwrap();
813 let signature = Signature::from_slice(&signature).unwrap();
814 let input = concat!(
815 "eyJhbGciOiJFUzI1NiJ9.",
816 "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt",
817 "cGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
818 );
819 verify_es256(&key, input.as_bytes(), &signature).unwrap();
820 }
821
822 #[test]
823 fn identity_defaults_an_absent_algorithm_to_es256() {
824 let parsed = IdentityHeader::decode(
825 format!("{RFC_TOKEN};info=<https://cert.example.org/passport.cer>").as_bytes(),
826 )
827 .unwrap();
828 assert_eq!(parsed.algorithm, "ES256");
829 }
830
831 #[test]
832 fn unknown_ppt_is_retained_for_mandatory_refusal() {
833 let parsed = IdentityHeader::decode(
834 format!("{RFC_TOKEN};info=<https://cert.example.org/c>;ppt=unknown").as_bytes(),
835 )
836 .unwrap();
837 assert_eq!(parsed.passport_type.as_deref(), Some("unknown"));
838 }
839
840 #[test]
841 fn canonicalization_distinguishes_phone_from_uri() {
842 let phone = Uri::parse(Bytes::from_static(
843 b"sip:+1-(215)-555-1212@example.com;user=phone",
844 ))
845 .unwrap();
846 assert_eq!(
847 CanonicalIdentity::from_uri(&phone).unwrap(),
848 CanonicalIdentity::TelephoneNumber("12155551212".to_owned())
849 );
850 let uri = Uri::parse(Bytes::from_static(
851 b"SIP:Al%69ce:secret@EXAMPLE.com:5060;transport=tcp?Subject=x",
852 ))
853 .unwrap();
854 assert_eq!(
855 CanonicalIdentity::from_uri(&uri).unwrap(),
856 CanonicalIdentity::Uri("sip:alice@example.com".to_owned())
857 );
858 }
859
860 #[test]
861 fn sip_dates_round_trip_and_reject_a_false_weekday() {
862 let date = date_from_timestamp(1_471_375_418).unwrap();
863 assert_eq!(date.0, b"Tue, 16 Aug 2016 19:23:38 GMT");
864 assert_eq!(date_timestamp(&date).unwrap(), 1_471_375_418);
865 assert!(date_timestamp(&Date(b"Mon, 16 Aug 2016 19:23:38 GMT".to_vec())).is_err());
866 }
867
868 #[test]
869 fn key_debug_reports_never_contain_key_material() {
870 let private = Es256SigningKey::from_pkcs8_pem(PRIVATE).unwrap();
871 let public = Es256VerifyingKey::from_public_key_pem(PUBLIC).unwrap();
872 assert!(!format!("{private:?}").contains("MIGH"));
873 assert!(!format!("{public:?}").contains("MFkw"));
874 }
875}