/*
* JBoss, Home of Professional Open Source
* Copyright 2016 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed 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.jboss.as.controller.security;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Collections;
import java.util.Set;
import javax.security.auth.Destroyable;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.AttributeMarshaller;
import org.jboss.as.controller.AttributeParser;
import org.jboss.as.controller.ObjectTypeAttributeDefinition;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.SimpleAttributeDefinition;
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
import org.jboss.msc.value.InjectedValue;
import org.jboss.staxmapper.XMLExtendedStreamReader;
import org.wildfly.security.credential.Credential;
import org.wildfly.security.credential.PasswordCredential;
import org.wildfly.security.credential.store.CredentialStore;
import org.wildfly.security.credential.store.CredentialStoreException;
import org.wildfly.security.credential.store.UnsupportedCredentialTypeException;
import org.wildfly.security.password.PasswordFactory;
import org.wildfly.security.password.interfaces.ClearPassword;
import org.wildfly.security.password.spec.ClearPasswordSpec;
/**
* Class unifying access to credentials defined through {@link org.wildfly.security.credential.store.CredentialStore}
* or holding simply {@code char[]} as a secret.
*
* It defines credential reference attribute that other subsystems can use to reference external credentials of various
* types.
*
* @author <a href="mailto:pskopek@redhat.com">Peter Skopek</a>
*/
public final class CredentialReference implements Destroyable {
/**
* Definition of id used in model
*/
public static final String CREDENTIAL_REFERENCE = "credential-reference";
/**
* Definition of id used in model
*/
public static final String STORE = "store";
/**
* Definition of id used in model
*/
public static final String ALIAS = "alias";
/**
* Definition of id used in model
*/
public static final String TYPE = "type";
/**
* Definition of id used in model
*/
public static final String CLEAR_TEXT = "clear-text";
static final SimpleAttributeDefinition credentialStoreAttribute;
static final SimpleAttributeDefinition credentialAliasAttribute;
static final SimpleAttributeDefinition credentialTypeAttribute;
static final SimpleAttributeDefinition clearTextAttribute;
static final ObjectTypeAttributeDefinition credentialReferenceAttributeDefinition;
private final String credentialStoreName;
private final String alias;
private final String credentialType;
private volatile char[] secret;
static {
credentialStoreAttribute = new SimpleAttributeDefinitionBuilder(STORE, ModelType.STRING, true)
.setXmlName(STORE)
.build();
credentialAliasAttribute = new SimpleAttributeDefinitionBuilder(ALIAS, ModelType.STRING, true)
.setXmlName(ALIAS)
.build();
credentialTypeAttribute = new SimpleAttributeDefinitionBuilder(TYPE, ModelType.STRING, true)
.setXmlName(TYPE)
.build();
clearTextAttribute = new SimpleAttributeDefinitionBuilder(CLEAR_TEXT, ModelType.STRING, true)
.setXmlName(CLEAR_TEXT)
.build();
credentialReferenceAttributeDefinition = getAttributeBuilder(CREDENTIAL_REFERENCE, CREDENTIAL_REFERENCE, false).build();
}
private CredentialReference(String credentialStoreName, String alias, String credentialType, char[] secret) {
this.credentialStoreName = credentialStoreName;
this.alias = alias;
this.credentialType = credentialType;
if (secret != null) {
this.secret = secret.clone();
} else {
this.secret = null;
}
}
/**
* Get the credential store name part of this reference.
* @return credential store name or {@code null}
*/
public String getCredentialStoreName() {
return credentialStoreName;
}
/**
* Get the credential alias which denotes credential stored inside named credential store.
* @return alias of the referenced credential or {@code null}
*/
public String getAlias() {
return alias;
}
/**
* Get credential type which narrows selection of the credential stored under the alias in the credential store.
* @return credential type (class name of desired credential type) or {@code null}
*/
public String getCredentialType() {
return credentialType;
}
/**
* Get the secret stored as clear text in this reference.
* @return secret value as clear text or {@code null}
*/
public char[] getSecret() {
return secret;
}
/**
* Destroy the secret stored in this {@code Object}.
*/
@Override
public void destroy() {
if (secret != null) {
for (int i = 0; i < secret.length; i++) {
secret[i] = 0;
}
secret = null;
}
}
/**
* Determine if this {@code Object} has been destroyed.
* @return true if this {@code Object} has been destroyed,
* false otherwise.
*/
@Override
public boolean isDestroyed() {
return secret == null;
}
// factory static methods
/**
* Method to create new {@link CredentialReference} based on {@link #secret} attribute only.
* @param secret to reference
* @return new {@link CredentialReference}
*/
public static CredentialReference createCredentialReference(char[] secret) {
return new CredentialReference(CredentialReference.class.getName(), null, null, secret);
}
/**
* Method to create new {@link CredentialReference} based on params
* @param credentialStoreName credential store name
* @param alias denoting the credential
* @param credentialType type of credential (can be {@code null})
* @return new {@link CredentialReference}
*/
public static CredentialReference createCredentialReference(String credentialStoreName, String alias, String credentialType) {
return new CredentialReference(credentialStoreName, alias, credentialType, null);
}
// utility static methods
/**
* Returns new definition for credential reference attribute.
*
* @return credential reference attribute definition
*/
public static ObjectTypeAttributeDefinition getAttributeDefinition() {
return credentialReferenceAttributeDefinition;
}
/**
* Get the attribute builder for credential-reference attribute with specified characteristics.
*
* @param name name of attribute
* @param xmlName name of xml element
* @param allowNull whether the attribute is required
* @return new {@link ObjectTypeAttributeDefinition.Builder} which can be used to build attribute definition
*/
public static ObjectTypeAttributeDefinition.Builder getAttributeBuilder(String name, String xmlName, boolean allowNull) {
return new ObjectTypeAttributeDefinition.Builder(name, credentialStoreAttribute, credentialAliasAttribute, credentialTypeAttribute, clearTextAttribute)
.setXmlName(xmlName)
.setAttributeMarshaller(credentialReferenceAttributeMarshaller())
.setAttributeParser(credentialReferenceAttributeParser())
.setAllowNull(allowNull);
}
/**
* Utility method to return part of {@link ObjectTypeAttributeDefinition} for credential reference attribute.
*
* {@see CredentialReference#getAttributeDefinition}
* @param context operational context
* @param attributeDefinition attribute definition
* @param model model
* @param name name of part to return (supported names: {@link #STORE} {@link #ALIAS} {@link #TYPE}
* {@link #CLEAR_TEXT}
* @return value of part as {@link String}
* @throws OperationFailedException when something goes wrong
*/
public static String credentialReferencePartAsStringIfDefined(OperationContext context, ObjectTypeAttributeDefinition attributeDefinition, ModelNode model, String name) throws OperationFailedException {
ModelNode value = attributeDefinition.resolveModelAttribute(context, model);
if (value.isDefined()) {
ModelNode namedNode = value.get(name);
if (namedNode != null && namedNode.isDefined()) {
return namedNode.asString();
}
return null;
}
return null;
}
/**
* Replace injection with new one referencing the same {@link org.wildfly.security.credential.store.CredentialStore} but
* based of new values of {@link CredentialReference}
* @param injectedCredentialStoreClient {@link InjectedValue} to replace the credential reference
* @param credentialReference new credential reference
* @throws ClassNotFoundException when credential reference holding credential type which cannot be resolved using current providers
*/
public static void reinjectCredentialStoreClient(InjectedValue<CredentialStoreClient> injectedCredentialStoreClient,
CredentialReference credentialReference) throws ClassNotFoundException {
CredentialStoreClient originalCredentialStoreClient = injectedCredentialStoreClient.getOptionalValue();
final CredentialStoreClient updatedCredentialStoreClient;
if (originalCredentialStoreClient != null) {
updatedCredentialStoreClient =
credentialReference.getCredentialType() != null
?
new CredentialStoreClient(
originalCredentialStoreClient.getCredentialStore(),
credentialReference.getCredentialStoreName(),
credentialReference.getAlias(),
credentialReference.getCredentialType())
:
new CredentialStoreClient(
originalCredentialStoreClient.getCredentialStore(),
credentialReference.getCredentialStoreName(),
credentialReference.getAlias());
} else {
final CredentialStore credentialStore = new ClearTextCredentialStore(credentialReference);
updatedCredentialStoreClient = new CredentialStoreClient(credentialStore, CredentialReference.class.getName(), ""); // non null otherwise irrelevant alias
}
injectedCredentialStoreClient.setValue(() -> updatedCredentialStoreClient);
}
private static AttributeMarshaller credentialReferenceAttributeMarshaller() {
return new AttributeMarshaller() {
@Override
public void marshallAsElement(AttributeDefinition attribute, ModelNode credentialReferenceModelNode, boolean marshallDefault, XMLStreamWriter writer) throws XMLStreamException {
writer.writeStartElement(attribute.getXmlName());
if (credentialReferenceModelNode.hasDefined(clearTextAttribute.getName())) {
clearTextAttribute.marshallAsAttribute(credentialReferenceModelNode, writer);
} else {
credentialStoreAttribute.marshallAsAttribute(credentialReferenceModelNode, writer);
credentialAliasAttribute.marshallAsAttribute(credentialReferenceModelNode, writer);
credentialTypeAttribute.marshallAsAttribute(credentialReferenceModelNode, writer);
}
writer.writeEndElement();
}
@Override
public boolean isMarshallableAsElement() {
return true;
}
};
}
private static AttributeParser credentialReferenceAttributeParser() {
return new AttributeParser() {
@Override
public void parseElement(AttributeDefinition attribute, XMLExtendedStreamReader reader, ModelNode operation) throws XMLStreamException {
AttributeParser.OBJECT_PARSER.parseElement(attribute, reader, operation);
}
@Override
public boolean isParseAsElement() {
return true;
}
};
}
/**
* Special implementation of Credential Store SPI which handles one clear text password alias.
*/
private static class ClearTextCredentialStore extends CredentialStore {
private static String TYPE = "ClearTextCredentialStore";
private CredentialReference credentialReference;
ClearTextCredentialStore(CredentialReference credentialReference) {
super(null, null, TYPE);
this.credentialReference = credentialReference;
}
/**
* Always return true. This is special implementation with only one credential alias stored.
*
* @return {@code true} in case of initialization passed successfully, {@code false} otherwise.
*/
@Override
public boolean isInitialized() {
return true;
}
/**
* Always return false. This is non modifiable credential store.
*
* @return false
*/
@Override
public boolean isModifiable() {
return false;
}
/**
* Always return false. As this is special implementation with only one credential alias stored.
* No check is irrelevant.
*
* @param credentialAlias alias to check existence
* @param credentialType to check existence in the credential store
* @return always return false
*/
@Override
public <C extends Credential> boolean exists(String credentialAlias, Class<C> credentialType) {
return false;
}
/**
* The method is not implemented and throws {@code RuntimeException} only.
*
* @param credentialAlias to store the credential to the store
* @param credential instance of {@link Credential} to store
* @throws RuntimeException any time it is called as this method is not implemented
*/
@Override
public <C extends Credential> void store(String credentialAlias, C credential) {
throw new RuntimeException("method not implemented");
}
/**
* Retrieve credential stored in the store under the key and of the credential type
*
* @param credentialAlias to find the credential in the store
* @param credentialType - credential type to retrieve from under the credentialAlias from the store
* @return instance of {@link Credential} stored in the store
* @throws CredentialStoreException - if credentialAlias credentialType combination doesn't exist or credentialAlias cannot be retrieved
* @throws UnsupportedCredentialTypeException when the credentialType is not supported
*/
@Override
public <C extends Credential> C retrieve(String credentialAlias, Class<C> credentialType) throws CredentialStoreException, UnsupportedCredentialTypeException {
try {
PasswordFactory passwordFactory = PasswordFactory.getInstance(ClearPassword.ALGORITHM_CLEAR);
return credentialType.cast(new PasswordCredential(passwordFactory.generatePassword(new ClearPasswordSpec(credentialReference.getSecret()))));
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new CredentialStoreException(e);
}
}
/**
* The method is not implemented and throws {@code RuntimeException} only.
*
* @param credentialAlias alias to remove
* @param credentialType - credential type to be removed from under the credentialAlias from the store
* @throws RuntimeException any time it is called as this method is not implemented
*/
@Override
public void remove(String credentialAlias, Class<? extends Credential> credentialType) {
throw new RuntimeException("method not implemented");
}
/**
* Always returns empty set of aliases as it is not supposed to be used.
*
* @return {@code Set<String>} {@code Collections.emptySet()}
*/
@Override
public Set<String> getAliases() {
return Collections.emptySet();
}
}
}