Skip to content

Commit 491978a

Browse files
authored
Test/core crypto primitives (#16)
* test(core/cert): add RefUpdateCert validation coverage * test(core/cid): add CID parsing and hash tests * test(core/did): add DID validation and predicate tests * test(core/identity): add keypair and signed payload tests * style(did): wrap long assert! messages to satisfy rustfmt * style(identity): wrap long assert! messages to satisfy rustfmt
1 parent 0b056f3 commit 491978a

4 files changed

Lines changed: 276 additions & 0 deletions

File tree

‎crates/gitlawb-core/src/cert.rs‎

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,4 +257,105 @@ mod tests {
257257
// Round-trip
258258
let _: RefUpdateCert = serde_json::from_str(&json).unwrap();
259259
}
260+
261+
#[test]
262+
fn validate_structure_rejects_wrong_cert_type() {
263+
let kp = Keypair::generate();
264+
let mut cert = RefUpdateCert::new(
265+
kp.did(),
266+
"refs/heads/main".to_string(),
267+
dummy_hash('0'),
268+
dummy_hash('a'),
269+
1,
270+
&kp,
271+
)
272+
.unwrap();
273+
cert.body.type_ = "gitlawb/ref-update/v0".to_string();
274+
assert!(cert.validate_structure().is_err());
275+
}
276+
277+
#[test]
278+
fn validate_structure_rejects_invalid_to_hash() {
279+
let kp = Keypair::generate();
280+
let mut cert = RefUpdateCert::new(
281+
kp.did(),
282+
"refs/heads/main".to_string(),
283+
dummy_hash('0'),
284+
dummy_hash('a'),
285+
1,
286+
&kp,
287+
)
288+
.unwrap();
289+
cert.body.to = "not-a-valid-sha256".to_string();
290+
assert!(cert.validate_structure().is_err());
291+
}
292+
293+
#[test]
294+
fn validate_structure_rejects_empty_signatures() {
295+
let kp = Keypair::generate();
296+
let mut cert = RefUpdateCert::new(
297+
kp.did(),
298+
"refs/heads/main".to_string(),
299+
dummy_hash('0'),
300+
dummy_hash('a'),
301+
1,
302+
&kp,
303+
)
304+
.unwrap();
305+
cert.signatures.clear();
306+
assert!(cert.validate_structure().is_err());
307+
}
308+
309+
#[test]
310+
fn tampered_signature_fails_verify_all() {
311+
let kp = Keypair::generate();
312+
let kp2 = Keypair::generate();
313+
let mut cert = RefUpdateCert::new(
314+
kp.did(),
315+
"refs/heads/main".to_string(),
316+
dummy_hash('0'),
317+
dummy_hash('a'),
318+
1,
319+
&kp,
320+
)
321+
.unwrap();
322+
// Replace kp's signature with a signature from kp2 over different bytes.
323+
// The signer DID still points to kp, so verification must fail.
324+
cert.signatures[0].sig = kp2.sign_b64(b"unrelated bytes");
325+
assert!(cert.verify_all().is_err());
326+
}
327+
328+
#[test]
329+
fn threshold_not_met_when_signer_outside_maintainer_list() {
330+
let kp_pusher = Keypair::generate();
331+
let kp_maintainer = Keypair::generate();
332+
333+
let cert = RefUpdateCert::new(
334+
kp_pusher.did(),
335+
"refs/heads/main".to_string(),
336+
dummy_hash('0'),
337+
dummy_hash('a'),
338+
1,
339+
&kp_pusher,
340+
)
341+
.unwrap();
342+
343+
let maintainers = vec![kp_maintainer.did()];
344+
assert!(!cert.satisfies_threshold(&maintainers, 1).unwrap());
345+
}
346+
347+
#[test]
348+
fn threshold_zero_is_always_satisfied() {
349+
let kp = Keypair::generate();
350+
let cert = RefUpdateCert::new(
351+
kp.did(),
352+
"refs/heads/main".to_string(),
353+
dummy_hash('0'),
354+
dummy_hash('a'),
355+
1,
356+
&kp,
357+
)
358+
.unwrap();
359+
assert!(cert.satisfies_threshold(&[], 0).unwrap());
360+
}
260361
}

