Added getFingerprint method to SSH Known Host entry API.

Removed BouncyCastle dependency for public key authentication.
This commit is contained in:
David Solin
2016-08-12 19:58:09 -05:00
parent df82774ea3
commit df710d8dc9
3 changed files with 276 additions and 91 deletions

View File

@@ -1,45 +1,46 @@
Default: all
TOP=$(realpath .)
include $(TOP)/../DeveloperTools/install/common.mk
RSRC=rsrc
LIBDIR=$(RSRC)/lib
LIB=$(subst $(SPACE),$(CLN),$(filter %.jar %.zip, $(wildcard $(LIBDIR)/*)))
BUILD=build
SRC=src/main/java
DOCS=docs
CLASSPATH="$(CLASSLIB)$(CLN)$(LIB)$(CLN)$(SRC)"
CWD=$(shell pwd)
include classes.mk
CLASS_FILES:=$(foreach class, $(CLASSES), $(BUILD)/$(subst .,/,$(class)).class)
PACKAGES=$(sort $(basename $(CLASSES)))
PACKAGEDIRS=$(subst .,/,$(PACKAGES))
all: sshj.jar
sshj.jar: classes
$(JAR) cvf $@ -C $(BUILD)/ .
javadocs:
mkdir -p $(DOCS)
$(JAVA_HOME)/bin/javadoc -d $(DOCS) -classpath $(CLASSPATH) $(PACKAGES)
clean:
rm -rf $(BUILD)
classes: classdirs $(CLASS_FILES)
install: all
cp sshj.jar $(TOP)/../jOVAL-Commercial/components/provider/remote/rsrc/lib
cp sshj.jar $(TOP)/../jOVAL-Commercial/components/sdk/dist/3rd-party
classdirs: $(foreach pkg, $(PACKAGEDIRS), $(BUILD)/$(pkg)/)
$(BUILD)/%.class: $(SRC)/%.java
$(JAVAC) $(JAVACFLAGS) -d $(BUILD) -classpath $(CLASSPATH) $<
$(BUILD)/%/:
mkdir -p $(subst PKG,,$@)
Default: all
TOP=$(realpath .)
include $(TOP)/../DeveloperTools/install/common.mk
RSRC=rsrc
LIBDIR=$(RSRC)/lib
LIB=$(subst $(SPACE),$(CLN),$(filter %.jar %.zip, $(wildcard $(LIBDIR)/*)))
BUILD=build
SRC=src/main/java
DOCS=docs
CLASSPATH="$(CLASSLIB)$(CLN)$(LIB)$(CLN)$(SRC)"
CWD=$(shell pwd)
include classes.mk
CLASS_FILES:=$(foreach class, $(CLASSES), $(BUILD)/$(subst .,/,$(class)).class)
PACKAGES=$(sort $(basename $(CLASSES)))
PACKAGEDIRS=$(subst .,/,$(PACKAGES))
all: sshj.jar
sshj.jar: classes
$(JAR) cvf $@ -C $(BUILD)/ .
javadocs:
mkdir -p $(DOCS)
$(JAVA_HOME)/bin/javadoc -d $(DOCS) -classpath $(CLASSPATH) $(PACKAGES)
clean:
rm -rf $(BUILD)
classes: classdirs $(CLASS_FILES)
install: all
cp sshj.jar $(TOP)/../jOVAL-Commercial/components/wsmv/winrs/rsrc/lib
# cp sshj.jar $(TOP)/../jOVAL-Commercial/components/provider/remote/rsrc/lib
# cp sshj.jar $(TOP)/../jOVAL-Commercial/components/sdk/dist/3rd-party
classdirs: $(foreach pkg, $(PACKAGEDIRS), $(BUILD)/$(pkg)/)
$(BUILD)/%.class: $(SRC)/%.java
$(JAVAC) $(JAVACFLAGS) -d $(BUILD) -classpath $(CLASSPATH) $<
$(BUILD)/%/:
mkdir -p $(subst PKG,,$@)

View File

@@ -262,6 +262,13 @@ public class OpenSSHKnownHosts
}
public interface HostEntry {
KeyType getType();
String getFingerprint();
boolean appliesTo(String host)
throws IOException;
boolean appliesTo(KeyType type, String host)
throws IOException;
@@ -279,6 +286,22 @@ public class OpenSSHKnownHosts
this.comment = comment;
}
@Override
public KeyType getType() {
return KeyType.UNKNOWN;
}
@Override
public String getFingerprint() {
return null;
}
@Override
public boolean appliesTo(String host)
throws IOException {
return false;
}
@Override
public boolean appliesTo(KeyType type, String host) {
return false;
@@ -308,6 +331,16 @@ public class OpenSSHKnownHosts
this.key = key;
}
@Override
public KeyType getType() {
return type;
}
@Override
public String getFingerprint() {
return SecurityUtils.getFingerprint(key);
}
@Override
public boolean verify(PublicKey key)
throws IOException {
@@ -349,6 +382,12 @@ public class OpenSSHKnownHosts
return hostnames;
}
@Override
public boolean appliesTo(String host)
throws IOException {
return hosts.contains(host);
}
@Override
public boolean appliesTo(KeyType type, String host)
throws IOException {
@@ -377,6 +416,12 @@ public class OpenSSHKnownHosts
}
}
@Override
public boolean appliesTo(String host)
throws IOException {
return hashedHost.equals(hashHost(host));
}
@Override
public boolean appliesTo(KeyType type, String host)
throws IOException {
@@ -426,4 +471,4 @@ public class OpenSSHKnownHosts
}
}
}

View File

@@ -15,29 +15,47 @@
*/
package net.schmizz.sshj.userauth.keyprovider;
import net.schmizz.sshj.common.Base64;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.transport.cipher.AES128CBC;
import net.schmizz.sshj.transport.cipher.AES192CBC;
import net.schmizz.sshj.transport.cipher.AES256CBC;
import net.schmizz.sshj.transport.cipher.Cipher;
import net.schmizz.sshj.transport.cipher.NoneCipher;
import net.schmizz.sshj.transport.cipher.TripleDESCBC;
import net.schmizz.sshj.transport.digest.Digest;
import net.schmizz.sshj.transport.digest.MD5;
import net.schmizz.sshj.userauth.password.PasswordFinder;
import net.schmizz.sshj.userauth.password.PasswordUtils;
import net.schmizz.sshj.userauth.password.PrivateKeyFileResource;
import net.schmizz.sshj.userauth.password.PrivateKeyReaderResource;
import net.schmizz.sshj.userauth.password.PrivateKeyStringResource;
import net.schmizz.sshj.userauth.password.Resource;
import org.bouncycastle.openssl.EncryptionException;
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.File;
import java.io.EOFException;
import java.io.IOException;
import java.io.Reader;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.DSAPrivateKeySpec;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Arrays;
import javax.xml.bind.DatatypeConverter;
/** Represents a PKCS8-encoded key file. This is the format used by OpenSSH and OpenSSL. */
public class PKCS8KeyFile
@@ -123,51 +141,172 @@ public class PKCS8KeyFile
protected KeyPair readKeyPair()
throws IOException {
KeyPair kp = null;
BufferedReader reader = new BufferedReader(resource.getReader());
try {
String type = null;
String line = null;
Cipher cipher = new NoneCipher();
StringBuffer sb = new StringBuffer();
byte[] iv = new byte[0]; // salt
while ((line = reader.readLine()) != null) {
if (line.startsWith("-----BEGIN ") && line.endsWith(" PRIVATE KEY-----")) {
int end = line.length() - 17;
if (end > 11) {
type = line.substring(11, line.length() - 17);
} else {
type = "UNKNOWN";
}
} else if (line.startsWith("-----END")) {
break;
} else if (type != null) {
if (line.startsWith("Proc-Type: ")) {
if (!"4,ENCRYPTED".equals(line.substring(11))) {
throw new IOException("Unrecognized Proc-Type: " + line.substring(11));
}
} else if (line.startsWith("DEK-Info: ")) {
int ptr = line.indexOf(",");
if (ptr == -1) {
throw new IOException("Unrecognized DEK-Info: " + line.substring(10));
} else {
String algorithm = line.substring(10,ptr);
if ("DES-EDE3-CBC".equals(algorithm)) {
cipher = new TripleDESCBC();
iv = DatatypeConverter.parseHexBinary(line.substring(ptr+1));
} else if ("AES-128-CBC".equals(algorithm)) {
cipher = new AES128CBC();
iv = DatatypeConverter.parseHexBinary(line.substring(ptr+1));
} else if ("AES-192-CBC".equals(algorithm)) {
cipher = new AES192CBC();
iv = Base64.decode(line.substring(ptr+1));
} else if ("AES-256-CBC".equals(algorithm)) {
cipher = new AES256CBC();
iv = Base64.decode(line.substring(ptr+1));
} else {
throw new IOException("Not a supported algorithm: " + algorithm);
}
}
} else if (line.length() > 0) {
sb.append(line);
}
}
}
for (PEMParser r = null; ; ) {
// while the PasswordFinder tells us we should retry
try {
r = new PEMParser(resource.getReader());
final Object o = r.readObject();
final JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter();
pemConverter.setProvider("BC");
if (o instanceof PEMEncryptedKeyPair) {
final PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair) o;
JcePEMDecryptorProviderBuilder decryptorBuilder = new JcePEMDecryptorProviderBuilder();
decryptorBuilder.setProvider("BC");
try {
passphrase = pwdf == null ? null : pwdf.reqPassword(resource);
kp = pemConverter.getKeyPair(encryptedKeyPair.decryptKeyPair(decryptorBuilder.build(passphrase)));
} finally {
PasswordUtils.blankOut(passphrase);
byte[] data = Base64.decode(sb.toString());
if (pwdf != null) {
boolean decrypted = false;
do {
CharBuffer cb = CharBuffer.wrap(pwdf.reqPassword(resource));
ByteBuffer bb = IOUtils.UTF8.encode(cb);
byte[] passphrase = Arrays.copyOfRange(bb.array(), bb.position(), bb.limit());
byte[] key = new byte[cipher.getBlockSize()];
iv = Arrays.copyOfRange(iv, 0, cipher.getIVSize());
Digest md5 = new MD5();
md5.init();
int hsize = md5.getBlockSize();
byte[] hn = new byte[key.length / hsize * hsize + (key.length % hsize == 0 ? 0 : hsize)];
byte[] tmp = null;
for (int i=0; i + hsize <= hn.length;) {
if (tmp != null) {
md5.update(tmp, 0, tmp.length);
}
md5.update(passphrase, 0, passphrase.length);
md5.update(iv, 0, iv.length > 8 ? 8 : iv.length);
tmp = md5.digest();
System.arraycopy(tmp, 0, hn, i, tmp.length);
i += tmp.length;
}
} else if (o instanceof PEMKeyPair) {
kp = pemConverter.getKeyPair((PEMKeyPair) o);
} else {
log.debug("Expected PEMEncryptedKeyPair or PEMKeyPair, got: {}", o);
}
System.arraycopy(hn, 0, key, 0, key.length);
cipher.init(Cipher.Mode.Decrypt, key, iv);
cipher.update(data, 0, data.length);
decrypted = 0x30 == data[0];
Arrays.fill(cb.array(), '\u0000');
Arrays.fill(bb.array(), (byte) 0);
Arrays.fill(key, (byte) 0);
} while (!decrypted && pwdf.shouldRetry(resource));
}
if (0x30 != data[0]) {
throw new IOException("Failed to decrypt key");
}
} catch (EncryptionException e) {
if (pwdf != null && pwdf.shouldRetry(resource))
continue;
else
throw e;
} finally {
IOUtils.closeQuietly(r);
}
break;
}
if (kp == null)
throw new IOException("Could not read key pair from: " + resource);
return kp;
ASN1Data asn = new ASN1Data(data);
if ("RSA".equals(type)) {
KeyFactory factory = KeyFactory.getInstance("RSA");
asn.readNext();
BigInteger modulus = asn.readNext();
BigInteger pubExp = asn.readNext();
BigInteger prvExp = asn.readNext();
PublicKey pubKey = factory.generatePublic(new RSAPublicKeySpec(modulus, pubExp));
PrivateKey prvKey = factory.generatePrivate(new RSAPrivateKeySpec(modulus, prvExp));
return new KeyPair(pubKey, prvKey);
} else if ("DSA".equals(type)) {
KeyFactory factory = KeyFactory.getInstance("DSA");
asn.readNext();
BigInteger p = asn.readNext();
BigInteger q = asn.readNext();
BigInteger g = asn.readNext();
BigInteger pub = asn.readNext();
BigInteger prv = asn.readNext();
PublicKey pubKey = factory.generatePublic(new DSAPublicKeySpec(pub, p, q, g));
PrivateKey prvKey = factory.generatePrivate(new DSAPrivateKeySpec(prv, p, q, g));
return new KeyPair(pubKey, prvKey);
} else {
throw new IOException("Unrecognized key type: " + type);
}
} catch (NoSuchAlgorithmException e) {
throw new IOException(e);
} catch (InvalidKeySpecException e) {
throw new IOException(e);
} finally {
reader.close();
}
}
@Override
public String toString() {
return "PKCS8KeyFile{resource=" + resource + "}";
}
}
class ASN1Data {
private byte[] buff;
private int index, length;
ASN1Data(byte[] buff) throws IOException {
this.buff = buff;
index = 0;
if (buff[index++] != (byte)0x30) {
throw new IOException("Not ASN.1 data");
}
length = buff[index++] & 0xff;
if ((length & 0x80) != 0) {
int counter = length & 0x7f;
length = 0;
while (counter-- > 0) {
length = (length << 8) + (buff[index++] & 0xff);
}
}
if ((index + length) > buff.length) {
throw new IOException("Length mismatch: " + buff.length + " != " + (index + length));
}
}
BigInteger readNext() throws IOException {
if (index >= length) {
throw new EOFException();
} else if (buff[index++] != 0x02) {
throw new IOException("Not an int code: " + Integer.toHexString(0xff & buff[index]));
}
int length = buff[index++] & 0xff;
if ((length & 0x80) != 0) {
int counter = length & 0x7f;
length = 0;
while (counter-- > 0) {
length = (length << 8) + (buff[index++] & 0xff);
}
}
byte[] sequence = new byte[length];
System.arraycopy(buff, index, sequence, 0, length);
index += length;
return new BigInteger(sequence);
}
}
}