Trust Mark Management

Trust marks are signed assertions about entities in the federation. They indicate that an entity meets certain criteria or belongs to a specific category defined by the trust mark type.

Understanding Trust Marks

A trust mark consists of:

  • Trust Mark Type: A URL identifier defining the type/category

  • Subject: The entity the trust mark is issued to

  • Issuer: The Trust Anchor issuing the mark

  • Validity: Issuance time and expiry

Trust marks are signed JWTs that can be verified by any federation participant.

Creating Trust Mark Types

Before issuing trust marks, define the trust mark types your federation supports.

Example: Creating a membership trust mark type

curl -X POST http://localhost:8000/api/v1/trustmarktypes \
  -H "Content-Type: application/json" \
  -d '{
    "tmtype": "https://federation.example.com/trustmarks/member",
    "valid_for": 8760,
    "autorenew": true,
    "renewal_time": 48,
    "active": true
  }'

Response:

{
  "id": 1,
  "tmtype": "https://federation.example.com/trustmarks/member",
  "valid_for": 8760,
  "autorenew": true,
  "renewal_time": 48,
  "active": true
}

Trust Mark Type Parameters

Parameter

Description

tmtype

URL identifier for the trust mark type. This should be a URL you control.

valid_for

Maximum validity period for trust marks in hours. Individual marks can have shorter validity but not longer.

autorenew

If true, trust marks of this type will be automatically renewed before expiry.

renewal_time

Hours before expiry when renewal should occur.

active

If false, no new trust marks of this type can be issued.

Common Trust Mark Types

  • Membership: Entity is a member of an organization

  • Certification: Entity has passed a certification process

  • Compliance: Entity complies with specific requirements

  • Accreditation: Entity is accredited by an authority

Issuing Trust Marks

Issue a trust mark to an entity:

curl -X POST http://localhost:8000/api/v1/trustmarks \
  -H "Content-Type: application/json" \
  -d '{
    "tmt": 1,
    "domain": "https://example-rp.com"
  }'

Response:

{
  "id": 1,
  "tmt_id": 1,
  "domain": "https://example-rp.com",
  "expire_at": "2027-01-15T12:00:00Z",
  "autorenew": true,
  "valid_for": 8760,
  "renewal_time": 48,
  "active": true,
  "mark": "eyJhbGciOiJFUzI1NiIsInR5cCI6InRydXN0LW1hcmsrand0In0...",
  "additional_claims": null
}

Trust Mark with Additional Claims

Add custom claims to the trust mark JWT:

curl -X POST http://localhost:8000/api/v1/trustmarks \
  -H "Content-Type: application/json" \
  -d '{
    "tmt": 1,
    "domain": "https://example-rp.com",
    "additional_claims": {
      "ref": "https://federation.example.com/verification/123",
      "certification_date": "2024-01-15",
      "certification_level": "gold"
    }
  }'

The additional_claims appear directly in the trust mark JWT payload:

{
  "iss": "https://federation.example.com",
  "sub": "https://example-rp.com",
  "iat": 1705315200,
  "exp": 1736851200,
  "trust_mark_type": "https://federation.example.com/trustmarks/member",
  "ref": "https://federation.example.com/verification/123",
  "certification_date": "2024-01-15",
  "certification_level": "gold"
}

Trust Mark with Custom Validity

Issue a trust mark with shorter validity than the type default:

curl -X POST http://localhost:8000/api/v1/trustmarks \
  -H "Content-Type: application/json" \
  -d '{
    "tmt": 1,
    "domain": "https://example-rp.com",
    "valid_for": 720
  }'

Note

The valid_for value cannot exceed the trust mark type’s valid_for.

Viewing Trust Marks

List All Trust Marks

curl http://localhost:8000/api/v1/trustmarks

List Trust Marks for an Entity

curl -X POST http://localhost:8000/api/v1/trustmarks/list \
  -H "Content-Type: application/json" \
  -d '{"domain": "https://example-rp.com"}'

Renewing Trust Marks

