Skip to content

Latest commit

 

History

History
205 lines (144 loc) · 10.3 KB

File metadata and controls

205 lines (144 loc) · 10.3 KB

HTMLTrust HTML Signature Protocol

This document specifies how content publishers embed cryptographic signatures into HTML so that browsers, extensions, and crawlers can discover and verify them.

Overview

Signed content uses the <signed-section> custom HTML element, as defined in the HTMLTrust specification. This element wraps or accompanies signed content and carries the cryptographic signature as attributes.

The <signed-section> Element

Required Attributes

Attribute Description Example
signature Base64-encoded cryptographic signature of the content hash + domain + author ID signature="aBcDeF123..."
keyid URL where the author's public key can be fetched, or a DID keyid="https://api.example.com/authors/123/public-key"
algorithm Cryptographic algorithm used for the signature algorithm="ed25519"
content-hash Hash of the canonicalized content, prefixed with the algorithm content-hash="sha256:abc123def456..."

Supported Algorithms

Value Description
ed25519 Ed25519 (recommended)
rsa RSA with SHA-256
ecdsa ECDSA with secp256k1

Inner Metadata

The <signed-section> element MAY contain <meta> tags that describe the signature's claims and context. This makes signatures self-describing — a crawler or verifier can read the claims directly from the HTML without calling the trust directory API.

Standard Meta Names

Name Description Example
author Display name of the content author <meta name="author" content="Alice Example">
signed-at ISO 8601 timestamp of when the content was signed <meta name="signed-at" content="2025-05-01T10:30:00Z">

Claim Meta Names

Claims use the prefix claim: followed by the claim type name:

Name Description Example
claim:ContentType Type of content Article, Opinion, Research, News
claim:License Content license CC-BY-4.0, Public Domain, All Rights Reserved
claim:AIAssistance Level of AI involvement None, Human+AI, AI+Human, AI-only
claim:LLMTraining AI training preference Allowed, NotAllowed

Custom claim types are permitted. The claim vocabulary is extensible.

HTML Structure

The <signed-section> element can either wrap the signed content:

<signed-section
    signature="BASE64_SIG"
    keyid="https://api.example.com/authors/123/public-key"
    algorithm="ed25519"
    content-hash="sha256:abc123def456...">
  <meta name="author" content="Alice Example">
  <meta name="signed-at" content="2025-05-01T10:30:00Z">
  <meta name="claim:ContentType" content="Article">
  <meta name="claim:License" content="CC-BY-4.0">
  <meta name="claim:AIAssistance" content="None">
  <article>
    <h1>Verifiable Web Content</h1>
    <p>This content is signed and verifiable.</p>
  </article>
</signed-section>

Or appear as a standalone marker alongside content (e.g., when added by a CMS after the content):

<article>
  <h1>Verifiable Web Content</h1>
  <p>This content is signed and verifiable.</p>
</article>
<signed-section
    signature="BASE64_SIG"
    keyid="https://api.example.com/authors/123/public-key"
    algorithm="ed25519"
    content-hash="sha256:abc123def456...">
  <meta name="author" content="Alice Example">
  <meta name="signed-at" content="2025-05-01T10:30:00Z">
  <meta name="claim:ContentType" content="Article">
  <meta name="claim:License" content="CC-BY-4.0">
</signed-section>

Both forms are valid. Verifying clients should handle either case.

Content Canonicalization

Before hashing, content MUST be canonicalized:

  1. Parse the HTML and extract text nodes in document order
  2. Strip all HTML markup (tags and attributes); only the text content contributes to the hash
  3. Collapse all whitespace sequences to a single space
  4. Trim leading and trailing whitespace
  5. Apply the text normalization defined by the @htmltrust/canonicalization library (NFKC, quote normalization, dash normalization, invisible character stripping)
  6. Encode as UTF-8

The resulting string is hashed with SHA-256 and expressed as sha256:<base64_digest>, where <base64_digest> is the unpadded Base64 encoding of the 32-byte digest.

Text-only scope

The canonicalization hashes text content only, not the HTML markup or attributes that surround it. This means an adversary with possession of signed text MAY:

  • Rewrap the text in misleading block elements (e.g., change an <h1> to a <del> strikethrough)
  • Alter link destinations (href values) on <a> elements surrounding the signed text
  • Introduce, remove, or swap images and other media around the signed text

