Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SSHD-506] Add support for RFC 5647 #132

Closed
wants to merge 20 commits into from
Closed

Conversation

jvz
Copy link
Member

@jvz jvz commented May 18, 2020

This is a work in progress that does not work yet, and the API changes to Cipher/CipherInformation are still in flux.

I've been testing this by changing CoreTestSupportUtils to set up clients and servers with only the AES/GCM cipher enabled. So far, I've had varying problems with getting this to work, and I've been using OpenSSH as a comparison to no further help. For reference, here are the relevant source files:

  • packet.c
  • cipher.c (and maybe a reference to the OpenSSL API to understand all the EVP_ functions)
  • kex.c (slightly less relevant)

This is a work in progress that does not work yet, and the API changes to Cipher/CipherInformation are still in flux.

Signed-off-by: Matt Sicker <boards@gmail.com>
Copy link
Contributor

@lgoldstein lgoldstein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks very promising - see a few minor comments. Please make sure you run the full mvn clean install (I see there are some Checkstyle issues...)

jvz added 2 commits May 20, 2020 17:55
Signed-off-by: Matt Sicker <boards@gmail.com>
Copy link
Contributor

@lgoldstein lgoldstein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we also add these ciphers to the default ones used by the client/server (see their respective builder(s)) ? If so, at which priority ?

@jvz
Copy link
Member Author

jvz commented May 21, 2020

Yes, these should be added as defaults once they're working. Based on how OpenSSH is distributed nowadays, these should the highest priority ciphers. I've seen ChaCha20 taking precedence as well, though that's not supported here yet (plus requires either Java 11 or Bouncycastle or some other library).

Signed-off-by: Matt Sicker <boards@gmail.com>
@jvz
Copy link
Member Author

jvz commented May 22, 2020

So it seems I've created an incompatible implementation with OpenSSH at the moment, but it talks to itself. Still debugging this before this can move anywhere.

