1 /**
2 *
3 * Copyright 2003-2005 The Apache Software Foundation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.geronimo.javamail.authentication;
19
20 import java.io.UnsupportedEncodingException;
21 import java.security.MessageDigest;
22 import java.security.NoSuchAlgorithmException;
23
24 import javax.mail.MessagingException;
25
26 import org.apache.geronimo.mail.util.Hex;
27
28 public class CramMD5Authenticator implements ClientAuthenticator {
29
30
31 protected String username;
32
33
34 protected String password;
35
36
37 protected boolean complete = false;
38
39 /**
40 * Main constructor.
41 *
42 * @param username
43 * The login user name.
44 * @param password
45 * The login password.
46 */
47 public CramMD5Authenticator(String username, String password) {
48 this.username = username;
49 this.password = password;
50 }
51
52 /**
53 * Respond to the hasInitialResponse query. This mechanism does not have an
54 * initial response.
55 *
56 * @return Always returns false.
57 */
58 public boolean hasInitialResponse() {
59 return false;
60 }
61
62 /**
63 * Indicate whether the challenge/response process is complete.
64 *
65 * @return True if the last challenge has been processed, false otherwise.
66 */
67 public boolean isComplete() {
68 return complete;
69 }
70
71 /**
72 * Retrieve the authenticator mechanism name.
73 *
74 * @return Always returns the string "CRAM-MD5"
75 */
76 public String getMechanismName() {
77 return "CRAM-MD5";
78 }
79
80 /**
81 * Evaluate a CRAM-MD5 login challenge, returning the a result string that
82 * should satisfy the clallenge.
83 *
84 * @param challenge
85 * The decoded challenge data, as a byte array.
86 *
87 * @return A formatted challege response, as an array of bytes.
88 * @exception MessagingException
89 */
90 public byte[] evaluateChallenge(byte[] challenge) throws MessagingException {
91
92
93 byte[] passBytes;
94
95 try {
96
97 passBytes = password.getBytes("UTF-8");
98
99 byte[] digest = computeCramDigest(passBytes, challenge);
100
101
102
103 String responseString = username + " " + new String(Hex.encode(digest));
104 complete = true;
105 return responseString.getBytes();
106 } catch (UnsupportedEncodingException e) {
107
108 throw new MessagingException("Invalid character encodings");
109 }
110
111 }
112
113 /**
114 * Compute a CRAM digest using the hmac_md5 algorithm. See the description
115 * of RFC 2104 for algorithm details.
116 *
117 * @param key
118 * The key (K) for the calculation.
119 * @param input
120 * The encrypted text value.
121 *
122 * @return The computed digest, as a byte array value.
123 * @exception NoSuchAlgorithmException
124 */
125 protected byte[] computeCramDigest(byte[] key, byte[] input) throws MessagingException {
126
127 MessageDigest digest;
128 try {
129 digest = MessageDigest.getInstance("MD5");
130 } catch (NoSuchAlgorithmException e) {
131 throw new MessagingException("Unable to access MD5 message digest", e);
132 }
133
134
135
136
137 if (key.length > 64) {
138 digest.update(key);
139 key = digest.digest();
140 }
141
142
143
144 byte[] ipad = new byte[64];
145 byte[] opad = new byte[64];
146
147 System.arraycopy(key, 0, ipad, 0, key.length);
148 System.arraycopy(key, 0, opad, 0, key.length);
149
150
151
152 for (int i = 0; i < 64; i++) {
153 ipad[i] ^= 0x36;
154 opad[i] ^= 0x5c;
155 }
156
157
158
159
160
161
162 digest.reset();
163 digest.update(ipad);
164 digest.update(input);
165 byte[] md5digest = digest.digest();
166
167
168 digest.reset();
169 digest.update(opad);
170 digest.update(md5digest);
171 return digest.digest();
172 }
173 }