1 /*
2  * Copyright (C) 2024 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.apksig.kms;
18 
19 import com.android.apksig.ApkSignerTest;
20 import com.android.apksig.internal.util.Resources;
21 
22 import com.google.crypto.tink.subtle.Kwp;
23 import com.google.protobuf.ByteString;
24 
25 import java.security.KeyFactory;
26 import java.security.PublicKey;
27 import java.security.SecureRandom;
28 import java.security.spec.MGF1ParameterSpec;
29 import java.security.spec.X509EncodedKeySpec;
30 
31 import javax.crypto.Cipher;
32 import javax.crypto.spec.OAEPParameterSpec;
33 import javax.crypto.spec.PSource;
34 
35 /** Utility class to wrap private keys for cloud import */
36 public class KeyWrapper {
KeyWrapper()37     private KeyWrapper() {}
38 
39     /**
40      * Inspired by <a
41      * href="https://cloud.google.com/kms/docs/importing-a-key#kms-import-manually-wrapped-key-java">GCP
42      * Docs</a>.
43      *
44      * @param keyNameInResources a private key from test resources
45      * @param wrappingPublicKeyBytes the DER encoded public key bytes, from a public key provided by
46      *     the cloud provider
47      * @return the wrapped key for upload
48      */
wrapKeyForImport(String keyNameInResources, byte[] wrappingPublicKeyBytes)49     public static byte[] wrapKeyForImport(String keyNameInResources, byte[] wrappingPublicKeyBytes)
50             throws Exception {
51         byte[] privateKeyBytes =
52                 Resources.toByteArray(ApkSignerTest.class, keyNameInResources + ".pk8");
53 
54         // Generate a temporary 32-byte key for AES-KWP and wrap the key material.
55         byte[] kwpKey = new byte[32];
56         new SecureRandom().nextBytes(kwpKey);
57         Kwp kwp = new Kwp(kwpKey);
58         final byte[] wrappedTargetKey = kwp.wrap(privateKeyBytes);
59 
60         PublicKey publicKey =
61                 KeyFactory.getInstance("RSA")
62                         .generatePublic(new X509EncodedKeySpec(wrappingPublicKeyBytes));
63 
64         // Wrap the KWP key using the import job key.
65         Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
66         cipher.init(
67                 Cipher.ENCRYPT_MODE,
68                 publicKey,
69                 new OAEPParameterSpec(
70                         "SHA-1", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT));
71         final byte[] wrappedWrappingKey = cipher.doFinal(kwpKey);
72 
73         // Concatenate the wrapped KWP key and the wrapped target key.
74         return ByteString.copyFrom(wrappedWrappingKey)
75                 .concat(ByteString.copyFrom(wrappedTargetKey))
76                 .toByteArray();
77     }
78 }
79