diff --git a/src/main/java/net/schmizz/sshj/common/ECDSAVariationsAdapter.java b/src/main/java/net/schmizz/sshj/common/ECDSAVariationsAdapter.java index 3b54be58..8e7d8003 100644 --- a/src/main/java/net/schmizz/sshj/common/ECDSAVariationsAdapter.java +++ b/src/main/java/net/schmizz/sshj/common/ECDSAVariationsAdapter.java @@ -18,9 +18,7 @@ package net.schmizz.sshj.common; import com.hierynomus.sshj.secg.SecgUtils; import org.bouncycastle.asn1.nist.NISTNamedCurves; import org.bouncycastle.asn1.x9.X9ECParameters; -import org.bouncycastle.jce.spec.ECParameterSpec; -import org.bouncycastle.jce.spec.ECPublicKeySpec; -import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.jce.spec.ECNamedCurveSpec; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,6 +29,8 @@ import java.security.KeyFactory; import java.security.PublicKey; import java.security.interfaces.ECKey; import java.security.interfaces.ECPublicKey; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -81,13 +81,14 @@ class ECDSAVariationsAdapter { BigInteger bigX = new BigInteger(1, x); BigInteger bigY = new BigInteger(1, y); - X9ECParameters ecParams = NISTNamedCurves.getByName(NIST_CURVES_NAMES.get(variation)); - ECPoint pPublicPoint = ecParams.getCurve().createPoint(bigX, bigY); - ECParameterSpec spec = new ECParameterSpec(ecParams.getCurve(), ecParams.getG(), ecParams.getN()); - ECPublicKeySpec publicSpec = new ECPublicKeySpec(pPublicPoint, spec); + String name = NIST_CURVES_NAMES.get(variation); + X9ECParameters ecParams = NISTNamedCurves.getByName(name); + ECNamedCurveSpec ecCurveSpec = new ECNamedCurveSpec(name, ecParams.getCurve(), ecParams.getG(), ecParams.getN()); + ECPoint p = new ECPoint(bigX, bigY); + ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(p, ecCurveSpec); KeyFactory keyFactory = KeyFactory.getInstance("ECDSA"); - return keyFactory.generatePublic(publicSpec); + return keyFactory.generatePublic(publicKeySpec); } catch (Exception ex) { throw new GeneralSecurityException(ex); } diff --git a/src/main/java/net/schmizz/sshj/transport/KeyExchanger.java b/src/main/java/net/schmizz/sshj/transport/KeyExchanger.java index 12432063..50f5b58d 100644 --- a/src/main/java/net/schmizz/sshj/transport/KeyExchanger.java +++ b/src/main/java/net/schmizz/sshj/transport/KeyExchanger.java @@ -197,6 +197,12 @@ final class KeyExchanger if (hkv.verify(transport.getRemoteHost(), transport.getRemotePort(), key)) return; } + log.error("Disconnecting because none of the configured Host key verifiers ({}) could verify '{}' host key with fingerprint {} for {}:{}", + hostVerifiers, + KeyType.fromKey(key), + SecurityUtils.getFingerprint(key), + transport.getRemoteHost(), + transport.getRemotePort()); throw new TransportException(DisconnectReason.HOST_KEY_NOT_VERIFIABLE, "Could not verify `" + KeyType.fromKey(key) diff --git a/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java b/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java index 18355e1c..dfcaebf8 100644 --- a/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java +++ b/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java @@ -355,7 +355,7 @@ public class OpenSSHKnownHosts @Override public boolean verify(PublicKey key) throws IOException { - return key.equals(this.key) && marker != Marker.REVOKED; + return getKeyString(key).equals(getKeyString(this.key)) && marker != Marker.REVOKED; } public String getLine() { @@ -365,12 +365,12 @@ public class OpenSSHKnownHosts line.append(getHostPart()); line.append(" ").append(type.toString()); - line.append(" ").append(getKeyString()); + line.append(" ").append(getKeyString(key)); return line.toString(); } - private String getKeyString() { - final Buffer.PlainBuffer buf = new Buffer.PlainBuffer().putPublicKey(key); + private String getKeyString(PublicKey pk) { + final Buffer.PlainBuffer buf = new Buffer.PlainBuffer().putPublicKey(pk); return Base64.encodeBytes(buf.array(), buf.rpos(), buf.available()); } diff --git a/src/test/groovy/com/hierynomus/sshj/transport/verification/OpenSSHKnownHostsSpec.groovy b/src/test/groovy/com/hierynomus/sshj/transport/verification/OpenSSHKnownHostsSpec.groovy index bf745f30..1eb20d90 100644 --- a/src/test/groovy/com/hierynomus/sshj/transport/verification/OpenSSHKnownHostsSpec.groovy +++ b/src/test/groovy/com/hierynomus/sshj/transport/verification/OpenSSHKnownHostsSpec.groovy @@ -18,20 +18,46 @@ package com.hierynomus.sshj.transport.verification import net.schmizz.sshj.common.Base64 import net.schmizz.sshj.common.Buffer import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts +import net.schmizz.sshj.util.KeyUtil import org.junit.Rule import org.junit.rules.TemporaryFolder import spock.lang.Specification import spock.lang.Unroll -import static org.hamcrest.CoreMatchers.equalTo -import static org.hamcrest.CoreMatchers.instanceOf -import static org.junit.Assert.assertThat -import static org.junit.Assert.assertThat +import java.security.PublicKey class OpenSSHKnownHostsSpec extends Specification { @Rule - TemporaryFolder folder + public TemporaryFolder temp = new TemporaryFolder(); + + def "should parse and verify hashed host entry"() { + given: + def f = knownHosts("|1|F1E1KeoE/eEWhi10WpGv4OdiO6Y=|3988QV0VE8wmZL7suNrYQLITLCg= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6P9Hlwdahh250jGZYKg2snRq2j2lFJVdKSHyxqbJiVy9VX9gTkN3K2MD48qyrYLYOyGs3vTttyUk+cK++JMzURWsrP4piby7LpeOT+3Iq8CQNj4gXZdcH9w15Vuk2qS11at6IsQPVHpKD9HGg9//EFUccI/4w06k4XXLm/IxOGUwj6I2AeWmEOL3aDi+fe07TTosSdLUD6INtR0cyKsg0zC7Da24ixoShT8Oy3x2MpR7CY3PQ1pUVmvPkr79VeA+4qV9F1JM09WdboAMZgWQZ+XrbtuBlGsyhpUHSCQOya+kOJ+bYryS+U7A+6nmTW3C9FX4FgFqTF89UHOC7V0zZQ=="); + final PublicKey key = KeyUtil + .newRSAPublicKey( + "e8ff4797075a861db9d2319960a836b2746ada3da514955d2921f2c6a6c9895cbd557f604e43772b6303e3cab2ad82d83b21acdef4edb72524f9c2bef893335115acacfe2989bcbb2e978e4fedc8abc090363e205d975c1fdc35e55ba4daa4b5d5ab7a22c40f547a4a0fd1c683dfff10551c708ff8c34ea4e175cb9bf2313865308fa23601e5a610e2f76838be7ded3b4d3a2c49d2d40fa20db51d1cc8ab20d330bb0dadb88b1a12853f0ecb7c7632947b098dcf435a54566bcf92befd55e03ee2a57d17524cd3d59d6e800c66059067e5eb6edb81946b3286950748240ec9afa4389f9b62bc92f94ec0fba9e64d6dc2f455f816016a4c5f3d507382ed5d3365", + "23"); + + when: + OpenSSHKnownHosts openSSHKnownHosts = new OpenSSHKnownHosts(f) + + then: + openSSHKnownHosts.verify("192.168.1.61", 22, key) + !openSSHKnownHosts.verify("192.168.1.2", 22, key) + } + + def "should parse and verify v1 host entry"() { + given: + def f = knownHosts("test.com,1.1.1.1 2048 35 22017496617994656680820635966392838863613340434802393112245951008866692373218840197754553998457793202561151141246686162285550121243768846314646395880632789308110750881198697743542374668273149584280424505890648953477691795864456749782348425425954366277600319096366690719901119774784695056100331902394094537054256611668966698242432417382422091372756244612839068092471592121759862971414741954991375710930168229171638843329213652899594987626853020377726482288618521941129157643483558764875338089684351824791983007780922947554898825663693324944982594850256042689880090306493029526546183035567296830604572253312294059766327") + def key = KeyUtil.newRSAPublicKey("ae6983ed63a33afc69fe0b88b4ba14393120a0b66e1460916a8390ff109139cd14f4e1701ab5c5feeb479441fe2091d04c0ba7d3fa1756b80ed103657ab53b5d7daa38af22f59f9cbfc16892d4ef1f8fd3ae49663c295be1f568a160d54328fbc2c0598f48d32296b1b9942336234952c440cda1bfac904e3391db98e52f9b1de229adc18fc34a9a569717aa9a5b1145e73b8a8394354028d02054ca760243fb8fc1575490607dd098e698e02b5d8bdf22d55ec958245222ef4c65b8836b9f13674a2d2895a587bfd4423b4eeb6d3ef98451640e3d63d2fc6a761ffd34446abab028494caf36d67ffd65298d69f19f2d90bae4c207b671db563a08f1bb9bf237", + "23") + when: + OpenSSHKnownHosts knownHosts = new OpenSSHKnownHosts(f) + + then: + knownHosts.verify("test.com", 22, key) + } def "should check all host entries for key"() { given: @@ -86,7 +112,7 @@ host1 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL knownHosts.entries().get(0) instanceof OpenSSHKnownHosts.CommentEntry where: - type << ["empty", "comment"] + type << ["newline", "comment"] s << ["\n", "#comment\n"] } @@ -107,7 +133,7 @@ host1 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL } def knownHosts(String s) { - def f = folder.newFile("known_hosts") + def f = temp.newFile("known_hosts") f.write(s) return f } diff --git a/src/test/resources/known_hosts b/src/test/resources/known_hosts deleted file mode 100644 index 7b345379..00000000 --- a/src/test/resources/known_hosts +++ /dev/null @@ -1,11 +0,0 @@ -# Line 1: bogus line, with invalid base64 string -# Line 2: syntactically correct line, but incorrect base64 string -# Line 3: correct line (that should match) Above we have a plain line -# Line 4: hashed line -# Line 5: v1 line -# Comment lines like these should be ignored -schmizz.net,69.163.155.180 ssh-rsa #BOGUS#aC1yc2EAAAABIwAAAQEA6P9Hlwdahh250jGZYKg2snRq2j2lFJVdKSHyxqbJiVy9VX9gTkN3K2MD48qyrYLYOyGs3vTttyUk+cK++JMzURWsrP4piby7LpeOT+3Iq8CQNj4gXZdcH9w15Vuk2qS11at6IsQPVHpKD9HGg9//EFUccI/4w06k4XXLm/IxOGUwj6I2AeWmEOL3aDi+fe07TTosSdLUD6INtR0cyKsg0zC7Da24ixoShT8Oy3x2MpR7CY3PQ1pUVmvPkr79VeA+4qV9F1JM09WdboAMZgWQZ+XrbtuBlGsyhpUHSCQOya+kOJ+bYryS+U7A+6nmTW3C9FX4FgFqTF89UHOC7V0zZQ== -schmizz.net,69.163.155.180 ssh-rsa AAAAAAAAAC1yc2EAAAABIwAAAQEA6P9Hlwdahh250jGZYKg2snRq2j2lFJVdKSHyxqbJiVy9VX9gTkN3K2MD48qyrYLYOyGs3vTttyUk+cK++JMzURWsrP4piby7LpeOT+3Iq8CQNj4gXZdcH9w15Vuk2qS11at6IsQPVHpKD9HGg9//EFUccI/4w06k4XXLm/IxOGUwj6I2AeWmEOL3aDi+fe07TTosSdLUD6INtR0cyKsg0zC7Da24ixoShT8Oy3x2MpR7CY3PQ1pUVmvPkr79VeA+4qV9F1JM09WdboAMZgWQZ+XrbtuBlGsyhpUHSCQOya+kOJ+bYryS+U7A+6nmTW3C9FX4FgFqTF89UHOC7V0zZQ== -schmizz.net,69.163.155.180 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6P9Hlwdahh250jGZYKg2snRq2j2lFJVdKSHyxqbJiVy9VX9gTkN3K2MD48qyrYLYOyGs3vTttyUk+cK++JMzURWsrP4piby7LpeOT+3Iq8CQNj4gXZdcH9w15Vuk2qS11at6IsQPVHpKD9HGg9//EFUccI/4w06k4XXLm/IxOGUwj6I2AeWmEOL3aDi+fe07TTosSdLUD6INtR0cyKsg0zC7Da24ixoShT8Oy3x2MpR7CY3PQ1pUVmvPkr79VeA+4qV9F1JM09WdboAMZgWQZ+XrbtuBlGsyhpUHSCQOya+kOJ+bYryS+U7A+6nmTW3C9FX4FgFqTF89UHOC7V0zZQ== -|1|dy7xSefq6NmJms6AzANG3w45W28=|SSCTlHs4pZbc2uaRoPvjyEAHE1g= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAu64GJcCkdtckPGt8uKTyhG1ShT1Np1kh10eE49imQ4Nh9Y/IrSPzDtYUAazQ88ABc2NffuOKkdn2qtUwZ1ulfcdNfN3oTim3BiVHqa041pKG0L+onQe8Bo+CaG5KBLy/C24eNGM9EcfQvDQOnq1eD3lnR/l8fFckldzjfxZgar0yT9Bb3pwp50oN+1wSEINJEHOgMIW8kZBQmyNr/B+b7yX+Y1s1vuYIP/i4WimCVmkdi9G87Ga8w7GxKalRD2QOG6Xms2YWRQDN6M/MOn4tda3EKolbWkctEWcQf/PcVJffTH4Wv5f0RjVyrQv4ha4FZcNAv6RkRd9WkiCsiTKioQ== -test.com,1.1.1.1 2048 35 22017496617994656680820635966392838863613340434802393112245951008866692373218840197754553998457793202561151141246686162285550121243768846314646395880632789308110750881198697743542374668273149584280424505890648953477691795864456749782348425425954366277600319096366690719901119774784695056100331902394094537054256611668966698242432417382422091372756244612839068092471592121759862971414741954991375710930168229171638843329213652899594987626853020377726482288618521941129157643483558764875338089684351824791983007780922947554898825663693324944982594850256042689880090306493029526546183035567296830604572253312294059766327