Version 2

Keyoxide Specification

Version 2

Keyoxide Specification

Author: Yarmo Mackenbach
Version: 2
Last updated: 2023-10-04

Abstract

The Keyoxide Specification provides a set of opinionated guidelines for designing interfaces and experiences related to the verification of decentralized online identities as described in the Ariadne Identity Specification.

Status

This version of the specification is currently active.

Copyright (c) 2023 Yarmo Mackenbach. All rights reserved.

1. Introduction

The principal function of a Keyoxide client app is to verify decentralized online identities. It does so through a combination of the following actions:

  • fetch cryptographic keys;
  • fetch cryptographically signed messages;
  • extract identity claims and profile information from the cryptographic material;
  • fetch data at locations deduced from the identity claims;
  • compare cryptographic material and proofs to verify the identity claims.

If the Keyoxide client app can not run any or all of these tasks, it can request a Keyoxide server to do it in its place.

A Keyoxide instance does not store user profile data on its server. It dynamically fetches user profile data

2. Terminology

A Keyoxide profile is a collection of identity claims stored in a cryptographically secured document.

An identity claim is a link to an account hosted by a third party service provider, stored inside a cryptographically secured document. It requires an identity proof to be verified.

An identity proof is a link to a cryptographic keypair, hosted as part of an account on a third party serivce provider.

To view a Keyoxide profile is to fetch a cryptographically secured document, extract the identity claims it contains and verify them.

To verify an identity claim is to parse the claim, match it to potential service providers, deduce accessible locations that could contain an identity proof, fetch data from these locations and, if found, compare the identity proof to the cryptographically secured document that contained the claim.

3. HTTP Server

The API endpoints described in the following sections may be hosted on different domains.

3.1. Core API endpoints

/api/3/profile/fetch

  • Description: fetch a Keyoxide profile
  • URL parameters:
    • query
      • Desciption: the identifier for the cryptographic document (fingerprint, email address, ASPE URI)
      • Type: string
    • doVerification
      • Description: if true, the identity claims are verified
      • Type: boolean
    • protocol
      • Description: specify the protocol to fetch the cryptographic document
      • Type: string
      • Values: ["aspe", "hkp", "wkd"]
  • Returns: a JSON object as defined in 4.1.

/api/3/profile/verify

  • Description: verify a Keyoxide profile obtained through /api/3/profile/fetch with doVerification=false
  • URL parameters:
    • data
      • Desciption: the stringified JSON object obtained through /api/3/profile/fetch
      • Type: string
  • Returns: JSON object as defined in 4.1.

3.1. Proxy API endpoints

/api/3/get/http

  • Description: Proxy a HTTP request
  • URL parameters:
    • url
      • Desciption: the address to request
      • Type: string
    • format
      • Description: format of the
      • Type: string
      • Values: ["text", "json"]
  • Returns: string or JSON object

/api/3/get/dns

  • Description: Proxy a DNS records request
  • URL parameters:
    • domain
      • Desciption: the domain for which to get the TXT DNS records
      • Type: string
  • Returns: JSON object as defined in 4.2.

/api/3/get/xmpp

  • Description: Proxy a XMPP vCard request
  • URL parameters:
    • id
      • Desciption: the jabber id to query
      • Type: string
    • field
      • Desciption: the vCard field to query
      • Type: string
  • Returns: JSON object

/api/3/get/twitter

  • Description: Proxy a Twitter API request
  • URL parameters:
    • tweetId
      • Desciption: the id of a tweet
      • Type: int
  • Returns: JSON object

/api/3/get/matrix

  • Description: Proxy a Matrix API request
  • URL parameters:
    • roomId
      • Desciption: the id of a Matrix room
      • Type: string
    • eventId
      • Desciption: the id of an event in the Matrix room
      • Type: string
  • Returns: JSON object

/api/3/get/telegram

  • Description: Proxy a Telegram API request
  • URL parameters:
    • user
      • Desciption: the username of a Telegram account
      • Type: string
    • chat
      • Desciption: the name of a Telegram group chat
      • Type: string
  • Returns: JSON object

/api/3/get/irc

  • Description: Proxy an IRC taxonomy request
  • URL parameters:
    • nick
      • Desciption: the nickname of a IRC account
      • Type: string
    • domain
      • Desciption: the domain of the server that hosts the IRC account
      • Type: string
  • Returns: JSON object

/api/3/get/gitlab

  • Description: Proxy a Gitlab request
  • URL parameters:
    • username
      • Desciption: the username of a Gitlab account
      • Type: string
    • domain
      • Desciption: the domain of the server that hosts the Gitlab account
      • Type: string
  • Returns: JSON object

/api/3/get/activitypub

  • Description: Proxy an ActivityPub request
  • URL parameters:
    • url
      • Desciption: the URL of an ActivityPub profile
      • Type: string
  • Returns: JSON object

4. Data schemas

4.1. JSON schema for a Keyoxide profile

The following schema requires the profileVersion to be set to 2 (integer).

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://spec.keyoxide.org/2/profile.schema.json",
  "title": "Profile",
  "description": "Keyoxide profile with personas",
  "type": "object",
  "properties": {
    "profileVersion": {
      "description": "The version of the profile",
      "type": "integer"
    },
    "profileType": {
      "description": "The type of the profile [openpgp, asp]",
      "type": "string"
    },
    "identifier": {
      "description": "Identifier of the profile (email, fingerprint, URI)",
      "type": "string"
    },
    "personas": {
      "description": "The personas inside the profile",
      "type": "array",
      "items": {
        "$ref": "https://spec.keyoxide.org/2/persona.schema.json"
      },
      "minItems": 1,
      "uniqueItems": true
    },
    "primaryPersonaIndex": {
      "description": "The index of the primary persona",
      "type": "integer"
    },
    "publicKey": {
      "description": "The cryptographic key associated with the profile",
      "type": "object",
      "properties": {
        "keyType": {
          "description": "The type of cryptographic key [eddsa, es256, openpgp, none]",
          "type": "string"
        },
        "fingerprint": {
          "description": "The fingerprint of the cryptographic key (not in URI format)",
          "type": "string"
        },
        "encoding": {
          "description": "The encoding of the cryptographic key [pem, jwk, armored_pgp, none]",
          "type": "string"
        },
        "encodedKey": {
          "description": "The encoded cryptographic key (PEM, stringified JWK, ...)",
          "type": ["string", "null"]
        },
        "fetch": {
          "description": "Details on how to fetch the public key",
          "type": "object",
          "properties": {
            "method": {
              "description": "The method to fetch the key [aspe, hkp, wkd, http, none]",
              "type": "string"
            },
            "query": {
              "description": "The query to fetch the key",
              "type": ["string", "null"]
            },
            "resolvedUrl": {
              "description": "The URL the method eventually resolved to",
              "type": ["string", "null"]
            }
          }
        }
      },
      "required": [
        "keyType",
        "fetch"
      ]
    },
    "verifiers": {
      "description": "A list of links to verifiers",
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": {
            "description": "Name of the verifier site",
            "type": "string"
          },
          "url": {
            "description": "URL to the profile page on the verifier site",
            "type": "string"
          }
        }
      },
      "uniqueItems": true
    }
  },
  "required": [
    "profileVersion",
    "profileType",
    "identifier",
    "personas",
    "primaryPersonaIndex",
    "publicKey",
    "verifiers"
  ],
  "additionalProperties": false
}

4.2. JSON schema for a Keyoxide persona

The following schema assumes the parent profile's profileVersion to be set to 2 (integer).

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://spec.keyoxide.org/2/persona.schema.json",
  "title": "Profile",
  "description": "Keyoxide persona with identity claims",
  "type": "object",
  "properties": {
    "identifier": {
      "description": "Identifier of the persona",
      "type": ["string", "null"]
    },
    "name": {
      "description": "Name of the persona",
      "type": "string"
    },
    "email": {
      "description": "Email address of the persona",
      "type": ["string", "null"]
    },
    "description": {
      "description": "Description of the persona",
      "type": ["string", "null"]
    },
    "avatarUrl": {
      "description": "URL to an avatar image",
      "type": ["string", "null"]
    },
    "isRevoked": {
      "type": "boolean"
    },
    "claims": {
      "description": "A list of identity claims",
      "type": "array",
      "items": {
        "$ref": "https://spec.keyoxide.org/2/claim.schema.json"
      },
      "uniqueItems": true
    }
  },
  "required": [
    "name",
    "claims"
  ],
  "additionalProperties": false
}

4.3. JSON schema for a Keyoxide identity claim

The following schema requires the claimVersion to be set to 2 (integer).

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://spec.keyoxide.org/2/claim.schema.json",
  "title": "Identity claim",
  "description": "Verifiable online identity claim",
  "type": "object",
  "properties": {
    "claimVersion": {
      "description": "The version of the claim",
      "type": "integer"
    },
    "uri": {
      "description": "The claim URI",
      "type": "string"
    },
    "proofs": {
      "description": "The proofs that would verify the claim",
      "type": "array",
      "items": {
        "type": "string"
      },
      "minItems": 1,
      "uniqueItems": true
    },
    "matches": {
      "description": "Service providers matched to the claim",
      "type": "array",
      "items": {
        "$ref": "https://spec.keyoxide.org/2/serviceprovider.schema.json"
      },
      "uniqueItems": true
    },
    "status": {
      "type": "integer",
      "description": "Claim status code"
    },
    "display": {
      "type": "object",
      "properties": {
        "profileName": {
          "type": "string",
          "description": "Account name to display in the user interface"
        },
        "profileUrl": {
          "type": ["string", "null"],
          "description": "Profile URL to link to in the user interface"
        },
        "proofUrl": {
          "type": ["string", "null"],
          "description": "Proof URL to link to in the user interface"
        },
        "serviceProviderName": {
          "type": ["string", "null"],
          "description": "Name of the service provider to display in the user interface"
        },
        "serviceProviderId": {
          "type": ["string", "null"],
          "description": "Id of the service provider"
        }
      }
    }
  },
  "required": [
    "claimVersion",
    "uri",
    "proofs",
    "status",
    "display"
  ],
  "additionalProperties": false
}

4.4. JSON schema for a Keyoxide service provider

