diff options
Diffstat (limited to 'src/DotNetOpenAuth.InfoCard/InfoCard/Token/TokenUtility.cs')
-rw-r--r-- | src/DotNetOpenAuth.InfoCard/InfoCard/Token/TokenUtility.cs | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.InfoCard/InfoCard/Token/TokenUtility.cs b/src/DotNetOpenAuth.InfoCard/InfoCard/Token/TokenUtility.cs new file mode 100644 index 0000000..4ac871a --- /dev/null +++ b/src/DotNetOpenAuth.InfoCard/InfoCard/Token/TokenUtility.cs @@ -0,0 +1,297 @@ +//----------------------------------------------------------------------- +// <copyright file="TokenUtility.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. All rights reserved. +// </copyright> +// <license> +// Microsoft Public License (Ms-PL). +// See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL +// </license> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.InfoCard { + using System; + using System.Collections.Generic; + using System.Configuration; + using System.Diagnostics.Contracts; + using System.IdentityModel.Claims; + using System.IdentityModel.Policy; + using System.IdentityModel.Selectors; + using System.IdentityModel.Tokens; + using System.IO; + using System.Linq; + using System.Net.Mail; + using System.Security.Cryptography; + using System.Security.Principal; + using System.ServiceModel.Security; + using System.Text; + using System.Xml; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Tools for reading InfoCard tokens. + /// </summary> + internal static class TokenUtility { + /// <summary> + /// Gets the maximum amount the token can be out of sync with time. + /// </summary> + internal static TimeSpan MaximumClockSkew { + get { return DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Configuration.Messaging.MaximumClockSkew; } + } + + /// <summary> + /// Token Authentication. Translates the decrypted data into a AuthContext. + /// </summary> + /// <param name="reader">The token XML reader.</param> + /// <param name="audience">The audience that the token must be scoped for. + /// Use <c>null</c> to indicate any audience is acceptable.</param> + /// <returns> + /// The authorization context carried by the token. + /// </returns> + internal static AuthorizationContext AuthenticateToken(XmlReader reader, Uri audience) { + Contract.Ensures(Contract.Result<AuthorizationContext>() != null); + + // Extensibility Point: + // in order to accept different token types, you would need to add additional + // code to create an authenticationcontext from the security token. + // This code only supports SamlSecurityToken objects. + SamlSecurityToken token = WSSecurityTokenSerializer.DefaultInstance.ReadToken(reader, null) as SamlSecurityToken; + + if (null == token) { + throw new InformationCardException("Unable to read security token"); + } + + ////if (null != token.SecurityKeys && token.SecurityKeys.Count > 0) + //// throw new InformationCardException("Token Security Keys Exist"); + + if (audience == null) { + Logger.InfoCard.Warn("SAML token Audience checking will be skipped."); + } else { + if (token.Assertion.Conditions != null && + token.Assertion.Conditions.Conditions != null) { + foreach (SamlCondition condition in token.Assertion.Conditions.Conditions) { + SamlAudienceRestrictionCondition audienceCondition = condition as SamlAudienceRestrictionCondition; + + if (audienceCondition != null) { + Logger.InfoCard.DebugFormat("SAML token audience(s): {0}", audienceCondition.Audiences.ToStringDeferred()); + bool match = audienceCondition.Audiences.Contains(audience); + + if (!match && Logger.InfoCard.IsErrorEnabled) { + Logger.InfoCard.ErrorFormat("Expected SAML token audience of {0} but found {1}.", audience.AbsoluteUri, audienceCondition.Audiences.Select(aud => aud.AbsoluteUri).ToStringDeferred()); + } + + // The token is invalid if any condition is not valid. + // An audience restriction condition is valid if any audience + // matches the Relying Party. + ErrorUtilities.VerifyInfoCard(match, InfoCardStrings.AudienceMismatch); + } + } + } + } + var samlAuthenticator = new SamlSecurityTokenAuthenticator( + new List<SecurityTokenAuthenticator>( + new SecurityTokenAuthenticator[] { + new RsaSecurityTokenAuthenticator(), + new X509SecurityTokenAuthenticator(), + }), + MaximumClockSkew); + + return AuthorizationContext.CreateDefaultAuthorizationContext(samlAuthenticator.ValidateToken(token)); + } + + /// <summary> + /// Translates claims to strings + /// </summary> + /// <param name="claim">Claim to translate to a string</param> + /// <returns>The string representation of a claim's value.</returns> + internal static string GetResourceValue(Claim claim) { + string strClaim = claim.Resource as string; + if (!string.IsNullOrEmpty(strClaim)) { + return strClaim; + } + + IdentityReference reference = claim.Resource as IdentityReference; + if (null != reference) { + return reference.Value; + } + + ICspAsymmetricAlgorithm rsa = claim.Resource as ICspAsymmetricAlgorithm; + if (null != rsa) { + using (SHA256 sha = new SHA256Managed()) { + return Convert.ToBase64String(sha.ComputeHash(rsa.ExportCspBlob(false))); + } + } + + MailAddress mail = claim.Resource as MailAddress; + if (null != mail) { + return mail.ToString(); + } + + byte[] bufferValue = claim.Resource as byte[]; + if (null != bufferValue) { + return Convert.ToBase64String(bufferValue); + } + + return claim.Resource.ToString(); + } + + /// <summary> + /// Generates a UniqueID based off the Issuer's key + /// </summary> + /// <param name="authzContext">the Authorization Context</param> + /// <returns>the hash of the internal key of the issuer</returns> + internal static string GetIssuerPubKeyHash(AuthorizationContext authzContext) { + foreach (ClaimSet cs in authzContext.ClaimSets) { + Claim currentIssuerClaim = GetUniqueRsaClaim(cs.Issuer); + + if (currentIssuerClaim != null) { + RSA rsa = currentIssuerClaim.Resource as RSA; + if (null == rsa) { + return null; + } + + return ComputeCombinedId(rsa, ""); + } + } + + return null; + } + + /// <summary> + /// Generates a UniqueID based off the Issuer's key and the PPID. + /// </summary> + /// <param name="authzContext">The Authorization Context</param> + /// <returns>A unique ID for this user at this web site.</returns> + internal static string GetUniqueName(AuthorizationContext authzContext) { + Contract.Requires<ArgumentNullException>(authzContext != null); + + Claim uniqueIssuerClaim = null; + Claim uniqueUserClaim = null; + + foreach (ClaimSet cs in authzContext.ClaimSets) { + Claim currentIssuerClaim = GetUniqueRsaClaim(cs.Issuer); + + foreach (Claim c in cs.FindClaims(ClaimTypes.PPID, Rights.PossessProperty)) { + if (null == currentIssuerClaim) { + // Found a claim in a ClaimSet with no RSA issuer. + return null; + } + + if (null == uniqueUserClaim) { + uniqueUserClaim = c; + uniqueIssuerClaim = currentIssuerClaim; + } else if (!uniqueIssuerClaim.Equals(currentIssuerClaim)) { + // Found two of the desired claims with different + // issuers. No unique name. + return null; + } else if (!uniqueUserClaim.Equals(c)) { + // Found two of the desired claims with different + // values. No unique name. + return null; + } + } + } + + // No claim of the desired type was found + if (null == uniqueUserClaim) { + return null; + } + + // Unexpected resource type + string claimValue = uniqueUserClaim.Resource as string; + if (null == claimValue) { + return null; + } + + // Unexpected resource type for RSA + RSA rsa = uniqueIssuerClaim.Resource as RSA; + if (null == rsa) { + return null; + } + + return ComputeCombinedId(rsa, claimValue); + } + + /// <summary> + /// Generates the Site Specific ID to match the one in the Identity Selector. + /// </summary> + /// <value>The ID displayed by the Identity Selector.</value> + /// <param name="ppid">The personal private identifier.</param> + /// <returns>A string containing the XXX-XXXX-XXX cosmetic value.</returns> + internal static string CalculateSiteSpecificID(string ppid) { + Contract.Requires<ArgumentNullException>(ppid != null); + Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>())); + + int callSignChars = 10; + char[] charMap = "QL23456789ABCDEFGHJKMNPRSTUVWXYZ".ToCharArray(); + int charMapLength = charMap.Length; + + byte[] raw = Convert.FromBase64String(ppid); + using (HashAlgorithm hasher = SHA1.Create()) { + raw = hasher.ComputeHash(raw); + } + + StringBuilder callSign = new StringBuilder(); + + for (int i = 0; i < callSignChars; i++) { + // after char 3 and char 7, place a dash + if (i == 3 || i == 7) { + callSign.Append('-'); + } + callSign.Append(charMap[raw[i] % charMapLength]); + } + return callSign.ToString(); + } + + /// <summary> + /// Gets the Unique RSA Claim from the SAML token. + /// </summary> + /// <param name="cs">the claimset which contains the claim</param> + /// <returns>a RSA claim</returns> + private static Claim GetUniqueRsaClaim(ClaimSet cs) { + Contract.Requires<ArgumentNullException>(cs != null); + + Claim rsa = null; + + foreach (Claim c in cs.FindClaims(ClaimTypes.Rsa, Rights.PossessProperty)) { + if (null == rsa) { + rsa = c; + } else if (!rsa.Equals(c)) { + // Found two non-equal RSA claims + return null; + } + } + return rsa; + } + + /// <summary> + /// Does the actual calculation of a combined ID from a value and an RSA key. + /// </summary> + /// <param name="issuerKey">The key of the issuer of the token</param> + /// <param name="claimValue">the claim value to hash with.</param> + /// <returns>A base64 representation of the combined ID.</returns> + private static string ComputeCombinedId(RSA issuerKey, string claimValue) { + Contract.Requires<ArgumentNullException>(issuerKey != null); + Contract.Requires<ArgumentNullException>(claimValue != null); + Contract.Ensures(Contract.Result<string>() != null); + + int nameLength = Encoding.UTF8.GetByteCount(claimValue); + RSAParameters rsaParams = issuerKey.ExportParameters(false); + byte[] shaInput; + byte[] shaOutput; + + int i = 0; + shaInput = new byte[rsaParams.Modulus.Length + rsaParams.Exponent.Length + nameLength]; + rsaParams.Modulus.CopyTo(shaInput, i); + i += rsaParams.Modulus.Length; + rsaParams.Exponent.CopyTo(shaInput, i); + i += rsaParams.Exponent.Length; + i += Encoding.UTF8.GetBytes(claimValue, 0, claimValue.Length, shaInput, i); + + using (SHA256 sha = SHA256.Create()) { + shaOutput = sha.ComputeHash(shaInput); + } + + return Convert.ToBase64String(shaOutput); + } + } +} |