-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
server: Add SecretExchange implementation
SecretExchange allows exchange of secrets between two processes on the same system without exposing those secrets. See https://gnome.pages.gitlab.gnome.org/gcr/gcr-4/class.SecretExchange.html Signed-off-by: Dhanuka Warusadura <[email protected]>
- Loading branch information
1 parent
01e5ee1
commit db19706
Showing
5 changed files
with
186 additions
and
2 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
mod secret_exchange; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
// SecretExchange: Exchange secrets between processes in an unexposed way. | ||
|
||
// Initial C implementation: https://gitlab.gnome.org/GNOME/gcr/-/blob/master/gcr/gcr-secret-exchange.c | ||
|
||
// The initial implementation of SecretExchange/GCRSecretExchange uses a KeyFile | ||
// to encode/parse the payload. In this implementation the payload is based | ||
// on a HashMap. | ||
// Before any transit operations the payload is base64 encoded and parsed into a | ||
// String. | ||
|
||
use std::collections::HashMap; | ||
|
||
use base64::prelude::*; | ||
use oo7::{crypto, Key}; | ||
use zeroize::Zeroizing; | ||
|
||
const SECRET: &str = "secret"; | ||
const PUBLIC: &str = "public"; | ||
const PRIVATE: &str = "private"; | ||
const IV: &str = "iv"; | ||
const PROTOCOL: &str = "[sx-aes-1]\n"; | ||
const CIPHER_TEXT_LEN: usize = 16; | ||
|
||
#[derive(Debug)] | ||
pub struct SecretExchange { | ||
private_key: Key, | ||
public_key: Key, | ||
} | ||
|
||
impl SecretExchange { | ||
// Creates the initial payload containing caller public_key | ||
pub fn begin(&self) -> String { | ||
let map = HashMap::from([(PUBLIC, self.public_key.as_ref())]); | ||
|
||
encode(&map) | ||
} | ||
|
||
// Creates the shared secret: an AES key | ||
pub fn create_shared_secret(&self, exchange: &str) -> Result<String, crypto::Error> { | ||
let decoded = decode(exchange) | ||
.expect("SecretExchange decode error: failed to decode exchange string"); | ||
let server_public_key = Key::new( | ||
decoded | ||
.get(PUBLIC) | ||
.expect("SecretExchange decode error: PUBLIC parameter is empty") | ||
.to_vec(), | ||
); | ||
// Above two calls should never fail during SecretExchange | ||
let aes_key = | ||
Key::generate_aes_key_for_secret_exchange(&self.private_key, &server_public_key)?; | ||
let map = HashMap::from([(PRIVATE, aes_key.as_ref())]); | ||
|
||
Ok(encode(&map)) | ||
} | ||
|
||
pub fn new() -> Result<Self, crypto::Error> { | ||
let private_key = Key::generate_private_key()?; | ||
let public_key = Key::generate_public_key_for_secret_exchange(&private_key)?; | ||
|
||
Ok(Self { | ||
private_key, | ||
public_key, | ||
}) | ||
} | ||
} | ||
|
||
// Converts a HashMap into a payload String | ||
fn encode(map: &HashMap<&str, &[u8]>) -> String { | ||
let mut exchange = map | ||
.iter() | ||
.map(|(key, value)| format!("{}={}", key, BASE64_STANDARD.encode(value))) | ||
.collect::<Vec<_>>() | ||
.join("\n"); | ||
exchange.insert_str(0, PROTOCOL); // Add PROTOCOL prefix | ||
|
||
exchange | ||
} | ||
|
||
// Converts a payload String into a HashMap | ||
fn decode(exchange: &str) -> Option<HashMap<&str, Vec<u8>>> { | ||
let (_, exchange) = exchange.split_once(PROTOCOL)?; // Remove PROTOCOL prefix | ||
let pairs = exchange.split("\n").collect::<Vec<_>>(); | ||
let mut map: HashMap<&str, Vec<u8>> = HashMap::new(); | ||
|
||
for pair in pairs { | ||
if pair.is_empty() { | ||
// To avoid splitting an empty line (last new line) | ||
break; | ||
} | ||
let (key, value) = pair.split_once("=")?; | ||
let encoded = BASE64_STANDARD.decode(value).unwrap_or(vec![]); | ||
if encoded.is_empty() { | ||
return None; | ||
} | ||
map.insert(key, encoded); | ||
} | ||
|
||
Some(map) | ||
} | ||
|
||
// Retrieves the secret from final secret exchange string | ||
pub(crate) fn retrieve_secret(exchange: &str, aes_key: &str) -> Option<Zeroizing<Vec<u8>>> { | ||
let decoded = decode(exchange)?; | ||
|
||
// If we cancel an ongoing prompt call, the final exchange won't have the secret | ||
// or IV. The following is to avoid `Option::unwrap()` on a `None` value | ||
let secret = decoded.get(SECRET)?; | ||
|
||
if secret.len() != CIPHER_TEXT_LEN { | ||
// To avoid a short secret/cipher-text causing an UnpadError during decryption | ||
let false_secret: Vec<u8> = vec![0, 1]; | ||
return Some(Zeroizing::new(false_secret)); | ||
} | ||
|
||
let iv = decoded.get(IV)?; | ||
let decoded = decode(aes_key)?; | ||
let aes_key = Key::new(decoded.get(PRIVATE)?.to_vec()); | ||
|
||
match crypto::decrypt(secret, &aes_key, iv) { | ||
Ok(decrypted) => Some(decrypted), | ||
Err(err) => { | ||
tracing::error!("Failed to do crypto decrypt: {}", err); | ||
None | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_retrieve_secret() { | ||
let exchange = "[sx-aes-1] | ||
public=/V6FpknNXlOGJwPqXtN0RaED2bS5JyYbftv7WbD0gWiVTMoNgxkAuOX2g+zUO/4TdfBJ6viPRcNdYV+KcxskGvhYouFXs+IgKqNO0MF0CNnWra1I6G56SM4Bgstkx9M5J+1f83l/BTAxlLsAppeLkqEEVSQoy9jXhPOrl5XlIzF2DvriYh+FInB7SFz4VzE3KVq40p7tA9+iAVQg1o9qkQHLazFb1DfbWRgvhDVhwNkk1fIlepIeM426gdmHIAxP | ||
secret=DBeLBvEgGuGygDm+XnkxyQ== | ||
iv=8e3N+gx553PgQlfTKRK3JA=="; | ||
|
||
let aes_key = "[sx-aes-1] | ||
private=zDWLKDent/C//LquHCTlGg=="; | ||
|
||
let decrypted = retrieve_secret(exchange, aes_key).unwrap(); | ||
assert_eq!(b"password".to_vec(), decrypted.to_vec()); | ||
} | ||
|
||
#[test] | ||
fn test_secret_exchange() { | ||
let peer_1 = SecretExchange::new().unwrap(); | ||
let peer_1_exchange = peer_1.begin(); | ||
let peer_2 = SecretExchange::new().unwrap(); | ||
let peer_2_exchange = peer_2.begin(); | ||
let peer_1_aes_key = peer_1.create_shared_secret(&peer_2_exchange).unwrap(); | ||
let peer_2_aes_key = peer_2.create_shared_secret(&peer_1_exchange).unwrap(); | ||
|
||
let decoded_pub = decode(&peer_2_exchange).unwrap(); | ||
let pub_key = Key::new(decoded_pub.get(PUBLIC).unwrap().to_vec()); | ||
|
||
let decoded_aes = decode(&peer_2_aes_key).unwrap(); | ||
let aes_key = Key::new(decoded_aes.get(PRIVATE).unwrap().to_vec()); | ||
let iv = crypto::generate_iv().unwrap(); | ||
let encrypted = crypto::encrypt(b"password".to_vec(), &aes_key, &iv).unwrap(); | ||
|
||
let map = HashMap::from([ | ||
(PUBLIC, pub_key.as_ref()), | ||
(SECRET, encrypted.as_ref()), | ||
(IV, iv.as_ref()), | ||
]); | ||
|
||
let final_exchange = encode(&map); | ||
|
||
let decrypted = retrieve_secret(&final_exchange, &peer_1_aes_key).unwrap(); | ||
assert_eq!(b"password".to_vec(), decrypted.to_vec()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
mod collection; | ||
mod error; | ||
mod gnome; | ||
mod item; | ||
mod prompt; | ||
mod service; | ||
|