All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.undertow.security.impl.GSSAPIAuthenticationMechanism Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

The newest version!
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 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 io.undertow.security.impl;

import io.undertow.UndertowLogger;
import io.undertow.security.api.AuthenticationMechanism;
import io.undertow.security.api.GSSAPIServerSubjectFactory;
import io.undertow.security.api.SecurityContext;
import io.undertow.security.idm.Account;
import io.undertow.security.idm.GSSContextCredential;
import io.undertow.security.idm.IdentityManager;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.ServerConnection;
import io.undertow.server.handlers.proxy.ExclusivityChecker;
import io.undertow.util.AttachmentKey;
import io.undertow.util.FlexBase64;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.Oid;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.List;
import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosPrincipal;

import static io.undertow.util.Headers.AUTHORIZATION;
import static io.undertow.util.Headers.HOST;
import static io.undertow.util.Headers.NEGOTIATE;
import static io.undertow.util.Headers.WWW_AUTHENTICATE;
import static io.undertow.util.StatusCodes.UNAUTHORIZED;

/**
 * {@link io.undertow.security.api.AuthenticationMechanism} for GSSAPI / SPNEGO based authentication.
 * 

* GSSAPI authentication is associated with the HTTP connection, as long as a connection is being re-used allow the * authentication state to be re-used. *

* TODO - May consider an option to allow it to also be associated with the underlying session but that has it's own risks so * would need to come with a warning. * * @author Darran Lofthouse */ public class GSSAPIAuthenticationMechanism implements AuthenticationMechanism { public static final ExclusivityChecker EXCLUSIVITY_CHECKER = new ExclusivityChecker() { @Override public boolean isExclusivityRequired(HttpServerExchange exchange) { List authHeaders = exchange.getRequestHeaders().get(AUTHORIZATION); if (authHeaders != null) { for (String current : authHeaders) { if (current.startsWith(NEGOTIATE_PREFIX)) { return true; } } } return false; } }; private static final String NEGOTIATION_PLAIN = NEGOTIATE.toString(); private static final String NEGOTIATE_PREFIX = NEGOTIATE + " "; private static final Oid[] DEFAULT_MECHANISMS; static { try { Oid spnego = new Oid("1.3.6.1.5.5.2"); Oid kerberos = new Oid("1.2.840.113554.1.2.2"); DEFAULT_MECHANISMS = new Oid[] { spnego, kerberos }; } catch (GSSException e) { throw new RuntimeException(e); } } private static final String name = "SPNEGO"; private final IdentityManager identityManager; private final GSSAPIServerSubjectFactory subjectFactory; private final Oid[] mechanisms; public GSSAPIAuthenticationMechanism(final GSSAPIServerSubjectFactory subjectFactory, IdentityManager identityManager, Oid ...supportedMechanisms) { this.subjectFactory = subjectFactory; this.identityManager = identityManager; this.mechanisms = supportedMechanisms; } public GSSAPIAuthenticationMechanism(final GSSAPIServerSubjectFactory subjectFactory, Oid ...supportedMechanisms) { this(subjectFactory, null, supportedMechanisms); } public GSSAPIAuthenticationMechanism(final GSSAPIServerSubjectFactory subjectFactory) { this(subjectFactory, DEFAULT_MECHANISMS); } @SuppressWarnings("deprecation") private IdentityManager getIdentityManager(SecurityContext securityContext) { return identityManager != null ? identityManager : securityContext.getIdentityManager(); } @Override public AuthenticationMechanismOutcome authenticate(final HttpServerExchange exchange, final SecurityContext securityContext) { ServerConnection connection = exchange.getConnection(); NegotiationContext negContext = connection.getAttachment(NegotiationContext.ATTACHMENT_KEY); if (negContext != null) { UndertowLogger.SECURITY_LOGGER.debugf("Existing negotiation context found for %s", exchange); exchange.putAttachment(NegotiationContext.ATTACHMENT_KEY, negContext); if (negContext.isEstablished()) { IdentityManager identityManager = getIdentityManager(securityContext); final Account account = identityManager.verify(new GSSContextCredential(negContext.getGssContext())); if (account != null) { securityContext.authenticationComplete(account, name, false); UndertowLogger.SECURITY_LOGGER.debugf("Authenticated as user %s with existing GSSAPI negotiation context for %s", account.getPrincipal().getName(), exchange); return AuthenticationMechanismOutcome.AUTHENTICATED; } else { UndertowLogger.SECURITY_LOGGER.debugf("Failed to authenticate with existing GSSAPI negotiation context for %s", exchange); return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } } } List authHeaders = exchange.getRequestHeaders().get(AUTHORIZATION); if (authHeaders != null) { for (String current : authHeaders) { if (current.startsWith(NEGOTIATE_PREFIX)) { String base64Challenge = current.substring(NEGOTIATE_PREFIX.length()); try { ByteBuffer challenge = FlexBase64.decode(base64Challenge); return runGSSAPI(exchange, challenge, securityContext); } catch (IOException e) { } // By this point we had a header we should have been able to verify but for some reason // it was not correctly structured. return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } } } // No suitable header was found so authentication was not even attempted. return AuthenticationMechanismOutcome.NOT_ATTEMPTED; } public ChallengeResult sendChallenge(final HttpServerExchange exchange, final SecurityContext securityContext) { NegotiationContext negContext = exchange.getAttachment(NegotiationContext.ATTACHMENT_KEY); String header = NEGOTIATION_PLAIN; if (negContext != null) { byte[] responseChallenge = negContext.useResponseToken(); exchange.putAttachment(NegotiationContext.ATTACHMENT_KEY, null); if (responseChallenge != null) { header = NEGOTIATE_PREFIX + FlexBase64.encodeString(responseChallenge, false); } } else { Subject server = null; try { server = subjectFactory.getSubjectForHost(getHostName(exchange)); } catch (GeneralSecurityException e) { // Deliberately ignore - no Subject so don't offer GSSAPI is our main concern here. } if (server == null) { return ChallengeResult.NOT_SENT; } } exchange.getResponseHeaders().add(WWW_AUTHENTICATE, header); UndertowLogger.SECURITY_LOGGER.debugf("Sending GSSAPI challenge for %s", exchange); return new ChallengeResult(true, UNAUTHORIZED); } public AuthenticationMechanismOutcome runGSSAPI(final HttpServerExchange exchange, final ByteBuffer challenge, final SecurityContext securityContext) { try { Subject server = subjectFactory.getSubjectForHost(getHostName(exchange)); // The AcceptSecurityContext takes over responsibility for setting the result. return Subject.doAs(server, new AcceptSecurityContext(exchange, challenge, securityContext)); } catch (GeneralSecurityException e) { UndertowLogger.SECURITY_LOGGER.failedToObtainSubject(exchange, e); return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } catch (PrivilegedActionException e) { UndertowLogger.SECURITY_LOGGER.failedToNegotiateAtGSSAPI(exchange, e.getCause()); return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } } private String getHostName(final HttpServerExchange exchange) { String hostName = exchange.getRequestHeaders().getFirst(HOST); if (hostName != null) { if (hostName.startsWith("[") && hostName.contains("]")) { hostName = hostName.substring(0, hostName.indexOf(']') + 1); } else if (hostName.contains(":")) { hostName = hostName.substring(0, hostName.indexOf(":")); } return hostName; } return null; } private class AcceptSecurityContext implements PrivilegedExceptionAction { private final HttpServerExchange exchange; private final ByteBuffer challenge; private final SecurityContext securityContext; private AcceptSecurityContext(final HttpServerExchange exchange, final ByteBuffer challenge, final SecurityContext securityContext) { this.exchange = exchange; this.challenge = challenge; this.securityContext = securityContext; } public AuthenticationMechanismOutcome run() throws GSSException { NegotiationContext negContext = exchange.getAttachment(NegotiationContext.ATTACHMENT_KEY); if (negContext == null) { negContext = new NegotiationContext(); exchange.putAttachment(NegotiationContext.ATTACHMENT_KEY, negContext); // Also cache it on the connection for future calls. exchange.getConnection().putAttachment(NegotiationContext.ATTACHMENT_KEY, negContext); } GSSContext gssContext = negContext.getGssContext(); if (gssContext == null) { GSSManager manager = GSSManager.getInstance(); GSSCredential credential = manager.createCredential(null, GSSCredential.INDEFINITE_LIFETIME, mechanisms, GSSCredential.ACCEPT_ONLY); gssContext = manager.createContext(credential); negContext.setGssContext(gssContext); } byte[] respToken = gssContext.acceptSecContext(challenge.array(), challenge.arrayOffset(), challenge.limit()); negContext.setResponseToken(respToken); if (negContext.isEstablished()) { if (respToken != null) { // There will be no further challenge but we do have a token so set it here. exchange.getResponseHeaders().add(WWW_AUTHENTICATE, NEGOTIATE_PREFIX + FlexBase64.encodeString(respToken, false)); } IdentityManager identityManager = securityContext.getIdentityManager(); final Account account = identityManager.verify(new GSSContextCredential(negContext.getGssContext())); if (account != null) { securityContext.authenticationComplete(account, name, false); return AuthenticationMechanismOutcome.AUTHENTICATED; } else { return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } } else { // This isn't a failure but as the context is not established another round trip with the client is needed. return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } } } private static class NegotiationContext { static final AttachmentKey ATTACHMENT_KEY = AttachmentKey.create(NegotiationContext.class); private GSSContext gssContext; private byte[] responseToken; private Principal principal; GSSContext getGssContext() { return gssContext; } void setGssContext(GSSContext gssContext) { this.gssContext = gssContext; } byte[] useResponseToken() { // The token only needs to be returned once so clear it once used. try { return responseToken; } finally { responseToken = null; } } void setResponseToken(byte[] responseToken) { this.responseToken = responseToken; } boolean isEstablished() { return gssContext != null ? gssContext.isEstablished() : false; } Principal getPrincipal() { if (!isEstablished()) { throw new IllegalStateException("No established GSSContext to use for the Principal."); } if (principal == null) { try { principal = new KerberosPrincipal(gssContext.getSrcName().toString()); } catch (GSSException e) { throw new IllegalStateException("Unable to create Principal", e); } } return principal; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy