Skip to content

Commit a2e243e

Browse files
committed
Implement password-protected key envelope
1 parent 390463c commit a2e243e

File tree

8 files changed

+572
-2
lines changed

8 files changed

+572
-2
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//! This example demonstrates how to securely protect keys with a password using the [PasswordProtectedKeyEnvelope].
2+
3+
use bitwarden_crypto::{
4+
key_ids, KeyStore, KeyStoreContext, PasswordProtectedKeyEnvelope,
5+
PasswordProtectedKeyEnvelopeError,
6+
};
7+
8+
fn main() {
9+
let key_story = KeyStore::<ExampleIds>::default();
10+
let mut ctx: KeyStoreContext<'_, ExampleIds> = key_story.context_mut();
11+
let mut disk = MockDisk::new();
12+
13+
// Alice wants to protect a key with a password.
14+
// For example to:
15+
// - Protect her vault with a pin
16+
// - Protect her exported vault with a password
17+
// - Protect a send with a URL fragment secret
18+
// For this, the `PasswordProtectedKeyEnvelope` is used.
19+
20+
// Alice has a vault protected with a symmetric key. She wants this protected with a PIN.
21+
let vault_key = ctx
22+
.generate_symmetric_key(ExampleSymmetricKey::VaultKey)
23+
.unwrap();
24+
25+
// Seal the key with the PIN
26+
// The KDF settings are chosen for you, and do not need to be separately tracked or synced
27+
// Next, story this protected key envelope on disk.
28+
let pin = "1234";
29+
let envelope =
30+
PasswordProtectedKeyEnvelope::seal(vault_key, pin, &ctx).expect("Sealing should work");
31+
disk.save("vault_key_envelope", (&envelope).try_into().unwrap());
32+
33+
// Wipe the context to simulate new session
34+
ctx.clear_local();
35+
36+
// Load the envelope from disk and unseal it with the PIN, and store it in the context.
37+
let deserialized: PasswordProtectedKeyEnvelope<ExampleIds> =
38+
PasswordProtectedKeyEnvelope::try_from(disk.load("vault_key_envelope").unwrap()).unwrap();
39+
deserialized
40+
.unseal(ExampleSymmetricKey::VaultKey, pin, &mut ctx)
41+
.unwrap();
42+
43+
// Alice wants to change her password; also her KDF settings are below the minimums.
44+
// Re-sealing will update the password, and KDF settings.
45+
let envelope = envelope
46+
.reseal(pin, "0000")
47+
.expect("The password should be valid");
48+
disk.save("vault_key_envelope", (&envelope).try_into().unwrap());
49+
50+
// Alice wants to change the protected key. This requires creating a new envelope
51+
ctx.generate_symmetric_key(ExampleSymmetricKey::VaultKey)
52+
.unwrap();
53+
let envelope = PasswordProtectedKeyEnvelope::seal(ExampleSymmetricKey::VaultKey, "0000", &ctx)
54+
.expect("Sealing should work");
55+
disk.save("vault_key_envelope", (&envelope).try_into().unwrap());
56+
57+
// Alice tries the password but it is wrong
58+
assert!(matches!(
59+
envelope.unseal(ExampleSymmetricKey::VaultKey, "9999", &mut ctx),
60+
Err(PasswordProtectedKeyEnvelopeError::WrongPassword)
61+
));
62+
}
63+
64+
pub(crate) struct MockDisk {
65+
map: std::collections::HashMap<String, Vec<u8>>,
66+
}
67+
68+
impl MockDisk {
69+
pub(crate) fn new() -> Self {
70+
MockDisk {
71+
map: std::collections::HashMap::new(),
72+
}
73+
}
74+
75+
pub(crate) fn save(&mut self, key: &str, value: Vec<u8>) {
76+
self.map.insert(key.to_string(), value);
77+
}
78+
79+
pub(crate) fn load(&self, key: &str) -> Option<&Vec<u8>> {
80+
self.map.get(key)
81+
}
82+
}
83+
84+
key_ids! {
85+
#[symmetric]
86+
pub enum ExampleSymmetricKey {
87+
#[local]
88+
VaultKey
89+
}
90+
91+
#[asymmetric]
92+
pub enum ExampleAsymmetricKey {
93+
Key(u8),
94+
}
95+
96+
#[signing]
97+
pub enum ExampleSigningKey {
98+
Key(u8),
99+
}
100+
101+
pub ExampleIds => ExampleSymmetricKey, ExampleAsymmetricKey, ExampleSigningKey;
102+
}

crates/bitwarden-crypto/src/cose.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,16 @@ use crate::{
2222
pub(crate) const XCHACHA20_POLY1305: i64 = -70000;
2323
const XCHACHA20_TEXT_PAD_BLOCK_SIZE: usize = 32;
2424

25+
pub(crate) const ALG_ARGON2ID13: i64 = -71000;
26+
pub(crate) const ARGON2_SALT: i64 = -71001;
27+
pub(crate) const ARGON2_ITERATIONS: i64 = -71002;
28+
pub(crate) const ARGON2_MEMORY: i64 = -71003;
29+
pub(crate) const ARGON2_PARALLELISM: i64 = -71004;
30+
2531
// Note: These are in the "unregistered" tree: https://datatracker.ietf.org/doc/html/rfc6838#section-3.4
2632
// These are only used within Bitwarden, and not meant for exchange with other systems.
2733
const CONTENT_TYPE_PADDED_UTF8: &str = "application/x.bitwarden.utf8-padded";
28-
const CONTENT_TYPE_BITWARDEN_LEGACY_KEY: &str = "application/x.bitwarden.legacy-key";
34+
pub(crate) const CONTENT_TYPE_BITWARDEN_LEGACY_KEY: &str = "application/x.bitwarden.legacy-key";
2935
const CONTENT_TYPE_SPKI_PUBLIC_KEY: &str = "application/x.bitwarden.spki-public-key";
3036

3137
// Labels

crates/bitwarden-crypto/src/keys/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ mod symmetric_crypto_key;
99
#[cfg(test)]
1010
pub use symmetric_crypto_key::derive_symmetric_key;
1111
pub use symmetric_crypto_key::{
12-
Aes256CbcHmacKey, Aes256CbcKey, SymmetricCryptoKey, XChaCha20Poly1305Key,
12+
Aes256CbcHmacKey, Aes256CbcKey, EncodedSymmetricKey, SymmetricCryptoKey, XChaCha20Poly1305Key,
1313
};
1414
mod asymmetric_crypto_key;
1515
pub use asymmetric_crypto_key::{

crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ impl From<EncodedSymmetricKey> for Vec<u8> {
407407
}
408408
}
409409
impl EncodedSymmetricKey {
410+
/// Returns the content format of the encoded symmetric key.
410411
#[allow(private_interfaces)]
411412
pub fn content_format(&self) -> ContentFormat {
412413
match self {

crates/bitwarden-crypto/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ mod store;
3434
pub use store::{KeyStore, KeyStoreContext};
3535
mod cose;
3636
pub use cose::CoseSerializable;
37+
mod safe;
38+
pub use safe::*;
3739
mod signing;
3840
pub use signing::*;
3941
mod traits;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
mod password_protected_key_envelope;
2+
pub use password_protected_key_envelope::*;

0 commit comments

Comments
 (0)