View Javadoc
1   /*
2    * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  package org.eclipse.jgit.internal.transport.sshd;
44  
45  import static java.text.MessageFormat.format;
46  
47  import java.io.IOException;
48  import java.io.InputStream;
49  import java.nio.file.Path;
50  import java.security.GeneralSecurityException;
51  import java.security.InvalidKeyException;
52  import java.security.KeyPair;
53  import java.security.NoSuchProviderException;
54  import java.security.PrivateKey;
55  import java.util.Collection;
56  import java.util.Iterator;
57  import java.util.List;
58  
59  import javax.security.auth.DestroyFailedException;
60  
61  import org.apache.sshd.common.config.keys.FilePasswordProvider;
62  import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser;
63  import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
64  import org.apache.sshd.common.util.io.IoUtils;
65  import org.apache.sshd.common.util.security.SecurityUtils;
66  import org.eclipse.jgit.internal.transport.sshd.RepeatingFilePasswordProvider.ResourceDecodeResult;
67  
68  /**
69   * A {@link FileKeyPairProvider} that asks repeatedly for a passphrase for an
70   * encrypted private key if the {@link FilePasswordProvider} is a
71   * {@link RepeatingFilePasswordProvider}.
72   */
73  public abstract class EncryptedFileKeyPairProvider extends FileKeyPairProvider {
74  
75  	// TODO: remove this class once we're based on sshd > 2.1.0. See upstream
76  	// issue SSHD-850 https://issues.apache.org/jira/browse/SSHD-850 and commit
77  	// https://github.com/apache/mina-sshd/commit/f19bd2e34
78  
79  	/**
80  	 * Creates a new {@link EncryptedFileKeyPairProvider} for the given
81  	 * {@link Path}s.
82  	 *
83  	 * @param paths
84  	 *            to read keys from
85  	 */
86  	public EncryptedFileKeyPairProvider(List<Path> paths) {
87  		super(paths);
88  	}
89  
90  	@Override
91  	protected KeyPair doLoadKey(String resourceKey, InputStream inputStream,
92  			FilePasswordProvider provider)
93  			throws IOException, GeneralSecurityException {
94  		if (!(provider instanceof RepeatingFilePasswordProvider)) {
95  			return super.doLoadKey(resourceKey, inputStream, provider);
96  		}
97  		KeyPairResourceParser parser = SecurityUtils.getKeyPairResourceParser();
98  		if (parser == null) {
99  			// This is an internal configuration error, thus no translation.
100 			throw new NoSuchProviderException(
101 					"No registered key-pair resource parser"); //$NON-NLS-1$
102 		}
103 		RepeatingFilePasswordProvider realProvider = (RepeatingFilePasswordProvider) provider;
104 		// Read the stream now so that we can process the content several
105 		// times.
106 		List<String> lines = IoUtils.readAllLines(inputStream);
107 		Collection<KeyPair> ids = null;
108 		while (ids == null) {
109 			try {
110 				ids = parser.loadKeyPairs(resourceKey, realProvider, lines);
111 				realProvider.handleDecodeAttemptResult(resourceKey, "", null); //$NON-NLS-1$
112 				// No exception; success. Exit the loop even if ids is still
113 				// null!
114 				break;
115 			} catch (IOException | GeneralSecurityException
116 					| RuntimeException e) {
117 				ResourceDecodeResult loadResult = realProvider
118 						.handleDecodeAttemptResult(resourceKey, "", e); //$NON-NLS-1$
119 				if (loadResult == null
120 						|| loadResult == ResourceDecodeResult.TERMINATE) {
121 					throw e;
122 				} else if (loadResult == ResourceDecodeResult.RETRY) {
123 					continue;
124 				}
125 				// IGNORE doesn't make any sense here, but OK, let's ignore it.
126 				// ids == null, so we'll throw an exception below.
127 				break;
128 			}
129 		}
130 		if (ids == null) {
131 			// The javadoc on loadKeyPairs says it might return null if no
132 			// key pair found. Bad API.
133 			throw new InvalidKeyException(
134 					format(SshdText.get().identityFileNoKey, resourceKey));
135 		}
136 		Iterator<KeyPair> keys = ids.iterator();
137 		if (!keys.hasNext()) {
138 			throw new InvalidKeyException(format(
139 					SshdText.get().identityFileUnsupportedFormat, resourceKey));
140 		}
141 		KeyPair result = keys.next();
142 		if (keys.hasNext()) {
143 			log.warn(format(SshdText.get().identityFileMultipleKeys,
144 					resourceKey));
145 			keys.forEachRemaining(k -> {
146 				PrivateKey pk = k.getPrivate();
147 				if (pk != null) {
148 					try {
149 						pk.destroy();
150 					} catch (DestroyFailedException e) {
151 						// Ignore
152 					}
153 				}
154 			});
155 		}
156 		return result;
157 	}
158 }