keyhog_scanner/checksum/
npm.rs1use super::github::{base62_encode_u32, crc32};
2use super::{ChecksumResult, ChecksumValidator};
3
4pub struct NpmTokenValidator;
9
10impl ChecksumValidator for NpmTokenValidator {
11 fn validator_id(&self) -> &str {
12 "npm-access-token"
13 }
14
15 fn validate(&self, credential: &str) -> ChecksumResult {
16 let payload = match credential.strip_prefix("npm_") {
17 Some(p) => p,
18 None => return ChecksumResult::NotApplicable,
19 };
20 if payload.len() != 36 {
21 return ChecksumResult::NotApplicable;
22 }
23 if !payload.chars().all(|c| c.is_ascii_alphanumeric()) {
24 return ChecksumResult::Invalid;
25 }
26 let entropy = &payload[..30];
27 let checksum_str = &payload[30..];
28 let expected = base62_encode_u32(crc32(entropy.as_bytes()), 6);
29 if expected == checksum_str {
30 ChecksumResult::Valid
31 } else {
32 ChecksumResult::Invalid
33 }
34 }
35}
36
37pub struct PypiTokenValidator;
44
45impl ChecksumValidator for PypiTokenValidator {
46 fn validator_id(&self) -> &str {
47 "pypi-api-token"
48 }
49
50 fn validate(&self, credential: &str) -> ChecksumResult {
51 let payload = match credential.strip_prefix("pypi-") {
52 Some(p) => p,
53 None => return ChecksumResult::NotApplicable,
54 };
55 if payload.len() < 20 {
56 return ChecksumResult::Invalid;
57 }
58 let decoded =
59 base64::Engine::decode(&base64::engine::general_purpose::URL_SAFE_NO_PAD, payload)
60 .or_else(|_| {
61 base64::Engine::decode(
62 &base64::engine::general_purpose::STANDARD_NO_PAD,
63 payload,
64 )
65 })
66 .or_else(|_| {
67 base64::Engine::decode(&base64::engine::general_purpose::URL_SAFE, payload)
68 })
69 .or_else(|_| {
70 base64::Engine::decode(&base64::engine::general_purpose::STANDARD, payload)
71 });
72
73 match decoded {
74 Ok(bytes) if bytes.len() >= 32 => ChecksumResult::Valid,
75 Ok(_) => ChecksumResult::Invalid,
76 Err(_) => ChecksumResult::Invalid,
77 }
78 }
79}