-
Notifications
You must be signed in to change notification settings - Fork 14
[PM-24127] Implement password-protected key envelope #335
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
bc5900b
2e905c4
b41f5ef
eb70dfc
1e2e95c
68be6e6
d6a18a4
6e9e526
50d8f70
e5bd251
b92010b
9635098
b8056c2
9440825
f8cb804
e6939e6
2a202c1
ba9e2c3
a42b1e7
5447cbb
66c345f
9b2f6c9
88b96b7
7e69b7b
b7d2bc8
acb6d12
723ec63
36d8de9
33da07b
2861a9a
1681df9
0cd85fa
f91f0b8
34ee00e
485b6d8
f122ff0
f22531c
ba10631
69d8c57
8833146
47ced5a
42dccb0
9d0ea55
a3ed9d6
9eceb32
f30c3ce
5f4dc3a
7b17ca3
38c8945
b5dd862
47c7764
b009c81
c9f6111
b9b0f6e
67dd5e9
7635cd0
ec14e51
75be6db
6d8bb8f
38b8958
9f334fb
02cc7b3
f145eb3
99d909c
d47e8c0
1920a49
625b830
24f1431
9eb4ff8
916a46e
8722077
670c6a7
6da4a0b
4e5510b
b00f48b
bf9f8c4
235f3bc
1953ba3
41bb1ad
c41e513
90d2295
de8f957
f6ad513
3c2984b
bdc90b3
80dde40
1f30896
1817ae0
416dbf5
aaad503
2e21906
dbdee15
375fd0d
fd22ded
7ee0278
9b3549a
a5abaae
81138ea
4445aec
25d1907
0d0f59b
e3ee279
bf7bc82
1da0173
87f87a6
15ffca9
144620f
53aeeee
81b5a15
9427634
d1f8029
7f52fb0
53c528b
c51e779
6c8092a
954a6a5
948baa5
b2f5211
f0b6ec5
390463c
a2e243e
616786e
b913762
120f8c6
b1a615a
25700ef
690c6df
fabee16
b81de59
1fef06f
b85806a
7848fa6
32aacd7
6fd28c2
d601b0c
6b11472
b8198bf
0892687
3e03ec3
9345408
6fb4e97
f685b35
8ff72f3
e65a3d4
3355c82
10f97df
06baa9e
6414e1d
9c78d35
01a2903
2c29cad
8e911af
d9c70f7
fab6509
17cdef1
a8f5353
eb6bff0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
//! Structs with generic parameters cannot be moved across FFI bounds (uniffi/wasm). | ||
//! This module contains wrapper structs that hide the generic parameter with instantiated versions. | ||
|
||
use std::ops::Deref; | ||
|
||
use serde::{Deserialize, Serialize}; | ||
#[cfg(feature = "wasm")] | ||
use tsify::Tsify; | ||
|
||
use crate::key_management::KeyIds; | ||
|
||
/// A non-generic wrapper around `bitwarden-crypto`'s `PasswordProtectedKeyEnvelope`. | ||
#[derive(Serialize, Deserialize)] | ||
#[serde(transparent)] | ||
#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] | ||
pub struct PasswordProtectedKeyEnvelope( | ||
#[cfg_attr( | ||
feature = "wasm", | ||
tsify(type = r#"Tagged<string, "PasswordProtectedKeyEnvelope">"#) | ||
)] | ||
pub(crate) bitwarden_crypto::safe::PasswordProtectedKeyEnvelope<KeyIds>, | ||
); | ||
|
||
impl Deref for PasswordProtectedKeyEnvelope { | ||
type Target = bitwarden_crypto::safe::PasswordProtectedKeyEnvelope<KeyIds>; | ||
|
||
fn deref(&self) -> &Self::Target { | ||
&self.0 | ||
} | ||
} | ||
|
||
impl std::fmt::Debug for PasswordProtectedKeyEnvelope { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
self.0.fmt(f) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
//! This example demonstrates how to securely protect keys with a password using the | ||
//! [PasswordProtectedKeyEnvelope]. | ||
|
||
use bitwarden_crypto::{ | ||
key_ids, | ||
safe::{PasswordProtectedKeyEnvelope, PasswordProtectedKeyEnvelopeError}, | ||
KeyStore, KeyStoreContext, | ||
}; | ||
|
||
fn main() { | ||
let key_store = KeyStore::<ExampleIds>::default(); | ||
let mut ctx: KeyStoreContext<'_, ExampleIds> = key_store.context_mut(); | ||
let mut disk = MockDisk::new(); | ||
|
||
// Alice wants to protect a key with a password. | ||
// For example to: | ||
// - Protect her vault with a pin | ||
// - Protect her exported vault with a password | ||
// - Protect a send with a URL fragment secret | ||
// For this, the `PasswordProtectedKeyEnvelope` is used. | ||
|
||
// Alice has a vault protected with a symmetric key. She wants the symmetric key protected with | ||
// a PIN. | ||
let vault_key = ctx | ||
.generate_symmetric_key(ExampleSymmetricKey::VaultKey) | ||
.expect("Generating vault key should work"); | ||
|
||
// Seal the key with the PIN | ||
// The KDF settings are chosen for you, and do not need to be separately tracked or synced | ||
// Next, store this protected key envelope on disk. | ||
let pin = "1234"; | ||
let envelope = | ||
PasswordProtectedKeyEnvelope::seal(vault_key, pin, &ctx).expect("Sealing should work"); | ||
disk.save("vault_key_envelope", (&envelope).into()); | ||
|
||
// Wipe the context to simulate new session | ||
ctx.clear_local(); | ||
|
||
// Load the envelope from disk and unseal it with the PIN, and store it in the context. | ||
let deserialized: PasswordProtectedKeyEnvelope<ExampleIds> = | ||
PasswordProtectedKeyEnvelope::try_from( | ||
disk.load("vault_key_envelope") | ||
.expect("Loading from disk should work"), | ||
) | ||
.expect("Deserializing envelope should work"); | ||
deserialized | ||
.unseal(ExampleSymmetricKey::VaultKey, pin, &mut ctx) | ||
.expect("Unsealing should work"); | ||
|
||
// Alice wants to change her password; also her KDF settings are below the minimums. | ||
// Re-sealing will update the password, and KDF settings. | ||
let envelope = envelope | ||
.reseal(pin, "0000") | ||
.expect("The password should be valid"); | ||
disk.save("vault_key_envelope", (&envelope).into()); | ||
|
||
// Alice wants to change the protected key. This requires creating a new envelope | ||
ctx.generate_symmetric_key(ExampleSymmetricKey::VaultKey) | ||
.expect("Generating vault key should work"); | ||
let envelope = PasswordProtectedKeyEnvelope::seal(ExampleSymmetricKey::VaultKey, "0000", &ctx) | ||
Thomas-Avery marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.expect("Sealing should work"); | ||
disk.save("vault_key_envelope", (&envelope).into()); | ||
|
||
// Alice tries the password but it is wrong | ||
assert!(matches!( | ||
envelope.unseal(ExampleSymmetricKey::VaultKey, "9999", &mut ctx), | ||
Err(PasswordProtectedKeyEnvelopeError::WrongPassword) | ||
)); | ||
} | ||
|
||
pub(crate) struct MockDisk { | ||
map: std::collections::HashMap<String, Vec<u8>>, | ||
} | ||
|
||
impl MockDisk { | ||
pub(crate) fn new() -> Self { | ||
MockDisk { | ||
map: std::collections::HashMap::new(), | ||
} | ||
} | ||
|
||
pub(crate) fn save(&mut self, key: &str, value: Vec<u8>) { | ||
self.map.insert(key.to_string(), value); | ||
} | ||
|
||
pub(crate) fn load(&self, key: &str) -> Option<&Vec<u8>> { | ||
self.map.get(key) | ||
} | ||
} | ||
|
||
key_ids! { | ||
#[symmetric] | ||
pub enum ExampleSymmetricKey { | ||
#[local] | ||
VaultKey | ||
} | ||
|
||
#[asymmetric] | ||
pub enum ExampleAsymmetricKey { | ||
Key(u8), | ||
} | ||
|
||
#[signing] | ||
pub enum ExampleSigningKey { | ||
Key(u8), | ||
} | ||
|
||
pub ExampleIds => ExampleSymmetricKey, ExampleAsymmetricKey, ExampleSigningKey; | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: Are we planning on implementing more safe interfaces, and what decides if something is safe or not? Is everything else in the care unsafe? Such as decrypting I suppose the goal is to ask people to use the higher level abstractions here rather than rolling their own using primitives? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, the goal here is higher-level abstractions with the general goals of:
Each of these should have an easy to understand usage story associated with it. "I want to protect a key, and have a password. That is done with a PasswordProtectedKeyEnvelope". The next safe interface I thought about proposing - it is still draft / RFC - is DataEnvelope: #336, with the usage story: "I have a struct of data and want to protect it. That is done with a DataEnvelope, and I receive back a sealed envelope and an envelope key". There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's worth adding a reference to the
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Bitwarden-crypto safe module | ||
|
||
The safe module provides high-level cryptographic tools for building secure protocols and features. | ||
When developing new features, use this module first before considering lower-level primitives from | ||
other parts of `bitwarden-crypto`. | ||
|
||
## Password-protected key envelope | ||
|
||
Use the password protected key envelope to protect a symmetric key with a password. Examples | ||
include: | ||
|
||
- locking a vault with a PIN/Password | ||
- protecting exports with a password | ||
|
||
Internally, the module uses a KDF to protect against brute-forcing, but it does not expose this to | ||
the consumer. The consumer only provides a password and key. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
#![doc = include_str!("./README.md")] | ||
|
||
mod password_protected_key_envelope; | ||
pub use password_protected_key_envelope::*; |
Uh oh!
There was an error while loading. Please reload this page.