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