1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
use crate::*;
use aes_gcm::{aead::Aead, Aes256Gcm, Key, KeyInit, Nonce};
use rand::rngs::OsRng;
use rsa::{pkcs8::ToPublicKey, PaddingScheme, RsaPrivateKey, RsaPublicKey};
use serde::Deserialize;
use serde_json;
use serde_json::json;
use std::collections::HashMap;
use std::result::Result;
use base64::{engine::general_purpose::STANDARD as base64, Engine as _};
use std::sync::Arc;
use sha2::Sha256;
use sha2::Digest;

#[derive(Debug, Deserialize)]
pub struct Secrets {
    pub keys: HashMap<String, String>,
}

/// 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 EncryptedSecretsData {
    /// 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,
}

fn handle_reqwest_err(e: reqwest::Error) -> SbError {
    let status = e.status().unwrap_or(reqwest::StatusCode::default());
    SbError::CustomError {
        message: format!(
            "reqwest_error: code = {}, message = {}",
            status,
            status.canonical_reason().unwrap_or("Unknown")
        ),
        source: std::sync::Arc::new(e),
    }
}

/// `fetch_secrets`: to be used in conjunction with the Switchboard Secrets Server stack.
///
/// When hosting your own secrets server, you may list the MR_ENCLAVE of the
/// functions you wish to reveal your secrets to.  This will only ever expose
/// your secrets to your code. Unless exported in your code, no chain or oracle
/// will be able to view these secrets:
///
/// # Relevant Materials:
/// - [Secret Server Github Repository](https://github.com/switchboard-xyz/secrets-server)
///
/// # Parameters:
/// - `fn_authority`: the authority of the function you wish to retrieve secrets for
/// - `url`: the url or ip address of the secrets server to use. If none are provided, the default
///   behavior will be to use Switchboard's hosted <https://api.secrets.switchboard.xyz>.
///
/// # Returns
/// - `Map<String, String>`: The key-value store of your secrets.
pub async fn fetch_secrets(
    cluster: &str,
    fn_authority: &str,
    url: Option<&str>,
    feed: &str,
    oracle: Pubkey,
    secrets_signer: Arc<libsecp256k1::SecretKey>,
) -> Result<Secrets, SbError> {
    // Unwrap the user-provided URL for a self-hosted secrets server, or default to
    // https://api.secrets.switchboard.xyz.
    let secrets_server_url = match url {
        Some(value) => value,
        None => "https://api.secrets.switchboard.xyz/",
    };

    // 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)?;
    let encryption_key = pub_key.to_pem().as_str().to_string();
    let encrypt_key_hash = &Sha256::digest(encryption_key.clone())[..];
    let msg = libsecp256k1::Message::parse(encrypt_key_hash.try_into().unwrap());
    let (sig, recovery_id) = libsecp256k1::sign(&msg, &secrets_signer);
    // Build and send request to fetch encrypted secrets
    let payload = json!({
        "user_pubkey": fn_authority,
        "cluster": cluster,
        "ciphersuite": "ed25519",
        "encryption_key": encryption_key,
        "feed_id": feed.to_string(),
        "oracle": oracle.to_string(),
        "key_signature": base64.encode(sig.serialize()),
        "recovery_id": recovery_id.serialize(),
    });
    println!("url: {:#?}", secrets_server_url);
    println!("Payload: {:#?}", payload);

    let response = reqwest::Client::new()
        .post(secrets_server_url)
        .json(&payload)
        .send()
        .await
        .map_err(handle_reqwest_err)?
        .error_for_status()
        .map_err(handle_reqwest_err)?;
    println!("Response: {:#?}", response);
    let encrypted_data = response
        .json::<EncryptedSecretsData>()
        .await
        .map_err(handle_reqwest_err)?;
    println!("Encrypted Data: {:#?}", encrypted_data);

    // First we need to decode and decrypt the encryption key.
    let key = match base64.decode(encrypted_data.key) {
        Ok(value) => value,
        Err(err) => {
            let error_msg = format!("Base64DecodeError: {:#?}", err);
            return Err(SbError::CustomMessage(error_msg));
        }
    };
    let key = match priv_key.decrypt(PaddingScheme::PKCS1v15Encrypt, &key) {
        Ok(value) => Key::<Aes256Gcm>::clone_from_slice(&value),
        Err(err) => {
            let error_msg = format!("DecryptKeyError: {:#?}", err);
            return Err(SbError::CustomMessage(error_msg));
        }
    };
    // Second we need to decode the nonce value from the encrypted data.
    let nonce = match base64.decode(encrypted_data.nonce) {
        Ok(value) => Nonce::clone_from_slice(&value),
        Err(err) => {
            let error_msg = format!("Base64DecodeError: {:#?}", err);
            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.decode(encrypted_data.data) {
        Ok(value) => value,
        Err(err) => {
            let error_msg = format!("Base64DecodeError: {:#?}", err);
            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);
            return Err(SbError::CustomMessage(error_msg));
        }
    };

    // The data can be parsed into a hashmap and returned.
    let keys: HashMap<String, String> = serde_json::from_slice(&data)?;
    Ok(Secrets { keys })
}