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