From 98f13a89eb7722fdc95d6dc7810f157fb8cfca6b Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Sun, 9 Oct 2022 22:16:26 +0200 Subject: Update BouncyCastle to version 1.69 The version 1.69 is chosen instead of 1.70, because the moxie build would not download the jars, trying to download `...1.7.jar` instead. Three class deprecations are fixed. `PEMWriter` and `X509Extension` are replaced with their drop-in replacements `JcaPEMWriter` and `Extension`. The `PasswordFinder` deprecation note says that "it is no longer used". It also was never used in Gitblit's code, so it is removed from the key par provider class. --- .../gitblit/transport/ssh/FileKeyPairProvider.java | 21 -------------- .../java/com/gitblit/transport/ssh/SshDaemon.java | 4 +-- src/main/java/com/gitblit/utils/X509Utils.java | 33 +++++++++++----------- 3 files changed, 18 insertions(+), 40 deletions(-) (limited to 'src') diff --git a/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java b/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java index cc91bb8c..38618baf 100644 --- a/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java +++ b/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java @@ -31,7 +31,6 @@ import org.bouncycastle.openssl.PEMDecryptorProvider; import org.bouncycastle.openssl.PEMEncryptedKeyPair; import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; -import org.bouncycastle.openssl.PasswordFinder; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; @@ -46,7 +45,6 @@ import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; public class FileKeyPairProvider extends AbstractKeyPairProvider { private String[] files; - private PasswordFinder passwordFinder; public FileKeyPairProvider() { } @@ -55,11 +53,6 @@ public class FileKeyPairProvider extends AbstractKeyPairProvider { this.files = files; } - public FileKeyPairProvider(String[] files, PasswordFinder passwordFinder) { - this.files = files; - this.passwordFinder = passwordFinder; - } - public String[] getFiles() { return files; } @@ -68,14 +61,6 @@ public class FileKeyPairProvider extends AbstractKeyPairProvider { this.files = files; } - public PasswordFinder getPasswordFinder() { - return passwordFinder; - } - - public void setPasswordFinder(PasswordFinder passwordFinder) { - this.passwordFinder = passwordFinder; - } - public Iterable loadKeys() { if (!SecurityUtils.isBouncyCastleRegistered()) { throw new IllegalStateException("BouncyCastle must be registered as a JCE provider"); @@ -130,12 +115,6 @@ public class FileKeyPairProvider extends AbstractKeyPairProvider { JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter(); pemConverter.setProvider("BC"); - if (passwordFinder != null && o instanceof PEMEncryptedKeyPair) { - JcePEMDecryptorProviderBuilder decryptorBuilder = new JcePEMDecryptorProviderBuilder(); - PEMDecryptorProvider pemDecryptor = decryptorBuilder.build(passwordFinder.getPassword()); - o = pemConverter.getKeyPair(((PEMEncryptedKeyPair) o).decryptKeyPair(pemDecryptor)); - } - if (o instanceof PEMKeyPair) { o = pemConverter.getKeyPair((PEMKeyPair)o); return (KeyPair) o; diff --git a/src/main/java/com/gitblit/transport/ssh/SshDaemon.java b/src/main/java/com/gitblit/transport/ssh/SshDaemon.java index 8bb880b0..7a31bc18 100644 --- a/src/main/java/com/gitblit/transport/ssh/SshDaemon.java +++ b/src/main/java/com/gitblit/transport/ssh/SshDaemon.java @@ -34,7 +34,7 @@ import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleSecurityPro import org.apache.sshd.common.util.security.eddsa.EdDSASecurityProviderRegistrar; import org.apache.sshd.server.SshServer; import org.apache.sshd.server.auth.pubkey.CachingPublicKeyAuthenticator; -import org.bouncycastle.openssl.PEMWriter; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; import org.eclipse.jgit.internal.JGitText; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -267,7 +267,7 @@ public class SshDaemon { } FileOutputStream os = new FileOutputStream(file); - PEMWriter w = new PEMWriter(new OutputStreamWriter(os)); + JcaPEMWriter w = new JcaPEMWriter(new OutputStreamWriter(os)); w.writeObject(kp); w.flush(); w.close(); diff --git a/src/main/java/com/gitblit/utils/X509Utils.java b/src/main/java/com/gitblit/utils/X509Utils.java index b661922d..4626622e 100644 --- a/src/main/java/com/gitblit/utils/X509Utils.java +++ b/src/main/java/com/gitblit/utils/X509Utils.java @@ -72,7 +72,7 @@ import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralNames; import org.bouncycastle.asn1.x509.KeyUsage; -import org.bouncycastle.asn1.x509.X509Extension; +import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.cert.X509CRLHolder; import org.bouncycastle.cert.X509v2CRLBuilder; import org.bouncycastle.cert.X509v3CertificateBuilder; @@ -82,7 +82,6 @@ import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.jce.PrincipalUtil; import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier; import org.bouncycastle.openssl.PEMEncryptor; -import org.bouncycastle.openssl.PEMWriter; import org.bouncycastle.openssl.jcajce.JcaPEMWriter; import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder; import org.bouncycastle.operator.ContentSigner; @@ -445,9 +444,9 @@ public class X509Utils { boolean asPem = targetFile.getName().toLowerCase().endsWith(".pem"); if (asPem) { // PEM encoded X509 - PEMWriter pemWriter = null; + JcaPEMWriter pemWriter = null; try { - pemWriter = new PEMWriter(new FileWriter(tmpFile)); + pemWriter = new JcaPEMWriter(new FileWriter(tmpFile)); pemWriter.writeObject(cert); pemWriter.flush(); } finally { @@ -560,9 +559,9 @@ public class X509Utils { pair.getPublic()); JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(); - certBuilder.addExtension(X509Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(pair.getPublic())); - certBuilder.addExtension(X509Extension.basicConstraints, false, new BasicConstraints(false)); - certBuilder.addExtension(X509Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(caCert.getPublicKey())); + certBuilder.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(pair.getPublic())); + certBuilder.addExtension(Extension.basicConstraints, false, new BasicConstraints(false)); + certBuilder.addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(caCert.getPublicKey())); // support alternateSubjectNames for SSL certificates List altNames = new ArrayList(); @@ -571,7 +570,7 @@ public class X509Utils { } if (altNames.size() > 0) { GeneralNames subjectAltName = new GeneralNames(altNames.toArray(new GeneralName [altNames.size()])); - certBuilder.addExtension(X509Extension.subjectAlternativeName, false, subjectAltName); + certBuilder.addExtension(Extension.subjectAlternativeName, false, subjectAltName); } ContentSigner caSigner = new JcaContentSignerBuilder(SIGNING_ALGORITHM) @@ -629,10 +628,10 @@ public class X509Utils { caPair.getPublic()); JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(); - caBuilder.addExtension(X509Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(caPair.getPublic())); - caBuilder.addExtension(X509Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(caPair.getPublic())); - caBuilder.addExtension(X509Extension.basicConstraints, false, new BasicConstraints(true)); - caBuilder.addExtension(X509Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyCertSign | KeyUsage.cRLSign)); + caBuilder.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(caPair.getPublic())); + caBuilder.addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(caPair.getPublic())); + caBuilder.addExtension(Extension.basicConstraints, false, new BasicConstraints(true)); + caBuilder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyCertSign | KeyUsage.cRLSign)); JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(BC); X509Certificate cert = converter.getCertificate(caBuilder.build(caSigner)); @@ -862,14 +861,14 @@ public class X509Utils { pair.getPublic()); JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(); - certBuilder.addExtension(X509Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(pair.getPublic())); - certBuilder.addExtension(X509Extension.basicConstraints, false, new BasicConstraints(false)); - certBuilder.addExtension(X509Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(caCert.getPublicKey())); - certBuilder.addExtension(X509Extension.keyUsage, true, new KeyUsage(KeyUsage.keyEncipherment | KeyUsage.digitalSignature)); + certBuilder.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(pair.getPublic())); + certBuilder.addExtension(Extension.basicConstraints, false, new BasicConstraints(false)); + certBuilder.addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(caCert.getPublicKey())); + certBuilder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyEncipherment | KeyUsage.digitalSignature)); if (!StringUtils.isEmpty(clientMetadata.emailAddress)) { GeneralNames subjectAltName = new GeneralNames( new GeneralName(GeneralName.rfc822Name, clientMetadata.emailAddress)); - certBuilder.addExtension(X509Extension.subjectAlternativeName, false, subjectAltName); + certBuilder.addExtension(Extension.subjectAlternativeName, false, subjectAltName); } ContentSigner signer = new JcaContentSignerBuilder(SIGNING_ALGORITHM).setProvider(BC).build(caPrivateKey); -- cgit v1.2.3 From 79e6561c1f6480ce57c265484534fdccfc5efa75 Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Sat, 22 Oct 2022 23:53:13 +0200 Subject: format: Cleanup formatting and whitespaces --- .../gitblit/transport/ssh/FileKeyPairProvider.java | 64 ++++++++++++++-------- 1 file changed, 41 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java b/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java index 38618baf..4ee0fbcd 100644 --- a/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java +++ b/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java @@ -27,57 +27,67 @@ import java.util.NoSuchElementException; import org.apache.sshd.common.keyprovider.AbstractKeyPairProvider; import org.apache.sshd.common.util.security.SecurityUtils; -import org.bouncycastle.openssl.PEMDecryptorProvider; -import org.bouncycastle.openssl.PEMEncryptedKeyPair; import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; -import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; /** * This host key provider loads private keys from the specified files. - * + *

