Website Legitimacy Index

Website Legitimacy Index (WLI) Specification — v0.1.0

Status: Draft

Last Updated: 2026-02-02


Overview

WLI provides a structured, machine-readable assessment of a domain’s TLS posture and operational legitimacy. It quantifies trust signals, flags weaknesses, and produces an evidence trail for downstream verification decisions.

TLS certificates are powerful but their surface details hide important nuance. A valid certificate from a recognized CA tells you nothing about whether the operator invested in organizational validation, maintains their infrastructure, enforces transport security, or participates in Certificate Transparency. WLI surfaces this information as discrete, scored signals that compose into a trust grade.

WLI is the domain layer in Kapwork’s verification stack. Invoice-level and record-level certificates inherit WLI attestations.


Prerequisite Reading

Topic What It Does Specification
TLS 1.3 Transport encryption with forward secrecy RFC 8446
X.509 Certificate format for public key infrastructure RFC 5280
Certificate Transparency Public audit logs for issued certificates RFC 6962
OCSP Online certificate revocation checking RFC 6960
HSTS HTTP Strict Transport Security RFC 6797
W3C Verifiable Credentials Standard for cryptographic attestations W3C VC Data Model v2.0

Design Goals

WLI is:


1. Technical Specification

1.1 What is WLI?

WLI is a structured JSON response (W3C Verifiable Credential) summarizing domain trust by analyzing:

WLI surfaces operational quality indicators that distinguish well-maintained domains from neglected or fraudulent ones.

1.2 Analogy

WLI is to domain trust what a credit report is to financial trustworthiness: a structured snapshot of observable signals that inform but do not determine a decision.

1.3 Non-goals


2. Response Schema

2.1 Top-level Object

A WLI response MUST be a W3C Verifiable Credential with the following structure.

{
  "@context": [
    "https://www.w3.org/ns/credentials/v2",
    {
      "kw": "https://schema.kapwork.com/credentials/v1#",
      "kw:domainAttestation": { "@id": "kw:domainAttestation", "@type": "@json" },
      "kw:tlsPosture": { "@id": "kw:tlsPosture", "@type": "@json" },
      "kw:trustScore": { "@id": "kw:trustScore", "@type": "@json" }
    }
  ],
  "id": "urn:uuid:<uuid>",
  "type": ["VerifiableCredential", "KapworkDomainAttestation"],
  "issuer": {
    "id": "did:web:verify.kapwork.com",
    "name": "Kapwork, Inc.",
    "url": "https://kapwork.com"
  },
  "validFrom": "<RFC3339>",
  "validUntil": "<RFC3339>",
  "credentialSubject": { ... }
}

2.2 Credential Subject

The credentialSubject MUST contain:

Field Type Description
id string https://<host>
type string "Domain"
domain object Host, port, inspection timestamp
tlsPosture object Protocol, cipher, ALPN, chain trust status
certificate object Subject, issuer, serial, validity, key type, fingerprint, extensions
chain array Certificate chain with depth, role, subject, issuer, expiry
trustScore object Composite score, grade, summary, signal array
httpPosture object Status code, server header, HSTS, CSP, X-Frame-Options

2.3 Optional Fields

Field Type Description
debtor object Debtor name and domain (set by invoice-level service)
invoiceReference string Invoice identifier (set by invoice-level service)

3. Trust Scoring

3.1 Scoring Method: WLI_SCORE_V1

The composite score is a weighted average of individual signal scores.

Each signal produces a score from 0 to 100 and carries a weight. The composite is:

composite = round(ÎŁ(signal_score Ă— signal_weight) / ÎŁ(signal_weight))

3.2 Critical Cap

If any signal has severity critical, the composite is capped at 25 regardless of the weighted average. This prevents a domain with a broken trust chain or expired certificate from scoring well on other signals.

3.3 Signals

ID Weight Description
chain.trusted 3.0 Certificate chain validates against system trust store
cert.expiry 3.0 Days to certificate expiry
chain.completeness 2.0 Chain depth, intermediate and root presence
cert.validation 2.0 DV / OV / EV validation type
protocol.version 1.5 TLS 1.3 / 1.2 / deprecated
http.hsts 1.5 HSTS header presence and max-age
cert.issuer 1.0 Issuer tier (commercial / free / cloud / unknown)
cert.age 1.0 Days since certificate issuance
cert.transparency 1.0 SCT count from Certificate Transparency logs
cert.revocation 1.0 OCSP and/or CRL availability
cert.keyStrength 0.5 RSA key size or ECDSA
cert.signatureAlg 0.5 SHA-256+ vs SHA-1
cert.sanMatch 0.5 Hostname matches a Subject Alternative Name

Total weight: 18.5

3.4 Signal Severities

Each signal reports a severity level:

Severity Meaning
pass Signal is healthy
info Acceptable but not optimal
warn Potential weakness
fail Problem detected
critical Serious problem, triggers score cap

