/* * 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.getLengthPrefixedSlice; import static com.android.apksig.internal.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray; import com.android.apksig.apk.ApkFormatException; import com.android.apksig.internal.apk.ApkSigningBlockUtils; import com.android.apksig.internal.apk.SignatureAlgorithm; import com.android.apksig.internal.util.GuaranteedEncodedFormX509Certificate; import com.android.apksig.internal.util.X509CertificateUtils; import java.io.IOException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.security.spec.AlgorithmParameterSpec; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; /** * APK Signer Lineage. * * <p>The signer lineage contains a history of signing certificates with each ancestor attesting to * the validity of its descendant. Each additional descendant represents a new identity that can be * used to sign an APK, and each generation has accompanying attributes which represent how the * APK would like to view the older signing certificates, specifically how they should be trusted in * certain situations. * * <p> Its primary use is to enable APK Signing Certificate Rotation. The Android platform verifies * the APK Signer Lineage, and if the current signing certificate for the APK is in the Signer * Lineage, and the Lineage contains the certificate the platform associates with the APK, it will * allow upgrades to the new certificate. * * @see <a href="https://source.android.com/security/apksigning/index.html">Application Signing</a> */ public class V3SigningCertificateLineage { private final static int FIRST_VERSION = 1; private final static int CURRENT_VERSION = FIRST_VERSION; /** * Deserializes the binary representation of an {@link V3SigningCertificateLineage}. Also * verifies that the structure is well-formed, e.g. that the signature for each node is from its * parent. */ public static List<SigningCertificateNode> readSigningCertificateLineage(ByteBuffer inputBytes) throws IOException { List<SigningCertificateNode> result = new ArrayList<>(); int nodeCount = 0; if (inputBytes == null || !inputBytes.hasRemaining()) { return null; } ApkSigningBlockUtils.checkByteOrderLittleEndian(inputBytes); // FORMAT (little endian): // * uint32: version code // * sequence of length-prefixed (uint32): nodes // * length-prefixed bytes: signed data // * length-prefixed bytes: certificate // * uint32: signature algorithm id // * uint32: flags // * uint32: signature algorithm id (used by to sign next cert in lineage) // * length-prefixed bytes: signature over above signed data X509Certificate lastCert = null; int lastSigAlgorithmId = 0; try { int version = inputBytes.getInt(); if (version != CURRENT_VERSION) { // we only have one version to worry about right now, so just check it throw new IllegalArgumentException("Encoded SigningCertificateLineage has a version" + " different than any of which we are aware"); } HashSet<X509Certificate> certHistorySet = new HashSet<>(); while (inputBytes.hasRemaining()) { nodeCount++; ByteBuffer nodeBytes = getLengthPrefixedSlice(inputBytes); ByteBuffer signedData = getLengthPrefixedSlice(nodeBytes); int flags = nodeBytes.getInt(); int sigAlgorithmId = nodeBytes.getInt(); SignatureAlgorithm sigAlgorithm = SignatureAlgorithm.findById(lastSigAlgorithmId); byte[] signature = readLengthPrefixedByteArray(nodeBytes); if (lastCert != null) { // Use previous level cert to verify current level String jcaSignatureAlgorithm = sigAlgorithm.getJcaSignatureAlgorithmAndParams().getFirst(); AlgorithmParameterSpec jcaSignatureAlgorithmParams = sigAlgorithm.getJcaSignatureAlgorithmAndParams().getSecond(); PublicKey publicKey = lastCert.getPublicKey(); Signature sig = Signature.getInstance(jcaSignatureAlgorithm); sig.initVerify(publicKey); if (jcaSignatureAlgorithmParams != null) { sig.setParameter(jcaSignatureAlgorithmParams); } sig.update(signedData); if (!sig.verify(signature)) { throw new SecurityException("Unable to verify signature of certificate #" + nodeCount + " using " + jcaSignatureAlgorithm + " when verifying" + " V3SigningCertificateLineage object"); } } signedData.rewind(); byte[] encodedCert = readLengthPrefixedByteArray(signedData); int signedSigAlgorithm = signedData.getInt(); if (lastCert != null && lastSigAlgorithmId != signedSigAlgorithm) { throw new SecurityException("Signing algorithm ID mismatch for certificate #" + nodeBytes + " when verifying V3SigningCertificateLineage object"); } lastCert = X509CertificateUtils.generateCertificate(encodedCert); lastCert = new GuaranteedEncodedFormX509Certificate(lastCert, encodedCert); if (certHistorySet.contains(lastCert)) { throw new SecurityException("Encountered duplicate entries in " + "SigningCertificateLineage at certificate #" + nodeCount + ". All " + "signing certificates should be unique"); } certHistorySet.add(lastCert); lastSigAlgorithmId = sigAlgorithmId; result.add(new SigningCertificateNode( lastCert, SignatureAlgorithm.findById(signedSigAlgorithm), SignatureAlgorithm.findById(sigAlgorithmId), signature, flags)); } } catch(ApkFormatException | BufferUnderflowException e){ throw new IOException("Failed to parse V3SigningCertificateLineage object", e); } catch(NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | SignatureException e){ throw new SecurityException( "Failed to verify signature over signed data for certificate #" + nodeCount + " when parsing V3SigningCertificateLineage object", e); } catch(CertificateException e){ throw new SecurityException("Failed to decode certificate #" + nodeCount + " when parsing V3SigningCertificateLineage object", e); } return result; } /** * encode the in-memory representation of this {@code V3SigningCertificateLineage} */ public static byte[] encodeSigningCertificateLineage( List<SigningCertificateNode> signingCertificateLineage) { // FORMAT (little endian): // * version code // * sequence of length-prefixed (uint32): nodes // * length-prefixed bytes: signed data // * length-prefixed bytes: certificate // * uint32: signature algorithm id // * uint32: flags // * uint32: signature algorithm id (used by to sign next cert in lineage) List<byte[]> nodes = new ArrayList<>(); for (SigningCertificateNode node : signingCertificateLineage) { nodes.add(encodeSigningCertificateNode(node)); } byte [] encodedSigningCertificateLineage = encodeAsSequenceOfLengthPrefixedElements(nodes); // add the version code (uint32) on top of the encoded nodes int payloadSize = 4 + encodedSigningCertificateLineage.length; ByteBuffer encodedWithVersion = ByteBuffer.allocate(payloadSize); encodedWithVersion.order(ByteOrder.LITTLE_ENDIAN); encodedWithVersion.putInt(CURRENT_VERSION); encodedWithVersion.put(encodedSigningCertificateLineage); return encodedWithVersion.array(); } public static byte[] encodeSigningCertificateNode(SigningCertificateNode node) { // FORMAT (little endian): // * length-prefixed bytes: signed data // * length-prefixed bytes: certificate // * uint32: signature algorithm id // * uint32: flags // * uint32: signature algorithm id (used by to sign next cert in lineage) // * length-prefixed bytes: signature over signed data int parentSigAlgorithmId = 0; if (node.parentSigAlgorithm != null) { parentSigAlgorithmId = node.parentSigAlgorithm.getId(); } int sigAlgorithmId = 0; if (node.sigAlgorithm != null) { sigAlgorithmId = node.sigAlgorithm.getId(); } byte[] prefixedSignedData = encodeSignedData(node.signingCert, parentSigAlgorithmId); byte[] prefixedSignature = encodeAsLengthPrefixedElement(node.signature); int payloadSize = prefixedSignedData.length + 4 + 4 + prefixedSignature.length; ByteBuffer result = ByteBuffer.allocate(payloadSize); result.order(ByteOrder.LITTLE_ENDIAN); result.put(prefixedSignedData); result.putInt(node.flags); result.putInt(sigAlgorithmId); result.put(prefixedSignature); return result.array(); } public static byte[] encodeSignedData(X509Certificate certificate, int flags) { try { byte[] prefixedCertificate = encodeAsLengthPrefixedElement(certificate.getEncoded()); int payloadSize = 4 + prefixedCertificate.length; ByteBuffer result = ByteBuffer.allocate(payloadSize); result.order(ByteOrder.LITTLE_ENDIAN); result.put(prefixedCertificate); result.putInt(flags); return encodeAsLengthPrefixedElement(result.array()); } catch (CertificateEncodingException e) { throw new RuntimeException( "Failed to encode V3SigningCertificateLineage certificate", e); } } /** * Represents one signing certificate in the {@link V3SigningCertificateLineage}, which * generally means it is/was used at some point to sign the same APK of the others in the * lineage. */ public static class SigningCertificateNode { public SigningCertificateNode( X509Certificate signingCert, SignatureAlgorithm parentSigAlgorithm, SignatureAlgorithm sigAlgorithm, byte[] signature, int flags) { this.signingCert = signingCert; this.parentSigAlgorithm = parentSigAlgorithm; this.sigAlgorithm = sigAlgorithm; this.signature = signature; this.flags = flags; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof SigningCertificateNode)) return false; SigningCertificateNode that = (SigningCertificateNode) o; if (!signingCert.equals(that.signingCert)) return false; if (parentSigAlgorithm != that.parentSigAlgorithm) return false; if (sigAlgorithm != that.sigAlgorithm) return false; if (!Arrays.equals(signature, that.signature)) return false; if (flags != that.flags) return false; // we made it return true; } /** * the signing cert for this node. This is part of the data signed by the parent node. */ public final X509Certificate signingCert; /** * the algorithm used by the this node's parent to bless this data. Its ID value is part of * the data signed by the parent node. {@code null} for first node. */ public final SignatureAlgorithm parentSigAlgorithm; /** * the algorithm used by the this nodeto bless the next node's data. Its ID value is part * of the signed data of the next node. {@code null} for the last node. */ public SignatureAlgorithm sigAlgorithm; /** * signature over the signed data (above). The signature is from this node's parent * signing certificate, which should correspond to the signing certificate used to sign an * APK before rotating to this one, and is formed using {@code signatureAlgorithm}. */ public final byte[] signature; /** * the flags detailing how the platform should treat this signing cert */ public int flags; } }