fxa_client/internal/
scoped_keys.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
6use jwcrypto::{self, DecryptionParameters, Jwk};
7use rc_crypto::{agreement, agreement::EphemeralKeyPair};
8
9use super::FirefoxAccount;
10use crate::{Error, Result, ScopedKey};
11
12impl FirefoxAccount {
13    pub(crate) fn get_scoped_key(&self, scope: &str) -> Result<&ScopedKey> {
14        self.state
15            .get_scoped_key(scope)
16            .ok_or_else(|| Error::NoScopedKey(scope.to_string()))
17    }
18}
19
20impl ScopedKey {
21    pub fn key_bytes(&self) -> Result<Vec<u8>> {
22        Ok(URL_SAFE_NO_PAD.decode(&self.k)?)
23    }
24}
25
26impl std::fmt::Debug for ScopedKey {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        f.debug_struct("ScopedKey")
29            .field("kty", &self.kty)
30            .field("scope", &self.scope)
31            .field("kid", &self.kid)
32            .finish()
33    }
34}
35
36pub struct ScopedKeysFlow {
37    key_pair: EphemeralKeyPair,
38}
39
40impl ScopedKeysFlow {
41    pub fn with_random_key() -> Result<Self> {
42        let key_pair = EphemeralKeyPair::generate(&agreement::ECDH_P256)?;
43        Ok(Self { key_pair })
44    }
45
46    #[cfg(test)]
47    pub fn from_static_key_pair(key_pair: agreement::KeyPair<agreement::Static>) -> Result<Self> {
48        let (private_key, _) = key_pair.split();
49        let ephemeral_prv_key = private_key._tests_only_dangerously_convert_to_ephemeral();
50        let key_pair = agreement::KeyPair::from_private_key(ephemeral_prv_key)?;
51        Ok(Self { key_pair })
52    }
53
54    pub fn get_public_key_jwk(&self) -> Result<Jwk> {
55        Ok(jwcrypto::ec::extract_pub_key_jwk(&self.key_pair)?)
56    }
57
58    pub fn decrypt_keys_jwe(self, jwe: &str) -> Result<String> {
59        let params = DecryptionParameters::ECDH_ES {
60            local_key_pair: self.key_pair,
61        };
62        Ok(jwcrypto::decrypt_jwe(jwe, params)?)
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use jwcrypto::JwkKeyParameters;
70    use rc_crypto::agreement::{KeyPair, PrivateKey};
71
72    #[test]
73    fn test_flow() {
74        nss::ensure_initialized();
75        let x = URL_SAFE_NO_PAD
76            .decode("ARvGIPJ5eIFdp6YTM-INVDqwfun2R9FfCUvXbH7QCIU")
77            .unwrap();
78        let y = URL_SAFE_NO_PAD
79            .decode("hk8gP0Po8nBh-WSiTsvsyesC5c1L6fGOEVuX8FHsvTs")
80            .unwrap();
81        let d = URL_SAFE_NO_PAD
82            .decode("UayD4kn_4QHvLvLLSSaANfDUp9AcQndQu_TohQKoyn8")
83            .unwrap();
84        let ec_key =
85            agreement::EcKey::from_coordinates(agreement::Curve::P256, &d, &x, &y).unwrap();
86        let private_key = PrivateKey::<rc_crypto::agreement::Static>::import(&ec_key).unwrap();
87        let key_pair = KeyPair::from(private_key).unwrap();
88        let flow = ScopedKeysFlow::from_static_key_pair(key_pair).unwrap();
89        let jwk = flow.get_public_key_jwk().unwrap();
90        let ec_key_params = match jwk.key_parameters {
91            JwkKeyParameters::EC(ref ec_key_params) => ec_key_params,
92            _ => unreachable!("test only does EC"),
93        };
94        assert_eq!(ec_key_params.crv, "P-256");
95        assert_eq!(
96            ec_key_params.x,
97            "ARvGIPJ5eIFdp6YTM-INVDqwfun2R9FfCUvXbH7QCIU"
98        );
99        assert_eq!(
100            ec_key_params.y,
101            "hk8gP0Po8nBh-WSiTsvsyesC5c1L6fGOEVuX8FHsvTs"
102        );
103
104        let jwe = "eyJhbGciOiJFQ0RILUVTIiwia2lkIjoiNFBKTTl5dGVGeUtsb21ILWd2UUtyWGZ0a0N3ak9HNHRfTmpYVXhLM1VqSSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IlB3eG9Na1RjSVZ2TFlKWU4wM2R0Y3o2TEJrR0FHaU1hZWlNQ3lTZXEzb2MiLCJ5IjoiLUYtTllRRDZwNUdSQ2ZoYm1hN3NvNkhxdExhVlNub012S0pFcjFBeWlaSSJ9LCJlbmMiOiJBMjU2R0NNIn0..b9FPhjjpmAmo_rP8.ur9jTry21Y2trvtcanSFmAtiRfF6s6qqyg6ruRal7PCwa7PxDzAuMN6DZW5BiK8UREOH08-FyRcIgdDOm5Zq8KwVAn56PGfcH30aNDGQNkA_mpfjx5Tj2z8kI6ryLWew4PGZb-PsL1g-_eyXhktq7dAhetjNYttKwSREWQFokv7N3nJGpukBqnwL1ost-MjDXlINZLVJKAiMHDcu-q7Epitwid2c2JVGOSCJjbZ4-zbxVmZ4o9xhFb2lbvdiaMygH6bPlrjEK99uT6XKtaIZmyDwftbD6G3x4On-CqA2TNL6ILRaJMtmyX--ctL0IrngUIHg_F0Wz94v.zBD8NACkUcZTPLH0tceGnA";
105        let keys = flow.decrypt_keys_jwe(jwe).unwrap();
106        assert_eq!(keys, "{\"https://identity.mozilla.com/apps/oldsync\":{\"kty\":\"oct\",\"scope\":\"https://identity.mozilla.com/apps/oldsync\",\"k\":\"8ek1VNk4sjrNP0DhGC4crzQtwmpoR64zHuFMHb4Tw-exR70Z2SSIfMSrJDTLEZid9lD05-hbA3n2Q4Esjlu1tA\",\"kid\":\"1526414944666-zgTjf5oXmPmBjxwXWFsDWg\"}}");
107    }
108}