Created
December 13, 2023 23:03
-
-
Save ronanyeah/e771ece48ee2ca10e8cd8991f8d70d51 to your computer and use it in GitHub Desktop.
Switchboard Function Secrets
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
[package] | |
name = "secrets_compile" | |
version = "0.1.0" | |
edition = "2021" | |
[dependencies] | |
aes-gcm = "0.10.3" | |
base64 = "0.21.5" | |
rand = "0.8.5" | |
reqwest = "0.11.22" | |
rsa = "0.9.6" | |
serde = "1.0.193" | |
serde_json = "1.0.108" | |
switchboard-solana = "0.29.73" | |
[patch.crates-io] | |
# https://github.com/solana-labs/solana/issues/26688#issuecomment-1841629159 | |
aes-gcm-siv = { git = "https://github.com/RustCrypto/AEADs", rev = "6105d7a5591aefa646a95d12b5e8d3f55a9214ef" } | |
curve25519-dalek = { git = "https://github.com/dalek-cryptography/curve25519-dalek", rev = "8274d5cbb6fc3f38cdc742b4798173895cd2a290" } |
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
use base64::Engine; | |
use std::collections::HashMap; | |
use aes_gcm::{aead::Aead, Aes256Gcm, Key, KeyInit, Nonce}; | |
use rand::rngs::OsRng; | |
use reqwest; | |
use rsa::pkcs8::EncodePublicKey; | |
use rsa::{RsaPrivateKey, RsaPublicKey}; | |
use serde::Deserialize; | |
use serde_json::json; | |
use switchboard_solana::*; | |
/// Represents encrypted data containing a key, nonce, and data. | |
/// | |
/// This structure holds information necessary for decrypting an AES-encrypted payload. | |
#[derive(Debug, Clone, Eq, PartialEq, Deserialize)] | |
struct EncryptedData { | |
/// A base64 encoded string containing the key used to decrypt the `data`. | |
/// | |
/// This key is itself encrypted with the request's public key and can be decrypted using the | |
/// corresponding private key. | |
key: String, | |
/// An AES nonce needed to decrypt the `data`. | |
/// | |
/// This value is used alongside the key to ensure secure decryption. | |
nonce: String, | |
/// The response payload that has been encrypted with AES. | |
/// | |
/// This data can be of any type, but using a binary format is recommended for efficiency. | |
data: String, | |
} | |
#[derive(Debug, Clone, Eq, PartialEq, Deserialize)] | |
pub struct SwitchboardSecret { | |
pub secrets: HashMap<String, String>, | |
} | |
fn handle_reqwest_err(e: reqwest::Error) -> SbError { | |
let status = e.status().unwrap_or(reqwest::StatusCode::default()); | |
println!( | |
"reqwest_error: code = {}, message = {}", | |
status, | |
status.canonical_reason().unwrap_or("Unknown") | |
); | |
SbError::CustomError { | |
message: format!( | |
"reqwest_error: code = {}, message = {}", | |
status, | |
status.canonical_reason().unwrap_or("Unknown") | |
), | |
source: std::sync::Arc::new(e), | |
} | |
} | |
impl SwitchboardSecret { | |
/// Fetch all of a user's secrets that have been whitelisted for the currently running mrEnclave | |
/// value. | |
pub async fn fetch(user_pubkey: &str) -> Result<Self, SbError> { | |
// Generate quote for secure request with user's public key | |
let mut os_rng = OsRng::default(); | |
let priv_key = RsaPrivateKey::new(&mut os_rng, 2048).map_err(|_| SbError::KeyParseError)?; | |
let pub_key = RsaPublicKey::from(&priv_key) | |
.to_public_key_der() | |
.map_err(|_| SbError::KeyParseError)?; | |
// The quote is generated around the public encryption key so that the server can validate | |
// that the request has not been tampered with. | |
let secrets_quote = | |
Gramine::generate_quote(pub_key.as_ref()).map_err(|_| SbError::SgxError)?; | |
let enc_key = pub_key | |
.to_pem("PUBLIC KEY", rsa::pkcs1::LineEnding::default()) | |
.map_err(|_| SbError::KeyParseError)?; | |
// Build and send request to fetch encrypted secrets | |
let payload = json!({ | |
"user_pubkey": user_pubkey, | |
"ciphersuite": "ed25519", | |
"encryption_key": enc_key, | |
"quote": &secrets_quote, | |
}); | |
let response = reqwest::Client::new() | |
.post("https://api.secrets.switchboard.xyz/") | |
.json(&payload) | |
.send() | |
.await | |
.map_err(handle_reqwest_err)? | |
.error_for_status() | |
.map_err(handle_reqwest_err)?; | |
let encrypted_data = response | |
.json::<EncryptedData>() | |
.await | |
.map_err(handle_reqwest_err)?; | |
// First we need to decode and decrypt the encryption key. | |
let key = match base64::engine::general_purpose::STANDARD.decode(encrypted_data.key) { | |
Ok(value) => value, | |
Err(err) => { | |
let error_msg = format!("Base64DecodeError: {:#?}", err); | |
println!("{}", error_msg); | |
return Err(SbError::CustomMessage(error_msg)); | |
} | |
}; | |
let key = match priv_key.decrypt(rsa::pkcs1v15::Pkcs1v15Encrypt, &key) { | |
Ok(value) => Key::<Aes256Gcm>::clone_from_slice(&value), | |
Err(err) => { | |
let error_msg = format!("DecryptKeyError: {:#?}", err); | |
println!("{}", error_msg); | |
return Err(SbError::CustomMessage(error_msg)); | |
} | |
}; | |
// Second we need to decode the nonce value from the encrypted data. | |
let nonce = match base64::engine::general_purpose::STANDARD.decode(encrypted_data.nonce) { | |
Ok(value) => Nonce::clone_from_slice(&value), | |
Err(err) => { | |
let error_msg = format!("Base64DecodeError: {:#?}", err); | |
println!("{}", error_msg); | |
return Err(SbError::CustomMessage(error_msg)); | |
} | |
}; | |
// Lastly, we can use our decrypted key and nonce values to decode and decrypt the payload. | |
let data = match base64::engine::general_purpose::STANDARD.decode(encrypted_data.data) { | |
Ok(value) => value, | |
Err(err) => { | |
let error_msg = format!("Base64DecodeError: {:#?}", err); | |
println!("{}", error_msg); | |
return Err(SbError::CustomMessage(error_msg)); | |
} | |
}; | |
let data = match Aes256Gcm::new(&key).decrypt(&nonce, data.as_ref()) { | |
Ok(value) => value, | |
Err(err) => { | |
let error_msg = format!("Aes256GcmError: {:#?}", err); | |
println!("{}", error_msg); | |
return Err(SbError::CustomMessage(error_msg)); | |
} | |
}; | |
// The data can be parsed into a hashmap and returned. | |
let secrets: HashMap<String, String> = serde_json::from_slice(&data)?; | |
Ok(Self { secrets }) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment