1 /*
2  * Copyright (C) 2019 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 com.android.server.backup.encryption.keys;
18 
19 import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
20 
21 import java.security.InvalidAlgorithmParameterException;
22 import java.security.InvalidKeyException;
23 import java.security.NoSuchAlgorithmException;
24 import java.util.Locale;
25 
26 import javax.crypto.Cipher;
27 import javax.crypto.IllegalBlockSizeException;
28 import javax.crypto.NoSuchPaddingException;
29 import javax.crypto.SecretKey;
30 import javax.crypto.spec.GCMParameterSpec;
31 
32 /** Utility functions for wrapping and unwrapping tertiary keys. */
33 public class KeyWrapUtils {
34     private static final String AES_GCM_MODE = "AES/GCM/NoPadding";
35     private static final int GCM_TAG_LENGTH_BYTES = 16;
36     private static final int BITS_PER_BYTE = 8;
37     private static final int GCM_TAG_LENGTH_BITS = GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE;
38     private static final String KEY_ALGORITHM = "AES";
39 
40     /**
41      * Uses the secondary key to unwrap the wrapped tertiary key.
42      *
43      * @param secondaryKey The secondary key used to wrap the tertiary key.
44      * @param wrappedKey The wrapped tertiary key.
45      * @return The unwrapped tertiary key.
46      * @throws InvalidKeyException if the provided secondary key cannot unwrap the tertiary key.
47      */
unwrap(SecretKey secondaryKey, WrappedKeyProto.WrappedKey wrappedKey)48     public static SecretKey unwrap(SecretKey secondaryKey, WrappedKeyProto.WrappedKey wrappedKey)
49             throws InvalidKeyException, NoSuchAlgorithmException,
50                     InvalidAlgorithmParameterException, NoSuchPaddingException {
51         if (wrappedKey.wrapAlgorithm != WrappedKeyProto.WrappedKey.AES_256_GCM) {
52             throw new InvalidKeyException(
53                     String.format(
54                             Locale.US,
55                             "Could not unwrap key wrapped with %s algorithm",
56                             wrappedKey.wrapAlgorithm));
57         }
58 
59         if (wrappedKey.metadata == null) {
60             throw new InvalidKeyException("Metadata missing from wrapped tertiary key.");
61         }
62 
63         if (wrappedKey.metadata.type != WrappedKeyProto.KeyMetadata.AES_256_GCM) {
64             throw new InvalidKeyException(
65                     String.format(
66                             Locale.US,
67                             "Wrapped key was unexpected %s algorithm. Only support"
68                                 + " AES/GCM/NoPadding.",
69                             wrappedKey.metadata.type));
70         }
71 
72         Cipher cipher = getCipher();
73 
74         cipher.init(
75                 Cipher.UNWRAP_MODE,
76                 secondaryKey,
77                 new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.nonce));
78 
79         return (SecretKey) cipher.unwrap(wrappedKey.key, KEY_ALGORITHM, Cipher.SECRET_KEY);
80     }
81 
82     /**
83      * Wraps the tertiary key with the secondary key.
84      *
85      * @param secondaryKey The secondary key to use for wrapping.
86      * @param tertiaryKey The key to wrap.
87      * @return The wrapped key.
88      * @throws InvalidKeyException if the key is not good for wrapping.
89      * @throws IllegalBlockSizeException if there is an issue wrapping.
90      */
wrap(SecretKey secondaryKey, SecretKey tertiaryKey)91     public static WrappedKeyProto.WrappedKey wrap(SecretKey secondaryKey, SecretKey tertiaryKey)
92             throws InvalidKeyException, IllegalBlockSizeException, NoSuchAlgorithmException,
93                     NoSuchPaddingException {
94         Cipher cipher = getCipher();
95         cipher.init(Cipher.WRAP_MODE, secondaryKey);
96 
97         WrappedKeyProto.WrappedKey wrappedKey = new WrappedKeyProto.WrappedKey();
98         wrappedKey.key = cipher.wrap(tertiaryKey);
99         wrappedKey.nonce = cipher.getIV();
100         wrappedKey.wrapAlgorithm = WrappedKeyProto.WrappedKey.AES_256_GCM;
101         wrappedKey.metadata = new WrappedKeyProto.KeyMetadata();
102         wrappedKey.metadata.type = WrappedKeyProto.KeyMetadata.AES_256_GCM;
103         return wrappedKey;
104     }
105 
106     /**
107      * Rewraps a tertiary key with a new secondary key.
108      *
109      * @param oldSecondaryKey The old secondary key, used to unwrap the tertiary key.
110      * @param newSecondaryKey The new secondary key, used to rewrap the tertiary key.
111      * @param tertiaryKey The tertiary key, wrapped by {@code oldSecondaryKey}.
112      * @return The tertiary key, wrapped by {@code newSecondaryKey}.
113      * @throws InvalidKeyException if the key is not good for wrapping or unwrapping.
114      * @throws IllegalBlockSizeException if there is an issue wrapping.
115      */
rewrap( SecretKey oldSecondaryKey, SecretKey newSecondaryKey, WrappedKeyProto.WrappedKey tertiaryKey)116     public static WrappedKeyProto.WrappedKey rewrap(
117             SecretKey oldSecondaryKey,
118             SecretKey newSecondaryKey,
119             WrappedKeyProto.WrappedKey tertiaryKey)
120             throws InvalidKeyException, IllegalBlockSizeException,
121                     InvalidAlgorithmParameterException, NoSuchAlgorithmException,
122                     NoSuchPaddingException {
123         return wrap(newSecondaryKey, unwrap(oldSecondaryKey, tertiaryKey));
124     }
125 
getCipher()126     private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
127         return Cipher.getInstance(AES_GCM_MODE);
128     }
129 
130     // Statics only
KeyWrapUtils()131     private KeyWrapUtils() {}
132 }
133