Score:2

ECDSA-SHA256 HTTP Signature String Construction

zw flag

I must verify an HTTP signature to guarantee the origin and integrity of a webhook data: https://www.blockcypher.com/dev/bitcoin/#webhook-signing

This is their x509 PKIX encoded signing key's public key: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEflgGqpIAC9k65JicOPBgXZUExen4rWLq05KwYmZHphTU/fmi3Oe/ckyxo2w3Ayo/SCO/rU2NB90jtCJfz9i1ow==

I am following this specification to construct the signing string: https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#section-2.3

Here is a POST request from said webhook:

POST /api/blockcypher HTTP/1.1
Host: (...removed...)
Accept-Encoding: gzip
Content-Length: 1551
Content-Type: application/json
Date: Thu, 16 Feb 2023 11:55:43 UTC
Digest: SHA-256=BYzO03UhXY2O4gzJd+8g367LSEtO3cAMJfOjL0F0HiE=
Signature: keyId="https://www.blockcypher.com/dev/bitcoin/#webhook-signing",algorithm="ecdsa-sha256",signature="ZJfhAFQP19xfnWZo9KD9hyzoyR8fZ5uEyh6Ltu3z/vWL7Jr+aezw8n9U9Yq41APWJG+vnhmS8KoGV48X34vMYQ==",headers="(request-target) digest date"
User-Agent: BlockCypher HTTP Invoker
X-Eventid: 2cb85bdf-6181-49e6-9d9a-5e419898ee33
X-Eventtype: unconfirmed-tx
X-Ratelimit-Remaining: 174
{
    (...removed...)
}

I've successfully matched the provided Digest header with a constructed digest of my own using the request's (removed) content (I've removed the content to reduce visual bloat).

From what I can gather with the specification provided above, this would be the constructed signature string for this particular request:

(request-target): post /api/blockcypher\n
digest: SHA-256=BYzO03UhXY2O4gzJd+8g367LSEtO3cAMJfOjL0F0HiE=\n
date: Thu, 16 Feb 2023 11:55:43 UTC

The specification mentions the (created) and (expired) headers' values "MUST be a Unix timestamp integer value", but doesn't talk about the Date header. I've tried both Thu, 16 Feb 2023 11:55:43 UTC and 1676548543 without success.

The specification is confusing to me in some aspects. For instance, the signature string where the (created) field is included, does not end with a new line (\n).

My question is, is my signature string well constructed? If not, what am I doing wrong?

To sum up:

PUBLIC KEY (x509 PKIX encoded, I don't know what that means)

MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEflgGqpIAC9k65JicOPBgXZUExen4rWLq05KwYmZHphTU/fmi3Oe/ckyxo2w3Ayo/SCO/rU2NB90jtCJfz9i1ow==

SIGNATURE

ZJfhAFQP19xfnWZo9KD9hyzoyR8fZ5uEyh6Ltu3z/vWL7Jr+aezw8n9U9Yq41APWJG+vnhmS8KoGV48X34vMYQ==

MESSAGE (I think)

(request-target): post /api/blockcypher\ndigest: SHA-256=BYzO03UhXY2O4gzJd+8g367LSEtO3cAMJfOjL0F0HiE=\ndate: Thu, 16 Feb 2023 11:55:43 UTC

The verification fails. I'm guessing it's because the signature string is not properly constructed.

Score:3
cn flag

Works for me:

PublicKey pubkey = KeyFactory.getInstance("EC").generatePublic( new X509EncodedKeySpec(
    Base64.getDecoder().decode("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEflgGqpIAC9k65JicOPBgXZUExen4rWLq05KwYmZHphTU/fmi3Oe/ckyxo2w3Ayo/SCO/rU2NB90jtCJfz9i1ow==") ));
Signature s = Signature.getInstance("SHA256withECDSAinP1363format"); s.initVerify(pubkey);
s.update("(request-target): post /api/blockcypher\ndigest: SHA-256=BYzO03UhXY2O4gzJd+8g367LSEtO3cAMJfOjL0F0HiE=\ndate: Thu, 16 Feb 2023 11:55:43 UTC".getBytes());
System.out.println ( s.verify( Base64.getDecoder().decode("ZJfhAFQP19xfnWZo9KD9hyzoyR8fZ5uEyh6Ltu3z/vWL7Jr+aezw8n9U9Yq41APWJG+vnhmS8KoGV48X34vMYQ==") ) );
->
true

Note that signature is in 'plain' P1363, CVC, or PKCS11 format, not ASN.1 DER format. Some verifiers require one, some the other, and some are selectable like the Java (9 up) provider I used. (And some generators create one, or the other, or both.) Are you using something that accepts plain format?

See Shouldn't a signature using ECDSA be exactly 96 bytes, not 102 or 103?
and ECDSA Signature R|S to ASN1 DER Encoding question
and cross https://stackoverflow.com/questions/61775022/openssl-ecdsa-sign-input-as-is-without-digest
and https://stackoverflow.com/questions/52122705/java-ecdsawithsha256-signature-with-inconsistent-length
and more dupes you can find by following 'Linked'

Carlos avatar
zw flag
Good to know the problem isn't with the signature construction. First I attempted PHP's [openssl_verify](https://www.php.net/manual/en/function.openssl-verify.php), but it [doesn't work](https://stackoverflow.com/a/32992458/20833405). Then I tried [phpseclib3](https://phpseclib.com/docs/publickeys) also [without success](https://crypto.stackexchange.com/questions/104242/verifying-ecdsa-sha256-http-signature?noredirect=1&lq=1).
dave_thompson_085 avatar
cn flag
Carlos: PHP openssl_ functions use OpenSSL (which should not be a surprise) and OpenSSL supports only DER format for ECDSA (and also DSA FWIW). I don't use phpseclib, but under your link, at https://phpseclib.com/docs/ec#creating--verifying-signatures . it says for ECDSA it supports a signature format 'SSH2' -- which is actually the SSH2 format for DSA-aka-DSS not(!) ECDSA -- but looks like it might work. (I can't easily test.)
I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.