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