connectbot/sshlib

View on GitHub
src/main/java/com/trilead/ssh2/KnownHosts.java

Summary

Maintainability
F
6 days
Test Coverage
F
44%
File `KnownHosts.java` has 601 lines of code (exceeds 250 allowed). Consider refactoring.
 
package com.trilead.ssh2;
 
import java.io.BufferedReader;
import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
 
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
 
import com.trilead.ssh2.crypto.Base64;
import com.trilead.ssh2.crypto.keys.Ed25519PublicKey;
import com.trilead.ssh2.signature.DSASHA1Verify;
import com.trilead.ssh2.signature.ECDSASHA2Verify;
import com.trilead.ssh2.signature.Ed25519Verify;
import com.trilead.ssh2.signature.RSASHA1Verify;
import com.trilead.ssh2.signature.RSASHA256Verify;
import com.trilead.ssh2.signature.RSASHA512Verify;
import com.trilead.ssh2.transport.KexManager;
 
/**
* The <code>KnownHosts</code> class is a handy tool to verify received server hostkeys
* based on the information in <code>known_hosts</code> files (the ones used by OpenSSH).
* <p>
* It offers basically an in-memory database for known_hosts entries, as well as some
* helper functions. Entries from a <code>known_hosts</code> file can be loaded at construction time.
* It is also possible to add more keys later (e.g., one can parse different
* <code>known_hosts</code> files).
* <p>
* It is a thread safe implementation, therefore, you need only to instantiate one
* <code>KnownHosts</code> for your whole application.
*
* @author Christian Plattner, plattner@trilead.com
* @version $Id: KnownHosts.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $
*/
 
