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 IDDisplay NameCategoryAuthoritative For
vendor:redhatRed Hat Product SecurityVendorpkg:rpm/redhat/*, pkg:oci/registry.redhat.io/*
vendor:canonicalUbuntu Security TeamDistributionpkg:deb/ubuntu/*
vendor:debianDebian Security TeamDistributionpkg:deb/debian/*
vendor:suseSUSE Security TeamDistributionpkg:rpm/suse/*, pkg:rpm/opensuse/*
vendor:microsoftMicrosoft Security ResponseVendorpkg:nuget/* (Microsoft packages)
vendor:oracleOracle SecurityVendorpkg:maven/com.oracle.*/*
vendor:apacheApache Security TeamCommunitypkg:maven/org.apache.*/*
vendor:googleGoogle Security TeamVendorpkg:golang/google.golang.org/*

3.2 Trusted Tier (Trust Tier 1)

Issuer IDDisplay NameCategory
cert:cisaCISACert
cert:nistNIST NVDCert
cert:githubGitHub Security AdvisoriesSecurityResearch
cert:snykSnyk SecurityCommercial
research:oss-fuzzGoogle OSS-FuzzSecurityResearch

3.3 Community Tier (Trust Tier 2)

Issuer IDDisplay NameCategory
community:osvOSV (Open Source Vulnerabilities)Community
community:vulndbVulnDBCommunity

4. API Endpoints

4.1 List Issuers

GET /api/v1/issuers

Query Parameters:

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:

  1. Check signature: If signed, lookup issuer by key fingerprint
  2. Check domain: Match issuer by advisory feed domain
  3. Check authoritativeFor: Match issuer by product PURL patterns
  4. Fallback: Assign Unknown tier 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:

  1. Verify Rekor inclusion proof
  2. Extract OIDC identity from certificate
  3. Match identity to registered issuer
  4. 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

VersionDateChanges
1.0.02025-12-19Initial release