Beispiel #1
0
        /// <summary>
        /// Signs the CDA Package and creates the signature document.
        /// </summary>
        /// <param name="package">A CDAPackageBase instance containing the root document to sign.</param>
        /// <param name="signingCert">The certificate used to sign the root document.</param>
        /// <returns>Signature of the root document.</returns>
        private static byte[] CreateSignature(CDAPackage package, X509Certificate2 signingCert)
        {
            package.SigningTime = DateTime.Now.ToUniversalTime();

            byte[] rootDocumentContent = package.CDADocumentRoot.FileContent;

            byte[] hash = CalculateSHA1(rootDocumentContent);

            var manifest = new ManifestType();

            manifest.Reference = new ReferenceType[]
            {
                new ReferenceType()
                {
                    URI          = package.CDADocumentRoot.FileName,
                    DigestMethod = new DigestMethodType()
                    {
                        Algorithm = SignedXml.XmlDsigSHA1Url
                    },
                    DigestValue = hash
                }
            };

            var approver = new ApproverType();

            approver.personId              = package.Approver.PersonId.ToString();
            approver.personName            = new PersonNameType();
            approver.personName.familyName = package.Approver.PersonFamilyName;
            if (package.Approver.PersonTitles != null)
            {
                approver.personName.nameTitle = package.Approver.PersonTitles.ToArray();
            }
            if (package.Approver.PersonGivenNames != null)
            {
                approver.personName.givenName = package.Approver.PersonGivenNames.ToArray();
            }
            if (package.Approver.PersonNameSuffixes != null)
            {
                approver.personName.nameSuffix = package.Approver.PersonNameSuffixes.ToArray();
            }

            var eSignature = new eSignatureType();

            eSignature.Manifest    = manifest;
            eSignature.approver    = approver;
            eSignature.signingTime = package.SigningTime.Value;

            XmlDocument eSignatureXml = eSignature.SerializeToXml();

            ISignedContainerProfileService signedContainerService = XspFactory.Instance.GetSignedContainerProfileService(XspVersion.V_2010);

            XmlDocument signedDoc = signedContainerService.Create(eSignatureXml, signingCert);

            var ms = new MemoryStream();

            signedDoc.Save(ms);

            return(ms.ToArray());
        }
Beispiel #2
0
        /// <summary>
        /// Extracts a CDA package zip file and verifies the signature. An exception is thrown if verification fails.
        /// </summary>
        /// <param name="packageFilePath">The path to the CDA package zip file to extract.</param>
        /// <param name="verifyCertificate">An optional delegate to verify the signature certificate (NULL accepted).</param>
        /// <returns>A CDAPackage instance constructed from the CDA package zip file.</returns>
        public static CDAPackage Extract(string packageFilePath, VerifyCertificateDelegate verifyCertificate)
        {
            byte[] zipContent = File.ReadAllBytes(packageFilePath);

            CDAPackage newPackage = Extract(zipContent, verifyCertificate);

            return(newPackage);
        }
