@@ -11,16 +11,16 @@ use yubicoerror::YubicoError;
1111use hyper:: Client ;
1212use hyper:: header:: { Headers } ;
1313use std:: io:: prelude:: * ;
14- use base64:: { encode} ;
14+ use base64:: { encode, decode } ;
1515use crypto:: mac:: { Mac } ;
1616use crypto:: hmac:: Hmac ;
1717use crypto:: sha1:: Sha1 ;
1818use rand:: { thread_rng, Rng } ;
19- use std:: collections:: HashMap ;
2019use threadpool:: ThreadPool ;
20+ use std:: collections:: BTreeMap ;
2121use std:: sync:: mpsc:: { channel, Sender } ;
22-
2322use url:: percent_encoding:: { utf8_percent_encode, SIMPLE_ENCODE_SET } ;
23+
2424define_encode_set ! {
2525 /// This encode set is used in the URL parser for query strings.
2626 pub QUERY_ENCODE_SET = [ SIMPLE_ENCODE_SET ] | { '+' , '=' }
@@ -52,15 +52,15 @@ pub struct Request {
5252#[ derive( Clone ) ]
5353pub struct Yubico {
5454 client_id : String ,
55- key : String ,
55+ key : Vec < u8 > ,
5656}
5757
5858impl Yubico {
5959 /// Creates a new Yubico instance.
6060 pub fn new ( client_id : String , key : String ) -> Self {
6161 Yubico {
6262 client_id : client_id,
63- key : key,
63+ key : decode ( key. as_ref ( ) ) . unwrap ( ) ,
6464 }
6565 }
6666
@@ -71,10 +71,14 @@ impl Yubico {
7171 _ => {
7272 // TODO: use OsRng to generate a most secure nonce
7373 let nonce: String = thread_rng ( ) . gen_ascii_chars ( ) . take ( 40 ) . collect ( ) ;
74- let mut query = format ! ( "id={}&otp ={}&nonce ={}&sl=secure" , self . client_id, otp , nonce ) ;
74+ let mut query = format ! ( "id={}&nonce ={}&otp ={}&sl=secure" , self . client_id, nonce , otp ) ;
7575
7676 let signature = self . build_signature ( query. clone ( ) ) ;
77- query. push_str ( signature. as_ref ( ) ) ;
77+
78+ // Append the value under key h to the message.
79+ let signature_param = format ! ( "&h={}" , signature) ;
80+ let encoded = utf8_percent_encode ( signature_param. as_ref ( ) , QUERY_ENCODE_SET ) . collect :: < String > ( ) ;
81+ query. push_str ( encoded. as_ref ( ) ) ;
7882
7983 let request = Request { otp : otp, nonce : nonce, signature : signature, query : query} ;
8084
@@ -119,13 +123,11 @@ impl Yubico {
119123
120124 // 1. Apply the HMAC-SHA-1 algorithm on the line as an octet string using the API key as key
121125 // 2. Base 64 encode the resulting value according to RFC 4648
122- // 3. Append the value under key h to the message.
123126 fn build_signature ( & self , query : String ) -> String {
124- let mut hmac = Hmac :: new ( Sha1 :: new ( ) , self . key . as_bytes ( ) ) ;
127+ let mut hmac = Hmac :: new ( Sha1 :: new ( ) , & self . key [ .. ] ) ;
125128 hmac. input ( query. as_bytes ( ) ) ;
126129 let signature = encode ( hmac. result ( ) . code ( ) ) ;
127- let signature_str = format ! ( "&h={}" , signature) ;
128- utf8_percent_encode ( signature_str. as_ref ( ) , QUERY_ENCODE_SET ) . collect :: < String > ( )
130+ format ! ( "{}" , signature)
129131 }
130132
131133 // Recommendation is that clients only check that the input consists of 32-48 printable characters
@@ -137,7 +139,13 @@ impl Yubico {
137139 let url = format ! ( "{}?{}" , api_host, request. query) ;
138140 match self . get ( url) {
139141 Ok ( result) => {
140- let response_map: HashMap < String , String > = self . build_response_map ( result) ;
142+ let response_map: BTreeMap < String , String > = self . build_response_map ( result) ;
143+
144+ // Signature located in the response must match the signature we will build
145+ let signature_response : & str = & * response_map. get ( "h" ) . unwrap ( ) ;
146+ if !self . is_same_signature ( signature_response, response_map. clone ( ) ) {
147+ sender. send ( Response :: Signal ( Err ( YubicoError :: SignatureMismatch ) ) ) . unwrap ( ) ;
148+ }
141149
142150 // Check if "otp" in the response is the same as the "otp" supplied in the request.
143151 let otp_response : & str = & * response_map. get ( "otp" ) . unwrap ( ) ;
@@ -173,8 +181,25 @@ impl Yubico {
173181 }
174182 }
175183
176- fn build_response_map ( & self , result : String ) -> HashMap < String , String > {
177- let mut parameters = HashMap :: new ( ) ;
184+ // Remove the signature itself from the values over for verification.
185+ // Sort the key/value pairs.
186+ fn is_same_signature ( & self , signature_response : & str , mut response_map : BTreeMap < String , String > ) -> bool {
187+ response_map. remove ( "h" ) ;
188+
189+ let mut query = String :: new ( ) ;
190+ for ( key, value) in response_map {
191+ let param = format ! ( "{}={}&" , key, value) ;
192+ query. push_str ( param. as_ref ( ) ) ;
193+ }
194+ query. pop ( ) ; // remove last &
195+
196+ let signature = self . build_signature ( query. clone ( ) ) ;
197+
198+ if signature == signature_response { true } else { false }
199+ }
200+
201+ fn build_response_map ( & self , result : String ) -> BTreeMap < String , String > {
202+ let mut parameters = BTreeMap :: new ( ) ;
178203 for line in result. lines ( ) {
179204 let param: Vec < & str > = line. splitn ( 2 , '=' ) . collect ( ) ;
180205 if param. len ( ) > 1 {
@@ -187,7 +212,7 @@ impl Yubico {
187212 pub fn get ( & self , url : String ) -> Result < String > {
188213 let client = Client :: new ( ) ;
189214 let mut custom_headers = Headers :: new ( ) ;
190- custom_headers. set ( UserAgent ( "yubico-rs" . to_owned ( ) ) ) ;
215+ custom_headers. set ( UserAgent ( "github.com/wisespace-io/ yubico-rs" . to_owned ( ) ) ) ;
191216
192217 let mut response = String :: new ( ) ;
193218 let mut res = try!( client. get ( & url) . headers ( custom_headers) . send ( ) ) ;
0 commit comments