diff --git a/.gitignore b/.gitignore index 8f0cd5d75..cc50e6a7b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ .settings/ # Output dirs +out/ target/ classes/ build/ diff --git a/.travis.yml b/.travis.yml index 72119fb48..246dcd989 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,30 @@ language: java -sudo: false +dist: trusty +sudo: required + +services: + - docker + jdk: - - oraclejdk7 - oraclejdk8 + - openjdk8 + - oraclejdk9 + +before_cache: + - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock + - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ + +cache: + directories: + - $HOME/.gradle/caches/ + - $HOME/.gradle/wrapper/ + +before_install: + - pip install --user codecov + +script: + - ./gradlew check + - ./gradlew integrationTest + +after_success: +- codecov diff --git a/README.adoc b/README.adoc index 858d397f8..e5b698423 100644 --- a/README.adoc +++ b/README.adoc @@ -1,11 +1,14 @@ = sshj - SSHv2 library for Java Jeroen van Erp :sshj_groupid: com.hierynomus -:sshj_version: 0.21.1 +:sshj_version: 0.23.0 :source-highlighter: pygments +image:https://api.bintray.com/packages/hierynomus/maven/sshj/images/download.svg[link="https://bintray.com/hierynomus/maven/sshj/_latestVersion"] image:https://travis-ci.org/hierynomus/sshj.svg?branch=master[link="https://travis-ci.org/hierynomus/sshj"] image:https://api.codacy.com/project/badge/Grade/14a0a316bb9149739b5ea26dbfa8da8a["Codacy code quality", link="https://www.codacy.com/app/jeroen_2/sshj?utm_source=github.com&utm_medium=referral&utm_content=hierynomus/sshj&utm_campaign=Badge_Grade"] +image:https://codecov.io/gh/hierynomus/sshj/branch/master/graph/badge.svg["codecov", link="https://codecov.io/gh/hierynomus/sshj"] +image:http://www.javadoc.io/badge/com.hierynomus/sshj.svg?color=blue["JavaDocs", link="http://www.javadoc.io/doc/com.hierynomus/sshj"] image:https://maven-badges.herokuapp.com/maven-central/com.hierynomus/sshj/badge.svg["Maven Central",link="https://maven-badges.herokuapp.com/maven-central/com.hierynomus/sshj"] image:https://javadoc-emblem.rhcloud.com/doc/com.hierynomus/sshj/badge.svg["Javadoc",link="http://www.javadoc.io/doc/com.hierynomus/sshj"] @@ -75,7 +78,7 @@ key exchange:: `diffie-hellman-group16-sha256`, `diffie-hellman-group16-sha384@ssh.com`, `diffie-hellman-group16-sha512@ssh.com`, `diffie-hellman-group18-sha512@ssh.com` signatures:: - `ssh-rsa`, `ssh-dss`, `ecdsa-sha2-nistp256`, `ssh-ed25519` + `ssh-rsa`, `ssh-dss`, `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`, `ecdsa-sha2-nistp521`, `ssh-ed25519` mac:: `hmac-md5`, `hmac-md5-96`, `hmac-sha1`, `hmac-sha1-96`, `hmac-sha2-256`, `hmac-sha2-512` @@ -104,7 +107,16 @@ Google Group: http://groups.google.com/group/sshj-users Fork away! == Release history -SSHJ 0.22.0 (2017-??-??):: +SSHJ 0.23.0 (2017-10-13):: +* Merged https://github.com/hierynomus/sshj/pulls/372[#372]: Upgrade to 'net.i2p.crypto:eddsa:0.2.0' +* Fixed https://github.com/hierynomus/sshj/issues/355[#355] and https://github.com/hierynomus/sshj/issues/354[#354]: Correctly decode signature bytes +* Fixed https://github.com/hierynomus/sshj/issues/365[#365]: Added support for new-style OpenSSH fingerprints of server keys +* Fixed https://github.com/hierynomus/sshj/issues/356[#356]: Fixed key type detection for ECDSA public keys +* Made SSHJ Java9 compatible +SSHJ 0.22.0 (2017-08-24):: +* Fixed https://github.com/hierynomus/sshj/pulls/341[#341]: Fixed path walking during recursive copy +* Merged https://github.com/hierynomus/sshj/pulls/338[#338]: Added ConsolePasswordFinder to read password from stdin +* Merged https://github.com/hierynomus/sshj/pulls/336[#336]: Added support for ecdsa-sha2-nistp384 and ecdsa-sha2-nistp521 signatures * Fixed https://github.com/hierynomus/sshj/issues/331[#331]: Added support for wildcards in known_hosts file SSHJ 0.21.1 (2017-04-25):: * Merged https://github.com/hierynomus/sshj/pulls/322[#322]: Fix regression from 40f956b (invalid length parameter on outputstream) diff --git a/build.gradle b/build.gradle index 5a883ebee..81b1f30b6 100644 --- a/build.gradle +++ b/build.gradle @@ -1,22 +1,32 @@ import java.text.SimpleDateFormat +import com.bmuschko.gradle.docker.tasks.container.* +import com.bmuschko.gradle.docker.tasks.image.* plugins { id "java" id "groovy" + id "jacoco" id "osgi" id "maven-publish" - id "org.ajoberstar.release-opinion" version "1.4.2" + id "com.bmuschko.docker-remote-api" version "3.2.1" + id 'pl.allegro.tech.build.axion-release' version '1.8.1' id "com.github.hierynomus.license" version "0.12.1" id "com.jfrog.bintray" version "1.7" - id 'ru.vyarus.pom' version '1.0.3' + id 'ru.vyarus.java-lib' version '1.0.5' + // id 'ru.vyarus.pom' version '1.0.3' id 'ru.vyarus.github-info' version '1.1.0' + id 'ru.vyarus.animalsniffer' version '1.4.2' } group = "com.hierynomus" + defaultTasks "build" repositories { mavenCentral() + maven { + url "https://dl.bintray.com/mockito/maven/" + } } sourceCompatibility = 1.6 @@ -24,19 +34,21 @@ targetCompatibility = 1.6 configurations.compile.transitive = false -def bouncycastleVersion = "1.56" +def bouncycastleVersion = "1.57" dependencies { + signature 'org.codehaus.mojo.signature:java16:1.1@signature' + compile "org.slf4j:slf4j-api:1.7.7" compile "org.bouncycastle:bcprov-jdk15on:$bouncycastleVersion" compile "org.bouncycastle:bcpkix-jdk15on:$bouncycastleVersion" compile "com.jcraft:jzlib:1.1.3" - compile "net.i2p.crypto:eddsa:0.1.0" + compile "net.i2p.crypto:eddsa:0.2.0" testCompile "junit:junit:4.11" testCompile 'org.spockframework:spock-core:1.0-groovy-2.4' - testCompile "org.mockito:mockito-core:2.8.47" + testCompile "org.mockito:mockito-core:2.9.2" testCompile "org.apache.sshd:sshd-core:1.2.0" testRuntime "ch.qos.logback:logback-classic:1.1.2" testCompile 'org.glassfish.grizzly:grizzly-http-server:2.3.17' @@ -53,14 +65,19 @@ license { excludes(['**/djb/Curve25519.java', '**/sshj/common/Base64.java']) } -if (project.file('.git').isDirectory()) { - release { - grgit = org.ajoberstar.grgit.Grgit.open(project.projectDir) +scmVersion { + tag { + prefix = 'v' + versionSeparator = '' + } + hooks { + pre 'fileUpdate', [file: 'README.adoc', pattern: { v, c -> /:sshj_version: .*/}, replacement: { v, c -> ":sshj_version: $v" }] + pre 'commit' } -} else { - version = "0.0.0-no.git" } +project.version = scmVersion.version + // This disables the pedantic doclint feature of JDK8 if (JavaVersion.current().isJava8Compatible()) { tasks.withType(Javadoc) { @@ -78,7 +95,6 @@ task writeSshjVersionProperties { } jar.dependsOn writeSshjVersionProperties - jar { manifest { // please see http://bnd.bndtools.org/chapters/390-wrapping.html @@ -99,14 +115,7 @@ jar { } } -task javadocJar(type: Jar) { - classifier = 'javadoc' - from javadoc -} - -task sourcesJar(type: Jar) { - classifier = 'sources' - from sourceSets.main.allSource +sourcesJar { manifest { attributes( // Add the needed OSGI attributes @@ -119,6 +128,27 @@ task sourcesJar(type: Jar) { } } +configurations { + integrationTestCompile.extendsFrom testCompile + integrationTestRuntime.extendsFrom testRuntime +} + +sourceSets { + integrationTest { + groovy { + compileClasspath += sourceSets.main.output + sourceSets.test.output + runtimeClasspath += sourceSets.main.output + sourceSets.test.output + srcDir file('src/itest/groovy') + } + resources.srcDir file('src/itest/resources') + } +} + +task integrationTest(type: Test) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} + tasks.withType(Test) { testLogging { exceptionFormat = 'full' @@ -185,21 +215,12 @@ pom { } } -publishing.publications { - Sshj(MavenPublication) { - from components.java - artifact sourcesJar - artifact javadocJar - } -} - - if (project.hasProperty("bintrayUsername") && project.hasProperty("bintrayApiKey")) { bintray { user = project.property("bintrayUsername") key = project.property("bintrayApiKey") publish = true - publications = ["Sshj"] + publications = ["maven"] pkg { repo = "maven" name = project.name @@ -226,4 +247,38 @@ if (project.hasProperty("bintrayUsername") && project.hasProperty("bintrayApiKey } } -project.tasks.release.dependsOn([project.tasks.build, project.tasks.bintrayUpload]) +jacocoTestReport { + reports { + xml.enabled true + html.enabled true + } +} + + +task buildItestImage(type: DockerBuildImage) { + inputDir = file('src/itest/docker-image') + tag = 'sshj/sshd-itest' +} + +task createItestContainer(type: DockerCreateContainer) { + dependsOn buildItestImage + targetImageId { buildItestImage.getImageId() } + portBindings = ['2222:22'] +} + +task startItestContainer(type: DockerStartContainer) { + dependsOn createItestContainer + targetContainerId { createItestContainer.getContainerId() } +} + +task stopItestContainer(type: DockerStopContainer) { + targetContainerId { createItestContainer.getContainerId() } +} + +project.tasks.integrationTest.dependsOn(startItestContainer) +project.tasks.integrationTest.finalizedBy(stopItestContainer) + +project.tasks.release.dependsOn([project.tasks.integrationTest, project.tasks.build]) +project.tasks.release.finalizedBy(project.tasks.bintrayUpload) +project.tasks.jacocoTestReport.dependsOn(project.tasks.test) +project.tasks.check.dependsOn(project.tasks.jacocoTestReport) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a02b60f27..318b9e3e5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-all.zip diff --git a/src/itest/docker-image/Dockerfile b/src/itest/docker-image/Dockerfile new file mode 100644 index 000000000..b306ac8ca --- /dev/null +++ b/src/itest/docker-image/Dockerfile @@ -0,0 +1,16 @@ +FROM sickp/alpine-sshd:7.5 + +ADD id_rsa.pub /home/sshj/.ssh/authorized_keys + +ADD test-container/ssh_host_ecdsa_key /etc/ssh/ssh_host_ecdsa_key +ADD test-container/ssh_host_ecdsa_key.pub /etc/ssh/ssh_host_ecdsa_key.pub + +RUN \ + echo "root:smile" | chpasswd && \ + adduser -D -s /bin/ash sshj && \ + passwd -u sshj && \ + chmod 600 /home/sshj/.ssh/authorized_keys && \ + chmod 600 /etc/ssh/ssh_host_ecdsa_key && \ + chmod 644 /etc/ssh/ssh_host_ecdsa_key.pub && \ + chown -R sshj:sshj /home/sshj + diff --git a/src/itest/docker-image/id_rsa.pub b/src/itest/docker-image/id_rsa.pub new file mode 100644 index 000000000..6c50ee239 --- /dev/null +++ b/src/itest/docker-image/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAoZ9l6Tkm2aL1tSBy2yw4xU5s8BE9MfqS/4J7DzvsYJxF6oQmTIjmStuhH/CT7UjuDtKXdXZUsIhKtafiizxGO8kHSzKDeitpth2RSr8ddMzZKyD6RNs7MfsgjA3UTtrrSrCXEY6O43S2cnuJrWzkPxtwxaQ3zOvDbS2tiulzyq0VzYmuhA/a4CyuQtJBuu+P2oqmu6pU/VB6IzONpvBvYbNPsH1WDmP7zko5wHPihXPCliztspKxS4DRtOZ7BGXyvg44UmIy0Kf4jOkaBV/eCCA4qH7ZHz71/5ceMOpszPcNOEmLGGYhwI+P3OuGMpkrSAv1f8IY6R8spZNncP6UaQ== no-passphrase diff --git a/src/itest/docker-image/test-container/ssh_host_ecdsa_key b/src/itest/docker-image/test-container/ssh_host_ecdsa_key new file mode 100644 index 000000000..cac0cbe71 --- /dev/null +++ b/src/itest/docker-image/test-container/ssh_host_ecdsa_key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIOpOBFjqe0hjK/hs4WZ3dZqnzanq1L3/JbvV1TCkbe4ToAoGCCqGSM49 +AwEHoUQDQgAEVzkrS7Yj0nXML7A3mE08YDthfBR/ZbyYJDIq1vTzcqs6KTaCT529 +swNXWLHO+mbHviZcRiI57ULXHZ1emom/Jw== +-----END EC PRIVATE KEY----- diff --git a/src/itest/docker-image/test-container/ssh_host_ecdsa_key.pub b/src/itest/docker-image/test-container/ssh_host_ecdsa_key.pub new file mode 100644 index 000000000..9b7f7995f --- /dev/null +++ b/src/itest/docker-image/test-container/ssh_host_ecdsa_key.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFc5K0u2I9J1zC+wN5hNPGA7YXwUf2W8mCQyKtb083KrOik2gk+dvbMDV1ixzvpmx74mXEYiOe1C1x2dXpqJvyc= root@404b27be2bf4 \ No newline at end of file diff --git a/src/itest/groovy/com/hierynomus/sshj/IntegrationBaseSpec.groovy b/src/itest/groovy/com/hierynomus/sshj/IntegrationBaseSpec.groovy new file mode 100644 index 000000000..19ae629c8 --- /dev/null +++ b/src/itest/groovy/com/hierynomus/sshj/IntegrationBaseSpec.groovy @@ -0,0 +1,36 @@ +/* + * 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 + +import net.schmizz.sshj.DefaultConfig +import net.schmizz.sshj.SSHClient +import net.schmizz.sshj.transport.verification.PromiscuousVerifier +import spock.lang.Specification + +class IntegrationBaseSpec extends Specification { + protected static final int DOCKER_PORT = 2222; + protected static final String USERNAME = "sshj"; + protected final static String SERVER_IP = System.getProperty("serverIP", "127.0.0.1"); + + protected static SSHClient getConnectedClient() throws IOException { + SSHClient sshClient = new SSHClient(new DefaultConfig()); + sshClient.addHostKeyVerifier(new PromiscuousVerifier()); + sshClient.connect(SERVER_IP, DOCKER_PORT); + + return sshClient; + } + +} diff --git a/src/itest/groovy/com/hierynomus/sshj/IntegrationSpec.groovy b/src/itest/groovy/com/hierynomus/sshj/IntegrationSpec.groovy new file mode 100644 index 000000000..16d3e004c --- /dev/null +++ b/src/itest/groovy/com/hierynomus/sshj/IntegrationSpec.groovy @@ -0,0 +1,71 @@ +/* + * 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 + +import net.schmizz.sshj.DefaultConfig +import net.schmizz.sshj.SSHClient +import net.schmizz.sshj.transport.TransportException +import net.schmizz.sshj.userauth.UserAuthException + +class IntegrationSpec extends IntegrationBaseSpec { + + def "should accept correct key"() { + given: + SSHClient sshClient = new SSHClient(new DefaultConfig()) + sshClient.addHostKeyVerifier("d3:6a:a9:52:05:ab:b5:48:dd:73:60:18:0c:3a:f0:a3") // test-containers/ssh_host_ecdsa_key's fingerprint + + when: + sshClient.connect(SERVER_IP, DOCKER_PORT) + + then: + sshClient.isConnected() + } + + def "should decline wrong key"() throws IOException { + given: + SSHClient sshClient = new SSHClient(new DefaultConfig()) + sshClient.addHostKeyVerifier("d4:6a:a9:52:05:ab:b5:48:dd:73:60:18:0c:3a:f0:a3") + + when: + sshClient.connect(SERVER_IP, DOCKER_PORT) + + then: + thrown(TransportException.class) + } + + def "should authenticate"() { + given: + SSHClient client = getConnectedClient() + + when: + client.authPublickey("sshj", "src/test/resources/id_rsa") + + then: + client.isAuthenticated() + } + + def "should not authenticate with wrong key"() { + given: + SSHClient client = getConnectedClient() + + when: + client.authPublickey("sshj", "src/test/resources/id_dsa") + + then: + thrown(UserAuthException.class) + !client.isAuthenticated() + } +} diff --git a/src/itest/groovy/com/hierynomus/sshj/sftp/FileWriteSpec.groovy b/src/itest/groovy/com/hierynomus/sshj/sftp/FileWriteSpec.groovy new file mode 100644 index 000000000..d61187df4 --- /dev/null +++ b/src/itest/groovy/com/hierynomus/sshj/sftp/FileWriteSpec.groovy @@ -0,0 +1,68 @@ +/* + * 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.sftp + +import com.hierynomus.sshj.IntegrationBaseSpec +import net.schmizz.sshj.SSHClient +import net.schmizz.sshj.sftp.OpenMode +import net.schmizz.sshj.sftp.RemoteFile +import net.schmizz.sshj.sftp.SFTPClient + +import java.nio.charset.StandardCharsets + +import static org.codehaus.groovy.runtime.IOGroovyMethods.withCloseable + +class FileWriteSpec extends IntegrationBaseSpec { + + def "should append to file (GH issue #390)"() { + given: + SSHClient client = getConnectedClient() + client.authPublickey("sshj", "src/test/resources/id_rsa") + SFTPClient sftp = client.newSFTPClient() + def file = "/home/sshj/test.txt" + def initialText = "This is the initial text.\n".getBytes(StandardCharsets.UTF_16) + def appendText = "And here's the appended text.\n".getBytes(StandardCharsets.UTF_16) + + when: + withCloseable(sftp.open(file, EnumSet.of(OpenMode.WRITE, OpenMode.CREAT))) { RemoteFile initial -> + initial.write(0, initialText, 0, initialText.length) + } + + then: + withCloseable(sftp.open(file, EnumSet.of(OpenMode.READ))) { RemoteFile read -> + def bytes = new byte[initialText.length] + read.read(0, bytes, 0, bytes.length) + bytes == initialText + } + + when: + withCloseable(sftp.open(file, EnumSet.of(OpenMode.WRITE, OpenMode.APPEND))) { RemoteFile append -> + append.write(0, appendText, 0, appendText.length) + } + + then: + withCloseable(sftp.open(file, EnumSet.of(OpenMode.READ))) { RemoteFile read -> + def bytes = new byte[initialText.length + appendText.length] + read.read(0, bytes, 0, bytes.length) + Arrays.copyOfRange(bytes, 0, initialText.length) == initialText + Arrays.copyOfRange(bytes, initialText.length, initialText.length + appendText.length) == appendText + } + + cleanup: + sftp.close() + client.close() + } +} diff --git a/src/main/java/com/hierynomus/sshj/backport/Jdk7HttpProxySocket.java b/src/main/java/com/hierynomus/sshj/backport/Jdk7HttpProxySocket.java index a0d617258..56ea256e6 100644 --- a/src/main/java/com/hierynomus/sshj/backport/Jdk7HttpProxySocket.java +++ b/src/main/java/com/hierynomus/sshj/backport/Jdk7HttpProxySocket.java @@ -15,12 +15,12 @@ */ package com.hierynomus.sshj.backport; +import net.schmizz.sshj.common.IOUtils; + import java.io.IOException; import java.io.InputStream; import java.net.*; -import net.schmizz.sshj.common.IOUtils; - public class Jdk7HttpProxySocket extends Socket { private Proxy httpProxy = null; diff --git a/src/main/java/com/hierynomus/sshj/signature/Ed25519PublicKey.java b/src/main/java/com/hierynomus/sshj/signature/Ed25519PublicKey.java index a9f49bf25..7bad0d3c5 100644 --- a/src/main/java/com/hierynomus/sshj/signature/Ed25519PublicKey.java +++ b/src/main/java/com/hierynomus/sshj/signature/Ed25519PublicKey.java @@ -23,8 +23,6 @@ import java.util.Arrays; -import static net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable.CURVE_ED25519_SHA512; - /** * Our own extension of the EdDSAPublicKey that comes from ECC-25519, as that class does not implement equality. * The code uses the equality of the keys as an indicator whether they're the same during host key verification. @@ -34,7 +32,7 @@ public class Ed25519PublicKey extends EdDSAPublicKey { public Ed25519PublicKey(EdDSAPublicKeySpec spec) { super(spec); - EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName(CURVE_ED25519_SHA512); + EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("Ed25519"); if (!spec.getParams().getCurve().equals(ed25519.getCurve())) { throw new SSHRuntimeException("Cannot create Ed25519 Public Key from wrong spec"); } diff --git a/src/main/java/com/hierynomus/sshj/signature/SignatureEdDSA.java b/src/main/java/com/hierynomus/sshj/signature/SignatureEdDSA.java index dee8f2752..19a1da4ab 100644 --- a/src/main/java/com/hierynomus/sshj/signature/SignatureEdDSA.java +++ b/src/main/java/com/hierynomus/sshj/signature/SignatureEdDSA.java @@ -16,14 +16,16 @@ package com.hierynomus.sshj.signature; import net.i2p.crypto.eddsa.EdDSAEngine; -import net.schmizz.sshj.common.Buffer; import net.schmizz.sshj.common.KeyType; import net.schmizz.sshj.common.SSHRuntimeException; +import net.schmizz.sshj.signature.AbstractSignature; import net.schmizz.sshj.signature.Signature; -import java.security.*; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; -public class SignatureEdDSA implements Signature { +public class SignatureEdDSA extends AbstractSignature { public static class Factory implements net.schmizz.sshj.common.Factory.Named { @Override @@ -37,50 +39,14 @@ public Signature create() { } } - final EdDSAEngine engine; - - protected SignatureEdDSA() { - try { - engine = new EdDSAEngine(MessageDigest.getInstance("SHA-512")); - } catch (NoSuchAlgorithmException e) { - throw new SSHRuntimeException(e); - } - } - - @Override - public void init(PublicKey pubkey, PrivateKey prvkey) { - try { - if (pubkey != null) { - engine.initVerify(pubkey); - } - - if (prvkey != null) { - engine.initSign(prvkey); - } - } catch (InvalidKeyException e) { - throw new SSHRuntimeException(e); - } - } - - @Override - public void update(byte[] H) { - update(H, 0, H.length); + SignatureEdDSA() { + super(getEngine()); } - @Override - public void update(byte[] H, int off, int len) { + private static EdDSAEngine getEngine() { try { - engine.update(H, off, len); - } catch (SignatureException e) { - throw new SSHRuntimeException(e); - } - } - - @Override - public byte[] sign() { - try { - return engine.sign(); - } catch (SignatureException e) { + return new EdDSAEngine(MessageDigest.getInstance("SHA-512")); + } catch (NoSuchAlgorithmException e) { throw new SSHRuntimeException(e); } } @@ -93,17 +59,9 @@ public byte[] encode(byte[] signature) { @Override public boolean verify(byte[] sig) { try { - Buffer.PlainBuffer plainBuffer = new Buffer.PlainBuffer(sig); - String algo = plainBuffer.readString(); - if (!"ssh-ed25519".equals(algo)) { - throw new SSHRuntimeException("Expected 'ssh-ed25519' key algorithm, but was: " + algo); - } - byte[] bytes = plainBuffer.readBytes(); - return engine.verify(bytes); + return signature.verify(extractSig(sig, "ssh-ed25519")); } catch (SignatureException e) { throw new SSHRuntimeException(e); - } catch (Buffer.BufferException e) { - throw new SSHRuntimeException(e); } } } diff --git a/src/main/java/com/hierynomus/sshj/transport/cipher/BlockCiphers.java b/src/main/java/com/hierynomus/sshj/transport/cipher/BlockCiphers.java index ea7818dae..99cf09f34 100644 --- a/src/main/java/com/hierynomus/sshj/transport/cipher/BlockCiphers.java +++ b/src/main/java/com/hierynomus/sshj/transport/cipher/BlockCiphers.java @@ -19,14 +19,15 @@ import net.schmizz.sshj.transport.cipher.Cipher; /** - * All BlockCiphers supported by SSH according to the following RFCs + * All BlockCiphers supported by SSH according to the following RFCs: * - * - https://tools.ietf.org/html/rfc4344#section-3.1 - * - https://tools.ietf.org/html/rfc4253#section-6.3 + * * - * TODO: https://tools.ietf.org/html/rfc5647 - * - * Some of the Ciphers are still implemented in net.schmizz.sshj.transport.cipher.*. These are scheduled to be migrated to here. + * Some of the Ciphers are still implemented in net.schmizz.sshj.transport.cipher.*. These are deprecated and scheduled to be removed. */ @SuppressWarnings("PMD.MethodNamingConventions") public class BlockCiphers { @@ -34,9 +35,30 @@ public class BlockCiphers { public static final String COUNTER_MODE = "CTR"; public static final String CIPHER_BLOCK_CHAINING_MODE = "CBC"; + public static Factory AES128CTR() { + return new Factory(16, 128, "aes128-ctr", "AES", COUNTER_MODE); + } + public static Factory AES192CTR() { + return new Factory(16, 192, "aes192-ctr", "AES", COUNTER_MODE); + } + public static Factory AES256CTR() { + return new Factory(16, 256, "aes256-ctr", "AES", COUNTER_MODE); + } + public static Factory AES128CBC() { + return new Factory(16, 128, "aes128-cbc", "AES", CIPHER_BLOCK_CHAINING_MODE); + } + public static Factory AES192CBC() { + return new Factory(16, 192, "aes192-cbc", "AES", CIPHER_BLOCK_CHAINING_MODE); + } + public static Factory AES256CBC() { + return new Factory(16, 256, "aes256-cbc", "AES", CIPHER_BLOCK_CHAINING_MODE); + } public static Factory BlowfishCTR() { return new Factory(8, 256, "blowfish-ctr", "Blowfish", COUNTER_MODE); } + public static Factory BlowfishCBC() { + return new Factory(8, 128, "blowfish-cbc", "Blowfish", CIPHER_BLOCK_CHAINING_MODE); + } public static Factory Twofish128CTR() { return new Factory(16, 128, "twofish128-ctr", "Twofish", COUNTER_MODE); } @@ -91,6 +113,9 @@ public static Factory Cast128CBC() { public static Factory TripleDESCTR() { return new Factory(8, 192, "3des-ctr", "DESede", COUNTER_MODE); } + public static Factory TripleDESCBC() { + return new Factory(8, 192, "3des-cbc", "DESede", CIPHER_BLOCK_CHAINING_MODE); + } /** Named factory for BlockCipher */ public static class Factory diff --git a/src/main/java/com/hierynomus/sshj/transport/kex/DHGroups.java b/src/main/java/com/hierynomus/sshj/transport/kex/DHGroups.java index 3efbddd83..d182e2a5d 100644 --- a/src/main/java/com/hierynomus/sshj/transport/kex/DHGroups.java +++ b/src/main/java/com/hierynomus/sshj/transport/kex/DHGroups.java @@ -15,14 +15,15 @@ */ package com.hierynomus.sshj.transport.kex; -import net.schmizz.sshj.transport.digest.*; +import net.schmizz.sshj.transport.digest.Digest; +import net.schmizz.sshj.transport.digest.SHA1; +import net.schmizz.sshj.transport.digest.SHA256; +import net.schmizz.sshj.transport.digest.SHA512; import net.schmizz.sshj.transport.kex.KeyExchange; import java.math.BigInteger; import static net.schmizz.sshj.transport.kex.DHGroupData.*; -import static net.schmizz.sshj.transport.kex.DHGroupData.P16; -import static net.schmizz.sshj.transport.kex.DHGroupData.P18; /** * Factory methods for Diffie Hellmann KEX algorithms based on MODP groups / Oakley Groups diff --git a/src/main/java/com/hierynomus/sshj/transport/verification/KnownHostMatchers.java b/src/main/java/com/hierynomus/sshj/transport/verification/KnownHostMatchers.java index 2c52d96b1..08e5d8b78 100644 --- a/src/main/java/com/hierynomus/sshj/transport/verification/KnownHostMatchers.java +++ b/src/main/java/com/hierynomus/sshj/transport/verification/KnownHostMatchers.java @@ -135,7 +135,7 @@ private static class WildcardHostMatcher implements HostMatcher { private final Pattern pattern; public WildcardHostMatcher(String hostEntry) { - this.pattern = Pattern.compile(hostEntry.replace(".", "\\.").replace("*", ".*").replace("?", ".")); + this.pattern = Pattern.compile("^" + hostEntry.replace("[", "\\[").replace("]", "\\]").replace(".", "\\.").replace("*", ".*").replace("?", ".") + "$"); } @Override diff --git a/src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java b/src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java index 1d28f1487..1e12badea 100644 --- a/src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java +++ b/src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java @@ -15,14 +15,6 @@ */ package com.hierynomus.sshj.userauth.keyprovider; -import java.io.BufferedReader; -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.PublicKey; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import net.i2p.crypto.eddsa.EdDSAPrivateKey; import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; @@ -31,8 +23,14 @@ import net.schmizz.sshj.userauth.keyprovider.BaseFileKeyProvider; import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider; import net.schmizz.sshj.userauth.keyprovider.KeyFormat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import static net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable.CURVE_ED25519_SHA512; +import java.io.BufferedReader; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.PublicKey; /** * Reads a key file in the new OpenSSH format. @@ -155,6 +153,6 @@ private KeyPair readUnencrypted(final PlainBuffer keyBuffer, final PublicKey pub throw new IOException("Padding of key format contained wrong byte at position: " + i); } } - return new KeyPair(publicKey, new EdDSAPrivateKey(new EdDSAPrivateKeySpec(privKey, EdDSANamedCurveTable.getByName(CURVE_ED25519_SHA512)))); + return new KeyPair(publicKey, new EdDSAPrivateKey(new EdDSAPrivateKeySpec(privKey, EdDSANamedCurveTable.getByName("Ed25519")))); } } diff --git a/src/main/java/net/schmizz/concurrent/Event.java b/src/main/java/net/schmizz/concurrent/Event.java index b5a449fb3..d02cd9328 100644 --- a/src/main/java/net/schmizz/concurrent/Event.java +++ b/src/main/java/net/schmizz/concurrent/Event.java @@ -15,11 +15,11 @@ */ package net.schmizz.concurrent; +import net.schmizz.sshj.common.LoggerFactory; + import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; -import net.schmizz.sshj.common.LoggerFactory; - /** * An event can be set, cleared, or awaited, similar to Python's {@code threading.event}. The key difference is that a * waiter may be delivered an exception of parameterized type {@code T}. diff --git a/src/main/java/net/schmizz/concurrent/Promise.java b/src/main/java/net/schmizz/concurrent/Promise.java index 8e6cce3f6..7511648d0 100644 --- a/src/main/java/net/schmizz/concurrent/Promise.java +++ b/src/main/java/net/schmizz/concurrent/Promise.java @@ -15,6 +15,7 @@ */ package net.schmizz.concurrent; +import net.schmizz.sshj.common.LoggerFactory; import org.slf4j.Logger; import java.util.concurrent.TimeUnit; @@ -22,8 +23,6 @@ import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; -import net.schmizz.sshj.common.LoggerFactory; - /** * Represents promised data of the parameterized type {@code V} and allows waiting on it. An exception may also be * delivered to a waiter, and will be of the parameterized type {@code T}. diff --git a/src/main/java/net/schmizz/sshj/AndroidConfig.java b/src/main/java/net/schmizz/sshj/AndroidConfig.java index 235d9fab9..9c230dd53 100644 --- a/src/main/java/net/schmizz/sshj/AndroidConfig.java +++ b/src/main/java/net/schmizz/sshj/AndroidConfig.java @@ -15,6 +15,8 @@ */ package net.schmizz.sshj; +import com.hierynomus.sshj.signature.SignatureEdDSA; + import net.schmizz.sshj.common.SecurityUtils; import net.schmizz.sshj.signature.SignatureDSA; import net.schmizz.sshj.signature.SignatureRSA; @@ -28,9 +30,18 @@ public class AndroidConfig SecurityUtils.registerSecurityProvider("org.spongycastle.jce.provider.BouncyCastleProvider"); } + public AndroidConfig(){ + super(); + initKeyExchangeFactories(true); + initRandomFactory(true); + initFileKeyProviderFactories(true); + } + // don't add ECDSA protected void initSignatureFactories() { - setSignatureFactories(new SignatureRSA.Factory(), new SignatureDSA.Factory()); + setSignatureFactories(new SignatureRSA.Factory(), new SignatureDSA.Factory(), + // but add EdDSA + new SignatureEdDSA.Factory()); } @Override diff --git a/src/main/java/net/schmizz/sshj/ConfigImpl.java b/src/main/java/net/schmizz/sshj/ConfigImpl.java index fce2cfda8..70df287cb 100644 --- a/src/main/java/net/schmizz/sshj/ConfigImpl.java +++ b/src/main/java/net/schmizz/sshj/ConfigImpl.java @@ -16,8 +16,8 @@ package net.schmizz.sshj; import net.schmizz.keepalive.KeepAliveProvider; -import net.schmizz.sshj.common.LoggerFactory; import net.schmizz.sshj.common.Factory; +import net.schmizz.sshj.common.LoggerFactory; import net.schmizz.sshj.signature.Signature; import net.schmizz.sshj.transport.cipher.Cipher; import net.schmizz.sshj.transport.compression.Compression; diff --git a/src/main/java/net/schmizz/sshj/DefaultConfig.java b/src/main/java/net/schmizz/sshj/DefaultConfig.java index 256b4e317..b2400d6b8 100644 --- a/src/main/java/net/schmizz/sshj/DefaultConfig.java +++ b/src/main/java/net/schmizz/sshj/DefaultConfig.java @@ -15,22 +15,12 @@ */ package net.schmizz.sshj; -import java.io.IOException; -import java.util.Arrays; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Properties; - -import org.slf4j.Logger; - import com.hierynomus.sshj.signature.SignatureEdDSA; import com.hierynomus.sshj.transport.cipher.BlockCiphers; import com.hierynomus.sshj.transport.cipher.StreamCiphers; import com.hierynomus.sshj.transport.kex.DHGroups; import com.hierynomus.sshj.transport.kex.ExtendedDHGroups; import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile; - import net.schmizz.keepalive.KeepAliveProvider; import net.schmizz.sshj.common.Factory; import net.schmizz.sshj.common.LoggerFactory; @@ -38,26 +28,13 @@ import net.schmizz.sshj.signature.SignatureDSA; import net.schmizz.sshj.signature.SignatureECDSA; import net.schmizz.sshj.signature.SignatureRSA; -import net.schmizz.sshj.transport.cipher.AES128CBC; -import net.schmizz.sshj.transport.cipher.AES128CTR; -import net.schmizz.sshj.transport.cipher.AES192CBC; -import net.schmizz.sshj.transport.cipher.AES192CTR; -import net.schmizz.sshj.transport.cipher.AES256CBC; -import net.schmizz.sshj.transport.cipher.AES256CTR; -import net.schmizz.sshj.transport.cipher.BlowfishCBC; -import net.schmizz.sshj.transport.cipher.Cipher; -import net.schmizz.sshj.transport.cipher.TripleDESCBC; +import net.schmizz.sshj.transport.cipher.*; import net.schmizz.sshj.transport.compression.NoneCompression; import net.schmizz.sshj.transport.kex.Curve25519SHA256; import net.schmizz.sshj.transport.kex.DHGexSHA1; import net.schmizz.sshj.transport.kex.DHGexSHA256; import net.schmizz.sshj.transport.kex.ECDHNistP; -import net.schmizz.sshj.transport.mac.HMACMD5; -import net.schmizz.sshj.transport.mac.HMACMD596; -import net.schmizz.sshj.transport.mac.HMACSHA1; -import net.schmizz.sshj.transport.mac.HMACSHA196; -import net.schmizz.sshj.transport.mac.HMACSHA2256; -import net.schmizz.sshj.transport.mac.HMACSHA2512; +import net.schmizz.sshj.transport.mac.*; import net.schmizz.sshj.transport.random.BouncyCastleRandom; import net.schmizz.sshj.transport.random.JCERandom; import net.schmizz.sshj.transport.random.SingletonRandomFactory; @@ -65,6 +42,10 @@ import net.schmizz.sshj.userauth.keyprovider.PKCS5KeyFile; import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile; import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile; +import org.slf4j.Logger; + +import java.io.IOException; +import java.util.*; /** * A {@link net.schmizz.sshj.Config} that is initialized as follows. Items marked with an asterisk are added to the config only if @@ -72,9 +53,7 @@ *

*