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

Commit c949517f authored by Al Sutton's avatar Al Sutton
Browse files

Migrate KeyWrapUtils

Bring KeyWrapUtils in from GMSCore. This class relies heavily on a set
of protobufs, so this CL includes the creation of the protobuf target
support it and the inclusion of that target in the tests.

Bug: 111386661
Test: atest BackupFrameworksServicesRoboTests
Change-Id: I89e0c68a449f784b132780410d9de32824bb674a
parent ad52c6bc
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
@@ -17,8 +17,15 @@
android_app {
    name: "BackupEncryption",
    srcs: ["src/**/*.java"],
    libs: ["backup-encryption-protos"],
    optimize: { enabled: false },
    platform_apis: true,
    certificate: "platform",
    privileged: true,
}

java_library {
    name: "backup-encryption-protos",
    proto: { type: "nano" },
    srcs: ["proto/**/*.proto"],
}
+52 −0
Original line number Diff line number Diff line
syntax = "proto2";

package android_backup_crypto;

option java_package = "com.android.server.backup.encryption.protos";
option java_outer_classname = "WrappedKeyProto";

// Metadata associated with a tertiary key.
message KeyMetadata {
  // Type of Cipher algorithm the key is used for.
  enum Type {
    UNKNOWN = 0;
    // No padding. Uses 12-byte nonce. Tag length 16 bytes.
    AES_256_GCM = 1;
  }

  // What kind of Cipher algorithm the key is used for. We assume at the moment
  // that this will always be AES_256_GCM and throw if this is not the case.
  // Provided here for forwards compatibility in case at some point we need to
  // change Cipher algorithm.
  optional Type type = 1;
}

// An encrypted tertiary key.
message WrappedKey {
  // The Cipher with which the key was encrypted.
  enum WrapAlgorithm {
    UNKNOWN = 0;
    // No padding. Uses 16-byte nonce (see nonce field). Tag length 16 bytes.
    // The nonce is 16-bytes as this is wrapped with a key in AndroidKeyStore.
    // AndroidKeyStore requires that it generates the IV, and it generates a
    // 16-byte IV for you. You CANNOT provide your own IV.
    AES_256_GCM = 1;
  }

  // Cipher algorithm used to wrap the key. We assume at the moment that this
  // is always AES_256_GC and throw if this is not the case. Provided here for
  // forwards compatibility if at some point we need to change Cipher algorithm.
  optional WrapAlgorithm wrap_algorithm = 1;

  // The nonce used to initialize the Cipher in AES/256/GCM mode.
  optional bytes nonce = 2;

  // The encrypted bytes of the key material.
  optional bytes key = 3;

  // Associated key metadata.
  optional KeyMetadata metadata = 4;

  // Deprecated field; Do not use
  reserved 5;
}
+132 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.backup.encryption.keys;

import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Locale;

import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;

/** Utility functions for wrapping and unwrapping tertiary keys. */
public class KeyWrapUtils {
    private static final String AES_GCM_MODE = "AES/GCM/NoPadding";
    private static final int GCM_TAG_LENGTH_BYTES = 16;
    private static final int BITS_PER_BYTE = 8;
    private static final int GCM_TAG_LENGTH_BITS = GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE;
    private static final String KEY_ALGORITHM = "AES";

    /**
     * Uses the secondary key to unwrap the wrapped tertiary key.
     *
     * @param secondaryKey The secondary key used to wrap the tertiary key.
     * @param wrappedKey The wrapped tertiary key.
     * @return The unwrapped tertiary key.
     * @throws InvalidKeyException if the provided secondary key cannot unwrap the tertiary key.
     */
    public static SecretKey unwrap(SecretKey secondaryKey, WrappedKeyProto.WrappedKey wrappedKey)
            throws InvalidKeyException, NoSuchAlgorithmException,
                    InvalidAlgorithmParameterException, NoSuchPaddingException {
        if (wrappedKey.wrapAlgorithm != WrappedKeyProto.WrappedKey.AES_256_GCM) {
            throw new InvalidKeyException(
                    String.format(
                            Locale.US,
                            "Could not unwrap key wrapped with %s algorithm",
                            wrappedKey.wrapAlgorithm));
        }

        if (wrappedKey.metadata == null) {
            throw new InvalidKeyException("Metadata missing from wrapped tertiary key.");
        }

        if (wrappedKey.metadata.type != WrappedKeyProto.KeyMetadata.AES_256_GCM) {
            throw new InvalidKeyException(
                    String.format(
                            Locale.US,
                            "Wrapped key was unexpected %s algorithm. Only support"
                                + " AES/GCM/NoPadding.",
                            wrappedKey.metadata.type));
        }

        Cipher cipher = getCipher();

        cipher.init(
                Cipher.UNWRAP_MODE,
                secondaryKey,
                new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.nonce));

        return (SecretKey) cipher.unwrap(wrappedKey.key, KEY_ALGORITHM, Cipher.SECRET_KEY);
    }

    /**
     * Wraps the tertiary key with the secondary key.
     *
     * @param secondaryKey The secondary key to use for wrapping.
     * @param tertiaryKey The key to wrap.
     * @return The wrapped key.
     * @throws InvalidKeyException if the key is not good for wrapping.
     * @throws IllegalBlockSizeException if there is an issue wrapping.
     */
    public static WrappedKeyProto.WrappedKey wrap(SecretKey secondaryKey, SecretKey tertiaryKey)
            throws InvalidKeyException, IllegalBlockSizeException, NoSuchAlgorithmException,
                    NoSuchPaddingException {
        Cipher cipher = getCipher();
        cipher.init(Cipher.WRAP_MODE, secondaryKey);

        WrappedKeyProto.WrappedKey wrappedKey = new WrappedKeyProto.WrappedKey();
        wrappedKey.key = cipher.wrap(tertiaryKey);
        wrappedKey.nonce = cipher.getIV();
        wrappedKey.wrapAlgorithm = WrappedKeyProto.WrappedKey.AES_256_GCM;
        wrappedKey.metadata = new WrappedKeyProto.KeyMetadata();
        wrappedKey.metadata.type = WrappedKeyProto.KeyMetadata.AES_256_GCM;
        return wrappedKey;
    }

    /**
     * Rewraps a tertiary key with a new secondary key.
     *
     * @param oldSecondaryKey The old secondary key, used to unwrap the tertiary key.
     * @param newSecondaryKey The new secondary key, used to rewrap the tertiary key.
     * @param tertiaryKey The tertiary key, wrapped by {@code oldSecondaryKey}.
     * @return The tertiary key, wrapped by {@code newSecondaryKey}.
     * @throws InvalidKeyException if the key is not good for wrapping or unwrapping.
     * @throws IllegalBlockSizeException if there is an issue wrapping.
     */
    public static WrappedKeyProto.WrappedKey rewrap(
            SecretKey oldSecondaryKey,
            SecretKey newSecondaryKey,
            WrappedKeyProto.WrappedKey tertiaryKey)
            throws InvalidKeyException, IllegalBlockSizeException,
                    InvalidAlgorithmParameterException, NoSuchAlgorithmException,
                    NoSuchPaddingException {
        return wrap(newSecondaryKey, unwrap(oldSecondaryKey, tertiaryKey));
    }

    private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
        return Cipher.getInstance(AES_GCM_MODE);
    }

    // Statics only
    private KeyWrapUtils() {}
}
+1 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ android_robolectric_test {
    ],
    java_resource_dirs: ["config"],
    libs: [
        "backup-encryption-protos",
        "platform-test-annotations",
        "testng",
    ],
+158 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.backup.encryption.keys;

import static com.android.server.backup.testing.CryptoTestUtils.generateAesKey;

import static com.google.common.truth.Truth.assertThat;

import android.platform.test.annotations.Presubmit;

import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;

import java.security.InvalidKeyException;

import javax.crypto.SecretKey;

/** Key wrapping tests */
@RunWith(RobolectricTestRunner.class)
@Presubmit
public class KeyWrapUtilsTest {
    private static final int KEY_SIZE_BITS = 256;
    private static final int BITS_PER_BYTE = 8;
    private static final int GCM_NONCE_LENGTH_BYTES = 16;
    private static final int GCM_TAG_LENGTH_BYTES = 16;

