1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use openssl::pkey::PKey;
use openssl::hash::MessageDigest;
use openssl::sign::{Signer as SslSigner};
use hyper::Uri;
use serde_json;
use error::WebPushError;
use base64::{self, URL_SAFE_NO_PAD};
use vapid::VapidKey;
use std::collections::BTreeMap;
use time;
use serde_json::{Value, Number};
lazy_static! {
static ref JWT_HEADERS: String =
base64::encode_config(
&serde_json::to_string(
&json!({
"typ": "JWT",
"alg": "ES256"
})
).unwrap(),
URL_SAFE_NO_PAD
);
}
#[derive(Debug)]
pub struct VapidSignature {
pub auth_t: String,
pub auth_k: String,
}
impl<'a> Into<String> for &'a VapidSignature {
fn into(self) -> String {
format!("WebPush {}", self.auth_t)
}
}
pub struct VapidSigner {}
impl VapidSigner {
pub fn sign(key: VapidKey, endpoint: &Uri, mut claims: BTreeMap<&str, Value>) -> Result<VapidSignature, WebPushError> {
if !claims.contains_key("aud") {
let audience = format!(
"{}://{}",
endpoint.scheme().unwrap(),
endpoint.host().unwrap()
);
claims.insert("aud", Value::String(audience));
}
if !claims.contains_key("exp") {
let expiry = time::now_utc() + time::Duration::hours(12);
let number = Number::from(expiry.to_timespec().sec);
claims.insert("exp", Value::Number(number));
}
let signing_input = format!(
"{}.{}",
*JWT_HEADERS,
base64::encode_config(&serde_json::to_string(&claims)?, URL_SAFE_NO_PAD));
let public_key = key.public_key();
let auth_k = base64::encode_config(&public_key, URL_SAFE_NO_PAD);
let pkey = PKey::from_ec_key(key.0)?;
let mut signer = SslSigner::new(MessageDigest::sha256(), &pkey)?;
signer.update(signing_input.as_bytes())?;
let signature = signer.sign_to_vec()?;
let r_off: usize = 3;
let r_len = signature[r_off] as usize;
let s_off: usize = r_off + r_len + 2;
let s_len = signature[s_off] as usize;
let mut r_val = &signature[(r_off + 1)..(r_off + 1 + r_len)];
let mut s_val = &signature[(s_off + 1)..(s_off + 1 + s_len)];
if r_len == 33 && r_val[0] == 0 {
r_val = &r_val[1..];
}
if s_len == 33 && s_val[0] == 0 {
s_val = &s_val[1..];
}
let mut sigval: Vec<u8> = Vec::with_capacity(64);
sigval.extend(r_val);
sigval.extend(s_val);
println!("{}", auth_k);
let auth_t = format!("{}.{}", signing_input, base64::encode_config(&sigval, URL_SAFE_NO_PAD));
Ok(VapidSignature { auth_t, auth_k })
}
}
#[cfg(test)]
mod tests {
use vapid::VapidSignature;
#[test]
fn test_vapid_signature_aesgcm_format() {
let vapid_signature = &VapidSignature {
auth_t: String::from("foo"),
auth_k: String::from("bar"),
};
let header_value: String = vapid_signature.into();
assert_eq!("WebPush foo", &header_value);
}
}