| /* |
| * 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() {} |
| } |