1
- use crate :: headers:: HeaderProvider ;
2
- use crate :: headers:: HeaderProviderError ;
1
+ use crate :: headers:: get_headermap;
2
+ use crate :: headers:: VssHeaderProvider ;
3
+ use crate :: headers:: VssHeaderProviderError ;
4
+ use crate :: util:: string:: UntrustedString ;
3
5
use async_trait:: async_trait;
4
6
use base64:: engine:: general_purpose:: URL_SAFE_NO_PAD ;
5
7
use base64:: Engine ;
@@ -10,11 +12,9 @@ use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine};
10
12
use bitcoin:: secp256k1:: { All , Message , Secp256k1 } ;
11
13
use bitcoin:: Network ;
12
14
use bitcoin:: PrivateKey ;
13
- use reqwest:: header:: HeaderMap ;
14
- use reqwest:: header:: AUTHORIZATION ;
15
15
use serde:: Deserialize ;
16
- use std:: str :: FromStr ;
17
- use std:: sync:: Mutex ;
16
+ use std:: ops :: Deref ;
17
+ use std:: sync:: RwLock ;
18
18
use std:: time:: SystemTime ;
19
19
use url:: Url ;
20
20
@@ -30,17 +30,24 @@ const K1_QUERY_PARAM: &str = "k1";
30
30
const SIG_QUERY_PARAM : & str = "sig" ;
31
31
// The key of the LNURL key query parameter.
32
32
const KEY_QUERY_PARAM : & str = "key" ;
33
+ // The authorization header name.
34
+ const AUTHORIZATION : & str = "authorization" ;
35
+
36
+ #[ derive( Debug , Clone ) ]
37
+ struct JwtToken {
38
+ token_str : String ,
39
+ expiry : Option < u64 > ,
40
+ }
33
41
34
42
/// Provides a JWT token based on LNURL Auth.
35
43
/// The LNURL and JWT token are exchanged over a Websocket connection.
36
44
pub struct LnurlAuthJwt {
37
45
engine : Secp256k1 < All > ,
38
46
parent_key : ExtendedPrivKey ,
39
47
url : String ,
40
- headers : HeaderMap ,
48
+ default_headers : Vec < ( String , String ) > ,
41
49
client : reqwest:: Client ,
42
- jwt_token : Mutex < Option < String > > ,
43
- expiry : Mutex < Option < u64 > > ,
50
+ jwt_token : RwLock < Option < JwtToken > > ,
44
51
}
45
52
46
53
impl LnurlAuthJwt {
@@ -51,48 +58,38 @@ impl LnurlAuthJwt {
51
58
/// The JWT token will be returned in response to the signed LNURL request under a token field.
52
59
/// The given set of headers will be used for LNURL requests, and will also be returned together
53
60
/// with the JWT authorization header for VSS requests.
54
- pub fn new ( seed : & [ u8 ] , url : String , headers : Vec < ( String , String ) > ) -> Result < LnurlAuthJwt , HeaderProviderError > {
61
+ pub fn new (
62
+ seed : & [ u8 ] , url : String , default_headers : Vec < ( String , String ) > ,
63
+ ) -> Result < LnurlAuthJwt , VssHeaderProviderError > {
55
64
let engine = Secp256k1 :: new ( ) ;
56
- let master = ExtendedPrivKey :: new_master ( Network :: Testnet , seed) . map_err ( HeaderProviderError :: from) ?;
65
+ let master = ExtendedPrivKey :: new_master ( Network :: Testnet , seed) . map_err ( VssHeaderProviderError :: from) ?;
57
66
let child_number =
58
- ChildNumber :: from_hardened_idx ( PARENT_DERIVATION_INDEX ) . map_err ( HeaderProviderError :: from) ?;
67
+ ChildNumber :: from_hardened_idx ( PARENT_DERIVATION_INDEX ) . map_err ( VssHeaderProviderError :: from) ?;
59
68
let parent_key = master
60
69
. derive_priv ( & engine, & vec ! [ child_number] )
61
- . map_err ( HeaderProviderError :: from) ?;
62
- let mut headermap = HeaderMap :: new ( ) ;
63
- for ( name, value) in headers {
64
- headermap. insert (
65
- reqwest:: header:: HeaderName :: from_str ( & name) . map_err ( HeaderProviderError :: from) ?,
66
- reqwest:: header:: HeaderValue :: from_str ( & value) . map_err ( HeaderProviderError :: from) ?,
67
- ) ;
68
- }
70
+ . map_err ( VssHeaderProviderError :: from) ?;
71
+ let default_headermap =
72
+ get_headermap ( & default_headers) . map_err ( |error| VssHeaderProviderError :: InvalidData { error } ) ?;
69
73
let client = reqwest:: Client :: builder ( )
70
- . default_headers ( headermap . clone ( ) )
74
+ . default_headers ( default_headermap )
71
75
. build ( )
72
- . map_err ( HeaderProviderError :: from) ?;
76
+ . map_err ( VssHeaderProviderError :: from) ?;
73
77
74
- Ok ( LnurlAuthJwt {
75
- engine,
76
- parent_key,
77
- url,
78
- headers : headermap,
79
- client,
80
- jwt_token : Mutex :: new ( None ) ,
81
- expiry : Mutex :: new ( None ) ,
82
- } )
78
+ Ok ( LnurlAuthJwt { engine, parent_key, url, default_headers, client, jwt_token : RwLock :: new ( None ) } )
83
79
}
84
80
85
- async fn fetch_jwt_token ( & self ) -> Result < String , HeaderProviderError > {
81
+ async fn fetch_jwt_token ( & self ) -> Result < JwtToken , VssHeaderProviderError > {
86
82
// Fetch the LNURL.
87
- let lnurl_str = self
88
- . client
89
- . get ( & self . url )
90
- . send ( )
91
- . await
92
- . map_err ( HeaderProviderError :: from) ?
93
- . text ( )
94
- . await
95
- . map_err ( HeaderProviderError :: from) ?;
83
+ let lnurl_str = UntrustedString :: new (
84
+ self . client
85
+ . get ( & self . url )
86
+ . send ( )
87
+ . await
88
+ . map_err ( VssHeaderProviderError :: from) ?
89
+ . text ( )
90
+ . await
91
+ . map_err ( VssHeaderProviderError :: from) ?,
92
+ ) ;
96
93
97
94
// Sign the LNURL and perform the request.
98
95
let signed_lnurl = sign_lnurl ( & self . engine , & self . parent_key , & lnurl_str) ?;
@@ -101,40 +98,45 @@ impl LnurlAuthJwt {
101
98
. get ( & signed_lnurl)
102
99
. send ( )
103
100
. await
104
- . map_err ( HeaderProviderError :: from) ?
101
+ . map_err ( VssHeaderProviderError :: from) ?
105
102
. json ( )
106
103
. await
107
- . map_err ( HeaderProviderError :: from) ?;
104
+ . map_err ( VssHeaderProviderError :: from) ?;
108
105
109
- match lnurl_auth_response {
110
- LnurlAuthResponse { token : Some ( token) , .. } => Ok ( token) ,
106
+ let untrusted_token = match lnurl_auth_response {
107
+ LnurlAuthResponse { token : Some ( token) , .. } => token,
111
108
LnurlAuthResponse { reason : Some ( reason) , .. } => {
112
- Err ( HeaderProviderError :: ApplicationError ( format ! ( "LNURL Auth failed, reason is: {}" , reason) ) )
109
+ return Err ( VssHeaderProviderError :: ApplicationError {
110
+ error : format ! ( "LNURL Auth failed, reason is: {}" , reason) ,
111
+ } ) ;
113
112
}
114
- _ => Err ( HeaderProviderError :: InvalidData (
115
- "LNURL Auth response did not contain a token nor an error" . to_string ( ) ,
116
- ) ) ,
117
- }
113
+ _ => {
114
+ return Err ( VssHeaderProviderError :: InvalidData {
115
+ error : "LNURL Auth response did not contain a token nor an error" . to_string ( ) ,
116
+ } ) ;
117
+ }
118
+ } ;
119
+ parse_jwt_token ( untrusted_token)
118
120
}
119
121
120
- async fn get_jwt_token ( & self , force_refresh : bool ) -> Result < String , HeaderProviderError > {
122
+ async fn get_jwt_token ( & self , force_refresh : bool ) -> Result < String , VssHeaderProviderError > {
121
123
if !self . is_expired ( ) && !force_refresh {
122
- let jwt_token = self . jwt_token . lock ( ) . unwrap ( ) ;
123
- if let Some ( jwt_token) = jwt_token. as_deref ( ) {
124
- return Ok ( jwt_token. to_string ( ) ) ;
124
+ let jwt_token = self . jwt_token . read ( ) . unwrap ( ) ;
125
+ if let Some ( jwt_token) = jwt_token. deref ( ) {
126
+ return Ok ( jwt_token. token_str . clone ( ) ) ;
125
127
}
126
128
}
127
129
let jwt_token = self . fetch_jwt_token ( ) . await ?;
128
- let expiry = parse_expiry ( & jwt_token) ?;
129
- * self . jwt_token . lock ( ) . unwrap ( ) = Some ( jwt_token. clone ( ) ) ;
130
- * self . expiry . lock ( ) . unwrap ( ) = expiry;
131
- Ok ( jwt_token)
130
+ * self . jwt_token . write ( ) . unwrap ( ) = Some ( jwt_token. clone ( ) ) ;
131
+ Ok ( jwt_token. token_str )
132
132
}
133
133
134
134
fn is_expired ( & self ) -> bool {
135
- self . expiry
136
- . lock ( )
135
+ self . jwt_token
136
+ . read ( )
137
137
. unwrap ( )
138
+ . as_ref ( )
139
+ . and_then ( |token| token. expiry )
138
140
. map ( |expiry| {
139
141
SystemTime :: now ( ) . duration_since ( SystemTime :: UNIX_EPOCH ) . unwrap ( ) . as_secs ( ) + EXPIRY_BUFFER_SECS
140
142
> expiry
@@ -144,41 +146,39 @@ impl LnurlAuthJwt {
144
146
}
145
147
146
148
#[ async_trait]
147
- impl HeaderProvider for LnurlAuthJwt {
148
- async fn get_headers ( & self ) -> Result < HeaderMap , HeaderProviderError > {
149
+ impl VssHeaderProvider for LnurlAuthJwt {
150
+ async fn get_headers ( & self ) -> Result < Vec < ( String , String ) > , VssHeaderProviderError > {
149
151
let jwt_token = self . get_jwt_token ( false ) . await ?;
150
- let mut headers = self . headers . clone ( ) ;
151
- let value = format ! ( "Bearer {}" , jwt_token) . parse ( ) . map_err ( HeaderProviderError :: from) ?;
152
- headers. insert ( AUTHORIZATION , value) ;
152
+ let mut headers = self . default_headers . clone ( ) ;
153
+ headers. push ( ( AUTHORIZATION . to_string ( ) , format ! ( "Bearer {}" , jwt_token) ) ) ;
153
154
Ok ( headers)
154
155
}
155
156
}
156
157
157
- fn hashing_key ( engine : & Secp256k1 < All > , parent_key : & ExtendedPrivKey ) -> Result < PrivateKey , HeaderProviderError > {
158
+ fn hashing_key ( engine : & Secp256k1 < All > , parent_key : & ExtendedPrivKey ) -> Result < PrivateKey , VssHeaderProviderError > {
158
159
let hashing_child_number =
159
- ChildNumber :: from_normal_idx ( HASHING_DERIVATION_INDEX ) . map_err ( HeaderProviderError :: from) ?;
160
+ ChildNumber :: from_normal_idx ( HASHING_DERIVATION_INDEX ) . map_err ( VssHeaderProviderError :: from) ?;
160
161
parent_key
161
162
. derive_priv ( engine, & vec ! [ hashing_child_number] )
162
163
. map ( |xpriv| xpriv. to_priv ( ) )
163
- . map_err ( HeaderProviderError :: from)
164
+ . map_err ( VssHeaderProviderError :: from)
164
165
}
165
166
166
- fn linking_key_path ( hashing_key : & PrivateKey , domain_name : & str ) -> Result < DerivationPath , HeaderProviderError > {
167
+ fn linking_key_path ( hashing_key : & PrivateKey , domain_name : & str ) -> Result < DerivationPath , VssHeaderProviderError > {
167
168
let mut engine = HmacEngine :: < sha256:: Hash > :: new ( & hashing_key. inner [ ..] ) ;
168
169
engine. input ( domain_name. as_bytes ( ) ) ;
169
170
let result = Hmac :: < sha256:: Hash > :: from_engine ( engine) . to_byte_array ( ) ;
170
- let children: Vec < ChildNumber > = ( 0 ..4 )
171
+ let children = ( 0 ..4 )
171
172
. map ( |i| u32:: from_be_bytes ( result[ ( i * 4 ) ..( ( i + 1 ) * 4 ) ] . try_into ( ) . unwrap ( ) ) )
172
- . map ( ChildNumber :: from)
173
- . collect :: < Vec < _ > > ( ) ;
174
- Ok ( DerivationPath :: from ( children) )
173
+ . map ( ChildNumber :: from) ;
174
+ Ok ( DerivationPath :: from_iter ( children) )
175
175
}
176
176
177
177
fn sign_lnurl (
178
- engine : & Secp256k1 < All > , parent_key : & ExtendedPrivKey , lnurl_str : & str ,
179
- ) -> Result < String , HeaderProviderError > {
178
+ engine : & Secp256k1 < All > , parent_key : & ExtendedPrivKey , lnurl_str : & UntrustedString ,
179
+ ) -> Result < String , VssHeaderProviderError > {
180
180
// Parse k1 parameter to sign.
181
- let invalid_lnurl = || HeaderProviderError :: InvalidData ( format ! ( "invalid lnurl: {}" , lnurl_str) ) ;
181
+ let invalid_lnurl = || VssHeaderProviderError :: InvalidData { error : format ! ( "invalid lnurl: {}" , lnurl_str) } ;
182
182
let mut lnurl = Url :: parse ( lnurl_str) . map_err ( |_| invalid_lnurl ( ) ) ?;
183
183
let domain = lnurl. domain ( ) . ok_or ( invalid_lnurl ( ) ) ?;
184
184
let k1_str = lnurl
@@ -194,11 +194,11 @@ fn sign_lnurl(
194
194
let linking_key_path = linking_key_path ( & hashing_key, domain) ?;
195
195
let private_key = parent_key
196
196
. derive_priv ( engine, & linking_key_path)
197
- . map_err ( HeaderProviderError :: from) ?
197
+ . map_err ( VssHeaderProviderError :: from) ?
198
198
. to_priv ( ) ;
199
199
let public_key = private_key. public_key ( engine) ;
200
- let message =
201
- Message :: from_slice ( & k1 ) . map_err ( |_| HeaderProviderError :: InvalidData ( format ! ( "invalid k1: {:?}" , k1) ) ) ?;
200
+ let message = Message :: from_slice ( & k1 )
201
+ . map_err ( |_| VssHeaderProviderError :: InvalidData { error : format ! ( "invalid k1: {:?}" , k1) } ) ?;
202
202
let sig = engine. sign_ecdsa ( & message, & private_key. inner ) ;
203
203
204
204
// Compose LNURL with signature and linking key.
@@ -209,55 +209,46 @@ fn sign_lnurl(
209
209
Ok ( lnurl. to_string ( ) )
210
210
}
211
211
212
- #[ derive( Deserialize ) ]
212
+ #[ derive( Deserialize , Debug , Clone ) ]
213
213
struct LnurlAuthResponse {
214
- reason : Option < String > ,
215
- token : Option < String > ,
214
+ reason : Option < UntrustedString > ,
215
+ token : Option < UntrustedString > ,
216
216
}
217
217
218
- #[ derive( Deserialize ) ]
218
+ #[ derive( Deserialize , Debug , Clone ) ]
219
219
struct ExpiryClaim {
220
220
exp : Option < u64 > ,
221
221
}
222
222
223
- fn parse_expiry ( jwt_token : & str ) -> Result < Option < u64 > , HeaderProviderError > {
223
+ fn parse_jwt_token ( jwt_token : UntrustedString ) -> Result < JwtToken , VssHeaderProviderError > {
224
224
let parts: Vec < & str > = jwt_token. split ( '.' ) . collect ( ) ;
225
- let invalid = || HeaderProviderError :: InvalidData ( format ! ( "invalid JWT token: {}" , jwt_token) ) ;
225
+ let invalid = || VssHeaderProviderError :: InvalidData { error : format ! ( "invalid JWT token: {}" , jwt_token) } ;
226
226
if parts. len ( ) != 3 {
227
227
return Err ( invalid ( ) ) ;
228
228
}
229
+ let _ = URL_SAFE_NO_PAD . decode ( parts[ 0 ] ) . map_err ( |_| invalid ( ) ) ?;
229
230
let bytes = URL_SAFE_NO_PAD . decode ( parts[ 1 ] ) . map_err ( |_| invalid ( ) ) ?;
231
+ let _ = URL_SAFE_NO_PAD . decode ( parts[ 2 ] ) . map_err ( |_| invalid ( ) ) ?;
230
232
let claim: ExpiryClaim = serde_json:: from_slice ( & bytes) . map_err ( |_| invalid ( ) ) ?;
231
- Ok ( claim. exp )
232
- }
233
-
234
- impl From < bitcoin:: bip32:: Error > for HeaderProviderError {
235
- fn from ( e : bitcoin:: bip32:: Error ) -> HeaderProviderError {
236
- HeaderProviderError :: InvalidData ( e. to_string ( ) )
237
- }
238
- }
239
-
240
- impl From < reqwest:: header:: InvalidHeaderName > for HeaderProviderError {
241
- fn from ( e : reqwest:: header:: InvalidHeaderName ) -> HeaderProviderError {
242
- HeaderProviderError :: InvalidData ( e. to_string ( ) )
243
- }
233
+ Ok ( JwtToken { token_str : jwt_token. into_inner ( ) , expiry : claim. exp } )
244
234
}
245
235
246
- impl From < reqwest :: header :: InvalidHeaderValue > for HeaderProviderError {
247
- fn from ( e : reqwest :: header :: InvalidHeaderValue ) -> HeaderProviderError {
248
- HeaderProviderError :: InvalidData ( e. to_string ( ) )
236
+ impl From < bitcoin :: bip32 :: Error > for VssHeaderProviderError {
237
+ fn from ( e : bitcoin :: bip32 :: Error ) -> VssHeaderProviderError {
238
+ VssHeaderProviderError :: InvalidData { error : e. to_string ( ) }
249
239
}
250
240
}
251
241
252
- impl From < reqwest:: Error > for HeaderProviderError {
253
- fn from ( e : reqwest:: Error ) -> HeaderProviderError {
254
- HeaderProviderError :: RequestError ( e. to_string ( ) )
242
+ impl From < reqwest:: Error > for VssHeaderProviderError {
243
+ fn from ( e : reqwest:: Error ) -> VssHeaderProviderError {
244
+ VssHeaderProviderError :: RequestError { error : e. to_string ( ) }
255
245
}
256
246
}
257
247
258
248
#[ cfg( test) ]
259
249
mod test {
260
250
use crate :: headers:: lnurl_auth_jwt:: { linking_key_path, sign_lnurl} ;
251
+ use crate :: util:: string:: UntrustedString ;
261
252
use bitcoin:: bip32:: ExtendedPrivKey ;
262
253
use bitcoin:: hashes:: hex:: FromHex ;
263
254
use bitcoin:: secp256k1:: Secp256k1 ;
@@ -288,7 +279,7 @@ mod test {
288
279
let signed = sign_lnurl (
289
280
& engine,
290
281
& master,
291
- "https://example.com/path?tag=login&k1=e2af6254a8df433264fa23f67eb8188635d15ce883e8fc020989d5f82ae6f11e" ,
282
+ & UntrustedString :: new ( "https://example.com/path?tag=login&k1=e2af6254a8df433264fa23f67eb8188635d15ce883e8fc020989d5f82ae6f11e" . to_string ( ) ) ,
292
283
)
293
284
. unwrap ( ) ;
294
285
assert_eq ! (
0 commit comments