Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How are digital signatures supported for Payment Requests? #291

Closed
msporny opened this issue Oct 12, 2016 · 16 comments
Closed

How are digital signatures supported for Payment Requests? #291

msporny opened this issue Oct 12, 2016 · 16 comments

Comments

@msporny
Copy link
Member

msporny commented Oct 12, 2016

I had a brief chat with @rsolomakhin about digital signature support for the Payment Request API.

To recap, here's the use case:

The merchant (and the payment app) desire that the payment request is digitally signed so that they may be certain that it has not been tampered with in transit.

There are a number of scenarios where a digital signature on a payment request is desirable:

  1. High value transactions (Payment Request for $10,000+ in US).
  2. Publishing Payment Requests in HTML via schema.org-like mechanisms (digital offers).
  3. Business requirements that require the payer to store information demonstrating that the payee has asked for a specific monetary amount (invoice before payment, audit-ability, etc).

To be clear, this issue isn't requesting that digital signatures are implemented in the Payment Request API. The request is to demonstrate that the Payment Request API is capable of supporting digital signatures in a way that is not onerous to Web developers.

@msporny
Copy link
Member Author

msporny commented Oct 12, 2016

Document Signatures

The Web Payments CG specs addressed this issue by digitally signing the entire Payment Request, which would look something like this if we were to use Linked Data Signatures:

{
  "@context": "https://w3id.org/payments/v1",
  "paymentMethods": [{
    "supportedMethods": ["visa","bitcoin"]
  }, {
    "supportedMethods": ["bobpay.com"],
    "data": {
      "merchantIdentifier": "XXXX",
      "bobPaySpecificField": true
    }
  }],
  "paymentDetails": {
    "displayItems": [{
      "label": "Sub-total",
      "amount": { "currency": "USD", "value": "55.00" }
    },{
      "label": "Sales Tax",
      "amount": { "currency": "USD", "value": "5.00" }
    }],
    "total":  {
      "label": "Total due",
      "amount": { "currency": "USD", "value": "60.00" }
    }
  },
  "paymentOptions": {
    "requestShipping": true
  },
  "signature": {
    "type": "LinkedDataSignature2015",
    "created": "2016-10-12T14:14:39Z",
    "creator": "https://merchant.example.com/keys/1",
    "signatureValue": "kC/MZTvo3ro8/yR+OPY4ZtWWpaddFmWDH35U0F5
UuwRgH9KXs9ersJEznQ3zOXIPALZTW/cXdzhDT5ogA+TLQcr7+YY0yGd6z
YgDdg1HVtzRDaXULCi+Admu6A3tKCLzku0+cHiRjDx/mIRTFHE6zoaUcTZJ
Gf8JiMSrHPRJBGQ="
  }
}

and it would look like this if we were to use JWTs (SHA256 + RSA signature):

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJAY29udGV4dCI6eyJAdm9jYWIiOi
JodHRwczovL3czaWQub3JnL3BheW1lbnRzIyJ9LCJwYXltZW50TWV0aG9kcyI6
W3sic3VwcG9ydGVkTWV0aG9kcyI6WyJ2aXNhIiwiYml0Y29pbiJdfSx7InN1cHBv
cnRlZE1ldGhvZHMiOlsiYm9icGF5LmNvbSJdLCJkYXRhIjp7Im1lcmNoYW50SWR
lbnRpZmllciI6IlhYWFgiLCJib2JQYXlTcGVjaWZpY0ZpZWxkIjp0cnVlfX1dLCJwYXl
tZW50RGV0YWlscyI6eyJkaXNwbGF5SXRlbXMiOlt7ImxhYmVsIjoiU3ViLXRvdGF
sIiwiYW1vdW50Ijp7ImN1cnJlbmN5IjoiVVNEIiwidmFsdWUiOiI1NS4wMCJ9fSx7Imx
hYmVsIjoiU2FsZXMgVGF4IiwiYW1vdW50Ijp7ImN1cnJlbmN5IjoiVVNEIiwidmFsdW
UiOiI1LjAwIn19XSwidG90YWwiOnsibGFiZWwiOiJUb3RhbCBkdWUiLCJhbW91b
nQiOnsiY3VycmVuY3kiOiJVU0QiLCJ2YWx1ZSI6IjYwLjAwIn19fSwicGF5bWVud
E9wdGlvbnMiOnsicmVxdWVzdFNoaXBwaW5nIjp0cnVlfX0.gff0EfDnNzi61KyeC
EwcT6ktx2HDG1Qy2UF1DshXp5uWkpbbfa9hirO1oC_CGaxgWjrJDOa0HaEtM
VUIpM1ta7AGWD7zhZdczRiUWjzppN9e8j6ZPV1I2YgERfP4-nuNSvWI7TqELlfZ
8fb06449ULL-Fu1znNY-JCeWwgKc75U

The problem that is introduced by the design of the Payment Request API is that we only want to forward the payment method to the payment app that has been selected by the user.

The simplest form of signature is what has been provided above, but because of the requirement to not share all supported payment methods, we end up in search of something more complex (as I'll demonstrate in the next comment in this threads).

One thing we could do is re-visit the decision to not share all supported payment methods w/ the payment app. If we can't find a compelling reason to only forward the pertinent payment app data (other than message size), then we may want to remove that requirement on the PaymentRequest API.

@ianbjacobs
Copy link
Collaborator

@msporny wrote

"If we can't find a compelling reason to only forward the pertinent payment app data (other than message size)..."

In addition to those efficiency questions (the payment app will ignore most data it receives otherwise, which is an inefficient use of time and bandwidth), privacy was cited - sharing more data (from the merchant) than needed to handle the payment.

Ian

@msporny
Copy link
Member Author

msporny commented Oct 12, 2016

Constructed Signatures

If the group desires to only forward the selected payment method information to the payment app, then a new signature construction algorithm will need to be defined which follows the basic algorithm below:

For each payment method pm in paymentMethods, generate a digital signature by:

  1. Create a new empty JSON object toSign.
  2. Set the toSign.paymentMethods key to the value of pm.
  3. Set the toSign.paymentDetails key to the value of the payment details.
  4. Set the toSign.paymentOptions key to the value of the payment options.
  5. Digitally sign toSign using the signature algorithm of choice.
  6. Store the digital signature as sig.
  7. Set the pm.signature key to the value of sig.

This will result in a payment methods array that looks like the following if using Linked Data Signatures:

[{
    "supportedMethods": ["visa","bitcoin"],
    "signature": {
      "type": "LinkedDataSignature2015",
      "created": "2016-10-12T14:14:39Z",
      "creator": "https://merchant.example.com/keys/1",
      "signatureValue": "kC/MZTvo3ro8/yR+OPY4ZtWWpaddFmWDH35U0F5
UuwRgH9KXs9ersJEznQ3zOXIPALZTW/cXdzhDT5ogA+TLQcr7+YY0yGd6z
YgDdg1HVtzRDaXULCi+Admu6A3tKCLzku0+cHiRjDx/mIRTFHE6zoaUcTZJ
Gf8JiMSrHPRJBGQ="
    }
  }, {
    "supportedMethods": ["bobpay.com"],
    "data": {
      "merchantIdentifier": "XXXX",
      "bobPaySpecificField": true
    },
    "signature": {
      "type": "LinkedDataSignature2015",
      "created": "2016-10-12T14:14:39Z",
      "creator": "https://merchant.example.com/keys/1",
      "signatureValue": "RlNJFKeeJax5I592PPGbN0IoCVqhLKothrq6DA9BX3
7RhoDWlVGCCBZLRgPbAeCw7HvDZIi7IXm0R4vXNEIeDsy6kKFzjooUm9IO
p6NADi6n1cMgbKzytJ7sXMhKWVgQZNZopNxWpT7f+SEPbF2FiOhJFN3wg
LyyqT62x8OUCI8="
    }
  }]

or the following if using JWTs:

[{
    "supportedMethods": ["visa","bitcoin"],
    "jwt": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJAY
29udGV4dCI6eyJAdm9jYWIiOiJodHRwczovL3czaWQub
3JnL3BheW1lbnRzIyJ9LCJwYXltZW50TWV0aG9kcyI6W
3sic3VwcG9ydGVkTWV0aG9kcyI6WyJ2aXNhIiwiYml0Y2
9pbiJdfV0sInBheW1lbnREZXRhaWxzIjp7ImRpc3BsYXlJd
GVtcyI6W3sibGFiZWwiOiJTdWItdG90YWwiLCJhbW91bn
QiOnsiY3VycmVuY3kiOiJVU0QiLCJ2YWx1ZSI6IjU1LjAwIn
19LHsibGFiZWwiOiJTYWxlcyBUYXgiLCJhbW91bnQiOnsi
Y3VycmVuY3kiOiJVU0QiLCJ2YWx1ZSI6IjUuMDAifX1dLC
J0b3RhbCI6eyJsYWJlbCI6IlRvdGFsIGR1ZSIsImFtb3VudC
I6eyJjdXJyZW5jeSI6IlVTRCIsInZhbHVlIjoiNjAuMDAifX19LC
JwYXltZW50T3B0aW9ucyI6eyJyZXF1ZXN0U2hpcHBpbmci
OnRydWV9fQ.SNvvQouMMOoYwwYHUb4m_Lvf7-iuB3fh
UgJaYkhCLxXGvwN8aVwgzyUUj12PMbwnvjzxQ_Lu
HEQUn2WW6lNvb-8BD1CZytUyW103FCyTvPMauho4TtQ7
GclurrKfDs1CUpXiy91XGm_wlpqTWc5eSTl1J-OrBYWTpJZ
Oy6KOvJ0"
  }, {
    "supportedMethods": ["bobpay.com"],
    "data": {
      "merchantIdentifier": "XXXX",
      "bobPaySpecificField": true
    },
    "jwt": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJAY
29udGV4dCI6eyJAdm9jYWIiOiJodHRwczovL3czaWQub
3JnL3BheW1lbnRzIyJ9LCJwYXltZW50TWV0aG9kcyI6W
3sic3VwcG9ydGVkTWV0aG9kcyI6WyJib2JwYXkuY29tIl0
sImRhdGEiOnsibWVyY2hhbnRJZGVudGlmaWVyIjoiWFh
YWCIsImJvYlBheVNwZWNpZmljRmllbGQiOnRydWV9fV0
sInBheW1lbnREZXRhaWxzIjp7ImRpc3BsYXlJdGVtcyI6W
3sibGFiZWwiOiJTdWItdG90YWwiLCJhbW91bnQiOnsiY3
VycmVuY3kiOiJVU0QiLCJ2YWx1ZSI6IjU1LjAwIn19LHsib
GFiZWwiOiJTYWxlcyBUYXgiLCJhbW91bnQiOnsiY3Vycm
VuY3kiOiJVU0QiLCJ2YWx1ZSI6IjUuMDAifX1dLCJ0b3Rhb
CI6eyJsYWJlbCI6IlRvdGFsIGR1ZSIsImFtb3VudCI6eyJjdX
JyZW5jeSI6IlVTRCIsInZhbHVlIjoiNjAuMDAifX19LCJwYXltZ
W50T3B0aW9ucyI6eyJyZXF1ZXN0U2hpcHBpbmciOnRyd
WV9fQ.m28k93A9nOlrsTcGfvVBT4yJxOB2j0xPJwbc2P3o
n9UJLGcslR0FNQYmito6LdH8RIng470FYOyf_eIYd6MqC
q5lWyDayfgQarjnZ9JDXDTzZ8QpTjJGyPChPal-shZ9edE
NgHSHUxojKFqM4JcYZ7xk9pyy8NRhGfVMP29tKRg"
    }
  }]

