package org.atricore.idbus.capabilities.spnego.jaas; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.commons.io.IOUtils; import org.atricore.idbus.capabilities.sts.main.SecurityTokenAuthenticationFailure; import org.ini4j.Ini; import org.ini4j.Wini; import org.osgi.framework.BundleContext; import org.atricore.idbus.kernel.common.support.osgi.OsgiBundleClassLoader; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import java.io.*; public class KerberosServiceInit { private static final Log logger = LogFactory.getLog(KerberosServiceInit.class); public static final String KEYTAB_RESOURCE_BASE = "META-INF/krb5"; private String realm; private String kerberosRealm; private String keyDistributionCenter; private String principal; private String keyTabResource; private boolean configureKerberos; private String keyTabRepository; private String defaultKrb5Config; private boolean authenticatOnInit; private BundleContext bundleContext; public void setBundleContext(BundleContext bundleContext) { this.bundleContext = bundleContext; } public String getPrincipal() { return principal; } public void setPrincipal(String principal) { this.principal = principal; } /** * Name of the JAAS Realm that configures the Kerberos Login Module * @return */ public String getRealm() { return realm; } public void setRealm(String realm) { this.realm = realm; } /** * Name of the Windows Domain (TODO : Remove 'windows' semantic from this module and call this Kerberos Realm ? ) * @return */ public String getKerberosRealm() { return kerberosRealm; } public void setKerberosRealm(String kerberosRealm) { this.kerberosRealm = kerberosRealm; } /** * Domain Controller server name * @return */ public String getKeyDistributionCenter() { return keyDistributionCenter; } public void setKeyDistributionCenter(String keyDistributionCenter) { this.keyDistributionCenter = keyDistributionCenter; } /** * Name of the embedded resource to be found in the classpath that is actually the keytab file * @return */ public String getKeyTabResource() { return keyTabResource; } public void setKeyTabResource(String keyTabResource) { this.keyTabResource = keyTabResource; } /** * Path to deploy the embedded keytab * @return */ public String getKeyTabRepository() { return keyTabRepository; } public void setKeyTabRepository(String keyTabRepository) { this.keyTabRepository = keyTabRepository; } /** * If true, the service will configure kerberos * @return */ public boolean isConfigureKerberos() { return configureKerberos; } public void setConfigureKerberos(boolean configureKerberos) { this.configureKerberos = configureKerberos; } /** * Kerberos 5 confgiruation file * @return */ public String getDefaultKrb5Config() { return defaultKrb5Config; } public void setDefaultKrb5Config(String defaultKrb5Config) { this.defaultKrb5Config = defaultKrb5Config; } public boolean isAuthenticatOnInit() { return authenticatOnInit; } public void setAuthenticatOnInit(boolean authenticatOnInit) { this.authenticatOnInit = authenticatOnInit; } /** * Initialize Kerberos * @throws Exception */ public void init() throws Exception { try { // Check if Kerberos setup must be updated if (configureKerberos) configureKerberos(true); } catch (Exception e) { logger.error("Cannot perform Kerberos configuration :" + e.getMessage(), e); throw e; } try { // Perform an automatic signon, to make sure that everything is working. if (authenticatOnInit) { try { authenticate(new String[] { principal } ); } catch (SecurityException e) { // Do not stop JOSSO startup, perhaps the DC will be back on-line later logger.error("Cannot perform Kerberos Sign-On:" + e.getMessage(), e); } } } catch (Exception e) { logger.error("Error initializing Kerberos " + e.getMessage(), e); throw e; } } public Subject authenticate(Object credentials) throws SecurityException { //sun.security.krb5.Config.getInstance(); if (logger.isDebugEnabled()) logger.debug("Performing automatic authentication using credentials " + credentials); if (!(credentials instanceof String[])) { throw new IllegalArgumentException("Expected String[1], got " + (credentials != null ? credentials.getClass().getName() : null)); } final String[] params = (String[]) credentials; if (params.length != 1) { throw new IllegalArgumentException("Expected String[1] but length was " + params.length); } try { if (logger.isDebugEnabled()) logger.debug("Performing automatic authentication using principal " + params[0]); LoginContext loginContext = new LoginContext(realm, new CallbackHandler() { public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (int i = 0; i < callbacks.length; i++) { if (callbacks[i] instanceof NameCallback) { ((NameCallback) callbacks[i]).setName(params[0]); } else { throw new UnsupportedCallbackException(callbacks[i]); } } } }); loginContext.login(); return loginContext.getSubject(); } catch (LoginException e) { logger.error("Login failure : " + e.getMessage()); throw new SecurityException("Authentication failed", e); } } public void configureKerberos(boolean overwriteExistingSetup) throws Exception { OutputStream keyTabOut = null; InputStream keyTabIn = null; OutputStream krb5ConfOut = null; try { // ============================================================== // Install KeyTab // ============================================================== keyTabIn = loadKeyTabResource(keyTabResource); File file = new File(keyTabRepository + keyTabResource); if (!file.exists() || overwriteExistingSetup) { keyTabOut = new FileOutputStream(file, false); if (logger.isDebugEnabled()) logger.debug("Installing keytab file to : " + file.getAbsolutePath()); IOUtils.copy(keyTabIn, keyTabOut); } // ============================================================== // Update Kerberos setup file: // ============================================================== // 1. read original file and conver to Windows INI format (hack!) File krb5ConfFile = new File(System.getProperty("java.security.krb5.conf", defaultKrb5Config)); if (logger.isDebugEnabled()) logger.debug("Using Kerberos config file : " + krb5ConfFile.getAbsolutePath()); if (!krb5ConfFile.exists()) throw new Exception("Kerberos config file not found : " + krb5ConfFile.getAbsolutePath()); FileInputStream fis = new FileInputStream(krb5ConfFile); Wini krb5Conf = new Wini(KerberosConfigUtil.toIni(fis)); // 2. Setup Kerberos realms Ini.Section krb5Realms = krb5Conf.get("realms"); String windowsDomainSetup = krb5Realms.get(kerberosRealm); if (kerberosRealm == null || overwriteExistingSetup) { windowsDomainSetup = "{ kdc = " + keyDistributionCenter + ":88 admin_server = "+ keyDistributionCenter +":749 default_domain = "+ kerberosRealm.toLowerCase() +" }"; krb5Realms.put(kerberosRealm, windowsDomainSetup); } // 3. Setup Kerberos domain realms Ini.Section krb5DomainRealms = krb5Conf.get("domain_realm"); String domainRealmSetup = krb5DomainRealms.get(kerberosRealm.toLowerCase()); if (domainRealmSetup == null || overwriteExistingSetup) { krb5DomainRealms.put(kerberosRealm.toLowerCase(), kerberosRealm); krb5DomainRealms.put("." + kerberosRealm.toLowerCase(), kerberosRealm); } // 4. Save KRB 5 Conf, convert from ini to Kerberos // Save ini to byte array ByteArrayOutputStream baos = new ByteArrayOutputStream(); krb5Conf.store(baos); // Transform ini to krb5 InputStream bios = new ByteArrayInputStream(baos.toByteArray()); bios = KerberosConfigUtil.toKrb5(bios); // Write krb5 to file krb5ConfOut = new FileOutputStream(krb5ConfFile, false); IOUtils.copy(bios, krb5ConfOut); } catch (Exception e) { logger.error("Error while configuring Kerberos :" + e.getMessage(), e); throw e; } finally { IOUtils.closeQuietly(keyTabOut); IOUtils.closeQuietly(keyTabIn); IOUtils.closeQuietly(krb5ConfOut); } } /** * This will try the default location and the resource name as is. */ public InputStream loadKeyTabResource(String keyTabResource) throws Exception { String resourcePath = KEYTAB_RESOURCE_BASE + "/" + keyTabResource; if (logger.isTraceEnabled()) logger.trace("Loading resource : " + resourcePath); ClassLoader cl = new OsgiBundleClassLoader(bundleContext.getBundle()); InputStream in = cl.getResourceAsStream(resourcePath); if (in == null) { logger.warn("Cannot load keytab resource from bundle classpath using : " + resourcePath); in = cl.getResourceAsStream(keyTabResource); if (in == null) { logger.warn("Cannot load keytab resource from bundle classpath using : " + keyTabResource); } } if (in == null) throw new Exception("Cannot load keytab resource from bundle classpath (check log for details)"); return in; } }