ECDSA key get JWS verification error

{“header”: {“alg”: “ES256”, “jwk”: {“crv”: “P-256”, “kty”: “EC”, “x”: “-7s7gqSW5TpiUblqrIOYDMgZsvY-Y8aGBGwc3i0kwNw”, “y”: “ZnRGX6lXVd38BHq9LClkluD_ewQRw2e_NGUZBrq5Cwo”}}, “protected”: “eyJhbGciOiAiRVMyNTYiLCAiandrIjogeyJjcnYiOiAiUC0yNTYiLCAia3R5IjogIkVDIiwgIngiOiAiLTdzN2dxU1c1VHBpVWJscXJJT1lETWdac3ZZLVk4YUdCR3djM2kwa3dOdyIsICJ5IjogIlpuUkdYNmxYVmQzOEJIcTlMQ2xrbHVEX2V3UVJ3MmVfTkdVWkJycTVDd28ifSwgIm5vbmNlIjogInFzTGt0VWktNjF4Qi1qanNxYzEyaXNiVG1mMXpwaFo0czBoWGN6WjNIb0EifQ”, “payload”: “eyJyZXNvdXJjZSI6ICJuZXctcmVnIiwgImFncmVlbWVudCI6ICJodHRwczovL2xldHNlbmNyeXB0Lm9yZy9kb2N1bWVudHMvTEUtU0EtdjEuMi1Ob3ZlbWJlci0xNS0yMDE3LnBkZiJ9”, “signature”: “MEUCIQC247WywLFIjZqtqvDfZ8cwDD7AvVjQEiZvfDT2R94KoAIgWfLe5_ZCxXSbaUmfGn6RpYF69a4WTkCWb4AKn9mAMZk”}

Above is the JWS that I am sending to the new-reg resource and below is my private key.
When I submit this I always get a “JWS verification error” which I assume is because my signature is incorrect. I have tried several ways of creating the signature but none have worked.
Any help or tips would be much appreciated.
I am writing the client in C FYI.

-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgDPyrAzckp0Ll1uWU
ocFEo2pVExxK59ZKFwccfp6S6zGhRANCAAT7uzuCpJblOmJRuWqsg5gMyBmy9j5j
xoYEbBzeLSTA3GZ0Rl+pV1Xd/AR6vSwpZJbg/3sEEcNnvzRlGQa6uQsK
-----END PRIVATE KEY-----

Thanks in advance :slight_smile:

Leaking your account PRIVATE key isn’t very wise. Anybody could revoke a certificate issued with this account…

Or is it “just” a staging server account?

1 Like

ACME v1 or v2 / which draft version? Looks like v1, just making sure.

Do you really need to use C? In 2018 :slight_smile: ? It seems there may already be existing ACME libraries out there for it anyway: https://github.com/kristapsdz/acme-client-portable

Other than that, if you are struggling with generating a valid signature, could you show what you have tried so far? Have you tried using an existing JOSE library, such as https://github.com/cisco/cjose ?

Maybe this example can help you get valid signatures going:

#include <cjose/cjose.h>
#include <jansson.h>
#include <stdio.h>
#include <string.h>

// Private form of JWK derived from private key PEM
static const char *JWK = "{ \"kty\": \"EC\","
  "\"crv\": \"P-256\","
  "\"d\": \"DPyrAzckp0Ll1uWUocFEo2pVExxK59ZKFwccfp6S6zE\","
  "\"x\": \"-7s7gqSW5TpiUblqrIOYDMgZsvY-Y8aGBGwc3i0kwNw\","
  "\"y\": \"ZnRGX6lXVd38BHq9LClkluD_ewQRw2e_NGUZBrq5Cwo\" }";

// Public form of JWK for embedding in header
static const char *JWK_PUB = "{\"crv\": \"P-256\","
  "\"kty\": \"EC\","
  "\"x\": \"-7s7gqSW5TpiUblqrIOYDMgZsvY-Y8aGBGwc3i0kwNw\","
  "\"y\": \"ZnRGX6lXVd38BHq9LClkluD_ewQRw2e_NGUZBrq5Cwo\"}";

static const char *PLAINTEXT = "{\"terms-of-service-agreed\": true}";

