@rwandatrust/trust-verifier
v2.0.0
Published
Standalone offline verification of RwandaTrust trust artifacts: signed attestations and ISO 27560 consent receipts (JAdES-B-B)
Downloads
74
Maintainers
Readme
@rwandatrust/trust-verifier
Standalone offline verification of RwandaTrust trust artifacts — signed attestations and ISO/IEC TS 27560:2023 consent receipts (JAdES-B-B compact JWS).
Lightweight, zero external dependencies beyond canonicalize. Works in any Node.js 18+ environment.
What this package verifies
| Artifact | Entry point | What it proves |
|---|---|---|
| Signed attestation | verifyAttestation() | A verification event happened and was signed by RwandaTrust |
| ISO 27560 consent receipt | verifyConsentReceipt() | A citizen granted consent for a specific purpose |
Both paths share the same JAdES-B-B + JWKS verification core, so alg=none rejection, algorithm-confusion defense (kty/crv cross-check against the header alg), and trusted-issuer enforcement apply uniformly.
Install
npm install @rwandatrust/trust-verifierQuick Start
import { verifyAttestation } from '@rwandatrust/trust-verifier';
const result = await verifyAttestation({
jws: attestation.jws,
jwksUrl: 'https://rwandatrust.com/.well-known/rwandatrust-signing-keys.json',
});
if (result.valid) {
console.log('Verified:', result.attestation.type, result.attestation.organizationId);
} else {
console.error('Verification failed:', result.reason);
}API
verifyAttestation(input): Promise<VerifyResult>
Main entry point. Parses a JWS, looks up the signing key in a JWKS document, verifies the cryptographic signature, and validates attestation claims.
interface VerifyInput {
jws: string; // Compact JWS: header.payload.signature
jwks?: JwksDocument; // Pre-fetched JWKS (offline mode)
jwksUrl?: string; // URL to fetch JWKS (online mode)
expectedSnapshotDigest?: string; // Pin JWKS to a specific digest
trustedIssuers?: string[]; // Issuer allowlist (e.g. ['https://rwandatrust.com'])
skipClaimValidation?: boolean; // Crypto-only mode (default: false)
allowMissingKeyStatus?: boolean; // Accept keys without status field (default: false)
}
interface VerifyResult {
valid: boolean;
reason?: string; // Human-readable failure reason
attestation?: ParsedAttestation; // Parsed payload (if valid)
jwksDigest?: string; // Computed JWKS snapshot digest
}Verification steps:
- Parse compact JWS into header / payload / signature
- Extract
kidfrom header, look up public key in JWKS - Reject keys that are not
ACTIVEorROTATING - Optionally verify JWKS snapshot digest
- Optionally verify JWKS issuer against allowlist
- Convert JOSE signature to DER (for ECDSA) and verify with Node.js
crypto - Validate required claims:
type,organizationId,subjectIdHash,issuedAt - Validate type-specific claims:
verificationRequestIdorconsentRecordId - Reject future
iat(5-minute clock skew tolerance)
pinJwks(jwks): string
Compute JWKS snapshot digest for key set pinning.
import { pinJwks, fetchJwks } from '@rwandatrust/trust-verifier';
const jwks = await fetchJwks('https://rwandatrust.com/.well-known/rwandatrust-signing-keys.json');
const digest = pinJwks(jwks);
// "sha256:a1b2c3..."
// Later, verify the JWKS hasn't changed:
const result = await verifyAttestation({
jws: attestation.jws,
jwks,
expectedSnapshotDigest: digest,
});toProvenanceGraph(attestation): ProvJsonDocument
Convert a verified attestation into a W3C PROV-JSON document for Data Hub integration.
import { verifyAttestation, toProvenanceGraph } from '@rwandatrust/trust-verifier';
const result = await verifyAttestation({ jws, jwks });
if (result.valid) {
const prov = toProvenanceGraph(result.attestation);
// prov.entity, prov.activity, prov.agent, prov.wasGeneratedBy, ...
}PROV-DM mapping:
prov:Entity= the signed attestationprov:Activity= the verification or consent ceremonyprov:Agent= the signing key (RwandaTrust platform)
parseCompactJws(jws): ParsedJws
Parse a JWS without verifying the signature.
import { parseCompactJws } from '@rwandatrust/trust-verifier';
const { header, payload, signatureB64 } = parseCompactJws(jws);
console.log(header.kid, header.alg);
console.log(payload.type, payload.organizationId);fetchJwks(url): Promise<JwksDocument>
Fetch and validate a JWKS document. Handles both raw JWKS and RwandaTrust API envelope format.
computeKeyThumbprint(jwk): string
SHA-256 hex digest of JCS-canonicalized required JWK members. Note: produces hex output (not base64url like vanilla RFC 7638) to match the RwandaTrust JWKS snapshot scheme.
findActiveKey(jwks, kid, options?): JwkKey | null
Look up a key by kid, accepting only ACTIVE or ROTATING status. Keys without a status field are rejected by default; pass { allowMissingStatus: true } for generic JWKS interop.
Offline Verification
For environments without network access, pre-fetch the JWKS and pin it:
// Setup (online, once)
const jwks = await fetchJwks('https://rwandatrust.com/.well-known/rwandatrust-signing-keys.json');
const pinnedDigest = pinJwks(jwks);
// Store jwks and pinnedDigest
// Verification (offline, anytime)
const result = await verifyAttestation({
jws: attestation.jws,
jwks: storedJwks,
expectedSnapshotDigest: pinnedDigest,
trustedIssuers: ['https://rwandatrust.com'],
});Supported Attestation Types
| Type | Required Claims |
|------|----------------|
| VERIFICATION_CERTIFICATE | organizationId, subjectIdHash, verificationRequestId, issuedAt |
| CONSENT_CERTIFICATE | organizationId, subjectIdHash, consentRecordId, issuedAt |
Supported Algorithms
| Algorithm | Curve/Key | Status | |-----------|-----------|--------| | ES256 | ECDSA P-256 + SHA-256 | Primary | | ES384 | ECDSA P-384 + SHA-384 | Supported | | ES512 | ECDSA P-521 + SHA-512 | Supported | | RS256 | RSA + SHA-256 | Fallback |
Key Lifecycle
The verifier respects RwandaTrust key lifecycle states:
| Status | Accepted? |
|--------|-----------|
| ACTIVE | Yes |
| ROTATING | Yes |
| RETIRED | No |
| COMPROMISED | No |
Dependencies
canonicalize-- RFC 8785 JSON Canonicalization Scheme- Node.js built-in
cryptomodule
License
MIT
