Add support for authentication with DSA & RSA user certificates (#153) (#319)

* Add support for authentication with DSA & RSA user certificates (#153)

Updates:

- KeyType.java - add support for two certificate key types
    ssh-rsa-cert-v01@openssh.com
    ssh-dsa-cert-v01@openssh.com

- Buffer.java - allow uint64s that overflow Long.MAX_VALUE, otherwise
  we break on certificates with serial numbers greater Long.MAX_VALUE

- OpenSSHKeyFile, KeyProviderUtil - prefer public key files that end
  "-cert.pub" if they exist

Added new class Certificate, which represents certificate key

Reference:

https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD

* Use BigInteger for certificate serial numbers, address Codacy issues

* Address code review concerns
This commit is contained in:
chqr
2017-04-14 02:49:16 -04:00
committed by Jeroen van Erp
parent 382321deca
commit e36fd0fb3d
15 changed files with 796 additions and 58 deletions

View File

@@ -57,6 +57,11 @@ public class Buffer<T extends Buffer<T>> {
/** The maximum valid size of buffer (i.e. biggest power of two that can be represented as an int - 2^30) */
public static final int MAX_SIZE = (1 << 30);
/** Maximum size of a uint64 */
private static final BigInteger MAX_UINT64_VALUE = BigInteger.ONE
.shiftLeft(64)
.subtract(BigInteger.ONE);
protected static int getNextPowerOf2(int i) {
int j = 1;
while (j < i) {
@@ -343,10 +348,29 @@ public class Buffer<T extends Buffer<T>> {
return uint64;
}
@SuppressWarnings("unchecked")
public BigInteger readUInt64AsBigInteger()
throws BufferException {
byte[] magnitude = new byte[8];
readRawBytes(magnitude);
return new BigInteger(1, magnitude);
}
public T putUInt64(long uint64) {
if (uint64 < 0)
throw new IllegalArgumentException("Invalid value: " + uint64);
return putUInt64Unchecked(uint64);
}
public T putUInt64(BigInteger uint64) {
if (uint64.compareTo(MAX_UINT64_VALUE) > 0 ||
uint64.compareTo(BigInteger.ZERO) < 0) {
throw new IllegalArgumentException("Invalid value: " + uint64);
}
return putUInt64Unchecked(uint64.longValue());
}
@SuppressWarnings("unchecked")
private T putUInt64Unchecked(long uint64) {
data[wpos++] = (byte) (uint64 >> 56);
data[wpos++] = (byte) (uint64 >> 48);
data[wpos++] = (byte) (uint64 >> 40);

View File

@@ -15,12 +15,26 @@
*/
package net.schmizz.sshj.common;
import com.hierynomus.sshj.secg.SecgUtils;
import com.hierynomus.sshj.signature.Ed25519PublicKey;
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.jce.spec.ECParameterSpec;
@@ -29,20 +43,19 @@ import org.bouncycastle.math.ec.ECPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.interfaces.*;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Arrays;
import com.hierynomus.sshj.secg.SecgUtils;
import com.hierynomus.sshj.signature.Ed25519PublicKey;
import com.hierynomus.sshj.userauth.certificate.Certificate;
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
import net.schmizz.sshj.common.Buffer.BufferException;
/** Type of key e.g. rsa, dsa */
public enum KeyType {
/** SSH identifier for RSA keys */
RSA("ssh-rsa") {
@Override
@@ -60,18 +73,16 @@ public enum KeyType {
}
@Override
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
final RSAPublicKey rsaKey = (RSAPublicKey) pk;
buf.putString(sType)
.putMPInt(rsaKey.getPublicExponent()) // e
.putMPInt(rsaKey.getModulus()); // n
buf.putMPInt(rsaKey.getPublicExponent()) // e
.putMPInt(rsaKey.getModulus()); // n
}
@Override
protected boolean isMyType(Key key) {
return (key instanceof RSAPublicKey || key instanceof RSAPrivateKey);
}
},
/** SSH identifier for DSA keys */
@@ -93,10 +104,9 @@ public enum KeyType {
}
@Override
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
final DSAPublicKey dsaKey = (DSAPublicKey) pk;
buf.putString(sType)
.putMPInt(dsaKey.getParams().getP()) // p
buf.putMPInt(dsaKey.getParams().getP()) // p
.putMPInt(dsaKey.getParams().getQ()) // q
.putMPInt(dsaKey.getParams().getG()) // g
.putMPInt(dsaKey.getY()); // y
@@ -161,12 +171,11 @@ public enum KeyType {
@Override
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
final ECPublicKey ecdsa = (ECPublicKey) pk;
byte[] encoded = SecgUtils.getEncoded(ecdsa.getW(), ecdsa.getParams().getCurve());
buf.putString(sType)
.putString(NISTP_CURVE)
buf.putString(NISTP_CURVE)
.putBytes(encoded);
}
@@ -202,9 +211,9 @@ public enum KeyType {
}
@Override
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
EdDSAPublicKey key = (EdDSAPublicKey) pk;
buf.putString(sType).putBytes(key.getAbyte());
buf.putBytes(key.getAbyte());
}
@Override
@@ -213,6 +222,44 @@ public enum KeyType {
}
},
/** Signed rsa certificate */
RSA_CERT("ssh-rsa-cert-v01@openssh.com") {
@Override
public PublicKey readPubKeyFromBuffer(Buffer<?> buf)
throws GeneralSecurityException {
return CertUtils.readPubKey(buf, RSA);
}
@Override
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
CertUtils.writePubKeyContentsIntoBuffer(pk, RSA, buf);
}
@Override
protected boolean isMyType(Key key) {
return CertUtils.isCertificateOfType(key, RSA);
}
},
/** Signed dsa certificate */
DSA_CERT("ssh-dss-cert-v01@openssh.com") {
@Override
public PublicKey readPubKeyFromBuffer(Buffer<?> buf)
throws GeneralSecurityException {
return CertUtils.readPubKey(buf, DSA);
}
@Override
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
CertUtils.writePubKeyContentsIntoBuffer(pk, DSA, buf);
}
@Override
protected boolean isMyType(Key key) {
return CertUtils.isCertificateOfType(key, DSA);
}
},
/** Unrecognized */
UNKNOWN("unknown") {
@Override
@@ -226,6 +273,11 @@ public enum KeyType {
throw new UnsupportedOperationException("Don't know how to encode key: " + pk);
}
@Override
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
throw new UnsupportedOperationException("Don't know how to encode key: " + pk);
}
@Override
protected boolean isMyType(Key key) {
return false;
@@ -244,7 +296,11 @@ public enum KeyType {
public abstract PublicKey readPubKeyFromBuffer(Buffer<?> buf)
throws GeneralSecurityException;
public abstract void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf);
protected abstract void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf);
public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
writePubKeyContentsIntoBuffer(pk, buf.putString(sType));
}
protected abstract boolean isMyType(Key key);
@@ -266,4 +322,129 @@ public enum KeyType {
public String toString() {
return sType;
}
static class CertUtils {
@SuppressWarnings("unchecked")
static <T extends PublicKey> Certificate<T> readPubKey(Buffer<?> buf, KeyType innerKeyType) throws GeneralSecurityException {
Certificate.Builder<T> builder = Certificate.getBuilder();
try {
builder.nonce(buf.readBytes());
builder.publicKey((T) innerKeyType.readPubKeyFromBuffer(buf));
builder.serial(buf.readUInt64AsBigInteger());
builder.type(buf.readUInt32());
builder.id(buf.readString());
builder.validPrincipals(unpackList(buf.readBytes()));
builder.validAfter(dateFromEpoch(buf.readUInt64()));
builder.validBefore(dateFromEpoch(buf.readUInt64()));
builder.critOptions(unpackMap(buf.readBytes()));
builder.extensions(unpackMap(buf.readBytes()));
buf.readString(); // reserved
builder.signatureKey(buf.readBytes());
builder.signature(buf.readBytes());
} catch (Buffer.BufferException be) {
throw new GeneralSecurityException(be);
}
return builder.build();
}
static void writePubKeyContentsIntoBuffer(PublicKey publicKey, KeyType innerKeyType, Buffer<?> buf) {
Certificate<PublicKey> certificate = toCertificate(publicKey);
buf.putBytes(certificate.getNonce());
innerKeyType.writePubKeyContentsIntoBuffer(certificate.getKey(), buf);
buf.putUInt64(certificate.getSerial())
.putUInt32(certificate.getType())
.putString(certificate.getId())
.putBytes(packList(certificate.getValidPrincipals()))
.putUInt64(epochFromDate(certificate.getValidAfter()))
.putUInt64(epochFromDate(certificate.getValidBefore()))
.putBytes(packMap(certificate.getCritOptions()))
.putBytes(packMap(certificate.getExtensions()))
.putString("") // reserved
.putBytes(certificate.getSignatureKey())
.putBytes(certificate.getSignature());
}
static boolean isCertificateOfType(Key key, KeyType innerKeyType) {
if (!(key instanceof Certificate)) {
return false;
}
@SuppressWarnings("unchecked")
Key innerKey = ((Certificate<PublicKey>) key).getKey();
return innerKeyType.isMyType(innerKey);
}
@SuppressWarnings("unchecked")
static Certificate<PublicKey> toCertificate(PublicKey key) {
if (!(key instanceof Certificate)) {
throw new UnsupportedOperationException("Can't convert non-certificate key " +
key.getAlgorithm() + " to certificate");
}
return ((Certificate<PublicKey>) key);
}
private static Date dateFromEpoch(long seconds) {
return new Date(seconds * 1000);
}
private static long epochFromDate(Date date) {
return date.getTime() / 1000;
}
private static String unpackString(byte[] packedString) throws BufferException {
if (packedString.length == 0) {
return "";
}
return new Buffer.PlainBuffer(packedString).readString();
}
private static List<String> unpackList(byte[] packedString) throws BufferException {
List<String> list = new ArrayList<String>();
Buffer<?> buf = new Buffer.PlainBuffer(packedString);
while (buf.available() > 0) {
list.add(buf.readString());
}
return list;
}
private static Map<String, String> unpackMap(byte[] packedString) throws BufferException {
Map<String, String> map = new LinkedHashMap<String, String>();
Buffer<?> buf = new Buffer.PlainBuffer(packedString);
while (buf.available() > 0) {
String name = buf.readString();
String data = unpackString(buf.readStringAsBytes());
map.put(name, data);
}
return map;
}
private static byte[] packString(String data) {
if (data == null || data.isEmpty()) {
return "".getBytes();
}
return new Buffer.PlainBuffer().putString(data).getCompactData();
}
private static byte[] packList(Iterable<String> strings) {
Buffer<?> buf = new Buffer.PlainBuffer();
for (String string : strings) {
buf.putString(string);
}
return buf.getCompactData();
}
private static byte[] packMap(Map<String, String> map) {
Buffer<?> buf = new Buffer.PlainBuffer();
List<String> keys = new ArrayList<String>(map.keySet());
Collections.sort(keys);
for (String key : keys) {
buf.putString(key);
String value = map.get(key);
buf.putString(packString(value));
}
return buf.getCompactData();
}
}
}

View File

@@ -34,7 +34,7 @@ public class KeyProviderUtil {
public static KeyFormat detectKeyFileFormat(File location)
throws IOException {
return detectKeyFileFormat(new FileReader(location),
new File(location + ".pub").exists());
new File(location + ".pub").exists() || new File(location + "-cert.pub").exists());
}
/**

View File

@@ -57,10 +57,14 @@ public class OpenSSHKeyFile
@Override
public void init(File location) {
final File f = new File(location + ".pub");
if (f.exists())
// try cert key location first
File pubKey = new File(location + "-cert.pub");
if (!pubKey.exists()) {
pubKey = new File(location + ".pub");
}
if (pubKey.exists())
try {
initPubKey(new FileReader(f));
initPubKey(new FileReader(pubKey));
} catch (IOException e) {
// let super provide both public & private key
log.warn("Error reading public key file: {}", e.toString());