Yet another "Parse error reading JWS"

Hi there,

I'm currently trying to implement ACME according to RFC 8555 as part of our web server's automated SSL issuance.

I'm getting the following error and after lots of trial and error - I'm stuck. This is the error I'm getting:

{
  type: 'urn:ietf:params:acme:error:malformed',
  detail: 'Parse error reading JWS',
  status: 400
}

Here is the POST request body that I'm sending to LetsEncrypt's Staging ACME newAccount endpoint; along with a random key pair I used:

JWS after encoding/signature
{
  protected: 'eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoiamE5SXJhNEdnVUVPMXlZcDF5YnBtNnoydU5aMmN3LUNSTkVHSEhzNHY4WVRCa1NnWTZDM1RzdGMxLXBwSXV2ZklZOVhaRGVtMzd6VkZPNUxZZHY3SF9BV1J6VV9neDV1Uk1hTXNXNzlXVGRiZlVHRzAyeDVjSzlfU0p2TDNCNHlfZlVuZGpfanBWQlVvMEo0ZElqazYxY2ZxWFEyVVdBa2NtNVVrekljLXVsd1czcWdkWUpETm5iTFFUVWR5d200Y0VxckJrTkUzdl9GTWEwZnNSd3NSaV9ISWpGZmRGNHFKZE9zb2tfcG9yQVBsVklBTlRRTUxscG9XLURZVzN3Y2RMTmpBMmxBWmRPX1hhOUVkS1l6R01DRlFMSHpXMkVWSzJwa2xXUDJBUjRaS2lsZ3d4cEdhZkR1U0tnYnpxdFRRQk5oRjQ5bTJSNWR4RmMxTDA4ajZ3IiwiZSI6IkFRQUIifSwibm9uY2UiOiIwMDAzM2c1bERsaUhLTl96WFRtRnZuV2NQR3Jab2FXVnVxZ01HeWc0cG5NQUUxVSIsInVybCI6Imh0dHBzOi8vYWNtZS1zdGFnaW5nLXYwMi5hcGkubGV0c2VuY3J5cHQub3JnL2FjbWUvbmV3LWFjY3QifQ',
  payload: 'eyJ0ZXJtc09mU2VydmljZUFncmVlZCI6dHJ1ZSwiY29udGFjdCI6WyJtYWlsdG86c3VwcG9ydEBnZW50bGVudC5jb20iXX0',
  signature: 'eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoiamE5SXJhNEdnVUVPMXlZcDF5YnBtNnoydU5aMmN3LUNSTkVHSEhzNHY4WVRCa1NnWTZDM1RzdGMxLXBwSXV2ZklZOVhaRGVtMzd6VkZPNUxZZHY3SF9BV1J6VV9neDV1Uk1hTXNXNzlXVGRiZlVHRzAyeDVjSzlfU0p2TDNCNHlfZlVuZGpfanBWQlVvMEo0ZElqazYxY2ZxWFEyVVdBa2NtNVVrekljLXVsd1czcWdkWUpETm5iTFFUVWR5d200Y0VxckJrTkUzdl9GTWEwZnNSd3NSaV9ISWpGZmRGNHFKZE9zb2tfcG9yQVBsVklBTlRRTUxscG9XLURZVzN3Y2RMTmpBMmxBWmRPX1hhOUVkS1l6R01DRlFMSHpXMkVWSzJwa2xXUDJBUjRaS2lsZ3d4cEdhZkR1U0tnYnpxdFRRQk5oRjQ5bTJSNWR4RmMxTDA4ajZ3IiwiZSI6IkFRQUIifSwibm9uY2UiOiIwMDAzM2c1bERsaUhLTl96WFRtRnZuV2NQR3Jab2FXVnVxZ01HeWc0cG5NQUUxVSIsInVybCI6Imh0dHBzOi8vYWNtZS1zdGFnaW5nLXYwMi5hcGkubGV0c2VuY3J5cHQub3JnL2FjbWUvbmV3LWFjY3QifQ.eyJ0ZXJtc09mU2VydmljZUFncmVlZCI6dHJ1ZSwiY29udGFjdCI6WyJtYWlsdG86c3VwcG9ydEBnZW50bGVudC5jb20iXX0.bQSelIn4TfUHFBzUjgg-ufNGMozy6r3hQUo76dm4p57XUiEVcCqZiWCg9HPCNDw7Avgc1CxjCVtrP_w0OP0MtmqK04S5zHzw3ipz0EExorb3TuPXIHV8P5n2cyMYqx0fj155m36NWwuGwalwn6b7j0NuJH0-fxxbbIbLyDW3cCGwNBLkOVmcim95cFOuuVqCt5kFP-FUU16zNoWf-ESui37fzyg-9uf0sBTMEGZeQNTFc5drxvOq6rFh8wQjvvQT0wSwstct0IBDfwH729E0JYkvhsQClp0zpxzjVyxyoMP9pfpbYHjow05INgBRndoi73LRJBWjtRNAOXzB7rC1bw'
}
JWS before encoding/signature
{
  protected: {
    alg: 'RS256',
    jwk: {
      kty: 'RSA',
      n: 'ja9Ira4GgUEO1yYp1ybpm6z2uNZ2cw-CRNEGHHs4v8YTBkSgY6C3Tstc1-ppIuvfIY9XZDem37zVFO5LYdv7H_AWRzU_gx5uRMaMsW79WTdbfUGG02x5cK9_SJvL3B4y_fUndj_jpVBUo0J4dIjk61cfqXQ2UWAkcm5UkzIc-ulwW3qgdYJDNnbLQTUdywm4cEqrBkNE3v_FMa0fsRwsRi_HIjFfdF4qJdOsok_porAPlVIANTQMLlpoW-DYW3wcdLNjA2lAZdO_Xa9EdKYzGMCFQLHzW2EVK2pklWP2AR4ZKilgwxpGafDuSKgbzqtTQBNhF49m2R5dxFc1L08j6w',
      e: 'AQAB'
    },
    nonce: '00033g5lDliHKN_zXTmFvnWcPGrZoaWVuqgMGyg4pnMAE1U',
    url: 'https://acme-staging-v02.api.letsencrypt.org/acme/new-acct'
  },
  payload: {
    termsOfServiceAgreed: true,
    contact: [ 'mailto:support@gentlent.com' ]
  }
}
RSA 2048-bit Key Pair
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAja9Ira4GgUEO1yYp1ybpm6z2uNZ2cw+CRNEGHHs4v8YTBkSg
Y6C3Tstc1+ppIuvfIY9XZDem37zVFO5LYdv7H/AWRzU/gx5uRMaMsW79WTdbfUGG
02x5cK9/SJvL3B4y/fUndj/jpVBUo0J4dIjk61cfqXQ2UWAkcm5UkzIc+ulwW3qg
dYJDNnbLQTUdywm4cEqrBkNE3v/FMa0fsRwsRi/HIjFfdF4qJdOsok/porAPlVIA
NTQMLlpoW+DYW3wcdLNjA2lAZdO/Xa9EdKYzGMCFQLHzW2EVK2pklWP2AR4ZKilg
wxpGafDuSKgbzqtTQBNhF49m2R5dxFc1L08j6wIDAQABAoIBAGNutRm2PLhvGOQi
01A77cwj6Y40bDI5mxmCe8B3NY9YibdnaAwjGSICFCkvSBOK5y/dMd6SM9lm4z9q
MJr/z9BNJMVaeDUjoQ1BLjHDi6cV824HE4DC6sMLVs91Du9ufii96LDTC4bTxmJA
3tohzE3g+cZpWUloqItqJI6cp7vqD0Zo3gP0hi4MBoMRTsj/yBtHojrPXIHZhWY9
LzrGIsrhhkEHNklFrz1dhoY5qekTuBhxzsH7iBk8x0IFKCdpqrV5TY3QSHc0dWPk
OebiMoVMk50GSDPNi/LXVu+77Qu0gXNZqofNupNMW7xIy0srQxZKFrTKVj/JJ9sA
+uVOetECgYEA7/EYR4iswx8cqR+U1ldQ20d8JjssUrysIIurZB2558D9lBTiFL3w
3qOzhZyvh77v09uT8jxNDhKwKkkgVtEh1hIrV6NJCOFy0HzTO0d6pc5paJenzoDD
vdWeNvU1XWsTi7HNoISiE6ivZXq9c/P2Q75IXq9cX/HU1tH3hXfRXDMCgYEAlyrB
+FDhiI9BTjmmdVCQTxitfg+8+WWJa8xK367ZsKKsFZfirIW5WjU/mIKFucpWnFcj
3i7nFMqDMZs8MbKYpcfn9W2Ho2sJaiGfcHLXCkSeKcr5/AJ8WM7Yw6txY9mFZ+qt
3LkTzRF9vgjdJjFsZ8JravdaSi4HloUNNu57YWkCgYABYViraKzdvnsSufzIMt5j
NIkRSzvZCf7PfR4ySD1+iMysomYeim0tSrHIrfsQg9erV1MFjLVAxHjA9YS7Gx7f
tWEMcwMDixB3BJDOgX3CIGrY0C6F8JWvrN/pHwX8kJYfMpmlN74a8Nh5c/hNB3U+
NIUEDmwwnRqFVdXfra3FowKBgBr8HAIq1gfdosfHDMr2Zbxo6FePkalnkb93iYoi
mWOptlRod6Naf7r1tHOSITMIRqvD9ngsIT4Z5I+PaZBplExLwkGSmtr2OYkDlYWq
Sm7fzugg/r0Y6H+l9RYA3c34P5NMVlsHgtxhmfO5e1jdC7/9+dTvcOO7Ecmx2KkM
q2O5AoGBAIsZfWUhX3U40Lr9bmOFnjnnbNpqShVfs5nDntMht2LzZOsKpP46T0Wk
FQy3gITTO8+h7E+ZD3TwP7rcTPFOajDhH9LNV9q4fxeyqTVy/OCJvUXRx29I+556
+uoXrNA2BKnZ2sESjEdFsTVVMe9r7YxU3WC6rkfuDAUOzqU2+d3c
-----END RSA PRIVATE KEY-----

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAja9Ira4GgUEO1yYp1ybp
m6z2uNZ2cw+CRNEGHHs4v8YTBkSgY6C3Tstc1+ppIuvfIY9XZDem37zVFO5LYdv7
H/AWRzU/gx5uRMaMsW79WTdbfUGG02x5cK9/SJvL3B4y/fUndj/jpVBUo0J4dIjk
61cfqXQ2UWAkcm5UkzIc+ulwW3qgdYJDNnbLQTUdywm4cEqrBkNE3v/FMa0fsRws
Ri/HIjFfdF4qJdOsok/porAPlVIANTQMLlpoW+DYW3wcdLNjA2lAZdO/Xa9EdKYz
GMCFQLHzW2EVK2pklWP2AR4ZKilgwxpGafDuSKgbzqtTQBNhF49m2R5dxFc1L08j
6wIDAQAB
-----END PUBLIC KEY-----

