feat: Add int "aes128gcm" content-type handling to python.#29
feat: Add int "aes128gcm" content-type handling to python.#29martinthomson merged 2 commits intoweb-push-libs:masterfrom
Conversation
martinthomson
left a comment
There was a problem hiding this comment.
The big open question here is to what extent we need to maintain backward compatibility in the API. A version bump should ensure that the new version isn't picked up automatically, but it could be surprising for some. Maybe the right answer is to maintain "aesgcm" as a default and change the default when we go to publication for the RFCs and have that coincide with a 1.0 release.
| def deriveKey(mode, salt, key=None, dh=None, keyid=None, authSecret=None, padSize=2): | ||
| def buildInfo(base, context): | ||
| return b"Content-Encoding: " + base + b"\0" + context | ||
| MAX_BUFFER_SIZE = pow(2, 36) - 31 |
There was a problem hiding this comment.
You can trim this down to 2^32-1. I doubt that anyone using the old stuff will care.
| nonceinfo = b"Content-Encoding: nonce" | ||
| elif content_type == "aes128gcm": | ||
| keyinfo = b"Content-Encoding: aes128gcm\x00\x01" | ||
| nonceinfo = b"Content-Encoding: nonce\x00\x01" |
There was a problem hiding this comment.
HKDF should be adding the 0x01 to the end, so you should remove it from here.
| "salt": content[:16], # 16 octets | ||
| "rs": struct.unpack("!l", content[16:20])[0], # 4 octets | ||
| "id_len": id_len, # 1 octet | ||
| "key_id": content[21:21 + id_len], # n octets |
| id_len = struct.unpack("!B", content[20])[0] | ||
| reply = { | ||
| "salt": content[:16], # 16 octets | ||
| "rs": struct.unpack("!l", content[16:20])[0], # 4 octets |
There was a problem hiding this comment.
Important: "!L" (and above)
| def decryptRecord(key, nonce, counter, buffer): | ||
|
|
||
| def decrypt(content, salt, key=None, keyid=None, dh=None, rs=4096, | ||
| auth_secret=None, pad_size=2, content_type="aesgcm", **kwargs): |
There was a problem hiding this comment.
content_type might be a bad name
the default for content_type is unfortunate. I would prefer to have this default to the new thing. (My js code does.) Is it too much to ask that existing users include content_type="aesgcm" in their code?
since pad_size is a legacy argument, I would remove it from here and fall back to your code with kwargs
| raise Exception(u"Message truncated") | ||
| # handle old, malformed args | ||
| pad_size = kwargs.get('padSize', pad_size) | ||
| auth_secret = kwargs.get('authSecret', auth_secret) |
There was a problem hiding this comment.
I don't think that you need to include this. I would prefer to leave code that hasn't been updated using the old code. To that end, pad_size can be removed entirely.
| "salt": os.urandom(16), | ||
| "salt": salt, | ||
| "rs": rlen() + 1, | ||
| "authSecret": os.urandom(10) |
There was a problem hiding this comment.
might pay to underscore this one too
| ): | ||
| log("Test: " + f.__name__) | ||
| f() | ||
| for c_type in [ "aes128gcm", "aesgcm", "aesgcm128"]: |
There was a problem hiding this comment.
I think that I'm going to steal your new argument for my JS code.
|
@jrconlin, thanks for picking this up. I think that you have one fairly big error in your code. I will make some changes to the JS code based on this though, it's a definite improvement. |
|
Well, the guiding principle is "Minor library versions DO NOT BREAK existing code". Since I don't think you're ready for this to go to 1.0 quite yet, I'm setting the default to be "aesgcm", which is the current standard. Once 1.0 is ready to roll, we can switch the default, provide proper docs about what the change is, and give folks a good heads up. That's one of the reasons that I have code to accept both the camel case as well as proper underscore vars, since just changing them will break existing code. (More fun for the CHANGELOG, I suppose.) I'm also probably going to add some static blocks to encrypt/decrypt to the units so that we can verify that we're not stomping on stuff that once worked. If you like, I'll see about putting them in a file so that we can run them on the node side as well. Thanks for the early review. I'm going to move the stuff in tests to proper unit tests and probably add a few scripts to setup.py. Once things are nailed down, I'll collapse the commits, clean up the comment and assign the PR to you so you can give it a proper yay/nay. |
|
Awesome, thanks for doing the work. You've much better python than I do, so I appreciate the help. BTW, you should check that your implementation agrees with mine. I've updated the node.js stuff (in #28) and the command line utilities to match. Incidentally, I settled on an argument called |
|
👍 You're protocol guy. I'll pound on the code to do and say what you want it to. |
1fb58c0 to
df08f3f
Compare
martinthomson
left a comment
There was a problem hiding this comment.
This is unconditionally better than what I have. I could quibble about things, but I'm lazy enough to accept this without too much more scrutiny. I will hold this until I get better confirmation that the direction of this stuff is good.
|
|
||
| (key_, nonce_) = derive_key(mode="decrypt", salt=salt, | ||
| key=key, keyid=keyid, dh=dh, | ||
| authSecret=auth_secret, |
| name='http_ece', | ||
| version='0.5.0', | ||
| version='0.6.0', | ||
| author='Martin Thomson', |
There was a problem hiding this comment.
@jrconlin, feel free to add yourself as a contributor here
There was a problem hiding this comment.
Thanks. PyPi can get a bit squirrelly if you have multiple authors or emails, so I'll just stick with the participants list on github. Plus, this way you look even better, so bonus on that front.
| if salt is None or len(salt) != 16: | ||
| raise Exception(u"'salt' must be a 16 octet value") | ||
| if version not in versions: | ||
| raise ECEException(u"invalid version specified") |
There was a problem hiding this comment.
I think that you need to move this below the block 97-100
| version = "aesgcm" | ||
|
|
||
| if salt is None or len(salt) != 16: | ||
| raise ECEException(u"'salt' must be a 16 octet value") |
There was a problem hiding this comment.
In the JS code, I just generate a random salt. That's probably nicer API ergonomics.
There was a problem hiding this comment.
This is a bit complicated, sadly. Since we have to respect prior versions and function, I can't just gen up a new salt, (We used to throw errors, so we should still do so for older versions.)
Fortunately, in the encrypt() function we do check if salt has not been set, create one, and also force the content type to aes128gcm.
| :param keyid: Internal key identifier for private key info | ||
| :type keyid: str | ||
| :param dh: Remote Diffie Hellman sequence | ||
| :type dh: str |
There was a problem hiding this comment.
In the JS code I read the DH key from the keyid in the header when I'm using DH.
There was a problem hiding this comment.
Yep, but we've got to preserve old function when possible. (else we'll have to bump up the major version prematurely)
* adds new content-type verison handling * fixes flake8 issues * adds documentation and type descriptions for parameters * moves tests to unit testing * adds ability to do cross library validation (note: node.js does not use valid ec256p curve points) * prep work for travis integration
df08f3f to
5483765
Compare
|
One general "TODO" would be to get travis integration so PRs get tested. I'm not fully versed enough in Travis arcana to know how you get it to test multiple languages of a given project, but I'll see what I can dig up. I need to do the same for one of my own projects. |
use valid ec256p curve points)
r? @martinthomson, @marco-c