package org.tmatesoft.svn.core.internal.io.dav.http; import java.io.IOException; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.Arrays; import java.util.HashMap; import java.util.Map; 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.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSCredential; import org.ietf.jgss.GSSException; import org.ietf.jgss.GSSManager; import org.ietf.jgss.GSSName; import org.ietf.jgss.Oid; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.internal.util.SVNBase64; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.util.SVNDebugLog; import org.tmatesoft.svn.util.SVNLogType; /** * @version 1.3 * @author TMate Software Ltd. */ public class DefaultHTTPNegotiateAuthentication extends HTTPNegotiateAuthentication { private static final String NEGOTIATE_TYPE_PROPERTY = "svnkit.negotiate.type"; private static final String NEGOTIATE_TYPE_SPNEGO = "spnego"; private static final String NEGOTIATE_TYPE_KERBEROS = "krb"; private static Map<String, Oid> ourOids = new HashMap<String, Oid>(); static { try { ourOids.put(NEGOTIATE_TYPE_KERBEROS, new Oid("1.2.840.113554.1.2.2") ); } catch (GSSException e) { SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, e); } try { ourOids.put(NEGOTIATE_TYPE_SPNEGO, new Oid("1.3.6.1.5.5.2") ); } catch (GSSException e) { SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, e); } } private static Oid getDefaultOID() { String defaultOid = System.getProperty(NEGOTIATE_TYPE_PROPERTY, NEGOTIATE_TYPE_KERBEROS); if (defaultOid == null || "".equals(defaultOid)) { defaultOid = NEGOTIATE_TYPE_KERBEROS; } Oid oid = (Oid) ourOids.get(defaultOid); if (oid != null) { return oid; } return (Oid) ourOids.get(NEGOTIATE_TYPE_KERBEROS); } private class SVNKitCallbackHandler implements 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(getUserName()); } else if (callbacks[i] instanceof PasswordCallback) { ((PasswordCallback)callbacks[i]).setPassword(getPassword()); } } } } private static volatile Boolean ourIsNegotiateSupported; private GSSManager myGSSManager = GSSManager.getInstance(); private GSSContext myGSSContext; private Oid mySpnegoOid; private Subject mySubject; public DefaultHTTPNegotiateAuthentication(DefaultHTTPNegotiateAuthentication prevAuth) { if (prevAuth != null) { mySubject = prevAuth.mySubject; } } public DefaultHTTPNegotiateAuthentication() { this(null); } public static synchronized boolean isSupported() { if (ourIsNegotiateSupported == null) { Oid spnegoOid = getDefaultOID(); Oid[] supportedOids = GSSManager.getInstance().getMechs(); for (int i = 0; i < supportedOids.length; i++) { SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "NEGOTIATE: supported OID: " + supportedOids[i]); } ourIsNegotiateSupported = Boolean.valueOf(Arrays.asList(GSSManager.getInstance().getMechs()).contains(spnegoOid)); } return ourIsNegotiateSupported.booleanValue(); } private byte[] myToken; private int myTokenLength; public void respondTo(String challenge) { SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "NEGOTIATE: respond to, challenge: " + challenge); if (challenge == null) { myToken = new byte[0]; myTokenLength = 0; } else { myToken = new byte[(challenge.length() * 3 + 3) / 4]; myTokenLength = SVNBase64.base64ToByteArray(new StringBuffer(challenge), myToken); } SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "NEGOTIATE: respond to, token length: " + myTokenLength); } private void initializeSubject() { SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "NEGOTIATE: initialize subject"); if (mySubject != null) { return; } try { LoginContext ctx = new LoginContext("com.sun.security.jgss.krb5.initiate", new SVNKitCallbackHandler()); SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "NEGOTIATE: initialize subject, login context: " + ctx); ctx.login(); mySubject = ctx.getSubject(); SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "NEGOTIATE: initialize subject, subject: " + mySubject); } catch (LoginException e) { SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, e); // SecurityException rethrown = new SecurityException(); // rethrown.initCause(e); // throw rethrown; } } private void initializeContext() throws GSSException { if (mySpnegoOid == null) { mySpnegoOid = getDefaultOID(); } SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "NEGOTIATE: initialize context, OID: " + mySpnegoOid); GSSCredential credentials = myGSSManager.createCredential(GSSCredential.INITIATE_ONLY); GSSName serverName = myGSSManager.createName(getServerPrincipalName(), GSSName.NT_HOSTBASED_SERVICE); SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "NEGOTIATE: initialize context, server name: " + serverName); myGSSContext = myGSSManager.createContext(serverName, mySpnegoOid, credentials, GSSContext.DEFAULT_LIFETIME); SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "NEGOTIATE: initialize context, GSS Context: " + myGSSContext); } public String authenticate() throws SVNException { SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "NEGOTIATE: authenticate: isStarted:" + isStarted()); if (!isStarted()) { initializeSubject(); } final PrivilegedExceptionAction<String> action = new PrivilegedExceptionAction<String>() { public String run() throws SVNException { SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "NEGOTIATE: authenticate action: isStarted: " + isStarted()); if (!isStarted()) { try { initializeContext(); SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "NEGOTIATE: authenticate action: context initializaed"); } catch (GSSException gsse) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_NOT_AUTHORIZED, "Negotiate authentication failed: ''{0}''", gsse.getMajorString()); SVNErrorManager.error(err, SVNLogType.NETWORK); return null; } } byte[] outtoken; try { myGSSContext.requestCredDeleg(true); outtoken = myGSSContext.initSecContext(myToken, 0, myTokenLength); SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "NEGOTIATE: authenticate action: out token: " + outtoken); if (outtoken != null) { SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "NEGOTIATE: authenticate action: out token: " + SVNBase64.byteArrayToBase64(outtoken)); } } catch (GSSException gsse) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_NOT_AUTHORIZED, "Negotiate authentication failed: ''{0}''", gsse.getMajorString()); SVNErrorManager.error(err, SVNLogType.NETWORK); return null; } if (myToken != null) { return "Negotiate " + SVNBase64.byteArrayToBase64(outtoken); } SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "NEGOTIATE: authenticate action: myToken is null"); return null; } }; SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "NEGOTIATE: authenticate: subject:" + mySubject); if (mySubject != null) { try { String result = (String) Subject.doAs(mySubject, action); SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "NEGOTIATE: authenticate: result:" + result); return result; } catch (PrivilegedActionException e) { SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, e); Throwable cause = e.getCause(); SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, cause); if (cause instanceof SVNException) { throw (SVNException)cause; } SVNErrorManager.error(SVNErrorMessage.create(SVNErrorCode.RA_NOT_AUTHORIZED, e), SVNLogType.NETWORK); } } try { String result = action.run(); SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "NEGOTIATE: authenticate: result (2):" + result); return result; } catch (Exception cause) { if (cause instanceof SVNException) { throw (SVNException) cause; } SVNErrorManager.error(SVNErrorMessage.create(SVNErrorCode.RA_NOT_AUTHORIZED, cause), SVNLogType.NETWORK); } return null; } public boolean isStarted() { SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "NEGOTIATE: isStarted: " + myGSSContext); return myGSSContext != null; } public boolean needsLogin() { SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "NEGOTIATE: needsLogin"); initializeSubject(); SVNDebugLog.getDefaultLog().logFine(SVNLogType.NETWORK, "NEGOTIATE: needsLogin, mySubject: " + mySubject); return mySubject == null; } }