using System; using System.Collections.Generic; using System.Text; using System.Xml; using System.Xml.Serialization; using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.Xml; using System.Security.Cryptography; using System.Xml.Linq; using System.Diagnostics; using CDO.Cnet.Interop.WebService.DSQ.Parametres; using System.Net.Http; using System.Web; using Microsoft.IdentityModel.Clients.ActiveDirectory; using Microsoft.IdentityModel.Tokens; using System.Threading.Tasks; using System.Net; using System.Security.Policy; using System.Net.Http.Headers; namespace CDO.Cnet.Interop.WebService.DSQ.Signature { public class SignatureCtrl { #region "Declaration" private XmlElement xmlContent; private XmlDocument xmlDocTemplate; private XmlElement EnvelopeTemplate; private XmlNodeList ListeHeader; private XmlElement HeaderTemplate; private XmlNodeList listeSecurity; private XmlNodeList listebody; private XmlElement bodyTemplate; private XmlElement securityEtemplate; private XmlElement securityItemplate; internal enum SignType { Key = 1, ModeOA = 2 } internal enum LogType { Debug = 1, Info = 2, Warn = 3, Error = 4, Fatal = 5 } #endregion #region "Internal" internal string SignMessage(SignerMessageRequest inputXml) { string retval; if (inputXml == null) { throw new ArgumentNullException(nameof(inputXml)); } LogEvent("Message reçu:", LogType.Debug, PrettyXml(inputXml.GetXml())); // détermine le type de signature requise avec la propriété XMLSign inputXml.Signature.XMLSign = decodeSignature(inputXml.Signature.XMLSign); SignType signType = getSignatureType(inputXml.Signature.XMLSign); LogEvent("Type de signature: " + signType.ToString()); // détermine le template à utiliser string template = getTemplate(signType); // signe le message try { retval = getSignedMessage(inputXml, signType, template).OuterXml; } catch (Exception ex) { throw new Exception("Il y a eu un problème lors de l'appel de la fonction getSignedMessage depuis la classe SignatureCtrl : ", ex); } // retourne la réponse LogEvent("Réponse donnée:", LogType.Debug, PrettyXml(retval)); return Base64Encode(retval); } #endregion #region "Private Stuff" ///////////////////////////////////////////////////////////////////////// //private stuff ///////////////////////////////////////////////////////////////////////// private string decodeSignature(string signature) { string trimSignature = String.Empty; trimSignature = System.Net.WebUtility.HtmlDecode(signature); if (trimSignature.IndexOf(" -1) { trimSignature = trimSignature.Substring(trimSignature.IndexOf(" signature.ToUpper() == SignType.ModeOA.ToString().ToUpper(); private String getTemplate(SignType signType) { switch (signType) { case SignType.Key: return TemplateCtrl.Instance().GetTemplateByName(DsqTemplateName.TemplateCle); case SignType.ModeOA: default: return TemplateCtrl.Instance().GetTemplateByName(DsqTemplateName.TemplateOA); } } private static string Base64Decode(string base64EncodedData) { var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData); return System.Text.Encoding.UTF8.GetString(base64EncodedBytes); } private static string Base64Encode(string plainText) { var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText); return System.Convert.ToBase64String(plainTextBytes); } private static XmlDocument getCleanDoc(SignerMessageRequest inMsg) { XmlDocument xmlDoc = new XmlDocument(); xmlDoc.PreserveWhitespace = true; /////////////////////////////////////////////////////////////////////////////// // RETIRER LES NAMESPACES DE L'APPEL DE L'ENGIN SI PRÉSENT(S) /////////////////////////////////////////////////////////////////////////////// string letexte = inMsg.Body.Xml.OuterXml;//xmlDoc.InnerXml; letexte = letexte.Replace("dsq:", ""); letexte = letexte.Replace("xmlns:dsq=\"http://chuq.qc.ca/cristalNet/services/dsq\"", ""); if (!string.IsNullOrEmpty(letexte) && letexte.TrimStart().StartsWith("<")) { //valider la licence if (inMsg.Author.License.ToString().Length > 0) { if (letexte.IndexOf("") > 0) { letexte = letexte.Insert(letexte.IndexOf(""), inMsg.Author.License.ToString()); } } if (inMsg.Body.query != null && inMsg.Body.query.ToString().Length > 0) { if (letexte.IndexOf("$RequestQuery$") > 0) { string larequete = inMsg.Body.query.ToString(); larequete = larequete.Replace("dsq:", ""); larequete = larequete.Replace("xmlns:dsq=\"http://chuq.qc.ca/cristalNet/services/dsq\"", ""); letexte = letexte.Replace("$RequestQuery$", larequete); } if (letexte.IndexOf("$Consentement$") > 0) { string larequete = inMsg.Body.query.ToString(); larequete = larequete.Replace("dsq:", ""); larequete = larequete.Replace("xmlns:dsq=\"http://chuq.qc.ca/cristalNet/services/dsq\"", ""); string consentement = Base64Decode(larequete); letexte = letexte.Replace("$Consentement$", consentement); } } xmlDoc.LoadXml(letexte); XmlNamespaceManager xnsm = new XmlNamespaceManager(xmlDoc.NameTable); xnsm.RemoveNamespace("dsq", "http://chuq.qc.ca/cristalNet/services/dsq"); } else { xmlDoc.LoadXml(string.Concat("", letexte, "")); } return xmlDoc; } private void InitDocumentElements() { EnvelopeTemplate = xmlDocTemplate.DocumentElement; ListeHeader = EnvelopeTemplate.GetElementsByTagName("soap:Header"); HeaderTemplate = (XmlElement)ListeHeader.Item(0); listeSecurity = HeaderTemplate.GetElementsByTagName("Security"); listebody = EnvelopeTemplate.GetElementsByTagName("soap:Body"); bodyTemplate = (XmlElement)listebody.Item(0); securityEtemplate = (XmlElement)listeSecurity.Item(0); securityItemplate = (XmlElement)listeSecurity.Item(0); for (int j = 0; j < listeSecurity.Count; j++) { switch (listeSecurity.Item(j).Attributes["soap:actor"].Value) { case Commun.ACTOR_INTERVENANT_ORGANISME_ORIGINE: securityItemplate = (XmlElement)listeSecurity.Item(j); break; case Commun.ACTOR_ORGANISME_EMETTEUR: securityEtemplate = (XmlElement)listeSecurity.Item(j); break; case Commun.ACTOR_INTERVENANT_ORIGINE: securityItemplate = (XmlElement)listeSecurity.Item(j); break; case Commun.ACTOR_INTERVENANT_EMETTEUR: securityEtemplate = (XmlElement)listeSecurity.Item(j); break; default: securityEtemplate = (XmlElement)listeSecurity.Item(0); securityItemplate = (XmlElement)listeSecurity.Item(0); break; } } bodyTemplate.AppendChild(xmlContent); } private void AppendEnvElement(string prefix, string id) { XmlElement headerElem = HeaderTemplate; XmlElement soapEnvElem = xmlDocTemplate.CreateElement(prefix, Commun.CAIS_NAMESPACE); soapEnvElem.SetAttribute("xmlns:cais", Commun.CAIS_NAMESPACE); soapEnvElem.SetAttribute("xmlns:xml", Commun.XML_NAMESPACE); soapEnvElem.AppendChild(xmlDocTemplate.CreateTextNode(id)); headerElem.AppendChild(soapEnvElem); } private void BuildHeader(string paramTrackingid) { XmlElement headerElem = HeaderTemplate; AppendEnvElement("cais:TrackingId", (string.IsNullOrEmpty(paramTrackingid)) ? Guid.NewGuid().ToString() : paramTrackingid); AppendEnvElement("cais:TransactionId", Guid.NewGuid().ToString()); } XmlElement getSignedMessageOAAzure(SignerMessageRequest inMsg) { ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 1- Construit SignatureIOA (Signature Intervenant - Organisme Autorisé) ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// string dateHeure = ""; using (X509Certificate2 cert = CertificateStoreCtrl.GetCertificate(inMsg.Signature.certificatHSM)) { try { if (cert.Subject == null) { } } catch { string erreur = @"Le certificat ORGANISME AUTORISÉ n'a pas été trouvé. Valider la configuration au niveau de l'engin d'intégration." + "" + @""; xmlDocTemplate.LoadXml(erreur); return xmlDocTemplate.DocumentElement; } //dateHeure = "2013-10-31 18:41:06Z"; dateHeure = Commun.TimeStamp("IOAZ"); XmlDocument xmlDocJeton = new XmlDocument(); XmlElement JetonOrgaAutor = xmlDocJeton.CreateElement("JetonOrgaAutor", "http://cais-icp.org/v1"); XmlElement xmlDateHeure = xmlDocJeton.CreateElement("DateHeure", "http://cais-icp.org/v1"); xmlDateHeure.InnerText = dateHeure; JetonOrgaAutor.AppendChild(xmlDateHeure); XmlElement xmlIntAut = xmlDocJeton.CreateElement("IntvnAutor", "http://cais-icp.org/v1"); xmlIntAut.InnerText = inMsg.Signature.UPN; JetonOrgaAutor.AppendChild(xmlIntAut); XmlElement xmlId = xmlDocJeton.CreateElement("IdJeton", "http://cais-icp.org/v1"); xmlId.InnerText = Guid.NewGuid().ToString().ToUpper(); JetonOrgaAutor.AppendChild(xmlId); XmlElement ObjectElement = xmlDocJeton.CreateElement("Object"); XmlElement SecurityE = securityEtemplate; XmlElement SecurityI = securityItemplate; string reference = "RefIntvnAutor"; SignedXml msgSigne = new SignedXml(); Dictionary idElmDic = new Dictionary(); idElmDic["_SigIntvnAutor"] = SecurityI; idElmDic["_Body01"] = bodyTemplate; MySignedXml msgSigneE = new MySignedXml(idElmDic); var rsaKey = new KeyVaultRsa(); msgSigneE.SigningKey = rsaKey; msgSigne.SigningKey = rsaKey; msgSigne.Signature.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl; msgSigne.Signature.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA256Url; System.Security.Cryptography.Xml.DataObject dObj = new System.Security.Cryptography.Xml.DataObject(reference, "", "", JetonOrgaAutor); msgSigne.AddObject(dObj); Reference refSigne = new Reference(); refSigne.Uri = "#" + reference; refSigne.DigestMethod = SignedXml.XmlDsigSHA256Url; refSigne.AddTransform(new XmlDsigExcC14NTransform()); msgSigne.AddReference(refSigne); KeyInfo infCle = new KeyInfo(); KeyInfoX509Data certInfCle = new KeyInfoX509Data(cert, X509IncludeOption.EndCertOnly); certInfCle.AddSubjectName(cert.SubjectName.Name); infCle.AddClause(certInfCle); msgSigne.KeyInfo = infCle; msgSigne.ComputeSignature(); SecurityI.AppendChild(xmlDocTemplate.ImportNode(msgSigne.GetXml(), true)); XmlElement envelopeElem = EnvelopeTemplate; msgSigneE.Signature.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl; msgSigneE.Signature.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA256Url; // Les références sont locales au xml foreach (string id in idElmDic.Keys) { Reference refSigneE = new Reference("#" + id); refSigneE.DigestMethod = SignedXml.XmlDsigSHA256Url; refSigneE.AddTransform(new XmlDsigExcC14NTransform()); msgSigneE.AddReference(refSigneE); } msgSigneE.KeyInfo = infCle; msgSigneE.ComputeSignature(); SecurityE.AppendChild(xmlDocTemplate.ImportNode(msgSigneE.GetXml(), true)); } return xmlDocTemplate.DocumentElement; } XmlElement getSignedMessageOA(SignerMessageRequest inMsg) { ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 1- Construit SignatureIOA (Signature Intervenant - Organisme Autorisé) ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// string dateHeure = ""; using (X509Certificate2 cert = CertificateStoreCtrl.GetCertificate(inMsg.Signature.certificatHSM)) { try { if (cert.Subject == null) { } } catch { string erreur = @"Le certificat ORGANISME AUTORISÉ n'a pas été trouvé. Valider la configuration au niveau de l'engin d'intégration." + "" + @""; xmlDocTemplate.LoadXml(erreur); return xmlDocTemplate.DocumentElement; } //dateHeure = "2013-10-31 18:41:06Z"; dateHeure = Commun.TimeStamp("IOAZ"); XmlDocument xmlDocJeton = new XmlDocument(); XmlElement JetonOrgaAutor = xmlDocJeton.CreateElement("JetonOrgaAutor", "http://cais-icp.org/v1"); XmlElement xmlDateHeure = xmlDocJeton.CreateElement("DateHeure", "http://cais-icp.org/v1"); xmlDateHeure.InnerText = dateHeure; JetonOrgaAutor.AppendChild(xmlDateHeure); XmlElement xmlIntAut = xmlDocJeton.CreateElement("IntvnAutor", "http://cais-icp.org/v1"); xmlIntAut.InnerText = inMsg.Signature.UPN; JetonOrgaAutor.AppendChild(xmlIntAut); XmlElement xmlId = xmlDocJeton.CreateElement("IdJeton", "http://cais-icp.org/v1"); xmlId.InnerText = Guid.NewGuid().ToString().ToUpper(); JetonOrgaAutor.AppendChild(xmlId); XmlElement ObjectElement = xmlDocJeton.CreateElement("Object"); XmlElement SecurityE = securityEtemplate; XmlElement SecurityI = securityItemplate; string reference = "RefIntvnAutor"; SignedXml msgSigne = new SignedXml(); Dictionary idElmDic = new Dictionary(); idElmDic["_SigIntvnAutor"] = SecurityI; idElmDic["_Body01"] = bodyTemplate; MySignedXml msgSigneE = new MySignedXml(idElmDic); if (cert.HasPrivateKey) { try { var SigningKey = cert.GetRSAPrivateKey(); if (SigningKey == null) { var SigningKeyP = cert.PrivateKey; msgSigne.SigningKey = SigningKeyP; msgSigneE.SigningKey = SigningKeyP; } else { msgSigne.SigningKey = SigningKey; msgSigneE.SigningKey = SigningKey; } } catch (CryptographicException e) { string erreur = @"Le certificat ORGANISME AUTORISÉ a été trouvé, mais la clé privée de signature n'a pu être obtenue" + e.Message.ToString() + @""; xmlDocTemplate.LoadXml(erreur); return xmlDocTemplate.DocumentElement; } } msgSigne.Signature.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl; msgSigne.Signature.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA256Url; System.Security.Cryptography.Xml.DataObject dObj = new System.Security.Cryptography.Xml.DataObject(reference, "", "", JetonOrgaAutor); msgSigne.AddObject(dObj); Reference refSigne = new Reference(); refSigne.Uri = "#" + reference; refSigne.DigestMethod = SignedXml.XmlDsigSHA256Url; refSigne.AddTransform(new XmlDsigExcC14NTransform()); msgSigne.AddReference(refSigne); KeyInfo infCle = new KeyInfo(); KeyInfoX509Data certInfCle = new KeyInfoX509Data(cert, X509IncludeOption.EndCertOnly); certInfCle.AddSubjectName(cert.SubjectName.Name); infCle.AddClause(certInfCle); msgSigne.KeyInfo = infCle; msgSigne.ComputeSignature(); SecurityI.AppendChild(xmlDocTemplate.ImportNode(msgSigne.GetXml(), true)); XmlElement envelopeElem = EnvelopeTemplate; msgSigneE.Signature.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl; msgSigneE.Signature.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA256Url; // Les références sont locales au xml foreach (string id in idElmDic.Keys) { Reference refSigneE = new Reference("#" + id); refSigneE.DigestMethod = SignedXml.XmlDsigSHA256Url; refSigneE.AddTransform(new XmlDsigExcC14NTransform()); msgSigneE.AddReference(refSigneE); } msgSigneE.KeyInfo = infCle; msgSigneE.ComputeSignature(); SecurityE.AppendChild(xmlDocTemplate.ImportNode(msgSigneE.GetXml(), true)); } return xmlDocTemplate.DocumentElement; } //************************************************************************************************************************** //************************************************************************************************************************** //************************************************************************************************************************** [return: XmlElement(Namespace = "", ElementName = "RETURNMESSAGE", Form = System.Xml.Schema.XmlSchemaForm.Qualified)] XmlElement getSignedMessage(SignerMessageRequest inMsg, SignType signType, String template) { /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// * * La fonction retourne un XML signé avec le certificat reçu lors de l'appel * Tout le message XML est inclu dans inMsg * */////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// xmlDocTemplate = new XmlDocument(); //xmlContent = (XmlElement)getCleanDoc(inMsg).DocumentElement.CloneNode(true); xmlDocTemplate = getCleanDoc(inMsg); xmlContent = (XmlElement)xmlDocTemplate.DocumentElement.CloneNode(true); xmlDocTemplate.LoadXml(template); InitDocumentElements(); BuildHeader(inMsg.Signature.TrackingId); if (signType.Equals(SignType.ModeOA)) { try { return getSignedMessageOA(inMsg); } catch (Exception ex) { throw new Exception("Il y a eu un problème lors de l'appel de la fonction getSignedMessageOA depuis la classe SignatureCtrl : ", ex); } } else { try { return getSignedMessageCle(inMsg); } catch (Exception ex) { throw new Exception("Il y a eu un problème lors de l'appel de la fonction getSignedMessageCle depuis la classe SignatureCtrl : ", ex); } } } private static string PrettyXml(string xml) { var stringBuilder = new StringBuilder(); var element = XElement.Parse(xml); var settings = new XmlWriterSettings(); settings.OmitXmlDeclaration = true; settings.Indent = true; settings.NewLineOnAttributes = true; using (var xmlWriter = XmlWriter.Create(stringBuilder, settings)) { element.Save(xmlWriter); } return stringBuilder.ToString(); } private static void LogEvent(String eventStr, LogType logType = LogType.Debug, String data = "") { String logStr = eventStr; if ((!String.IsNullOrEmpty(logStr)) && (!String.IsNullOrEmpty(data))) { logStr += "\r\n" + data; } switch (logType) { case LogType.Debug: //LogCtrl.Journal.Debug(logStr); break; case LogType.Error: //LogCtrl.Journal.Error(logStr); break; case LogType.Fatal: //LogCtrl.Journal.Fatal(logStr); break; case LogType.Info: //LogCtrl.Journal.Info(logStr); break; case LogType.Warn: //LogCtrl.Journal.Warn(logStr); break; } Debug.Print(logStr); } #endregion } internal class KeyVaultRsa : RSA { public override RSAParameters ExportParameters(bool includePrivateParameters) { RSAParameters rSAParameters = new RSAParameters(); return rSAParameters; } public override void ImportParameters(RSAParameters parameters) { } public static async Task GetAccessToken() { var authority = $"https://login.microsoftonline.com/{ConfigurationAzure.TenantId}"; var resource = "https://vault.azure.net"; AuthenticationContext authContext = new AuthenticationContext(authority); ClientCredential clientCredential = new ClientCredential(ConfigurationAzure.ClientId, ConfigurationAzure.ClientSecret); //Prendre la bonne version de TLS System.Net.ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; AuthenticationResult authResult = await authContext.AcquireTokenAsync(resource, clientCredential); return authResult.AccessToken; } public static async Task SignHashAsync(byte[] hash) { var accessToken = await GetAccessToken(); var httpClient = new HttpClient(); var uri = new Uri($"https://{ConfigurationAzure.KeyId}.vault.azure.net/keys/{ConfigurationAzure.KeyName}/{ConfigurationAzure.KeyVersion}/sign?api-version=7.0"); var requestContent = new StringContent($"{{\"alg\":\"RS256\",\"value\":\"{Convert.ToBase64String(hash)}\"}}", Encoding.UTF8, "application/json"); // Ajouter le jeton d'authentification à l'en-tête de la requête httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); var response = await httpClient.PostAsync(uri, requestContent); response.EnsureSuccessStatusCode(); string rep = await response.Content.ReadAsStringAsync(); string signature = rep.Split('"')[7]; return Encoding.UTF8.GetBytes(signature); } public override byte[] SignHash(byte[] hash, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding) { //var clientSecretCredential = new ClientSecretCredential(ConfigurationHSM.TenantId, ConfigurationHSM.ClientId, ConfigurationHSM.ClientSecret); //var rsaCryptoClient = new CryptographyClient(ConfigurationHSM.KeyId, clientSecretCredential); //var result = rsaCryptoClient.Sign(Azure.Security.KeyVault.Keys.Cryptography.SignatureAlgorithm.RS256, hash); //return result.Signature; return SignHashAsync(hash).ConfigureAwait(false).GetAwaiter().GetResult(); } } }