diff options
author | Florian Zschocke <2362065+flaix@users.noreply.github.com> | 2022-10-25 18:22:52 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-25 18:22:52 +0200 |
commit | 7a2c589d54f9bf4e810e123e629f1c1f32ee6d00 (patch) | |
tree | 078a76cbdab5cb4977021c8746d9a40f164ecbe6 | |
parent | 32b1e66805f4e924f5fb72de61f99941967ab125 (diff) | |
parent | 27b51f69b7c336c082f80896ee580bd836350960 (diff) | |
download | gitblit-7a2c589d54f9bf4e810e123e629f1c1f32ee6d00.tar.gz gitblit-7a2c589d54f9bf4e810e123e629f1c1f32ee6d00.zip |
Merge pull request #1429 from flaix/ssh-host-algs
Add new SSH host key types
-rw-r--r-- | .classpath | 7 | ||||
-rw-r--r-- | build.moxie | 2 | ||||
-rw-r--r-- | gitblit.iml | 29 | ||||
-rw-r--r-- | src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java | 153 | ||||
-rw-r--r-- | src/main/java/com/gitblit/transport/ssh/SshDaemon.java | 59 | ||||
-rw-r--r-- | src/main/java/com/gitblit/utils/X509Utils.java | 33 | ||||
-rw-r--r-- | src/test/java/com/gitblit/transport/ssh/FileKeyPairProviderTest.java | 134 |
7 files changed, 333 insertions, 84 deletions
@@ -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()); + } +} |