1
2
3
4
5
6
7
8
9
10 package org.eclipse.jgit.internal.transport.sshd;
11
12 import static java.text.MessageFormat.format;
13
14 import java.io.IOException;
15 import java.io.InputStream;
16 import java.nio.file.Files;
17 import java.nio.file.Path;
18 import java.security.GeneralSecurityException;
19 import java.security.InvalidKeyException;
20 import java.security.KeyPair;
21 import java.security.PrivateKey;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.NoSuchElementException;
28 import java.util.concurrent.CancellationException;
29
30 import javax.security.auth.DestroyFailedException;
31
32 import org.apache.sshd.common.NamedResource;
33 import org.apache.sshd.common.config.keys.FilePasswordProvider;
34 import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
35 import org.apache.sshd.common.session.SessionContext;
36 import org.apache.sshd.common.util.io.resource.IoResource;
37 import org.apache.sshd.common.util.security.SecurityUtils;
38 import org.eclipse.jgit.transport.sshd.KeyCache;
39
40
41
42
43 public class CachingKeyPairProvider extends FileKeyPairProvider
44 implements Iterable<KeyPair> {
45
46 private final KeyCache cache;
47
48
49
50
51
52
53
54
55
56
57
58 public CachingKeyPairProvider(List<Path> paths, KeyCache cache) {
59 super(paths);
60 this.cache = cache;
61 }
62
63 @Override
64 public Iterator<KeyPair> iterator() {
65 return iterator(null);
66 }
67
68 private Iterator<KeyPair> iterator(SessionContext session) {
69 Collection<? extends Path> resources = getPaths();
70 if (resources.isEmpty()) {
71 return Collections.emptyListIterator();
72 }
73 return new CancellingKeyPairIterator(session, resources);
74 }
75
76 @Override
77 public Iterable<KeyPair> loadKeys(SessionContext session) {
78 return () -> iterator(session);
79 }
80
81 private KeyPair loadKey(SessionContext session, Path path)
82 throws IOException, GeneralSecurityException {
83 if (!Files.exists(path)) {
84 log.warn(format(SshdText.get().identityFileNotFound, path));
85 return null;
86 }
87 IoResource<Path> resource = getIoResource(session, path);
88 if (cache == null) {
89 return loadKey(session, resource, path, getPasswordFinder());
90 }
91 Throwable[] t = { null };
92 KeyPair key = cache.get(path, p -> {
93 try {
94 return loadKey(session, resource, p, getPasswordFinder());
95 } catch (IOException | GeneralSecurityException e) {
96 t[0] = e;
97 return null;
98 }
99 });
100 if (t[0] != null) {
101 if (t[0] instanceof CancellationException) {
102 throw (CancellationException) t[0];
103 }
104 throw new IOException(
105 format(SshdText.get().keyLoadFailed, resource), t[0]);
106 }
107 return key;
108 }
109
110 private KeyPair loadKey(SessionContext session, NamedResource resource,
111 Path path, FilePasswordProvider passwordProvider)
112 throws IOException, GeneralSecurityException {
113 try (InputStream stream = Files.newInputStream(path)) {
114 Iterable<KeyPair> ids = SecurityUtils.loadKeyPairIdentities(session,
115 resource, stream, passwordProvider);
116 if (ids == null) {
117 throw new InvalidKeyException(
118 format(SshdText.get().identityFileNoKey, path));
119 }
120 Iterator<KeyPair> keys = ids.iterator();
121 if (!keys.hasNext()) {
122 throw new InvalidKeyException(format(
123 SshdText.get().identityFileUnsupportedFormat, path));
124 }
125 KeyPair result = keys.next();
126 if (keys.hasNext()) {
127 log.warn(format(SshdText.get().identityFileMultipleKeys, path));
128 keys.forEachRemaining(k -> {
129 PrivateKey pk = k.getPrivate();
130 if (pk != null) {
131 try {
132 pk.destroy();
133 } catch (DestroyFailedException e) {
134
135 }
136 }
137 });
138 }
139 return result;
140 }
141 }
142
143 private class CancellingKeyPairIterator implements Iterator<KeyPair> {
144
145 private final SessionContext context;
146
147 private final Iterator<Path> paths;
148
149 private KeyPair nextItem;
150
151 private boolean nextSet;
152
153 public CancellingKeyPairIterator(SessionContext session,
154 Collection<? extends Path> resources) {
155 List<Path> copy = new ArrayList<>(resources.size());
156 copy.addAll(resources);
157 paths = copy.iterator();
158 context = session;
159 }
160
161 @Override
162 public boolean hasNext() {
163 if (nextSet) {
164 return nextItem != null;
165 }
166 nextSet = true;
167 while (nextItem == null && paths.hasNext()) {
168 try {
169 nextItem = loadKey(context, paths.next());
170 } catch (CancellationException cancelled) {
171 throw cancelled;
172 } catch (Exception other) {
173 log.warn(other.getMessage(), other);
174 }
175 }
176 return nextItem != null;
177 }
178
179 @Override
180 public KeyPair next() {
181 if (!nextSet && !hasNext()) {
182 throw new NoSuchElementException();
183 }
184 KeyPair result = nextItem;
185 nextItem = null;
186 nextSet = false;
187 if (result == null) {
188 throw new NoSuchElementException();
189 }
190 return result;
191 }
192
193 }
194 }