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