Secure Communication

NOTE: In the current version of the API the secure communication is not required and will not be enforced in production.
Nonetheless, it is highly recommended to implement the secure communication, as it will be mandatory in the next API version.

Encryption

All request and response payloads can be encrypted to increase the security for the whole API communication. This can be achieved by signing the payload with JWS and afterwards encrypting it with JWE.

Signing with JWS

The signing of the payload is done with the sender's private key. The used algorithm has to be RSA_USING_SHA512. The content type of the signed payload is application/json.

Pseudo code:

yourPrivateKeyForRequest{id, key} = loadYourPrivateKey(id=X)

request = {body: "{...jsonRequest...}", contentType: "application/json"}

jwsToken = jwsSign({
    payload: request.body,
    // "cty" property for JWS header
    header.payloadContentType: request.contentType,
    key: yourPrivateKeyForRequest.key,
    // "kid" property for JWS header
    header.keyId: yourPrivateKeyForRequest.id,
    // "alg´" property for JWS header
    header.algorithm: RSA_USING_SHA512,
    keyValidation: true
})

Encryption with JWE

The encryption is done with the public key of the recipient. The used algorithm has to be RSA_OAEP_256. The algorithm for the content encryption has to be AES_256_GCM. The content type of the signed payload is application/jose.

Pseudo code:

jwsToken = // the JWS Signed Data

recipientPublicKey{id, key} = loadRecipientPublicKey(id=Y)

jweToken = jweEncrypt({
    payload: jwsToken,
    // "cty" property for JWE header
    header.payloadContentType: "application/jose",
    key: recipientPublicKey.key,
    // "kid" property for JWE header
    header.keyId: recipientPublicKey.id,
    // "alg" property for JWE header
    header.algorithm: RSA_OAEP_256,
    // "enc" property for JWE header
    header.encryptionAlgorithm: AES_256_GCM,
    keyValidation: true
})

Decryption

The decryption has to be executed in reverse order compared to the encryption. First the JWE token is decrypted and then the JSON Web Signature (JWS) is verified.

Decryption of the JWE Token

Pseudo code:

jweToken{header, cipher-text, auth-tag} // the JWE encrypted data

yourPrivateKeyForResponse{id, key} = loadYourPrivateKey(id=jweToken.header.keyId)

jwsToken{header, payload, signature} = jweDecrypt({
    payload: jweToken,
    key: yourPrivateKeyForResponse.key,
    // "alg" property from JWE header
    header.algorithm: jweTokenForResponse.header.algorithm,
    // "enc" property from JWE header
    header.encryptionAlgorithm: jweTokenForResponse.header.encryptionAlgorithm,
    keyValidation: true
})

Verification of the JSON Web Signature (JWS)

Pseudo code:

jwsToken = // the JWS Signed Data

theirPublicKeyForResponse{id, key} = loadTheirPublicKey(id=jwsTokenForResponse.header.keyId)

verifiedData{data, contentType} = jwsVerify({
    payload: jwsToken,
    key: theirPublicKeyForResponse.key,
    // "alg" property from JWS header
    header.algorithm: jwsToken.header.algorithm,
    keyValidation: true
})
// Example for verifiedData
=> {data: "...jsonResponse...", "contentType": "application/json"}

Request/Response Structure

The content type of a request or a response with an encrypted payload must always be application/jose. The body contains the JWE token.

Here is an example for an encrypted API call:

Request

PUT /xs2a/v1/sessions HTTP/1.1
Content-Type: application/jose
Authorization: Token <Token>
Host: <Host>

eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIiwia2lkIjoiMkFGOTJCMUQifQ.OxHUvzpTq1HMz0EmWVkxAG8SAs14PJUJWIt2XOK4I8IRx-uNxAwqNQLSVx7reWvCra538PLuNmteY2LQsKwttZyMu9AR7hV-a-u8_wqxEKCVxkJHUh4VgGlJ-7aMzmTk1FMon0HI-xtm7okfXI5D62KAv_6VXRMlZUf-pdggDyPHPk7worLgcuOBy0AUOY0yiYN3vQrN8Qpv0m4nEQiHL1wj4fzkfma6MuNS62Q7E_2Pl60uKDYNpm8jdBMmFrsuFQXos3JspZvsIalmghagFQA5YtBzlw-Dry8tjxCezXZzx1M21vf_lVcDDO0BaNjROExdTKfwWlRWnMv30Lx6nA.ApNGrmoYssIbo-in.DNfg73P1u8EN9gOxmKbwmhJtJJlzXbuw89_3XIzRAOg_9-HUm-a9pZJGO1PQjetfW7AY5RxQjUcm8LI6_QbCDuybXDh064Z52zLVS4D1P8VNVRJN4lc0_crs7OYOnfHw77X03WSUaZ_CGvKdHdI-GW8ve3g-_OiVHshWwoC0D6Y5wciURBVTZrQKadfjeJoUHFQEUQJGdZP24O91ZAPYxE8w2_4KRAcH3OwFB-bI5NzeYj72Pr3Oa2D8y4PxIydwsuRSR1ZALddZbk6HomYVTfdWTricrx3RJRH2PtoKUveTYnPObeeYIrU1F7ph8-UgWIKwYeRzoCiwZdy4DVuqlrZuxIHUUWSa07eSp-ZGbqfOx9pm640Cz-3EfYYZgqZW_-WNaZnDyQdtCAH_i_XICuRWzPWI4mDf2Sw917PRO5xsoRkp0rZfT7K2AnFiE6vFblA_QZz5T3D41sqlPG-0zCVSwGwbRfmvt3-VdNewpj82ToHZgCOQYYJFcr9c_M_80oAtyu9fvAPiFJHc9t8GmaUvlR_kFD3BNeCq2q7ynj779ixrK-tKkdW2UECQQe5YeaHdaKlVtewJslwE54Ymd6kNZmBQ_OrSA4nAvNOjpaSAD9a1dyJHYyDddwlYdTrkS9VHiS-xejDIleMf7hfBgHqa0RevlPLamW5rMty9nKOQv1XZg9QQllKpxUEqmrubkHg.4g1a-iY77voFCpDHht2iMw

Response

HTTP/1.1 201 Created
Content-Type: application/jose

eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIiwia2lkIjoiNUYwQzlEMzcifQ.QjfGSGF2HbEH63bZu_0HKvDpILk4JbRZ_LxFh1XJ034fwzbQEFv8KjXLUgVGWT2yb6Wk72M4fnwgVVWtgyYX1ZN0y8aRYHkj58xFOxN4aUKPAquScbjIQUZBjD6d-DQSj9K8bWtRI2WmmWIzH8-1gR3Ykpia5FxE0qGEr7aBSC15a2n0__v3fnLUL821RKnBCW_MQbiFXh2dYMZD54-caz8w_KGuhiHvss1CMl0-DCw_Xr5xHlNsgkDNts8ycyAzuxkLLgK_Au3MFIvtWzKSDfKbj2aNr8tu5gc0mGya25VZ0KaRsuE1iJy_IvMpPQXV_VHdeOVIpAuptKmlv6yyYQ.897L3hsLMz-ftjbo.Ttjc9tGe1HcJsFrUy2vRaNpkIOUSkugE0qCOJ4ZNALQ4DOngmxOpu-4N0J53jk3v_OXqATabWdAQjKiXmwDdosxib-ymwIEyUX5E6jarDNITaZY54JeiMr1hQ0gYuUfvLMLXw5ZwntA-Gmq8xpwONNxBLIasgvD6h6t0xYGA2fQVD8PhtYWbONU-xIkG7UWg27SLPXUh97rbhidcP4zZhIhk3W8db4P91YnLqmiJkiOtcLfU1NEr21iJSBNnH4A9MW-nd3t3BLBJatQEeZVMrp1WPlCyFDyxOjp3HCVV-w_jwrnLH2x_ew7ANY_mgEZhyRBqAfgpcgFekvpegv3LL9lXLln4K_5HrAtgIr26_fmFQO_qvOBD-tSf6wyWKXPmhC9zBeCas3HnquZZCHWxrg_EUMfQMtbSG50VqUQYqZvDY1HKogiDrtq1r_yBnWM_vuTANxNyhjHtLI97CQ6raFDFzw1E-E9I43QyTYPSYIESHdIRavFcBqeSsx75PyMW2dhBu3qV0jUdgpiMywc6MLCjP5lCLbuH0wZjvO1qTc61-Wc3cKSo12_LrMplq-8-gqVmoHvZsKYF3LfpTgX0i0L8I19dmGk5on0T4uD31PWAVDobN34LN7nNmG2GPwmJibl5jsljIY78lUD5Jhj1Qk6vj2_2F91S7iEoxUBUClbORvx--uif1ljVHC_kUPU6FWflRREZEVTfKdkY_S2bhzCuGwg6aYumLem1ddZmcKaDBNNi8Romv1cCV-qVyB0F4uLWZ1IFRbdgxKRvpvoZzN0kApNhWWDiG7vWpbzOkLdHwey_BRMtrbXLfMwtuJ-nLTooK-H7pqun_scmFGt57eMmh7ALW9jKWgawo1nQ0IXMpXJ3aGSMroiYxTqi9APpfHXOvs5Urw1hAqhdl4H2fGxBBLD3WW9DAiUFpuzMe9xQQ1qEFNvfMBIvv2ekjJZIzPnsZ0ahgv5ymaNXxFhx2h9t8KPMLJ7VOZgBPjvf7GYiEfJYf33JTmCBlBKmyh1olWxs5iAvcIQva1moYbiMJAESmQmjAyOcxnxZuu1WsnPX1BYNiJyXs7LsiIpI6Oo39mJVbjh7jh1xRLccFccSUZYk7KZFCC1EGiE0z8NQrr3F1_lHyeaFHd4mEJvY5_1lF1z7FqkkCwydNqcEBH0RJ-Fu_ShEquTffLa5udNrIZBQRpivirHhSVcfw23JSM9FZ2be6W1tcD8_sroUuEeBMGtxCAzRF4Gq9WRR1xe68WmEOHmx6OEHgQIAPf6KTYGIgrx3Aw2VkeofcIeA8gUc4PgZNKq23TAA9m_ZhVVLykDUZyQfljYiNY-RsdjkXw1CCTQP-1TRbBYnQalZlCfnYM1nvdJIJ_JEXix1Y8hMioJ0BsaYGtjDfEM6mGPeMZ-L6RMAcjM62KU6utcJ6zlkl16QitHQJt0uvu1tVRFdpYdjcwFAD__161Yk-JVHPQ3UBFoZ7NOq7IDySkzogulQvH1BahAf7TQJDffj9loNK4g5x4D__0-XaTKSubg3655s24oCDSav3gKoqzrOIMuuDMreKVlFDtGbT9c6ySTqvM3g5hITeqJ34IAa4pTrRM-RdT8Z1g6C4FKhvFHOHR9ArCSyrm2EBPO1RrBMi9xejZrhGRzdYjWeaInV4aoOvJCdiDCkMI4JJKa_vnnqB0wzmlrhuE69_GtLLLCeDfV9RWvjCDVcERpu1fO5pmSnhrWU_n0dgPQjK0KQhX97LnTNcXTbklf11A7-vpmrW1-wJ7zXDmf2N_IWN8-vji4x7hp5o9roUjS5rG0IRSUYAZqMMT3H6uXqtX8M_CA_UzM0fyFyXafCk1IvPdGeqyr8pITPVFdeOlCmiOBCH78aaSPO-dQFAghASP1qFdH4I1h2DP7hHQGNNT8.miLjzfNjBifoGQEqCr0w1g

