..
This file is part of GNU TALER.
Copyright (C) 2014-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
Foundation; either version 2.1, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, see
@author Christian Grothoff
@author Florian Dold
.. _KycOperatorManual:
Exchange KYC/AML Operator Manual
################################
.. contents:: Table of Contents
:depth: 1
:local:
Introduction
============
About GNU Taler
---------------
.. include:: frags/about-taler.rst
About this manual
-----------------
This chapter describes how to setup certain compliance aspects of a GNU Taler
exchange. Users that just want to set up an exchange as an experiment without
legal or regulatory requirements can safely skip this chapter.
This manual targets compliance experts working with system administrators and
developers to configure legitimization (KYC/KYB) and anti-money laundering
(AML) processes for a GNU Taler exchange. Expertise in all three domains is
required, as Taler's KYC and AML processes are highly configurable and
programmable.
Legal conditions for using the service
--------------------------------------
.. include:: frags/legal.rst
Know-Your-Customer Setup
========================
To legally operate, Taler exchange operators may have to comply with KYC
regulation that requires financial institutions to identify parties involved
in transactions at certain points.
Taler permits an exchange to require know-your-customer (KYC) or
know-your-business (KYB) data under the following circumstances:
* Customer withdraws money over a threshold
* Wallet receives (via refunds) money resulting in a balance over a threshold
* Wallet receives money via P2P payments over a threshold
* Merchant receives money over a threshold
* Merchant deposits digital cash over a threshold (**planned feature, bug 9040**)
* Reserve is "opened" for invoicing (**planned feature**)
Any of the above requests can trigger the KYC process,
which can be illustrated as follows:
.. image:: images/kyc-process.png
At the end of the KYC process, the wallet re-tries the
original request, and assuming KYC was successful, the
request should then succeed.
KYC Terminology
---------------
* **Attributes**: Attributes are used to represent KYC data obtained about
an account holder. Attributes include passport images, address data,
business registration documents, and indeed arbitrary forms filed by
AML staff or the customer themselves. Attribute data is considered
sensitive private information and is thus stored encrypted within the
exchange database.
* **Check**: A check establishes a particular attribute of a user, such as
their name based on an ID document and lifeness, mailing address, phone
number, taxpayer identity, etc. Checks may be given *context* (such as
whether a customer is an individual or a business) to run correctly. Checks
can also be AML staff inserting information for plausibilization. Checks
result in *attributes* about the account's owner which are given to an
external AML *program* together with the *context* to determine an *outcome*.
KYC checks are always specified with a fallback *measure* to be taken if
the check fails.
* **Condition**: A condition specifies when KYC is required. Conditions
include the *type of operation*, a threshold amount (e.g. above EUR:1000)
and possibly a time period (e.g. over the last month).
* **Configuration**: The configuration determines the *legitimization rules*,
and specifies which providers offer which *checks*.
* **Context**: Context is information provided as input into a *check* and
*program* to customize their execution. The context is initially set by the
*measure* (possibly including data from the *trigger*). Naturally, the
*program* may use its `AmlProgramInput` which includes *context* and
*attribute* data to compute an update *context* for the next set of
*measures* that it specifies in the `LegitimizationRuleSet` as part
of the `AmlOutcome`. Thus, *context* is something that typically
evolves as the *account* undergoes *measures*. Context is lost if
an account transitions to default *legitimization rules* due to
*expiration*.
* **Decision**: AML decisions are these as taken by AML officers (humans). AML
outcomes are the results of AML programs (code). Legitimization outcomes (DB
table) can arise from either.
* **Display priority**: Every rule has a *display priority*. If a second
*rule* is *triggered* before the *outcome* of a *rule* could be determined,
the *rule* with the larger *display priority* becomes the requirement that
the account owner has to satisfy (and that thus will be displayed by the
KYC SPA).
* **Expiration**: Except for the default rules, any set of KYC rules is
subject to *expiration*. This can be because *attributes* become outdated or
because sanctions have a time limit. The expiration time thus determines
when a new *measure* is triggered in the absence of a transaction crossing
thresholds in the current set of *legtimization rules*.
* **Legitimization rules**: The *legitimization rules* determine under which
*conditions* which *measures* will be taken. A `LegitimizationRuleSet`
always also includes an *expiration* time period for (custom, non-default)
*legitimization rules* after which a fallback measure* will automatically
apply. Legitimization rules may be *exposed* to the client (for example,
to allow a wallet to stay below hard withdraw thresholds) or could be secret.
* **Logic**: Logic refers to a specific bit of code (realized as an exchange
plugin) that enables the interaction with a specific *provider*. Logic
typically requires *configuration* for access control (such as an
authorization token) and possibly the endpoint of the specific *provider*
implementing the respective API.
* **Measure**: Describes the possible outgoing edges from one state in the
state machine (including how to show the current state). Each edge is given
some *context* and a *check* to be performed as well as an AML *program*
which determines the *outcome*. We generally distinguish between
"original" measures (defined globally in the exchange configuration) and
"custom" measures (defined specifically for an account by AML staff).
Additionally, only *origional* measures that have a *check* of type
"SKIP" and that require no inputs can be used as *FALLBACK* measures.
* **Outcome**: An `AmlOutcome` describes the account state that an account
ends up in due to either an AML staff action or an AML *program* doing some
computation over the attributes resulting from a *check*. Outcomes can be
that certain types of transactions are "verboten", that the account is (or
remains) under investigation by AML staff, that the account is given certain
properties, and/or that certain events are to be logged. Outcomes also
include a new set of *legitimization rules* to apply (and an *expiration*
time at which point a successor *measure* will be automatically taken).
* **Provider**: A provider performs a specific set of *checks* at a certain
*cost*. Interaction with a provider is performed by provider-specific
*logic*.
* **Program**: An AML helper *program* is given *context* about the current
state of an account and the attribute data from a *check* to compute the
*outcome*. For example, a *program* may look at the "PEP" field of a KYC
check and decide if the outcome is to put the account into ``normal`` or
``held-for-manual-review`` state. AML programs are always specified
with a fallback *measure* to be taken if the program fails.
* **Trigger**: A specific transaction that satisfies a **Condition**.
* **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),
aggregate transfer (to merchant), P2P receive (by wallet) and
(high) wallet balance.
Configuration of possible KYC/AML providers
-------------------------------------------
The KYC configuration determines the *legitimization rules*, and specifies
which providers offer which *checks*.
The configuration specifies a set of providers, one per configuration
section. The names of the configuration sections must being with
``kyc-proider-`` followed by an arbitrary ``$PROVIDER_ID``:
.. code-block:: ini
:caption: /etc/taler-exchange/conf.d/exchange-kyc-providers.conf
[kyc-provider-$PROVIDER_ID]
# Which plugin is responsible for this provider?
# Choices include "oauth2", "kycaid" and "persona".
LOGIC = oauth2
# Plus additional logic-specific options, e.g.:
AUTHORIZATION_TOKEN = superdupersecret
# Other logic-specific internal options (example):
FORM_ID = business_legi_form
OAuth 2.0 specifics
^^^^^^^^^^^^^^^^^^^
In terms of configuration, the OAuth 2.0 logic requires the respective client
credentials to be configured apriori to enable access to the legitimization
service. The OAuth 2.0 configuration options are:
.. code-block:: ini
:caption: /etc/taler-exchange/conf.d/exchange-oauth2.conf
[kyc-provider-example-oauth2]
LOGIC = oauth2
# (generic options omitted)
# How long is the KYC check valid?
KYC_OAUTH2_VALIDITY = forever
# URL to which we redirect the user for the login process
KYC_OAUTH2_AUTHORIZE_URL = "http://kyc.example.com/authorize"
# URL where we POST the user's authentication information
KYC_OAUTH2_TOKEN_URL = "http://kyc.example.com/token"
# URL of the user info access point.
KYC_OAUTH2_INFO_URL = "http://kyc.example.com/info"
# Where does the client get redirected upon completion?
KYC_OAUTH2_POST_URL = "http://example.com/thank-you"
# For authentication to the OAuth2.0 service
KYC_OAUTH2_CLIENT_ID = testcase
KYC_OAUTH2_CLIENT_SECRET = password
# Mustach template that converts OAuth2.0 data about the user
# into GNU Taler standardized attribute data.
KYC_OAUTH2_CONVERTER_HELPER = taler-exchange-kyc-oauth2-challenger.sh
The converter helper is expected to be customized to the selected OAuth2.0
service: different services may return different details about the user or
business, hence there cannot be a universal converter for all purposes. The
default shell script uses the ``jq`` tool to convert the JSON returned by the
service into the KYC attributes (also in JSON) expected by the exchange. The
script will need to be adjusted based on the attributes collected by the
specific backend.
The Challenger service for address validation supports OAuth2.0, but does not
have a static AUTHORIZE_URL. Instead, the AUTHORIZE_URL must be enabled by the client
using a special authenticated request to the Challenger's ``/setup`` endpoint.
The exchange supports this by appending ``#setup`` to the AUTHORIZE_URL (note
that fragments are illegal in OAuth2.0 URLs). Be careful to quote the URL,
as ``#`` is otherwise interpreted as the beginning of a comment by the
configuration file syntax.
.. code-block:: ini
:caption: /etc/taler-exchange/conf.d/exchange-challenger-oauth2.conf
[kyc-provider-challenger-oauth2]
LOGIC = oauth2
KYC_OAUTH2_AUTHORIZE_URL = "http://challenger.example.com/authorize/#setup"
KYC_OAUTH2_TOKEN_URL = "http://challenger.example.com/token"
KYC_OAUTH2_INFO_URL = "http://challenger.example.com/info"
When using OAuth 2.0, the *CLIENT REDIRECT URI* must be set to the
``/kyc-proof/$PROVIDER_SECTION`` endpoint. For example, given the
configuration above and an exchange running on the host
``exchange.example.com``, the redirect URI would be
``https://exchange.example.com/kyc-proof/kyc-provider-challenger-oauth2/``.
Using the OAuth 2.0 logic with the ``/setup`` endpoint it is possible to
pass Challenger the address to validate via the **context** of the KYC check.
To do so, the *address type* specific address data must be provided in a
field ``initial_address`` of the **context** object.
Persona specifics
^^^^^^^^^^^^^^^^^
We use the hosted flow. The Persona endpoints return a ``request-id``, which
we log for diagnosis.
Persona should be configured to use the ``/kyc-webhook/`` endpoint of the
exchange to notify the exchange about the completion of KYC processes. The
webhook is authenticated using a shared secret, which should be in the
configuration. To use the Persona webhook, you must set the webhook URL in
the Persona service to ``$EXCHANGE_BASE_URL/kyc-webhook/$SECTION_NAME/`` where
``$SECTION_NAME`` is the name of the configuration section. You should also
extract the authentication token for the webhook and put it into the
configuration as shown above.
.. code-block:: ini
:caption: /etc/taler-exchange/conf.d/exchange-persona.conf
[kyclogic-persona]
# Webhook authorization token. Global for all uses
# of the persona provider!
WEBHOOK_AUTH_TOKEN = wbhsec_698b5a19-c790-47f6-b396-deb572ec82f9
[kyc-provider-example-persona]
LOGIC = persona
# (generic options omitted)
# How long is the KYC check valid?
KYC_PERSONA_VALIDITY = 365d
# Which subdomain is used for our API?
KYC_PERSONA_SUBDOMAIN = taler
# Authentication token to use.
KYC_PERSONA_AUTH_TOKEN = persona_sandbox_42XXXX
# Form to use.
KYC_PERSONA_TEMPLATE_ID = itempl_Uj6Xxxxx
# Where do we redirect to after KYC finished successfully.
KYC_PERSONA_POST_URL = "https://taler.net/kyc-done"
# Salt to give to requests for idempotency.
# Optional.
# KYC_PERSONA_SALT = salt
# Helper to convert JSON with KYC data returned by Persona into GNU Taler
# internal format. Should probably always be set to some variant of
# "taler-exchange-kyc-persona-converter.sh".
KYC_PERSONA_CONVERTER_HELPER = "taler-exchange-kyc-persona-converter.sh"
The converter helper is expected to be customized to the
selected template: different templates may return different details
about the user or business, hence there cannot be a universal converter
for all purposes. The default shell script uses the ``jq`` tool to
convert the JSON returned by Persona into the KYC attributes (also
in JSON) expected by the exchange. The script will need to be adjusted
based on the attributes collected by the specific template.
KYC AID specifics
^^^^^^^^^^^^^^^^^
We use the hosted flow.
KYCAID must be configured to use the ``/kyc-webhook/$SECTION_NAME/`` endpoint
of the exchange to notify the exchange about the completion of KYC processes.
.. code-block:: ini
:caption: /etc/taler-exchange/conf.d/exchange-kycaid.conf
[kyc-provider-example-kycaid]
LOGIC = kycaid
# (generic options omitted)
# How long is the KYC check valid?
KYC_KYCAID_VALIDITY = 365d
# Authentication token to use.
KYC_KYCAID_AUTH_TOKEN = XXX
# Form to use.
KYC_KYCAID_FORM_ID = XXX
# URL to go to after the process is complete.
KYC_KYCAID_POST_URL = "https://taler.net/kyc-done"
# Script to convert the KYCAID data into the Taler format.
KYC_KYCAID_CONVERTER_HELPER = taler-exchange-kyc-kycaid-converter.sh
The converter helper is expected to be customized to the selected template:
different templates may return different details about the user or business,
hence there cannot be a universal converter for all purposes. The default
shell script uses the ``jq`` tool to convert the JSON returned by Persona into
the KYC attributes (also in JSON) expected by the exchange. The script will
need to be adjusted based on the attributes collected by the specific
template.
Configuration of possible KYC/AML checks
----------------------------------------
The configuration specifies a set of possible KYC checks offered by external
providers, one per configuration section. The names of the configuration
sections must being with ``kyc-check-`` followed by an arbitrary
``$CHECK_NAME``.
.. code-block:: ini
[kyc-check-$CHECK_NAME]
# Which type of check is this? Also determines
# the SPA form to show to the user for this check.
#
# INFO: wait for staff or contact staff out-of band
# (only information shown, no SPA action)
# FORM: SPA should show an inline (HTML) form
# LINK: SPA may start external KYC process or upload
#
TYPE = INFO|LINK|FORM
# Optional. Set to YES to allow this check be
# done voluntarily by a client (they may then
# still have to pay for it). Used to offer the
# SPA to display checks even if they are
# not required. Default is NO.
# Since **vATTEST**.
VOLUNTARY = YES/NO
# Provider id, present only if type is LINK.
# Refers to a ``kyc-provider-$PROVIDER_ID`` section.
PROVIDER_ID = id
# Name of the SPA form, if type is FORM
# "INFO" and "LINK" are reserved and must not be used.
# The exchange server and the SPA must agree on a list
# of supported forms and the resulting attributes.
#
# The SPA should include a JSON resource file
# "forms.json" mapping form names to arrays of
# attribute names each form provides.
FORM_NAME = name
# Descriptions to use in the SPA to display the check.
DESCRIPTION = "Upload your passport picture"
DESCRIPTION_I18N = "{"en":"Upload scan of your passport"}"
# ';'-separated list of fields that the CONTEXT must
# provide as inputs to this check. For example,
# for a FORM of type CHOICE, this might state
# ``choices: string[];``. The type after the ":"
# is for now purely for documentation and is
# not checked. However, it may be shown to AML staff
# when they configure measures.
REQUIRES = requirement;
# Description of the outputs provided by the check.
# Basically, the check's output is expected to
# provide the following fields as attribute inputs into
# a subsequent AML program.
# INFO never has any outputs.
OUTPUTS = "business_name street city country registration"
# **original** measure to take if the check fails
# (for any reason, e.g. provider or form fail to
# satisfy constraints or provider signals user error)
# Usually should point to a measure that requests
# AML staff to investigate. The fallback measure
# context always includes the reasons for the
# failure. Fallback measures MUST be *origional*
# measures and MUST use a check of
# type "SKIP" and MUST NOT require any inputs.
FALLBACK = MEASURE_NAME
The list of possible FORM names is fixed in the SPA
for a particular exchange release.
The "check_name" value "skip" is reserved and must not be defined. It can be
used in measures where the AML program must be run immediately without any
input.
The outcome of *any* check is stored encrypted in the ``kyc_attributes``
table. It MUST include an ``expiration_time``.
The INFO Type
^^^^^^^^^^^^^
When using KYC checks of type "INFO", the KYC-SPA
will simply show the given DESCRIPTION (or translations
from DESCRIPTION_I18N) to the user.
The FORM Type
^^^^^^^^^^^^^
When using KYC checks of type "FORM", the KYC-SPA
will show different forms based on "FORM_NAME" while
also showing the user the text from DESCRIPTION
as instructions.
Some of the forms may be further parameterized via
the context in which the form is executed.
.. note::
For build-in forms, it should in the future not be
necessary to specify the context requirements via
REQUIRES as the KYC SPA should inform the exchange
about the requirements of each form automatically.
However, this is not yet implemented, see #9187.
When forms are submitted, the exchange converts the form data into key-value
pairs where the key is the form field name and the value is of type
`KycStructuredFormData`. The respective AML program can then evaluate the data from the form
submission from `attributes`.
.. ts:def:: KycStructuredFormData
interface KycStructuredFormData {
// Content type. Missing if unknown.
content_type?: string;
// Name of the uploaded file. Missing if unknown
// or this was not a file upload.
filename?: string;
// Base32-encoded binary form value. Only present
// if form data was determined to be binary data.
data?: string;
// Text form value. Only present if the form data
// was determined to be in textual format.
text?: string;
}
The LINK Type
^^^^^^^^^^^^^
When using KYC checks of type "FORM", the KYC-SPA will show a link that allows
the user to begin the KYC process at an external provider under the given
DESCRIPTION.
The external providers are expected to yield KYC attributes in the form of
key-value pairs where the list of key is defined in the GANA
``gnu-taler-kyc-attributes`` registry, which also defines the format of each
attribute. External providers may not directly yield attributes using the
correct encodings, thus converter helper programs are typically used to convert
external attribute data into the standardized format.
Configuration of legitimization rules
-------------------------------------
The configuration also must specify a set of legitimization rules, one per
configuration section. Each rule specifies the condition and the measure the
condition triggers:
.. code-block:: ini
[kyc-rule-$RULE_NAME]
# Operation that triggers this rule.
# Must be one of WITHDRAW, DEPOSIT, MERGE,
# AGGREGATE or BALANCE.
OPERATION_TYPE = WITHDRAW
# Space-separated list of next measures to be performed.
# The SPA should display *all* of these measures to the user.
# (They have a choice of either which ones, or in
# which order they are to be performed.)
# A special measure name "verboten" is used if the
# specified threshold may never be crossed
# (under this set of rules).
NEXT_MEASURES = SWISSNESS KYB
# "YES" if all NEXT_MEASURES will eventually need
# to be satisfied, "NO" if the user has a choice between
# them. Not actually enforced by the exchange, but
# primarily used to inform the user whether this is
# an "and" or "or". YES for "and".
IS_AND_COMBINATOR = YES
# YES if the rule (specifically, operation type,
# threshold, timeframe) and the general nature of
# the next measure (verboten or approval required)
# should be exposed to the client.
# Defaults to NO if not set.
EXPOSED = YES
# Threshold amount above which the rule is
# triggered. The total must be exceeded in the given
# timeframe.
THRESHOLD = KUDOS:100
# Timeframe over which the amount to be compared to
# the THRESHOLD is calculated.
# Ignored for WALLET-BALANCE. Can be 'forever'.
TIMEFRAME = 30 days
# Set to YES to enable the rule (default is NO)
ENABLED = NO
Configuration of AML programs
-----------------------------
AML decision processes are automatically executed under certain configurable
conditions. Basically, any AML program that is run can flag an account for
investigation by setting the respective flag in the `AmlOutcome`. Once this
happens, the respective account will be highlighted to AML staff. The AML
program can also limit the operations of the account by setting arbitrary
thresholds for the various operations (including freezing the account by
setting the thresholds to zero). AML staff investigating the account can then
request further documentation or set new rules, including new thresholds.
AML programs can base their decisions on arbitrary attributes created by the
KYC check (such as the user being a politically exposed person).
AML programs will be given the KYC attributes in JSON format on standard
input, and must output the `AmlOutcome`. If AML programs fail (return non-zero
status codes), a FALLBACK measure is automatically triggered. FALLBACK
measures MUST be *original* measures and MUST have a check of type "SKIP"
and MUST NOT require any inputs.
AML programs are listed in the configuration file, one program per section:
.. code-block:: ini
[aml-program-$PROG_NAME]
# Program to run.
COMMAND = taler-helper-aml-pep
# Human-readable description of what this
# AML helper program will do. Used to show
# to the AML staff.
DESCRIPTION = "check if the customer is a PEP"
# True if this AML program is enabled (and thus can be
# used in measures and exposed to AML staff).
# Optional, default is NO.
ENABLED = YES
# **original** measure to take if COMMAND fails
# Usually points to a measure that asks AML staff
# to contact the systems administrator. The fallback measure
# context always includes the reasons for the
# failure. FALLBACK measures MUST be *original*
# measures with a check type of "SKIP" without any required
# inputs.
FALLBACK = MEASURE_NAME
Implementing your own AML programs
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When implementing an AML program, developers must
ensure to provide four main execution paths:
* Generate a list of *required* context field names
for the helper (introspection!) when given the "-r"
command-line switch. The output should use the same
syntax as the REQUIRES clause of ``[kyc-check-]``
configuration sections, except that new lines
MUST be used to separate fields instead of ";".
* Generate a list of *required* inputs fields
for the helper (introspection!) when given the "-i"
command-line switch. The input fields are named
after the respective fields of the `AmlProgramInput`,
thus "attributes", "aml_history", "context", "kyc_history",
"default_rules" and "current_rules" are valid strings
to return. Additionally, for the histories, it is
possible to specify "\*_history[$NUMBER]" where
"$NUMBER" is the number of entries to return, with
positive numbers counting from the start of the
history and negative numbers from the end of the
history. Thus, "aml_history[-1]" will cause only
the last AML history entry to be included.
* Generate a list of *required* attribute names
for the helper (introspection!) when given the "-a"
command-line switch. The output should use the same
list of names as the ATTRIBUTES in the
``[kyc-provider-]`` configuration section
(but may also include FORM field names).
* Process an input JSON object of type
`AmlProgramInput` into a JSON object of
type `AmlOutcome`.
This is the default behavior if no command-line switches
are provided.
AML programs will be given the exchange's configuration filename with the "-c
FILENAME" command-line option. They may or may not use the configuration file
as they see fit. AML programs should support the canonical "-h" (help) and
"-v" (version) command-line options to display a help text and their version
number respectively. AML programs may be given the "-V" option to put them
into verbose mode, suggesting that they should log additional information to
standard error.
If the AML program fails (exits with a failure code or does not provide
well-formed JSON output) the AML/KYC process continues with the FALLBACK
measure. This should usually be one that asks AML staff to contact the systems
administrator.
.. ts:def:: AmlProgramInput
interface AmlProgramInput {
// JSON object that was provided as
// part of the *measure*. This JSON object is
// provided under "context" in the main JSON object
// input to the AML program. This "context" should
// satify both the REQUIRES clause of the respective
// check and the output of "-r" from the
// AML program's command-line option.
context?: Object;
// JSON object that captures the
// output of a ``[kyc-provider-]`` or (HTML) FORM.
// In the case of KYC data provided by providers,
// the keys in the JSON object will be the attribute
// names and the values must be strings representing
// the data. In the case of file uploads, the data
// MUST be base64-encoded.
// In the case of KYC data provided by HTML FORMs, the
// keys will match the HTML FORM field names and
// the values will use the `KycStructuredFormData`
// encoding.
// Attributes are only provided if the AML program
// specifies "attributes" for its input requirements.
attributes?: Object;
// JSON array with the results of historic
// AML desisions about the account.
//
// AML history is only provided if the AML program
// specifies "aml_history" for its input requirements.
aml_history?: AmlHistoryEntry[];
// JSON array with the results of historic
// KYC data about the account.
//
// KYC history is only provided if the AML program
// specifies "kyc_history" for its input requirements.
kyc_history?: KycHistoryEntry[];
// Default KYC rules of the exchange (exposed and not exposed).
//
// Default KYC rules are only provided if the AML program
// specifies "default_rules" for its input requirements.
default_rules?: LegitimizationRuleSet;
// Current KYC rules the exchange applies for this user.
// (exposed and not exposed).
//
// Current KYC rules are only provided if the AML program
// specifies "current_rules" for its input requirements.
current_rules?: LegitimizationRuleSet;
}
.. ts:def:: AmlHistoryEntry
interface AmlHistoryEntry {
// When was the AML decision taken.
decision_time : Timestamp;
// What was the justification given for the decision.
justification : string;
// Public key of the AML officer taking the decision.
decider_pub : AmlOfficerPublicKeyP;
// Properties associated with the account by the decision.
properties : Object;
// New set of legitimization rules that was put in place.
new_rules : LegitimizationRuleSet;
// True if the account was flagged for (further)
// investigation.
to_investigate : boolean;
// True if this is the currently active decision.
is_active : boolean;
}
.. ts:def:: KycHistoryEntry
interface KycHistoryEntry {
// Name of the provider
// which was used to collect the attributes. NULL if they were
// just uploaded via a form by the account owner.
provider_name?: string;
// True if the KYC process completed.
finished: boolean;
// Numeric `error code `, if the
// KYC process did not succeed; 0 on success.
code: number;
// Human-readable description of ``code``. Optional.
hint?: string;
// Optional detail given when the KYC process failed.
error_message?: string;
// Identifier of the user at the KYC provider. Optional.
provider_user_id?: string;
// Identifier of the KYC process at the KYC provider. Optional.
provider_legitimization_id? :string;
// The collected KYC data.
// NULL if the attribute data could not
// be decrypted or was not yet collected.
attributes?: Object;
// Time when the KYC data was collected
collection_time: Timestamp;
// Time when the KYC data will expire.
expiration_time: Timestamp;
}
.. ts:def:: AmlOutcome
interface AmlOutcome {
// Should the client's account be investigated
// by AML staff?
// Defaults to false.
to_investigate?: boolean;
// Free-form properties about the account.
// Can be used to store properties such as PEP,
// risk category, type of business, hits on
// sanctions lists, etc.
properties?: AccountProperties;
// Types of events to add to the KYC events table.
// (for statistics).
events?: string[];
// Space-separated list of measures to trigger
// immediately on the account.
// Prefixed with a "+" to indicate that the
// measures should be ANDed.
// Should typically be used to give the user some
// information or request additional information.
//
// MUST NOT contain an instant measure.
// AML programs may be chained directly via
// exec / fork+exec, but the exchange does not
// support chaining.
new_measures?: string;
// KYC rules to apply. Note that this
// overrides *all* of the default rules
// until the ``expiration_time`` and specifies
// the successor measure to apply after the
// expiration time.
new_rules: LegitimizationRuleSet;
}
If the AML program fails (exits with a failure code or
does not provide well-formed JSON output) the AML/KYC
process continues with the FALLBACK measure. This should
usually be one that asks AML staff to contact the
systems administrator.
Configuration of measures
-------------------------
Finally, the configuration specifies a set of
**original** *measures* one per configuration section:
.. code-block:: ini
[kyc-measure-$MEASURE_NAME]
# Possible check for this measure. Optional.
# If not given (or set to "SKIP"), PROGRAM should
# be run immediately (on an empty set of attributes).
CHECK_NAME = IB_FORM
# Context for the check. The context can be
# just an empty JSON object if there is none.
CONTEXT = {"choices":["individual","business"]}
# Program name to run on the context and check data to
# determine the outcome and next measure.
# Refers to a ``[aml-program-$PROG_NAME]`` section name.
PROGRAM = taler-aml-program
If ``CHECK_NAME`` is set to "SKIP" (or is not provided at all), the AML
``PROGRAM`` is to be run immediately. This is useful if no client-interaction
is required to arrive at a decision.
.. note::
The list of *measures* in the configuration is not complete: AML staff may
freely define new measures dynamically, usually by selecting checks, an AML
program, and providing context. The measures specified in the configuration
are called the *original measures* and only those can be used as FALLBACK
measures.
AML Configuration
=================
The AML configuration steps are used to add or remove keys of exchange
operator staff that are responsible for anti-money laundering (AML)
compliance. These AML officers are shown suspicious transactions and are
granted access to the KYC data of an exchange. They can then investigate the
transaction and decide on new rules for the respective user. They may request
additional KYC data from the consumer, can change the thresholds up to which
amounts transactions are allowed, and associate properties with the account
that AML programs (and AML officers) may interpret for arbitrary future
actions.
AML Officer Setup
-----------------
To begin the AML setup, AML staff should launch the GNU Taler exchange AML SPA
Web interface by going to the ``/aml-spa/`` endpoint of the exchange. This is
generally a public endpoint, but of course an operator may restrict access via
the reverse proxy. The SPA will generate a public-private key pair and store
it in the local storage of the browser. The public key will be displayed and
**must** be securely transmitted to the offline system for approval. Using the
offline system, one can then configure which staff has access to the AML
operations:
.. code-block:: shell-session
[root@exchange-offline]# taler-exchange-offline \
aml-enable "$PUBLIC_KEY" "Legal Name" rw > aml.json
[root@exchange-online]# taler-exchange-offline \
upload < aml.json
The above commands would add an AML officer with the given "Legal Name" with
read-write (rw) access to the AML officer database. Using "ro" instead of
"rw" would grant read-only access to the data, leaving out the ability to
actually make AML decisions. Once AML access has been granted, the AML
officer can use the SPA to review cases and (with "rw" access) take AML
decisions.
Access rights can be revoked at any time using:
.. code-block:: shell-session
[root@exchange-offline]# taler-exchange-offline \
aml-disable $PUBLIC_KEY "Legal Name" > aml-off.json
[root@exchange-online]# taler-exchange-offline \
upload < aml-off.json
AML Forms
---------
AML forms are defined by the :ref:`dynamic forms design document `.
The shipped implementation with of the exchange is installed in
.. code-block:: shell-session
${INSTALL_PREFIX}/share/taler-exchange/spa/forms.js
The variable ``form`` contains the list of all form available. For
every entry in the list the next properties are expected to be present:
``label``: used in the UI as the name of the form
``id``: identification name, this will be saved in the exchange database
along with the values to correctly render the form again.
It should simple, short and without any character outside numbers,
letters and underscore.
``version``: when editing a form, instead of just replacing fields
it will be better to create a new form with the same id and new version.
That way old forms in the database will used old definition of the form.
It should be a number.
``impl`` : a function that returns the design and behavior of form.
See DD 54 dynamic forms.
.. attention::
do not remove a form the list if it has been used. Otherwise you
won't be able to see the information save in the exchange database.
To add a new one you can simply copy and paste one element, and edit it.
It is much easier to download ``@gnu-taler/aml-backoffice-ui`` source
from ``https://git.taler.net/wallet-core.git/``, compile and copy the file
from the ``dist/prod``.
.. _ExchangeTemplateCustomization:
KYC Process Template Customization
==================================
The Exchange comes with various HTML templates that are shown to guide users
through the KYC process. The Exchange uses `C implementation of mustache
`__ as the templating engine. This section
describes the various templates. In general, the templates must be installed
to the ``share/taler-exchange/templates/`` directory. The file names must be
of the form ``$NAME.$LANG.must`` where ``$NAME`` is the name of the template
and ``$LANG`` is the 2-letter language code of the template. English templates
must exist and will be used as a fallback. If the browser (user-agent) has
provided language preferences in the HTTP header and the respective language
exists, the correct language will be automatically served.
The following subsections give details about each of the templates. Most
subsection titles are the ``$NAME`` of the respective template.
Generic Errors Templates
------------------------
A number of templates are used for generic errors. These are:
* kyc-proof-already-done (KYC process already completed)
* kyc-bad-request (400 Bad Request)
* kyc-proof-endpoint-unknown (404 Not Found for KYC logic)
* kyc-proof-internal-error (500 Internal Server Error)
* kyc-proof-target-unknown (404 Not Found for KYC operation)
All of these templates are instantiated using the following information:
* ec: Integer; numeric Taler error code, should be shown to indicate the
error compactly for reporting to developers
* hint: String; human-readable Taler error code, should be shown for the
user to understand the error
* message: String; optional, extended human-readable text provided to elaborate
on the error, should be shown to provide additional context
kycaid-invalid-request
----------------------
The KYCaid plugin does not support requests to the
``/kyc-proof/`` endpoint (HTTP 400 bad request).
This template is instantiated using the following information:
* ec: Integer; numeric Taler error code, should be shown to indicate the
error compactly for reporting to developers
* hint: String; human-readable Taler error code, should be shown for the
user to understand the error
* error: String; error code from the server
* error_details: String; optional error description from the server
* error_uri: optional URI with further details about the error from the server
oauth2-authentication-failure
-----------------------------
The OAuth2 server said that the request was not
properly authenticated (HTTP 403 Forbidden).
This template is instantiated using the following information:
* ec: Integer; numeric Taler error code, should be shown to indicate the
error compactly for reporting to developers
* hint: String; human-readable Taler error code, should be shown for the
user to understand the error
oauth2-authorization-failure
----------------------------
The OAuth2 server refused to return the KYC data because the authorization
code provided was invalid (HTTP 403 Forbidden).
This template is instantiated using the following information:
* ec: Integer; numeric Taler error code, should be shown to indicate the
error compactly for reporting to developers
* hint: String; human-readable Taler error code, should be shown for the
user to understand the error
* error: String; error code from the server
* error_message: String; error message from the server
oauth2-authorization-failure-malformed
--------------------------------------
The server refused the authorization, but then provided
a malformed response (HTTP 502 Bad Gateway).
This template is instantiated using the following information:
* ec: Integer; numeric Taler error code, should be shown to indicate the
error compactly for reporting to developers
* hint: string; human-readable Taler error code, should be shown for the
user to understand the error
* debug: boolean; true if we are running in debug mode and are allowed to return HTML with potentially sensitive information
* server_response: Object; could be NULL; this includes the (malformed) OAuth2 server response, it should be shown to the use if "debug" is true
oauth2-bad-request
------------------
The client made an invalid request (HTTP 400 Bad Request).
This template is instantiated using the following information:
* ec: Integer; numeric Taler error code, should be shown to indicate the
error compactly for reporting to developers
* hint: String; human-readable Taler error code, should be shown for the
user to understand the error
* message: String; additional error message elaborating on what was bad about the request
oauth2-conversion-failure
-------------------------
Converting the KYC data into the exchange's internal
format failed (HTTP 502 Bad Gateway).
This template is instantiated using the following information:
* ec: Integer; numeric Taler error code, should be shown to indicate the
error compactly for reporting to developers
* hint: string; human-readable Taler error code, should be shown for the
user to understand the error
* debug: boolean; true if we are running in debug mode and are allowed to return HTML with potentially sensitive information
* converter: String; name of the conversion command that failed which was used by the Exchange
* attributes: Object; attributes returned by the conversion command, often NULL (after all, conversion failed)
* message: error message elaborating on the conversion failure
oauth2-provider-failure
-----------------------
We did not get an acceptable response from the OAuth2
provider (HTTP 502 Bad Gateway).
This template is instantiated using the following information:
* ec: Integer; numeric Taler error code, should be shown to indicate the
error compactly for reporting to developers
* hint: String; human-readable Taler error code, should be shown for the
user to understand the error
* message: String; could be NULL; text elaborating on the details of the failure
persona-exchange-unauthorized
-----------------------------
The Persona server refused our request (HTTP 403 Forbidden from Persona, returned as a HTTP 502 Bad Gateway).
This template is instantiated using the following information:
* ec: Integer; numeric Taler error code, should be shown to indicate the
error compactly for reporting to developers
* hint: String; human-readable Taler error code, should be shown for the
user to understand the error
* data: Object; data returned from Persona service, optional
* persona_http_status: Integer; HTTP status code returned by Persona
persona-load-failure
--------------------
The Persona server refused our request (HTTP 429 Too Many Requests from Persona, returned as a HTTP 503 Service Unavailable).
This template is instantiated using the following information:
* ec: Integer; numeric Taler error code, should be shown to indicate the
error compactly for reporting to developers
* hint: String; human-readable Taler error code, should be shown for the
user to understand the error
* data: Object; data returned from Persona service, optional
* persona_http_status: Integer; HTTP status code returned by Persona
persona-exchange-unpaid
-----------------------
The Persona server refused our request (HTTP 402 Payment REquired from Persona, returned as a HTTP 503 Service Unavailable).
This template is instantiated using the following information:
* ec: Integer; numeric Taler error code, should be shown to indicate the
error compactly for reporting to developers
* hint: String; human-readable Taler error code, should be shown for the
user to understand the error
* data: Object; data returned from Persona service, optional
* persona_http_status: Integer; HTTP status code returned by Persona
persona-logic-failure
---------------------
The Persona server refused our request (HTTP 400, 403, 409, 422 from Persona, returned as a HTTP 502 Bad Gateway).
This template is instantiated using the following information:
* ec: Integer; numeric Taler error code, should be shown to indicate the
error compactly for reporting to developers
* hint: String; human-readable Taler error code, should be shown for the
user to understand the error
* data: Object; data returned from Persona service, optional
* persona_http_status: Integer; HTTP status code returned by Persona
persona-invalid-response
------------------------
The Persona server refused our request in an
unexpected way; returned as a HTTP 502 Bad Gateway.
This template is instantiated using the following information:
* ec: Integer; numeric Taler error code, should be shown to indicate the
error compactly for reporting to developers
* hint: string; human-readable Taler error code, should be shown for the
user to understand the error
* debug: boolean; true if we are running in debug mode and are allowed to return HTML with potentially sensitive information
* server_response: Object; could be NULL; this includes the (malformed) OAuth2 server response, it should be shown to the use if "debug" is true
persona-network-timeout
-----------------------
The Persona server refused our request (HTTP 408 from Persona, returned as a HTTP 504 Gateway Timeout).
This template is instantiated using the following information:
* ec: Integer; numeric Taler error code, should be shown to indicate the
error compactly for reporting to developers
* hint: String; human-readable Taler error code, should be shown for the
user to understand the error
* data: Object; data returned from Persona service, optional
* persona_http_status: Integer; HTTP status code returned by Persona
persona-kyc-failed
------------------
The Persona server indicated a problem with the KYC process, saying it was not completed.
This template is instantiated using the following information:
* persona_inquiry_id: String; internal ID of the inquiry within Persona, useful for further diagnostics by staff
* data: Object; could be NULL; this includes the server response, it contains extensive diagnostics, see Persona documentation on their ``/api/v1/inquiries/$ID``.
* persona_http_status: Integer; HTTP status code returned by Persona
persona-provider-failure
------------------------
The Persona server refused our request (HTTP 500 from Persona, returned as a HTTP 502 Bad Gateway).
This template is instantiated using the following information:
* ec: Integer; numeric Taler error code, should be shown to indicate the
error compactly for reporting to developers
* hint: String; human-readable Taler error code, should be shown for the
user to understand the error
* data: Object; data returned from Persona service, optional
* persona_http_status: Integer; HTTP status code returned by Persona