Elliptic Curve account key info

I thought it was about time I added the option for Elliptic Curve Account Keys, but getting an error. I’m assuming this is because I have the parameters from the key incorrect.

Given a test key

-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIIKnIM3m3nLuPYHqDmEeNlC5pz4IXiFiV3SFLKKwloTLoAoGCCqGSM49
AwEHoUQDQgAEj+tDu+ai1Sb0vFRRz9Pw1LplyHk0J1xvO3GAK+n4vSE5qpEkg7Bb
6Ilv5E20cxyTrivey+DPQ0o639GXHVqEVw==
-----END EC PRIVATE KEY-----

If I’m calculating it correctly, then I think
x=8feb43bbe6a2d526f4bc5451cfd3f0d4ba65c87934275c6f3b71802be9f8bd21
y=39aa912483b05be8896fe44db4731c93ae2bdecbe0cf434a3adfd1971d5a8457

and hence
jwk = {“kty”: “EC”, “crv”: “P-256”, “x”: “j-tDu-ai1Sb0vFRRz9Pw1LplyHk0J1xvO3GAK-n4vSE”, “y”: “OaqRJIOwW-iJb-RNtHMck64r3svgz0NKOt_Rlx1ahFc”}

I’m assuming this is wrong somewhere though as I get an error;

{
  "type": "urn:acme:error:malformed",
  "detail": "JWS verification error",
  "status": 400
}

when trying to register the account.

Can anyone tell me what the correct x,y and jwk should be for that key ?

Thanks

I’m not that familiar with the ACME protocol (just looking into it b/c of your question), but can you point me where it says you need a x and a y?

https://tools.ietf.org/html/rfc7638#section-3.2

https://tools.ietf.org/html/rfc7518#section-6.2.1.1

What steps did you make calculating x and y?

Basically use “openssl ec -in file.key -noout -text” to get the public key.

pub: 
    04:8f:eb:43:bb:e6:a2:d5:26:f4:bc:54:51:cf:d3:
    f0:d4:ba:65:c8:79:34:27:5c:6f:3b:71:80:2b:e9:
    f8:bd:21:39:aa:91:24:83:b0:5b:e8:89:6f:e4:4d:
    b4:73:1c:93:ae:2b:de:cb:e0:cf:43:4a:3a:df:d1:
    97:1d:5a:84:57

Then remove the first number (04). Split the string in half … the first half being the x and the second being the y

Hmm, assuming you’ve got the right JWK/JSON hex2base64url algo, looks good to me?

It’s the same hex2base64url algo that I use ( and works fine ) when using the RSA key, so I assume so.

Hence why I was a little lost as to why it wasn’t working :wink:

What is your “alg” field? It needs to be “ES256”, “ES384” and “ES512” for “P-256”, “P-384” and “P-521” curves, respectively. You also need to sign the message with SHA256, SHA384 or SHA512, respectively.

Finally, you can’t use P-521 yet, but that would yield a different error message telling you so.

Edit: You could cross-check your x, y parameters with Perl:

$key = Crypt::PK::ECC->new($keyfile);
print Dumper($key->export_key_jwk('public'));

Thanks, was using ES256 (for this key, and the appropriate ones for the other keys). And yes, I was signing it with the SHA256 in this case.

Thanks, will check the perl.

Just did that quickly and I get

{"crv":"P-256","kty":"EC","x":"j-tDu-ai1Sb0vFRRz9Pw1LplyHk0J1xvO3GAK-n4vSE","y":"OaqRJIOwW-iJb-RNtHMck64r3svgz0NKOt_Rlx1ahFc"}

Thanks - that helps. So my jwk (x, y etc) are correct. I’ll double check my signing etc.

I just tried a single new-authz request and got

{
  "detail" => "No registration exists matching provided key",
  "status" => 403,
  "type" => "urn:acme:error:unauthorized"
}

as expected. So something is wrong in your signing. What module do you use to sign? I use Perl Crypt::PK::ECC and I have to use the special function $key->sign_message_rfc7518(). Maybe that’s a clue.

I gave a new example key above ( so wasn’t one I’d used … but I got the same response on all other keys I was trying … )

I’m writing completely in bash ( getssl ) so was signing with openssl (openssl dgst -"$signalg" -sign “${ACCOUNT_KEY}” ) as I do with RSA keys.

will check that, thanks - it would appear to be the signing ( as the raw info is correct )

I strongly suspect you’re running into http://search.cpan.org/~mik/CryptX/lib/Crypt/PK/ECC.pm#sign_message_rfc7518

“Same as sign_message only the signature format is as defined by https://tools.ietf.org/html/rfc7518 (JWA - JSON Web Algorithms).”

