CachingKeyPairProvider.java
- /*
- * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Distribution License v. 1.0 which is available at
- * https://www.eclipse.org/org/documents/edl-v10.php.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
- 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.Files;
- import java.nio.file.Path;
- import java.security.GeneralSecurityException;
- import java.security.InvalidKeyException;
- import java.security.KeyPair;
- import java.security.PrivateKey;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.Iterator;
- import java.util.List;
- import java.util.NoSuchElementException;
- import java.util.concurrent.CancellationException;
- import javax.security.auth.DestroyFailedException;
- import org.apache.sshd.common.NamedResource;
- import org.apache.sshd.common.config.keys.FilePasswordProvider;
- import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
- import org.apache.sshd.common.session.SessionContext;
- import org.apache.sshd.common.util.io.resource.IoResource;
- import org.apache.sshd.common.util.security.SecurityUtils;
- import org.eclipse.jgit.transport.sshd.KeyCache;
- /**
- * A {@link FileKeyPairProvider} that uses an external {@link KeyCache}.
- */
- public class CachingKeyPairProvider extends FileKeyPairProvider
- implements Iterable<KeyPair> {
- private final KeyCache cache;
- /**
- * Creates a new {@link CachingKeyPairProvider} using the given
- * {@link KeyCache}. If the cache is {@code null}, this is a simple
- * {@link FileKeyPairProvider}.
- *
- * @param paths
- * to load keys from
- * @param cache
- * to use, may be {@code null} if no external caching is desired
- */
- public CachingKeyPairProvider(List<Path> paths, KeyCache cache) {
- super(paths);
- this.cache = cache;
- }
- @Override
- public Iterator<KeyPair> iterator() {
- return iterator(null);
- }
- private Iterator<KeyPair> iterator(SessionContext session) {
- Collection<? extends Path> resources = getPaths();
- if (resources.isEmpty()) {
- return Collections.emptyListIterator();
- }
- return new CancellingKeyPairIterator(session, resources);
- }
- @Override
- public Iterable<KeyPair> loadKeys(SessionContext session) {
- return () -> iterator(session);
- }
- private KeyPair loadKey(SessionContext session, Path path)
- throws IOException, GeneralSecurityException {
- if (!Files.exists(path)) {
- log.warn(format(SshdText.get().identityFileNotFound, path));
- return null;
- }
- IoResource<Path> resource = getIoResource(session, path);
- if (cache == null) {
- return loadKey(session, resource, path, getPasswordFinder());
- }
- Throwable[] t = { null };
- KeyPair key = cache.get(path, p -> {
- try {
- return loadKey(session, resource, p, getPasswordFinder());
- } catch (IOException | GeneralSecurityException e) {
- t[0] = e;
- return null;
- }
- });
- if (t[0] != null) {
- if (t[0] instanceof CancellationException) {
- throw (CancellationException) t[0];
- }
- throw new IOException(
- format(SshdText.get().keyLoadFailed, resource), t[0]);
- }
- return key;
- }
- private KeyPair loadKey(SessionContext session, NamedResource resource,
- Path path, FilePasswordProvider passwordProvider)
- throws IOException, GeneralSecurityException {
- try (InputStream stream = Files.newInputStream(path)) {
- Iterable<KeyPair> ids = SecurityUtils.loadKeyPairIdentities(session,
- resource, stream, passwordProvider);
- if (ids == null) {
- throw new InvalidKeyException(
- format(SshdText.get().identityFileNoKey, path));
- }
- Iterator<KeyPair> keys = ids.iterator();
- if (!keys.hasNext()) {
- throw new InvalidKeyException(format(
- SshdText.get().identityFileUnsupportedFormat, path));
- }
- KeyPair result = keys.next();
- if (keys.hasNext()) {
- log.warn(format(SshdText.get().identityFileMultipleKeys, path));
- keys.forEachRemaining(k -> {
- PrivateKey pk = k.getPrivate();
- if (pk != null) {
- try {
- pk.destroy();
- } catch (DestroyFailedException e) {
- // Ignore
- }
- }
- });
- }
- return result;
- }
- }
- private class CancellingKeyPairIterator implements Iterator<KeyPair> {
- private final SessionContext context;
- private final Iterator<Path> paths;
- private KeyPair nextItem;
- private boolean nextSet;
- public CancellingKeyPairIterator(SessionContext session,
- Collection<? extends Path> resources) {
- List<Path> copy = new ArrayList<>(resources.size());
- copy.addAll(resources);
- paths = copy.iterator();
- context = session;
- }
- @Override
- public boolean hasNext() {
- if (nextSet) {
- return nextItem != null;
- }
- nextSet = true;
- while (nextItem == null && paths.hasNext()) {
- try {
- nextItem = loadKey(context, paths.next());
- } catch (CancellationException cancelled) {
- throw cancelled;
- } catch (Exception other) {
- log.warn(other.getMessage(), other);
- }
- }
- return nextItem != null;
- }
- @Override
- public KeyPair next() {
- if (!nextSet && !hasNext()) {
- throw new NoSuchElementException();
- }
- KeyPair result = nextItem;
- nextItem = null;
- nextSet = false;
- if (result == null) {
- throw new NoSuchElementException();
- }
- return result;
- }
- }
- }