NativeRSACipher.java revision 11132:c8c8e1a13fa6
1/*
2 * Copyright (c) 2014, 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 com.oracle.security.ucrypto;
27
28import java.util.Arrays;
29import java.util.WeakHashMap;
30import java.util.Collections;
31import java.util.Map;
32
33import java.security.AlgorithmParameters;
34import java.security.InvalidAlgorithmParameterException;
35import java.security.InvalidKeyException;
36import java.security.Key;
37import java.security.PublicKey;
38import java.security.PrivateKey;
39import java.security.spec.RSAPrivateCrtKeySpec;
40import java.security.spec.RSAPublicKeySpec;
41import java.security.interfaces.RSAKey;
42import java.security.interfaces.RSAPrivateCrtKey;
43import java.security.interfaces.RSAPublicKey;
44
45import java.security.KeyFactory;
46import java.security.NoSuchAlgorithmException;
47import java.security.SecureRandom;
48
49import java.security.spec.AlgorithmParameterSpec;
50import java.security.spec.InvalidParameterSpecException;
51import java.security.spec.InvalidKeySpecException;
52
53import javax.crypto.BadPaddingException;
54import javax.crypto.Cipher;
55import javax.crypto.CipherSpi;
56import javax.crypto.SecretKey;
57import javax.crypto.IllegalBlockSizeException;
58import javax.crypto.NoSuchPaddingException;
59import javax.crypto.ShortBufferException;
60
61import javax.crypto.spec.SecretKeySpec;
62
63import sun.security.internal.spec.TlsRsaPremasterSecretParameterSpec;
64import sun.security.util.KeyUtil;
65
66/**
67 * Asymmetric Cipher wrapper class utilizing ucrypto APIs. This class
68 * currently supports
69 * - RSA/ECB/NOPADDING
70 * - RSA/ECB/PKCS1PADDING
71 *
72 * @since 1.9
73 */
74public class NativeRSACipher extends CipherSpi {
75    // fields set in constructor
76    private final UcryptoMech mech;
77    private final int padLen;
78    private final NativeRSAKeyFactory keyFactory;
79    private AlgorithmParameterSpec spec;
80    private SecureRandom random;
81
82    // Keep a cache of RSA keys and their RSA NativeKey for reuse.
83    // When the RSA key is gc'ed, we let NativeKey phatom references cleanup
84    // the native allocation
85    private static final Map<Key, NativeKey> keyList =
86            Collections.synchronizedMap(new WeakHashMap<Key, NativeKey>());
87
88    //
89    // fields (re)set in every init()
90    //
91    private NativeKey key = null;
92    private int outputSize = 0; // e.g. modulus size in bytes
93    private boolean encrypt = true;
94    private byte[] buffer;
95    private int bufOfs = 0;
96
97    // public implementation classes
98    public static final class NoPadding extends NativeRSACipher {
99        public NoPadding() throws NoSuchAlgorithmException {
100            super(UcryptoMech.CRYPTO_RSA_X_509, 0);
101        }
102    }
103
104    public static final class PKCS1Padding extends NativeRSACipher {
105        public PKCS1Padding() throws NoSuchAlgorithmException {
106            super(UcryptoMech.CRYPTO_RSA_PKCS, 11);
107        }
108    }
109
110    NativeRSACipher(UcryptoMech mech, int padLen)
111        throws NoSuchAlgorithmException {
112        this.mech = mech;
113        this.padLen = padLen;
114        this.keyFactory = new NativeRSAKeyFactory();
115    }
116
117    @Override
118    protected void engineSetMode(String mode) throws NoSuchAlgorithmException {
119        // Disallow change of mode for now since currently it's explicitly
120        // defined in transformation strings
121        throw new NoSuchAlgorithmException("Unsupported mode " + mode);
122    }
123
124    // see JCE spec
125    @Override
126    protected void engineSetPadding(String padding)
127            throws NoSuchPaddingException {
128        // Disallow change of padding for now since currently it's explicitly
129        // defined in transformation strings
130        throw new NoSuchPaddingException("Unsupported padding " + padding);
131    }
132
133    // see JCE spec
134    @Override
135    protected int engineGetBlockSize() {
136        return 0;
137    }
138
139    // see JCE spec
140    @Override
141    protected synchronized int engineGetOutputSize(int inputLen) {
142        return outputSize;
143    }
144
145    // see JCE spec
146    @Override
147    protected byte[] engineGetIV() {
148        return null;
149    }
150
151    // see JCE spec
152    @Override
153    protected AlgorithmParameters engineGetParameters() {
154        return null;
155    }
156
157    @Override
158    protected int engineGetKeySize(Key key) throws InvalidKeyException {
159        if (!(key instanceof RSAKey)) {
160            throw new InvalidKeyException("RSAKey required");
161        }
162        int n = ((RSAKey)key).getModulus().bitLength();
163        // strip off the leading extra 0x00 byte prefix
164        int realByteSize = (n + 7) >> 3;
165        return realByteSize * 8;
166    }
167
168    // see JCE spec
169    @Override
170    protected synchronized void engineInit(int opmode, Key key, SecureRandom random)
171            throws InvalidKeyException {
172        try {
173            engineInit(opmode, key, (AlgorithmParameterSpec)null, random);
174        } catch (InvalidAlgorithmParameterException e) {
175            throw new InvalidKeyException("init() failed", e);
176        }
177    }
178
179    // see JCE spec
180    @Override
181    @SuppressWarnings("deprecation")
182    protected synchronized void engineInit(int opmode, Key newKey,
183            AlgorithmParameterSpec params, SecureRandom random)
184            throws InvalidKeyException, InvalidAlgorithmParameterException {
185        if (newKey == null) {
186            throw new InvalidKeyException("Key cannot be null");
187        }
188        if (opmode != Cipher.ENCRYPT_MODE &&
189            opmode != Cipher.DECRYPT_MODE &&
190            opmode != Cipher.WRAP_MODE &&
191            opmode != Cipher.UNWRAP_MODE) {
192            throw new InvalidAlgorithmParameterException
193                ("Unsupported mode: " + opmode);
194        }
195        if (params != null) {
196            if (!(params instanceof TlsRsaPremasterSecretParameterSpec)) {
197                throw new InvalidAlgorithmParameterException(
198                        "No Parameters can be specified");
199            }
200            spec = params;
201            this.random = random;   // for TLS RSA premaster secret
202        }
203        boolean doEncrypt = (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE);
204
205        // Make sure the proper opmode uses the proper key
206        if (doEncrypt && (!(newKey instanceof RSAPublicKey))) {
207            throw new InvalidKeyException("RSAPublicKey required for encryption");
208        } else if (!doEncrypt && (!(newKey instanceof RSAPrivateCrtKey))) {
209            throw new InvalidKeyException("RSAPrivateCrtKey required for decryption");
210        }
211
212        NativeKey nativeKey = null;
213        // Check keyList cache for a nativeKey
214        nativeKey = keyList.get(newKey);
215        if (nativeKey == null) {
216            // With no existing nativeKey for this newKey, create one
217            if (doEncrypt) {
218                RSAPublicKey publicKey = (RSAPublicKey) newKey;
219                try {
220                    nativeKey = (NativeKey) keyFactory.engineGeneratePublic
221                        (new RSAPublicKeySpec(publicKey.getModulus(), publicKey.getPublicExponent()));
222                } catch (InvalidKeySpecException ikse) {
223                    throw new InvalidKeyException(ikse);
224                }
225            } else {
226                RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey) newKey;
227                try {
228                    nativeKey = (NativeKey) keyFactory.engineGeneratePrivate
229                        (new RSAPrivateCrtKeySpec(privateKey.getModulus(),
230                                                  privateKey.getPublicExponent(),
231                                                  privateKey.getPrivateExponent(),
232                                                  privateKey.getPrimeP(),
233                                                  privateKey.getPrimeQ(),
234                                                  privateKey.getPrimeExponentP(),
235                                                  privateKey.getPrimeExponentQ(),
236                                                  privateKey.getCrtCoefficient()));
237                } catch (InvalidKeySpecException ikse) {
238                    throw new InvalidKeyException(ikse);
239                }
240            }
241
242            // Add nativeKey to keyList cache and associate it with newKey
243            keyList.put(newKey, nativeKey);
244        }
245
246        init(doEncrypt, nativeKey);
247    }
248
249    // see JCE spec
250    @Override
251    protected synchronized void engineInit(int opmode, Key key, AlgorithmParameters params,
252            SecureRandom random)
253            throws InvalidKeyException, InvalidAlgorithmParameterException {
254        if (params != null) {
255            throw new InvalidAlgorithmParameterException("No Parameters can be specified");
256        }
257        engineInit(opmode, key, (AlgorithmParameterSpec) null, random);
258    }
259
260    // see JCE spec
261    @Override
262    protected synchronized byte[] engineUpdate(byte[] in, int inOfs, int inLen) {
263        if (inLen > 0) {
264            update(in, inOfs, inLen);
265        }
266        return null;
267    }
268
269    // see JCE spec
270    @Override
271    protected synchronized int engineUpdate(byte[] in, int inOfs, int inLen, byte[] out,
272            int outOfs) throws ShortBufferException {
273        if (out.length - outOfs < outputSize) {
274            throw new ShortBufferException("Output buffer too small");
275        }
276        if (inLen > 0) {
277            update(in, inOfs, inLen);
278        }
279        return 0;
280    }
281
282    // see JCE spec
283    @Override
284    protected synchronized byte[] engineDoFinal(byte[] in, int inOfs, int inLen)
285            throws IllegalBlockSizeException, BadPaddingException {
286        byte[] out = new byte[outputSize];
287        try {
288            // delegate to the other engineDoFinal(...) method
289            int actualLen = engineDoFinal(in, inOfs, inLen, out, 0);
290            if (actualLen != outputSize) {
291                return Arrays.copyOf(out, actualLen);
292            } else {
293                return out;
294            }
295        } catch (ShortBufferException e) {
296            throw new UcryptoException("Internal Error", e);
297        }
298    }
299
300    // see JCE spec
301    @Override
302    protected synchronized int engineDoFinal(byte[] in, int inOfs, int inLen, byte[] out,
303                                             int outOfs)
304        throws ShortBufferException, IllegalBlockSizeException,
305               BadPaddingException {
306        if (inLen != 0) {
307            update(in, inOfs, inLen);
308        }
309        return doFinal(out, outOfs, out.length - outOfs);
310    }
311
312
313    // see JCE spec
314    @Override
315    protected synchronized byte[] engineWrap(Key key) throws IllegalBlockSizeException,
316                                                             InvalidKeyException {
317        try {
318            byte[] encodedKey = key.getEncoded();
319            if ((encodedKey == null) || (encodedKey.length == 0)) {
320                throw new InvalidKeyException("Cannot get an encoding of " +
321                                              "the key to be wrapped");
322            }
323            if (encodedKey.length > buffer.length) {
324                throw new InvalidKeyException("Key is too long for wrapping");
325            }
326            return engineDoFinal(encodedKey, 0, encodedKey.length);
327        } catch (BadPaddingException e) {
328            // Should never happen for key wrapping
329            throw new UcryptoException("Internal Error", e);
330        }
331    }
332
333    // see JCE spec
334    @Override
335    @SuppressWarnings("deprecation")
336    protected synchronized Key engineUnwrap(byte[] wrappedKey,
337            String wrappedKeyAlgorithm, int wrappedKeyType)
338            throws InvalidKeyException, NoSuchAlgorithmException {
339
340        if (wrappedKey.length > buffer.length) {
341            throw new InvalidKeyException("Key is too long for unwrapping");
342        }
343
344        boolean isTlsRsaPremasterSecret =
345                wrappedKeyAlgorithm.equals("TlsRsaPremasterSecret");
346        Exception failover = null;
347
348        byte[] encodedKey = null;
349        try {
350            encodedKey = engineDoFinal(wrappedKey, 0, wrappedKey.length);
351        } catch (BadPaddingException bpe) {
352            if (isTlsRsaPremasterSecret) {
353                failover = bpe;
354            } else {
355                throw new InvalidKeyException("Unwrapping failed", bpe);
356            }
357        } catch (Exception e) {
358            throw new InvalidKeyException("Unwrapping failed", e);
359        }
360
361        if (isTlsRsaPremasterSecret) {
362            if (!(spec instanceof TlsRsaPremasterSecretParameterSpec)) {
363                throw new IllegalStateException(
364                        "No TlsRsaPremasterSecretParameterSpec specified");
365            }
366
367            // polish the TLS premaster secret
368            encodedKey = KeyUtil.checkTlsPreMasterSecretKey(
369                ((TlsRsaPremasterSecretParameterSpec)spec).getClientVersion(),
370                ((TlsRsaPremasterSecretParameterSpec)spec).getServerVersion(),
371                random, encodedKey, (failover != null));
372        }
373
374        return NativeCipher.constructKey(wrappedKeyType,
375                encodedKey, wrappedKeyAlgorithm);
376    }
377
378    /**
379     * calls ucrypto_encrypt(...) or ucrypto_decrypt(...)
380     * @returns the length of output or an negative error status code
381     */
382    private native static int nativeAtomic(int mech, boolean encrypt,
383                                           long keyValue, int keyLength,
384                                           byte[] in, int inLen,
385                                           byte[] out, int ouOfs, int outLen);
386
387    // do actual initialization
388    private void init(boolean encrypt, NativeKey key) {
389        this.encrypt = encrypt;
390        this.key = key;
391        try {
392            this.outputSize = engineGetKeySize(key)/8;
393        } catch (InvalidKeyException ike) {
394            throw new UcryptoException("Internal Error", ike);
395        }
396        this.buffer = new byte[outputSize];
397        this.bufOfs = 0;
398    }
399
400    // store the specified input into the internal buffer
401    private void update(byte[] in, int inOfs, int inLen) {
402        if ((inLen <= 0) || (in == null)) {
403            return;
404        }
405        // buffer bytes internally until doFinal is called
406        if ((bufOfs + inLen + (encrypt? padLen:0)) > buffer.length) {
407            // lead to IllegalBlockSizeException when doFinal() is called
408            bufOfs = buffer.length + 1;
409            return;
410        }
411        System.arraycopy(in, inOfs, buffer, bufOfs, inLen);
412        bufOfs += inLen;
413    }
414
415    // return the actual non-negative output length
416    private int doFinal(byte[] out, int outOfs, int outLen)
417            throws ShortBufferException, IllegalBlockSizeException,
418            BadPaddingException {
419        if (bufOfs > buffer.length) {
420            throw new IllegalBlockSizeException(
421                "Data must not be longer than " +
422                (buffer.length - (encrypt ? padLen : 0)) + " bytes");
423        }
424        if (outLen < outputSize) {
425            throw new ShortBufferException();
426        }
427        try {
428            long keyValue = key.value();
429            int k = nativeAtomic(mech.value(), encrypt, keyValue,
430                                 key.length(), buffer, bufOfs,
431                                 out, outOfs, outLen);
432            if (k < 0) {
433                if ( k == -16 || k == -64) {
434                    // -16: CRYPTO_ENCRYPTED_DATA_INVALID
435                    // -64: CKR_ENCRYPTED_DATA_INVALID, see bug 17459266
436                    UcryptoException ue = new UcryptoException(16);
437                    BadPaddingException bpe =
438                        new BadPaddingException("Invalid encryption data");
439                    bpe.initCause(ue);
440                    throw bpe;
441                }
442                throw new UcryptoException(-k);
443            }
444
445            return k;
446        } finally {
447            bufOfs = 0;
448        }
449    }
450}
451