/// <summary>ChangeToIdTokenFromJwt</summary> /// <param name="access_token">Jwt (string)</param> /// <returns>IdToken (string)</returns> public static string ChangeToIdTokenFromJwt(string access_token) { if (access_token.Contains(".")) { string[] temp = access_token.Split('.'); string json = CustomEncode.ByteToString(CustomEncode.FromBase64UrlString(temp[1]), CustomEncode.UTF_8); Dictionary <string, object> authTokenClaimSet = JsonConvert.DeserializeObject <Dictionary <string, object> >(json); // ・access_tokenがJWTで、payloadに"nonce" and "scope=openidクレームが存在する場合、 if (authTokenClaimSet.ContainsKey("nonce") && authTokenClaimSet.ContainsKey("scopes")) { JArray scopes = (JArray)authTokenClaimSet["scopes"]; // ・OpenID Connect : response_type=codeに対応する。 if (scopes.Any(x => x.ToString() == ASPNETIdentityConst.Scope_Openid)) { //・payloadからscopeを削除する。 authTokenClaimSet.Remove("scopes"); //・編集したpayloadを再度JWTとして署名する。 string newPayload = JsonConvert.SerializeObject(authTokenClaimSet); JWT_RS256 jwtRS256 = null; // 署名 jwtRS256 = new JWT_RS256(ASPNETIdentityConfig.OAuthJWT_pfx, ASPNETIdentityConfig.OAuthJWTPassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); string id_token = jwtRS256.Create(newPayload); // 検証 jwtRS256 = new JWT_RS256(ASPNETIdentityConfig.OAuthJWT_cer, ASPNETIdentityConfig.OAuthJWTPassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); if (jwtRS256.Verify(id_token)) { // 検証できた。 return(id_token); } else { // 検証できなかった。 } } else { // OIDCでない。 } } else { // OIDCでない。 } } else { // JWTでない。 } return(""); }
/// <summary> /// ProtectFromPayload /// Hybrid Flow対応(access_token_payloadを処理) /// </summary> /// <param name="access_token_payload">AccessTokenのPayload</param> /// <param name="customExp">Hybrid Flowのtokenに対応したexp</param> /// <returns></returns> public static string ProtectFromPayload(string access_token_payload, ulong customExp) { string json = ""; string jwt = ""; // ticketの値を使用(これは、codeのexpっぽい。300秒になっているのでNG。) //authTokenClaimSet.Add("exp", ticket.Properties.ExpiresUtc.Value.ToUnixTimeSeconds().ToString()); //authTokenClaimSet.Add("exp", DateTimeOffset.Now.AddSeconds(customExp).ToUnixTimeSeconds().ToString()); #region JSON編集 // access_token_payloadのDictionary化 Dictionary <string, object> dic = JsonConvert.DeserializeObject <Dictionary <string, object> >(access_token_payload); // ★ customExpの値を使用する。 dic["exp"] = DateTimeOffset.Now.AddSeconds(customExp).ToUnixTimeSeconds().ToString(); // ★ Hybrid Flow対応なので、scopeを制限してもイイ。 dic["scopes"] = dic["scopes"]; json = JsonConvert.SerializeObject(dic); #endregion #region JWT化 JWT_RS256 jwtRS256 = null; // 署名 jwtRS256 = new JWT_RS256(ASPNETIdentityConfig.OAuthJWT_pfx, ASPNETIdentityConfig.OAuthJWTPassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); jwt = jwtRS256.Create(json); // 検証 jwtRS256 = new JWT_RS256(ASPNETIdentityConfig.OAuthJWT_cer, ASPNETIdentityConfig.OAuthJWTPassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); if (jwtRS256.Verify(jwt)) { return(jwt); // 検証できた。 } else { return(""); // 検証できなかった。 } #endregion }
/// <summary>JWT検証</summary> private void btnJWTVerify_Click(object sender, EventArgs e) { bool ret = false; if (rbnJWTHS256.Checked) { // HS256 // 入力 string[] temp = this.txtJWTSign.Text.Split('.'); // 改変可能なフィールドから入力 string newJWT = CustomEncode.ToBase64UrlString(CustomEncode.StringToByte(this.txtJWTHeader.Text, CustomEncode.UTF_8)) + "." + CustomEncode.ToBase64UrlString(CustomEncode.StringToByte(this.txtJWTPayload.Text, CustomEncode.UTF_8)) + "." + temp[2]; // 検証 JWT_HS256 jwtHS256 = new JWT_HS256(CustomEncode.StringToByte(this.txtJWTKey.Text, CustomEncode.UTF_8)); ret = jwtHS256.Verify(newJWT); } else { // RS256 (X509Cer) // 入力 string[] temp = this.txtJWTSign.Text.Split('.'); // 改変可能なフィールドから入力 string newJWT = CustomEncode.ToBase64UrlString(CustomEncode.StringToByte(this.txtJWTHeader.Text, CustomEncode.UTF_8)) + "." + CustomEncode.ToBase64UrlString(CustomEncode.StringToByte(this.txtJWTPayload.Text, CustomEncode.UTF_8)) + "." + temp[2]; // 検証 JWT_RS256 jwtRS256 = new JWT_RS256(this.CertificateFilePath_cer, ""); ret = jwtRS256.Verify(newJWT); } if (ret) { MessageBox.Show("検証成功"); } else { MessageBox.Show("検証失敗"); } }
/// <summary>JWT生成</summary> private void btnJWTSign_Click(object sender, EventArgs e) { if (rbnJWTHS256.Checked) { // HS256 string password = GetPassword.Generate(20, 10); JWT_HS256 jwtHS256 = new JWT_HS256(CustomEncode.StringToByte(password, CustomEncode.UTF_8)); // 生成 string jwt = jwtHS256.Create(this.txtJWTPayload.Text); // 出力 this.txtJWTKey.Text = password; this.txtJWTJWK.Text = jwtHS256.JWK; this.txtJWTSign.Text = jwt; // 改竄可能なフィールドに出力 string[] temp = jwt.Split('.'); this.txtJWTHeader.Text = CustomEncode.ByteToString( CustomEncode.FromBase64UrlString(temp[0]), CustomEncode.UTF_8); this.txtJWTPayload.Text = CustomEncode.ByteToString( CustomEncode.FromBase64UrlString(temp[1]), CustomEncode.UTF_8); } else { // RS256 (X509Cer) JWT_RS256 jwtRS256 = new JWT_RS256(this.CertificateFilePath_pfx, this.CertificateFilePassword); // 生成 string jwt = jwtRS256.Create(this.txtJWTPayload.Text); // 出力 this.txtJWTSign.Text = jwt; // 改竄可能なフィールドに出力 string[] temp = jwt.Split('.'); this.txtJWTHeader.Text = CustomEncode.ByteToString( CustomEncode.FromBase64UrlString(temp[0]), CustomEncode.UTF_8); this.txtJWTPayload.Text = CustomEncode.ByteToString( CustomEncode.FromBase64UrlString(temp[1]), CustomEncode.UTF_8); } }
/// <summary>Protect</summary> /// <param name="ticket">AuthenticationTicket</param> /// <returns>JWT文字列</returns> public string Protect(AuthenticationTicket ticket) { string json = ""; string jwt = ""; // チェック if (ticket == null) { throw new ArgumentNullException("ticket"); } ApplicationUserManager userManager = HttpContext.Current.GetOwinContext().GetUserManager <ApplicationUserManager>(); ApplicationUser user = userManager.FindByName(ticket.Identity.Name); // 同期版でOK。 #region ClaimSetの生成 Dictionary <string, object> authTokenClaimSet = new Dictionary <string, object>(); List <string> scopes = new List <string>(); List <string> roles = new List <string>(); foreach (Claim c in ticket.Identity.Claims) { if (c.Type == ASPNETIdentityConst.Claim_Issuer) { authTokenClaimSet.Add("iss", c.Value); } else if (c.Type == ASPNETIdentityConst.Claim_Audience) { authTokenClaimSet.Add("aud", c.Value); } else if (c.Type == ASPNETIdentityConst.Claim_Nonce) { authTokenClaimSet.Add("nonce", c.Value); } else if (c.Type == ASPNETIdentityConst.Claim_Scope) { scopes.Add(c.Value); } else if (c.Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/role") { roles.Add(c.Value); } } authTokenClaimSet.Add("sub", ticket.Identity.Name); authTokenClaimSet.Add("iat", ticket.Properties.IssuedUtc.Value.ToUnixTimeSeconds().ToString()); authTokenClaimSet.Add("exp", ticket.Properties.ExpiresUtc.Value.ToUnixTimeSeconds().ToString()); // scope値によって、返す値を変更する。 foreach (string scope in scopes) { switch (scope.ToLower()) { #region OpenID Connect case ASPNETIdentityConst.Scope_Profile: // ・・・ break; case ASPNETIdentityConst.Scope_Email: authTokenClaimSet.Add("email", user.Email); authTokenClaimSet.Add("email_verified", user.EmailConfirmed.ToString()); break; case ASPNETIdentityConst.Scope_Phone: authTokenClaimSet.Add("phone_number", user.PhoneNumber); authTokenClaimSet.Add("phone_number_verified", user.PhoneNumberConfirmed.ToString()); break; case ASPNETIdentityConst.Scope_Address: // ・・・ break; #endregion #region Else case ASPNETIdentityConst.Scope_Userid: authTokenClaimSet.Add(ASPNETIdentityConst.Scope_Userid, user.Id); break; case ASPNETIdentityConst.Scope_Roles: authTokenClaimSet.Add(ASPNETIdentityConst.Scope_Roles, roles); break; #endregion } } authTokenClaimSet.Add("scopes", scopes); json = JsonConvert.SerializeObject(authTokenClaimSet); #endregion #region JWT化 JWT_RS256 jwtRS256 = null; // 署名 jwtRS256 = new JWT_RS256(ASPNETIdentityConfig.OAuthJWT_pfx, ASPNETIdentityConfig.OAuthJWTPassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); jwt = jwtRS256.Create(json); // 検証 jwtRS256 = new JWT_RS256(ASPNETIdentityConfig.OAuthJWT_cer, ASPNETIdentityConfig.OAuthJWTPassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); if (jwtRS256.Verify(jwt)) { return(jwt); // 検証できた。 } else { return(""); // 検証できなかった。 } #endregion }
/// <summary>Unprotect</summary> /// <param name="jwt">JWT文字列</param> /// <returns>AuthenticationTicket</returns> public AuthenticationTicket Unprotect(string jwt) { // 検証 JWT_RS256 jwtRS256 = new JWT_RS256(ASPNETIdentityConfig.OAuthJWT_cer, ASPNETIdentityConfig.OAuthJWTPassword); if (jwtRS256.Verify(jwt)) { // 検証できた。 // デシリアライズ、 string[] temp = jwt.Split('.'); string json = CustomEncode.ByteToString(CustomEncode.FromBase64UrlString(temp[1]), CustomEncode.UTF_8); Dictionary <string, object> authTokenClaimSet = JsonConvert.DeserializeObject <Dictionary <string, object> >(json); // 以下の検証処理 // ★ "iss":"accounts.google.com", // ★ "aud":"クライアント識別子.apps.googleusercontent.com", // ★ "sub":"ユーザーの一意識別子", // ★ "exp":JWT の有効期限(Unix時間) // ☆ "nonce":Implicitで必須 // authToken.iss, authToken.expの検証 if ((string)authTokenClaimSet["iss"] == ASPNETIdentityConfig.OAuthIssuerId && OAuth2ProviderHelper.GetInstance().GetClientSecret((string)authTokenClaimSet["aud"]) != null && long.Parse((string)authTokenClaimSet["exp"]) >= DateTimeOffset.Now.ToUnixTimeSeconds()) { // authToken.subの検証 // ApplicationUser を取得する。 ApplicationUserManager userManager = HttpContext.Current.GetOwinContext().GetUserManager <ApplicationUserManager>(); ApplicationUser user = userManager.FindByName((string)authTokenClaimSet["sub"]); // 同期版でOK。 if (user != null) { // User Accountの場合 // ユーザーに対応するClaimsIdentityを生成する。 ClaimsIdentity identity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ExternalBearer); // ClaimsIdentityに、その他、所定のClaimを追加する。 List <string> scopes = new List <string>(); foreach (string s in (JArray)authTokenClaimSet["scopes"]) { scopes.Add(s); } OAuth2ProviderHelper.AddClaim(identity, (string)authTokenClaimSet["aud"], "", (string)authTokenClaimSet["nonce"], scopes); // AuthenticationPropertiesの生成 AuthenticationProperties prop = new AuthenticationProperties(); // AuthenticationTicketに格納不要 //prop.IssuedUtc = DateTimeOffset.FromUnixTimeSeconds(long.Parse((string)authTokenClaimSet["iat"])); //prop.ExpiresUtc = DateTimeOffset.FromUnixTimeSeconds(long.Parse((string)authTokenClaimSet["exp"])); AuthenticationTicket auth = new AuthenticationTicket(identity, prop); // 認証結果を返す。 return(auth); } else { // Client Accountの場合 // ClaimとStoreのAudienceに対応するSubjectが一致するかを確認し、一致する場合のみ、認証する。 // でないと、UserStoreから削除されたUser Accountが、Client Accountに化けることになる。 if ((string)authTokenClaimSet["sub"] == OAuth2ProviderHelper.GetInstance().GetClientName((string)authTokenClaimSet["aud"])) { // ClaimsIdentityを生成し、 ClaimsIdentity identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType); // ClaimsIdentityに、client_idに対応するclient_nameを設定する。 identity.AddClaim(new Claim(ClaimTypes.Name, (string)authTokenClaimSet["sub"])); // ClaimsIdentityに、その他、所定のClaimを追加する。 List <string> scopes = new List <string>(); foreach (string s in (JArray)authTokenClaimSet["scopes"]) { scopes.Add(s); } OAuth2ProviderHelper.AddClaim(identity, (string)authTokenClaimSet["aud"], "", (string)authTokenClaimSet["nonce"], scopes); // AuthenticationPropertiesの生成 AuthenticationProperties prop = new AuthenticationProperties(); // AuthenticationTicketに格納不要 //prop.IssuedUtc = DateTimeOffset.FromUnixTimeSeconds(long.Parse((string)authTokenClaimSet["iat"])); //prop.ExpiresUtc = DateTimeOffset.FromUnixTimeSeconds(long.Parse((string)authTokenClaimSet["exp"])); AuthenticationTicket auth = new AuthenticationTicket(identity, prop); // 認証結果を返す。 return(auth); } } } } // 検証、認証ナドナド、できなかった。 return(null); }
/// <summary>Unprotect</summary> /// <param name="jwt">JWT文字列</param> /// <returns>AuthenticationTicket</returns> public AuthenticationTicket Unprotect(string jwt) { // 空のケースあり。 if (string.IsNullOrEmpty(jwt)) { return(null); } // 検証 JWT_RS256 jwtRS256 = new JWT_RS256(ASPNETIdentityConfig.OAuthJWT_cer, ASPNETIdentityConfig.OAuthJWTPassword); if (jwtRS256.Verify(jwt)) { // 検証できた。 // デシリアライズ、 string[] temp = jwt.Split('.'); string json = CustomEncode.ByteToString(CustomEncode.FromBase64UrlString(temp[1]), CustomEncode.UTF_8); Dictionary <string, object> authTokenClaimSet = JsonConvert.DeserializeObject <Dictionary <string, object> >(json); // 以下の検証処理 // ★ "iss": accounts.google.com的な, // ★ "aud": client_id(クライアント識別子) // ★ "sub": ユーザーの一意識別子(uname, email) // ★ "exp": JWT の有効期限(Unix時間) // ☆ "jti": JWT のID(OAuth Token Revocation) DateTime?datetime = OAuth2RevocationProvider.GetInstance().Get((string)authTokenClaimSet["jti"]); if (datetime == null) { // authToken.iss, authToken.expの検証 if ((string)authTokenClaimSet["iss"] == ASPNETIdentityConfig.OAuthIssuerId && OAuth2Helper.GetInstance().GetClientSecret((string)authTokenClaimSet["aud"]) != null && long.Parse((string)authTokenClaimSet["exp"]) >= DateTimeOffset.Now.ToUnixTimeSeconds()) { // authToken.subの検証 // ApplicationUser を取得する。 ApplicationUserManager userManager = HttpContext.Current.GetOwinContext().GetUserManager <ApplicationUserManager>(); ApplicationUser user = userManager.FindByName((string)authTokenClaimSet["sub"]); // 同期版でOK。 if (user != null) { // User Accountの場合 // ユーザーに対応するClaimsIdentityを生成し、 ClaimsIdentity identity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ExternalBearer); // aud、scopes、nonceなどのClaimを追加する。 List <string> scopes = new List <string>(); foreach (string s in (JArray)authTokenClaimSet["scopes"]) { scopes.Add(s); } OAuth2Helper.AddClaim(identity, (string)authTokenClaimSet["aud"], "", scopes, (string) authTokenClaimSet["nonce"]); // その他、所定のClaimを追加する。 identity.AddClaim(new Claim(ASPNETIdentityConst.Claim_ExpirationTime, (string)authTokenClaimSet["exp"])); identity.AddClaim(new Claim(ASPNETIdentityConst.Claim_NotBefore, (string)authTokenClaimSet["nbf"])); identity.AddClaim(new Claim(ASPNETIdentityConst.Claim_IssuedAt, (string)authTokenClaimSet["iat"])); identity.AddClaim(new Claim(ASPNETIdentityConst.Claim_JwtId, (string)authTokenClaimSet["jti"])); // AuthenticationPropertiesの生成 AuthenticationProperties prop = new AuthenticationProperties(); prop.IssuedUtc = DateTimeOffset.FromUnixTimeSeconds(long.Parse((string)authTokenClaimSet["iat"])); prop.ExpiresUtc = DateTimeOffset.FromUnixTimeSeconds(long.Parse((string)authTokenClaimSet["exp"])); AuthenticationTicket auth = new AuthenticationTicket(identity, prop); // 認証結果を返す。 return(auth); } else { // Client Accountの場合 // ClaimとStoreのAudience(aud)に対応するSubject(sub)が一致するかを確認し、一致する場合のみ、認証する。 // ※ でないと、UserStoreから削除されたUser Accountが、Client Accountに化けることになる。 if ((string)authTokenClaimSet["sub"] == OAuth2Helper.GetInstance().GetClientName((string)authTokenClaimSet["aud"])) { // ClaimsIdentityを生成し、 ClaimsIdentity identity = new ClaimsIdentity(DefaultAuthenticationTypes.ExternalBearer); // sub(client_idに対応するclient_name)Claimを設定する。 identity.AddClaim(new Claim(ClaimTypes.Name, (string)authTokenClaimSet["sub"])); // aud、scopes、nonceなどのClaimを追加する。 List <string> scopes = new List <string>(); foreach (string s in (JArray)authTokenClaimSet["scopes"]) { scopes.Add(s); } OAuth2Helper.AddClaim(identity, (string)authTokenClaimSet["aud"], "", scopes, (string)authTokenClaimSet["nonce"]); // その他、所定のClaimを追加する。 identity.AddClaim(new Claim(ASPNETIdentityConst.Claim_ExpirationTime, (string)authTokenClaimSet["exp"])); identity.AddClaim(new Claim(ASPNETIdentityConst.Claim_NotBefore, (string)authTokenClaimSet["nbf"])); identity.AddClaim(new Claim(ASPNETIdentityConst.Claim_IssuedAt, (string)authTokenClaimSet["iat"])); identity.AddClaim(new Claim(ASPNETIdentityConst.Claim_JwtId, (string)authTokenClaimSet["jti"])); // AuthenticationPropertiesの生成 AuthenticationProperties prop = new AuthenticationProperties(); prop.IssuedUtc = DateTimeOffset.FromUnixTimeSeconds(long.Parse((string)authTokenClaimSet["iat"])); prop.ExpiresUtc = DateTimeOffset.FromUnixTimeSeconds(long.Parse((string)authTokenClaimSet["exp"])); AuthenticationTicket auth = new AuthenticationTicket(identity, prop); // 認証結果を返す。 return(auth); } } } else { // クレーム検証の失敗 } } else { // 取り消し済み } } else { // JWT署名検証の失敗 } // 検証、認証ナドナド、できなかった。 return(null); }