CH-J Toolsupravljanje poslužiteljima preko SSH
Meni
Objavljen izvor

CH-J Tools

Pregledaj direktorije i datoteke za određeni programski izdanje.

Preuzmi ZIP izvora
CH-J / Server Manager 1704 datoteke
0.1.2-beta Dodano: · Izgrađeno:
openpgp_algorithms.rs Dodano: Datum nije zabilježen 10,8 KB · 261 linije
Oznaka Preuzmi datoteku
1//! One catalog for Core generation and the Generator/Keychain UI. No vault or update policy.
2use crate::core::error::{CoreError, CoreResult};
3use pgp::{
4 crypto::{hash::HashAlgorithm, public_key::PublicKeyAlgorithm},
5 ser::Serialize,
6 types::{KeyVersion, Mpi, PlainSecretParams, PublicParams, SecretParams},
7};
8use serde_json::Value;
9use std::sync::LazyLock;
10use zeroize::Zeroizing;
12static CATALOG: LazyLock<Value> = LazyLock::new(|| {
13 serde_json::from_str(include_str!("../../resources/openpgp-algorithms.json"))
14 .expect("bundled algorithm catalog")
15});
16fn invalid() -> CoreError {
17 CoreError::new(
18 "OPENPGP_ALGORITHM_INVALID",
19 "Choose an algorithm and capabilities supported by this OpenPGP profile.",
20 )
22pub fn catalog() -> Value {
23 CATALOG.clone()
25pub fn validate(version: u8, id: &str, caps: &str) -> CoreResult<()> {
26 let spec = CATALOG
27 .as_array()
28 .unwrap()
29 .iter()
30 .find(|s| {
31 s["id"] == id
32 && s["versions"]
33 .as_array()
34 .unwrap()
35 .iter()
36 .any(|v| v == version)
37 })
38 .ok_or_else(invalid)?;
39 if caps.is_empty()
40 || caps
41 .chars()
42 .any(|c| !spec["capabilities"].as_str().unwrap().contains(c))
43 {
44 return Err(invalid());
45 }
46 Ok(())
48pub fn is_dsa(id: &str) -> bool {
49 CATALOG
50 .as_array()
51 .unwrap()
52 .iter()
53 .any(|s| s["id"] == id && s["qBits"].is_number())
55/// RustCrypto supplies domain/key generation for all four p/q sizes, including
56/// 2048/224 which rPGP's builder enum cannot express. No crypto is reimplemented.
57pub fn generate_dsa(version: u8, id: &str) -> CoreResult<(PublicParams, SecretParams)> {
58 validate(version, id, "S")?;
59 let size = match id {
60 #[allow(deprecated)]
61 "dsa1024" => dsa::KeySize::DSA_1024_160,
62 "dsa2048q224" => dsa::KeySize::DSA_2048_224,
63 "dsa2048q256" => dsa::KeySize::DSA_2048_256,
64 "dsa3072" => dsa::KeySize::DSA_3072_256,
65 _ => return Err(invalid()),
66 };
67 let mut rng = rand::thread_rng();
68 let components = dsa::Components::generate(&mut rng, size);
69 let secret = dsa::SigningKey::generate(&mut rng, components);
70 let public = PublicParams::DSA(pgp::types::DsaPublicParams {
71 key: secret.verifying_key().clone(),
72 });
73 let x = Zeroizing::new(secret.x().to_bytes_be());
74 let mut material = Zeroizing::new(Vec::new());
75 Mpi::from_slice(&x)
76 .to_writer(&mut *material)
77 .map_err(|_| invalid())?;
78 let plain = PlainSecretParams::try_from_reader_no_checksum(
79 std::io::Cursor::new(material.as_slice()),
80 KeyVersion::V4,
81 PublicKeyAlgorithm::DSA,
82 &public,
83 )
84 .map_err(|_| invalid())?;
85 Ok((public, SecretParams::Plain(plain)))
87pub fn signing_hash(params: &PublicParams) -> HashAlgorithm {
88 if let PublicParams::DSA(p) = params {
89 match p.key.components().q().bits() {
90 0..=160 => HashAlgorithm::Sha1,
91 161..=224 => HashAlgorithm::Sha224,
92 _ => HashAlgorithm::Sha256,
93 }
94 } else {
95 params.hash_alg()
96 }
98pub fn details(params: &PublicParams) -> Value {
99 if let PublicParams::DSA(p) = params {
100 serde_json::json!({"bits":p.key.components().p().bits(),"qBits":p.key.components().q().bits(),"signatureHash":match signing_hash(params){HashAlgorithm::Sha1=>"SHA-1",HashAlgorithm::Sha224=>"SHA-224",_=>"SHA-256"}})
101 } else if let PublicParams::RSA(p) = params {
102 use rsa::traits::PublicKeyParts;
103 serde_json::json!({"bits":p.key.n().bits()})
104 } else if let PublicParams::Elgamal(p) = params {
105 // The serialized form starts with the MPI of the prime p: a two-byte bit count.
106 use pgp::ser::Serialize;
107 p.to_bytes().ok().filter(|b| b.len() >= 2).map(|b| serde_json::json!({"bits":u16::from_be_bytes([b[0],b[1]])})).unwrap_or_else(|| serde_json::json!({}))
108 } else {
109 serde_json::json!({})
110 }
113#[cfg(test)]
114mod tests {
115 use super::*;
116 use crate::core::{keychain, openpgp};
117 use serde_json::json;
118 #[test]
119 fn policy_rejects_dsa_in_other_versions_and_wrong_capabilities() {
120 for id in ["dsa1024", "dsa2048q224", "dsa2048q256", "dsa3072"] {
121 for version in [2, 3, 6] {
122 assert!(validate(version, id, "CS").is_err());
123 assert!(openpgp::generate(&json!({"version":version,"name":"Rejected DSA fixture","algorithm":id,"legacyConfirmed":true})).is_err());
124 if version == 6 {
125 assert!(openpgp::generate(&json!({"name":"Rejected subkey","subkeys":[{"role":"sign","algorithm":id}]})).is_err());
126 }
127 }
128 for version in [4, 5] {
129 assert!(validate(version, id, "CS").is_ok());
130 assert!(validate(version, id, "E").is_err());
131 assert!(validate(version, id, "A").is_err());
132 }
133 }
134 // Reject imported V6 DSA before performing certificate/signature verification.
135 let packet = [0xc6, 6, 6, 0, 0, 0, 0, 17];
136 assert!(keychain::parse(&packet, "", "").is_err());
137 }
138 #[test]
139 #[ignore = "independent GnuPG signatures in an explicitly selected synthetic fixture directory"]
140 fn verify_independent_dsa_signatures() {
141 let root = std::path::PathBuf::from(std::env::var("CHJ_DSA_FIXTURES").unwrap());
142 for version in [4, 5] {
143 for id in ["dsa1024", "dsa2048q224", "dsa2048q256", "dsa3072"] {
144 let dir = root.join(format!("v{version}-{id}"));
145 let entry = keychain::parse(
146 &std::fs::read(dir.join("public.asc")).unwrap(),
147 "Independent DSA fixture",
148 "",
149 )
150 .unwrap();
151 let sig = std::fs::read(dir.join("gpg-signature.asc")).unwrap();
152 keychain::verify(&entry, &dir.join("payload"), &sig).unwrap();
153 }
154 }
155 }
156 #[test]
157 fn dsa_profiles_sign_revoke_and_roundtrip_with_exact_parameters() {
158 let dir = tempfile::tempdir().unwrap();
159 let path = dir.path().join("payload");
160 std::fs::write(&path, b"Synthetic DSA interoperability payload\n").unwrap();
161 let export = std::env::var("CHJ_DSA_FIXTURES")
162 .ok()
163 .map(std::path::PathBuf::from);
164 if let Some(root) = &export {
165 std::fs::create_dir(root).unwrap();
166 #[cfg(unix)]
167 {
168 use std::os::unix::fs::PermissionsExt;
169 std::fs::set_permissions(root, std::fs::Permissions::from_mode(0o700)).unwrap();
170 }
171 }
172 for version in [4, 5] {
173 for (id, p, q, hash) in [
174 ("dsa1024", 1024, 160, 2),
175 ("dsa2048q224", 2048, 224, 11),
176 ("dsa2048q256", 2048, 256, 8),
177 ("dsa3072", 3072, 256, 8),
178 ] {
179 let started = std::time::Instant::now();
180 let (out,entry)=openpgp::generate(&json!({"name":"Synthetic DSA fixture","version":version,"algorithm":id,"primaryCapabilities":"CS","passphrase":"synthetic-dsa-password","subkeys":[{"role":"encrypt","algorithm":"curve25519"}]})).unwrap();
181 let details = keychain::describe(&entry);
182 assert_eq!(details["algorithm"], "DSA");
183 assert_eq!(details["version"], version);
184 assert_eq!(details["bits"], p);
185 assert_eq!(details["qBits"], q);
186 assert_eq!(details["effectiveCapabilities"], "CS");
187 assert_eq!(details["certificateCapabilities"], "CSE");
188 let imported = keychain::parse(
189 entry["private"].as_str().unwrap().as_bytes(),
190 "Imported",
191 "",
192 )
193 .unwrap();
194 assert_eq!(imported["fingerprint"], entry["fingerprint"]);
195 let sig = keychain::sign(&entry, &path, "synthetic-dsa-password").unwrap();
196 use base64::Engine;
197 let encoded = sig
198 .lines()
199 .filter(|line| {
200 !line.is_empty()
201 && !line.starts_with("-----")
202 && !line.starts_with('=')
203 && !line.contains(':')
204 })
205 .collect::<String>();
206 let raw = base64::engine::general_purpose::STANDARD
207 .decode(encoded)
208 .unwrap();
209 let offset = if raw[0] & 64 != 0 {
210 if raw[1] < 192 {
211 2
212 } else if raw[1] < 224 {
213 3
214 } else {
215 6
216 }
217 } else {
218 match raw[0] & 3 {
219 0 => 2,
220 1 => 3,
221 2 => 5,
222 _ => panic!("indeterminate signature"),
223 }
224 };
225 assert_eq!(raw[offset], version as u8);
226 assert_eq!(raw[offset + 3], hash);
227 keychain::verify(&imported, &path, sig.as_bytes()).unwrap();
228 assert!(keychain::sign(&entry, &path, "wrong").is_err());
229 let revoked = keychain::revoke(&entry, "synthetic-dsa-password").unwrap();
230 assert_eq!(keychain::describe(&revoked)["revoked"], true);
231 assert!(keychain::verify(&revoked, &path, sig.as_bytes()).is_err());
232 let public =
233 keychain::parse(entry["public"].as_str().unwrap().as_bytes(), "Public", "")
234 .unwrap();
235 assert!(public["private"].is_null());
236 if let Some(root) = &export {
237 let base = root.join(format!("v{version}-{id}"));
238 std::fs::create_dir(&base).unwrap();
239 for file in out["files"].as_array().unwrap() {
240 std::fs::write(
241 base.join(format!("{}.asc", file["kind"].as_str().unwrap())),
242 file["content"].as_str().unwrap(),
243 )
244 .unwrap();
245 }
246 std::fs::write(base.join("signature.asc"), &sig).unwrap();
247 std::fs::copy(&path, base.join("payload")).unwrap();
248 std::fs::write(
249 base.join("metadata.json"),
250 serde_json::to_vec_pretty(&details).unwrap(),
251 )
252 .unwrap();
253 }
254 eprintln!(
255 "DSA profile V{version} {id} passed in {}ms",
256 started.elapsed().as_millis()
257 );
258 }
259 }
260 }
SHA-2563d8dea434d987168b9f3b6786a0d58b633226600b7646b6f44a795d0269618ae

CH-J Proprietary Software License 1.14 · Izvor je osiguran pod CH-J Proprietary Software License 1.14. Njena dostupnost ne mijenja uvjete licenciranja niti daje dodatna prava.

Arhiv SHA-256ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f
Datoteke