Skip to main content

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::DerSignature;
13use p256::ecdsa::signature::Verifier as _;
14use rand::{Rng, rng};
15use serde::{Deserialize, Deserializer, Serialize, Serializer};
16use sha2::{Digest, Sha256, digest};
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        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 { request_body: request.get_serialized_body()?, public_key_id, nonce })
238    }
239
240    fn verify_response(
241        &self,
242        request_metadata: &RequestMetadata,
243        resp: &Response<Vec<u8>>,
244        public_key_id: PublicKeyId,
245    ) -> Result<DerSignature, CupVerificationError> {
246        // Per
247        // https://github.com/google/omaha/blob/master/doc/ClientUpdateProtocolEcdsa.md#top-level-description,
248        //
249        // The client receives the response XML, observed client hash, and ECDSA
250        // signature. It concatenates its copy of the request hash to the
251        // response XML, and attempts to verify the ECDSA signature using its
252        // public key. If the signature does not match, the client recognizes
253        // that the server response has been tampered in transit, and rejects
254        // the exchange.
255        //
256        // The client then compares the SHA-256 hash in the response to the
257        // original hash of the request. If the hashes do not match, the client
258        // recognizes that the request has been tampered in transit, and rejects
259        // the exchange.
260
261        let etag_header = resp
262            .headers()
263            .get(ETAG)
264            .ok_or(CupVerificationError::EtagHeaderMissing)?
265            .to_str()
266            .map_err(CupVerificationError::EtagNotString)
267            .map(parse_etag)?;
268
269        let (encoded_signature, hex_hash): (&str, &str) =
270            etag_header.split_once(':').ok_or(CupVerificationError::EtagMalformed)?;
271
272        let actual_hash =
273            &hex::decode(hex_hash).map_err(|_| CupVerificationError::RequestHashMalformed)?;
274
275        let request_body_hash = Sha256::digest(&request_metadata.request_body);
276        if *request_body_hash != *actual_hash {
277            return Err(CupVerificationError::RequestHashMismatch);
278        }
279
280        let signature = DerSignature::from_bytes(
281            &hex::decode(encoded_signature)
282                .map_err(|_| CupVerificationError::SignatureMalformed)?,
283        )?;
284
285        let () = self.verify_response_with_signature(
286            &signature,
287            &request_metadata.request_body,
288            resp.body(),
289            public_key_id,
290            &request_metadata.nonce,
291        )?;
292
293        Ok(signature)
294    }
295}
296
297pub fn make_transaction_hash(
298    request_body: &[u8],
299    response_body: &[u8],
300    public_key_id: PublicKeyId,
301    nonce: &Nonce,
302) -> digest::Output<Sha256> {
303    let request_hash = Sha256::digest(request_body);
304    let response_hash = Sha256::digest(response_body);
305    let cup2_urlparam = format!("{public_key_id}:{nonce}");
306
307    let mut hasher = Sha256::new();
308    hasher.update(request_hash);
309    hasher.update(response_hash);
310    hasher.update(cup2_urlparam);
311    hasher.finalize()
312}
313
314impl Cupv2Verifier for StandardCupv2Handler {
315    fn verify_response_with_signature(
316        &self,
317        ecdsa_signature: &DerSignature,
318        request_body: &[u8],
319        response_body: &[u8],
320        public_key_id: PublicKeyId,
321        nonce: &Nonce,
322    ) -> Result<(), CupVerificationError> {
323        let transaction_hash =
324            make_transaction_hash(request_body, response_body, public_key_id, nonce);
325
326        let public_key: &PublicKey = self
327            .parameters_by_id
328            .get(&public_key_id)
329            .ok_or(CupVerificationError::SpecifiedPublicKeyIdMissing)?;
330        // Since we pass DerSignature by reference, and it doesn't implement
331        // clone, we must reconstitute it into bytes and then back into
332        // der::Signature,
333        let der_signature: ecdsa::der::Signature<p256::NistP256> =
334            ecdsa_signature.as_ref().try_into()?;
335        // and then into Signature for verification.
336        let signature: ecdsa::Signature<p256::NistP256> = der_signature.try_into()?;
337        Ok(public_key.verify(&transaction_hash, &signature)?)
338    }
339}
340
341fn parse_etag(etag: &str) -> &str {
342    // ETag headers are wrapped in double quotes, and can optionally have a W/
343    // prefix. Examples:
344    //     ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
345    //     ETag: W/"0815"
346    match etag.as_bytes() {
347        // If W/".." is present, strip this prefix and the trailing quote.
348        //
349        // NB: Since the &str is valid utf8, removing bytes results in a valid
350        // byte sequence which can be reconstituted into a utf8 without
351        // checking.
352        [b'W', b'/', b'\"', inner @ .., b'\"'] => unsafe { std::str::from_utf8_unchecked(inner) },
353        // If only ".." is present, strip the surrounding quotes.
354        [b'\"', inner @ .., b'\"'] => unsafe { std::str::from_utf8_unchecked(inner) },
355        // Otherwise, leave the str unchanged.
356        _ => etag,
357    }
358}
359
360pub mod test_support {
361    use super::*;
362    use crate::{
363        protocol::request::{Request, RequestWrapper},
364        request_builder::Intermediate,
365    };
366    use p256::ecdsa::SigningKey;
367    use std::{convert::TryInto, str::FromStr};
368
369    pub const RAW_PRIVATE_KEY_FOR_TEST: &str = include_str!("testing_keys/test_private_key.pem");
370    pub const RAW_PUBLIC_KEY_FOR_TEST: &str = include_str!("testing_keys/test_public_key.pem");
371
372    pub fn make_default_public_key_id_for_test() -> PublicKeyId {
373        123456789.try_into().unwrap()
374    }
375    pub fn make_default_private_key_for_test() -> SigningKey {
376        SigningKey::from_str(RAW_PRIVATE_KEY_FOR_TEST).unwrap()
377    }
378    pub fn make_default_public_key_for_test() -> PublicKey {
379        PublicKey::from_str(RAW_PUBLIC_KEY_FOR_TEST).unwrap()
380    }
381
382    pub fn make_keys_for_test() -> (SigningKey, PublicKey) {
383        (make_default_private_key_for_test(), make_default_public_key_for_test())
384    }
385
386    pub fn make_public_keys_for_test(
387        public_key_id: PublicKeyId,
388        public_key: PublicKey,
389    ) -> PublicKeys {
390        PublicKeys {
391            latest: PublicKeyAndId { id: public_key_id, key: public_key },
392            historical: vec![],
393        }
394    }
395
396    pub fn make_default_public_keys_for_test() -> PublicKeys {
397        let (_priv_key, public_key) = make_keys_for_test();
398        make_public_keys_for_test(make_default_public_key_id_for_test(), public_key)
399    }
400
401    pub fn make_default_json_public_keys_for_test() -> serde_json::Value {
402        serde_json::json!({
403            "latest": {
404                "id": make_default_public_key_id_for_test(),
405                "key": RAW_PUBLIC_KEY_FOR_TEST,
406            },
407            "historical": []
408        })
409    }
410    pub fn make_cup_handler_for_test() -> StandardCupv2Handler {
411        let (_signing_key, public_key) = make_keys_for_test();
412        let public_keys =
413            make_public_keys_for_test(make_default_public_key_id_for_test(), public_key);
414        StandardCupv2Handler::new(&public_keys)
415    }
416
417    pub fn make_expected_signature_for_test(
418        signing_key: &SigningKey,
419        request_metadata: &RequestMetadata,
420        response_body: &[u8],
421    ) -> Vec<u8> {
422        use signature::Signer;
423        let transaction_hash = make_transaction_hash(
424            &request_metadata.request_body,
425            response_body,
426            request_metadata.public_key_id,
427            &request_metadata.nonce,
428        );
429        let sig: ecdsa::Signature<p256::NistP256> = signing_key.sign(&transaction_hash);
430        sig.to_der().as_bytes().to_vec()
431    }
432
433    // Mock Cupv2Handler which can be used to fail at request decoration or verification.
434    pub struct MockCupv2Handler {
435        decoration_error: fn() -> Option<CupDecorationError>,
436        verification_error: fn() -> Option<CupVerificationError>,
437    }
438    impl MockCupv2Handler {
439        pub fn new() -> MockCupv2Handler {
440            MockCupv2Handler {
441                decoration_error: || None::<CupDecorationError>,
442                verification_error: || None::<CupVerificationError>,
443            }
444        }
445        pub fn set_decoration_error(
446            mut self,
447            e: fn() -> Option<CupDecorationError>,
448        ) -> MockCupv2Handler {
449            self.decoration_error = e;
450            self
451        }
452        pub fn set_verification_error(
453            mut self,
454            e: fn() -> Option<CupVerificationError>,
455        ) -> MockCupv2Handler {
456            self.verification_error = e;
457            self
458        }
459    }
460
461    impl Default for MockCupv2Handler {
462        fn default() -> Self {
463            Self::new()
464        }
465    }
466
467    impl Cupv2RequestHandler for MockCupv2Handler {
468        fn decorate_request(
469            &self,
470            _request: &mut impl CupRequest,
471        ) -> Result<RequestMetadata, CupDecorationError> {
472            match (self.decoration_error)() {
473                Some(e) => Err(e),
474                None => Ok(RequestMetadata {
475                    request_body: vec![],
476                    public_key_id: 0.try_into().unwrap(),
477                    nonce: [0u8; 32].into(),
478                }),
479            }
480        }
481
482        fn verify_response(
483            &self,
484            request_metadata: &RequestMetadata,
485            resp: &Response<Vec<u8>>,
486            public_key_id: PublicKeyId,
487        ) -> Result<DerSignature, CupVerificationError> {
488            use p256::elliptic_curve::Generate as _;
489            let signing_key = SigningKey::generate();
490            let signature = DerSignature::from_bytes(&make_expected_signature_for_test(
491                &signing_key,
492                request_metadata,
493                resp.body(),
494            ))
495            .unwrap();
496            let () = self.verify_response_with_signature(
497                &signature,
498                &request_metadata.request_body,
499                resp.body(),
500                public_key_id,
501                &request_metadata.nonce,
502            )?;
503            Ok(signature)
504        }
505    }
506
507    impl Cupv2Verifier for MockCupv2Handler {
508        fn verify_response_with_signature(
509            &self,
510            _ecdsa_signature: &DerSignature,
511            _request_body: &[u8],
512            _response_body: &[u8],
513            _public_key_id: PublicKeyId,
514            _nonce: &Nonce,
515        ) -> Result<(), CupVerificationError> {
516            match (self.verification_error)() {
517                Some(e) => Err(e),
518                None => Ok(()),
519            }
520        }
521    }
522
523    pub fn make_standard_intermediate_for_test(request: Request) -> Intermediate {
524        Intermediate {
525            uri: "http://fuchsia.dev".to_string(),
526            headers: [].into(),
527            body: RequestWrapper { request },
528        }
529    }
530}
531
532#[cfg(test)]
533mod tests {
534    use super::*;
535    use crate::{
536        protocol::request::{Request, RequestWrapper},
537        request_builder::Intermediate,
538    };
539    use assert_matches::assert_matches;
540    use p256::ecdsa::SigningKey;
541    use url::Url;
542
543    // For testing only, it is useful to compute equality for CupVerificationError enums.
544    impl PartialEq for CupVerificationError {
545        fn eq(&self, other: &Self) -> bool {
546            format!("{self:?}") == format!("{other:?}")
547        }
548    }
549
550    #[test]
551    fn test_standard_cup_handler_decorate() -> Result<(), anyhow::Error> {
552        let (_, public_key) = test_support::make_keys_for_test();
553        let public_key_id: PublicKeyId = 42.try_into()?;
554        let public_keys = test_support::make_public_keys_for_test(public_key_id, public_key);
555        let cup_handler = StandardCupv2Handler::new(&public_keys);
556
557        let mut intermediate =
558            test_support::make_standard_intermediate_for_test(Request::default());
559
560        let request_metadata = cup_handler.decorate_request(&mut intermediate)?;
561
562        // check cup2key value newly set on request.
563        let cup2key_value: String = Url::parse(&intermediate.uri)?
564            .query_pairs()
565            .find_map(|(k, v)| if k == "cup2key" { Some(v) } else { None })
566            .unwrap()
567            .to_string();
568
569        let (public_key_decimal, nonce_hex) = cup2key_value.split_once(':').unwrap();
570        assert_eq!(public_key_decimal, public_key_id.to_string());
571        assert_eq!(nonce_hex, request_metadata.nonce.to_string());
572        // Assert that the nonce is being generated randomly inline (i.e. not the default value).
573        assert_ne!(request_metadata.nonce, [0_u8; 32].into());
574
575        Ok(())
576    }
577
578    #[test]
579    fn test_standard_cup_handler_decorate_ipv6_link_local() -> Result<(), anyhow::Error> {
580        let (_, public_key) = test_support::make_keys_for_test();
581        let public_key_id: PublicKeyId = 42.try_into()?;
582        let public_keys = test_support::make_public_keys_for_test(public_key_id, public_key);
583        let cup_handler = StandardCupv2Handler::new(&public_keys);
584
585        let mut intermediate = Intermediate {
586            uri: "http://[::1%eth0]".to_string(),
587            headers: [].into(),
588            body: RequestWrapper { request: Request::default() },
589        };
590
591        let request_metadata = cup_handler.decorate_request(&mut intermediate)?;
592
593        assert_eq!(
594            intermediate.uri,
595            format!("http://[::1%eth0]/?cup2key={}:{}", public_key_id, request_metadata.nonce,)
596        );
597
598        // Assert that the nonce is being generated randomly inline (i.e. not the default value).
599        assert_ne!(request_metadata.nonce, [0_u8; 32].into());
600
601        Ok(())
602    }
603
604    #[test]
605    fn test_verify_response_missing_etag_header() -> Result<(), anyhow::Error> {
606        let (_, public_key) = test_support::make_keys_for_test();
607        let public_key_id: PublicKeyId = 12345.try_into()?;
608        let public_keys = test_support::make_public_keys_for_test(public_key_id, public_key);
609        let cup_handler = StandardCupv2Handler::new(&public_keys);
610
611        let mut intermediate =
612            test_support::make_standard_intermediate_for_test(Request::default());
613        let request_metadata = cup_handler.decorate_request(&mut intermediate)?;
614
615        // No .header(ETAG, <val>), which is a problem.
616        let response: Response<Vec<u8>> =
617            hyper::Response::builder().status(200).body("foo".as_bytes().to_vec())?;
618
619        assert_matches!(
620            cup_handler.verify_response(&request_metadata, &response, public_key_id),
621            Err(CupVerificationError::EtagHeaderMissing)
622        );
623        Ok(())
624    }
625
626    #[test]
627    fn test_verify_response_malformed_etag_header() -> Result<(), anyhow::Error> {
628        let (_, public_key) = test_support::make_keys_for_test();
629        let public_key_id: PublicKeyId = 12345.try_into()?;
630        let public_keys = test_support::make_public_keys_for_test(public_key_id, public_key);
631        let cup_handler = StandardCupv2Handler::new(&public_keys);
632
633        let mut intermediate =
634            test_support::make_standard_intermediate_for_test(Request::default());
635        let request_metadata = cup_handler.decorate_request(&mut intermediate)?;
636
637        let response: Response<Vec<u8>> = hyper::Response::builder()
638            .status(200)
639            .header(ETAG, "\u{FEFF}")
640            .body("foo".as_bytes().to_vec())?;
641
642        assert_matches!(
643            cup_handler.verify_response(&request_metadata, &response, public_key_id),
644            Err(CupVerificationError::EtagNotString(_))
645        );
646        Ok(())
647    }
648
649    #[test]
650    fn test_verify_cached_signature_against_message() -> Result<(), anyhow::Error> {
651        let (priv_key, public_key) = test_support::make_keys_for_test();
652        let response_body = "bar";
653        let correct_public_key_id: PublicKeyId = 24682468.try_into()?;
654        let wrong_public_key_id: PublicKeyId = 12341234.try_into()?;
655
656        let public_keys =
657            test_support::make_public_keys_for_test(correct_public_key_id, public_key);
658        let cup_handler = StandardCupv2Handler::new(&public_keys);
659        let mut intermediate =
660            test_support::make_standard_intermediate_for_test(Request::default());
661        let request_metadata = cup_handler.decorate_request(&mut intermediate)?;
662        let expected_request_metadata = RequestMetadata {
663            request_body: intermediate.serialize_body()?,
664            public_key_id: correct_public_key_id,
665            nonce: request_metadata.nonce,
666        };
667
668        let expected_hash = Sha256::digest(&request_metadata.request_body);
669
670        let expected_hash_hex: String = hex::encode(expected_hash);
671        let expected_signature = hex::encode(test_support::make_expected_signature_for_test(
672            &priv_key,
673            &expected_request_metadata,
674            response_body.as_bytes(),
675        ));
676
677        for (etag, public_key_id, expected_err) in [
678            // This etag doesn't even have the form foo:bar.
679            ("bar", correct_public_key_id, Some(CupVerificationError::EtagMalformed)),
680            // This etag has the form foo:bar, but the latter isn't a real hash.
681            ("foo:bar", correct_public_key_id, Some(CupVerificationError::RequestHashMalformed)),
682            // This hash is the right length, but doesn't decode to the right value.
683            (
684                &format!("foo:{}", hex::encode([1; 32])),
685                correct_public_key_id,
686                Some(CupVerificationError::RequestHashMismatch),
687            ),
688            // The hash is the right length and the right value.
689            // But the signature is malformed.
690            (
691                &format!("foo:{expected_hash_hex}"),
692                correct_public_key_id,
693                Some(CupVerificationError::SignatureMalformed),
694            ),
695            // The hash is the right length and the right value.
696            // But the signature decodes to the wrong value.
697            (
698                &format!("{}:{}", hex::encode([1; 64]), expected_hash_hex),
699                correct_public_key_id,
700                Some(CupVerificationError::SignatureError(ecdsa::Error::new())),
701            ),
702            // Wrong public key ID.
703            (
704                &format!("{expected_signature}:{expected_hash_hex}",),
705                wrong_public_key_id,
706                Some(CupVerificationError::SpecifiedPublicKeyIdMissing),
707            ),
708            // Finally, the happy path.
709            (&format!("{expected_signature}:{expected_hash_hex}",), correct_public_key_id, None),
710        ] {
711            let response: Response<Vec<u8>> = hyper::Response::builder()
712                .status(200)
713                .header(ETAG, etag)
714                .body(response_body.as_bytes().to_vec())?;
715            let actual_err =
716                cup_handler.verify_response(&request_metadata, &response, public_key_id).err();
717            assert_eq!(
718                actual_err, expected_err,
719                "Received error {actual_err:?}, expected error {expected_err:?}"
720            );
721        }
722
723        Ok(())
724    }
725
726    // Helper method which, given some setup arguments, can produce a valid
727    // matching request metadata hash and response. Used in historical
728    // verification below.
729    fn make_verify_response_arguments(
730        request_handler: &impl Cupv2RequestHandler,
731        private_key: SigningKey,
732        response_body: &str,
733    ) -> Result<(RequestMetadata, Response<Vec<u8>>), anyhow::Error> {
734        let mut intermediate =
735            test_support::make_standard_intermediate_for_test(Request::default());
736        let request_metadata = request_handler.decorate_request(&mut intermediate)?;
737
738        let signature = hex::encode(test_support::make_expected_signature_for_test(
739            &private_key,
740            &request_metadata,
741            response_body.as_bytes(),
742        ));
743
744        let etag = &format!(
745            "{}:{}",
746            signature,
747            hex::encode(Sha256::digest(&request_metadata.request_body))
748        );
749
750        let response: Response<Vec<u8>> = hyper::Response::builder()
751            .status(200)
752            .header(ETAG, etag)
753            .body(response_body.as_bytes().to_vec())?;
754        Ok((request_metadata, response))
755    }
756
757    #[test]
758    fn test_historical_verification() -> Result<(), anyhow::Error> {
759        let (private_key_a, public_key_a) = test_support::make_keys_for_test();
760        let public_key_id_a: PublicKeyId = 24682468.try_into()?;
761        let response_body_a = "foo";
762
763        let public_keys = PublicKeys {
764            latest: PublicKeyAndId { id: public_key_id_a, key: public_key_a },
765            historical: vec![],
766        };
767        let mut cup_handler = StandardCupv2Handler::new(&public_keys);
768        let (request_metadata_a, response_a) =
769            make_verify_response_arguments(&cup_handler, private_key_a, response_body_a)?;
770        assert_matches!(
771            cup_handler.verify_response(&request_metadata_a, &response_a, public_key_id_a),
772            Ok(_)
773        );
774
775        // Now introduce a new set of keys,
776        let (private_key_b, public_key_b) = test_support::make_keys_for_test();
777        let public_key_id_b: PublicKeyId = 12341234.try_into()?;
778        let response_body_b = "bar";
779
780        // and redefine the cuphandler with new keys and knowledge of historical keys.
781        let public_keys = PublicKeys {
782            latest: PublicKeyAndId { id: public_key_id_b, key: public_key_b },
783            historical: vec![PublicKeyAndId { id: public_key_id_a, key: public_key_a }],
784        };
785        cup_handler = StandardCupv2Handler::new(&public_keys);
786
787        let (request_metadata_b, response_b) =
788            make_verify_response_arguments(&cup_handler, private_key_b, response_body_b)?;
789        // and verify that the cup handler can verify a newly generated response,
790        assert_matches!(
791            cup_handler.verify_response(&request_metadata_b, &response_b, public_key_id_b),
792            Ok(_)
793        );
794
795        // as well as a response which has already been generated and stored.
796        assert_matches!(
797            cup_handler.verify_response(&request_metadata_a, &response_a, public_key_id_a),
798            Ok(_)
799        );
800
801        // finally, assert that verification fails if either (1) the hash, (2)
802        // the stored response, or (3) the key ID itself is wrong.
803        assert!(
804            cup_handler.verify_response(&request_metadata_a, &response_a, public_key_id_b).is_err()
805        );
806        assert!(
807            cup_handler.verify_response(&request_metadata_a, &response_b, public_key_id_a).is_err()
808        );
809        assert!(
810            cup_handler.verify_response(&request_metadata_b, &response_a, public_key_id_a).is_err()
811        );
812
813        Ok(())
814    }
815
816    #[test]
817    fn test_deserialize_public_keys() {
818        let public_key_and_id: PublicKeyAndId = serde_json::from_value(serde_json::json!(
819            {
820                 "id": 123,
821                 "key": test_support::RAW_PUBLIC_KEY_FOR_TEST,
822            }
823        ))
824        .unwrap();
825
826        assert_eq!(public_key_and_id.key, test_support::make_default_public_key_for_test());
827    }
828
829    #[test]
830    fn test_publickeys_roundtrip() {
831        // Test that serializing and deserializing a PublicKeys struct using
832        // from_pem / to_pem results in the same struct.
833        let public_keys = test_support::make_default_public_keys_for_test();
834        let public_keys_serialized = serde_json::to_string(&public_keys).unwrap();
835        let public_keys_reconstituted = serde_json::from_str(&public_keys_serialized).unwrap();
836        assert_eq!(public_keys, public_keys_reconstituted);
837    }
838
839    #[test]
840    fn test_parse_etag() {
841        // W/ prefix
842        assert_eq!(parse_etag("W/\"foo\""), "foo");
843        assert_eq!(parse_etag("W/\"thing-\"with\"-quotes\""), "thing-\"with\"-quotes");
844        assert_eq!(parse_etag("W/\"\""), "");
845        // only surrounding quotes
846        assert_eq!(parse_etag("\"foo\""), "foo");
847        assert_eq!(parse_etag("\"thing-\"with\"-quotes\""), "thing-\"with\"-quotes",);
848        assert_eq!(parse_etag("\"\""), "");
849        // otherwise, left unchanged
850        for v in ["foo", "1", "W", "W\"", "W/\"", "W/", "w/\"bar\"", "W/'bar'", ""] {
851            //
852            assert_eq!(parse_etag(v), v);
853        }
854    }
855}