Issuer Directory Contract v1.0.0
Status: APPROVED Version: 1.0.0 Effective: 2025-12-19 Owner: VEX Lens Guild + Issuer Directory Guild Sprint: SPRINT_0129_0001_0001 (unblocks VEXLENS-30-003)
1. Purpose
The Issuer Directory provides a registry of known VEX statement issuers with trust metadata, signing key information, and provenance tracking.
2. Data Model
2.1 Issuer Entity
public sealed record Issuer
{
/// <summary>Unique issuer identifier (e.g., "vendor:redhat", "cert:cisa").</summary>
public required string IssuerId { get; init; }
/// <summary>Issuer category.</summary>
public required IssuerCategory Category { get; init; }
/// <summary>Display name.</summary>
public required string DisplayName { get; init; }
/// <summary>Trust tier assignment.</summary>
public required IssuerTrustTier TrustTier { get; init; }
/// <summary>Official website URL.</summary>
public string? WebsiteUrl { get; init; }
/// <summary>Security advisory feed URL.</summary>
public string? AdvisoryFeedUrl { get; init; }
/// <summary>Registered signing keys.</summary>
public ImmutableArray<SigningKeyInfo> SigningKeys { get; init; }
/// <summary>Products/ecosystems this issuer is authoritative for.</summary>
public ImmutableArray<string> AuthoritativeFor { get; init; }
/// <summary>When this issuer record was created.</summary>
public DateTimeOffset CreatedAt { get; init; }
/// <summary>When this issuer record was last updated.</summary>
public DateTimeOffset UpdatedAt { get; init; }
/// <summary>Whether issuer is active.</summary>
public bool IsActive { get; init; } = true;
}
2.2 Issuer Category
public enum IssuerCategory
{
/// <summary>Software vendor/maintainer.</summary>
Vendor = 0,
/// <summary>Linux distribution.</summary>
Distribution = 1,
/// <summary>CERT/security response team.</summary>
Cert = 2,
/// <summary>Security research organization.</summary>
SecurityResearch = 3,
/// <summary>Community project.</summary>
Community = 4,
/// <summary>Commercial security vendor.</summary>
Commercial = 5
}
2.3 Signing Key Info
public sealed record SigningKeyInfo
{
/// <summary>Key fingerprint (SHA-256).</summary>
public required string Fingerprint { get; init; }
/// <summary>Key type (pgp, x509, sigstore).</summary>
public required string KeyType { get; init; }
/// <summary>Key algorithm (rsa, ecdsa, ed25519).</summary>
public string? Algorithm { get; init; }
/// <summary>Key size in bits.</summary>
public int? KeySize { get; init; }
/// <summary>Key creation date.</summary>
public DateTimeOffset? CreatedAt { get; init; }
/// <summary>Key expiration date.</summary>
public DateTimeOffset? ExpiresAt { get; init; }
/// <summary>Whether key is currently valid.</summary>
public bool IsValid { get; init; } = true;
/// <summary>Public key location (URL or inline).</summary>
public string? PublicKeyUri { get; init; }
}
3. Pre-Registered Issuers
3.1 Authoritative Tier (Trust Tier 0)
| Issuer ID | Display Name | Category | Authoritative For |
|---|---|---|---|
vendor:redhat | Red Hat Product Security | Vendor | pkg:rpm/redhat/*, pkg:oci/registry.redhat.io/* |
vendor:canonical | Ubuntu Security Team | Distribution | pkg:deb/ubuntu/* |
vendor:debian | Debian Security Team | Distribution | pkg:deb/debian/* |
vendor:suse | SUSE Security Team | Distribution | pkg:rpm/suse/*, pkg:rpm/opensuse/* |
vendor:microsoft | Microsoft Security Response | Vendor | pkg:nuget/* (Microsoft packages) |
vendor:oracle | Oracle Security | Vendor | pkg:maven/com.oracle.*/* |
vendor:apache | Apache Security Team | Community | pkg:maven/org.apache.*/* |
vendor:google | Google Security Team | Vendor | pkg:golang/google.golang.org/* |
3.2 Trusted Tier (Trust Tier 1)
| Issuer ID | Display Name | Category |
|---|---|---|
cert:cisa | CISA | Cert |
cert:nist | NIST NVD | Cert |
cert:github | GitHub Security Advisories | SecurityResearch |
cert:snyk | Snyk Security | Commercial |
research:oss-fuzz | Google OSS-Fuzz | SecurityResearch |
3.3 Community Tier (Trust Tier 2)
| Issuer ID | Display Name | Category |
|---|---|---|
community:osv | OSV (Open Source Vulnerabilities) | Community |
community:vulndb | VulnDB | Community |
4. API Endpoints
4.1 List Issuers
GET /api/v1/issuers
Query Parameters:
category: Filter by categorytrust_tier: Filter by trust tieractive: Filter by active status (default: true)limit: Max results (default: 100)cursor: Pagination cursor
4.2 Get Issuer
GET /api/v1/issuers/{issuerId}
4.3 Register Issuer (Admin)
POST /api/v1/issuers
Authorization: Bearer {admin_token}
{
"issuerId": "vendor:acme",
"category": "vendor",
"displayName": "ACME Security",
"trustTier": "trusted",
"websiteUrl": "https://security.acme.example",
"advisoryFeedUrl": "https://security.acme.example/feed.json",
"authoritativeFor": ["pkg:npm/@acme/*"]
}
4.4 Register Signing Key (Admin)
POST /api/v1/issuers/{issuerId}/keys
Authorization: Bearer {admin_token}
{
"fingerprint": "sha256:abc123...",
"keyType": "pgp",
"algorithm": "rsa",
"keySize": 4096,
"publicKeyUri": "https://security.acme.example/keys/signing.asc"
}
4.5 Lookup by Fingerprint
GET /api/v1/issuers/by-fingerprint/{fingerprint}
Returns the issuer associated with a signing key fingerprint.
5. Trust Tier Resolution
5.1 Automatic Assignment
When a VEX statement is received:
- Check signature: If signed, lookup issuer by key fingerprint
- Check domain: Match issuer by advisory feed domain
- Check authoritativeFor: Match issuer by product PURL patterns
- Fallback: Assign
Unknowntier if no match
5.2 Override Rules
Operators can configure trust overrides:
# etc/vexlens.yaml
issuer_overrides:
- issuer_id: "community:custom-feed"
trust_tier: "trusted" # Promote community to trusted
- issuer_id: "vendor:untrusted-vendor"
trust_tier: "community" # Demote vendor to community
6. Issuer Verification
6.1 PGP Signature Verification
public interface IIssuerVerifier
{
/// <summary>
/// Verifies a VEX document signature against registered issuer keys.
/// </summary>
Task<IssuerVerificationResult> VerifyAsync(
byte[] documentBytes,
byte[] signatureBytes,
CancellationToken cancellationToken = default);
}
public sealed record IssuerVerificationResult
{
public bool IsValid { get; init; }
public string? IssuerId { get; init; }
public string? KeyFingerprint { get; init; }
public IssuerTrustTier? TrustTier { get; init; }
public string? VerificationError { get; init; }
}
6.2 Sigstore Verification
For Sigstore-signed documents:
- Verify Rekor inclusion proof
- Extract OIDC identity from certificate
- Match identity to registered issuer
- Return issuer info with trust tier
7. Database Schema
CREATE TABLE vex.issuers (
issuer_id TEXT PRIMARY KEY,
category TEXT NOT NULL,
display_name TEXT NOT NULL,
trust_tier INT NOT NULL DEFAULT 3,
website_url TEXT,
advisory_feed_url TEXT,
authoritative_for TEXT[] DEFAULT '{}',
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE vex.issuer_signing_keys (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
issuer_id TEXT NOT NULL REFERENCES vex.issuers(issuer_id),
fingerprint TEXT NOT NULL UNIQUE,
key_type TEXT NOT NULL,
algorithm TEXT,
key_size INT,
public_key_uri TEXT,
is_valid BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ,
expires_at TIMESTAMPTZ,
registered_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_issuer_signing_keys_fingerprint ON vex.issuer_signing_keys(fingerprint);
CREATE INDEX idx_issuers_trust_tier ON vex.issuers(trust_tier);
Changelog
| Version | Date | Changes |
|---|---|---|
| 1.0.0 | 2025-12-19 | Initial release |
