Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a3fd82ef authored by Guojing Yuan's avatar Guojing Yuan
Browse files

Add a CipherManager for en/decryption using AndroidCrypto

CipherManager can be used to exchange the Android version with the
remote device.

Bug: 253307662

Test: Manually tested encrypt() then descrypt() and checked the output
string matches the original string

Change-Id: I08efe8fa831ddae3fd5adb11b82c3b8803d1f4e5
parent 00abedf6
Loading
Loading
Loading
Loading
+143 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.companion.transport;

import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Slog;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

/**
 * This class can be used to encrypt and decrypt bytes using Android Cryptography
 */
public class CryptoManager {

    private static final String TAG = "CDM_CryptoManager";

    private static final String KEY_STORE_ALIAS = "cdm_secret";
    private static final String ALGORITHM = KeyProperties.KEY_ALGORITHM_AES;
    private static final String BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC;
    private static final String PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7;
    private static final String TRANSFORMATION = ALGORITHM + "/" + BLOCK_MODE + "/" + PADDING;

    private final KeyStore mKeyStore;

    public CryptoManager() {
        // Initialize KeyStore
        try {
            mKeyStore = KeyStore.getInstance("AndroidKeyStore");
            mKeyStore.load(null);
        } catch (KeyStoreException | IOException | NoSuchAlgorithmException
                 | CertificateException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Output format: | iv length (int) | iv | encrypted bytes length (int) | encrypted bytes |
     */
    public byte[] encrypt(byte[] input) {
        try {
            // Encrypt using Cipher
            Cipher encryptCipher = Cipher.getInstance(TRANSFORMATION);
            encryptCipher.init(Cipher.ENCRYPT_MODE, getKey());
            byte[] encryptedBytes = encryptCipher.doFinal(input);

            // Write to bytes
            ByteBuffer buffer = ByteBuffer.allocate(
                            4 + encryptCipher.getIV().length + 4 + encryptedBytes.length)
                    .putInt(encryptCipher.getIV().length)
                    .put(encryptCipher.getIV())
                    .putInt(encryptedBytes.length)
                    .put(encryptedBytes);
            return buffer.array();
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
                 | IllegalBlockSizeException | BadPaddingException e) {
            Slog.e(TAG, e.getMessage());
            return null;
        }
    }

    /**
     * Input format: | iv length (int) | iv | encrypted bytes length (int) | encrypted bytes |
     */
    public byte[] decrypt(byte[] input) {
        ByteBuffer buffer = ByteBuffer.wrap(input);
        byte[] iv = new byte[buffer.getInt()];
        buffer.get(iv);
        byte[] encryptedBytes = new byte[buffer.getInt()];
        buffer.get(encryptedBytes);
        try {
            Cipher decryptCipher = Cipher.getInstance(TRANSFORMATION);
            decryptCipher.init(Cipher.DECRYPT_MODE, getKey(), new IvParameterSpec(iv));
            return decryptCipher.doFinal(encryptedBytes);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
                 | InvalidAlgorithmParameterException | IllegalBlockSizeException
                 | BadPaddingException e) {
            Slog.e(TAG, e.getMessage());
            return null;
        }
    }

    private SecretKey getKey() {
        try {
            KeyStore.Entry keyEntry = mKeyStore.getEntry(KEY_STORE_ALIAS, null);
            if (keyEntry instanceof KeyStore.SecretKeyEntry
                    && ((KeyStore.SecretKeyEntry) keyEntry).getSecretKey() != null) {
                return ((KeyStore.SecretKeyEntry) keyEntry).getSecretKey();
            } else {
                return createKey();
            }
        } catch (NoSuchAlgorithmException | UnrecoverableEntryException | KeyStoreException e) {
            throw new RuntimeException(e);
        }
    }

    private SecretKey createKey() {
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
            keyGenerator.init(
                    new KeyGenParameterSpec.Builder(KEY_STORE_ALIAS,
                            KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                            .setBlockModes(BLOCK_MODE)
                            .setEncryptionPaddings(PADDING)
                            .setUserAuthenticationRequired(false)
                            .setRandomizedEncryptionRequired(true)
                            .build());
            return keyGenerator.generateKey();
        } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
            throw new RuntimeException(e);
        }
    }
}