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 
  26 package com.sun.security.sasl;
  27 
  28 import javax.security.sasl.*;
  29 import java.security.NoSuchAlgorithmException;
  30 
  31 import java.util.logging.Logger;
  32 import 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   */
  45 final 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 }