From 27da1a2a012cda8ad1445744c35a07021d12f575 Mon Sep 17 00:00:00 2001 From: Waldir Pimenta Date: Mon, 26 Oct 2020 18:20:24 +0000 Subject: [PATCH] Document the Protected Cards feature --- _protected-cards.md | 157 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 _protected-cards.md diff --git a/_protected-cards.md b/_protected-cards.md new file mode 100644 index 0000000..1d90518 --- /dev/null +++ b/_protected-cards.md @@ -0,0 +1,157 @@ +# Protected Cards + +The protected cards feature allows securing an Uphold card by associating a cryptographic key pair to it, so that a signature with the secret key of that pair will be required for performing transactions with it (except deposits into the card, which remain possible with unsigned requests). + +The instructions below describe in detail how to make use of this feature. + +## Generate a Key Pair + +> Create an EdDSA cryptographic key pair, e.g. using the [tweetnacl](https://www.npmjs.com/package/tweetnacl) package: + +```js +const nacl = require('tweetnacl'); +const keyPair = nacl.sign.keyPair(); // Generate an Ed25519 key pair +const publicKey = Buffer.from(keyPair.publicKey).toString('hex'); +const secretKey = Buffer.from(keyPair.secretKey).toString('hex'); +``` + +The public-private key pair used for signing transactions of protected cards must be generated by you, and it must be of the [EdDSA](https://en.wikipedia.org/wiki/EdDSA) type (specifically, `Ed25519`). + +This step should be performed on your server-side, and the private key must be stored securely. +Never expose or transmit the private key to Uphold, or any other third party outside your control. + +## Create a Protected Card + +> Construct the body of the request to create a card: + +```js +const data = { + currency: 'USD', + label: 'My Protected Card', + publicKey: publicKey +} +``` + +> Create a SHA-256 (or SHA-512) hash of the request body, and store it as a base64-encoded digest: + +```js +const crypto = require('crypto'); +const digest = crypto.createHash('sha256').update(JSON.stringify(data)).digest('base64'); +``` + +> Create the signature, e.g. using the [http-request-signature](https://www.npmjs.com/package/http-request-signature) package: + +```js +const { sign } = require('http-request-signature'); +const signature = sign({ + headers: { + digest: `SHA-256=${digest}` // This must match the `Digest` header + }, // that will be sent in the request. + keyId: 'primary', // We require the `keyId` to be "primary". + secretKey +}, { algorithm: 'ed25519' }); // We only support the `ed25519` algorithm. +``` + +> Submit the request including the `Digest` and the `Signature` headers, as well as the data used to generate them: + +```bash +$ curl 'https://api.uphold.com/v0/me/cards' \ + -H 'Accept: application/json' \ + -H 'Authorization: Bearer ' \ + -H 'Digest: SHA-256=' \ + -H 'Signature: ' \ + -H 'Content-Type: application/json' \ + -d '{ "currency": "USD", "label": "My Protected Card", "publicKey": "" }' +``` + +> The response should be a [card object](https://uphold.com/en/developer/api/documentation/#card-object) +> (sample output truncated for conciseness): + +```json +{ + "available": "0.00", + "balance": "0.00", + "currency": "USD", + "id": "71064207-b557-4808-ac33-e4eb86d78a01", + "label": "My Protected Card" +} +``` + +Once a key pair has been generated, a protected card can be created by adding the public key in the data of a request for [creating a card](https://uphold.com/en/developer/api/documentation/#create-card). + +As additional safeguards, two headers must be included in this request: a `Digest` (consisting of a SHA-256 hash of the body of the request) to guard against transmission errors in the request data; and a `Signature` (consisting of the same digest payload, but encrypted with the private key, and formatted to be compliant with the draft Internet Standard "[Signing HTTP Messages](https://tools.ietf.org/html/draft-cavage-http-signatures-12)"), which validates that the public key included in the data does match the private key used for encryption. + + + +## Create Signed Transactions + +> Construct the body of the request to create a transaction from a protected card: + +```js +const data = { + denomination: { + amount: '10', + currency: USD' + }, + destination: '
' +} +``` + +> Generate the digest and signature of the request data: + +```js +const crypto = require('crypto'); +const digest = crypto.createHash('256').update(JSON.stringify(data)).digest('base64'); + +const { sign } = require('http-request-signature'); +const signature = sign({ + headers: { + digest: `SHA-256=${digest}` + }, + keyId: 'primary', + secretKey +}, { algorithm: 'ed25519' }); +``` + +> Submit a request for creating a signed transaction, using the id of the protected card in the URL parameters: + +```bash +$ curl 'https://api.uphold.com/v0/me/cards//transactions?commit=true' \ + -H 'Accept: application/json' \ + -H 'Authorization: Bearer ' \ + -H 'Digest: SHA-256=' \ + -H 'Signature: ' \ + -H 'Content-Type: application/json' \ + -d +``` + +> Uphold's server verifies that the transaction's signature is correct and proceeds with committing the transaction +> (sample output truncated for conciseness): + +```json +{ + "createdAt": "2017-06-26T17:17:57.532Z", + "denomination": { + "pair": "USDUSD", + "rate": "1.00", + "amount": "1.00", + "currency": "USD" + }, + "id": "efc5aadf-87eb-4731-8697-eb0dd8d48b48", + "status": "completed", + "type": "transfer" +} +``` + +> Note that the actual response will contain several fields in addition to those shown in this simplified example. + +In protected cards, the only operations that can be performed without a signature are deposits _into_ the card. +In order to transact _from_ a protected card to any destination, we'll need to sign the request. + +Creating signed transactions from a protected card can be done in much the same way as the process for creating the protected cards themselves — that is, via normal transaction creation requests that include the `Digest` and `Signature` headers. + +In this case, since the public key is not transmitted in the request body, the signature serves as a cryptographically strong assurance that the originator of the transaction is authorized to move funds from this card. +More concretely, it proves that they have access to the private part of the key pair that's linked to the protected card in Uphold's internal records.