These are semantic integrity concerns, not cryptographic ones. HTMLTrust addresses them through a layered design:

  1. Domain binding (see Signature Data Format below): signatures bind the content to a specific publication origin. A reader or crawler encountering signed content at an unexpected origin is alerted by signature check failure.
  2. Research and reputation path: crawlers and researchers can trace signed content back to its canonical publication origin through the trust directory, flag imposter copies, and mark manipulated surrounding context. Over time the directory's reputation and reports surface altered copies to any consumer whose trust policy considers them.

The layered design keeps cryptographic verification simple and portable across language implementations, while delegating semantic-integrity detection to the research ecosystem where it can evolve without breaking existing signatures.

Open design question: a future revision MAY extend the hash to cover particularly meaningful attributes, especially href on <a> elements (since link-swap within the original publication origin is a phishing vector that domain-binding and research cannot address alone). Feedback on which attributes to cover is explicitly welcome.

Signature Data Format

The signature binds four values, concatenated with : separators:

{content-hash}:{claims-hash}:{domain}:{signed-at}
  • content-hash — hash of the canonicalized text content (see above)
  • claims-hash — SHA-256 hash of the canonical serialization of all inner <meta> claim elements, ordered lexically by name (ensures tamper-evident claim metadata)
  • domain — the origin where the content is authoritatively published (anti-theft binding)
  • signed-at — the ISO-8601 timestamp from the <meta name="signed-at"> element

For example:

sha256:RAyBCvKT...:sha256:eFgHiJkL...:example.com:2025-05-01T10:30:00Z

The author's identity is not included in the binding because it is implicit in the keyid resolution step: any attempt to claim a signature under a different identity would resolve to a different public key and fail verification. This string is signed with the author's private key using the algorithm declared in the algorithm attribute.

Hash encoding (open feedback): hashes are encoded as unpadded Base64, which is shorter than hexadecimal by roughly one-third. Community feedback on alternative encodings (hex, Base32) for ecosystem alignment is welcome.

Verification Flow

HTMLTrust separates verification into two distinct layers, per the specification:

Layer 1: Cryptographic verification (local, deterministic)

A verifying client (browser extension, crawler, library) performs these steps locally, with no network calls beyond the key resolution step:

  1. Discover <signed-section> elements in the page DOM
  2. Read the signature, keyid, algorithm, and content-hash attributes
  3. Resolve the keyid to a public key. The keyid may be a DID (e.g., did:web:author.example), a direct URL to a public key JSON document, or a trust directory reference. Implementations MUST accept multiple resolution methods.
  4. Canonicalize the inner text content per the rules above and compute its hash
  5. Compare the computed hash with the content-hash attribute (content integrity check)
  6. Compute the claims-hash from the canonical serialization of inner <meta> claim elements
  7. Construct the binding string {content-hash}:{claims-hash}:{domain}:{signed-at}
  8. Verify the cryptographic signature over the binding string using the resolved public key and the declared algorithm

This layer produces a deterministic yes/no result: either the signature is cryptographically valid or it is not. No server or directory is required for this step beyond whatever key resolution demands.

Layer 2: Trust decision (client policy)

Given a cryptographically valid signature, the client then applies the user's trust policy to decide how to present the content. This layer is entirely client-side and may draw on:

  • A personal list of trusted keyids (option A)
  • Trusted origin domains (option B)
  • Endorsements from designated third parties (fetched from trust directories and independently verified)
  • Reputation scores from one or more user-selected trust directories
  • Local or cached revocation state
  • Any combination of the above, weighted as the user configures

The output is a trust score or ranking, not a binary verdict. User interfaces SHOULD present the outcome as a graduated signal (for example a red/yellow/green score) with hover or detail views exposing which inputs contributed to the final score.

Optional directory queries

In addition to the two layers above, a client MAY query one or more trust directories for:

  • Author reputation (signer-level trust, ongoing curatorial opinion)
  • Content endorsements (point-in-time attestations from third parties)
  • Key revocation and reports

These queries enrich the trust decision but are never required for signature verification itself.

Multiple Signatures

A single page MAY contain multiple <signed-section> elements (e.g., a forum with posts from different authors). Each is verified independently.

Backwards Compatibility

<signed-section> is a custom HTML element. Browsers that don't recognize it treat it as an unknown inline element and render its children normally. Adding signed-section { display: block; } in CSS ensures consistent block-level rendering. The content remains fully readable and functional.

CSS

Implementations should include:

signed-section {
  display: block;
}