com.sun.enterprise.security.jaspic.callback.BaseContainerCallbackHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of payara-micro Show documentation
Show all versions of payara-micro Show documentation
Micro Distribution of the Payara Project
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
// Portions Copyright 2016-2022 Payara Foundation and/or its affiliates
// Payara Foundation and/or its affiliates elects to include this software in this distribution under the GPL Version 2 license
/*
* BaseContainerCallbackHandler.java
*
* Created on April 21, 2004, 11:56 AM
*/
package com.sun.enterprise.security.jaspic.callback;
import com.sun.enterprise.security.SecurityContext;
import com.sun.enterprise.security.SecurityServicesUtil;
import com.sun.enterprise.security.auth.JaspicToJaasBridge;
import com.sun.enterprise.security.auth.login.DistinguishedPrincipalCredential;
import com.sun.enterprise.security.auth.login.common.LoginException;
import com.sun.enterprise.security.auth.realm.certificate.CertificateRealm;
import com.sun.enterprise.security.common.AppservAccessController;
import com.sun.enterprise.security.jaspic.config.CallbackHandlerConfig;
import com.sun.enterprise.security.jaspic.config.HandlerContext;
import com.sun.enterprise.security.ssl.SSLUtils;
import com.sun.enterprise.security.store.PasswordAdapter;
import com.sun.enterprise.security.web.integration.WebPrincipal;
import com.sun.enterprise.server.pluggable.SecuritySupport;
import com.sun.logging.LogDomains;
import jakarta.security.auth.message.callback.CallerPrincipalCallback;
import jakarta.security.auth.message.callback.CertStoreCallback;
import jakarta.security.auth.message.callback.GroupPrincipalCallback;
import jakarta.security.auth.message.callback.PasswordValidationCallback;
import jakarta.security.auth.message.callback.PrivateKeyCallback;
import jakarta.security.auth.message.callback.SecretKeyCallback;
import jakarta.security.auth.message.callback.TrustStoreCallback;
import org.glassfish.internal.api.Globals;
import org.glassfish.security.common.Group;
import org.glassfish.security.common.MasterPassword;
import org.glassfish.security.common.PrincipalImpl;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.x500.X500Principal;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.PrivilegedAction;
import java.security.cert.CertStore;
import java.security.cert.Certificate;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.sun.enterprise.security.SecurityContext.getDefaultCallerPrincipal;
import static com.sun.enterprise.security.common.AppservAccessController.privileged;
import static java.util.Arrays.stream;
/**
* Base Callback Handler for Jakarta Authentication
*
* @author Harpreet Singh
* @author Shing Wai Chan
*/
abstract class BaseContainerCallbackHandler implements CallbackHandler, CallbackHandlerConfig {
private static final String DEFAULT_DIGEST_ALGORITHM = "SHA-1";
private static final String CLIENT_SECRET_KEYSTORE = "com.sun.appserv.client.secretKeyStore";
private static final String CLIENT_SECRET_KEYSTORE_PASSWORD = "com.sun.appserv.client.secretKeyStorePassword";
protected final static Logger _logger = LogDomains.getLogger(BaseContainerCallbackHandler.class, LogDomains.SECURITY_LOGGER);
protected HandlerContext handlerContext;
protected final SSLUtils sslUtils;
protected final SecuritySupport securitySupport;
protected final MasterPassword masterPasswordHelper;
protected BaseContainerCallbackHandler() {
if (Globals.getDefaultHabitat() == null) {
sslUtils = new SSLUtils();
securitySupport = SecuritySupport.getDefaultInstance();
masterPasswordHelper = null;
sslUtils.postConstruct();
} else {
sslUtils = Globals.getDefaultHabitat().getService(SSLUtils.class);
securitySupport = Globals.getDefaultHabitat().getService(SecuritySupport.class);
masterPasswordHelper = Globals.getDefaultHabitat().getService(MasterPassword.class, "Security SSL Password Provider Service");
}
}
@Override
public void setHandlerContext(HandlerContext handlerContext) {
this.handlerContext = handlerContext;
}
/*
* To be implemented by a sub-class. The sub class decides which callbacks it supports.
* EjbServletWSSCallbackHandler supports: SecretKeyCallback TrustStoreCallback
* PasswordValidationCallback CertStoreCallback PrivateKeyCallback
* AppclientWSSCallbackHandler supports: NameCallback PasswordCallback ChoiceCallback
*/
protected abstract boolean isSupportedCallback(Callback callback);
protected abstract void handleSupportedCallbacks(Callback[] callbacks) throws IOException, UnsupportedCallbackException;
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
if (callbacks == null) {
return;
}
for (Callback callback : callbacks) {
if (!isSupportedCallback(callback)) {
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE, "JASPIC: UnsupportedCallback : " + callback.getClass().getName());
}
throw new UnsupportedCallbackException(callback);
}
}
handleSupportedCallbacks(callbacks);
}
/**
* gets the appropriate callback processor and hands the callback to processor to process the callback.
*/
protected void processCallback(Callback callback) throws UnsupportedCallbackException {
if (callback instanceof CallerPrincipalCallback) {
processCallerPrincipal((CallerPrincipalCallback) callback);
} else if (callback instanceof GroupPrincipalCallback) {
processGroupPrincipal((GroupPrincipalCallback) callback);
} else if (callback instanceof PasswordValidationCallback) {
processPasswordValidation((PasswordValidationCallback) callback);
} else if (callback instanceof PrivateKeyCallback) {
processPrivateKey((PrivateKeyCallback) callback);
} else if (callback instanceof TrustStoreCallback) {
TrustStoreCallback tstoreCallback = (TrustStoreCallback) callback;
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE, "JASPIC: In TrustStoreCallback Processor");
}
tstoreCallback.setTrustStore(sslUtils.getMergedTrustStore());
} else if (callback instanceof CertStoreCallback) {
processCertStore((CertStoreCallback) callback);
} else if (callback instanceof SecretKeyCallback) {
processSecretKey((SecretKeyCallback) callback);
} else {
// sanity check =- should never come here.
// the isSupportedCallback method already takes care of this case
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE, "JASPIC: UnsupportedCallback : " + callback.getClass().getName());
}
throw new UnsupportedCallbackException(callback);
}
}
/**
* This method will distinguish the initiator principal (of the SecurityContext obtained from the WebPrincipal) as the
* caller principal, and copy all the other principals into the subject....
*
* It is assumed that the input WebPrincipal is coming from a SAM, and that it was created either by the SAM (as
* described below) or by calls to the LoginContextDriver made by an Authenticator.
*
* A WebPrincipal constructed by the RealmAdapter will include a DPC; other constructions may not; this method
* interprets the absence of a DPC as evidence that the resulting WebPrincipal was not constructed by the RealmAdapter
* as described below. Note that presence of a DPC does not necessarily mean that the resulting WebPrincipal was
* constructed by the RealmAdapter... since some authenticators also add the credential).
*
* A. handling of CPCB by CBH:
*
* 1. handling of CPC by CBH modifies subject a. constructs principalImpl if called by name b. uses LoginContextDriver
* to add group principals for name c. puts principal in principal set, and DPC in public credentials
*
* B. construction of WebPrincipal by RealmAdapter (occurs after SAM uses CBH to set other than an unauthenticated
* result in the subject:
*
* a. SecurityContext construction done with subject (returned by SAM). Construction sets initiator/caller principal
* within SC from DPC set by CBH in public credentials of subject
*
* b WebPrincipal is constructed with initiator principal and SecurityContext
*
* @param fs receiving Subject
* @param wp WebPrincipal
*
* @return true when Security Context has been obtained from webPrincipal, and CB is finished. returns false when more
* CB processing is required.
*/
private boolean reuseWebPrincipal(final Subject fs, final WebPrincipal wp) {
SecurityContext sc = wp.getSecurityContext();
final Subject wps = sc != null ? sc.getSubject() : null;
final Principal callerPrincipal = sc != null ? sc.getCallerPrincipal() : null;
final Principal defaultPrincipal = SecurityContext.getDefaultCallerPrincipal();
return ((Boolean) AppservAccessController.doPrivileged(new PrivilegedAction() {
/**
* this method uses 4 (numbered) criteria to determine if the argument WebPrincipal can be reused
*/
@Override
public Boolean run() {
/*
* 1. WebPrincipal must contain a SecurityContext and SC must have a non-null, non-default callerPrincipal and a Subject
*/
if (callerPrincipal == null || callerPrincipal.equals(defaultPrincipal) || wps == null) {
return Boolean.FALSE;
}
boolean hasObject = false;
Set distinguishedCreds = wps.getPublicCredentials(DistinguishedPrincipalCredential.class);
if (distinguishedCreds.size() == 1) {
for (DistinguishedPrincipalCredential cred : distinguishedCreds) {
if (cred.getPrincipal().equals(callerPrincipal)) {
hasObject = true;
}
}
}
/**
* 2. Subject within SecurityContext must contain a single DPC that identifies the Caller Principal
*/
if (!hasObject) {
return Boolean.FALSE;
}
hasObject = wps.getPrincipals().contains(callerPrincipal);
/**
* 3. Subject within SecurityContext must contain the caller principal
*/
if (!hasObject) {
return Boolean.FALSE;
}
/**
* 4. The webPrincipal must have a non null name that equals the name of the callerPrincipal.
*/
if (wp.getName() == null || !wp.getName().equals(callerPrincipal.getName())) {
return Boolean.FALSE;
}
/*
* remove any existing DistinguishedPrincipalCredentials from receiving Subject
*
*/
Iterator iter = fs.getPublicCredentials().iterator();
while (iter.hasNext()) {
Object obj = iter.next();
if (obj instanceof DistinguishedPrincipalCredential) {
iter.remove();
}
}
/**
* Copy principals from Subject within SecurityContext to receiving Subject
*/
for (Principal p : wps.getPrincipals()) {
fs.getPrincipals().add(p);
}
/**
* Copy public credentials from Subject within SecurityContext to receiving Subject
*/
for (Object publicCred : wps.getPublicCredentials()) {
fs.getPublicCredentials().add(publicCred);
}
/**
* Copy private credentials from Subject within SecurityContext to receiving Subject
*/
for (Object privateCred : wps.getPrivateCredentials()) {
fs.getPrivateCredentials().add(privateCred);
}
return Boolean.TRUE;
}
})).booleanValue();
}
private void processCallerPrincipal(CallerPrincipalCallback callerPrincipalCallback) {
Subject subject = callerPrincipalCallback.getSubject();
Principal principal = callerPrincipalCallback.getPrincipal();
// PAYARA-755 If the SAM has set a custom principal then we check that the original WebPrincipal has
// the same custom principal within it
if (principal != null && !(principal instanceof WebPrincipal)) {
Principal additional = SecurityContext.getCurrent().getAdditionalPrincipal();
if ((additional != null) && (additional instanceof WebPrincipal)
&& ((WebPrincipal) additional).getCustomPrincipal() == principal) {
principal = additional;
}
}
if (principal instanceof WebPrincipal) {
WebPrincipal webPrincipal = (WebPrincipal) principal;
/**
* Check if the WebPrincipal satisfies the criteria for reuse. If it does, the CBH will have already
* copied its contents into the Subject, and established the caller principal.
*/
if (reuseWebPrincipal(subject, webPrincipal)) {
return;
}
/**
* Otherwise the webPrincipal must be distinguished as the callerPrincipal, but the contents of its
* internal SecurityContext will not be copied. For the special case where the WebPrincipal
* represents the defaultCallerPrincipal, the argument principal is set to null to cause the handler
* to assign its representation of the unauthenticated caller in the Subject.
*/
Principal defaultCallerPrincipal = SecurityContext.getDefaultCallerPrincipal();
SecurityContext securityContext = webPrincipal.getSecurityContext();
Principal callerPrincipal = securityContext != null ? securityContext.getCallerPrincipal() : null;
if (webPrincipal.getName() == null || webPrincipal.equals(defaultCallerPrincipal) || callerPrincipal == null || callerPrincipal.equals(defaultCallerPrincipal)) {
principal = null;
}
}
String realmName = null;
if (handlerContext != null) {
realmName = handlerContext.getRealmName();
}
boolean isCertRealm = CertificateRealm.AUTH_TYPE.equals(realmName);
if (principal == null) {
if (callerPrincipalCallback.getName() != null) {
if (isCertRealm) {
principal = new X500Principal(callerPrincipalCallback.getName());
} else {
principal = new PrincipalImpl(callerPrincipalCallback.getName());
}
} else {
// Jakarta Authentication unauthenticated caller principal
principal = SecurityContext.getDefaultCallerPrincipal();
}
}
if (isCertRealm) {
if (principal instanceof X500Principal) {
JaspicToJaasBridge.jaasX500Login(subject, (X500Principal) principal);
}
} else {
if (!principal.equals(getDefaultCallerPrincipal())) {
JaspicToJaasBridge.addRealmGroupsToSubject(subject, principal.getName(), realmName);
}
}
final Principal finalPrincipal = principal;
DistinguishedPrincipalCredential distinguishedPrincipalCredential = new DistinguishedPrincipalCredential(principal);
privileged(() -> {
subject.getPrincipals().add(finalPrincipal);
Iterator