Beispiel #3
0
        /// <summary>
        /// Creates a signed CDA package zip file. Can be used for CdaPackageBase; and for the ADD or REPLACE operations.
        /// </summary>
        /// <param name="package">The CDAPackageBase instance used to generate the zip file content.</param>
        /// <param name="outputFilePath">The path to output the zip file.</param>
        /// <param name="signingCert">The certificate used to sign the CDA root document.</param>
        public static void CreateZip(CDAPackage package, string outputFilePath, X509Certificate2 signingCert)
        {
            // Validation on package, outputFilePath
            Validation.ValidateArgumentRequired("package", package);
            Validation.ValidateArgumentRequired("outputFilePath", outputFilePath);

            var zipFileContent = Create(package, signingCert);

            File.WriteAllBytes(outputFilePath, zipFileContent);
        }
        /// <summary>
        /// Creates a signed CDA package zip file.
        /// </summary>
        /// <param name="package">The CDAPackageBase instance used to generate the zip file content.</param>
        /// <param name="signingCert">The certificate used to sign the CDA root document.</param>
        /// <returns>A byte array of the zip file content.</returns>
        public static byte[] Create(CDAPackage package, X509Certificate2 signingCert)
        {
            // Validation on package
            Validation.ValidateArgumentRequired("package", package);

            // Validate CDAPackage
            CDAPackageValidation.ValidateCDAPackage(package, signingCert != null);

            var ms = new MemoryStream();

            // Generate signature if package operation is ADD or REPLACE
            byte[] signatureContent = null;
            if (signingCert != null)
            {
                signatureContent = CreateSignature(package, signingCert);

                package.CDASignature = new CDAPackageFile();
                package.CDASignature.CDAPackageFileType = CDAPackageFile.FileType.Signature;
                package.CDASignature.FileContent        = signatureContent;
                package.CDASignature.FileName           = "CDA_SIGN.XML";
            }

            using (var zip = new ZipFile(Encoding.Default))
            {
                // Add folder entries
                zip.AddEntry("IHE_XDM/", "");
                zip.AddEntry("IHE_XDM/SUBSET01/", "");

                zip.AddEntry("IHE_XDM/SUBSET01/" + package.CDADocumentRoot.FileName, package.CDADocumentRoot.FileContent);

                // Add signature if present
                if (signatureContent != null)
                {
                    zip.AddEntry("IHE_XDM/SUBSET01/" + package.CDASignature.FileName, signatureContent);
                }

                if (package.CDADocumentAttachments != null)
                {
                    foreach (var file in package.CDADocumentAttachments)
                    {
                        zip.AddEntry("IHE_XDM/SUBSET01/" + file.FileName, file.FileContent);
                    }
                }

                // Save output file
                zip.Save(ms);
            }

            var zipContent = ms.ToArray();

            ms.Close();

            return(zipContent);
        }
Beispiel #5
0
        /// <summary>
        /// Validates a CDAPackage.
        /// </summary>
        /// <param name="package">The CDAPackage to validate.</param>
        internal static void ValidateCDAPackage(CDAPackage package, bool validateApprover)
        {
            ValidationBuilder vb = new ValidationBuilder("package");

            // Validate approver
            if (validateApprover)
            {
                vb.ArgumentRequiredCheck("Approver", package.Approver);

                if (package.Approver != null)
                {
                    vb.ArgumentRequiredCheck("Approver.PersonFamilyName", package.Approver.PersonFamilyName);
                    vb.ArgumentRequiredCheck("Approver.PersonId", package.Approver.PersonId);
                }
            }

            // Validate root
            if (vb.ArgumentRequiredCheck("CDADocumentRoot", package.CDADocumentRoot))
            {
                vb.ArgumentRequiredCheck("CDADocumentRoot.FileName", package.CDADocumentRoot.FileName);
                vb.ArgumentRequiredCheck("CDADocumentRoot.FileContent", package.CDADocumentRoot.FileContent);
            }

            // Validate attachments
            if (package.CDADocumentAttachments != null)
            {
                for (int x = 0; x < package.CDADocumentAttachments.Count; x++)
                {
                    vb.ArgumentRequiredCheck(string.Format("CDADocumentAttachments[{0}].FileName", x), package.CDADocumentAttachments[x].FileName);
                    vb.ArgumentRequiredCheck(string.Format("CDADocumentAttachments[{0}].FileContent", x), package.CDADocumentAttachments[x].FileContent);
                }
            }

            if (vb.Messages.Count > 0)
            {
                throw new ValidationException(vb.Messages);
            }
        }