‎crates/gitlawb-core/src/cid.rs‎

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,61 @@ mod tests {
129129
let bytes = sha256_hex_to_bytes(&hex).unwrap();
130130
assert_eq!(sha256_bytes(data), bytes);
131131
}
132+
133+
#[test]
134+
fn from_sha256_bytes_matches_object_bytes() {
135+
// from_sha256_bytes must produce the same CID as from_git_object_bytes
136+
// when given the SHA-256 of those bytes — the two construction paths
137+
// must be equivalent for self-verifying content addressing to hold.
138+
let data = b"blob 5\0hello";
139+
let cid_from_object = Cid::from_git_object_bytes(data);
140+
let hash = sha256_bytes(data);
141+
let cid_from_hash = Cid::from_sha256_bytes(&hash);
142+
assert_eq!(cid_from_object, cid_from_hash);
143+
}
144+
145+
#[test]
146+
fn from_str_parses_valid_cid() {
147+
let data = b"tree content for test";
148+
let cid = Cid::from_git_object_bytes(data);
149+
let parsed = Cid::from_str(cid.as_str()).unwrap();
150+
assert_eq!(cid, parsed);
151+
}
152+
153+
#[test]
154+
fn from_str_rejects_invalid_string() {
155+
let result = Cid::from_str("not-a-valid-cid");
156+
assert!(result.is_err());
157+
}
158+
159+
#[test]
160+
fn different_data_produces_different_cids() {
161+
let c1 = Cid::from_git_object_bytes(b"blob 5\0hello");
162+
let c2 = Cid::from_git_object_bytes(b"blob 5\0world");
163+
assert_ne!(c1, c2);
164+
}
165+
166+
#[test]
167+
fn sha256_hex_to_bytes_rejects_non_hex() {
168+
let non_hex = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
169+
assert_eq!(non_hex.len(), 64);
170+
let result = sha256_hex_to_bytes(non_hex);
171+
assert!(result.is_err());
172+
}
173+
174+
#[test]
175+
fn sha256_hex_to_bytes_rejects_wrong_length() {
176+
let result = sha256_hex_to_bytes("deadbeef");
177+
assert!(result.is_err());
178+
}
179+
180+
#[test]
181+
fn sha256_hex_of_empty_input_is_well_known() {
182+
// SHA-256("") is a fixed constant; verifies the hasher is wired correctly.
183+
let h = sha256_hex(b"");
184+
assert_eq!(
185+
h,
186+
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
187+
);
188+
}
132189
}