int main() {
  cjose_err err;

  // import the JWK key from JSON
  cjose_jwk_t *jwk = cjose_jwk_import(JWK, strlen(JWK), &err);
  if(jwk == NULL) {
    fprintf(stderr, "Failed to import JWK: %s\n", err.message);
    return 1;
  }  

  // Set the protected headers
  // TODO: you also need to set the nonce, and url
  cjose_header_t *hdr = cjose_header_new(&err);
  cjose_header_set(hdr, CJOSE_HDR_ALG, CJOSE_HDR_ALG_ES256, &err);
  cjose_header_set_raw(hdr, "jwk", JWK_PUB, &err);

  // Sign the JWS: payload with the headers
  cjose_jws_t *jws = cjose_jws_sign(jwk, hdr, PLAINTEXT, strlen(PLAINTEXT), &err);
  if(jws == NULL) {
    fprintf(stderr, "Failed to sign JWS header: %s\n", err.message);    
    return 1;
  }

  // Serialize the result.
  // This is given as compact serialization, i.e.
  // https://tools.ietf.org/html/rfc7515#section-3.1
  // but can easily be converted to standard serialization
  const char *serialized = NULL;
  if(!cjose_jws_export(jws, &serialized, &err)) {
    fprintf(stderr, "Failed to serialize JWS: %s\n", err.message);     
    return 1;   
  }

  fprintf(stdout, "%s\n", serialized);

  cjose_jwk_release(jwk);

  return 0;
}

I was able to send a request that passed JWS verification to the v1 staging server using your EC private key parameters with it.

$ ./blah
eyJhbGciOiAiRVMyNTYiLCAiandrIjogeyJjcnYiOiAiUC0yNTYiLCAia3R5IjogIkVDIiwgIngiOiAiLTdzN2dxU1c1VHBpVWJscXJJT1lETWdac3ZZLVk4YUdCR3djM2kwa3dOdyIsICJ5IjogIlpuUkdYNmxYVmQzOEJIcTlMQ2xrbHVEX2V3UVJ3MmVfTkdVWkJycTVDd28ifX0.eyJ0ZXJtcy1vZi1zZXJ2aWNlLWFncmVlZCI6IHRydWV9.A9bfjF4hRtTD4dfO0Ke-q_RTUJNWFA_2McnWPNZotyvRDFp7-sZ1PhEnc9mgr9hJCs2MQHUxchWISLWvnNtcTg

$ curl -X POST -i -H 'Content-Type: application/jose+json' --data '{"signature":"A9bfjF4hRtTD4dfO0Ke-q_RTUJNWFA_2McnWPNZotyvRDFp7-sZ1PhEnc9mgr9hJCs2MQHUxchWISLWvnNtcTg","protected":"eyJhbGciOiAiRVMyNTYiLCAiandrIjogeyJjcnYiOiAiUC0yNTYiLCAia3R5IjogIkVDIiwgIngiOiAiLTdzN2dxU1c1VHBpVWJscXJJT1lETWdac3ZZLVk4YUdCR3djM2kwa3dOdyIsICJ5IjogIlpuUkdYNmxYVmQzOEJIcTlMQ2xrbHVEX2V3UVJ3MmVfTkdVWkJycTVDd28ifX0","payload":"eyJ0ZXJtcy1vZi1zZXJ2aWNlLWFncmVlZCI6IHRydWV9"}' https://acme-staging.api.letsencrypt.org/acme/new-reg
HTTP/1.1 400 Bad Request
Server: nginx
Content-Type: application/problem+json
Content-Length: 100
Replay-Nonce: aTVzjoiKPeXOCWj79HGVJqixKWwco7S7lsnbaV-Up5E
Expires: Tue, 20 Mar 2018 10:37:45 GMT
Cache-Control: max-age=0, no-cache, no-store
Pragma: no-cache
Date: Tue, 20 Mar 2018 10:37:45 GMT
Connection: close

{
  "type": "urn:acme:error:badNonce",
  "detail": "JWS has no anti-replay nonce",
  "status": 400
}

Since the replay nonce is checked after the JWS in Boulder, we know that the JWS was valid.

2 Likes

Thanks for getting back to me. Unfortunately I have to use openSSL to do my signature (should have mentioned sorry). Oh well guess I’ll continue, thanks for your time anyways :slight_smile:

cjose is just a wrapper over openssl anyway, so you should be able to compare your implementation to what they do: https://github.com/cisco/cjose/blob/master/src/jws.c#L520-L534

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.