mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-08 16:18:05 +03:00
* 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:
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user