Beispiel #6
0
        /// <summary>
        /// Verify the signature of a CDA package.
        /// </summary>
        /// <param name="package">The CDA package to verify.</param>
        /// <param name="verifyCertificate">A delegate to verify the signature certificate.</param>
        public static void VerifySignature(CDAPackage package, VerifyCertificateDelegate verifyCertificate)
        {
            // Return if package doesn't contain a signature.
            if (package.CDASignature == null)
            {
                return;
            }

            byte[] signatureDocumentContent = package.CDASignature.FileContent;
            byte[] rootDocumentContent      = package.CDADocumentRoot.FileContent;

            byte[] hash = CalculateSHA1(rootDocumentContent);

            var signatureDocument = new XmlDocument();

            signatureDocument.PreserveWhitespace = true;
            signatureDocument.Load(new MemoryStream(signatureDocumentContent));

            // Get eSignature
            eSignatureType eSignature = null;

            try
            {
                var eSignatureElement = signatureDocument.GetElementsByTagName("eSignature", "*")[0] as XmlElement;
                eSignature = eSignatureElement.Deserialize <eSignatureType>();
            }
            catch (Exception ex)
            {
                throw new SignatureVerificationException("Error extracting eSignature");
            }

            if (eSignature == null)
            {
                throw new SignatureVerificationException("Error extracting eSignature");
            }

            var manifest = eSignature.Manifest;

            var approver = eSignature.approver;

            // Get signing time

            package.SigningTime = eSignature.signingTime;

            // Get approver

            package.Approver = new Approver();
            if (eSignature.approver != null)
            {
                if (eSignature.approver.personId != null)
                {
                    package.Approver.PersonId = new Uri(eSignature.approver.personId, UriKind.RelativeOrAbsolute);
                }
                var personName = eSignature.approver.personName;
                if (personName != null)
                {
                    package.Approver.PersonFamilyName = personName.familyName;
                    if (personName.givenName != null)
                    {
                        package.Approver.PersonGivenNames = personName.givenName.ToList();
                    }
                    if (personName.nameSuffix != null)
                    {
                        package.Approver.PersonNameSuffixes = personName.nameSuffix.ToList();
                    }
                    if (personName.nameTitle != null)
                    {
                        package.Approver.PersonTitles = personName.nameTitle.ToList();
                    }
                }
            }

            // Check signature digest

            var manifestElement = signatureDocument.GetElementsByTagName("Manifest", "*")[0] as XmlElement;

            if (manifest.Reference[0].URI != "CDA_ROOT.XML")
            {
                throw new SignatureVerificationException("Error verifying document - Manifest reference must have a URI of 'CDA_ROOT.XML'");
            }

            if (manifest.Reference[0].DigestMethod.Algorithm != SignedXml.XmlDsigSHA1Url)
            {
                throw new SignatureVerificationException("Error verifying document - Manifest digest method must have an algorithm of '" + SignedXml.XmlDsigSHA1Url + "'");
            }

            if (Convert.ToBase64String(manifest.Reference[0].DigestValue) != Convert.ToBase64String(hash))
            {
                throw new SignatureVerificationException("Error verifying document - Manifest digest value mismatch");
            }

            // Verify certificate

            ICertificateVerifier verifier = new CertificateVerifier(verifyCertificate);

            ISignedContainerProfileService signedContainerService = XspFactory.Instance.GetSignedContainerProfileService(XspVersion.V_2010);

            signedContainerService.Check(signatureDocument, verifier);

            // Verify attachments integrity checks
            if (package.CDADocumentAttachments != null && package.CDADocumentAttachments.Count > 0)
            {
                VerifyAttachments(package);
            }

            return;
        }
Beispiel #7
0
        /// <summary>
        /// Verify CDA package attachments integrity checks.
        /// </summary>
        /// <param name="package">The CDA package to verify.</param>
        public static void VerifyAttachments(CDAPackage package)
        {
            List <KeyValuePair <string, string> > hashFails = new List <KeyValuePair <string, string> >();

            // Verify document attachments
            XmlDocument rootDocument = new XmlDocument();

            try
            {
                rootDocument.Load(new MemoryStream(package.CDADocumentRoot.FileContent));
            }
            catch (Exception ex)
            {
                throw new ValidationException("package", null, "CDA_ROOT.XML cannot be extracted as an XML document.");
            }

            XmlNamespaceManager nm = new XmlNamespaceManager(rootDocument.NameTable);

            nm.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
            nm.AddNamespace("nm", "urn:hl7-org:v3");

            var nodeList = rootDocument.SelectNodes("//nm:value[@integrityCheck!='' and descendant::nm:reference[@value!='']]", nm);

            for (int x = 0; x < nodeList.Count; x++)
            {
                var    node           = nodeList[x];
                string attachmentHash = node.Attributes["integrityCheck"].Value;
                string attachmentUri  = node.SelectSingleNode("nm:reference", nm).Attributes["value"].Value.ToLower();
                string algorithm      = "sha-1";
                if (node.Attributes["integrityCheckAlgorithm"] != null && !string.IsNullOrEmpty(node.Attributes["integrityCheckAlgorithm"].Value))
                {
                    algorithm = node.Attributes["integrityCheckAlgorithm"].Value.ToLower();
                }

                foreach (var attachment in package.CDADocumentAttachments.Where(a => a.FileName.ToLower() == attachmentUri))
                {
                    string calculatedHash = null;

                    if (algorithm == "sha-1")
                    {
                        calculatedHash = Convert.ToBase64String(CalculateSHA1(attachment.FileContent));
                    }
                    else if (algorithm == "sha-256")
                    {
                        calculatedHash = Convert.ToBase64String(CalculateSHA256(attachment.FileContent));
                    }

                    if (calculatedHash != attachmentHash)
                    {
                        hashFails.Add(new KeyValuePair <string, string>(attachmentUri, attachmentHash));
                    }
                }
            }

            if (hashFails.Count > 0)
            {
                string exceptionList = "Error verifying document reference: ";
                foreach (var pair in hashFails)
                {
                    exceptionList += string.Format("\n- {0} ({1})", pair.Key, pair.Value);
                }

                throw new SignatureVerificationException(exceptionList);
            }
        }
Beispiel #8
0
        /// <summary>
        /// Extracts a CDA package zip file without verifying the signature.
        /// </summary>
        /// <param name="package">A byte array of a CDA package zip file.</param>
        /// <returns>A CDAPackage instance constructed from the CDA package zip file.</returns>
        public static CDAPackage ExtractAndIgnoreSignatureVerification(byte[] package)
        {
            CDAPackage extractedPackage = null;

            // Validation on package
            Validation.ValidateArgumentRequired("package", package);

            // Get zip entries in zip package
            var inputStream = new MemoryStream(package);
            Dictionary <string, byte[]> entries = GetZipEntriesFromZipStream(new ZipArchive(inputStream));

            // Check to ensure that there is only one submission set folder
            string submissionPath = null;

            foreach (var name in entries.Keys)
            {
                var m = Regex.Match(name, @"^([^/\\]+[/\\][^/\\]+[/\\])[^/\\]+$", RegexOptions.IgnoreCase);
                if (m.Success)
                {
                    if (submissionPath == null)
                    {
                        submissionPath = m.Groups[1].Value;
                    }
                    else if (submissionPath != m.Groups[1].Value)
                    {
                        throw new ArgumentException("More than one submission set folder found.");
                    }
                }
            }

            // Get root document
            var rootDoc = entries.FirstOrDefault(a => Regex.IsMatch(a.Key, @"^[^/\\]+[/\\][^/\\]+[/\\]CDA_ROOT.XML$", RegexOptions.IgnoreCase));

            if (string.IsNullOrEmpty(rootDoc.Key))
            {
                throw new ArgumentException("CDA_ROOT.XML not found.");
            }

            // Get submission set folder
            string submissionSetPath = Regex.Match(rootDoc.Key, @"^([^/\\]+[/\\][^/\\]+[/\\])CDA_ROOT.XML$", RegexOptions.IgnoreCase).Groups[1].Value;

            var newPackage = new CDAPackage();

            newPackage.CDADocumentRoot = new CDAPackageFile();
            newPackage.CDADocumentRoot.CDAPackageFileType = CDAPackageFile.FileType.RootDocument;
            newPackage.CDADocumentRoot.FileName           = rootDoc.Key.Replace(submissionPath, "");
            newPackage.CDADocumentRoot.FileContent        = rootDoc.Value;

            foreach (var entry in entries)
            {
                if (entry.Key == rootDoc.Key)
                {
                    continue;
                }
                else if (entry.Key.Equals(submissionPath + "CDA_SIGN.XML", StringComparison.InvariantCultureIgnoreCase))
                {
                    var signature = new CDAPackageFile();
                    signature.CDAPackageFileType = CDAPackageFile.FileType.Signature;
                    signature.FileName           = entry.Key.Replace(submissionPath, "");
                    signature.FileContent        = entry.Value;

                    newPackage.CDASignature = signature;
                }
                else if (entry.Key.StartsWith(submissionPath, true, CultureInfo.InvariantCulture))
                {
                    var attachment = new CDAPackageFile();
                    attachment.CDAPackageFileType = CDAPackageFile.FileType.Attachment;
                    attachment.FileName           = entry.Key.Replace(submissionPath, "");
                    attachment.FileContent        = entry.Value;

                    if (newPackage.CDADocumentAttachments == null)
                    {
                        newPackage.CDADocumentAttachments = new List <CDAPackageFile>();
                    }

                    newPackage.CDADocumentAttachments.Add(attachment);
                }
            }

            extractedPackage = newPackage;

            return(extractedPackage);
        }
