공개된 소스
소스 ZIP 다운로드
CH-J Tools
특정 애플리케이션 릴리스의 디렉터리와 파일을 살펴보세요.
CH-J / Server Manager
1704 개 파일
0.1.2-beta
추가됨: · 빌드됨:
1
//! One catalog for Core generation and the Generator/Keychain UI. No vault or update policy.2
use crate::core::error::{CoreError, CoreResult};3
use pgp::{4
crypto::{hash::HashAlgorithm, public_key::PublicKeyAlgorithm},5
ser::Serialize,6
types::{KeyVersion, Mpi, PlainSecretParams, PublicParams, SecretParams},7
};8
use serde_json::Value;9
use std::sync::LazyLock;10
use zeroize::Zeroizing;12
static CATALOG: LazyLock<Value> = LazyLock::new(|| {13
serde_json::from_str(include_str!("../../resources/openpgp-algorithms.json"))14
.expect("bundled algorithm catalog")15
});16
fn invalid() -> CoreError {17
CoreError::new(18
"OPENPGP_ALGORITHM_INVALID",19
"Choose an algorithm and capabilities supported by this OpenPGP profile.",20
)21
}22
pub fn catalog() -> Value {23
CATALOG.clone()24
}25
pub fn validate(version: u8, id: &str, caps: &str) -> CoreResult<()> {26
let spec = CATALOG27
.as_array()28
.unwrap()29
.iter()30
.find(|s| {31
s["id"] == id32
&& 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
|| caps41
.chars()42
.any(|c| !spec["capabilities"].as_str().unwrap().contains(c))43
{44
return Err(invalid());45
}46
Ok(())47
}48
pub fn is_dsa(id: &str) -> bool {49
CATALOG50
.as_array()51
.unwrap()52
.iter()53
.any(|s| s["id"] == id && s["qBits"].is_number())54
}55
/// RustCrypto supplies domain/key generation for all four p/q sizes, including56
/// 2048/224 which rPGP's builder enum cannot express. No crypto is reimplemented.57
pub 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)))86
}87
pub 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
}97
}98
pub 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
}111
}113
#[cfg(test)]114
mod 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 = sig198
.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::STANDARD207
.decode(encoded)208
.unwrap();209
let offset = if raw[0] & 64 != 0 {210
if raw[1] < 192 {211
2212
} else if raw[1] < 224 {213
3214
} else {215
6216
}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
}261
}SHA-256
3d8dea434d987168b9f3b6786a0d58b633226600b7646b6f44a795d0269618aeCH-J Proprietary Software License 1.14 · 소스는 CH-J Proprietary Software License 1.14에 따라 제공됩니다. 소스를 공개한다고 해서 라이선스 조항이 바뀌거나 추가 권리가 부여되지는 않습니다.
압축 파일 SHA-256
ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f