diff options
author | Christian Grothoff <christian@grothoff.org> | 2024-04-19 16:41:01 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2024-04-19 16:41:01 +0200 |
commit | 0fc5dd62d57b722bb1e2b29fc99aa919d6c89324 (patch) | |
tree | c9b9b2f5cf66c34f54bbc31194f384594235e890 | |
parent | 67e6516a51af264e260490b596b9ab7c6d100e63 (diff) | |
download | docs-0fc5dd62d57b722bb1e2b29fc99aa919d6c89324.tar.gz docs-0fc5dd62d57b722bb1e2b29fc99aa919d6c89324.tar.bz2 docs-0fc5dd62d57b722bb1e2b29fc99aa919d6c89324.zip |
improve KYC spec
-rw-r--r-- | design-documents/023-taler-kyc.rst | 273 |
1 files changed, 190 insertions, 83 deletions
diff --git a/design-documents/023-taler-kyc.rst b/design-documents/023-taler-kyc.rst index 0ecfc4aa..77dbe2bb 100644 --- a/design-documents/023-taler-kyc.rst +++ b/design-documents/023-taler-kyc.rst @@ -240,10 +240,43 @@ Terminology * **Type of operation**: The operation type determines which Taler-specific operation has triggered the KYC requirement. We support four types of operation: withdraw (by customer), deposit (by merchant), P2P receive (by wallet) and (high) wallet balance. +451 Response +^^^^^^^^^^^^ + +When KYC operations are required, various endpoints may respond with a +``451 Unavailable for Legal Reasons`` status code and a `KycNeededRedirect` +body. + + .. ts:def:: KycNeededRedirect + + interface KycNeededRedirect { + + // Numeric `error code <error-codes>` unique to the condition. + // Should always be ``TALER_EC_EXCHANGE_GENERIC_KYC_REQUIRED``. + code: number; + + // Human-readable description of the error, i.e. "missing parameter", "commitment violation", ... + // Should give a human-readable hint about the error's nature. Optional, may change without notice! + hint?: string; + + // Public key associated with the account. The client must sign + // the initial request for the KYC status using the corresponding + // private key. Will be either a reserve public key or a merchant + // (instance) public key. + account_pub: EddsaPublicKey; + + // Legitimization target that the merchant should + // use to check for its KYC status using + // the ``/kyc-check/$REQUIREMENT_ROW`` endpoint. + requirement_row: Integer; + + } + + New Endpoints ^^^^^^^^^^^^^ -.. http:get:: /kyc-check/$REQUIREMENT_ROW/$H_PAYTO +.. http:get:: /kyc-check/$REQUIREMENT_ROW Checks the KYC status of a particular payment target and possibly begins the KYC process. This endpoint is typically used by wallets or merchants that @@ -251,27 +284,28 @@ New Endpoints requirement has been fulfilled. Long-polling may be used to instantly observe a change in the KYC requirement status. - The ``/kyc-check/`` endpoint is based on the legitimization measure's - serial number. It is returned in `KycNeededRedirect` responses via - the ``requirement_row`` field together with the ``h_payto``. - Access is *authenticated* by also passing the hash of the payto://-URI. + The requirement row of the ``/kyc-check/`` endpoint encodes the + legitimization measure's serial number. It is returned in + `KycNeededRedirect` responses via the ``requirement_row`` field. - .. note:: - - Weak authentication is acceptable, as the KYC status or the ability to - initiate a KYC process are not very sensitive. - - Given a valid pair of requirement row and payto-hash, the ``/kyc-check/`` - endpoint returns either just the KYC status or redirects the client (202) to - the next required stage of the KYC process. The redirection must be for an - HTTP(S) endpoint to be triggered via a simple HTTP GET. It must always be - the same endpoint for the same client, as the wallet/merchant backend are - not required to check for changes to this endpoint. Clients that received a - 202 status code may repeat the request and use long-polling to detect a - change of the HTTP status. + Given a valid pair of requirement row and account owner signature, the + ``/kyc-check/`` endpoint returns either just the KYC status or redirects the + client (202) to the next required stage of the KYC process. The redirection + must be for an HTTP(S) endpoint to be triggered via a simple HTTP GET. It + must always be the same endpoint for the same client, as the wallet/merchant + backend are not required to check for changes to this endpoint. Clients + that received a 202 status code may repeat the request and use long-polling + to detect a change of the HTTP status. **Request:** + *Account-Owner-Signature*: The client must provide Base-32 encoded EdDSA + signature with ``$ACCOUNT_PRIV``, affirming the desire to obtain KYC data. + Note that this is merely a simple authentication mechanism, the details of + the request are not protected by the signature. The ``$ACCOUNT_PRIV`` + is either the (wallet long-term) reserve private key or the merchant + instance private key. + :query timeout_ms=NUMBER: *Optional.* If specified, the exchange will wait up to ``timeout_ms`` milliseconds if the requirement continues to be mandatory provisioning of KYC data by the client. @@ -380,31 +414,32 @@ New Endpoints } -.. http:get:: /kyc-spa/{$HASH,$FILENAME} - - A set of ``/kyc-spa/$HASH`` GET endpoints is created per client ``$HASH`` - that serves the KYC SPA. This is where the ``/kyc-check/`` endpoint will - redirect clients unless all KYC/AML requirements are satisfied. The KYC SPA - will use the ``$HASH`` of its URL to initialize itself via the - ``/kyc-info/$HASH`` endpoint family. The KYC SPA may download additional - resources via ``/kyc-spa/$FILENAME``. The filenames must not match - base32-encoded SHA-512 hashes. - -.. http:get:: /kyc-info/$HASH - - A new set of ``/kyc-info/$HASH`` GET endpoints is created per client - ``$HASH`` to return information about the state of the KYC or AML process to - the KYC SPA. The SPA uses this information to show the user an appropriate - dialog. The SPA should also long-poll this endpoint for changes to the AML/KYC - state. Note that this is a client-facing endpoint, so it will only provide a - restricted amount of information to the customer (as some laws may forbid us - to inform particular customers about their true status). The endpoint will - typically inform the SPA about possible choices to proceed, such as directly - uploading files, contacting AML staff, or proceeding with a particular KYC - process at an external provider (such as Challenger). If the user chooses to - initate a KYC process at an external provider, the SPA must request the - respective process to be set-up by the exchange via the ``/kyc-start/`` - endpoint. +.. http:get:: /kyc-spa/$TARGET_TOKEN +.. http:get:: /kyc-spa/$FILENAME + + A set of ``/kyc-spa/$TARGET_TOKEN`` GET endpoints is created per account + hash that serves the KYC SPA. This is where the ``/kyc-check/`` endpoint + will in principle redirect clients. The KYC SPA will use the + ``$TARGET_TOKEN`` of its URL to initialize itself via the + ``/kyc-info/$TARGET_TOKEN`` endpoint family. The KYC SPA may download + additional resources via ``/kyc-spa/$FILENAME``. The filenames must not + match base32-encoded 256-bit values. + +.. http:get:: /kyc-info/$TARGET_TOKEN + + A new set of ``/kyc-info/$TARGET_TOKEN`` GET endpoints is created per client + account hash to return information about the state of the KYC or AML process + to the KYC SPA. The SPA uses this information to show the user an + appropriate dialog. The SPA should also long-poll this endpoint for changes + to the AML/KYC state. Note that this is a client-facing endpoint, so it will + only provide a restricted amount of information to the customer (as some + laws may forbid us to inform particular customers about their true status). + The endpoint will typically inform the SPA about possible choices to + proceed, such as directly uploading files, contacting AML staff, or + proceeding with a particular KYC process at an external provider (such as + Challenger). If the user chooses to initate a KYC process at an external + provider, the SPA must request the respective process to be set-up by the + exchange via the ``/kyc-start/`` endpoint. **Request**: @@ -458,7 +493,9 @@ New Endpoints // ID of the requirement, useful to construct the // ``/kyc-upload/$ID`` or ``/kyc-start/$ID`` endpoint URLs. - // Present if and only if "form" is not "INFO". + // Present if and only if "form" is not "INFO". The + // ``$ID`` value may itself contain ``/`` or ``?`` and + // basically encode any URL path (and optional arguments). id?: string; } @@ -492,13 +529,15 @@ New Endpoints The ``/kyc-upload/$ID`` POST endpoint allows the SPA to upload client-provided evidence. The ``$ID`` will be provided as part of the - ``/kyc-info`` body. This is for checks of type ``FORM``. + ``/kyc-info`` body. This is for checks of type ``FORM``. In practice, + ``$ID`` will encode both the ``$TARGET_TOKEN`` and the index of the selected + measure (but this should be irrelevant for the client). **Request**: Basically oriented along the possible formats of a HTTP form being - POSTed. Details will depend on the form. The server will try to - decode the uploaded body from whatever format it is provided in. + POSTed. Details will depend on the form. The server will try to decode the + uploaded body from whatever format it is provided in. **Response**: @@ -514,11 +553,12 @@ New Endpoints .. http:post:: /kyc-start/$ID - The ``/kyc-start/$ID`` POST endpoint allows the SPA to set up a new - external KYC process. It will return the URL that the client must GET - to begin the KYC process. The SPA should probably open this URL in a new - window or tab. The ``$ID`` will be provided as part of the ``/kyc-info`` - body. + The ``/kyc-start/$ID`` POST endpoint allows the SPA to set up a new external + KYC process. It will return the URL that the client must GET to begin the + KYC process. The SPA should probably open this URL in a new window or tab. + The ``$ID`` will be provided as part of the ``/kyc-info`` body. In + practice, ``$ID`` will encode both the ``$TARGET_TOKEN`` and the index of + the selected measure (but this should be irrelevant for the client). **Request**: @@ -548,21 +588,72 @@ New Endpoints in the future (since **vATTEST**). -.. http:get:: /kyc-proof/$H_PAYTO/$PROVIDER_SECTION +.. http:get:: /kyc-proof/$PROVIDER_SECTION?state=$H_PAYTO Upon completion of the process at the external KYC provider, the provider - must trigger a GET request to a new ``/kyc-proof/$H_PAYTO/$PROVIDER_SECTION`` - endpoint. This may be done either by redirecting the browser of the user to - that endpoint. Once this endpoint is triggered, the exchange will pass the - received arguments to the respective logic plugin. The logic plugin will then - (asynchronously) update the KYC status of the user. The logic plugin should - return a human-readable HTML page with the KYC result to the user. + must redirect the client (browser) to trigger a GET request to a new + ``/kyc-proof/$H_PAYTO/$PROVIDER_SECTION`` endpoint. Once this endpoint is + triggered, the exchange will pass the received arguments to the respective + logic plugin. The logic plugin will then (asynchronously) update the KYC + status of the user. The logic plugin should return a human-readable HTML + page with the KYC result to the user. This endpoint deliberately does + not use the ``$TARGET_TOKEN`` as the provider should not learn that token. + + This endpoint is thus accessed from the user's browser at the *end* of a KYC + process, possibly providing the exchange with additional credentials to + obtain the results of the KYC process. Specifically, the URL arguments + should provide information to the exchange that allows it to verify that the + user has completed the KYC process. The details depend on the logic, which + is selected by the "$PROVIDER_SECTION". + + While this is a GET (and thus safe, and idempotent), the operation may + actually trigger significant changes in the exchange's state. In + particular, it may update the KYC status of a particular payment target. + + **Request:** + + Details on the request depend on the specific KYC logic that was used. + + If the KYC plugin logic is OAuth 2.0, the query parameters are: + + :query code=CODE: OAuth 2.0 code argument. + :query state=STATE: OAuth 2.0 state argument with the H_PAYTO. + + .. note:: + + Depending on the OAuth variant used, additional + query parameters may need to be passed here. + + **Response:** + + Given that the response is returned to a user using a browser and **not** to + a Taler wallet, the response format is in human-readable HTML and not in + machine-readable JSON. + + :http:statuscode:`302 Found`: + The KYC operation succeeded and the + payment target is now authorized to transact. + The browser is redirected to a human-readable + page configured by the exchange operator. + :http:statuscode:`401 Unauthorized`: + The provided authorization token is invalid. + :http:statuscode:`404 Not found`: + The payment target is unknown. + :http:statuscode:`502 Bad Gateway`: + The exchange received an invalid reply from the + legitimization service. + :http:statuscode:`504 Gateway Timeout`: + The exchange did not receive a reply from the legitimization + service within a reasonable time period. -.. http:get:: /kyc-webhook/*`` -.. http:post:: /kyc-webhook/*`` + +.. http:get:: /kyc-webhook/$PROVIDER_SECTION/* +.. http:post:: /kyc-webhook/$PROVIDER_SECTION/* +.. http:get:: /kyc-webhook/$LOGIC/* +.. http:post:: /kyc-webhook/$LOGIC/* Alternatively, the KYC confirmation may be triggered by a ``/kyc-webhook`` - request. As KYC providers do not necessarily support passing detailed + request. As KYC **providers** do not necessarily support passing detailed information in the URL arguments, the ``/kyc-webhook`` only needs to specify either the ``PROVIDER_SECTION`` *or* the ``LOGIC`` (the name of the plugin implementing the KYC API). The API-specific webhook logic must then figure @@ -571,6 +662,18 @@ New Endpoints In contrast to ``kyc-proof``, the response does NOT go to the end-users' browser and should thus only indicate success or failure. + **Request:** + + Details on the request depend on the specific KYC logic that was used. + + **Response:** + + :http:statuscode:`204 No content`: + The operation succeeded. + :http:statuscode:`404 Not found`: + The specified logic is unknown. + + .. http:post:: /kyc-wallet`` The ``/wallet-kyc`` POST endpoint allows a wallet to notify an exchange if @@ -584,12 +687,11 @@ New Endpoints cryptographically enforce this), such modifications are a terms of service violation which may have legal consequences for the user. - Setup KYC identification for a wallet. Returns the KYC UUID. - This endpoint is used by compliant Taler wallets when they - are about to hit the balance threshold and thus need to have - the customer provide their personal details to the exchange. - The wallet is identified by its long-lived reserve public key - (which is used for P2P payments, not for withdrawals). + Setup KYC identification for a wallet. Returns the KYC UUID. This endpoint + is used by compliant Taler wallets when they are about to hit the balance + threshold and thus need to have the customer provide their personal details + to the exchange. The wallet is identified by its long-lived reserve public + key (which is used for P2P payments, not for withdrawals). **Request:** @@ -598,10 +700,9 @@ New Endpoints **Response:** :http:statuscode:`204 No Content`: - KYC is disabled at this exchange, or the balance - is below the threshold that requires KYC, or this - wallet already satisfied the KYC check for the - given balance. + KYC is disabled at this exchange, or the balance is below the + threshold that requires KYC, or this wallet already satisfied + the KYC check for the given balance. :http:statuscode:`403 Forbidden`: The provided signature is invalid. This response comes with a standard `ErrorDetail` response. @@ -703,11 +804,9 @@ New Endpoints // Description of what the AML program does. description: string; - // List of required field names in the context - // to run this AML program. - // SPA must check that the AML staff is providing - // adequate CONTEXT when defining a measure using - // this AML program. + // List of required field names in the context to run this + // AML program. SPA must check that the AML staff is providing + // adequate CONTEXT when defining a measure using this program. context: string[]; // List of required attribute names in the @@ -1237,8 +1336,10 @@ on GET ``/deposits/`` with the respective legitimization requirement row. CREATE TABLE wire_targets (wire_target_serial_id BIGSERIAL UNIQUE ,h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=64), + ,target_token BYTEA UNIQUE CHECK (LENGTH(target_token)=32) + ,target_pub BYTEA CHECK (LENGTH(target_pub)=32) ,payto_uri STRING NOT NULL - ,PRIMARY KEY (h_payto) + ,PRIMARY KEY (h_payto,target_pub) ) PARTITION BY HASH (h_payto); @@ -1248,25 +1349,31 @@ on GET ``/deposits/`` with the respective legitimization requirement row. IS 'Can be a regular bank account, or also be a URI identifying a reserve-account (for P2P payments)'; COMMENT ON COLUMN wire_targets.h_payto IS 'Unsalted hash of payto_uri'; + COMMENT ON COLUMN wire_targets.target_token + IS 'high-entropy random value that uniquely identifies the wire target and is used as a token to authorize access to the KYC process (without requiring a signature by target_priv); NULL if KYC is not allowed for the account (legacy)'; + COMMENT ON COLUMN wire_targets.target_pub + IS 'Public key (reserve_pub or merchant_pub) associated with the account; NULL if KYC is not allowed for the account (legacy)'; CREATE TABLE IF NOT EXISTS legitimization_measures (legitimization_measure_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY - ,h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=32) - REFERENCES wire_targets (h_payto) + ,target_token BYTEA NOT NULL UNIQUE CHECK (LENGTH(target_token)=32) + REFERENCES wire_targets (target_token) ,start_time INT8 NOT NULL ,jmeasures VARCHAR[] NOT NULL ,is_finished BOOL NOT NULL DEFAULT(FALSE) ) PARTITION BY HASH (h_payto); + COMMENT ON COLUMN legitimization_requirements.target_token + IS 'Used to uniquely identify the account and as a symmetric access control mechanism for the SPA'; COMMENT ON COLUMN legitimization_requirements.start_time IS 'Time when the measure was triggered (by decision or rule)'; - COMMENT ON COLUMN legitimization_requirements.is_finished - IS 'Set to TRUE if this set of measures was processed; used to avoid indexing measures that are done'; COMMENT ON COLUMN legitimization_requirements.jmeasures IS 'JSON object of type LegitimizationMeasures with KYC/AML measures for the account encoded'; + COMMENT ON COLUMN legitimization_requirements.is_finished + IS 'Set to TRUE if this set of measures was processed; used to avoid indexing measures that are done'; - CREATE INDEX ON legitimization_measures (h_payto) + CREATE INDEX ON legitimization_measures (target_token) WHERE NOT finished; CREATE TABLE legitimization_outcomes |