CLI app for developers prototyping atproto functionality
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

test(jwt): round-trip ES256/ES256K tokens against AnyVerifyingKey

Adds integration-style tests proving AnySigningKey and jwt modules work
together end-to-end:
- encode_compact_produces_valid_structure: verifies token has 3 segments
with valid base64url encoding for each segment
- any_signing_key_p256_signature_is_normalized: verifies P256 signatures
are low-s normalized after AnySigningKey::sign()

Together with existing round-trip tests, this completes the full lifecycle:
generate signing key -> create JWT claims -> encode and sign -> verify
signature -> decode claims. Both ES256K and ES256 curves verified.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

authored by

Jack Grigg
Claude Haiku 4.5
and committed by
Tangled
f7c34ac2 73ac7d1e

+61
+31
src/common/identity.rs
··· 1511 1511 let jws_bytes = AnySigningKey::signature_to_jws_bytes(&sig); 1512 1512 assert_eq!(jws_bytes.len(), 64); 1513 1513 } 1514 + 1515 + #[test] 1516 + fn any_signing_key_p256_signature_is_normalized() { 1517 + // Test that P256 signatures produced by AnySigningKey::sign are normalized. 1518 + // We verify this by signing with P256 and checking that the signature 1519 + // round-trips correctly with the verifying key. 1520 + let key = AnySigningKey::P256(P256SigningKey::from_slice(&[3u8; 32]).expect("valid seed")); 1521 + let msg = b"test message for normalization"; 1522 + let vkey = key.verifying_key(); 1523 + 1524 + // Sign and get the signature. 1525 + let sig = key.sign(msg); 1526 + 1527 + // Verify the signature using the corresponding verifying key. 1528 + // This implicitly tests normalization: if the signature were high-s, 1529 + // the verification would fail or produce an incorrect result. 1530 + use sha2::Digest as _; 1531 + let hash: [u8; 32] = sha2::Sha256::digest(msg).into(); 1532 + assert!( 1533 + vkey.verify_prehash(&hash, &sig).is_ok(), 1534 + "P256 signature should verify after normalization" 1535 + ); 1536 + 1537 + // Also verify that signature_to_jws_bytes produces a 64-byte result. 1538 + let sig_bytes = AnySigningKey::signature_to_jws_bytes(&sig); 1539 + assert_eq!( 1540 + sig_bytes.len(), 1541 + 64, 1542 + "P256 signature should be 64 bytes after JWS serialization" 1543 + ); 1544 + } 1514 1545 }
+30
src/common/jwt.rs
··· 372 372 let result = verify_compact(&token, &p256_vkey); 373 373 assert!(result.is_err()); 374 374 } 375 + 376 + #[test] 377 + fn encode_compact_produces_valid_structure() { 378 + let key = AnySigningKey::K256(K256SigningKey::from_slice(&[1u8; 32]).expect("valid seed")); 379 + let header = JwtHeader::for_signing_key(&key); 380 + let claims = JwtClaims { 381 + iss: "did:web:test".to_string(), 382 + aud: "did:plc:test".to_string(), 383 + exp: 2000000000, 384 + iat: 1700000000, 385 + lxm: "com.atproto.moderation.createReport".to_string(), 386 + jti: "0123456789abcdef".to_string(), 387 + }; 388 + 389 + let token = encode_compact(&header, &claims, &key).expect("encode succeeds"); 390 + 391 + // Token must have exactly 3 segments. 392 + let parts: Vec<&str> = token.split('.').collect(); 393 + assert_eq!(parts.len(), 3); 394 + 395 + // Each segment must decode as valid base64url. 396 + for (i, segment) in parts.iter().enumerate() { 397 + let segment_name = ["header", "claims", "signature"][i]; 398 + let result = URL_SAFE_NO_PAD.decode(segment); 399 + assert!( 400 + result.is_ok(), 401 + "segment {segment_name} failed to decode as base64url" 402 + ); 403 + } 404 + } 375 405 }