org.apache.activemq.artemis.utils.PasswordMaskingUtil Maven / Gradle / Ivy
/*
* 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.activemq.artemis.utils;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.ActiveMQExceptionType;
import org.apache.activemq.artemis.logs.ActiveMQUtilBundle;
public final class PasswordMaskingUtil {
public static final String BEGIN_ENC = "ENC(";
public static final String END_ENC = ")";
private PasswordMaskingUtil() {
}
/**
* This method deals with password masking and returns the password in its plain text form.
* @param password : the original value of password string; interpreted as masked if wrapped in ENC()
* or as plain text otherwise.
* @param codecClass : the codec used to decode the password. Only when the password is interpreted
* as masked will this codec be used. Ignored otherwise.
* @return
*/
public static String resolveMask(String password, String codecClass) throws Exception {
return resolveMask(null, password, codecClass);
}
/**
* This method deals with password masking and returns the password in its plain text form.
* @param maskPassword : explicit mask flag. If it's true, the password is interpreted as
* masked. If it is false, the password is interpreted as plain text.
* if it is null, the password will be interpreted as masked if the
* password is wrapped in ENC(), or as plain text otherwise.
* @param password : the original value of password string
* @param codecClass : the codec used to decode the password. Only when the password is interpreted
* as masked will this codec be used. Ignored otherwise.
* @return
*/
public static String resolveMask(Boolean maskPassword, String password, String codecClass) throws Exception {
String plainText = password;
if (maskPassword == null) {
if (isEncMasked(password)) {
//masked
String bareMaskedPassword = unwrap(password);
plainText = getCodec(codecClass).decode(bareMaskedPassword);
}
} else if (maskPassword) {
plainText = getCodec(codecClass).decode(password);
}
return plainText;
}
public static boolean isEncMasked(String password) {
return password == null ? false : (password.startsWith(BEGIN_ENC) && password.endsWith(END_ENC));
}
//remove ENC() from the password body
public static String unwrap(String password) {
return password.substring(4, password.length() - 1);
}
public static String wrap(String password) {
return BEGIN_ENC + password + END_ENC;
}
private static final class LazyPlainTextProcessorHolder {
private LazyPlainTextProcessorHolder() {
}
private static final HashProcessor INSTANCE = new NoHashProcessor();
}
private static final class LazySecureProcessorHolder {
private LazySecureProcessorHolder() {
}
private static final HashProcessor INSTANCE;
private static final Exception EXCEPTION;
static {
HashProcessor processor = null;
Exception exception = null;
final String codecDesc = new StringBuilder().append(DefaultSensitiveStringCodec.class.getName()).append(";").append(DefaultSensitiveStringCodec.ALGORITHM).append("=").append(DefaultSensitiveStringCodec.ONE_WAY).toString();
try {
final DefaultSensitiveStringCodec codec = (DefaultSensitiveStringCodec) PasswordMaskingUtil.getCodec(codecDesc);
processor = new SecureHashProcessor(codec);
} catch (Exception e) {
//THE STACK TRACE IS THE ORIGINAL ONE!
exception = e;
} finally {
EXCEPTION = exception;
INSTANCE = processor;
}
}
}
//stored password takes 2 forms, ENC() or plain text
public static HashProcessor getHashProcessor(String storedPassword) {
return getHashProcessor(storedPassword, null);
}
public static HashProcessor getHashProcessor(String storedPassword, HashProcessor secureHashProcessor) {
if (!isEncoded(storedPassword)) {
return LazyPlainTextProcessorHolder.INSTANCE;
}
if (secureHashProcessor != null) {
return secureHashProcessor;
}
final Exception secureProcessorException = LazySecureProcessorHolder.EXCEPTION;
if (secureProcessorException != null) {
//reuse old descriptions/messages of the exception but refill the stack trace
throw new RuntimeException(secureProcessorException);
}
return LazySecureProcessorHolder.INSTANCE;
}
private static boolean isEncoded(String storedPassword) {
return storedPassword == null || isEncMasked(storedPassword);
}
public static HashProcessor getHashProcessor() {
HashProcessor processor = LazySecureProcessorHolder.INSTANCE;
//it can be null due to a previous failed attempts to instantiate it!
if (processor == null) {
processor = LazyPlainTextProcessorHolder.INSTANCE;
}
return processor;
}
/*
* Loading the codec class.
*
* @param codecDesc This parameter must have the following format:
*
* ;key=value;key1=value1;...
*
* Where only is required. key/value pairs are optional
*/
public static SensitiveDataCodec getCodec(String codecDesc) throws ActiveMQException {
SensitiveDataCodec codecInstance;
if (codecDesc == null) {
return getDefaultCodec();
}
// semi colons
String[] parts = codecDesc.split(";");
if (parts.length < 1)
throw new ActiveMQException(ActiveMQExceptionType.ILLEGAL_STATE, "Invalid PasswordCodec value: " + codecDesc);
final String codecClassName = parts[0];
// load class
codecInstance = AccessController.doPrivileged((PrivilegedAction>) () -> {
ServiceLoader serviceLoader = ServiceLoader.load(SensitiveDataCodec.class, PasswordMaskingUtil.class.getClassLoader());
try {
// Service load the codec, if a service is available
for (SensitiveDataCodec codec : serviceLoader) {
if (codec.getClass().getCanonicalName().equals(codecClassName)) {
return codec.getClass().newInstance();
}
}
} catch (Exception e) {
// Will ignore the exception and attempt to load the class directly
}
try {
// If a service is not available, load the codec class using this class's class loader
return (SensitiveDataCodec) PasswordMaskingUtil.class.getClassLoader().loadClass(codecClassName).newInstance();
} catch (Exception e) {
try {
// As a last resort, load the codec class using the current thread's context class loader
return (SensitiveDataCodec) Thread.currentThread().getContextClassLoader().loadClass(codecClassName).newInstance();
} catch (Exception e2) {
throw ActiveMQUtilBundle.BUNDLE.errorCreatingCodec(codecClassName, e2);
}
}
});
if (parts.length > 1) {
Map props = new HashMap<>();
for (int i = 1; i < parts.length; i++) {
String[] keyVal = parts[i].split("=");
if (keyVal.length != 2)
throw ActiveMQUtilBundle.BUNDLE.invalidProperty(parts[i]);
props.put(keyVal[0], keyVal[1]);
}
try {
codecInstance.init(props);
} catch (Exception e) {
throw new ActiveMQException("Fail to init codec", e, ActiveMQExceptionType.SECURITY_EXCEPTION);
}
}
return codecInstance;
}
public static DefaultSensitiveStringCodec getDefaultCodec() {
return new DefaultSensitiveStringCodec();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy