Example #1
0
 internal SignatureInfo(XadesForm form, X509Certificate2 certificate, DateTimeOffset?time, ManifestResult[] manifestResults)
 {
     this.form            = form;
     this.certificate     = certificate;
     this.time            = time;
     this.manifestResults = manifestResults;
 }
Example #2
0
        /// <summary>
        /// Verify any XAdES (-BES, -T) signature.
        /// </summary>
        /// <remarks>
        /// Requires the XAdES QualifyingProperties and not the signature, it will resolve the signature itself.
        /// </remarks>
        /// <param name="doc">The document for which the </param>
        /// <param name="xadesProps">The XAdES 1.4.1 QualifyingProperties xml-element</param>
        /// <returns>The (useful) information of the signature and xades properties</returns>
        /// <exception cref="ArgumentNullException">When the xades props param is null</exception>
        /// <exception cref="InvalidXadesException">When the XAdES isn't correctly formatted</exception>
        /// <exception cref="XadesValidationException">When the signature isn't valid</exception>
        /// <exception cref="NotSupportedException">When a XAdES or the signature contains unsupported sections</exception>
        public SignatureInfo Verify(XmlDocument doc, XmlElement xadesProps)
        {
            XadesForm form = XadesForm.XadesBes;

            if (doc == null)
            {
                throw new ArgumentNullException("doc", "The doc argument can't be null");
            }
            if (xadesProps == null)
            {
                throw new ArgumentNullException("xadesProps", "The xades props argument can't be null");
            }

            //check if we get a valid xades-props
            //TODO:support QualifyingPropertiesReference
            if (xadesProps.LocalName != "QualifyingProperties" || xadesProps.NamespaceURI != Extra.XadesTools.NS)
            {
                throw new InvalidXadesException("The provider xades properties aren't actually xades properties");
            }

            //Get the corresponding signature of the xades props
            String targetRef;

            if (xadesProps.Attributes["Target"] == null)
            {
                throw new InvalidXadesException("the XAdES Properties has no Target attribute defined");
            }
            targetRef = xadesProps.Attributes["Target"].Value;
            if (targetRef == null || !targetRef.StartsWith("#"))
            {
                throw new InvalidXadesException("the XAdES Properties has an invalid Target attribute value");
            }
            var signatureNode = (XmlElement)xadesProps.OwnerDocument.SelectSingleNode("//ds:Signature[@Id='" + targetRef.Substring(1) + "']", nsMgr);

            if (signatureNode == null)
            {
                throw new InvalidXadesException("The signature referenced by the XAdES Properties was not found (Target-attribute)");
            }

            //Load the signature
            var signature = new SignedXml(doc);

            signature.LoadXml(signatureNode);
            signature.SafeCanonicalizationMethods.Add(OptionalDeflateTransform.AlgorithmUri);

            //check if the signature contains a reference to the xades signed props.
            var xadesRef          = new Reference();
            var signedPropsIdAttr = (XmlAttribute)xadesProps.SelectSingleNode("./xades:SignedProperties/@Id", nsMgr);

            if (signedPropsIdAttr == null)
            {
                throw new InvalidXadesException("The xades Signed Properties do not have an Id which should be referenced in the signature");
            }
            var xadesRefNode = (XmlElement)signatureNode.SelectSingleNode("./ds:SignedInfo/ds:Reference[@Type='http://uri.etsi.org/01903#SignedProperties']", nsMgr);

            if (xadesRefNode == null)
            {
                throw new InvalidXadesException("The signature referenced by the XAdES Properties does not contain a reference element of te type 'http://uri.etsi.org/01903#SignedProperties'");
            }
            xadesRef.LoadXml(xadesRefNode);
            if (xadesRef.Uri != ("#" + signedPropsIdAttr.Value))
            {
                throw new InvalidXadesException("The Signed Properties references does not reference the signed properties");
            }

            //Check for illegal transforms in the reference to the xades signed props
            foreach (Transform t in xadesRef.TransformChain)
            {
                if (t.GetType() != typeof(XmlDsigC14NTransform) && t.GetType() != typeof(XmlDsigExcC14NTransform))
                {
                    throw new InvalidXadesException(String.Format("The signed property reference does contain a transform that isn't allowed {0}", t.Algorithm));
                }
            }

            //Get the provided certificates
            X509Certificate2Collection includedCerts = new X509Certificate2Collection();
            IEnumerator keyInfo = signature.Signature.KeyInfo.GetEnumerator();

            while (keyInfo.MoveNext())
            {
                KeyInfoClause clause = (KeyInfoClause)keyInfo.Current;
                if (clause.GetType() == typeof(KeyInfoX509Data))
                {
                    KeyInfoX509Data x509 = (KeyInfoX509Data)clause;
                    includedCerts.AddRange((X509Certificate2[])x509.Certificates.ToArray(typeof(X509Certificate2)));
                }
                else
                {
                    throw new NotSupportedException("Only X509Data is supported");
                }
            }
            if (includedCerts == null || includedCerts.Count == 0)
            {
                throw new InvalidXadesException("No certificates where found in the the signature key info");
            }

            //Check if any of the verified certificates is used for the signature
            bool                       valid       = false;
            X509Certificate2           signingCert = null;
            X509Certificate2Enumerator vce         = includedCerts.GetEnumerator();

            while (!valid && vce.MoveNext())
            {
                signingCert = vce.Current;
                AsymmetricAlgorithm key = (AsymmetricAlgorithm)signingCert.GetRSAPublicKey() ?? signingCert.GetECDsaPublicKey();
                valid = signature.CheckSignature(key);
            }
            if (!valid)
            {
                throw new XadesValidationException("The signature is invalid");
            }

            //Verify the manifests if present.
            List <ManifestResult> manifestResults = new List <ManifestResult>();

            if (VerifyManifest)
            {
                XmlNodeList manifestNodes = signatureNode.SelectNodes("./ds:Object/ds:Manifest", nsMgr);
                foreach (XmlNode manifestNode in manifestNodes)
                {
                    if (manifestNode.Attributes["Id"] == null)
                    {
                        throw new NotSupportedException("The Xades library only supports manifests with and Id");
                    }

                    int         manifestRefIndex = 0;
                    String      manifestId       = manifestNode.Attributes["Id"].Value;
                    XmlNodeList manifestRefNodes = manifestNode.SelectNodes("./ds:Reference", nsMgr);
                    foreach (XmlNode manifestRefNode in manifestRefNodes)
                    {
                        Reference manifestRef = new Reference();
                        manifestRef.LoadXml((XmlElement)manifestRefNode);
                        byte[] orgValue = manifestRef.DigestValue;

                        SignedXml signatureTmp = new SignedXml(doc);
                        signatureTmp.AddReference(manifestRef);
                        try
                        {
                            signatureTmp.ComputeSignature(new HMACMD5()); //we don't need the signature, so it can be as weak as it wants.
                        }
                        catch (CryptographicException ce)
                        {
                            throw new InvalidXadesException("The the reference " + manifestRefIndex + " of manifest " + manifestNode.Attributes["Id"].Value + " can't be validated", ce);
                        }

                        ManifestResultStatus status;
                        if (orgValue.SequenceEqual(manifestRef.DigestValue))
                        {
                            status = ManifestResultStatus.Valid;
                        }
                        else
                        {
                            status = ManifestResultStatus.Invalid;
                        }
                        String xpath = String.Format("//ds:Signature[@Id='{0}']/ds:Object/ds:Manifest[@Id='{1}']/ds:Reference[{2}]", targetRef.Substring(1), manifestId, ++manifestRefIndex);
                        manifestResults.Add(new ManifestResult(xpath, status));
                    }
                }
            }

            //Signing time retrieval
            DateTimeOffset?signingTime        = null;
            XmlNode        signingTimeTxtNode = xadesProps.SelectSingleNode("./xades:SignedProperties/xades:SignedSignatureProperties/xades:SigningTime/text()", nsMgr);

            if (signingTimeTxtNode != null)
            {
                DateTimeOffset signingTimeValue;
                if (!DateTimeOffset.TryParse(signingTimeTxtNode.Value, CultureInfo.InvariantCulture, DateTimeStyles.None, out signingTimeValue))
                {
                    throw new InvalidXadesException("Signing time provided in the xades information isn't valid");
                }
                signingTime = signingTimeValue;
            }

            //TODO:check for EPES.

            //check time-stamp
            XmlNodeList timestamps = xadesProps.SelectNodes("./xades:UnsignedProperties/xades:UnsignedSignatureProperties/xades:SignatureTimeStamp", nsMgr);

            if (timestamps != null && timestamps.Count > 0)
            {
                form = form | XadesForm.XadesT;
                foreach (XmlNode timestamp in timestamps)
                {
                    XmlNode timestampC14NAlgoNode = timestamp.SelectSingleNode("./ds:CanonicalizationMethod/@Algorithm", nsMgr);
                    if (timestampC14NAlgoNode == null)
                    {
                        new InvalidXadesException("Canonicalization method missing in the signature timestamp");
                    }

                    var signatureValue = (XmlElement)signatureNode.SelectSingleNode("./ds:SignatureValue", nsMgr);
                    if (signatureValue == null)
                    {
                        throw new InvalidXadesException("Can't find the signature value for the signature timestamp");
                    }

                    var timestampC14NAlgo = (Transform)CryptoConfig.CreateFromName(timestampC14NAlgoNode.Value);
                    if (timestampC14NAlgo == null || timestampC14NAlgo.GetType() != typeof(XmlDsigC14NTransform) && timestampC14NAlgo.GetType() != typeof(XmlDsigExcC14NTransform))
                    {
                        throw new InvalidXadesException(String.Format("The signature timestamp has a canonicalization method that isn't allowed {0}", timestampC14NAlgoNode.Value));
                    }

                    //Serialize because the C14N overloads which accepts lists is totally wrong (it C14N's the document)
                    MemoryStream stream = new MemoryStream();
                    using (var writer = XmlWriter.Create(stream))
                    {
                        signatureValue.WriteTo(writer);
                    }
                    stream.Seek(0, SeekOrigin.Begin);

                    //Canonicalize the signature value
                    timestampC14NAlgo.LoadInput(stream);
                    var canonicalized = (Stream)timestampC14NAlgo.GetOutput(typeof(Stream));

                    XmlNode timestampValueTxtNode = timestamp.SelectSingleNode("./xades:EncapsulatedTimeStamp/text()", nsMgr);
                    if (timestampValueTxtNode != null)
                    {
                        //Get the timestamp token
                        TimeStampToken tst = Convert.FromBase64String(timestampValueTxtNode.Value).ToTimeStampToken();

                        if (!tst.IsMatch(canonicalized))
                        {
                            throw new XadesValidationException("The timestamp doesn't match the signature value");
                        }

                        //verify the time-stamp
                        Timestamp ts = tst.Validate(ExtraStore);
                        if (ts.TimestampStatus.Count(x => x.Status != X509ChainStatusFlags.NoError) > 0)
                        {
                            throw new XadesValidationException(String.Format("The timestamp TSA has an invalid status {0}: {1}",
                                                                             ts.TimestampStatus[0].Status, ts.TimestampStatus[0].StatusInformation));
                        }
                        foreach (ChainElement chainE in ts.CertificateChain.ChainElements)
                        {
                            if (chainE.ChainElementStatus.Count(x => x.Status != X509ChainStatusFlags.NoError) > 0)
                            {
                                throw new XadesValidationException(String.Format("The timestamp TSA chain contains an invalid certificate '{0}' ({1}: {2})",
                                                                                 chainE.Certificate.Subject, chainE.ChainElementStatus[0].Status, chainE.ChainElementStatus[0].StatusInformation));
                            }
                        }

                        //check the timestamp token against the signing time.
                        DateTime tsTime = ts.Time;
                        if (signingTime != null)
                        {
                            DateTime signingTimeUtc = signingTime.Value.UtcDateTime;
                            if (Math.Abs((tsTime - signingTimeUtc).TotalSeconds) > TimestampGracePeriod.TotalSeconds)
                            {
                                throw new XadesValidationException("The signature timestamp it to old with regards to the signing time");
                            }
                        }
                        else
                        {
                            signingTime = tsTime;
                        }
                    }
                    else
                    {
                        //TODO:support xml timestamps
                        throw new NotSupportedException("Only Encapsulated timestamps are supported");
                    }
                }
            }

            //check check the chain
            Chain chain = signingCert.BuildChain(signingTime == null ? DateTime.UtcNow : signingTime.Value.UtcDateTime,
                                                 includedCerts);

            if (chain.ChainStatus.Count(x => x.Status != X509ChainStatusFlags.NoError) > 0)
            {
                throw new XadesValidationException(String.Format("The signing certificate chain is invalid ({0}: {1})",
                                                                 chain.ChainStatus[0].Status, chain.ChainStatus[0].StatusInformation));
            }

            //Select the correct certificate based on the xades-bes info
            XmlNodeList signedCerts = xadesProps.SelectNodes("./xades:SignedProperties/xades:SignedSignatureProperties/xades:SigningCertificate/xades:Cert", nsMgr);

            //TODO:Support the fact that it is also legal to sign the KeyInfo (G.2.2.1)
            if (signedCerts.Count == 0)
            {
                throw new InvalidXadesException("No signing certificates provided in the xades information");
            }

            //Find certs via signed info, checking with hash.
            X509Certificate2Collection unsignedChainCerts = new X509Certificate2Collection(chain.ChainElements.Select(c => c.Certificate).ToArray());

            foreach (XmlNode signedCert in signedCerts)
            {
                XmlNode issuerTxtNode = signedCert.SelectSingleNode("./xades:IssuerSerial/ds:X509IssuerName/text()", nsMgr);
                if (issuerTxtNode == null)
                {
                    throw new InvalidXadesException("Xades information does not contain an issuer name for the signing certificate");
                }
                XmlNode serialNumberTxtNode = signedCert.SelectSingleNode("./xades:IssuerSerial/ds:X509SerialNumber/text()", nsMgr);
                if (serialNumberTxtNode == null)
                {
                    throw new InvalidXadesException("Xades information does not contain an serial number for the signing certificate");
                }

                X509Certificate2Collection certsSameIssuer = unsignedChainCerts.Find(X509FindType.FindByIssuerDistinguishedName, issuerTxtNode.Value, false);
                if (certsSameIssuer.Count == 0)
                {
                    throw new InvalidXadesException(String.Format("Xades provided signed certificate {0} ({1}) can't be found in the chain", serialNumberTxtNode.Value, issuerTxtNode.Value));
                }
                X509Certificate2Collection exactCerts = certsSameIssuer.Find(X509FindType.FindBySerialNumber, serialNumberTxtNode.Value, false);
                if (exactCerts.Count == 0)
                {
                    throw new InvalidXadesException(String.Format("Xades provided signed certificate {0} ({1}) can't be found in the chain", serialNumberTxtNode.Value, issuerTxtNode.Value));
                }
                if (exactCerts.Count > 1)
                {
                    throw new InvalidXadesException(String.Format("Xades provided signed certificate {0} ({1}) can be found more then once in the chain", serialNumberTxtNode.Value, issuerTxtNode.Value));
                }

                XmlNode digestMethodTxtNode = signedCert.SelectSingleNode("./xades:CertDigest/ds:DigestMethod/@Algorithm", nsMgr);
                if (digestMethodTxtNode == null)
                {
                    throw new InvalidXadesException("Xades information does not contain the digest method for the signing certificate");
                }
                XmlNode digestValueTxtNode = signedCert.SelectSingleNode("./xades:CertDigest/ds:DigestValue/text()", nsMgr);
                if (digestValueTxtNode == null)
                {
                    throw new InvalidXadesException("Xades information does not contain the digest value for the signing certificate");
                }

                HashAlgorithm algo;
                try
                {
                    algo = (HashAlgorithm)CryptoConfig.CreateFromName(digestMethodTxtNode.Value);
                }
                catch (Exception e)
                {
                    throw new InvalidXadesException("The provided digest method of the signing certificate in xades isn't valid or isn't supported", e);
                }
                String digestValueReal = Convert.ToBase64String(algo.ComputeHash(exactCerts[0].GetRawCertData()));
                if (digestValueTxtNode.Value != digestValueReal)
                {
                    throw new XadesValidationException("The certificate of the key info isn't correct according to the certificate info in xades");
                }

                unsignedChainCerts.Remove(exactCerts[0]);
            }

            //has the end cert being signed?
            if (unsignedChainCerts.Contains(signingCert))
            {
                throw new XadesValidationException(String.Format("Signing certificate not part of the signature"));
            }
            //TODO::add some kind of warning or option to test for all.

            return(new SignatureInfo(form, signingCert, signingTime, manifestResults.ToArray()));
        }