Beispiel #9
0
 /// <summary>
 /// Creates an unsigned CDA package zip file. Can be used for CdaPackageBase; and for the ADD, REPLACE or REMOVE operations.
 /// </summary>
 /// <param name="package">The CDAPackageBase instance used to generate the zip file content.</param>
 /// <param name="outputFilePath">The path to output the zip file.</param>
 public static void CreateZip(CDAPackage package, string outputFilePath)
 {
     CreateZip(package, outputFilePath, null);
 }
Beispiel #10
0
 /// <summary>
 /// Creates an unsigned CDA package zip file. Can be used for CdaPackageBase; and for the ADD, REPLACE or REMOVE operations.
 /// </summary>
 /// <param name="package">The CDAPackageBase instance used to generate the zip file content.</param>
 /// <returns>A byte array of the zip file content.</returns>
 public static byte[] Create(CDAPackage package)
 {
     return(Create(package, null));
 }
Beispiel #11
0
        public void Process(PackagerInput Input)
        {
            // ------------------------------------------------------------------------------
            // Set up signing certificate and identifiers
            // ------------------------------------------------------------------------------

            // Load certificate used to sign the CDA document
            X509Certificate2 signingCert = X509CertificateUtil.GetCertificate(
                Input.NashCertificateSerial,
                X509FindType.FindBySerialNumber,
                StoreName.My,
                StoreLocation.CurrentUser,
                true);

            // ------------------------------------------------------------------------------
            // Create CDAPackage
            // ------------------------------------------------------------------------------

            // Create an approver
            var approver = new Approver()
            {
                PersonId         = new Uri($"http://ns.electronichealth.net.au/id/hi/hpii/1.0/{Input.Approver.Hpii}"),
                PersonFamilyName = Input.Approver.FamilyName,
                PersonGivenNames = new List <string> {
                    Input.Approver.GivenName
                },
                //PersonNameSuffixes = new List<string> { Input.Approver },
                PersonTitles = new List <string> {
                    Input.Approver.Title
                }
            };

            // Create a CDAPackage instance
            var package = new Nehta.VendorLibrary.CDAPackage.CDAPackage(approver);

            if (!File.Exists(Input.CdaDocumentInputFilePath))
            {
                throw new ApplicationException($"Unable to locate the CDA file at file path: {Input.CdaDocumentInputFilePath}");
            }
            // Create the CDA root document for the CDA package
            package.CreateRootDocument(File.ReadAllBytes(Input.CdaDocumentInputFilePath));

            //Add the PDF report attachment
            var PdfAtachmentFileInfo = new FileInfo(Input.PdfReportAttachment);

            if (!PdfAtachmentFileInfo.Exists)
            {
                throw new ApplicationException($"Unable to locate attachment file at file path: {Input.PdfReportAttachment}");
            }
            package.AddDocumentAttachment("attachment.pdf", File.ReadAllBytes(Input.PdfReportAttachment));

            //Add the logo image attachment
            package.AddDocumentAttachment("logo.png", Input.CdaDocumentLogoBytes);

            FileInfo CdaDocumentFileinfo = new FileInfo(Input.CdaDocumentInputFilePath);

            //string CdaPackageFileName = CdaDocumentFileinfo.Name.Replace(CdaDocumentFileinfo.Extension, ".zip");
            //CdaPackageFileName = Path.Combine(CdaDocumentFileinfo.DirectoryName, CdaPackageFileName);
            // Create the CDA package zip
            CDAPackageUtility.CreateZip(package, Input.CdaPackageOutputFilePath, signingCert);

            //Delete the raw CDA xml file
            //CdaDocumentFileinfo.Delete();
        }