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): cover InvalidSignatureScalar verification path

Add tests for the InvalidSignatureScalar error variant to ensure it's
properly mapped in both K256 and P256 signature verification paths.

- verify_compact_invalid_signature_scalar_k256: Tests that an all-zero
K256 signature is rejected with InvalidSignatureScalar.
- verify_compact_invalid_signature_scalar_p256: Tests that an all-zero
P256 signature is rejected with InvalidSignatureScalar.

Both tests hand-craft a compact JWT with valid structure but replace the
signature segment with base64url-encoded zeros, which causes both curves
to reject the signature during parsing (r and s are 0).

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

authored by

Jack Grigg
Claude Haiku 4.5
and committed by
Tangled
2fe2c840 cb623ffa

+52
+52
src/common/jwt.rs
··· 388 388 ); 389 389 } 390 390 } 391 + 392 + #[test] 393 + fn verify_compact_invalid_signature_scalar_k256() { 394 + let key = AnySigningKey::K256(K256SigningKey::from_slice(&[1u8; 32]).expect("valid seed")); 395 + let vkey = key.verifying_key(); 396 + let header = JwtHeader::for_signing_key(&key); 397 + let claims = JwtClaims { 398 + iss: "did:web:127.0.0.1%3A5000".to_string(), 399 + aud: "did:plc:test".to_string(), 400 + exp: 2000000000, 401 + iat: 1700000000, 402 + lxm: "com.atproto.moderation.createReport".to_string(), 403 + jti: "0123456789abcdef".to_string(), 404 + }; 405 + 406 + let token = encode_compact(&header, &claims, &key).expect("encode succeeds"); 407 + let parts: Vec<&str> = token.split('.').collect(); 408 + assert_eq!(parts.len(), 3); 409 + 410 + // Replace the signature with all zeros (64 bytes, base64url-encoded). 411 + let zero_sig = URL_SAFE_NO_PAD.encode([0u8; 64]); 412 + let tampered = format!("{}.{}.{}", parts[0], parts[1], zero_sig); 413 + 414 + let result = verify_compact(&tampered, &vkey); 415 + assert!(matches!(result, Err(JwtError::InvalidSignatureScalar))); 416 + } 417 + 418 + #[test] 419 + fn verify_compact_invalid_signature_scalar_p256() { 420 + let key = AnySigningKey::P256(P256SigningKey::from_slice(&[2u8; 32]).expect("valid seed")); 421 + let vkey = key.verifying_key(); 422 + let header = JwtHeader::for_signing_key(&key); 423 + let claims = JwtClaims { 424 + iss: "did:web:example.com".to_string(), 425 + aud: "did:plc:test".to_string(), 426 + exp: 2000000000, 427 + iat: 1700000000, 428 + lxm: "com.atproto.moderation.createReport".to_string(), 429 + jti: "fedcba9876543210".to_string(), 430 + }; 431 + 432 + let token = encode_compact(&header, &claims, &key).expect("encode succeeds"); 433 + let parts: Vec<&str> = token.split('.').collect(); 434 + assert_eq!(parts.len(), 3); 435 + 436 + // Replace the signature with all zeros (64 bytes, base64url-encoded). 437 + let zero_sig = URL_SAFE_NO_PAD.encode([0u8; 64]); 438 + let tampered = format!("{}.{}.{}", parts[0], parts[1], zero_sig); 439 + 440 + let result = verify_compact(&tampered, &vkey); 441 + assert!(matches!(result, Err(JwtError::InvalidSignatureScalar))); 442 + } 391 443 }