/// <summary> /// Initializes a new instance of the <see cref="TokenClaimsPrincipal"/> class. /// </summary> /// <param name="idToken">Token.</param> /// <param name="tokenType">Token type.</param> public TokenClaimsPrincipal(String accessToken, String idToken, String tokenType, String refreshToken) : base() { if (String.IsNullOrEmpty(idToken)) { throw new ArgumentNullException(nameof(idToken)); } else if (String.IsNullOrEmpty(tokenType)) { throw new ArgumentNullException(nameof(tokenType)); } else if (tokenType != "urn:ietf:params:oauth:token-type:jwt" && tokenType != "bearer") { throw new ArgumentOutOfRangeException(nameof(tokenType), "expected urn:ietf:params:oauth:token-type:jwt"); } // Token this.m_idToken = idToken; this.m_accessToken = accessToken; String[] tokenObjects = idToken.Split('.'); // Correct each token to be proper B64 encoding for (int i = 0; i < tokenObjects.Length; i++) { tokenObjects[i] = tokenObjects[i].PadRight(tokenObjects[i].Length + (tokenObjects[i].Length % 4), '=').Replace("===", "="); } JObject headers = JObject.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(tokenObjects[0]))), body = JObject.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(tokenObjects[1]))); // Attempt to get the certificate if (((String)headers["alg"]).StartsWith("RS")) { var cert = SecurityUtils.FindCertificate(X509FindType.FindByThumbprint, StoreLocation.CurrentUser, StoreName.My, headers["x5t"].ToString()); //if (cert == null) // throw new SecurityTokenException(SecurityTokenExceptionType.KeyNotFound, String.Format ("Cannot find certificate {0}", headers ["x5t"])); // TODO: Verify signature } else if (((String)headers["alg"]).StartsWith("HS")) { // TODO: Verify key } // Parse the jwt List <IClaim> claims = new List <IClaim>(); foreach (var kf in body) { String claimName = kf.Key; if (!claimMap.TryGetValue(kf.Key, out claimName)) { claims.AddRange(this.ProcessClaim(kf, kf.Key)); } else { claims.AddRange(this.ProcessClaim(kf, claimName)); } } IClaim expiryClaim = claims.Find(o => o.Type == SanteDBClaimTypes.Expiration), notBeforeClaim = claims.Find(o => o.Type == SanteDBClaimTypes.AuthenticationInstant); if (expiryClaim == null || notBeforeClaim == null) { throw new SecurityTokenException("Missing NBF or EXP claim"); } else { DateTime expiry = expiryClaim.AsDateTime().ToLocalTime(), notBefore = notBeforeClaim.AsDateTime().ToLocalTime(); if (expiry == null || expiry < DateTime.Now) { throw new SecurityTokenException("Token expired"); } else if (notBefore == null || Math.Abs(notBefore.Subtract(DateTime.Now).TotalMinutes) > 3) { throw new SecurityTokenException("Token cannot yet be used (issued in the future)"); } } this.RefreshToken = refreshToken; this.AddIdentity(new SanteDBClaimsIdentity(body["unique_name"]?.Value <String>().ToLower() ?? body["sub"]?.Value <String>().ToLower(), true, "OAUTH", claims)); }
/// <summary> /// Initializes a new instance of the <see cref="SanteDB.DisconnectedClient.Security.TokenClaimsPrincipal"/> class. /// </summary> /// <param name="idToken">Token.</param> /// <param name="tokenType">Token type.</param> public TokenClaimsPrincipal(String accessToken, String idToken, String tokenType, String refreshToken, OpenIdConfigurationInfo configuration) : base() { if (String.IsNullOrEmpty(idToken)) { throw new ArgumentNullException(nameof(idToken)); } else if (String.IsNullOrEmpty(tokenType)) { throw new ArgumentNullException(nameof(tokenType)); } else if ( tokenType != "urn:santedb:session-info" && tokenType != "urn:ietf:params:oauth:token-type:jwt" && tokenType != "bearer") { throw new ArgumentOutOfRangeException(nameof(tokenType), "expected urn:ietf:params:oauth:token-type:jwt"); } // Token this.m_idToken = idToken; this.m_accessToken = accessToken; JObject tokenBody = null; if (tokenType == "urn:santedb:session-info") { tokenBody = JObject.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(idToken))); } else { String[] tokenObjects = idToken.Split('.'); // Correct each token to be proper B64 encoding for (int i = 0; i < tokenObjects.Length; i++) { tokenObjects[i] = tokenObjects[i].PadRight(tokenObjects[i].Length + (tokenObjects[i].Length % 4), '=').Replace("===", "="); } JObject headers = JObject.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(tokenObjects[0]))); tokenBody = JObject.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(tokenObjects[1]))); // Attempt to get the certificate if (((String)headers["alg"]).StartsWith("RS")) { var cert = X509CertificateUtils.FindCertificate(X509FindType.FindByThumbprint, StoreLocation.CurrentUser, StoreName.My, headers["x5t"].ToString()); //if (cert == null) // throw new SecurityTokenException(SecurityTokenExceptionType.KeyNotFound, String.Format ("Cannot find certificate {0}", headers ["x5t"])); // TODO: Verify signature } else if (((String)headers["alg"]).StartsWith("HS")) { // TODO: Verfiy signature using our client secret } } // Parse the jwt List <IClaim> claims = new List <IClaim>(); foreach (var kf in tokenBody) { String claimName = kf.Key; if (!claimMap.TryGetValue(kf.Key, out claimName)) { claims.AddRange(this.ProcessClaim(kf, kf.Key)); } else { claims.AddRange(this.ProcessClaim(kf, claimName)); } } // Validate issuer if (configuration != null && !claims.Any(c => c.Type == "iss" && c.Value == configuration?.Issuer)) { throw new SecurityTokenException(SecurityTokenExceptionType.InvalidIssuer, "Issuer mismatch"); } // Validate validity IClaim expiryClaim = claims.Find(o => o.Type == SanteDBClaimTypes.Expiration), notBeforeClaim = claims.Find(o => o.Type == SanteDBClaimTypes.AuthenticationInstant); if (expiryClaim == null || notBeforeClaim == null) { throw new SecurityTokenException(SecurityTokenExceptionType.InvalidClaim, "Missing NBF or EXP claim"); } else { DateTime expiry = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), notBefore = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); expiry = expiryClaim.AsDateTime(); notBefore = notBeforeClaim.AsDateTime(); if (expiry == null || expiry < DateTime.Now) { throw new SecurityTokenException(SecurityTokenExceptionType.TokenExpired, "Token expired"); } else if (notBefore == null || Math.Abs(notBefore.Subtract(DateTime.Now).TotalMinutes) > 3) { throw new SecurityTokenException(SecurityTokenExceptionType.NotYetValid, $"Token cannot yet be used (issued {notBefore} but current time is {DateTime.Now})"); } } this.RefreshToken = refreshToken; this.m_identities.Clear(); this.m_identities.Add(new SanteDBClaimsIdentity(tokenBody["unique_name"]?.Value <String>() ?? tokenBody["sub"]?.Value <String>(), true, "OAUTH2", claims)); }