GUACAMOLE-641: Alternatively download .pem files for private keys.

This commit is contained in:
Michael Jumper
2022-01-21 15:23:41 -08:00
parent 86d1de5f2c
commit e89a65586c
3 changed files with 98 additions and 25 deletions

View File

@@ -24,13 +24,10 @@ import com.google.inject.Singleton;
import com.keepersecurity.secretsManager.core.Hosts;
import com.keepersecurity.secretsManager.core.KeeperFile;
import com.keepersecurity.secretsManager.core.KeeperRecord;
import com.keepersecurity.secretsManager.core.KeeperRecordData;
import com.keepersecurity.secretsManager.core.KeeperRecordField;
import com.keepersecurity.secretsManager.core.KeeperSecrets;
import com.keepersecurity.secretsManager.core.Login;
import com.keepersecurity.secretsManager.core.Notation;
import com.keepersecurity.secretsManager.core.SecretsManager;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -446,16 +443,10 @@ public class KsmClient {
cacheLock.readLock().lock();
try {
// Retrieve any relevant file asynchronously
Matcher fileNotationMatcher = KEEPER_FILE_NOTATION.matcher(notation);
if (fileNotationMatcher.matches()) {
// Retrieve any relevant file asynchronously
KeeperFile file = Notation.getFile(cachedSecrets, notation);
return CompletableFuture.supplyAsync(() -> {
return new String(SecretsManager.downloadFile(file), StandardCharsets.UTF_8);
});
}
if (fileNotationMatcher.matches())
return recordService.download(Notation.getFile(cachedSecrets, notation));
// Retrieve string values synchronously
return CompletableFuture.completedFuture(Notation.getValue(cachedSecrets, notation));

View File

@@ -23,6 +23,7 @@ import com.google.inject.Singleton;
import com.keepersecurity.secretsManager.core.HiddenField;
import com.keepersecurity.secretsManager.core.Host;
import com.keepersecurity.secretsManager.core.Hosts;
import com.keepersecurity.secretsManager.core.KeeperFile;
import com.keepersecurity.secretsManager.core.KeeperRecord;
import com.keepersecurity.secretsManager.core.KeeperRecordData;
import com.keepersecurity.secretsManager.core.KeeperRecordField;
@@ -30,8 +31,12 @@ import com.keepersecurity.secretsManager.core.KeyPair;
import com.keepersecurity.secretsManager.core.KeyPairs;
import com.keepersecurity.secretsManager.core.Login;
import com.keepersecurity.secretsManager.core.Password;
import com.keepersecurity.secretsManager.core.SecretsManager;
import com.keepersecurity.secretsManager.core.Text;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -77,6 +82,13 @@ public class KsmRecordService {
private static final Pattern PRIVATE_KEY_LABEL_PATTERN =
Pattern.compile("private\\s*key", Pattern.CASE_INSENSITIVE);
/**
* Regular expression which matches the filenames of private keys attached
* to Keeper records.
*/
private static final Pattern PRIVATE_KEY_FILENAME_PATTERN =
Pattern.compile(".*\\.pem", Pattern.CASE_INSENSITIVE);
/**
* Returns the single value stored in the given list. If the list is empty
* or contains multiple values, null is returned.
@@ -234,6 +246,70 @@ public class KsmRecordService {
}
/**
* Returns the file attached to the give Keeper record whose filename
* matches the given pattern. If there are no such files, or multiple such
* files, null is returned.
*
* @param record
* The record to retrieve the file from.
*
* @param filenamePattern
* The pattern to match filenames against.
*
* @return
* The single matching file attached to the given Keeper record, or
* null if there is not exactly one matching file.
*/
private KeeperFile getFile(KeeperRecord record, Pattern filenamePattern) {
List<KeeperFile> files = record.getFiles();
if (files == null)
return null;
KeeperFile foundFile = null;
for (KeeperFile file : files) {
// Ignore files whose filenames do not match
Matcher filenameMatcher = filenamePattern.matcher(file.getData().getName());
if (!filenameMatcher.matches())
continue;
// Ignore ambiguous fields
if (foundFile != null)
return null;
foundFile = file;
}
return foundFile;
}
/**
* Downloads the given file from the Keeper vault asynchronously. All files
* are read as UTF-8.
*
* @param file
* The file to download, which may be null.
*
* @return
* A Future which resolves with the contents of the file once
* downloaded. If no file was provided (file was null), this Future
* resolves with null.
*/
public Future<String> download(final KeeperFile file) {
if (file == null)
return CompletableFuture.completedFuture(null);
return CompletableFuture.supplyAsync(() -> {
return new String(SecretsManager.downloadFile(file), StandardCharsets.UTF_8);
});
}
/**
* Returns the single hostname (or address) associated with the given
* record. If the record has no associated hostname, or multiple hostnames,
@@ -341,23 +417,30 @@ public class KsmRecordService {
* Returns the private key associated with the given record. If the record
* has no associated private key, or multiple private keys, null is
* returned. Private keys are retrieved from "KeyPairs" fields.
* Alternatively, private keys are retrieved from custom fields with the
* label "private key" (case-insensitive, space optional) if they are
* "KeyPairs", "Password", or "Hidden" fields.
* Alternatively, private keys are retrieved from PEM-type attachments or
* custom fields with the label "private key" (case-insensitive, space
* optional) if they are "KeyPairs", "Password", or "Hidden" fields. If
* file downloads are required, they will be performed asynchronously.
*
* @param record
* The record to retrieve the private key from.
*
* @return
* The private key associated with the given record, or null if the
* record has no associated private key or multiple private keys.
* A Future which resolves with the private key associated with the
* given record. If the record has no associated private key or
* multiple private keys, the returned Future will resolve to null.
*/
public String getPrivateKey(KeeperRecord record) {
public Future<String> getPrivateKey(KeeperRecord record) {
// Attempt to find single matching keypair field
KeyPairs keyPairsField = getField(record, KeyPairs.class, PRIVATE_KEY_LABEL_PATTERN);
if (keyPairsField != null)
return getSingleValue(keyPairsField.getValue(), KeyPair::getPrivateKey);
return CompletableFuture.completedFuture(getSingleValue(keyPairsField.getValue(), KeyPair::getPrivateKey));
// Lacking a typed keypair field, prefer a PEM-type attachment
KeeperFile keyFile = getFile(record, PRIVATE_KEY_FILENAME_PATTERN);
if (keyFile != null)
return download(keyFile);
KeeperRecordData data = record.getData();
List<KeeperRecordField> custom = data.getCustom();
@@ -365,14 +448,14 @@ public class KsmRecordService {
// Use password "private key" custom field as fallback ...
Password passwordField = getField(custom, Password.class, PRIVATE_KEY_LABEL_PATTERN);
if (passwordField != null)
return getSingleValue(passwordField.getValue());
return CompletableFuture.completedFuture(getSingleValue(passwordField.getValue()));
// ... or hidden "private key" custom field
HiddenField hiddenField = getField(custom, HiddenField.class, PRIVATE_KEY_LABEL_PATTERN);
if (hiddenField != null)
return getSingleValue(hiddenField.getValue());
return CompletableFuture.completedFuture(getSingleValue(hiddenField.getValue()));
return null;
return CompletableFuture.completedFuture(null);
}

View File

@@ -109,9 +109,8 @@ public class KsmSecretService implements VaultSecretService {
tokens.put(prefix + "PASSPHRASE", CompletableFuture.completedFuture(passphrase));
// Private key of server-related record
String privateKey = recordService.getPrivateKey(record);
if (privateKey != null)
tokens.put(prefix + "KEY", CompletableFuture.completedFuture(privateKey));
Future<String> privateKey = recordService.getPrivateKey(record);
tokens.put(prefix + "KEY", privateKey);
}