"Parse error reading JWS" but JWT is valid

Hi,
I read through a lot of posts on similar errors but can't get to get it to work completely.
Developing a custom client and trying to create a new account, using Pebble hosted locally as ACME server.
I tried different formats for the request to be send to pebble which result in different error messages.
Using Python as programming language;

My JWT is valid: jwt.io

Error message 1 that I get:

{'type': 'urn:ietf:params:acme:error:malformed', 'detail': "Parse error reading JWS: invalid character 'e' looking for beginning of value", 'status': 400}

with payload created as follows:

public_key = self.signature_acme.get_public_key_json()
protected = {
    "alg": "RS256",
    "jwk": public_key,
    "nonce": self.nonce,
    "url": self.endpoints["newAccount"]
}
payload = {
    "termsOfServiceAgreed": "true",
    "contact": [
        "mailto:test@gmail.com"
    ]
}
encoded_protected = self.signature_acme.base64url_encode(protected)
encoded_payload = self.signature_acme.base64url_encode(payload)
msg = f"{encoded_protected}.{encoded_payload}"
signature = self.signature_acme.sign_string(msg)
final_payload_data_1 = f"{msg}.{signature}"
# is_valid = self.signature_acme.verify_signature(message, signature, public_key)
print(f"Sending message to ACME Server to create new Account: {final_payload_data_1}")
headers = {"content-type": "application/jose+json"}
try:
    response = requests.post(url=self.endpoints["newAccount"], verify=self.server_cert_location,
                             data=final_payload_data_1,
                             headers=headers)
    response_body = json.loads(response.content)
    return response_body
except requests.exceptions.RequestException as e:
    print(e)

encode:

def base64url_encode(self, obj_to_encode):
        json_str = json.dumps(obj_to_encode)
        json_bytes = json_str.encode('utf-8')
        base64_bytes = base64.urlsafe_b64encode(json_bytes)
        return base64_bytes.decode('utf-8').rstrip('=')

sign:

def sign_string(self, message):
        print("Sign message")
        # Convert string to bytes
        private_key = self.jwk_to_private_key()
        message_bytes = message.encode('utf-8')
        signature = private_key.sign(
            message_bytes,
            padding.PKCS1v15(),
            hashes.SHA256()
        )
        return base64.urlsafe_b64encode(signature).decode('utf-8').rstrip('=')

As the JWT is correct and the signature verification works (jwt.io) I tried several different ways to send the message resulting in different errors.

Payload as json:

final_payload_2 = {
            "protected": encoded_protected,
            "payload": encoded_payload,
            "signature": signature
        }
# is_valid = self.signature_acme.verify_signature(message, signature, public_key)
headers = {"content-type": "application/jose+json"}
try:
    response = requests.post(url=self.endpoints["newAccount"], verify=self.server_cert_location,
                             data=final_payload_2,
                             headers=headers)
    response_body = json.loads(response.content)
    return response_body
except requests.exceptions.RequestException as e:
    print(e)

results in:

{'type': 'urn:ietf:params:acme:error:malformed', 'detail': "Parse error reading JWS: invalid character 'p' looking for beginning of value", 'status': 400}

trying to specify the input as json in the post with:

try:
response = requests.post(url=self.endpoints["newAccount"], verify=self.server_cert_location,
                         json=final_payload_2,
                         headers=headers)

results in this error:

{'type': 'urn:ietf:params:acme:error:malformed', 'detail': 'Error unmarshaling body JSON', 'status': 400}

Trying to dump the json with json.dumps as follows results in yet another error:

response = requests.post(url=self.endpoints["newAccount"], verify=self.server_cert_location,
                           json=json.dumps(final_payload_2),
                           headers=headers)

results in error:

{'type': 'urn:ietf:params:acme:error:malformed', 'detail': 'Parse error reading JWS: json: cannot unmarshal string into Go value of type struct { Header map[string]string; Signatures []interface {} }', 'status': 400}

At this point I'm just blindly trying things out and don't know anymore what can be wrong, especially as the signature is valid ... . My guess is that I'm somehow sending it in the wrong way / format to pebble and that's why it can't be processed properly.
Any help or pointers are very appreciated :slight_smile:

Forgot the JSON Payload:

 {'protected': 'eyJhbGciOiAiUlMyNTYiLCAiandrIjogeyJrdHkiOiAiUlNBIiwgImtpZCI6ICJteS1rZXktMjAyNCIsICJ1c2UiOiAic2lnIiwgImFsZyI6ICJSUzI1NiIsICJuIjogIjgzQ3k2ZGRKLWw5Um5aVmhucTJFbEU5a1VobkZGeWFLTmhYcUo5LVVKbWRmZW9pSnR2U1dxUERjdjB0V3E5NW5tZzhOZjlYRi00YnB6YU14ODUtUVlRRkdoZVByVEU5bDRqNU5pYVB6enNYY29MeXlsZU85VjNUaVY5ejdzQXNHTXdXY3VRVzdFc3FzSDdvTDl1SDNQMlRNS0UtaVJvU1htY0RBQl93UWprUnpEOWVFbXZRa3dGUW1sVEhOUGVOZkFqX2QyY0xvd0ppa3BLbXRZV1NNa0JVZTlIVkd4MHRlbTl5TGUyZ05yeFpVTkdmTVBpS1l5aXk2SXYyTnV1aWpWWDQ3WnlOX0phQ1Vqb1pVekhBeWZGeWdNWklzSWl4QXh2NC14RlltSm51QkdTVGZITFRQWDJGbW5jbFlYNU52OV95aTFpVy00OUtIdGlZTDliRkZGUSIsICJlIjogIkFRQUIifSwgIm5vbmNlIjogImVRblE1WEQwcm9qZlJnZ3NrZnhCY3ciLCAidXJsIjogImh0dHBzOi8vbG9jYWxob3N0OjE0MDAwL3NpZ24tbWUtdXAifQ', 
'payload': 'eyJ0ZXJtc09mU2VydmljZUFncmVlZCI6ICJ0cnVlIiwgImNvbnRhY3QiOiBbIm1haWx0bzp0ZXN0QGdtYWlsLmNvbSJdfQ', 
'signature': '1aCBrXr7DgOIShwR9mX8hecXlGjFH5gMTubQ1c5PZVRGiQ3GObhVII6R3lmFvo0hI_J2WGVAF7PRWfWmwwwY9vDHzC9GWOHBiXobp5CzYxyn0zwWNbvO-R0WfwMVG6wtP0VQdRLNenDjlyj7gexa-liPBy_BOj79oEWeNzUVH1p5_uWiYl7di5nkQnDJkHexIu2yrQKZkMSWR6Jb92zuLlHdF0yMZNgghH3SKXMEhHgN1XXPR_A4jyEoq2aKLDVKxb_vsElfZqW2yBU_t3RXdklytEH6fmoIZdvtaQtbFnOBNbqaRyy7Z-uQfXSQZYenys9xnb9pvSmXRF2Lb0IDhw'}

That's not JSON, but a Python dictionary. In JSON you cannot use single quotes.

In your code further up, data=final_payload_2 uses the wrong encoding (it's not encoded as JSON, but likely form encoded), while json=json.dumps(final_payload_2) encodes the JSON document as a JSON string, which is wrong as well. json=final_payload_2 is the correct way to send JSON with requests. Alternatively data=json.dumps(final_payload_2) gives you more control over how the data is JSON encoded.

The reason Pebble still doesn't like your payload is "termsOfServiceAgreed": "true",: here you provide true as a string, and not as a boolean. Replace it with "termsOfServiceAgreed": True,, then account registration should work.

4 Likes

Wow, thanks. That was it ... . Thanks a lot!!!

{'status': 'valid', 'contact': ['mailto:test@gmail.com'], 'orders': 'https://localhost:14000/list-orderz/664fe473b0258ca', 'key': {'use': 'sig', 'kty': 'RSA', 'kid': 'my-key-2024', 'alg': 'RS256', 'n': 'vP131F1V6BwD4bJZy8Ovxc1GwWscj0zFMr1i2LAr5EoqfYJoXLdhAJ9LehTyduT6Ir9rRjM93hY24sBNalGgz7HF7NZU3JNACbOPp5aMxGZKJEZMJUMXP8aii_FEgp9A_XnxeHNUHX8pVvlIskYUSdkPVGBvy4QKhQqKwbEHV48KkCDjvWKLhnCsE3CyB6l0xsEkcxYPpdG--Bm7EakiFKaUKQnk7iLWBKiS6CbWeF0A-YWWuyIyT_TzZeqNzhELK43rY4HMd5VnzA0Lac9SoprzmnJR7ghC96QTb7AvWmPadkDDT8M4Vo5xoM6gWTL9-zqfHmPvky98cP9PNKb3RQ', 'e': 'AQAB'}}
1 Like