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