Manually renew a trust mark:

curl -X POST http://localhost:8000/api/v1/trustmarks/1/renew

This generates a new JWT with updated iat and exp claims while preserving all other trust mark properties.

Automatic Renewal

Trust marks with autorenew: true will be automatically renewed when:

  1. The renewal_time threshold is reached (e.g., 48 hours before expiry)

  2. The trust mark type is active

  3. The trust mark itself is active

The reload_issued_tms management command handles automatic renewal:

docker compose exec admin python manage.py reload_issued_tms

This command is run automatically when the admin container starts.

Revoking Trust Marks

Revoke a trust mark by setting it to inactive:

curl -X PUT http://localhost:8000/api/v1/trustmarks/1 \
  -H "Content-Type: application/json" \
  -d '{"active": false}'

When a trust mark is revoked:

  1. The mark field is cleared (set to null)

  2. Redis is updated to mark the trust mark as “revoked”

  3. The entity is removed from the trust mark type’s member list

  4. Status checks return “revoked” for the old JWT

Updating Trust Mark Claims

Update the additional claims in a trust mark:

curl -X PUT http://localhost:8000/api/v1/trustmarks/1 \
  -H "Content-Type: application/json" \
  -d '{
    "additional_claims": {
      "ref": "https://updated-reference.example.com",
      "certification_level": "platinum"
    }
  }'

This generates a new JWT with the updated claims.

Verifying Trust Marks

Public Verification

Any federation participant can verify a trust mark by calling the trust mark status endpoint:

# Get the trust mark JWT
TRUST_MARK=$(curl -s "https://federation.example.com/trust_mark?trust_mark_type=https://federation.example.com/trustmarks/member&sub=https://example-rp.com")

# Verify its status
curl -X POST https://federation.example.com/trust_mark_status \
  -d "trust_mark=$TRUST_MARK"

Status Response:

{
  "iss": "https://federation.example.com",
  "iat": 1705315200,
  "status": "active",
  "sub": "https://example-rp.com",
  "trust_mark_type": "https://federation.example.com/trustmarks/member"
}

Status Values

Status

Meaning

active

Trust mark is valid, not expired, and not revoked

revoked

Trust mark was explicitly revoked by the issuer

expired

Trust mark JWT has passed its expiry time

invalid

Signature verification failed or unknown issuer

Listing Trust Mark Holders

Get all entities with a specific trust mark:

Via Trust Anchor API:

curl "https://federation.example.com/trust_mark_list?trust_mark_type=https://federation.example.com/trustmarks/member"

Response:

[
  "https://example-rp.com",
  "https://other-entity.com",
  "https://federation.example.com"
]

Via Subordinate List with Filter:

curl "https://federation.example.com/list?trust_mark_type=https://federation.example.com/trustmarks/member"

Trust Marks for the Trust Anchor

The Trust Anchor itself can have trust marks issued by external parties. Configure these in localsettings.py:

TA_TRUSTMARKS = [
    {
        "trust_mark_type": "https://root-ta.example.com/trustmarks/verified-anchor",
        "mark": "eyJhbGciOiJFUzI1NiIsInR5cCI6InRydXN0LW1hcmsrand0In0..."
    },
    {
        "trust_mark_type": "https://accreditation.example.com/trustmarks/certified",
        "mark": "eyJ..."
    }
]

These trust marks appear in the TA’s entity configuration (/.well-known/openid-federation).

Trusted Trust Mark Issuers (Federation Recognition)

OpenID Federation §3.1.2 defines the trust_mark_issuers claim on a Trust Anchor’s entity configuration. It tells the federation which combinations of trust mark type and issuer are recognised. Inmor publishes this claim on /.well-known/openid-federation and uses it during /resolve (§8.3) to decide which trust marks may be included in the resolve response.

