/// <summary> /// Build a signed SAML authentication request. /// </summary> /// <param name="uuid"></param> /// <param name="destination"></param> /// <param name="consumerServiceURL"></param> /// <param name="securityLevel"></param> /// <param name="certFile"></param> /// <param name="certPassword"></param> /// <param name="storeLocation"></param> /// <param name="storeName"></param> /// <param name="findType"></param> /// <param name="findValue"></param> /// <param name="identityProvider"></param> /// <param name="enviroment"></param> /// <returns>Returns a Base64 Encoded String of the SAML request</returns> public static string BuildAuthnPostRequest(string uuid, string destination, string consumerServiceURL, int securityLevel, X509Certificate2 certificate, IdentityProvider identityProvider, int enviroment) { if (string.IsNullOrWhiteSpace(uuid)) { throw new ArgumentNullException("The uuid parameter can't be null or empty."); } if (string.IsNullOrWhiteSpace(destination)) { throw new ArgumentNullException("The destination parameter can't be null or empty."); } if (string.IsNullOrWhiteSpace(consumerServiceURL)) { throw new ArgumentNullException("The consumerServiceURL parameter can't be null or empty."); } if (certificate == null) { throw new ArgumentNullException("The certificate parameter can't be null."); } if (identityProvider == null) { throw new ArgumentNullException("The identityProvider parameter can't be null."); } if (enviroment < 0) { throw new ArgumentNullException("The enviroment parameter can't be less than zero."); } DateTime now = DateTime.UtcNow; AuthnRequestType authnRequest = new AuthnRequestType { ID = "_" + uuid, Version = "2.0", IssueInstant = identityProvider.Now(now), Destination = destination, AssertionConsumerServiceIndex = (ushort)enviroment, AssertionConsumerServiceIndexSpecified = true, AttributeConsumingServiceIndex = 1, AttributeConsumingServiceIndexSpecified = true, ForceAuthn = (securityLevel > 1), ForceAuthnSpecified = (securityLevel > 1), Issuer = new NameIDType { Value = consumerServiceURL.Trim(), Format = "urn:oasis:names:tc:SAML:2.0:nameid-format:entity", NameQualifier = consumerServiceURL }, NameIDPolicy = new NameIDPolicyType { Format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" }, Conditions = new ConditionsType { NotBefore = identityProvider.NotBefore(now), NotBeforeSpecified = true, NotOnOrAfter = identityProvider.After(now.AddMinutes(10)), NotOnOrAfterSpecified = true }, RequestedAuthnContext = new RequestedAuthnContextType { Comparison = AuthnContextComparisonType.minimum, ComparisonSpecified = true, ItemsElementName = new ItemsChoiceType7[] { ItemsChoiceType7.AuthnContextClassRef }, Items = new string[] { "https://www.spid.gov.it/SpidL" + securityLevel.ToString() } } }; XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); ns.Add("saml2p", "urn:oasis:names:tc:SAML:2.0:protocol"); ns.Add("saml2", "urn:oasis:names:tc:SAML:2.0:assertion"); StringWriter stringWriter = new StringWriter(); XmlWriterSettings settings = new XmlWriterSettings { OmitXmlDeclaration = true, Indent = true, Encoding = Encoding.UTF8 }; XmlWriter responseWriter = XmlTextWriter.Create(stringWriter, settings); XmlSerializer responseSerializer = new XmlSerializer(authnRequest.GetType()); responseSerializer.Serialize(responseWriter, authnRequest, ns); responseWriter.Close(); string samlString = stringWriter.ToString(); stringWriter.Close(); XmlDocument doc = new XmlDocument(); doc.LoadXml(samlString); XmlElement signature = XmlSigningHelper.SignXMLDoc(doc, certificate, "_" + uuid); doc.DocumentElement.InsertBefore(signature, doc.DocumentElement.ChildNodes[1]); return(Convert.ToBase64String(Encoding.UTF8.GetBytes("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + doc.OuterXml))); }
public SamlBodyRequest GetSamlRequest(Dictionary <CCAtributes, bool> CCRequestAttrs) { #region SAML initial request configs AuthnRequestType _request = new AuthnRequestType(); // saml-core-2.0-os - 3.2.1 // An identifier for the request. It is of type xs:ID and MUST follow the requirements specified in Section // 1.3.4 for identifier uniqueness. The values of the ID attribute in a request and the InResponseTo // attribute in the corresponding response MUST match _request.ID = "_" + Guid.NewGuid().ToString(); // saml-core-2.0-os - 3.2.1 // The version of this request. The identifier for the version of SAML defined in this specification is "2.0". // SAML versioning is discussed in Section 4. _request.Version = "2.0"; // saml-core-2.0-os - 3.2.1 // The time instant of issue of the request. The time value is encoded in UTC, as described in Section // 1.3.3. _request.IssueInstant = DateTime.UtcNow; // saml-core-2.0-os - 3.2.1 // A URI reference indicating the address to which this request has been sent. This is useful to prevent // malicious forwarding of requests to unintended recipients, a protection that is required by some // protocol bindings. If it is present, the actual recipient MUST check that the URI reference identifies the // location at which the message was received. If it does not, the request MUST be discarded. Some // protocol bindings may require the use of this attribute (see [SAMLBind]). _request.Destination = appSettings.Get("AuthGovPT.Saml.Request.Destination.Url"); // saml-core-2.0-os - 3.2.1 // Indicates whether or not (and under what conditions) consent has been obtained from a principal in // the sending of this request. See Section 8.4 for some URI references that MAY be used as the value // of the Consent attribute and their associated descriptions. If no Consent value is provided, the // identifier urn:oasis:names:tc:SAML:2.0:consent:unspecified (see Section 8.4.1) is in // effect. _request.Consent = "urn:oasis:names:tc:SAML:2.0:consent:unspecified"; // saml-core-2.0-os - 3.4.1 // A URI reference that identifies a SAML protocol binding to be used when returning the <Response> // message. See [SAMLBind] for more information about protocol bindings and URI references defined // for them. This attribute is mutually exclusive with the AssertionConsumerServiceIndex attribute // and is typically accompanied by the AssertionConsumerServiceURL attribute. _request.ProtocolBinding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"; // saml-core-2.0-os - 3.4.1 // Specifies by value the location to which the <Response> message MUST be returned to the // requester. The responder MUST ensure by some means that the value specified is in fact associated // with the requester. [SAMLMeta] provides one possible mechanism; signing the enclosing // <AuthnRequest> message is another. This attribute is mutually exclusive with the // AssertionConsumerServiceIndex attribute and is typically accompanied by the // ProtocolBinding attribute. _request.AssertionConsumerServiceURL = appSettings.Get("AuthGovPT.Saml.Request.AssertionService.Url"); // saml-core-2.0-os - 3.4.1 // Specifies the human-readable name of the requester for use by the presenter's user agent or the // identity provider. _request.ProviderName = appSettings.Get("AuthGovPT.Saml.Request.Provider.Name"); // saml-core-2.0-os - 2.2.5 // The <Issuer> element, with complex type NameIDType, provides information about the issuer of a // SAML assertion or protocol message. The element requires the use of a string to carry the issuer's name, // but permits various pieces of descriptive data (see Section 2.2.2). _request.Issuer = new NameIDType(); _request.Issuer.Value = appSettings.Get("AuthGovPT.Saml.Request.Issuer.Value"); // saml-core-2.0-os - 3.2.1 // This extension point contains optional protocol message extension elements that are agreed on // between the communicating parties. No extension schema is required in order to make use of this // extension point, and even if one is provided, the lax validation setting does not impose a requirement // for the extension to be valid. SAML extension elements MUST be namespace-qualified in a non- // SAML-defined namespace. _request.Extensions = new ExtensionsType(); #endregion #region Load Cartão de Cidadão Attributes _request.Extensions.Any = CCAttributes.RegisterCCAtributes(CCRequestAttrs, EnableAuthWithCMD); #endregion #region SAML Xml convert to stream XmlDocument doc = null; // Converter objeto para XmlDocument via stream usando serialização com os tipos AuthnRequestType e XmlDocument // http://support.microsoft.com/kb/815813/en-us try { MemoryStream stream = new MemoryStream(); XmlSerializer requestSerializer = new XmlSerializer(_request.GetType()); requestSerializer.Serialize(stream, _request, xmlNamespaces); stream.Flush(); StreamReader reader = new StreamReader(stream); stream.Seek(0, SeekOrigin.Begin); XmlTextReader xmlReader = new XmlTextReader(new StringReader(reader.ReadToEnd())); XmlSerializer xmlDocumentSerializer = new XmlSerializer(typeof(XmlDocument)); doc = (XmlDocument)xmlDocumentSerializer.Deserialize(xmlReader); doc.PreserveWhitespace = true; } catch (Exception ex) { //log SamlBodyRequest.Success = false; SamlBodyRequest.ErrorMessage = $"Error on XmlDocument object convertion. EX: {ex.ToString()} "; } #endregion #region SAML Xml Signning try { XmlElement element = doc.DocumentElement; SignedXml signedXml = new SignedXml(element) { SigningKey = FaX509Certificate.PrivateKey }; // Tipo de dados "ID" é restrito às strings em NCName: //<xs:simpleType name="ID" id="ID"> // <xs:annotation> // <xs:documentation source="http://www.w3.org/TR/xmlschema-2/#ID"/> // </xs:annotation> // <xs:restriction base="xs:NCName"/> //</xs:simpleType> // NCName está definido em http://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-NCName como: // NCName ::= (Letter | '_') (NCNameChar)* Reference reference = new Reference("#" + element.Attributes["ID"].Value); // Vide 5.4.3 "Canonicalization Method" e 5.4.4 "Transforms" em // http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf reference.AddTransform(new XmlDsigEnvelopedSignatureTransform()); reference.AddTransform(new XmlDsigExcC14NTransform()); signedXml.AddReference(reference); signedXml.KeyInfo.AddClause(new KeyInfoX509Data(FaX509Certificate)); signedXml.ComputeSignature(); XmlElement xmlDigitalSignature = signedXml.GetXml(); // AuthnRequestType define a ordem dos elementos filhos na schema saml-schema-protocol-2.0.xsd: //<complexType name="RequestAbstractType" abstract="true"> // <sequence> // <element ref="saml:Issuer" minOccurs="0"/> // <element ref="ds:Signature" minOccurs="0"/> // <element ref="ds:Signature" minOccurs="0"/> // <element ref="samlp:Extensions" minOccurs="0"/> // </sequence> // ... //</complexType> XmlNode refNode = doc.GetElementsByTagName("Issuer", "urn:oasis:names:tc:SAML:2.0:assertion").Item(0); element.InsertAfter(xmlDigitalSignature, refNode); } catch (Exception ex) { //TODO:: log exception ex SamlBodyRequest.Success = false; SamlBodyRequest.ErrorMessage = $"Error on Xml signing process. EX: {ex.ToString()}"; } #endregion #region Return SAML Request into Auth.gov FA SamlBodyRequest.RelayState = RelayStateToBepersistedAcross; SamlBodyRequest.SAMLRequest = Convert.ToBase64String(Encoding.UTF8.GetBytes(doc.OuterXml)); SamlBodyRequest.PostRequestUrl = appSettings.Get("AuthGovPT.Saml.Request.Post.Url"); SamlBodyRequest.Success = true; return(SamlBodyRequest); // Vide 3.5.3 "RelayState" em // http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf // "...The value MUST NOT exceed 80 bytes in length and SHOULD be integrity protected by the entity // creating the message independent of any other protections that may or may not exist during message // transmission..." // Vide 3.5 "HTTP POST Binding" em // http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf #endregion }
/// <summary> /// Gets the authentication request. /// </summary> /// <returns></returns> public string GetAuthRequest() { string result = ""; DateTime requestDatTime = DateTime.UtcNow; //New AuthnRequestType AuthnRequestType request = new AuthnRequestType(); request.Version = Options.Version; //Unique UUID request.ID = "_" + this.Options.UUID; //Request DateTime request.IssueInstant = requestDatTime; //Request Force Authn if ((int)Options.SPIDLevel > 1) { request.ForceAuthn = true; request.ForceAuthnSpecified = true; } else { request.ForceAuthn = false; request.ForceAuthnSpecified = true; } //SSO Destination URI request.Destination = this.Options.Destination; //Service Provider Assertion Consumer Service Index request.AssertionConsumerServiceIndex = this.Options.AssertionConsumerServiceIndex; request.AssertionConsumerServiceIndexSpecified = true; //Service Provider Attribute Consumer Service Index request.AttributeConsumingServiceIndex = this.Options.AttributeConsumingServiceIndex; request.AttributeConsumingServiceIndexSpecified = true; //Service Provider Attribute Consumer Service Index request.AttributeConsumingServiceIndex = this.Options.AttributeConsumingServiceIndex; request.AttributeConsumingServiceIndexSpecified = true; //Issuer Data request.Issuer = new NameIDType() { Format = "urn:oasis:names:tc:SAML:2.0:nameid-format:entity", Value = Options.SPUID, NameQualifier = Options.SPUID }; request.NameIDPolicy = new NameIDPolicyType() { Format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", AllowCreate = true }; //NotRequired request.Conditions = new ConditionsType() { NotBefore = requestDatTime.Add(this.Options.NotBefore), NotBeforeSpecified = true, NotOnOrAfter = requestDatTime.Add(this.Options.NotOnOrAfter), NotOnOrAfterSpecified = true }; RequestedAuthnContextType requestedAuthn = new RequestedAuthnContextType { Comparison = AuthnContextComparisonType.minimum, ComparisonSpecified = true, ItemsElementName = new ItemsChoiceType7[] { ItemsChoiceType7.AuthnContextClassRef }, Items = new string[] { "https://www.spid.gov.it/SpidL" + ((int)Options.SPIDLevel).ToString() } }; request.RequestedAuthnContext = requestedAuthn; string samlString = ""; XmlSerializer serializer = new XmlSerializer(request.GetType()); using (StringWriter stringWriter = new StringWriter()) { XmlWriterSettings settings = new XmlWriterSettings() { OmitXmlDeclaration = true, Indent = true, Encoding = Encoding.UTF8 }; using (XmlWriter writer = XmlWriter.Create(stringWriter, settings)) { XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces(); namespaces.Add("saml2p", "urn:oasis:names:tc:SAML:2.0:protocol"); serializer.Serialize(writer, request, namespaces); samlString = stringWriter.ToString(); } } result = samlString; return(result); }
/// <summary> /// /// </summary> /// <param name="UUID"></param> /// <param name="Destination"></param> /// <param name="ConsumerServiceURL"></param> /// <param name="certFile"></param> /// <param name="certPassword"></param> /// <param name="storeLocation"></param> /// <param name="storeName"></param> /// <param name="findType"></param> /// <param name="findValue"></param> /// <param name="signatureType"></param> /// <returns></returns> public static string BuildPostSamlRequest(string UUID, string Destination, string ConsumerServiceURL, int SecurityLevel, string certFile, string certPassword, StoreLocation storeLocation, StoreName storeName, X509FindType findType, object findValue, SigningHelper.SignatureType signatureType, string IdentityProvider, int Enviroment) { AuthnRequestType MyRequest = new AuthnRequestType { ID = UUID, Version = "2.0" }; DateTime now = DateTime.UtcNow; DateTime after = now.AddMinutes(10); string nowString = String.Empty; string afterString = String.Empty; if (IdentityProvider.Contains("sielte")) { // SIELTE nowString = now.AddMinutes(-2).ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"); afterString = after.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"); } else { // POSTE - TIM - INFOCERT nowString = now.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'"); afterString = after.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'"); } MyRequest.IssueInstant = nowString; if (SecurityLevel > 1) { MyRequest.ForceAuthn = true; MyRequest.ForceAuthnSpecified = true; } MyRequest.Destination = Destination; MyRequest.AssertionConsumerServiceIndex = (ushort)Enviroment; MyRequest.AssertionConsumerServiceIndexSpecified = true; MyRequest.AttributeConsumingServiceIndex = 1; MyRequest.AttributeConsumingServiceIndexSpecified = true; NameIDType IssuerForRequest = new NameIDType { Value = ConsumerServiceURL.Trim(), Format = "urn:oasis:names:tc:SAML:2.0:nameid-format:entity", NameQualifier = ConsumerServiceURL }; MyRequest.Issuer = IssuerForRequest; NameIDPolicyType NameIdPolicyForRequest = new NameIDPolicyType { Format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", AllowCreate = true, AllowCreateSpecified = true }; MyRequest.NameIDPolicy = NameIdPolicyForRequest; ConditionsType Conditional = new ConditionsType(); if (IdentityProvider.Contains("sielte")) { // SIELTE Conditional.NotBefore = nowString; } else { // POSTE - TIM - INFOCERT Conditional.NotBefore = now.AddMinutes(-2).ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'"); } Conditional.NotBeforeSpecified = true; Conditional.NotOnOrAfter = afterString; Conditional.NotOnOrAfterSpecified = true; MyRequest.Conditions = Conditional; RequestedAuthnContextType RequestedAuthn = new RequestedAuthnContextType { Comparison = AuthnContextComparisonType.minimum, ComparisonSpecified = true, ItemsElementName = new ItemsChoiceType7[] { ItemsChoiceType7.AuthnContextClassRef }, Items = new string[] { "https://www.spid.gov.it/SpidL" + SecurityLevel.ToString() } }; MyRequest.RequestedAuthnContext = RequestedAuthn; XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); ns.Add("saml2p", "urn:oasis:names:tc:SAML:2.0:protocol"); //ns.Add("saml2", "urn:oasis:names:tc:SAML:2.0:assertion"); XmlSerializer responseSerializer = new XmlSerializer(MyRequest.GetType()); StringWriter stringWriter = new StringWriter(); XmlWriterSettings settings = new XmlWriterSettings { OmitXmlDeclaration = true, Indent = true, Encoding = Encoding.UTF8 }; XmlWriter responseWriter = XmlTextWriter.Create(stringWriter, settings); responseSerializer.Serialize(responseWriter, MyRequest, ns); responseWriter.Close(); string samlString = string.Empty; samlString = stringWriter.ToString(); stringWriter.Close(); XmlDocument doc = new XmlDocument(); doc.LoadXml(samlString); X509Certificate2 cert = null; if (System.IO.File.Exists(certFile)) { cert = new X509Certificate2(certFile, certPassword); } else { X509Store store = new X509Store(storeName, storeLocation); store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); X509Certificate2Collection CertCol = store.Certificates; X509Certificate2Collection coll = store.Certificates.Find(findType, findValue.ToString(), false); if (coll.Count < 1) { throw new ArgumentException("Unable to locate certificate"); } cert = coll[0]; store.Close(); } XmlElement signature = SigningHelper.SignDoc(doc, cert, UUID); doc.DocumentElement.InsertBefore(signature, doc.DocumentElement.ChildNodes[1]); string responseStr = doc.OuterXml; //byte[] base64EncodedBytes = // Encoding.UTF8.GetBytes(responseStr); //string returnValue = System.Convert.ToBase64String( // base64EncodedBytes); return("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + responseStr); }