Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions services/ontology/schemas/documentKey.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"schemaId": "83e80d2b-eb06-46f9-9ef8-a2d0ea262801",
"title": "DocumentKey",
"description": "The key that opens an encrypted document, held in a vault rather than in any application's database.\n\nThis exists for INTEROPERABILITY, not secrecy. A signing document belongs to the people signing it, not to the service that happened to send it — so if the key lived in one platform's database, that platform would be the only software able to open the document, and a signer using a different application simply could not read what they were asked to sign. Putting the key in the participants' own storage is what keeps the document theirs.\n\nBe exact about what this does and does not protect. Encryption keeps the bytes from anyone who merely knows the blob URL — which matters, because blob storage serves those bytes to unauthenticated requests. It does NOT keep them from other platforms: any admitted platform can read this envelope, as it can read any other. Nothing available today changes that; the eID wallet can sign and nothing else, so wrapping a key to a person's own key is not currently possible.\n\nAlso: access here cannot be withdrawn. A key that has been read has been read, and a copy in someone else's vault cannot be deleted. Anything an interface calls 'revoke' is a change of status, never a removal of access.",
"type": "object",
"properties": {
"isReference": { "type": "boolean", "description": "false on the canonical record held by the initiator, true on each participant's reference to it." },
"keyId": { "type": "string", "minLength": 1 },
"envelopeId": { "type": "string", "minLength": 1, "description": "The signing envelope this key opens." },
"canonicalOwnerEName": { "type": "string", "minLength": 1 },
"canonicalKeyId": { "type": "string", "minLength": 1, "description": "Meta envelope id of the canonical key record. References only." },
"algorithm": { "type": "string", "enum": ["aes-256-gcm"] },
"key": { "type": "string", "description": "The symmetric key, base64. Present on the canonical record and on references — a reference that does not carry the key would not let its holder open anything, which is the entire purpose." },
"iv": { "type": "string", "description": "Initialisation vector for the document ciphertext, base64. Random per encryption and never reused: two ciphertexts under one key and IV break GCM completely." },
"authTag": { "type": "string", "description": "GCM authentication tag, base64." },
"plaintextSha256": { "type": "string", "pattern": "^[a-f0-9]{64}$", "description": "SHA-256 of the decrypted document, so a holder can confirm they decrypted the thing that was signed. Deliberately repeated from the signing envelope: a key record that cannot be checked against the document is a key to an unknown lock." },
"sharedBy": { "type": "string", "description": "References only." },
"sharedAt": { "type": "string", "format": "date-time", "description": "References only." },
"createdAt": { "type": "string", "format": "date-time" }
},
"required": ["isReference", "keyId", "envelopeId", "canonicalOwnerEName", "algorithm", "key", "iv", "authTag", "createdAt"],
"oneOf": [
{
"title": "CanonicalDocumentKey",
"properties": { "isReference": { "const": false } },
"required": ["isReference", "keyId", "envelopeId", "canonicalOwnerEName", "algorithm", "key", "iv", "authTag", "plaintextSha256", "createdAt"],
"not": { "anyOf": [{ "required": ["canonicalKeyId"] }, { "required": ["sharedBy"] }, { "required": ["sharedAt"] }] }
},
{
"title": "DocumentKeyReference",
"properties": { "isReference": { "const": true } },
"required": ["isReference", "keyId", "envelopeId", "canonicalOwnerEName", "canonicalKeyId", "algorithm", "key", "iv", "authTag", "sharedBy", "sharedAt", "createdAt"]
}
]
}
175 changes: 175 additions & 0 deletions services/ontology/schemas/eidSignature.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"schemaId": "ede4c610-9e22-4074-9598-82c915058f4f",
"title": "EIDSignature",
"description": "One participant's eID signature over a signing envelope, always written to the signer's own vault so they can produce it even if the initiator disappears. Carries the signer's own field values inline, because fieldsHash cannot be recomputed without them and verification must not depend on the platform's database. Distinct from the registered Signature ontology (b2c3d4e5-f6a7-8901-bcde-f12345678901), which sets additionalProperties:false and so cannot carry envelopeId, fieldsHash, role or order.",
"type": "object",
"properties": {
"signatureId": {
"type": "string",
"minLength": 1
},
"envelopeId": {
"type": "string",
"minLength": 1
},
"canonicalOwnerEName": {
"type": "string",
"minLength": 1,
"description": "eName of the envelope initiator. Required: this ontology exists so a signer can produce their signature when the initiator is gone, and without this field an envelope found on a vault cannot be tied back to the canonical record without our database — which is exactly the situation the field is for."
},
"signerEName": {
"type": "string",
"minLength": 1,
"description": "eName of the signer. The registered Signature ontology calls this field publicKey and stores an eName in it in live data; this ontology names it honestly."
},
"docPlaintextSha256": {
"type": "string",
"pattern": "^[a-f0-9]{64}$",
"description": "SHA-256 of the original unencrypted PDF, as covered by this signature."
},
"fieldValues": {
"type": "array",
"description": "This signer's own field values, exactly as canonicalised into fieldsHash. Present so a third party can recompute fieldsHash a year from now without access to our database. Layout (page, coordinates, widget size) is deliberately absent: it is a product concern and proves nothing.",
"items": {
"type": "object",
"properties": {
"fieldId": {
"type": "string",
"minLength": 1
},
"type": {
"type": "string",
"enum": [
"signature",
"initials",
"date",
"text",
"checkbox",
"dropdown"
]
},
"label": {
"type": "string"
},
"value": {
"type": [
"string",
"boolean",
"null"
],
"description": "For signature and initials this is the SHA-256 of the drawn mark, not the mark itself, so the visual does not leak through an unencrypted envelope."
}
},
"required": [
"fieldId",
"type",
"value"
]
}
},
"fieldsHash": {
"type": "string",
"pattern": "^[a-f0-9]{64}$",
"description": "SHA-256 of the canonical JSON of fieldValues."
},
"fieldSchemaVersion": {
"type": "integer",
"minimum": 1,
"description": "Canonicalisation version, so the meaning of fieldsHash stays provable over time."
},
"signedPayload": {
"type": "string",
"minLength": 1,
"description": "The exact string handed to the wallet and covered by the signature, recorded verbatim so verification never has to reconstruct how it was composed."
},
"signature": {
"type": "string",
"minLength": 1,
"description": "The signature returned by the eID wallet."
},
"role": {
"type": "string",
"enum": [
"signer",
"approver"
]
},
"identityAssurance": {
"type": "object",
"description": "What the signing platform knew about this person's identity at the moment of the act, read from their own vault. RECORDED, not recomputed: a passport check added a year later does not make an earlier signature better evidence, and an attestation withdrawn since does not make it worse, so a reader of the finished document needs what was true then. IMPORTANT: this is an observation made BY the platform, sitting beside the signature. It is not covered by the signature and must never be presented as a claim the person made. Anyone who does not wish to take it on trust can recompute today's answer from the same vault.",
"properties": {
"level": {
"type": "string",
"enum": [
"verified",
"attested",
"unknown",
"unavailable"
],
"description": "verified — a licensed identity vendor checked an identity document, and the attestation is signed by an authority whose key is published. attested — no document was checked, but other people have mutually attested to this identity. unknown — we looked and nothing at all is known about who holds this eName. unavailable — we could not look: their storage did not answer at the time. The last two are deliberately distinct and MUST NOT be collapsed by a reader: 'we found nothing' is an observation about the person, 'we could not ask' is an admission about us. This record is written once beside a signature and read for years, so a momentary failure recorded as 'unknown' would be a permanent false statement about somebody whose identity is in fact established."
},
"verifiedName": {
"type": [
"string",
"null"
],
"description": "The name the vendor checked. Never a name the person stated about themselves."
},
"verifiedBy": {
"type": [
"string",
"null"
],
"description": "Which identity vendor performed the check."
},
"attestations": {
"type": "integer",
"minimum": 0,
"description": "How many other people had mutually attested to this identity at the time."
},
"authenticatedBy": {
"type": "string",
"description": "How the person proved, in this act, that they hold the eName. Currently always 'eid-key-signature' — a signature from the key their eID wallet holds. Named explicitly so a reader is not left to assume something weaker or stronger."
},
"capturedAt": {
"type": "string",
"format": "date-time",
"description": "When the observation was made — the moment of signing or sealing."
}
},
"required": [
"level",
"authenticatedBy",
"capturedAt"
]
},
"order": {
"type": "integer",
"minimum": 1,
"description": "Product-level position in the signing order. Signatures remain cryptographically independent of one another."
},
"signedAt": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"signatureId",
"envelopeId",
"canonicalOwnerEName",
"signerEName",
"docPlaintextSha256",
"fieldValues",
"fieldsHash",
"fieldSchemaVersion",
"signedPayload",
"signature",
"signedAt",
"createdAt"
]
}
91 changes: 91 additions & 0 deletions services/ontology/schemas/envelopeAuditEvent.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"schemaId": "8c2c245e-8714-41da-8407-14ab5ce8ea38",
"title": "EnvelopeAuditEvent",
"description": "One recorded step in the life of a signing envelope, written to the vault of whoever performed it.\n\nIMPORTANT — what this is and is not. Any platform holding a developer key can write an envelope to any vault by naming its owner in X-ENAME; there is no place in the request for the owner's consent. An unsigned event is therefore FORGEABLE: a 'signed' or 'opened' event can be fabricated on the vault of someone who never opened the application. Unsigned events are an operational journal, not evidence.\n\nFor the actions a person may genuinely need to prove — above all 'declined', which is a statement someone may have to defend — the actor signs the event with their eID key and fills signedPayload and signature. Only those events carry evidentiary weight, on exactly the same footing as EIDSignature: the private key lives in the wallet and the platform key cannot forge it. This is deliberately carried no IP address or user agent: those are personal data and would be written into someone else's vault.",
"type": "object",
"properties": {
"eventId": {
"type": "string",
"minLength": 1
},
"envelopeId": {
"type": "string",
"minLength": 1
},
"canonicalOwnerEName": {
"type": "string",
"minLength": 1,
"description": "eName of the envelope initiator. Required for the same reason as on EIDSignature: an event found on a vault must be tied back to the canonical envelope without our database."
},
"actorEName": {
"type": "string",
"minLength": 1,
"description": "eName of whoever performed the action. Equals the vault this event is written to."
},
"action": {
"type": "string",
"enum": [
"created",
"sent",
"access_granted",
"opened",
"fields_filled",
"signed",
"declined",
"completed",
"revoked",
"expired",
"downloaded"
]
},
"subjectEName": {
"type": [
"string",
"null"
],
"description": "eName the action was directed at, where that differs from the actor. For example the invitee on access_granted."
},
"detail": {
"type": "string",
"description": "Short human-readable note. Must not contain document contents."
},
"occurredAt": {
"type": "string",
"format": "date-time"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"signedPayload": {
"type": "string",
"description": "The exact string signed by the actor, composed as `${action}|${envelopeId}|${actorEName}|${occurredAt}`. Present only on signed events. Absent means this event is unproven and must not be presented as evidence."
},
"signature": {
"type": "string",
"description": "The actor's eID signature over signedPayload. Its presence is what turns this record from a journal entry into something provable."
},
"isAttested": {
"type": "boolean",
"description": "True when signedPayload and signature are present and verified. Explicit rather than inferred, so a reader cannot mistake a missing signature for an unchecked one."
}
},
"required": [
"eventId",
"envelopeId",
"canonicalOwnerEName",
"actorEName",
"action",
"occurredAt",
"createdAt"
],
"dependencies": {
"signature": [
"signedPayload"
],
"signedPayload": [
"signature"
]
}
}
27 changes: 27 additions & 0 deletions services/ontology/schemas/handwrittenSignature.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"schemaId": "2a062bd7-c7a3-451e-b4ff-d1ada729bf46",
"title": "HandwrittenSignature",
"description": "A person's handwritten signature or initials, kept as a property of the person rather than of any one document — like their name or avatar. It lives on their own vault so that ANY signing application can offer it back to them instead of making them draw it again in every service.\n\nIMPORTANT — this is a convenience, not a credential. Possessing this image lets nobody sign anything: what binds a document is the eID signature, and the signature envelope records the SHA-256 of the mark rather than the mark itself. Treat a stolen image as an embarrassment, not a compromise, and do not build any check that trusts it.\n\nIt is still personal, so the image is stored encrypted and referenced by w3ds://file rather than inlined. Be honest about what that buys: it keeps the bytes away from anyone who merely knows the blob URL, which is a real exposure, but not away from platforms — the key is readable by any of them, as everything in a vault is.",
"type": "object",
"properties": {
"signatureId": { "type": "string", "minLength": 1 },
"ownerEName": { "type": "string", "minLength": 1, "description": "The person this signature belongs to. Always the vault it lives on." },
"kind": { "type": "string", "enum": ["signature", "initials"], "description": "A full signature or a short initials mark. People generally want both." },
"label": { "type": "string", "description": "Optional name for this mark, so somebody can keep more than one (a formal signature and a quick one, say)." },
"method": {
"type": "string",
"enum": ["drawn", "typed", "uploaded"],
"description": "How the mark was produced. Recorded because it is honest to show, not because it changes what the mark is worth."
},
"imageUri": { "type": "string", "description": "w3ds://file URI of the image. Referenced rather than inlined: an inline image bloats every read of this envelope, and the File record supplies size and content type for free." },
"contentEncoding": { "type": "string", "enum": ["none", "aes-256-gcm"], "description": "How the bytes behind imageUri are protected." },
"imageSha256": { "type": "string", "pattern": "^[a-f0-9]{64}$", "description": "SHA-256 of the decrypted image. This is the value a signature envelope records when this mark is used, so a verifier can confirm the mark shown on a document is the one that was signed for." },
"widthPx": { "type": "integer", "minimum": 1 },
"heightPx": { "type": "integer", "minimum": 1 },
"isDefault": { "type": "boolean", "description": "Offer this one first." },
"createdAt": { "type": "string", "format": "date-time" },
"updatedAt": { "type": "string", "format": "date-time" }
},
"required": ["signatureId", "ownerEName", "kind", "imageUri", "imageSha256", "createdAt"]
}
Loading
Loading