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