summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFlorian Zschocke <2362065+flaix@users.noreply.github.com>2022-10-25 18:22:52 +0200
committerGitHub <noreply@github.com>2022-10-25 18:22:52 +0200
commit7a2c589d54f9bf4e810e123e629f1c1f32ee6d00 (patch)
tree078a76cbdab5cb4977021c8746d9a40f164ecbe6
parent32b1e66805f4e924f5fb72de61f99941967ab125 (diff)
parent27b51f69b7c336c082f80896ee580bd836350960 (diff)
downloadgitblit-7a2c589d54f9bf4e810e123e629f1c1f32ee6d00.tar.gz
gitblit-7a2c589d54f9bf4e810e123e629f1c1f32ee6d00.zip
Merge pull request #1429 from flaix/ssh-host-algs
Add new SSH host key types
-rw-r--r--.classpath7
-rw-r--r--build.moxie2
-rw-r--r--gitblit.iml29
-rw-r--r--src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java153
-rw-r--r--src/main/java/com/gitblit/transport/ssh/SshDaemon.java59
-rw-r--r--src/main/java/com/gitblit/utils/X509Utils.java33
-rw-r--r--src/test/java/com/gitblit/transport/ssh/FileKeyPairProviderTest.java134
7 files changed, 333 insertions, 84 deletions
diff --git a/.classpath b/.classpath
index 7c32205b..394584d3 100644
--- a/.classpath
+++ b/.classpath
@@ -51,9 +51,10 @@
<classpathentry kind="lib" path="ext/commons-logging-1.1.3.jar" sourcepath="ext/src/commons-logging-1.1.3.jar" />
<classpathentry kind="lib" path="ext/commons-codec-1.7.jar" sourcepath="ext/src/commons-codec-1.7.jar" />
<classpathentry kind="lib" path="ext/org.eclipse.jgit.http.server-4.5.7.201904151645-r.jar" sourcepath="ext/src/org.eclipse.jgit.http.server-4.5.7.201904151645-r.jar" />
- <classpathentry kind="lib" path="ext/bcprov-jdk15on-1.57.jar" sourcepath="ext/src/bcprov-jdk15on-1.57.jar" />
- <classpathentry kind="lib" path="ext/bcmail-jdk15on-1.57.jar" sourcepath="ext/src/bcmail-jdk15on-1.57.jar" />
- <classpathentry kind="lib" path="ext/bcpkix-jdk15on-1.57.jar" sourcepath="ext/src/bcpkix-jdk15on-1.57.jar" />
+ <classpathentry kind="lib" path="ext/bcprov-jdk15on-1.69.jar" sourcepath="ext/src/bcprov-jdk15on-1.69.jar" />
+ <classpathentry kind="lib" path="ext/bcmail-jdk15on-1.69.jar" sourcepath="ext/src/bcmail-jdk15on-1.69.jar" />
+ <classpathentry kind="lib" path="ext/bcutil-jdk15on-1.69.jar" sourcepath="ext/src/bcutil-jdk15on-1.69.jar" />
+ <classpathentry kind="lib" path="ext/bcpkix-jdk15on-1.69.jar" sourcepath="ext/src/bcpkix-jdk15on-1.69.jar" />
<classpathentry kind="lib" path="ext/eddsa-0.2.0.jar" sourcepath="ext/src/eddsa-0.2.0.jar" />
<classpathentry kind="lib" path="ext/sshd-core-1.7.0.jar" sourcepath="ext/src/sshd-core-1.7.0.jar" />
<classpathentry kind="lib" path="ext/mina-core-2.0.21.jar" sourcepath="ext/src/mina-core-2.0.21.jar" />
diff --git a/build.moxie b/build.moxie
index 026ab5bb..d78733bf 100644
--- a/build.moxie
+++ b/build.moxie
@@ -111,7 +111,7 @@ properties: {
lucene.version : 5.5.2
jgit.version : 4.5.7.201904151645-r
groovy.version : 2.4.4
- bouncycastle.version : 1.57
+ bouncycastle.version : 1.69
selenium.version : 2.28.0
wikitext.version : 1.4
sshd.version: 1.7.0
diff --git a/gitblit.iml b/gitblit.iml
index 694cd94f..e2ed5b0f 100644
--- a/gitblit.iml
+++ b/gitblit.iml
@@ -508,35 +508,46 @@
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="bcprov-jdk15on-1.57.jar">
+ <library name="bcprov-jdk15on-1.69.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/bcprov-jdk15on-1.57.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/bcprov-jdk15on-1.69.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/bcprov-jdk15on-1.57.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/bcprov-jdk15on-1.69.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="bcmail-jdk15on-1.57.jar">
+ <library name="bcmail-jdk15on-1.69.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/bcmail-jdk15on-1.57.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/bcmail-jdk15on-1.69.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/bcmail-jdk15on-1.57.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/bcmail-jdk15on-1.69.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
- <library name="bcpkix-jdk15on-1.57.jar">
+ <library name="bcutil-jdk15on-1.69.jar">
<CLASSES>
- <root url="jar://$MODULE_DIR$/ext/bcpkix-jdk15on-1.57.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/bcutil-jdk15on-1.69.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/ext/src/bcpkix-jdk15on-1.57.jar!/" />
+ <root url="jar://$MODULE_DIR$/ext/src/bcutil-jdk15on-1.69.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library">
+ <library name="bcpkix-jdk15on-1.69.jar">
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/ext/bcpkix-jdk15on-1.69.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/ext/src/bcpkix-jdk15on-1.69.jar!/" />
</SOURCES>
</library>
</orderEntry>
diff --git a/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java b/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java
index cc91bb8c..0e97f557 100644
--- a/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java
+++ b/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java
@@ -18,81 +18,92 @@
*/
package com.gitblit.transport.ssh;
+import java.io.File;
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.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;
+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.
- *
+ * <p>
* 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 <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
-public class FileKeyPairProvider extends AbstractKeyPairProvider {
+public class FileKeyPairProvider extends AbstractKeyPairProvider
+{
private String[] files;
- private PasswordFinder passwordFinder;
-
- public FileKeyPairProvider() {
- }
- public FileKeyPairProvider(String[] files) {
- this.files = files;
+ public FileKeyPairProvider()
+ {
}
- public FileKeyPairProvider(String[] files, PasswordFinder passwordFinder) {
+ public FileKeyPairProvider(String[] files)
+ {
this.files = files;
- this.passwordFinder = passwordFinder;
}
- public String[] getFiles() {
+ public String[] getFiles()
+ {
return files;
}
- public void setFiles(String[] files) {
+ public void setFiles(String[] files)
+ {
this.files = files;
}
- public PasswordFinder getPasswordFinder() {
- return passwordFinder;
- }
-
- public void setPasswordFinder(PasswordFinder passwordFinder) {
- this.passwordFinder = passwordFinder;
- }
- public Iterable<KeyPair> loadKeys() {
+ @Override
+ public Iterable<KeyPair> loadKeys()
+ {
if (!SecurityUtils.isBouncyCastleRegistered()) {
throw new IllegalStateException("BouncyCastle must be registered as a JCE provider");
}
- return new Iterable<KeyPair>() {
+ return new Iterable<KeyPair>()
+ {
@Override
- public Iterator<KeyPair> iterator() {
- return new Iterator<KeyPair>() {
+ public Iterator<KeyPair> iterator()
+ {
+ return new Iterator<KeyPair>()
+ {
private final Iterator<String> 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();
@@ -101,13 +112,22 @@ 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();
+ 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;
@@ -122,30 +142,71 @@ 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<? 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();
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;
- } else if (o instanceof KeyPair) {
- return (KeyPair) o;
+ return (KeyPair)o;
+ }
+ 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) {
+
+ }
+ catch (Exception e) {
log.warn("Unable to read key " + file, e);
}
return null;
diff --git a/src/main/java/com/gitblit/transport/ssh/SshDaemon.java b/src/main/java/com/gitblit/transport/ssh/SshDaemon.java
index 8bb880b0..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.PEMWriter;
+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);
- PEMWriter w = new PEMWriter(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/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<GeneralName> altNames = new ArrayList<GeneralName>();
@@ -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);
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<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());
+ }
+}