![JAR search and dependency download from the Maven repository](/logo.png)
org.apache.sshd.openpgp.PGPPublicRingWatcher Maven / Gradle / Ivy
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd.openpgp;
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.KeyFactory;
import java.security.PublicKey;
import java.security.spec.KeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.sshd.common.NamedResource;
import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.MapEntryUtils;
import org.apache.sshd.common.util.functors.UnaryEquator;
import org.apache.sshd.common.util.io.ModifiableFileWatcher;
import org.apache.sshd.common.util.io.resource.IoResource;
import org.apache.sshd.common.util.io.resource.PathResource;
import org.apache.sshd.common.util.security.SecurityUtils;
import org.bouncycastle.openpgp.PGPException;
import org.c02e.jpgpj.Key;
import org.c02e.jpgpj.Ring;
import org.c02e.jpgpj.Subkey;
/**
* TODO Add javadoc
*
* @author Apache MINA SSHD Project
*/
public class PGPPublicRingWatcher extends ModifiableFileWatcher implements PGPAuthorizedKeyEntriesLoader {
/**
* @see Removal of the secret keyring
*/
public static final String GPG_V1_PUBLIC_RING_FILENAME = "pubring.gpg";
public static final String GPG_V2_PUBLIC_RING_FILENAME = "pubring.kbx";
/** V1 and V2 known public ring file names in order of preference */
public static final List PUBLIC_RING_FILES = Collections.unmodifiableList(
Arrays.asList(GPG_V2_PUBLIC_RING_FILENAME, GPG_V1_PUBLIC_RING_FILENAME));
/**
* Holds a {@link Map} whose key=the fingerprint (case insensitive), value=the associated {@link PublicKey}
*/
protected final AtomicReference> ringKeys
= new AtomicReference<>(Collections.emptyNavigableMap());
public PGPPublicRingWatcher(Path file) {
super(file);
}
@Override
public List loadMatchingKeyFingerprints(
SessionContext session, Collection fingerprints)
throws IOException, GeneralSecurityException, PGPException {
int numEntries = GenericUtils.size(fingerprints);
if (numEntries <= 0) {
return Collections.emptyList();
}
Map keysMap = resolveRingKeys(session);
if (MapEntryUtils.isEmpty(keysMap)) {
return Collections.emptyList();
}
List matches = Collections.emptyList();
for (String fp : fingerprints) {
PublicKey key = keysMap.get(fp);
if (key == null) {
continue;
}
if (GenericUtils.isEmpty(matches)) {
matches = new ArrayList<>(numEntries);
}
matches.add(key);
}
return matches;
}
protected NavigableMap resolveRingKeys(SessionContext session)
throws IOException, GeneralSecurityException, PGPException {
NavigableMap keysMap = ringKeys.get();
if (MapEntryUtils.isEmpty(keysMap) || checkReloadRequired()) {
ringKeys.set(Collections.emptyNavigableMap()); // mark stale
if (!exists()) {
return ringKeys.get();
}
Path file = getPath();
keysMap = reloadRingKeys(session, new PathResource(file));
int numKeys = MapEntryUtils.size(keysMap);
if (log.isDebugEnabled()) {
log.debug("resolveRingKeys({}) reloaded {} keys from {}", session, numKeys, file);
}
if (numKeys > 0) {
ringKeys.set(keysMap);
updateReloadAttributes();
}
}
return keysMap;
}
protected NavigableMap reloadRingKeys(
SessionContext session, IoResource> resourceKey)
throws IOException, GeneralSecurityException, PGPException {
Ring ring;
try (InputStream stream = resourceKey.openInputStream()) {
ring = new Ring(stream);
}
return reloadRingKeys(session, resourceKey, ring);
}
protected NavigableMap reloadRingKeys(
SessionContext session, NamedResource resourceKey, Ring ring)
throws IOException, GeneralSecurityException, PGPException {
return reloadRingKeys(session, resourceKey, ring.getKeys());
}
protected NavigableMap reloadRingKeys(
SessionContext session, NamedResource resourceKey, Collection keys)
throws IOException, GeneralSecurityException, PGPException {
if (GenericUtils.isEmpty(keys)) {
return Collections.emptyNavigableMap();
}
NavigableMap keysMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
boolean debugEnabled = log.isDebugEnabled();
for (Key k : keys) {
Map subKeys = PGPUtils.mapSubKeysByFingerprint(k);
for (Map.Entry se : subKeys.entrySet()) {
String fp = se.getKey();
Subkey sk = se.getValue();
PublicKey pubKey;
try {
pubKey = extractPublicKey(resourceKey, sk);
} catch (IOException | GeneralSecurityException | RuntimeException e) {
pubKey = handlePublicKeyExtractionError(session, resourceKey, fp, sk, e);
}
if (debugEnabled) {
log.debug("reloadRingKeys({}) loaded {} key ({}) for fingerprint={} from {}",
session, KeyUtils.getKeyType(pubKey), KeyUtils.getFingerPrint(pubKey), fp, resourceKey.getName());
}
if (pubKey == null) {
continue;
}
PublicKey prev = keysMap.put(fp, pubKey);
if (prev != null) {
PublicKey effective = handleDuplicateKeyFingerprint(session, resourceKey, fp, sk, prev, pubKey);
if (effective == null) {
keysMap.remove(fp);
} else if (!UnaryEquator.isSameReference(effective, pubKey)) {
keysMap.put(fp, effective);
}
}
}
}
return keysMap;
}
/**
* Invoked if failed to extract a {@link PublicKey} from a given {@link Subkey}
*
* @param session The {@link SessionContext} of the invocation - may be {@code null} if no session
* context available (e.g., offline tool invocation)
* @param resourceKey A key representing the resource from which the key data was read
* @param fingerprint The fingerprint value
* @param subKey The {@link Subkey} that contains the failed public key
* @param reason The reason for the failure
* @return The effective key to use - if {@code null} (default behavior) then sub-key is
* skipped
* @throws IOException If failed to process some internal data stream
* @throws GeneralSecurityException If failed to generate a surrogate key
* @throws PGPException If failed to convert PGP key to Java one
*/
protected PublicKey handlePublicKeyExtractionError(
SessionContext session, NamedResource resourceKey, String fingerprint, Subkey subKey, Throwable reason)
throws IOException, GeneralSecurityException, PGPException {
log.warn("handlePublicKeyExtractionError({}) failed ({}) to extract value for fingerprint={} from {}: {}",
session, reason.getClass().getSimpleName(), fingerprint, resourceKey.getName(), reason.getMessage());
return null;
}
/**
* /** Invoked if duplicate public keys found for the same fingerprint
*
* @param session The {@link SessionContext} of the invocation - may be {@code null} if no session
* context available (e.g., offline tool invocation)
* @param resourceKey A key representing the resource from which the key data was read
* @param fingerprint The duplicate fingerprint
* @param subKey The {@link Subkey} from which the duplicate originated
* @param k1 The original {@link PublicKey} associated with this fingerprint
* @param k2 The replacing {@link PublicKey} associated for same fingerprint
* @return The effective key to use (default=the replacing one) - if {@code null} then
* associated for the specified fingerprint is nullified
* @throws IOException If failed to process some internal data stream
* @throws GeneralSecurityException If failed to generate a surrogate key
* @throws PGPException If failed to convert PGP key to Java one
*/
protected PublicKey handleDuplicateKeyFingerprint(
SessionContext session, NamedResource resourceKey, String fingerprint, Subkey subKey, PublicKey k1, PublicKey k2)
throws IOException, GeneralSecurityException, PGPException {
log.warn("handleDuplicateKeyFingerprint({}) duplicate keys found for fingerprint={} ({}[{}] / {}[{}]) in {}",
session, fingerprint, KeyUtils.getKeyType(k1), KeyUtils.getFingerPrint(k1),
KeyUtils.getKeyType(k2), KeyUtils.getFingerPrint(k2), resourceKey.getName());
return k2;
}
@Override
public K generatePublicKey(String algorithm, Class keyType, KeySpec keySpec)
throws GeneralSecurityException {
KeyFactory factory = getKeyFactory(algorithm);
PublicKey pubKey = factory.generatePublic(keySpec);
return keyType.cast(pubKey);
}
protected KeyFactory getKeyFactory(String algorithm) throws GeneralSecurityException {
return SecurityUtils.getKeyFactory(algorithm);
}
public static Path detectDefaultPublicRingFilePath() {
return detectDefaultPublicRingFilePath(PGPUtils.getDefaultPgpFolderPath());
}
/**
* Checks if either the {@value #GPG_V1_PUBLIC_RING_FILENAME} or {@value #GPG_V2_PUBLIC_RING_FILENAME} exist as a
* regular file and can be read. Note: it attempts the V2 file first.
*
* @param dir The directory to look into
* @return The resolved {@link Path} - {@code null} if none of the files exists.
*/
public static Path detectDefaultPublicRingFilePath(Path dir) {
for (String name : PUBLIC_RING_FILES) {
Path file = dir.resolve(name);
if (!Files.exists(file)) {
continue;
}
if (!Files.isRegularFile(file)) {
continue;
}
if (!Files.isReadable(file)) {
continue;
}
return file;
}
return null;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy