Exemple #1
0
        /// <summary>Flush</summary>
        public override void Flush()
        {
            byte[] bb = TempBuffer.GetBuffer();

            if (bb != null && bb.Length > 0)
            {
                // 書き換え処理
                Encoding enc     = Response.ContentEncoding;
                string   content = enc.GetString(bb);

                // JSON形式なので、JsonConvertでaccess_tokenを抜き出す。
                Dictionary <string, object> accessTokenResponse = JsonConvert.DeserializeObject <Dictionary <string, object> >(content);

                // access_tokenを
                if (accessTokenResponse.ContainsKey("access_token"))
                {
                    string access_token = (string)accessTokenResponse["access_token"];
                    string id_token     = OidcTokenEditor.ChangeToIdTokenFromAccessToken(access_token, "", HashClaimType.None);
                    if (!string.IsNullOrEmpty(id_token))
                    {
                        // responseにid_tokenとして、このJWTを追加する。
                        accessTokenResponse.Add("id_token", id_token);
                        bb = enc.GetBytes(JsonConvert.SerializeObject(accessTokenResponse));
                    }
                }
            }

            ResponseStream.Write(bb, 0, bb.Length);
            ResponseStream.Flush();
        }
Exemple #2
0
        /// <summary>
        /// ChangeToIdTokenFromAccessToken
        ///   OIDC対応(AccessTokenからIdTokenを生成)
        /// </summary>
        /// <param name="access_token">string</param>
        /// <param name="code">string</param>
        /// <param name="HashClaimType">HashClaimType</param>
        /// <returns>IdToken</returns>
        /// <remarks>
        /// OIDC対応
        /// </remarks>

        public static string ChangeToIdTokenFromAccessToken(string access_token, string code, HashClaimType hct)
        {
            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にat_hash, c_hashを追加する。
                        switch (hct)
                        {
                        case HashClaimType.None:
                            break;

                        case HashClaimType.AtHash:
                            // at_hash
                            authTokenClaimSet.Add(
                                "at_hash",
                                OidcTokenEditor.CreateHash(access_token));
                            break;

                        case HashClaimType.CHash:
                            // c_hash
                            authTokenClaimSet.Add(
                                "c_hash",
                                OidcTokenEditor.CreateHash(code));
                            break;

                        case HashClaimType.Both:
                            // at_hash, c_hash
                            authTokenClaimSet.Add(
                                "at_hash",
                                OidcTokenEditor.CreateHash(access_token));
                            authTokenClaimSet.Add(
                                "c_hash",
                                OidcTokenEditor.CreateHash(code));
                            break;
                        }

                        //・編集した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>OnPreSendRequestHeaders</summary>
        /// <param name="sender">object</param>
        /// <param name="e">EventArgs</param>
        private void OnPreSendRequestHeaders(object sender, EventArgs e)
        {
            // PreSendRequestHeadersのロジックはここに挿入

            #region  数

            #region Context
            HttpApplication application = (HttpApplication)sender;
            HttpContext     context     = application.Context;
            HttpResponse    response    = context.Response;
            #endregion

            #region ワーク

            // code
            string code = "";
            // state
            string state = "";
            // expires_in
            ulong expires_in = 0;
            // redirect_uri
            string redirect_url = "";
            // access_token
            string access_token = "";
            // id_token
            string id_token = "";

            #endregion

            #endregion

            if (context.Request.Url.AbsolutePath.IndexOf(ASPNETIdentityConfig.OAuth2AuthorizeEndpoint2) == -1 &&
                context.Request.Url.AbsolutePath.IndexOf(ASPNETIdentityConfig.OAuth2AuthorizeEndpoint) != -1)
            {
                #region response_type

                if (this.RewritedResponseTypeFrom_IdToken ||
                    this.RewritedResponseTypeFrom_IdTokenToken)
                {
                    // OpenID Connect : Implicit Flowに対応
                    //                  - [response_type=id_token]
                    //                  - or [response_type=id_token token]

                    // レスポンス内容を参照して書き換え(fragmentにid_tokenを追加)
                    string location = response.Headers[OAuth2AndOIDCConst.HttpHeader_Location];

                    if (!string.IsNullOrEmpty(location) &&
                        location.IndexOf("#access_token=") != -1)
                    {
                        string pattern = "";

                        // ・正規表現でaccess_tokenを抜き出す。
                        pattern      = "(\\#access_token=)(?<accessToken>.+?)(\\&)";
                        access_token = Regex.Match(location, pattern).Groups["accessToken"].Value;

                        // ・正規表現でstateを抜き出す。
                        pattern = "&state=(?<state>.+)";
                        state   = Regex.Match(location, pattern).Groups["state"].Value;

                        // at_hash と s_hashを付与
                        id_token = IdToken.ChangeToIdTokenFromAccessToken(
                            access_token, "", state, HashClaimType.AtHash | HashClaimType.SHash, // ★
                            ASPNETIdentityConfig.OAuth2JWT_pfx, ASPNETIdentityConfig.OAuth2JWTPassword);

                        if (!string.IsNullOrEmpty(id_token))
                        {
                            // responseにid_tokenとして、このJWTを追加する。
                            if (this.RewritedResponseTypeFrom_IdTokenToken)
                            {
                                response.Headers[OAuth2AndOIDCConst.HttpHeader_Location] = location + "&id_token=" + id_token;
                            }
                            else if (this.RewritedResponseTypeFrom_IdToken)
                            {
                                // ココは未サポート状態なので、テストできていない。
                                location = location.Replace("access_token=" + access_token + "&", "");
                                location = location.Replace("&token_type=beara", "");
                                response.Headers[OAuth2AndOIDCConst.HttpHeader_Location] = location + "&id_token=" + id_token;
                            }
                        }
                    }
                }
                else if (this.RewritedResponseTypeFrom_CodeIdToken ||
                         this.RewritedResponseTypeFrom_CodeToken ||
                         this.RewritedResponseTypeFrom_CodeIdTokenToken)
                {
                    // OpenID Connect : Hybrid Flowに対応
                    //                  - [response_type=code id_token]
                    //                  - or [response_type=code token]
                    //                  - or [response_type=code id_token token]

                    //レスポンス内容を参照して書き換え(RedirectをFragmentに変更)
                    if (response.StatusCode == 302) // 302 Found
                    {
                        // ・Rewriteした為か、
                        //   何故か、以下が、
                        //     "?code=code値&state=state値&redirect_uri=Urlエンコードされたredirect_uri値"
                        //   となっている。
                        //   ※ redirect_uriがlocationのQueryStringに混じる。
                        //
                        // ・本来は、
                        //     "http(s)://redirect_uri値?code=code値&state=state値"
                        //   を期待していた。
                        string location = response.Headers[OAuth2AndOIDCConst.HttpHeader_Location];

                        string paramStrCode        = "?code=";
                        string paramStrState       = "&state=";
                        string paramStrRedirectUri = "&redirect_uri=";

                        if (location.IndexOf(paramStrCode) != -1 ||
                            location.IndexOf(paramStrState) != -1 ||
                            location.IndexOf(paramStrRedirectUri) != -1)
                        {
                            response.Headers.Remove(OAuth2AndOIDCConst.HttpHeader_Location);

                            // 以下は、"?code=XXX&state=YYY&" という並びが前提。
                            MatchCollection matches = StringChecker.Matches(
                                location,
                                "\\?code=(?<code>.+)&state=(?<state>.+)");

                            foreach (Match match in matches)
                            {
                                GroupCollection groups = match.Groups;
                                code  = groups[OAuth2AndOIDCConst.code].Value;
                                state = groups[OAuth2AndOIDCConst.state].Value;
                            }

                            redirect_url = location.Substring(0, location.IndexOf('?'));

                            // ★ Hybrid Flow対応なので、expを短縮してもイイ。
                            expires_in = ulong.Parse(
                                ASPNETIdentityConfig.OAuth2AccessTokenExpireTimeSpanFromMinutes.TotalSeconds.ToString());

                            // Fragmentに組み込む
                            string fragment = "";

                            if (this.RewritedResponseTypeFrom_CodeIdToken)
                            {
                                // id_tokenを取得
                                string access_token_payload = AuthorizationCodeProvider.GetInstance().GetAccessTokenPayload(code);

                                // c_hash, s_hashを付与
                                id_token = IdToken.ChangeToIdTokenFromAccessToken(
                                    OidcTokenEditor.ProtectFromAccessTokenPayload(access_token_payload, expires_in),
                                    code, state, HashClaimType.CHash | HashClaimType.SHash,
                                    ASPNETIdentityConfig.OAuth2JWT_pfx, ASPNETIdentityConfig.OAuth2JWTPassword);

                                fragment = "#id_token={0}&token_type=Bearer&code={1}&expires_in={2}&state={3}";
                                fragment = string.Format(fragment, new object[] { id_token, code, expires_in, state });
                            }
                            else if (this.RewritedResponseTypeFrom_CodeToken)
                            {
                                // access_tokenを取得
                                string access_token_payload = AuthorizationCodeProvider.GetInstance().GetAccessTokenPayload(code);
                                access_token = OidcTokenEditor.ProtectFromAccessTokenPayload(access_token_payload, expires_in);

                                fragment = "#access_token={0}&token_type=Bearer&code={1}&expires_in={2}&state={3}";
                                fragment = string.Format(fragment, new object[] { access_token, code, expires_in, state });
                            }
                            else if (this.RewritedResponseTypeFrom_CodeIdTokenToken)
                            {
                                // id_token, access_tokenを取得
                                string access_token_payload = AuthorizationCodeProvider.GetInstance().GetAccessTokenPayload(code);
                                access_token = OidcTokenEditor.ProtectFromAccessTokenPayload(access_token_payload, expires_in);

                                // at_hash, c_hash, s_hashを付与
                                id_token = IdToken.ChangeToIdTokenFromAccessToken(access_token, code, state,
                                                                                  HashClaimType.AtHash | HashClaimType.CHash | HashClaimType.SHash,
                                                                                  ASPNETIdentityConfig.OAuth2JWT_pfx, ASPNETIdentityConfig.OAuth2JWTPassword);

                                fragment = "#access_token={0}&id_token={1}&token_type=Bearer&code={2}&expires_in={3}&state={4}";
                                fragment = string.Format(fragment, new object[] { access_token, id_token, code, expires_in, state });
                            }

                            // Locationを追加(redirect_url + fragment)。
                            response.Headers.Add(OAuth2AndOIDCConst.HttpHeader_Location, redirect_url + fragment);
                        }
                    }
                }

                #endregion

                #region response_mode

                if (this.RewritedResponseModeFrom_FromPost)
                {
                    // OAuth2.0, OpenID Connect : response_mode=form_postに対応

                    //レスポンス内容を参照して書き換え
                    //(Redirect(get)をAuto-Submit Form(post)に変更)
                    if (response.StatusCode == 302) // 302 Found
                    {
                        // ・Rewriteした為か、
                        //   何故か、以下が、
                        //     "?code=code値&state=state値&redirect_uri=Urlエンコードされたredirect_uri値"
                        //   となっている。
                        //   ※ redirect_uriがlocationのQueryStringに混じる。
                        //
                        // ・本来は、
                        //     "http(s)://redirect_uri値?code=code値&state=state値"
                        //   を期待していた。
                        string location = response.Headers[OAuth2AndOIDCConst.HttpHeader_Location];

                        string paramStrCode        = "?code=";
                        string paramStrState       = "&state=";
                        string paramStrRedirectUri = "&redirect_uri=";

                        if (location.IndexOf(paramStrCode) != -1 ||
                            location.IndexOf(paramStrState) != -1 ||
                            location.IndexOf(paramStrRedirectUri) != -1)
                        {
                            // 302 Found ---> 200 OK
                            response.StatusCode = 200;
                            response.Headers.Remove(OAuth2AndOIDCConst.HttpHeader_Location);

                            // 以下は、"?code=XXX&state=YYY&redirect_uri=ZZZ" という並びが前提。
                            MatchCollection matches = StringChecker.Matches(
                                location,
                                "\\?code=(?<code>.+)&state=(?<state>.+)&redirect_uri=(?<redirect_uri>.+)");

                            foreach (Match match in matches)
                            {
                                GroupCollection groups = match.Groups;
                                code         = groups[OAuth2AndOIDCConst.code].Value;
                                state        = groups[OAuth2AndOIDCConst.state].Value;
                                redirect_url = CustomEncode.UrlDecode(groups[OAuth2AndOIDCConst.redirect_uri].Value);
                            }

                            // form_postに必要な、HTTP response message body
                            string body =
                                "<html>" +
                                "  <body onload=\"javascript: document.forms[0].submit()\">" +
                                "    <form method=\"post\" action =\"{0}\">" +
                                "      <input type=\"hidden\" name =\"code\" value =\"{1}\"/>" +
                                "      <input type=\"hidden\" name =\"state\"  value =\"{2}\"/>" +
                                "    </form>" +
                                "  </body>" +
                                "</html>";

                            // bodyに組み込んで
                            body = string.Format(body, redirect_url, code, state);

                            // HTTP Responseに書き出し
                            byte[] buffer = response.ContentEncoding.GetBytes(body);
                            response.OutputStream.Write(buffer, 0, buffer.Length);
                        }
                    }
                }

                #endregion

                if (this.RewritedResponseTypeFrom_IdToken ||
                    this.RewritedResponseTypeFrom_IdTokenToken ||
                    this.RewritedResponseTypeFrom_CodeIdToken ||
                    this.RewritedResponseTypeFrom_CodeToken ||
                    this.RewritedResponseTypeFrom_CodeIdTokenToken)
                //|| this.RewritedResponseModeFrom_FromPost)
                {
                    context.RewritePath(this.OriginalVirtualPath);
                }

                #region 再利用されるinstance memberの初期化を忘れずに!!
                //      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                this.OriginalVirtualPath = "";

                this.RewritedResponseTypeFrom_IdTokenToken = false;
                this.RewritedResponseTypeFrom_IdToken      = false;

                this.RewritedResponseTypeFrom_CodeIdToken      = false;
                this.RewritedResponseTypeFrom_CodeToken        = false;
                this.RewritedResponseTypeFrom_CodeIdTokenToken = false;

                this.RewritedResponseModeFrom_FromPost = false;

                #endregion
            }
        }