Home | History | Annotate | Download | only in conscrypt
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package org.conscrypt;
     18 
     19 import java.security.InvalidAlgorithmParameterException;
     20 import java.security.InvalidKeyException;
     21 import java.security.NoSuchAlgorithmException;
     22 import java.security.SecureRandom;
     23 import java.security.spec.AlgorithmParameterSpec;
     24 import javax.crypto.BadPaddingException;
     25 import javax.crypto.IllegalBlockSizeException;
     26 import javax.crypto.NoSuchPaddingException;
     27 import javax.crypto.ShortBufferException;
     28 import javax.crypto.spec.IvParameterSpec;
     29 
     30 /**
     31  * Implementation of the ChaCha20 stream cipher.
     32  *
     33  * @hide
     34  */
     35 @Internal
     36 public class OpenSSLCipherChaCha20 extends OpenSSLCipher {
     37 
     38     private static final int BLOCK_SIZE_BYTES = 64;
     39     private static final int NONCE_SIZE_BYTES = 12;
     40 
     41     // BoringSSL's interface encrypts by the block, so we need to keep track of whether we
     42     // had unused keystream bytes at the end of the previous encryption operation, so that
     43     // we can use them before moving on to the next block.
     44     private int currentBlockConsumedBytes = 0;
     45     private int blockCounter = 0;
     46 
     47     public OpenSSLCipherChaCha20() {}
     48 
     49     @Override
     50     void engineInitInternal(byte[] encodedKey, AlgorithmParameterSpec params, SecureRandom random)
     51             throws InvalidKeyException, InvalidAlgorithmParameterException {
     52         if (params instanceof IvParameterSpec) {
     53             IvParameterSpec ivParams = (IvParameterSpec) params;
     54             if (ivParams.getIV().length != NONCE_SIZE_BYTES) {
     55                 throw new InvalidAlgorithmParameterException("IV must be 12 bytes long");
     56             }
     57             iv = ivParams.getIV();
     58         } else {
     59             if (!isEncrypting()) {
     60                 throw new InvalidAlgorithmParameterException(
     61                         "IV must be specified when decrypting");
     62             }
     63             iv = new byte[NONCE_SIZE_BYTES];
     64             if (random != null) {
     65                 random.nextBytes(iv);
     66             } else {
     67                 NativeCrypto.RAND_bytes(iv);
     68             }
     69         }
     70     }
     71 
     72     @Override
     73     int updateInternal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset,
     74             int maximumLen) throws ShortBufferException {
     75         int inputLenRemaining = inputLen;
     76         if (currentBlockConsumedBytes > 0) {
     77             // A previous operation ended with a partial block, so we need to encrypt using
     78             // the remainder of that block before beginning to use the next block
     79             int len = Math.min(BLOCK_SIZE_BYTES - currentBlockConsumedBytes, inputLenRemaining);
     80             byte[] singleBlock = new byte[BLOCK_SIZE_BYTES];
     81             byte[] singleBlockOut = new byte[BLOCK_SIZE_BYTES];
     82             System.arraycopy(input, inputOffset, singleBlock, currentBlockConsumedBytes, len);
     83             NativeCrypto.chacha20_encrypt_decrypt(singleBlock, 0, singleBlockOut, 0,
     84                     BLOCK_SIZE_BYTES, encodedKey, iv, blockCounter);
     85             System.arraycopy(singleBlockOut, currentBlockConsumedBytes, output, outputOffset, len);
     86             currentBlockConsumedBytes += len;
     87             if (currentBlockConsumedBytes < BLOCK_SIZE_BYTES) {
     88                 // We still didn't finish this block, so we're done.
     89                 return len;
     90             }
     91             assert currentBlockConsumedBytes == BLOCK_SIZE_BYTES;
     92             currentBlockConsumedBytes = 0;
     93             inputOffset += len;
     94             outputOffset += len;
     95             inputLenRemaining -= len;
     96             blockCounter++;
     97         }
     98         NativeCrypto.chacha20_encrypt_decrypt(input, inputOffset, output,
     99                 outputOffset, inputLenRemaining, encodedKey, iv, blockCounter);
    100         currentBlockConsumedBytes = inputLenRemaining % BLOCK_SIZE_BYTES;
    101         blockCounter += inputLenRemaining / BLOCK_SIZE_BYTES;
    102         return inputLen;
    103     }
    104 
    105     @Override
    106     int doFinalInternal(byte[] output, int outputOffset, int maximumLen)
    107             throws IllegalBlockSizeException, BadPaddingException, ShortBufferException {
    108         reset();
    109         return 0;
    110     }
    111 
    112     private void reset() {
    113         blockCounter = 0;
    114         currentBlockConsumedBytes = 0;
    115     }
    116 
    117     @Override
    118     String getBaseCipherName() {
    119         return "ChaCha20";
    120     }
    121 
    122     @Override
    123     void checkSupportedKeySize(int keySize) throws InvalidKeyException {
    124         if (keySize != 32) {
    125             throw new InvalidKeyException("Unsupported key size: " + keySize
    126                     + " bytes (must be 32)");
    127         }
    128     }
    129 
    130     @Override
    131     void checkSupportedMode(Mode mode) throws NoSuchAlgorithmException {
    132         if (mode != Mode.NONE) {
    133             throw new NoSuchAlgorithmException("Mode must be NONE");
    134         }
    135     }
    136 
    137     @Override
    138     void checkSupportedPadding(Padding padding) throws NoSuchPaddingException {
    139         if (padding != Padding.NOPADDING) {
    140             throw new NoSuchPaddingException("Must be NoPadding");
    141         }
    142     }
    143 
    144     @Override
    145     int getCipherBlockSize() {
    146         return 0;
    147     }
    148 
    149     @Override
    150     int getOutputSizeForFinal(int inputLen) {
    151         return inputLen;
    152     }
    153 
    154     @Override
    155     int getOutputSizeForUpdate(int inputLen) {
    156         return inputLen;
    157     }
    158 
    159 }
    160