The recognised list is built from two sources:

  1. External issuers — configured via TA_TRUSTED_TRUSTMARK_ISSUERS in localsettings.py (a Python dict[str, list[str]] mapping trust mark type URL to a list of allowed issuer entity IDs). This is the authoritative source for issuers other than this TA itself.

  2. Self-issuance — for every active TrustMarkType row in the admin database, this TA’s own entity ID (settings.TRUSTMARK_PROVIDER — the iss of every trust mark issued by this admin) is appended automatically. Operators do not need to restate this in localsettings.py.

# localsettings.py — only external issuers go here
TA_TRUSTED_TRUSTMARK_ISSUERS = {
    "https://refeds.org/trustmarks/sirtfi": ["https://swamid.se"],
    "https://openid.net/certification/op": [],   # any issuer (per §3.1.2)
}

After changing this setting (or after adding/removing TrustMarkType rows), run the management command to refresh the published entity configuration:

docker compose exec admin python manage.py regenerate_entity

Per spec §3.1.2, an empty list for a given type means “anyone may issue trust marks with that identifier”; signature, expiry, and (for external issuers) revocation status are still verified.

How /resolve Uses the List

For every trust mark on a resolved subject, the Rust TA:

  1. Skips the mark if its trust_mark_type is not a key in trust_mark_issuers.

  2. Skips the mark if its iss is not in the allowed list for that type (empty list bypasses this check).

  3. For TA-issued marks (iss equals this TA): verifies the signature against this TA’s public keyset and checks Redis for a revocation entry.

  4. For external-issued marks: fetches the issuer’s entity configuration, discovers the issuer’s federation_trust_mark_status_endpoint from its federation_entity metadata, POSTs the trust mark to that endpoint, and verifies the response JWT signature against the issuer’s JWKS. The mark is included only if the response’s status claim is "active".

The behaviour is fail-closed: any error (network failure, missing status endpoint, signature failure, non-active status, malformed JWT) causes that single trust mark to be omitted from the resolve response. The rest of the resolve still succeeds. Skipped marks are logged at WARN.

If an external issuer does not advertise federation_trust_mark_status_endpoint, marks issued by it are omitted from the resolve response (and logged at WARN, like every other skipped mark). Issuers that want their marks honoured by Inmor MUST publish a status endpoint (per spec §8.4.1).

The resolve response’s exp claim is the minimum of every trust chain exp and every included trust mark’s exp (per spec §8.3.2).

Best Practices

  1. Use Meaningful Type URLs: Trust mark type URLs should be under your control and document what the mark represents.

  2. Set Appropriate Validity: Balance security (shorter validity) with operational overhead (more frequent renewals).

  3. Enable Auto-Renewal: For operational trust marks, enable auto-renewal to prevent accidental expiry.

  4. Document Requirements: Clearly document what criteria an entity must meet to receive each trust mark type.

  5. Use Additional Claims: Include reference URLs or other metadata that help verify the basis for the trust mark.

  6. Regular Audits: Periodically review issued trust marks and revoke any that are no longer valid.

Workflow Example

Complete workflow for managing trust marks:

  1. Define Trust Mark Type:

    curl -X POST http://localhost:8000/api/v1/trustmarktypes \
      -H "Content-Type: application/json" \
      -d '{
        "tmtype": "https://federation.example.com/trustmarks/verified-rp",
        "valid_for": 4320,
        "autorenew": true
      }'
    
  2. Register Entity (if not already registered):

    See Subordinate Management for entity registration.

  3. Issue Trust Mark:

    curl -X POST http://localhost:8000/api/v1/trustmarks \
      -H "Content-Type: application/json" \
      -d '{
        "tmt": 1,
        "domain": "https://verified-rp.example.com",
        "additional_claims": {
          "verification_date": "2024-01-15",
          "ref": "https://federation.example.com/verifications/abc123"
        }
      }'
    
  4. Entity Retrieves Trust Mark:

    curl "https://federation.example.com/trust_mark?trust_mark_type=https://federation.example.com/trustmarks/verified-rp&sub=https://verified-rp.example.com"
    
  5. Other Parties Verify:

    curl -X POST https://federation.example.com/trust_mark_status \
      -d "trust_mark=eyJ..."