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 
  26 package com.oracle.security.ucrypto;
  27 
  28 import java.util.Arrays;
  29 import java.util.WeakHashMap;
  30 import java.util.Collections;
  31 import java.util.Map;
  32 
  33 import java.security.AlgorithmParameters;
  34 import java.security.InvalidAlgorithmParameterException;
  35 import java.security.InvalidKeyException;
  36 import java.security.Key;
  37 import java.security.PublicKey;
  38 import java.security.PrivateKey;
  39 import java.security.spec.RSAPrivateCrtKeySpec;
  40 import java.security.spec.RSAPublicKeySpec;
  41 import java.security.interfaces.RSAKey;
  42 import java.security.interfaces.RSAPrivateCrtKey;
  43 import java.security.interfaces.RSAPublicKey;
  44 
  45 import java.security.KeyFactory;
  46 import java.security.NoSuchAlgorithmException;
  47 import java.security.SecureRandom;
  48 
  49 import java.security.spec.AlgorithmParameterSpec;
  50 import java.security.spec.InvalidParameterSpecException;
  51 import java.security.spec.InvalidKeySpecException;
  52 
  53 import javax.crypto.BadPaddingException;
  54 import javax.crypto.Cipher;
  55 import javax.crypto.CipherSpi;
  56 import javax.crypto.SecretKey;
  57 import javax.crypto.IllegalBlockSizeException;
  58 import javax.crypto.NoSuchPaddingException;
  59 import javax.crypto.ShortBufferException;
  60 
  61 import javax.crypto.spec.SecretKeySpec;
  62 
  63 import sun.security.internal.spec.TlsRsaPremasterSecretParameterSpec;
  64 import 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  */
  74 public 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 }