@@ -11,16 +11,16 @@ use yubicoerror::YubicoError;
11
11
use hyper:: Client ;
12
12
use hyper:: header:: { Headers } ;
13
13
use std:: io:: prelude:: * ;
14
- use base64:: { encode} ;
14
+ use base64:: { encode, decode } ;
15
15
use crypto:: mac:: { Mac } ;
16
16
use crypto:: hmac:: Hmac ;
17
17
use crypto:: sha1:: Sha1 ;
18
18
use rand:: { thread_rng, Rng } ;
19
- use std:: collections:: HashMap ;
20
19
use threadpool:: ThreadPool ;
20
+ use std:: collections:: BTreeMap ;
21
21
use std:: sync:: mpsc:: { channel, Sender } ;
22
-
23
22
use url:: percent_encoding:: { utf8_percent_encode, SIMPLE_ENCODE_SET } ;
23
+
24
24
define_encode_set ! {
25
25
/// This encode set is used in the URL parser for query strings.
26
26
pub QUERY_ENCODE_SET = [ SIMPLE_ENCODE_SET ] | { '+' , '=' }
@@ -52,15 +52,15 @@ pub struct Request {
52
52
#[ derive( Clone ) ]
53
53
pub struct Yubico {
54
54
client_id : String ,
55
- key : String ,
55
+ key : Vec < u8 > ,
56
56
}
57
57
58
58
impl Yubico {
59
59
/// Creates a new Yubico instance.
60
60
pub fn new ( client_id : String , key : String ) -> Self {
61
61
Yubico {
62
62
client_id : client_id,
63
- key : key,
63
+ key : decode ( key. as_ref ( ) ) . unwrap ( ) ,
64
64
}
65
65
}
66
66
@@ -71,10 +71,14 @@ impl Yubico {
71
71
_ => {
72
72
// TODO: use OsRng to generate a most secure nonce
73
73
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 ) ;
75
75
76
76
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 ( ) ) ;
78
82
79
83
let request = Request { otp : otp, nonce : nonce, signature : signature, query : query} ;
80
84
@@ -119,13 +123,11 @@ impl Yubico {
119
123
120
124
// 1. Apply the HMAC-SHA-1 algorithm on the line as an octet string using the API key as key
121
125
// 2. Base 64 encode the resulting value according to RFC 4648
122
- // 3. Append the value under key h to the message.
123
126
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 [ .. ] ) ;
125
128
hmac. input ( query. as_bytes ( ) ) ;
126
129
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)
129
131
}
130
132
131
133
// Recommendation is that clients only check that the input consists of 32-48 printable characters
@@ -137,7 +139,13 @@ impl Yubico {
137
139
let url = format ! ( "{}?{}" , api_host, request. query) ;
138
140
match self . get ( url) {
139
141
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
+ }
141
149
142
150
// Check if "otp" in the response is the same as the "otp" supplied in the request.
143
151
let otp_response : & str = & * response_map. get ( "otp" ) . unwrap ( ) ;
@@ -173,8 +181,25 @@ impl Yubico {
173
181
}
174
182
}
175
183
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 ( ) ;
178
203
for line in result. lines ( ) {
179
204
let param: Vec < & str > = line. splitn ( 2 , '=' ) . collect ( ) ;
180
205
if param. len ( ) > 1 {
@@ -187,7 +212,7 @@ impl Yubico {
187
212
pub fn get ( & self , url : String ) -> Result < String > {
188
213
let client = Client :: new ( ) ;
189
214
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 ( ) ) ) ;
191
216
192
217
let mut response = String :: new ( ) ;
193
218
let mut res = try!( client. get ( & url) . headers ( custom_headers) . send ( ) ) ;
0 commit comments