binance/spot/apis/
signing.rs

1/*
2 * Binance Spot API
3 *
4 * OpenAPI specification for Binance exchange - Spot API
5 *
6 * The version of the OpenAPI document: 0.2.0
7 * 
8 * Generated by: https://openapi-generator.tech
9 */
10
11
12use base64::{Engine as _, engine::general_purpose::STANDARD};
13use hmac::{Hmac, Mac};
14use sha2::Sha256;
15use std::fs;
16use std::path::Path;
17use std::fmt;
18use std::error::Error as StdError;
19use openssl::rsa::Rsa;
20use openssl::pkey::{PKey, Private};
21use openssl::sign::Signer;
22use openssl::hash::MessageDigest;
23use ed25519_dalek::{SigningKey, Signer as DalekSigner, SECRET_KEY_LENGTH};
24use hex;
25
26#[derive(Debug, Clone, PartialEq)]
27pub enum KeyType {
28    HMAC,
29    RSA,
30    ED25519,
31}
32
33#[derive(Debug)]
34pub enum BinanceAuthError {
35    IoError(std::io::Error),
36    CryptoError(String),
37    ConfigError(String),
38}
39
40impl fmt::Display for BinanceAuthError {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        match self {
43            BinanceAuthError::IoError(e) => write!(f, "IO error: {}", e),
44            BinanceAuthError::CryptoError(s) => write!(f, "Crypto error: {}", s),
45            BinanceAuthError::ConfigError(s) => write!(f, "Config error: {}", s),
46        }
47    }
48}
49
50impl StdError for BinanceAuthError {}
51
52impl From<std::io::Error> for BinanceAuthError {
53    fn from(err: std::io::Error) -> Self {
54        BinanceAuthError::IoError(err)
55    }
56}
57
58impl From<openssl::error::ErrorStack> for BinanceAuthError {
59    fn from(err: openssl::error::ErrorStack) -> Self {
60        BinanceAuthError::CryptoError(err.to_string())
61    }
62}
63
64impl From<ed25519_dalek::SignatureError> for BinanceAuthError {
65    fn from(err: ed25519_dalek::SignatureError) -> Self {
66        BinanceAuthError::CryptoError(err.to_string())
67    }
68}
69
70#[derive(Debug, Clone)]
71pub enum PrivateKeyData {
72    SecretKey(String),
73    RsaKey(Vec<u8>),
74    Ed25519Key(Vec<u8>),
75}
76
77#[derive(Debug, Clone)]
78pub struct BinanceAuth {
79    api_key: String,
80    private_key_data: PrivateKeyData,
81    key_type: KeyType,
82}
83
84impl BinanceAuth {
85    /// Create a new BinanceAuth with HMAC signing using the provided secret key
86    pub fn new_with_secret_key(api_key: &str, secret_key: &str) -> Result<Self, BinanceAuthError> {
87        if api_key.is_empty() {
88            return Err(BinanceAuthError::ConfigError("API Key is required".to_string()));
89        }
90        if secret_key.is_empty() {
91            return Err(BinanceAuthError::ConfigError("Secret Key is required for HMAC authentication".to_string()));
92        }
93
94        Ok(BinanceAuth {
95            api_key: api_key.to_string(),
96            private_key_data: PrivateKeyData::SecretKey(secret_key.to_string()),
97            key_type: KeyType::HMAC,
98        })
99    }
100
101    /// Create a new BinanceAuth with RSA or ED25519 signing using the provided PEM file path
102    pub fn new_with_private_key_path(api_key: &str, private_key_path: &str, passphrase: Option<&str>) -> Result<Self, BinanceAuthError> {
103        if api_key.is_empty() {
104            return Err(BinanceAuthError::ConfigError("API Key is required".to_string()));
105        }
106        if private_key_path.is_empty() {
107            return Err(BinanceAuthError::ConfigError("Private Key path is required for RSA/ED25519 authentication".to_string()));
108        }
109
110        let pem_data = fs::read_to_string(Path::new(private_key_path))?;
111        Self::new_with_private_key_pem(api_key, &pem_data, passphrase)
112    }
113
114    /// Create a new BinanceAuth with RSA or ED25519 signing using the provided PEM data
115    pub fn new_with_private_key_pem(api_key: &str, private_key_pem: &str, passphrase: Option<&str>) -> Result<Self, BinanceAuthError> {
116        if api_key.is_empty() {
117            return Err(BinanceAuthError::ConfigError("API Key is required".to_string()));
118        }
119        if private_key_pem.is_empty() {
120            return Err(BinanceAuthError::ConfigError("Private Key PEM data is required for RSA/ED25519 authentication".to_string()));
121        }
122
123        let passphrase_bytes = passphrase.map(|p| p.as_bytes()).unwrap_or(&[]);
124
125        // Try parsing as different key types
126        Self::try_parse_rsa_key(api_key, private_key_pem, passphrase_bytes)
127            .or_else(|_| Self::try_parse_pkcs8_key(api_key, private_key_pem, passphrase_bytes))
128            .or_else(|_| Self::try_parse_openssh_key(api_key, private_key_pem))
129            .or_else(|_| Self::try_parse_base64_ed25519(api_key, private_key_pem))
130            .or_else(|_| Self::try_parse_hex_ed25519(api_key, private_key_pem))
131            .or_else(|_| Err(BinanceAuthError::ConfigError(
132                "Could not parse private key as RSA or Ed25519. Ensure the key is in a supported format.".to_string()
133            )))
134    }
135
136    // Helper functions for key parsing
137
138    fn try_parse_rsa_key(api_key: &str, private_key_pem: &str, passphrase_bytes: &[u8]) -> Result<Self, BinanceAuthError> {
139        let rsa_key = Rsa::private_key_from_pem_passphrase(private_key_pem.as_bytes(), passphrase_bytes)?;
140        let key_data = rsa_key.private_key_to_der()
141            .map_err(|e| BinanceAuthError::CryptoError(format!("Failed to convert RSA key to DER: {}", e)))?;
142
143        Ok(BinanceAuth {
144            api_key: api_key.to_string(),
145            private_key_data: PrivateKeyData::RsaKey(key_data),
146            key_type: KeyType::RSA,
147        })
148    }
149
150    fn try_parse_pkcs8_key(api_key: &str, private_key_pem: &str, passphrase_bytes: &[u8]) -> Result<Self, BinanceAuthError> {
151        if !private_key_pem.contains("-----BEGIN PRIVATE KEY-----") {
152            return Err(BinanceAuthError::ConfigError("Not a PKCS#8 key".to_string()));
153        }
154
155        // Try to parse as RSA PKCS#8
156        let pkey = PKey::private_key_from_pem_passphrase(private_key_pem.as_bytes(), passphrase_bytes)?;
157
158        // Check if it's an RSA key
159        if let Ok(rsa) = pkey.rsa() {
160            let key_data = rsa.private_key_to_der()
161                .map_err(|e| BinanceAuthError::CryptoError(format!("Failed to convert RSA key to DER: {}", e)))?;
162
163            return Ok(BinanceAuth {
164                api_key: api_key.to_string(),
165                private_key_data: PrivateKeyData::RsaKey(key_data),
166                key_type: KeyType::RSA,
167            });
168        }
169
170        // If it's not RSA, try as Ed25519 in PKCS#8 format
171        let clean_pem = private_key_pem
172            .replace("-----BEGIN PRIVATE KEY-----", "")
173            .replace("-----END PRIVATE KEY-----", "")
174            .replace("\n", "");
175
176        let decoded = STANDARD.decode(clean_pem)
177            .map_err(|_| BinanceAuthError::CryptoError("Failed to decode PEM data".to_string()))?;
178
179        // Look for Ed25519 OID pattern
180        let ed25519_oid = [0x06, 0x03, 0x2B, 0x65, 0x70]; // OID 1.3.101.112
181        let pos = decoded.windows(ed25519_oid.len())
182            .position(|window| window == ed25519_oid)
183            .ok_or_else(|| BinanceAuthError::CryptoError("Ed25519 OID not found".to_string()))?;
184
185        // Look for the octet string that contains the key
186        for i in pos + ed25519_oid.len()..decoded.len().saturating_sub(SECRET_KEY_LENGTH) {
187            if decoded[i] == 0x04 && decoded[i+1] == SECRET_KEY_LENGTH as u8 {
188                // Found the octet string with key data
189                let key_data = decoded[i+2..i+2+SECRET_KEY_LENGTH].to_vec();
190                if key_data.len() == SECRET_KEY_LENGTH {
191                    return Ok(BinanceAuth {
192                        api_key: api_key.to_string(),
193                        private_key_data: PrivateKeyData::Ed25519Key(key_data),
194                        key_type: KeyType::ED25519,
195                    });
196                }
197            }
198        }
199
200        Err(BinanceAuthError::CryptoError("Could not extract Ed25519 key from PKCS#8".to_string()))
201    }
202
203    fn try_parse_openssh_key(api_key: &str, private_key_pem: &str) -> Result<Self, BinanceAuthError> {
204        if !private_key_pem.contains("-----BEGIN OPENSSH PRIVATE KEY-----") {
205            return Err(BinanceAuthError::ConfigError("Not an OpenSSH key".to_string()));
206        }
207
208        let clean_pem = private_key_pem
209            .replace("-----BEGIN OPENSSH PRIVATE KEY-----", "")
210            .replace("-----END OPENSSH PRIVATE KEY-----", "")
211            .replace("\n", "");
212
213        let decoded = STANDARD.decode(clean_pem)
214            .map_err(|_| BinanceAuthError::CryptoError("Failed to decode OpenSSH key".to_string()))?;
215
216        // Extract what might be the secret key portion
217        // Note: This is simplified and needs proper OpenSSH key parsing in production
218        if decoded.len() >= SECRET_KEY_LENGTH {
219            let key_data = decoded.get(decoded.len().saturating_sub(SECRET_KEY_LENGTH)..)
220                .unwrap_or(&[])
221                .to_vec();
222
223            if key_data.len() == SECRET_KEY_LENGTH {
224                return Ok(BinanceAuth {
225                    api_key: api_key.to_string(),
226                    private_key_data: PrivateKeyData::Ed25519Key(key_data),
227                    key_type: KeyType::ED25519,
228                });
229            }
230        }
231
232        Err(BinanceAuthError::CryptoError("Could not extract Ed25519 key from OpenSSH format".to_string()))
233    }
234
235    fn try_parse_base64_ed25519(api_key: &str, private_key_pem: &str) -> Result<Self, BinanceAuthError> {
236        let decoded = STANDARD.decode(private_key_pem.trim())
237            .map_err(|_| BinanceAuthError::CryptoError("Not a valid base64-encoded key".to_string()))?;
238
239        if decoded.len() != SECRET_KEY_LENGTH {
240            return Err(BinanceAuthError::CryptoError(
241                format!("Invalid key length for Ed25519: expected {}, got {}", SECRET_KEY_LENGTH, decoded.len())
242            ));
243        }
244
245        Ok(BinanceAuth {
246            api_key: api_key.to_string(),
247            private_key_data: PrivateKeyData::Ed25519Key(decoded),
248            key_type: KeyType::ED25519,
249        })
250    }
251
252    fn try_parse_hex_ed25519(api_key: &str, private_key_pem: &str) -> Result<Self, BinanceAuthError> {
253        if private_key_pem.len() != SECRET_KEY_LENGTH * 2 {
254            return Err(BinanceAuthError::CryptoError(
255                format!("Invalid hex length for Ed25519: expected {}, got {}", SECRET_KEY_LENGTH * 2, private_key_pem.len())
256            ));
257        }
258
259        let decoded = hex::decode(private_key_pem)
260            .map_err(|_| BinanceAuthError::CryptoError("Not a valid hex-encoded key".to_string()))?;
261
262        if decoded.len() != SECRET_KEY_LENGTH {
263            return Err(BinanceAuthError::CryptoError(
264                format!("Invalid key length after hex decode: expected {}, got {}", SECRET_KEY_LENGTH, decoded.len())
265            ));
266        }
267
268        Ok(BinanceAuth {
269            api_key: api_key.to_string(),
270            private_key_data: PrivateKeyData::Ed25519Key(decoded),
271            key_type: KeyType::ED25519,
272        })
273    }
274
275    /// Sign a request with the appropriate algorithm
276    pub fn sign(&self, query_params: Option<&Vec<(String, String)>>, request_body: Option<&[u8]>) -> Result<String, BinanceAuthError> {
277        // Generate payload
278        let payload = self.get_signature_payload(query_params, request_body)?;
279        
280        match &self.key_type {
281            KeyType::HMAC => {
282                if let PrivateKeyData::SecretKey(secret) = &self.private_key_data {
283                    let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes())
284                        .map_err(|e| BinanceAuthError::CryptoError(e.to_string()))?;
285                    mac.update(&payload);
286                    let result = mac.finalize();
287                    let bytes = result.into_bytes();
288                    Ok(hex::encode(bytes))
289                } else {
290                    Err(BinanceAuthError::ConfigError("Secret key not available for HMAC signing".to_string()))
291                }
292            },
293            KeyType::RSA => {
294                if let PrivateKeyData::RsaKey(key_data) = &self.private_key_data {
295                    let pkey = PKey::private_key_from_der(key_data)?;
296                    let mut signer = Signer::new(MessageDigest::sha256(), &pkey)?;
297                    signer.update(&payload)?;
298                    let signature = signer.sign_to_vec()?;
299                    Ok(STANDARD.encode(&signature))
300                } else {
301                    Err(BinanceAuthError::ConfigError("RSA key not available for RSA signing".to_string()))
302                }
303            },
304            KeyType::ED25519 => {
305                if let PrivateKeyData::Ed25519Key(key_data) = &self.private_key_data {
306                    // Ensure we have exactly 32 bytes for Ed25519 key
307                    if key_data.len() != SECRET_KEY_LENGTH {
308                        return Err(BinanceAuthError::CryptoError(
309                            format!("Ed25519 key must be exactly {} bytes, found {} bytes", 
310                                SECRET_KEY_LENGTH, key_data.len())
311                        ));
312                    }
313
314                    // Convert Vec<u8> to [u8; 32]
315                    let mut fixed_key = [0u8; SECRET_KEY_LENGTH];
316                    fixed_key.copy_from_slice(&key_data[..SECRET_KEY_LENGTH]);
317
318                    let signing_key = SigningKey::from_bytes(&fixed_key);
319                    let signature = signing_key.sign(&payload);
320                    Ok(STANDARD.encode(signature.to_bytes()))
321                } else {
322                    Err(BinanceAuthError::ConfigError("Ed25519 key not available for Ed25519 signing".to_string()))
323                }
324            }
325        }
326    }
327
328    /// Constructs the payload string for signing according to Binance rules
329    fn get_signature_payload(&self, query_params: Option<&Vec<(String, String)>>, request_body: Option<&[u8]>) -> Result<Vec<u8>, BinanceAuthError> {
330        let mut payload = String::new();
331        
332        // Add query parameters
333        if let Some(params) = query_params {
334            if !params.is_empty() {
335                let query_string = params.iter()
336                    .map(|(k, v)| format!("{}={}", 
337                        k, 
338                        // Binance doesn't escape @ characters in signature
339                        urlencoding::encode(v).replace("%40", "@")
340                    ))
341                    .collect::<Vec<String>>()
342                    .join("&");
343                
344                payload.push_str(&query_string);
345            }
346        }
347        
348        // Add request body if present
349        if let Some(body) = request_body {
350            if !body.is_empty() {
351                let body_str = std::str::from_utf8(body)
352                    .map_err(|e| BinanceAuthError::ConfigError(format!("Invalid UTF-8 in request body: {}", e)))?;
353                
354                if !payload.is_empty() {
355                    payload.push_str(body_str);
356                } else {
357                    payload = body_str.to_string();
358                }
359            }
360        }
361        
362        Ok(payload.into_bytes())
363    }
364
365    /// Get the API key
366    pub fn api_key(&self) -> &str {
367        &self.api_key
368    }
369
370    /// Get the key type
371    pub fn key_type(&self) -> &KeyType {
372        &self.key_type
373    }
374}