public DefaultApkSignerEngine(X509Certificate2 certificate, int minSdkVersion, bool v1SigningEnabled, bool v2SigningEnabled, DigestAlgorithm digestAlgorithm) { _v1SigningEnabled = v1SigningEnabled; _v2SigningEnabled = v2SigningEnabled; _v1SignaturePending = v1SigningEnabled; _v2SignaturePending = v2SigningEnabled; if (v1SigningEnabled) { var v1SignerName = V1SchemeSigner.GetSafeSignerName(certificate.FriendlyName); // Check whether the signer's name is unique among all v1 signers var v1SignatureDigestAlgorithm = digestAlgorithm ?? V1SchemeSigner.GetSuggestedSignatureDigestAlgorithm( certificate.PublicKey, minSdkVersion); var v1SignerConfig = new V1SchemeSigner.SignerConfig(); v1SignerConfig.Name = v1SignerName; v1SignerConfig.Certificate = certificate; v1SignerConfig.SignatureDigestAlgorithm = v1SignatureDigestAlgorithm; _v1SignerConfigs = v1SignerConfig; _v1ContentDigestAlgorithm = v1SignatureDigestAlgorithm; _signatureExpectedOutputJarEntryNames = V1SchemeSigner.GetOutputEntryNames(_v1SignerConfigs, minSdkVersion); _v1ContentDigestAlgorithm = V1SchemeSigner.GetSuggestedSignatureDigestAlgorithm(certificate.PublicKey, minSdkVersion); } if (v2SigningEnabled) { var v2SignerConfig = new V2SchemeSigner.SignerConfig(); v2SignerConfig.Certificates = certificate; v2SignerConfig.SignatureAlgorithm = V2SchemeSigner.GetSuggestedSignatureAlgorithms(certificate.PublicKey, minSdkVersion, digestAlgorithm); _v2SignerConfigs = v2SignerConfig; } }
/// <summary> /// Returns the output policy for the provided input JAR entry. /// </summary> /// <param name="entryName"></param> /// <returns></returns> private OutputPolicy GetInputJarEntryOutputPolicy(string entryName) { if (_signatureExpectedOutputJarEntryNames.Contains(entryName)) { return(OutputPolicy.OutputByEngine); } if (V1SchemeSigner.IsJarEntryDigestNeededInManifest(entryName)) { return(OutputPolicy.Output); } return(OutputPolicy.Skip); }
/// <summary> /// Indicates to this engine that the specified JAR entry was output. /// /// It is unnecessary to invoke this method for entries added to output by this engine (e.g., /// requested by <see cref="OutputJarEntries"/> provided the entries were output with exactly the /// data requested by the engine. /// </summary> /// <param name="entryName"></param> /// <returns> /// request to inspect the entry or <code>null</code> if the engine does not need to inspect /// the entry.The request must be fulfilled before<see cref="OutputJarEntries"/> is invoked. /// </returns> public IInspectJarEntryRequest OutputJarEntry(string entryName) { InvalidateV2Signature(); if (!_v1SigningEnabled) { // No need to inspect JAR entries when v1 signing is not enabled. return(null); } // v1 signing is enabled if (V1SchemeSigner.IsJarEntryDigestNeededInManifest(entryName)) { // This entry is covered by v1 signature. We thus need to inspect the entry's data to // compute its digest(s) for v1 signature. // TODO: Handle the case where other signer's v1 signatures are present and need to be // preserved. In that scenario we can't modify MANIFEST.MF and add/remove JAR entries // covered by v1 signature. InvalidateV1Signature(); var dataDigestRequest = new GetJarEntryDataDigestRequest(entryName, _v1ContentDigestAlgorithm); _outputJarEntryDigestRequests.Add(entryName, dataDigestRequest); _outputJarEntryDigests.Remove(entryName); return(dataDigestRequest); } if (_signatureExpectedOutputJarEntryNames.Contains(entryName)) { // This entry is part of v1 signature generated by this engine. We need to check whether // the entry's data is as output by the engine. InvalidateV1Signature(); GetJarEntryDataRequest dataRequest; if (V1SchemeSigner.ManifestEntryName.Equals(entryName)) { dataRequest = new GetJarEntryDataRequest(entryName); _inputJarManifestEntryDataRequest = dataRequest; } else { // If this entry is part of v1 signature which has been emitted by this engine, // check whether the output entry's data matches what the engine emitted. dataRequest = (_emittedSignatureJarEntryData.ContainsKey(entryName)) ? new GetJarEntryDataRequest(entryName) : null; } if (dataRequest != null) { _outputSignatureJarEntryDataRequests.Add(entryName, dataRequest); } return(dataRequest); } // This entry is not covered by v1 signature and isn't part of v1 signature. return(null); }
/// <summary> /// Indicates to this engine that all JAR entries have been output. /// </summary> /// <returns> /// request to add JAR signature to the output or <code>null</code> if there is no need to add /// a JAR signature.The request will contain additional JAR entries to be output.The /// request must be fulfilled before <see cref="OutputZipSections"/> is invoked. /// </returns> public IOutputJarSignatureRequest OutputJarEntries() { if (!_v1SignaturePending) { return(null); } if ((_inputJarManifestEntryDataRequest != null) && (!_inputJarManifestEntryDataRequest.IsDone)) { throw new InvalidOperationException( "Still waiting to inspect input APK's " + _inputJarManifestEntryDataRequest.EntryName); } foreach (var digestRequest in _outputJarEntryDigestRequests.Values) { var entryName = digestRequest.EntryName; if (!digestRequest.IsDone) { throw new InvalidOperationException( "Still waiting to inspect output APK's " + entryName); } _outputJarEntryDigests.Add(entryName, digestRequest.Digest); } _outputJarEntryDigestRequests.Clear(); foreach (var dataRequest in _outputSignatureJarEntryDataRequests.Values) { if (!dataRequest.IsDone) { throw new InvalidOperationException( "Still waiting to inspect output APK's " + dataRequest.EntryName); } } IList <int> apkSigningSchemeIds = new List <int>(); if (_v2SigningEnabled) { apkSigningSchemeIds.Add(2); } var inputJarManifest = (_inputJarManifestEntryDataRequest != null) ? _inputJarManifestEntryDataRequest.Data : null; // Check whether the most recently used signature (if present) is still fine. List <Tuple <string, byte[]> > signatureZipEntries; if ((_addV1SignatureRequest == null) || (!_addV1SignatureRequest.IsDone)) { try { signatureZipEntries = V1SchemeSigner.Sign( _v1SignerConfigs, _v1ContentDigestAlgorithm, _outputJarEntryDigests, apkSigningSchemeIds, inputJarManifest); } catch (CryptographicException e) { throw new CryptographicException("Failed to generate v1 signature", e); } } else { var newManifest = V1SchemeSigner.GenerateManifestFile(_v1ContentDigestAlgorithm, _outputJarEntryDigests, inputJarManifest); var emittedSignatureManifest = _emittedSignatureJarEntryData[V1SchemeSigner.ManifestEntryName]; if (!newManifest.Contents.SequenceEqual(emittedSignatureManifest)) { // Emitted v1 signature is no longer valid. try { signatureZipEntries = V1SchemeSigner.SignManifest( _v1SignerConfigs, _v1ContentDigestAlgorithm, apkSigningSchemeIds, newManifest); } catch (CryptographicException e) { throw new CryptographicException("Failed to generate v1 signature", e); } } else { // Emitted v1 signature is still valid. Check whether the signature is there in the // output. signatureZipEntries = new List <Tuple <string, byte[]> >(); foreach (var expectedOutputEntry in _emittedSignatureJarEntryData) { var entryName = expectedOutputEntry.Key; var expectedData = expectedOutputEntry.Value; var actualDataRequest = _outputSignatureJarEntryDataRequests[entryName]; if (actualDataRequest == null) { // This signature entry hasn't been output. signatureZipEntries.Add(Tuple.Create(entryName, expectedData)); continue; } var actualData = actualDataRequest.Data; if (!expectedData.SequenceEqual(actualData)) { signatureZipEntries.Add(Tuple.Create(entryName, expectedData)); } } if (signatureZipEntries.Count == 0) { // v1 signature in the output is valid return(null); } // v1 signature in the output is not valid. } } if (signatureZipEntries.Count == 0) { // v1 signature in the output is valid _v1SignaturePending = false; return(null); } var sigEntries = new List <JarEntry>(signatureZipEntries.Count()); foreach (var entry in signatureZipEntries) { var entryName = entry.Item1; var entryData = entry.Item2; sigEntries.Add(new JarEntry(entryName, entryData)); _emittedSignatureJarEntryData.Add(entryName, entryData); } _addV1SignatureRequest = new OutputJarSignatureRequestImpl(sigEntries); return(_addV1SignatureRequest); }