‎crates/gitlawb-core/src/did.rs‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,4 +252,60 @@ mod tests {
252252
let json = serde_json::to_string_pretty(&doc).unwrap();
253253
assert!(json.contains("Ed25519VerificationKey2020"));
254254
}
255+
256+
#[test]
257+
fn to_verifying_key_fails_for_did_web() {
258+
let web = Did::web("example.com");
259+
let result = web.to_verifying_key();
260+
assert!(
261+
result.is_err(),
262+
"did:web cannot resolve to a verifying key locally"
263+
);
264+
}
265+
266+
#[test]
267+
fn to_verifying_key_fails_for_did_gitlawb() {
268+
let gl = Did::gitlawb("z6MkSomeKey");
269+
let result = gl.to_verifying_key();
270+
assert!(
271+
result.is_err(),
272+
"did:gitlawb cannot resolve to a verifying key locally"
273+
);
274+
}
275+
276+
#[test]
277+
fn from_str_rejects_missing_did_prefix() {
278+
let result = "key:z6MkABCD".parse::<Did>();
279+
assert!(result.is_err());
280+
}
281+
282+
#[test]
283+
fn from_str_rejects_unsupported_method() {
284+
let result = "did:ethr:0x1234abcd".parse::<Did>();
285+
assert!(result.is_err());
286+
}
287+
288+
#[test]
289+
fn validate_accepts_all_supported_methods() {
290+
Did::web("example.com").validate().unwrap();
291+
Did::gitlawb("z6MkSomeKey").validate().unwrap();
292+
Keypair::generate().did().validate().unwrap();
293+
}
294+
295+
#[test]
296+
fn is_did_key_and_is_did_gitlawb_predicates() {
297+
let key_did = Keypair::generate().did();
298+
assert!(key_did.is_did_key());
299+
assert!(!key_did.is_did_gitlawb());
300+
301+
let gl = Did::gitlawb("z6MkSomeKey");
302+
assert!(!gl.is_did_key());
303+
assert!(gl.is_did_gitlawb());
304+
}
305+
306+
#[test]
307+
fn method_id_extraction() {
308+
assert_eq!(Did::web("example.com").method_id(), "example.com");
309+
assert_eq!(Did::gitlawb("z6MkSomeKey").method_id(), "z6MkSomeKey");
310+
}
255311
}

‎crates/gitlawb-core/src/identity.rs‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,66 @@ mod tests {
157157
let did = kp.did();
158158
assert!(did.to_string().starts_with("did:key:z6Mk"));
159159
}
160+
161+
#[test]
162+
fn from_seed_round_trip() {
163+
let kp = Keypair::generate();
164+
let seed = kp.to_seed();
165+
let kp2 = Keypair::from_seed(&seed).unwrap();
166+
assert_eq!(kp.verifying_key(), kp2.verifying_key());
167+
}
168+
169+
#[test]
170+
fn sign_b64_decodes_to_valid_signature() {
171+
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
172+
let kp = Keypair::generate();
173+
let msg = b"test payload for b64 signing";
174+
let b64 = kp.sign_b64(msg);
175+
let sig_bytes: [u8; 64] = URL_SAFE_NO_PAD
176+
.decode(&b64)
177+
.expect("sign_b64 must produce valid base64url")
178+
.try_into()
179+
.expect("Ed25519 signature must be 64 bytes");
180+
verify(&kp.verifying_key(), msg, &sig_bytes).unwrap();
181+
}
182+
183+
#[test]
184+
fn verify_rejects_wrong_key() {
185+
let kp1 = Keypair::generate();
186+
let kp2 = Keypair::generate();
187+
let msg = b"some message";
188+
let sig = kp1.sign(msg);
189+
let result = verify(&kp2.verifying_key(), msg, &sig.to_bytes());
190+
assert!(
191+
result.is_err(),
192+
"signature from kp1 must not verify under kp2"
193+
);
194+
}
195+
196+
#[test]
197+
fn signed_payload_round_trip() {
198+
let kp = Keypair::generate();
199+
let payload = serde_json::json!({"action": "push", "repo": "my-repo"});
200+
let signed = Signed::new(payload, &kp).unwrap();
201+
signed.verify(&kp.verifying_key()).unwrap();
202+
}
203+
204+
#[test]
205+
fn signed_payload_wrong_key_fails() {
206+
let kp1 = Keypair::generate();
207+
let kp2 = Keypair::generate();
208+
let payload = serde_json::json!({"action": "push"});
209+
let signed = Signed::new(payload, &kp1).unwrap();
210+
let result = signed.verify(&kp2.verifying_key());
211+
assert!(
212+
result.is_err(),
213+
"Signed payload must not verify under a different key"
214+
);
215+
}
216+
217+
#[test]
218+
fn from_pem_invalid_input_fails() {
219+
let result = Keypair::from_pem("this is not valid PEM at all");
220+
assert!(result.is_err());
221+
}
160222
}

0 commit comments

Comments
 (0)