Skip to content

Commit

Permalink
Support cipher chacha20-poly1305@openssh.com (#682)
Browse files Browse the repository at this point in the history
* Added cipher chacha20-poly1305@openssh.com

* Small refactoring and remove mutable static buffer

Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
  • Loading branch information
hpoettker and hierynomus authored Apr 20, 2021
1 parent e283880 commit 16db036
Show file tree
Hide file tree
Showing 11 changed files with 343 additions and 10 deletions.
2 changes: 1 addition & 1 deletion README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ In the `examples` directory, there is a separate Maven project that shows how th
Implementations / adapters for the following algorithms are included:

ciphers::
`aes{128,192,256}-{cbc,ctr}`, `aes{128,256}-gcm@openssh.com`, `blowfish-{cbc,ctr}`, `3des-{cbc,ctr}`, `twofish{128,192,256}-{cbc,ctr}`, `twofish-cbc`, `serpent{128,192,256}-{cbc,ctr}`, `idea-{cbc,ctr}`, `cast128-{cbc,ctr}`, `arcfour`, `arcfour{128,256}`
`aes{128,192,256}-{cbc,ctr}`, `aes{128,256}-gcm@openssh.com`, `blowfish-{cbc,ctr}`, `chacha20-poly1305@openssh.com`, `3des-{cbc,ctr}`, `twofish{128,192,256}-{cbc,ctr}`, `twofish-cbc`, `serpent{128,192,256}-{cbc,ctr}`, `idea-{cbc,ctr}`, `cast128-{cbc,ctr}`, `arcfour`, `arcfour{128,256}`
SSHJ also supports the following extended (non official) ciphers: `camellia{128,192,256}-{cbc,ctr}`, `camellia{128,192,256}-{cbc,ctr}@openssh.org`

key exchange::
Expand Down
2 changes: 1 addition & 1 deletion src/itest/docker-image/test-container/sshd_config
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,4 @@ macs umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.

TrustedUserCAKeys /etc/ssh/users_rsa_ca.pub

Ciphers 3des-cbc,blowfish-cbc,aes128-cbc,aes192-cbc,aes256-cbc,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com
Ciphers 3des-cbc,blowfish-cbc,aes128-cbc,aes192-cbc,aes256-cbc,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com,chacha20-poly1305@openssh.com
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ class CipherSpec extends IntegrationBaseSpec {
BlockCiphers.AES256CBC(),
BlockCiphers.AES256CTR(),
GcmCiphers.AES128GCM(),
GcmCiphers.AES256GCM()]
GcmCiphers.AES256GCM(),
ChachaPolyCiphers.CHACHA_POLY_OPENSSH()]
cipher = cipherFactory.name
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hierynomus.sshj.transport.cipher;

import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;

import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays;
import javax.crypto.spec.IvParameterSpec;

import net.schmizz.sshj.common.SSHRuntimeException;
import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.transport.cipher.BaseCipher;

public class ChachaPolyCipher extends BaseCipher {

private static final int CHACHA_KEY_SIZE = 32;
private static final int AAD_LENGTH = 4;
private static final int POLY_TAG_LENGTH = 16;

private static final String CIPHER_CHACHA = "CHACHA";
private static final String MAC_POLY1305 = "POLY1305";

private static final byte[] POLY_KEY_INPUT = new byte[32];

private final int authSize;

private byte[] encryptedAad;

protected Mode mode;
protected javax.crypto.Cipher aadCipher;
protected javax.crypto.Mac mac;
protected java.security.Key cipherKey;
protected java.security.Key aadCipherKey;

public ChachaPolyCipher(int authSize, int bsize, String algorithm) {
super(0, bsize, algorithm, CIPHER_CHACHA);
this.authSize = authSize;
}

@Override
public int getAuthenticationTagSize() {
return authSize;
}

@Override
public void setSequenceNumber(long seq) {
byte[] seqAsBytes = longToBytes(seq);
AlgorithmParameterSpec ivSpec = new IvParameterSpec(seqAsBytes);

try {
cipher.init(getMode(mode), cipherKey, ivSpec);
aadCipher.init(getMode(mode), aadCipherKey, ivSpec);
} catch (GeneralSecurityException e) {
throw new SSHRuntimeException(e);
}

byte[] polyKeyBytes = cipher.update(POLY_KEY_INPUT);
cipher.update(POLY_KEY_INPUT); // this update is required to set the block counter of ChaCha to 1
try {
mac.init(getKeySpec(polyKeyBytes));
} catch (GeneralSecurityException e) {
throw new SSHRuntimeException(e);
}

encryptedAad = null;
}

@Override
protected void initCipher(javax.crypto.Cipher cipher, Mode mode, byte[] key, byte[] iv)
throws InvalidKeyException, InvalidAlgorithmParameterException {
this.mode = mode;

cipherKey = getKeySpec(Arrays.copyOfRange(key, 0, CHACHA_KEY_SIZE));
aadCipherKey = getKeySpec(Arrays.copyOfRange(key, CHACHA_KEY_SIZE, 2 * CHACHA_KEY_SIZE));

try {
aadCipher = SecurityUtils.getCipher(CIPHER_CHACHA);
mac = SecurityUtils.getMAC(MAC_POLY1305);
} catch (GeneralSecurityException e) {
cipher = null;
aadCipher = null;
mac = null;
throw new SSHRuntimeException(e);
}

setSequenceNumber(0);
}

@Override
public void updateAAD(byte[] data, int offset, int length) {
if (offset != 0 || length != AAD_LENGTH) {
throw new IllegalArgumentException(
String.format("updateAAD called with offset %d and length %d", offset, length));
}

if (mode == Mode.Decrypt) {
encryptedAad = Arrays.copyOfRange(data, 0, AAD_LENGTH);
}

try {
aadCipher.update(data, 0, AAD_LENGTH, data, 0);
} catch (GeneralSecurityException e) {
throw new SSHRuntimeException("Error updating data through cipher", e);
}
}

@Override
public void updateAAD(byte[] data) {
updateAAD(data, 0, AAD_LENGTH);
}

@Override
public void update(byte[] input, int inputOffset, int inputLen) {
if (inputOffset != AAD_LENGTH) {
throw new IllegalArgumentException("updateAAD called with inputOffset " + inputOffset);
}

final int macInputLength = AAD_LENGTH + inputLen;

if (mode == Mode.Decrypt) {
byte[] macInput = new byte[macInputLength];
System.arraycopy(encryptedAad, 0, macInput, 0, AAD_LENGTH);
System.arraycopy(input, AAD_LENGTH, macInput, AAD_LENGTH, inputLen);

byte[] expectedPolyTag = mac.doFinal(macInput);
byte[] actualPolyTag = Arrays.copyOfRange(input, macInputLength, macInputLength + POLY_TAG_LENGTH);
if (!Arrays.equals(actualPolyTag, expectedPolyTag)) {
throw new SSHRuntimeException("MAC Error");
}
}

try {
cipher.update(input, AAD_LENGTH, inputLen, input, AAD_LENGTH);
} catch (GeneralSecurityException e) {
throw new SSHRuntimeException("Error updating data through cipher", e);
}

if (mode == Mode.Encrypt) {
byte[] macInput = Arrays.copyOf(input, macInputLength);
byte[] polyTag = mac.doFinal(macInput);
System.arraycopy(polyTag, 0, input, macInputLength, POLY_TAG_LENGTH);
}
}

private byte[] longToBytes(long lng) {
return new byte[] { (byte) (lng >> 56), (byte) (lng >> 48), (byte) (lng >> 40), (byte) (lng >> 32),
(byte) (lng >> 24), (byte) (lng >> 16), (byte) (lng >> 8), (byte) lng };
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hierynomus.sshj.transport.cipher;

import net.schmizz.sshj.transport.cipher.Cipher;

public class ChachaPolyCiphers {

public static Factory CHACHA_POLY_OPENSSH() {
return new Factory(16, 512, "chacha20-poly1305@openssh.com", "ChaCha20");
}

public static class Factory
implements net.schmizz.sshj.common.Factory.Named<Cipher> {

private final int authSize;
private final int keySize;
private final String name;
private final String cipher;

public Factory(int authSize, int keySize, String name, String cipher) {
this.authSize = authSize;
this.keySize = keySize;
this.name = name;
this.cipher = cipher;
}

@Override
public Cipher create() {
return new ChachaPolyCipher(authSize, keySize / 8, cipher);
}

@Override
public String getName() {
return name;
}

@Override
public String toString() {
return getName();
}
}
}
5 changes: 1 addition & 4 deletions src/main/java/net/schmizz/sshj/transport/Decoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ private SSHPacket decompressed()
}

private int decryptLengthAAD() throws TransportException {
cipher.setSequenceNumber(seq + 1 & 0xffffffffL);
cipher.updateAAD(inputBuffer.array(), 0, 4);

final int len;
Expand Down Expand Up @@ -185,10 +186,6 @@ private void checkPacketLength(int len) throws TransportException {
}
}

// private void decryptPayload(final byte[] data, int offset, int length) {
// cipher.update(data, cipherSize, packetLength + 4 - cipherSize);
// }

/**
* Adds {@code len} bytes from {@code b} to the decoder buffer. When a packet has been successfully decoded, hooks
* in to {@link SSHPacketHandler#handle} of the {@link SSHPacketHandler} this decoder was initialized with.
Expand Down
5 changes: 2 additions & 3 deletions src/main/java/net/schmizz/sshj/transport/Encoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@ private void putMAC(SSHPacket buffer, int startOfPacket, int endOfPadding) {
* @param buffer the buffer to encode
*
* @return the sequence no. of encoded packet
*
* @throws TransportException
*/
long encode(SSHPacket buffer) {
encodeLock.lock();
Expand Down Expand Up @@ -140,11 +138,12 @@ long encode(SSHPacket buffer) {
}
}

protected void aeadOutgoingBuffer(Buffer buf, int offset, int len) {
protected void aeadOutgoingBuffer(Buffer<?> buf, int offset, int len) {
if (cipher == null || cipher.getAuthenticationTagSize() == 0) {
throw new IllegalArgumentException("AEAD mode requires an AEAD cipher");
}
byte[] data = buf.array();
cipher.setSequenceNumber(seq);
cipher.updateWithAAD(data, offset, 4, len);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,9 @@ public void updateWithAAD(byte[] input, int offset, int aadLen, int inputLen) {
updateAAD(input, offset, aadLen);
update(input, offset + aadLen, inputLen);
}

@Override
public void setSequenceNumber(long seq) {

}
}
2 changes: 2 additions & 0 deletions src/main/java/net/schmizz/sshj/transport/cipher/Cipher.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,6 @@ enum Mode {
* @param inputLen The number of bytes to update - starting at offset + aadLen
*/
void updateWithAAD(byte[] input, int offset, int aadLen, int inputLen);

void setSequenceNumber(long seq);
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,9 @@ public void updateAAD(byte[] data) {
public void updateWithAAD(byte[] input, int offset, int aadLen, int inputLen) {

}

@Override
public void setSequenceNumber(long seq) {

}
}
Loading

0 comments on commit 16db036

Please sign in to comment.