| /* |
| * Copyright (c) 2003, 2007, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package sun.security.pkcs11; |
| |
| import java.math.BigInteger; |
| |
| import java.security.*; |
| import java.security.spec.*; |
| |
| import javax.crypto.*; |
| import javax.crypto.interfaces.*; |
| import javax.crypto.spec.*; |
| |
| import static sun.security.pkcs11.TemplateManager.*; |
| import sun.security.pkcs11.wrapper.*; |
| import static sun.security.pkcs11.wrapper.PKCS11Constants.*; |
| |
| /** |
| * KeyAgreement implementation class. This class currently supports |
| * DH. |
| * |
| * @author Andreas Sterbenz |
| * @since 1.5 |
| */ |
| final class P11KeyAgreement extends KeyAgreementSpi { |
| |
| // token instance |
| private final Token token; |
| |
| // algorithm name |
| private final String algorithm; |
| |
| // mechanism id |
| private final long mechanism; |
| |
| // private key, if initialized |
| private P11Key privateKey; |
| |
| // other sides public value ("y"), if doPhase() already called |
| private BigInteger publicValue; |
| |
| // length of the secret to be derived |
| private int secretLen; |
| |
| // KeyAgreement from SunJCE as fallback for > 2 party agreement |
| private KeyAgreement multiPartyAgreement; |
| |
| P11KeyAgreement(Token token, String algorithm, long mechanism) { |
| super(); |
| this.token = token; |
| this.algorithm = algorithm; |
| this.mechanism = mechanism; |
| } |
| |
| // see JCE spec |
| protected void engineInit(Key key, SecureRandom random) |
| throws InvalidKeyException { |
| if (key instanceof PrivateKey == false) { |
| throw new InvalidKeyException |
| ("Key must be instance of PrivateKey"); |
| } |
| privateKey = P11KeyFactory.convertKey(token, key, algorithm); |
| publicValue = null; |
| multiPartyAgreement = null; |
| } |
| |
| // see JCE spec |
| protected void engineInit(Key key, AlgorithmParameterSpec params, |
| SecureRandom random) throws InvalidKeyException, |
| InvalidAlgorithmParameterException { |
| if (params != null) { |
| throw new InvalidAlgorithmParameterException |
| ("Parameters not supported"); |
| } |
| engineInit(key, random); |
| } |
| |
| // see JCE spec |
| protected Key engineDoPhase(Key key, boolean lastPhase) |
| throws InvalidKeyException, IllegalStateException { |
| if (privateKey == null) { |
| throw new IllegalStateException("Not initialized"); |
| } |
| if (publicValue != null) { |
| throw new IllegalStateException("Phase already executed"); |
| } |
| // PKCS#11 only allows key agreement between 2 parties |
| // JCE allows >= 2 parties. To support that case (for compatibility |
| // and to pass JCK), fall back to SunJCE in this case. |
| // NOTE that we initialize using the P11Key, which will fail if it |
| // is sensitive/unextractable. However, this is not an issue in the |
| // compatibility configuration, which is all we are targeting here. |
| if ((multiPartyAgreement != null) || (lastPhase == false)) { |
| if (multiPartyAgreement == null) { |
| try { |
| multiPartyAgreement = KeyAgreement.getInstance |
| ("DH", P11Util.getSunJceProvider()); |
| multiPartyAgreement.init(privateKey); |
| } catch (NoSuchAlgorithmException e) { |
| throw new InvalidKeyException |
| ("Could not initialize multi party agreement", e); |
| } |
| } |
| return multiPartyAgreement.doPhase(key, lastPhase); |
| } |
| if ((key instanceof PublicKey == false) |
| || (key.getAlgorithm().equals(algorithm) == false)) { |
| throw new InvalidKeyException |
| ("Key must be a PublicKey with algorithm DH"); |
| } |
| BigInteger p, g, y; |
| if (key instanceof DHPublicKey) { |
| DHPublicKey dhKey = (DHPublicKey)key; |
| y = dhKey.getY(); |
| DHParameterSpec params = dhKey.getParams(); |
| p = params.getP(); |
| g = params.getG(); |
| } else { |
| // normally, DH PublicKeys will always implement DHPublicKey |
| // just in case not, attempt conversion |
| P11DHKeyFactory kf = new P11DHKeyFactory(token, "DH"); |
| try { |
| DHPublicKeySpec spec = (DHPublicKeySpec)kf.engineGetKeySpec |
| (key, DHPublicKeySpec.class); |
| y = spec.getY(); |
| p = spec.getP(); |
| g = spec.getG(); |
| } catch (InvalidKeySpecException e) { |
| throw new InvalidKeyException("Could not obtain key values", e); |
| } |
| } |
| // if parameters of private key are accessible, verify that |
| // they match parameters of public key |
| // XXX p and g should always be readable, even if the key is sensitive |
| if (privateKey instanceof DHPrivateKey) { |
| DHPrivateKey dhKey = (DHPrivateKey)privateKey; |
| DHParameterSpec params = dhKey.getParams(); |
| if ((p.equals(params.getP()) == false) |
| || (g.equals(params.getG()) == false)) { |
| throw new InvalidKeyException |
| ("PublicKey DH parameters must match PrivateKey DH parameters"); |
| } |
| } |
| publicValue = y; |
| // length of the secret is length of key |
| secretLen = (p.bitLength() + 7) >> 3; |
| return null; |
| } |
| |
| // see JCE spec |
| protected byte[] engineGenerateSecret() throws IllegalStateException { |
| if (multiPartyAgreement != null) { |
| byte[] val = multiPartyAgreement.generateSecret(); |
| multiPartyAgreement = null; |
| return val; |
| } |
| if ((privateKey == null) || (publicValue == null)) { |
| throw new IllegalStateException("Not initialized correctly"); |
| } |
| Session session = null; |
| try { |
| session = token.getOpSession(); |
| CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[] { |
| new CK_ATTRIBUTE(CKA_CLASS, CKO_SECRET_KEY), |
| new CK_ATTRIBUTE(CKA_KEY_TYPE, CKK_GENERIC_SECRET), |
| }; |
| attributes = token.getAttributes |
| (O_GENERATE, CKO_SECRET_KEY, CKK_GENERIC_SECRET, attributes); |
| long keyID = token.p11.C_DeriveKey(session.id(), |
| new CK_MECHANISM(mechanism, publicValue), privateKey.keyID, |
| attributes); |
| attributes = new CK_ATTRIBUTE[] { |
| new CK_ATTRIBUTE(CKA_VALUE) |
| }; |
| token.p11.C_GetAttributeValue(session.id(), keyID, attributes); |
| byte[] secret = attributes[0].getByteArray(); |
| token.p11.C_DestroyObject(session.id(), keyID); |
| // trim leading 0x00 bytes per JCE convention |
| return P11Util.trimZeroes(secret); |
| } catch (PKCS11Exception e) { |
| throw new ProviderException("Could not derive key", e); |
| } finally { |
| publicValue = null; |
| token.releaseSession(session); |
| } |
| } |
| |
| // see JCE spec |
| protected int engineGenerateSecret(byte[] sharedSecret, int |
| offset) throws IllegalStateException, ShortBufferException { |
| if (multiPartyAgreement != null) { |
| int n = multiPartyAgreement.generateSecret(sharedSecret, offset); |
| multiPartyAgreement = null; |
| return n; |
| } |
| if (offset + secretLen > sharedSecret.length) { |
| throw new ShortBufferException("Need " + secretLen |
| + " bytes, only " + (sharedSecret.length - offset) + " available"); |
| } |
| byte[] secret = engineGenerateSecret(); |
| System.arraycopy(secret, 0, sharedSecret, offset, secret.length); |
| return secret.length; |
| } |
| |
| // see JCE spec |
| protected SecretKey engineGenerateSecret(String algorithm) |
| throws IllegalStateException, NoSuchAlgorithmException, |
| InvalidKeyException { |
| if (multiPartyAgreement != null) { |
| SecretKey key = multiPartyAgreement.generateSecret(algorithm); |
| multiPartyAgreement = null; |
| return key; |
| } |
| if (algorithm == null) { |
| throw new NoSuchAlgorithmException("Algorithm must not be null"); |
| } |
| if (algorithm.equals("TlsPremasterSecret")) { |
| // For now, only perform native derivation for TlsPremasterSecret |
| // as that is required for FIPS compliance. |
| // For other algorithms, there are unresolved issues regarding |
| // how this should work in JCE plus a Solaris truncation bug. |
| // (bug not yet filed). |
| return nativeGenerateSecret(algorithm); |
| } |
| byte[] secret = engineGenerateSecret(); |
| // Maintain compatibility for SunJCE: |
| // verify secret length is sensible for algorithm / truncate |
| // return generated key itself if possible |
| int keyLen; |
| if (algorithm.equalsIgnoreCase("DES")) { |
| keyLen = 8; |
| } else if (algorithm.equalsIgnoreCase("DESede")) { |
| keyLen = 24; |
| } else if (algorithm.equalsIgnoreCase("Blowfish")) { |
| keyLen = Math.min(56, secret.length); |
| } else if (algorithm.equalsIgnoreCase("TlsPremasterSecret")) { |
| keyLen = secret.length; |
| } else { |
| throw new NoSuchAlgorithmException |
| ("Unknown algorithm " + algorithm); |
| } |
| if (secret.length < keyLen) { |
| throw new InvalidKeyException("Secret too short"); |
| } |
| if (algorithm.equalsIgnoreCase("DES") || |
| algorithm.equalsIgnoreCase("DESede")) { |
| for (int i = 0; i < keyLen; i+=8) { |
| P11SecretKeyFactory.fixDESParity(secret, i); |
| } |
| } |
| return new SecretKeySpec(secret, 0, keyLen, algorithm); |
| } |
| |
| private SecretKey nativeGenerateSecret(String algorithm) |
| throws IllegalStateException, NoSuchAlgorithmException, |
| InvalidKeyException { |
| if ((privateKey == null) || (publicValue == null)) { |
| throw new IllegalStateException("Not initialized correctly"); |
| } |
| long keyType = CKK_GENERIC_SECRET; |
| Session session = null; |
| try { |
| session = token.getObjSession(); |
| CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[] { |
| new CK_ATTRIBUTE(CKA_CLASS, CKO_SECRET_KEY), |
| new CK_ATTRIBUTE(CKA_KEY_TYPE, keyType), |
| }; |
| attributes = token.getAttributes |
| (O_GENERATE, CKO_SECRET_KEY, keyType, attributes); |
| long keyID = token.p11.C_DeriveKey(session.id(), |
| new CK_MECHANISM(mechanism, publicValue), privateKey.keyID, |
| attributes); |
| CK_ATTRIBUTE[] lenAttributes = new CK_ATTRIBUTE[] { |
| new CK_ATTRIBUTE(CKA_VALUE_LEN), |
| }; |
| token.p11.C_GetAttributeValue(session.id(), keyID, lenAttributes); |
| int keyLen = (int)lenAttributes[0].getLong(); |
| SecretKey key = P11Key.secretKey |
| (session, keyID, algorithm, keyLen << 3, attributes); |
| if ("RAW".equals(key.getFormat())) { |
| // Workaround for Solaris bug 6318543. |
| // Strip leading zeroes ourselves if possible (key not sensitive). |
| // This should be removed once the Solaris fix is available |
| // as here we always retrieve the CKA_VALUE even for tokens |
| // that do not have that bug. |
| byte[] keyBytes = key.getEncoded(); |
| byte[] newBytes = P11Util.trimZeroes(keyBytes); |
| if (keyBytes != newBytes) { |
| key = new SecretKeySpec(newBytes, algorithm); |
| } |
| } |
| return key; |
| } catch (PKCS11Exception e) { |
| throw new InvalidKeyException("Could not derive key", e); |
| } finally { |
| publicValue = null; |
| token.releaseSession(session); |
| } |
| } |
| |
| } |