wwauth/Google.Solutions.WWAuth/Data/Saml2/AuthenticationRequest.cs (109 lines of code) (raw):

// // Copyright 2022 Google LLC // // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // using Google.Apis.Util; using System; using System.IO; using System.IO.Compression; using System.Linq; using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.Xml; using System.Text; using System.Xml; using System.Xml.Serialization; namespace Google.Solutions.WWAuth.Data.Saml2 { /// <summary> /// Saml 2.0 authentication request. /// </summary> internal class AuthenticationRequest { /// <summary> /// AD FS Relying Party ID . Typed as string (and not Uri) to prevent /// canonicaliuation, which might break the STS token exchange. /// </summary> public string RelyingPartyId { get; } /// <summary> /// ACS URL. This can be a dummy URL such as 'https://app.example/', but /// it must be configured in ADFS. /// </summary> public string AssertionConsumerServiceUrl { get; } /// <summary> /// Destination URL, typically the URL of the IdP. /// </summary> public string Destination { get; } public X509Certificate2 SigningCertificate { get; set; } public AuthenticationRequest( string destination, string relyingPartyId, string acsUrl) { if (!Uri.IsWellFormedUriString(relyingPartyId, UriKind.Absolute)) { throw new ArgumentException("Relying party ID must be a URI"); } if (!Uri.IsWellFormedUriString(acsUrl, UriKind.Absolute)) { throw new ArgumentException("ACS must be a URI"); } if (!Uri.IsWellFormedUriString(destination, UriKind.Absolute)) { throw new ArgumentException("Destination must be a URI"); } this.RelyingPartyId = relyingPartyId.ThrowIfNull(nameof(relyingPartyId)); this.AssertionConsumerServiceUrl = acsUrl.ThrowIfNull(nameof(acsUrl)); this.Destination = destination.ThrowIfNull(nameof(destination)); } private XmlDocument ToDocument() { var doc = new XmlDocument(); using (var writer = doc.CreateNavigator().AppendChild()) { var request = new Saml2Schema.AuthnRequest() { ID = "_" + Guid.NewGuid().ToString(), AssertionConsumerServiceURL = this.AssertionConsumerServiceUrl, Destination = this.Destination, IsPassive = true, IssueInstant = DateTime.UtcNow, Issuer = this.RelyingPartyId, ProtocolBinding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", Version = "2.0", NameIDPolicy = new Saml2Schema.AuthnRequestNameIDPolicy() { AllowCreate = true, Format = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" } }; new XmlSerializer(typeof(Saml2Schema.AuthnRequest)).Serialize(writer, request); } return doc; } /// <summary> /// Return the deflated, base64 encoded representation of /// the request. /// </summary> public override string ToString() { using (var output = new MemoryStream()) { using (var zip = new DeflateStream(output, CompressionMode.Compress)) using (var streamWriter = new StreamWriter(zip, new UTF8Encoding(false))) using (var writer = XmlWriter.Create(streamWriter)) { var xml = ToDocument(); if (this.SigningCertificate != null) { var signedXml = new SignedXml(xml.DocumentElement) { SigningKey = this.SigningCertificate.GetRSAPrivateKey() }; signedXml.SignedInfo.SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"; signedXml.SignedInfo.CanonicalizationMethod = "http://www.w3.org/2001/10/xml-exc-c14n#"; // // Sign entire document using "SAML style" transforms. // var reference = new Reference() { Uri = string.Empty }; reference.AddTransform(new XmlDsigEnvelopedSignatureTransform()); reference.AddTransform(new XmlDsigExcC14NTransform()); signedXml.AddReference(reference); // // Embed certificate. // var keyInfo = new KeyInfo(); keyInfo.AddClause(new KeyInfoX509Data(this.SigningCertificate)); signedXml.KeyInfo = keyInfo; // // Add signature after the Issuer element. // signedXml.ComputeSignature(); var issuer = xml.DocumentElement .ChildNodes .Cast<XmlNode>() .OfType<XmlElement>() .First(e => e.Name == "Issuer"); xml.DocumentElement.InsertAfter( signedXml.GetXml(), issuer); } xml.WriteTo(writer); } return Convert.ToBase64String(output.ToArray()); } } } }