mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-06 23:30:55 +03:00
Added support for new-style fingerprints (#365)
* Added support for new-style fingerprints * Fixed codacy warnings
This commit is contained in:
@@ -38,6 +38,7 @@ import net.schmizz.sshj.transport.compression.DelayedZlibCompression;
|
|||||||
import net.schmizz.sshj.transport.compression.NoneCompression;
|
import net.schmizz.sshj.transport.compression.NoneCompression;
|
||||||
import net.schmizz.sshj.transport.compression.ZlibCompression;
|
import net.schmizz.sshj.transport.compression.ZlibCompression;
|
||||||
import net.schmizz.sshj.transport.verification.AlgorithmsVerifier;
|
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.HostKeyVerifier;
|
||||||
import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts;
|
import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts;
|
||||||
import net.schmizz.sshj.userauth.UserAuth;
|
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
|
* 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)
|
* @param fingerprint expected fingerprint in colon-delimited format (16 octets in hex delimited by a colon)
|
||||||
*
|
*
|
||||||
* @see SecurityUtils#getFingerprint
|
* @see SecurityUtils#getFingerprint
|
||||||
*/
|
*/
|
||||||
public void addHostKeyVerifier(final String fingerprint) {
|
public void addHostKeyVerifier(final String fingerprint) {
|
||||||
addHostKeyVerifier(new HostKeyVerifier() {
|
addHostKeyVerifier(FingerprintVerifier.getInstance(fingerprint));
|
||||||
@Override
|
|
||||||
public boolean verify(String h, int p, PublicKey k) {
|
|
||||||
return SecurityUtils.getFingerprint(k).equals(fingerprint);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: there are way too many auth... overrides. Better API needed.
|
// FIXME: there are way too many auth... overrides. Better API needed.
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user