Join GitHub today
GitHub is home to over 20 million developers working together to host and review code, manage projects, and build software together.
[#8966] Adding curve25519 and ed25519 #644
Conversation
the0id
and others
added some commits
Jul 27, 2016
the0id
changed the title from
the0id curve25519
to
Adding curve25519 and ed25519
Dec 23, 2016
codecov-io
commented
Dec 23, 2016
•
Codecov Report
@@ Coverage Diff @@
## trunk #644 +/- ##
==========================================
- Coverage 91.3% 90.93% -0.38%
==========================================
Files 839 839
Lines 148399 148609 +210
Branches 12970 13006 +36
==========================================
- Hits 135502 135141 -361
- Misses 10821 11356 +535
- Partials 2076 2112 +36 |
the0id
changed the title from
Adding curve25519 and ed25519
to
[#8966] Adding curve25519 and ed25519
Dec 23, 2016
the0id
added some commits
Jan 3, 2017
the0id
added some commits
Jan 17, 2017
glyph
reviewed
Jan 28, 2017
Thanks for your continued work on Conch's cipher suites.
We should discuss the results of this review, but my general feeling here is that even if the dependency isn't optional, we are 4 mostly copy/pasted algorithms in to needing to refactor the way that conch deals with curves. I think this one might be a bridge too far. Do you see an easy way to modularize? I'm OK with deferring to a subsequent ticket, but I just worry that this is becoming less and less maintainable.
Please address this feedback and resubmit.
| @@ -19,6 +19,10 @@ | ||
| from hashlib import md5, sha1, sha256, sha384, sha512 | ||
| +import nacl.public |
| - int( | ||
| - binascii.hexlify( | ||
| - ecPriv.exchange(ec.ECDH(), theirECPub)), 16)) | ||
| + if self.kexAlg.find(b'25519') != -1: |
glyph
Jan 28, 2017
Owner
Just a nitpick, but, this is perhaps more idiomatically expressed as if b'25519' in self.kexAlg
the0id
Jan 30, 2017
Contributor
I remember if X in Y causing problems with python3, but that seems to be incorrect and I agree if X in Y is better. Changed.
| + # Always generate new private and public keys | ||
| + ecPriv = nacl.public.PrivateKey.generate() | ||
| + ecPub = ecPriv.public_key | ||
| + encPub = ecPub._public_key |
glyph
Jan 28, 2017
Owner
This looks like it's reaching into the private implementation details of a dependency. Is it? If so - why does it need to?
the0id
Jan 30, 2017
Contributor
It is, and it's because of how pynacl decided to do their secret key generation. They call their method "salsa" which follows the standard way of generating a curve25519 secret key but then throws an extra transform on it. Which means to get it to work with Twisted I have to call the private method.
glyph
Feb 21, 2017
Owner
I don't quite understand; wouldn't "throwing an extra transform" on it mean that the values are different?
the0id
Feb 23, 2017
Contributor
Yup. If you use the standard method it will return a value that works for pynacl, but won't work for almost every other implementation, including OpenSSH.
Why they do this I have no idea, I'm sure they have their reasons, but this was the only way I could get it to work.
| + int(binascii.hexlify( | ||
| + crypto_scalarmult(ecPriv._private_key, | ||
| + theirECPub._public_key)), 16)) | ||
| + else: |
glyph
Jan 28, 2017
Owner
One other nice thing about making this an optional dependency would be that we could potentially factor out this extremely long function into a couple of smaller cipher-management functions so as to avoid monolithic changes like having to indent this lengthy "else" block even though the lines aren't being touched.
| + self.curve, pubKey).public_key( | ||
| + default_backend()) | ||
| + | ||
| + # We need to convert to hex, |
glyph
Jan 28, 2017
Owner
Given that this comment is repeated twice, and now the MP(... expression is repeated 4 times, some refactoring seems to be in order.
the0id
Jan 30, 2017
Contributor
I see what you're saying, and have had similar thoughts, but I don't know if I'm convinced it's needed yet.
DH, ECDH, and curve25519 all do something like this, but each of them do it in a slightly different way. So if we were to refactor and abstract this behavior I think we'd just end up moving the same code to a different location.
| @@ -84,6 +85,16 @@ | ||
| 'curve': b'ecdsa-sha2-nistp521' | ||
| } | ||
| +ED25519Data = { |
glyph
Jan 28, 2017
Owner
I realize that this is just repeating an instance of an existing pattern, but could we replace this with something that generates a test key on the fly, rather than supplying static data? Long term, we should be deleting every static private key in this module.
the0id
Jan 30, 2017
Contributor
I don't have a strong opinion on whether or not this should be done, but I think it'd be best to do it as it's own ticket.
| + keydata.publicED25519openssh) | ||
| + | ||
| + # We need to patch the comparison value because SecureRandom | ||
| + # gets patched when run as a unit test. |
glyph
Jan 28, 2017
Owner
How is this comparison value "patched"? Also; can this refer more specifically to what does the SecureRandom patching, rather than just alluding to it? Long-term, this is the sort of thing we should parameterize rather than patch.
the0id
Jan 30, 2017
Contributor
When trial is run, SecureRandom is patched by test_keys.py:217 to return '\xff' for each byte requested.
I wasn't sure how detailed to go into this explanation in the comment since it's only relevant when running trial. I saw it that it was more important to let a normal user know that every time this function is called the output will be slightly different.
| @@ -96,6 +96,7 @@ | ||
| 'pyasn1', | ||
| 'cryptography >= 0.9.1', | ||
| 'appdirs >= 1.4.0', | ||
| + 'pynacl >= 1.0.1', |
glyph
Jan 28, 2017
Owner
Additional cryptographic dependencies are often met with high levels of scrutiny. If possible, I'd really like to see this implemented in a way which makes pynacl an optional dependency.
This doesn't necessarily mean that the entry in setup.py needs to go away, but it strikes me that "cryptography yes, pynacl no" might end up being a configuration someone cares about.
glyph
Jan 28, 2017
Owner
This is not mandatory feedback, feel free to push back on this, if you disagree. I am not really sure about the political environment re: nacl as a crypto lib. Perhaps @reaperhulk or @alex could opine as well, if this is a legitimate concern.
reaperhulk
Jan 28, 2017
Contributor
libsodium is definitely still the "less acceptable to upper management" crypto library, but its presence here is because OpenSSH already extensively uses crypto primitives from djb and OpenSSL doesn't (yet) support them. If you want to support these things in the near term you'll need to pull in pynacl or do it in pure Python (don't do that).
glyph
Jan 28, 2017
Owner
The question is really: should Twisted be concerned about environments where someone will say "uh oh, pynacl, you can't install that, denied"? Or is this the kind of thing that will be configured at a policy level? (After all you'd need to actually use these ciphers in your deployment to exercise this code.)
reaperhulk
Jan 28, 2017
Contributor
Good question. I've never been a huge fan of optional deps, but a project like twisted probably does need to care about things like that. It doesn't seem unreasonable to state that a base install of twisted conch must support RSA and NIST ecdsa (via a non-optional cryptography dep), but that users who want access to ed25519 should install pynacl. ed25519 is likely to appear in cryptography soon-ish as well, albeit only accessible to users linking against the latest OpenSSL.
the0id
Jan 30, 2017
Contributor
I have issues with how pynacl operates myself, but I found it to be the lesser evil. There are other libraries that support *25519, but some only support curve25519, some only support ed25519, and most of them haven't been updated in at least a year or two.
How I see things is that pynacl is optional to the extent that cryptography and pyasn1 are optional. They're not needed unless you want to use conch, and since *25519 is the default (and strongly encouraged) algorithm of OpenSSH it should be included when conch is.
glyph
Feb 21, 2017
Owner
@the0id I don't think you're grasping my concern here:
Let's say I work at a company that's using Twisted for SSH, but that has a heavy-weight dependency audit process for each new library that gets deployed. Now, we're still adopting new dependencies and causing more trouble for people working at such shops; however, new crypto dependencies are often given much tighter scrutiny. By making this change unconditional, I have to upgrade Twisted and go through the pynacl review process at the exact same time - rather than installing new Twisted immediately and kicking off the review process for pynacl, which, once it gets approved, gives me a new capability.
the0id
Feb 23, 2017
Contributor
I see what you're saying now, and making curve25519 an optional dependency may not be a much work as I thought. So I'll look into it.
glyph
Feb 24, 2017
Owner
I should note; it's OK to make the conch extra hard depend on this, since for most users, that should be the right thing. I'm mainly talking about leaving the code in such a state that an escape hatch like a separate conch-no25519 extra would be possiblel.
the0id
added some commits
Jan 30, 2017
|
I think you have a merge issue, this appears to be readding back 1024-bit DH and a few other curves we removed. |
|
Now that I've had some time to look at this again, making pynacl an option dependency was pretty easy. With the exception that pyflakes doesn't like how I'm importing nacl.public into _kex.py but not using it. I see why pyflakes is objecting to this, but I'm doing this to check to see if pynacl is installed before adding curve25519 as a supported kex. I could create a nacl.public object to make pyflakes happy, but that doesn't seem like a good solution to me. How should I go about this? |
|
You can use |
|
@alex yeah as with our conversation on #749 I think those curves should be in Twisted, and since they were originally included in this request I'm adding them back in. 1024-DH I put back in as well because it was also originally in this merge, and while I know it's been deprecated, it's still supported and I think it's worth supporting here as it's still widely used and I believe Twisted should be as compatible with as many applications as possible. |
|
@alex Oh thanks for the link, I'll look into that. |
|
This PR is for adding X25519 and Ed25519 support, disagreements about 1024-bit DH or other curves belong somewhere else, not here. |
the0id
added some commits
Apr 7, 2017
|
@alex I guess you're right. I'm reverting those changes and will open a new PR. |
the0id
added some commits
Apr 7, 2017
|
I'm not sure if my last commit made things better or worse, but the root of the problem seems to be the same. The tests are failing because nacl isn't present. Since those tests are skipped, codecov fails. Is there a way to get TravisCI to use nacl without having it be required in _setup.py? How should I do this? |
|
My preference would be for you to add it as a new required dependency of the |
the0id
added some commits
Apr 11, 2017
|
@glyph I see what you're saying now. No problem, can do, as soon as I figure out how to do. :) |
the0id
added some commits
Apr 14, 2017
|
I can't tell if I did this right. The new tests ran but codecov didn't. |
|
Codecov has the occasional reliability issue :-\ |
the0id
added some commits
Apr 19, 2017
|
I got 99 commits, so lets merge this one. |
| @@ -126,6 +127,25 @@ class _DHGroupExchangeSHA1(object): | ||
| @implementer(_IFixedGroupKexAlgorithm) | ||
| +class _DHGroup1SHA1(object): |
| @@ -3,7 +3,7 @@ | ||
| # See LICENSE for details. | ||
| """ | ||
| -Handling of RSA, DSA, and EC keys. | ||
| +Handling of RSA, DSA, EC, and *25519 keys. |
alex
May 27, 2017
Contributor
This should just say Ed25519, this module doesn't do anything with X25519
| @@ -289,6 +304,9 @@ def _fromString_PRIVATE_BLOB(cls, blob): | ||
| elif keyType == b'ssh-dss': | ||
| p, q, g, y, x, rest = common.getMP(rest, 5) | ||
| return cls._fromDSAComponents(y=y, g=g, p=p, q=q, x=x) | ||
| + elif keyType == b'ssh-ed25519': | ||
| + pr = nacl.signing.SigningKey(common.getNS(rest)[0]) |
alex
May 27, 2017
Contributor
The variable name pr has me a bit confused; can we pick something more intuitive?
| @@ -589,7 +617,10 @@ def _guessStringType(cls, data): | ||
| return 'public_lsh' | ||
| elif data.startswith(b'('): | ||
| return 'private_lsh' | ||
| - elif data.startswith(b'\x00\x00\x00\x07ssh-') or data.startswith(b'\x00\x00\x00\x13ecdsa-'): | ||
| + elif data.startswith(b'\x00\x00\x00\x0bssh-'): # ED25519 |
alex
May 27, 2017
Contributor
The data will continue ssh-ed25519 right? Might be easier to read if you include that in the string.
the0id
May 31, 2017
Contributor
It will, and I see what you're saying, but I think it's better how it is. It's a little faster to process and it fits the convention of the rest of the checks. :)
| @@ -786,7 +841,8 @@ def __repr__(self): | ||
| o = o[:-1] | ||
| lines.append('\t' + o) | ||
| lines[-1] = lines[-1] + '>' | ||
| - return '\n'.join(lines) | ||
| + reprstr = '\n'.join(lines) | ||
| + return reprstr |
alex
May 27, 2017
Contributor
These changes appear to be unrelated, there's nothing wrong with just return when you have the value.
the0id
May 31, 2017
Contributor
Moving the return where it is now fits in better with the block. Each conditional creates a reprstr value, with a single return of that value at the end.
| @@ -901,7 +958,10 @@ def public(self): | ||
| @rtype: L{Key} | ||
| @return: A public key. | ||
| """ | ||
| - return Key(self._keyObject.public_key()) | ||
| + if self.type() is 'ED25519': |
| + elif isinstance( | ||
| + self._keyObject, (nacl.signing.VerifyKey, | ||
| + nacl.signing.SigningKey)): | ||
| + return 'ED25519' |
the0id
May 31, 2017
Contributor
I think it's better to leave this all caps because the rest of the return strings are in all caps.
| - | ||
| - if self.type() == 'EC': | ||
| + if self.type() == 'ED25519': | ||
| + # OpenSSH loosely follows the openssh-key-v1 specification |
| + try: | ||
| + key.verify(data, common.getNS(signature)[0]) | ||
| + return True | ||
| + except: |
| @@ -1578,8 +1602,17 @@ def ssh_KEXINIT(self, packet): | ||
| # Connection was disconnected while doing base processing. | ||
| # Maybe no common protocols were agreed. | ||
| return | ||
| + # Curve25519 | ||
| + if self.kexAlg.find(b'curve25519') >= 0: |
|
There's also a merge conflict. |
|
You need to take a merge from trunk and move your 8966.feature file to the newsfragments directory. Things have moved around due to this: #763 |
the0id
added some commits
May 31, 2017
|
@alex, @rodrigc thanks for the review. I've made some of the changes requested, and gave my reasons for not changing the others. @rodrigc thanks for the head's up about moving the file to newsfragments. @markrwilliams helped me out with the same thing on another PR. :) |
the0id
added some commits
Aug 18, 2017
|
The last two times appveyor has run two of the tests fail because openssh can't be installed.
|
|
@rodrigc Thanks, I missed that message. |
the0id commentedDec 23, 2016
This adds curve25519 and ed25519 to Twisted. Cryptography doesn't support 25519 yet, so pyNaCL is required.