GET and DELETE Endpoints

There are new POST endpoints specifically for encryption as it is not common to have a body in GET and DELETE requests.

All GET and DELETE requests can be converted to POST by appending the corresponding HTTP method to the URL. With GET it would be /get and with DELETE /delete.

Example 1:

GET /xs2a/v1/sessions/{session_id}

will become

POST /xs2a/v1/sessions/{session_id}/get

Example 2:

DELETE /xs2a/v1/sessions/{session_id}/flows/{flow_id}

will become

POST /xs2a/v1/sessions/{session_id}/flows/{flow_id}/delete

Replay Attack Prevention

To prevent replay attacks there are two new mandatory fields for every request:

request_id String, required

The request_id has to be a unique string identifier for every request. The length of this identifier has to be between 10 and 100 characters. We recommend using a UUID. Sending two requests with the same identifier in a timeframe of 5 minutes will reject the second request.

request_timestamp Integer, required

The request_timestamp field should contain a timestamp in UTC milliseconds. Requests with a request_timestamp older than 2 minutes will be rejected.

This example shows what the unencrypted create session request body could look like:

{
    "request_id": "b5c9edb2-319f-4f5a-bef1-9cad8f7e785e",
    "request_timestamp": 1585063812156,
    "psu": {
        "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1",
        "ip_address": "136.71.143.70"
    }
}

Additionally, all encrypted responses will also contain a new field after decrypting the payload:

response_timestamp Integer, always present

The response_timestamp field contains the response creation timestamp in UTC milliseconds. To further improve security this timestamp should be verified by the recipient of the response to detect manipulation. Responses with a response_timestamp older than 2 minutes should not be accepted.

This example shows what the decrypted create session response body could look like:

{
    "response_timestamp": 1585063812228,
    "data": {
        "session_id": "<session_id>",
        "session_id_short": "<session_id_short>",
        "flows": {
           ...
        },
        "self": "<self_url"
    }
}

Key Creation

A valid key pair can be created like this:

openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout > public.pub

The content of the public.pub file has to be sent to Klarna to start using the JWE encrypted communication. All other required data will be generated and provided by Klarna.

results matching ""

    No results matching ""