ockam_entity/
authentication.rs

1use crate::{EntityError, ProfileVault};
2use ockam_core::compat::vec::Vec;
3use ockam_core::vault::Signature;
4use ockam_core::{Decodable, Encodable, Result};
5use ockam_vault::{PublicKey, Secret};
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Serialize, Deserialize)]
9pub(crate) struct AuthenticationProof {
10    signature: Signature,
11}
12
13impl AuthenticationProof {
14    pub(crate) fn signature(&self) -> &Signature {
15        &self.signature
16    }
17}
18
19impl AuthenticationProof {
20    pub(crate) fn new(signature: Signature) -> Self {
21        AuthenticationProof { signature }
22    }
23}
24
25pub(crate) struct Authentication {}
26
27impl Authentication {
28    pub(crate) async fn generate_proof<V: ProfileVault>(
29        channel_state: &[u8],
30        secret: &Secret,
31        vault: &mut V,
32    ) -> Result<Vec<u8>> {
33        let signature = vault.sign(secret, channel_state).await?;
34
35        let proof = AuthenticationProof::new(signature);
36
37        proof.encode().map_err(|_| EntityError::BareError.into())
38    }
39
40    pub(crate) async fn verify_proof<V: ProfileVault>(
41        channel_state: &[u8],
42        responder_public_key: &PublicKey,
43        proof: &[u8],
44        vault: &mut V,
45    ) -> Result<bool> {
46        let proof = AuthenticationProof::decode(proof).map_err(|_| EntityError::BareError)?;
47
48        vault
49            .verify(proof.signature(), responder_public_key, channel_state)
50            .await
51    }
52}
53
54#[cfg(test)]
55mod test {
56
57    use crate::{Entity, Identity};
58    use ockam_core::{Error, Result};
59    use ockam_node::Context;
60    use ockam_vault_sync_core::Vault;
61    use rand::{thread_rng, RngCore};
62
63    fn test_error<S: Into<String>>(error: S) -> Result<()> {
64        Err(Error::new(0, error))
65    }
66
67    async fn test_auth_use_case(ctx: &Context) -> Result<()> {
68        let alice_vault = Vault::create(ctx).await.expect("failed to create vault");
69        let bob_vault = Vault::create(ctx).await.expect("failed to create vault");
70
71        // Alice and Bob are distinct Entities.
72        let mut alice = Entity::create(ctx, &alice_vault).await?;
73        let mut bob = Entity::create(ctx, &bob_vault).await?;
74
75        // Alice and Bob create unique profiles for a Chat app.
76        let mut alice_chat = alice.create_profile(&alice_vault).await?;
77        let mut bob_chat = bob.create_profile(&bob_vault).await?;
78
79        // Alice and Bob create Contacts
80        let alice_contact = alice_chat.as_contact().await?;
81        let bob_contact = bob_chat.as_contact().await?;
82
83        // Alice and Bob exchange Contacts
84        if !alice_chat
85            .verify_and_add_contact(bob_contact.clone())
86            .await?
87        {
88            return test_error("alice failed to add bob");
89        }
90
91        if !bob_chat
92            .verify_and_add_contact(alice_contact.clone())
93            .await?
94        {
95            return test_error("bob failed to add alice");
96        }
97
98        // Some state known to both parties. In Noise this would be a computed hash, for example.
99        let state = {
100            let mut state = [0u8; 32];
101            let mut rng = thread_rng();
102            rng.fill_bytes(&mut state);
103            state
104        };
105
106        let alice_proof = alice_chat.create_auth_proof(&state).await?;
107        let bob_proof = bob_chat.create_auth_proof(&state).await?;
108
109        if !alice_chat
110            .verify_auth_proof(&state, bob_contact.identifier(), &bob_proof)
111            .await?
112        {
113            return test_error("bob's proof was invalid");
114        }
115
116        if !bob_chat
117            .verify_auth_proof(&state, alice_contact.identifier(), &alice_proof)
118            .await?
119        {
120            return test_error("alice's proof was invalid");
121        }
122        Ok(())
123    }
124
125    async fn test_key_rotation(ctx: &Context) -> Result<()> {
126        let alice_vault = Vault::create(ctx).await.expect("failed to create vault");
127        let bob_vault = Vault::create(ctx).await.expect("failed to create vault");
128
129        // Alice and Bob are distinct Entities.
130        let mut alice = Entity::create(ctx, &alice_vault).await?;
131        let mut bob = Entity::create(ctx, &bob_vault).await?;
132
133        // Alice and Bob create unique profiles for a Chat app.
134        let mut alice_chat = alice.create_profile(&alice_vault).await?;
135        let mut bob_chat = bob.create_profile(&bob_vault).await?;
136
137        // Both profiles rotate keys.
138        alice_chat.rotate_root_secret_key().await?;
139        bob_chat.rotate_root_secret_key().await?;
140
141        // Alice and Bob create Contacts
142        let alice_contact = alice_chat.as_contact().await?;
143        let bob_contact = bob_chat.as_contact().await?;
144
145        // Alice and Bob exchange Contacts. Verification still works with a rotation.
146        if !alice_chat
147            .verify_and_add_contact(bob_contact.clone())
148            .await?
149        {
150            return test_error("alice failed to add bob");
151        }
152
153        if !bob_chat
154            .verify_and_add_contact(alice_contact.clone())
155            .await?
156        {
157            return test_error("bob failed to add alice");
158        }
159
160        Ok(())
161    }
162
163    async fn test_update_contact_and_reprove(ctx: &Context) -> Result<()> {
164        let alice_vault = Vault::create(ctx).await.expect("failed to create vault");
165        let bob_vault = Vault::create(ctx).await.expect("failed to create vault");
166
167        let mut alice = Entity::create(ctx, &alice_vault).await?;
168        let mut bob = Entity::create(ctx, &bob_vault).await?;
169
170        // Alice and Bob create unique profiles for a Chat app.
171        let mut alice_chat = alice.create_profile(&alice_vault).await?;
172        let mut bob_chat = bob.create_profile(&bob_vault).await?;
173
174        // Alice and Bob create Contacts
175        let alice_contact = alice_chat.as_contact().await?;
176        let bob_contact = bob_chat.as_contact().await?;
177
178        // Alice and Bob exchange Contacts
179        if !alice_chat
180            .verify_and_add_contact(bob_contact.clone())
181            .await?
182        {
183            return test_error("alice failed to add bob");
184        }
185
186        if !bob_chat
187            .verify_and_add_contact(alice_contact.clone())
188            .await?
189        {
190            return test_error("bob failed to add alice");
191        }
192
193        // Some state known to both parties. In Noise this would be a computed hash, for example.
194        let state = {
195            let mut state = [0u8; 32];
196            let mut rng = thread_rng();
197            rng.fill_bytes(&mut state);
198            state
199        };
200
201        let alice_proof = alice_chat.create_auth_proof(&state).await?;
202        let bob_proof = bob_chat.create_auth_proof(&state).await?;
203
204        if !alice_chat
205            .verify_auth_proof(&state, bob_contact.identifier(), &bob_proof)
206            .await?
207        {
208            return test_error("bob's proof was invalid");
209        }
210
211        if !bob_chat
212            .verify_auth_proof(&state, alice_contact.identifier(), &alice_proof)
213            .await?
214        {
215            return test_error("alice's proof was invalid");
216        }
217
218        alice_chat.rotate_root_secret_key().await?;
219        bob_chat.rotate_root_secret_key().await?;
220
221        let alice_contact = alice_chat.as_contact().await?;
222        let bob_contact = bob_chat.as_contact().await?;
223
224        // Copy Bob's last event (the rotation) and update Alice's view of Bob's Contact.
225        let bob_last_event = bob_contact.change_events().last().unwrap().clone();
226        if !alice_chat
227            .verify_and_update_contact(bob_contact.identifier(), &[bob_last_event])
228            .await?
229        {
230            return test_error("alice failed to add bob");
231        }
232
233        // Copy Bob's last event (the rotation) and update Bob's view of Alice's Contact.
234        let alice_last_event = alice_contact.change_events().last().unwrap().clone();
235        if !bob_chat
236            .verify_and_update_contact(alice_contact.identifier(), &[alice_last_event])
237            .await?
238        {
239            return test_error("bob failed to add alice");
240        }
241
242        // Re-Prove
243        let state = {
244            let mut state = [0u8; 32];
245            let mut rng = thread_rng();
246            rng.fill_bytes(&mut state);
247            state
248        };
249
250        let alice_proof = alice_chat.create_auth_proof(&state).await?;
251        let bob_proof = bob_chat.create_auth_proof(&state).await?;
252
253        if !alice_chat
254            .verify_auth_proof(&state, bob_contact.identifier(), &bob_proof)
255            .await?
256        {
257            return test_error("bob's proof was invalid");
258        }
259
260        if !bob_chat
261            .verify_auth_proof(&state, alice_contact.identifier(), &alice_proof)
262            .await?
263        {
264            return test_error("alice's proof was invalid");
265        }
266
267        Ok(())
268    }
269
270    #[test]
271    fn authentication_tests() {
272        let (mut ctx, mut exe) = ockam_node::start_node();
273        exe.execute(async move {
274            let mut results = Vec::new();
275
276            // Individual Tests
277            results.push(test_auth_use_case(&ctx).await);
278            results.push(test_key_rotation(&ctx).await);
279            results.push(test_update_contact_and_reprove(&ctx).await);
280
281            // Stop before any assertions, or the panics are lost
282            ctx.stop().await.unwrap();
283
284            for r in results {
285                match r {
286                    Err(e) => panic!("test failure: {}", e),
287                    _ => (),
288                }
289            }
290        })
291        .unwrap();
292    }
293}