/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ 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.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. *

* 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 { private String[] files; public FileKeyPairProvider() { } public FileKeyPairProvider(String[] files) { this.files = files; } public String[] getFiles() { return files; } public void setFiles(String[] files) { this.files = files; } @Override public Iterable loadKeys() { if (!SecurityUtils.isBouncyCastleRegistered()) { throw new IllegalStateException("BouncyCastle must be registered as a JCE provider"); } return new Iterable() { @Override 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() { return nextKeyPairSet || setNextObject(); } @Override public KeyPair next() { if (!nextKeyPairSet) { if (!setNextObject()) { throw new NoSuchElementException(); } } nextKeyPairSet = false; return nextKeyPair; } @Override public void remove() { throw new UnsupportedOperationException(); } 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; return true; } } return false; } }; } }; } private KeyPair doLoadKey(String 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(); pemConverter.setProvider("BC"); if (o instanceof PEMKeyPair) { o = pemConverter.getKeyPair((PEMKeyPair)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()); } } } catch (Exception e) { log.warn("Unable to read key " + file, e); } return null; } }