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             privateKey.incNativeKeyRef();
 213             long keyID;
 214             try {
 215                 keyID = token.p11.C_DeriveKey(session.id(),
 216                         new CK_MECHANISM(mechanism, publicValue),
 217                         privateKey.keyID, attributes);
 218             } finally {
 219                 privateKey.decNativeKeyRef();
 220             }
 221             attributes = new CK_ATTRIBUTE[] {
 222                 new CK_ATTRIBUTE(CKA_VALUE)
 223             };
 224             token.p11.C_GetAttributeValue(session.id(), keyID, attributes);
 225             byte[] secret = attributes[0].getByteArray();
 226             token.p11.C_DestroyObject(session.id(), keyID);
 227             // Some vendors, e.g. NSS, trim off the leading 0x00 byte(s) from
 228             // the generated secret. Thus, we need to check the secret length
 229             // and trim/pad it so the returned value has the same length as
 230             // the modulus size
 231             if (secret.length == secretLen) {
 232                 return secret;
 233             } else {
 234                 if (secret.length > secretLen) {
 235                     // Shouldn't happen; but check just in case
 236                     throw new ProviderException("generated secret is out-of-range");
 237                 }
 238                 byte[] newSecret = new byte[secretLen];
 239                 System.arraycopy(secret, 0, newSecret, secretLen - secret.length,
 240                     secret.length);
 241                 return newSecret;
 242             }
 243         } catch (PKCS11Exception e) {
 244             throw new ProviderException("Could not derive key", e);
 245         } finally {
 246             publicValue = null;
 247             token.releaseSession(session);
 248         }
 249     }
 250 
 251     // see JCE spec
 252     protected int engineGenerateSecret(byte[] sharedSecret, int
 253             offset) throws IllegalStateException, ShortBufferException {
 254         if (multiPartyAgreement != null) {
 255             int n = multiPartyAgreement.generateSecret(sharedSecret, offset);
 256             multiPartyAgreement = null;
 257             return n;
 258         }
 259         if (offset + secretLen > sharedSecret.length) {
 260             throw new ShortBufferException("Need " + secretLen
 261                 + " bytes, only " + (sharedSecret.length - offset) + " available");
 262         }
 263         byte[] secret = engineGenerateSecret();
 264         System.arraycopy(secret, 0, sharedSecret, offset, secret.length);
 265         return secret.length;
 266     }
 267 
 268     // see JCE spec
 269     protected SecretKey engineGenerateSecret(String algorithm)
 270             throws IllegalStateException, NoSuchAlgorithmException,
 271             InvalidKeyException {
 272         if (multiPartyAgreement != null) {
 273             SecretKey key = multiPartyAgreement.generateSecret(algorithm);
 274             multiPartyAgreement = null;
 275             return key;
 276         }
 277         if (algorithm == null) {
 278             throw new NoSuchAlgorithmException("Algorithm must not be null");
 279         }
 280 
 281         if (algorithm.equals("TlsPremasterSecret")) {
 282             // For now, only perform native derivation for TlsPremasterSecret
 283             // as that is required for FIPS compliance.
 284             // For other algorithms, there are unresolved issues regarding
 285             // how this should work in JCE plus a Solaris truncation bug.
 286             // (bug not yet filed).
 287             return nativeGenerateSecret(algorithm);
 288         }
 289 
 290         if (!algorithm.equalsIgnoreCase("TlsPremasterSecret") &&
 291             !AllowKDF.VALUE) {
 292 
 293             throw new NoSuchAlgorithmException("Unsupported secret key "
 294                                                + "algorithm: " + algorithm);
 295         }
 296 
 297         byte[] secret = engineGenerateSecret();
 298         // Maintain compatibility for SunJCE:
 299         // verify secret length is sensible for algorithm / truncate
 300         // return generated key itself if possible
 301         int keyLen;
 302         if (algorithm.equalsIgnoreCase("DES")) {
 303             keyLen = 8;
 304         } else if (algorithm.equalsIgnoreCase("DESede")) {
 305             keyLen = 24;
 306         } else if (algorithm.equalsIgnoreCase("Blowfish")) {
 307             keyLen = Math.min(56, secret.length);
 308         } else if (algorithm.equalsIgnoreCase("TlsPremasterSecret")) {
 309             keyLen = secret.length;
 310         } else {
 311             throw new NoSuchAlgorithmException
 312                 ("Unknown algorithm " + algorithm);
 313         }
 314         if (secret.length < keyLen) {
 315             throw new InvalidKeyException("Secret too short");
 316         }
 317         if (algorithm.equalsIgnoreCase("DES") ||
 318             algorithm.equalsIgnoreCase("DESede")) {
 319                 for (int i = 0; i < keyLen; i+=8) {
 320                     P11SecretKeyFactory.fixDESParity(secret, i);
 321                 }
 322         }
 323         return new SecretKeySpec(secret, 0, keyLen, algorithm);
 324     }
 325 
 326     private SecretKey nativeGenerateSecret(String algorithm)
 327             throws IllegalStateException, NoSuchAlgorithmException,
 328             InvalidKeyException {
 329         if ((privateKey == null) || (publicValue == null)) {
 330             throw new IllegalStateException("Not initialized correctly");
 331         }
 332         long keyType = CKK_GENERIC_SECRET;
 333         Session session = null;
 334         try {
 335             session = token.getObjSession();
 336             CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[] {
 337                 new CK_ATTRIBUTE(CKA_CLASS, CKO_SECRET_KEY),
 338                 new CK_ATTRIBUTE(CKA_KEY_TYPE, keyType),
 339             };
 340             attributes = token.getAttributes
 341                 (O_GENERATE, CKO_SECRET_KEY, keyType, attributes);
 342             privateKey.incNativeKeyRef();
 343             long keyID;
 344             try {
 345                 keyID = token.p11.C_DeriveKey(session.id(),
 346                         new CK_MECHANISM(mechanism, publicValue),
 347                         privateKey.keyID, attributes);
 348             } finally {
 349                 privateKey.decNativeKeyRef();
 350             }
 351             CK_ATTRIBUTE[] lenAttributes = new CK_ATTRIBUTE[] {
 352                 new CK_ATTRIBUTE(CKA_VALUE_LEN),
 353             };
 354             token.p11.C_GetAttributeValue(session.id(), keyID, lenAttributes);
 355             int keyLen = (int)lenAttributes[0].getLong();
 356             SecretKey key = P11Key.secretKey(session, keyID, algorithm,
 357                     keyLen << 3, attributes, true);
 358             if ("RAW".equals(key.getFormat())) {
 359                 // Workaround for Solaris bug 6318543.
 360                 // Strip leading zeroes ourselves if possible (key not sensitive).
 361                 // This should be removed once the Solaris fix is available
 362                 // as here we always retrieve the CKA_VALUE even for tokens
 363                 // that do not have that bug.
 364                 byte[] keyBytes = key.getEncoded();
 365                 byte[] newBytes = KeyUtil.trimZeroes(keyBytes);
 366                 if (keyBytes != newBytes) {
 367                     key = new SecretKeySpec(newBytes, algorithm);
 368                 }
 369             }
 370             return key;
 371         } catch (PKCS11Exception e) {
 372             throw new InvalidKeyException("Could not derive key", e);
 373         } finally {
 374             publicValue = null;
 375             token.releaseSession(session);
 376         }
 377     }
 378 
 379 }