summaryrefslogtreecommitdiffstats
blob: 639c353e1341a23c4c0d963e398c07255ae7c615 (plain)
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/*
 * Copyright (c) 2010-2013 Isode Limited.
 * All rights reserved.
 * See the COPYING file for more information.
 */
/*
 * Copyright (c) 2015 Tarun Gupta.
 * Licensed under the simplified BSD license.
 * See Documentation/Licenses/BSD-simplified.txt for more information.
 */

package com.isode.stroke.sasl;

import com.isode.stroke.sasl.ClientAuthenticator;
import com.isode.stroke.sasl.DIGESTMD5Properties;
import com.isode.stroke.crypto.CryptoProvider;
import com.isode.stroke.base.SafeByteArray;
import com.isode.stroke.base.ByteArray;
import com.isode.stroke.stringcodecs.Hexify;

public class DIGESTMD5ClientAuthenticator extends ClientAuthenticator {

	private enum Step {
		Initial,
		Response,
		Final
	};
	private Step step;
	private String host = "";
	private String cnonce = "";
	private CryptoProvider crypto;
	private DIGESTMD5Properties challenge = new DIGESTMD5Properties();

	public DIGESTMD5ClientAuthenticator(final String host, final String nonce, CryptoProvider crypto) {
		super("DIGEST-MD5");
		this.step = Step.Initial;
		this.host = host;
		this.cnonce = nonce;
		this.crypto = crypto;
	}

	public SafeByteArray getResponse() {
		if (Step.Initial.equals(step)) {
			return null;
		}
		else if (Step.Response.equals(step)) {
			String realm = "";
			if (challenge.getValue("realm") != null) {
				realm = challenge.getValue("realm");
			}
			String qop = "auth";
			String digestURI = "xmpp/" + host;
			String nc = "00000001";

			ByteArray A11 = crypto.getMD5Hash(new SafeByteArray().append(getAuthenticationID()).append(":").append(realm).append(":").append(getPassword()));
			ByteArray A12 = new ByteArray().append(":").append(challenge.getValue("nonce")).append(":").append(cnonce);
			// Compute the response value
			ByteArray A1 = A11.append(A12);
			if (!getAuthorizationID().isEmpty()) {
				A1.append(new ByteArray(":" + getAuthenticationID()));
			}
			ByteArray A2 = new ByteArray("AUTHENTICATE:" + digestURI);

			String responseValue = Hexify.hexify(crypto.getMD5Hash(new ByteArray(
				Hexify.hexify(crypto.getMD5Hash(A1)) + ":"
				+ challenge.getValue("nonce") + ":" + nc + ":" + cnonce + ":" + qop + ":"
				+ Hexify.hexify(crypto.getMD5Hash(A2)))));


			DIGESTMD5Properties response = new DIGESTMD5Properties();
			response.setValue("username", getAuthenticationID());
			if (!realm.isEmpty()) {
				response.setValue("realm", realm);
			}
			response.setValue("nonce", challenge.getValue("nonce"));
			response.setValue("cnonce", cnonce);
			response.setValue("nc", "00000001");
			response.setValue("qop", qop);
			response.setValue("digest-uri", digestURI);
			response.setValue("charset", "utf-8");
			response.setValue("response", responseValue);
			if (!getAuthorizationID().isEmpty()) {
				response.setValue("authzid", getAuthorizationID());
			}
			return new SafeByteArray(response.serialize());
		}
		else {
			return null;
		}
	}

	public boolean setChallenge(final ByteArray challengeData) {
		if (Step.Initial.equals(step)) {
			if (challengeData == null) {
				return false;
			}
			challenge = DIGESTMD5Properties.parse(challengeData);

			// Sanity checks
			if (challenge.getValue("nonce") == null) {
				return false;
			}
			if (challenge.getValue("charset") ==null || !(challenge.getValue("charset").equals("utf-8"))) {
				return false;
			}
			step = Step.Response;
			return true;
		}
		else {
			step = Step.Final;
			// TODO: Check RSPAuth
			return true;
		}
	}
}