ACME v1 or v2 / which draft version? Looks like v1, just making sure.
Do you really need to use C? In 2018 ? 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.