Source for gnu.javax.crypto.keyring.PasswordAuthenticatedEntry

   1: /* PasswordAuthenticatedEntry.java --
   2:    Copyright (C) 2003, 2006 Free Software Foundation, Inc.
   3: 
   4: This file is a part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2 of the License, or (at
   9: your option) any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; if not, write to the Free Software
  18: Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
  19: 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: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version.  */
  37: 
  38: 
  39: package gnu.javax.crypto.keyring;
  40: 
  41: import gnu.java.security.Configuration;
  42: import gnu.java.security.Registry;
  43: import gnu.java.security.prng.IRandom;
  44: import gnu.java.security.prng.LimitReachedException;
  45: import gnu.java.security.util.PRNG;
  46: import gnu.java.security.util.Util;
  47: import gnu.javax.crypto.mac.IMac;
  48: import gnu.javax.crypto.mac.MacFactory;
  49: import gnu.javax.crypto.mac.MacInputStream;
  50: import gnu.javax.crypto.mac.MacOutputStream;
  51: import gnu.javax.crypto.prng.IPBE;
  52: import gnu.javax.crypto.prng.PRNGFactory;
  53: 
  54: import java.io.ByteArrayInputStream;
  55: import java.io.ByteArrayOutputStream;
  56: import java.io.DataInputStream;
  57: import java.io.DataOutputStream;
  58: import java.io.IOException;
  59: import java.security.InvalidKeyException;
  60: import java.util.Arrays;
  61: import java.util.HashMap;
  62: import java.util.Iterator;
  63: import java.util.logging.Logger;
  64: 
  65: /**
  66:  * An entry authenticated with a password-based MAC.
  67:  */
  68: public final class PasswordAuthenticatedEntry
  69:     extends MaskableEnvelopeEntry
  70:     implements PasswordProtectedEntry, Registry
  71: {
  72:   private static final Logger log = Logger.getLogger(PasswordAuthenticatedEntry.class.getName());
  73:   public static final int TYPE = 3;
  74: 
  75:   public PasswordAuthenticatedEntry(String mac, int maclen,
  76:                                     Properties properties)
  77:   {
  78:     super(TYPE, properties);
  79:     if (mac == null || mac.length() == 0)
  80:       throw new IllegalArgumentException("no MAC specified");
  81:     this.properties.put("mac", mac);
  82:     this.properties.put("maclen", String.valueOf(maclen));
  83:     setMasked(false);
  84:   }
  85: 
  86:   private PasswordAuthenticatedEntry()
  87:   {
  88:     super(TYPE);
  89:     setMasked(true);
  90:   }
  91: 
  92:   public static PasswordAuthenticatedEntry decode(DataInputStream in,
  93:                                                   char[] password)
  94:       throws IOException
  95:   {
  96:     PasswordAuthenticatedEntry entry = new PasswordAuthenticatedEntry();
  97:     entry.properties.decode(in);
  98:     IMac mac = entry.getMac(password);
  99:     int len = in.readInt() - mac.macSize();
 100:     MeteredInputStream min = new MeteredInputStream(in, len);
 101:     MacInputStream macin = new MacInputStream(min, mac);
 102:     DataInputStream in2 = new DataInputStream(macin);
 103:     entry.setMasked(false);
 104:     entry.decodeEnvelope(in2);
 105:     byte[] macValue = new byte[mac.macSize()];
 106:     in.readFully(macValue);
 107:     if (! Arrays.equals(macValue, mac.digest()))
 108:       throw new MalformedKeyringException("MAC verification failed");
 109:     return entry;
 110:   }
 111: 
 112:   public static PasswordAuthenticatedEntry decode(DataInputStream in)
 113:       throws IOException
 114:   {
 115:     PasswordAuthenticatedEntry entry = new PasswordAuthenticatedEntry();
 116:     entry.defaultDecode(in);
 117:     if (! entry.properties.containsKey("mac"))
 118:       throw new MalformedKeyringException("no MAC");
 119:     if (! entry.properties.containsKey("maclen"))
 120:       throw new MalformedKeyringException("no MAC length");
 121:     if (! entry.properties.containsKey("salt"))
 122:       throw new MalformedKeyringException("no salt");
 123:     return entry;
 124:   }
 125: 
 126:   public void verify(char[] password)
 127:   {
 128:     if (Configuration.DEBUG)
 129:       log.entering(this.getClass().getName(), "verify");
 130:     if (isMasked() && payload != null)
 131:       {
 132:         if (Configuration.DEBUG)
 133:           log.fine("payload to verify: " + Util.dumpString(payload));
 134:         long tt = -System.currentTimeMillis();
 135:         IMac m = null;
 136:         try
 137:           {
 138:             m = getMac(password);
 139:           }
 140:         catch (Exception x)
 141:           {
 142:             throw new IllegalArgumentException(x.toString(), x);
 143:           }
 144:         int limit = payload.length - m.macSize();
 145:         m.update(payload, 0, limit);
 146:         byte[] macValue = new byte[m.macSize()];
 147:         System.arraycopy(payload, payload.length - macValue.length, macValue,
 148:                          0, macValue.length);
 149:         if (! Arrays.equals(macValue, m.digest()))
 150:           throw new IllegalArgumentException("MAC verification failed");
 151:         setMasked(false);
 152:         ByteArrayInputStream bais;
 153:         try
 154:           {
 155:             bais = new ByteArrayInputStream(payload, 0, limit);
 156:             DataInputStream in = new DataInputStream(bais);
 157:             decodeEnvelope(in);
 158:           }
 159:         catch (IOException ioe)
 160:           {
 161:             throw new IllegalArgumentException("malformed keyring fragment");
 162:           }
 163:         tt += System.currentTimeMillis();
 164:         if (Configuration.DEBUG)
 165:           log.fine("Verified in " + tt + "ms.");
 166:       }
 167:     else if (Configuration.DEBUG)
 168:       log.fine("Skip verification; "
 169:                + (isMasked() ? "null payload" : "unmasked"));
 170:     if (Configuration.DEBUG)
 171:       log.exiting(this.getClass().getName(), "verify");
 172:   }
 173: 
 174:   public void authenticate(char[] password) throws IOException
 175:   {
 176:     if (Configuration.DEBUG)
 177:       log.entering(this.getClass().getName(), "authenticate");
 178:     long tt = -System.currentTimeMillis();
 179:     long t1 = -System.currentTimeMillis();
 180:     if (isMasked())
 181:       throw new IllegalStateException("entry is masked");
 182:     byte[] salt = new byte[8];
 183:     PRNG.getInstance().nextBytes(salt);
 184:     t1 += System.currentTimeMillis();
 185:     if (Configuration.DEBUG)
 186:       log.fine("-- Generated salt in " + t1 + "ms.");
 187:     properties.put("salt", Util.toString(salt));
 188:     IMac m = getMac(password);
 189:     ByteArrayOutputStream bout = new ByteArrayOutputStream(1024);
 190:     MacOutputStream macout = new MacOutputStream(bout, m);
 191:     DataOutputStream out2 = new DataOutputStream(macout);
 192:     for (Iterator it = entries.iterator(); it.hasNext();)
 193:       {
 194:         Entry entry = (Entry) it.next();
 195:         if (Configuration.DEBUG)
 196:           log.fine("-- About to authenticate one " + entry);
 197:         t1 = -System.currentTimeMillis();
 198:         entry.encode(out2);
 199:         t1 += System.currentTimeMillis();
 200:         if (Configuration.DEBUG)
 201:           log.fine("-- Authenticated an Entry in " + t1 + "ms.");
 202:       }
 203:     bout.write(m.digest());
 204:     payload = bout.toByteArray();
 205:     if (Configuration.DEBUG)
 206:       log.fine("authenticated payload: " + Util.dumpString(payload));
 207:     setMasked(true);
 208:     tt += System.currentTimeMillis();
 209:     if (Configuration.DEBUG)
 210:       {
 211:         log.fine("Authenticated in " + tt + "ms.");
 212:         log.exiting(this.getClass().getName(), "authenticate");
 213:       }
 214:   }
 215: 
 216:   public void encode(DataOutputStream out, char[] password) throws IOException
 217:   {
 218:     authenticate(password);
 219:     encode(out);
 220:   }
 221: 
 222:   protected void encodePayload(DataOutputStream out) throws IOException
 223:   {
 224:     if (payload == null)
 225:       {
 226:         log.fine("Null payload: " + this);
 227:         throw new IllegalStateException("mac not computed");
 228:       }
 229:   }
 230: 
 231:   private IMac getMac(char[] password) throws MalformedKeyringException
 232:   {
 233:     if (Configuration.DEBUG)
 234:       log.entering(this.getClass().getName(), "getMac");
 235:     String saltString = properties.get("salt");
 236:     if (saltString == null)
 237:       throw new MalformedKeyringException("no salt");
 238:     byte[] salt = Util.toBytesFromString(saltString);
 239:     String macAlgorithm = properties.get("mac");
 240:     IMac mac = MacFactory.getInstance(macAlgorithm);
 241:     if (mac == null)
 242:       throw new MalformedKeyringException("no such mac: " + macAlgorithm);
 243:     String macLenString = properties.get("maclen");
 244:     if (macLenString == null)
 245:       throw new MalformedKeyringException("no MAC length");
 246:     int maclen;
 247:     try
 248:       {
 249:         maclen = Integer.parseInt(macLenString);
 250:       }
 251:     catch (NumberFormatException nfe)
 252:       {
 253:         throw new MalformedKeyringException("bad MAC length");
 254:       }
 255:     HashMap pbAttr = new HashMap();
 256:     pbAttr.put(IPBE.PASSWORD, password);
 257:     pbAttr.put(IPBE.SALT, salt);
 258:     pbAttr.put(IPBE.ITERATION_COUNT, ITERATION_COUNT);
 259:     IRandom kdf = PRNGFactory.getInstance("PBKDF2-HMAC-SHA");
 260:     kdf.init(pbAttr);
 261:     int keylen = mac.macSize();
 262:     byte[] dk = new byte[keylen];
 263:     try
 264:       {
 265:         kdf.nextBytes(dk, 0, keylen);
 266:       }
 267:     catch (LimitReachedException shouldNotHappen)
 268:       {
 269:         throw new Error(shouldNotHappen.toString());
 270:       }
 271:     HashMap macAttr = new HashMap();
 272:     macAttr.put(IMac.MAC_KEY_MATERIAL, dk);
 273:     macAttr.put(IMac.TRUNCATED_SIZE, Integer.valueOf(maclen));
 274:     try
 275:       {
 276:         mac.init(macAttr);
 277:       }
 278:     catch (InvalidKeyException shouldNotHappen)
 279:       {
 280:         throw new Error(shouldNotHappen.toString());
 281:       }
 282:     if (Configuration.DEBUG)
 283:       log.exiting(this.getClass().getName(), "getMac");
 284:     return mac;
 285:   }
 286: }