This seems to match what OpenSSH is doing. I think.
Comment on lines 51 to 60
protected AlgorithmParameterSpec getNextAlgorithmParameters() {
Cipher cipher = getCipherInstance();
Buffer iv = new ByteArrayBuffer(cipher.getIV());
iv.rpos(Integer.BYTES);
long ic = iv.getLong();
ic = (ic + 1) & 0x0ffffffffL;
iv.wpos(Integer.BYTES);
iv.putLong(ic);
return new GCMParameterSpec(getAuthenticationTagSize() * Byte.SIZE, iv.array());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty expensive in memory - every time it is called (which is for every packet) a new byte[] (cipher.getIv() returns a clone), new Buffer and new GCMParameterSpec instance is created. Can we do something about it ?

Moreover, this code is exactly the same as initializeAlgorithmParameters except it does +1 instead of -1 - perhaps we could use an internal (protected) updateAlgorithmParameters(byte[] iv, long delta) and call it once with +1 and once with -1

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we can avoid creating the Buffer since all the code does is replace an 8-byte section of the IV at offset 4:

byte[] iv = cipher.getIV();
// Need to write decodeLong  but it should be similar to decodeInt in the same class
// can use the code of Buffer#getLong as base
long ic = KeyEntryResolver.decodeLong(iv, Integer.BYTES, iv.length - Integer.BYTES);
ic = (ic + delta) & 0x0ffffffffL;
// Need to write it - can use the code of Buffer#putLong as base
KeyEntryResolver.encodeLong(iv, ic, Integer.BYTES, iv.length - Integer.BYTES);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooh, nice find. Yes, we just need to update the 8-byte section of the buffer. The update algorithm parameters API idea sounds good to me; it's more in line with how OpenSSH is structured, too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding the GCMParameterSpec based on this link we might be able to execute an ugly hack via reflection API and manipulate the internal IV data directly instead of creating a new instance every time... will need to think about it some more - but only after this code is working.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I came up with a better idea: extend the class and don't clone the IV. Less safe, but doesn't continually create garbage.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've mostly optimized away all the garbage here. I do an initial IV clone when calling the public init method, but that's the only time. I don't see the IV being cloned further by the Cipher classes that use GCMParameterSpec.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, I optimized it so much, both AES/GCM engines can't tell that the IV changes between invocations and throw an exception. Nice.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't figure out how to get the engine to consider the IV as different, so I still have some clone calls happening, but much less waste than before.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into the internal implementation of the cipher engine, and it makes its own clone regardless of what we do, so this seems to be about as optimal as I can get it without digging into Bouncycastle internals or something similar.

jvz added 6 commits May 22, 2020 18:51
Signed-off-by: Matt Sicker <boards@gmail.com>
Signed-off-by: Matt Sicker <boards@gmail.com>
Signed-off-by: Matt Sicker <boards@gmail.com>
Forgot about this

Signed-off-by: Matt Sicker <boards@gmail.com>
Works on my machine®

Signed-off-by: Matt Sicker <boards@gmail.com>
@jvz
Copy link
Member Author

jvz commented May 23, 2020

The tests all pass on my machine. I'm not sure what's going on with the failing one on here.

@lgoldstein
Copy link
Contributor

The tests all pass on my machine. I'm not sure what's going on with the failing one on here.

That's OK - if you feel the code is ready as far as you are concerned, let me know. Once I have examined it and successfully ran a full mvn clean install on my machine I am willing to merge it. Our Travis builds are not yet 100% stable so if they fail but the tests succeed on my machine I usually consider the code safe to merge.

That being said, please run some tests with OpenSSH clients/servers that support these ciphers as well as some popular applications such as Putty, WinSCP, FileZilla

@jvz
Copy link
Member Author

jvz commented May 24, 2020 via email

@lgoldstein
Copy link
Contributor

This may be on hold for a week or so as I’m on vacation this week.

Enjoy your vacation - no hurry - when you feel ready I will be happy to review...

@jvz
Copy link
Member Author

jvz commented Jun 2, 2020

I merged the latest changes from master and did some basic smoketesting using the following:

Server: OpenSSH_8.1p1, LibreSSL 2.7.3
Command: ./ssh.sh -c 'aes128-gcm@openssh.com' localhost

I'll do some more testing in the opposite direction as well as some cleanups.

@jvz
Copy link
Member Author

jvz commented Jun 2, 2020

Also tested with FileZilla so far. It even defaulted to selecting aes256-gcm! Seems to work.

Signed-off-by: Matt Sicker <boards@gmail.com>
@jvz
Copy link
Member Author

jvz commented Jun 2, 2020

I can't seem to find any official documentation on what the RFC 5647 cipher names are supposed to be unless they meant to literally use AEAD_AES_128_GCM and AEAD_AES_256_GCM as the cipher names. That seems odd since every other cipher name is in lowercase and uses dashes instead of underscores.

Edit: neither FileZilla nor OpenSSH even reference RFC 5647. Is this the first implementation of the spec in 11 years?

@jvz jvz marked this pull request as ready for review June 2, 2020 03:34
@jvz
Copy link
Member Author

jvz commented Jun 2, 2020

Based on what I'm finding online, it does seem like the RFC specifies the all caps version. I'm going to try adding that as well.

jvz added 2 commits June 13, 2020 16:28
This simplifies the new AEAD API a bit based on usage.

Signed-off-by: Matt Sicker <boards@gmail.com>
Signed-off-by: Matt Sicker <boards@gmail.com>
@jvz
Copy link
Member Author

jvz commented Jun 14, 2020

I also tested this with dropbear so far, all good. Also tested with PuTTY (on macOS).

Feel free to test in any other platforms you're concerned with, but I feel fairly confident with the patch.

@lgoldstein
Copy link
Contributor

Feel free to test in any other platforms you're concerned with, but I feel fairly confident with the patch.

I will do so some time soon - does this mean that I can merge this patch if I am satisfied with it ?

Signed-off-by: Matt Sicker <boards@gmail.com>
@jvz
Copy link
Member Author

jvz commented Jun 14, 2020

I will do so some time soon - does this mean that I can merge this patch if I am satisfied with it ?

Yes, I think so. I'm a little iffy on the BaseGCMCipher name for the class as I'm imagining a different use of AES/GCM along the lines of how OpenSSH implements ChaCha20-Poly1305 which would behave differently (the additional authenticated data would be encrypted but not authenticated using another AES key using AES/CTR like how in the packet length is encrypted). Otherwise ready to merge.

@lgoldstein
Copy link
Contributor

Yes, I think so. I'm a little iffy on the BaseGCMCipher name for the class as I'm imagining a different use of AES/GCM along the lines of how OpenSSH implements ChaCha20-Poly1305 which would behave differently (the additional authenticated data would be encrypted but not authenticated using another AES key using AES/CTR like how in the packet length is encrypted).

We'll cross that bridge when we come to it - refactoring is not a dirty word in this project

@jvz
Copy link
Member Author

jvz commented Jun 20, 2020

Tested on an RPi4 (arm64), works fine.

jvz added 3 commits June 21, 2020 14:18
Signed-off-by: Matt Sicker <boards@gmail.com>
Signed-off-by: Matt Sicker <boards@gmail.com>
@jvz
Copy link
Member Author

jvz commented Jun 27, 2020

While working on SSHD-1017, I discovered a couple refactoring improvements to add here.

@lgoldstein
Copy link
Contributor

Seems like it is still work in progress - I will hold off testing it and merging into master branch for now

@jvz
Copy link
Member Author

jvz commented Jun 27, 2020 via email

@lgoldstein
Copy link
Contributor

I’m trying to make the changes required for both ciphers to be as similar and minimal as possible. I’ll let you know once this is final again!

Great - this contribution is very important so thank you for the effort...

@lgoldstein
Copy link
Contributor

Sorry to bother you, but have you made any progress ? The reason I ask is that I would like to start deprecating unsafe settings (see SSHD-1004) - and that includes some ciphers. Since this may require some non-trivial changes, I would prefer to do it after having added these ciphers. If the work on ChaCha seems to take a short while (let's say another 1-2 weeks) then I can wait. Otherwise, I would rather incorporate the GCM code into the master branch.

@jvz
Copy link
Member Author

jvz commented Jul 28, 2020

Oh hey, sorry about that. This branch is basically ready at this point as I got stuck trying to get ChaCha20-Poly1305 to work properly. From what I recall, I already updated the code in this branch to match the same style I was hoping to use for that cipher (particularly by making the ChaCha version of updateAAD handle the encryption/decryption aspect, too). As that aspect shouldn't change too much, and based on the refactoring culture here, I don't think there's much need to wait any longer on the second feature.

@lgoldstein
Copy link
Contributor

Thanks - then I'll start merging it and testing...

@lgoldstein
Copy link
Contributor

Committed dc73e260f with many thanks - you can close the PR

@jvz
Copy link
Member Author

jvz commented Jul 31, 2020

Thanks!

@jvz jvz closed this Jul 31, 2020
@jvz jvz deleted the gcm-cipher-SSHD-506 branch July 31, 2020 14:19
@jvz jvz restored the gcm-cipher-SSHD-506 branch July 31, 2020 14:19
@jvz jvz deleted the gcm-cipher-SSHD-506 branch July 31, 2020 14:19
@jvz jvz restored the gcm-cipher-SSHD-506 branch July 31, 2020 14:19
hierynomus pushed a commit to hierynomus/sshj that referenced this pull request Sep 9, 2020
* Implement AES-GCM cipher support

Fixes #217.

A port of AES-GCM cipher support from Apache MINA-SSHD, based on apache/mina-sshd#132.

Included tests for decoding SSH packets sent from Apache MINA-SSHD and OpenSSH (Version 7.9p1 as used by Debian 10).

Manual tests also done on OpenSSH server 7.9p1 running Debian 10 with its available ciphers, including 3des-cbc, aes128-cbc, aes192-cbc, aes256-cbc, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com and aes256-gcm@openssh.com.

* Changes per PR feedback

- Fixed variable/statement whitespaces and add back missing braces per coding standard requirement
- Moved Buffer.putLong() and Buffer.getLong() into GcmCipher.CounterGCMParameterSpec since it's the only user
- Moved BaseCipher.authSize into GcmCipher since it is the only cipher that would return a non-zero. BaseCipher will keep return 0 instead
- Made BaseCipher.cipher protected instead of making it publicly accessible
- Combined the three decoding modes in Decoder.decode() into one single method, to reduce code duplication
- Added integration test for the ciphers, along with the newly implemented AES-GCM ciphers
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants