1 /*
   2  * Copyright (c) 2003, 2014, 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.SaslException;
  29 import javax.security.sasl.Sasl;
  30 
  31 // For HMAC_MD5
  32 import java.security.NoSuchAlgorithmException;
  33 import java.security.MessageDigest;
  34 
  35 import java.util.Arrays;
  36 import java.util.logging.Logger;
  37 
  38 /**
  39   * Base class for implementing CRAM-MD5 client and server mechanisms.
  40   *
  41   * @author Vincent Ryan
  42   * @author Rosanna Lee
  43   */
  44 abstract class CramMD5Base {
  45     protected boolean completed = false;
  46     protected boolean aborted = false;
  47     protected byte[] pw;
  48 
  49     protected CramMD5Base() {
  50         initLogger();
  51     }
  52 
  53     /**
  54      * Retrieves this mechanism's name.
  55      *
  56      * @return  The string "CRAM-MD5".
  57      */
  58     public String getMechanismName() {
  59         return "CRAM-MD5";
  60     }
  61 
  62     /**
  63      * Determines whether this mechanism has completed.
  64      * CRAM-MD5 completes after processing one challenge from the server.
  65      *
  66      * @return true if has completed; false otherwise;
  67      */
  68     public boolean isComplete() {
  69         return completed;
  70     }
  71 
  72     /**
  73       * Unwraps the incoming buffer. CRAM-MD5 supports no security layer.
  74       *
  75       * @throws SaslException If attempt to use this method.
  76       */
  77     public byte[] unwrap(byte[] incoming, int offset, int len)
  78         throws SaslException {
  79         if (completed) {
  80             throw new IllegalStateException(
  81                 "CRAM-MD5 supports neither integrity nor privacy");
  82         } else {
  83             throw new IllegalStateException(
  84                 "CRAM-MD5 authentication not completed");
  85         }
  86     }
  87 
  88     /**
  89       * Wraps the outgoing buffer. CRAM-MD5 supports no security layer.
  90       *
  91       * @throws SaslException If attempt to use this method.
  92       */
  93     public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException {
  94         if (completed) {
  95             throw new IllegalStateException(
  96                 "CRAM-MD5 supports neither integrity nor privacy");
  97         } else {
  98             throw new IllegalStateException(
  99                 "CRAM-MD5 authentication not completed");
 100         }
 101     }
 102 
 103     /**
 104      * Retrieves the negotiated property.
 105      * This method can be called only after the authentication exchange has
 106      * completed (i.e., when <tt>isComplete()</tt> returns true); otherwise, a
 107      * <tt>SaslException</tt> is thrown.
 108      *
 109      * @return value of property; only QOP is applicable to CRAM-MD5.
 110      * @exception IllegalStateException if this authentication exchange has not completed
 111      */
 112     public Object getNegotiatedProperty(String propName) {
 113         if (completed) {
 114             if (propName.equals(Sasl.QOP)) {
 115                 return "auth";
 116             } else {
 117                 return null;
 118             }
 119         } else {
 120             throw new IllegalStateException(
 121                 "CRAM-MD5 authentication not completed");
 122         }
 123     }
 124 
 125     public void dispose() throws SaslException {
 126         clearPassword();
 127     }
 128 
 129     protected void clearPassword() {
 130         if (pw != null) {
 131             // zero out password
 132             for (int i = 0; i < pw.length; i++) {
 133                 pw[i] = (byte)0;
 134             }
 135             pw = null;
 136         }
 137     }
 138 
 139     protected void finalize() {
 140         clearPassword();
 141     }
 142 
 143     static private final int MD5_BLOCKSIZE = 64;
 144     /**
 145      * Hashes its input arguments according to HMAC-MD5 (RFC 2104)
 146      * and returns the resulting digest in its ASCII representation.
 147      *
 148      * HMAC-MD5 function is described as follows:
 149      *
 150      *       MD5(key XOR opad, MD5(key XOR ipad, text))
 151      *
 152      * where key  is an n byte key
 153      *       ipad is the byte 0x36 repeated 64 times
 154      *       opad is the byte 0x5c repeated 64 times
 155      *       text is the data to be protected
 156      */
 157     final static String HMAC_MD5(byte[] key, byte[] text)
 158         throws NoSuchAlgorithmException {
 159 
 160         MessageDigest md5 = MessageDigest.getInstance("MD5");
 161 
 162         /* digest the key if longer than 64 bytes */
 163         if (key.length > MD5_BLOCKSIZE) {
 164             key = md5.digest(key);
 165         }
 166 
 167         byte[] ipad = new byte[MD5_BLOCKSIZE];  /* inner padding */
 168         byte[] opad = new byte[MD5_BLOCKSIZE];  /* outer padding */
 169         byte[] digest;
 170         int i;
 171 
 172         /* store key in pads */
 173         for (i = 0; i < key.length; i++) {
 174             ipad[i] = key[i];
 175             opad[i] = key[i];
 176         }
 177 
 178         /* XOR key with pads */
 179         for (i = 0; i < MD5_BLOCKSIZE; i++) {
 180             ipad[i] ^= 0x36;
 181             opad[i] ^= 0x5c;
 182         }
 183 
 184         /* inner MD5 */
 185         md5.update(ipad);
 186         md5.update(text);
 187         digest = md5.digest();
 188 
 189         /* outer MD5 */
 190         md5.update(opad);
 191         md5.update(digest);
 192         digest = md5.digest();
 193 
 194         // Get character representation of digest
 195         StringBuilder digestString = new StringBuilder();
 196 
 197         for (i = 0; i < digest.length; i++) {
 198             if ((digest[i] & 0x000000ff) < 0x10) {
 199                 digestString.append("0" +
 200                     Integer.toHexString(digest[i] & 0x000000ff));
 201             } else {
 202                 digestString.append(
 203                     Integer.toHexString(digest[i] & 0x000000ff));
 204             }
 205         }
 206 
 207         Arrays.fill(ipad, (byte)0);
 208         Arrays.fill(opad, (byte)0);
 209         ipad = null;
 210         opad = null;
 211 
 212         return (digestString.toString());
 213     }
 214 
 215     /**
 216      * Sets logger field.
 217      */
 218     private static synchronized void initLogger() {
 219         if (logger == null) {
 220             logger = Logger.getLogger(SASL_LOGGER_NAME);
 221         }
 222     }
 223     /**
 224      * Logger for debug messages
 225      */
 226     private static final String SASL_LOGGER_NAME = "javax.security.sasl";
 227     protected static Logger logger;  // set in initLogger(); lazily loads logger
 228 }