GssApiWithMicAuthentication.java
- /*
- * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Distribution License v. 1.0 which is available at
- * https://www.eclipse.org/org/documents/edl-v10.php.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
- package org.eclipse.jgit.internal.transport.sshd;
- import static java.text.MessageFormat.format;
- import java.io.IOException;
- import java.net.InetAddress;
- import java.net.InetSocketAddress;
- import java.net.SocketAddress;
- import java.net.UnknownHostException;
- import java.util.Collection;
- import java.util.Iterator;
- import org.apache.sshd.client.auth.AbstractUserAuth;
- import org.apache.sshd.client.session.ClientSession;
- import org.apache.sshd.common.SshConstants;
- import org.apache.sshd.common.util.buffer.Buffer;
- import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
- import org.ietf.jgss.GSSContext;
- import org.ietf.jgss.GSSException;
- import org.ietf.jgss.MessageProp;
- import org.ietf.jgss.Oid;
- /**
- * GSSAPI-with-MIC authentication handler (Kerberos 5).
- *
- * @see <a href="https://tools.ietf.org/html/rfc4462">RFC 4462</a>
- */
- public class GssApiWithMicAuthentication extends AbstractUserAuth {
- /** Synonym used in RFC 4462. */
- private static final byte SSH_MSG_USERAUTH_GSSAPI_RESPONSE = SshConstants.SSH_MSG_USERAUTH_INFO_REQUEST;
- /** Synonym used in RFC 4462. */
- private static final byte SSH_MSG_USERAUTH_GSSAPI_TOKEN = SshConstants.SSH_MSG_USERAUTH_INFO_RESPONSE;
- private enum ProtocolState {
- STARTED, TOKENS, MIC_SENT, FAILED
- }
- private Collection<Oid> mechanisms;
- private Iterator<Oid> nextMechanism;
- private Oid currentMechanism;
- private ProtocolState state;
- private GSSContext context;
- /** Creates a new {@link GssApiWithMicAuthentication}. */
- public GssApiWithMicAuthentication() {
- super(GssApiWithMicAuthFactory.NAME);
- }
- @Override
- protected boolean sendAuthDataRequest(ClientSession session, String service)
- throws Exception {
- if (mechanisms == null) {
- mechanisms = GssApiMechanisms.getSupportedMechanisms();
- nextMechanism = mechanisms.iterator();
- }
- if (context != null) {
- close(false);
- }
- if (!nextMechanism.hasNext()) {
- return false;
- }
- state = ProtocolState.STARTED;
- currentMechanism = nextMechanism.next();
- // RFC 4462 states that SPNEGO must not be used with ssh
- while (GssApiMechanisms.SPNEGO.equals(currentMechanism)) {
- if (!nextMechanism.hasNext()) {
- return false;
- }
- currentMechanism = nextMechanism.next();
- }
- try {
- String hostName = getHostName(session);
- context = GssApiMechanisms.createContext(currentMechanism,
- hostName);
- context.requestMutualAuth(true);
- context.requestConf(true);
- context.requestInteg(true);
- context.requestCredDeleg(true);
- context.requestAnonymity(false);
- } catch (GSSException | NullPointerException e) {
- close(true);
- if (log.isDebugEnabled()) {
- log.debug(format(SshdText.get().gssapiInitFailure,
- currentMechanism.toString()));
- }
- currentMechanism = null;
- state = ProtocolState.FAILED;
- return false;
- }
- Buffer buffer = session
- .createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST);
- buffer.putString(session.getUsername());
- buffer.putString(service);
- buffer.putString(getName());
- buffer.putInt(1);
- buffer.putBytes(currentMechanism.getDER());
- session.writePacket(buffer);
- return true;
- }
- @Override
- protected boolean processAuthDataRequest(ClientSession session,
- String service, Buffer in) throws Exception {
- // SSH_MSG_USERAUTH_FAILURE and SSH_MSG_USERAUTH_SUCCESS, as well as
- // SSH_MSG_USERAUTH_BANNER are handled by the framework.
- int command = in.getUByte();
- if (context == null) {
- return false;
- }
- try {
- switch (command) {
- case SSH_MSG_USERAUTH_GSSAPI_RESPONSE: {
- if (state != ProtocolState.STARTED) {
- return unexpectedMessage(command);
- }
- // Initial reply from the server with the mechanism to use.
- Oid mechanism = new Oid(in.getBytes());
- if (!currentMechanism.equals(mechanism)) {
- return false;
- }
- replyToken(session, service, new byte[0]);
- return true;
- }
- case SSH_MSG_USERAUTH_GSSAPI_TOKEN: {
- if (context.isEstablished() || state != ProtocolState.TOKENS) {
- return unexpectedMessage(command);
- }
- // Server sent us a token
- replyToken(session, service, in.getBytes());
- return true;
- }
- default:
- return unexpectedMessage(command);
- }
- } catch (GSSException e) {
- log.warn(format(SshdText.get().gssapiFailure,
- currentMechanism.toString()), e);
- state = ProtocolState.FAILED;
- return false;
- }
- }
- @Override
- public void destroy() {
- try {
- close(false);
- } finally {
- super.destroy();
- }
- }
- private void close(boolean silent) {
- try {
- if (context != null) {
- context.dispose();
- context = null;
- }
- } catch (GSSException e) {
- if (!silent) {
- log.warn(SshdText.get().gssapiFailure, e);
- }
- }
- }
- private void sendToken(ClientSession session, byte[] receivedToken)
- throws IOException, GSSException {
- state = ProtocolState.TOKENS;
- byte[] token = context.initSecContext(receivedToken, 0,
- receivedToken.length);
- if (token != null) {
- Buffer buffer = session.createBuffer(SSH_MSG_USERAUTH_GSSAPI_TOKEN);
- buffer.putBytes(token);
- session.writePacket(buffer);
- }
- }
- private void sendMic(ClientSession session, String service)
- throws IOException, GSSException {
- state = ProtocolState.MIC_SENT;
- // Produce MIC
- Buffer micBuffer = new ByteArrayBuffer();
- micBuffer.putBytes(session.getSessionId());
- micBuffer.putByte(SshConstants.SSH_MSG_USERAUTH_REQUEST);
- micBuffer.putString(session.getUsername());
- micBuffer.putString(service);
- micBuffer.putString(getName());
- byte[] micBytes = micBuffer.getCompactData();
- byte[] mic = context.getMIC(micBytes, 0, micBytes.length,
- new MessageProp(0, true));
- Buffer buffer = session
- .createBuffer(SshConstants.SSH_MSG_USERAUTH_GSSAPI_MIC);
- buffer.putBytes(mic);
- session.writePacket(buffer);
- }
- private void replyToken(ClientSession session, String service, byte[] bytes)
- throws IOException, GSSException {
- sendToken(session, bytes);
- if (context.isEstablished()) {
- sendMic(session, service);
- }
- }
- private String getHostName(ClientSession session) {
- SocketAddress remote = session.getConnectAddress();
- if (remote instanceof InetSocketAddress) {
- InetAddress address = GssApiMechanisms
- .resolve((InetSocketAddress) remote);
- if (address != null) {
- return address.getCanonicalHostName();
- }
- }
- if (session instanceof JGitClientSession) {
- String hostName = ((JGitClientSession) session).getHostConfigEntry()
- .getHostName();
- try {
- hostName = InetAddress.getByName(hostName)
- .getCanonicalHostName();
- } catch (UnknownHostException e) {
- // Ignore here; try with the non-canonical name
- }
- return hostName;
- }
- throw new IllegalStateException(
- "Wrong session class :" + session.getClass().getName()); //$NON-NLS-1$
- }
- private boolean unexpectedMessage(int command) {
- log.warn(format(SshdText.get().gssapiUnexpectedMessage, getName(),
- Integer.toString(command)));
- return false;
- }
- }