-
Notifications
You must be signed in to change notification settings - Fork 15
encrypted_card
This document describes a proposal for a payment handler that behaves like basic card, but provides additional security, by ensuring that the card information is not transmitted in the clear to the merchant.
This reduces PCI scope for the merchant, especially v3+
We'd like to consider this colloquially as "BasicCard++". In that sense, there are a few things that a similar to BasicCard
and a few differences.
Similarities:
- Form Fills are used to populate payment instruments
- Payment Request invocation is similar
Differences:
- The PAN is encrypted following an exchange with a key provider
- The returned value from the
PaymentRequest
API does not contain the PAN, but contains an opaque blob - The returned blob can only be used to process the transaction with the key provider that created it
- Additional constraints may be put by the key provider on the blob (could be a nonce, time bounded, or permanent)
The payment handler can be specified using the same syntax as BasicCard
with a notable difference: a key provider will be specified.
This parameter will be provided in addition to those specified in basic-card
:
dictionary EncryptedCardRequest {
sequence<DOMString> supportedNetworks; // Defined in Basic Card
sequence<CardType> supportedTypes; // Defined in Basic Card
required DOMString keyProviderURL;
};
The Javascript would look as follows:
var supportedInstruments = [
{
supportedMethods: ['encrypted-card']
data: {
supportedNetworks: ['amex', 'discover', 'mastercard', 'visa'],
supportedTypes: ['credit', 'debit'],
keyProviderURL: 'https://www.bobpay.com/encrypted-cards'
}
},
...,
];
var payment = new PaymentRequest(
supportedInstruments,
details,
options
);
The exact same data contained in the BasicCardResponse
dictionary, is encrypted.
dictionary EncryptedCardData {
required DOMString cardNumber;
DOMString cardholderName;
DOMString cardSecurityCode;
DOMString expiryMonth;
DOMString expiryYear;
PaymentAddress? billingAddress;
};
This data is returned as part of the PaymentResponse
as follows:
dictionary EncryptedCardResponse {
DOMString cardholderName; // data from BasicCardResponse
optional DOMString suffix; // Last four digits of card number
required DOMString encryptedCardData; // base64-encoded string
};
The encryption can occur in one of these ways:
- through the public key in the TLS certificate of the key provider if a manifest is not found or the parameter is not found in the manifest
- through a Web App Manifest parameter on the key provider's origin, specifying a public key and list of ciphers as defined by WebCrypto
- through a public key provided as part of the
PaymentMethodData
dictionary
There's an inherent advantage to re-use the public key in the TLS certificate. This is because a lot can be cached/short circuited.
If there has already been a cipher exchange, the public key is potentially in the Browser's cache, and the result of the TLS handshaking will have selected a cipher suite for the key exchange step in TLS. The same cipher suite can be used to encrypt the card data.
That said, re-using the public key, where TLS expressly chooses not to for Forward Secrecy for transport makes this method less ideal for that reason.
A web manifest file can be provided with additional parameters specifying cryptographic parameters. Here's a quick example:
{
encryptedCardKey: {
format,
keyData,
algo,
extractable,
usages
}
}
This data will be fed directly in SubtleCrypto's
importKey IDL
function.
Alternatively, encryption configuration can be specified in PaymentMethodData
. It can use the same parameters specified above in the web app manifest, as defined by importKey IDL
.
dictionary EncryptedCardRequest {
sequence<DOMString> supportedNetworks; // Defined in Basic Card
sequence<CardType> supportedTypes; // Defined in Basic Card
KeyFormat format;
(BufferSource or JsonWebKey) keyData;
AlgorithmIdentifier algorithm;
boolean extractable;
sequence<KeyUsage> keyUsages;
}
When making a POST
request to the keyProviderURL
, the response will be a cypher as defined in Web Crypto.
This use-case is added to support the current scheme employed by Braintree.
Tokenization can be enabled by setting a true
value to the tokenized
option, as defined above.
The PaymentMethodData would look as follows:
dictionary EncryptedCardRequest {
sequence<DOMString> supportedNetworks; // Defined in Basic Card
sequence<CardType> supportedTypes; // Defined in Basic Card
required DOMString keyProviderURL;
optional boolean tokenized; // option to tokenize card
};
The javascript would look as follows:
var supportedInstruments = [
{
supportedMethods: ['encrypted-card']
data: {
supportedNetworks: ['amex', 'discover', 'mastercard', 'visa'],
supportedTypes: ['credit', 'debit'],
keyProviderURL: 'https://www.bobpay.com/encrypted-cards',
tokenized: true
}
},
...,
];
var payment = new PaymentRequest(
supportedInstruments,
details,
options
);
This will add a roundtrip from the Payment Method
to the key provider.
The data passed to the key provider looks as follows:
dictionary EncryptedCardKeyTokenizationRequest {
EncryptedCardData cardData;
}
The response will be:
dictionary EncryptedCardKeyTokenizationRequest {
DOMString encryptedCardData;
optional DOMString expiry;
}
The key provider returns an opaque payload in encryptedCardData
, which is then passed on to the merchant.
Additionally, this payload may have a new parameter: expiry
, which can be one of:
nil
"once"
- ISO8061-formatted datetime string
The response to the Payment Request
is unchanged:
dictionary EncryptedCardResponse {
DOMString cardholderName; // data from BasicCardResponse
optional DOMString suffix; // Last four digits of card number
required DOMString encryptedCardData; // base64-encoded string
};