import java.io.FileInputStream;
import java.io.InputStreamReader;
+import java.security.KeyFactory;
import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;
+import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
import org.apache.sshd.common.keyprovider.AbstractKeyPairProvider;
import org.apache.sshd.common.util.security.SecurityUtils;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
+import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
+import org.bouncycastle.crypto.util.OpenSSHPrivateKeyUtil;
+import org.bouncycastle.crypto.util.OpenSSHPublicKeyUtil;
+import org.bouncycastle.jcajce.spec.OpenSSHPrivateKeySpec;
+import org.bouncycastle.jcajce.spec.OpenSSHPublicKeySpec;
+import org.bouncycastle.util.io.pem.PemObject;
+import org.bouncycastle.util.io.pem.PemReader;
/**
* This host key provider loads private keys from the specified files.
this.files = files;
}
+
+ @Override
public Iterable<KeyPair> loadKeys()
{
if (!SecurityUtils.isBouncyCastleRegistered()) {
};
}
- protected KeyPair doLoadKey(String file)
+
+ private KeyPair doLoadKey(String file)
{
try {
- PEMParser r = new PEMParser(new InputStreamReader(new FileInputStream(file)));
- try {
+
+ try (PemReader r = new PemReader(new InputStreamReader(new FileInputStream(file)))) {
+ PemObject pemObject = r.readPemObject();
+ if ("OPENSSH PRIVATE KEY".equals(pemObject.getType())) {
+ // This reads a properly OpenSSH formatted ed25519 private key file.
+ // It is currently unused because the SSHD library in play doesn't work with proper keys.
+ // This is kept in the hope that in the future the library offers proper support.
+ try {
+ byte[] privateKeyContent = pemObject.getContent();
+ AsymmetricKeyParameter privateKeyParameters = OpenSSHPrivateKeyUtil.parsePrivateKeyBlob(privateKeyContent);
+ if (privateKeyParameters instanceof Ed25519PrivateKeyParameters) {
+ OpenSSHPrivateKeySpec privkeySpec = new OpenSSHPrivateKeySpec(privateKeyContent);
+
+ Ed25519PublicKeyParameters publicKeyParameters = ((Ed25519PrivateKeyParameters)privateKeyParameters).generatePublicKey();
+ OpenSSHPublicKeySpec pubKeySpec = new OpenSSHPublicKeySpec(OpenSSHPublicKeyUtil.encodePublicKey(publicKeyParameters));
+
+ KeyFactory kf = KeyFactory.getInstance("Ed25519", "BC");
+ PrivateKey privateKey = kf.generatePrivate(privkeySpec);
+ PublicKey publicKey = kf.generatePublic(pubKeySpec);
+ return new KeyPair(publicKey, privateKey);
+ }
+ else {
+ log.warn("OpenSSH format is only supported for Ed25519 key type. Unable to read key " + file);
+ }
+ }
+ catch (Exception e) {
+ log.warn("Unable to read key " + file, e);
+ }
+ return null;
+ }
+
+ if ("EDDSA PRIVATE KEY".equals(pemObject.getType())) {
+ // This reads the ed25519 key from a file format that we created in SshDaemon.
+ // The type EDDSA PRIVATE KEY was given by us and nothing official.
+ byte[] privateKeyContent = pemObject.getContent();
+ PrivateKeyEntryDecoder<? extends PublicKey,? extends PrivateKey> decoder = SecurityUtils.getOpenSSHEDDSAPrivateKeyEntryDecoder();
+ PrivateKey privateKey = decoder.decodePrivateKey(null, privateKeyContent, 0, privateKeyContent.length);
+ PublicKey publicKey = SecurityUtils. recoverEDDSAPublicKey(privateKey);
+ return new KeyPair(publicKey, privateKey);
+ }
+ }
+
+ try (PEMParser r = new PEMParser(new InputStreamReader(new FileInputStream(file)))) {
Object o = r.readObject();
JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter();
else if (o instanceof KeyPair) {
return (KeyPair)o;
}
+ else {
+ log.warn("Cannot read unsupported PEM object of type: " + o.getClass().getCanonicalName());
+ }
}
- finally {
- r.close();
- }
+
}
catch (Exception e) {
log.warn("Unable to read key " + file, e);
*/
package com.gitblit.transport.ssh;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
import java.text.MessageFormat;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
+import net.i2p.crypto.eddsa.EdDSAPrivateKey;
+import org.apache.sshd.common.config.keys.KeyEntryResolver;
import org.apache.sshd.common.io.IoServiceFactoryFactory;
import org.apache.sshd.common.io.mina.MinaServiceFactoryFactory;
import org.apache.sshd.common.io.nio2.Nio2ServiceFactoryFactory;
import org.apache.sshd.common.util.security.SecurityUtils;
import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleSecurityProviderRegistrar;
import org.apache.sshd.common.util.security.eddsa.EdDSASecurityProviderRegistrar;
+import org.apache.sshd.common.util.security.eddsa.OpenSSHEd25519PrivateKeyEntryDecoder;
import org.apache.sshd.server.SshServer;
import org.apache.sshd.server.auth.pubkey.CachingPublicKeyAuthenticator;
-import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.util.OpenSSHPrivateKeyUtil;
+import org.bouncycastle.crypto.util.PrivateKeyFactory;
+import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator;
+import org.bouncycastle.util.io.pem.PemObject;
+import org.bouncycastle.util.io.pem.PemWriter;
import org.eclipse.jgit.internal.JGitText;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
*/
public class SshDaemon {
- private final Logger log = LoggerFactory.getLogger(SshDaemon.class);
+ private static final Logger log = LoggerFactory.getLogger(SshDaemon.class);
private static final String AUTH_PUBLICKEY = "publickey";
private static final String AUTH_PASSWORD = "password";
// Generate host RSA and DSA keypairs and create the host keypair provider
File rsaKeyStore = new File(gitblit.getBaseFolder(), "ssh-rsa-hostkey.pem");
File dsaKeyStore = new File(gitblit.getBaseFolder(), "ssh-dsa-hostkey.pem");
+ File ecdsaKeyStore = new File(gitblit.getBaseFolder(), "ssh-ecdsa-hostkey.pem");
+ File eddsaKeyStore = new File(gitblit.getBaseFolder(), "ssh-eddsa-hostkey.pem");
+ File ed25519KeyStore = new File(gitblit.getBaseFolder(), "ssh-ed25519-hostkey.pem");
generateKeyPair(rsaKeyStore, "RSA", 2048);
- generateKeyPair(dsaKeyStore, "DSA", 0);
+ generateKeyPair(ecdsaKeyStore, "ECDSA", 256);
+ generateKeyPair(eddsaKeyStore, "EdDSA", 0);
FileKeyPairProvider hostKeyPairProvider = new FileKeyPairProvider();
- hostKeyPairProvider.setFiles(new String [] { rsaKeyStore.getPath(), dsaKeyStore.getPath(), dsaKeyStore.getPath() });
+ hostKeyPairProvider.setFiles(new String [] { ecdsaKeyStore.getPath(), eddsaKeyStore.getPath(), ed25519KeyStore.getPath(), rsaKeyStore.getPath(), dsaKeyStore.getPath() });
// Configure the preferred SSHD backend
}
}
- private void generateKeyPair(File file, String algorithm, int keySize) {
+ static void generateKeyPair(File file, String algorithm, int keySize) {
if (file.exists()) {
return;
}
}
FileOutputStream os = new FileOutputStream(file);
- JcaPEMWriter w = new JcaPEMWriter(new OutputStreamWriter(os));
- w.writeObject(kp);
+ PemWriter w = new PemWriter(new OutputStreamWriter(os));
+ if (algorithm.equals("ED25519")) {
+ // This generates a proper OpenSSH formatted ed25519 private key file.
+ // It is currently unused because the SSHD library in play doesn't work with proper keys.
+ // This is kept in the hope that in the future the library offers proper support.
+ AsymmetricKeyParameter keyParam = PrivateKeyFactory.createKey(kp.getPrivate().getEncoded());
+ byte[] encKey = OpenSSHPrivateKeyUtil.encodePrivateKey(keyParam);
+ w.writeObject(new PemObject("OPENSSH PRIVATE KEY", encKey));
+ }
+ else if (algorithm.equals("EdDSA")) {
+ // This saves the ed25519 key in a file format that the current SSHD library can work with.
+ // We call it EDDSA PRIVATE KEY, but that string is given by us and nothing official.
+ PrivateKey privateKey = kp.getPrivate();
+ if (privateKey instanceof EdDSAPrivateKey) {
+ OpenSSHEd25519PrivateKeyEntryDecoder encoder = (OpenSSHEd25519PrivateKeyEntryDecoder)SecurityUtils.getOpenSSHEDDSAPrivateKeyEntryDecoder();
+ EdDSAPrivateKey dsaPrivateKey = (EdDSAPrivateKey)privateKey;
+ // Jumping through some hoops here, because the decoder expects the key type as a string at the
+ // start, but the encoder doesn't put it in. So we have to put it in ourselves.
+ ByteArrayOutputStream encos = new ByteArrayOutputStream();
+ String type = encoder.encodePrivateKey(encos, dsaPrivateKey);
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ KeyEntryResolver.encodeString(bos, type);
+ encos.writeTo(bos);
+ w.writeObject(new PemObject("EDDSA PRIVATE KEY", bos.toByteArray()));
+ }
+ else {
+ log.warn("Unable to encode EdDSA key, got key type " + privateKey.getClass().getCanonicalName());
+ }
+ }
+ else {
+ w.writeObject(new JcaMiscPEMGenerator(kp));
+ }
w.flush();
w.close();
} catch (Exception e) {
log.warn(MessageFormat.format("Unable to generate {0} keypair", algorithm), e);
- return;
}
}
}
--- /dev/null
+package com.gitblit.transport.ssh;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.KeyPair;
+import java.util.Iterator;
+
+import static org.junit.Assert.*;
+
+public class FileKeyPairProviderTest
+{
+ @Rule
+ public TemporaryFolder testFolder = new TemporaryFolder();
+
+ private void generateKeyPair(File file, String algorithm, int keySize) {
+ if (file.exists()) {
+ file.delete();
+ }
+ SshDaemon.generateKeyPair(file, algorithm, keySize);
+ }
+
+ @Test
+ public void loadKeysEddsa() throws IOException
+ {
+ File file = testFolder.newFile("eddsa.pem");
+ generateKeyPair(file, "EdDSA", 0);
+
+ FileKeyPairProvider hostKeyPairProvider = new FileKeyPairProvider();
+ hostKeyPairProvider.setFiles(new String [] { file.getPath() });
+
+ Iterable<KeyPair> keyPairs = hostKeyPairProvider.loadKeys();
+ Iterator<KeyPair> iterator = keyPairs.iterator();
+ assertTrue(iterator.hasNext());
+ KeyPair keyPair = iterator.next();
+ assertNotNull(keyPair);
+ assertEquals("Unexpected key pair type", "EdDSA", keyPair.getPrivate().getAlgorithm());
+ }
+
+ @Test
+ public void loadKeysEd25519() throws IOException
+ {
+ File file = testFolder.newFile("ed25519.pem");
+ generateKeyPair(file, "ED25519", 0);
+
+ FileKeyPairProvider hostKeyPairProvider = new FileKeyPairProvider();
+ hostKeyPairProvider.setFiles(new String [] { file.getPath() });
+
+ Iterable<KeyPair> keyPairs = hostKeyPairProvider.loadKeys();
+ Iterator<KeyPair> iterator = keyPairs.iterator();
+ assertTrue(iterator.hasNext());
+ KeyPair keyPair = iterator.next();
+ assertNotNull(keyPair);
+ assertEquals("Unexpected key pair type", "Ed25519", keyPair.getPrivate().getAlgorithm());
+ }
+
+ @Test
+ public void loadKeysECDSA() throws IOException
+ {
+ File file = testFolder.newFile("ecdsa.pem");
+ generateKeyPair(file, "ECDSA", 0);
+
+ FileKeyPairProvider hostKeyPairProvider = new FileKeyPairProvider();
+ hostKeyPairProvider.setFiles(new String [] { file.getPath() });
+
+ Iterable<KeyPair> keyPairs = hostKeyPairProvider.loadKeys();
+ Iterator<KeyPair> iterator = keyPairs.iterator();
+ assertTrue(iterator.hasNext());
+ KeyPair keyPair = iterator.next();
+ assertNotNull(keyPair);
+ assertEquals("Unexpected key pair type", "ECDSA", keyPair.getPrivate().getAlgorithm());
+ }
+
+ @Test
+ public void loadKeysRSA() throws IOException
+ {
+ File file = testFolder.newFile("rsa.pem");
+ generateKeyPair(file, "RSA", 4096);
+
+ FileKeyPairProvider hostKeyPairProvider = new FileKeyPairProvider();
+ hostKeyPairProvider.setFiles(new String [] { file.getPath() });
+
+ Iterable<KeyPair> keyPairs = hostKeyPairProvider.loadKeys();
+ Iterator<KeyPair> iterator = keyPairs.iterator();
+ assertTrue(iterator.hasNext());
+ KeyPair keyPair = iterator.next();
+ assertNotNull(keyPair);
+ assertEquals("Unexpected key pair type", "RSA", keyPair.getPrivate().getAlgorithm());
+ }
+
+ @Test
+ public void loadKeysDefault() throws IOException
+ {
+ File rsa = testFolder.newFile("rsa.pem");
+ generateKeyPair(rsa, "RSA", 2048);
+ File ecdsa = testFolder.newFile("ecdsa.pem");
+ generateKeyPair(ecdsa, "ECDSA", 0);
+ File eddsa = testFolder.newFile("eddsa.pem");
+ generateKeyPair(eddsa, "EdDSA", 0);
+ File ed25519 = testFolder.newFile("ed25519.pem");
+ generateKeyPair(ed25519, "ED25519", 0);
+
+ FileKeyPairProvider hostKeyPairProvider = new FileKeyPairProvider();
+ hostKeyPairProvider.setFiles(new String [] { ecdsa.getPath(), eddsa.getPath(), rsa.getPath(), ed25519.getPath() });
+
+ Iterable<KeyPair> keyPairs = hostKeyPairProvider.loadKeys();
+ Iterator<KeyPair> iterator = keyPairs.iterator();
+
+ assertTrue(iterator.hasNext());
+ KeyPair keyPair = iterator.next();
+ assertNotNull(keyPair);
+ assertEquals("Unexpected key pair type", "ECDSA", keyPair.getPrivate().getAlgorithm());
+
+ assertTrue(iterator.hasNext());
+ keyPair = iterator.next();
+ assertNotNull(keyPair);
+ assertEquals("Unexpected key pair type", "EdDSA", keyPair.getPrivate().getAlgorithm());
+
+ assertTrue(iterator.hasNext());
+ keyPair = iterator.next();
+ assertNotNull(keyPair);
+ assertEquals("Unexpected key pair type", "RSA", keyPair.getPrivate().getAlgorithm());
+
+ assertTrue(iterator.hasNext());
+ keyPair = iterator.next();
+ assertNotNull(keyPair);
+ assertEquals("Unexpected key pair type", "Ed25519", keyPair.getPrivate().getAlgorithm());
+
+ assertFalse(iterator.hasNext());
+ }
+}