CachingKeyPairProvider.java

  1. /*
  2.  * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */
  10. package org.eclipse.jgit.internal.transport.sshd;

  11. import static java.text.MessageFormat.format;

  12. import java.io.IOException;
  13. import java.io.InputStream;
  14. import java.nio.file.Files;
  15. import java.nio.file.Path;
  16. import java.security.GeneralSecurityException;
  17. import java.security.InvalidKeyException;
  18. import java.security.KeyPair;
  19. import java.security.PrivateKey;
  20. import java.util.ArrayList;
  21. import java.util.Collection;
  22. import java.util.Collections;
  23. import java.util.Iterator;
  24. import java.util.List;
  25. import java.util.NoSuchElementException;
  26. import java.util.concurrent.CancellationException;

  27. import javax.security.auth.DestroyFailedException;

  28. import org.apache.sshd.common.NamedResource;
  29. import org.apache.sshd.common.config.keys.FilePasswordProvider;
  30. import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
  31. import org.apache.sshd.common.session.SessionContext;
  32. import org.apache.sshd.common.util.io.resource.IoResource;
  33. import org.apache.sshd.common.util.security.SecurityUtils;
  34. import org.eclipse.jgit.transport.sshd.KeyCache;

  35. /**
  36.  * A {@link FileKeyPairProvider} that uses an external {@link KeyCache}.
  37.  */
  38. public class CachingKeyPairProvider extends FileKeyPairProvider
  39.         implements Iterable<KeyPair> {

  40.     private final KeyCache cache;

  41.     /**
  42.      * Creates a new {@link CachingKeyPairProvider} using the given
  43.      * {@link KeyCache}. If the cache is {@code null}, this is a simple
  44.      * {@link FileKeyPairProvider}.
  45.      *
  46.      * @param paths
  47.      *            to load keys from
  48.      * @param cache
  49.      *            to use, may be {@code null} if no external caching is desired
  50.      */
  51.     public CachingKeyPairProvider(List<Path> paths, KeyCache cache) {
  52.         super(paths);
  53.         this.cache = cache;
  54.     }

  55.     @Override
  56.     public Iterator<KeyPair> iterator() {
  57.         return iterator(null);
  58.     }

  59.     private Iterator<KeyPair> iterator(SessionContext session) {
  60.         Collection<? extends Path> resources = getPaths();
  61.         if (resources.isEmpty()) {
  62.             return Collections.emptyListIterator();
  63.         }
  64.         return new CancellingKeyPairIterator(session, resources);
  65.     }

  66.     @Override
  67.     public Iterable<KeyPair> loadKeys(SessionContext session) {
  68.         return () -> iterator(session);
  69.     }

  70.     private KeyPair loadKey(SessionContext session, Path path)
  71.             throws IOException, GeneralSecurityException {
  72.         if (!Files.exists(path)) {
  73.             log.warn(format(SshdText.get().identityFileNotFound, path));
  74.             return null;
  75.         }
  76.         IoResource<Path> resource = getIoResource(session, path);
  77.         if (cache == null) {
  78.             return loadKey(session, resource, path, getPasswordFinder());
  79.         }
  80.         Throwable[] t = { null };
  81.         KeyPair key = cache.get(path, p -> {
  82.             try {
  83.                 return loadKey(session, resource, p, getPasswordFinder());
  84.             } catch (IOException | GeneralSecurityException e) {
  85.                 t[0] = e;
  86.                 return null;
  87.             }
  88.         });
  89.         if (t[0] != null) {
  90.             if (t[0] instanceof CancellationException) {
  91.                 throw (CancellationException) t[0];
  92.             }
  93.             throw new IOException(
  94.                     format(SshdText.get().keyLoadFailed, resource), t[0]);
  95.         }
  96.         return key;
  97.     }

  98.     private KeyPair loadKey(SessionContext session, NamedResource resource,
  99.             Path path, FilePasswordProvider passwordProvider)
  100.             throws IOException, GeneralSecurityException {
  101.         try (InputStream stream = Files.newInputStream(path)) {
  102.             Iterable<KeyPair> ids = SecurityUtils.loadKeyPairIdentities(session,
  103.                     resource, stream, passwordProvider);
  104.             if (ids == null) {
  105.                 throw new InvalidKeyException(
  106.                         format(SshdText.get().identityFileNoKey, path));
  107.             }
  108.             Iterator<KeyPair> keys = ids.iterator();
  109.             if (!keys.hasNext()) {
  110.                 throw new InvalidKeyException(format(
  111.                         SshdText.get().identityFileUnsupportedFormat, path));
  112.             }
  113.             KeyPair result = keys.next();
  114.             if (keys.hasNext()) {
  115.                 log.warn(format(SshdText.get().identityFileMultipleKeys, path));
  116.                 keys.forEachRemaining(k -> {
  117.                     PrivateKey pk = k.getPrivate();
  118.                     if (pk != null) {
  119.                         try {
  120.                             pk.destroy();
  121.                         } catch (DestroyFailedException e) {
  122.                             // Ignore
  123.                         }
  124.                     }
  125.                 });
  126.             }
  127.             return result;
  128.         }
  129.     }

  130.     private class CancellingKeyPairIterator implements Iterator<KeyPair> {

  131.         private final SessionContext context;

  132.         private final Iterator<Path> paths;

  133.         private KeyPair nextItem;

  134.         private boolean nextSet;

  135.         public CancellingKeyPairIterator(SessionContext session,
  136.                 Collection<? extends Path> resources) {
  137.             List<Path> copy = new ArrayList<>(resources.size());
  138.             copy.addAll(resources);
  139.             paths = copy.iterator();
  140.             context = session;
  141.         }

  142.         @Override
  143.         public boolean hasNext() {
  144.             if (nextSet) {
  145.                 return nextItem != null;
  146.             }
  147.             nextSet = true;
  148.             while (nextItem == null && paths.hasNext()) {
  149.                 try {
  150.                     nextItem = loadKey(context, paths.next());
  151.                 } catch (CancellationException cancelled) {
  152.                     throw cancelled;
  153.                 } catch (Exception other) {
  154.                     log.warn(other.getMessage(), other);
  155.                 }
  156.             }
  157.             return nextItem != null;
  158.         }

  159.         @Override
  160.         public KeyPair next() {
  161.             if (!nextSet && !hasNext()) {
  162.                 throw new NoSuchElementException();
  163.             }
  164.             KeyPair result = nextItem;
  165.             nextItem = null;
  166.             nextSet = false;
  167.             if (result == null) {
  168.                 throw new NoSuchElementException();
  169.             }
  170.             return result;
  171.         }

  172.     }
  173. }