I'm not running your code, but _az might. 
Oh, I see.
Well, the new "Parse error" is because the jwk in your protected is:
- Double JSON-encoded
- A private key. You should only be sending the public JWK.
Try:
jwk_param = key.export(private_key=False, as_dict=True)

That's not cool.
It was a string and not an object, right? The JWK that is.
Now the code is
import requests
import url64
import json
from jose import jws
from pprint import pprint as pp
import base64
from jwcrypto import jwk, jws
from jwcrypto.common import json_encode
#
# HEAD newNonce
#
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:'
pp(replay_nonce)
session = requests.Session()
session.headers.update({'Content-Type': 'application/jose+json'})
key = jwk.JWK.generate(kty='EC')
jwk_param = key.export(private_key=False, as_dict=True)
print '-------- ec key is ------------'
print jwk_param
print '-------------------------------'
protected_header = {
"alg": "ES256",
# "alg": "RS256",
"nonce": replay_nonce,
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/new-acct",
# "jwk": {"d":"CVP5w9src5X9GcqpiWnP2yQhh03vwi6CMVDqsX9-qdhltbj63ac5JThm5tKwyRrO5d4UZ-wFFmVG66pyX-hDu2k2KGrqEu7JulsUBVb86bQhDgpGJ_ITL6iA2PyKDwxpwl9EaMgJkHyEPtsR0v-g61NzOy6EGG-rFXHa1hQcVPf7ncueL_Zh3rf9NyhAerzzBJJK2YjFEiwK0UX8pF8eGjRt1yKvcbHkae6zFKyuysmpXxS36lW7x5NILn5mrFY1Vl8Sw3CS0czYBkkyT0rDCsonWJkP4MYr4SrjiPwZ2wH3YHE-jU3gccgJKjWn1r2faiZ6mpmkOk36cgxpPJCj2Q","dp":"IIlHMqsXBw9ZPuR28SXImeE1k9jPTz5qcXmUCEWDUlSmfH0DHfBCE9mO0PRJ9kgHBWbBrueCgwAKxX0wXVdyAX4R4xxJEntwTurDZ48deuAW2okCj7q8jjciBsFI70yDHk06OufVgk11s1yFRaEDTiKj80m3mfQ83QlE75drcCU","dq":"FtGnslHPwAT89s_tkYPOoEFYgqUbDO17Pee8XnCnBcQAiJXGTU5y7IJD6Unv_aXFOvbME4rx7-bHWwJ8bevHSl-7aNvb_4u5UqndnrS1YDRAzOzIHocU1eVzzv3sE62S35WCyTqHH7d9ujSR2tXgUMU__JFSewUq9yNPlsLIMQ0","e":"AQAB","kty":"RSA","n":"sSVk8sp2vwy8Z4ombntiNnK77wwFpYS9AE4KHpOiCZLrm-gCRbyz2T5lRjgzUX7GtTk20fcGVhPUc4cWWthvP36-eGbaFPusHIujJ-kFw2WcUBDti41mRr76_fOddN7mnTE9f_78s2Bqelk3aybKcAqTOQbYYjLaUwEiMVOiBKEN6_2WRoMtKebCjsVCpJbZdPmFaUBKX84IFQLiNBqqH_rdQLv5YEXXQGp-5LPBC3j0D1XOba9dqhCSpYSsRiWxADHBrqtYxk7X-IkgC2MEeigTqDe-IzuVwesKDp9jQnnxn0jzNsSFp5lBaiyuis8Wnz6Q8Em10-eztAUOZlYquw","p":"5zjJ-maJMU_KNViauMnqyEDU5AdqixGJEwszhJFWqVVbI5Ly7H7OiQMEO9M4pUfvDPxBOdQt0VLUIGwXnEgoONqeQ1mO0TYZDJO-_udvCd7Y7oHyJAM6YJhaGtouw3fu-mbyn5690xyXs6q9YX__iD49WNJjQnPH2YdbBSxVRAc","q":"xCEe_NxynXxbUF86XbeNcsmrdf-4n493ZrwoF831AHiKNZgV2_eMEX-PlH3goM9yvwkiJ-uApkgGPNSR_g9IfarZgr7LgRTyFHPn-vmXFDuGxWZVlwWErstyd8g8fIwFaSHja4OFMgMR86eix3x_IctQiY_mF7D3hB8_fYt2vq0","qi":"YKAWre_MhAO02t3yM692rzE1MFpuNYbZZAAdUr8jmQgHI3R5KL6lOOa0aZ6DS-Jq15GmgY9mmSi4N0-qgGG7Xt2NXPd4c0FGQPTP6qs1ULa0upd3fXd-EydjihiOK-kpET2Rk6bpNKb5INpTkPABJaWnpq3G1_d2q2rIao6NpG8"}
"jwk": jwk_param
}
payload = {
"termsOfServiceAgreed": True,
"contact": [
"mailto:test_mail_new@sina.com"
]
}
req = {
"protected": url64.encode(json.dumps(protected_header)),
# "protected": base64.urlsafe_b64encode(json.dumps(protected_header)),
"payload": url64.encode(json.dumps(payload)),
# "payload": base64.urlsafe_b64encode(json.dumps(payload)),
}
print req
print "JSON payload ============"
print json.dumps(payload)
print "JSON payload ============"
jwstoken = jws.JWS(json.dumps(payload))
jwstoken.add_signature(
key,
'ES256',
json.dumps(protected_header),
# header=header,
# json_encode({"kid": key.thumbprint()})
)
sig = jwstoken.serialize()
print sig
req.update({"signature": url64.encode(sig)})
print '=======req======='
pp(req)
resp = session.post(
url='https://acme-staging-v02.api.letsencrypt.org/acme/new-acct',
json=req,
)
print '========response========'
print resp.headers
print resp.json()
AND get the error response
{u'status': 400, u'type': u'urn:ietf:params:acme:error:malformed', u'detail': u'JWS verification error'}
I think your signing process is off.
Should be signing "$protected.$payload" .
I can't really be bothered explaining the things that were wrong, they were mostly about misunderstanding how the jwcrypto library works. Better to read each function carefully on https://jwcrypto.readthedocs.io/en/latest/jws.html. The example there is a little misleading because it generates an encoded JWS and then names it sig.
.
Here is your program working:
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))
# Generate a key
key = jwk.JWK.generate(kty='EC')
# Create the protected and payload structures
protected_header = {
"alg": "ES256",
"nonce": replay_nonce,
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/new-acct",
"jwk": key.export(private_key=False, as_dict=True)
}
payload = {
"termsOfServiceAgreed": True,
"contact": [
"mailto:test_mail_new@sina.com"
]
}
protected_enc = json.dumps(protected_header, sort_keys=True)
payload_enc = json.dumps(payload, sort_keys=True)
print("protected: {}".format(protected_enc))
print("payload: {}".format(payload_enc))
# Sign the JWS
jwstoken = jws.JWS(payload=payload_enc)
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='https://acme-staging-v02.api.letsencrypt.org/acme/new-acct',
data=jws_enc,
)
print('========response========')
print(resp.headers)
print(resp.json())
Oh , thank you all.
And sorry about JWS thing .
Glad it's working. 
Amazing as always, _az!