* Note that this class has a direct dependency on BouncyCastle and won't work * unless it has been correctly registered as a security provider. * * @author Apache MINA SSHD Project */ -public class FileKeyPairProvider extends AbstractKeyPairProvider { +public class FileKeyPairProvider extends AbstractKeyPairProvider +{ private String[] files; - public FileKeyPairProvider() { + public FileKeyPairProvider() + { } - public FileKeyPairProvider(String[] files) { + public FileKeyPairProvider(String[] files) + { this.files = files; } - public String[] getFiles() { + public String[] getFiles() + { return files; } - public void setFiles(String[] files) { + public void setFiles(String[] files) + { this.files = files; } - public Iterable loadKeys() { + public Iterable loadKeys() + { if (!SecurityUtils.isBouncyCastleRegistered()) { throw new IllegalStateException("BouncyCastle must be registered as a JCE provider"); } - return new Iterable() { + return new Iterable() + { @Override - public Iterator iterator() { - return new Iterator() { + public Iterator iterator() + { + return new Iterator() + { private final Iterator iterator = Arrays.asList(files).iterator(); private KeyPair nextKeyPair; private boolean nextKeyPairSet = false; + @Override - public boolean hasNext() { + public boolean hasNext() + { return nextKeyPairSet || setNextObject(); } + @Override - public KeyPair next() { + public KeyPair next() + { if (!nextKeyPairSet) { if (!setNextObject()) { throw new NoSuchElementException(); @@ -86,11 +96,15 @@ public class FileKeyPairProvider extends AbstractKeyPairProvider { nextKeyPairSet = false; return nextKeyPair; } + @Override - public void remove() { + public void remove() + { throw new UnsupportedOperationException(); } - private boolean setNextObject() { + + private boolean setNextObject() + { while (iterator.hasNext()) { String file = iterator.next(); nextKeyPair = doLoadKey(file); @@ -107,7 +121,8 @@ public class FileKeyPairProvider extends AbstractKeyPairProvider { }; } - protected KeyPair doLoadKey(String file) { + protected KeyPair doLoadKey(String file) + { try { PEMParser r = new PEMParser(new InputStreamReader(new FileInputStream(file))); try { @@ -117,14 +132,17 @@ public class FileKeyPairProvider extends AbstractKeyPairProvider { pemConverter.setProvider("BC"); if (o instanceof PEMKeyPair) { o = pemConverter.getKeyPair((PEMKeyPair)o); - return (KeyPair) o; - } else if (o instanceof KeyPair) { - return (KeyPair) o; + return (KeyPair)o; + } + else if (o instanceof KeyPair) { + return (KeyPair)o; } - } finally { + } + finally { r.close(); } - } catch (Exception e) { + } + catch (Exception e) { log.warn("Unable to read key " + file, e); } return null; -- cgit v1.2.3 From 366a14f278095bb28956298bd8c3c64b247700cb Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Mon, 24 Oct 2022 21:10:13 +0200 Subject: Add SSH host keys with ECDSA and Ed25519 Create new host keys, one with ECDSA and one with Ed25519 algorithms. For the Ed25519 currently the EdDSA library from i2p is used. This requires some quirks, compared to a modern BouncyCastle. But the SSHD library used cannot use BouncyCastle yet for Ed25519. No DSA key is generated anymore, but we still support existing ones. --- .../gitblit/transport/ssh/FileKeyPairProvider.java | 70 ++++++++++- .../java/com/gitblit/transport/ssh/SshDaemon.java | 59 +++++++-- .../transport/ssh/FileKeyPairProviderTest.java | 134 +++++++++++++++++++++ 3 files changed, 249 insertions(+), 14 deletions(-) create mode 100644 src/test/java/com/gitblit/transport/ssh/FileKeyPairProviderTest.java (limited to 'src') diff --git a/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java b/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java index 4ee0fbcd..aaa606ce 100644 --- a/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java +++ b/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java @@ -20,16 +20,29 @@ package com.gitblit.transport.ssh; 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. @@ -63,6 +76,8 @@ public class FileKeyPairProvider extends AbstractKeyPairProvider this.files = files; } + + @Override public Iterable loadKeys() { if (!SecurityUtils.isBouncyCastleRegistered()) { @@ -121,11 +136,53 @@ public class FileKeyPairProvider extends AbstractKeyPairProvider }; } - 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 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(); @@ -137,10 +194,11 @@ public class FileKeyPairProvider extends AbstractKeyPairProvider 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); diff --git a/src/main/java/com/gitblit/transport/ssh/SshDaemon.java b/src/main/java/com/gitblit/transport/ssh/SshDaemon.java index 7a31bc18..d4f1fab0 100644 --- a/src/main/java/com/gitblit/transport/ssh/SshDaemon.java +++ b/src/main/java/com/gitblit/transport/ssh/SshDaemon.java @@ -15,6 +15,7 @@ */ package com.gitblit.transport.ssh; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -22,19 +23,28 @@ import java.io.OutputStreamWriter; 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; @@ -56,7 +66,7 @@ import com.google.common.io.Files; */ 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"; @@ -107,10 +117,14 @@ public class SshDaemon { // 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 @@ -244,7 +258,7 @@ public class SshDaemon { } } - private void generateKeyPair(File file, String algorithm, int keySize) { + static void generateKeyPair(File file, String algorithm, int keySize) { if (file.exists()) { return; } @@ -267,13 +281,42 @@ public class SshDaemon { } 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; } } } diff --git a/src/test/java/com/gitblit/transport/ssh/FileKeyPairProviderTest.java b/src/test/java/com/gitblit/transport/ssh/FileKeyPairProviderTest.java new file mode 100644 index 00000000..d36adc7f --- /dev/null +++ b/src/test/java/com/gitblit/transport/ssh/FileKeyPairProviderTest.java @@ -0,0 +1,134 @@ +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 keyPairs = hostKeyPairProvider.loadKeys(); + Iterator 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 keyPairs = hostKeyPairProvider.loadKeys(); + Iterator 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 keyPairs = hostKeyPairProvider.loadKeys(); + Iterator 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 keyPairs = hostKeyPairProvider.loadKeys(); + Iterator 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 keyPairs = hostKeyPairProvider.loadKeys(); + Iterator 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()); + } +} -- cgit v1.2.3 From 27b51f69b7c336c082f80896ee580bd836350960 Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Tue, 25 Oct 2022 00:00:03 +0200 Subject: Skip SSH host key files that do not exist Since we now do not generate a DSA host key file anymore, but keep it in the list of potential keys so that existing keys still work, it can happen that the files for DSA (and Ed25519) are getting loaded but they do not exist. This results in an error in the log. So instead check if the file exists and only try to load files that exist. This prevents from errors (which are none) being spammed in the log. --- src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') diff --git a/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java b/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java index aaa606ce..0e97f557 100644 --- a/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java +++ b/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java @@ -18,6 +18,7 @@ */ package com.gitblit.transport.ssh; +import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; import java.security.KeyFactory; @@ -122,6 +123,11 @@ public class FileKeyPairProvider extends AbstractKeyPairProvider { while (iterator.hasNext()) { String file = iterator.next(); + File f = new File(file); + if (!f.isFile()) { + log.debug("File does not exist, skipping {}", file); + continue; + } nextKeyPair = doLoadKey(file); if (nextKeyPair != null) { nextKeyPairSet = true; -- cgit v1.2.3