3.5 Grades

Grade Score Range
A 90-100
B 80-89
C 65-79
D 50-64
F 0-49

3.6 Scoring Rationale

The weighted average model was chosen over the base+penalty model used by ELI because domain trust signals are independent observations, not deductions from a baseline. There is no inherent “starting score” for a domain. Each signal contributes proportionally to the composite.

Weights reflect the signal’s relevance to operational legitimacy in a receivables verification context:


4. Signal Specifications

4.1 chain.trusted

Checks whether the certificate chain validates against the system trust store.

Condition Score Severity
Chain validates 100 pass
Chain does not validate 0 critical

4.2 protocol.version

Condition Score Severity
TLS 1.3 100 pass
TLS 1.2 70 info
TLS 1.1 or below 0 critical

4.3 cert.expiry

Condition Score Severity
Expired 0 critical
< 7 days 10 critical
7-13 days 30 warn
14-29 days 60 warn
30-59 days 80 info
60+ days 100 pass

4.4 cert.age

Condition Score Severity
Future issuance date 0 critical
< 7 days 50 info
7-29 days 70 info
30+ days 100 pass

4.5 cert.validation

Condition Score Severity
Extended Validation (EV) 100 pass
Organization Validated (OV) 80 pass
Domain Validated (DV) 50 info

4.6 cert.issuer

Condition Score Severity
Tier 1 commercial CA (DigiCert, Entrust, GlobalSign, Sectigo) 100 pass
Tier 2 (Let’s Encrypt, ZeroSSL, cloud CAs, GoDaddy) 70 info
Known issuer, untiered 60 info
Unknown issuer 30 warn

4.7 cert.keyStrength

Condition Score Severity
ECDSA or EdDSA 100 pass
RSA 4096+ 100 pass
RSA 2048 80 pass
RSA < 2048 0 critical

4.8 cert.signatureAlg

Condition Score Severity
SHA-256, SHA-384, SHA-512, EdDSA 100 pass
SHA-1 0 critical
Unknown/undetermined 50 info

Requires --extended flag. Without it, reports 50/info.

4.9 chain.completeness

Condition Score Severity
No chain received 0 critical
Leaf only 30 warn
2+ certs including root 100 pass
2+ certs without root 90 pass

4.10 http.hsts

Condition Score Severity
Not set 30 warn
Present, unparseable max-age 60 info
max-age < 1 day 50 warn
max-age < 6 months 70 info
max-age >= 6 months 100 pass

4.11 cert.transparency

Condition Score Severity
2+ SCTs 100 pass
1 SCT 70 info
SCT extension present but unparseable 60 info
No SCTs 30 warn

Requires --extended flag. Without it, reports 50/info.

4.12 cert.revocation

Condition Score Severity
OCSP + CRL 100 pass
OCSP only 90 pass
CRL only 70 info
Neither 20 warn

Requires --extended flag. Without it, reports 50/info.

4.13 cert.sanMatch

Condition Score Severity
No SANs in certificate 30 warn
Hostname matches (exact or wildcard) 100 pass
Hostname does not match any SAN 10 critical

5. Credential Signing

5.1 Overview

WLI signs attestation credentials as JWS (JSON Web Signature, RFC 7515) using compact serialization. The signing key is the Kapwork ASP key pair published via the ASPE protocol at Keyoxide.

The credential JSON is the JWS payload. The signature proves that Kapwork issued the attestation and that the content has not been modified.

5.2 Signing Key

The signing key is an Ed25519 or P-256 key pair. The same key pair is used to sign the Kapwork ASP profile at Keyoxide. This creates a verifiable link between the WLI attestation and the Kapwork identity.

ASPE URI: aspe:keyoxide.org:EYOOS2SHKRAAQTVJ2APXDSMXYU

ASPE endpoint: GET https://keyoxide.org/.well-known/aspe/id/EYOOS2SHKRAAQTVJ2APXDSMXYU

5.3 JWS Protected Header

Field Value Description
alg EdDSA or ES256 Signing algorithm (matches key type)
typ vc+jwt Media type per W3C VC-JOSE-COSE
jwk {...} Public key in JWK format (inline for convenience)
kid aspe:keyoxide.org:EYOOS2SHKRAAQTVJ2APXDSMXYU Key identifier (ASPE URI for discovery)

5.4 Verification Flow

To verify a signed WLI attestation:

  1. Decode the JWS protected header.
  2. Extract the kid field (ASPE URI).
  3. Fetch the ASP profile JWS from the Keyoxide ASPE endpoint.
  4. Decode the ASP profile JWS header to extract the authoritative public key.
  5. If the attestation JWS contains an inline jwk, confirm it matches the ASPE-sourced key.
  6. Verify the attestation JWS signature using the ASPE-sourced public key.
  7. Parse the JWS payload as the W3C Verifiable Credential JSON.

The verification endpoint (POST /verify) implements this flow. Alternatively, the public key is available at GET /.well-known/jwks.json for clients that prefer JWKS-based discovery.

