001: package ch.ethz.ssh2.auth;
002:
003: import java.io.IOException;
004: import java.security.SecureRandom;
005: import java.util.Vector;
006:
007: import ch.ethz.ssh2.InteractiveCallback;
008: import ch.ethz.ssh2.crypto.PEMDecoder;
009: import ch.ethz.ssh2.packets.PacketServiceAccept;
010: import ch.ethz.ssh2.packets.PacketServiceRequest;
011: import ch.ethz.ssh2.packets.PacketUserauthBanner;
012: import ch.ethz.ssh2.packets.PacketUserauthFailure;
013: import ch.ethz.ssh2.packets.PacketUserauthInfoRequest;
014: import ch.ethz.ssh2.packets.PacketUserauthInfoResponse;
015: import ch.ethz.ssh2.packets.PacketUserauthRequestInteractive;
016: import ch.ethz.ssh2.packets.PacketUserauthRequestNone;
017: import ch.ethz.ssh2.packets.PacketUserauthRequestPassword;
018: import ch.ethz.ssh2.packets.PacketUserauthRequestPublicKey;
019: import ch.ethz.ssh2.packets.Packets;
020: import ch.ethz.ssh2.packets.TypesWriter;
021: import ch.ethz.ssh2.signature.DSAPrivateKey;
022: import ch.ethz.ssh2.signature.DSASHA1Verify;
023: import ch.ethz.ssh2.signature.DSASignature;
024: import ch.ethz.ssh2.signature.RSAPrivateKey;
025: import ch.ethz.ssh2.signature.RSASHA1Verify;
026: import ch.ethz.ssh2.signature.RSASignature;
027: import ch.ethz.ssh2.transport.MessageHandler;
028: import ch.ethz.ssh2.transport.TransportManager;
029:
030: /**
031: * AuthenticationManager.
032: *
033: * @author Christian Plattner, plattner@inf.ethz.ch
034: * @version $Id: AuthenticationManager.java,v 1.14 2006/07/30 21:59:29 cplattne Exp $
035: */
036: public class AuthenticationManager implements MessageHandler {
037: TransportManager tm;
038:
039: Vector packets = new Vector();
040: boolean connectionClosed = false;
041:
042: String banner;
043:
044: String[] remainingMethods = null;
045: boolean isPartialSuccess = false;
046:
047: boolean authenticated = false;
048: boolean initDone = false;
049:
050: public AuthenticationManager(TransportManager tm) {
051: this .tm = tm;
052: }
053:
054: boolean methodPossible(String methName) {
055: if (remainingMethods == null)
056: return false;
057:
058: for (int i = 0; i < remainingMethods.length; i++) {
059: if (remainingMethods[i].compareTo(methName) == 0)
060: return true;
061: }
062: return false;
063: }
064:
065: byte[] deQueue() throws IOException {
066: synchronized (packets) {
067: while (packets.size() == 0) {
068: if (connectionClosed)
069: throw (IOException) new IOException(
070: "The connection is closed.").initCause(tm
071: .getReasonClosedCause());
072:
073: try {
074: packets.wait();
075: } catch (InterruptedException ign) {
076: }
077: }
078: /* This sequence works with J2ME */
079: byte[] res = (byte[]) packets.firstElement();
080: packets.removeElementAt(0);
081: return res;
082: }
083: }
084:
085: byte[] getNextMessage() throws IOException {
086: while (true) {
087: byte[] msg = deQueue();
088:
089: if (msg[0] != Packets.SSH_MSG_USERAUTH_BANNER)
090: return msg;
091:
092: PacketUserauthBanner sb = new PacketUserauthBanner(msg, 0,
093: msg.length);
094:
095: banner = sb.getBanner();
096: }
097: }
098:
099: public String[] getRemainingMethods(String user) throws IOException {
100: initialize(user);
101: return remainingMethods;
102: }
103:
104: public boolean getPartialSuccess() {
105: return isPartialSuccess;
106: }
107:
108: private boolean initialize(String user) throws IOException {
109: if (initDone == false) {
110: tm.registerMessageHandler(this , 0, 255);
111:
112: PacketServiceRequest sr = new PacketServiceRequest(
113: "ssh-userauth");
114: tm.sendMessage(sr.getPayload());
115:
116: PacketUserauthRequestNone urn = new PacketUserauthRequestNone(
117: "ssh-connection", user);
118: tm.sendMessage(urn.getPayload());
119:
120: byte[] msg = getNextMessage();
121: new PacketServiceAccept(msg, 0, msg.length);
122: msg = getNextMessage();
123:
124: initDone = true;
125:
126: if (msg[0] == Packets.SSH_MSG_USERAUTH_SUCCESS) {
127: authenticated = true;
128: return true;
129: }
130:
131: if (msg[0] == Packets.SSH_MSG_USERAUTH_FAILURE) {
132: PacketUserauthFailure puf = new PacketUserauthFailure(
133: msg, 0, msg.length);
134:
135: remainingMethods = puf.getAuthThatCanContinue();
136: isPartialSuccess = puf.isPartialSuccess();
137: return false;
138: }
139:
140: throw new IOException("Unexpected SSH message (type "
141: + msg[0] + ")");
142: }
143: return authenticated;
144: }
145:
146: public boolean authenticatePublicKey(String user,
147: char[] PEMPrivateKey, String password, SecureRandom rnd)
148: throws IOException {
149: try {
150: initialize(user);
151:
152: if (methodPossible("publickey") == false)
153: throw new IOException(
154: "Authentication method publickey not supported by the server at this stage.");
155:
156: Object key = PEMDecoder.decode(PEMPrivateKey, password);
157:
158: if (key instanceof DSAPrivateKey) {
159: DSAPrivateKey pk = (DSAPrivateKey) key;
160:
161: byte[] pk_enc = DSASHA1Verify.encodeSSHDSAPublicKey(pk
162: .getPublicKey());
163:
164: TypesWriter tw = new TypesWriter();
165:
166: byte[] H = tm.getSessionIdentifier();
167:
168: tw.writeString(H, 0, H.length);
169: tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST);
170: tw.writeString(user);
171: tw.writeString("ssh-connection");
172: tw.writeString("publickey");
173: tw.writeBoolean(true);
174: tw.writeString("ssh-dss");
175: tw.writeString(pk_enc, 0, pk_enc.length);
176:
177: byte[] msg = tw.getBytes();
178:
179: DSASignature ds = DSASHA1Verify.generateSignature(msg,
180: pk, rnd);
181:
182: byte[] ds_enc = DSASHA1Verify.encodeSSHDSASignature(ds);
183:
184: PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey(
185: "ssh-connection", user, "ssh-dss", pk_enc,
186: ds_enc);
187: tm.sendMessage(ua.getPayload());
188: } else if (key instanceof RSAPrivateKey) {
189: RSAPrivateKey pk = (RSAPrivateKey) key;
190:
191: byte[] pk_enc = RSASHA1Verify.encodeSSHRSAPublicKey(pk
192: .getPublicKey());
193:
194: TypesWriter tw = new TypesWriter();
195: {
196: byte[] H = tm.getSessionIdentifier();
197:
198: tw.writeString(H, 0, H.length);
199: tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST);
200: tw.writeString(user);
201: tw.writeString("ssh-connection");
202: tw.writeString("publickey");
203: tw.writeBoolean(true);
204: tw.writeString("ssh-rsa");
205: tw.writeString(pk_enc, 0, pk_enc.length);
206: }
207:
208: byte[] msg = tw.getBytes();
209:
210: RSASignature ds = RSASHA1Verify.generateSignature(msg,
211: pk);
212:
213: byte[] rsa_sig_enc = RSASHA1Verify
214: .encodeSSHRSASignature(ds);
215:
216: PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey(
217: "ssh-connection", user, "ssh-rsa", pk_enc,
218: rsa_sig_enc);
219: tm.sendMessage(ua.getPayload());
220: } else {
221: throw new IOException(
222: "Unknown private key type returned by the PEM decoder.");
223: }
224:
225: byte[] ar = getNextMessage();
226:
227: if (ar[0] == Packets.SSH_MSG_USERAUTH_SUCCESS) {
228: authenticated = true;
229: tm.removeMessageHandler(this , 0, 255);
230: return true;
231: }
232:
233: if (ar[0] == Packets.SSH_MSG_USERAUTH_FAILURE) {
234: PacketUserauthFailure puf = new PacketUserauthFailure(
235: ar, 0, ar.length);
236:
237: remainingMethods = puf.getAuthThatCanContinue();
238: isPartialSuccess = puf.isPartialSuccess();
239:
240: return false;
241: }
242:
243: throw new IOException("Unexpected SSH message (type "
244: + ar[0] + ")");
245:
246: } catch (IOException e) {
247: tm.close(e, false);
248: throw (IOException) new IOException(
249: "Publickey authentication failed.").initCause(e);
250: }
251: }
252:
253: public boolean authenticatePassword(String user, String pass)
254: throws IOException {
255: try {
256: initialize(user);
257:
258: if (methodPossible("password") == false)
259: throw new IOException(
260: "Authentication method password not supported by the server at this stage.");
261:
262: PacketUserauthRequestPassword ua = new PacketUserauthRequestPassword(
263: "ssh-connection", user, pass);
264: tm.sendMessage(ua.getPayload());
265:
266: byte[] ar = getNextMessage();
267:
268: if (ar[0] == Packets.SSH_MSG_USERAUTH_SUCCESS) {
269: authenticated = true;
270: tm.removeMessageHandler(this , 0, 255);
271: return true;
272: }
273:
274: if (ar[0] == Packets.SSH_MSG_USERAUTH_FAILURE) {
275: PacketUserauthFailure puf = new PacketUserauthFailure(
276: ar, 0, ar.length);
277:
278: remainingMethods = puf.getAuthThatCanContinue();
279: isPartialSuccess = puf.isPartialSuccess();
280:
281: return false;
282: }
283:
284: throw new IOException("Unexpected SSH message (type "
285: + ar[0] + ")");
286:
287: } catch (IOException e) {
288: tm.close(e, false);
289: throw (IOException) new IOException(
290: "Password authentication failed.").initCause(e);
291: }
292: }
293:
294: public boolean authenticateInteractive(String user,
295: String[] submethods, InteractiveCallback cb)
296: throws IOException {
297: try {
298: initialize(user);
299:
300: if (methodPossible("keyboard-interactive") == false)
301: throw new IOException(
302: "Authentication method keyboard-interactive not supported by the server at this stage.");
303:
304: if (submethods == null)
305: submethods = new String[0];
306:
307: PacketUserauthRequestInteractive ua = new PacketUserauthRequestInteractive(
308: "ssh-connection", user, submethods);
309:
310: tm.sendMessage(ua.getPayload());
311:
312: while (true) {
313: byte[] ar = getNextMessage();
314:
315: if (ar[0] == Packets.SSH_MSG_USERAUTH_SUCCESS) {
316: authenticated = true;
317: tm.removeMessageHandler(this , 0, 255);
318: return true;
319: }
320:
321: if (ar[0] == Packets.SSH_MSG_USERAUTH_FAILURE) {
322: PacketUserauthFailure puf = new PacketUserauthFailure(
323: ar, 0, ar.length);
324:
325: remainingMethods = puf.getAuthThatCanContinue();
326: isPartialSuccess = puf.isPartialSuccess();
327:
328: return false;
329: }
330:
331: if (ar[0] == Packets.SSH_MSG_USERAUTH_INFO_REQUEST) {
332: PacketUserauthInfoRequest pui = new PacketUserauthInfoRequest(
333: ar, 0, ar.length);
334:
335: String[] responses;
336:
337: try {
338: responses = cb.replyToChallenge(pui.getName(),
339: pui.getInstruction(), pui
340: .getNumPrompts(), pui
341: .getPrompt(), pui.getEcho());
342: } catch (Exception e) {
343: throw (IOException) new IOException(
344: "Exception in callback.").initCause(e);
345: }
346:
347: if (responses == null)
348: throw new IOException(
349: "Your callback may not return NULL!");
350:
351: PacketUserauthInfoResponse puir = new PacketUserauthInfoResponse(
352: responses);
353: tm.sendMessage(puir.getPayload());
354:
355: continue;
356: }
357:
358: throw new IOException("Unexpected SSH message (type "
359: + ar[0] + ")");
360: }
361: } catch (IOException e) {
362: tm.close(e, false);
363: throw (IOException) new IOException(
364: "Keyboard-interactive authentication failed.")
365: .initCause(e);
366: }
367: }
368:
369: public void handleMessage(byte[] msg, int msglen)
370: throws IOException {
371: synchronized (packets) {
372: if (msg == null) {
373: connectionClosed = true;
374: } else {
375: byte[] tmp = new byte[msglen];
376: System.arraycopy(msg, 0, tmp, 0, msglen);
377: packets.addElement(tmp);
378: }
379:
380: packets.notifyAll();
381:
382: if (packets.size() > 5) {
383: connectionClosed = true;
384: throw new IOException(
385: "Error, peer is flooding us with authentication packets.");
386: }
387: }
388: }
389: }
|