omaha_client/
cup_ecdsa.rs

1// Copyright 2022 The Fuchsia Authors
2//
3// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
4// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
5// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
6// This file may not be copied, modified, or distributed except according to
7// those terms.
8
9use crate::http_uri_ext::HttpUriExt as _;
10use http::{Response, Uri};
11use hyper::header::ETAG;
12use p256::ecdsa::{signature::Verifier as _, DerSignature};
13use rand::{thread_rng, Rng};
14use serde::{Deserialize, Deserializer, Serialize, Serializer};
15use sha2::{digest, Digest, Sha256};
16use signature::Signature;
17use std::{collections::HashMap, convert::TryInto, fmt, fmt::Debug};
18
19/// Error enum listing different kinds of CUPv2 decoration errors.
20#[derive(Debug, thiserror::Error)]
21pub enum CupDecorationError {
22    #[error("could not serialize request.")]
23    SerializationError(#[from] serde_json::Error),
24    #[error("could not parse existing URI.")]
25    ParseError(#[from] http::uri::InvalidUri),
26    #[error("could not append query parameter.")]
27    AppendQueryParameterError(#[from] crate::http_uri_ext::Error),
28}
29
30/// Error enum listing different kinds of CUPv2 verification errors.
31#[derive(Debug, thiserror::Error)]
32pub enum CupVerificationError {
33    #[error("etag header missing.")]
34    EtagHeaderMissing,
35    #[error("etag header is not a string.")]
36    EtagNotString(hyper::header::ToStrError),
37    #[error("etag header is malformed.")]
38    EtagMalformed,
39    #[error("etag header's request hash is malformed.")]
40    RequestHashMalformed,
41    #[error("etag header's request hash doesn't match.")]
42    RequestHashMismatch,
43    #[error("etag header's signature is malformed.")]
44    SignatureMalformed,
45    #[error("specified public key ID not found in internal map.")]
46    SpecifiedPublicKeyIdMissing,
47    #[error("could not verify etag header's signature.")]
48    SignatureError(#[from] ecdsa::Error),
49}
50
51/// By convention, this is always the u64 hash of the public key
52/// value.
53pub type PublicKeyId = u64;
54pub type PublicKey = p256::ecdsa::VerifyingKey;
55
56fn from_pem<'de, D>(deserializer: D) -> Result<PublicKey, D::Error>
57where
58    D: Deserializer<'de>,
59{
60    use serde::de;
61    let s = String::deserialize(deserializer)?;
62    s.parse().map_err(de::Error::custom)
63}
64
65fn to_pem<S>(public_key: &PublicKey, serializer: S) -> Result<S::Ok, S::Error>
66where
67    S: Serializer,
68{
69    use pkcs8::EncodePublicKey;
70    use serde::ser;
71    serializer.serialize_str(
72        &elliptic_curve::PublicKey::from(public_key)
73            .to_public_key_pem(pkcs8::LineEnding::LF)
74            .map_err(ser::Error::custom)?,
75    )
76}
77
78#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
79pub struct PublicKeyAndId {
80    #[serde(deserialize_with = "from_pem", serialize_with = "to_pem")]
81    pub key: PublicKey,
82    pub id: PublicKeyId,
83}
84
85#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
86pub struct PublicKeys {
87    /// The latest public key will be used when decorating requests.
88    pub latest: PublicKeyAndId,
89    /// Historical public keys and IDs. May be empty.
90    pub historical: Vec<PublicKeyAndId>,
91}
92
93#[derive(PartialEq, Eq, Debug, Copy, Clone)]
94pub struct Nonce([u8; 32]);
95
96impl From<[u8; 32]> for Nonce {
97    fn from(array: [u8; 32]) -> Self {
98        Nonce(array)
99    }
100}
101impl From<&[u8; 32]> for Nonce {
102    fn from(array: &[u8; 32]) -> Self {
103        Nonce(*array)
104    }
105}
106
107#[allow(clippy::from_over_into)]
108impl Into<[u8; 32]> for Nonce {
109    fn into(self) -> [u8; 32] {
110        self.0
111    }
112}
113
114impl Default for Nonce {
115    fn default() -> Self {
116        Self::new()
117    }
118}
119
120impl Nonce {
121    pub fn new() -> Nonce {
122        let mut nonce_bits = [0_u8; 32];
123        thread_rng().fill(&mut nonce_bits[..]);
124        Nonce(nonce_bits)
125    }
126}
127
128impl fmt::Display for Nonce {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        write!(f, "{}", hex::encode(self.0))
131    }
132}
133
134/// RequestMetadata is a request decoration return type, containing request internals.
135///
136/// Clients of this library can call .hash() and store/retrieve the hash, or they
137/// can inspect the request, public key ID, nonce used if necessary.
138#[derive(Clone, Debug, PartialEq, Eq)]
139pub struct RequestMetadata {
140    pub request_body: Vec<u8>,
141    pub public_key_id: PublicKeyId,
142    pub nonce: Nonce,
143}
144
145/// An interface to an under-construction server request, providing read/write
146/// access to the URI and read access to the serialized request body.
147pub trait CupRequest {
148    /// Get the request URI.
149    fn get_uri(&self) -> &str;
150    /// Set a new request URI.
151    fn set_uri(&mut self, uri: String);
152    /// Get a serialized copy of the request body.
153    fn get_serialized_body(&self) -> serde_json::Result<Vec<u8>>;
154}
155
156// General trait for a decorator which knows how to decorate and verify CUPv2
157// requests.
158pub trait Cupv2RequestHandler {
159    /// Decorate an outgoing client request with query parameters `cup2key`.
160    /// Returns a struct of request metadata, the hash of which can be stored and
161    /// used later.
162    fn decorate_request(
163        &self,
164        request: &mut impl CupRequest,
165    ) -> Result<RequestMetadata, CupDecorationError>;
166
167    /// Examines an incoming client request with an ETag HTTP Header. Returns an
168    /// error if the response is not authentic.
169    fn verify_response(
170        &self,
171        request_metadata: &RequestMetadata,
172        resp: &Response<Vec<u8>>,
173        public_key_id: PublicKeyId,
174    ) -> Result<DerSignature, CupVerificationError>;
175}
176
177// General trait for something which can verify CUPv2 signatures.
178pub trait Cupv2Verifier {
179    /// The same behavior as verify_response, but designed for verifying stored
180    /// signatures which are not hyper-aware.
181    fn verify_response_with_signature(
182        &self,
183        ecdsa_signature: &DerSignature,
184        request_body: &[u8],
185        response_body: &[u8],
186        public_key_id: PublicKeyId,
187        nonce: &Nonce,
188    ) -> Result<(), CupVerificationError>;
189}
190
191pub trait Cupv2Handler: Cupv2RequestHandler + Cupv2Verifier {}
192
193impl<T> Cupv2Handler for T where T: Cupv2RequestHandler + Cupv2Verifier {}
194
195// Default Cupv2Handler.
196#[derive(Debug)]
197pub struct StandardCupv2Handler {
198    /// Device-wide map from public key ID# to public key. Should be ingested at
199    /// startup. This map should never be empty.
200    parameters_by_id: HashMap<PublicKeyId, PublicKey>,
201    latest_public_key_id: PublicKeyId,
202}
203
204impl StandardCupv2Handler {
205    /// Constructor for the standard CUPv2 handler.
206    pub fn new(public_keys: &PublicKeys) -> Self {
207        Self {
208            parameters_by_id: std::iter::once(&public_keys.latest)
209                .chain(&public_keys.historical)
210                .map(|k| (k.id, k.key))
211                .collect(),
212            latest_public_key_id: public_keys.latest.id,
213        }
214    }
215}
216
217impl Cupv2RequestHandler for StandardCupv2Handler {
218    fn decorate_request(
219        &self,
220        request: &mut impl CupRequest,
221    ) -> Result<RequestMetadata, CupDecorationError> {
222        // Per
223        // https://github.com/google/omaha/blob/master/doc/ClientUpdateProtocolEcdsa.md#top-level-description,
224        //
225        // formatting will be similar to CUP -- namely: “cup2key=%d:%u” where
226        // the first parameter is the key pair id, and the second is the client
227        // freshness nonce.
228
229        let public_key_id: PublicKeyId = self.latest_public_key_id;
230
231        let nonce = Nonce::new();
232
233        let uri: Uri = request.get_uri().parse()?;
234        let uri = uri.append_query_parameter("cup2key", &format!("{public_key_id}:{nonce}"))?;
235        request.set_uri(uri.to_string());
236
237        Ok(RequestMetadata {
238            request_body: request.get_serialized_body()?,
239            public_key_id,
240            nonce,
241        })
242    }
243
244    fn verify_response(
245        &self,
246        request_metadata: &RequestMetadata,
247        resp: &Response<Vec<u8>>,
248        public_key_id: PublicKeyId,
249    ) -> Result<DerSignature, CupVerificationError> {
250        // Per
251        // https://github.com/google/omaha/blob/master/doc/ClientUpdateProtocolEcdsa.md#top-level-description,
252        //
253        // The client receives the response XML, observed client hash, and ECDSA
254        // signature. It concatenates its copy of the request hash to the
255        // response XML, and attempts to verify the ECDSA signature using its
256        // public key. If the signature does not match, the client recognizes
257        // that the server response has been tampered in transit, and rejects
258        // the exchange.
259        //
260        // The client then compares the SHA-256 hash in the response to the
261        // original hash of the request. If the hashes do not match, the client
262        // recognizes that the request has been tampered in transit, and rejects
263        // the exchange.
264
265        let etag_header = resp
266            .headers()
267            .get(ETAG)
268            .ok_or(CupVerificationError::EtagHeaderMissing)?
269            .to_str()
270            .map_err(CupVerificationError::EtagNotString)
271            .map(parse_etag)?;
272
273        let (encoded_signature, hex_hash): (&str, &str) = etag_header
274            .split_once(':')
275            .ok_or(CupVerificationError::EtagMalformed)?;
276
277        let actual_hash =
278            &hex::decode(hex_hash).map_err(|_| CupVerificationError::RequestHashMalformed)?;
279
280        let request_body_hash = Sha256::digest(&request_metadata.request_body);
281        if *request_body_hash != *actual_hash {
282            return Err(CupVerificationError::RequestHashMismatch);
283        }
284
285        let signature = DerSignature::from_bytes(
286            &hex::decode(encoded_signature)
287                .map_err(|_| CupVerificationError::SignatureMalformed)?,
288        )?;
289
290        let () = self.verify_response_with_signature(
291            &signature,
292            &request_metadata.request_body,
293            resp.body(),
294            public_key_id,
295            &request_metadata.nonce,
296        )?;
297
298        Ok(signature)
299    }
300}
301
302pub fn make_transaction_hash(
303    request_body: &[u8],
304    response_body: &[u8],
305    public_key_id: PublicKeyId,
306    nonce: &Nonce,
307) -> digest::Output<Sha256> {
308    let request_hash = Sha256::digest(request_body);
309    let response_hash = Sha256::digest(response_body);
310    let cup2_urlparam = format!("{public_key_id}:{nonce}");
311
312    let mut hasher = Sha256::new();
313    hasher.update(request_hash);
314    hasher.update(response_hash);
315    hasher.update(cup2_urlparam);
316    hasher.finalize()
317}
318
319impl Cupv2Verifier for StandardCupv2Handler {
320    fn verify_response_with_signature(
321        &self,
322        ecdsa_signature: &DerSignature,
323        request_body: &[u8],
324        response_body: &[u8],
325        public_key_id: PublicKeyId,
326        nonce: &Nonce,
327    ) -> Result<(), CupVerificationError> {
328        let transaction_hash =
329            make_transaction_hash(request_body, response_body, public_key_id, nonce);
330
331        let public_key: &PublicKey = self
332            .parameters_by_id
333            .get(&public_key_id)
334            .ok_or(CupVerificationError::SpecifiedPublicKeyIdMissing)?;
335        // Since we pass DerSignature by reference, and it doesn't implement
336        // clone, we must reconstitute it into bytes and then back into
337        // der::Signature,
338        let der_signature: ecdsa::der::Signature<p256::NistP256> =
339            ecdsa_signature.as_ref().try_into()?;
340        // and then into Signature for verification.
341        let signature: ecdsa::Signature<p256::NistP256> = der_signature.try_into()?;
342        Ok(public_key.verify(&transaction_hash, &signature)?)
343    }
344}
345
346fn parse_etag(etag: &str) -> &str {
347    // ETag headers are wrapped in double quotes, and can optionally have a W/
348    // prefix. Examples:
349    //     ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
350    //     ETag: W/"0815"
351    match etag.as_bytes() {
352        // If W/".." is present, strip this prefix and the trailing quote.
353        //
354        // NB: Since the &str is valid utf8, removing bytes results in a valid
355        // byte sequence which can be reconstituted into a utf8 without
356        // checking.
357        [b'W', b'/', b'\"', inner @ .., b'\"'] => unsafe { std::str::from_utf8_unchecked(inner) },
358        // If only ".." is present, strip the surrounding quotes.
359        [b'\"', inner @ .., b'\"'] => unsafe { std::str::from_utf8_unchecked(inner) },
360        // Otherwise, leave the str unchanged.
361        _ => etag,
362    }
363}
364
365pub mod test_support {
366    use super::*;
367    use crate::{
368        protocol::request::{Request, RequestWrapper},
369        request_builder::Intermediate,
370    };
371    use p256::ecdsa::SigningKey;
372    use std::{convert::TryInto, str::FromStr};
373
374    pub const RAW_PRIVATE_KEY_FOR_TEST: &str = include_str!("testing_keys/test_private_key.pem");
375    pub const RAW_PUBLIC_KEY_FOR_TEST: &str = include_str!("testing_keys/test_public_key.pem");
376
377    pub fn make_default_public_key_id_for_test() -> PublicKeyId {
378        123456789.try_into().unwrap()
379    }
380    pub fn make_default_private_key_for_test() -> SigningKey {
381        SigningKey::from_str(RAW_PRIVATE_KEY_FOR_TEST).unwrap()
382    }
383    pub fn make_default_public_key_for_test() -> PublicKey {
384        PublicKey::from_str(RAW_PUBLIC_KEY_FOR_TEST).unwrap()
385    }
386
387    pub fn make_keys_for_test() -> (SigningKey, PublicKey) {
388        (
389            make_default_private_key_for_test(),
390            make_default_public_key_for_test(),
391        )
392    }
393
394    pub fn make_public_keys_for_test(
395        public_key_id: PublicKeyId,
396        public_key: PublicKey,
397    ) -> PublicKeys {
398        PublicKeys {
399            latest: PublicKeyAndId {
400                id: public_key_id,
401                key: public_key,
402            },
403            historical: vec![],
404        }
405    }
406
407    pub fn make_default_public_keys_for_test() -> PublicKeys {
408        let (_priv_key, public_key) = make_keys_for_test();
409        make_public_keys_for_test(make_default_public_key_id_for_test(), public_key)
410    }
411
412    pub fn make_default_json_public_keys_for_test() -> serde_json::Value {
413        serde_json::json!({
414            "latest": {
415                "id": make_default_public_key_id_for_test(),
416                "key": RAW_PUBLIC_KEY_FOR_TEST,
417            },
418            "historical": []
419        })
420    }
421    pub fn make_cup_handler_for_test() -> StandardCupv2Handler {
422        let (_signing_key, public_key) = make_keys_for_test();
423        let public_keys =
424            make_public_keys_for_test(make_default_public_key_id_for_test(), public_key);
425        StandardCupv2Handler::new(&public_keys)
426    }
427
428    pub fn make_expected_signature_for_test(
429        signing_key: &SigningKey,
430        request_metadata: &RequestMetadata,
431        response_body: &[u8],
432    ) -> Vec<u8> {
433        use signature::Signer;
434        let transaction_hash = make_transaction_hash(
435            &request_metadata.request_body,
436            response_body,
437            request_metadata.public_key_id,
438            &request_metadata.nonce,
439        );
440        signing_key
441            .sign(&transaction_hash)
442            .to_der()
443            .as_bytes()
444            .to_vec()
445    }
446
447    // Mock Cupv2Handler which can be used to fail at request decoration or verification.
448    pub struct MockCupv2Handler {
449        decoration_error: fn() -> Option<CupDecorationError>,
450        verification_error: fn() -> Option<CupVerificationError>,
451    }
452    impl MockCupv2Handler {
453        pub fn new() -> MockCupv2Handler {
454            MockCupv2Handler {
455                decoration_error: || None::<CupDecorationError>,
456                verification_error: || None::<CupVerificationError>,
457            }
458        }
459        pub fn set_decoration_error(
460            mut self,
461            e: fn() -> Option<CupDecorationError>,
462        ) -> MockCupv2Handler {
463            self.decoration_error = e;
464            self
465        }
466        pub fn set_verification_error(
467            mut self,
468            e: fn() -> Option<CupVerificationError>,
469        ) -> MockCupv2Handler {
470            self.verification_error = e;
471            self
472        }
473    }
474
475    impl Default for MockCupv2Handler {
476        fn default() -> Self {
477            Self::new()
478        }
479    }
480
481    impl Cupv2RequestHandler for MockCupv2Handler {
482        fn decorate_request(
483            &self,
484            _request: &mut impl CupRequest,
485        ) -> Result<RequestMetadata, CupDecorationError> {
486            match (self.decoration_error)() {
487                Some(e) => Err(e),
488                None => Ok(RequestMetadata {
489                    request_body: vec![],
490                    public_key_id: 0.try_into().unwrap(),
491                    nonce: [0u8; 32].into(),
492                }),
493            }
494        }
495
496        fn verify_response(
497            &self,
498            request_metadata: &RequestMetadata,
499            resp: &Response<Vec<u8>>,
500            public_key_id: PublicKeyId,
501        ) -> Result<DerSignature, CupVerificationError> {
502            use rand::rngs::OsRng;
503            let signing_key = SigningKey::random(&mut OsRng);
504            let signature = DerSignature::from_bytes(&make_expected_signature_for_test(
505                &signing_key,
506                request_metadata,
507                resp.body(),
508            ))
509            .unwrap();
510            let () = self.verify_response_with_signature(
511                &signature,
512                &request_metadata.request_body,
513                resp.body(),
514                public_key_id,
515                &request_metadata.nonce,
516            )?;
517            Ok(signature)
518        }
519    }
520
521    impl Cupv2Verifier for MockCupv2Handler {
522        fn verify_response_with_signature(
523            &self,
524            _ecdsa_signature: &DerSignature,
525            _request_body: &[u8],
526            _response_body: &[u8],
527            _public_key_id: PublicKeyId,
528            _nonce: &Nonce,
529        ) -> Result<(), CupVerificationError> {
530            match (self.verification_error)() {
531                Some(e) => Err(e),
532                None => Ok(()),
533            }
534        }
535    }
536
537    pub fn make_standard_intermediate_for_test(request: Request) -> Intermediate {
538        Intermediate {
539            uri: "http://fuchsia.dev".to_string(),
540            headers: [].into(),
541            body: RequestWrapper { request },
542        }
543    }
544}
545
546#[cfg(test)]
547mod tests {
548    use super::*;
549    use crate::{
550        protocol::request::{Request, RequestWrapper},
551        request_builder::Intermediate,
552    };
553    use assert_matches::assert_matches;
554    use p256::ecdsa::SigningKey;
555    use url::Url;
556
557    // For testing only, it is useful to compute equality for CupVerificationError enums.
558    impl PartialEq for CupVerificationError {
559        fn eq(&self, other: &Self) -> bool {
560            format!("{self:?}") == format!("{other:?}")
561        }
562    }
563
564    #[test]
565    fn test_standard_cup_handler_decorate() -> Result<(), anyhow::Error> {
566        let (_, public_key) = test_support::make_keys_for_test();
567        let public_key_id: PublicKeyId = 42.try_into()?;
568        let public_keys = test_support::make_public_keys_for_test(public_key_id, public_key);
569        let cup_handler = StandardCupv2Handler::new(&public_keys);
570
571        let mut intermediate =
572            test_support::make_standard_intermediate_for_test(Request::default());
573
574        let request_metadata = cup_handler.decorate_request(&mut intermediate)?;
575
576        // check cup2key value newly set on request.
577        let cup2key_value: String = Url::parse(&intermediate.uri)?
578            .query_pairs()
579            .find_map(|(k, v)| if k == "cup2key" { Some(v) } else { None })
580            .unwrap()
581            .to_string();
582
583        let (public_key_decimal, nonce_hex) = cup2key_value.split_once(':').unwrap();
584        assert_eq!(public_key_decimal, public_key_id.to_string());
585        assert_eq!(nonce_hex, request_metadata.nonce.to_string());
586        // Assert that the nonce is being generated randomly inline (i.e. not the default value).
587        assert_ne!(request_metadata.nonce, [0_u8; 32].into());
588
589        Ok(())
590    }
591
592    #[test]
593    fn test_standard_cup_handler_decorate_ipv6_link_local() -> Result<(), anyhow::Error> {
594        let (_, public_key) = test_support::make_keys_for_test();
595        let public_key_id: PublicKeyId = 42.try_into()?;
596        let public_keys = test_support::make_public_keys_for_test(public_key_id, public_key);
597        let cup_handler = StandardCupv2Handler::new(&public_keys);
598
599        let mut intermediate = Intermediate {
600            uri: "http://[::1%eth0]".to_string(),
601            headers: [].into(),
602            body: RequestWrapper {
603                request: Request::default(),
604            },
605        };
606
607        let request_metadata = cup_handler.decorate_request(&mut intermediate)?;
608
609        assert_eq!(
610            intermediate.uri,
611            format!(
612                "http://[::1%eth0]/?cup2key={}:{}",
613                public_key_id, request_metadata.nonce,
614            )
615        );
616
617        // Assert that the nonce is being generated randomly inline (i.e. not the default value).
618        assert_ne!(request_metadata.nonce, [0_u8; 32].into());
619
620        Ok(())
621    }
622
623    #[test]
624    fn test_verify_response_missing_etag_header() -> Result<(), anyhow::Error> {
625        let (_, public_key) = test_support::make_keys_for_test();
626        let public_key_id: PublicKeyId = 12345.try_into()?;
627        let public_keys = test_support::make_public_keys_for_test(public_key_id, public_key);
628        let cup_handler = StandardCupv2Handler::new(&public_keys);
629
630        let mut intermediate =
631            test_support::make_standard_intermediate_for_test(Request::default());
632        let request_metadata = cup_handler.decorate_request(&mut intermediate)?;
633
634        // No .header(ETAG, <val>), which is a problem.
635        let response: Response<Vec<u8>> = hyper::Response::builder()
636            .status(200)
637            .body("foo".as_bytes().to_vec())?;
638
639        assert_matches!(
640            cup_handler.verify_response(&request_metadata, &response, public_key_id),
641            Err(CupVerificationError::EtagHeaderMissing)
642        );
643        Ok(())
644    }
645
646    #[test]
647    fn test_verify_response_malformed_etag_header() -> Result<(), anyhow::Error> {
648        let (_, public_key) = test_support::make_keys_for_test();
649        let public_key_id: PublicKeyId = 12345.try_into()?;
650        let public_keys = test_support::make_public_keys_for_test(public_key_id, public_key);
651        let cup_handler = StandardCupv2Handler::new(&public_keys);
652
653        let mut intermediate =
654            test_support::make_standard_intermediate_for_test(Request::default());
655        let request_metadata = cup_handler.decorate_request(&mut intermediate)?;
656
657        let response: Response<Vec<u8>> = hyper::Response::builder()
658            .status(200)
659            .header(ETAG, "\u{FEFF}")
660            .body("foo".as_bytes().to_vec())?;
661
662        assert_matches!(
663            cup_handler.verify_response(&request_metadata, &response, public_key_id),
664            Err(CupVerificationError::EtagNotString(_))
665        );
666        Ok(())
667    }
668
669    #[test]
670    fn test_verify_cached_signature_against_message() -> Result<(), anyhow::Error> {
671        let (priv_key, public_key) = test_support::make_keys_for_test();
672        let response_body = "bar";
673        let correct_public_key_id: PublicKeyId = 24682468.try_into()?;
674        let wrong_public_key_id: PublicKeyId = 12341234.try_into()?;
675
676        let public_keys =
677            test_support::make_public_keys_for_test(correct_public_key_id, public_key);
678        let cup_handler = StandardCupv2Handler::new(&public_keys);
679        let mut intermediate =
680            test_support::make_standard_intermediate_for_test(Request::default());
681        let request_metadata = cup_handler.decorate_request(&mut intermediate)?;
682        let expected_request_metadata = RequestMetadata {
683            request_body: intermediate.serialize_body()?,
684            public_key_id: correct_public_key_id,
685            nonce: request_metadata.nonce,
686        };
687
688        let expected_hash = Sha256::digest(&request_metadata.request_body);
689
690        let expected_hash_hex: String = hex::encode(expected_hash);
691        let expected_signature = hex::encode(test_support::make_expected_signature_for_test(
692            &priv_key,
693            &expected_request_metadata,
694            response_body.as_bytes(),
695        ));
696
697        for (etag, public_key_id, expected_err) in vec![
698            // This etag doesn't even have the form foo:bar.
699            (
700                "bar",
701                correct_public_key_id,
702                Some(CupVerificationError::EtagMalformed),
703            ),
704            // This etag has the form foo:bar, but the latter isn't a real hash.
705            (
706                "foo:bar",
707                correct_public_key_id,
708                Some(CupVerificationError::RequestHashMalformed),
709            ),
710            // This hash is the right length, but doesn't decode to the right value.
711            (
712                &format!("foo:{}", hex::encode([1; 32])),
713                correct_public_key_id,
714                Some(CupVerificationError::RequestHashMismatch),
715            ),
716            // The hash is the right length and the right value.
717            // But the signature is malformed.
718            (
719                &format!("foo:{expected_hash_hex}"),
720                correct_public_key_id,
721                Some(CupVerificationError::SignatureMalformed),
722            ),
723            // The hash is the right length and the right value.
724            // But the signature decodes to the wrong value.
725            (
726                &format!("{}:{}", hex::encode([1; 64]), expected_hash_hex),
727                correct_public_key_id,
728                Some(CupVerificationError::SignatureError(ecdsa::Error::new())),
729            ),
730            // Wrong public key ID.
731            (
732                &format!("{expected_signature}:{expected_hash_hex}",),
733                wrong_public_key_id,
734                Some(CupVerificationError::SpecifiedPublicKeyIdMissing),
735            ),
736            // Finally, the happy path.
737            (
738                &format!("{expected_signature}:{expected_hash_hex}",),
739                correct_public_key_id,
740                None,
741            ),
742        ] {
743            let response: Response<Vec<u8>> = hyper::Response::builder()
744                .status(200)
745                .header(ETAG, etag)
746                .body(response_body.as_bytes().to_vec())?;
747            let actual_err = cup_handler
748                .verify_response(&request_metadata, &response, public_key_id)
749                .err();
750            assert_eq!(
751                actual_err, expected_err,
752                "Received error {actual_err:?}, expected error {expected_err:?}"
753            );
754        }
755
756        Ok(())
757    }
758
759    // Helper method which, given some setup arguments, can produce a valid
760    // matching request metadata hash and response. Used in historical
761    // verification below.
762    fn make_verify_response_arguments(
763        request_handler: &impl Cupv2RequestHandler,
764        private_key: SigningKey,
765        response_body: &str,
766    ) -> Result<(RequestMetadata, Response<Vec<u8>>), anyhow::Error> {
767        let mut intermediate =
768            test_support::make_standard_intermediate_for_test(Request::default());
769        let request_metadata = request_handler.decorate_request(&mut intermediate)?;
770
771        let signature = hex::encode(test_support::make_expected_signature_for_test(
772            &private_key,
773            &request_metadata,
774            response_body.as_bytes(),
775        ));
776
777        let etag = &format!(
778            "{}:{}",
779            signature,
780            hex::encode(Sha256::digest(&request_metadata.request_body))
781        );
782
783        let response: Response<Vec<u8>> = hyper::Response::builder()
784            .status(200)
785            .header(ETAG, etag)
786            .body(response_body.as_bytes().to_vec())?;
787        Ok((request_metadata, response))
788    }
789
790    #[test]
791    fn test_historical_verification() -> Result<(), anyhow::Error> {
792        let (private_key_a, public_key_a) = test_support::make_keys_for_test();
793        let public_key_id_a: PublicKeyId = 24682468.try_into()?;
794        let response_body_a = "foo";
795
796        let public_keys = PublicKeys {
797            latest: PublicKeyAndId {
798                id: public_key_id_a,
799                key: public_key_a,
800            },
801            historical: vec![],
802        };
803        let mut cup_handler = StandardCupv2Handler::new(&public_keys);
804        let (request_metadata_a, response_a) =
805            make_verify_response_arguments(&cup_handler, private_key_a, response_body_a)?;
806        assert_matches!(
807            cup_handler.verify_response(&request_metadata_a, &response_a, public_key_id_a),
808            Ok(_)
809        );
810
811        // Now introduce a new set of keys,
812        let (private_key_b, public_key_b) = test_support::make_keys_for_test();
813        let public_key_id_b: PublicKeyId = 12341234.try_into()?;
814        let response_body_b = "bar";
815
816        // and redefine the cuphandler with new keys and knowledge of historical keys.
817        let public_keys = PublicKeys {
818            latest: PublicKeyAndId {
819                id: public_key_id_b,
820                key: public_key_b,
821            },
822            historical: vec![PublicKeyAndId {
823                id: public_key_id_a,
824                key: public_key_a,
825            }],
826        };
827        cup_handler = StandardCupv2Handler::new(&public_keys);
828
829        let (request_metadata_b, response_b) =
830            make_verify_response_arguments(&cup_handler, private_key_b, response_body_b)?;
831        // and verify that the cup handler can verify a newly generated response,
832        assert_matches!(
833            cup_handler.verify_response(&request_metadata_b, &response_b, public_key_id_b),
834            Ok(_)
835        );
836
837        // as well as a response which has already been generated and stored.
838        assert_matches!(
839            cup_handler.verify_response(&request_metadata_a, &response_a, public_key_id_a),
840            Ok(_)
841        );
842
843        // finally, assert that verification fails if either (1) the hash, (2)
844        // the stored response, or (3) the key ID itself is wrong.
845        assert!(cup_handler
846            .verify_response(&request_metadata_a, &response_a, public_key_id_b)
847            .is_err());
848        assert!(cup_handler
849            .verify_response(&request_metadata_a, &response_b, public_key_id_a)
850            .is_err());
851        assert!(cup_handler
852            .verify_response(&request_metadata_b, &response_a, public_key_id_a)
853            .is_err());
854
855        Ok(())
856    }
857
858    #[test]
859    fn test_deserialize_public_keys() {
860        let public_key_and_id: PublicKeyAndId = serde_json::from_value(serde_json::json!(
861            {
862                 "id": 123,
863                 "key": test_support::RAW_PUBLIC_KEY_FOR_TEST,
864            }
865        ))
866        .unwrap();
867
868        assert_eq!(
869            public_key_and_id.key,
870            test_support::make_default_public_key_for_test()
871        );
872    }
873
874    #[test]
875    fn test_publickeys_roundtrip() {
876        // Test that serializing and deserializing a PublicKeys struct using
877        // from_pem / to_pem results in the same struct.
878        let public_keys = test_support::make_default_public_keys_for_test();
879        let public_keys_serialized = serde_json::to_string(&public_keys).unwrap();
880        let public_keys_reconstituted = serde_json::from_str(&public_keys_serialized).unwrap();
881        assert_eq!(public_keys, public_keys_reconstituted);
882    }
883
884    #[test]
885    fn test_parse_etag() {
886        // W/ prefix
887        assert_eq!(parse_etag("W/\"foo\""), "foo");
888        assert_eq!(
889            parse_etag("W/\"thing-\"with\"-quotes\""),
890            "thing-\"with\"-quotes"
891        );
892        assert_eq!(parse_etag("W/\"\""), "");
893        // only surrounding quotes
894        assert_eq!(parse_etag("\"foo\""), "foo");
895        assert_eq!(
896            parse_etag("\"thing-\"with\"-quotes\""),
897            "thing-\"with\"-quotes",
898        );
899        assert_eq!(parse_etag("\"\""), "");
900        // otherwise, left unchanged
901        for v in [
902            "foo",
903            "1",
904            "W",
905            "W\"",
906            "W/\"",
907            "W/",
908            "w/\"bar\"",
909            "W/'bar'",
910            "",
911        ] {
912            //
913            assert_eq!(parse_etag(v), v);
914        }
915    }
916}