goal: need python 3 wrapper chef's rest api. because python-3, pychef out of question.
problem: trying replicate chef request python rsa. wrapper results in error message: "invalid signature user or client 'xxx'".
i approached wrapper trying replicate curl script shown in chef authentication , authorization curl using python rsa package: rsa signing , verification.
here's rewrite. simpler started getting paranoid line breaks , headers order, added few unnecessary things:
import base64 import hashlib import datetime import rsa import requests import os collections import ordereddict body = "" path = "/nodes" client_name = "anton" client_key = "/users/velvetbaldmime/.chef/anton.pem" # client_pub_key = "/users/velvetbaldmime/.chef/anton.pub" hashed_body = base64.b64encode(hashlib.sha1(body.encode()).digest()).decode("ascii") hashed_path = base64.b64encode(hashlib.sha1(path.encode()).digest()).decode("ascii") timestamp = datetime.datetime.now().strftime("%y-%m-%dt%h:%m:%sz") canonical_request = 'method:get\\nhashed path:{hashed_path}\\nx-ops-content-hash:{hashed_body}\\nx-ops-timestamp:{timestamp}\\nx-ops-userid:{client_name}' canonical_request = canonical_request.format( hashed_body=hashed_body, hashed_path=hashed_path, timestamp=timestamp, client_name=client_name) headers = "x-ops-timestamp:{timestamp}\nx-ops-userid:{client_name}\nx-chef-version:0.10.4\naccept:application/json\nx-ops-content-hash:{hashed_body}\nx-ops-sign:version=1.0" headers = headers.format( hashed_body=hashed_body, hashed_path=hashed_path, timestamp=timestamp, client_name=client_name) headers = ordereddict((a.split(":", 2)[0], a.split(":", 2)[1]) in headers.split("\n")) headers["x-ops-timestamp"] = timestamp open(client_key, 'rb') privatefile: keydata = privatefile.read() privkey = rsa.privatekey.load_pkcs1(keydata) open("pubkey.pem", 'rb') pubfile: keydata = pubfile.read() pubkey = rsa.publickey.load_pkcs1_openssl_pem(keydata) signed_request = base64.b64encode(rsa.sign(canonical_request.encode(), privkey, "sha-1")) dummy_sign = base64.b64encode(rsa.sign("hello".encode(), privkey, "sha-1")) print(dummy_sign) def chunks(l, n): n = max(1, n) return [l[i:i + n] in range(0, len(l), n)] auth_headers = ordereddict(("x-ops-authorization-{0}".format(i+1), chunk) i, chunk in enumerate(chunks(signed_request, 60))) all_headers = ordereddict(headers) all_headers.update(auth_headers) # print('curl '+' \\\n'.join("-h {0}: {1}".format(i[0], i[1]) in all_headers.items())+" \\\nhttps://chef.local/nodes") print(requests.get("https://chef.local"+path, headers=all_headers).text)
at each step tried check if variables have same result counterparts in curl script.
the problem seems @ signing stage - there's obvious discrepancy between output of python's packages , mac's openssl tools. due discrepancy, chef returns {"error":["invalid signature user or client 'anton'"]}
. curl script same values , keys works fine.
dummy_sign = base64.b64encode(rsa.sign("hello".encode(), privkey, "sha-1"))
python has value of
n7qszrd495vv9cc35vqsdyxfovbmn3tcnu78in911r54iwhzpuknjtdfz4d/kpzytvmvbpor4ny5um9qvcihhqtjqky+opf+8w61hyr7yyxzrqmx6sjijrffc4uogb5wjot8csaurseuuhantl6hccfrknwuznb7sctkok6fxv0skwn2czv9cjfhbyct3oiy/xadtz6ib+fliwsquf1k7lj4/cmljlp/gu/qalkvwoydakxmavv3vyx/knhzapkgtypmw6l5k1adjgrvm9ch/bnqbg1wfzit6lk+m4kamfbtorfeh45kgwbcj9zsyetymcatuycebjqmujmqewzv7w==
while output of echo -n "hello" | openssl rsautl -sign -inkey ~/.chef/anton.pem | openssl enc -base64
wfoasf1f5dpt3cvplwdriitwuenjr5ycv+wilbqlfmwm3nfhiqftplytm56swtsg ckdbovu4ebfxc3rsu2appelqrh6+fnl2tl273vo6klzvc/8+tubtdnzdzsphx6s8 x+6wzvfxsd3qegawohkegtkodsbyfzarnzfxo2jzue4dnygijwruhdf9s4ldrro6 eashwaxunzm0cil+umz5iym3ccd6gfl13njmxzs3chrlesbtlka7pnxj1udf2wn2 ok09ak+bham4jl5heq2sdnzbqikvydcxx4divnf2i/0tzd16j6bemgcftfsi2f3k tvgulq81+sh9zo8lgnpdrw==
i couldn't find information on default hashing algorithm in openssl rsautl
, guess it's sha-1.
at point don't know way look, hope can make right.
from chef authentication , authorization curl,
timestamp=$(date -u "+%y-%m-%dt%h:%m:%sz")
time in utc, in python, has be
timestamp = datetime.datetime.now(datetime.timezone.utc).strftime("%y-%m-%dt%h:%m:%sz")
openssl
equivalent of python,
dummy_sign = base64.b64encode(rsa.sign("hello".encode(), privkey, "sha-1"))
is
echo -n hello|openssl dgst -sha1 -sign ~/.chef/anton.pem -keyform pem|openssl enc -base64
in python code, you're signing message digest, sha-1, of message. that's called detached signature.
echo -n "hello" | openssl rsautl -sign -inkey ~/.chef/anton.pem | openssl enc -base64
1 signs whole message, without making digest.
python rsa
module has no equivalent of openssl rsautl -sign
. defined function fill space.
from rsa import common, transform, core, varblock rsa.pkcs1 import _pad_for_signing def pure_sign(message, priv_key): '''signs message private key. :param message: message sign. can 8-bit string or file-like object. if ``message`` has ``read()`` method, assumed file-like object. :param priv_key: :py:class:`rsa.privatekey` sign :return: message signature block. :raise overflowerror: if private key small contain requested hash. ''' keylength = common.byte_size(priv_key.n) padded = _pad_for_signing(message, keylength) payload = transform.bytes2int(padded) encrypted = core.encrypt_int(payload, priv_key.d, priv_key.n) block = transform.int2bytes(encrypted, keylength) return block
test;
openssl
echo -n hello|openssl rsautl -sign -inkey .chef/anton.pem |base64 foiy6hvpfipnk4hmyg8ywcezwz7w4qexr6kxdbj7/vr5jym56joofkn1quak57isercqq1xqbsit fo6bds2suyuku15nj3frq54+lcvkjdruueyl2kfjgvtxlsdhzyj1sbfjznbz32irvmvytarwqusy b2f2gqkltogghcywffyhw5ypahmkc2cqihw+ssvngcprmvaatvczqrnv5zr61icipcknexnya8/j ga34ntyelxwdrady74726oljsgszphbaomk02c4yx7ou32gwlplszbugaqs5tu4msjld1f/eqbsf x/pn8dep4yur1294dtp7dsz9ml64zlcilg==
python
base64.b64encode(pure_sign.pure_sign(b'hello',prik)).decode() 'foiy6hvpfipnk4hmyg8ywcezwz7w4qexr6kxdbj7/vr5jym56joofkn1quak57isercqq1xqbsitfo6bds2suyuku15nj3frq54+lcvkjdruueyl2kfjgvtxlsdhzyj1sbfjznbz32irvmvytarwqusyb2f2gqkltogghcywffyhw5ypahmkc2cqihw+ssvngcprmvaatvczqrnv5zr61icipcknexnya8/jga34ntyelxwdrady74726oljsgszphbaomk02c4yx7ou32gwlplszbugaqs5tu4msjld1f/eqbsfx/pn8dep4yur1294dtp7dsz9ml64zlcilg=='
change line;
signed_request = base64.b64encode(rsa.sign(canonical_request.encode(), privkey, "sha-1"))
to
signed_request = base64.b64encode(pure_sign(canonical_request.encode(), privkey))
Comments
Post a Comment