Note that in the case of the JWT above, all data is duplicated and no data outside of the base64 encoded blob of information should be used. In addition, all data is duplicated N times for every payment method in the array of payment methods. So, if a merchant has 32 payment methods that they accept, the data will be duplicated 32 times (this isn't the case for the Linked Data Signatures approach).

@msporny
Copy link
Member Author

msporny commented Oct 12, 2016

@ianbjacobs wrote:

In addition to those efficiency questions (the payment app will ignore most data it receives otherwise, which is an inefficient use of time and bandwidth), privacy was cited - sharing more data (from the merchant) than needed to handle the payment.

So, I'm hearing the decision was made due to:

  1. inefficient use of time (assuming you mean "time to transmit" or "time to process"?)
  2. inefficient use of bandwidth
  3. merchant privacy

I'm not taking a position on this yet because we're talking about a fairly complex trade-off, but pushing back on those points above a bit:

Regarding inefficient use of time - perhaps we should have some timing statistics to see how much time we're talking about. I expect that it will be on the order of a few hundred microseconds at most since the Payment Request communication channel is web page to web page. Page bloat is at least 2MB at present: https://gigaom.com/2014/12/29/the-overweight-web-average-web-page-size-is-up-15-in-2014/ so even if it's shoved down an HTTP pipeline, I can't imagine it being more than a few milliseconds (a tiny fraction of page load time). I don't find this argument compelling.

Regarding inefficient use of bandwidth - the biggest contributor to the bandwidth is the array of payment methods which are a couple of hundred bytes in all the cases we've seen. Again, a few hundred microseconds at most since the Payment Request communication channel is page to page. I know WorldPay has stated that they support 200+ payment methods, but do those boil down to mostly cards? The question is, what's the largest array of payment methods we're expecting and how many transactions are going to use that largest array? If we multiply the example in the spec by 10x, we get around 1.8KB for the payment method array. That doesn't seem like a waste of bandwidth. Multiplying it to 200x gives us 36.6Kb, which is much larger, but certainly a drop in the bucket compared to what the rest of the page is using. I'm not convinced by the inefficient use of bandwidth argument.

Regarding the privacy argument - the assumption is that the merchant doesn't want to advertise all of the payment methods that they support to payment apps. I find this argument the most compelling, but don't fully understand how we're protecting this information from being leaked. For example, (in 80+% of cases) can't anyone come to the merchants website and execute a payment request and then cancel it? In that scenario, one can find out the payment methods that the merchant supports even with the "protections" we've built in. The only time the merchant is protected against this "privacy violation" is when the transaction is such that the payment app provider can't get access to the merchant website to execute a payment request. Do merchants really care about this form of privacy? Do they understand that we can't really protect them against this privacy violation unless they put access control of some kind in front of their Buy button?

So, I think there is a very thin "privacy protection" argument for only forwarding payment app specific payment method information to the payment app. If that argument holds, then we're going to have to create an entirely new type of algorithm for digitally signing and verifying payment requests.

@ianbjacobs
Copy link
Collaborator

@msporny,

I also recall that we said that payees could verify data upon receipt of the payment response. For what use cases does that not suffice?

Ian

@dlongley
Copy link

@ianbjacobs,

I also recall that we said that payees could verify data upon receipt of the payment response.

Which data are you talking about?

Digital signatures are a solution for use cases where the merchant isn't immediately in the loop (e.g. execution of a Digital Offer from a search results page) or where the payer wants assurance that the push payment method they are about to use will send the proper amount of money to the proper party (in return for the proper goods/services). In these scenarios, payee verification of information after the fact will not protect users and will lead to undue liability on the merchant end.

@msporny
Copy link
Member Author

msporny commented Oct 12, 2016

I also recall that we said that payees could verify data upon receipt of the payment response. For what use cases does that not suffice?

It does not suffice for at least the 3 scenarios listed at the top of this issue:

High value transactions (Payment Request for $10,000+ in US).

In this scenario, the payment app would like to make sure that the amount is correct before executing the payment. This is even more important for any sort of push-based payment.

Publishing Payment Requests in HTML via schema.org-like mechanisms (digital offers).

How can a payment app ensure that a payment request published on a merchant website, indexed by a search crawler, and executed by a customer to a has not been tampered with before the payment request got to the payment app?

Business requirements that require the payer to store information demonstrating that the payee has asked for a specific monetary amount (invoice before payment, audit-ability, etc).

The business requirement here is on the payment app to collect something equivalent to a "proof of invoice", not the merchant.

So, in each one of these cases, the payee checking the result doesn't achieve the desired results of the use case.

@adrianhopebailie
Copy link
Collaborator

adrianhopebailie commented Oct 13, 2016

What's the motivation to sign the whole request and not just provide the signature in the payment method specific data?

i.e. For payment methods that support signatures

@msporny
Copy link
Member Author

msporny commented Oct 14, 2016

What's the motivation to sign the whole request and not just provide the signature in the payment method specific data?

Signing the whole request is simpler in at least these ways:

  1. Cognitive load on developers is reduced greatly. Either the request is signed or not. If it's signed, all data is protected.
  2. Attack surface is reduced. Either the entire request is signed or it's not. If the entire request is signed, it's easier to understand the attack surface on payment request messages.
  3. No extra digital signature construction algorithms are required.
  4. All payment requests are protected, not just certain payment methods that support digital signatures.

There are some downsides:

  1. Merchant payment method privacy is "violated", but as explained above, it's arguable that we can protect merchant payment method privacy in the vast majority of cases.
  2. We'd have to provide a single argument option to the PaymentRequest constructor.

There are two options as far as I can see wrt. digital signatures on payment requests (there may be more):

  1. Create a new digital signature algorithm that builds a document that you can then digitally sign and then put that digital signature in the payment method's 'data' object.
  2. Create a one parameter option to the Payment Request constructor that enables you to feed in an object that looks something like this: How are digital signatures supported for Payment Requests? #291 (comment) and ensure that object is what's serialized and sent to the payment app.

@msporny
Copy link
Member Author

msporny commented Oct 14, 2016

What's the motivation to sign the whole request and not just provide the signature in the payment method specific data?

Another way to phrase this question is "What is the information that needs to be digitally signed?"

The assertion is that there are some scenarios (like the ones mentioned above) where you'd want to digitally sign the payment items, the payment options, and the payment method data.

In the very least, we'll want to sign the payment method data. If that's the only requirement, then the solution is fairly easy. Unfortunately, that means that we definitely wouldn't be signing the payment details, which means none of the products or totals/discounts are digitally signed (which doesn't address a number of the use cases).

