oauth.rs (5233B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2025, 2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 17 use std::{borrow::Cow, time::SystemTime}; 18 19 use aws_lc_rs::hmac; 20 use base64::{Engine as _, prelude::BASE64_STANDARD}; 21 use http_client::builder::Req; 22 use hyper::header; 23 use percent_encoding::NON_ALPHANUMERIC; 24 use serde::{Deserialize, Serialize}; 25 26 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] 27 pub struct Token { 28 #[serde(rename = "oauth_token")] 29 pub key: String, 30 #[serde(rename = "oauth_token_secret")] 31 pub secret: String, 32 } 33 34 #[derive(Debug, Deserialize)] 35 pub struct TokenAuth { 36 pub oauth_token: String, 37 pub oauth_verifier: String, 38 } 39 40 /** Generate a secure OAuth nonce */ 41 fn oauth_nonce() -> String { 42 // Generate 8 secure random bytes 43 let mut buf = [0u8; 8]; 44 getrandom::fill(&mut buf).expect("Failed to get random bytes from OS"); 45 // Encode as base64 string 46 BASE64_STANDARD.encode(buf) 47 } 48 49 /** Generate an OAuth timestamp */ 50 fn oauth_timestamp() -> u64 { 51 let start = SystemTime::now(); 52 let since_the_epoch = start 53 .duration_since(std::time::UNIX_EPOCH) 54 .expect("Time went backwards"); 55 56 since_the_epoch.as_secs() 57 } 58 59 /** Generate a valid OAuth Authorization header */ 60 fn oauth_header( 61 method: &hyper::Method, 62 url: &url::Url, 63 consumer: &Token, 64 access: Option<&Token>, 65 verifier: Option<&str>, 66 ) -> String { 67 // Per request value 68 let oauth_nonce = oauth_nonce(); 69 let oauth_timestamp = oauth_timestamp().to_string(); 70 71 // Base string 72 let base_string = { 73 let oauth_data = { 74 let mut oauth_query: Vec<(&str, &str)> = vec![ 75 ("oauth_consumer_key", &consumer.key), 76 ("oauth_nonce", &oauth_nonce), 77 ("oauth_signature_method", "HMAC-SHA1"), 78 ("oauth_timestamp", &oauth_timestamp), 79 ]; 80 if let Some(token) = &access { 81 oauth_query.push(("oauth_token", &token.key)); 82 } 83 if let Some(verifier) = &verifier { 84 oauth_query.push(("oauth_verifier", verifier)); 85 } 86 oauth_query.push(("oauth_version", "1.0")); 87 let mut all_query: Vec<_> = oauth_query 88 .into_iter() 89 .map(|(a, b)| (Cow::Borrowed(a), Cow::Borrowed(b))) 90 .chain(url.query_pairs()) 91 .collect(); 92 all_query.sort_unstable(); 93 94 let mut tmp: form_urlencoded::Serializer<'_, String> = 95 form_urlencoded::Serializer::new(String::new()); 96 for (k, v) in all_query { 97 tmp.append_pair(&k, &v); 98 } 99 tmp.finish() 100 }; 101 let mut stripped = url.clone(); 102 stripped.set_query(None); 103 form_urlencoded::Serializer::new(String::new()) 104 .append_key_only(method.as_str()) 105 .append_key_only(stripped.as_str()) 106 .append_key_only(&oauth_data) 107 .finish() 108 }; 109 110 // Signature 111 let key = { 112 let mut buf = consumer.secret.clone(); 113 buf.push('&'); 114 if let Some(token) = access { 115 buf.push_str(&token.secret); 116 } 117 buf 118 }; 119 let key = hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, key.as_bytes()); 120 let signature = hmac::sign(&key, base_string.as_bytes()); 121 let signature_encoded = BASE64_STANDARD.encode(signature.as_ref()); 122 123 // Authorization header 124 { 125 let mut buf = "OAuth ".to_string(); 126 let mut append = |key: &str, value: &str| { 127 buf.push_str(key); 128 buf.push_str("=\""); 129 for part in percent_encoding::percent_encode(value.as_bytes(), NON_ALPHANUMERIC) { 130 buf.push_str(part); 131 } 132 buf.push_str("\","); 133 }; 134 append("oauth_consumer_key", &consumer.key); 135 append("oauth_nonce", &oauth_nonce); 136 append("oauth_signature_method", "HMAC-SHA1"); 137 append("oauth_timestamp", &oauth_timestamp); 138 if let Some(token) = &access { 139 append("oauth_token", &token.key); 140 } 141 if let Some(verifier) = &verifier { 142 append("oauth_verifier", verifier); 143 } 144 append("oauth_version", "1.0"); 145 append("oauth_signature", &signature_encoded); 146 buf 147 } 148 } 149 150 /** Perform OAuth on an HTTP request */ 151 pub fn oauth(req: Req, consumer: &Token, access: Option<&Token>, verifier: Option<&str>) -> Req { 152 let header = oauth_header(req.method(), req.url(), consumer, access, verifier); 153 req.sensitive_header(header::AUTHORIZATION, header) 154 }