zkgroup/api/profiles/
profile_key.rs

1//
2// Copyright 2020 Signal Messenger, LLC.
3// SPDX-License-Identifier: AGPL-3.0-only
4//
5
6use aes::cipher::{BlockEncrypt as _, KeyInit as _};
7use partial_default::PartialDefault;
8use serde::{Deserialize, Serialize};
9use subtle::ConstantTimeEq;
10
11use crate::common::constants::*;
12use crate::common::sho::*;
13use crate::common::simple_types::*;
14use crate::{api, crypto};
15
16#[derive(Copy, Clone, Serialize, Deserialize, PartialDefault)]
17pub struct ProfileKey {
18    pub bytes: ProfileKeyBytes,
19}
20
21impl std::fmt::Debug for ProfileKey {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        f.debug_struct("ProfileKey")
24            .field("bytes", &zkcredential::PrintAsHex(self.bytes.as_slice()))
25            .finish()
26    }
27}
28
29impl PartialEq for ProfileKey {
30    fn eq(&self, other: &Self) -> bool {
31        self.bytes.as_slice().ct_eq(other.bytes.as_slice()).into()
32    }
33}
34
35impl ProfileKey {
36    pub fn generate(randomness: RandomnessBytes) -> Self {
37        let mut sho = Sho::new(
38            b"Signal_ZKGroup_20200424_Random_ProfileKey_Generate",
39            &randomness,
40        );
41        let bytes = sho.squeeze_as_array();
42        Self { bytes }
43    }
44
45    pub fn create(bytes: ProfileKeyBytes) -> Self {
46        Self { bytes }
47    }
48
49    pub fn get_bytes(&self) -> ProfileKeyBytes {
50        self.bytes
51    }
52
53    pub fn get_commitment(
54        &self,
55        user_id: libsignal_core::Aci,
56    ) -> api::profiles::ProfileKeyCommitment {
57        let uid_bytes = uuid::Uuid::from(user_id).into_bytes();
58        let profile_key = crypto::profile_key_struct::ProfileKeyStruct::new(self.bytes, uid_bytes);
59        let commitment =
60            crypto::profile_key_commitment::CommitmentWithSecretNonce::new(profile_key, uid_bytes);
61        api::profiles::ProfileKeyCommitment {
62            reserved: Default::default(),
63            commitment: commitment.get_profile_key_commitment(),
64        }
65    }
66
67    pub fn derive_access_key(&self) -> [u8; ACCESS_KEY_LEN] {
68        // Uses AES to implement a seeded PRNG, taking the first block of output as the result.
69        // Originally defined as AES-GCM(&mut [0; ACCESS_KEY_LEN], [0; NONCE_LEN], init_ctr=1).
70        // AES-GCM uses the first block to initialize its tag hash, which we discard in this case,
71        // so we can simplify to AES-CTR(&mut [0; ACCESS_KEY_LEN], [0; NONCE_LEN], init_ctr=2)
72        // and then since our "plaintext" is zeros, this becomes simply raw AES([0, 0, ..., 2]).
73        static_assertions::const_assert_eq!(ACCESS_KEY_LEN, {
74            type BlockSize = <::aes::Aes256Enc as ::aes::cipher::BlockSizeUser>::BlockSize;
75            <BlockSize as ::aes::cipher::Unsigned>::USIZE
76        });
77        let aes = ::aes::Aes256Enc::new((&self.bytes).into());
78        let mut buf = [0u8; ACCESS_KEY_LEN];
79        buf[ACCESS_KEY_LEN - 1] = 2;
80        aes.encrypt_block((&mut buf).into());
81        buf
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn access_key_kat() {
91        // Pairs of profile keys and derived access keys
92        let kat = [
93            (
94                [
95                    0xb9, 0x50, 0x42, 0xa2, 0xc2, 0xd9, 0xe5, 0xb3, 0xbb, 0x9, 0x30, 0xe, 0xe4,
96                    0x8, 0xa1, 0x72, 0xfa, 0xcd, 0x96, 0xe9, 0x1b, 0x50, 0x4e, 0x4, 0x3a, 0x5a,
97                    0x2, 0x3d, 0xc4, 0xcf, 0xf3, 0x59,
98                ],
99                [
100                    0x24, 0xfb, 0x96, 0xd4, 0xa5, 0xe3, 0x33, 0xe9, 0xd4, 0x45, 0x12, 0x5, 0xb9,
101                    0xe2, 0xfa, 0xed,
102                ],
103            ),
104            (
105                [
106                    0x26, 0x19, 0x7b, 0x17, 0xe5, 0xa2, 0xc3, 0x6d, 0x8c, 0x95, 0x18, 0xc3, 0x53,
107                    0x58, 0xf1, 0x23, 0xc4, 0x76, 0x0, 0xd, 0xb6, 0xda, 0x75, 0x65, 0xc0, 0xd4,
108                    0x1f, 0x66, 0x74, 0x46, 0x2c, 0x4d,
109                ],
110                [
111                    0xe8, 0x95, 0xc3, 0xc, 0xf7, 0x80, 0x75, 0x7d, 0x22, 0xf7, 0xa1, 0x79, 0x70,
112                    0x8b, 0x14, 0xa1,
113                ],
114            ),
115        ];
116
117        for (pk, ak) in kat {
118            let profile_key = ProfileKey::create(pk);
119            let access_key = profile_key.derive_access_key();
120            assert_eq!(ak, access_key);
121        }
122    }
123}