If we are able to sign everything (like the WPCG spec supported), then we don't have to keep coming up with piece meal solutions every time we find out that we need to sign something else. The entire message is protected instead of developers having to understand what parts of the message are protected and what parts are not.

Again, I'm not taking a position on this. I'm just outlining the options available to us and noting that none of them seem to be easy decisions based on the series of design decisions that have gone into the Payment Request API.

@adrianhopebailie
Copy link
Collaborator

Another way to phrase this question is "What is the information that needs to be digitally signed?"

No, I think it's possible for each payment method to define a signature mechanism that also specifies what information needs to be signed.

Different payment systems are going to have different requirements for the digital signatures and so the data that is signed for each payment method is likely to be different (and the algorithm used etc).

I would class signatures in the same category as callbacks. They will likely be used by a lot of payment methods but it's not clear that we should try to standardize them at the top-level yet.

@msporny
Copy link
Member Author

msporny commented Dec 15, 2016

They will likely be used by a lot of payment methods but it's not clear that we should try to standardize them at the top-level yet.

Yes, but we're making a decision where we may never be able to standardize a good design at the top-level due to the way the PaymentRequest constructor is currently designed. I agree that we may not want to standardize the top-level yet, but we're specifically making a decision where we cannot easily standardize it at the top-level later on. If someone can produce a well designed strategy for standardizing at the top level later on, that would go a long way to alleviating my concerns around digital signatures and this API.

@adrianhopebailie
Copy link
Collaborator

I agree that we may not want to standardize the top-level yet, but we're specifically making a decision where we cannot easily standardize it at the top-level later on.

I don't follow this logic. If the change that is required later is the addition of a new constructor parameter then that can be done easily without breaking the API. It will simply be added as an optional parameter.

@cyberphone
Copy link

I'm sure people can debate to death how Merchant signatures should be introduced and formatted. However, a bigger problem is how you create scalable trust infrastructures, because without such, Merchant signatures are merely "decoration" :-)

@marcoscaceres
Copy link
Member

Given the work happening in tokenized payments, I'm closing this so discussions can happen there.

@adrianhopebailie
Copy link
Collaborator

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants