1 /*
2  * Copyright (C) 2012 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.internal.util;
18 
19 import com.android.apksig.ApkSignerTest;
20 import com.android.apksig.KeyConfig;
21 import com.android.apksig.SigningCertificateLineage;
22 import com.android.apksig.util.DataSource;
23 
24 // BEGIN-AOSP
25 import com.google.cloud.kms.v1.KeyRingName;
26 // END-AOSP
27 
28 import org.junit.rules.TemporaryFolder;
29 
30 import java.io.File;
31 import java.io.FileOutputStream;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.OutputStream;
35 import java.nio.ByteBuffer;
36 import java.security.KeyFactory;
37 import java.security.NoSuchAlgorithmException;
38 import java.security.PrivateKey;
39 import java.security.cert.Certificate;
40 import java.security.cert.CertificateException;
41 import java.security.cert.X509Certificate;
42 import java.security.spec.InvalidKeySpecException;
43 import java.security.spec.PKCS8EncodedKeySpec;
44 import java.util.ArrayList;
45 import java.util.Collection;
46 import java.util.List;
47 import java.util.Locale;
48 
49 /**
50  * Assorted methods to obtaining test input from resources.
51  */
52 public final class Resources {
Resources()53     private Resources() {}
54 
55     // All signers with the same prefix and an _X suffix were signed with the private key of the
56     // (X-1) signer.
57     public static final String FIRST_RSA_2048_SIGNER_RESOURCE_NAME = "rsa-2048";
58     public static final String SECOND_RSA_2048_SIGNER_RESOURCE_NAME = "rsa-2048_2";
59     public static final String THIRD_RSA_2048_SIGNER_RESOURCE_NAME = "rsa-2048_3";
60     public static final String FIRST_RSA_1024_SIGNER_RESOURCE_NAME = "rsa-1024";
61     public static final String SECOND_RSA_1024_SIGNER_RESOURCE_NAME = "rsa-1024_2";
62 
63     public static final String FIRST_RSA_4096_SIGNER_RESOURCE_NAME = "rsa-4096";
64 
65     public static final String EC_P256_SIGNER_RESOURCE_NAME = "ec-p256";
66     public static final String EC_P256_2_SIGNER_RESOURCE_NAME = "ec-p256_2";
67 
68     // BEGIN-AOSP
69     public static final KeyRingName TEST_GCP_KEY_RING =
70             KeyRingName.of("apksigner-cloud-kms", "us-central1", "testV3");
71     // END-AOSP
72 
73     // This is the same cert as above with the modulus reencoded to remove the leading 0 sign bit.
74     public static final String FIRST_RSA_2048_SIGNER_CERT_WITH_NEGATIVE_MODULUS =
75             "rsa-2048_negmod.x509.der";
76 
77     public static final String LINEAGE_RSA_2048_2_SIGNERS_RESOURCE_NAME =
78             "rsa-2048-lineage-2-signers";
79     public static final String LINEAGE_RSA_2048_3_SIGNERS_RESOURCE_NAME =
80             "rsa-2048-lineage-3-signers";
81     public static final String LINEAGE_RSA_2048_3_SIGNERS_1_NO_CAPS_RESOURCE_NAME =
82             "rsa-2048-lineage-3-signers-1-no-caps";
83     public static final String LINEAGE_RSA_2048_2_SIGNERS_2_3_RESOURCE_NAME =
84             "rsa-2048-lineage-2-signers-2-3";
85     public static final String LINEAGE_RSA_2048_TO_RSA_4096_RESOURCE_NAME =
86             "rsa-2048-to-4096-lineage-2-signers";
87 
88     public static final String LINEAGE_EC_P256_2_SIGNERS_RESOURCE_NAME =
89             "ec-p256-lineage-2-signers";
90 
toByteArray(Class<?> cls, String resourceName)91     public static byte[] toByteArray(Class<?> cls, String resourceName) throws IOException {
92         try (InputStream in = cls.getResourceAsStream(resourceName)) {
93             if (in == null) {
94                 throw new IllegalArgumentException("Resource not found: " + resourceName);
95             }
96             return ByteStreams.toByteArray(in);
97         }
98     }
99 
toInputStream(Class<?> cls, String resourceName)100     public static InputStream toInputStream(Class<?> cls, String resourceName) throws IOException {
101             InputStream in = cls.getResourceAsStream(resourceName);
102             if (in == null) {
103                 throw new IllegalArgumentException("Resource not found: " + resourceName);
104             }
105             return in;
106     }
107 
toCertificate( Class <?> cls, String resourceName)108     public static X509Certificate toCertificate(
109             Class <?> cls, String resourceName) throws IOException, CertificateException {
110         try (InputStream in = cls.getResourceAsStream(resourceName)) {
111             if (in == null) {
112                 throw new IllegalArgumentException("Resource not found: " + resourceName);
113             }
114             return X509CertificateUtils.generateCertificate(in);
115         }
116     }
117 
toCertificateChain( Class <?> cls, String resourceName)118     public static List<X509Certificate> toCertificateChain(
119             Class <?> cls, String resourceName) throws IOException, CertificateException {
120         Collection<? extends Certificate> certs;
121         try (InputStream in = cls.getResourceAsStream(resourceName)) {
122             if (in == null) {
123                 throw new IllegalArgumentException("Resource not found: " + resourceName);
124             }
125             certs = X509CertificateUtils.generateCertificates(in);
126         }
127         List<X509Certificate> result = new ArrayList<>(certs.size());
128         for (Certificate cert : certs) {
129             result.add((X509Certificate) cert);
130         }
131         return result;
132     }
133 
toPrivateKey(Class <?> cls, String resourceName)134     public static PrivateKey toPrivateKey(Class <?> cls, String resourceName)
135                     throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
136         int delimiterIndex = resourceName.indexOf('-');
137         if (delimiterIndex == -1) {
138             throw new IllegalArgumentException(
139                     "Failed to autodetect key algorithm from resource name: " + resourceName);
140         }
141         String keyAlgorithm = resourceName.substring(0, delimiterIndex).toUpperCase(Locale.US);
142         return toPrivateKey(cls, resourceName, keyAlgorithm);
143     }
144 
toPrivateKey( Class <?> cls, String resourceName, String keyAlgorithm)145     public static PrivateKey toPrivateKey(
146             Class <?> cls, String resourceName, String keyAlgorithm)
147                     throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
148         byte[] encoded = toByteArray(cls, resourceName);
149 
150         // Keep overly strictly linter happy by limiting what JCA KeyFactory algorithms are used
151         // here
152         KeyFactory keyFactory;
153         switch (keyAlgorithm.toUpperCase(Locale.US)) {
154             case "RSA":
155                 keyFactory = KeyFactory.getInstance("rsa");
156                 break;
157             case "DSA":
158                 keyFactory = KeyFactory.getInstance("dsa");
159                 break;
160             case "EC":
161                 keyFactory = KeyFactory.getInstance("ec");
162                 break;
163             default:
164                 throw new InvalidKeySpecException("Unsupported key algorithm: " + keyAlgorithm);
165         }
166 
167         return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded));
168     }
169 
toLineageSignerConfig(Class<?> cls, String resourcePrefix)170     public static SigningCertificateLineage.SignerConfig toLineageSignerConfig(Class<?> cls,
171             String resourcePrefix) throws Exception {
172         PrivateKey privateKey = toPrivateKey(cls, resourcePrefix + ".pk8");
173         X509Certificate cert = Resources.toCertificate(cls,
174                 resourcePrefix + ".x509.pem");
175         return new SigningCertificateLineage.SignerConfig.Builder(
176                         new KeyConfig.Jca(privateKey), cert)
177                 .build();
178     }
179 
toDataSource(Class<?> cls, String dataSourceResourceName)180     public static DataSource toDataSource(Class<?> cls, String dataSourceResourceName)
181             throws IOException {
182         return new ByteBufferDataSource(ByteBuffer.wrap(Resources
183                 .toByteArray(ApkSignerTest.class, dataSourceResourceName)));
184     }
185 
toSigningCertificateLineage(Class<?> cls, String fileResourceName)186     public static SigningCertificateLineage toSigningCertificateLineage(Class<?> cls,
187             String fileResourceName) throws IOException {
188         DataSource lineageDataSource = toDataSource(cls, fileResourceName);
189         return SigningCertificateLineage.readFromDataSource(lineageDataSource);
190     }
191 
toFile(Class<?> cls, String fileResourceName, TemporaryFolder temporaryFolder)192     public static File toFile(Class<?> cls, String fileResourceName,
193             TemporaryFolder temporaryFolder) throws IOException {
194         File outFile = temporaryFolder.newFile();
195         try (InputStream in = cls.getResourceAsStream(fileResourceName);
196              OutputStream out = new FileOutputStream(outFile)) {
197             if (in == null) {
198                 throw new IllegalArgumentException("Resource not found: " + fileResourceName);
199             }
200             in.transferTo(out);
201             return outFile;
202         }
203     }
204 }
205