Sorgente pubblicata
Scarica sorgente ZIP
CH-J Tools
Sfoglia directory e file per una specifica release di applicazioni.
CH-J / Server Manager
1704 file
0.1.2-beta
Aggiunto: · Costruito:
1
use super::{key::Key, signature::Signature, wire::*};2
use crate::core::error::CoreResult;3
use pgp::types::KeyDetails;4
use pgp::types::Timestamp;5
use serde_json::{json, Value};7
pub struct Certificate {8
pub packets: Vec<Packet>,9
pub primary: Key,10
}11
pub fn certificate_data(key: &Key, uid: &[u8]) -> CoreResult<Vec<u8>> {12
let mut data = key.prefix()?;13
if key.version == 5 {14
data.push(0xb4);15
data.extend((uid.len() as u32).to_be_bytes());16
}17
data.extend(uid);18
Ok(data)19
}20
pub fn binding_data(primary: &Key, sub: &Key) -> CoreResult<Vec<u8>> {21
let mut data = primary.prefix()?;22
data.extend(sub.prefix()?);23
Ok(data)24
}25
pub fn allowed(key: &Key, primary: bool) -> &'static str {26
match key.algorithm {27
1 => {28
if primary {29
"CSAE"30
} else {31
"SAE"32
}33
}34
17 => {35
if primary {36
"CS"37
} else {38
"S"39
}40
}41
3 | 19 | 22 => {42
if primary {43
"CSA"44
} else {45
"SA"46
}47
}48
2 | 18 => "E",49
_ => "",50
}51
}52
fn flags(sig: &Signature) -> Option<u8> {53
sig.field(27).and_then(|b| b.first()).copied()54
}55
fn key_meta(56
key: &Key,57
primary: bool,58
binding: Option<&Signature>,59
revoked: bool,60
parent: bool,61
verified: bool,62
) -> CoreResult<Value> {63
let now = Timestamp::now().as_secs();64
let bits = if key.version <= 3 {65
None66
} else {67
binding.and_then(flags)68
};69
let declared = bits70
.map(crate::core::openpgp::capabilities_from_bits)71
.unwrap_or_default();72
let derived = if bits.is_none() {73
allowed(key, primary).replace('A', "")74
} else {75
String::new()76
};77
let requested = if bits.is_some() { &declared } else { &derived };78
let expires = if key.version <= 3 {79
(key.days > 0).then_some(key.created as u64 + key.days as u64 * 86400)80
} else {81
binding82
.and_then(|s| s.duration(9))83
.filter(|d| *d > 0)84
.map(|d| key.created as u64 + d as u64)85
};86
let expired =87
expires.is_some_and(|d| d <= now as u64) || binding.is_some_and(|s| !s.current(now));88
let usable = verified89
&& parent90
&& !revoked91
&& !expired92
&& key.created <= now.saturating_add(300)93
&& binding.is_some_and(|s| s.current(now));94
let effective: String = if usable {95
requested96
.chars()97
.filter(|c| allowed(key, primary).contains(*c))98
.collect()99
} else {100
String::new()101
};102
let mut details = json!({"fingerprint":hex::encode_upper(key.fingerprint()?),"version":key.version,"keyId":hex::encode_upper(key.key_id()?),"algorithm":match key.algorithm{1|2|3=>"RSA",17=>"DSA",18=>"ECDH",19=>"ECDSA",22=>"EdDSALegacy",_=>"Unsupported"},"declaredCapabilities":declared,"derivedCapabilities":derived,"effectiveCapabilities":effective,"capabilitySource":if bits.is_some(){"declared"}else{"derived"},"keyFlagBits":bits,"revoked":revoked,"expired":expired,"bindingValid":verified,"usable":usable,"keyCreatedAt":key.created,"expiresAt":expires});103
for (name, value) in104
crate::core::openpgp_algorithms::details(key.parameters()?.public.public_params())105
.as_object()106
.unwrap()107
{108
details[name] = value.clone();109
}110
Ok(details)111
}112
impl Certificate {113
pub fn parse(bytes: &[u8]) -> CoreResult<Self> {114
let packets = packets(bytes)?;115
let first = packets.first().ok_or_else(invalid)?;116
if ![5, 6].contains(&first.tag) {117
return Err(invalid());118
}119
let primary = Key::parse(first)?;120
let mut keys = 0;121
for (i, p) in packets.iter().enumerate() {122
match p.tag {123
5 | 6 => {124
if i != 0 {125
return Err(invalid());126
}127
}128
7 | 14 => {129
if (first.tag == 6 && p.tag == 7)130
|| primary.version != 5131
|| Key::parse(p)?.version != 5132
{133
return Err(invalid());134
}135
keys += 1;136
if keys > 64 {137
return Err(invalid());138
}139
}140
2 => {141
Signature::parse(&p.body)?;142
}143
13 => {144
if p.body.len() > 1024 {145
return Err(invalid());146
}147
}148
17 if primary.version == 5 => {}149
_ => return Err(invalid()),150
}151
}152
Ok(Self { packets, primary })153
}154
pub fn public_packets(&self) -> CoreResult<Vec<Packet>> {155
self.packets156
.iter()157
.map(|p| {158
if [5, 7].contains(&p.tag) {159
let key = Key::parse(p)?;160
Ok(Packet::new(if p.tag == 5 { 6 } else { 14 }, key.public))161
} else {162
Ok(p.clone())163
}164
})165
.collect()166
}167
pub fn public_armor(&self) -> CoreResult<String> {168
Ok(armor(169
"PUBLIC KEY BLOCK",170
&encode(&self.public_packets()?, self.primary.version)?,171
))172
}173
pub fn private_armor(&self) -> CoreResult<String> {174
if self.packets[0].tag != 5 {175
return Err(invalid());176
}177
Ok(armor(178
"PRIVATE KEY BLOCK",179
&encode(&self.packets, self.primary.version)?,180
))181
}182
fn group(&self, index: usize) -> impl Iterator<Item = (usize, &Packet)> {183
self.packets184
.iter()185
.enumerate()186
.skip(index + 1)187
.take_while(|(_, p)| p.tag == 2)188
}189
pub fn metadata(&self) -> CoreResult<Value> {190
let params = self.primary.parameters()?;191
let now = Timestamp::now().as_secs();192
let mut candidates: Vec<(usize, Signature)> = Vec::new();193
let mut user_ids = Vec::new();194
for (index, p) in self.packets.iter().enumerate() {195
if p.tag == 13 {196
let data = certificate_data(&self.primary, &p.body)?;197
let revoked_at = self198
.group(index)199
.filter_map(|(_, p)| Signature::parse(&p.body).ok())200
.filter(|s| s.typ == 0x30 && s.verify(¶ms, &data).is_ok())201
.map(|s| s.created)202
.max();203
for (i, p) in self.group(index) {204
let sig = Signature::parse(&p.body)?;205
if (0x10..=0x13).contains(&sig.typ)206
&& (sig.version == self.primary.version207
|| (sig.version <= 3 && self.primary.version <= 3))208
&& sig.created <= now.saturating_add(300)209
&& revoked_at.is_none_or(|t| sig.created > t)210
&& sig.verify(¶ms, &data).is_ok()211
{212
candidates.push((i, sig));213
}214
}215
user_ids.push(String::from_utf8_lossy(&p.body).to_string());216
}217
}218
if self.primary.version == 5 {219
for (i, p) in self.group(0) {220
let sig = Signature::parse(&p.body)?;221
if sig.typ == 0x1f222
&& sig.version == 5223
&& sig.created <= now.saturating_add(300)224
&& sig.verify(¶ms, &self.primary.prefix()?).is_ok()225
{226
candidates.push((i, sig));227
}228
}229
}230
let binding = candidates231
.iter()232
.max_by_key(|(i, s)| (s.created, s.typ == 0x1f, *i))233
.map(|(_, s)| s);234
let revoked = self235
.packets236
.iter()237
.filter(|p| p.tag == 2)238
.filter_map(|p| Signature::parse(&p.body).ok())239
.any(|s| {240
s.typ == 0x20241
&& (s.version == self.primary.version242
|| (s.version <= 3 && self.primary.version <= 3))243
&& s.verify(¶ms, &self.primary.prefix().unwrap_or_default())244
.is_ok()245
});246
let mut result = key_meta(247
&self.primary,248
true,249
binding,250
revoked,251
true,252
binding.is_some(),253
)?;254
let parent = result["usable"] == true255
&& result["effectiveCapabilities"]256
.as_str()257
.unwrap_or("")258
.contains('C');259
let mut subkeys = Vec::new();260
for (index, p) in self261
.packets262
.iter()263
.enumerate()264
.filter(|(_, p)| [7, 14].contains(&p.tag))265
{266
let key = Key::parse(p)?;267
let data = binding_data(&self.primary, &key)?;268
let bindings: Vec<_> = self269
.group(index)270
.filter_map(|(i, p)| Signature::parse(&p.body).ok().map(|s| (i, s)))271
.filter(|(_, s)| {272
s.typ == 0x18273
&& s.version == 5274
&& s.created <= now.saturating_add(300)275
&& s.verify(¶ms, &data).is_ok()276
})277
.collect();278
let binding = bindings279
.iter()280
.max_by_key(|(i, s)| (s.created, *i))281
.map(|(_, s)| s);282
let revoked = self283
.group(index)284
.filter_map(|(_, p)| Signature::parse(&p.body).ok())285
.any(|s| s.typ == 0x28 && s.version == 5 && s.verify(¶ms, &data).is_ok());286
let verified = binding.is_some_and(|s| {287
let signing = flags(s)288
.map(|b| b & 2 != 0)289
.unwrap_or_else(|| allowed(&key, false).contains('S'));290
!signing291
|| s.field(32)292
.and_then(|bytes| Signature::parse(bytes).ok())293
.is_some_and(|back| {294
back.version == 5295
&& back.typ == 0x19296
&& back.current(now)297
&& key298
.parameters()299
.is_ok_and(|p| back.verify(&p, &data).is_ok())300
})301
});302
let mut sub = key_meta(&key, false, binding, revoked, parent, verified)?;303
let caps = sub["effectiveCapabilities"].as_str().unwrap_or("");304
sub["roles"] = json!([('S', "sign"), ('A', "authenticate"), ('E', "encrypt")]305
.iter()306
.filter_map(|(c, role)| caps.contains(*c).then_some(*role))307
.collect::<Vec<_>>());308
subkeys.push(sub);309
}310
let mut aggregate = String::new();311
for cap in "CSAE".chars() {312
if std::iter::once(&result).chain(subkeys.iter()).any(|m| {313
m["effectiveCapabilities"]314
.as_str()315
.unwrap_or("")316
.contains(cap)317
}) {318
aggregate.push(cap)319
}320
}321
result["userIds"] = json!(user_ids);322
result["subkeys"] = json!(subkeys);323
result["certificateCapabilities"] = json!(aggregate);324
result["profile"] = json!(match self.primary.version {325
2 => "PGP 2.x / RFC 1991",326
3 => "Historical PGP / RFC 1991 and RFC 2440",327
_ => "LibrePGP / draft-koch-librepgp-06",328
});329
result["keyserverSupported"] = json!(false);330
result["editSupported"] = json!(self.primary.version == 5);331
result["revokeSupported"] = json!(true);332
result["signSupported"] = json!(aggregate.contains('S'));333
Ok(result)334
}335
}SHA-256
665a949e32f528b4ce02398ccf3a961ce9088c755824729ecbb1abee805360ccCH-J Proprietary Software License 1.14 · La sorgente è fornita sotto CH-J Proprietary Software License 1.14. La sua disponibilità non modifica i termini di licenza o concede diritti aggiuntivi.
Archivio SHA-256
ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f