1 /*
   2  * Copyright (c) 2014, 2015, 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.nio.ByteBuffer;
  29 import java.util.Set;
  30 import java.util.Arrays;
  31 import java.util.concurrent.ConcurrentSkipListSet;
  32 import java.lang.ref.*;
  33 
  34 import java.security.*;
  35 import java.security.spec.*;
  36 import javax.crypto.*;
  37 
  38 import javax.crypto.spec.SecretKeySpec;
  39 import javax.crypto.spec.IvParameterSpec;
  40 
  41 import sun.security.jca.JCAUtil;
  42 
  43 /**
  44  * Cipher wrapper class utilizing ucrypto APIs. This class currently supports
  45  * - AES/ECB/NOPADDING
  46  * - AES/CBC/NOPADDING
  47  * - AES/CTR/NOPADDING
  48  * - AES/CFB128/NOPADDING
  49  * (Support for GCM mode is inside the child class NativeGCMCipher)
  50  *
  51  * @since 9
  52  */
  53 class NativeCipher extends CipherSpi {
  54 
  55     // public implementation classes
  56     public static final class AesEcbNoPadding extends NativeCipher {
  57         public AesEcbNoPadding() throws NoSuchAlgorithmException {
  58             super(UcryptoMech.CRYPTO_AES_ECB);
  59         }
  60         public AesEcbNoPadding(int keySize) throws NoSuchAlgorithmException {
  61             super(UcryptoMech.CRYPTO_AES_ECB, keySize);
  62         }
  63     }
  64     public static final class AesCbcNoPadding extends NativeCipher {
  65         public AesCbcNoPadding() throws NoSuchAlgorithmException {
  66             super(UcryptoMech.CRYPTO_AES_CBC);
  67         }
  68         public AesCbcNoPadding(int keySize) throws NoSuchAlgorithmException {
  69             super(UcryptoMech.CRYPTO_AES_CBC, keySize);
  70         }
  71     }
  72     public static final class AesCtrNoPadding extends NativeCipher {
  73         public AesCtrNoPadding() throws NoSuchAlgorithmException {
  74             super(UcryptoMech.CRYPTO_AES_CTR);
  75         }
  76     }
  77     public static final class AesCfb128NoPadding extends NativeCipher {
  78         public AesCfb128NoPadding() throws NoSuchAlgorithmException {
  79             super(UcryptoMech.CRYPTO_AES_CFB128);
  80         }
  81     }
  82 
  83     // ok as constants since AES is all we support
  84     public static final int AES_BLOCK_SIZE = 16;
  85     public static final String AES_KEY_ALGO = "AES";
  86 
  87     // fields set in constructor
  88     protected final UcryptoMech mech;
  89     protected String keyAlgo;
  90     protected int blockSize;
  91     protected int fixedKeySize;
  92 
  93     //
  94     // fields (re)set in every init()
  95     //
  96     protected CipherContextRef pCtxt = null;
  97     protected byte[] keyValue = null;
  98     protected byte[] iv = null;
  99     protected boolean initialized = false;
 100     protected boolean encrypt = true;
 101     protected int bytesBuffered = 0;
 102 
 103     // private utility methods for key re-construction
 104     private static final PublicKey constructPublicKey(byte[] encodedKey,
 105                                               String encodedKeyAlgorithm)
 106         throws InvalidKeyException, NoSuchAlgorithmException {
 107 
 108         PublicKey key = null;
 109         try {
 110             KeyFactory keyFactory =
 111                 KeyFactory.getInstance(encodedKeyAlgorithm);
 112             X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedKey);
 113             key = keyFactory.generatePublic(keySpec);
 114         } catch (NoSuchAlgorithmException nsae) {
 115             throw new NoSuchAlgorithmException("No provider found for " +
 116                                                encodedKeyAlgorithm +
 117                                                " KeyFactory");
 118         } catch (InvalidKeySpecException ikse) {
 119             // Should never happen
 120             throw new InvalidKeyException("Cannot construct public key", ikse);
 121         }
 122         return key;
 123     }
 124 
 125     private static final PrivateKey constructPrivateKey(byte[] encodedKey,
 126                                                 String encodedKeyAlgorithm)
 127         throws InvalidKeyException, NoSuchAlgorithmException {
 128 
 129         PrivateKey key = null;
 130         try {
 131             KeyFactory keyFactory =
 132                 KeyFactory.getInstance(encodedKeyAlgorithm);
 133             PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encodedKey);
 134             key = keyFactory.generatePrivate(keySpec);
 135         } catch (NoSuchAlgorithmException nsae) {
 136             throw new NoSuchAlgorithmException("No provider found for " +
 137                                                encodedKeyAlgorithm +
 138                                                " KeyFactory");
 139         } catch (InvalidKeySpecException ikse) {
 140             // Should never happen
 141             throw new InvalidKeyException("Cannot construct private key", ikse);
 142         }
 143         return key;
 144     }
 145 
 146     private static final SecretKey constructSecretKey(byte[] encodedKey,
 147                                               String encodedKeyAlgorithm) {
 148         return new SecretKeySpec(encodedKey, encodedKeyAlgorithm);
 149     }
 150 
 151     // package-private utility method for general key re-construction
 152     static final Key constructKey(int keyType, byte[] encodedKey,
 153                                   String encodedKeyAlgorithm)
 154         throws InvalidKeyException, NoSuchAlgorithmException {
 155         Key result = null;
 156         switch (keyType) {
 157         case Cipher.SECRET_KEY:
 158             result = constructSecretKey(encodedKey,
 159                                         encodedKeyAlgorithm);
 160             break;
 161         case Cipher.PRIVATE_KEY:
 162             result = constructPrivateKey(encodedKey,
 163                                          encodedKeyAlgorithm);
 164             break;
 165         case Cipher.PUBLIC_KEY:
 166             result = constructPublicKey(encodedKey,
 167                                         encodedKeyAlgorithm);
 168             break;
 169         }
 170         return result;
 171     }
 172 
 173     NativeCipher(UcryptoMech mech, int fixedKeySize) throws NoSuchAlgorithmException {
 174         this.mech = mech;
 175         // defaults to AES - the only supported symmetric cipher algo
 176         this.blockSize = AES_BLOCK_SIZE;
 177         this.keyAlgo = AES_KEY_ALGO;
 178         this.fixedKeySize = fixedKeySize;
 179     }
 180 
 181     NativeCipher(UcryptoMech mech) throws NoSuchAlgorithmException {
 182         this(mech, -1);
 183     }
 184 
 185     @Override
 186     protected void engineSetMode(String mode) throws NoSuchAlgorithmException {
 187         // Disallow change of mode for now since currently it's explicitly
 188         // defined in transformation strings
 189         throw new NoSuchAlgorithmException("Unsupported mode " + mode);
 190     }
 191 
 192     // see JCE spec
 193     @Override
 194     protected void engineSetPadding(String padding)
 195             throws NoSuchPaddingException {
 196         // Disallow change of padding for now since currently it's explicitly
 197         // defined in transformation strings
 198         throw new NoSuchPaddingException("Unsupported padding " + padding);
 199     }
 200 
 201     // see JCE spec
 202     @Override
 203     protected int engineGetBlockSize() {
 204         return blockSize;
 205     }
 206 
 207     // see JCE spec
 208     @Override
 209     protected synchronized int engineGetOutputSize(int inputLen) {
 210         return getOutputSizeByOperation(inputLen, true);
 211     }
 212 
 213     // see JCE spec
 214     @Override
 215     protected synchronized byte[] engineGetIV() {
 216         return (iv != null? iv.clone() : null);
 217     }
 218 
 219     // see JCE spec
 220     @Override
 221     protected synchronized AlgorithmParameters engineGetParameters() {
 222         AlgorithmParameters params = null;
 223         try {
 224             if (iv != null) {
 225                 IvParameterSpec ivSpec = new IvParameterSpec(iv.clone());
 226                 params = AlgorithmParameters.getInstance(keyAlgo);
 227                 params.init(ivSpec);
 228             }
 229         } catch (GeneralSecurityException e) {
 230             // NoSuchAlgorithmException, NoSuchProviderException
 231             // InvalidParameterSpecException
 232             throw new UcryptoException("Could not encode parameters", e);
 233         }
 234         return params;
 235     }
 236 
 237     @Override
 238     protected int engineGetKeySize(Key key) throws InvalidKeyException {
 239         return checkKey(key) * 8;
 240     }
 241 
 242     // see JCE spec
 243     @Override
 244     protected synchronized void engineInit(int opmode, Key key,
 245             SecureRandom random) throws InvalidKeyException {
 246         try {
 247             engineInit(opmode, key, (AlgorithmParameterSpec)null, random);
 248         } catch (InvalidAlgorithmParameterException e) {
 249             throw new InvalidKeyException("init() failed", e);
 250         }
 251     }
 252 
 253     // see JCE spec
 254     @Override
 255     protected synchronized void engineInit(int opmode, Key key,
 256             AlgorithmParameterSpec params, SecureRandom random)
 257             throws InvalidKeyException, InvalidAlgorithmParameterException {
 258         checkKey(key);
 259         if (opmode != Cipher.ENCRYPT_MODE &&
 260             opmode != Cipher.DECRYPT_MODE &&
 261             opmode != Cipher.WRAP_MODE &&
 262             opmode != Cipher.UNWRAP_MODE) {
 263             throw new InvalidAlgorithmParameterException
 264                 ("Unsupported mode: " + opmode);
 265         }
 266         boolean doEncrypt =
 267                 (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE);
 268 
 269         byte[] ivBytes = null;
 270         if (mech == UcryptoMech.CRYPTO_AES_ECB) {
 271             if (params != null) {
 272                 throw new InvalidAlgorithmParameterException
 273                         ("No Parameters for ECB mode");
 274             }
 275         } else {
 276             if (params != null) {
 277                 if (!(params instanceof IvParameterSpec)) {
 278                     throw new InvalidAlgorithmParameterException
 279                             ("IvParameterSpec required. Received: " +
 280                             params.getClass().getName());
 281                 } else {
 282                     ivBytes = ((IvParameterSpec) params).getIV();
 283                     if (ivBytes.length != blockSize) {
 284                         throw new InvalidAlgorithmParameterException
 285                              ("Wrong IV length: must be " + blockSize +
 286                               " bytes long. Received length:" + ivBytes.length);
 287                     }
 288                 }
 289             } else {
 290                 if (encrypt) {
 291                     // generate IV if none supplied for encryption
 292                     ivBytes = new byte[blockSize];
 293                     if (random == null) {
 294                         random = JCAUtil.getSecureRandom();
 295                     }
 296                     random.nextBytes(ivBytes);
 297                 } else {
 298                     throw new InvalidAlgorithmParameterException
 299                             ("Parameters required for decryption");
 300                 }
 301             }
 302         }
 303         init(doEncrypt, key.getEncoded().clone(), ivBytes);
 304     }
 305 
 306     // see JCE spec
 307     @Override
 308     protected synchronized void engineInit(int opmode, Key key,
 309             AlgorithmParameters params, SecureRandom random)
 310             throws InvalidKeyException, InvalidAlgorithmParameterException {
 311         AlgorithmParameterSpec spec = null;
 312         if (params != null) {
 313             try {
 314                 spec = params.getParameterSpec(IvParameterSpec.class);
 315             } catch (InvalidParameterSpecException iaps) {
 316                 throw new InvalidAlgorithmParameterException(iaps);
 317             }
 318         }
 319         engineInit(opmode, key, spec, random);
 320     }
 321 
 322     // see JCE spec
 323     @Override
 324     protected synchronized byte[] engineUpdate(byte[] in, int ofs, int len) {
 325         byte[] out = new byte[getOutputSizeByOperation(len, false)];
 326         int n = update(in, ofs, len, out, 0);
 327         if (n == 0) {
 328             return null;
 329         } else if (out.length != n) {
 330             out = Arrays.copyOf(out, n);
 331         }
 332         return out;
 333     }
 334 
 335     // see JCE spec
 336     @Override
 337     protected synchronized int engineUpdate(byte[] in, int inOfs, int inLen,
 338         byte[] out, int outOfs) throws ShortBufferException {
 339         int min = getOutputSizeByOperation(inLen, false);
 340         if (out.length - outOfs < min) {
 341             throw new ShortBufferException("min " + min + "-byte buffer needed");
 342         }
 343         return update(in, inOfs, inLen, out, outOfs);
 344     }
 345 
 346     // see JCE spec
 347     @Override
 348     protected synchronized void engineUpdateAAD(byte[] src, int ofs, int len)
 349             throws IllegalStateException {
 350         throw new IllegalStateException("No AAD can be supplied");
 351     }
 352 
 353     // see JCE spec
 354     @Override
 355     protected void engineUpdateAAD(ByteBuffer src)
 356             throws IllegalStateException {
 357         throw new IllegalStateException("No AAD can be supplied");
 358     }
 359 
 360     // see JCE spec
 361     @Override
 362     protected synchronized byte[] engineDoFinal(byte[] in, int ofs, int len)
 363             throws IllegalBlockSizeException, BadPaddingException {
 364         byte[] out = new byte[getOutputSizeByOperation(len, true)];
 365         try {
 366             // delegate to the other engineDoFinal(...) method
 367             int k = engineDoFinal(in, ofs, len, out, 0);
 368             if (out.length != k) {
 369                 out = Arrays.copyOf(out, k);
 370             }
 371             return out;
 372         } catch (ShortBufferException e) {
 373             throw new UcryptoException("Internal Error", e);
 374         }
 375     }
 376 
 377     // see JCE spec
 378     @Override
 379     protected synchronized int engineDoFinal(byte[] in, int inOfs, int inLen,
 380                                              byte[] out, int outOfs)
 381             throws ShortBufferException, IllegalBlockSizeException,
 382             BadPaddingException {
 383         int k = 0;
 384         int min = getOutputSizeByOperation(inLen, true);
 385         if (out.length - outOfs < min) {
 386             throw new ShortBufferException("min " + min + "-byte buffer needed");
 387         }
 388         if (inLen > 0) {
 389             k = update(in, inOfs, inLen, out, outOfs);
 390             outOfs += k;
 391         }
 392         k += doFinal(out, outOfs);
 393         return k;
 394     }
 395 
 396 
 397     // see JCE spec
 398     @Override
 399     protected synchronized byte[] engineWrap(Key key)
 400             throws IllegalBlockSizeException, InvalidKeyException {
 401         byte[] result = null;
 402         try {
 403             byte[] encodedKey = key.getEncoded();
 404             if ((encodedKey == null) || (encodedKey.length == 0)) {
 405                 throw new InvalidKeyException("Cannot get an encoding of " +
 406                                               "the key to be wrapped");
 407             }
 408             result = engineDoFinal(encodedKey, 0, encodedKey.length);
 409         } catch (BadPaddingException e) {
 410             // Should never happen for key wrapping
 411             throw new UcryptoException("Internal Error" , e);
 412         }
 413         return result;
 414     }
 415 
 416     // see JCE spec
 417     @Override
 418     protected synchronized Key engineUnwrap(byte[] wrappedKey,
 419             String wrappedKeyAlgorithm, int wrappedKeyType)
 420             throws InvalidKeyException, NoSuchAlgorithmException {
 421 
 422         byte[] encodedKey;
 423         Key result = null;
 424         try {
 425             encodedKey = engineDoFinal(wrappedKey, 0,
 426                                        wrappedKey.length);
 427         } catch (Exception e) {
 428             throw (InvalidKeyException)
 429                 (new InvalidKeyException()).initCause(e);
 430         }
 431 
 432         return constructKey(wrappedKeyType, encodedKey, wrappedKeyAlgorithm);
 433     }
 434 
 435     final int checkKey(Key key) throws InvalidKeyException {
 436         if (key == null || key.getEncoded() == null) {
 437             throw new InvalidKeyException("Key cannot be null");
 438         } else {
 439             // check key algorithm and format
 440             if (!keyAlgo.equalsIgnoreCase(key.getAlgorithm())) {
 441                 throw new InvalidKeyException("Key algorithm must be " +
 442                     keyAlgo);
 443             }
 444             if (!"RAW".equalsIgnoreCase(key.getFormat())) {
 445                 throw new InvalidKeyException("Key format must be RAW");
 446             }
 447             int keyLen = key.getEncoded().length;
 448             if (fixedKeySize == -1) {
 449                 // all 3 AES key lengths are allowed
 450                 if (keyLen != 16 && keyLen != 24 && keyLen != 32) {
 451                     throw new InvalidKeyException("Key size is not valid." +
 452                         " Got key length of: " + keyLen);
 453                 }
 454             } else {
 455                 if (keyLen != fixedKeySize) {
 456                     throw new InvalidKeyException("Only " + fixedKeySize +
 457                         "-byte keys are accepted. Got: " + keyLen);
 458                 }
 459             }
 460             // return the validated key length in bytes
 461             return keyLen;
 462         }
 463     }
 464 
 465     protected void reset(boolean doCancel) {
 466         initialized = false;
 467         bytesBuffered = 0;
 468         if (pCtxt != null) {
 469             pCtxt.dispose(doCancel);
 470             pCtxt = null;
 471         }
 472     }
 473 
 474     /**
 475      * calls ucrypto_encrypt_init(...) or ucrypto_decrypt_init(...)
 476      * @return pointer to the context
 477      */
 478     protected native static long nativeInit(int mech, boolean encrypt,
 479                                             byte[] key, byte[] iv,
 480                                             int tagLen, byte[] aad);
 481 
 482     /**
 483      * calls ucrypto_encrypt_update(...) or ucrypto_decrypt_update(...)
 484      * @return the length of output or if negative, an error status code
 485      */
 486     private native static int nativeUpdate(long pContext, boolean encrypt,
 487                                            byte[] in, int inOfs, int inLen,
 488                                            byte[] out, int outOfs);
 489 
 490     /**
 491      * calls ucrypto_encrypt_final(...) or ucrypto_decrypt_final(...)
 492      * @return the length of output or if negative, an error status code
 493      */
 494     native static int nativeFinal(long pContext, boolean encrypt,
 495                                           byte[] out, int outOfs);
 496 
 497     protected void ensureInitialized() {
 498         if (!initialized) {
 499             init(encrypt, keyValue, iv);
 500             if (!initialized) {
 501                 throw new UcryptoException("Cannot initialize Cipher");
 502             }
 503         }
 504     }
 505 
 506     protected int getOutputSizeByOperation(int inLen, boolean isDoFinal) {
 507         if (inLen <= 0) {
 508             inLen = 0;
 509         }
 510         if (!isDoFinal && (inLen == 0)) {
 511             return 0;
 512         }
 513         return inLen + bytesBuffered;
 514     }
 515 
 516     // actual init() implementation - caller should clone key and iv if needed
 517     protected void init(boolean encrypt, byte[] keyVal, byte[] ivVal) {
 518         reset(true);
 519         this.encrypt = encrypt;
 520         this.keyValue = keyVal;
 521         this.iv = ivVal;
 522         long pCtxtVal = nativeInit(mech.value(), encrypt, keyValue, iv, 0, null);
 523         initialized = (pCtxtVal != 0L);
 524         if (initialized) {
 525             pCtxt = new CipherContextRef(this, pCtxtVal, encrypt);
 526         } else {
 527             throw new UcryptoException("Cannot initialize Cipher");
 528         }
 529     }
 530 
 531     // Caller MUST check and ensure output buffer has enough capacity
 532     private int update(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) {
 533         ensureInitialized();
 534         if (inLen <= 0) { return 0; }
 535 
 536         int k = nativeUpdate(pCtxt.id, encrypt, in, inOfs, inLen, out, outOfs);
 537         if (k < 0) {
 538             reset(false);
 539             // cannot throw ShortBufferException here since it's too late
 540             // native context is invalid upon any failure
 541             throw new UcryptoException(-k);
 542         }
 543         bytesBuffered += (inLen - k);
 544         return k;
 545     }
 546 
 547     // Caller MUST check and ensure output buffer has enough capacity
 548     private int doFinal(byte[] out, int outOfs) throws IllegalBlockSizeException,
 549             BadPaddingException {
 550         try {
 551             ensureInitialized();
 552 
 553             int k = nativeFinal(pCtxt.id, encrypt, out, outOfs);
 554             if (k < 0) {
 555                 String cause = UcryptoException.getErrorMessage(-k);
 556                 if (cause.endsWith("_LEN_RANGE")) {
 557                     throw new IllegalBlockSizeException(cause);
 558                 } else if (cause.endsWith("_DATA_INVALID")) {
 559                     throw new BadPaddingException(cause);
 560                 } else {
 561                     throw new UcryptoException(-k);
 562                 }
 563             }
 564             return k;
 565         } finally {
 566             reset(false);
 567         }
 568     }
 569 }