function sign_encrypt($payload, $sig, $alg, $enc, $jwks_uri = null, $jwks = null, $client_secret = null, &$cryptoError = null)
{
    global $signing_alg_values_supported, $encryption_alg_values_supported, $encryption_enc_values_supported;
    log_debug("sign_encrypt sig = %s alg = %s enc = %s", $sig, $alg, $enc);
    $jwt = is_array($payload) ? json_encode($payload) : $payload;
    if (isset($sig)) {
        $sig_param = array('alg' => 'none');
        $sig_key = NULL;
        if (in_array($sig, $signing_alg_values_supported)) {
            $sig_param['alg'] = $sig;
            if (substr($sig, 0, 2) == 'HS') {
                $sig_key = $client_secret;
            } elseif (substr($sig, 0, 2) == 'RS') {
                $sig_param['kid'] = OP_SIG_KID;
                $sig_param['jku'] = OP_JWK_URL;
                $sig_key = array('key_file' => OP_SIG_PKEY, 'password' => OP_SIG_PKEY_PASSPHRASE);
            }
        } else {
            log_error("sig alg %s not supported", $sig);
            if ($cryptoError) {
                $cryptoError = 'error_sig';
            }
            return null;
        }
        $jwt = jwt_sign($jwt, $sig_param, $sig_key);
        if (!$jwt) {
            if ($cryptoError) {
                $cryptoError = 'error_sig';
            }
            log_error("Unable to sign payload %s", $jwt);
            return null;
        }
        log_debug('jws = %s', $jwt);
    }
    if (isset($alg) && isset($enc)) {
        if (in_array($alg, $encryption_alg_values_supported) && in_array($enc, $encryption_enc_values_supported)) {
            $jwk_uri = '';
            $encryption_keys = NULL;
            if ($jwks_uri) {
                $jwk = get_url($jwks_uri);
                if ($jwk) {
                    $jwk_uri = $jwks_uri;
                    $encryption_keys = jwk_get_keys($jwk, 'RSA', 'enc', NULL);
                    if (!$encryption_keys || !count($encryption_keys)) {
                        $encryption_keys = NULL;
                    }
                }
            }
            if (!$encryption_keys && !empty($jwks)) {
                $encryption_keys = jwk_get_keys($jwks, 'RSA', 'enc', NULL);
                if (!$encryption_keys || !count($encryption_keys)) {
                    $encryption_keys = NULL;
                }
                $jwk_uri = NULL;
            }
            if (!$encryption_keys) {
                if ($cryptoError) {
                    $cryptoError = 'error_enc';
                }
                log_error("Unable to get enc keys");
                return null;
            }
            if (!empty($jwk_uri)) {
                $header_params = array('jku' => $jwk_uri);
            }
            if (isset($encryption_keys[0]['kid'])) {
                $header_params['kid'] = $encryption_keys[0]['kid'];
            }
            $jwt = jwt_encrypt2($jwt, $encryption_keys[0], false, NULL, $header_params, NULL, $alg, $enc, false);
            if (!$jwt) {
                if ($cryptoError) {
                    $cryptoError = 'error_enc';
                }
                log_error("Unable to encrypt %s", $jwt);
                return null;
            }
            log_debug('jwe = %s', $jwt);
        } else {
            $cryptoError = 'error_enc';
            log_error("encryption algs not supported %s %s", $alg, $enc);
            return null;
        }
    }
    return $jwt;
}
function handle_start()
{
    global $g_error;
    $update = false;
    if ($_REQUEST['submit'] == 'Logout') {
        handle_logout();
        exit;
    }
    unset($_SESSION['id_token']);
    unset($_SESSION['session_state']);
    unset($_SESSION['code_verifier']);
    remember_session_form_options($_REQUEST);
    log_debug("handle_start : %s", print_r($_REQUEST, true));
    $provider = $_REQUEST['provider'];
    $identifier = $_REQUEST['identifier'];
    if (!$provider && !$identifier) {
        $g_error .= "No Identity Provider";
        return;
    }
    if ($identifier) {
        $discovery = webfinger_get_provider_info($identifier);
        if (!$discovery) {
            $g_error .= "Unable to perform discovery";
            return;
        }
        if (!check_client_update_options($_REQUEST, $discovery)) {
            return;
        }
        $db_provider = db_get_provider_by_url($discovery['url']);
        $provider = $db_provider;
        if (!$provider || !(isset($provider['client_id']) && isset($provider['client_secret']))) {
            if (!isset($discovery['registration_endpoint'])) {
                $g_error .= "Provider not found in db for {$discovery['issuer']} and no registration endpoint";
                return;
            }
            $client_options = get_update_options($_REQUEST);
            $client_info = register_client($discovery['registration_endpoint'], $client_options);
            if (!$client_info) {
                $g_error .= "Unable to register client";
                return;
            }
            $provider = array_merge(array('name' => $discovery['issuer'], 'url' => $discovery['url'], 'issuer' => $discovery['issuer'], 'client_id' => $client_info['client_id'], 'client_id_issued_at' => $client_info['client_id_issued_at'], 'client_secret' => $client_info['client_secret'], 'registration_access_token' => $client_info['registration_access_token'], 'registration_client_uri' => $client_info['registration_client_uri'], 'client_secret_expires_at' => $client_info['client_secret_expires_at']), $client_options);
            db_save_provider($discovery['issuer'], $provider);
            $provider = array_merge($provider, $client_info);
            $provider = array_merge($provider, $discovery);
        } else {
            $provider->delete();
            if (!isset($discovery['registration_endpoint'])) {
                $g_error .= "Provider not found in db for {$discovery['issuer']} and no registration endpoint";
                return;
            }
            $client_options = get_update_options($_REQUEST);
            $client_info = register_client($discovery['registration_endpoint'], $client_options);
            if (!$client_info) {
                $g_error .= "Unable to register client";
                return;
            }
            $provider = array_merge(array('name' => $discovery['issuer'], 'url' => $discovery['url'], 'issuer' => $discovery['issuer'], 'client_id' => $client_info['client_id'], 'client_id_issued_at' => $client_info['client_id_issued_at'], 'client_secret' => $client_info['client_secret'], 'registration_access_token' => $client_info['registration_access_token'], 'registration_client_uri' => $client_info['registration_client_uri'], 'client_secret_expires_at' => $client_info['client_secret_expires_at']), $client_options);
            db_save_provider($discovery['issuer'], $provider);
            $provider = array_merge($provider, $client_info);
            $provider = array_merge($provider, $discovery);
        }
    } elseif ($provider) {
        $db_provider = db_get_provider($provider);
        if (!$db_provider) {
            $g_error .= "Unregistered Identity Provider";
            return;
        }
        $p_info = $db_provider->toArray();
        if ($p_info['authorization_endpoint']) {
            $provider = $p_info;
            if (!$provider['client_id'] || !$provider['client_secret']) {
                $client_options = get_update_options($_REQUEST);
                log_debug('update options = %s', print_r($client_options, true));
                $client_info = register_client($provider['registration_endpoint'], $client_options);
                if (!$client_info) {
                    $g_error .= "Unable to register client";
                    return;
                }
                $provider = array_merge(array('client_id' => $client_info['client_id'], 'client_secret' => $client_info['client_secret']), $client_options);
                db_save_provider($db_provider['name'], $provider);
            }
            if ($p_info['name'] == RP_PROTOCOL . 'self-issued.me') {
                $provider['client_id'] = RP_REDIRECT_URI;
                $provider['client_secret'] = '';
            }
        } else {
            $provider_url = $p_info['url'];
            log_info("Provider URL = %s", $provider_url);
            $discovery = webfinger_get_provider_info($p_info['url']);
            if (!check_client_update_options($_REQUEST, $discovery)) {
                return;
            }
            if (!isset($discovery['registration_endpoint'])) {
                $g_error .= "Provider not found in db for {$discovery['issuer']} and no registration endpoint";
                return;
            }
            $client_options = get_update_options($_REQUEST);
            $client_info = register_client($discovery['registration_endpoint'], $client_options);
            log_debug('update options = %s', print_r($client_options, true));
            if (!$client_info) {
                $g_error .= "Unable to register client";
                return;
            }
            $provider = array_merge(array('name' => $discovery['issuer'], 'url' => $discovery['url'], 'issuer' => $discovery['issuer'], 'client_id' => $client_info['client_id'], 'client_id_issued_at' => $client_info['client_id_issued_at'], 'client_secret' => $client_info['client_secret'], 'registration_access_token' => $client_info['registration_access_token'], 'registration_client_uri' => $client_info['registration_client_uri'], 'client_secret_expires_at' => $client_info['client_secret_expires_at']), $client_options);
            $db_provider->delete();
            db_save_provider($discovery['issuer'], $provider);
            $provider = array_merge($provider, $client_info);
            $provider = array_merge($provider, $discovery);
        }
    }
    $_SESSION['provider'] = $provider;
    log_debug('final provider info %s', print_r($provider, true));
    $state = bin2hex(mcrypt_create_iv(16, MCRYPT_DEV_URANDOM));
    $nonce = bin2hex(mcrypt_create_iv(16, MCRYPT_DEV_URANDOM));
    $response_type = '';
    if ($_REQUEST['response_type']) {
        $response_type = $_REQUEST['response_type'];
    }
    if (!$response_type) {
        if ($_REQUEST['response_type1']) {
            $response_type = $_REQUEST['response_type1'];
        }
    }
    if (!$response_type) {
        $g_error = 'No response type';
        return;
    }
    $query_params = array('state' => $state, 'redirect_uri' => RP_REDIRECT_URI, 'response_type' => $response_type, 'client_id' => $provider['client_id'], 'nonce' => $nonce);
    $scope_types = array('openid', 'profile', 'email', 'address', 'phone', 'offline_access');
    $scopes = array();
    foreach ($scope_types as $scope_type) {
        $param_name = 'scope_' . $scope_type;
        if ($_REQUEST[$param_name] == 'on') {
            $scopes[] = $scope_type;
        }
    }
    if (isset($provider['scopes_supported'])) {
        $provider_scopes = $provider['scopes_supported'];
        if (!is_array($provider_scopes)) {
            $provider_scopes = explode(' ', $provider_scopes);
        }
        log_debug('provider scopes = %s', print_r($provider_scopes, true));
        $diff = array_diff($provider_scopes, $scope_types);
        log_debug('diff = %s', print_r($diff, true));
        if (isset($db_provider) && isset($db_provider['authorization_endpoint'])) {
            $provider_scopes = $diff;
        } else {
            $provider_scopes = array();
        }
    } else {
        $provider_scopes = array();
    }
    $unique_scopes = array_unique(array_merge($scopes, $provider_scopes), SORT_STRING);
    $query_params['scope'] = implode(' ', $unique_scopes);
    log_debug('scopes = %s', print_r($query_params['scope'], true));
    $code_verifier = base64url_encode(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM));
    $_SESSION['code_verifier'] = $code_verifier;
    $code_challenge = base64url_encode(hash('sha256', $code_verifier, true));
    $query_params['code_challenge_method'] = 'S256';
    $query_params['code_challenge'] = $code_challenge;
    log_debug("code verifier : %s challenge : %s method : %s", $code_verifier, $code_challenge, $query_params['code_challenge_method']);
    if ($_REQUEST['response_mode']) {
        $query_params['response_mode'] = $_REQUEST['response_mode'];
    }
    if ($_REQUEST['page']) {
        $query_params['page'] = $_REQUEST['page'];
    }
    $prompt_types = array('none', 'login', 'consent', 'select_account');
    $prompt = array();
    foreach ($prompt_types as $prompt_type) {
        $param_name = 'prompt_' . $prompt_type;
        if ($_REQUEST[$param_name] == 'on') {
            $prompt[] = $prompt_type;
        }
    }
    if (count($prompt)) {
        $query_params['prompt'] = implode(' ', $prompt);
    }
    if ($_REQUEST['id_token']) {
        $query_params['id_token_hint'] = $_REQUEST['id_token'];
    }
    if ($_REQUEST['login_hint']) {
        $query_params['login_hint'] = $_REQUEST['login_hint'];
    }
    $custom_params = array();
    if ($_REQUEST['request_option']) {
        //        $custom_query = array();
        if (strstr($_REQUEST['request_option'], 'Custom') !== false) {
            switch ($_REQUEST['request_option']) {
                case 'Custom 19':
                    $custom_params['claims'] = array('userinfo' => array('name' => array('essential' => true)));
                    break;
                case 'Custom 20':
                    $custom_params['claims'] = array('userinfo' => array('email' => NULL, 'picture' => NULL));
                    break;
                case 'Custom 21':
                    $custom_params['claims'] = array('userinfo' => array('name' => array('essential' => true), 'email' => NULL, 'picture' => NULL));
                    break;
                case 'Custom 22':
                    $custom_params['claims'] = array('id_token' => array('auth_time' => array('essential' => true)));
                    break;
                case 'Custom 23':
                    $custom_params['claims'] = array('id_token' => array('acr' => array('values' => array('0', '1', '2'), 'essential' => true)));
                    break;
                case 'Custom 24':
                    $custom_params['claims'] = array('id_token' => array('acr' => array('values' => array('0', '1', '2'))));
                    break;
                case 'Custom 25a':
                    $query_params['max_age'] = 1;
                    break;
                case 'Custom 25b':
                    $query_params['max_age'] = 10;
                    break;
                case 'Custom Dist':
                    $custom_params['claims'] = array('userinfo' => array('name' => array('essential' => true), 'email' => NULL, 'picture' => NULL, 'undergrad_school' => array('essential' => true), 'graduate_school' => array('essential' => true), 'undergrad_degrees' => array('essential' => true), 'graduate_degrees' => array('essential' => true)));
                    break;
                case 'Custom Req 1':
                    $query_params['max_age'] = 1 * 60;
                    $custom_params['claims'] = array('userinfo' => array('name' => array('essential' => true), 'given_name' => array('essential' => true), 'family_name' => array('essential' => true), 'middle_name' => array('essential' => true), 'address' => array('essential' => true), 'nickname' => NULL, 'profile' => NULL, 'given_name#ja-Kana-JP' => NULL, 'given_name#ja_Hani-JP' => NULL), 'id_token' => array('given_name' => array('essential' => true), 'family_name' => array('essential' => true), 'email' => array('essential' => true), 'gender' => array('essential' => true), 'address' => array('essential' => true), 'auth_time' => array('essential' => true)));
                    break;
                case 'Custom Req 2':
                    $query_params['max_age'] = 1 * 60;
                    $custom_params['claims'] = array('userinfo' => array('name' => array('essential' => true)), 'id_token' => array('auth_time' => array('essential' => true)));
                    break;
                default:
                    break;
            }
        }
    }
    $request_method = $_REQUEST['request_method'];
    if ($_REQUEST['request_option']) {
        if ($request_method == 'GET') {
            if (count($custom_params)) {
                $query_params['claims'] = json_encode($custom_params['claims']);
            }
        } else {
            if (strstr($request_method, 'Request File') !== false) {
                $fileid = bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM));
                // $query_params['request_uri'] = RP_INDEX_PAGE . "/reqfile?fileid={$fileid}";
            } else {
                $fileid = NULL;
            }
            $sig_param = array('alg' => 'none');
            if ($_REQUEST['request_object_signing_alg']) {
                $sig_param['alg'] = $_REQUEST['request_object_signing_alg'];
            }
            if (substr($sig_param['alg'], 0, 2) == 'HS') {
                $sig_key = $provider['client_secret'];
            } elseif (substr($sig_param['alg'], 0, 2) == 'RS') {
                $sig_param['jku'] = RP_JWK_URL;
                $sig_param['kid'] = RP_SIG_KID;
                $sig_key = array('key_file' => RP_SIG_PKEY, 'password' => RP_SIG_PKEY_PASSPHRASE);
            }
            log_debug("Request Object Using Sig Alg %s", $sig_param['alg']);
            $request_jwt = jwt_sign(array_merge(array_diff_key($query_params, array('id_token' => 0)), $custom_params), $sig_param, $sig_key);
            if (!$request_jwt) {
                $g_error .= 'Unable to sign request object';
                return;
            }
            if ($_REQUEST['request_object_encrypted_response_alg'] && $_REQUEST['request_object_encrypted_response_enc']) {
                $supported_cek_algs = array('RSA1_5', 'RSA-OAEP');
                $supported_plaintext_algs = array('A128GCM', 'A256GCM', 'A128CBC-HS256', 'A256CBC-HS512');
                if (!in_array($_REQUEST['request_object_encrypted_response_alg'], $supported_cek_algs)) {
                    $g_error .= "Unsupported Request Object CEK Alg";
                    return;
                }
                if (!in_array($_REQUEST['request_object_encrypted_response_enc'], $supported_plaintext_algs)) {
                    $g_error .= "Unsupported Request Object Plaintext Alg";
                    return;
                }
                $jwk_uri = '';
                $encryption_keys = NULL;
                if ($provider['jwks_uri']) {
                    $jwk = get_url($provider['jwks_uri']);
                    if ($jwk) {
                        $jwk_uri = $provider['jwks_uri'];
                        $encryption_keys = jwk_get_keys($jwk, 'RSA', 'enc', NULL);
                        if (!$encryption_keys || !count($encryption_keys)) {
                            $encryption_keys = NULL;
                        }
                    }
                }
                if (!$encryption_keys) {
                    $g_error .= 'No JWK key for encryption';
                    return NULL;
                }
                $header_params = array('jku' => $jwk_uri);
                if (isset($encryption_keys[0]['kid'])) {
                    $header_params['kid'] = $encryption_keys[0]['kid'];
                }
                $encrypted_jwt = jwt_encrypt2($request_jwt, $encryption_keys[0], false, NULL, $header_params, NULL, $_REQUEST['request_object_encrypted_response_alg'], $_REQUEST['request_object_encrypted_response_enc'], false);
                if (!$encrypted_jwt) {
                    $g_error .= "Unable to encrypt request object.";
                    return;
                } else {
                    $request_jwt = $encrypted_jwt;
                }
            } else {
                //            $custom_query['request'] = $request_jwt;
            }
            // if(isset($query_params['request_uri'])) { // save file to db
            if (isset($fileid)) {
                // save file to db
                $query_params['request_uri'] = RP_INDEX_PAGE . "/reqfile?fileid={$fileid}";
                $reqfile = array('type' => $encrypted_jwt ? 1 : 0, 'request' => json_encode(array_merge($query_params, $custom_params)), 'jwt' => $request_jwt);
                log_debug('query_params = %s custom = %s', print_r($query_params, true), print_r($custom_params, true));
                db_save_request_file($fileid, $reqfile);
            } else {
                $query_params['request'] = $request_jwt;
            }
        }
    }
    $url = $provider['authorization_endpoint'] . '?' . http_build_query($query_params);
    log_info("redirect to %s", $url);
    header("Location: {$url}");
    exit;
}