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
26package sun.security.pkcs11;
27
28import java.math.BigInteger;
29
30import java.security.*;
31import java.security.spec.*;
32
33import javax.crypto.*;
34import javax.crypto.interfaces.*;
35import javax.crypto.spec.*;
36
37import static sun.security.pkcs11.TemplateManager.*;
38import sun.security.pkcs11.wrapper.*;
39import static sun.security.pkcs11.wrapper.PKCS11Constants.*;
40import 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 */
49final 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}
348