/*
* 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.impl;
import static org.wildfly.security._private.ElytronMessages.log;
import static org.wildfly.security.http.HttpConstants.CLIENT_CERT_NAME;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import javax.net.ssl.SSLSession;
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.EvidenceVerifyCallback;
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.HttpServerAuthenticationMechanism;
import org.wildfly.security.http.HttpServerRequest;
import org.wildfly.security.mechanism.AuthenticationMechanismException;
import org.wildfly.security.mechanism.MechanismUtil;
import org.wildfly.security.ssl.SSLUtils;
import org.wildfly.security.x500.X500;
/**
* The CLIENT_CERT authentication mechanism.
*
* @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a>
*/
public class ClientCertAuthenticationMechanism implements HttpServerAuthenticationMechanism {
private final CallbackHandler callbackHandler;
/**
* 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.
*/
ClientCertAuthenticationMechanism(CallbackHandler callbackHandler) {
this.callbackHandler = callbackHandler;
}
/**
* @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 {
SSLSession sslSession = request.getSSLSession();
if (sslSession == null) {
log.trace("CLIENT-CERT no SSL session");
request.noAuthenticationInProgress();
return;
}
if (attemptReAuthentication(request)) {
log.trace("ClientCertAuthenticationMechanism: re-authentication succeed");
return;
}
if (attemptAuthentication(request)) {
return;
}
log.trace("ClientCertAuthenticationMechanism: both, re-authentication and authentication, failed");
fail(request);
}
private boolean attemptAuthentication(HttpServerRequest request) throws HttpAuthenticationException {
Certificate[] peerCertificates = request.getPeerCertificates();
if (peerCertificates == null) {
log.trace("CLIENT-CERT Peer Unverified");
request.noAuthenticationInProgress();
return true;
}
X509Certificate[] x509Certificates = X500.asX509CertificateArray(peerCertificates);
X509PeerCertificateChainEvidence evidence = new X509PeerCertificateChainEvidence(x509Certificates);
log.tracef("Using ClientCertAuthenticationMechanism to authenticate the following certificates: [%s]", x509Certificates);
EvidenceVerifyCallback callback = new EvidenceVerifyCallback(evidence);
try {
MechanismUtil.handleCallbacks(CLIENT_CERT_NAME, callbackHandler, callback);
} catch (AuthenticationMechanismException e) {
throw e.toHttpAuthenticationException();
} catch (UnsupportedCallbackException e) {
throw log.mechCallbackHandlerFailedForUnknownReason(CLIENT_CERT_NAME, e).toHttpAuthenticationException();
}
boolean verified = callback.isVerified();
log.tracef("X509PeerCertificateChainEvidence was verified by EvidenceVerifyCallback handler: %b", verified);
if (verified) {
CachedIdentityAuthorizeCallback authorizeCallback = new CachedIdentityAuthorizeCallback(evidence.getPrincipal(), createIdentityCache(request), true);
try {
MechanismUtil.handleCallbacks(CLIENT_CERT_NAME, callbackHandler, authorizeCallback);
} catch (AuthenticationMechanismException e) {
throw e.toHttpAuthenticationException();
} catch (UnsupportedCallbackException e) {
throw log.mechCallbackHandlerFailedForUnknownReason(CLIENT_CERT_NAME, e).toHttpAuthenticationException();
}
boolean authorized = authorizeCallback.isAuthorized();
log.tracef("X509PeerCertificateChainEvidence was authorized by CachedIdentityAuthorizeCallback(%s) handler: %b", evidence.getPrincipal(), authorized);
if (authorized && succeed(request)) {
log.trace("ClientCertAuthenticationMechanism: authentication succeed");
return true;
}
}
return false;
}
private boolean succeed(HttpServerRequest request) throws HttpAuthenticationException {
try {
MechanismUtil.handleCallbacks(CLIENT_CERT_NAME, 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(CLIENT_CERT_NAME, callbackHandler, AuthenticationCompleteCallback.FAILED);
request.authenticationFailed(log.authenticationFailed(CLIENT_CERT_NAME));
} catch (AuthenticationMechanismException e) {
throw e.toHttpAuthenticationException();
} catch (UnsupportedCallbackException ignored) {
// ignored
}
}
private boolean attemptReAuthentication(HttpServerRequest request) throws HttpAuthenticationException {
CachedIdentityAuthorizeCallback authorizeCallback = new CachedIdentityAuthorizeCallback(createIdentityCache(request), true);
try {
MechanismUtil.handleCallbacks(CLIENT_CERT_NAME, callbackHandler, authorizeCallback);
} catch (AuthenticationMechanismException e) {
throw e.toHttpAuthenticationException();
} catch (UnsupportedCallbackException e) {
throw log.mechCallbackHandlerFailedForUnknownReason(CLIENT_CERT_NAME, e).toHttpAuthenticationException();
}
boolean authorized = authorizeCallback.isAuthorized();
log.tracef("Identity was authorized by CachedIdentityAuthorizeCallback handler: %b", authorized);
if (authorized) {
return succeed(request);
}
return false;
}
private Function<SecurityDomain, IdentityCache> createIdentityCache(HttpServerRequest request) {
SSLSession sslSession = request.getSSLSession();
if (sslSession == null) {
return null;
}
return securityDomain -> new IdentityCache() {
final Map<SecurityDomain, CachedIdentity> identities = SSLUtils.computeIfAbsent(sslSession, "org.wildfly.elytron.identity-cache", key -> new ConcurrentHashMap<>());
@Override
public void put(SecurityIdentity identity) {
identities.putIfAbsent(securityDomain, new CachedIdentity(getMechanismName(), identity));
}
@Override
public CachedIdentity get() {
return identities.get(securityDomain);
}
@Override
public CachedIdentity remove() {
return identities.remove(securityDomain);
}
};
}
}