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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
use std::fs::File;
use std::io::prelude::*;
use super::errors;
use serde::{Deserialize, Serialize};
use serde_json;
use std::collections::BTreeMap;
use std::str::FromStr;
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct JWSEntry {
#[serde(flatten)]
headers: biscuit::jws::RegisteredHeader,
#[serde(flatten)]
ne: biscuit::jwk::RSAKeyParameters,
}
#[derive(Serialize, Deserialize)]
pub struct JWKSetDTO {
pub keys: Vec<JWSEntry>,
}
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct Credentials {
pub project_id: String,
pub private_key_id: String,
private_key: String,
pub client_email: String,
pub client_id: String,
pub api_key: String,
#[serde(default, skip)]
pub pub_key: BTreeMap<String, biscuit::jwk::RSAKeyParameters>,
#[serde(default)]
pub private_key_der: Vec<u8>,
}
use regex::Regex;
use rustc_serialize::base64::FromBase64;
const REGEX: &'static str = r"(-----BEGIN .*-----\n)((?:(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)*\n)+)(-----END .*-----)";
fn pem_to_der(pem_file_contents: &str) -> Vec<u8> {
let re = Regex::new(REGEX).unwrap();
let contents_without_headers = re.replace(pem_file_contents, "$2");
let base64_body = contents_without_headers.replace("\n", "");
base64_body.from_base64().unwrap()
}
fn download_google_jwks(account_mail: &str) -> errors::Result<JWKSetDTO> {
let mut resp = reqwest::Client::new()
.get(&format!(
"https://www.googleapis.com/service_accounts/v1/jwk/{}",
account_mail
))
.send()?;
let jwkset: JWKSetDTO = resp.json()?;
Ok(jwkset)
}
impl Credentials {
pub fn public_key(&self, kid: &str) -> Option<&biscuit::jwk::RSAKeyParameters> {
self.pub_key.get(kid)
}
pub fn create_rsa_key_pair(&self) -> errors::Result<ring::signature::RsaKeyPair> {
ring::signature::RsaKeyPair::from_pkcs8(&self.private_key_der).map_err(|e| e.into())
}
pub fn from_file(credential_file: &str) -> errors::Result<Self> {
let mut f = File::open(credential_file)?;
let mut buffer = Vec::new();
f.read_to_end(&mut buffer)?;
let mut credentials: Credentials = serde_json::from_slice(buffer.as_slice())?;
credentials.compute_missing_fields()?;
Ok(credentials)
}
pub fn add_jwks_public_keys(&mut self, mut jwkset: JWKSetDTO) {
for entry in jwkset.keys.drain(..) {
if !entry.headers.key_id.is_some() {
continue;
}
self.pub_key
.insert(entry.headers.key_id.as_ref().unwrap().to_owned(), entry.ne);
}
}
pub fn compute_missing_fields(&mut self) -> errors::Result<()> {
self.private_key_der = pem_to_der(&self.private_key);
if self.pub_key.is_empty() {
let jwks = download_google_jwks(&self.client_email)?;
self.add_jwks_public_keys(jwks);
let jwks = download_google_jwks("securetoken@system.gserviceaccount.com")?;
self.add_jwks_public_keys(jwks);
}
Ok(())
}
}
impl FromStr for Credentials {
type Err = serde_json::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(serde_json::from_str(s)?)
}
}