org.wildfly.security.http.cert.ClientCertAuthenticationMechanism Maven / Gradle / Ivy
The newest version!
/*
* 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.wildfly.security.http.cert;
import static org.wildfly.security.http.HttpConstants.CLIENT_CERT_NAME;
import static org.wildfly.security.mechanism._private.ElytronMessages.httpClientCert;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.wildfly.security.auth.callback.AuthenticationCompleteCallback;
import org.wildfly.security.auth.callback.CachedIdentityAuthorizeCallback;
import org.wildfly.security.auth.callback.EvidenceDecodePrincipalCallback;
import org.wildfly.security.auth.callback.EvidenceVerifyCallback;
import org.wildfly.security.auth.callback.PrincipalAuthorizeCallback;
import org.wildfly.security.auth.server.SecurityDomain;
import org.wildfly.security.auth.server.SecurityIdentity;
import org.wildfly.security.cache.CachedIdentity;
import org.wildfly.security.cache.IdentityCache;
import org.wildfly.security.evidence.X509PeerCertificateChainEvidence;
import org.wildfly.security.http.HttpAuthenticationException;
import org.wildfly.security.http.HttpScope;
import org.wildfly.security.http.HttpServerAuthenticationMechanism;
import org.wildfly.security.http.HttpServerRequest;
import org.wildfly.security.http.Scope;
import org.wildfly.security.mechanism.AuthenticationMechanismException;
import org.wildfly.security.mechanism._private.MechanismUtil;
import org.wildfly.security.x500.X500;
/**
* The CLIENT_CERT authentication mechanism.
*
* @author Darran Lofthouse
*/
final class ClientCertAuthenticationMechanism implements HttpServerAuthenticationMechanism {
private final CallbackHandler callbackHandler;
private final boolean skipVerification;
/**
* Construct a new instance of the {@code ClientCertAuthenticationMechanism} mechanism.
*
* @param callbackHandler the {@link CallbackHandler} to use to verify the supplied credentials and to notify to establish the current identity.
* @param skipVerification whether the certificate verification using {@link EvidenceVerifyCallback} should be skipped
*/
ClientCertAuthenticationMechanism(CallbackHandler callbackHandler, boolean skipVerification) {
this.callbackHandler = callbackHandler;
this.skipVerification = skipVerification;
}
/**
* @see org.wildfly.security.http.HttpServerAuthenticationMechanism#getMechanismName()
*/
@Override
public String getMechanismName() {
return CLIENT_CERT_NAME;
}
/**
* @see org.wildfly.security.http.HttpServerAuthenticationMechanism#evaluateRequest(org.wildfly.security.http.HttpServerRequest)
*/
@Override
public void evaluateRequest(HttpServerRequest request) throws HttpAuthenticationException {
Function cacheFunction = createIdentityCacheFunction(request);
if (cacheFunction != null && attemptReAuthentication(request, cacheFunction)) {
httpClientCert.trace("Re-authentication succeed");
return;
}
if (attemptAuthentication(request, cacheFunction)) {
return;
}
httpClientCert.trace("Both, re-authentication and authentication, failed");
fail(request);
}
private boolean attemptAuthentication(HttpServerRequest request, Function cacheFunction) throws HttpAuthenticationException {
Certificate[] peerCertificates = request.getPeerCertificates();
if (peerCertificates == null) {
httpClientCert.trace("Peer Unverified");
request.noAuthenticationInProgress();
return true;
}
X509Certificate[] x509Certificates = X500.asX509CertificateArray(peerCertificates);
X509PeerCertificateChainEvidence evidence = new X509PeerCertificateChainEvidence(x509Certificates);
if (httpClientCert.isTraceEnabled()) {
httpClientCert.tracef("Authenticating using following certificates: [%s]", Arrays.toString(x509Certificates));
}
EvidenceVerifyCallback callback = new EvidenceVerifyCallback(evidence);
try {
if (! skipVerification) {
MechanismUtil.handleCallbacks(httpClientCert, callbackHandler, callback);
} else {
// Since handling of EvidenceVerifyCallback is being skipped here, ensure evidence
// decoding still takes place
EvidenceDecodePrincipalCallback evidenceDecodePrincipalCallback = new EvidenceDecodePrincipalCallback(evidence);
MechanismUtil.handleCallbacks(httpClientCert, callbackHandler, evidenceDecodePrincipalCallback);
}
} catch (AuthenticationMechanismException e) {
throw e.toHttpAuthenticationException();
} catch (UnsupportedCallbackException e) {
throw httpClientCert.mechCallbackHandlerFailedForUnknownReason(e).toHttpAuthenticationException();
}
boolean verified = callback.isVerified();
httpClientCert.tracef("X509PeerCertificateChainEvidence was verified by EvidenceVerifyCallback handler: %b verification skipped: %b", verified, skipVerification);
if (verified || skipVerification) {
final BooleanSupplier authorizedFunction;
final Callback authorizeCallBack;
if (cacheFunction != null) {
CachedIdentityAuthorizeCallback cacheCallback = new CachedIdentityAuthorizeCallback(evidence.getDecodedPrincipal(), cacheFunction, true);
authorizedFunction = cacheCallback::isAuthorized;
authorizeCallBack = cacheCallback;
} else {
PrincipalAuthorizeCallback principalCallback = new PrincipalAuthorizeCallback(evidence.getDecodedPrincipal());
authorizedFunction = principalCallback::isAuthorized;
authorizeCallBack = principalCallback;
}
try {
MechanismUtil.handleCallbacks(httpClientCert, callbackHandler, authorizeCallBack);
} catch (AuthenticationMechanismException e) {
throw e.toHttpAuthenticationException();
} catch (UnsupportedCallbackException e) {
throw httpClientCert.mechCallbackHandlerFailedForUnknownReason(e).toHttpAuthenticationException();
}
boolean authorized = authorizedFunction.getAsBoolean();
httpClientCert.tracef("X509PeerCertificateChainEvidence was authorized by CachedIdentityAuthorizeCallback(%s) handler: %b", evidence.getDecodedPrincipal(), authorized);
if (authorized && succeed(request)) {
httpClientCert.trace("Authentication succeed");
return true;
}
}
return false;
}
private boolean succeed(HttpServerRequest request) throws HttpAuthenticationException {
try {
MechanismUtil.handleCallbacks(httpClientCert, callbackHandler, AuthenticationCompleteCallback.SUCCEEDED);
request.authenticationComplete();
return true;
} catch (AuthenticationMechanismException e) {
throw e.toHttpAuthenticationException();
} catch (UnsupportedCallbackException ignored) {
// ignored
}
return false;
}
private void fail(HttpServerRequest request) throws HttpAuthenticationException {
try {
MechanismUtil.handleCallbacks(httpClientCert, callbackHandler, AuthenticationCompleteCallback.FAILED);
request.authenticationFailed(httpClientCert.authenticationFailed());
} catch (AuthenticationMechanismException e) {
throw e.toHttpAuthenticationException();
} catch (UnsupportedCallbackException ignored) {
// ignored
}
}
private boolean attemptReAuthentication(HttpServerRequest request, Function cacheFunction) throws HttpAuthenticationException {
CachedIdentityAuthorizeCallback authorizeCallback = new CachedIdentityAuthorizeCallback(cacheFunction, true);
try {
MechanismUtil.handleCallbacks(httpClientCert, callbackHandler, authorizeCallback);
} catch (AuthenticationMechanismException e) {
throw e.toHttpAuthenticationException();
} catch (UnsupportedCallbackException e) {
throw httpClientCert.mechCallbackHandlerFailedForUnknownReason(e).toHttpAuthenticationException();
}
boolean authorized = authorizeCallback.isAuthorized();
httpClientCert.tracef("Identity was authorized by CachedIdentityAuthorizeCallback handler: %b", authorized);
if (authorized) {
return succeed(request);
}
return false;
}
private Function createIdentityCacheFunction(HttpServerRequest request) {
HttpScope scope = request.getScope(Scope.SSL_SESSION);
return scope == null ? null : securityDomain -> new IdentityCache() {
final Map identities = MechanismUtil.computeIfAbsent(scope,
"org.wildfly.elytron.identity-cache", key -> new ConcurrentHashMap<>());
@Override
public void put(SecurityIdentity identity) {
CachedIdentity cachedIdentity = new CachedIdentity(CLIENT_CERT_NAME, false, identity);
httpClientCert.tracef("storing into cache: %s", cachedIdentity);
identities.putIfAbsent(securityDomain, cachedIdentity);
}
@Override
public CachedIdentity get() {
CachedIdentity cachedIdentity = identities.get(securityDomain);
httpClientCert.tracef("loading from cache: %s", cachedIdentity);
return cachedIdentity;
}
@Override
public CachedIdentity remove() {
httpClientCert.tracef("clearing identity cache");
return identities.remove(securityDomain);
}
};
}
}