You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

GssApiWithMicAuthentication.java 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. /*
  2. * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.internal.transport.sshd;
  11. import static java.text.MessageFormat.format;
  12. import java.io.IOException;
  13. import java.net.InetAddress;
  14. import java.net.InetSocketAddress;
  15. import java.net.SocketAddress;
  16. import java.net.UnknownHostException;
  17. import java.util.Collection;
  18. import java.util.Iterator;
  19. import org.apache.sshd.client.auth.AbstractUserAuth;
  20. import org.apache.sshd.client.session.ClientSession;
  21. import org.apache.sshd.common.SshConstants;
  22. import org.apache.sshd.common.util.buffer.Buffer;
  23. import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
  24. import org.ietf.jgss.GSSContext;
  25. import org.ietf.jgss.GSSException;
  26. import org.ietf.jgss.MessageProp;
  27. import org.ietf.jgss.Oid;
  28. /**
  29. * GSSAPI-with-MIC authentication handler (Kerberos 5).
  30. *
  31. * @see <a href="https://tools.ietf.org/html/rfc4462">RFC 4462</a>
  32. */
  33. public class GssApiWithMicAuthentication extends AbstractUserAuth {
  34. /** Synonym used in RFC 4462. */
  35. private static final byte SSH_MSG_USERAUTH_GSSAPI_RESPONSE = SshConstants.SSH_MSG_USERAUTH_INFO_REQUEST;
  36. /** Synonym used in RFC 4462. */
  37. private static final byte SSH_MSG_USERAUTH_GSSAPI_TOKEN = SshConstants.SSH_MSG_USERAUTH_INFO_RESPONSE;
  38. private enum ProtocolState {
  39. STARTED, TOKENS, MIC_SENT, FAILED
  40. }
  41. private Collection<Oid> mechanisms;
  42. private Iterator<Oid> nextMechanism;
  43. private Oid currentMechanism;
  44. private ProtocolState state;
  45. private GSSContext context;
  46. /** Creates a new {@link GssApiWithMicAuthentication}. */
  47. public GssApiWithMicAuthentication() {
  48. super(GssApiWithMicAuthFactory.NAME);
  49. }
  50. @Override
  51. protected boolean sendAuthDataRequest(ClientSession session, String service)
  52. throws Exception {
  53. if (mechanisms == null) {
  54. mechanisms = GssApiMechanisms.getSupportedMechanisms();
  55. nextMechanism = mechanisms.iterator();
  56. }
  57. if (context != null) {
  58. close(false);
  59. }
  60. if (!nextMechanism.hasNext()) {
  61. return false;
  62. }
  63. state = ProtocolState.STARTED;
  64. currentMechanism = nextMechanism.next();
  65. // RFC 4462 states that SPNEGO must not be used with ssh
  66. while (GssApiMechanisms.SPNEGO.equals(currentMechanism)) {
  67. if (!nextMechanism.hasNext()) {
  68. return false;
  69. }
  70. currentMechanism = nextMechanism.next();
  71. }
  72. try {
  73. String hostName = getHostName(session);
  74. context = GssApiMechanisms.createContext(currentMechanism,
  75. hostName);
  76. context.requestMutualAuth(true);
  77. context.requestConf(true);
  78. context.requestInteg(true);
  79. context.requestCredDeleg(true);
  80. context.requestAnonymity(false);
  81. } catch (GSSException | NullPointerException e) {
  82. close(true);
  83. if (log.isDebugEnabled()) {
  84. log.debug(format(SshdText.get().gssapiInitFailure,
  85. currentMechanism.toString()));
  86. }
  87. currentMechanism = null;
  88. state = ProtocolState.FAILED;
  89. return false;
  90. }
  91. Buffer buffer = session
  92. .createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST);
  93. buffer.putString(session.getUsername());
  94. buffer.putString(service);
  95. buffer.putString(getName());
  96. buffer.putInt(1);
  97. buffer.putBytes(currentMechanism.getDER());
  98. session.writePacket(buffer);
  99. return true;
  100. }
  101. @Override
  102. protected boolean processAuthDataRequest(ClientSession session,
  103. String service, Buffer in) throws Exception {
  104. // SSH_MSG_USERAUTH_FAILURE and SSH_MSG_USERAUTH_SUCCESS, as well as
  105. // SSH_MSG_USERAUTH_BANNER are handled by the framework.
  106. int command = in.getUByte();
  107. if (context == null) {
  108. return false;
  109. }
  110. try {
  111. switch (command) {
  112. case SSH_MSG_USERAUTH_GSSAPI_RESPONSE: {
  113. if (state != ProtocolState.STARTED) {
  114. return unexpectedMessage(command);
  115. }
  116. // Initial reply from the server with the mechanism to use.
  117. Oid mechanism = new Oid(in.getBytes());
  118. if (!currentMechanism.equals(mechanism)) {
  119. return false;
  120. }
  121. replyToken(session, service, new byte[0]);
  122. return true;
  123. }
  124. case SSH_MSG_USERAUTH_GSSAPI_TOKEN: {
  125. if (context.isEstablished() || state != ProtocolState.TOKENS) {
  126. return unexpectedMessage(command);
  127. }
  128. // Server sent us a token
  129. replyToken(session, service, in.getBytes());
  130. return true;
  131. }
  132. default:
  133. return unexpectedMessage(command);
  134. }
  135. } catch (GSSException e) {
  136. log.warn(format(SshdText.get().gssapiFailure,
  137. currentMechanism.toString()), e);
  138. state = ProtocolState.FAILED;
  139. return false;
  140. }
  141. }
  142. @Override
  143. public void destroy() {
  144. try {
  145. close(false);
  146. } finally {
  147. super.destroy();
  148. }
  149. }
  150. private void close(boolean silent) {
  151. try {
  152. if (context != null) {
  153. context.dispose();
  154. context = null;
  155. }
  156. } catch (GSSException e) {
  157. if (!silent) {
  158. log.warn(SshdText.get().gssapiFailure, e);
  159. }
  160. }
  161. }
  162. private void sendToken(ClientSession session, byte[] receivedToken)
  163. throws IOException, GSSException {
  164. state = ProtocolState.TOKENS;
  165. byte[] token = context.initSecContext(receivedToken, 0,
  166. receivedToken.length);
  167. if (token != null) {
  168. Buffer buffer = session.createBuffer(SSH_MSG_USERAUTH_GSSAPI_TOKEN);
  169. buffer.putBytes(token);
  170. session.writePacket(buffer);
  171. }
  172. }
  173. private void sendMic(ClientSession session, String service)
  174. throws IOException, GSSException {
  175. state = ProtocolState.MIC_SENT;
  176. // Produce MIC
  177. Buffer micBuffer = new ByteArrayBuffer();
  178. micBuffer.putBytes(session.getSessionId());
  179. micBuffer.putByte(SshConstants.SSH_MSG_USERAUTH_REQUEST);
  180. micBuffer.putString(session.getUsername());
  181. micBuffer.putString(service);
  182. micBuffer.putString(getName());
  183. byte[] micBytes = micBuffer.getCompactData();
  184. byte[] mic = context.getMIC(micBytes, 0, micBytes.length,
  185. new MessageProp(0, true));
  186. Buffer buffer = session
  187. .createBuffer(SshConstants.SSH_MSG_USERAUTH_GSSAPI_MIC);
  188. buffer.putBytes(mic);
  189. session.writePacket(buffer);
  190. }
  191. private void replyToken(ClientSession session, String service, byte[] bytes)
  192. throws IOException, GSSException {
  193. sendToken(session, bytes);
  194. if (context.isEstablished()) {
  195. sendMic(session, service);
  196. }
  197. }
  198. private String getHostName(ClientSession session) {
  199. SocketAddress remote = session.getConnectAddress();
  200. if (remote instanceof InetSocketAddress) {
  201. InetAddress address = GssApiMechanisms
  202. .resolve((InetSocketAddress) remote);
  203. if (address != null) {
  204. return address.getCanonicalHostName();
  205. }
  206. }
  207. if (session instanceof JGitClientSession) {
  208. String hostName = ((JGitClientSession) session).getHostConfigEntry()
  209. .getHostName();
  210. try {
  211. hostName = InetAddress.getByName(hostName)
  212. .getCanonicalHostName();
  213. } catch (UnknownHostException e) {
  214. // Ignore here; try with the non-canonical name
  215. }
  216. return hostName;
  217. }
  218. throw new IllegalStateException(
  219. "Wrong session class :" + session.getClass().getName()); //$NON-NLS-1$
  220. }
  221. private boolean unexpectedMessage(int command) {
  222. log.warn(format(SshdText.get().gssapiUnexpectedMessage, getName(),
  223. Integer.toString(command)));
  224. return false;
  225. }
  226. }