]>
Commit | Line | Data |
---|---|---|
6c30068e | 1 | use failure::*; |
d01e2420 | 2 | use lazy_static::lazy_static; |
6c30068e DM |
3 | |
4 | use openssl::rsa::{Rsa}; | |
d01e2420 | 5 | use openssl::pkey::{PKey, Public, Private}; |
1e76cbc6 | 6 | use openssl::sha; |
d01e2420 | 7 | |
6c30068e DM |
8 | use std::path::PathBuf; |
9 | ||
e18a6c9e DM |
10 | use proxmox::tools::{ |
11 | try_block, | |
feaa1ad3 | 12 | fs::{file_get_contents, replace_file, CreateOptions}, |
e18a6c9e DM |
13 | }; |
14 | ||
5ddf8cb1 DM |
15 | fn compute_csrf_secret_digest( |
16 | timestamp: i64, | |
1e76cbc6 DM |
17 | secret: &[u8], |
18 | username: &str, | |
19 | ) -> String { | |
20 | ||
1e76cbc6 | 21 | let mut hasher = sha::Sha256::new(); |
5ddf8cb1 | 22 | let data = format!("{:08X}:{}:", timestamp, username); |
cf671670 | 23 | hasher.update(data.as_bytes()); |
1e76cbc6 DM |
24 | hasher.update(secret); |
25 | ||
5ddf8cb1 DM |
26 | base64::encode_config(&hasher.finish(), base64::STANDARD_NO_PAD) |
27 | } | |
28 | ||
29 | pub fn assemble_csrf_prevention_token( | |
30 | secret: &[u8], | |
31 | username: &str, | |
32 | ) -> String { | |
33 | ||
34 | let epoch = std::time::SystemTime::now().duration_since( | |
35 | std::time::SystemTime::UNIX_EPOCH).unwrap().as_secs() as i64; | |
36 | ||
37 | let digest = compute_csrf_secret_digest(epoch, secret, username); | |
1e76cbc6 | 38 | |
cf671670 | 39 | format!("{:08X}:{}", epoch, digest) |
1e76cbc6 DM |
40 | } |
41 | ||
5ddf8cb1 DM |
42 | pub fn verify_csrf_prevention_token( |
43 | secret: &[u8], | |
44 | username: &str, | |
45 | token: &str, | |
46 | min_age: i64, | |
47 | max_age: i64, | |
48 | ) -> Result<i64, Error> { | |
49 | ||
50 | use std::collections::VecDeque; | |
51 | ||
52 | let mut parts: VecDeque<&str> = token.split(':').collect(); | |
53 | ||
54 | try_block!({ | |
55 | ||
56 | if parts.len() != 2 { | |
57 | bail!("format error - wrong number of parts."); | |
58 | } | |
59 | ||
60 | let timestamp = parts.pop_front().unwrap(); | |
61 | let sig = parts.pop_front().unwrap(); | |
62 | ||
63 | let ttime = i64::from_str_radix(timestamp, 16). | |
64 | map_err(|err| format_err!("timestamp format error - {}", err))?; | |
65 | ||
66 | let digest = compute_csrf_secret_digest(ttime, secret, username); | |
67 | ||
68 | if digest != sig { | |
69 | bail!("invalid signature."); | |
70 | } | |
71 | ||
72 | let now = std::time::SystemTime::now().duration_since( | |
73 | std::time::SystemTime::UNIX_EPOCH)?.as_secs() as i64; | |
74 | ||
75 | let age = now - ttime; | |
76 | if age < min_age { | |
77 | bail!("timestamp newer than expected."); | |
78 | } | |
79 | ||
80 | if age > max_age { | |
81 | bail!("timestamp too old."); | |
82 | } | |
83 | ||
84 | Ok(age) | |
85 | }).map_err(|err| format_err!("invalid csrf token - {}", err)) | |
86 | } | |
87 | ||
6c30068e DM |
88 | pub fn generate_csrf_key() -> Result<(), Error> { |
89 | ||
9f4962d3 | 90 | let path = PathBuf::from(configdir!("/csrf.key")); |
6c30068e DM |
91 | |
92 | if path.exists() { return Ok(()); } | |
93 | ||
94 | let rsa = Rsa::generate(2048).unwrap(); | |
95 | ||
96 | let pem = rsa.private_key_to_pem()?; | |
97 | ||
98 | use nix::sys::stat::Mode; | |
99 | ||
f74a03da | 100 | let backup_user = crate::backup::backup_user()?; |
17ed456c | 101 | |
feaa1ad3 WB |
102 | replace_file( |
103 | &path, | |
104 | &pem, | |
105 | CreateOptions::new() | |
106 | .perm(Mode::from_bits_truncate(0o0640)) | |
107 | .owner(nix::unistd::ROOT) | |
f74a03da | 108 | .group(backup_user.gid), |
feaa1ad3 | 109 | )?; |
6c30068e DM |
110 | |
111 | Ok(()) | |
112 | } | |
113 | ||
114 | pub fn generate_auth_key() -> Result<(), Error> { | |
115 | ||
9f4962d3 | 116 | let priv_path = PathBuf::from(configdir!("/authkey.key")); |
6c30068e DM |
117 | |
118 | let mut public_path = priv_path.clone(); | |
119 | public_path.set_extension("pub"); | |
120 | ||
121 | if priv_path.exists() && public_path.exists() { return Ok(()); } | |
122 | ||
123 | let rsa = Rsa::generate(4096).unwrap(); | |
124 | ||
125 | let priv_pem = rsa.private_key_to_pem()?; | |
126 | ||
127 | use nix::sys::stat::Mode; | |
128 | ||
feaa1ad3 WB |
129 | replace_file( |
130 | &priv_path, &priv_pem, CreateOptions::new().perm(Mode::from_bits_truncate(0o0600)))?; | |
6c30068e DM |
131 | |
132 | let public_pem = rsa.public_key_to_pem()?; | |
133 | ||
f74a03da | 134 | let backup_user = crate::backup::backup_user()?; |
ba3eb88d | 135 | |
feaa1ad3 WB |
136 | replace_file( |
137 | &public_path, | |
138 | &public_pem, | |
139 | CreateOptions::new() | |
140 | .perm(Mode::from_bits_truncate(0o0640)) | |
141 | .owner(nix::unistd::ROOT) | |
f74a03da | 142 | .group(backup_user.gid), |
feaa1ad3 | 143 | )?; |
6c30068e DM |
144 | |
145 | Ok(()) | |
146 | } | |
d01e2420 DM |
147 | |
148 | pub fn csrf_secret() -> &'static [u8] { | |
149 | ||
150 | lazy_static! { | |
151 | static ref SECRET: Vec<u8> = | |
e18a6c9e | 152 | file_get_contents(configdir!("/csrf.key")).unwrap(); |
d01e2420 DM |
153 | } |
154 | ||
155 | &SECRET | |
156 | } | |
157 | ||
158 | fn load_private_auth_key() -> Result<PKey<Private>, Error> { | |
159 | ||
e18a6c9e | 160 | let pem = file_get_contents(configdir!("/authkey.key"))?; |
d01e2420 DM |
161 | let rsa = Rsa::private_key_from_pem(&pem)?; |
162 | let key = PKey::from_rsa(rsa)?; | |
163 | ||
164 | Ok(key) | |
165 | } | |
166 | ||
167 | pub fn private_auth_key() -> &'static PKey<Private> { | |
168 | ||
169 | lazy_static! { | |
170 | static ref KEY: PKey<Private> = load_private_auth_key().unwrap(); | |
171 | } | |
172 | ||
173 | &KEY | |
174 | } | |
175 | ||
176 | fn load_public_auth_key() -> Result<PKey<Public>, Error> { | |
177 | ||
e18a6c9e | 178 | let pem = file_get_contents(configdir!("/authkey.pub"))?; |
d01e2420 DM |
179 | let rsa = Rsa::public_key_from_pem(&pem)?; |
180 | let key = PKey::from_rsa(rsa)?; | |
181 | ||
182 | Ok(key) | |
183 | } | |
184 | ||
185 | pub fn public_auth_key() -> &'static PKey<Public> { | |
186 | ||
187 | lazy_static! { | |
188 | static ref KEY: PKey<Public> = load_public_auth_key().unwrap(); | |
189 | } | |
190 | ||
191 | &KEY | |
192 | } |