Compare commits
No commits in common. 'main' and 'stable' have entirely different histories.
@ -1,4 +1,5 @@
|
|||||||
dependencies {
|
dependencies {
|
||||||
api project(':files-api')
|
api project(':files-api')
|
||||||
api project(':files-sftp')
|
api project(':files-sftp')
|
||||||
|
testImplementation libs.net.security
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
package org.apache.sshd.fs.test;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import org.apache.sshd.client.ClientBuilder;
|
||||||
|
import org.apache.sshd.client.SshClient;
|
||||||
|
import org.apache.sshd.fs.SftpFileSystem;
|
||||||
|
import org.apache.sshd.fs.SftpFileSystemProvider;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import org.xbib.net.security.PrivateKeyReader;
|
||||||
|
|
||||||
|
public class SFTPFileSystemTest {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(SFTPFileSystemTest.class.getName());
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testXbib() throws Exception {
|
||||||
|
Map<String, String> env = new HashMap<>();
|
||||||
|
env.put("username", "joerg");
|
||||||
|
URI uri = URI.create("sftp://xbib.org");
|
||||||
|
SshClient sshClient = ClientBuilder.builder().build();
|
||||||
|
Path privateKeyPath = Paths.get(System.getProperty("user.home") + "/.ssh/id_ed25519");
|
||||||
|
PrivateKeyReader privateKeyReader = new PrivateKeyReader();
|
||||||
|
KeyPair keyPair = privateKeyReader.generateFrom(Files.newInputStream(privateKeyPath), null);
|
||||||
|
sshClient.addPublicKeyIdentity(keyPair);
|
||||||
|
sshClient.setNioWorkers(1);
|
||||||
|
sshClient.start();
|
||||||
|
SftpFileSystem fileSystem = new SftpFileSystemProvider(sshClient).newFileSystem(uri, env);
|
||||||
|
fileSystem.close();
|
||||||
|
// ...
|
||||||
|
sshClient.stop();
|
||||||
|
sshClient.close();
|
||||||
|
}
|
||||||
|
}
|
@ -1,41 +0,0 @@
|
|||||||
package org.apache.sshd.fs.test;
|
|
||||||
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import org.apache.sshd.client.ClientBuilder;
|
|
||||||
import org.apache.sshd.client.SshClient;
|
|
||||||
import org.apache.sshd.fs.SftpFileSystem;
|
|
||||||
import org.apache.sshd.fs.SftpFileSystemProvider;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
public class SftpWithPrivateKeyReaderTest {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(SftpWithPrivateKeyReaderTest.class.getName());
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testXbib() throws Exception {
|
|
||||||
Map<String, String> env = new HashMap<>();
|
|
||||||
env.put("username", "joerg");
|
|
||||||
URI uri = URI.create("sftp://alkmene");
|
|
||||||
try (SshClient sshClient = ClientBuilder.builder()
|
|
||||||
.build()) {
|
|
||||||
sshClient.setNioWorkers(1);
|
|
||||||
sshClient.start();
|
|
||||||
SftpFileSystem fileSystem = new SftpFileSystemProvider(sshClient).newFileSystem(uri, env);
|
|
||||||
assertTrue(Files.exists(fileSystem.getDefaultDir()));
|
|
||||||
try (Stream<Path> stream = Files.list(fileSystem.getDefaultDir())) {
|
|
||||||
stream.forEach(p -> logger.log(Level.INFO, "p = " + p));
|
|
||||||
}
|
|
||||||
fileSystem.close();
|
|
||||||
sshClient.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,3 @@
|
|||||||
|
dependencies {
|
||||||
|
implementation libs.net.security
|
||||||
|
}
|
@ -1,82 +0,0 @@
|
|||||||
package org.apache.sshd.common.config.keys.impl;
|
|
||||||
|
|
||||||
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.keyprovider.KeyPairProvider;
|
|
||||||
import org.apache.sshd.common.session.SessionContext;
|
|
||||||
import org.apache.sshd.common.util.ArrayUtil;
|
|
||||||
|
|
||||||
public final class Ed25519PublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EdECPublicKey, EdECPrivateKey> {
|
|
||||||
|
|
||||||
public static final Ed25519PublicKeyEntryDecoder INSTANCE = new Ed25519PublicKeyEntryDecoder();
|
|
||||||
|
|
||||||
private Ed25519PublicKeyEntryDecoder() {
|
|
||||||
super(EdECPublicKey.class, EdECPrivateKey.class, Collections.singletonList(KeyPairProvider.SSH_ED25519));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
|
|
||||||
return KeyPairGenerator.getInstance("Ed25519");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String encodePublicKey(OutputStream s, EdECPublicKey key) throws IOException {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@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[] arr = keyData.readAllBytes();
|
|
||||||
byte msb = arr[arr.length - 1];
|
|
||||||
boolean xOdd = (msb & 0x80) != 0;
|
|
||||||
arr[arr.length - 1] &= (byte) 0x7F;
|
|
||||||
ArrayUtil.reverse(arr);
|
|
||||||
BigInteger y = new BigInteger(1, arr);
|
|
||||||
NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519;
|
|
||||||
EdECPoint edECPoint = new EdECPoint(xOdd, y);
|
|
||||||
return generatePublicKey(new EdECPublicKeySpec(namedParameterSpec, edECPoint));
|
|
||||||
}
|
|
||||||
|
|
||||||
@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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,164 +0,0 @@
|
|||||||
package org.apache.sshd.common.config.keys.loader.openssh;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.StreamCorruptedException;
|
|
||||||
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.InvalidKeySpecException;
|
|
||||||
import java.security.spec.NamedParameterSpec;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import org.apache.sshd.common.config.keys.FilePasswordProvider;
|
|
||||||
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.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();
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
byte[] b = keyData.readAllBytes();
|
|
||||||
OpenSsh openSsh = decodeFromOpenSsh(b);
|
|
||||||
NamedParameterSpec params = NamedParameterSpec.ED25519;
|
|
||||||
EdECPrivateKeySpec spec = new EdECPrivateKeySpec(params, openSsh.privatekey);
|
|
||||||
KeyFactory factory = KeyFactory.getInstance("EdDSA");
|
|
||||||
return (EdECPrivateKey) factory.generatePrivate(spec);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String encodePrivateKey(SecureByteArrayOutputStream s,
|
|
||||||
EdECPrivateKey key,
|
|
||||||
EdECPublicKey pubKey) throws IOException {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public EdECPublicKey recoverPublicKey(EdECPrivateKey key) throws GeneralSecurityException {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@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 {
|
|
||||||
try (DERParser parser = new DERParser(key.getEncoded())) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
NamedParameterSpec spec = NamedParameterSpec.ED25519;
|
|
||||||
EdECPrivateKeySpec keySpec = new EdECPrivateKeySpec(spec, obj.getValue());
|
|
||||||
KeyFactory factory = KeyFactory.getInstance("EdDSA");
|
|
||||||
return (EdECPrivateKey) factory.generatePrivate(keySpec);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new GeneralSecurityException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
|
|
||||||
return KeyPairGenerator.getInstance("Ed25519");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
|
|
||||||
return KeyFactory.getInstance("EdDSA");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static OpenSsh decodeFromOpenSsh(byte[] rawKey) throws InvalidKeySpecException {
|
|
||||||
OpenSsh openSsh = new OpenSsh();
|
|
||||||
openSsh.verify = Arrays.copyOfRange(rawKey, 0, OPENSSH_KEY_V1.length());
|
|
||||||
if (!new String(openSsh.verify).equals(OPENSSH_KEY_V1)) {
|
|
||||||
throw new InvalidKeySpecException("invalid OpenSSH key file");
|
|
||||||
}
|
|
||||||
boolean occurred = false;
|
|
||||||
int index = 0;
|
|
||||||
for (int i = 0; i < rawKey.length; i++) {
|
|
||||||
if (rawKey[i] == 's'
|
|
||||||
&& rawKey[i + 1] == 's'
|
|
||||||
&& rawKey[i + 2] == 'h'
|
|
||||||
&& rawKey[i + 3] == '-'
|
|
||||||
&& rawKey[i + 4] == 'e'
|
|
||||||
&& rawKey[i + 5] == 'd'
|
|
||||||
&& rawKey[i + 6] == '2'
|
|
||||||
&& rawKey[i + 7] == '5'
|
|
||||||
&& rawKey[i + 8] == '5'
|
|
||||||
&& rawKey[i + 9] == '1'
|
|
||||||
&& rawKey[i + 10] == '9'
|
|
||||||
&& rawKey[i + 11] == 0x00
|
|
||||||
&& rawKey[i + 12] == 0x00
|
|
||||||
&& rawKey[i + 13] == 0x00
|
|
||||||
&& rawKey[i + 14] == ' ') {
|
|
||||||
index = i + 15;
|
|
||||||
if (occurred) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
occurred = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
openSsh.publickey = Arrays.copyOfRange(rawKey, index, index + 32);
|
|
||||||
index += 32;
|
|
||||||
for (int i = index; i < rawKey.length; i++) {
|
|
||||||
if (rawKey[i] == 0x00
|
|
||||||
&& rawKey[i + 1] == 0x00
|
|
||||||
&& rawKey[i + 2] == 0x00
|
|
||||||
&& rawKey[i + 3] == '@') {
|
|
||||||
index = i + 4;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
openSsh.privatekey = Arrays.copyOfRange(rawKey, index, index + 32);
|
|
||||||
return openSsh;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String OPENSSH_KEY_V1 = "openssh-key-v1";
|
|
||||||
|
|
||||||
private static class OpenSsh {
|
|
||||||
byte[] verify;
|
|
||||||
byte[] publickey;
|
|
||||||
byte[] privatekey;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
package org.apache.sshd.common.signature;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import org.apache.sshd.common.session.SessionContext;
|
|
||||||
import org.apache.sshd.common.util.ValidateUtils;
|
|
||||||
|
|
||||||
public class SignatureEd25519 extends AbstractSignature {
|
|
||||||
|
|
||||||
public SignatureEd25519() {
|
|
||||||
super("EdDSA");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean verify(SessionContext session, byte[] sig) throws Exception {
|
|
||||||
byte[] data = sig;
|
|
||||||
Map.Entry<String, byte[]> encoding = extractEncodedSignature(data,
|
|
||||||
s -> s.equalsIgnoreCase("ssh-ed25519"));
|
|
||||||
if (encoding != null) {
|
|
||||||
String keyType = encoding.getKey();
|
|
||||||
ValidateUtils.checkTrue("ssh-ed25519".equals(keyType), "Mismatched key type: %s", keyType);
|
|
||||||
data = encoding.getValue();
|
|
||||||
}
|
|
||||||
return doVerify(data);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package org.apache.sshd.common.util;
|
|
||||||
|
|
||||||
public class ArrayUtil {
|
|
||||||
|
|
||||||
private ArrayUtil() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String toHex(byte[] data) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
for (byte b : data) {
|
|
||||||
sb.append(Character.forDigit((b & 240) >> 4, 16)).append(Character.forDigit((b & 15), 16));
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void reverse(byte[] arr) {
|
|
||||||
int i = 0;
|
|
||||||
int j = arr.length - 1;
|
|
||||||
while (i < j) {
|
|
||||||
swap(arr, i, j);
|
|
||||||
i++;
|
|
||||||
j--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void swap(byte[] arr, int i, int j) {
|
|
||||||
byte tmp = arr[i];
|
|
||||||
arr[i] = arr[j];
|
|
||||||
arr[j] = tmp;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* 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.eddsa;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
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;
|
||||||
|
import org.apache.sshd.common.util.security.SecurityUtils;
|
||||||
|
import org.xbib.net.security.eddsa.EdDSAPrivateKey;
|
||||||
|
import org.xbib.net.security.eddsa.EdDSAPublicKey;
|
||||||
|
import org.xbib.net.security.eddsa.spec.EdDSAPrivateKeySpec;
|
||||||
|
import org.xbib.net.security.eddsa.spec.EdDSAPublicKeySpec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
||||||
|
*/
|
||||||
|
public final class Ed25519PublicKeyDecoder extends AbstractPublicKeyEntryDecoder<EdDSAPublicKey, EdDSAPrivateKey> {
|
||||||
|
public static final int MAX_ALLOWED_SEED_LEN = 1024; // in reality it is much less than this
|
||||||
|
|
||||||
|
public static final Ed25519PublicKeyDecoder INSTANCE = new Ed25519PublicKeyDecoder();
|
||||||
|
|
||||||
|
private Ed25519PublicKeyDecoder() {
|
||||||
|
super(EdDSAPublicKey.class, EdDSAPrivateKey.class,
|
||||||
|
Collections.unmodifiableList(
|
||||||
|
Collections.singletonList(
|
||||||
|
KeyPairProvider.SSH_ED25519)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EdDSAPublicKey clonePublicKey(EdDSAPublicKey key) throws GeneralSecurityException {
|
||||||
|
if (key == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return generatePublicKey(new EdDSAPublicKeySpec(key.getA(), key.getParams()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EdDSAPrivateKey clonePrivateKey(EdDSAPrivateKey key) throws GeneralSecurityException {
|
||||||
|
if (key == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return generatePrivateKey(new EdDSAPrivateKeySpec(key.getSeed(), key.getParams()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
|
||||||
|
return SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String encodePublicKey(OutputStream s, EdDSAPublicKey key) throws IOException {
|
||||||
|
Objects.requireNonNull(key, "No public key provided");
|
||||||
|
KeyEntryResolver.encodeString(s, KeyPairProvider.SSH_ED25519);
|
||||||
|
byte[] seed = getSeedValue(key);
|
||||||
|
KeyEntryResolver.writeRLEBytes(s, seed);
|
||||||
|
return KeyPairProvider.SSH_ED25519;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
|
||||||
|
return SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EdDSAPublicKey decodePublicKey(
|
||||||
|
SessionContext session, String keyType, InputStream keyData, Map<String, String> headers)
|
||||||
|
throws IOException, GeneralSecurityException {
|
||||||
|
byte[] seed = KeyEntryResolver.readRLEBytes(keyData, MAX_ALLOWED_SEED_LEN);
|
||||||
|
return EdDSAPublicKey.class.cast(SecurityUtils.generateEDDSAPublicKey(keyType, seed));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] getSeedValue(EdDSAPublicKey key) {
|
||||||
|
// a bit of reverse-engineering on the EdDSAPublicKeySpec
|
||||||
|
return (key == null) ? null : key.getAbyte();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* 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.eddsa;
|
||||||
|
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.security.Provider;
|
||||||
|
import java.security.Signature;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import org.apache.sshd.common.util.GenericUtils;
|
||||||
|
import org.apache.sshd.common.util.ReflectionUtils;
|
||||||
|
import org.apache.sshd.common.util.security.AbstractSecurityProviderRegistrar;
|
||||||
|
import org.apache.sshd.common.util.security.SecurityUtils;
|
||||||
|
import org.apache.sshd.common.util.threads.ThreadUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
||||||
|
*/
|
||||||
|
public class EdDSASecurityProviderRegistrar extends AbstractSecurityProviderRegistrar {
|
||||||
|
public static final String PROVIDER_CLASS = "org.xbib.net.security.eddsa.EdDSASecurityProvider";
|
||||||
|
// Do not define a static registrar instance to minimize class loading issues
|
||||||
|
private final AtomicReference<Boolean> supportHolder = new AtomicReference<>(null);
|
||||||
|
|
||||||
|
public EdDSASecurityProviderRegistrar() {
|
||||||
|
super(SecurityUtils.EDDSA);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return super.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Provider getSecurityProvider() {
|
||||||
|
try {
|
||||||
|
return getOrCreateProvider(PROVIDER_CLASS);
|
||||||
|
} catch (ReflectiveOperationException t) {
|
||||||
|
Throwable e = GenericUtils.peelException(t);
|
||||||
|
if (e instanceof RuntimeException) {
|
||||||
|
throw (RuntimeException) e;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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(SecurityUtils.CURVE_ED25519_SHA512, name, String.CASE_INSENSITIVE_ORDER) == 0;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupported() {
|
||||||
|
Boolean supported;
|
||||||
|
synchronized (supportHolder) {
|
||||||
|
supported = supportHolder.get();
|
||||||
|
if (supported != null) {
|
||||||
|
return supported.booleanValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassLoader cl = ThreadUtils.resolveDefaultClassLoader(getClass());
|
||||||
|
supported = ReflectionUtils.isClassAvailable(cl, "org.xbib.net.security.eddsa.EdDSAKey");
|
||||||
|
supportHolder.set(supported);
|
||||||
|
}
|
||||||
|
|
||||||
|
return supported.booleanValue();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,203 @@
|
|||||||
|
/*
|
||||||
|
* 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.eddsa;
|
||||||
|
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.Key;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
|
||||||
|
import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
|
||||||
|
import org.apache.sshd.common.util.ValidateUtils;
|
||||||
|
import org.apache.sshd.common.util.buffer.Buffer;
|
||||||
|
import org.apache.sshd.common.util.security.SecurityUtils;
|
||||||
|
import org.xbib.net.security.eddsa.EdDSAEngine;
|
||||||
|
import org.xbib.net.security.eddsa.EdDSAKey;
|
||||||
|
import org.xbib.net.security.eddsa.EdDSAPrivateKey;
|
||||||
|
import org.xbib.net.security.eddsa.EdDSAPublicKey;
|
||||||
|
import org.xbib.net.security.eddsa.spec.EdDSANamedCurveTable;
|
||||||
|
import org.xbib.net.security.eddsa.spec.EdDSAParameterSpec;
|
||||||
|
import org.xbib.net.security.eddsa.spec.EdDSAPrivateKeySpec;
|
||||||
|
import org.xbib.net.security.eddsa.spec.EdDSAPublicKeySpec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
||||||
|
*/
|
||||||
|
public final class EdDSASecurityProviderUtils {
|
||||||
|
// See EdDSANamedCurveTable
|
||||||
|
public static final String CURVE_ED25519_SHA512 = "Ed25519";
|
||||||
|
public static final int KEY_SIZE = 256;
|
||||||
|
|
||||||
|
private EdDSASecurityProviderUtils() {
|
||||||
|
throw new UnsupportedOperationException("No instance");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Class<? extends PublicKey> getEDDSAPublicKeyType() {
|
||||||
|
return EdDSAPublicKey.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Class<? extends PrivateKey> getEDDSAPrivateKeyType() {
|
||||||
|
return EdDSAPrivateKey.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isEDDSAKey(Key key) {
|
||||||
|
return getEDDSAKeySize(key) == KEY_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getEDDSAKeySize(Key key) {
|
||||||
|
return (SecurityUtils.isEDDSACurveSupported() && (key instanceof EdDSAKey)) ? KEY_SIZE : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) {
|
||||||
|
if (!SecurityUtils.isEDDSACurveSupported()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((k1 instanceof EdDSAPublicKey) && (k2 instanceof EdDSAPublicKey)) {
|
||||||
|
if (Objects.equals(k1, k2)) {
|
||||||
|
return true;
|
||||||
|
} else if (k1 == null || k2 == null) {
|
||||||
|
return false; // both null is covered by Objects#equals
|
||||||
|
}
|
||||||
|
|
||||||
|
EdDSAPublicKey ed1 = (EdDSAPublicKey) k1;
|
||||||
|
EdDSAPublicKey ed2 = (EdDSAPublicKey) k2;
|
||||||
|
return Arrays.equals(ed1.getAbyte(), ed2.getAbyte())
|
||||||
|
&& compareEDDSAKeyParams(ed1.getParams(), ed2.getParams());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isEDDSASignatureAlgorithm(String algorithm) {
|
||||||
|
return EdDSAEngine.SIGNATURE_ALGORITHM.equalsIgnoreCase(algorithm);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EdDSAPublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException {
|
||||||
|
ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported");
|
||||||
|
if (!(key instanceof EdDSAPrivateKey)) {
|
||||||
|
throw new InvalidKeyException("Private key is not " + SecurityUtils.EDDSA);
|
||||||
|
}
|
||||||
|
|
||||||
|
EdDSAPrivateKey prvKey = (EdDSAPrivateKey) key;
|
||||||
|
EdDSAPublicKeySpec keySpec = new EdDSAPublicKeySpec(prvKey.getAbyte(), prvKey.getParams());
|
||||||
|
KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);
|
||||||
|
return EdDSAPublicKey.class.cast(factory.generatePublic(keySpec));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static org.apache.sshd.common.signature.Signature getEDDSASignature() {
|
||||||
|
ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported");
|
||||||
|
return new SignatureEd25519();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isEDDSAKeyFactoryAlgorithm(String algorithm) {
|
||||||
|
return SecurityUtils.EDDSA.equalsIgnoreCase(algorithm);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isEDDSAKeyPairGeneratorAlgorithm(String algorithm) {
|
||||||
|
return SecurityUtils.EDDSA.equalsIgnoreCase(algorithm);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PublicKeyEntryDecoder<? extends PublicKey, ? extends PrivateKey> getEDDSAPublicKeyEntryDecoder() {
|
||||||
|
ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported");
|
||||||
|
return Ed25519PublicKeyDecoder.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PrivateKeyEntryDecoder<? extends PublicKey, ? extends PrivateKey> getOpenSSHEDDSAPrivateKeyEntryDecoder() {
|
||||||
|
ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported");
|
||||||
|
return OpenSSHEd25519PrivateKeyEntryDecoder.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) {
|
||||||
|
if (!SecurityUtils.isEDDSACurveSupported()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((k1 instanceof EdDSAPrivateKey) && (k2 instanceof EdDSAPrivateKey)) {
|
||||||
|
if (Objects.equals(k1, k2)) {
|
||||||
|
return true;
|
||||||
|
} else if (k1 == null || k2 == null) {
|
||||||
|
return false; // both null is covered by Objects#equals
|
||||||
|
}
|
||||||
|
|
||||||
|
EdDSAPrivateKey ed1 = (EdDSAPrivateKey) k1;
|
||||||
|
EdDSAPrivateKey ed2 = (EdDSAPrivateKey) k2;
|
||||||
|
return Arrays.equals(ed1.getSeed(), ed2.getSeed())
|
||||||
|
&& compareEDDSAKeyParams(ed1.getParams(), ed2.getParams());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean compareEDDSAKeyParams(EdDSAParameterSpec s1, EdDSAParameterSpec s2) {
|
||||||
|
if (Objects.equals(s1, s2)) {
|
||||||
|
return true;
|
||||||
|
} else if (s1 == null || s2 == null) {
|
||||||
|
return false; // both null is covered by Objects#equals
|
||||||
|
} else {
|
||||||
|
return Objects.equals(s1.getHashAlgorithm(), s2.getHashAlgorithm())
|
||||||
|
&& Objects.equals(s1.getCurve(), s2.getCurve())
|
||||||
|
&& Objects.equals(s1.getB(), s2.getB());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PublicKey generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException {
|
||||||
|
if (!SecurityUtils.isEDDSACurveSupported()) {
|
||||||
|
throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
EdDSAParameterSpec params = EdDSANamedCurveTable.getByName(CURVE_ED25519_SHA512);
|
||||||
|
EdDSAPublicKeySpec keySpec = new EdDSAPublicKeySpec(seed, params);
|
||||||
|
KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);
|
||||||
|
return factory.generatePublic(keySpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PrivateKey generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException {
|
||||||
|
if (!SecurityUtils.isEDDSACurveSupported()) {
|
||||||
|
throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
EdDSAParameterSpec params = EdDSANamedCurveTable.getByName(CURVE_ED25519_SHA512);
|
||||||
|
EdDSAPrivateKeySpec keySpec = new EdDSAPrivateKeySpec(seed, params);
|
||||||
|
KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);
|
||||||
|
return factory.generatePrivate(keySpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <B extends Buffer> B putRawEDDSAPublicKey(B buffer, PublicKey key) {
|
||||||
|
ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported");
|
||||||
|
EdDSAPublicKey edKey = ValidateUtils.checkInstanceOf(key, EdDSAPublicKey.class, "Not an EDDSA public key: %s", key);
|
||||||
|
byte[] seed = Ed25519PublicKeyDecoder.getSeedValue(edKey);
|
||||||
|
ValidateUtils.checkNotNull(seed, "No seed extracted from key: %s", edKey.getA());
|
||||||
|
buffer.putBytes(seed);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <B extends Buffer> B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey) {
|
||||||
|
ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported");
|
||||||
|
ValidateUtils.checkInstanceOf(pubKey, EdDSAPublicKey.class, "Not an EDDSA public key: %s", pubKey);
|
||||||
|
ValidateUtils.checkInstanceOf(prvKey, EdDSAPrivateKey.class, "Not an EDDSA private key: %s", prvKey);
|
||||||
|
throw new UnsupportedOperationException("Full SSHD-440 implementation N/A");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
* 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.eddsa;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
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.security.SecurityUtils;
|
||||||
|
import org.xbib.net.security.eddsa.EdDSAPrivateKey;
|
||||||
|
import org.xbib.net.security.eddsa.EdDSAPublicKey;
|
||||||
|
import org.xbib.net.security.eddsa.spec.EdDSANamedCurveTable;
|
||||||
|
import org.xbib.net.security.eddsa.spec.EdDSAParameterSpec;
|
||||||
|
import org.xbib.net.security.eddsa.spec.EdDSAPrivateKeySpec;
|
||||||
|
import org.xbib.net.security.eddsa.spec.EdDSAPublicKeySpec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
||||||
|
*/
|
||||||
|
public class OpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder<EdDSAPublicKey, EdDSAPrivateKey> {
|
||||||
|
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(EdDSAPublicKey.class, EdDSAPrivateKey.class,
|
||||||
|
Collections.unmodifiableList(
|
||||||
|
Collections.singletonList(
|
||||||
|
KeyPairProvider.SSH_ED25519)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EdDSAPrivateKey decodePrivateKey(
|
||||||
|
SessionContext session, String keyType, FilePasswordProvider passwordProvider, InputStream keyData)
|
||||||
|
throws IOException, GeneralSecurityException {
|
||||||
|
if (!KeyPairProvider.SSH_ED25519.equals(keyType)) {
|
||||||
|
throw new InvalidKeyException("Unsupported key type: " + keyType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SecurityUtils.isEDDSACurveSupported()) {
|
||||||
|
throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " provider not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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[] sk = Arrays.copyOf(keypair, SK_SIZE);
|
||||||
|
EdDSAParameterSpec params = EdDSANamedCurveTable.getByName(EdDSASecurityProviderUtils.CURVE_ED25519_SHA512);
|
||||||
|
EdDSAPrivateKey privateKey = generatePrivateKey(new EdDSAPrivateKeySpec(sk, params));
|
||||||
|
|
||||||
|
// the private key class contains the calculated public key (Abyte)
|
||||||
|
// pointers to the corresponding code:
|
||||||
|
// EdDSAPrivateKeySpec.EdDSAPrivateKeySpec(byte[], EdDSAParameterSpec): A = spec.getB().scalarMultiply(a);
|
||||||
|
// EdDSAPrivateKey.EdDSAPrivateKey(EdDSAPrivateKeySpec): this.Abyte = this.A.toByteArray();
|
||||||
|
|
||||||
|
// we can now verify the generated pk matches the one we read
|
||||||
|
if (!Arrays.equals(privateKey.getAbyte(), pk)) {
|
||||||
|
throw new InvalidKeyException("The provided pk does NOT match the computed pk for the given sk.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return privateKey;
|
||||||
|
} 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, EdDSAPrivateKey key, EdDSAPublicKey pubKey)
|
||||||
|
throws IOException {
|
||||||
|
Objects.requireNonNull(key, "No private key provided");
|
||||||
|
|
||||||
|
// ed25519 bernstein naming: pk .. public key, sk .. secret key
|
||||||
|
// we are expected to write the following arrays (type:size):
|
||||||
|
// [pk:32], [sk:32,pk:32]
|
||||||
|
|
||||||
|
byte[] sk = key.getSeed();
|
||||||
|
byte[] pk = key.getAbyte();
|
||||||
|
|
||||||
|
Objects.requireNonNull(sk, "No seed");
|
||||||
|
|
||||||
|
byte[] keypair = new byte[KEYPAIR_SIZE];
|
||||||
|
System.arraycopy(sk, 0, keypair, 0, SK_SIZE);
|
||||||
|
System.arraycopy(pk, 0, keypair, SK_SIZE, PK_SIZE);
|
||||||
|
|
||||||
|
KeyEntryResolver.writeRLEBytes(s, pk);
|
||||||
|
KeyEntryResolver.writeRLEBytes(s, keypair);
|
||||||
|
|
||||||
|
return KeyPairProvider.SSH_ED25519;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPublicKeyRecoverySupported() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EdDSAPublicKey recoverPublicKey(EdDSAPrivateKey prvKey) throws GeneralSecurityException {
|
||||||
|
return EdDSASecurityProviderUtils.recoverEDDSAPublicKey(prvKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EdDSAPublicKey clonePublicKey(EdDSAPublicKey key) throws GeneralSecurityException {
|
||||||
|
if (key == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return generatePublicKey(new EdDSAPublicKeySpec(key.getA(), key.getParams()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EdDSAPrivateKey clonePrivateKey(EdDSAPrivateKey key) throws GeneralSecurityException {
|
||||||
|
if (key == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return generatePrivateKey(new EdDSAPrivateKeySpec(key.getSeed(), key.getParams()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
|
||||||
|
return SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
|
||||||
|
return SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* 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.eddsa;
|
||||||
|
|
||||||
|
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;
|
||||||
|
import org.xbib.net.security.eddsa.EdDSAEngine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
||||||
|
*/
|
||||||
|
public class SignatureEd25519 extends AbstractSignature {
|
||||||
|
public SignatureEd25519() {
|
||||||
|
super(EdDSAEngine.SIGNATURE_ALGORITHM);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* 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));
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,3 @@
|
|||||||
group = org.xbib
|
group = org.xbib
|
||||||
name = files
|
name = files
|
||||||
version = 4.7.0
|
version = 4.6.0
|
||||||
|
Loading…
Reference in New Issue