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

Support cipher chacha20-poly1305@openssh.com #682

Merged
merged 2 commits into from
Apr 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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