I want to GET info about auth object.
POST to https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/172480296
but get response:
{u'status': 400, u'type': u'urn:ietf:params:acme:error:malformed', u'detail': u'Invalid status value'}
here is req:
protected: {"alg": "ES256", "kid": "https://acme-staging-v02.api.letsencrypt.org/acme/acct/17092284 ", "nonce": "0003QE4ai_1cEssEm4VZ6z6MSrdbAPl4lDSkQgHv72jEK0k", "url": "https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/172480296 "}
payload: {}
what does status mean in this situation?
AND
How to use POST-as-GET properly ?
1 Like
_az
December 15, 2020, 2:09am
2
1 Like
IF payload is empty.
how to make signature and JWT ?
as far as I know the format is "protected_header.payload.signature"
1 Like
griffin
December 15, 2020, 2:34am
4
only use "protected_header."
2 Likes
griffin
December 15, 2020, 2:38am
6
You sign "protected_header."
The payload part of the json object will be an empty string.
3 Likes
# -*- coding: utf-8 -*-
auth_url = "https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/172480296"
account_url = "https://acme-staging-v02.api.letsencrypt.org/acme/acct/17092284"
key_dict = {
"y": "...",
"x": "...",
"crv": "P-256",
"kty": "EC",
"d": "..."
}
import requests
import json
from jwcrypto import jwk, jws
from pprint import pprint as pp
# Get a nonce
resp = requests.head('https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce')
pp(resp.headers)
replay_nonce = resp.headers['Replay-Nonce']
print('Nonce is: {}'.format(replay_nonce))
key = jwk.JWK.from_json(json.dumps(key_dict))
# Create the protected and payload structures
protected_header = {
"alg": "ES256",
"nonce": replay_nonce,
"url": auth_url,
# "jwk": key.export(private_key=False, as_dict=True)
"kid": account_url
}
# payload = ""
payload = None
protected_enc = json.dumps(protected_header, sort_keys=True)
payload_enc = json.dumps(payload)
print("protected: {}".format(protected_enc))
print("payload: {}".format(payload_enc))
# Sign the JWS
jwstoken = jws.JWS(payload=payload_enc)
# jwstoken.detach_payload()
jwstoken.add_signature(
key,
'ES256',
protected=protected_enc,
# header=header,
# json_encode({"kid": key.thumbprint()})
)
jws_enc = jwstoken.serialize()
print('=======req=======')
pp(jws_enc)
# Send the JWS to the ACME server
session = requests.Session()
session.headers.update({'Content-Type': 'application/jose+json'})
resp = session.post(
url=auth_url,
data=jws_enc,
)
print('========response========')
print(resp.headers)
print(resp.json())
response:
{'Content-Length': '105', 'Cache-Control': 'public, max-age=0, no-cache', 'Server': 'nginx', 'Connection': 'keep-alive', 'Link': 'https://acme-staging-v02.api.letsencrypt.org/directory ;rel="index"', 'Boulder-Requester': '17092284', 'Date': 'Tue, 15 Dec 2020 05:16:04 GMT', 'Content-Type': 'application/problem+json', 'Replay-Nonce': '0003PiFeJx5C-dhiU90uXsb5460HCjMWADFKuO5wjdKBgTg'}
{u'status': 400, u'type': u'urn:ietf:params:acme:error:malformed', u'detail': u'Invalid status value'}
@_az
It seems that jwcrytpo has bug in this scenario.
_az
December 15, 2020, 5:25am
9
Just do:
payload_enc = ""
JSON-encoding None
or ""
is going to result in something other than a zero-length string, which is no longer POST-as-GET.
1 Like
then jwcrypto raise exception like this:
raise InvalidJWSObject('Missing Payload')
payload:
jwcrypto.jws.InvalidJWSObject: Invalid JWS Object [Missing Payload]
_az
December 15, 2020, 5:36am
11
Ah, yeah.
The problem is that an empty string is not a "truthy" value in Python, so the library raises an error.
I agree with you that that is probably a bug in jwcrypto. There's no requirement in JWS standard that it cannot be an empty value, all it says is "arbitrary sequence of octets".
Other libraries I've checked all appear to support blank payloads, so I'd suggest filing an issue against jwcrypto.
2 Likes
Could you give some example here ? which package do well with this ?
_az
December 15, 2020, 6:18am
13
I was looking at libraries in other languages (like go-jose), so they might not be a great help to you.
I know of two Python libraries which can sign empty payloads:
from jose import jws
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.backends import default_backend
account_key = ec.generate_private_key(curve=ec.SECP256R1, backend=default_backend())
payload = ""
protected = {"url": "http://xyz"}
signed_parts = jws.sign(
payload.encode("utf-8"),
account_key,
headers=protected,
algorithm=jws.ALGORITHMS.ES256,
).split(".")
signed = {
"protected": signed_parts[0],
"payload": signed_parts[1],
"signature": signed_parts[2],
}
print(signed)
2 Likes
OK, thank you. I will try it now.
I think you must a PRO.
Does cryptography have any method to load a existed KEY ?
for example:
{
"y": "...",
"x": "...",
"crv": "P-256",
"kty": "EC",
"d": "..."
}
_az
December 15, 2020, 6:46am
16
cryptography doesn't know about JWKs. This functionality would need to come from the jose library.
If you have an account key in dictionary format like:
account_key = json.loads(some_file.read())
then you can pass that directly to jws.sign
.
# -*- coding: utf-8 -*-
auth_url = "https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/172480296"
account_url = "https://acme-staging-v02.api.letsencrypt.org/acme/acct/17092284"
key_dict = {
"y": "...",
"x": "...",
"crv": "P-256",
"kty": "EC",
"d": "..."
}
import requests
import json
from jose import jws
from pprint import pprint as pp
# Get a nonce
resp = requests.head('https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce')
pp(resp.headers)
replay_nonce = resp.headers['Replay-Nonce']
print('Nonce is: {}'.format(replay_nonce))
# Generate a key
# Create the protected and payload structures
protected_header = {
"alg": "ES256",
"nonce": replay_nonce,
"url": auth_url,
# "jwk": key.export(private_key=False, as_dict=True)
"kid": account_url
}
payload = ""
protected_enc = json.dumps(protected_header, sort_keys=True)
payload_enc = ""
print("protected: {}".format(protected_enc))
print("payload: {}".format(payload_enc))
# Sign the JWS
signed_parts = jws.sign(
payload.encode("utf-8"),
key_dict,
headers=protected_header,
algorithm=jws.ALGORITHMS.ES256,
).split(".")
signed = {
"protected": signed_parts[0],
"payload": signed_parts[1],
"signature": signed_parts[2],
}
print('=======req=======')
pp(signed)
# Send the JWS to the ACME server
session = requests.Session()
session.headers.update({'Content-Type': 'application/jose+json'})
resp = session.post(
url=auth_url,
data=signed,
)
print('========response========')
print(resp.headers)
pp(resp.json())
OUTPUT:
=======req=======
{'payload': u'',
'protected': u'eyJhbGciOiJFUzI1NiIsImtpZCI6Imh0dHBzOi8vYWNtZS1zdGFnaW5nLXYwMi5hcGkubGV0c2VuY3J5cHQub3JnL2FjbWUvYWNjdC8xNzA5MjI4NCIsIm5vbmNlIjoiMDAwMzRqZXptdEtSN1N5akF0Q1psNXd0RDlKd1pkZ1V0dXZ0czl2WWNMdXpXejAiLCJ0eXAiOiJKV1QiLCJ1cmwiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2F1dGh6LXYzLzE3MjQ4MDI5NiJ9',
'signature': u'9N-E9aMsH2gyNcjUkyYT52CWphyjHDO2_BJr-Twduo3UVjhPko9F_RSTixN74mrnKWoCdByhxQvY4NUnMWtaBw'}
========response========
{'Content-Length': '108', 'Server': 'nginx', 'Connection': 'keep-alive', 'Link': 'https://acme-staging-v02.api.letsencrypt.org/directory ;rel="index"', 'Cache-Control': 'public, max-age=0, no-cache', 'Date': 'Tue, 15 Dec 2020 06:51:41 GMT', 'Content-Type': 'application/problem+json', 'Replay-Nonce': '0003JyPk5Js9L2c1tH4Hwq0BAOUb_BFStbocQ1agEil2kgI'}
{u'detail': u'Parse error reading JWS',
u'status': 400,
u'type': u'urn:ietf:params:acme:error:malformed'}
_az
December 15, 2020, 7:22am
18
My internet is having a meltdown so I can't really test your code, but you will probably need to change data=signed
to json=signed
, since the JWS library you are using no longer returns a JSON-encoded string.f
2 Likes
Oh, yes. It works.
My mistake of Data Format.
griffin
December 15, 2020, 8:04am
20
Or you can just write your own library, like I did.