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

Commit e2f931d7 authored by Al Sutton's avatar Al Sutton Committed by Android (Google) Code Review
Browse files

Merge "Migrate KeyWrapUtils"

parents 42dfdf00 c949517f
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);
    }
}