| /* |
| * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> |
| * and other copyright owners as documented in the project's IP log. |
| * |
| * This program and the accompanying materials are made available |
| * under the terms of the Eclipse Distribution License v1.0 which |
| * accompanies this distribution, is reproduced below, and is |
| * available at http://www.eclipse.org/org/documents/edl-v10.php |
| * |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or |
| * without modification, are permitted provided that the following |
| * conditions are met: |
| * |
| * - Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials provided |
| * with the distribution. |
| * |
| * - Neither the name of the Eclipse Foundation, Inc. nor the |
| * names of its contributors may be used to endorse or promote |
| * products derived from this software without specific prior |
| * written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
| * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
| * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
| * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| package org.eclipse.jgit.internal.transport.sshd; |
| |
| import static java.text.MessageFormat.format; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.nio.file.Path; |
| import java.security.GeneralSecurityException; |
| import java.security.InvalidKeyException; |
| import java.security.KeyPair; |
| import java.security.NoSuchProviderException; |
| import java.security.PrivateKey; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import javax.security.auth.DestroyFailedException; |
| |
| import org.apache.sshd.common.config.keys.FilePasswordProvider; |
| import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser; |
| import org.apache.sshd.common.keyprovider.FileKeyPairProvider; |
| import org.apache.sshd.common.util.io.IoUtils; |
| import org.apache.sshd.common.util.security.SecurityUtils; |
| import org.eclipse.jgit.internal.transport.sshd.RepeatingFilePasswordProvider.ResourceDecodeResult; |
| |
| /** |
| * A {@link FileKeyPairProvider} that asks repeatedly for a passphrase for an |
| * encrypted private key if the {@link FilePasswordProvider} is a |
| * {@link RepeatingFilePasswordProvider}. |
| */ |
| public abstract class EncryptedFileKeyPairProvider extends FileKeyPairProvider { |
| |
| // TODO: remove this class once we're based on sshd > 2.1.0. See upstream |
| // issue SSHD-850 https://issues.apache.org/jira/browse/SSHD-850 and commit |
| // https://github.com/apache/mina-sshd/commit/f19bd2e34 |
| |
| /** |
| * Creates a new {@link EncryptedFileKeyPairProvider} for the given |
| * {@link Path}s. |
| * |
| * @param paths |
| * to read keys from |
| */ |
| public EncryptedFileKeyPairProvider(List<Path> paths) { |
| super(paths); |
| } |
| |
| @Override |
| protected KeyPair doLoadKey(String resourceKey, InputStream inputStream, |
| FilePasswordProvider provider) |
| throws IOException, GeneralSecurityException { |
| if (!(provider instanceof RepeatingFilePasswordProvider)) { |
| return super.doLoadKey(resourceKey, inputStream, provider); |
| } |
| KeyPairResourceParser parser = SecurityUtils.getKeyPairResourceParser(); |
| if (parser == null) { |
| // This is an internal configuration error, thus no translation. |
| throw new NoSuchProviderException( |
| "No registered key-pair resource parser"); //$NON-NLS-1$ |
| } |
| RepeatingFilePasswordProvider realProvider = (RepeatingFilePasswordProvider) provider; |
| // Read the stream now so that we can process the content several |
| // times. |
| List<String> lines = IoUtils.readAllLines(inputStream); |
| Collection<KeyPair> ids = null; |
| while (ids == null) { |
| try { |
| ids = parser.loadKeyPairs(resourceKey, realProvider, lines); |
| realProvider.handleDecodeAttemptResult(resourceKey, "", null); //$NON-NLS-1$ |
| // No exception; success. Exit the loop even if ids is still |
| // null! |
| break; |
| } catch (IOException | GeneralSecurityException |
| | RuntimeException e) { |
| ResourceDecodeResult loadResult = realProvider |
| .handleDecodeAttemptResult(resourceKey, "", e); //$NON-NLS-1$ |
| if (loadResult == null |
| || loadResult == ResourceDecodeResult.TERMINATE) { |
| throw e; |
| } else if (loadResult == ResourceDecodeResult.RETRY) { |
| continue; |
| } |
| // IGNORE doesn't make any sense here, but OK, let's ignore it. |
| // ids == null, so we'll throw an exception below. |
| break; |
| } |
| } |
| if (ids == null) { |
| // The javadoc on loadKeyPairs says it might return null if no |
| // key pair found. Bad API. |
| throw new InvalidKeyException( |
| format(SshdText.get().identityFileNoKey, resourceKey)); |
| } |
| Iterator<KeyPair> keys = ids.iterator(); |
| if (!keys.hasNext()) { |
| throw new InvalidKeyException(format( |
| SshdText.get().identityFileUnsupportedFormat, resourceKey)); |
| } |
| KeyPair result = keys.next(); |
| if (keys.hasNext()) { |
| log.warn(format(SshdText.get().identityFileMultipleKeys, |
| resourceKey)); |
| keys.forEachRemaining(k -> { |
| PrivateKey pk = k.getPrivate(); |
| if (pk != null) { |
| try { |
| pk.destroy(); |
| } catch (DestroyFailedException e) { |
| // Ignore |
| } |
| } |
| }); |
| } |
| return result; |
| } |
| } |