Added support for new-style fingerprints (#365)

* Added support for new-style fingerprints

* Fixed codacy warnings
This commit is contained in:
Jeroen van Erp
2017-09-28 14:01:04 +02:00
committed by GitHub
parent 99c85672b8
commit 762d088388
3 changed files with 194 additions and 7 deletions

View File

@@ -38,6 +38,7 @@ import net.schmizz.sshj.transport.compression.DelayedZlibCompression;
import net.schmizz.sshj.transport.compression.NoneCompression;
import net.schmizz.sshj.transport.compression.ZlibCompression;
import net.schmizz.sshj.transport.verification.AlgorithmsVerifier;
import net.schmizz.sshj.transport.verification.FingerprintVerifier;
import net.schmizz.sshj.transport.verification.HostKeyVerifier;
import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts;
import net.schmizz.sshj.userauth.UserAuth;
@@ -169,19 +170,23 @@ public class SSHClient
/**
* Add a {@link HostKeyVerifier} that will verify any host that's able to claim a host key with the given {@code
* fingerprint}, e.g. {@code "4b:69:6c:72:6f:79:20:77:61:73:20:68:65:72:65:21"}
* fingerprint}.
*
* The fingerprint can be specified in either an MD5 colon-delimited format (16 hexadecimal octets, delimited by a colon),
* or in a Base64 encoded format for SHA-1 or SHA-256 fingerprints.
* Valid examples are:
*
* <ul><li>"SHA1:2Fo8c/96zv32xc8GZWbOGYOlRak="</li>
* <li>"SHA256:oQGbQTujGeNIgh0ONthcEpA/BHxtt3rcYY+NxXTxQjs="</li>
* <li>"MD5:d3:5e:40:72:db:08:f1:6d:0c:d7:6d:35:0d:ba:7c:32"</li>
* <li>"d3:5e:40:72:db:08:f1:6d:0c:d7:6d:35:0d:ba:7c:32"</li></ul>
*
* @param fingerprint expected fingerprint in colon-delimited format (16 octets in hex delimited by a colon)
*
* @see SecurityUtils#getFingerprint
*/
public void addHostKeyVerifier(final String fingerprint) {
addHostKeyVerifier(new HostKeyVerifier() {
@Override
public boolean verify(String h, int p, PublicKey k) {
return SecurityUtils.getFingerprint(k).equals(fingerprint);
}
});
addHostKeyVerifier(FingerprintVerifier.getInstance(fingerprint));
}
// FIXME: there are way too many auth... overrides. Better API needed.

View File

@@ -0,0 +1,124 @@
/*
* 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 net.schmizz.sshj.transport.verification;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.regex.Pattern;
import net.schmizz.sshj.common.Base64;
import net.schmizz.sshj.common.Buffer;
import net.schmizz.sshj.common.SSHRuntimeException;
import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.transport.verification.HostKeyVerifier;
public class FingerprintVerifier implements HostKeyVerifier {
private static final Pattern MD5_FINGERPRINT_PATTERN = Pattern.compile("[0-9a-f]{2}+(:[0-9a-f]{2}+){15}+");
/**
* Valid examples:
*
* <ul>
* <li><code>4b:69:6c:72:6f:79:20:77:61:73:20:68:65:72:65:21</code></li>
* <li><code>MD5:4b:69:6c:72:6f:79:20:77:61:73:20:68:65:72:65:21</code></li>
* <li><code>SHA1:FghNYu1l/HyE/qWbdQ2mkxrd0rU</code></li>
* <li><code>SHA1:FghNYu1l/HyE/qWbdQ2mkxrd0rU=</code></li>
* <li><code>SHA256:l/SjyCoKP8jAx3d8k8MWH+UZG0gcuIR7TQRE/A3faQo</code></li>
* <li><code>SHA256:l/SjyCoKP8jAx3d8k8MWH+UZG0gcuIR7TQRE/A3faQo=</code></li>
* </ul>
*
*
* @param fingerprint of an SSH fingerprint in MD5 (hex), SHA-1 (base64) or SHA-256(base64) format
*
* @return
*/
public static HostKeyVerifier getInstance(String fingerprint) {
try {
if (fingerprint.startsWith("SHA1:")) {
return new FingerprintVerifier("SHA-1", fingerprint.substring(5));
}
if (fingerprint.startsWith("SHA256:")) {
return new FingerprintVerifier("SHA-256", fingerprint.substring(7));
}
final String md5;
if (fingerprint.startsWith("MD5:")) {
md5 = fingerprint.substring(4); // remove the MD5: prefix
} else {
md5 = fingerprint;
}
if (!MD5_FINGERPRINT_PATTERN.matcher(md5).matches()) {
throw new SSHRuntimeException("Invalid MD5 fingerprint: " + fingerprint);
}
// Use the old default fingerprint verifier for md5 fingerprints
return (new HostKeyVerifier() {
@Override
public boolean verify(String h, int p, PublicKey k) {
return SecurityUtils.getFingerprint(k).equals(md5);
}
});
} catch (SSHRuntimeException e) {
throw e;
} catch (IOException e) {
throw new SSHRuntimeException(e);
}
}
private final String digestAlgorithm;
private final byte[] fingerprintData;
/**
*
* @param digestAlgorithm
* the used digest algorithm
* @param base64Fingerprint
* base64 encoded fingerprint data
*
* @throws IOException
*/
private FingerprintVerifier(String digestAlgorithm, String base64Fingerprint) throws IOException {
this.digestAlgorithm = digestAlgorithm;
// if the length is not padded with "=" chars at the end so that it is divisible by 4 the SSHJ Base64 implementation does not work correctly
StringBuilder base64FingerprintBuilder = new StringBuilder(base64Fingerprint);
while (base64FingerprintBuilder.length() % 4 != 0) {
base64FingerprintBuilder.append("=");
}
fingerprintData = Base64.decode(base64FingerprintBuilder.toString());
}
@Override
public boolean verify(String hostname, int port, PublicKey key) {
MessageDigest digest;
try {
digest = SecurityUtils.getMessageDigest(digestAlgorithm);
} catch (GeneralSecurityException e) {
throw new SSHRuntimeException(e);
}
digest.update(new Buffer.PlainBuffer().putPublicKey(key).getCompactData());
byte[] digestData = digest.digest();
return Arrays.equals(fingerprintData, digestData);
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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 net.schmizz.sshj.transport.verification
import net.schmizz.sshj.common.Base64
import net.schmizz.sshj.common.Buffer
import spock.lang.Specification
import spock.lang.Unroll
class FingerprintVerifierSpec extends Specification {
@Unroll
def "should accept #digest fingerprints"() {
given:
def verifier = FingerprintVerifier.getInstance(fingerprint)
expect:
verifier.verify("", 0, getPublicKey())
where:
digest << ["SHA-1", "SHA-256", "MD5", "old style"]
fingerprint << ["SHA1:2Fo8c/96zv32xc8GZWbOGYOlRak=",
"SHA256:oQGbQTujGeNIgh0ONthcEpA/BHxtt3rcYY+NxXTxQjs=",
"MD5:d3:5e:40:72:db:08:f1:6d:0c:d7:6d:35:0d:ba:7c:32",
"d3:5e:40:72:db:08:f1:6d:0c:d7:6d:35:0d:ba:7c:32"]
}
@Unroll
def "should accept too short #digest fingerprints"() {
given:
def verifier = FingerprintVerifier.getInstance(fingerprint)
expect:
verifier.verify("", 0, getPublicKey())
where:
digest << ["SHA-1", "SHA-256"]
fingerprint << ["SHA1:2Fo8c/96zv32xc8GZWbOGYOlRak",
"SHA256:oQGbQTujGeNIgh0ONthcEpA/BHxtt3rcYY+NxXTxQjs"]
}
def getPublicKey() {
def lines = new File("src/test/resources/keytypes/test_ed25519.pub").readLines()
def keystring = lines[0].split(" ")[1]
return new Buffer.PlainBuffer(Base64.decode(keystring)).readPublicKey()
}
}