1use 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 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 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 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 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 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 let pkey = PKey::private_key_from_pem_passphrase(private_key_pem.as_bytes(), passphrase_bytes)?;
157
158 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 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 let ed25519_oid = [0x06, 0x03, 0x2B, 0x65, 0x70]; 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 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 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 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 pub fn sign(&self, query_params: Option<&Vec<(String, String)>>, request_body: Option<&[u8]>) -> Result<String, BinanceAuthError> {
277 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 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 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 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 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 urlencoding::encode(v).replace("%40", "@")
340 ))
341 .collect::<Vec<String>>()
342 .join("&");
343
344 payload.push_str(&query_string);
345 }
346 }
347
348 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 pub fn api_key(&self) -> &str {
367 &self.api_key
368 }
369
370 pub fn key_type(&self) -> &KeyType {
372 &self.key_type
373 }
374}