/*
* 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.ops4j.pax.jdbc.config.impl;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;
import org.jasypt.encryption.StringEncryptor;
/**
* Decryptor for cipher texts.
*/
public class Decryptor {
private static final String ENCRYPTED_PROPERTY_PREFIX = "ENC(";
private static final String ENCRYPTED_PROPERTY_SUFFIX = ")";
private static final char ALIAS_SEPARATOR = ',';
private static final String DECRYPTOR_ALIAS = "decryptor";
private StringEncryptor decryptor;
/**
* Create new decryptor instance.
*
* @param decryptor custom StringEncryptor tracker the supports aliases
*/
public Decryptor(final StringEncryptor decryptor) {
this.decryptor = decryptor;
}
public static String getAlias(final Dictionary<String, Object> config) {
String alias = getValue(config, DECRYPTOR_ALIAS);
for (Enumeration<String> e = config.keys(); e.hasMoreElements();) {
final String key = (String) e.nextElement();
String value = getValue(config, key);
String newAlias = getAlias(value);
if (newAlias != null) {
if (alias == null) {
alias = newAlias;
} else {
if (!alias.equals(newAlias)) {
throw new RuntimeException("Only one alias is supported but found at least two: " + newAlias + ", " + alias);
}
}
}
}
return alias;
}
private static String getValue(final Dictionary<String, Object> config, final String key) {
Object value = config.get(key);
return value == null ? null : value.toString();
}
private static String getAlias(final String value) {
if (!isEncrypted(value)) {
return null;
}
final String argument = value.substring(ENCRYPTED_PROPERTY_PREFIX.length(),
value.length() - ENCRYPTED_PROPERTY_SUFFIX.length());
final int aliasPos = argument.indexOf(ALIAS_SEPARATOR);
return aliasPos > -1 ? argument.substring(aliasPos + 1).trim() : null;
}
/**
* Decrypt configuration.
*
* @param config configuration to decrypt
* @return decrypted configuration
*/
public Dictionary<String, Object> decrypt(final Dictionary<String, Object> config) {
if (decryptor == null) {
return config;
}
Dictionary<String, Object> decryptedConfig = new Hashtable<>();
for (Enumeration<String> e = config.keys(); e.hasMoreElements();) {
final String key = (String) e.nextElement();
String value = String.valueOf(config.get(key));
if (config.get(key) instanceof String && isEncrypted(value)) {
final String plainText = decryptValue(value);
if (plainText != null) {
decryptedConfig.put(key, plainText);
}
} else {
decryptedConfig.put(key, value);
}
}
return decryptedConfig;
}
/**
* Decrypt encrypted configuration value. Alias is optional and separated with ALIAS_SEPARATOR character.
*
* @param value encrypted configuration value, composite of cipher text and alias
* @return decrypted (plain text) configuration value
*/
private String decryptValue(final String value) {
final String argument = value.substring(ENCRYPTED_PROPERTY_PREFIX.length(),
value.length() - ENCRYPTED_PROPERTY_SUFFIX.length());
final int aliasPos = argument.indexOf(ALIAS_SEPARATOR);
final String cipherText = aliasPos > -1 ? argument.substring(0, aliasPos) : argument;
return decryptor.decrypt(cipherText);
}
/**
* Check whether a value is encrypted.
*
* @param value configuration value
* @return <code>true</code> if value is encrypted, <code>false</code> otherwise
*/
public static boolean isEncrypted(String value) {
return value.startsWith(ENCRYPTED_PROPERTY_PREFIX)
&& value.endsWith(ENCRYPTED_PROPERTY_SUFFIX);
}
public static boolean isEncrypted(Dictionary<String, Object> loadedConfig) {
Enumeration<Object> values = loadedConfig.elements();
while (values.hasMoreElements()) {
Object value = values.nextElement();
if (value instanceof String && isEncrypted((String)value)) {
return true;
}
}
return false;
}
}