public void FromJson() { //given var json = @"{ ""keys"": [ { ""kty"": ""oct"", ""k"": ""GawgguFyGrWKav7AX4VKUg"" }, { ""kty"": ""RSA"", ""e"": ""AQAB"", ""n"": ""qFZv0pea_jn5Mo4qEUmStuhlulso8n1inXbEotd_zTrQp9K0RK0hf7t0K4BjKVhaiqIam4tVVQvkmYeBeYr1MmnO_0N97dMBz_7fmvyv0hgHaBdQ5mR5u3LTlHo8tjRE7-GzZmGs6jMcyj7HbXobDPQJZpqNy6JjliDVXxW8nWJDetxGBlqmTj1E1fr2RCsZLreDOPSDIedG1upz9RraShsIDzeefOcKibcAaKeeVI3rkAU8_mOauLSXv37hlk0h6sStJb3qZQXyOUkVkjXIkhvNu_ve0v7LiLT4G_OxYGzpOQcCnimKdojzNP6GtVDaMPh-QkSJE32UCos9R3wI2Q"" } ] }"; //when JwkSet test = JwkSet.FromJson(json, JWT.DefaultSettings.JsonMapper); //then Assert.Equal(2, test.Keys.Count); Assert.Equal("oct", test.Keys[0].Kty); Assert.Equal("GawgguFyGrWKav7AX4VKUg", test.Keys[0].K); Assert.Equal("RSA", test.Keys[1].Kty); Assert.Equal("AQAB", test.Keys[1].E); Assert.Equal("qFZv0pea_jn5Mo4qEUmStuhlulso8n1inXbEotd_zTrQp9K0RK0hf7t0K4BjKVhaiqIam4tVVQvkmYeBeYr1MmnO_0N97dMBz_7fmvyv0hgHaBdQ5mR5u3LTlHo8tjRE7-GzZmGs6jMcyj7HbXobDPQJZpqNy6JjliDVXxW8nWJDetxGBlqmTj1E1fr2RCsZLreDOPSDIedG1upz9RraShsIDzeefOcKibcAaKeeVI3rkAU8_mOauLSXv37hlk0h6sStJb3qZQXyOUkVkjXIkhvNu_ve0v7LiLT4G_OxYGzpOQcCnimKdojzNP6GtVDaMPh-QkSJE32UCos9R3wI2Q", test.Keys[1].N); }
public void LinqSearch() { //given Jwk key1 = new Jwk(new byte[] { 25, 172, 32, 130, 225, 114, 26, 181, 138, 106, 254, 192, 95, 133, 74, 82 }); key1.Alg = "sig"; Jwk key2 = new Jwk( e: "AQAB", n: "qFZv0pea_jn5Mo4qEUmStuhlulso8n1inXbEotd_zTrQp9K0RK0hf7t0K4BjKVhaiqIam4tVVQvkmYeBeYr1MmnO_0N97dMBz_7fmvyv0hgHaBdQ5mR5u3LTlHo8tjRE7-GzZmGs6jMcyj7HbXobDPQJZpqNy6JjliDVXxW8nWJDetxGBlqmTj1E1fr2RCsZLreDOPSDIedG1upz9RraShsIDzeefOcKibcAaKeeVI3rkAU8_mOauLSXv37hlk0h6sStJb3qZQXyOUkVkjXIkhvNu_ve0v7LiLT4G_OxYGzpOQcCnimKdojzNP6GtVDaMPh-QkSJE32UCos9R3wI2Q" ); key2.Alg = "enc"; Jwk key3 = new Jwk(crv: "P-256", x: "BHId3zoDv6pDgOUh8rKdloUZ0YumRTcaVDCppUPoYgk", y: "g3QIDhaWEksYtZ9OWjNHn9a6-i_P9o5_NrdISP0VWDU" ); key3.Alg = "enc"; JwkSet keySet = new JwkSet(key1, key2); //when var test = (from key in keySet where key.Alg == "enc" && key.Kty == Jwk.KeyTypes.RSA select key).ToList(); //then Assert.Equal(1, test.Count); Assert.Equal(key2, test[0]); }
/// <summary>constructor</summary> public JwkSetStore() { if (string.IsNullOrEmpty(OAuth2AndOIDCParams.JwkSetUri)) { this._jwkSet = new JwkSet(); } else { // _jwkSet 更新 this._jwkSet = JsonConvert.DeserializeObject <JwkSet>( OAuth2AndOIDCClient.GetJwkSetAsync( new Uri(OAuth2AndOIDCParams.JwkSetUri)).Result); // _dateTime 更新 this._dateTime = DateTime.Now; if (this._jwkSet.keys.Count == 0) { Debug.WriteLine("JwkSet was abnormally initarized with an empty state in JwkSetStore constructor."); } else { Debug.WriteLine("JwkSet was initarized normally in JwkSetStore constructor."); } } }
public void ToDictionary_EmptySet() { //given JwkSet keySet = new JwkSet(); //when var test = keySet.ToDictionary(); //then Assert.Equal(1, test.Count); List <IDictionary <string, object> > list = (List <IDictionary <string, object> >)test["keys"]; Assert.Empty(list); }
static void BuildCompact(CBORObject control, JwkSet keys) { // Encrypted or Signed? if (control.ContainsKey("signing")) { SignMessage sign = new SignMessage(); Signer signer = new Signer(keys[0]); sign.SetContent(control["input"]["payload"].AsString()); sign.AddSigner(signer); CBORObject xx = control["signing"]["protected"]; foreach (CBORObject key in xx.Keys) { signer.AddAttribute(key, xx[key], Attributes.PROTECTED); } string output = sign.EncodeCompressed(); Message msg = Message.DecodeFromString(output); CheckMessage(msg, keys[0], control["input"]); } else if (control.ContainsKey("encrypting_key")) { EncryptMessage enc = new EncryptMessage(); CBORObject xx = control["encrypting_content"]["protected"]; foreach (CBORObject key in xx.Keys) { enc.AddAttribute(key, xx[key], Attributes.PROTECTED); } Recipient recip = new Recipient(keys[0], control["input"]["alg"].AsString(), enc); enc.AddRecipient(recip); enc.SetContent(control["input"]["plaintext"].AsString()); string output = enc.EncodeCompressed(); Message msg = Message.DecodeFromString(output); CheckMessage(msg, keys[0], control["input"]); } }
public void ToJson() { //given JwkSet keySet = new JwkSet(new List <Jwk> { new Jwk(new byte[] { 25, 172, 32, 130, 225, 114, 26, 181, 138, 106, 254, 192, 95, 133, 74, 82 }), new Jwk( e: "AQAB", n: "qFZv0pea_jn5Mo4qEUmStuhlulso8n1inXbEotd_zTrQp9K0RK0hf7t0K4BjKVhaiqIam4tVVQvkmYeBeYr1MmnO_0N97dMBz_7fmvyv0hgHaBdQ5mR5u3LTlHo8tjRE7-GzZmGs6jMcyj7HbXobDPQJZpqNy6JjliDVXxW8nWJDetxGBlqmTj1E1fr2RCsZLreDOPSDIedG1upz9RraShsIDzeefOcKibcAaKeeVI3rkAU8_mOauLSXv37hlk0h6sStJb3qZQXyOUkVkjXIkhvNu_ve0v7LiLT4G_OxYGzpOQcCnimKdojzNP6GtVDaMPh-QkSJE32UCos9R3wI2Q" ) }); //when var test = keySet.ToJson(JWT.DefaultSettings.JsonMapper); //then Console.Out.WriteLine(test); Assert.Equal(@"{""keys"":[{""kty"":""oct"",""k"":""GawgguFyGrWKav7AX4VKUg""},{""kty"":""RSA"",""e"":""AQAB"",""n"":""qFZv0pea_jn5Mo4qEUmStuhlulso8n1inXbEotd_zTrQp9K0RK0hf7t0K4BjKVhaiqIam4tVVQvkmYeBeYr1MmnO_0N97dMBz_7fmvyv0hgHaBdQ5mR5u3LTlHo8tjRE7-GzZmGs6jMcyj7HbXobDPQJZpqNy6JjliDVXxW8nWJDetxGBlqmTj1E1fr2RCsZLreDOPSDIedG1upz9RraShsIDzeefOcKibcAaKeeVI3rkAU8_mOauLSXv37hlk0h6sStJb3qZQXyOUkVkjXIkhvNu_ve0v7LiLT4G_OxYGzpOQcCnimKdojzNP6GtVDaMPh-QkSJE32UCos9R3wI2Q""}]}", test); }
/// <summary>GetJwkObject</summary> /// <param name="kid">string</param> /// <returns>JwkObject</returns> public JObject GetJwkObject(string kid) { try { // リーダーロックを取得 this._rwLock.AcquireReaderLock(Timeout.Infinite); #region 読取 // jwkを返す。 return(JwkSet.GetJwkObject(this._jwkSet, kid)); #endregion } finally { // リーダーロックを解放 this._rwLock.ReleaseReaderLock(); } }
static void Main(string[] args) { // 現在の証明書のJwk JObject jwkObject = JsonConvert.DeserializeObject <JObject>( RS256_KeyConverter.X509CerToJwkPublicKey(OAuth2AndOIDCParams.RS256Cer)); // JwkSet.jsonファイルの存在チェック if (!ResourceLoader.Exists(OAuth2AndOIDCParams.JwkSetFilePath, false)) { // 新規 File.Create(OAuth2AndOIDCParams.JwkSetFilePath).Close(); } else { // 既存? } // JwkSet.jsonファイルのロード JwkSet jwkSetObject = JwkSet.LoadJwkSet(OAuth2AndOIDCParams.JwkSetFilePath); // 判定 if (jwkSetObject == null) { // 新規 jwkSetObject = new JwkSet(); jwkSetObject.keys.Add(jwkObject); } else { // 既存 // kidの重複確認 JwkSet.AddJwkToJwkSet(jwkSetObject, jwkObject); } // jwkSetObjectのセーブ JwkSet.SaveJwkSet(OAuth2AndOIDCParams.JwkSetFilePath, jwkSetObject); }
/// <summary>汎用認証サイトの発行したJWT形式のTokenを検証する。</summary> /// <param name="jwtToken">JWT形式のToken</param> /// <param name="jwtPayload"> /// JWS, JWS + JEWの場合があるのでペイロードを返す。 /// </param> /// <returns>検証結果</returns> public static bool Verify(string jwtToken, out string jwtPayload) { jwtPayload = ""; JWE jwe = null; JWS jws = null; // 復号化(JWEの場合) bool isJWE_FAPI2 = false; if (3 < jwtToken.Split('.').Length) { isJWE_FAPI2 = true; // ヘッダ JWE_Header jweHeader = JsonConvert.DeserializeObject <JWE_Header>( CustomEncode.ByteToString(CustomEncode.FromBase64UrlString(jwtToken.Split('.')[0]), CustomEncode.UTF_8)); if (jweHeader.alg == JwtConst.RSA_OAEP) { jwe = new JWE_RsaOaepAesGcm_X509( CmnClientParams.RsaPfxFilePath, CmnClientParams.RsaPfxPassword); } else if (jweHeader.alg == JwtConst.RSA1_5) { jwe = new JWE_Rsa15A128CbcHS256_X509( CmnClientParams.RsaPfxFilePath, CmnClientParams.RsaPfxPassword); } else { throw new NotSupportedException(string.Format( "This jwe alg of {0} is not supported.", jweHeader.alg)); } jwe.Decrypt(jwtToken, out jwtToken); } else { isJWE_FAPI2 = false; } // 検証 // ヘッダ JWS_Header jwsHeader = JsonConvert.DeserializeObject <JWS_Header>( CustomEncode.ByteToString(CustomEncode.FromBase64UrlString(jwtToken.Split('.')[0]), CustomEncode.UTF_8)); if (jwsHeader.alg == JwtConst.ES256 && isJWE_FAPI2) { } // 正常 else if (jwsHeader.alg == JwtConst.RS256 && !isJWE_FAPI2) { } // 正常 else { throw new NotSupportedException("Unexpected combination of JWS and JWE."); } // 証明書を使用するか、Jwkを使用するか判定 if (string.IsNullOrEmpty(jwsHeader.jku) || string.IsNullOrEmpty(jwsHeader.kid)) { // 旧バージョン(証明書を使用 if (isJWE_FAPI2) { #if NET45 || NET46 throw new NotSupportedException("FAPI2 is not supported in this dotnet version."); #else jws = new JWS_ES256_X509(CmnClientParams.EcdsaCerFilePath, ""); #endif } else { jws = new JWS_RS256_X509(CmnClientParams.RsaCerFilePath, ""); } } else { // 新バージョン(Jwkを使用 if (string.IsNullOrEmpty(OAuth2AndOIDCParams.JwkSetFilePath)) { // jku(jwks_uri)使用のカバレッジ // Client側 JObject jwkObject = JwkSetStore.GetInstance().GetJwkObject(jwsHeader.kid); // チェック if (jwkObject == null) { // 書込 jwkObject = JwkSetStore.GetInstance().SetJwkSetObject(jwsHeader.jku, jwsHeader.kid); } // チェック if (jwkObject == null) { // 証明書を使用 if (isJWE_FAPI2) { #if NET45 || NET46 throw new NotSupportedException("FAPI2 is not supported in this dotnet version."); #else jws = new JWS_ES256_X509(CmnClientParams.EcdsaCerFilePath, ""); #endif } else { jws = new JWS_RS256_X509(CmnClientParams.RsaCerFilePath, ""); } } else { // Jwkを使用 if (isJWE_FAPI2) { #if NET45 || NET46 throw new NotSupportedException("FAPI2 is not supported in this dotnet version."); #else EccPublicKeyConverter epkc = new EccPublicKeyConverter(); jws = new JWS_ES256_Param(epkc.JwkToParam(jwkObject), false); #endif } else { RsaPublicKeyConverter rpkc = new RsaPublicKeyConverter(); jws = new JWS_RS256_Param(rpkc.JwkToParam(jwkObject)); } } } else { // JwkSet使用のカバレッジ // AuthZ側でClient側テストを行うためのカバレージ JObject jwkObject = null; if (ResourceLoader.Exists(OAuth2AndOIDCParams.JwkSetFilePath, false)) { JwkSet jwkSet = JwkSet.LoadJwkSet(OAuth2AndOIDCParams.JwkSetFilePath); jwkObject = JwkSet.GetJwkObject(jwkSet, jwsHeader.kid); } if (jwkObject == null) { // 証明書を使用 if (isJWE_FAPI2) { #if NET45 || NET46 throw new NotSupportedException("FAPI2 is not supported in this dotnet version."); #else jws = new JWS_ES256_X509(CmnClientParams.EcdsaCerFilePath, ""); #endif } else { jws = new JWS_RS256_X509(CmnClientParams.RsaCerFilePath, ""); } } else { // Jwkを使用 if (isJWE_FAPI2) { #if NET45 || NET46 throw new NotSupportedException("FAPI2 is not supported in this dotnet version."); #else EccPublicKeyConverter epkc = new EccPublicKeyConverter(); jws = new JWS_ES256_Param(epkc.JwkToParam(jwkObject), false); #endif } else { RsaPublicKeyConverter rpkc = new RsaPublicKeyConverter(); jws = new JWS_RS256_Param(rpkc.JwkToParam(jwkObject)); } } } } bool ret = jws.Verify(jwtToken); if (ret) { jwtPayload = CustomEncode.ByteToString( CustomEncode.FromBase64UrlString(jwtToken.Split('.')[1]), CustomEncode.us_ascii); } return(ret); }
/// <summary>Unprotect</summary> /// <param name="jwt">JWT文字列</param> /// <returns>AuthenticationTicket</returns> public AuthenticationTicket Unprotect(string jwt) { // 空のケースあり。 if (string.IsNullOrEmpty(jwt)) { return(null); } // 検証 JWS_RS256 jwsRS256 = null; // 証明書を使用するか、Jwkを使用するか判定 Dictionary <string, string> header = JsonConvert.DeserializeObject <Dictionary <string, string> >( CustomEncode.ByteToString(CustomEncode.FromBase64UrlString(jwt.Split('.')[0]), CustomEncode.UTF_8)); if (header.Keys.Any(s => s == JwtConst.kid)) { if (string.IsNullOrEmpty(header[JwtConst.kid])) { // 証明書を使用 jwsRS256 = new JWS_RS256_X509(OAuth2AndOIDCParams.RS256Cer, ""); } else { JwkSet jwkSetObject = JwkSet.LoadJwkSet(OAuth2AndOIDCParams.JwkSetFilePath); JObject jwkObject = JwkSet.GetJwkObject(jwkSetObject, header[JwtConst.kid]); if (jwkObject == null) { // 証明書を使用 jwsRS256 = new JWS_RS256_X509(OAuth2AndOIDCParams.RS256Cer, ""); } else { // Jwkを使用 jwsRS256 = new JWS_RS256_Param( RS256_KeyConverter.JwkToProvider(jwkObject).ExportParameters(false)); } } } if (jwsRS256.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[OAuth2AndOIDCConst.jti]); if (datetime == null) { // authToken.iss, authToken.expの検証 if ((string)authTokenClaimSet[OAuth2AndOIDCConst.iss] == ASPNETIdentityConfig.OAuth2IssuerId && OAuth2Helper.GetInstance().GetClientSecret((string)authTokenClaimSet[OAuth2AndOIDCConst.aud]) != null && long.Parse((string)authTokenClaimSet[OAuth2AndOIDCConst.exp]) >= DateTimeOffset.Now.ToUnixTimeSeconds()) { // authToken.subの検証 // ApplicationUser を取得する。 ApplicationUserManager userManager = HttpContext.Current.GetOwinContext().GetUserManager <ApplicationUserManager>(); ApplicationUser user = userManager.FindByName((string)authTokenClaimSet[OAuth2AndOIDCConst.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[OAuth2AndOIDCConst.scopes]) { scopes.Add(s); } OAuth2Helper.AddClaim(identity, (string)authTokenClaimSet[OAuth2AndOIDCConst.aud], "", scopes, (string) authTokenClaimSet[OAuth2AndOIDCConst.nonce]); // その他、所定のClaimを追加する。 identity.AddClaim(new Claim(OAuth2AndOIDCConst.Claim_ExpirationTime, (string)authTokenClaimSet[OAuth2AndOIDCConst.exp])); identity.AddClaim(new Claim(OAuth2AndOIDCConst.Claim_NotBefore, (string)authTokenClaimSet[OAuth2AndOIDCConst.nbf])); identity.AddClaim(new Claim(OAuth2AndOIDCConst.Claim_IssuedAt, (string)authTokenClaimSet[OAuth2AndOIDCConst.iat])); identity.AddClaim(new Claim(OAuth2AndOIDCConst.Claim_JwtId, (string)authTokenClaimSet[OAuth2AndOIDCConst.jti])); // AuthenticationPropertiesの生成 AuthenticationProperties prop = new AuthenticationProperties(); prop.IssuedUtc = DateTimeOffset.FromUnixTimeSeconds(long.Parse((string)authTokenClaimSet[OAuth2AndOIDCConst.iat])); prop.ExpiresUtc = DateTimeOffset.FromUnixTimeSeconds(long.Parse((string)authTokenClaimSet[OAuth2AndOIDCConst.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[OAuth2AndOIDCConst.sub] == OAuth2Helper.GetInstance().GetClientName((string)authTokenClaimSet[OAuth2AndOIDCConst.aud])) { // ClaimsIdentityを生成し、 ClaimsIdentity identity = new ClaimsIdentity(DefaultAuthenticationTypes.ExternalBearer); // sub(client_idに対応するclient_name)Claimを設定する。 identity.AddClaim(new Claim(ClaimTypes.Name, (string)authTokenClaimSet[OAuth2AndOIDCConst.sub])); // aud、scopes、nonceなどのClaimを追加する。 List <string> scopes = new List <string>(); foreach (string s in (JArray)authTokenClaimSet[OAuth2AndOIDCConst.scopes]) { scopes.Add(s); } OAuth2Helper.AddClaim(identity, (string)authTokenClaimSet[OAuth2AndOIDCConst.aud], "", scopes, (string)authTokenClaimSet[OAuth2AndOIDCConst.nonce]); // その他、所定のClaimを追加する。 identity.AddClaim(new Claim(OAuth2AndOIDCConst.Claim_ExpirationTime, (string)authTokenClaimSet[OAuth2AndOIDCConst.exp])); identity.AddClaim(new Claim(OAuth2AndOIDCConst.Claim_NotBefore, (string)authTokenClaimSet[OAuth2AndOIDCConst.nbf])); identity.AddClaim(new Claim(OAuth2AndOIDCConst.Claim_IssuedAt, (string)authTokenClaimSet[OAuth2AndOIDCConst.iat])); identity.AddClaim(new Claim(OAuth2AndOIDCConst.Claim_JwtId, (string)authTokenClaimSet[OAuth2AndOIDCConst.jti])); // AuthenticationPropertiesの生成 AuthenticationProperties prop = new AuthenticationProperties(); prop.IssuedUtc = DateTimeOffset.FromUnixTimeSeconds(long.Parse((string)authTokenClaimSet[OAuth2AndOIDCConst.iat])); prop.ExpiresUtc = DateTimeOffset.FromUnixTimeSeconds(long.Parse((string)authTokenClaimSet[OAuth2AndOIDCConst.exp])); AuthenticationTicket auth = new AuthenticationTicket(identity, prop); // 認証結果を返す。 return(auth); } } } else { // クレーム検証の失敗 } } else { // 取り消し済み } } else { // JWT署名検証の失敗 } // 検証、認証ナドナド、できなかった。 return(null); }
/// <summary>SetJwkSetObjectAsync</summary> /// <param name="jku">string</param> /// <param name="kid">string</param> /// <returns>JwkObject</returns> public JObject SetJwkSetObject(string jku, string kid) { if (jku != OAuth2AndOIDCParams.JwkSetUri) { // 一致しなかった場合、以下の処理を施しリトライ。 if (jku.EndsWith("/")) { jku = jku.Substring(0, jku.Length - 1); } else { jku = jku + "/"; } if (jku != OAuth2AndOIDCParams.JwkSetUri) { return(null); // 上位で証明書利用へ遷移 } } try { // ライターロックを取得 this._rwLock.AcquireWriterLock(Timeout.Infinite); #region 書込 TimeSpan timeSpan = DateTime.Now.Subtract(this._dateTime); if (timeSpan.TotalSeconds < OAuth2AndOIDCParams.JwkSetUpdateIntervalInSeconds) { // x秒(既定10秒)以内に更新済み ≒ 更新済みと判断。 } else { // x秒(既定10秒)以内に更新済みでない // ≒ 鍵変更後、更新済みでないと判断。 // JwkSetUri string jwkSetString = OAuth2AndOIDCClient.GetJwkSetAsync(new Uri(jku)).Result; if (string.IsNullOrEmpty(jwkSetString)) { // jwkSetStringが空文字列 Debug.WriteLine("JwkSet was not updated, because jwkSetString is null or empty in JwkSetStore.SetJwkSetObject method."); } else { JwkSet jwkSet = JsonConvert.DeserializeObject <JwkSet>(jwkSetString); // _jwkSet 更新 this._jwkSet = jwkSet; // _dateTime 更新 this._dateTime = DateTime.Now; Debug.WriteLine("JwkSet was updated normally in JwkSetStore.SetJwkSetObject method."); } } #endregion } catch (Exception ex) { Debug.WriteLine("Exception was catched in JwkSetStore.SetJwkSetObject method: " + ex.ToString()); } finally { // ライターロックを解放 this._rwLock.ReleaseWriterLock(); } // JwkSetからJwkを返す。 return(JwkSet.GetJwkObject(this._jwkSet, kid)); }