1/*
2 * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.security.sasl;
27
28import javax.security.sasl.*;
29import java.security.NoSuchAlgorithmException;
30
31import java.util.logging.Logger;
32import java.util.logging.Level;
33
34/**
35  * Implements the CRAM-MD5 SASL client-side mechanism.
36  * (<A HREF="http://www.ietf.org/rfc/rfc2195.txt">RFC 2195</A>).
37  * CRAM-MD5 has no initial response. It receives bytes from
38  * the server as a challenge, which it hashes by using MD5 and the password.
39  * It concatenates the authentication ID with this result and returns it
40  * as the response to the challenge. At that point, the exchange is complete.
41  *
42  * @author Vincent Ryan
43  * @author Rosanna Lee
44  */
45final class CramMD5Client extends CramMD5Base implements SaslClient {
46    private String username;
47
48    /**
49     * Creates a SASL mechanism with client credentials that it needs
50     * to participate in CRAM-MD5 authentication exchange with the server.
51     *
52     * @param authID A  non-null string representing the principal
53     * being authenticated.
54     *
55     * @param pw A non-null String or byte[]
56     * containing the password. If it is an array, it is first cloned.
57     */
58    CramMD5Client(String authID, byte[] pw) throws SaslException {
59        if (authID == null || pw == null) {
60            throw new SaslException(
61                "CRAM-MD5: authentication ID and password must be specified");
62        }
63
64        username = authID;
65        this.pw = pw;  // caller should have already cloned
66    }
67
68    /**
69     * CRAM-MD5 has no initial response.
70     */
71    public boolean hasInitialResponse() {
72        return false;
73    }
74
75    /**
76     * Processes the challenge data.
77     *
78     * The server sends a challenge data using which the client must
79     * compute an MD5-digest with its password as the key.
80     *
81     * @param challengeData A non-null byte array containing the challenge
82     *        data from the server.
83     * @return A non-null byte array containing the response to be sent to
84     *        the server.
85     * @throws SaslException If platform does not have MD5 support
86     * @throw IllegalStateException if this method is invoked more than once.
87     */
88    public byte[] evaluateChallenge(byte[] challengeData)
89        throws SaslException {
90
91        // See if we've been here before
92        if (completed) {
93            throw new IllegalStateException(
94                "CRAM-MD5 authentication already completed");
95        }
96
97        if (aborted) {
98            throw new IllegalStateException(
99                "CRAM-MD5 authentication previously aborted due to error");
100        }
101
102        // generate a keyed-MD5 digest from the user's password and challenge.
103        try {
104            if (logger.isLoggable(Level.FINE)) {
105                logger.log(Level.FINE, "CRAMCLNT01:Received challenge: {0}",
106                    new String(challengeData, "UTF8"));
107            }
108
109            String digest = HMAC_MD5(pw, challengeData);
110
111            // clear it when we no longer need it
112            clearPassword();
113
114            // response is username + " " + digest
115            String resp = username + " " + digest;
116
117            logger.log(Level.FINE, "CRAMCLNT02:Sending response: {0}", resp);
118
119            completed = true;
120
121            return resp.getBytes("UTF8");
122        } catch (java.security.NoSuchAlgorithmException e) {
123            aborted = true;
124            throw new SaslException("MD5 algorithm not available on platform", e);
125        } catch (java.io.UnsupportedEncodingException e) {
126            aborted = true;
127            throw new SaslException("UTF8 not available on platform", e);
128        }
129    }
130}
131