working on EdEC (JDK based ed25519)
This commit is contained in:
parent
5c18f0704f
commit
be26f3e231
7 changed files with 503 additions and 64 deletions
|
@ -1,4 +1,6 @@
|
||||||
module org.xbib.files.sftp {
|
module org.xbib.files.sftp {
|
||||||
|
requires org.xbib.net.security;
|
||||||
|
requires java.logging;
|
||||||
exports org.apache.sshd.client;
|
exports org.apache.sshd.client;
|
||||||
exports org.apache.sshd.client.auth;
|
exports org.apache.sshd.client.auth;
|
||||||
exports org.apache.sshd.client.auth.password;
|
exports org.apache.sshd.client.auth.password;
|
||||||
|
@ -79,7 +81,5 @@ module org.xbib.files.sftp {
|
||||||
exports org.apache.sshd.common.util.security;
|
exports org.apache.sshd.common.util.security;
|
||||||
exports org.apache.sshd.common.util.security.eddsa;
|
exports org.apache.sshd.common.util.security.eddsa;
|
||||||
exports org.apache.sshd.common.util.threads;
|
exports org.apache.sshd.common.util.threads;
|
||||||
requires org.xbib.net.security;
|
|
||||||
requires java.logging;
|
|
||||||
uses org.apache.sshd.common.io.IoServiceFactoryFactory;
|
uses org.apache.sshd.common.io.IoServiceFactoryFactory;
|
||||||
}
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
package org.apache.sshd.common.util.security.edec;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.security.interfaces.EdECPrivateKey;
|
||||||
|
import java.security.interfaces.EdECPublicKey;
|
||||||
|
import java.security.spec.EdECPoint;
|
||||||
|
import java.security.spec.EdECPrivateKeySpec;
|
||||||
|
import java.security.spec.EdECPublicKeySpec;
|
||||||
|
import java.security.spec.NamedParameterSpec;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.apache.sshd.common.config.keys.KeyEntryResolver;
|
||||||
|
import org.apache.sshd.common.config.keys.impl.AbstractPublicKeyEntryDecoder;
|
||||||
|
import org.apache.sshd.common.keyprovider.KeyPairProvider;
|
||||||
|
import org.apache.sshd.common.session.SessionContext;
|
||||||
|
|
||||||
|
public final class Ed25519PublicKeyDecoder extends AbstractPublicKeyEntryDecoder<EdECPublicKey, EdECPrivateKey> {
|
||||||
|
|
||||||
|
public static final int MAX_ALLOWED_SEED_LEN = 1024;
|
||||||
|
|
||||||
|
public static final Ed25519PublicKeyDecoder INSTANCE = new Ed25519PublicKeyDecoder();
|
||||||
|
|
||||||
|
private Ed25519PublicKeyDecoder() {
|
||||||
|
super(EdECPublicKey.class, EdECPrivateKey.class, Collections.singletonList(KeyPairProvider.SSH_ED25519));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EdECPublicKey clonePublicKey(EdECPublicKey key) throws GeneralSecurityException {
|
||||||
|
if (key == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519;
|
||||||
|
EdECPoint edECPoint = key.getPoint();
|
||||||
|
return generatePublicKey(new EdECPublicKeySpec(namedParameterSpec, edECPoint));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EdECPrivateKey clonePrivateKey(EdECPrivateKey key) throws GeneralSecurityException {
|
||||||
|
if (key == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519;
|
||||||
|
return generatePrivateKey(new EdECPrivateKeySpec(namedParameterSpec, key.getEncoded()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
|
||||||
|
return KeyPairGenerator.getInstance("Ed25519");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String encodePublicKey(OutputStream s, EdECPublicKey key) throws IOException {
|
||||||
|
// TODO
|
||||||
|
return "ssh-ed25519";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
|
||||||
|
return KeyFactory.getInstance("EdDSA");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EdECPublicKey decodePublicKey(SessionContext session,
|
||||||
|
String keyType,
|
||||||
|
InputStream keyData,
|
||||||
|
Map<String, String> headers)
|
||||||
|
throws IOException, GeneralSecurityException {
|
||||||
|
byte[] pk = KeyEntryResolver.readRLEBytes(keyData, MAX_ALLOWED_SEED_LEN);
|
||||||
|
boolean xisodd = false;
|
||||||
|
int lastbyteInt = pk[pk.length - 1];
|
||||||
|
if ((lastbyteInt & 255) >> 7 == 1) {
|
||||||
|
xisodd = true;
|
||||||
|
}
|
||||||
|
pk[pk.length - 1] &= 127;
|
||||||
|
BigInteger y = new BigInteger(1, pk);
|
||||||
|
NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519;
|
||||||
|
EdECPoint ep = new EdECPoint(xisodd, y);
|
||||||
|
EdECPublicKeySpec spec = new EdECPublicKeySpec(namedParameterSpec, ep);
|
||||||
|
return EdECPublicKey.class.cast(generatePublicKey(spec));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,166 @@
|
||||||
|
package org.apache.sshd.common.util.security.edec;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.StreamCorruptedException;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.interfaces.EdECPrivateKey;
|
||||||
|
import java.security.interfaces.EdECPublicKey;
|
||||||
|
import java.security.spec.EdECPoint;
|
||||||
|
import java.security.spec.EdECPrivateKeySpec;
|
||||||
|
import java.security.spec.EdECPublicKeySpec;
|
||||||
|
import java.security.spec.NamedParameterSpec;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.apache.sshd.common.NamedResource;
|
||||||
|
import org.apache.sshd.common.config.keys.FilePasswordProvider;
|
||||||
|
import org.apache.sshd.common.config.keys.loader.pem.AbstractPEMResourceKeyPairParser;
|
||||||
|
import org.apache.sshd.common.session.SessionContext;
|
||||||
|
import org.apache.sshd.common.util.GenericUtils;
|
||||||
|
import org.apache.sshd.common.util.io.NoCloseInputStream;
|
||||||
|
import org.apache.sshd.common.util.io.der.ASN1Object;
|
||||||
|
import org.apache.sshd.common.util.io.der.ASN1Type;
|
||||||
|
import org.apache.sshd.common.util.io.der.DERParser;
|
||||||
|
|
||||||
|
public class EdECPEMResourceKeyParser extends AbstractPEMResourceKeyPairParser {
|
||||||
|
|
||||||
|
public static final String BEGIN_MARKER = "BEGIN OPENSSH PRIVATE KEY";
|
||||||
|
|
||||||
|
public static final List<String> BEGINNERS = Collections.singletonList(BEGIN_MARKER);
|
||||||
|
|
||||||
|
public static final String END_MARKER = "END OPENSSH PRIVATE KEY";
|
||||||
|
public static final List<String> ENDERS = Collections.singletonList(END_MARKER);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see <A HREF="https://tools.ietf.org/html/rfc8410#section-3">RFC8412 section 3</A>
|
||||||
|
*/
|
||||||
|
public static final String ED25519_OID = "1.3.101.112";
|
||||||
|
|
||||||
|
public static final EdECPEMResourceKeyParser INSTANCE = new EdECPEMResourceKeyParser();
|
||||||
|
|
||||||
|
public EdECPEMResourceKeyParser() {
|
||||||
|
super("Ed25519", ED25519_OID, BEGINNERS, ENDERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<KeyPair> extractKeyPairs(SessionContext session,
|
||||||
|
NamedResource resourceKey,
|
||||||
|
String beginMarker, String endMarker,
|
||||||
|
FilePasswordProvider passwordProvider,
|
||||||
|
InputStream stream,
|
||||||
|
Map<String, String> headers) throws IOException, GeneralSecurityException {
|
||||||
|
KeyPair kp = parseEd25519KeyPair(stream, false);
|
||||||
|
return Collections.singletonList(kp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static KeyPair parseEd25519KeyPair(InputStream inputStream,
|
||||||
|
boolean okToClose) throws IOException, GeneralSecurityException {
|
||||||
|
try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(inputStream, okToClose))) {
|
||||||
|
return parseKeyPair(parser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* See https://tools.ietf.org/html/rfc8410#section-7
|
||||||
|
*
|
||||||
|
* SEQUENCE {
|
||||||
|
* INTEGER 0x00 (0 decimal)
|
||||||
|
* SEQUENCE {
|
||||||
|
* OBJECTIDENTIFIER 1.3.101.112
|
||||||
|
* }
|
||||||
|
* OCTETSTRING keyData
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* NOTE: there is another variant that also has some extra parameters
|
||||||
|
* but it has the same "prefix" structure so we don't care
|
||||||
|
*/
|
||||||
|
private static KeyPair parseKeyPair(DERParser parser) throws IOException, GeneralSecurityException {
|
||||||
|
ASN1Object obj = parser.readObject();
|
||||||
|
if (obj == null) {
|
||||||
|
throw new StreamCorruptedException("Missing version value");
|
||||||
|
}
|
||||||
|
BigInteger version = obj.asInteger();
|
||||||
|
if (!BigInteger.ZERO.equals(version)) {
|
||||||
|
throw new StreamCorruptedException("Invalid version: " + version);
|
||||||
|
}
|
||||||
|
obj = parser.readObject();
|
||||||
|
if (obj == null) {
|
||||||
|
throw new StreamCorruptedException("Missing OID container");
|
||||||
|
}
|
||||||
|
ASN1Type objType = obj.getObjType();
|
||||||
|
if (objType != ASN1Type.SEQUENCE) {
|
||||||
|
throw new StreamCorruptedException("Unexpected OID object type: " + objType);
|
||||||
|
}
|
||||||
|
List<Integer> curveOid;
|
||||||
|
try (DERParser oidParser = obj.createParser()) {
|
||||||
|
obj = oidParser.readObject();
|
||||||
|
if (obj == null) {
|
||||||
|
throw new StreamCorruptedException("Missing OID value");
|
||||||
|
}
|
||||||
|
curveOid = obj.asOID();
|
||||||
|
}
|
||||||
|
String oid = GenericUtils.join(curveOid, '.');
|
||||||
|
// TODO modify if more curves supported
|
||||||
|
if (!ED25519_OID.equals(oid)) {
|
||||||
|
throw new StreamCorruptedException("Unsupported curve OID: " + oid);
|
||||||
|
}
|
||||||
|
obj = parser.readObject();
|
||||||
|
if (obj == null) {
|
||||||
|
throw new StreamCorruptedException("Missing key data");
|
||||||
|
}
|
||||||
|
return decodeKeyPair(obj.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static KeyPair decodeKeyPair(byte[] keyData) throws IOException, GeneralSecurityException {
|
||||||
|
EdECPrivateKey privateKey = getPrivateKey(keyData);
|
||||||
|
EdECPublicKey publicKey = getPublicKey(privateKey);
|
||||||
|
return new KeyPair(publicKey, privateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EdECPrivateKey getPrivateKey(byte[] keyData) throws IOException, GeneralSecurityException {
|
||||||
|
try (DERParser parser = new DERParser(keyData)) {
|
||||||
|
ASN1Object obj = parser.readObject();
|
||||||
|
if (obj == null) {
|
||||||
|
throw new StreamCorruptedException("Missing key data container");
|
||||||
|
}
|
||||||
|
ASN1Type objType = obj.getObjType();
|
||||||
|
if (objType != ASN1Type.OCTET_STRING) {
|
||||||
|
throw new StreamCorruptedException("Mismatched key data container type: " + objType);
|
||||||
|
}
|
||||||
|
return generatePrivateKey(obj.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EdECPrivateKey generatePrivateKey(byte[] seed) throws GeneralSecurityException {
|
||||||
|
NamedParameterSpec spec = NamedParameterSpec.ED25519;
|
||||||
|
EdECPrivateKeySpec keySpec = new EdECPrivateKeySpec(spec, seed);
|
||||||
|
KeyFactory factory = KeyFactory.getInstance("EdDSA");
|
||||||
|
return EdECPrivateKey.class.cast(factory.generatePrivate(keySpec));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EdECPublicKey getPublicKey(PrivateKey key) throws GeneralSecurityException {
|
||||||
|
if (!(key instanceof EdECPrivateKey)) {
|
||||||
|
throw new InvalidKeyException("Private key is not EdEC private key");
|
||||||
|
}
|
||||||
|
byte[] pk = key.getEncoded();
|
||||||
|
boolean xisodd = false;
|
||||||
|
int lastbyteInt = pk[pk.length - 1];
|
||||||
|
if ((lastbyteInt & 255) >> 7 == 1) {
|
||||||
|
xisodd = true;
|
||||||
|
}
|
||||||
|
pk[pk.length - 1] &= 127;
|
||||||
|
BigInteger y = new BigInteger(1, pk);
|
||||||
|
NamedParameterSpec paramSpec = new NamedParameterSpec("Ed25519");
|
||||||
|
EdECPoint ep = new EdECPoint(xisodd, y);
|
||||||
|
EdECPublicKeySpec publicKeySpec = new EdECPublicKeySpec(paramSpec, ep);
|
||||||
|
KeyFactory factory = KeyFactory.getInstance("EdDSA");
|
||||||
|
return EdECPublicKey.class.cast(factory.generatePublic(publicKeySpec));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package org.apache.sshd.common.util.security.edec;
|
||||||
|
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.security.Provider;
|
||||||
|
import java.security.Security;
|
||||||
|
import java.security.Signature;
|
||||||
|
import java.util.Objects;
|
||||||
|
import org.apache.sshd.common.util.security.AbstractSecurityProviderRegistrar;
|
||||||
|
|
||||||
|
public class EdECSecurityProviderRegistrar extends AbstractSecurityProviderRegistrar {
|
||||||
|
|
||||||
|
public EdECSecurityProviderRegistrar() {
|
||||||
|
super("Ed25519");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return super.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Provider getSecurityProvider() {
|
||||||
|
return Security.getProvider("SunJCE");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSecurityEntitySupported(Class<?> entityType, String name) {
|
||||||
|
if (!isSupported()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (KeyPairGenerator.class.isAssignableFrom(entityType) || KeyFactory.class.isAssignableFrom(entityType)) {
|
||||||
|
return Objects.compare(name, getName(), String.CASE_INSENSITIVE_ORDER) == 0;
|
||||||
|
} else if (Signature.class.isAssignableFrom(entityType)) {
|
||||||
|
return Objects.compare("NONEwithEdDSA", name, String.CASE_INSENSITIVE_ORDER) == 0;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupported() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
package org.apache.sshd.common.util.security.edec;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.StreamCorruptedException;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.security.interfaces.EdECPrivateKey;
|
||||||
|
import java.security.interfaces.EdECPublicKey;
|
||||||
|
import java.security.spec.EdECPoint;
|
||||||
|
import java.security.spec.EdECPrivateKeySpec;
|
||||||
|
import java.security.spec.EdECPublicKeySpec;
|
||||||
|
import java.security.spec.NamedParameterSpec;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
import org.apache.sshd.common.config.keys.FilePasswordProvider;
|
||||||
|
import org.apache.sshd.common.config.keys.KeyEntryResolver;
|
||||||
|
import org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder;
|
||||||
|
import org.apache.sshd.common.keyprovider.KeyPairProvider;
|
||||||
|
import org.apache.sshd.common.session.SessionContext;
|
||||||
|
import org.apache.sshd.common.util.GenericUtils;
|
||||||
|
import org.apache.sshd.common.util.io.SecureByteArrayOutputStream;
|
||||||
|
import org.apache.sshd.common.util.io.der.ASN1Object;
|
||||||
|
import org.apache.sshd.common.util.io.der.ASN1Type;
|
||||||
|
import org.apache.sshd.common.util.io.der.DERParser;
|
||||||
|
|
||||||
|
|
||||||
|
public class OpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder<EdECPublicKey, EdECPrivateKey> {
|
||||||
|
|
||||||
|
public static final OpenSSHEd25519PrivateKeyEntryDecoder INSTANCE = new OpenSSHEd25519PrivateKeyEntryDecoder();
|
||||||
|
|
||||||
|
private static final int PK_SIZE = 32;
|
||||||
|
|
||||||
|
private static final int SK_SIZE = 32;
|
||||||
|
|
||||||
|
private static final int KEYPAIR_SIZE = PK_SIZE + SK_SIZE;
|
||||||
|
|
||||||
|
public OpenSSHEd25519PrivateKeyEntryDecoder() {
|
||||||
|
super(EdECPublicKey.class, EdECPrivateKey.class, Collections.singletonList(KeyPairProvider.SSH_ED25519));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EdECPrivateKey decodePrivateKey(SessionContext session,
|
||||||
|
String keyType,
|
||||||
|
FilePasswordProvider passwordProvider,
|
||||||
|
InputStream keyData)
|
||||||
|
throws IOException, GeneralSecurityException {
|
||||||
|
if (!"ssh-ed25519".equals(keyType)) {
|
||||||
|
throw new InvalidKeyException("Unsupported key type: " + keyType);
|
||||||
|
}
|
||||||
|
// ed25519 bernstein naming: pk .. public key, sk .. secret key
|
||||||
|
// we expect to find two byte arrays with the following structure (type:size):
|
||||||
|
// [pk:32], [sk:32,pk:32]
|
||||||
|
byte[] pk = GenericUtils.EMPTY_BYTE_ARRAY;
|
||||||
|
byte[] keypair = GenericUtils.EMPTY_BYTE_ARRAY;
|
||||||
|
try {
|
||||||
|
pk = KeyEntryResolver.readRLEBytes(keyData, PK_SIZE * 2);
|
||||||
|
keypair = KeyEntryResolver.readRLEBytes(keyData, KEYPAIR_SIZE * 2);
|
||||||
|
if (pk.length != PK_SIZE) {
|
||||||
|
throw new InvalidKeyException(
|
||||||
|
String.format(Locale.ENGLISH, "Unexpected pk size: %s (expected %s)", pk.length, PK_SIZE));
|
||||||
|
}
|
||||||
|
if (keypair.length != KEYPAIR_SIZE) {
|
||||||
|
throw new InvalidKeyException(
|
||||||
|
String.format(Locale.ENGLISH, "Unexpected keypair size: %s (expected %s)", keypair.length,
|
||||||
|
KEYPAIR_SIZE));
|
||||||
|
}
|
||||||
|
// verify that the keypair contains the expected pk
|
||||||
|
// yes, it's stored redundant, this seems to mimic the output structure of the keypair generation interface
|
||||||
|
if (!Arrays.equals(pk, Arrays.copyOfRange(keypair, SK_SIZE, KEYPAIR_SIZE))) {
|
||||||
|
throw new InvalidKeyException("Keypair did not contain the public key.");
|
||||||
|
}
|
||||||
|
byte[] seed = Arrays.copyOf(keypair, SK_SIZE);
|
||||||
|
NamedParameterSpec spec = NamedParameterSpec.ED25519;
|
||||||
|
EdECPrivateKeySpec keySpec = new EdECPrivateKeySpec(spec, seed);
|
||||||
|
KeyFactory factory = KeyFactory.getInstance("EdDSA");
|
||||||
|
return EdECPrivateKey.class.cast(factory.generatePrivate(keySpec));
|
||||||
|
} finally {
|
||||||
|
// get rid of sensitive data a.s.a.p
|
||||||
|
Arrays.fill(pk, (byte) 0);
|
||||||
|
Arrays.fill(keypair, (byte) 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String encodePrivateKey(SecureByteArrayOutputStream s,
|
||||||
|
EdECPrivateKey key,
|
||||||
|
EdECPublicKey pubKey) throws IOException {
|
||||||
|
Objects.requireNonNull(key, "No private key provided");
|
||||||
|
return "ssh-ed25519";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPublicKeyRecoverySupported() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EdECPublicKey recoverPublicKey(EdECPrivateKey key) throws GeneralSecurityException {
|
||||||
|
byte[] pk = key.getEncoded();
|
||||||
|
boolean xisodd = false;
|
||||||
|
int lastbyteInt = pk[pk.length - 1];
|
||||||
|
if ((lastbyteInt & 255) >> 7 == 1) {
|
||||||
|
xisodd = true;
|
||||||
|
}
|
||||||
|
pk[pk.length - 1] &= 127;
|
||||||
|
BigInteger y = new BigInteger(1, pk);
|
||||||
|
NamedParameterSpec paramSpec = new NamedParameterSpec("Ed25519");
|
||||||
|
EdECPoint ep = new EdECPoint(xisodd, y);
|
||||||
|
EdECPublicKeySpec publicKeySpec = new EdECPublicKeySpec(paramSpec, ep);
|
||||||
|
KeyFactory factory = KeyFactory.getInstance("EdDSA");
|
||||||
|
return EdECPublicKey.class.cast(factory.generatePublic(publicKeySpec));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EdECPublicKey clonePublicKey(EdECPublicKey key) throws GeneralSecurityException {
|
||||||
|
if (key == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519;
|
||||||
|
EdECPoint edECPoint = key.getPoint();
|
||||||
|
EdECPublicKeySpec spec = new EdECPublicKeySpec(namedParameterSpec, edECPoint);
|
||||||
|
return generatePublicKey(spec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EdECPrivateKey clonePrivateKey(EdECPrivateKey key) throws GeneralSecurityException {
|
||||||
|
if (key == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return getPrivateKey(key.getEncoded());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
|
||||||
|
return KeyPairGenerator.getInstance("Ed25519");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
|
||||||
|
return KeyFactory.getInstance("EdDSA");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EdECPrivateKey getPrivateKey(byte[] keyData) throws GeneralSecurityException {
|
||||||
|
try (DERParser parser = new DERParser(keyData)) {
|
||||||
|
ASN1Object obj = parser.readObject();
|
||||||
|
if (obj == null) {
|
||||||
|
throw new StreamCorruptedException("Missing key data container");
|
||||||
|
}
|
||||||
|
ASN1Type objType = obj.getObjType();
|
||||||
|
if (objType != ASN1Type.OCTET_STRING) {
|
||||||
|
throw new StreamCorruptedException("Mismatched key data container type: " + objType);
|
||||||
|
}
|
||||||
|
return generatePrivateKey(obj.getValue());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new GeneralSecurityException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EdECPrivateKey generatePrivateKey(byte[] seed) throws GeneralSecurityException {
|
||||||
|
NamedParameterSpec spec = NamedParameterSpec.ED25519;
|
||||||
|
EdECPrivateKeySpec keySpec = new EdECPrivateKeySpec(spec, seed);
|
||||||
|
KeyFactory factory = KeyFactory.getInstance("EdDSA");
|
||||||
|
return EdECPrivateKey.class.cast(factory.generatePrivate(keySpec));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.apache.sshd.common.util.security.edec;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import org.apache.sshd.common.keyprovider.KeyPairProvider;
|
||||||
|
import org.apache.sshd.common.session.SessionContext;
|
||||||
|
import org.apache.sshd.common.signature.AbstractSignature;
|
||||||
|
import org.apache.sshd.common.util.ValidateUtils;
|
||||||
|
|
||||||
|
public class SignatureEd25519 extends AbstractSignature {
|
||||||
|
|
||||||
|
public SignatureEd25519() {
|
||||||
|
super("NONEwithEdDSA");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean verify(SessionContext session, byte[] sig) throws Exception {
|
||||||
|
byte[] data = sig;
|
||||||
|
Map.Entry<String, byte[]> encoding = extractEncodedSignature(data, KeyPairProvider.SSH_ED25519::equalsIgnoreCase);
|
||||||
|
if (encoding != null) {
|
||||||
|
String keyType = encoding.getKey();
|
||||||
|
ValidateUtils.checkTrue(KeyPairProvider.SSH_ED25519.equals(keyType), "Mismatched key type: %s", keyType);
|
||||||
|
data = encoding.getValue();
|
||||||
|
}
|
||||||
|
return doVerify(data);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,62 +0,0 @@
|
||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you 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 org.apache.sshd.common.util.security.xbib;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import org.apache.sshd.common.NamedResource;
|
|
||||||
import org.apache.sshd.common.config.keys.FilePasswordProvider;
|
|
||||||
import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser;
|
|
||||||
import org.apache.sshd.common.session.SessionContext;
|
|
||||||
import org.xbib.net.security.PrivateKeyReader;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
|
||||||
*/
|
|
||||||
public class XbibKeyPairResourceParser implements KeyPairResourceParser {
|
|
||||||
|
|
||||||
public static final XbibKeyPairResourceParser INSTANCE = new XbibKeyPairResourceParser();
|
|
||||||
|
|
||||||
public XbibKeyPairResourceParser() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canExtractKeyPairs(NamedResource resourceKey, List<String> lines) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<KeyPair> loadKeyPairs(SessionContext session,
|
|
||||||
NamedResource resourceKey,
|
|
||||||
FilePasswordProvider passwordProvider,
|
|
||||||
List<String> lines) throws IOException, GeneralSecurityException {
|
|
||||||
PrivateKeyReader privateKeyReader = new PrivateKeyReader();
|
|
||||||
InputStream inputStream = new ByteArrayInputStream(String.join("\n", lines).getBytes(StandardCharsets.US_ASCII));
|
|
||||||
String password = passwordProvider.getPassword(session, resourceKey, 0);
|
|
||||||
return Collections.singleton(privateKeyReader.generateFrom(inputStream, password));
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue