/// <summary> /// Load all manifest entries from a given manifest in a JAR /// </summary> /// <param name="source">source JAR</param> /// <param name="manifest">manifest to load</param> /// <returns>manifest data</returns> public ManifestData Load(IJar source, string path) { if (source == null) { throw new ArgumentNullException(nameof(source)); } if (path.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(path)); } // If this file does not exist, obviously we cannot load it if (!source.Contains(path)) { throw new ManifestException($"Manifest {path} does not exist"); } ManifestData manifest = new ManifestData { ManifestDigest = String.Empty, Entries = new List <ManifestEntry>() }; // The manifest digest is supposed to refer to the digest that THIS manifest // expects the main manifest to have. Until set otherwise, assume this digest // is = to our own hash (which means loading the main manifest populates this // value with the main manifest hash for future comparison) using (Hasher h = new Hasher()) { manifest.ManifestDigest = source.SHA256(h, path).ToBase64(); } try { using (StreamReader reader = new StreamReader(source.Open(path))) { string[] lines = Unwrap70(reader.ReadToEnd().Split( new char[] { (char)10, (char)13 }, StringSplitOptions.RemoveEmptyEntries)); Populate(manifest, lines); } } catch (Exception ex) { throw new ManifestException($"Failed to open or parse manifest {path}", ex); } return(manifest); }
/// <summary> /// Produce a SHA256 of a file in the JAR. If the file does not exist, an exception is thrown. /// </summary> /// <param name="this"></param> /// <param name="hasher">hasher implementation</param> /// <param name="path">filename to generate a hash of</param> /// <returns>the byte data of the SHA-256 hash</returns> public static byte[] SHA256(this IJar @this, Hasher hasher, string path) { if (hasher == null) { throw new ArgumentNullException(nameof(hasher)); } if (path.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(path)); } if ([email protected](path)) { throw new JarException($"File to hash {path} does not exist in JAR"); } using (Stream file = @this.Open(path)) { return(hasher.SHA256(file)); } }
/// <summary> /// Verify a PKCS7 signature block /// </summary> /// <param name="jar">JAR from which to read and verify the signature</param> /// <param name="sig">the signature being verified</param> /// <param name="certificate">the raw certificate bytes against which to verify (i.e. public key)</param> /// <returns>whether the PKCS signature is valid</returns> private bool VerifyPKCS7(IJar jar, Signature sig, byte[] certificate) { try { // Detached content to verify - in this case, the .SF file // (against which the signature block validates its hash) CmsProcessableByteArray detachedContent; // We cannot easily reuse a reader against the SF file // So instead, copy to memory in entirety and build from byte array using (Stream sigFile = jar.Open(sig.ManifestPath)) using (MemoryStream sigFileMemory = new MemoryStream()) { sigFile.CopyTo(sigFileMemory); detachedContent = new CmsProcessableByteArray(sigFileMemory.ToArray()); } // Open the signature block (e.g. .RSA or .DSA) using (Stream block = jar.Open(sig.Block.Path)) { X509CertificateParser certParser = new X509CertificateParser(); // Read the caller's certificate (assumed to have a matching public key) X509Certificate cert = certParser.ReadCertificate(certificate); CmsSignedData signedData = new CmsSignedData(detachedContent, block); SignerInformationStore signers = signedData.GetSignerInfos(); int verified = 0; foreach (SignerInformation signer in signers.GetSigners()) { Log.Message($"Verifying against {cert.SubjectDN.ToString()}"); if (signer.Verify(cert)) { verified++; Log.Message($"Signature valid for {cert.SubjectDN.ToString()}"); } else { Log.Message($"Signature INVALID for {cert.SubjectDN.ToString()}"); } } // Every signer must verify OK return(verified == signers.GetSigners().Count); } } catch (Exception ex) { // Cert verification can trigger a number of different possible errors // (Ranging from cert bytes invalid -> key type mismatch) Log.Error(ex, "Failed to verify certifiate: assuming invalid"); return(false); } }