1 package gov.nist.javax.sip.clientauthutils; 2 3 import gov.nist.core.StackLogger; 4 5 import java.security.MessageDigest; 6 import java.security.NoSuchAlgorithmException; 7 8 /** 9 * The class takes standard Http Authentication details and returns a response according to the 10 * MD5 algorithm 11 * 12 * @author Emil Ivov 13 */ 14 15 public class MessageDigestAlgorithm { 16 /** 17 * Calculates an http authentication response in accordance with rfc2617. 18 * <p> 19 * 20 * @param algorithm a string indicating a pair of algorithms (MD5 (default), or MD5-sess) used 21 * to produce the digest and a checksum. 22 * @param hashUserNameRealmPasswd MD5 hash of (username:realm:password) 23 * @param nonce_value A server-specified data string provided in the challenge. 24 * @param cnonce_value an optional client-chosen value whose purpose is to foil chosen 25 * plaintext attacks. 26 * @param method the SIP method of the request being challenged. 27 * @param digest_uri_value the value of the "uri" directive on the Authorization header in the 28 * request. 29 * @param entity_body the entity-body 30 * @param qop_value Indicates what "quality of protection" the client has applied to the 31 * message. 32 * @param nc_value the hexadecimal count of the number of requests (including the current 33 * request) that the client has sent with the nonce value in this request. 34 * @return a digest response as defined in rfc2617 35 * @throws NullPointerException in case of incorrectly null parameters. 36 */ 37 calculateResponse(String algorithm, String hashUserNameRealmPasswd, String nonce_value, String nc_value, String cnonce_value, String method, String digest_uri_value, String entity_body, String qop_value, StackLogger stackLogger)38 static String calculateResponse(String algorithm, String hashUserNameRealmPasswd, 39 String nonce_value, String nc_value, String cnonce_value, 40 String method, String digest_uri_value, String entity_body, String qop_value, 41 StackLogger stackLogger) { 42 if (stackLogger.isLoggingEnabled()) { 43 stackLogger.logDebug("trying to authenticate using : " + algorithm + ", "+ 44 hashUserNameRealmPasswd + ", " + nonce_value + ", " 45 + nc_value + ", " + cnonce_value + ", " + method + ", " + digest_uri_value 46 + ", " + entity_body + ", " + qop_value); 47 } 48 49 if (hashUserNameRealmPasswd == null || method == null 50 || digest_uri_value == null || nonce_value == null) 51 throw new NullPointerException( 52 "Null parameter to MessageDigestAlgorithm.calculateResponse()"); 53 54 // The following follows closely the algorithm for generating a response 55 // digest as specified by rfc2617 56 57 if (cnonce_value == null || cnonce_value.length() == 0) 58 throw new NullPointerException( 59 "cnonce_value may not be absent for MD5-Sess algorithm."); 60 61 62 String A2 = null; 63 if (qop_value == null || qop_value.trim().length() == 0 64 || qop_value.trim().equalsIgnoreCase("auth")) { 65 A2 = method + ":" + digest_uri_value; 66 } else { 67 if (entity_body == null) 68 entity_body = ""; 69 A2 = method + ":" + digest_uri_value + ":" + H(entity_body); 70 } 71 72 String request_digest = null; 73 74 if (cnonce_value != null && qop_value != null && nc_value != null 75 && (qop_value.equalsIgnoreCase("auth") || qop_value.equalsIgnoreCase("auth-int"))) 76 77 { 78 request_digest = KD(hashUserNameRealmPasswd, nonce_value + ":" + nc_value + ":" + cnonce_value + ":" 79 + qop_value + ":" + H(A2)); 80 81 } else { 82 request_digest = KD(hashUserNameRealmPasswd, nonce_value + ":" + H(A2)); 83 } 84 85 return request_digest; 86 87 88 } 89 90 /** 91 * Calculates an http authentication response in accordance with rfc2617. 92 * <p> 93 * 94 * @param algorithm a string indicating a pair of algorithms (MD5 (default), or MD5-sess) used 95 * to produce the digest and a checksum. 96 * @param username_value username_value (see rfc2617) 97 * @param realm_value A string that has been displayed to the user in order to determine the 98 * context of the username and password to use. 99 * @param passwd the password to encode in the challenge response. 100 * @param nonce_value A server-specified data string provided in the challenge. 101 * @param cnonce_value an optional client-chosen value whose purpose is to foil chosen 102 * plaintext attacks. 103 * @param method the SIP method of the request being challenged. 104 * @param digest_uri_value the value of the "uri" directive on the Authorization header in the 105 * request. 106 * @param entity_body the entity-body 107 * @param qop_value Indicates what "quality of protection" the client has applied to the 108 * message. 109 * @param nc_value the hexadecimal count of the number of requests (including the current 110 * request) that the client has sent with the nonce value in this request. 111 * @return a digest response as defined in rfc2617 112 * @throws NullPointerException in case of incorrectly null parameters. 113 */ calculateResponse(String algorithm, String username_value, String realm_value, String passwd, String nonce_value, String nc_value, String cnonce_value, String method, String digest_uri_value, String entity_body, String qop_value, StackLogger stackLogger)114 static String calculateResponse(String algorithm, String username_value, String realm_value, 115 String passwd, String nonce_value, String nc_value, String cnonce_value, 116 String method, String digest_uri_value, String entity_body, String qop_value, 117 StackLogger stackLogger) { 118 if (stackLogger.isLoggingEnabled()) { 119 stackLogger.logDebug("trying to authenticate using : " + algorithm + ", " 120 + username_value + ", " + realm_value + ", " 121 + (passwd != null && passwd.trim().length() > 0) + ", " + nonce_value + ", " 122 + nc_value + ", " + cnonce_value + ", " + method + ", " + digest_uri_value 123 + ", " + entity_body + ", " + qop_value); 124 } 125 126 if (username_value == null || realm_value == null || passwd == null || method == null 127 || digest_uri_value == null || nonce_value == null) 128 throw new NullPointerException( 129 "Null parameter to MessageDigestAlgorithm.calculateResponse()"); 130 131 // The following follows closely the algorithm for generating a response 132 // digest as specified by rfc2617 133 String A1 = null; 134 135 if (algorithm == null || algorithm.trim().length() == 0 136 || algorithm.trim().equalsIgnoreCase("MD5")) { 137 A1 = username_value + ":" + realm_value + ":" + passwd; 138 } else { 139 if (cnonce_value == null || cnonce_value.length() == 0) 140 throw new NullPointerException( 141 "cnonce_value may not be absent for MD5-Sess algorithm."); 142 143 A1 = H(username_value + ":" + realm_value + ":" + passwd) + ":" + nonce_value + ":" 144 + cnonce_value; 145 } 146 147 String A2 = null; 148 if (qop_value == null || qop_value.trim().length() == 0 149 || qop_value.trim().equalsIgnoreCase("auth")) { 150 A2 = method + ":" + digest_uri_value; 151 } else { 152 if (entity_body == null) 153 entity_body = ""; 154 A2 = method + ":" + digest_uri_value + ":" + H(entity_body); 155 } 156 157 String request_digest = null; 158 159 if (cnonce_value != null && qop_value != null && nc_value != null 160 && (qop_value.equalsIgnoreCase("auth") || qop_value.equalsIgnoreCase("auth-int"))) 161 162 { 163 request_digest = KD(H(A1), nonce_value + ":" + nc_value + ":" + cnonce_value + ":" 164 + qop_value + ":" + H(A2)); 165 166 } else { 167 request_digest = KD(H(A1), nonce_value + ":" + H(A2)); 168 } 169 170 return request_digest; 171 } 172 173 /** 174 * Defined in rfc 2617 as H(data) = MD5(data); 175 * 176 * @param data data 177 * @return MD5(data) 178 */ H(String data)179 private static String H(String data) { 180 try { 181 MessageDigest digest = MessageDigest.getInstance("MD5"); 182 183 return toHexString(digest.digest(data.getBytes())); 184 } catch (NoSuchAlgorithmException ex) { 185 // shouldn't happen 186 throw new RuntimeException("Failed to instantiate an MD5 algorithm", ex); 187 } 188 } 189 190 /** 191 * Defined in rfc 2617 as KD(secret, data) = H(concat(secret, ":", data)) 192 * 193 * @param data data 194 * @param secret secret 195 * @return H(concat(secret, ":", data)); 196 */ KD(String secret, String data)197 private static String KD(String secret, String data) { 198 return H(secret + ":" + data); 199 } 200 201 // the following code was copied from the NIST-SIP instant 202 // messenger (its author is Olivier Deruelle). Thanks for making it public! 203 /** 204 * to hex converter 205 */ 206 private static final char[] toHex = { 207 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' 208 }; 209 210 /** 211 * Converts b[] to hex string. 212 * 213 * @param b the bte array to convert 214 * @return a Hex representation of b. 215 */ toHexString(byte b[])216 private static String toHexString(byte b[]) { 217 int pos = 0; 218 char[] c = new char[b.length * 2]; 219 for (int i = 0; i < b.length; i++) { 220 c[pos++] = toHex[(b[i] >> 4) & 0x0F]; 221 c[pos++] = toHex[b[i] & 0x0f]; 222 } 223 return new String(c); 224 } 225 } 226