Merge "Handle OOM error when allocating space for signature block" into main
diff --git a/Android.bp b/Android.bp
index 8f3c7a6..86b1643 100644
--- a/Android.bp
+++ b/Android.bp
@@ -35,14 +35,10 @@
java_library_host {
name: "apksig",
- srcs: [
- "src/main/java/**/*.java",
- "src/stub/java/**/*.java",
- ],
+ srcs: ["src/main/java/**/*.java"],
exclude_srcs: [
"src/main/java/com/android/apksig/kms/aws/**/*.java",
"src/main/java/com/android/apksig/kms/gcp/**/*.java",
- "src/main/java/com/android/apksig/kms/KmsSignerEngine.java",
],
java_version: "1.8",
target: {
@@ -53,17 +49,34 @@
}
java_library_host {
- name: "apksig-kms",
+ name: "apksig-kms-provider-aws",
srcs: [
- "src/main/java/**/*.java",
+ "src/main/java/com/android/apksig/kms/aws/**/*.java",
],
libs: [
+ "apksig",
"awssdk-kms",
"awssdk-url-connection-client",
- "google-cloud-kms",
- "libprotobuf-java-util-full",
- "slf4j-api",
],
+ services: ["src/providers/aws/*"],
+ java_version: "1.8",
+ target: {
+ windows: {
+ enabled: true,
+ },
+ },
+}
+
+java_library_host {
+ name: "apksig-kms-provider-gcp",
+ srcs: [
+ "src/main/java/com/android/apksig/kms/gcp/**/*.java",
+ ],
+ libs: [
+ "apksig",
+ "google-cloud-kms",
+ ],
+ services: ["src/providers/gcp/*"],
java_version: "1.8",
target: {
windows: {
@@ -106,7 +119,9 @@
defaults: ["apksigner-defaults"],
wrapper: "etc/apksigner-kms",
static_libs: [
- "apksig-kms",
+ "apksig",
+ "apksig-kms-provider-aws",
+ "apksig-kms-provider-gcp",
"awssdk-kms",
"awssdk-url-connection-client",
"conscrypt-unbundled",
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 9aa811d..8a6c84d 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,8 +1,5 @@
[Builtin Hooks]
google_java_format = true
-[Builtin Hooks Options]
-google_java_format = --sort-imports
-
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
diff --git a/src/apksigner/java/com/android/apksigner/ApkSignerTool.java b/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
index 4d3f195..519fe66 100644
--- a/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
+++ b/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
@@ -265,6 +265,12 @@
signerParams.setKeystoreProviderArg(
optionsParser.getRequiredValue(
"JCA KeyStore Provider constructor argument"));
+ } else if ("kms-type".equals(optionName)) {
+ signerParams.setKmsType(
+ optionsParser.getRequiredValue("Key Management Service (KMS) type"));
+ } else if ("kms-key-alias".equals(optionName)) {
+ signerParams.setKmsKeyAlias(
+ optionsParser.getRequiredValue("Key Management Service (KMS) key alias"));
} else if ("key".equals(optionName)) {
signerParams.setKeyFile(optionsParser.getRequiredValue("Private key file"));
} else if ("cert".equals(optionName)) {
@@ -471,6 +477,8 @@
} else {
v1SigBasename = keyFileName.substring(0, delimiterIndex);
}
+ } else if (signer.getKmsKeyAlias() != null) {
+ v1SigBasename = signer.getKmsKeyAlias();
} else {
throw new RuntimeException("Neither KeyStore key alias nor private key file available");
}
@@ -672,6 +680,10 @@
verbose, printCertsPem);
}
}
+ if (sourceStampInfo != null && verbose) {
+ System.out.println(
+ "Source Stamp Timestamp: " + sourceStampInfo.getTimestampEpochSeconds());
+ }
} else {
System.err.println("DOES NOT VERIFY");
}
@@ -1068,6 +1080,10 @@
signerParams.setKeystoreProviderArg(
optionsParser.getRequiredValue(
"JCA KeyStore Provider constructor argument"));
+ } else if ("kms-type".equals(optionName)) {
+ signerParams.setKmsType(optionsParser.getRequiredValue("KMS Type"));
+ } else if ("kms-key-alias".equals(optionName)) {
+ signerParams.setKmsKeyAlias(optionsParser.getRequiredValue("KMS Key Alias"));
} else if ("key".equals(optionName)) {
signerParams.setKeyFile(optionsParser.getRequiredValue("Private key file"));
} else if ("cert".equals(optionName)) {
diff --git a/src/apksigner/java/com/android/apksigner/SignerParams.java b/src/apksigner/java/com/android/apksigner/SignerParams.java
index 0736738..8f4f323 100644
--- a/src/apksigner/java/com/android/apksigner/SignerParams.java
+++ b/src/apksigner/java/com/android/apksigner/SignerParams.java
@@ -16,13 +16,11 @@
package com.android.apksigner;
-import static java.util.Arrays.stream;
import com.android.apksig.KeyConfig;
import com.android.apksig.SigningCertificateLineage;
import com.android.apksig.SigningCertificateLineage.SignerCapabilities;
import com.android.apksig.internal.util.X509CertificateUtils;
-import com.android.apksig.kms.KmsType;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -72,6 +70,8 @@
private String keyFile;
private String certFile;
+ private String mKmsType;
+ private String mKmsKeyAlias;
private String v1SigFileBasename;
@@ -139,6 +139,18 @@
this.keyFile = keyFile;
}
+ public void setKmsType(String mKmsType) {
+ this.mKmsType = mKmsType;
+ }
+
+ public String getKmsKeyAlias() {
+ return mKmsKeyAlias;
+ }
+
+ public void setKmsKeyAlias(String mKmsKeyAlias) {
+ this.mKmsKeyAlias = mKmsKeyAlias;
+ }
+
public void setCertFile(String certFile) {
this.certFile = certFile;
}
@@ -205,23 +217,20 @@
&& (certFile == null)
&& (v1SigFileBasename == null)
&& (mKeyConfig == null)
- && (certs == null);
+ && (certs == null)
+ && (mKmsType == null)
+ && (mKmsKeyAlias == null);
}
public void loadPrivateKeyAndCerts(PasswordRetriever passwordRetriever) throws Exception {
- KmsType kmsType =
- stream(KmsType.values())
- .filter(v -> v.toString().equalsIgnoreCase(keystoreType))
- .findFirst()
- .orElse(null);
-
- if (kmsType != null) {
- if (keystoreKeyAlias == null) {
+ if (mKmsType != null) {
+ if (mKmsKeyAlias == null) {
throw new ParameterException(
- "key alias (--ks-key-alias) is required if ks-type is a cloud KMS");
+ "kms key alias (--kms-key-alias) is required if kms type (--kms-type) is"
+ + " provided");
}
certs = loadCertsFromFile(certFile);
- mKeyConfig = new KeyConfig.Kms(kmsType, keystoreKeyAlias);
+ mKeyConfig = new KeyConfig.Kms(mKmsType, mKmsKeyAlias);
return;
}
@@ -244,8 +253,8 @@
}
throw new ParameterException(
- "KeyStore (--ks), private key file (--key), or key alias and cloud provider"
- + " (--ks-key-alias and --ks-type) must be specified");
+ "KeyStore (--ks), private key file (--key), or KMS key alias and type"
+ + " (--kms-key-alias and --kms-type) must be specified");
}
private void loadPrivateKeyAndCertsFromKeyStore(PasswordRetriever passwordRetriever)
diff --git a/src/main/java/com/android/apksig/KeyConfig.java b/src/main/java/com/android/apksig/KeyConfig.java
index 8f9aa15..71179b8 100644
--- a/src/main/java/com/android/apksig/KeyConfig.java
+++ b/src/main/java/com/android/apksig/KeyConfig.java
@@ -16,7 +16,6 @@
package com.android.apksig;
-import com.android.apksig.kms.KmsType;
import java.security.PrivateKey;
import java.util.function.Function;
@@ -50,7 +49,7 @@
/** For signing via a Key Management Service (KMS). */
public static class Kms extends KeyConfig {
- public final KmsType kmsType;
+ public final String kmsType;
public final String keyAlias;
@Override
@@ -58,7 +57,7 @@
return kms.apply(this);
}
- public Kms(KmsType kmsType, String keyAlias) {
+ public Kms(String kmsType, String keyAlias) {
this.kmsType = kmsType;
this.keyAlias = keyAlias;
}
diff --git a/src/main/java/com/android/apksig/SignerEngineFactory.java b/src/main/java/com/android/apksig/SignerEngineFactory.java
index ee20c58..4301ac7 100644
--- a/src/main/java/com/android/apksig/SignerEngineFactory.java
+++ b/src/main/java/com/android/apksig/SignerEngineFactory.java
@@ -16,9 +16,12 @@
package com.android.apksig;
-import com.android.apksig.kms.KmsSignerEngine;
+import com.android.apksig.kms.KmsException;
+import com.android.apksig.kms.KmsSignerEngineProvider;
import java.security.spec.AlgorithmParameterSpec;
+import java.util.Objects;
+import java.util.ServiceLoader;
/** Simple util to fetch a signer engine based on provided config values. */
public class SignerEngineFactory {
@@ -41,6 +44,23 @@
jca ->
new JcaSignerEngine(
jca.privateKey, jcaSignatureAlgorithm, algorithmParameterSpec),
- kms -> KmsSignerEngine.fromKmsConfig(kms, jcaSignatureAlgorithm));
+ kms -> getKmsImplementation(kms, jcaSignatureAlgorithm, algorithmParameterSpec));
+ }
+
+ private static SignerEngine getKmsImplementation(
+ KeyConfig.Kms keyConfig,
+ String jcaSignatureAlgorithm,
+ AlgorithmParameterSpec algorithmParameterSpec) {
+ ServiceLoader<KmsSignerEngineProvider> providers =
+ ServiceLoader.load(KmsSignerEngineProvider.class);
+ for (KmsSignerEngineProvider provider : providers) {
+ if (Objects.equals(provider.getKmsType(), keyConfig.kmsType)) {
+ return provider.getInstance(
+ keyConfig, jcaSignatureAlgorithm, algorithmParameterSpec);
+ }
+ }
+
+ throw new KmsException(
+ keyConfig.kmsType, "No SignerEngine implementation found on the classpath");
}
}
diff --git a/src/main/java/com/android/apksig/SigningCertificateLineage.java b/src/main/java/com/android/apksig/SigningCertificateLineage.java
index 1af64f8..1af8fd4 100644
--- a/src/main/java/com/android/apksig/SigningCertificateLineage.java
+++ b/src/main/java/com/android/apksig/SigningCertificateLineage.java
@@ -942,10 +942,6 @@
private final int mCallerConfiguredFlags;
- private SignerCapabilities(int flags) {
- this(flags, 0);
- }
-
private SignerCapabilities(int flags, int callerConfiguredFlags) {
mFlags = flags;
mCallerConfiguredFlags = callerConfiguredFlags;
diff --git a/src/main/java/com/android/apksig/apk/ApkUtils.java b/src/main/java/com/android/apksig/apk/ApkUtils.java
index 156ea17..1a0db19 100644
--- a/src/main/java/com/android/apksig/apk/ApkUtils.java
+++ b/src/main/java/com/android/apksig/apk/ApkUtils.java
@@ -353,6 +353,10 @@
* @throws CodenameMinSdkVersionException if the {@code codename} is not supported
*/
static int getMinSdkVersionForCodename(String codename) throws CodenameMinSdkVersionException {
+ if ("Baklava".equals(codename)) {
+ return 34; // VIC (35) was the version before Baklava, return VIC version minus one
+ }
+
char firstChar = codename.isEmpty() ? ' ' : codename.charAt(0);
// Codenames are case-sensitive. Only codenames starting with A-Z are supported for now.
// We only look at the first letter of the codename as this is the most important letter.
@@ -373,7 +377,7 @@
// element at insertionIndex (if present) is greater than firstChar.
int insertionIndex = -1 - searchResult; // insertionIndex is in [0; array length]
if (insertionIndex == 0) {
- // 'A' or 'B' -- never released to public
+ // 'A' or 'B' (not Baklava) -- never released to public
return 1;
} else {
// The element at insertionIndex - 1 is the newest older codename.
diff --git a/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeSigner.java b/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeSigner.java
index 7bf952d..416cf87 100644
--- a/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeSigner.java
+++ b/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeSigner.java
@@ -180,9 +180,6 @@
if (signerConfig.certificates.isEmpty()) {
throw new SignatureException("No certificates configured for signer");
}
- if (signerConfig.certificates.size() != 1) {
- throw new CertificateEncodingException("Should only have one certificate");
- }
// Collecting data for signing.
final PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey();
diff --git a/src/main/java/com/android/apksig/internal/util/VerityTreeBuilder.java b/src/main/java/com/android/apksig/internal/util/VerityTreeBuilder.java
index 81026ba..5c1f407 100644
--- a/src/main/java/com/android/apksig/internal/util/VerityTreeBuilder.java
+++ b/src/main/java/com/android/apksig/internal/util/VerityTreeBuilder.java
@@ -28,13 +28,11 @@
import java.nio.ByteOrder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-
import java.util.ArrayList;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Phaser;
import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
/**
* VerityTreeBuilder is used to generate the root hash of verity tree built from the input file.
@@ -60,10 +58,6 @@
* Typical prefetch size.
*/
private final static int MAX_PREFETCH_CHUNKS = 1024;
- /**
- * Minimum chunks to be processed by a single worker task.
- */
- private final static int MIN_CHUNKS_PER_WORKER = 8;
/**
* Digest algorithm (JCA Digest algorithm name) used in the tree.
diff --git a/src/main/java/com/android/apksig/kms/KmsException.java b/src/main/java/com/android/apksig/kms/KmsException.java
index 47a124d..9daceaa 100644
--- a/src/main/java/com/android/apksig/kms/KmsException.java
+++ b/src/main/java/com/android/apksig/kms/KmsException.java
@@ -18,25 +18,25 @@
/** Represents an exception thrown by the external KMS. */
public class KmsException extends RuntimeException {
- private final KmsType mKmsType;
+ private final String mKmsType;
- public KmsException(KmsType kmsType, String message) {
+ public KmsException(String kmsType, String message) {
super(message);
this.mKmsType = kmsType;
}
- public KmsException(KmsType kmsType, String message, Throwable cause) {
+ public KmsException(String kmsType, String message, Throwable cause) {
super(message, cause);
this.mKmsType = kmsType;
}
- public KmsException(KmsType kmsType, Throwable cause) {
+ public KmsException(String kmsType, Throwable cause) {
super(cause);
this.mKmsType = kmsType;
}
@Override
public String getMessage() {
- return "KMS " + mKmsType.toString() + " threw exception: " + super.getMessage();
+ return "KMS " + mKmsType + " threw exception: " + super.getMessage();
}
}
diff --git a/src/main/java/com/android/apksig/kms/KmsSignerEngine.java b/src/main/java/com/android/apksig/kms/KmsSignerEngine.java
deleted file mode 100644
index 5b1f2cf..0000000
--- a/src/main/java/com/android/apksig/kms/KmsSignerEngine.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2024 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.apksig.kms;
-
-import com.android.apksig.KeyConfig;
-import com.android.apksig.SignerEngine;
-import com.android.apksig.kms.aws.AwsSignerEngine;
-import com.android.apksig.kms.gcp.GcpSignerEngine;
-
-/** Performs cryptographic signing with a Key Management Service (KMS). */
-public abstract class KmsSignerEngine implements SignerEngine {
- public final KmsType kmsType;
- public final String keyAlias;
-
- /** Subclasses must specify the type of KMS and a signing key alias. */
- public KmsSignerEngine(KmsType kmsType, String keyAlias) {
- this.kmsType = kmsType;
- this.keyAlias = keyAlias;
- }
-
- @Override
- public abstract byte[] sign(byte[] data);
-
- /** Fetch a concrete implementation based on the provided config. */
- public static KmsSignerEngine fromKmsConfig(
- KeyConfig.Kms kmsConfig, String jcaSignatureAlgorithm) {
- switch (kmsConfig.kmsType) {
- case AWS:
- return new AwsSignerEngine(kmsConfig.keyAlias, jcaSignatureAlgorithm);
- case GCP:
- return new GcpSignerEngine(kmsConfig.keyAlias);
- default:
- throw new KmsException(kmsConfig.kmsType, "Unsupported KMS");
- }
- }
-}
diff --git a/src/main/java/com/android/apksig/kms/KmsSignerEngineProvider.java b/src/main/java/com/android/apksig/kms/KmsSignerEngineProvider.java
new file mode 100644
index 0000000..957917b
--- /dev/null
+++ b/src/main/java/com/android/apksig/kms/KmsSignerEngineProvider.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 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.apksig.kms;
+
+import com.android.apksig.KeyConfig;
+import com.android.apksig.SignerEngine;
+
+import java.security.spec.AlgorithmParameterSpec;
+
+public interface KmsSignerEngineProvider {
+
+ /** Instantiates a concrete signer engine */
+ SignerEngine getInstance(
+ KeyConfig.Kms kmsConfig,
+ String jcaSignatureAlgorithm,
+ AlgorithmParameterSpec algorithmParameterSpec);
+
+ /** Which KMS provider this engine applies to */
+ String getKmsType();
+}
diff --git a/src/main/java/com/android/apksig/kms/KmsType.java b/src/main/java/com/android/apksig/kms/KmsType.java
index c135ddf..de81f4d 100644
--- a/src/main/java/com/android/apksig/kms/KmsType.java
+++ b/src/main/java/com/android/apksig/kms/KmsType.java
@@ -17,7 +17,7 @@
package com.android.apksig.kms;
/** Represents the supported Key Management Services. */
-public enum KmsType {
- AWS,
- GCP,
+public class KmsType {
+ public static String AWS = "aws";
+ public static String GCP = "gcp";
}
diff --git a/src/main/java/com/android/apksig/kms/aws/AwsSignerEngine.java b/src/main/java/com/android/apksig/kms/aws/AwsSignerEngine.java
index 74a7cd5..8851a1d 100644
--- a/src/main/java/com/android/apksig/kms/aws/AwsSignerEngine.java
+++ b/src/main/java/com/android/apksig/kms/aws/AwsSignerEngine.java
@@ -18,8 +18,8 @@
import static com.android.apksig.kms.KmsType.AWS;
+import com.android.apksig.SignerEngine;
import com.android.apksig.kms.KmsException;
-import com.android.apksig.kms.KmsSignerEngine;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient;
@@ -27,12 +27,13 @@
import software.amazon.awssdk.services.kms.model.SignRequest;
import software.amazon.awssdk.services.kms.model.SigningAlgorithmSpec;
-public class AwsSignerEngine extends KmsSignerEngine {
+public class AwsSignerEngine implements SignerEngine {
+ private final String mKeyAlias;
private static final String ALIAS_PREFIX = "alias/";
private final SigningAlgorithmSpec mSigningAlgorithmSpec;
public AwsSignerEngine(String keyAlias, String jcaSignatureAlgorithm) {
- super(AWS, keyAlias);
+ mKeyAlias = keyAlias;
mSigningAlgorithmSpec = fromJcaSignatureAlgorithm(jcaSignatureAlgorithm);
}
@@ -42,7 +43,7 @@
KmsClient.builder().httpClientBuilder(UrlConnectionHttpClient.builder()).build()) {
return client.sign(
SignRequest.builder()
- .keyId(ALIAS_PREFIX + keyAlias)
+ .keyId(ALIAS_PREFIX + mKeyAlias)
.signingAlgorithm(mSigningAlgorithmSpec)
.message(SdkBytes.fromByteArray(data))
.build())
diff --git a/src/main/java/com/android/apksig/kms/aws/AwsSignerEngineProvider.java b/src/main/java/com/android/apksig/kms/aws/AwsSignerEngineProvider.java
new file mode 100644
index 0000000..bc55e6a
--- /dev/null
+++ b/src/main/java/com/android/apksig/kms/aws/AwsSignerEngineProvider.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 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.apksig.kms.aws;
+
+import com.android.apksig.KeyConfig;
+import com.android.apksig.SignerEngine;
+import com.android.apksig.kms.KmsSignerEngineProvider;
+import com.android.apksig.kms.KmsType;
+
+import java.security.spec.AlgorithmParameterSpec;
+
+public class AwsSignerEngineProvider implements KmsSignerEngineProvider {
+
+ @Override
+ public SignerEngine getInstance(
+ KeyConfig.Kms kmsConfig,
+ String jcaSignatureAlgorithm,
+ AlgorithmParameterSpec algorithmParameterSpec) {
+ return new AwsSignerEngine(kmsConfig.keyAlias, jcaSignatureAlgorithm);
+ }
+
+ @Override
+ public String getKmsType() {
+ return KmsType.AWS;
+ }
+}
diff --git a/src/main/java/com/android/apksig/kms/gcp/GcpSignerEngine.java b/src/main/java/com/android/apksig/kms/gcp/GcpSignerEngine.java
index 61e0ad3..c72c2c6 100644
--- a/src/main/java/com/android/apksig/kms/gcp/GcpSignerEngine.java
+++ b/src/main/java/com/android/apksig/kms/gcp/GcpSignerEngine.java
@@ -16,9 +16,10 @@
package com.android.apksig.kms.gcp;
+import static com.android.apksig.kms.KmsType.GCP;
+
+import com.android.apksig.SignerEngine;
import com.android.apksig.kms.KmsException;
-import com.android.apksig.kms.KmsSignerEngine;
-import com.android.apksig.kms.KmsType;
import com.google.cloud.kms.v1.AsymmetricSignRequest;
import com.google.cloud.kms.v1.CryptoKeyVersionName;
@@ -28,7 +29,8 @@
import java.io.IOException;
/** Signs data using Google Cloud Platform. */
-public class GcpSignerEngine extends KmsSignerEngine {
+public class GcpSignerEngine implements SignerEngine {
+ private final String mKeyAlias;
/**
* Create an engine to sign data with GCP
@@ -37,13 +39,13 @@
* href="https://cloud.google.com/java/docs/reference/google-cloud-spanner/latest/com.google.spanner.admin.database.v1.CryptoKeyVersionName">CryptoKeyVersionName</a>
*/
public GcpSignerEngine(String keyAlias) {
- super(KmsType.GCP, keyAlias);
+ mKeyAlias = keyAlias;
}
@Override
public byte[] sign(byte[] data) {
try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) {
- CryptoKeyVersionName cryptoKeyVersionName = CryptoKeyVersionName.parse(this.keyAlias);
+ CryptoKeyVersionName cryptoKeyVersionName = CryptoKeyVersionName.parse(mKeyAlias);
return client.asymmetricSign(
AsymmetricSignRequest.newBuilder()
.setName(cryptoKeyVersionName.toString())
@@ -52,8 +54,7 @@
.getSignature()
.toByteArray();
} catch (IOException e) {
- throw new KmsException(
- this.kmsType, "Error initializing KeyManagementServiceClient", e);
+ throw new KmsException(GCP, "Error initializing KeyManagementServiceClient", e);
}
}
}
diff --git a/src/main/java/com/android/apksig/kms/gcp/GcpSignerEngineProvider.java b/src/main/java/com/android/apksig/kms/gcp/GcpSignerEngineProvider.java
new file mode 100644
index 0000000..2f95bcc
--- /dev/null
+++ b/src/main/java/com/android/apksig/kms/gcp/GcpSignerEngineProvider.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 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.apksig.kms.gcp;
+
+import com.android.apksig.KeyConfig;
+import com.android.apksig.SignerEngine;
+import com.android.apksig.kms.KmsSignerEngineProvider;
+import com.android.apksig.kms.KmsType;
+
+import java.security.spec.AlgorithmParameterSpec;
+
+public class GcpSignerEngineProvider implements KmsSignerEngineProvider {
+
+ @Override
+ public SignerEngine getInstance(
+ KeyConfig.Kms kmsConfig,
+ String jcaSignatureAlgorithm,
+ AlgorithmParameterSpec algorithmParameterSpec) {
+ return new GcpSignerEngine(kmsConfig.keyAlias);
+ }
+
+ @Override
+ public String getKmsType() {
+ return KmsType.GCP;
+ }
+}
diff --git a/src/main/resources/META-INF/services/com.android.apksig.kms.KmsSignerEngineProvider b/src/main/resources/META-INF/services/com.android.apksig.kms.KmsSignerEngineProvider
new file mode 100644
index 0000000..bb4e9ab
--- /dev/null
+++ b/src/main/resources/META-INF/services/com.android.apksig.kms.KmsSignerEngineProvider
@@ -0,0 +1,2 @@
+com.android.apksig.kms.aws.AwsSignerEngineProvider
+com.android.apksig.kms.gcp.GcpSignerEngineProvider
diff --git a/src/providers/aws/com.android.apksig.kms.KmsSignerEngineProvider b/src/providers/aws/com.android.apksig.kms.KmsSignerEngineProvider
new file mode 100644
index 0000000..61a33da
--- /dev/null
+++ b/src/providers/aws/com.android.apksig.kms.KmsSignerEngineProvider
@@ -0,0 +1 @@
+com.android.apksig.kms.aws.AwsSignerEngineProvider
diff --git a/src/providers/gcp/com.android.apksig.kms.KmsSignerEngineProvider b/src/providers/gcp/com.android.apksig.kms.KmsSignerEngineProvider
new file mode 100644
index 0000000..5cda539
--- /dev/null
+++ b/src/providers/gcp/com.android.apksig.kms.KmsSignerEngineProvider
@@ -0,0 +1 @@
+com.android.apksig.kms.gcp.GcpSignerEngineProvider
diff --git a/src/stub/java/com/android/apksig/kms/KmsSignerEngine.java b/src/stub/java/com/android/apksig/kms/KmsSignerEngine.java
deleted file mode 100644
index 5cb11a3..0000000
--- a/src/stub/java/com/android/apksig/kms/KmsSignerEngine.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2024 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.apksig.kms;
-
-import com.android.apksig.KeyConfig;
-import com.android.apksig.SignerEngine;
-
-/** Stub KMS engine for builds that don't care about a KMS. */
-public abstract class KmsSignerEngine implements SignerEngine {
- public final KmsType kmsType;
- public final String keyAlias;
-
- /** Subclasses must specify the type of KMS and a signing key alias. */
- public KmsSignerEngine(KmsType kmsType, String keyAlias) {
- this.kmsType = kmsType;
- this.keyAlias = keyAlias;
- }
-
- @Override
- public abstract byte[] sign(byte[] data);
-
- /**
- * Always throws an exception. This class is only included in builds that don't use the KMS
- * feature.
- */
- public static KmsSignerEngine fromKmsConfig(
- KeyConfig.Kms kmsConfig, String jcaSignatureAlgorithm) {
- throw new KmsException(
- kmsConfig.kmsType,
- "This code path should never be executed if you are using a KMS. Are you using the"
- + " right dependency (apksig-kms)?");
- }
-}
diff --git a/src/test/java/com/android/apksig/ApkSignerTest.java b/src/test/java/com/android/apksig/ApkSignerTest.java
index 0a9d659..a2ecf8d 100644
--- a/src/test/java/com/android/apksig/ApkSignerTest.java
+++ b/src/test/java/com/android/apksig/ApkSignerTest.java
@@ -25,6 +25,7 @@
import static com.android.apksig.apk.ApkUtils.findZipSections;
import static com.android.apksig.internal.util.Resources.EC_P256_2_SIGNER_RESOURCE_NAME;
import static com.android.apksig.internal.util.Resources.EC_P256_SIGNER_RESOURCE_NAME;
+import static com.android.apksig.internal.util.Resources.FIRST_AND_SECOND_RSA_2048_SIGNER_RESOURCE_NAME;
import static com.android.apksig.internal.util.Resources.FIRST_RSA_2048_SIGNER_CERT_WITH_NEGATIVE_MODULUS;
import static com.android.apksig.internal.util.Resources.FIRST_RSA_2048_SIGNER_RESOURCE_NAME;
import static com.android.apksig.internal.util.Resources.FIRST_RSA_4096_SIGNER_RESOURCE_NAME;
@@ -35,7 +36,9 @@
import static com.android.apksig.internal.util.Resources.LINEAGE_RSA_2048_3_SIGNERS_RESOURCE_NAME;
import static com.android.apksig.internal.util.Resources.LINEAGE_RSA_2048_TO_RSA_4096_RESOURCE_NAME;
import static com.android.apksig.internal.util.Resources.SECOND_RSA_2048_SIGNER_RESOURCE_NAME;
+// BEGIN-AOSP
import static com.android.apksig.internal.util.Resources.TEST_GCP_KEY_RING;
+// END-AOSP
import static com.android.apksig.internal.util.Resources.THIRD_RSA_2048_SIGNER_RESOURCE_NAME;
import static org.junit.Assert.assertArrayEquals;
@@ -66,10 +69,12 @@
import com.android.apksig.internal.x509.SubjectPublicKeyInfo;
import com.android.apksig.internal.zip.CentralDirectoryRecord;
import com.android.apksig.internal.zip.LocalFileRecord;
+// BEGIN-AOSP
import com.android.apksig.kms.aws.AwsSignerConfigGenerator;
import com.android.apksig.kms.aws.KeyAliasClient;
import com.android.apksig.kms.gcp.GcpSignerConfigGenerator;
import com.android.apksig.kms.gcp.KeyRingClient;
+// END-AOSP
import com.android.apksig.util.DataSource;
import com.android.apksig.util.DataSources;
import com.android.apksig.zip.ZipFormatException;
@@ -732,6 +737,7 @@
.setAlignmentPreserved(true));
}
+ // BEGIN-AOSP
@Test
public void testAws_Golden() throws Exception {
try (KeyAliasClient client = new KeyAliasClient()) {
@@ -881,7 +887,9 @@
.setSigningCertificateLineage(lineage)
.setAlignmentPreserved(true));
}
+ // END-AOSP
+ // BEGIN-AOSP
@Test
public void testGcp_Golden() throws Exception {
try (KeyRingClient keyRingClient = new KeyRingClient(TEST_GCP_KEY_RING)) {
@@ -1030,6 +1038,7 @@
.setSigningCertificateLineage(lineage)
.setAlignmentPreserved(true));
}
+ // END-AOSP
@Test
public void testMinSdkVersion_Golden() throws Exception {
@@ -3454,6 +3463,34 @@
}
@Test
+ public void testV4_certificateChainInSignerConfig_v4UsesCurrentSigner() throws Exception {
+ // The APK SignerConfig supports a certificate chain as input; this chain represents the
+ // current signing certificate, the previous issuer of this certificate, and any previous
+ // issuers back to the root. As long as the current signer for the SignerConfig is
+ // specified as the first certificate, all of the certificates in the chain should be
+ // stored in the length-prefixed sequence of X.509 certificates. To remain consistent
+ // with SigningConfigs for previous signature schemes, the V4 signature scheme should also
+ // accept SigningConfigs with a certificate chain; while the entire chain will not be
+ // stored in the V4 signature, this will allow SignerConfig instances with certificate
+ // chains to be used across all signature schemes. For more details about the certificate
+ // chain in the V3 signature block, see
+ // https://source.android.com/docs/security/features/apksigning/v3#format
+ List<ApkSigner.SignerConfig> rsa2048SignerConfig = Arrays.asList(
+ getDefaultSignerConfigFromResources(
+ FIRST_AND_SECOND_RSA_2048_SIGNER_RESOURCE_NAME));
+
+ File signedApk = sign("original.apk",
+ new ApkSigner.Builder(rsa2048SignerConfig)
+ .setV1SigningEnabled(true)
+ .setV2SigningEnabled(true)
+ .setV3SigningEnabled(true)
+ .setV4SigningEnabled(true));
+ ApkVerifier.Result result = verify(signedApk, null);
+
+ assertResultContainsV4Signers(result, SECOND_RSA_2048_SIGNER_RESOURCE_NAME);
+ }
+
+ @Test
public void
testSourceStampTimestamp_signWithSourceStampAndTimestampDefault_validTimestampValue()
throws Exception {
diff --git a/src/test/java/com/android/apksig/SigningCertificateLineageTest.java b/src/test/java/com/android/apksig/SigningCertificateLineageTest.java
index 4ca8959..a8b3d0b 100644
--- a/src/test/java/com/android/apksig/SigningCertificateLineageTest.java
+++ b/src/test/java/com/android/apksig/SigningCertificateLineageTest.java
@@ -20,7 +20,9 @@
import static com.android.apksig.internal.util.Resources.FIRST_RSA_2048_SIGNER_RESOURCE_NAME;
import static com.android.apksig.internal.util.Resources.SECOND_RSA_1024_SIGNER_RESOURCE_NAME;
import static com.android.apksig.internal.util.Resources.SECOND_RSA_2048_SIGNER_RESOURCE_NAME;
+// BEGIN-AOSP
import static com.android.apksig.internal.util.Resources.TEST_GCP_KEY_RING;
+// END-AOSP
import static com.android.apksig.internal.util.Resources.THIRD_RSA_2048_SIGNER_RESOURCE_NAME;
import static org.junit.Assert.assertEquals;
@@ -38,10 +40,12 @@
import com.android.apksig.internal.apk.v3.V3SchemeSigner;
import com.android.apksig.internal.util.ByteBufferUtils;
import com.android.apksig.internal.util.Resources;
+// BEGIN-AOSP
import com.android.apksig.kms.aws.AwsSignerConfigGenerator;
import com.android.apksig.kms.aws.KeyAliasClient;
import com.android.apksig.kms.gcp.GcpSignerConfigGenerator;
import com.android.apksig.kms.gcp.KeyRingClient;
+// END-AOSP
import com.android.apksig.util.DataSource;
import org.junit.Before;
@@ -280,6 +284,7 @@
assertExpectedCapabilityValues(newSignerCapabilities, newSignerCapabilityValues);
}
+ // BEGIN-AOSP
@Test
public void
testRotationWithExitingLineageAndNonDefaultCapabilitiesForNewSigner_previousSignerAws()
@@ -305,7 +310,9 @@
SignerCapabilities newSignerCapabilities = lineage.getSignerCapabilities(newSigner);
assertExpectedCapabilityValues(newSignerCapabilities, newSignerCapabilityValues);
}
+ // END-AOSP
+ // BEGIN-AOSP
@Test
public void
testRotationWithExitingLineageAndNonDefaultCapabilitiesForNewSigner_originalSignerAws()
@@ -331,7 +338,9 @@
SignerCapabilities newSignerCapabilities = lineage.getSignerCapabilities(newSigner);
assertExpectedCapabilityValues(newSignerCapabilities, newSignerCapabilityValues);
}
+ // END-AOSP
+ // BEGIN-AOSP
@Test
public void
testRotationWithExitingLineageAndNonDefaultCapabilitiesForNewSigner_previousSignerGcp()
@@ -357,7 +366,9 @@
SignerCapabilities newSignerCapabilities = lineage.getSignerCapabilities(newSigner);
assertExpectedCapabilityValues(newSignerCapabilities, newSignerCapabilityValues);
}
+ // END-AOSP
+ // BEGIN-AOSP
@Test
public void
testRotationWithExitingLineageAndNonDefaultCapabilitiesForNewSigner_originalSignerGcp()
@@ -383,6 +394,7 @@
SignerCapabilities newSignerCapabilities = lineage.getSignerCapabilities(newSigner);
assertExpectedCapabilityValues(newSignerCapabilities, newSignerCapabilityValues);
}
+ // END-AOSP
@Test
public void testRotationWithExitingLineageAndNonDefaultCapabilitiesForNewSigner()
diff --git a/src/test/java/com/android/apksig/internal/util/Resources.java b/src/test/java/com/android/apksig/internal/util/Resources.java
index 75677cf..5120544 100644
--- a/src/test/java/com/android/apksig/internal/util/Resources.java
+++ b/src/test/java/com/android/apksig/internal/util/Resources.java
@@ -21,7 +21,9 @@
import com.android.apksig.SigningCertificateLineage;
import com.android.apksig.util.DataSource;
+// BEGIN-AOSP
import com.google.cloud.kms.v1.KeyRingName;
+// END-AOSP
import org.junit.rules.TemporaryFolder;
@@ -58,13 +60,20 @@
public static final String FIRST_RSA_1024_SIGNER_RESOURCE_NAME = "rsa-1024";
public static final String SECOND_RSA_1024_SIGNER_RESOURCE_NAME = "rsa-1024_2";
+ // This resource uses a PEM certificate file containing the certificate chain with both the
+ // first and second RSA-2048 signers. This resource should be used for any tests that require
+ // a certificate chain in the SignerConfig.
+ public static final String FIRST_AND_SECOND_RSA_2048_SIGNER_RESOURCE_NAME = "rsa-2048-2-1";
+
public static final String FIRST_RSA_4096_SIGNER_RESOURCE_NAME = "rsa-4096";
public static final String EC_P256_SIGNER_RESOURCE_NAME = "ec-p256";
public static final String EC_P256_2_SIGNER_RESOURCE_NAME = "ec-p256_2";
+ // BEGIN-AOSP
public static final KeyRingName TEST_GCP_KEY_RING =
KeyRingName.of("apksigner-cloud-kms", "us-central1", "testV3");
+ // END-AOSP
// This is the same cert as above with the modulus reencoded to remove the leading 0 sign bit.
public static final String FIRST_RSA_2048_SIGNER_CERT_WITH_NEGATIVE_MODULUS =
diff --git a/src/test/resources/com/android/apksig/rsa-2048-2-1.pk8 b/src/test/resources/com/android/apksig/rsa-2048-2-1.pk8
new file mode 100644
index 0000000..5a572ff
--- /dev/null
+++ b/src/test/resources/com/android/apksig/rsa-2048-2-1.pk8
Binary files differ
diff --git a/src/test/resources/com/android/apksig/rsa-2048-2-1.x509.pem b/src/test/resources/com/android/apksig/rsa-2048-2-1.x509.pem
new file mode 100644
index 0000000..b14fae4
--- /dev/null
+++ b/src/test/resources/com/android/apksig/rsa-2048-2-1.x509.pem
@@ -0,0 +1,37 @@
+-----BEGIN CERTIFICATE-----
+MIIC+zCCAeOgAwIBAgIJANh7AUYJKp9PMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNV
+BAMMCHJzYS0yMDQ4MB4XDTE4MDYxOTAwMDUwMFoXDTI4MDYxNjAwMDUwMFowFTET
+MBEGA1UEAwwKcnNhLTIwNDhfMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBALgwhcEJHldw5BD4tZP3aeeYyi8FCVx68WvuRcpJ1IwCbXd03giTyc3Y8Olr
+7D67Y5aLdCW+XE8Z9pNHd43dXe9aRN6kcbhVynzVfe5PKCeYt3dVYgxh8eQqO6A5
+f6DpJjF1jkqRmR6BipsDw5t8PwiiJ03jnoaepdGvnQpwxHEf7izWte+XHBPbJH6A
+vqXCUVlHw+CpI4J2NhZqfSa60F4y5heKF4mF4+97JPODopVeFXvS1VctYEY3ycsB
+uumZy3Q9Lp2E07KP7SP7oKAegg0uReqeqVcofBQESP4iMefw86QhqkTysfG3q3Sf
+RmmbTLnovAxSjjQZ5oZzGJuBQXECAwEAAaNQME4wHQYDVR0OBBYEFD/NVrkySRE2
+6bctYSVM+os/31yRMB8GA1UdIwQYMBaAFBcCLXMQfzibORLraiGzAYZZLB1vMAwG
+A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBACIYKbWz0byV8hCwVhK3UGrI
+PV6+DWAbRrx74y0cqhlPIyroOdSSEsmGythQMMVdvkvHGLqdA9gwQqYkNkWFQQqp
+Cfj9+YJdEKTKJtMO/ZTATJyN0ACEXr5YQo9ivAASY2pfiTfQXbPGsEhycuG3gJ1Y
+rTC3imERhkHomf8ZvTJEwSOF0bQYNg/Ar2nNYNdf3oCrrylLIxUaq0pp/5CrvZT4
+SMKLBfimirfxGS31Z9TEuvDma1Rn2xwfDOhmiSvC30PK/xPsN1xvallHtJDmTdNV
+M4E7Z5hDyCOQ3y1o3VHr73dZnA0M0WdM7JIH9Vcs+R0otFdyXrfeMw4YvMZ0/Qc=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC+TCCAeGgAwIBAgIJAI41MGzdARX3MA0GCSqGSIb3DQEBCwUAMBMxETAPBgNV
+BAMMCHJzYS0yMDQ4MB4XDTE2MDMzMTE0NTc0OVoXDTQzMDgxNzE0NTc0OVowEzER
+MA8GA1UEAwwIcnNhLTIwNDgwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQDQ4JI1EJ239V4wss0jpVlZMudh2/kARCVdoBgsRQuvc2RNnO23Eyynlt9UN+Dc
+NRdQIhbCpVTjdEl/bePECHlqg9NE3frAj5GebiUdWL6A/idKsZA1nAKyIgxxjcnu
++38OcrlO6XOm36euxGfd/ULrghZGXzMVFq4uLiIv3DqFkUcIlE0BvUiUoNwpopV4
+MKj1GQgoaEObJG5xkMBKO6vg36VfJ3s3V3r48uJxYGhhBZEB0EpoXLd4i0piAB8S
+MLb0Ek6wA/HZ8A2rdnStk1wl/83OM1jO0uB3hyfJpqIijlvNGnrloYyyOIqS0LGH
+nxSJD7goASH2Ef0h4yxbsOvHAgMBAAGjUDBOMB0GA1UdDgQWBBQXAi1zEH84mzkS
+62ohswGGWSwdbzAfBgNVHSMEGDAWgBQXAi1zEH84mzkS62ohswGGWSwdbzAMBgNV
+HRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAB92T5toLkF6dLl65/boH5Qvub
+5wfIk0AD12T3t3kYWQFOH0YDCHNL3SfmrjYM/CwNJAd1KuCL5AZcn0km/n0SFXt5
+8Ps/MBcb0eK1fYezeEehKUyt5IBgDTKeQOel6So8rGuQRrDf/WV8rt6fugkIODFx
+sB3oj4ESaGXbvmvWD6q4a3koq/nV26kALchnAr7/FTNq3HEIQ1BDr9pldVh1gEV/
+ohHKcQP4M22Es7lredzpIcb5K6Ko/UtwsSRtHnoOjwmb+L/FsgAJsekmcJG5TK1X
+ciIsrrNFDCYzf/d9O1PD/V95kB7460qMzrGWZpc3mLe+OnmVMq6c4omOtIKl
+-----END CERTIFICATE-----
+