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:
- Deterministic: same domain state produces same score
- Auditable: every grade is backed by enumerated signals with evidence
- Composable: embeddable into higher-level verification certificates
- Point-in-time: attestations represent the domain’s state at inspection time, not a persistent rating
1. Technical Specification
1.1 What is WLI?
WLI is a structured JSON response (W3C Verifiable Credential) summarizing domain trust by analyzing:
- TLS protocol negotiation and cipher selection
- X.509 certificate validity, issuer, key strength, and extensions
- Certificate chain completeness and trust anchor validation
- HTTP security headers (HSTS, CSP, X-Frame-Options)
- Certificate Transparency participation
- Revocation infrastructure availability (OCSP, CRL)
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
- WLI does not perform vulnerability scanning or penetration testing.
- WLI does not replace SSL Labs or similar grading services. WLI measures operational legitimacy signals, not protocol compliance depth.
- WLI does not assess content, reputation, or business legitimacy beyond what TLS posture reveals.
- WLI does not make funding or verification decisions. It provides inputs to those decisions.
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:
- Chain trust and expiry (3.0): Fundamental. An untrusted or expired cert is disqualifying.
- Chain completeness and validation type (2.0): Server configuration quality and identity assurance.
- Protocol and HSTS (1.5): Transport security posture.
- Everything else (0.5-1.0): Supporting signals that refine the picture.
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:
- Decode the JWS protected header.
- Extract the
kidfield (ASPE URI). - Fetch the ASP profile JWS from the Keyoxide ASPE endpoint.
- Decode the ASP profile JWS header to extract the authoritative public key.
- If the attestation JWS contains an inline
jwk, confirm it matches the ASPE-sourced key. - Verify the attestation JWS signature using the ASPE-sourced public key.
- 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:
- A debtor domain gets a WLI attestation.
- Emails from that domain get ELI evaluations.
- Invoice-level certificates inherit both.
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
- Node.js TLS API:
getPeerCertificate()does not expose all raw X.509 extensions. Extended parsing requires @peculiar/x509 and the raw DER certificate. - No OCSP stapling: Node.js does not expose OCSP stapling status on the TLS socket. WLI can check for OCSP responder URLs in AIA but cannot confirm stapling.
- SCT parsing: No dedicated library. SCTs are parsed via manual DER walking of the CT extension value.
- Point-in-time only: WLI attestations expire after 24 hours. No historical tracking in v0.1.0.
- No active probing: WLI does not attempt deprecated protocol downgrades, cipher enumeration, or vulnerability scanning.
Changelog
v0.1.0
- Initial specification.
- 13 scored signals with weighted composite.
- W3C Verifiable Credential output format.
- Human-readable attestation report.
- CLI and API server.
- Extended X.509 parsing via @peculiar/x509.
License and Copyright
© 2026 Kapwork