Hello,
My name is Abe, and I'm one of the developers behind osvauld, an open-source credentials manager for developers (
github ||
website). We're building our UI as a Chrome extension and utilizing WASM and Sequoia PGP for cryptographic operations (
wasm repo).
We'd greatly appreciate your expertise on a couple of key management challenges we're facing.
Our current Setup:
Initial Key Generation:
When a user first logs in, they set up a passphrase. We use this
passphrase to secure their certificate within the extension's local
storage:```
let (cert, _revocation) = CertBuilder::new()
.add_userid("
someone@example.org")
.set_cipher_suite(CipherSuite::Cv25519) // This specifies ECC keys with Curve25519
.add_subkey(flags, None, None)
.set_password(Some(passphrase))
.generate()?;
```
Logging In:
Now when the user logs in the next time using the passphrase, passphrase and protected certificates are given to wasm it decrypts the secret key material and stores that in memory.
```
let cert = Cert::from_bytes(&private_key_bytes)?;
let p = &StandardPolicy::new();
let keypair =cert.keys()
.with_policy(p, None)
.secret()
.for_storage_encryption()
.nth(0)
.ok_or_else(|| "No suitable key found in Cert.")?
.key()
.clone()
let password = Password::from(password);
let decrypted_keypair = keypair.decrypt_secret(&password)?;
```
Decryption:
When user requests for decryption
```
pub fn decrypt_message(
p: &dyn Policy,
sk: &Key<openpgp::packet::key::SecretParts, openpgp::packet::key::UnspecifiedRole>,
ciphertext: &[u8],
) -> openpgp::Result<Vec<u8>> {
let helper = Helper {
secret: &sk,
policy: p,
};
let mut decryptor = DecryptorBuilder::from_bytes(ciphertext)?.with_policy(p, None, helper)?;
let mut plaintext = Cursor::new(Vec::new());
std::io::copy(&mut decryptor, &mut plaintext)?;
let plaintext = plaintext.into_inner();
Ok(plaintext)
}
struct Helper<'a> {
secret: &'a Key<openpgp::packet::key::SecretParts, openpgp::packet::key::UnspecifiedRole>,
policy: &'a dyn Policy,
}
impl<'a> openpgp::parse::stream::DecryptionHelper for Helper<'a> {
fn decrypt<D>(
&mut self,
pkesks: &[openpgp::packet::PKESK],
_skesks: &[openpgp::packet::SKESK],
sym_algo: Option<openpgp::types::SymmetricAlgorithm>,
mut decrypt: D,
) -> openpgp::Result<Option<openpgp::Fingerprint>>
where
D: FnMut(openpgp::types::SymmetricAlgorithm, &openpgp::crypto::SessionKey) -> bool,
{
// The secret key is already decrypted.
let mut pair = KeyPair::from(self.secret.clone().into_keypair()?);
pkesks[0]
.decrypt(&mut pair, sym_algo)
.map(|(algo, session_key)| decrypt(algo, &session_key));
Ok(None)
}
}
```
We need your advice on handling these scenarios:
- Certificate Export:
- We would like to
provide users with the ability to export their unprotected certificate.
Could you please advise us on how this can be achieved using the Sequoia
PGP library?
- Password Change:
- In case a user needs to change their password, how can we implement this functionality within our current setup?
- If directly changing
the password is not feasible, would it be safe to use (KDF) to encrypt the unprotected certificate with the new
password?
- Session Key
Management and Extension Constraints:
Due to the limitations imposed by Chrome extension manifest v3, we are
unable to securely store decrypted private keys in memory for extended
periods. To address this, we are considering a hybrid approach:
- Encrypt the decrypted certificate (secret key material) with a session key and store the encrypted version locally.
- Store the session key securely on our server and fetch it using a user-specific token when needed.
Our questions regarding this approach are:
- Does the Sequoia PGP
library provide any built-in functionality or provisions that can
assist us in implementing this session key management scheme?
- Could you offer any suggestions or best practices on how to securely implement this approach within the Sequoia framework?
- Are there any alternative solutions or architectures you would recommend for managing session keys in our specific use case?
Thank you for your time and for the tremendous work on Sequoia PGP!
Sincerely,
Abe