/** * Returns positive number if {@code alg1} is preferred over {@code alg2}, {@code -1} if * {@code alg2} is preferred over {@code alg1}, and {@code 0} if there is no preference. */ private static int compareSignatureAlgorithm(SignatureAlgorithm alg1, SignatureAlgorithm alg2) { ContentDigestAlgorithm digestAlg1 = alg1.getContentDigestAlgorithm(); ContentDigestAlgorithm digestAlg2 = alg2.getContentDigestAlgorithm(); return compareContentDigestAlgorithm(digestAlg1, digestAlg2); }
private V2SchemeSignerInfo(V2SchemeVerifier.Result.SignerInfo result) { mIndex = result.index; mCerts = result.certs; mErrors = result.getErrors(); mWarnings = result.getWarnings(); }
Set<ContentDigestAlgorithm> contentDigestsToVerify) throws ApkFormatException, NoSuchAlgorithmException { ByteBuffer signedData = getLengthPrefixedSlice(signerBlock); byte[] signedDataBytes = new byte[signedData.remaining()]; signedData.get(signedDataBytes); ByteBuffer signatures = getLengthPrefixedSlice(signerBlock); byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock); signatureCount++; try { ByteBuffer signature = getLengthPrefixedSlice(signatures); int sigAlgorithmId = signature.getInt(); byte[] sigBytes = readLengthPrefixedByteArray(signature); result.signatures.add( new Result.SignerInfo.Signature(sigAlgorithmId, sigBytes)); SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.findById(sigAlgorithmId); if (signatureAlgorithm == null) { result.addWarning(Issue.V2_SIG_UNKNOWN_SIG_ALGORITHM, sigAlgorithmId); continue; supportedSignatures.add(new SupportedSignature(signatureAlgorithm, sigBytes)); } catch (ApkFormatException | BufferUnderflowException e) { result.addError(Issue.V2_SIG_MALFORMED_SIGNATURE, signatureCount); return; result.addError(Issue.V2_SIG_NO_SIGNATURES); return;
byte[] encodedPublicKey = encodePublicKey(publicKey); V2SignatureSchemeBlock.SignedData signedData = new V2SignatureSchemeBlock.SignedData(); try { signedData.certificates = encodeCertificates(signerConfig.certificates); } catch (CertificateEncodingException e) { throw new SignatureException("Failed to encode certificates", e); for (SignatureAlgorithm signatureAlgorithm : signerConfig.signatureAlgorithms) { ContentDigestAlgorithm contentDigestAlgorithm = signatureAlgorithm.getContentDigestAlgorithm(); byte[] contentDigest = contentDigests.get(contentDigestAlgorithm); if (contentDigest == null) { digests.add(Pair.of(signatureAlgorithm.getId(), contentDigest)); V2SignatureSchemeBlock.Signer signer = new V2SignatureSchemeBlock.Signer(); signer.signedData = encodeAsSequenceOfLengthPrefixedElements(new byte[][] { encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(signedData.digests), encodeAsSequenceOfLengthPrefixedElements(signedData.certificates), for (SignatureAlgorithm signatureAlgorithm : signerConfig.signatureAlgorithms) { Pair<String, ? extends AlgorithmParameterSpec> sigAlgAndParams = signatureAlgorithm.getJcaSignatureAlgorithmAndParams(); String jcaSignatureAlgorithm = sigAlgAndParams.getFirst(); AlgorithmParameterSpec jcaSignatureAlgorithmParams = sigAlgAndParams.getSecond(); signer.signatures.add(Pair.of(signatureAlgorithm.getId(), signatureBytes));
try { actualContentDigests = V2SchemeSigner.computeContentDigests( contentDigestAlgorithms, new DataSource[] { for (Result.SignerInfo.ContentDigest expected : signerInfo.contentDigests) { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.findById(expected.getSignatureAlgorithmId()); if (signatureAlgorithm == null) { continue; signatureAlgorithm.getContentDigestAlgorithm(); byte[] expectedDigest = expected.getValue(); byte[] actualDigest = actualContentDigests.get(contentDigestAlgorithm); if (!Arrays.equals(expectedDigest, actualDigest)) { signerInfo.addError( Issue.V2_SIG_APK_DIGEST_DID_NOT_VERIFY, contentDigestAlgorithm, toHex(expectedDigest), toHex(actualDigest)); continue;
private static ByteBuffer findApkSignatureSchemeV2Block( ByteBuffer apkSigningBlock, Result result) throws SignatureNotFoundException { checkByteOrderLittleEndian(apkSigningBlock); ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24); entryCount++; if (pairs.remaining() < 8) { throw new SignatureNotFoundException( "Insufficient data to read size of APK Signing Block entry #" + entryCount); throw new SignatureNotFoundException( "APK Signing Block entry #" + entryCount + " size out of range: " + lenLong); int nextEntryPos = pairs.position() + len; if (len > pairs.remaining()) { throw new SignatureNotFoundException( "APK Signing Block entry #" + entryCount + " size out of range: " + len + ", available: " + pairs.remaining()); return getByteBuffer(pairs, len - 4); result.addWarning(Issue.APK_SIG_BLOCK_UNKNOWN_ENTRY_ID, id); pairs.position(nextEntryPos); throw new SignatureNotFoundException( "No APK Signature Scheme v2 block in APK Signing Block");
ByteBuffer signers; try { signers = getLengthPrefixedSlice(apkSignatureSchemeV2Block); } catch (ApkFormatException e) { result.addError(Issue.V2_SIG_MALFORMED_SIGNERS); return; result.addError(Issue.V2_SIG_NO_SIGNERS); return; int signerIndex = signerCount; signerCount++; Result.SignerInfo signerInfo = new Result.SignerInfo(); signerInfo.index = signerIndex; result.signers.add(signerInfo); try { ByteBuffer signer = getLengthPrefixedSlice(signers); parseSigner(signer, certFactory, signerInfo, contentDigestsToVerify); } catch (ApkFormatException | BufferUnderflowException e) { signerInfo.addError(Issue.V2_SIG_MALFORMED_SIGNER); return;
/** * Verifies the provided APK's v2 signatures and outputs the results into the provided * {@code result}. APK is considered verified only if there are no errors reported in the * {@code result}. */ private static void verify( DataSource beforeApkSigningBlock, ByteBuffer apkSignatureSchemeV2Block, DataSource centralDir, ByteBuffer eocd, Result result) throws IOException, NoSuchAlgorithmException { Set<ContentDigestAlgorithm> contentDigestsToVerify = new HashSet<>(1); parseSigners(apkSignatureSchemeV2Block, contentDigestsToVerify, result); if (result.containsErrors()) { return; } verifyIntegrity( beforeApkSigningBlock, centralDir, eocd, contentDigestsToVerify, result); if (!result.containsErrors()) { result.verified = true; } }
/** * Verifies the provided APK's APK Signature Scheme v2 signatures and returns the result of * verification. APK is considered verified only if {@link Result#verified} is {@code true}. If * verification fails, the result will contain errors -- see {@link Result#getErrors()}. * * @throws ApkFormatException if the APK is malformed * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a * required cryptographic algorithm implementation is missing * @throws SignatureNotFoundException if no APK Signature Scheme v2 signatures are found * @throws IOException if an I/O error occurs when reading the APK */ public static Result verify(DataSource apk, ApkUtils.ZipSections zipSections) throws IOException, ApkFormatException, NoSuchAlgorithmException, SignatureNotFoundException { Result result = new Result(); SignatureInfo signatureInfo = findSignature(apk, zipSections, result); DataSource beforeApkSigningBlock = apk.slice(0, signatureInfo.apkSigningBlockOffset); DataSource centralDir = apk.slice( signatureInfo.centralDirOffset, signatureInfo.eocdOffset - signatureInfo.centralDirOffset); ByteBuffer eocd = signatureInfo.eocd; verify(beforeApkSigningBlock, signatureInfo.signatureBlock, centralDir, eocd, result); return result; }
/** * Returns the APK Signature Scheme v2 block contained in the provided APK file and the * additional information relevant for verifying the block against the file. * * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2 * @throws IOException if an I/O error occurs while reading the APK */ private static SignatureInfo findSignature( DataSource apk, ApkUtils.ZipSections zipSections, Result result) throws IOException, SignatureNotFoundException { // Find the APK Signing Block. The block immediately precedes the Central Directory. ByteBuffer eocd = zipSections.getZipEndOfCentralDirectory(); Pair<DataSource, Long> apkSigningBlockAndOffset = findApkSigningBlock(apk, zipSections); DataSource apkSigningBlock = apkSigningBlockAndOffset.getFirst(); long apkSigningBlockOffset = apkSigningBlockAndOffset.getSecond(); ByteBuffer apkSigningBlockBuf = apkSigningBlock.getByteBuffer(0, (int) apkSigningBlock.size()); apkSigningBlockBuf.order(ByteOrder.LITTLE_ENDIAN); // Find the APK Signature Scheme v2 Block inside the APK Signing Block. ByteBuffer apkSignatureSchemeV2Block = findApkSignatureSchemeV2Block(apkSigningBlockBuf, result); return new SignatureInfo( apkSignatureSchemeV2Block, apkSigningBlockOffset, zipSections.getZipCentralDirectoryOffset(), zipSections.getZipEndOfCentralDirectoryOffset(), eocd); }
private static List<SupportedSignature> getSignaturesToVerify( List<SupportedSignature> signatures) { // Pick the signature with the strongest algorithm, to mimic Android's behavior. SignatureAlgorithm bestSigAlgorithm = null; byte[] bestSigAlgorithmSignatureBytes = null; for (SupportedSignature sig : signatures) { SignatureAlgorithm sigAlgorithm = sig.algorithm; if ((bestSigAlgorithm == null) || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) { bestSigAlgorithm = sigAlgorithm; bestSigAlgorithmSignatureBytes = sig.signature; } } if (bestSigAlgorithm == null) { return Collections.emptyList(); } else { return Collections.singletonList( new SupportedSignature(bestSigAlgorithm, bestSigAlgorithmSignatureBytes)); } }
private void mergeFrom(V2SchemeVerifier.Result source) { mVerifiedUsingV2Scheme = source.verified; mErrors.addAll(source.getErrors()); mWarnings.addAll(source.getWarnings()); for (V2SchemeVerifier.Result.SignerInfo signer : source.signers) { mV2SchemeSigners.add(new V2SchemeSignerInfo(signer)); } }
private static byte[] generateApkSigningBlock(byte[] apkSignatureSchemeV2Block) { // FORMAT: // uint64: size (excluding this field) // repeated ID-value pairs: // uint64: size (excluding this field) // uint32: ID // (size - 4) bytes: value // uint64: size (same as the one above) // uint128: magic int resultSize = 8 // size + 8 + 4 + apkSignatureSchemeV2Block.length // v2Block as ID-value pair + 8 // size + 16 // magic ; ByteBuffer result = ByteBuffer.allocate(resultSize); result.order(ByteOrder.LITTLE_ENDIAN); long blockSizeFieldValue = resultSize - 8; result.putLong(blockSizeFieldValue); long pairSizeFieldValue = 4 + apkSignatureSchemeV2Block.length; result.putLong(pairSizeFieldValue); result.putInt(APK_SIGNATURE_SCHEME_V2_BLOCK_ID); result.put(apkSignatureSchemeV2Block); result.putLong(blockSizeFieldValue); result.put(APK_SIGNING_BLOCK_MAGIC); return result.array(); }
private static byte[] generateApkSignatureSchemeV2Block( 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 encodeAsSequenceOfLengthPrefixedElements( new byte[][] { encodeAsSequenceOfLengthPrefixedElements(signerBlocks), }); }
public boolean containsErrors() { if (!mErrors.isEmpty()) { return true; } if (!signers.isEmpty()) { for (SignerInfo signer : signers) { if (signer.containsErrors()) { return true; } } } return false; }
private static byte[] encodeAsSequenceOfLengthPrefixedElements(List<byte[]> sequence) { return encodeAsSequenceOfLengthPrefixedElements( sequence.toArray(new byte[sequence.size()][])); }
private static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws ApkFormatException { if (source.remaining() < 4) { throw new ApkFormatException( "Remaining buffer too short to contain length of length-prefixed field" + ". Remaining: " + source.remaining()); } int len = source.getInt(); if (len < 0) { throw new IllegalArgumentException("Negative length"); } else if (len > source.remaining()) { throw new ApkFormatException( "Length-prefixed field longer than remaining buffer" + ". Field length: " + len + ", remaining: " + source.remaining()); } return getByteBuffer(source, len); }
long eocdStartOffset = zipSections.getZipEndOfCentralDirectoryOffset(); if (centralDirEndOffset != eocdStartOffset) { throw new SignatureNotFoundException( "ZIP Central Directory is not immediately followed by End of Central Directory" + ". CD end: " + centralDirEndOffset throw new SignatureNotFoundException( "APK too small for APK Signing Block. ZIP Central Directory offset: " + centralDirStartOffset); if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO) || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) { throw new SignatureNotFoundException( "No APK Signing Block before ZIP Central Directory"); if ((apkSigBlockSizeInFooter < footer.capacity()) || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) { throw new SignatureNotFoundException( "APK Signing Block size out of range: " + apkSigBlockSizeInFooter); long apkSigBlockOffset = centralDirStartOffset - totalSize; if (apkSigBlockOffset < 0) { throw new SignatureNotFoundException( "APK Signing Block offset out of range: " + apkSigBlockOffset); long apkSigBlockSizeInHeader = apkSigBlock.getLong(0); if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) { throw new SignatureNotFoundException( "APK Signing Block sizes in header and footer do not match: "
@Override public OutputApkSigningBlockRequest outputZipSections( DataSource zipEntries, DataSource zipCentralDirectory, DataSource zipEocd) throws IOException, InvalidKeyException, SignatureException, NoSuchAlgorithmException { checkNotClosed(); checkV1SigningDoneIfEnabled(); if (!mV2SigningEnabled) { return null; } invalidateV2Signature(); byte[] apkSigningBlock = V2SchemeSigner.generateApkSigningBlock( zipEntries, zipCentralDirectory, zipEocd, mV2SignerConfigs); mAddV2SignatureRequest = new OutputApkSigningBlockRequestImpl(apkSigningBlock); return mAddV2SignatureRequest; }