Source for gnu.inet.util.SaslCramMD5

   1: /*
   2:  * SaslCramMD5.java
   3:  * Copyright (C) 2004 The Free Software Foundation
   4:  * 
   5:  * This file is part of GNU inetlib, a library.
   6:  * 
   7:  * GNU inetlib is free software; you can redistribute it and/or modify
   8:  * it under the terms of the GNU General Public License as published by
   9:  * the Free Software Foundation; either version 2 of the License, or
  10:  * (at your option) any later version.
  11:  * 
  12:  * GNU inetlib is distributed in the hope that it will be useful,
  13:  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14:  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15:  * GNU General Public License for more details.
  16:  * 
  17:  * You should have received a copy of the GNU General Public License
  18:  * along with this library; if not, write to the Free Software
  19:  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  20:  *
  21:  * Linking this library statically or dynamically with other modules is
  22:  * making a combined work based on this library.  Thus, the terms and
  23:  * conditions of the GNU General Public License cover the whole
  24:  * combination.
  25:  *
  26:  * As a special exception, the copyright holders of this library give you
  27:  * permission to link this library with independent modules to produce an
  28:  * executable, regardless of the license terms of these independent
  29:  * modules, and to copy and distribute the resulting executable under
  30:  * terms of your choice, provided that you also meet, for each linked
  31:  * independent module, the terms and conditions of the license of that
  32:  * module.  An independent module is a module which is not derived from
  33:  * or based on this library.  If you modify this library, you may extend
  34:  * this exception to your version of the library, but you are not
  35:  * obliged to do so.  If you do not wish to do so, delete this
  36:  * exception statement from your version.
  37:  */
  38: 
  39: package gnu.inet.util;
  40: 
  41: import java.io.UnsupportedEncodingException;
  42: import java.security.MessageDigest;
  43: import java.security.NoSuchAlgorithmException;
  44: import javax.security.sasl.SaslClient;
  45: import javax.security.sasl.SaslException;
  46: 
  47: /**
  48:  * SASL mechanism for CRAM-MD5.
  49:  *
  50:  * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
  51:  */
  52: public class SaslCramMD5
  53:   implements SaslClient
  54: {
  55: 
  56:   private String username;
  57:   private String password;
  58:   private boolean complete;
  59: 
  60:   public SaslCramMD5(String username, String password)
  61:   {
  62:     this.username = username;
  63:     this.password = password;
  64:   }
  65: 
  66:   public String getMechanismName()
  67:   {
  68:     return "CRAM-MD5";
  69:   }
  70: 
  71:   public boolean hasInitialResponse()
  72:   {
  73:     return false;
  74:   }
  75: 
  76:   public byte[] evaluateChallenge(byte[] challenge)
  77:     throws SaslException
  78:   {
  79:     try
  80:       {
  81:         byte[] s = password.getBytes("US-ASCII");
  82:         byte[] digest = hmac_md5(s, challenge);
  83:         byte[] r0 = username.getBytes("US-ASCII");
  84:         byte[] r1 = new byte[r0.length + digest.length + 1];
  85:         System.arraycopy(r0, 0, r1, 0, r0.length); // add username
  86:         r1[r0.length] = 0x20; // SPACE
  87:         System.arraycopy(digest, 0, r1, r0.length+1, digest.length);
  88:         complete = true;
  89:         return r1;
  90:       }
  91:     catch (UnsupportedEncodingException e)
  92:       {
  93:         String msg = "Username or password contains non-ASCII characters";
  94:         throw new SaslException(msg, e);
  95:       }
  96:     catch (NoSuchAlgorithmException e)
  97:       {
  98:         String msg = "MD5 algorithm not available";
  99:         throw new SaslException(msg, e);
 100:       }
 101:   }
 102: 
 103:   public boolean isComplete()
 104:   {
 105:     return complete;
 106:   }
 107: 
 108:   public byte[] unwrap(byte[] incoming, int off, int len)
 109:     throws SaslException
 110:   {
 111:     byte[] ret = new byte[len - off];
 112:     System.arraycopy(incoming, off, ret, 0, len);
 113:     return ret;
 114:   }
 115: 
 116:   public byte[] wrap(byte[] outgoing, int off, int len)
 117:     throws SaslException
 118:   {
 119:     byte[] ret = new byte[len - off];
 120:     System.arraycopy(outgoing, off, ret, 0, len);
 121:     return ret;
 122:   }
 123: 
 124:   public Object getNegotiatedProperty(String name)
 125:   {
 126:     return null;
 127:   }
 128: 
 129:   public void dispose()
 130:   {
 131:   }
 132: 
 133:   /**
 134:    * Computes a CRAM digest using the HMAC algorithm:
 135:    * <pre>
 136:    * MD5(key XOR opad, MD5(key XOR ipad, text))
 137:    * </pre>.
 138:    * <code>secret</code> is null-padded to a length of 64 bytes.
 139:    * If the shared secret is longer than 64 bytes, the MD5 digest of the
 140:    * shared secret is used as a 16 byte input to the keyed MD5 calculation.
 141:    * See RFC 2104 for details.
 142:    */
 143:   private static byte[] hmac_md5(byte[] key, byte[] text)
 144:     throws NoSuchAlgorithmException
 145:   {
 146:     byte[] k_ipad = new byte[64];
 147:     byte[] k_opad = new byte[64];
 148:     byte[] digest;
 149:     MessageDigest md5 = MessageDigest.getInstance("MD5");
 150:     // if key is longer than 64 bytes reset it to key=MD5(key)
 151:     if (key.length>64)
 152:       {
 153:         md5.update(key);
 154:         key = md5.digest();
 155:       }
 156:     // start out by storing key in pads
 157:     System.arraycopy(key, 0, k_ipad, 0, key.length);
 158:     System.arraycopy(key, 0, k_opad, 0, key.length);
 159:     // XOR key with ipad and opad values
 160:     for (int i=0; i<64; i++)
 161:       {
 162:         k_ipad[i] ^= 0x36;
 163:         k_opad[i] ^= 0x5c;
 164:       }
 165:     // perform inner MD5
 166:     md5.reset();
 167:     md5.update(k_ipad);
 168:     md5.update(text);
 169:     digest = md5.digest();
 170:     // perform outer MD5
 171:     md5.reset();
 172:     md5.update(k_opad);
 173:     md5.update(digest);
 174:     digest = md5.digest();
 175:     return digest;
 176:   }
 177:   
 178: }