`KnownHosts` has 22 methods (exceeds 20 allowed). Consider refactoring.
public class KnownHosts
{
public static final int HOSTKEY_IS_OK = 0;
public static final int HOSTKEY_IS_NEW = 1;
public static final int HOSTKEY_HAS_CHANGED = 2;
 
protected class KnownHostsEntry
{
String[] patterns;
PublicKey key;
 
KnownHostsEntry(String[] patterns, PublicKey key)
{
this.patterns = patterns;
this.key = key;
}
 
@Override
public String toString() {
return "KnownHostsEntry{keyType=" + key.getAlgorithm() + "}";
}
}
 
protected final LinkedList<KnownHostsEntry> publicKeys = new LinkedList<>();
 
public KnownHosts()
{
}
 
public KnownHosts(char[] knownHostsData) throws IOException
{
initialize(knownHostsData);
}
 
public KnownHosts(File knownHosts) throws IOException
{
initialize(knownHosts);
}
 
/**
* Adds a single public key entry to the database. Note: this will NOT add the public key
* to any physical file (e.g., "~/.ssh/known_hosts") - use <code>addHostkeyToFile()</code> for that purpose.
* This method is designed to be used in a {@link ServerHostKeyVerifier}.
*
* @param hostnames a list of hostname patterns - at least one most be specified. Check out the
* OpenSSH sshd man page for a description of the pattern matching algorithm.
* @param serverHostKeyAlgorithm as passed to the {@link ServerHostKeyVerifier}.
* @param serverHostKey as passed to the {@link ServerHostKeyVerifier}.
* @throws IOException
*/
Method `addHostkey` has 44 lines of code (exceeds 25 allowed). Consider refactoring.
Method `addHostkey` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.
public void addHostkey(String[] hostnames, String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException
{
if (hostnames == null)
throw new IllegalArgumentException("hostnames may not be null");
 
if (RSASHA1Verify.ID_SSH_RSA.equals(serverHostKeyAlgorithm) ||
RSASHA512Verify.ID_RSA_SHA_2_512.equals(serverHostKeyAlgorithm) ||
RSASHA256Verify.ID_RSA_SHA_2_256.equals(serverHostKeyAlgorithm))
{
PublicKey rpk = RSASHA1Verify.get().decodePublicKey(serverHostKey);
 
synchronized (publicKeys)
{
publicKeys.add(new KnownHostsEntry(hostnames, rpk));
}
} else if (serverHostKeyAlgorithm.equals(DSASHA1Verify.ID_SSH_DSS)) {
PublicKey dpk = DSASHA1Verify.get().decodePublicKey(serverHostKey);
 
synchronized (publicKeys)
{
publicKeys.add(new KnownHostsEntry(hostnames, dpk));
}
} else if (serverHostKeyAlgorithm.equals(ECDSASHA2Verify.ECDSASHA2NISTP256Verify.get().getKeyFormat())) {
PublicKey epk = ECDSASHA2Verify.ECDSASHA2NISTP256Verify.get().decodePublicKey(serverHostKey);
 
synchronized (publicKeys)
{
publicKeys.add(new KnownHostsEntry(hostnames, epk));
}
} else if (serverHostKeyAlgorithm.equals(ECDSASHA2Verify.ECDSASHA2NISTP384Verify.get().getKeyFormat())) {
PublicKey epk = ECDSASHA2Verify.ECDSASHA2NISTP384Verify.get().decodePublicKey(serverHostKey);
 
synchronized (publicKeys)
{
publicKeys.add(new KnownHostsEntry(hostnames, epk));
}
} else if (serverHostKeyAlgorithm.equals(ECDSASHA2Verify.ECDSASHA2NISTP521Verify.get().getKeyFormat())) {
PublicKey epk = ECDSASHA2Verify.ECDSASHA2NISTP521Verify.get().decodePublicKey(serverHostKey);
 
synchronized (publicKeys)
{
publicKeys.add(new KnownHostsEntry(hostnames, epk));
}
} else if (Ed25519Verify.ED25519_ID.equals(serverHostKeyAlgorithm)) {
PublicKey edpk = Ed25519Verify.get().decodePublicKey(serverHostKey);
 
synchronized (publicKeys)
{
publicKeys.add(new KnownHostsEntry(hostnames, edpk));
}
} else {
throw new IOException("Unknown host key type (" + serverHostKeyAlgorithm + ")");
}
}
 
/**
* Parses the given known_hosts data and adds entries to the database.
*
* @param knownHostsData
* @throws IOException
*/
public void addHostkeys(char[] knownHostsData) throws IOException
{
initialize(knownHostsData);
}
 
/**
* Parses the given known_hosts file and adds entries to the database.
*
* @param knownHosts
* @throws IOException
*/
public void addHostkeys(File knownHosts) throws IOException
{
initialize(knownHosts);
}
 
/**
* Generate the hashed representation of the given hostname. Useful for adding entries
* with hashed hostnames to a known_hosts file. (see -H option of OpenSSH key-gen).
*
* @param hostname
* @return the hashed representation, e.g., "|1|cDhrv7zwEUV3k71CEPHnhHZezhA=|Xo+2y6rUXo2OIWRAYhBOIijbJMA="
*/
public static final String createHashedHostname(String hostname)
{
MessageDigest sha1;
try {
sha1 = MessageDigest.getInstance("SHA1");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("VM doesn't support SHA1", e);
}
 
byte[] salt = new byte[sha1.getDigestLength()];
 
new SecureRandom().nextBytes(salt);
 
byte[] hash = hmacSha1Hash(salt, hostname);
 
String base64_salt = new String(Base64.encode(salt));
String base64_hash = new String(Base64.encode(hash));
 
return new String("|1|" + base64_salt + "|" + base64_hash);
}
 
private static final byte[] hmacSha1Hash(byte[] salt, String hostname)
{
Mac hmac;
try {
hmac = Mac.getInstance("HmacSHA1");
if (salt.length != hmac.getMacLength())
throw new IllegalArgumentException("Salt has wrong length (" + salt.length + ")");
hmac.init(new SecretKeySpec(salt, "HmacSHA1"));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Unable to HMAC-SHA1", e);
} catch (InvalidKeyException e) {
throw new RuntimeException("Unable to create SecretKey", e);
}
 
try {
hmac.update(hostname.getBytes("ISO-8859-1"));
} catch (UnsupportedEncodingException e) {
hmac.update(hostname.getBytes());
}
 
return hmac.doFinal();
}
 
Method `checkHashed` has 30 lines of code (exceeds 25 allowed). Consider refactoring.
Method `checkHashed` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.
private final boolean checkHashed(String entry, String hostname)
{
if (!entry.startsWith("|1|"))
return false;
 
int delim_idx = entry.indexOf('|', 3);
 
if (delim_idx == -1)
return false;
 
String salt_base64 = entry.substring(3, delim_idx);
String hash_base64 = entry.substring(delim_idx + 1);
 
byte[] salt = null;
byte[] hash = null;
 
try
{
salt = Base64.decode(salt_base64.toCharArray());
hash = Base64.decode(hash_base64.toCharArray());
}
catch (IOException e)
{
return false;
}
 
try {
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
if (salt.length != sha1.getDigestLength())
return false;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("VM does not support SHA1", e);
}
 
byte[] dig = hmacSha1Hash(salt, hostname);
 
for (int i = 0; i < dig.length; i++)
if (dig[i] != hash[i])
Avoid too many `return` statements within this method.
return false;
 
Avoid too many `return` statements within this method.
return true;
}
 
private int checkKey(String remoteHostname, PublicKey remoteKey)
{
int result = HOSTKEY_IS_NEW;
 
synchronized (publicKeys)
{
Iterator<KnownHostsEntry> i = publicKeys.iterator();
 
while (i.hasNext())
{
KnownHostsEntry ke = i.next();
 
if (!hostnameMatches(ke.patterns, remoteHostname))
continue;
 
boolean res = matchKeys(ke.key, remoteKey);
 
if (res)
return HOSTKEY_IS_OK;
 
result = HOSTKEY_HAS_CHANGED;
}
}
return result;
}
 
private List<PublicKey> getAllKeys(String hostname)
{
List<PublicKey> keys = new ArrayList<>();
 
synchronized (publicKeys)
{
Iterator<KnownHostsEntry> i = publicKeys.iterator();
 
while (i.hasNext())
{
KnownHostsEntry ke = i.next();
 
if (!hostnameMatches(ke.patterns, hostname))
continue;
 
keys.add(ke.key);
}
}
 
return keys;
}
 
/**
* Try to find the preferred order of hostkey algorithms for the given hostname.
* Based on the type of hostkey that is present in the internal database
* (i.e., either <code>ssh-rsa</code> or <code>ssh-dss</code>)
* an ordered list of hostkey algorithms is returned which can be passed
* to <code>Connection.setServerHostKeyAlgorithms</code>.
*
* @param hostname
* @return <code>null</code> if no key for the given hostname is present or
* there are keys of multiple types present for the given hostname. Otherwise,
* an array with hostkey algorithms is returned (i.e., an array of length 2).
*/
public String[] getPreferredServerHostkeyAlgorithmOrder(String hostname)
{
String[] algos = recommendHostkeyAlgorithms(hostname);
 
if (algos != null)
return algos;
 
InetAddress[] ipAddresses;
 
try
{
ipAddresses = InetAddress.getAllByName(hostname);
} catch (UnknownHostException e) {
return null;
}
 
for (InetAddress ipAddress : ipAddresses) {
algos = recommendHostkeyAlgorithms(ipAddress.getHostAddress());
 
if (algos != null)
return algos;
}
 
return null;
}
 
Method `hostnameMatches` has a Cognitive Complexity of 38 (exceeds 5 allowed). Consider refactoring.
Method `hostnameMatches` has 50 lines of code (exceeds 25 allowed). Consider refactoring.
private final boolean hostnameMatches(String[] hostpatterns, String hostname)
{
boolean isMatch = false;
boolean negate = false;
 
hostname = hostname.toLowerCase(Locale.US);
 
for (int k = 0; k < hostpatterns.length; k++)
{
if (hostpatterns[k] == null)
continue;
 
String pattern = null;
 
/* In contrast to OpenSSH we also allow negated hash entries (as well as hashed
* entries in lines with multiple entries).
*/
 
if ((hostpatterns[k].length() > 0) && (hostpatterns[k].charAt(0) == '!'))
{
pattern = hostpatterns[k].substring(1);
negate = true;
}
else
{
pattern = hostpatterns[k];
negate = false;
}
 
/* Optimize, no need to check this entry */
 
if ((isMatch) && (!negate))
continue;
 
/* Now compare */
 
if (pattern.charAt(0) == '|')
{
if (checkHashed(pattern, hostname))
{
if (negate)
return false;
isMatch = true;
}
}
else
{
pattern = pattern.toLowerCase(Locale.US);
 
if ((pattern.indexOf('?') != -1) || (pattern.indexOf('*') != -1))
{
if (pseudoRegex(pattern.toCharArray(), 0, hostname.toCharArray(), 0))
{
if (negate)
return false;
isMatch = true;
}
}
else if (pattern.compareTo(hostname) == 0)
{
if (negate)
return false;
isMatch = true;
}
}
}
 
return isMatch;
}
 
Method `initialize` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.
private void initialize(char[] knownHostsData) throws IOException
{
BufferedReader br = new BufferedReader(new CharArrayReader(knownHostsData));
 
while (true)
{
String line = br.readLine();
 
if (line == null)
break;
 
line = line.trim();
 
if (line.startsWith("#"))
continue;
 
String[] arr = line.split(" ");
 
if (arr.length >= 3)
{
String[] hostnames = arr[0].split(",");
 
byte[] msg = Base64.decode(arr[2].toCharArray());
 
addHostkey(hostnames, arr[1], msg);
}
}
}
 
private void initialize(File knownHosts) throws IOException
{
char[] buff = new char[512];
 
CharArrayWriter cw = new CharArrayWriter();
 
knownHosts.createNewFile();
 
FileReader fr = new FileReader(knownHosts);
 
Identical blocks of code found in 2 locations. Consider refactoring.
while (true)
{
int len = fr.read(buff);
if (len < 0)
break;
cw.write(buff, 0, len);
}
 
fr.close();
 
initialize(cw.toCharArray());
}
 
private final boolean matchKeys(PublicKey key1, PublicKey key2)
{
return key1.equals(key2);
}
 
Method `pseudoRegex` has a Cognitive Complexity of 45 (exceeds 5 allowed). Consider refactoring.
Method `pseudoRegex` has 36 lines of code (exceeds 25 allowed). Consider refactoring.
private final boolean pseudoRegex(char[] pattern, int i, char[] match, int j)
{
/* This matching logic is equivalent to the one present in OpenSSH 4.1 */
 
while (true)
{
/* Are we at the end of the pattern? */
 
if (pattern.length == i)
return (match.length == j);
 
if (pattern[i] == '*')
{
i++;
 
if (pattern.length == i)
return true;
 
if ((pattern[i] != '*') && (pattern[i] != '?'))
{
while (true)
{
Avoid deeply nested control flow statements.
if ((pattern[i] == match[j]) && pseudoRegex(pattern, i + 1, match, j + 1))
return true;
j++;
Avoid deeply nested control flow statements.
if (match.length == j)
return false;
}
}
 
while (true)
{
if (pseudoRegex(pattern, i, match, j))
Avoid too many `return` statements within this method.
return true;
j++;
if (match.length == j)
Avoid too many `return` statements within this method.
return false;
}
}
 
if (match.length == j)
Avoid too many `return` statements within this method.
return false;
 
if ((pattern[i] != '?') && (pattern[i] != match[j]))
Avoid too many `return` statements within this method.
return false;
 
i++;
j++;
}
}
 
private final String[] ALGOS_FOR_RSA = new String[] {
RSASHA512Verify.ID_RSA_SHA_2_512,
RSASHA256Verify.ID_RSA_SHA_2_256,
RSASHA1Verify.ID_SSH_RSA,
};
 
private final String ALGO_FOR_DSS = DSASHA1Verify.ID_SSH_DSS;
 
private final String ALGO_FOR_EDDSA = Ed25519Verify.ED25519_ID;
 
Method `recommendHostkeyAlgorithms` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.
Method `recommendHostkeyAlgorithms` has 26 lines of code (exceeds 25 allowed). Consider refactoring.
private String[] recommendHostkeyAlgorithms(String hostname) {
List<String> preferredAlgos = new ArrayList<>();
 
List<PublicKey> keys = getAllKeys(hostname);
 
for (PublicKey key : keys) {
if (key instanceof RSAPublicKey) {
preferredAlgos.addAll(Arrays.asList(ALGOS_FOR_RSA));
} else if (key instanceof DSAPublicKey) {
preferredAlgos.add(ALGO_FOR_DSS);
} else if (key instanceof Ed25519PublicKey) {
preferredAlgos.add(ALGO_FOR_EDDSA);
} else if (key instanceof ECPublicKey) {
preferredAlgos.add(ECDSASHA2Verify.getSshKeyType((ECPublicKey) key));
}
}
 
/* If we did not find anything that we know of, return null */
if (preferredAlgos.isEmpty())
return null;
 
/* Now put the preferred algo to the start of the array.
* You may ask yourself why we do it that way - basically, we could just
* return only the preferred algorithm: since we have a saved key of that
* type (sent earlier from the remote host), then that should work out.
* However, imagine that the server is (for whatever reasons) not offering
* that type of hostkey anymore (e.g., "ssh-rsa" was disabled and
* now "ssh-dss" is being used). If we then do not let the server send us
* a fresh key of the new type, then we shoot ourself into the foot:
* the connection cannot be established and hence the user cannot decide
* if he/she wants to accept the new key.
*/
 
List<String> preferredAndOthers = new ArrayList<>();
List<String> notPreferred = new ArrayList<>();
for (String algo : KexManager.getDefaultServerHostkeyAlgorithmList()) {
if (preferredAlgos.contains(algo)) {
preferredAndOthers.add(algo);
} else {
notPreferred.add(algo);
}
}
preferredAndOthers.addAll(notPreferred);
return preferredAndOthers.toArray(new String[0]);
}
 
/**
* Checks the internal hostkey database for the given hostkey.
* If no matching key can be found, then the hostname is resolved to an IP address
* and the search is repeated using that IP address.
*
* @param hostname the server's hostname, will be matched with all hostname patterns
* @param serverHostKeyAlgorithm type of hostkey, either <code>ssh-rsa</code> or <code>ssh-dss</code>
* @param serverHostKey the key blob
* @return <ul>
* <li><code>HOSTKEY_IS_OK</code>: the given hostkey matches an entry for the given hostname</li>
* <li><code>HOSTKEY_IS_NEW</code>: no entries found for this hostname and this type of hostkey</li>
* <li><code>HOSTKEY_HAS_CHANGED</code>: hostname is known, but with another key of the same type
* (man-in-the-middle attack?)</li>
* </ul>
* @throws IOException if the supplied key blob cannot be parsed or does not match the given hostkey type.
*/
Method `verifyHostkey` has 49 lines of code (exceeds 25 allowed). Consider refactoring.
Method `verifyHostkey` has a Cognitive Complexity of 14 (exceeds 5 allowed). Consider refactoring.
public int verifyHostkey(String hostname, String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException
{
PublicKey remoteKey = null;
 
if (RSASHA1Verify.ID_SSH_RSA.equals(serverHostKeyAlgorithm) ||
RSASHA256Verify.ID_RSA_SHA_2_256.equals(serverHostKeyAlgorithm) ||
RSASHA512Verify.ID_RSA_SHA_2_512.equals(serverHostKeyAlgorithm))
{
remoteKey = RSASHA1Verify.get().decodePublicKey(serverHostKey);
}
else if (DSASHA1Verify.ID_SSH_DSS.equals(serverHostKeyAlgorithm))
{
remoteKey = DSASHA1Verify.get().decodePublicKey(serverHostKey);
}
else if (ECDSASHA2Verify.ECDSASHA2NISTP256Verify.get().getKeyFormat().equals(serverHostKeyAlgorithm))
{
remoteKey = ECDSASHA2Verify.ECDSASHA2NISTP256Verify.get().decodePublicKey(serverHostKey);
}
else if (ECDSASHA2Verify.ECDSASHA2NISTP384Verify.get().getKeyFormat().equals(serverHostKeyAlgorithm))
{
remoteKey = ECDSASHA2Verify.ECDSASHA2NISTP384Verify.get().decodePublicKey(serverHostKey);
}
else if (ECDSASHA2Verify.ECDSASHA2NISTP521Verify.get().getKeyFormat().equals(serverHostKeyAlgorithm))
{
remoteKey = ECDSASHA2Verify.ECDSASHA2NISTP521Verify.get().decodePublicKey(serverHostKey);
}
else if (Ed25519Verify.ED25519_ID.equals(serverHostKeyAlgorithm))
{
remoteKey = Ed25519Verify.get().decodePublicKey(serverHostKey);
}
else
throw new IllegalArgumentException("Unknown hostkey type " + serverHostKeyAlgorithm);
 
int result = checkKey(hostname, remoteKey);
 
if (result == HOSTKEY_IS_OK)
return result;
 
InetAddress[] ipAddresses = null;
 
try
{
ipAddresses = InetAddress.getAllByName(hostname);
}
catch (UnknownHostException e)
{
return result;
}
 
for (InetAddress ipAddress : ipAddresses) {
int newresult = checkKey(ipAddress.getHostAddress(), remoteKey);
 
if (newresult == HOSTKEY_IS_OK)
return newresult;
 
if (newresult == HOSTKEY_HAS_CHANGED)
result = HOSTKEY_HAS_CHANGED;
}
 
return result;
}
 
/**
* Adds a single public key entry to the a known_hosts file.
* This method is designed to be used in a {@link ServerHostKeyVerifier}.
*
* @param knownHosts the file where the publickey entry will be appended.
* @param hostnames a list of hostname patterns - at least one most be specified. Check out the
* OpenSSH sshd man page for a description of the pattern matching algorithm.
* @param serverHostKeyAlgorithm as passed to the {@link ServerHostKeyVerifier}.
* @param serverHostKey as passed to the {@link ServerHostKeyVerifier}.
* @throws IOException
*/
Method `addHostkeyToFile` has 32 lines of code (exceeds 25 allowed). Consider refactoring.
Method `addHostkeyToFile` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.
public final static void addHostkeyToFile(File knownHosts, String[] hostnames, String serverHostKeyAlgorithm,
byte[] serverHostKey) throws IOException
{
if ((hostnames == null) || (hostnames.length == 0))
throw new IllegalArgumentException("Need at least one hostname specification");
 
if ((serverHostKeyAlgorithm == null) || (serverHostKey == null))
throw new IllegalArgumentException();
 
CharArrayWriter writer = new CharArrayWriter();
 
Similar blocks of code found in 2 locations. Consider refactoring.
for (int i = 0; i < hostnames.length; i++)
{
if (i != 0)
writer.write(',');
writer.write(hostnames[i]);
}
 
writer.write(' ');
writer.write(serverHostKeyAlgorithm);
writer.write(' ');
writer.write(Base64.encode(serverHostKey));
writer.write("\n");
 
char[] entry = writer.toCharArray();
 
RandomAccessFile raf = new RandomAccessFile(knownHosts, "rw");
 
long len = raf.length();
 
if (len > 0)
{
raf.seek(len - 1);
int last = raf.read();
if (last != '\n')
raf.write('\n');
}
 
try {
raf.write(new String(entry).getBytes("ISO-8859-1"));
} catch (UnsupportedEncodingException e) {
raf.write(new String(entry).getBytes());
}
raf.close();
}
 
/**
* Generates a "raw" fingerprint of a hostkey.
*
* @param type either "md5" or "sha1"
* @param keyType either "ssh-rsa" or "ssh-dss"
* @param hostkey the hostkey
* @return the raw fingerprint
*/
Method `rawFingerPrint` has 41 lines of code (exceeds 25 allowed). Consider refactoring.
Method `rawFingerPrint` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.
private static byte[] rawFingerPrint(String type, String keyType, byte[] hostkey)
{
MessageDigest dig = null;
 
try {
if ("md5".equals(type))
{
dig = MessageDigest.getInstance("MD5");
}
else if ("sha1".equals(type))
{
dig = MessageDigest.getInstance("SHA1");
}
else
{
throw new IllegalArgumentException("Unknown hash type " + type);
}
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException("Unknown hash type " + type);
}
 
if (Ed25519Verify.ED25519_ID.equals(keyType))
{
}
else if (keyType.startsWith(ECDSASHA2Verify.ECDSA_SHA2_PREFIX))
{
}
else if (RSASHA1Verify.ID_SSH_RSA.equals(keyType))
{
}
else if (DSASHA1Verify.ID_SSH_DSS.equals(keyType))
{
}
else if (RSASHA256Verify.ID_RSA_SHA_2_256.equals(keyType))
{
}
else if (RSASHA512Verify.ID_RSA_SHA_2_512.equals(keyType))
{
}
else
throw new IllegalArgumentException("Unknown key type " + keyType);
 
if (hostkey == null)
throw new IllegalArgumentException("hostkey is null");
 
dig.update(hostkey);
return dig.digest();
}
 
/**
* Convert a raw fingerprint to hex representation (XX:YY:ZZ...).
* @param fingerprint raw fingerprint
* @return the hex representation
*/
private static String rawToHexFingerprint(byte[] fingerprint)
{
final char[] alpha = "0123456789abcdef".toCharArray();
 
StringBuilder sb = new StringBuilder();
 
for (int i = 0; i < fingerprint.length; i++)
{
if (i != 0)
sb.append(':');
int b = fingerprint[i] & 0xff;
sb.append(alpha[b >> 4]);
sb.append(alpha[b & 15]);
}
 
return sb.toString();
}
 
/**
* Convert a raw fingerprint to bubblebabble representation.
* @param raw raw fingerprint
* @return the bubblebabble representation
*/
Method `rawToBubblebabbleFingerprint` has 30 lines of code (exceeds 25 allowed). Consider refactoring.
Method `rawToBubblebabbleFingerprint` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.
static final private String rawToBubblebabbleFingerprint(byte[] raw)
{
final char[] v = "aeiouy".toCharArray();
final char[] c = "bcdfghklmnprstvzx".toCharArray();
 
StringBuilder sb = new StringBuilder();
 
int seed = 1;
 
int rounds = (raw.length / 2) + 1;
 
sb.append('x');
 
for (int i = 0; i < rounds; i++)
{
if (((i + 1) < rounds) || ((raw.length) % 2 != 0))
{
sb.append(v[(((raw[2 * i] >> 6) & 3) + seed) % 6]);
sb.append(c[(raw[2 * i] >> 2) & 15]);
sb.append(v[((raw[2 * i] & 3) + (seed / 6)) % 6]);
 
if ((i + 1) < rounds)
{
sb.append(c[(((raw[(2 * i) + 1])) >> 4) & 15]);
sb.append('-');
sb.append(c[(((raw[(2 * i) + 1]))) & 15]);
// As long as seed >= 0, seed will be >= 0 afterwards
seed = ((seed * 5) + (((raw[2 * i] & 0xff) * 7) + (raw[(2 * i) + 1] & 0xff))) % 36;
}
}
else
{
sb.append(v[seed % 6]); // seed >= 0, therefore index positive
sb.append('x');
sb.append(v[seed / 6]);
}
}
 
sb.append('x');
 
return sb.toString();
}
 
/**
* Convert a ssh2 key-blob into a human readable hex fingerprint.
* Generated fingerprints are identical to those generated by OpenSSH.
* <p>
* Example fingerprint: d0:cb:76:19:99:5a:03:fc:73:10:70:93:f2:44:63:47.
 
* @param keytype either "ssh-rsa" or "ssh-dss"
* @param publickey key blob
* @return Hex fingerprint
*/
public final static String createHexFingerprint(String keytype, byte[] publickey)
{
byte[] raw = rawFingerPrint("md5", keytype, publickey);
return rawToHexFingerprint(raw);
}
 
/**
* Convert a ssh2 key-blob into a human readable bubblebabble fingerprint.
* The used bubblebabble algorithm (taken from OpenSSH) generates fingerprints
* that are easier to remember for humans.
* <p>
* Example fingerprint: xofoc-bubuz-cazin-zufyl-pivuk-biduk-tacib-pybur-gonar-hotat-lyxux.
*
* @param keytype either "ssh-rsa" or "ssh-dss"
* @param publickey key data
* @return Bubblebabble fingerprint
*/
public final static String createBubblebabbleFingerprint(String keytype, byte[] publickey)
{
byte[] raw = rawFingerPrint("sha1", keytype, publickey);
return rawToBubblebabbleFingerprint(raw);
}
}