blob: df4c7fec8c9a4a7572a2522a3c96be27d7892adc [file] [log] [blame]
/*
* 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.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.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.config.keys.FilePasswordProviderManager;
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.io.resource.PathResource;
import org.apache.sshd.common.util.logging.AbstractLoggingBean;
import org.apache.sshd.common.util.security.SecurityUtils;
import org.bouncycastle.openpgp.PGPException;
import org.c02e.jpgpj.Key;
import org.c02e.jpgpj.Subkey;
/**
* TODO Add javadoc
*
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class PGPAuthorizedEntriesTracker
extends AbstractLoggingBean
implements PGPAuthorizedKeyEntriesLoader,
FilePasswordProviderManager {
private FilePasswordProvider filePasswordProvider;
private final List<PGPPublicKeyFileWatcher> keyFiles;
public PGPAuthorizedEntriesTracker() {
this(Collections.emptyList());
}
public PGPAuthorizedEntriesTracker(Path path) {
this(path, null);
}
public PGPAuthorizedEntriesTracker(Path path, FilePasswordProvider passwordProvider) {
this(Collections.singletonList(Objects.requireNonNull(path, "No path provided")), passwordProvider);
}
public PGPAuthorizedEntriesTracker(Collection<? extends Path> keys) {
this(keys, null);
}
public PGPAuthorizedEntriesTracker(Collection<? extends Path> keys, FilePasswordProvider passwordProvider) {
this.keyFiles = GenericUtils.isEmpty(keys)
? new ArrayList<>()
: keys.stream()
.map(PGPPublicKeyFileWatcher::new)
.collect(Collectors.toCollection(() -> new ArrayList<>(keys.size())));
}
@Override
public FilePasswordProvider getFilePasswordProvider() {
return filePasswordProvider;
}
@Override
public void setFilePasswordProvider(FilePasswordProvider filePasswordProvider) {
this.filePasswordProvider = filePasswordProvider;
}
public List<PGPPublicKeyFileWatcher> getWatchedFiles() {
return keyFiles;
}
public void addWatchedFile(Path p) {
Objects.requireNonNull(p, "No file provided");
List<PGPPublicKeyFileWatcher> files = getWatchedFiles();
files.add(new PGPPublicKeyFileWatcher(p));
}
@Override
public List<PublicKey> loadMatchingKeyFingerprints(
SessionContext session, Collection<String> fingerprints)
throws IOException, GeneralSecurityException, PGPException {
int numEntries = GenericUtils.size(fingerprints);
if (numEntries <= 0) {
return Collections.emptyList();
}
Collection<PGPPublicKeyFileWatcher> files = getWatchedFiles();
int numFiles = GenericUtils.size(files);
if (numFiles <= 0) {
return Collections.emptyList();
}
List<PublicKey> keys = new ArrayList<>(Math.min(numEntries, numFiles));
FilePasswordProvider provider = getFilePasswordProvider();
boolean debugEnabled = log.isDebugEnabled();
for (PGPPublicKeyFileWatcher f : files) {
PathResource resourceKey = f.toPathResource();
Key container = f.loadPublicKey(session, resourceKey, provider);
Map<String, Subkey> fpMap = PGPUtils.mapSubKeysByFingerprint(container);
int numSubKeys = MapEntryUtils.size(fpMap);
Collection<Subkey> matches = (numSubKeys <= 0)
? Collections.emptyList()
: fpMap.entrySet()
.stream()
.filter(e -> fingerprints.contains(e.getKey()))
.map(Map.Entry::getValue)
.collect(Collectors.toCollection(() -> new ArrayList<>(numSubKeys)));
int numMatches = GenericUtils.size(matches);
if (debugEnabled) {
log.debug("loadMatchingKeyFingerprints({}) found {}/{} matches in {}",
session, numMatches, numEntries, resourceKey);
}
if (numMatches <= 0) {
continue; // debug breakpoint
}
for (Subkey sk : matches) {
PublicKey pk;
try {
pk = extractPublicKey(resourceKey, sk);
if (pk == null) {
continue; // debug breakpoint
}
} catch (IOException | GeneralSecurityException | RuntimeException e) {
error("loadMatchingKeyFingerprints({}) failed ({}) to convert {} from {} to public key: {}",
session, e.getClass().getSimpleName(), sk, resourceKey, e.getMessage(), e);
throw e;
}
if (debugEnabled) {
log.debug("loadMatchingKeyFingerprints({}) loaded key={}, fingerprint={}, hash={} from {}",
session, KeyUtils.getKeyType(pk), sk.getFingerprint(), KeyUtils.getFingerPrint(pk), resourceKey);
}
keys.add(pk);
}
}
return keys;
}
@Override
public <K extends PublicKey> K generatePublicKey(String algorithm, Class<K> 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);
}
}