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