5.5 Unsigned Credentials

If the WLI server has no signing key configured (WLI_SIGNING_KEY / WLI_SIGNING_KEY_FILE not set), credentials are issued without a JWS wrapper. The signed field in the API response indicates whether the credential was signed.

Unsigned credentials are structurally valid W3C VCs but carry no cryptographic proof of issuance. They are suitable for development, testing, and internal use but should not be trusted in production verification flows.


6. Output Formats

7.1 Machine-readable

W3C Verifiable Credential JSON-LD, as specified in §2.

7.2 Human-readable

Plain text report with sections:

KAPWORK DOMAIN ATTESTATION
Certificate ID: urn:uuid:...
Issued: <timestamp>
Valid until: <timestamp>
Issuer: Kapwork, Inc.

DEBTOR (optional)
  Name:   ...
  Domain: ...

DOMAIN
  Host:       ...
  Protocol:   ...
  Cipher:     ...
  Trusted:    Yes/NO

TRUST SCORE
  Grade X (nn/100). Summary.

FINDINGS
  [severity] finding text
  ...

CERTIFICATE
  Subject/Issuer/Serial/Validity/Key/SHA-256/etc.

CERTIFICATE CHAIN
  [depth] ROLE: subject
      Issued by: issuer
      Expires:   nn days

DISCLAIMERS
  Point-in-time attestation, no warranty on future state.

7. API Endpoints

7.1 POST /inspect

Raw TLS inspection. Returns connection, certificate, chain, and HTTP header data.

Request body:

{
  "url": "example.com",
  "extended": true,
  "pem": false,
  "score": false
}

7.2 POST /score

Trust score only. Runs extended inspection internally.

Request body:

{
  "url": "example.com"
}

Response:

{
  "host": "example.com",
  "inspectedAt": "<RFC3339>",
  "score": 89,
  "grade": "B",
  "summary": "Grade B (89/100). No issues detected.",
  "signals": [...]
}

7.3 POST /certificate

Kapwork Domain Attestation. Returns W3C Verifiable Credential.

Request body:

{
  "url": "example.com",
  "debtor": { "name": "Acme Corp", "domain": "acme.com" },
  "invoiceRef": "INV-2025-00142"
}

Response varies by Accept header: - application/json (default): W3C VC JSON - text/plain: human-readable report

7.4 POST /verify

Verify a signed WLI attestation. Fetches the public key from Keyoxide ASPE and verifies the JWS signature.

Request body (JSON):

{
  "jws": "eyJhbGciOiJFZERTQSIs..."
}

Or raw JWS with Content-Type: application/jwt.

Response (valid):

{
  "valid": true,
  "issuer": "did:web:verify.kapwork.com",
  "keySource": "inline+aspe",
  "credential": { ... }
}

Response (invalid):

{
  "valid": false,
  "error": "signature verification failed"
}

7.5 GET /.well-known/jwks.json

Returns the public signing key in JWKS format.

{
  "keys": [{
    "kty": "OKP",
    "crv": "Ed25519",
    "x": "...",
    "use": "sig",
    "alg": "EdDSA",
    "kid": "aspe:keyoxide.org:EYOOS2SHKRAAQTVJ2APXDSMXYU"
  }]
}

Returns 404 if no signing key is configured.

7.6 GET /health

{
  "status": "ok",
  "timestamp": "<RFC3339>"
}

All POST endpoints have GET equivalents with ?url= query parameter.

7.7 GET /scanner

Web-based UI for interactive domain scanning. Serves a single-page HTML application that lets users enter a domain, submits it to POST /score, and displays the grade, score, signals, and a link to the full W3C VC certificate.

No authentication required. The scanner is intended for demos and ad-hoc use.


8. Issuer Tiers

WLI groups certificate issuers into tiers as a signal of the operator’s investment in their TLS posture. This is not a judgment on CA quality. A free DV cert from Let’s Encrypt is cryptographically sound. But a paid OV cert from DigiCert signals that the operator went through organizational validation and paid for the privilege, which is a meaningful operational signal in a receivables context.

Tier 1: Commercial CAs with OV/EV validation

DigiCert, Entrust, GlobalSign, Sectigo.

Tier 2: Free/automated CAs and cloud vendor managed certs

Let’s Encrypt, ZeroSSL, Buypass, AWS ACM, Google Trust Services, Cloudflare, GoDaddy, Microsoft.


9. Relationship to ELI

WLI and ELI are siblings. ELI assesses email authentication (DKIM, SPF, DMARC). WLI assesses domain TLS posture. Both produce scored attestations with supporting evidence. They are designed to be composed:

The scoring models differ intentionally. ELI uses base+penalty because emails start from a verdict baseline. WLI uses weighted average because domain signals are independent observations with no inherent baseline.


10. Limitations


Changelog

v0.1.0


© 2026 Kapwork