Add new SSH host key typespull/1433/head
@@ -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" /> |
@@ -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 |
@@ -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> |
@@ -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; |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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); |
@@ -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()); | |||
} | |||
} |