Files
skills/docs/security-stack/security/jws/src/serialization/general/general.md
2026-05-29 08:20:56 +01:00

3.0 KiB

JSON General serialization (multi-signature)

(no submodules)

Purpose

RFC 7515 §3.2 + §7.2.1 JSON-general JWS — { payload, signatures: JwsSignature[] }. Each entry in signatures carries its own protected / header / signature so the same payload can be co-signed by N parties. Common use cases: cross-tenant message attestation, multi-party authorisation, signed installer manifests where multiple maintainers' signatures must all attach to the same artifact.

Features

  • generalDecode — Zod-validated decode; signatures must be non-empty.
  • generalVerify — verifies every signer via a per-signer key resolver; returns { verifiedIndices, failedIndices }.
  • generalSign — signs the same payload under N signers; rejects mixed-b64 signers.
  • requireAll: true causes the first failing index to throw JWS.MULTI_SIG_FAILED with error.context.failedIndex.

Usage: Basic Example

import { generalSign, generalVerify } from "@suaveplan/jws";

const general = await generalSign("hello", [
    { key: secret, alg: "HS256" },
    { key: rsaPriv, alg: "RS256" },
]);

const result = await generalVerify(
    general,
    async (i) => (i === 0 ? secret : rsaPub),
    { algorithms: ["HS256", "RS256"] }
);
// result.verifiedIndices === [0, 1]

Usage: Advanced Example

import { generalVerify, JwsError } from "@suaveplan/jws";

try {
    await generalVerify(
        general,
        async (i, protectedHdr) => keyStoreByIssuer.get(protectedHdr["iss"] as string),
        {
            algorithms: ["HS256", "RS256", "ES256"],
            requireAll: true,
        }
    );
} catch (err) {
    if (err instanceof JwsError && err.code === "JWS.MULTI_SIG_FAILED") {
        const failedIndex = (err.context as { failedIndex: number }).failedIndex;
        // err.cause === underlying JwsError for that signer.
    }
}

API Reference

  • generalDecode(json: unknown): GeneralJws
  • generalVerify(json: unknown, keyResolver, opts: GeneralVerifyOptions): Promise<GeneralVerifyResult>
    • keyResolver: (signerIndex, protectedHeader, unprotectedHeader?) => Promise<unknown>
    • GeneralVerifyOptions extends VerifyOptions { requireAll?: boolean }
    • GeneralVerifyResult = { verifiedIndices, failedIndices, payloadString }
  • generalSign(payload, signers: GeneralSigner[]): Promise<GeneralJws>
    • GeneralSigner = { key, alg, protectedHeader?, unprotectedHeader? }

Implementation Notes

The default requireAll value is false — more flexible for use cases like "any one of N trust roots verifies." Callers needing the stricter "every signer must verify" semantics opt in via requireAll: true. Mixed b64 settings across signers are NOT supported because each signer's signing input would differ in interpretation and the receiver-side verifier cannot know which interpretation to apply — generalSign rejects mixed-b64 arrangements with JWS.MALFORMED. The payload segment is computed from the first signer's b64 setting and then asserted byte-identical across the rest.