1 /* 2 * Copyright (c) 2003, 2020, 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 CramMD5Base()49 protected CramMD5Base() { 50 initLogger(); 51 } 52 53 /** 54 * Retrieves this mechanism's name. 55 * 56 * @return The string "CRAM-MD5". 57 */ getMechanismName()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 */ isComplete()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 */ unwrap(byte[] incoming, int offset, int len)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 */ wrap(byte[] outgoing, int offset, int len)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 {@code isComplete()} returns true); otherwise, a 107 * {@code SaslException} 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 */ getNegotiatedProperty(String propName)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 dispose()125 public void dispose() throws SaslException { 126 clearPassword(); 127 } 128 clearPassword()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 @SuppressWarnings("deprecation") finalize()140 protected void finalize() { 141 clearPassword(); 142 } 143 144 static private final int MD5_BLOCKSIZE = 64; 145 /** 146 * Hashes its input arguments according to HMAC-MD5 (RFC 2104) 147 * and returns the resulting digest in its ASCII representation. 148 * 149 * HMAC-MD5 function is described as follows: 150 * 151 * MD5(key XOR opad, MD5(key XOR ipad, text)) 152 * 153 * where key is an n byte key 154 * ipad is the byte 0x36 repeated 64 times 155 * opad is the byte 0x5c repeated 64 times 156 * text is the data to be protected 157 */ HMAC_MD5(byte[] key, byte[] text)158 final static String HMAC_MD5(byte[] key, byte[] text) 159 throws NoSuchAlgorithmException { 160 161 MessageDigest md5 = MessageDigest.getInstance("MD5"); 162 163 /* digest the key if longer than 64 bytes */ 164 if (key.length > MD5_BLOCKSIZE) { 165 key = md5.digest(key); 166 } 167 168 byte[] ipad = new byte[MD5_BLOCKSIZE]; /* inner padding */ 169 byte[] opad = new byte[MD5_BLOCKSIZE]; /* outer padding */ 170 byte[] digest; 171 int i; 172 173 /* store key in pads */ 174 for (i = 0; i < key.length; i++) { 175 ipad[i] = key[i]; 176 opad[i] = key[i]; 177 } 178 179 /* XOR key with pads */ 180 for (i = 0; i < MD5_BLOCKSIZE; i++) { 181 ipad[i] ^= 0x36; 182 opad[i] ^= 0x5c; 183 } 184 185 /* inner MD5 */ 186 md5.update(ipad); 187 md5.update(text); 188 digest = md5.digest(); 189 190 /* outer MD5 */ 191 md5.update(opad); 192 md5.update(digest); 193 digest = md5.digest(); 194 195 // Get character representation of digest 196 StringBuilder digestString = new StringBuilder(); 197 198 for (i = 0; i < digest.length; i++) { 199 if ((digest[i] & 0x000000ff) < 0x10) { 200 digestString.append('0').append(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 */ initLogger()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 } 229