The following schema assumes the parent claim's claimVersion to be set to 2 (integer).

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://spec.keyoxide.org/2/serviceprovider.schema.json",
  "title": "Service provider",
  "description": "A service provider that can be matched to identity claims",
  "type": "object",
  "properties": {
    "about": {
      "description": "Details about the service provider",
      "type": "object",
      "properties": {
        "name": {
          "description": "Full name of the service provider",
          "type": "string"
        },
        "id": {
          "description": "Identifier of the service provider (no whitespace or symbols, lowercase)",
          "type": "string"
        },
        "homepage": {
          "description": "URL to the homepage of the service provider",
          "type": ["string", "null"]
        }
      }
    },
    "profile": {
      "description": "What the profile would look like if the match is correct",
      "type": "object",
      "properties": {
        "display": {
          "description": "Profile name to be displayed",
          "type": "string"
        },
        "uri": {
          "description": "URI or URL for public access to the profile",
          "type": "string"
        },
        "qr": {
          "description": "URI or URL associated with the profile usually served as a QR code",
          "type": ["string", "null"]
        }
      }
    },
    "claim": {
      "description": "Details from the claim matching process",
      "type": "object",
      "properties": {
        "uriRegularExpression": {
          "description": "Regular expression used to parse the URI",
          "type": "string"
        },
        "uriIsAmbiguous": {
          "description": "Whether this match automatically excludes other matches",
          "type": "boolean"
        }
      }
    },
    "proof": {
      "description": "Information for the proof verification process",
      "type": "object",
      "properties": {
        "request": {
          "description": "Details to request the potential proof",
          "type": "object",
          "properties": {
            "uri": {
              "description": "Location of the proof",
              "type": ["string", "null"]
            },
            "accessRestriction": {
              "description": "Type of access restriction [none, nocors, granted, server]",
              "type": "string"
            },
            "fetcher": {
              "description": "Name of the fetcher to use",
              "type": "string"
            },
            "data": {
              "description": "Data needed by the fetcher or proxy to request the proof",
              "type": "object",
              "additionalProperties": true
            }
          }
        },
        "response": {
          "description": "Details about the expected response",
          "type": "object",
          "properties": {
            "format": {
              "description": "Expected format of the proof [text, json]",
              "type": "string"
            },
          }
        },
        "target": {
          "description": "Details about the target located in the response",
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "format": {
                "description": "How is the proof formatted [uri, fingerprint]",
                "type": "string"
              },
              "encoding": {
                "description": "How is the proof encoded [plain, html, xml]",
                "type": "string"
              },
              "relation": {
                "description": "How are the response and the target related [contains, equals]",
                "type": "string"
              },
              "path": {
                "description": "Path to the target location if the response is JSON",
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  },
  "required": [
    "about",
    "profile",
    "claim",
    "proof"
  ],
  "additionalProperties": false
}

4.5. JSON schema for proxied DNS records

{
  "$schema": "http://json-schema.org/draft-07/schema",
  "$id": "https://keyoxide.org/dns.proxy.schema.json",
  "type": "object",
  "properties": {
    "domain": {
      "type": "string"
    },
    "records": {
      "type": "object",
      "properties": {
        "txt": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      }
    }
  },
  "required": [
    "domain",
    "records"
  ],
  "additionalProperties": false
}

5. Detailed overview of JSON schema values

5.1. profile.profileType

From https://spec.keyoxide.org/2/profile.schema.json.

Possible values:

  • "openpgp": profile data is gathered based on an OpenPGP public key.
  • "asp": profile data is gathered based on an Ariadne Signature Profile.

5.2. profile.publicKey.keyType

From https://spec.keyoxide.org/2/profile.schema.json.

Possible values:

  • "eddsa": Ed25519 and Ed448 cryptographic key for ASP profiles.
  • "es256": ES-256 cryptographic key for ASP profiles.
  • "openpgp": OpenPGP cryptographic key for OpenPGP profiles.
  • "none": no public key included in profile.

5.3. profile.publicKey.format

From https://spec.keyoxide.org/2/profile.schema.json.

Possible values:

  • "pem": PEM-encoded cryptographic key for eddsa and es256 key types.
  • "jwk": stringified-JWK-encoded cryptographic key for eddsa and es256 key types.
  • "armored_pgp": armor-exported cryptographic key for openpgp key type.
  • "none": no public key included in profile.

5.4. profile.publicKey.fetch.method

From https://spec.keyoxide.org/2/profile.schema.json.

Possible values:

  • "aspe": use the ASPE protocol to fetch the public key.
  • "hkp": use the HKP protocol to fetch the public key.
  • "wkd": use the WKD protocol to fetch the public key.
  • "http": fetch the public key using a HTTP GET request.
  • "none": no method to fetch the public key.

5.5. claim.status

From https://spec.keyoxide.org/2/claim.schema.json.

Possible values:

  • 100: not yet matched
  • 101: matched but not yet verified
  • 200: claim successfully verified by proof
  • 201: claim successfully verified by proof obtained through proxy
  • 300: unknown matching error
  • 301: matching error: no matched service providers
  • 400: unknown verification error
  • 401: verification error: no proof found