| /* |
| * Copyright (C) 2018 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.internal.apk.v3; |
| |
| import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsLengthPrefixedElement; |
| import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsSequenceOfLengthPrefixedElements; |
| import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes; |
| import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeCertificates; |
| import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodePublicKey; |
| |
| import com.android.apksig.SigningCertificateLineage; |
| import com.android.apksig.internal.apk.ApkSigningBlockUtils; |
| import com.android.apksig.internal.apk.ApkSigningBlockUtils.SignerConfig; |
| import com.android.apksig.internal.apk.ContentDigestAlgorithm; |
| import com.android.apksig.internal.apk.SignatureAlgorithm; |
| import com.android.apksig.internal.util.Pair; |
| import com.android.apksig.util.DataSource; |
| import com.android.apksig.util.RunnablesExecutor; |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| import java.security.InvalidKeyException; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.PublicKey; |
| import java.security.SignatureException; |
| import java.security.cert.CertificateEncodingException; |
| import java.security.interfaces.ECKey; |
| import java.security.interfaces.RSAKey; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * APK Signature Scheme v3 signer. |
| * |
| * <p>APK Signature Scheme v3 builds upon APK Signature Scheme v3, and maintains all of the APK |
| * Signature Scheme v2 goals. |
| * |
| * @see <a href="https://source.android.com/security/apksigning/v2.html">APK Signature Scheme v2</a> |
| * <p>The main contribution of APK Signature Scheme v3 is the introduction of the {@link |
| * SigningCertificateLineage}, which enables an APK to change its signing certificate as long as |
| * it can prove the new siging certificate was signed by the old. |
| */ |
| public abstract class V3SchemeSigner { |
| |
| public static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0; |
| |
| /** Hidden constructor to prevent instantiation. */ |
| private V3SchemeSigner() {} |
| |
| /** |
| * Gets the APK Signature Scheme v3 signature algorithms to be used for signing an APK using the |
| * provided key. |
| * |
| * @param minSdkVersion minimum API Level of the platform on which the APK may be installed (see |
| * AndroidManifest.xml minSdkVersion attribute). |
| * @throws InvalidKeyException if the provided key is not suitable for signing APKs using APK |
| * Signature Scheme v3 |
| */ |
| public static List<SignatureAlgorithm> getSuggestedSignatureAlgorithms(PublicKey signingKey, |
| int minSdkVersion, boolean verityEnabled) throws InvalidKeyException { |
| String keyAlgorithm = signingKey.getAlgorithm(); |
| if ("RSA".equalsIgnoreCase(keyAlgorithm)) { |
| // Use RSASSA-PKCS1-v1_5 signature scheme instead of RSASSA-PSS to guarantee |
| // deterministic signatures which make life easier for OTA updates (fewer files |
| // changed when deterministic signature schemes are used). |
| |
| // Pick a digest which is no weaker than the key. |
| int modulusLengthBits = ((RSAKey) signingKey).getModulus().bitLength(); |
| if (modulusLengthBits <= 3072) { |
| // 3072-bit RSA is roughly 128-bit strong, meaning SHA-256 is a good fit. |
| List<SignatureAlgorithm> algorithms = new ArrayList<>(); |
| algorithms.add(SignatureAlgorithm.RSA_PKCS1_V1_5_WITH_SHA256); |
| if (verityEnabled) { |
| algorithms.add(SignatureAlgorithm.VERITY_RSA_PKCS1_V1_5_WITH_SHA256); |
| } |
| return algorithms; |
| } else { |
| // Keys longer than 3072 bit need to be paired with a stronger digest to avoid the |
| // digest being the weak link. SHA-512 is the next strongest supported digest. |
| return Collections.singletonList(SignatureAlgorithm.RSA_PKCS1_V1_5_WITH_SHA512); |
| } |
| } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) { |
| // DSA is supported only with SHA-256. |
| List<SignatureAlgorithm> algorithms = new ArrayList<>(); |
| algorithms.add(SignatureAlgorithm.DSA_WITH_SHA256); |
| if (verityEnabled) { |
| algorithms.add(SignatureAlgorithm.VERITY_DSA_WITH_SHA256); |
| } |
| return algorithms; |
| } else if ("EC".equalsIgnoreCase(keyAlgorithm)) { |
| // Pick a digest which is no weaker than the key. |
| int keySizeBits = ((ECKey) signingKey).getParams().getOrder().bitLength(); |
| if (keySizeBits <= 256) { |
| // 256-bit Elliptic Curve is roughly 128-bit strong, meaning SHA-256 is a good fit. |
| List<SignatureAlgorithm> algorithms = new ArrayList<>(); |
| algorithms.add(SignatureAlgorithm.ECDSA_WITH_SHA256); |
| if (verityEnabled) { |
| algorithms.add(SignatureAlgorithm.VERITY_ECDSA_WITH_SHA256); |
| } |
| return algorithms; |
| } else { |
| // Keys longer than 256 bit need to be paired with a stronger digest to avoid the |
| // digest being the weak link. SHA-512 is the next strongest supported digest. |
| return Collections.singletonList(SignatureAlgorithm.ECDSA_WITH_SHA512); |
| } |
| } else { |
| throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm); |
| } |
| } |
| |
| public static ApkSigningBlockUtils.SigningSchemeBlockAndDigests |
| generateApkSignatureSchemeV3Block( |
| RunnablesExecutor executor, |
| DataSource beforeCentralDir, |
| DataSource centralDir, |
| DataSource eocd, |
| List<SignerConfig> signerConfigs) |
| throws IOException, InvalidKeyException, NoSuchAlgorithmException, |
| SignatureException { |
| Pair<List<SignerConfig>, Map<ContentDigestAlgorithm, byte[]>> digestInfo = |
| ApkSigningBlockUtils.computeContentDigests( |
| executor, beforeCentralDir, centralDir, eocd, signerConfigs); |
| return new ApkSigningBlockUtils.SigningSchemeBlockAndDigests( |
| generateApkSignatureSchemeV3Block(digestInfo.getFirst(), digestInfo.getSecond()), |
| digestInfo.getSecond()); |
| } |
| |
| private static Pair<byte[], Integer> generateApkSignatureSchemeV3Block( |
| List<SignerConfig> signerConfigs, Map<ContentDigestAlgorithm, byte[]> contentDigests) |
| throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { |
| // FORMAT: |
| // * length-prefixed sequence of length-prefixed signer blocks. |
| List<byte[]> signerBlocks = new ArrayList<>(signerConfigs.size()); |
| int signerNumber = 0; |
| for (SignerConfig signerConfig : signerConfigs) { |
| signerNumber++; |
| byte[] signerBlock; |
| try { |
| signerBlock = generateSignerBlock(signerConfig, contentDigests); |
| } catch (InvalidKeyException e) { |
| throw new InvalidKeyException("Signer #" + signerNumber + " failed", e); |
| } catch (SignatureException e) { |
| throw new SignatureException("Signer #" + signerNumber + " failed", e); |
| } |
| signerBlocks.add(signerBlock); |
| } |
| |
| return Pair.of( |
| encodeAsSequenceOfLengthPrefixedElements( |
| new byte[][] { |
| encodeAsSequenceOfLengthPrefixedElements(signerBlocks), |
| }), |
| APK_SIGNATURE_SCHEME_V3_BLOCK_ID); |
| } |
| |
| private static byte[] generateSignerBlock( |
| SignerConfig signerConfig, Map<ContentDigestAlgorithm, byte[]> contentDigests) |
| throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { |
| if (signerConfig.certificates.isEmpty()) { |
| throw new SignatureException("No certificates configured for signer"); |
| } |
| PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey(); |
| |
| byte[] encodedPublicKey = encodePublicKey(publicKey); |
| |
| V3SignatureSchemeBlock.SignedData signedData = new V3SignatureSchemeBlock.SignedData(); |
| try { |
| signedData.certificates = encodeCertificates(signerConfig.certificates); |
| } catch (CertificateEncodingException e) { |
| throw new SignatureException("Failed to encode certificates", e); |
| } |
| |
| List<Pair<Integer, byte[]>> digests = |
| new ArrayList<>(signerConfig.signatureAlgorithms.size()); |
| for (SignatureAlgorithm signatureAlgorithm : signerConfig.signatureAlgorithms) { |
| ContentDigestAlgorithm contentDigestAlgorithm = |
| signatureAlgorithm.getContentDigestAlgorithm(); |
| byte[] contentDigest = contentDigests.get(contentDigestAlgorithm); |
| if (contentDigest == null) { |
| throw new RuntimeException( |
| contentDigestAlgorithm |
| + " content digest for " |
| + signatureAlgorithm |
| + " not computed"); |
| } |
| digests.add(Pair.of(signatureAlgorithm.getId(), contentDigest)); |
| } |
| signedData.digests = digests; |
| signedData.minSdkVersion = signerConfig.minSdkVersion; |
| signedData.maxSdkVersion = signerConfig.maxSdkVersion; |
| signedData.additionalAttributes = generateAdditionalAttributes(signerConfig); |
| |
| V3SignatureSchemeBlock.Signer signer = new V3SignatureSchemeBlock.Signer(); |
| |
| signer.signedData = encodeSignedData(signedData); |
| |
| signer.minSdkVersion = signerConfig.minSdkVersion; |
| signer.maxSdkVersion = signerConfig.maxSdkVersion; |
| signer.publicKey = encodedPublicKey; |
| signer.signatures = |
| ApkSigningBlockUtils.generateSignaturesOverData(signerConfig, signer.signedData); |
| |
| return encodeSigner(signer); |
| } |
| |
| private static byte[] encodeSigner(V3SignatureSchemeBlock.Signer signer) { |
| byte[] signedData = encodeAsLengthPrefixedElement(signer.signedData); |
| byte[] signatures = |
| encodeAsLengthPrefixedElement( |
| encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( |
| signer.signatures)); |
| byte[] publicKey = encodeAsLengthPrefixedElement(signer.publicKey); |
| |
| // FORMAT: |
| // * length-prefixed signed data |
| // * uint32: minSdkVersion |
| // * uint32: maxSdkVersion |
| // * length-prefixed sequence of length-prefixed signatures: |
| // * uint32: signature algorithm ID |
| // * length-prefixed bytes: signature of signed data |
| // * length-prefixed bytes: public key (X.509 SubjectPublicKeyInfo, ASN.1 DER encoded) |
| int payloadSize = signedData.length + 4 + 4 + signatures.length + publicKey.length; |
| |
| ByteBuffer result = ByteBuffer.allocate(payloadSize); |
| result.order(ByteOrder.LITTLE_ENDIAN); |
| result.put(signedData); |
| result.putInt(signer.minSdkVersion); |
| result.putInt(signer.maxSdkVersion); |
| result.put(signatures); |
| result.put(publicKey); |
| |
| return result.array(); |
| } |
| |
| private static byte[] encodeSignedData(V3SignatureSchemeBlock.SignedData signedData) { |
| byte[] digests = |
| encodeAsLengthPrefixedElement( |
| encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( |
| signedData.digests)); |
| byte[] certs = |
| encodeAsLengthPrefixedElement( |
| encodeAsSequenceOfLengthPrefixedElements(signedData.certificates)); |
| byte[] attributes = encodeAsLengthPrefixedElement(signedData.additionalAttributes); |
| |
| // FORMAT: |
| // * length-prefixed sequence of length-prefixed digests: |
| // * uint32: signature algorithm ID |
| // * length-prefixed bytes: digest of contents |
| // * length-prefixed sequence of certificates: |
| // * length-prefixed bytes: X.509 certificate (ASN.1 DER encoded). |
| // * uint-32: minSdkVersion |
| // * uint-32: maxSdkVersion |
| // * length-prefixed sequence of length-prefixed additional attributes: |
| // * uint32: ID |
| // * (length - 4) bytes: value |
| // * uint32: Proof-of-rotation ID: 0x3ba06f8c |
| // * length-prefixed roof-of-rotation structure |
| int payloadSize = digests.length + certs.length + 4 + 4 + attributes.length; |
| |
| ByteBuffer result = ByteBuffer.allocate(payloadSize); |
| result.order(ByteOrder.LITTLE_ENDIAN); |
| result.put(digests); |
| result.put(certs); |
| result.putInt(signedData.minSdkVersion); |
| result.putInt(signedData.maxSdkVersion); |
| result.put(attributes); |
| |
| return result.array(); |
| } |
| |
| public static final int PROOF_OF_ROTATION_ATTR_ID = 0x3ba06f8c; |
| |
| private static byte[] generateAdditionalAttributes(SignerConfig signerConfig) { |
| if (signerConfig.mSigningCertificateLineage == null) { |
| return new byte[0]; |
| } |
| return signerConfig.mSigningCertificateLineage.generateV3SignerAttribute(); |
| } |
| |
| private static final class V3SignatureSchemeBlock { |
| private static final class Signer { |
| public byte[] signedData; |
| public int minSdkVersion; |
| public int maxSdkVersion; |
| public List<Pair<Integer, byte[]>> signatures; |
| public byte[] publicKey; |
| } |
| |
| private static final class SignedData { |
| public List<Pair<Integer, byte[]>> digests; |
| public List<byte[]> certificates; |
| public int minSdkVersion; |
| public int maxSdkVersion; |
| public byte[] additionalAttributes; |
| } |
| } |
| } |