openssl - Replicating request to Chef with Python RSA -


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