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