Security Guidelines for Banks
Version: 1.1 updated 02.12.2022
The goal of this document is to describe BankID project security. It provides both conceptual and implementation guidance on how to build the API in a secure way.
How to read the guidelines
The CAPITALIZED words throughout these guidelines have a special meaning:
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC2119.
Refer to RFC2119 for details.
- PII - Personal Identifiable Information - Also known under the broader term Personal Data in the EU
- Provider - A Bank or another entity that provides KYC APIs to the consumers
- RP - Relying Party - A KYC API consumer
- TP - Third Party - A party which the RP can share PII and other End-User data (with their consent) with
KYC and KYC Connect are, from the security standpoint, very sensitive services. The following requirements are posed on the implementation:
- Confidentiality - The Bank exposes the Personally Identifiable Information (PII) via the APIs.
- Non-repudiation - Critical business decisions are made. There are legal implications if the data provided turns out to be incorrect. API consumers must be sure the data came from the bank and is genuine.
- Integrity - The PII exposed is valid as a set of properties and the claim made about the user must remain unchanged by any third parties when transmitted between the API Consumer and the Bank.
- Availability - The system must be highly secure, but also highly available as critical infrastructure would rely on the KYC and KYC Connect implementations.
The following principles were employed while designing the security of this system:
- Use standards where possible - The proposed security solution relies heavily on PKI and OpenID Connect security that is standardized.
- Err on the side of security - If there are multiple standardized ways of implementing things, the more secure one is chosen.
Ciphers & Hash algorithms
- MD5 and SHA1 MUST NOT be used due to known collision attacks
- Weak ciphers such as DES and RC4 MUST NOT be used
- ECDSA key size MUST be at least 256 bits (this solution is preferred)
- RSA key size MUST be at least 3072 bits
For the purposes of this guideline, STRONG JWT CIPHER is defined as one of the following signing algorithms:
- ES512 (recommended)
A256GCM is the alternative for encryption.
The protocol layer
All communication MUST use a secure version of TLS. As of June 2020, TLS 1.2 is the most wide-spread version with no known serious vulnerabilities. The server MUST NOT allow protocol renegotiation to an older/weaker TLS version. Communication over the HTTP protocol MUST use HTTPS.
Any non-TLS requests SHOULD be dropped.
The server of the Provider SHOULD be protected from CRIME attack; TLS compression MUST be disabled. (See CVE-2012-4929.)
The Provider MUST use QWAC certificates for server authentication.
The Provider MUST use QSEAL certificates for JSON Web Signatures in payloads.
Server certificates MUST be rotated regularly, at least once a year.
- X.509 certificate key length MUST be strong
- ECDSA at least 256 bits (this solution is preferred)
- RSA at least 2048 bits
- X.509 certificates MUST only be signed with secure hashing algorithms
- A certificate with FQDN MUST be used for server authentication
- An SSL Wildcard certificate MUST NOT be used for server authentication
When verifying certificates, the following checks MUST be passed (refer to RFC5280):
- Check if the Certificate Authority (CA) is a known one (meaning one considered trusted)
- Check that the certificate is currently valid
- Check that the name of the site and the FQDN reported in the certificate match.
- If certificate pinning is used, the certificate or intermediate certificate MUST match
mTLS (two-way authentication)
mTLS MUST be employed for increased transport layer security.
RP Client certificates MUST be rotated regularly, at least once a year.
The RP SHOULD use QWAC certificates for client authentication. Trusted commercial certificate can be used.
The process of client certificate handover to the Provider is beyond the scope of these guidelines.
The RP MAY use certificate pinning for added security when establishing TLS connection. The RP should consider whether to pin the leaf certificate or an intermediate certificate. The latter provides a smaller guarantee of authenticity of the Provider in exchange for a smaller operational burden. The decision is left to the implementing party and SHALL be made on a case-by-case basis. We suggest to not use certificate pinning.
Key management in BankID JWKs
BankID exposes JWKs URI with compliance with OpenID Discovery Framework. However we need to be able to change signing, encryption and mTLS keys without any operational impact on services. Let's dive into basic key structure of BankID JWKs, purpose of individual key categories and how should other parties use them.
BankID uses multiple key categories for different purposes.
- Signing keys
- Encryption keys
- mTLS keys
Signing keys are key used for signature of JWT
- They have key usage set to "sig" value and are distinguished from mTLS keys by using prefix "*rp-sign" or "rp-sign.
- Difference between "*rp-sign" and "rp-sign" is that "rp-sign" keys are internal keys in contrast to "*rp-sign" which are used for external users.
- Signing keys in BankID are always accompanied by QSEAL certificate i.e. signature of JWT is always done by QSEAL.
- QSEAL certificate might be issued by any certification authority in EU Trusted list!
- Signing keys might be both RSA and EC keys
Encryption keys are used for encrypting JWT in communication between SeP and BankID (i.e. /ros endpoint) and between BankID and IdP (IdP responses to BankID).
- They have key usage set to "enc" value and are distinguished from other "enc" keys by prefix "rp-encrypt".
- Those keys have also corresponding certificate however this certificate is issued by our internal authority.
mTLS key are special keys and certificate exposed for IdP.
- This key is always only one, corresponding certificate is issued by our internal authority and this element is used for mTLS validation by IdPs.
Since BankID may have exposed multiple signing keys that are used for signature of JWT we always specify in JWT header kid of key that was used for signature. Therefore other party may use specific key for validation of signature.
Since BankID may have exposed multiple encryption keys that may be used for encryption of JWT other party must specify which key was used for encryption of JWT. Special behaviour is reserved for IdPs for limited time that if no key is specified BankID will try all possible keys. For SeP no such behaviour will be implemented.
Key rotation procedure
BankID will use following key rotation procedure:
- BankID will be deploy new keys without removing old ones
- BankID will publish those keys in JWKs
- After 10 minutes BankID will remove old keys and publish those changes to JWKs.
- For IdPs in next 10 minutes old encryption keys will be valid and may be used to encrypt token
- No such behaviour is available to SeP
- After another 10 minutes BankID will remove opportunity for IdP to use old keys and rotation is finished
Authentication (OpenID Connect)
The client certificate used for authentication MUST be a VALID certificate verifiable against a trusted root certificate store.
Most security features of OAuth2 and OpenID Connect are used.
Encryption MUST be done after the signature as stated in the OpenID specification to ensure integrity and non-repudiation.
- ID Token in token endpoint response (signed and encrypted)
- UserInfo endpoint response (signed and encrypted)
The Provider MUST support encryption and signature of request objects and responses and it needs to indicate it in the OpenID discovery endpoint. Provider signatures MUST be done using the X.509 certificate. The Provider MUST encrypt responses to the RP using the RP's key.
The OpenID connect server MUST have the following settings:
- jwks_uri - Must be set and point to X.509 based JSON Web Key Set.
- id_token_signing_alg_values_supported - MUST be set to one or more STRONG JWT CIPHER algorithms
- id_token_encryption_alg_values_supported - MUST be set to STRONG JWT CIPHER
- id_token_encryption_enc_values_supported - MUST be set to STRONG JWT CIPHER
- userinfo_signing_alg_values_supported - MUST be set to STRONG JWT CIPHER
- userinfo_encryption_alg_values_supported - MUST be set to STRONG JWT CIPHER
- userinfo_encryption_enc_values_supported - MUST be set to STRONG JWT CIPHER
- request_object_signing_alg_values_supported- MUST be set to STRONG JWT CIPHER
- request_object_encryption_alg_values_supported - MUST be set to STRONG JWT CIPHER
- request_object_encryption_enc_values_supported - MUST be set to STRONG JWT CIPHER
- token_endpoint_auth_methods_supported - MUST include private_key_jwt
- token_endpoint_auth_signing_alg_values_supported - MUST include private_key_jwt
The dynamic registration endpoint MUST be protected using mTLS.
The dynamic registration endpoint MUST be protected by using a JWT signed with a certificate of the RP. The Consumer provides this secret in the Authorization HTTP Header using Bearer authentication scheme.
During client registration, the RP MUST use the following parameters:
- token_endpoint_auth_method - MUST be set to private_key_jwt
- grant_types - MUST be set to authorization_code refresh_token
- jwks_uri - MUST be set and point to X.509 based JSON Web Key Set.
- id_token_signed_response_alg - MUST be set to STRONG JWT CIPHER
- id_token_encrypted_response_alg - MUST be set to STRONG JWT CIPHER
- id_token_encrypted_response_enc - MUST be set to STRONG JWT CIPHER
- userinfo_signed_response_alg - MUST be set to STRONG JWT CIPHER
- userinfo_encrypted_response_alg - MUST be set to STRONG JWT CIPHER
- userinfo_encrypted_response_enc - MUST be set to STRONG JWT CIPHER
- request_object_signing_alg - MUST be set to STRONG JWT CIPHER
- request_object_encryption_alg - MUST be set to STRONG JWT CIPHER
- request_object_encryption_enc - MUST be set to STRONG JWT CIPHER
- request_uris - MUST be set to HTTPS based URL fully controlled by the RP.
The registration_access_token returned MUST be treated as a security asset and be stored securely.
The RP MUST pass request parameters as JWTs signed (by its key) and encrypted (by the key of the Provider) as per the OpenID specification.
Possible attack vectors and their mitigation
Please be sure to read through some common OAuth2 threats and their mitigation in RFC6819.
Dynamic registration by an unauthorized third party
- The adversary would need access to the mTLS keys to be able to call the Dynamic Registration API
Dynamic registration response eavesdropping
- If the adversary had access to the mTLS signing keys and was able to eavesdrop the Dynamic Registration response, they would gain access to the client_id; however they would still not be able to craft signed and encrypted JWTs used in the following steps since asymmetric keys are used.
Eavesdropped communication between the RP and the Bank
- RP <==> Bank communication is protected against eavesdropping using Mutual TLS
Eavesdropped communication between mTLS terminator and OIDC handler
- Even if an adversary manages to gain access to inner network (after mTLS is terminated), they will not be able to read specific message payloads thanks to JWT encryption and signing
Compromised encryption keys
- There are mechanisms in place to mitigate this issue, namely PKI based certificate revocation and an easy way to propagate rotated JWK keys through the use of the JWKS endpoint (from Discovery or Dynamic Registration)
redirect_uri change during the /auth call
- A redirect_uris whitelist is a mandatory requirement during dynamic registration
CSRF attacks on the /auth endpoint
- These are out of scope, since protection will be handled on the BankID side
Leak of Confidential Data in HTTP Proxies
- Since mTLS 1.2 is used, all proxies along the way would need to have access to the mTLS keys, which means that only trusted entities can access Credential Data through HTTP proxies
Replay of Authorized Resource Server Requests
- To prevent replay attacks, we use the nonce query parameter in the authorization call and in JWTs
Authorization code in the browser history
- Since code is passed as a query parameter during a redirect from the Bank consent screen, it MAY be visible in the browser history. However, this does not pose a security threat because code is short lived and is exchanged (and blacklisted for reuse) in the Bank immediately upon its reception by BankID
- To prevent the CRIME attack, mTLS compression SHOULD be avoided
The API layer
Securing the API layer between the Consumer and the Provider is done mainly by using the OpenID Connect keys to sign and encrypt API communication.
- OpenID Provider key from jwks_uri is used to sign KYC and other future api responses (MUST)
- OpenID Consumer key is used to encrypt KYC and other future api responses (MUST)
All responses from KYC and other future APIs MUST be signed by key from jwks_uri on the OpenID Provider side. jwks_uri MUST contain X.509 v3 extensions to validate the certificate and all its chain. jwks_uri MUST contain all certificates from certificate chain, from leaf to root, in one .pem file.
- All responses from KYC and other future APIs MUST be encrypted by a key from jwks_uri on the OpenID Consumer side.
- JWT from KYC and other future APIs MUST be first signed by the OpenID Producer key and then encrypted by the OpenID Consumer key.
- Secure parsing:
It is recommended to use parsing packages for validating inputs. These packages should NOT be VULNERABLE to XXE or similar attacks.
- Strong typing:
It is recommended to use strongly typed inputs.
- Validating incoming content-types:
Server SHOULD NEVER assume the Content-Type. The Content-Type Header and content SHOULD ALWAYS be checked to make sure they are of the same type. An unexpected Content-Type or a missing Header SHOULD result in a 406 Not Acceptable response.
- Validating response types:
Server SHOULD NEVER copy the Accept Header to the response Content-Type. Server SHOULD ALWAYS check the Accept Header for allowed types.
- Server and browser MUST ALWAYS sanitize all input data from HTML tags and attributes. NEVER TRY TO DO IT BY YOURSELF. This SHOULD be done by a known library or the auto-escaping features of a template library.
Protection against Cross-Site Request Forgery:
- Every RESTful resource MUST be protected against Cross Site Request Forgery. See more information at How to prevent XSS.
HTTP Status codes:
While designing a REST API, DO NOT just use 200 for success or 404 for error. Every error message needs to be customized so as NOT to reveal any unnecessary information. Here are some guidelines to consider for each REST API status return code. Proper error handling may help to validate the incoming requests and better identify any potential security risks.
- 200 OK - Response to a successful REST API action.
- 400 Bad Request - The request is malformed, e.g. there is a message body format error.
- 401 Unauthorized - Wrong or no authentication ID/password provided.
- 403 Forbidden - The authentication succeeded but the authenticated user does not have permission for the requested resource
- 404 Not Found - A non-existent resource is requested
- 405 Method Not Allowed - An unexpected HTTP method was used; for example, the Rest API is expecting HTTP GET, but HTTP PUT is used.
- 429 Too Many Requests - A DoS attack may be detected or the request is rejected due to rate limiting.
- Server versioning information or any other sensitive information from the HTTP headers SHOULD BE removed/masked according to industry best practices. This prevents any form of targeted attacks since the vulnerabilities are mostly specific to the vendors.
This header contains information about the backend server (type and version). For instance, the screenshot below shows that the webserver that runs Nike’s web page is Jetty, version 9.4.8.v20171121.
It contains the details of the web framework or programming language used in the web application.
As the name suggests, it shows the version details of the ASP .NET framework. This information may help an adversary to fine-tune their attack based on the framework and its version.
- If the communication between the OpenID Provider and the Consumer uses mTLS, the following headers MUST be set:
- Strict-Transport-Security: max-age=<MAX_AGE>; IncludeSubDomains
Without HSTS enabled, an adversary can perform a man-in-the-middle attack and steal sensitive information from the web session of a user. Imagine a scenario where a victim connects to an open Wi-Fi which is actually controlled by an attacker. Accessing a website over HTTP would allow the attacker to intercept the request and read the sensitive information. (The site is on HTTPS but the user accesses it with HTTP which later gets redirected to HTTPS). If the same user had accessed the website earlier, the HSTS details recorded in the browser would have caused the connection to be made over HTTPS automatically.
Content Security Policy is used to instruct the browser to load only allowed content defined in the policy. This uses the whitelisting approach which tells the browser where to load the images, scripts, CSS, applets, etc. from. If implemented properly, this policy prevents exploitation of Cross-Site Scripting (XSS), ClickJacking and HTML injection attacks.
Access-Control-Allow-Origin is a CORS (Cross-Origin Resource Sharing) header. This header allows the defined third party to access a given resource. This header is a workaround for restrictions posed by the Same Origin Policy which does not allow two different origins to read each other's data.
- X-XSS-Protection: 1; mode=block
This header is designed to protect against Cross-Site Scripting attacks. It works with the XSS filters used by modern browsers
- X-Content-Type-Options: nosniff
This response header is used to protect against MIME sniffing vulnerabilities. MIME sniffing is a feature of the web browser that serves for examining the content of the file being served.
- Strict-Transport-Security: max-age=<MAX_AGE>; IncludeSubDomains
Compression MUST be DISABLED in mTLS communication to prevent the CRIME attack.
The CRIME attack is used to extract session tokens protected by the SSL/TLS protocol. CRIME exploits the data compression feature of SSL and TLS. As the compression happens at the SSL/TLS level, both the header and the body are subjected to compression. SSL/TLS and SPDY compression use an algorithm called DEFLATE which compresses HTTP requests by eliminating duplicate strings. CRIME takes advantage of the way duplicate strings are eliminated to guess session tokens by systematically brute forcing them.
HTTP compression MUST be DISABLED to prevent the BREACH attack.
The BREACH attack is quite similar to the CRIME attack but there are subtle differences. This attack also leverages compression to extract data from an SSL/TLS channel. However, its focus is not on SSL/TLS compression; instead, it exploits HTTP compression. Here, the attack tries to exploit the compressed and encrypted HTTP responses instead of requests as it was the case with the CRIME attack.
Audit on the side of BankID
In certain legal cases, BankID MUST be able to provide the content received from the Bank. This content MUST be auditable. On the other hand, the content contains PII and thus is very sensitive by its nature.
The following guidelines MUST be adhered to for auditing:
- If the data from the Bank is relayed to the SeP, a digest of the original data MUST be included and signed in the relayed data.
- Digests of messages from the Bank MUST be stored together with digests of any potential derived messages to SeP.
- The dates indicating when each message was sent and received MUST be logged.
- Data MUST be auditable for a duration of 15 years.
- A special key pair for auditing MUST exist. The parts of the key are defined as APub for public part and APriv for the private part.
- APub MUST be used by the RP systems to encrypt payloads of Bank data messages. The payloads of the messages MUST NOT be stored in plaintext or be encrypted using a symmetric cipher.
- APriv MUST be in cold storage and MUST be stored in a tamper-resistant device (HSM, for example).
- Access to APriv MUST be limited and controlled by a strict security process. (This security process is beyond the scope of these guidelines.)
- The BankID systems MUST NOT have access to APriv. Decryption by APriv MUST be a manual process and the act itself MUST be audited.
Certain APIs (for example the planned document signing API) MAY require authentication that is not bound to a specific End-User but they still need to enforce rate-limits and associate, log or bill requests per application. API-Key authentication SHOULD be used to satisfy this use case.
During dynamic registration, the Bank generates a client_api_key API-Key token and BankID stores it. BankID MUST include the API-Key token in the X-API-Key HTTP header when requesting said Bank APIs.
Please note that the API-Key token is NOT required when requesting OAuth2/OIDC resources.
A specific mechanism of the way this flow between SeP and BankID will be handled is not covered by this document. The following example solutions exist:
- API-Key tokens are to be issued through the Developer Portal and SeP clients are required to use the API-Key when requesting said APIs.
- Client Credentials Grant from the OAuth2 protocol can be utilized. This grant exchanges client id and client secret for an access token. The SeP can use this access token when requesting said APIs. BankID can translate this access token to the API-Key token to be used for Bank communication.