oracle.kv.impl.security.util.SecurityUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of oracle-nosql-server Show documentation
Show all versions of oracle-nosql-server Show documentation
NoSQL Database Server - supplies build and runtime support for the server (store) side of the Oracle NoSQL Database.
/*-
* Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle NoSQL
* Database made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle NoSQL Database for a copy of the license and
* additional information.
*/
package oracle.kv.impl.security.util;
import static oracle.kv.impl.param.ParameterState.SECURITY_TRANSPORT_CLIENT;
import static oracle.kv.impl.param.ParameterState.SECURITY_TRANSPORT_INTERNAL;
import static oracle.kv.impl.param.ParameterState.SECURITY_TRANSPORT_JE_HA;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.security.KeyStore;
import java.security.KeyStore.PasswordProtection;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertPathBuilder;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.security.auth.x500.X500Principal;
import oracle.kv.impl.admin.param.SecurityParams;
import oracle.kv.impl.param.ParameterMap;
import oracle.kv.impl.param.ParameterState;
import oracle.kv.impl.param.ParameterState.Info;
import oracle.kv.impl.security.KVStoreUserPrincipal;
import oracle.kv.impl.security.ResourceOwner;
import oracle.kv.impl.security.ssl.KeyStorePasswordSource;
import oracle.kv.impl.util.ConfigUtils;
import oracle.kv.impl.util.FileNames;
import oracle.kv.impl.util.FileUtils;
import oracle.kv.impl.util.SecurityConfigCreator.IOHelper;
import oracle.kv.impl.util.SecurityConfigCreator.ParsedConfig.ParamSetting;
/**
* A collection of security-related utilities.
*/
public final class SecurityUtils {
public static final String KEY_CERT_FILE = "certFileName";
private static final String CERT_FILE_DEFAULT = "store.cert";
public static final String KEY_KEY_ALGORITHM = "keyAlgorithm";
private static final String KEY_ALGORITHM_DEFAULT = "RSA";
public static final String KEY_KEY_SIZE = "keySize";
private static final String KEY_SIZE_DEFAULT = "1024";
public static final String KEY_DISTINGUISHED_NAME = "distinguishedName";
private static final String DISTINGUISHED_NAME_DEFAULT = "cn=NoSQL";
public static final String KEY_KEY_ALIAS = "keyAlias";
public static final String KEY_ALIAS_DEFAULT = "shared";
public static final String KEY_VALIDITY = "validity";
private static final String VALIDITY_DEFAULT = "365";
/*
* The list of preferred protocols. Both KV and JE SSL implementations
* will filter out any that are not supported. If none are supported,
* an exception will be thrown.
*/
public static final String PREFERRED_PROTOCOLS_DEFAULT =
"TLSv1.2,TLSv1.1,TLSv1";
/*
* The strings used by the keystore utility. Probably subject to
* localization, etc..
*/
private static final String KS_PRIVATE_KEY_ENTRY = "PrivateKeyEntry";
private static final String KS_SECRET_KEY_ENTRY = "SecretKeyEntry";
private static final String KS_TRUSTED_CERT_ENTRY = "trustedCertEntry";
private static final String TEMP_CERT_FILE = "temp.cert";
/*
* The strings used by the Kerberos utility.
*/
public static final String KADMIN_DEFAULT = "/usr/kerberos/sbin/kadmin";
public static final String KRB_CONF_FILE = "/etc/krb5.conf";
private static final String PRINCIPAL_VALIDITY = "krbPrincValidity";
private static final String PRINC_VALIDITY_DEFAULT = "365days";
private static final String KEYSALT_LIST = "krbKeysalt";
private static final String PRINCIPAL_PWD_EXPIRE = "krbPrincPwdExpire";
private static final String PRINC_PWD_EXPIRE_DEFAULT = "365days";
private static final String KEYSALT_LIST_DEFAULT = "des3-cbc-sha1:normal," +
"aes128-cts-hmac-sha1-96:normal,arcfour-hmac:normal";
public static final String KERBEROS_AUTH_NAME = "KERBEROS";
public static final String KRB_NAME_COMPONENT_SEPARATOR_STR = "/";
public static final String KRB_NAME_REALM_SEPARATOR_STR = "@";
public static final Properties princDefaultProps = new Properties();
/* The strings used by the IDCS OAuth */
public static final String OAUTH_AUTH_NAME = "IDCSOAUTH";
private static final String digitSet = "0123456789";
private static final String upperSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final String lowerSet = "abcdefghijklmnopqrstuvwxyz";
private static final String specialSet = "!#$%&'()*+,-./:; <>?@[]^_`{|}~";
private static final String allCharSet = digitSet + upperSet +
lowerSet + specialSet;
private static final SecureRandom random = new SecureRandom();
/* User id prefix used for creating user principal */
public static final String IDCS_OAUTH_USER_ID_PREFIX = "idcs";
static {
princDefaultProps.put(PRINCIPAL_VALIDITY, PRINC_VALIDITY_DEFAULT);
princDefaultProps.put(KEYSALT_LIST, KEYSALT_LIST_DEFAULT);
princDefaultProps.put(PRINCIPAL_PWD_EXPIRE, PRINC_PWD_EXPIRE_DEFAULT);
}
private static final Set preferredProtocols = new HashSet<>();
static {
preferredProtocols.add("TLSv1.2");
preferredProtocols.add("TLSv1.1");
preferredProtocols.add("TLSv1");
}
/* not instantiable */
private SecurityUtils() {
}
/**
* Given an abstract file, attempt to change permissions so that it is
* readable only by the owner of the file.
* @param f a File referencing a file or directory on which permissions are
* to be changed.
* @return true if the permissions were successfully changed
*/
public static boolean makeOwnerAccessOnly(File f)
throws IOException {
if (!f.exists()) {
return false;
}
final FileSysUtils.Operations osOps = FileSysUtils.selectOsOperations();
return osOps.makeOwnerAccessOnly(f);
}
/**
* Given an abstract file, attempt to change permissions so that it is
* writable only by the owner of the file.
* @param f a File referencing a file or directory on which permissions are
* to be changed.
* @return true if the permissions were successfully changed
*/
public static boolean makeOwnerOnlyWriteAccess(File f)
throws IOException {
if (!f.exists()) {
return false;
}
final FileSysUtils.Operations osOps = FileSysUtils.selectOsOperations();
return osOps.makeOwnerAccessOnly(f);
}
public static boolean passwordsMatch(char[] pwd1, char[] pwd2) {
if (pwd1 == pwd2) {
return true;
}
if (pwd1 == null || pwd2 == null) {
return false;
}
return Arrays.equals(pwd1, pwd2);
}
public static void clearPassword(char[] pwd) {
if (pwd != null) {
for (int i = 0; i < pwd.length; i++) {
pwd[i] = ' ';
}
}
}
/**
* Make a java keystore and an associated trustStore.
* @param securityDir the directory in which the keystore and truststore
* will be created.
* @param sp a SecurityParams instance containing information regarding
* the keystore and truststore file names
* @param keyStorePassword the password with which the keystore and
* truststore will be secured
* @param props a set of optional settings that can alter the
* keystore creation.
* @return true if the creation process was successful and false
* if an error occurred.
*/
public static boolean initKeyStore(File securityDir,
SecurityParams sp,
char[] keyStorePassword,
Properties props) {
if (props == null) {
props = new Properties();
}
final String certFileName = props.getProperty(KEY_CERT_FILE,
CERT_FILE_DEFAULT);
final String keyStoreFile =
new File(securityDir.getPath(), sp.getKeystoreFile()).getPath();
final String keyStoreType = sp.getKeystoreType();
final String trustStoreFile =
new File(securityDir.getPath(), sp.getTruststoreFile()).getPath();
final String trustStoreType = sp.getTruststoreType();
final String certFile =
new File(securityDir.getPath(), certFileName).getPath();
try {
final String keyAlg = props.getProperty(KEY_KEY_ALGORITHM,
KEY_ALGORITHM_DEFAULT);
final String keySize = props.getProperty(KEY_KEY_SIZE,
KEY_SIZE_DEFAULT);
final String dname = props.getProperty(KEY_DISTINGUISHED_NAME,
DISTINGUISHED_NAME_DEFAULT);
final String keyAlias = props.getProperty(KEY_KEY_ALIAS,
KEY_ALIAS_DEFAULT);
/*
* TODO: converting to String here introduces some security risk.
* Consider changing the keytool invocation to respond directly to
* the password prompt rather an converting to String and sticking
* on the command line. In the meantime, this is a relatively low
* security risk since it is only used in one-shot setup commands.
*/
final String keyStorePasswordStr = new String(keyStorePassword);
final String validityDays = props.getProperty(KEY_VALIDITY,
VALIDITY_DEFAULT);
final String[] keyStoreCmds = new String[] {
"keytool",
"-genkeypair",
"-keystore", keyStoreFile,
"-storetype", keyStoreType,
"-storepass", keyStorePasswordStr,
"-keypass", keyStorePasswordStr,
"-alias", keyAlias,
"-dname", dname,
"-keyAlg", keyAlg,
"-keysize", keySize,
"-validity", validityDays };
int result = runCmd(keyStoreCmds);
if (result != 0) {
System.err.println(
"Error creating keyStore: return code " + result);
return false;
}
final String[] exportCertCmds = new String[] {
"keytool",
"-export",
"-file", certFile,
"-keystore", keyStoreFile,
"-storetype", keyStoreType,
"-storepass", keyStorePasswordStr,
"-alias", keyAlias };
result = runCmd(exportCertCmds);
if (result != 0) {
System.err.println(
"Error exporting certificate: return code " + result);
return false;
}
try {
/*
* We will re-use the keystore password for the truststore
*/
final String[] importCertCmds = new String[] {
"keytool",
"-import",
"-file", certFile,
"-keystore", trustStoreFile,
"-storetype", trustStoreType,
"-storepass", keyStorePasswordStr,
"-noprompt" };
result = runCmd(importCertCmds);
if (result != 0) {
System.err.println(
"Error importing certificate to trustStore: " +
"return code " + result);
return false;
}
} finally {
/* Delete the cert file when done - we no longer need it */
new File(certFile).delete();
}
makeOwnerOnlyWriteAccess(new File(keyStoreFile));
makeOwnerOnlyWriteAccess(new File(trustStoreFile));
} catch (IOException ioe) {
System.err.println("IO error encountered: " + ioe.getMessage());
return false;
}
return true;
}
/**
* Update the security parameters in the given security directory.
* @param secDir security configuration directory
* @param params list of security parameters to update
* @throws IllegalStateException if an error occurs in the update process
*/
public static void updateSecurityParams(File secDir,
List params) {
final File secXmlFile =
new File(secDir, FileNames.SECURITY_CONFIG_FILE);
if (!secXmlFile.exists()) {
throw new IllegalStateException(
"security.xml file does not exist, " +
"cannot update the security parameters");
}
/* Get original security parameters */
final SecurityParams sp = loadSecurityParams(secDir);
applyParamsChanges(sp, params);
ConfigUtils.createSecurityConfig(sp,secXmlFile);
}
public static void applyParamsChanges(SecurityParams sp,
List paramSettings) {
final ParameterMap pmap = sp.getMap();
for (ParamSetting setting : paramSettings) {
final ParameterState pstate = setting.getParameterState();
if (pstate.appliesTo(Info.TRANSPORT)) {
if (setting.getTransportName() == null) {
for (ParameterMap tmap : sp.getTransportMaps()) {
tmap.setParameter(setting.getParamName(),
setting.getParamValue());
}
} else {
final ParameterMap tmap =
sp.getTransportMap(setting.getTransportName());
tmap.setParameter(setting.getParamName(),
setting.getParamValue());
}
} else {
pmap.setParameter(setting.getParamName(),
setting.getParamValue());
}
}
}
/**
* Merges the trust information from srcSecDir into updateSecDir.
* @param srcSecDir a File reference to the security directory from which
* trust information will be extracted
* @param updateSecDir a File reference to the security directory into
* which trust information will be merged
* @return true if the merge was successful and false otherwise
*/
public static boolean mergeTrust(File srcSecDir,
File updateSecDir) {
/* Get source truststore info */
final SecurityParams srcSp = loadSecurityParams(srcSecDir);
final String srcTrustFile =
new File(srcSecDir, srcSp.getTruststoreFile()).getPath();
/*
* TODO: converting to String here introduces some security risk.
* Consider changing the keytool invocation to respond directly to
* the password prompt rather an converting to String and sticking
* on the command line. In the meantime, this is a relatively low
* security risk since it is only used in one-shot setup commands.
*/
final String srcTruststorePwd =
new String(retrieveKeystorePassword(srcSp));
final String srcTruststoreType = srcSp.getTruststoreType();
final List stEntries =
listKeystore(new File(srcTrustFile), srcTruststoreType,
srcTruststorePwd);
/* Get dest truststore info */
final SecurityParams updateSp = loadSecurityParams(updateSecDir);
final String updateTrustFile =
new File(updateSecDir, updateSp.getTruststoreFile()).getPath();
final String updateTruststorePwd =
new String(retrieveKeystorePassword(updateSp));
final String updateTruststoreType = updateSp.getTruststoreType();
final List utEntries =
listKeystore(new File(updateTrustFile), updateTruststoreType,
updateTruststorePwd);
/*
* Convert the to-be-updated list to a set of alias names for
* ease of later access.
*/
final Set utAliasSet = new HashSet();
for (KeystoreEntry entry : utEntries) {
utAliasSet.add(entry.getAlias());
}
/* The file to hold the temporary cert */
final String certFileName = TEMP_CERT_FILE;
final String certFile =
new File(srcSecDir.getPath(), certFileName).getPath();
try {
for (KeystoreEntry entry : stEntries) {
final String[] exportCertCmds = new String[] {
"keytool",
"-export",
"-file", certFile,
"-keystore", srcTrustFile,
"-storetype", srcTruststoreType,
"-storepass", srcTruststorePwd,
"-alias", entry.getAlias() };
int result = runCmd(exportCertCmds);
if (result != 0) {
System.err.println(
"Error exporting certificate: return code " + result);
return false;
}
/*
* Determine an available alias
*/
String alias = entry.getAlias();
if (utAliasSet.contains(alias)) {
int i = 2;
while (true) {
final String tryAlias = alias + "_" + i;
if (!utAliasSet.contains(tryAlias)) {
alias = tryAlias;
break;
}
i++;
}
}
utAliasSet.add(alias);
final String[] importCertCmds = new String[] {
"keytool",
"-import",
"-file", certFile,
"-alias", alias,
"-keystore", updateTrustFile,
"-storetype", updateTruststoreType,
"-storepass", updateTruststorePwd,
"-noprompt" };
result = runCmd(importCertCmds);
if (result != 0) {
System.err.println(
"Error importing certificate to trustStore: " +
"return code " + result);
return false;
}
}
} catch (IOException ioe) {
System.err.println(
"Exception " + ioe + " while merging truststore files");
return false;
}
/*
* Copy the new truststore file to a client.trust file so that the
* two are consistent.
*/
final File srcFile = new File(updateTrustFile);
final File dstFile =
new File(updateSecDir, FileNames.CLIENT_TRUSTSTORE_FILE);
try {
SecurityUtils.copyOwnerWriteFile(srcFile, dstFile);
} catch (IOException ioe) {
System.err.println(
"Exception " + ioe + " while copying " + srcFile +
" to " + dstFile);
return false;
}
return true;
}
public static class KeystoreEntry {
private final String alias;
private final EntryType entryType;
public enum EntryType {
PRIVATE_KEY,
SECRET_KEY,
TRUSTED_CERT,
OTHER;
}
public KeystoreEntry(String alias, EntryType entryType) {
this.alias = alias;
this.entryType = entryType;
}
String getAlias() {
return alias;
}
EntryType getEntryType() {
return entryType;
}
}
/**
* Print out entries in keystores of given security configuration
* directory.
* @param secConfigDir security configuration directory.
* @return a string of entries information in keystores if successful,
* or null otherwise.
*/
public static String printKeystores(File secConfigDir) {
final SecurityParams sp = loadSecurityParams(secConfigDir);
final String keystoreFile =
new File(secConfigDir, sp.getKeystoreFile()).getPath();
final String truststoreFile =
new File(secConfigDir, sp.getTruststoreFile()).getPath();
final String keystorePwd = new String(retrieveKeystorePassword(sp));
final String keystoreType = sp.getKeystoreType();
final String truststoreType = sp.getTruststoreType();
return printKeystore(keystoreFile, keystoreType, keystorePwd) + "\n" +
printKeystore(truststoreFile, truststoreType, keystorePwd);
}
private static String printKeystore(String keystoreFile,
String keystoreType,
String keystorePwd) {
final StringBuilder sb = new StringBuilder();
try {
final String[] keyStoreCmds = new String[] {
"keytool",
"-list",
"-keystore", keystoreFile,
"-storetype", keystoreType,
"-storepass", keystorePwd };
final List output = new ArrayList();
final int result = runCmd(keyStoreCmds, output);
if (result != 0) {
sb.append("Error listing keyStore: ").append(output);
return sb.toString();
}
sb.append("Keystore: " + keystoreFile + "\n");
for (String s : output) {
sb.append(s + "\n");
}
return sb.toString();
} catch (IOException ioe) {
sb.append("IO error encountered: ").append(ioe.getMessage());
return sb.toString();
}
}
/**
* List the entries in a keystore (or truststore) file.
* @param keystoreFile the keystore file
* @param keystoreType the type of the keystore file
* @param storePassword the password for the store
* @return a list of the keystore entries if successful, or null otherwise
*/
public static List listKeystore(File keystoreFile,
String keystoreType,
String storePassword) {
try {
final String[] keyStoreCmds = new String[] {
"keytool",
"-list",
"-keystore", keystoreFile.getPath(),
"-storetype", keystoreType,
"-storepass", storePassword };
final List output = new ArrayList();
final int result = runCmd(keyStoreCmds, output);
if (result != 0) {
System.err.println(
"Error listing keyStore: return code " + result);
return null;
}
final Pattern keystoreContains =
Pattern.compile("Your keystore contains ([0-9]+) entr.*");
/*
* Entries look like this:
* shared, Dec 31, 2013, PrivateKeyEntry,
*/
final Pattern entryPattern =
Pattern.compile("([^,]+),([^,]+, [0-9]+, )([a-zA-Z]+),.*");
final List entries = new ArrayList();
boolean startFound = false;
for (String s : output) {
if (!startFound) {
final Matcher m = keystoreContains.matcher(s);
if (m.matches()) {
startFound = true;
}
} else {
final Matcher m = entryPattern.matcher(s);
if (m.matches()) {
final String entryTypeStr = m.group(3);
final KeystoreEntry.EntryType entryType;
if (entryTypeStr.equals(KS_PRIVATE_KEY_ENTRY)) {
entryType = KeystoreEntry.EntryType.PRIVATE_KEY;
} else if (entryTypeStr.equals(KS_SECRET_KEY_ENTRY)) {
entryType = KeystoreEntry.EntryType.SECRET_KEY;
} else if (entryTypeStr.equals(KS_TRUSTED_CERT_ENTRY)) {
entryType = KeystoreEntry.EntryType.TRUSTED_CERT;
} else {
entryType = KeystoreEntry.EntryType.OTHER;
}
entries.add(new KeystoreEntry(m.group(1), entryType));
}
}
}
return entries;
} catch (IOException ioe) {
System.err.println("IO error encountered: " + ioe.getMessage());
return null;
}
}
/**
* Make a copy of a file where the resulting copy should be writable only
* by the owner with read privilege determined by system policy
* (i.e. umask).
* @param srcFile a file to be copied
* @param destFile the destination file
* @throws IOException if an error occurs in the copy process
*/
public static void copyOwnerWriteFile(File srcFile, File destFile)
throws IOException {
FileUtils.copyFile(srcFile, destFile);
SecurityUtils.makeOwnerOnlyWriteAccess(destFile);
}
/**
* Create store service principal and extract keytab file.
*
* @param securityDir the directory in which the keytab will be created.
* @param sp a SecurityParams instance containing information regarding
* the store service principal and keytab file names
* @param kadminSetting kadmin settings
* @param props a set of optional settings that can alter the
* principal creation, or null
* @param ioHelper I/O helper class used to read kadmin password
* @return true if the generation process was successful and false
* if an error occurred.
*/
public static boolean generateKeyTabFile(File securityDir,
SecurityParams sp,
KadminSetting kadminSetting,
Properties props,
IOHelper ioHelper) {
if (props == null) {
props = new Properties();
}
final String keytabFile = new File(
securityDir.getPath(), sp.getKerberosKeytabFile()).getPath();
try {
final String princName = sp.getKerberosServiceName();
final String validityDays = props.getProperty(PRINCIPAL_VALIDITY);
final String keysaltList = props.getProperty(KEYSALT_LIST);
final String pwdExpire = props.getProperty(PRINCIPAL_PWD_EXPIRE);
final String instance = sp.getKerberosInstanceName();
final String realm = sp.getKerberosRealmName();
final String principal = (instance != null) ?
princName + "/" + instance :
princName;
final List kadminCmdsList =
generateKadminCmds(kadminSetting, realm);
/* Add store service principal */
final String addPrincCmds = "add_principal" +
" -expire " + validityDays +
" -pwexpire " + pwdExpire +
" -randkey " + "\"" + principal + "\"";
kadminCmdsList.add(addPrincCmds);
System.out.println("Adding principal " + principal);
int result = runKadminCmd(kadminSetting, ioHelper, kadminCmdsList);
if (result != 0) {
System.err.println(
"Error adding service principal: return code " + result);
return false;
}
kadminCmdsList.remove(kadminCmdsList.size() - 1);
/* Extract keytab of service principal */
System.out.println("Extracting keytab " + keytabFile);
final String extractKeytabCmds = "ktadd" +
" -k " + keytabFile +
" -e " + keysaltList +
" " + "\"" + principal + "\"";
kadminCmdsList.add(extractKeytabCmds);
result = runKadminCmd(kadminSetting, ioHelper, kadminCmdsList);
if (result != 0) {
System.err.println(
"Error extracting keytab file: return code " + result);
return false;
}
makeOwnerOnlyWriteAccess(new File(keytabFile));
} catch (IOException ioe) {
System.err.println("IO error encountered: " + ioe.getMessage());
return false;
}
return true;
}
/**
* Renew keytab file in given security directory.
*
* @param secDir security directory
* @param keysaltList keysalt list used to generate new keytab file
* @param kadminSetting settings for connecting kadmin
* @param ioHelper I/O helper class to read kadmin password
* @return true if the renew process was successful and false
* if an error occurred.
*/
public static boolean renewKeytab(File secDir,
String keysaltList,
KadminSetting kadminSetting,
IOHelper ioHelper) {
final SecurityParams sp = loadSecurityParams(secDir);
final File keytabFile = new File(secDir, sp.getKerberosKeytabFile());
if (!keytabFile.exists()) {
System.err.println("keytab " + keytabFile + " does not exist");
return false;
}
final String principal = SecurityUtils.getCanonicalPrincName(sp);
File tmpKeytab = null;
try {
final List kadminCmdsList =
generateKadminCmds(kadminSetting, sp.getKerberosRealmName());
if (keysaltList == null) {
keysaltList = KEYSALT_LIST_DEFAULT;
}
/*
* Create a temporary file to store the new keys of principal,
* so that old key can be reserved in case of extracting keytab
* error.
*
* Using createTempFile method to create temporary file then
* remove it and only keep the file name, since ktadd command
* cannot store keys to a file generated by Java.
*/
tmpKeytab = File.createTempFile("tmp", ".keytab");
if (!tmpKeytab.delete()) {
System.err.println("Error generating a temporary keytab file");
return false;
}
final String extractKeytabCmds = "ktadd" +
" -k " + tmpKeytab.getAbsolutePath() +
" -e " + keysaltList +
" " + principal;
kadminCmdsList.add(extractKeytabCmds);
final int result =
runKadminCmd(kadminSetting, ioHelper, kadminCmdsList);
if (result != 0) {
System.err.println(
"Error extracting keytab file: return code " + result);
return false;
}
if (!keytabFile.delete()) {
System.err.println("Old keytab " + keytabFile +
" cannot be deleted");
return false;
}
if (!tmpKeytab.renameTo(keytabFile)) {
System.err.println("keytab " + tmpKeytab +
" cannot be renamed as " + keytabFile);
return false;
}
makeOwnerOnlyWriteAccess(keytabFile);
} catch (IOException ioe) {
System.err.println("IO error encountered: " + ioe.getMessage());
return false;
} finally {
if (tmpKeytab != null &&
tmpKeytab.exists() &&
!tmpKeytab.delete()) {
System.err.println("Temporary keytab " + tmpKeytab +
" cannot be deleted");
return false;
}
}
return true;
}
/**
* Check given authentication method name is Kerberos.
*/
public static boolean isKerberos(String authMethod) {
if (authMethod == null) {
return false;
}
return authMethod.equalsIgnoreCase(KERBEROS_AUTH_NAME);
}
/**
* Check given authentication methods in format "authMethod1,authMethod2"
* contains Kerberos.
*/
public static boolean hasKerberos(String authMethods) {
if (authMethods == null) {
return false;
}
for (String method : authMethods.split(",")) {
if (isKerberos(method)) {
return true;
}
}
return false;
}
/**
* Check given authentication method name is IDCS OAuth.
*/
private static boolean isIDCSOAuth(String authMethod) {
if (authMethod == null) {
return false;
}
return authMethod.equalsIgnoreCase(OAUTH_AUTH_NAME);
}
/**
* Check given authentication methods in format "authMethod1,authMethod2"
* contains IDCS OAuth.
*/
public static boolean hasIDCSOAuth(String authMethods) {
if (authMethods == null) {
return false;
}
for (String method : authMethods.split(",")) {
if (isIDCSOAuth(method)) {
return true;
}
}
return false;
}
/**
* Check given authentication methods array contains element of IDCS OAuth.
*/
public static boolean hasIDCSOAuth(String[] authMethods) {
if (authMethods == null) {
return false;
}
for (String method : authMethods) {
if (isIDCSOAuth(method)) {
return true;
}
}
return false;
}
/**
* Check given authentication methods array contains element of Kerberos.
*/
public static boolean hasKerberos(String[] authMethods) {
if (authMethods == null) {
return false;
}
for (String method : authMethods) {
if (isKerberos(method)) {
return true;
}
}
return false;
}
/**
* Return canonical Kerberos service principal.
*/
public static String getCanonicalPrincName(SecurityParams secParams) {
final StringBuilder sb = new StringBuilder();
sb.append(secParams.getKerberosServiceName());
String instanceName = secParams.getKerberosInstanceName();
if (instanceName != null && !instanceName.equals("")) {
sb.append(KRB_NAME_COMPONENT_SEPARATOR_STR);
sb.append(secParams.getKerberosInstanceName());
}
if (!secParams.getKerberosRealmName().equals("")) {
sb.append(KRB_NAME_REALM_SEPARATOR_STR);
sb.append(secParams.getKerberosRealmName());
}
return sb.toString();
}
private static List generateKadminCmds(KadminSetting kadminSetting,
String defaultRealm) {
final List kadminCmdsList = new ArrayList();
kadminCmdsList.add(kadminSetting.getKrbAdminPath());
kadminCmdsList.add("-r");
kadminCmdsList.add(defaultRealm);
if (kadminSetting.useKeytab()) {
kadminCmdsList.add("-k");
kadminCmdsList.add("-t");
kadminCmdsList.add(kadminSetting.getKrbAdminKeytab());
System.out.println(String.format(
"Login Kerberos admin via keytab %s with %s",
kadminSetting.getKrbAdminKeytab(),
kadminSetting.getKrbAdminPrinc()));
} else if (kadminSetting.useCcache()) {
kadminCmdsList.add("-c");
kadminCmdsList.add(kadminSetting.getKrbAdminCcache());
System.out.println(String.format(
"Login Kerberos admin via credential cache %s with %s",
kadminSetting.getKrbAdminCcache(),
kadminSetting.getKrbAdminPrinc()));
}
if (kadminSetting.getKrbAdminPrinc() != null) {
kadminCmdsList.add("-p");
kadminCmdsList.add(kadminSetting.getKrbAdminPrinc());
}
kadminCmdsList.add("-q");
return kadminCmdsList;
}
/**
* The class maintain Kerberos V5 configuration information that retrieved
* from user specified configuration files. Copy the approach used by the
* Java internal Kerberos parsing code to retrieve and validate default
* realm and kdc, which is required for NoSQL Kerberos authentication.
* The rest of configuration parameters are validated by Java Kerberos
* login module when performing the actual authentication.
*/
public static class Krb5Config {
private File configFile;
private String defaultRealm;
private String realmKdc;
public Krb5Config(File krb5ConfFile) {
configFile = krb5ConfFile;
}
/**
* Parse krb5 configuration file and identify default realm and
* corresponding kdc.
*/
public void parseConfigFile() throws IOException {
final List lines = loadConfigFile();
final Map kdcs = new HashMap<>();
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i).trim();
/* Find default realm from libdefaults */
if (line.equalsIgnoreCase("[libdefaults]")) {
for (int count = i + 1; count < lines.size(); count++) {
line = lines.get(count).trim();
final int equalsPos = line.indexOf('=');
if (equalsPos > 0) {
final String key =
line.substring(0, equalsPos).trim();
if (key.equalsIgnoreCase("default_realm")) {
defaultRealm =
trimmed(line.substring(equalsPos + 1));
}
}
if (lines.get(count).startsWith("[")) {
i = count - 1;
break;
}
}
} else if (line.equalsIgnoreCase("[realms]")) {
/* Parse all realms and cache their corresponding kdc */
String realm = "";
for (int count = i + 1; count < lines.size(); count++) {
line = lines.get(count).trim();
if (line.endsWith("{")) {
final int equalsPos = line.indexOf('=');
if (equalsPos > 0) {
realm = line.substring(0, equalsPos).trim();
}
} else if (!line.startsWith("}")) {
final int equalsPos = line.indexOf('=');
if (equalsPos > 0) {
final String key =
line.substring(0, equalsPos).trim();
if (key.equalsIgnoreCase("kdc") &&
!realm.equals("")) {
/*
* User can specify multiple realms in
* the configuration file, cache them
* firstly and find the default kdc
* later.
*/
kdcs.put(realm, trimmed(
line.substring(equalsPos + 1)));
}
}
}
if (lines.get(count).startsWith("[")) {
i = count - 1;
break;
}
}
}
}
if (defaultRealm != null) {
realmKdc = kdcs.get(defaultRealm);
}
}
public String getDefaultRealm() {
return defaultRealm;
}
public String getKdc() {
return realmKdc;
}
public String getConfigFilePath() {
return configFile.getAbsolutePath();
}
private String trimmed(String s) {
s = s.trim();
if (s.charAt(0) == '"' && s.charAt(s.length()-1) == '"' ||
s.charAt(0) == '\'' && s.charAt(s.length()-1) == '\'') {
s = s.substring(1, s.length()-1).trim();
}
return s;
}
private List loadConfigFile() throws IOException {
final List lines = new ArrayList();
try (final BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream(configFile)))) {
String line;
/*
* Cache previous line, used to resolve the case that Kerberos
* configuration file accepts and convert to standard format.
* EXAMPLE.COM =
* {
* kdc = kerberos.example.com
* }
*/
String previous = null;
while ((line = br.readLine()) != null) {
/* Ignore comments and blank lines */
if (!(line.startsWith("#") || line.trim().isEmpty())) {
String current = line.trim();
if (current.startsWith("{")) {
if (previous == null) {
throw new IOException(
"Config file should not start with \"{\"");
}
previous += " " + current;
} else {
if (previous != null) {
lines.add(previous);
}
previous = current;
}
}
}
if (previous != null) {
lines.add(previous);
}
return lines;
}
}
}
/**
* The class maintains the setting used to connecting kadmin utility.
*/
public static class KadminSetting {
private static final String NO_KADMIN = "NONE";
private String krbAdminPath = KADMIN_DEFAULT;
private String krbAdminPrinc;
private String krbAdminKeytab;
private String krbAdminCcache;
public KadminSetting setKrbAdminPath(String kadminPath) {
this.krbAdminPath = kadminPath;
return this;
}
public String getKrbAdminPath() {
return krbAdminPath;
}
public KadminSetting setKrbAdminPrinc(String kadminPrinc) {
this.krbAdminPrinc = kadminPrinc;
return this;
}
public String getKrbAdminPrinc() {
return krbAdminPrinc;
}
public KadminSetting setKrbAdminKeytab(String adminKeytab) {
this.krbAdminKeytab = adminKeytab;
return this;
}
public String getKrbAdminKeytab() {
return krbAdminKeytab;
}
public KadminSetting setKrbAdminCcache(String adminCcache) {
this.krbAdminCcache = adminCcache;
return this;
}
public String getKrbAdminCcache() {
return krbAdminCcache;
}
public boolean doNotPerformKadmin() {
return krbAdminPath.equalsIgnoreCase(NO_KADMIN);
}
/**
* Validate if given kadmin settings are appropriate.
*
* @throws IllegalArgumentException
*/
public void validateKadminSetting()
throws IllegalArgumentException {
if (doNotPerformKadmin()) {
return;
}
/*
* Check if user specified admin keytab and credential cache at the
* same time
*/
if (krbAdminKeytab != null) {
if (krbAdminCcache != null) {
throw new IllegalArgumentException(
"cannot use admin ketyab and credential cache together");
}
if (krbAdminPrinc == null) {
throw new IllegalArgumentException(
"must specify admin principal when using keytab file");
}
if (!new File(krbAdminKeytab).exists()) {
throw new IllegalArgumentException(
"keytab file " + krbAdminKeytab + " does not exist");
}
}
/* check if kadmin ccache exists */
if (krbAdminCcache != null && !new File(krbAdminCcache).exists()) {
throw new IllegalArgumentException(
"credential cache " + krbAdminCcache + " does not exist");
}
/* Must specify principal if use password */
if (krbAdminKeytab == null &&
krbAdminCcache == null &&
krbAdminPrinc == null) {
throw new IllegalArgumentException("use kadmin with password " +
"must specify principal name");
}
}
/**
* Whether use keytab to connect kadmin utility.
*/
public boolean useKeytab() {
return krbAdminKeytab != null &&
krbAdminPrinc != null &&
krbAdminCcache == null;
}
/**
* Whether use credential cache to connect kadmin utility.
*/
public boolean useCcache() {
return krbAdminCcache != null &&
krbAdminKeytab == null;
}
/**
* Whether prompt password to connect kadmin utility.
*/
public boolean promptPwd() {
return krbAdminCcache == null && krbAdminKeytab == null;
}
}
/**
* Run a command in a subshell.
* @param args an array of command-line arguments, in the format expected
* by Runtime.exec(String[]).
* @return the process exit code
* @throws IOException if an IO error occurs during the exec process
*/
static int runCmd(String[] args)
throws IOException {
final Process proc = Runtime.getRuntime().exec(args);
boolean done = false;
int returnCode = 0;
while (!done) {
try {
returnCode = proc.waitFor();
done = true;
} catch (InterruptedException e) /* CHECKSTYLE:OFF */ {
} /* CHECKSTYLE:ON */
}
return returnCode;
}
/**
* Run kadmin-related commands. If users do not specify keytab or credential
* cache for kadmin user, prompt for admin user password.
*
* @param setting settings for connecting kadmin utility
* @param ioHelper I/O helper may be used for password prompt
* @param args an list of command line arguments.
* @return the process exit code
* @throws IOException if an IO error occurs during the process execution.
*/
private static int runKadminCmd(KadminSetting setting,
IOHelper ioHelper,
List args)
throws IOException {
final List output = new ArrayList<>();
final ProcessBuilder pb = new ProcessBuilder(args);
pb.redirectErrorStream(true);
char[] pwd = null;
final Process proc = pb.start();
final BufferedReader br =
new BufferedReader(new InputStreamReader(proc.getInputStream()));
if (setting.promptPwd()) {
final BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(proc.getOutputStream()));
pwd = ioHelper.readPassword(
"Password for " + setting.getKrbAdminPrinc() + ": ");
if (pwd == null) {
System.err.println("Failed to acquire kadmin password");
}
writer.write(pwd);
SecurityUtils.clearPassword(pwd);
writer.write("\n");
writer.flush();
}
/* Read lines of input */
boolean done = false;
while (!done) {
final String s = br.readLine();
if (s == null) {
done = true;
} else {
output.add(s);
}
}
/* Then get exit code */
done = false;
int returnCode = 0;
while (!done) {
try {
returnCode = proc.waitFor();
done = true;
} catch (InterruptedException e) /* CHECKSTYLE:OFF */ {
} /* CHECKSTYLE:ON */
}
/* Output kadmin error and std output for easier debugging */
for (String s : output) {
System.err.println(s);
}
return returnCode;
}
private static int runCmd(String[] args, List output)
throws IOException {
final Process proc = Runtime.getRuntime().exec(args);
final BufferedReader br =
new BufferedReader(new InputStreamReader(proc.getInputStream()));
/* Read lines of input */
boolean done = false;
while (!done) {
final String s = br.readLine();
if (s == null) {
done = true;
} else {
output.add(s);
}
}
/* Then get exit code */
done = false;
int returnCode = 0;
while (!done) {
try {
returnCode = proc.waitFor();
done = true;
} catch (InterruptedException e) /* CHECKSTYLE:OFF */ {
} /* CHECKSTYLE:ON */
}
return returnCode;
}
/**
* Report whether the host parameter is an address that is local to this
* machine. If the host is a name rather than a literal address, all
* resolutions of the name must be local in order for the host to be
* considered local.
*
* @param host either an IP address literal or host name
* @return true it the host represents a local address
* @throws SocketException if an IO exception occurs
*/
public static boolean isLocalHost(String host)
throws SocketException {
try {
boolean anyLocal = false;
for (InetAddress hostAddr : InetAddress.getAllByName(host)) {
if (isLocalAddress(hostAddr)) {
anyLocal = true;
} else {
return false;
}
}
return anyLocal;
} catch (UnknownHostException uhe) {
return false;
}
}
/**
* Determine whether the address portion of the InetAddress (host name is
* ignored) is an address that is local to this machine.
*/
private static boolean isLocalAddress(InetAddress address)
throws SocketException {
final Enumeration netIfs =
NetworkInterface.getNetworkInterfaces();
while (netIfs.hasMoreElements()) {
final NetworkInterface netIf = netIfs.nextElement();
if (isLocalAddress(netIf, address)) {
return true;
}
final Enumeration subIfs =
netIf.getSubInterfaces();
while (subIfs.hasMoreElements()) {
if (isLocalAddress(subIfs.nextElement(), address)) {
return true;
}
}
}
return false;
}
/**
* Determine whether the address portion of the InetAddress (host name is
* ignored) is an address that is local to a network interface.
*/
private static boolean isLocalAddress(NetworkInterface netIf,
InetAddress address) {
final Enumeration addrs = netIf.getInetAddresses();
while (addrs.hasMoreElements()) {
final InetAddress addr = addrs.nextElement();
if (addr.equals(address)) {
return true;
}
}
return false;
}
public static SecurityParams loadSecurityParams(File secDir) {
final File secFile = new File(secDir, FileNames.SECURITY_CONFIG_FILE);
return ConfigUtils.getSecurityParams(secFile);
}
private static char[] retrieveKeystorePassword(SecurityParams sp) {
final KeyStorePasswordSource pwdSrc = KeyStorePasswordSource.create(sp);
return (pwdSrc == null) ? null : pwdSrc.getPassword();
}
/**
* Constructs a resource owner from KVStore user in current context. Null
* will be return if we could not detect a user principal in current
* execution context, or the current execution context is null.
*/
public static ResourceOwner currentUserAsOwner() {
final KVStoreUserPrincipal currentUserPrinc =
KVStoreUserPrincipal.getCurrentUser();
if (currentUserPrinc == null) {
return null;
}
return new ResourceOwner(currentUserPrinc.getUserId(),
currentUserPrinc.getName());
}
/**
* Return the default Kerberos principal configuration properties. These
* properties are used whiling adding principal and extracting keytab.
*/
public static Properties getDefaultKrbPrincipalProperties() {
return (Properties) princDefaultProps.clone();
}
/**
* Run various verifications against given security configuration directory.
*
* @param secConfigDir security configuration directory
*/
public static String verifyConfiguration(File secConfigDir) {
final SecurityParams sp = loadSecurityParams(secConfigDir);
final StringBuilder errors = new StringBuilder();
/* Verify security parameters */
/*
* Check that JE HA, internal, and client transports are using
* preferred protocols.
*/
/*
* JE HA transport does not have notion of client and server: the
* allowed protocols applies to both of them.
*/
try {
final String specifiedProtocols =
sp.getTransAllowProtocols(SECURITY_TRANSPORT_JE_HA);
if (!checkIfProtocolsAllowed(specifiedProtocols)) {
errors.append(
"Transport JE HA is not using preferred protocols.")
.append(" Found: ")
.append(specifiedProtocols)
.append(" Preferred protocols: ")
.append(PREFERRED_PROTOCOLS_DEFAULT);
}
} catch (IllegalArgumentException iae) {
errors.append(
"Problem with protocols specified for transport JE HA: ")
.append(iae.getMessage());
}
/* Internal and client transport only have client allowed protocols */
String verifyResult = checkClientAllowedProtocols(
SECURITY_TRANSPORT_INTERNAL, sp);
if (verifyResult != null) {
errors.append(verifyResult);
}
verifyResult = checkClientAllowedProtocols(
SECURITY_TRANSPORT_CLIENT, sp);
if (verifyResult != null) {
errors.append(verifyResult);
}
/* Check if all transport are using the same key alias */
final String serverInternalKeyAlias =
sp.getTransServerKeyAlias(SECURITY_TRANSPORT_INTERNAL);
final String clientInternalKeyAlias =
sp.getTransClientKeyAlias(SECURITY_TRANSPORT_INTERNAL);
if (!serverInternalKeyAlias.equals(clientInternalKeyAlias)) {
errors.append("Key alias of internal transport server ")
.append(serverInternalKeyAlias)
.append(" is not the same as client ")
.append(clientInternalKeyAlias)
.append(".\n");
}
final String jeHaKeyAlias =
sp.getTransServerKeyAlias(SECURITY_TRANSPORT_JE_HA);
if (!serverInternalKeyAlias.equals(jeHaKeyAlias)) {
errors.append("Key alias of internal transport server ")
.append(serverInternalKeyAlias)
.append(" is not the same as JE HA transport ")
.append(jeHaKeyAlias)
.append(".\n");
}
final String clientServerKeyAlias =
sp.getTransServerKeyAlias(SECURITY_TRANSPORT_CLIENT);
if (!serverInternalKeyAlias.equals(clientServerKeyAlias)) {
errors.append("Key alias of internal transport server ")
.append(serverInternalKeyAlias)
.append(" is not the same as server for client transport ")
.append(clientServerKeyAlias)
.append(".\n");
}
/* Check if all transport are configured to allow the same identity */
final String clientInternalAllowedIdentity =
sp.getTransClientIdentityAllowed(SECURITY_TRANSPORT_INTERNAL);
final String serverAllowedIdentity =
sp.getTransServerIdentityAllowed(SECURITY_TRANSPORT_INTERNAL);
if (!clientInternalAllowedIdentity.equals(serverAllowedIdentity)) {
errors.append("Identities allowed by server side of internal")
.append(" transport ")
.append(serverAllowedIdentity)
.append(" are not the same as client side of internal")
.append(" transport ")
.append(clientInternalAllowedIdentity)
.append(".\n");
}
final String jeHaAllowedIdentity =
sp.getTransServerIdentityAllowed(SECURITY_TRANSPORT_JE_HA);
if (!clientInternalAllowedIdentity.equals(jeHaAllowedIdentity)) {
errors.append("Identities allowed by JE HA transport ")
.append(jeHaAllowedIdentity)
.append(" are not the same as internal transport ")
.append(clientInternalAllowedIdentity)
.append(".\n");
}
final String clientAllowedIdentity =
sp.getTransServerIdentityAllowed(SECURITY_TRANSPORT_CLIENT);
if (!clientInternalAllowedIdentity.equals(clientAllowedIdentity)) {
errors.append("Identities allowed by client transport ")
.append(clientAllowedIdentity)
.append(" are not the same as internal transport ")
.append(clientInternalAllowedIdentity)
.append(".\n");
}
/* Verify keystore and truststore installation */
final String result = checkKeystoreInstallation(sp);
if (result != null) {
errors.append(result);
}
return errors.toString();
}
/**
* Load KeyStore from given key or trust store file.
* @param storeName KeyStore file name
* @param storePassword KeyStore password
* @param storeFlavor the flavor of KeyStore, like keystore that contains
* private key entries and truststore that contains certificates
* @param storeType KeyStore type, like jks
* @return Loaded KeyStore object
* @throws IllegalArgumentException if any errors occurs during loading
*/
public static KeyStore loadKeyStore(String storeName,
char[] storePassword,
String storeFlavor,
String storeType)
throws IllegalArgumentException {
if (storeType == null || storeType.isEmpty()) {
storeType = KeyStore.getDefaultType();
}
final KeyStore ks;
try {
ks = KeyStore.getInstance(storeType);
} catch (KeyStoreException kse) {
throw new IllegalArgumentException(
"Unable to find a " + storeFlavor + " instance of type " +
storeType, kse);
}
final FileInputStream fis;
try {
fis = new FileInputStream(storeName);
} catch (FileNotFoundException fnfe) {
throw new IllegalArgumentException(
"Unable to locate specified " + storeFlavor + " " + storeName,
fnfe);
}
try {
ks.load(fis, storePassword);
} catch (IOException ioe) {
throw new IllegalArgumentException(
"Error reading from " + storeFlavor + " file " + storeName,
ioe);
} catch (NoSuchAlgorithmException nsae) {
throw new IllegalArgumentException(
"Unable to check " + storeFlavor + " integrity: " + storeName,
nsae);
} catch (CertificateException ce) {
throw new IllegalArgumentException(
"Not all certificates could be loaded: " + storeName,
ce);
} finally {
try {
fis.close();
} catch (IOException ioe) {
/* ignored */
}
}
return ks;
}
/*
* Check if given transport specify the preferred protocol for client.
*/
private static String checkClientAllowedProtocols(String trans,
SecurityParams sp) {
final String specifiedProtocol = sp.getTransClientAllowProtocols(trans);
try {
if (!checkIfProtocolsAllowed(specifiedProtocol)) {
return "Transport " + trans +
" is not using preferred protocols " + specifiedProtocol +
" , the prefered protocols are " +
PREFERRED_PROTOCOLS_DEFAULT;
}
} catch (IllegalArgumentException iae) {
return "Problem with protocols specified for transport " + trans +
": " + iae.getMessage();
}
return null;
}
/*
* Check if given protocols are allowed. Given protocols string must be in
* the format of "x,y,z" using commas as delimiters, and at least one of
* the specified protocols must be in the preferred protocols list.
*/
private static boolean checkIfProtocolsAllowed(String protocols) {
final String[] protocolList = protocols.split(",");
if (protocolList.length == 0) {
throw new IllegalArgumentException(
"'" + protocols + "' does not have the correct format," +
" must be specified in the format 'x,y,z', using commas as" +
" delimiters");
}
for (String protocol : protocolList) {
if (preferredProtocols.contains(protocol.trim())) {
return true;
}
}
return false;
}
private static String checkKeystoreInstallation(SecurityParams sp) {
final KeyStorePasswordSource pwdSrc = KeyStorePasswordSource.create(sp);
if (pwdSrc == null) {
/*
* Return directly, cannot perform following verification
* without password
*/
return "Unable to create keystore password source.\n";
}
final String keystoreName =
sp.getConfigDir() + File.separator + sp.getKeystoreFile();
final String truststoreName =
sp.getConfigDir() + File.separator + sp.getTruststoreFile();
final String keyalias =
sp.getTransServerKeyAlias(SECURITY_TRANSPORT_JE_HA);
final String allowedIdentities =
sp.getTransClientIdentityAllowed(SECURITY_TRANSPORT_JE_HA);
final KeyStore keystore;
final KeyStore truststore;
char[] ksPwd = null;
try {
ksPwd = pwdSrc.getPassword();
keystore = loadKeyStore(
keystoreName, ksPwd, "keystore", sp.getKeystoreType());
truststore = loadKeyStore(
truststoreName, ksPwd, "truststore", sp.getTruststoreType());
/* Check if private key entry exist */
final PrivateKeyEntry pkEntry = (PrivateKeyEntry)
keystore.getEntry(keyalias, new PasswordProtection(ksPwd));
if (pkEntry == null) {
/*
* Return directly, cannot perform following verification
* without a private key in key store.
*/
return "Private key " + keyalias +
" does not exist in the keystore.";
}
final Certificate cert = pkEntry.getCertificate();
if (!(cert instanceof X509Certificate)) {
/*
* Return directly, cannot perform following verification
* if private key in key store does not have valid x509
* certificate.
*/
return "Certificate of " + keyalias +
" is not a valid X509 certificate.\n";
}
/*
* Check if the subject of key is one of the identities allowed.
* Using regular expression matching to check this as the internal
* SSL verifier does.
*/
final X509Certificate x509Cert = (X509Certificate) cert;
final X500Principal subject = x509Cert.getSubjectX500Principal();
final String verifyResult =
verifyCertIdentityAllowed(subject, allowedIdentities);
if (verifyResult != null) {
return verifyResult;
}
final String issuer = x509Cert.getIssuerX500Principal().getName();
/* Self-signed certificate, skip following verification */
if (subject.getName().equals(issuer)) {
return null;
}
final X509CertSelector target = new X509CertSelector();
final Certificate[] chain = pkEntry.getCertificateChain();
target.setCertificate((X509Certificate)chain[0]);
/* Convert certificates array to X509 certificates*/
final List x509Chain =
new ArrayList();
for (Certificate certificate : chain) {
if (!(certificate instanceof X509Certificate)) {
return "Certificate chain contains invalid " +
"X509 certificate " + certificate.toString() + ".\n";
}
x509Chain.add((X509Certificate)certificate);
}
try {
final PKIXBuilderParameters params =
new PKIXBuilderParameters(keystore, target);
final CertPathBuilder builder =
CertPathBuilder.getInstance("PKIX");
/*
* Disable revocation check for now since we don't have support
* to get CRL information, but should be enabled once we can
* provide CRL list.
*/
params.setRevocationEnabled(false);
/*
* Attempts to build certificate path, if fail,
* it will throw a CertPathBuilderException
*/
builder.build(params);
} catch (Exception e) {
/*
* Hide the original exception since the failure of build is
* difficult to track down from CertPathBuilderException. The
* error message of this exception is not very descriptive, so
* here we only warns users the installation is incorrect.
*/
return "Problem with verifying certificate chain in keystore " +
keystoreName + ".\n";
}
/* Check truststore contains necessary certificates */
boolean foundRequiredTrust = false;
final Enumeration aliases = truststore.aliases();
while (aliases.hasMoreElements()) {
final Certificate trust =
truststore.getCertificate(aliases.nextElement());
if (trust instanceof X509Certificate) {
if (x509Cert.equals(trust)) {
foundRequiredTrust = true;
}
}
}
if (!foundRequiredTrust) {
return truststoreName +
" must contain the certificate " +
x509Cert.getSubjectDN().getName() + ".\n";
}
} catch (Exception e) {
return "Unexpected error: " + e.getMessage();
} finally {
SecurityUtils.clearPassword(ksPwd);
}
return null;
}
/**
* Verify name of X500 principal in RFC1779 format match the regular
* expression specified in allowedIdentities.
*/
static String verifyCertIdentityAllowed(X500Principal principal,
String allowedIdentities) {
final String subjectName = principal.toString();
final String rfc1779Name = principal.getName(X500Principal.RFC1779);
if (!checkIdentityAllowed(allowedIdentities, rfc1779Name)) {
return "The certificate's subject name '" + subjectName +
"' when displayed in RFC 1779 format as '" + rfc1779Name +
"' does not match '" + allowedIdentities +
"' specified in allowedIdentities.\n";
}
return null;
}
/*
* Using regular expression matching for now to check if given identity
* is allowed.
*/
private static boolean checkIdentityAllowed(String allowedIdentities,
String identity) {
final String regex = allowedIdentities.substring(
"dnmatch(".length(), allowedIdentities.length() - 1);
return identity.matches(regex);
}
/*
* Generate random password in the specified length.
*/
public static char[] generateKeyStorePassword(int length) {
final char[] pwd = new char[length];
for (int i = 0; i < length; i++) {
pwd[i] = allCharSet.charAt(random.nextInt(allCharSet.length()));
}
return pwd;
}
/*
* Generate fixed length of password used for creating a default
* user of secured KVLite. The fixed length is 12.
*/
public static char[] generateUserPassword() {
char[] pwd = new char[12];
for (int i = 0; i < 3; i++) {
pwd[i] = upperSet.charAt(random.nextInt(upperSet.length()));
pwd[i + 3] = lowerSet.charAt(random.nextInt(lowerSet.length()));
pwd[i + 6] = specialSet.charAt(random.nextInt(specialSet.length()));
pwd[i + 9] = digitSet.charAt(random.nextInt(digitSet.length()));
}
return permuteCharArray(pwd);
}
private static char[] permuteCharArray(char[] chars) {
final List list = new ArrayList();
for (char c : chars) {
list.add(c);
}
Collections.shuffle(list);
char[] result = new char[list.size()];
for (int i = 0; i < list.size(); i++) {
Character c = list.get(i);
result[i] = c.charValue();
}
list.clear();
return result;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy