public byte[] requestServiceTicket(Subject clientSubject, String servicePrincipalName) { // For some reason SPNs in the format HTTP/servicehost.domain end up as // HTTP/servicehost.domain/hostname. SPNs defined as [email protected] work fine. servicePrincipalName = servicePrincipalName.replace('/', '@'); try { Oid krb5Oid = new Oid("1.2.840.113554.1.2.2"); GSSManager manager = GSSManager.getInstance(); GSSName serverName = manager.createName(servicePrincipalName, GSSName.NT_HOSTBASED_SERVICE, krb5Oid); final GSSContext context = manager.createContext(serverName, krb5Oid, null, GSSContext.DEFAULT_LIFETIME); byte[] serviceTicket = Subject.doAs( clientSubject, new PrivilegedAction<byte[]>() { public byte[] run() { byte[] token = new byte[0]; // This is a one pass context initialisation. try { context.requestMutualAuth(false); context.requestCredDeleg(false); return context.initSecContext(token, 0, token.length); } catch (GSSException e) { e.printStackTrace(); return null; } } }); return serviceTicket; } catch (GSSException e) { e.printStackTrace(); return null; } }
/** Called to invalidate this credential element. */ public void dispose() throws GSSException { try { destroy(); } catch (javax.security.auth.DestroyFailedException e) { GSSException gssException = new GSSException( GSSException.FAILURE, -1, "Could not destroy credentials - " + e.getMessage()); gssException.initCause(e); } }
/** * Closes the session. If any {@link GSSContext} is present in the session then it is closed. * * @param message the error message */ @Override protected void closeSession(String message) { GSSContext ctx = (GSSContext) getSession().getAttribute(GSS_CONTEXT); if (ctx != null) { try { ctx.dispose(); } catch (GSSException e) { e.printStackTrace(); super.closeSession(message, e); return; } } super.closeSession(message); }
private static KerberosTicket getTgt(int caller, Krb5NameElement name, int initLifetime) throws GSSException { String realm = null; final String clientPrincipal, tgsPrincipal = null; /* * Find the TGT for the realm that the client is in. If the client * name is not available, then use the default realm. */ if (name != null) { clientPrincipal = (name.getKrb5PrincipalName()).getName(); realm = (name.getKrb5PrincipalName()).getRealmAsString(); } else { clientPrincipal = null; try { Config config = Config.getInstance(); realm = config.getDefaultRealm(); } catch (KrbException e) { GSSException ge = new GSSException( GSSException.NO_CRED, -1, "Attempt to obtain INITIATE credentials failed!" + " (" + e.getMessage() + ")"); ge.initCause(e); throw ge; } } final AccessControlContext acc = AccessController.getContext(); try { final int realCaller = (caller == GSSUtil.CALLER_UNKNOWN) ? GSSUtil.CALLER_INITIATE : caller; return AccessController.doPrivileged( new PrivilegedExceptionAction<KerberosTicket>() { public KerberosTicket run() throws Exception { return Krb5Util.getTicket(realCaller, clientPrincipal, tgsPrincipal, acc); } }); } catch (PrivilegedActionException e) { GSSException ge = new GSSException( GSSException.NO_CRED, -1, "Attempt to obtain new INITIATE credentials failed!" + " (" + e.getMessage() + ")"); ge.initCause(e.getException()); throw ge; } }
@Override public Header authenticate( final Credentials credentials, final HttpRequest request, final HttpContext context) throws AuthenticationException { Args.notNull(request, "HTTP request"); switch (state) { case UNINITIATED: throw new AuthenticationException( getSchemeName() + " authentication has not been initiated"); case FAILED: throw new AuthenticationException(getSchemeName() + " authentication has failed"); case CHALLENGE_RECEIVED: try { final HttpRoute route = (HttpRoute) context.getAttribute(HttpClientContext.HTTP_ROUTE); if (route == null) { throw new AuthenticationException("Connection route is not available"); } HttpHost host; if (isProxy()) { host = route.getProxyHost(); if (host == null) { host = route.getTargetHost(); } } else { host = route.getTargetHost(); } final String authServer; String hostname = host.getHostName(); if (this.useCanonicalHostname) { try { // TODO: uncomment this statement and delete the resolveCanonicalHostname, // TODO: as soon canonical hostname resolving is implemented in the // SystemDefaultDnsResolver // final DnsResolver dnsResolver = SystemDefaultDnsResolver.INSTANCE; // hostname = dnsResolver.resolveCanonicalHostname(host.getHostName()); hostname = resolveCanonicalHostname(hostname); } catch (UnknownHostException ignore) { } } if (this.stripPort) { // || host.getPort()==80 || host.getPort()==443) { authServer = hostname; } else { authServer = hostname + ":" + host.getPort(); } if (log.isDebugEnabled()) { log.debug("init " + authServer); } token = generateToken(token, authServer, credentials); state = State.TOKEN_GENERATED; } catch (final GSSException gsse) { state = State.FAILED; if (gsse.getMajor() == GSSException.DEFECTIVE_CREDENTIAL || gsse.getMajor() == GSSException.CREDENTIALS_EXPIRED) { throw new InvalidCredentialsException(gsse.getMessage(), gsse); } if (gsse.getMajor() == GSSException.NO_CRED) { throw new InvalidCredentialsException(gsse.getMessage(), gsse); } if (gsse.getMajor() == GSSException.DEFECTIVE_TOKEN || gsse.getMajor() == GSSException.DUPLICATE_TOKEN || gsse.getMajor() == GSSException.OLD_TOKEN) { throw new AuthenticationException(gsse.getMessage(), gsse); } // other error throw new AuthenticationException(gsse.getMessage()); } case TOKEN_GENERATED: final String tokenstr = new String(base64codec.encode(token)); if (log.isDebugEnabled()) { log.debug("Sending response '" + tokenstr + "' back to the auth server"); } final CharArrayBuffer buffer = new CharArrayBuffer(32); if (isProxy()) { buffer.append(AUTH.PROXY_AUTH_RESP); } else { buffer.append(AUTH.WWW_AUTH_RESP); } buffer.append(": Negotiate "); buffer.append(tokenstr); return new BufferedHeader(buffer); default: throw new IllegalStateException("Illegal state: " + state); } }
void go() throws Exception { OneKDC k = new OneKDC(null); k.writeJAASConf(); Files.delete(Paths.get(OneKDC.KTAB)); // Starts with no keytab c = Context.fromJAAS("client"); s = Context.fromJAAS("com.sun.security.jgss.krb5.accept"); // Test 1: read new key 1 from keytab k.addPrincipal(OneKDC.SERVER, "pass1".toCharArray()); k.writeKtab(OneKDC.KTAB); connect(); // Test 2: service key cached, find 1 in keytab (now contains 1 and 2) k.addPrincipal(OneKDC.SERVER, "pass2".toCharArray()); k.appendKtab(OneKDC.KTAB); connect(); // Test 3: re-login. Now find 2 in keytab c = Context.fromJAAS("client"); connect(); // Test 4: re-login, KDC use 3 this time. c = Context.fromJAAS("client"); // Put 3 and 4 into keytab but keep the real key back to 3. k.addPrincipal(OneKDC.SERVER, "pass3".toCharArray()); k.appendKtab(OneKDC.KTAB); k.addPrincipal(OneKDC.SERVER, "pass4".toCharArray()); k.appendKtab(OneKDC.KTAB); k.addPrincipal(OneKDC.SERVER, "pass3".toCharArray()); connect(); // Test 5: invalid keytab file, should ignore try (FileOutputStream fos = new FileOutputStream(OneKDC.KTAB)) { fos.write("BADBADBAD".getBytes()); } connect(); // Test 6: delete keytab file, identical to revoke all Files.delete(Paths.get(OneKDC.KTAB)); try { connect(); throw new Exception("Should not success"); } catch (GSSException gsse) { System.out.println(gsse); KrbException ke = (KrbException) gsse.getCause(); // KrbApReq.authenticate(*) if (dkey == null)... // This should have been Krb5.KRB_AP_ERR_NOKEY if (ke.returnCode() != Krb5.API_INVALID_ARG) { throw new Exception("Not expected failure code: " + ke.returnCode()); } } // Test 7: 3 revoked, should fail (now contains only 5) k.addPrincipal(OneKDC.SERVER, "pass5".toCharArray()); k.writeKtab(OneKDC.KTAB); // overwrite keytab, which means // old key is revoked try { connect(); throw new Exception("Should not success"); } catch (GSSException gsse) { System.out.println(gsse); // Since 7197159, different kvno is accepted, this return code // will never be thrown out again. // KrbException ke = (KrbException)gsse.getCause(); // if (ke.returnCode() != Krb5.KRB_AP_ERR_BADKEYVER) { // throw new Exception("Not expected failure code: " + // ke.returnCode()); // } } // Test 8: an empty KDC means revoke all KDC.create("EMPTY.REALM").writeKtab(OneKDC.KTAB); try { connect(); throw new Exception("Should not success"); } catch (GSSException gsse) { System.out.println(gsse); KrbException ke = (KrbException) gsse.getCause(); // KrbApReq.authenticate(*) if (dkey == null)... // This should have been Krb5.KRB_AP_ERR_NOKEY if (ke.returnCode() != Krb5.API_INVALID_ARG) { throw new Exception("Not expected failure code: " + ke.returnCode()); } } }
public int initSecContext(InputStream inStream, OutputStream outStream) throws GSSException { if (mechCtxt != null && currentState != IN_PROGRESS) { throw new GSSExceptionImpl(GSSException.FAILURE, "Illegal call to initSecContext"); } GSSHeader gssHeader = null; int inTokenLen = -1; GSSCredentialSpi credElement = null; boolean firstToken = false; try { if (mechCtxt == null) { if (myCred != null) { try { credElement = myCred.getElement(mechOid, true); } catch (GSSException ge) { if (GSSUtil.isSpNegoMech(mechOid) && ge.getMajor() == GSSException.NO_CRED) { credElement = myCred.getElement(myCred.getMechs()[0], true); } else { throw ge; } } } GSSNameSpi nameElement = targName.getElement(mechOid); mechCtxt = gssManager.getMechanismContext(nameElement, credElement, reqLifetime, mechOid); mechCtxt.requestConf(reqConfState); mechCtxt.requestInteg(reqIntegState); mechCtxt.requestCredDeleg(reqCredDelegState); mechCtxt.requestMutualAuth(reqMutualAuthState); mechCtxt.requestReplayDet(reqReplayDetState); mechCtxt.requestSequenceDet(reqSequenceDetState); mechCtxt.requestAnonymity(reqAnonState); mechCtxt.setChannelBinding(channelBindings); mechCtxt.requestDelegPolicy(reqDelegPolicyState); objId = new ObjectIdentifier(mechOid.toString()); currentState = IN_PROGRESS; firstToken = true; } else { if (mechCtxt.getProvider().getName().equals("SunNativeGSS") || GSSUtil.isSpNegoMech(mechOid)) { // do not parse GSS header for native provider or SPNEGO // mech } else { // parse GSS header gssHeader = new GSSHeader(inStream); if (!gssHeader.getOid().equals((Object) objId)) throw new GSSExceptionImpl( GSSException.DEFECTIVE_TOKEN, "Mechanism not equal to " + mechOid.toString() + " in initSecContext token"); inTokenLen = gssHeader.getMechTokenLength(); } } byte[] obuf = mechCtxt.initSecContext(inStream, inTokenLen); int retVal = 0; if (obuf != null) { retVal = obuf.length; if (mechCtxt.getProvider().getName().equals("SunNativeGSS") || (!firstToken && GSSUtil.isSpNegoMech(mechOid))) { // do not add GSS header for native provider or SPNEGO // except for the first SPNEGO token } else { // add GSS header gssHeader = new GSSHeader(objId, obuf.length); retVal += gssHeader.encode(outStream); } outStream.write(obuf); } if (mechCtxt.isEstablished()) currentState = READY; return retVal; } catch (IOException e) { throw new GSSExceptionImpl(GSSException.DEFECTIVE_TOKEN, e.getMessage()); } }
/** * Processes the authentication request. * * @param callbacks * @param state * @return -1 as succeeded; 0 as failed. * @exception AuthLoginException upon any failure. */ public int process(Callback[] callbacks, int state) throws AuthLoginException { int result = ISAuthConstants.LOGIN_IGNORE; if (!getConfigParams()) { initWindowsDesktopSSOAuth(options); } // retrieve the spnego token byte[] spnegoToken = getSPNEGOTokenFromHTTPRequest(getHttpServletRequest()); if (spnegoToken == null) { spnegoToken = getSPNEGOTokenFromCallback(callbacks); } if (spnegoToken == null) { debug.message("spnego token is not valid."); throw new AuthLoginException(amAuthWindowsDesktopSSO, "token", null); } if (debug.messageEnabled()) { debug.message( "SPNEGO token: \n" + DerValue.printByteArray(spnegoToken, 0, spnegoToken.length)); } // parse the spnego token and extract the kerberos mech token from it final byte[] kerberosToken = parseToken(spnegoToken); if (kerberosToken == null) { debug.message("kerberos token is not valid."); throw new AuthLoginException(amAuthWindowsDesktopSSO, "token", null); } if (debug.messageEnabled()) { debug.message( "Kerberos token retrieved from SPNEGO token: \n" + DerValue.printByteArray(kerberosToken, 0, kerberosToken.length)); } // authenticate the user with the kerberos token try { authenticateToken(kerberosToken); debug.message("WindowsDesktopSSO authentication succeeded."); result = ISAuthConstants.LOGIN_SUCCEED; } catch (PrivilegedActionException pe) { Exception e = extractException(pe); if (e instanceof GSSException) { int major = ((GSSException) e).getMajor(); if (major == GSSException.CREDENTIALS_EXPIRED) { debug.message("Credential expired. Re-establish credential..."); serviceLogin(); try { authenticateToken(kerberosToken); debug.message("Authentication succeeded with new cred."); result = ISAuthConstants.LOGIN_SUCCEED; } catch (Exception ee) { debug.message("Authentication failed with new cred."); throw new AuthLoginException(amAuthWindowsDesktopSSO, "auth", null, ee); } } else { debug.message("Authentication failed with GSSException."); throw new AuthLoginException(amAuthWindowsDesktopSSO, "auth", null, e); } } } catch (GSSException e) { int major = e.getMajor(); if (major == GSSException.CREDENTIALS_EXPIRED) { debug.message("Credential expired. Re-establish credential..."); serviceLogin(); try { authenticateToken(kerberosToken); debug.message("Authentication succeeded with new cred."); result = ISAuthConstants.LOGIN_SUCCEED; } catch (Exception ee) { debug.message("Authentication failed with new cred."); throw new AuthLoginException(amAuthWindowsDesktopSSO, "auth", null, ee); } } else { debug.message("Authentication failed with GSSException."); throw new AuthLoginException(amAuthWindowsDesktopSSO, "auth", null, e); } } catch (AuthLoginException e) { throw e; } catch (Exception e) { debug.message("Authentication failed with generic exception."); throw new AuthLoginException(amAuthWindowsDesktopSSO, "auth", null, e); } return result; }
public String generateToken(String authServer) throws Throwable { try { if (this.stripPort) { authServer = authServer.substring(0, authServer.indexOf(":")); } if (log.isDebugEnabled()) { log.debug("init " + authServer); } /* Using the SPNEGO OID is the correct method. * Kerberos v5 works for IIS but not JBoss. Unwrapping * the initial token when using SPNEGO OID looks like what is * described here... * * http://msdn.microsoft.com/en-us/library/ms995330.aspx * * Another helpful URL... * * http://publib.boulder.ibm.com/infocenter/wasinfo/v7r0/index.jsp?topic=/com.ibm.websphere.express.doc/info/exp/ae/tsec_SPNEGO_token.html * * Unfortunately SPNEGO is JRE >=1.6. */ /** Try SPNEGO by default, fall back to Kerberos later if error */ negotiationOid = new Oid(SPNEGO_OID); boolean tryKerberos = false; try { GSSManager manager = GSSManager.getInstance(); GSSName serverName = manager.createName("HTTP/" + authServer, null); gssContext = manager.createContext( serverName.canonicalize(negotiationOid), negotiationOid, null, GSSContext.DEFAULT_LIFETIME); gssContext.requestMutualAuth(true); gssContext.requestCredDeleg(true); } catch (GSSException ex) { log.error("generateToken", ex); // BAD MECH means we are likely to be using 1.5, fall back to Kerberos MECH. // Rethrow any other exception. if (ex.getMajor() == GSSException.BAD_MECH) { log.debug("GSSException BAD_MECH, retry with Kerberos MECH"); tryKerberos = true; } else { throw ex; } } if (tryKerberos) { /* Kerberos v5 GSS-API mechanism defined in RFC 1964.*/ log.debug("Using Kerberos MECH " + KERBEROS_OID); negotiationOid = new Oid(KERBEROS_OID); GSSManager manager = GSSManager.getInstance(); GSSName serverName = manager.createName("HTTP/" + authServer, null); gssContext = manager.createContext( serverName.canonicalize(negotiationOid), negotiationOid, null, GSSContext.DEFAULT_LIFETIME); gssContext.requestMutualAuth(true); gssContext.requestCredDeleg(true); } if (token == null) { token = new byte[0]; } token = gssContext.initSecContext(token, 0, token.length); if (token == null) { throw new Exception("GSS security context initialization failed"); } /* * IIS accepts Kerberos and SPNEGO tokens. Some other servers Jboss, Glassfish? * seem to only accept SPNEGO. Below wraps Kerberos into SPNEGO token. */ if (spengoGenerator != null && negotiationOid.toString().equals(KERBEROS_OID)) { token = spengoGenerator.generateSpnegoDERObject(token); } String tokenstr = new String(Base64.encode(token)); if (log.isDebugEnabled()) { log.debug("Sending response '" + tokenstr + "' back to the auth server"); } return "Negotiate " + tokenstr; } catch (GSSException gsse) { log.error("generateToken", gsse); if (gsse.getMajor() == GSSException.DEFECTIVE_CREDENTIAL || gsse.getMajor() == GSSException.CREDENTIALS_EXPIRED) throw new Exception(gsse.getMessage(), gsse); if (gsse.getMajor() == GSSException.NO_CRED) throw new Exception(gsse.getMessage(), gsse); if (gsse.getMajor() == GSSException.DEFECTIVE_TOKEN || gsse.getMajor() == GSSException.DUPLICATE_TOKEN || gsse.getMajor() == GSSException.OLD_TOKEN) throw new Exception(gsse.getMessage(), gsse); // other error throw new Exception(gsse.getMessage()); } catch (IOException ex) { throw new Exception(ex.getMessage()); } }
@Override public Header authenticate( final Credentials credentials, final HttpRequest request, final HttpContext context) throws AuthenticationException { Args.notNull(request, "HTTP request"); switch (state) { case UNINITIATED: throw new AuthenticationException( getSchemeName() + " authentication has not been initiated"); case FAILED: throw new AuthenticationException(getSchemeName() + " authentication has failed"); case CHALLENGE_RECEIVED: try { final HttpRoute route = (HttpRoute) context.getAttribute(HttpClientContext.HTTP_ROUTE); if (route == null) { throw new AuthenticationException("Connection route is not available"); } HttpHost host; if (isProxy()) { host = route.getProxyHost(); if (host == null) { host = route.getTargetHost(); } } else { host = route.getTargetHost(); } final String authServer; if (!this.stripPort && host.getPort() > 0) { authServer = host.toHostString(); } else { authServer = host.getHostName(); } if (log.isDebugEnabled()) { log.debug("init " + authServer); } token = generateToken(token, authServer); state = State.TOKEN_GENERATED; } catch (final GSSException gsse) { state = State.FAILED; if (gsse.getMajor() == GSSException.DEFECTIVE_CREDENTIAL || gsse.getMajor() == GSSException.CREDENTIALS_EXPIRED) { throw new InvalidCredentialsException(gsse.getMessage(), gsse); } if (gsse.getMajor() == GSSException.NO_CRED) { throw new InvalidCredentialsException(gsse.getMessage(), gsse); } if (gsse.getMajor() == GSSException.DEFECTIVE_TOKEN || gsse.getMajor() == GSSException.DUPLICATE_TOKEN || gsse.getMajor() == GSSException.OLD_TOKEN) { throw new AuthenticationException(gsse.getMessage(), gsse); } // other error throw new AuthenticationException(gsse.getMessage()); } case TOKEN_GENERATED: final String tokenstr = new String(base64codec.encode(token)); if (log.isDebugEnabled()) { log.debug("Sending response '" + tokenstr + "' back to the auth server"); } final CharArrayBuffer buffer = new CharArrayBuffer(32); if (isProxy()) { buffer.append(AUTH.PROXY_AUTH_RESP); } else { buffer.append(AUTH.WWW_AUTH_RESP); } buffer.append(": Negotiate "); buffer.append(tokenstr); return new BufferedHeader(buffer); default: throw new IllegalStateException("Illegal state: " + state); } }