1
2
3
4
5
6
7
8
9
10 package org.eclipse.jgit.internal.transport.sshd;
11
12 import static java.text.MessageFormat.format;
13
14 import java.io.IOException;
15 import java.net.InetAddress;
16 import java.net.InetSocketAddress;
17 import java.net.SocketAddress;
18 import java.net.UnknownHostException;
19 import java.util.Collection;
20 import java.util.Iterator;
21 import java.util.List;
22
23 import org.apache.sshd.client.auth.AbstractUserAuth;
24 import org.apache.sshd.client.session.ClientSession;
25 import org.apache.sshd.common.SshConstants;
26 import org.apache.sshd.common.util.buffer.Buffer;
27 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
28 import org.ietf.jgss.GSSContext;
29 import org.ietf.jgss.GSSException;
30 import org.ietf.jgss.MessageProp;
31 import org.ietf.jgss.Oid;
32
33
34
35
36
37
38 public class GssApiWithMicAuthentication extends AbstractUserAuth {
39
40
41 private static final byte SSH_MSG_USERAUTH_GSSAPI_RESPONSE = SshConstants.SSH_MSG_USERAUTH_INFO_REQUEST;
42
43
44 private static final byte SSH_MSG_USERAUTH_GSSAPI_TOKEN = SshConstants.SSH_MSG_USERAUTH_INFO_RESPONSE;
45
46 private enum ProtocolState {
47 STARTED, TOKENS, MIC_SENT, FAILED
48 }
49
50 private Collection<Oid> mechanisms;
51
52 private Iterator<Oid> nextMechanism;
53
54 private Oid currentMechanism;
55
56 private ProtocolState state;
57
58 private GSSContext context;
59
60
61 public GssApiWithMicAuthentication() {
62 super(GssApiWithMicAuthFactory.NAME);
63 }
64
65 @Override
66 protected boolean sendAuthDataRequest(ClientSession session, String service)
67 throws Exception {
68 if (mechanisms == null) {
69 mechanisms = GssApiMechanisms.getSupportedMechanisms();
70 nextMechanism = mechanisms.iterator();
71 }
72 if (context != null) {
73 close(false);
74 }
75 GssApiWithMicAuthenticationReporter reporter = session.getAttribute(
76 GssApiWithMicAuthenticationReporter.GSS_AUTHENTICATION_REPORTER);
77 if (!nextMechanism.hasNext()) {
78 reporter.signalAuthenticationExhausted(session, service);
79 return false;
80 }
81 state = ProtocolState.STARTED;
82 currentMechanism = nextMechanism.next();
83
84 while (GssApiMechanisms.SPNEGO.equals(currentMechanism)) {
85 if (!nextMechanism.hasNext()) {
86 reporter.signalAuthenticationExhausted(session, service);
87 return false;
88 }
89 currentMechanism = nextMechanism.next();
90 }
91 try {
92 String hostName = getHostName(session);
93 context = GssApiMechanisms.createContext(currentMechanism,
94 hostName);
95 context.requestMutualAuth(true);
96 context.requestConf(true);
97 context.requestInteg(true);
98 context.requestCredDeleg(true);
99 context.requestAnonymity(false);
100 } catch (GSSException | NullPointerException e) {
101 close(true);
102 if (log.isDebugEnabled()) {
103 log.debug(format(SshdText.get().gssapiInitFailure,
104 currentMechanism.toString()));
105 }
106 currentMechanism = null;
107 state = ProtocolState.FAILED;
108 return false;
109 }
110 if (reporter != null) {
111 reporter.signalAuthenticationAttempt(session, service,
112 currentMechanism.toString());
113 }
114 Buffer buffer = session
115 .createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST);
116 buffer.putString(session.getUsername());
117 buffer.putString(service);
118 buffer.putString(getName());
119 buffer.putInt(1);
120 buffer.putBytes(currentMechanism.getDER());
121 session.writePacket(buffer);
122 return true;
123 }
124
125 @Override
126 protected boolean processAuthDataRequest(ClientSession session,
127 String service, Buffer in) throws Exception {
128
129
130 int command = in.getUByte();
131 if (context == null) {
132 return false;
133 }
134 try {
135 switch (command) {
136 case SSH_MSG_USERAUTH_GSSAPI_RESPONSE: {
137 if (state != ProtocolState.STARTED) {
138 return unexpectedMessage(command);
139 }
140
141 Oid mechanism = new Oid(in.getBytes());
142 if (!currentMechanism.equals(mechanism)) {
143 return false;
144 }
145 replyToken(session, service, new byte[0]);
146 return true;
147 }
148 case SSH_MSG_USERAUTH_GSSAPI_TOKEN: {
149 if (context.isEstablished() || state != ProtocolState.TOKENS) {
150 return unexpectedMessage(command);
151 }
152
153 replyToken(session, service, in.getBytes());
154 return true;
155 }
156 default:
157 return unexpectedMessage(command);
158 }
159 } catch (GSSException e) {
160 log.warn(format(SshdText.get().gssapiFailure,
161 currentMechanism.toString()), e);
162 state = ProtocolState.FAILED;
163 return false;
164 }
165 }
166
167 @Override
168 public void destroy() {
169 try {
170 close(false);
171 } finally {
172 super.destroy();
173 }
174 }
175
176 private void close(boolean silent) {
177 try {
178 if (context != null) {
179 context.dispose();
180 context = null;
181 }
182 } catch (GSSException e) {
183 if (!silent) {
184 log.warn(SshdText.get().gssapiFailure, e);
185 }
186 }
187 }
188
189 private void sendToken(ClientSession session, byte[] receivedToken)
190 throws IOException, GSSException {
191 state = ProtocolState.TOKENS;
192 byte[] token = context.initSecContext(receivedToken, 0,
193 receivedToken.length);
194 if (token != null) {
195 Buffer buffer = session.createBuffer(SSH_MSG_USERAUTH_GSSAPI_TOKEN);
196 buffer.putBytes(token);
197 session.writePacket(buffer);
198 }
199 }
200
201 private void sendMic(ClientSession session, String service)
202 throws IOException, GSSException {
203 state = ProtocolState.MIC_SENT;
204
205 Buffer micBuffer = new ByteArrayBuffer();
206 micBuffer.putBytes(session.getSessionId());
207 micBuffer.putByte(SshConstants.SSH_MSG_USERAUTH_REQUEST);
208 micBuffer.putString(session.getUsername());
209 micBuffer.putString(service);
210 micBuffer.putString(getName());
211 byte[] micBytes = micBuffer.getCompactData();
212 byte[] mic = context.getMIC(micBytes, 0, micBytes.length,
213 new MessageProp(0, true));
214 Buffer buffer = session
215 .createBuffer(SshConstants.SSH_MSG_USERAUTH_GSSAPI_MIC);
216 buffer.putBytes(mic);
217 session.writePacket(buffer);
218 }
219
220 private void replyToken(ClientSession session, String service, byte[] bytes)
221 throws IOException, GSSException {
222 sendToken(session, bytes);
223 if (context.isEstablished()) {
224 sendMic(session, service);
225 }
226 }
227
228 private String getHostName(ClientSession session) {
229 SocketAddress remote = session.getConnectAddress();
230 if (remote instanceof InetSocketAddress) {
231 InetAddress address = GssApiMechanisms
232 .resolve((InetSocketAddress) remote);
233 if (address != null) {
234 return address.getCanonicalHostName();
235 }
236 }
237 if (session instanceof JGitClientSession) {
238 String hostName = ((JGitClientSession) session).getHostConfigEntry()
239 .getHostName();
240 try {
241 hostName = InetAddress.getByName(hostName)
242 .getCanonicalHostName();
243 } catch (UnknownHostException e) {
244
245 }
246 return hostName;
247 }
248 throw new IllegalStateException(
249 "Wrong session class :" + session.getClass().getName());
250 }
251
252 private boolean unexpectedMessage(int command) {
253 log.warn(format(SshdText.get().gssapiUnexpectedMessage, getName(),
254 Integer.toString(command)));
255 return false;
256 }
257
258 @Override
259 public void signalAuthMethodSuccess(ClientSession session, String service,
260 Buffer buffer) throws Exception {
261 GssApiWithMicAuthenticationReporter reporter = session.getAttribute(
262 GssApiWithMicAuthenticationReporter.GSS_AUTHENTICATION_REPORTER);
263 if (reporter != null) {
264 reporter.signalAuthenticationSuccess(session, service,
265 currentMechanism.toString());
266 }
267 }
268
269 @Override
270 public void signalAuthMethodFailure(ClientSession session, String service,
271 boolean partial, List<String> serverMethods, Buffer buffer)
272 throws Exception {
273 GssApiWithMicAuthenticationReporter reporter = session.getAttribute(
274 GssApiWithMicAuthenticationReporter.GSS_AUTHENTICATION_REPORTER);
275 if (reporter != null) {
276 reporter.signalAuthenticationFailure(session, service,
277 currentMechanism.toString(), partial, serverMethods);
278 }
279 }
280 }