I hope someone is able to point me in the right direction so I can solve this.

-Tom

2 Likes

What code are you using for signing the JWS?

Also, I'm totally not an expert, but should the signature of the signed JWS be a "$payload.$signature" combination? Sounds a little bit redundant to me.

3 Likes

@Osiris Well... that actually solved it. I tried fixing this for hours and again, it's such a "simple" mistake.

Thanks so much! :sweat_smile:

4 Likes

Note that you should use a different private key from now on, as you've published this one (even though it's a staging account I assume).

4 Likes

Hi @tomklein

yes, there is a simple mistake.

That's

{"alg":"RS256","jwk":{"kty":"RSA","n":"ja9Ira4GgUEO1yYp1ybpm6z2uNZ2cw-CRNEGHHs4v8YTBkSgY6C3Tstc1-ppIuvfIY9XZDem37zVFO5LYdv7H_AWRzU_gx5uRMaMsW79WTdbfUGG02x5cK9_SJvL3B4y_fUndj_jpVBUo0J4dIjk61cfqXQ2UWAkcm5UkzIc-ulwW3qgdYJDNnbLQTUdywm4cEqrBkNE3v_FMa0fsRwsRi_HIjFfdF4qJdOsok_porAPlVIANTQMLlpoW-DYW3wcdLNjA2lAZdO_Xa9EdKYzGMCFQLHzW2EVK2pklWP2AR4ZKilgwxpGafDuSKgbzqtTQBNhF49m2R5dxFc1L08j6w","e":"AQAB"},"nonce":"00033g5lDliHKN_zXTmFvnWcPGrZoaWVuqgMGyg4pnMAE1U","url":"https://acme-staging-v02.api.letsencrypt.org/acme/new-acct"}

But e < kty, so the order of the jwk object is wrong.

3 Likes

@Osiris Definitely using a new one.

@JuergenAuer Shouldn't the order of the objects inside JSON not matter (despite code readability)? It seems to work without order.

1 Like

When you sign the object, the order matters tremendously.

3 Likes

If a signature is computed, the order is relevant.

sig("AB") <> sig("BA").

Reread my own code. The signature is computed

compute_Signature(protected + "." + payload)

"protected" contains the jwk.

But I see: My own code has a function

generate_JSON_public_Key_From_Certificate

with a different order:

kty
n
e
alg

Looks like this is the canonical order.

5 Likes

Below is a snippet of test code based on my client that works. I can confirm that the PHP JSON encoder does not sort the keys as indicated by the two different thumbprints. The first thumbprint is correct.

<?php

$configargs = [
  "digest_alg" => "sha256",
  "private_key_bits" => 2048,
  "private_key_type" => OPENSSL_KEYTYPE_RSA
];

$privateKeyObject = openssl_pkey_new($configargs);

openssl_pkey_export($privateKeyObject, $privateKey);

$privateKeyDetails = openssl_pkey_get_details($privateKeyObject);

$jwk = [
  "e" => strtr(rtrim(base64_encode($privateKeyDetails["rsa"]["e"]), '='), '+/', '-_'),
  "kty" => "RSA",
  "n" => strtr(rtrim(base64_encode($privateKeyDetails["rsa"]["n"]), '='), '+/', '-_')
];

$thumbprint = strtr(rtrim(base64_encode(openssl_digest(json_encode($jwk, JSON_UNESCAPED_SLASHES), "sha256", true)), '='), '+/', '-_');

$jwk2 = [
  "kty" => "RSA",
  "n" => strtr(rtrim(base64_encode($privateKeyDetails["rsa"]["n"]), '='), '+/', '-_'),
  "e" => strtr(rtrim(base64_encode($privateKeyDetails["rsa"]["e"]), '='), '+/', '-_')
];

$thumbprint2 = strtr(rtrim(base64_encode(openssl_digest(json_encode($jwk2, JSON_UNESCAPED_SLASHES), "sha256", true)), '='), '+/', '-_');

$protected = [
  "url" => "account url",
  "alg" => "RS256",
  "nonce" => "current nonce",
  "jwk" => $jwk
];

$payload = [
  "termsOfServiceAgreed" => true
];

echo "<pre>";
print_r($thumbprint);
echo "<br><br>";
print_r($thumbprint2);
echo "<br><br>";
print_r($protected);
echo "<br>";
print_r($payload);
echo "</pre>";

?>

Can be tested with:

https://extendsclass.com/php.html

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