Пример #1
0
        /// <summary>
        /// Get only the files in the JAR that have no connection to the signing process
        /// </summary>
        /// <param name="this"></param>
        /// <returns>set of files which are not metadata/signing related</returns>
        public static IEnumerable <string> NonSignatureFiles(this IJar @this)
        {
            string[] manifestExtensions = new string[]
            {
                ".RSA",
                ".DSA",
                ".MF",
                ".SF"
            };

            return(@this.Files()
                   .Where(f =>
            {
                if (f.ToForwardSlashes().StartsWith(@"META-INF/"))
                {
                    // Is it a manifest or signature?
                    if (manifestExtensions.Any(ext =>
                                               ext.Equals(Path.GetExtension(f), StringComparison.InvariantCultureIgnoreCase)))
                    {
                        return false;
                    }

                    return true;
                }
                else
                {
                    // Not metadata
                    return true;
                }
            }));
        }
Пример #2
0
        /// <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);
        }
Пример #3
0
        /// <summary>
        /// Verify all signatures in the JAR
        /// </summary>
        /// <param name="jar">JAR to verify</param>
        /// <param name="centralManifest">the main MANIFEST.MF</param>
        /// <param name="signatures">the set of signatures to verify</param>
        /// <param name="certificates">the set of permitted certificates we verify against</param>
        /// <returns>true if all signatures verify as valid - otherwise false</returns>
        public bool Verify(
            IJar jar,
            ManifestData centralManifest,
            List <Signature> signatures,
            IVerificationCertificates certificates)
        {
            if (jar == null)
            {
                throw new ArgumentNullException(nameof(jar));
            }

            if (centralManifest == null)
            {
                throw new ArgumentNullException(nameof(centralManifest));
            }

            if (certificates == null)
            {
                throw new ArgumentNullException(nameof(certificates));
            }

            if (!signatures.Any())
            {
                return(false);
            }

            foreach (Signature sig in signatures)
            {
                ManifestData signFile = _loader.Load(jar, sig.ManifestPath);

                Log.Message($"Signature {sig.BaseName} @ {sig.ManifestPath} with block {sig.Block.Path} type {sig.Block.Type}");

                // Sign file hash mismatch
                if (!VerifyManifestHashes(centralManifest, signFile))
                {
                    return(false);
                }

                // Ensure we actually have a certificate to verify against
                if (!certificates.Contains(sig.BaseName))
                {
                    throw new MissingCertificateException($"Signature with base name {sig.BaseName} must have a matching certificate " +
                                                          $"supplied in order to verify");
                }
            }

            return(signatures.All(s => VerifyPKCS7(jar, s, certificates.Get(s.BaseName))));
        }
Пример #4
0
        public List <Signature> Find(IJar jar)
        {
            if (jar == null)
            {
                throw new ArgumentNullException(nameof(jar));
            }

            // Signature from base name -> data
            Dictionary <string, Signature> found = new Dictionary <string, Signature>(StringComparer.InvariantCultureIgnoreCase);

            foreach (string candidate in jar.Files())
            {
                string[] pathParts = candidate.Split('/');

                // Must be in META-INF
                if (pathParts.Length == 2 && pathParts[0] == "META-INF")
                {
                    string filenameOnly = pathParts[1];

                    if (filenameOnly.Equals("MANIFEST.MF", StringComparison.InvariantCultureIgnoreCase))
                    {
                        // We don't care about the non-signature manifest
                        continue;
                    }

                    // Base name being the actual overall signature name
                    string baseName = Path.GetFileNameWithoutExtension(filenameOnly);

                    Populate(found.AddOrGet(baseName, () => new Signature
                    {
                        BaseName = baseName
                    }),
                             candidate, filenameOnly);
                }
            }

            return(found.Values
                   .Where(s => !string.IsNullOrEmpty(s.ManifestPath) || s.Block != null)
                   .ToList());
        }
Пример #5
0
        /// <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));
            }
        }
Пример #6
0
        /// <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);
            }
        }
Пример #7
0
        /// <summary>
        /// Perform JAR digital signature verification against a JAR filename on disk
        /// </summary>
        /// <param name="jar">JAR container. The caller is expected to dispose this type themselves - it will not be disposed
        /// by this method</param>
        /// <param name="certificates">certificate to verify / accept against</param>
        /// <param name="nonStandardCountCheck">whether to perform the additional file count verification check against
        /// MANIFEST.MF (recommended if the file is actually an arbitrary ZIP)</param>
        /// <returns>digital signature verification state of the JAR</returns>
        public static VerificationResult Jar(IJar jar, IVerificationCertificates certificates, bool nonStandardCountCheck = true)
        {
            // Unsigned ZIP and probably not even a JAR
            if (!jar.Contains(@"META-INF\MANIFEST.MF"))
            {
                return(new VerificationResult
                {
                    Status = SigningStatus.NotSigned,
                    Valid = false
                });
            }

            IManifestLoader manifestLoader = new ManifestLoader();

            ManifestData centralManifest = manifestLoader.Load(jar, @"META-INF\MANIFEST.MF");

            if (nonStandardCountCheck)
            {
                // Non-standard check: Ensure that no unsigned files have been ADDED
                // to the JAR (file qty. [except signature itself] must match manifest entries)
                //
                int nonManifestFiles = jar.NonSignatureFiles().Count();

                if (centralManifest.Entries.Count != nonManifestFiles)
                {
                    Log.Message($"Expected {centralManifest.Entries.Count} file(s) found {nonManifestFiles}");

                    return(new VerificationResult
                    {
                        Status = SigningStatus.FundamentalHashMismatch,
                        Valid = false
                    });
                }
            }

            // Verify the hashes of every file in the JAR
            //
            using (var h = new Hasher())
            {
                Log.Message($"Central manifest contains {centralManifest.Entries.Count} entries");

                foreach (ManifestEntry e in centralManifest.Entries)
                {
                    Log.Message($"Digest check {e.Path} ({e.Digest})");

                    // Check each file matches the hash in the manifest
                    if (jar.SHA256(h, e.Path).ToBase64() != e.Digest)
                    {
                        Log.Message($"{e.Path} has an incorrect digest");

                        return(new VerificationResult
                        {
                            Status = SigningStatus.FundamentalHashMismatch,
                            Valid = false
                        });
                    }
                }
            }

            // Detect signatures
            //
            //
            ISignatureFinder finder = new SignatureFinder();

            List <Signature> signatures = finder.Find(jar);

            if (!signatures.Any())
            {
                Log.Message("No signatures detected");

                return(new VerificationResult
                {
                    Status = SigningStatus.NotSigned,
                    Valid = false
                });
            }

            Log.Message($"{signatures.Count} signature(s) detected");

            // Verify signatures
            //
            //
            SignatureVerifier ver = new SignatureVerifier();

            if (ver.Verify(jar, centralManifest, signatures, certificates))
            {
                return(new VerificationResult
                {
                    Status = SigningStatus.SignedValid,
                    Valid = true
                });
            }
            else
            {
                return(new VerificationResult
                {
                    Status = SigningStatus.SignedInvalid,
                    Valid = false
                });
            }
        }