    /** Test a wrapped key has metadata */
    @Test
    public void wrap_addsMetadata() throws Exception {
        WrappedKeyProto.WrappedKey wrappedKey =
                KeyWrapUtils.wrap(
                        /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
        assertThat(wrappedKey.metadata).isNotNull();
        assertThat(wrappedKey.metadata.type).isEqualTo(WrappedKeyProto.KeyMetadata.AES_256_GCM);
    }

    /** Test a wrapped key has an algorithm specified */
    @Test
    public void wrap_addsWrapAlgorithm() throws Exception {
        WrappedKeyProto.WrappedKey wrappedKey =
                KeyWrapUtils.wrap(
                        /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
        assertThat(wrappedKey.wrapAlgorithm).isEqualTo(WrappedKeyProto.WrappedKey.AES_256_GCM);
    }

    /** Test a wrapped key haas an nonce of the right length */
    @Test
    public void wrap_addsNonceOfAppropriateLength() throws Exception {
        WrappedKeyProto.WrappedKey wrappedKey =
                KeyWrapUtils.wrap(
                        /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
        assertThat(wrappedKey.nonce).hasLength(GCM_NONCE_LENGTH_BYTES);
    }

    /** Test a wrapped key has a key of the right length */
    @Test
    public void wrap_addsTagOfAppropriateLength() throws Exception {
        WrappedKeyProto.WrappedKey wrappedKey =
                KeyWrapUtils.wrap(
                        /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
        assertThat(wrappedKey.key).hasLength(KEY_SIZE_BITS / BITS_PER_BYTE + GCM_TAG_LENGTH_BYTES);
    }

    /** Ensure a key can be wrapped and unwrapped again */
    @Test
    public void unwrap_unwrapsEncryptedKey() throws Exception {
        SecretKey secondaryKey = generateAesKey();
        SecretKey tertiaryKey = generateAesKey();
        WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, tertiaryKey);
        SecretKey unwrappedKey = KeyWrapUtils.unwrap(secondaryKey, wrappedKey);
        assertThat(unwrappedKey).isEqualTo(tertiaryKey);
    }

    /** Ensure the unwrap method rejects keys with bad algorithms */
    @Test(expected = InvalidKeyException.class)
    public void unwrap_throwsForBadWrapAlgorithm() throws Exception {
        SecretKey secondaryKey = generateAesKey();
        WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, generateAesKey());
        wrappedKey.wrapAlgorithm = WrappedKeyProto.WrappedKey.UNKNOWN;

        KeyWrapUtils.unwrap(secondaryKey, wrappedKey);
    }

    /** Ensure the unwrap method rejects metadata indicating the encryption type is unknown */
    @Test(expected = InvalidKeyException.class)
    public void unwrap_throwsForBadKeyAlgorithm() throws Exception {
        SecretKey secondaryKey = generateAesKey();
        WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, generateAesKey());
        wrappedKey.metadata.type = WrappedKeyProto.KeyMetadata.UNKNOWN;

        KeyWrapUtils.unwrap(secondaryKey, wrappedKey);
    }

    /** Ensure the unwrap method rejects wrapped keys missing the metadata */
    @Test(expected = InvalidKeyException.class)
    public void unwrap_throwsForMissingMetadata() throws Exception {
        SecretKey secondaryKey = generateAesKey();
        WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, generateAesKey());
        wrappedKey.metadata = null;

        KeyWrapUtils.unwrap(secondaryKey, wrappedKey);
    }

    /** Ensure unwrap rejects invalid secondary keys */
    @Test(expected = InvalidKeyException.class)
    public void unwrap_throwsForBadSecondaryKey() throws Exception {
        WrappedKeyProto.WrappedKey wrappedKey =
                KeyWrapUtils.wrap(
                        /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());

        KeyWrapUtils.unwrap(generateAesKey(), wrappedKey);
    }

    /** Ensure rewrap can rewrap keys */
    @Test
    public void rewrap_canBeUnwrappedWithNewSecondaryKey() throws Exception {
        SecretKey tertiaryKey = generateAesKey();
        SecretKey oldSecondaryKey = generateAesKey();
        SecretKey newSecondaryKey = generateAesKey();
        WrappedKeyProto.WrappedKey wrappedWithOld = KeyWrapUtils.wrap(oldSecondaryKey, tertiaryKey);

        WrappedKeyProto.WrappedKey wrappedWithNew =
                KeyWrapUtils.rewrap(oldSecondaryKey, newSecondaryKey, wrappedWithOld);

        assertThat(KeyWrapUtils.unwrap(newSecondaryKey, wrappedWithNew)).isEqualTo(tertiaryKey);
    }

    /** Ensure rewrap doesn't create something decryptable by an old key */
    @Test(expected = InvalidKeyException.class)
    public void rewrap_cannotBeUnwrappedWithOldSecondaryKey() throws Exception {
        SecretKey tertiaryKey = generateAesKey();
        SecretKey oldSecondaryKey = generateAesKey();
        SecretKey newSecondaryKey = generateAesKey();
        WrappedKeyProto.WrappedKey wrappedWithOld = KeyWrapUtils.wrap(oldSecondaryKey, tertiaryKey);

        WrappedKeyProto.WrappedKey wrappedWithNew =
                KeyWrapUtils.rewrap(oldSecondaryKey, newSecondaryKey, wrappedWithOld);

        KeyWrapUtils.unwrap(oldSecondaryKey, wrappedWithNew);
    }
}