1 /*
   2  * Copyright (c) 2003, 2013, 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 sun.security.pkcs11;
  27 
  28 import java.math.BigInteger;
  29 
  30 import java.security.*;
  31 import java.security.spec.*;
  32 
  33 import javax.crypto.*;
  34 import javax.crypto.interfaces.*;
  35 import javax.crypto.spec.*;
  36 
  37 import static sun.security.pkcs11.TemplateManager.*;
  38 import sun.security.pkcs11.wrapper.*;
  39 import static sun.security.pkcs11.wrapper.PKCS11Constants.*;
  40 import sun.security.util.KeyUtil;
  41 
  42 /**
  43  * KeyAgreement implementation class. This class currently supports
  44  * DH.
  45  *
  46  * @author  Andreas Sterbenz
  47  * @since   1.5
  48  */
  49 final class P11KeyAgreement extends KeyAgreementSpi {
  50 
  51     // token instance
  52     private final Token token;
  53 
  54     // algorithm name
  55     private final String algorithm;
  56 
  57     // mechanism id
  58     private final long mechanism;
  59 
  60     // private key, if initialized
  61     private P11Key privateKey;
  62 
  63     // other sides public value ("y"), if doPhase() already called
  64     private BigInteger publicValue;
  65 
  66     // length of the secret to be derived
  67     private int secretLen;
  68 
  69     // KeyAgreement from SunJCE as fallback for > 2 party agreement
  70     private KeyAgreement multiPartyAgreement;
  71 
  72     P11KeyAgreement(Token token, String algorithm, long mechanism) {
  73         super();
  74         this.token = token;
  75         this.algorithm = algorithm;
  76         this.mechanism = mechanism;
  77     }
  78 
  79     // see JCE spec
  80     protected void engineInit(Key key, SecureRandom random)
  81             throws InvalidKeyException {
  82         if (key instanceof PrivateKey == false) {
  83             throw new InvalidKeyException
  84                         ("Key must be instance of PrivateKey");
  85         }
  86         privateKey = P11KeyFactory.convertKey(token, key, algorithm);
  87         publicValue = null;
  88         multiPartyAgreement = null;
  89     }
  90 
  91     // see JCE spec
  92     protected void engineInit(Key key, AlgorithmParameterSpec params,
  93             SecureRandom random) throws InvalidKeyException,
  94             InvalidAlgorithmParameterException {
  95         if (params != null) {
  96             throw new InvalidAlgorithmParameterException
  97                         ("Parameters not supported");
  98         }
  99         engineInit(key, random);
 100     }
 101 
 102     // see JCE spec
 103     protected Key engineDoPhase(Key key, boolean lastPhase)
 104             throws InvalidKeyException, IllegalStateException {
 105         if (privateKey == null) {
 106             throw new IllegalStateException("Not initialized");
 107         }
 108         if (publicValue != null) {
 109             throw new IllegalStateException("Phase already executed");
 110         }
 111         // PKCS#11 only allows key agreement between 2 parties
 112         // JCE allows >= 2 parties. To support that case (for compatibility
 113         // and to pass JCK), fall back to SunJCE in this case.
 114         // NOTE that we initialize using the P11Key, which will fail if it
 115         // is sensitive/unextractable. However, this is not an issue in the
 116         // compatibility configuration, which is all we are targeting here.
 117         if ((multiPartyAgreement != null) || (lastPhase == false)) {
 118             if (multiPartyAgreement == null) {
 119                 try {
 120                     multiPartyAgreement = KeyAgreement.getInstance
 121                         ("DH", P11Util.getSunJceProvider());
 122                     multiPartyAgreement.init(privateKey);
 123                 } catch (NoSuchAlgorithmException e) {
 124                     throw new InvalidKeyException
 125                         ("Could not initialize multi party agreement", e);
 126                 }
 127             }
 128             return multiPartyAgreement.doPhase(key, lastPhase);
 129         }
 130         if ((key instanceof PublicKey == false)
 131                 || (key.getAlgorithm().equals(algorithm) == false)) {
 132             throw new InvalidKeyException
 133                 ("Key must be a PublicKey with algorithm DH");
 134         }
 135         BigInteger p, g, y;
 136         if (key instanceof DHPublicKey) {
 137             DHPublicKey dhKey = (DHPublicKey)key;
 138 
 139             // validate the Diffie-Hellman public key
 140             KeyUtil.validate(dhKey);
 141 
 142             y = dhKey.getY();
 143             DHParameterSpec params = dhKey.getParams();
 144             p = params.getP();
 145             g = params.getG();
 146         } else {
 147             // normally, DH PublicKeys will always implement DHPublicKey
 148             // just in case not, attempt conversion
 149             P11DHKeyFactory kf = new P11DHKeyFactory(token, "DH");
 150             try {
 151                 DHPublicKeySpec spec = kf.engineGetKeySpec(
 152                         key, DHPublicKeySpec.class);
 153 
 154                 // validate the Diffie-Hellman public key
 155                 KeyUtil.validate(spec);
 156 
 157                 y = spec.getY();
 158                 p = spec.getP();
 159                 g = spec.getG();
 160             } catch (InvalidKeySpecException e) {
 161                 throw new InvalidKeyException("Could not obtain key values", e);
 162             }
 163         }
 164         // if parameters of private key are accessible, verify that
 165         // they match parameters of public key
 166         // XXX p and g should always be readable, even if the key is sensitive
 167         if (privateKey instanceof DHPrivateKey) {
 168             DHPrivateKey dhKey = (DHPrivateKey)privateKey;
 169             DHParameterSpec params = dhKey.getParams();
 170             if ((p.equals(params.getP()) == false)
 171                                 || (g.equals(params.getG()) == false)) {
 172                 throw new InvalidKeyException
 173                 ("PublicKey DH parameters must match PrivateKey DH parameters");
 174             }
 175         }
 176         publicValue = y;
 177         // length of the secret is length of key
 178         secretLen = (p.bitLength() + 7) >> 3;
 179         return null;
 180     }
 181 
 182     // see JCE spec
 183     protected byte[] engineGenerateSecret() throws IllegalStateException {
 184         if (multiPartyAgreement != null) {
 185             byte[] val = multiPartyAgreement.generateSecret();
 186             multiPartyAgreement = null;
 187             return val;
 188         }
 189         if ((privateKey == null) || (publicValue == null)) {
 190             throw new IllegalStateException("Not initialized correctly");
 191         }
 192         Session session = null;
 193         try {
 194             session = token.getOpSession();
 195             CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[] {
 196                 new CK_ATTRIBUTE(CKA_CLASS, CKO_SECRET_KEY),
 197                 new CK_ATTRIBUTE(CKA_KEY_TYPE, CKK_GENERIC_SECRET),
 198             };
 199             attributes = token.getAttributes
 200                 (O_GENERATE, CKO_SECRET_KEY, CKK_GENERIC_SECRET, attributes);
 201             long keyID = token.p11.C_DeriveKey(session.id(),
 202                 new CK_MECHANISM(mechanism, publicValue), privateKey.keyID,
 203                 attributes);
 204             attributes = new CK_ATTRIBUTE[] {
 205                 new CK_ATTRIBUTE(CKA_VALUE)
 206             };
 207             token.p11.C_GetAttributeValue(session.id(), keyID, attributes);
 208             byte[] secret = attributes[0].getByteArray();
 209             token.p11.C_DestroyObject(session.id(), keyID);
 210             // Some vendors, e.g. NSS, trim off the leading 0x00 byte(s) from
 211             // the generated secret. Thus, we need to check the secret length
 212             // and trim/pad it so the returned value has the same length as
 213             // the modulus size
 214             if (secret.length == secretLen) {
 215                 return secret;
 216             } else {
 217                 if (secret.length > secretLen) {
 218                     // Shouldn't happen; but check just in case
 219                     throw new ProviderException("generated secret is out-of-range");
 220                 }
 221                 byte[] newSecret = new byte[secretLen];
 222                 System.arraycopy(secret, 0, newSecret, secretLen - secret.length,
 223                     secret.length);
 224                 return newSecret;
 225             }
 226         } catch (PKCS11Exception e) {
 227             throw new ProviderException("Could not derive key", e);
 228         } finally {
 229             publicValue = null;
 230             token.releaseSession(session);
 231         }
 232     }
 233 
 234     // see JCE spec
 235     protected int engineGenerateSecret(byte[] sharedSecret, int
 236             offset) throws IllegalStateException, ShortBufferException {
 237         if (multiPartyAgreement != null) {
 238             int n = multiPartyAgreement.generateSecret(sharedSecret, offset);
 239             multiPartyAgreement = null;
 240             return n;
 241         }
 242         if (offset + secretLen > sharedSecret.length) {
 243             throw new ShortBufferException("Need " + secretLen
 244                 + " bytes, only " + (sharedSecret.length - offset) + " available");
 245         }
 246         byte[] secret = engineGenerateSecret();
 247         System.arraycopy(secret, 0, sharedSecret, offset, secret.length);
 248         return secret.length;
 249     }
 250 
 251     // see JCE spec
 252     protected SecretKey engineGenerateSecret(String algorithm)
 253             throws IllegalStateException, NoSuchAlgorithmException,
 254             InvalidKeyException {
 255         if (multiPartyAgreement != null) {
 256             SecretKey key = multiPartyAgreement.generateSecret(algorithm);
 257             multiPartyAgreement = null;
 258             return key;
 259         }
 260         if (algorithm == null) {
 261             throw new NoSuchAlgorithmException("Algorithm must not be null");
 262         }
 263         if (algorithm.equals("TlsPremasterSecret")) {
 264             // For now, only perform native derivation for TlsPremasterSecret
 265             // as that is required for FIPS compliance.
 266             // For other algorithms, there are unresolved issues regarding
 267             // how this should work in JCE plus a Solaris truncation bug.
 268             // (bug not yet filed).
 269             return nativeGenerateSecret(algorithm);
 270         }
 271         byte[] secret = engineGenerateSecret();
 272         // Maintain compatibility for SunJCE:
 273         // verify secret length is sensible for algorithm / truncate
 274         // return generated key itself if possible
 275         int keyLen;
 276         if (algorithm.equalsIgnoreCase("DES")) {
 277             keyLen = 8;
 278         } else if (algorithm.equalsIgnoreCase("DESede")) {
 279             keyLen = 24;
 280         } else if (algorithm.equalsIgnoreCase("Blowfish")) {
 281             keyLen = Math.min(56, secret.length);
 282         } else if (algorithm.equalsIgnoreCase("TlsPremasterSecret")) {
 283             keyLen = secret.length;
 284         } else {
 285             throw new NoSuchAlgorithmException
 286                 ("Unknown algorithm " + algorithm);
 287         }
 288         if (secret.length < keyLen) {
 289             throw new InvalidKeyException("Secret too short");
 290         }
 291         if (algorithm.equalsIgnoreCase("DES") ||
 292             algorithm.equalsIgnoreCase("DESede")) {
 293                 for (int i = 0; i < keyLen; i+=8) {
 294                     P11SecretKeyFactory.fixDESParity(secret, i);
 295                 }
 296         }
 297         return new SecretKeySpec(secret, 0, keyLen, algorithm);
 298     }
 299 
 300     private SecretKey nativeGenerateSecret(String algorithm)
 301             throws IllegalStateException, NoSuchAlgorithmException,
 302             InvalidKeyException {
 303         if ((privateKey == null) || (publicValue == null)) {
 304             throw new IllegalStateException("Not initialized correctly");
 305         }
 306         long keyType = CKK_GENERIC_SECRET;
 307         Session session = null;
 308         try {
 309             session = token.getObjSession();
 310             CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[] {
 311                 new CK_ATTRIBUTE(CKA_CLASS, CKO_SECRET_KEY),
 312                 new CK_ATTRIBUTE(CKA_KEY_TYPE, keyType),
 313             };
 314             attributes = token.getAttributes
 315                 (O_GENERATE, CKO_SECRET_KEY, keyType, attributes);
 316             long keyID = token.p11.C_DeriveKey(session.id(),
 317                 new CK_MECHANISM(mechanism, publicValue), privateKey.keyID,
 318                 attributes);
 319             CK_ATTRIBUTE[] lenAttributes = new CK_ATTRIBUTE[] {
 320                 new CK_ATTRIBUTE(CKA_VALUE_LEN),
 321             };
 322             token.p11.C_GetAttributeValue(session.id(), keyID, lenAttributes);
 323             int keyLen = (int)lenAttributes[0].getLong();
 324             SecretKey key = P11Key.secretKey
 325                         (session, keyID, algorithm, keyLen << 3, attributes);
 326             if ("RAW".equals(key.getFormat())) {
 327                 // Workaround for Solaris bug 6318543.
 328                 // Strip leading zeroes ourselves if possible (key not sensitive).
 329                 // This should be removed once the Solaris fix is available
 330                 // as here we always retrieve the CKA_VALUE even for tokens
 331                 // that do not have that bug.
 332                 byte[] keyBytes = key.getEncoded();
 333                 byte[] newBytes = KeyUtil.trimZeroes(keyBytes);
 334                 if (keyBytes != newBytes) {
 335                     key = new SecretKeySpec(newBytes, algorithm);
 336                 }
 337             }
 338             return key;
 339         } catch (PKCS11Exception e) {
 340             throw new InvalidKeyException("Could not derive key", e);
 341         } finally {
 342             publicValue = null;
 343             token.releaseSession(session);
 344         }
 345     }
 346 
 347 }