If you can read C, this is what the code does:

if (sigformat == 1) {
  /* RFC7518 format */
  if (*outlen < 2*pbytes) { err = CRYPT_MEM; goto errnokey; }
  zeromem(out, 2*pbytes); 
  i = mp_unsigned_bin_size(r);
  if ((err = mp_to_unsigned_bin(r, out + (pbytes - i)))   != CRYPT_OK) goto errnokey;
  i = mp_unsigned_bin_size(s);
  if ((err = mp_to_unsigned_bin(s, out + (2*pbytes - i))) != CRYPT_OK) goto errnokey;
  *outlen = 2*pbytes;
  err = CRYPT_OK;
}
else {
  /* store as ASN.1 SEQUENCE { r, s -- integer } */
  err = der_encode_sequence_multi(out, outlen,
                            LTC_ASN1_INTEGER, 1UL, r,
                            LTC_ASN1_INTEGER, 1UL, s,
                            LTC_ASN1_EOL, 0UL, NULL);
}

Thanks, Can just about get by in C - that makes sense, and yes I suspect you are right in that's where my issue is. I'll throw together a little bit of bash to test.

can you tell me what the output of signing your perl response

{"crv":"P-256","kty":"EC","x":"j-tDu-ai1Sb0vFRRz9Pw1LplyHk0J1xvO3GAK-n4vSE","y":"OaqRJIOwW-iJb-RNtHMck64r3svgz0NKOt_Rlx1ahFc"}

with that key is ? (or the perl you use ). That way I have something to compare and know when it looks as if my bash version is right :wink:

Note the debug log contains perl hashes, not JSON. What is being signed and base64-encoded is of course JSON.

2016-10-23 20:11:46+0000: main::send_signed_req:197: HTTP POST to https://acme-staging.api.letsencrypt.org/acme/new-authz
2016-10-23 20:11:46+0000: main::send_signed_req:198: HTTP POST plaintext:
$VAR1 = {
  "payload" => {
    "identifier" => {
      "type" => "dns",
      "value" => "foo.bar"
    },
    "resource" => "new-authz"
  },
  "protected" => {
    "alg" => "ES256",
    "jwk" => {
      "crv" => "P-256",
      "kty" => "EC",
      "x" => "j-tDu-ai1Sb0vFRRz9Pw1LplyHk0J1xvO3GAK-n4vSE",
      "y" => "OaqRJIOwW-iJb-RNtHMck64r3svgz0NKOt_Rlx1ahFc"
    },
    "nonce" => "K_q9YbRdLDyYjs2757-BkIgDfnlwVE3mQJhmyQC4b74",
    "url" => undef
  }
};

2016-10-23 20:11:46+0000: main::send_signed_req:199: HTTP POST base64:
$VAR1 = {
  "payload" => "eyJpZGVudGlmaWVyIjp7InR5cGUiOiJkbnMiLCJ2YWx1ZSI6ImZvby5iYXIifSwicmVzb3VyY2UiOiJuZXctYXV0aHoifQ",
  "protected" => "eyJhbGciOiJFUzI1NiIsImp3ayI6eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImotdER1LWFpMVNiMHZGUlJ6OVB3MUxwbHlIazBKMXh2TzNHQUstbjR2U0UiLCJ5IjoiT2FxUkpJT3dXLWlKYi1STnRITWNrNjRyM3N2Z3owTktPdF9SbHgxYWhGYyJ9LCJub25jZSI6IktfcTlZYlJkTER5WWpzMjc1Ny1Ca0lnRGZubHdWRTNtUUpobXlRQzRiNzQiLCJ1cmwiOm51bGx9",
  "signature" => "crm3PtXFIqEbQiJMIET3cXxss3Vq8A7n-PjvWuN3cCReQHuqD-ZW2A8kQnNJ_5i47_Aa4q-VdM0rkLddgHn9SQ"
};

2016-10-23 20:11:46+0000: main::send_signed_req:204: HTTP POST to https://acme-staging.api.letsencrypt.org/acme/new-authz: 403 Forbidden
1 Like

Many Thanks for that :slight_smile:

I just noticed the undef “url” key. Make sure your “protected” base64 is correct :slight_smile: I’m not sure why the “url” key isn’t needed. It’s something I wanted to check later.

I’m assuming ( for now ) that the “protected” base64 is correct, since there doesn’t seem to be any difference in that whether the key is RSA and EC (and everything is fine with RSA). I’d just (wrongly) assumed that the sha256 signing was the same in both cases :wink:

Thanks @TCM - you were completely correct, and that solved the problem. Bash version working nicely now in my tests :slight_smile: