diff --git a/.github/workflows/fapi-oidc-conformance-test.yml b/.github/workflows/fapi-oidc-conformance-test.yml new file mode 100644 index 0000000000..f18f916561 --- /dev/null +++ b/.github/workflows/fapi-oidc-conformance-test.yml @@ -0,0 +1,222 @@ +# This workflow will test IS for OIDC FAPI conformance + +name: FAPI-OIDC-Conformance-Test + +on: + schedule: + # Everyday at 08:30 UTC (2:00 AM SL time) + - cron: '30 20 * * *' + # Allows the workflow to run automatically after a release + release: + types: [published] + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + inputs: + tag: + description: 'Product-is release tag (Ex: v7.0.0-alpha2). If not provided, latest release tag is used.' + required: false + conformance-suite-version: + description: 'Conformance suite branch to clone in https://gitlab.com/openid/conformance-suite.git (Ex: release-v5.1.10). If not provided, latest release tag branch is used.' + required: false + +jobs: + build: + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + path: './product-is' + + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11.0.18+10 + + - name: Setup Python + run: | + python3 -m pip install --upgrade pip setuptools wheel + pip3 install psutil + pip3 install httpx + pip3 install httplib2 + + - name: Get IS zip + run: | + INPUT_TAG=${{github.event.inputs.tag}} + if [[ -z "${INPUT_TAG}" ]]; then + echo ">>> Building IS from source..." + mkdir cloned-product-is + cd cloned-product-is + git clone https://github.com/wso2/product-is + cd product-is + mvn clean install -Dmaven.test.skip=true | tee mvn-build.log + + REPO_BUILD_STATUS=$(cat mvn-build.log | grep "\[INFO\] BUILD" | grep -oE '[^ ]+$') + echo "===========================================================" + echo "BUILD $REPO_BUILD_STATUS" + echo "==========================================================" + + if [[ "${REPO_BUILD_STATUS}" != "SUCCESS" ]]; then + exit 1 + fi + + zip_file=$(find . -name 'wso2is-*.zip' -type f -not -name 'wso2is-*-src.zip' -print -quit) + + if [[ -z "$zip_file" ]]; then + echo "Zip file not found" + exit 1 + fi + + echo ">>> Zip file found: $zip_file" + echo ">>> Copying zip file to the root directory ..." + + cp "$zip_file" ./../../ + cd ../.. + ls + echo ">>> Remove cloned-product-is directory" + rm -rf cloned-product-is + ls + else + owner="wso2" + repo="product-is" + if [[ -z "${INPUT_TAG}" ]]; then + tag=${GITHUB_REF:10} + tag_trimmed=${tag// } + else + tag=${{github.event.inputs.tag}} + tag_trimmed=${tag// } + fi + + artifact="wso2is-${tag_trimmed:1}.zip" + echo "Tag=$tag" + echo "Artifact=$artifact" + list_asset_url="https://api.github.com/repos/${owner}/${repo}/releases/tags/${tag_trimmed}" + asset_url=$(curl "${list_asset_url}" | jq ".assets[] | select(.name==\"${artifact}\") | .url" | sed 's/\"//g') + curl -vLJO -H 'Accept: application/octet-stream' \ + "${asset_url}" + fi + + - name: Add deployment toml configs to IS + run: | + PRODUCT_IS_ZIP=$(find ./ -name 'wso2is*' -type f -printf "%f\n") + echo ">>> Unzipping Product IS: ${PRODUCT_IS_ZIP} ..." + unzip -qq ${PRODUCT_IS_ZIP} + + PRODUCT_IS=$(find ./ -maxdepth 1 -name 'wso2is*' -type d -printf "%f\n") + + echo ">>> Adding deployment-fapi-config.toml configs to deployment.toml..." + cp -f ./product-is/oidc-fapi-conformance-tests/config/deployment-fapi-config.toml $PRODUCT_IS/repository/conf/deployment.toml + + echo ">>> Zipping $PRODUCT_IS to $PRODUCT_IS_ZIP" + zip -qq -r $PRODUCT_IS_ZIP $PRODUCT_IS + rm -r $PRODUCT_IS + + - name: Clone conformance suite + run: | + sudo snap install jq + LATEST_RELEASE_BRANCH=$(curl -s https://gitlab.com/api/v4/projects/4175605/releases/ | jq '.[]' | jq -r '.name' | head -1) + echo ">>> Conformance suite latest release branch: $LATEST_RELEASE_BRANCH" + PROVIDED_VERSION=${{github.event.inputs.conformance-suite-version}} + if [[ -z "${PROVIDED_VERSION}" ]]; then + CONFORMANCE_SUITE_BRANCH=$LATEST_RELEASE_BRANCH + echo ">>> Conformance suite latest release branch is taken: $CONFORMANCE_SUITE_BRANCH" + else + CONFORMANCE_SUITE_BRANCH=$PROVIDED_VERSION + echo ">>> Conformance suite provided branch is taken: $CONFORMANCE_SUITE_BRANCH" + fi + echo ">>> Selected conformance suite branch: $CONFORMANCE_SUITE_BRANCH" + git clone --depth 1 --branch ${CONFORMANCE_SUITE_BRANCH} https://gitlab.com/openid/conformance-suite.git + + - name: Adding extra hosts to docker-compose.yml and adding iam as a localhost to /etc/hosts + run: | + sed -i '/^ volumes.*/i \ \ \ \ extra_hosts:\n \ \ \ \ - "localhost:\$IP\"\n \ \ \ \ - "iam:\$IP\"' ./conformance-suite/docker-compose-dev.yml + sed -i '/^ volumes.*/i \ \ \ \ extra_hosts:\n \ \ \ \ - "localhost:\$IP\"\n \ \ \ \ - "iam:\$IP\"' ./conformance-suite/docker-compose.yml + sudo -- sh -c -e "echo '127.0.1.1 iam' >> /etc/hosts" + sudo -- sh -c -e "echo '127.0.1.1 www.iam.com' >> /etc/hosts" + + - name: Start FAPI resource server + run: | + sudo apt update + sudo apt install nginx + echo ">>> NGINX installed successfully !" + sudo chmod 777 -R /etc/nginx + echo ">>> Permission changed for /etc/nginx" + mkdir -p /etc/nginx/ssl + echo ">>> /etc/nginx/ssl created successfully!" + + sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/serverCA.key -out /etc/nginx/ssl/serverCA.crt -subj "/C=US/ST=California/L=San Francisco/O=My Company/OU=IT Department/CN=mycompany.com" + echo ">>> FAPI resource server keys saved successfully!" + + cd product-is/oidc-fapi-conformance-tests/resource-server + cp -f nginx-proxy /etc/nginx/sites-enabled + sudo nginx -t + sudo service nginx restart + echo ">>> NGINX reverse proxy server started successfully!" + + pip install virtualenv + python3 -m virtualenv venv + source ./venv/bin/activate + pip install -r requirements.txt + echo ">>> FAPI resource server starting..." + . ./venv/bin/activate + nohup python3 resource-server.py > resource-server.log & + sleep 5 + cat resource-server.log + + - name: Configure IS and Conformance Suite and run IS + run: | + PRODUCT_IS_ZIP=$(find ./ -name wso2is* -type f -printf "%f\n") + cd ./product-is/oidc-fapi-conformance-tests + python3 ./configure_is_fapi.py ../../$PRODUCT_IS_ZIP + + - name: Start Conformance Suite server + run: | + DOCKER_COMPOSE_FILE=./docker-compose.yml + cd conformance-suite + IP=$(/sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1) + export IP + echo ">>> Host ip: " + printenv IP + mvn clean package + python3 ../product-is/oidc-fapi-conformance-tests/start_conformance_suite.py $DOCKER_COMPOSE_FILE + + - name: Run Tests + run: bash ./product-is/oidc-fapi-conformance-tests/test_runner_fapi.sh + + - name: Test Results + run: | + IS_SUCCESSFUL=false + if python3 ./product-is/oidc-fapi-conformance-tests/export_results_fapi.py https://localhost:8443 + then + IS_SUCCESSFUL=true + fi + if $IS_SUCCESSFUL + then + echo "======================" + echo "All Test Cases Passed!" + echo "======================" + exit 0 + else + echo "=============================================" + echo "Failed Test Cases Found. Exiting with Failure" + echo "=============================================" + exit 1 + fi + + - name: Archive test results + uses: actions/upload-artifact@v2 + if: always() + with: + name: test-results + path: ./*test_results.zip + + - name: Archive test logs + uses: actions/upload-artifact@v2 + if: always() + with: + name: test-logs + path: ./*log.txt + + + + diff --git a/oidc-fapi-conformance-tests/config/browser_configuration.py b/oidc-fapi-conformance-tests/config/browser_configuration.py new file mode 100644 index 0000000000..650bf4e2fa --- /dev/null +++ b/oidc-fapi-conformance-tests/config/browser_configuration.py @@ -0,0 +1,243 @@ + +import constants_fapi as constants + +VERIFY_ERROR = { + "task": "Verify error page", + "match": constants.BASE_URL + "/authenticationendpoint/oauth2_error.do*", + "commands": [ + [ + "wait", + "xpath", + "//*", + 10, + "Need help\? Contact us via", + "update-image-placeholder" + ] + ] +} + +LOGIN_BASIC = { + "task": "Login", + "match": constants.BASE_URL + "/authenticationendpoint/login*", + "optional": True, + "commands": [ + [ + "text", + "id", + "usernameUserInput", + "admin" + ], + [ + "text", + "id", + "password", + "admin" + ], + [ + "click", + "xpath", + "//*[@id=\"loginForm\"]/div[8]/div[1]/button" + ], + [ + "wait", + "contains", + "fapi-wso2is/callback", + 10 + ] + ] +} + +LOGIN_BEFORE_CONSENT = { + "task": "Login", + "match": constants.BASE_URL + "/authenticationendpoint/login*", + "optional": True, + "commands": [ + [ + "text", + "id", + "usernameUserInput", + "admin" + ], + [ + "text", + "id", + "password", + "admin" + ], + [ + "click", + "xpath", + "//*[@id=\"loginForm\"]/div[8]/div[1]/button" + ], + [ + "wait", + "contains", + "/authenticationendpoint/oauth2_consent", + 10 + ] + ] +} + +CONSENT_APPROVE = { + "task": "Consent", + "match": constants.BASE_URL + "/authenticationendpoint/oauth2_consent*", + "optional": True, + "commands": [ + [ + "wait", + "id", + "approve", + 10 + ], + [ + "click", + "id", + "approve" + ], + [ + "wait", + "contains", + "callback", + 10 + ] + ] +} + +LOGIN_BEFORE_CONSENT_USER1 = { + "task": "Login", + "match": constants.BASE_URL + "/authenticationendpoint/login*", + "optional": True, + "commands": [ + [ + "text", + "id", + "usernameUserInput", + "user1" + ], + [ + "text", + "id", + "password", + "User1@password" + ], + [ + "click", + "xpath", + "//*[@id=\"loginForm\"]/div[8]/div[1]/button" + ], + [ + "wait", + "contains", + "/authenticationendpoint/oauth2_consent", + 10 + ] + ] +} + +CONSENT_DENY = { + "task": "Consent", + "match": constants.BASE_URL + "/authenticationendpoint/oauth2_consent*", + "optional": True, + "commands": [ + [ + "wait", + "id", + "consent", + 10 + ], + [ + "click", + "xpath", + "//*[@id=\"profile\"]/div[2]/div[5]/input[2]" + ], + [ + "wait", + "contains", + "callback", + 10 + ] + ] +} + +VERIFY_COMPLETE = { + "task": "Verify Complete", + "match": "*", + "optional": False, + "commands": [ + [ + "click", + "id", + "approve", + "optional" + ], + [ + "wait", + "id", + "submission_complete", + 10 + ] + ] +} + +CONFIG = { + "basic": { + "browser": [ + { + "match": constants.BASE_URL + "/oauth2/authorize*", + "tasks": [ + LOGIN_BASIC, + VERIFY_COMPLETE + + ] + } + ], + "override": { + "fapi1-advanced-final": { + "browser": [ + { + "match": constants.BASE_URL + "/oauth2/authorize*", + "tasks": [ + LOGIN_BEFORE_CONSENT, + CONSENT_APPROVE, + VERIFY_COMPLETE + + ] + } + ] + }, + "fapi1-advanced-final-user-rejects-authentication": { + "browser": [ + { + "match": constants.BASE_URL + "/oauth2/authorize*", + "tasks": [ + LOGIN_BEFORE_CONSENT_USER1, + CONSENT_DENY, + VERIFY_COMPLETE + + ] + } + ] + }, + "fapi1-advanced-final-ensure-registered-redirect-uri": { + "browser": [ + { + "match": constants.BASE_URL + "/oauth2/authorize*", + "tasks": [ + VERIFY_ERROR + ] + } + ] + }, + "fapi1-advanced-final-ensure-redirect-uri-in-authorization-request": { + "browser": [ + { + "match": constants.BASE_URL + "/oauth2/authorize*", + "tasks": [ + VERIFY_ERROR + ] + } + ] + }, + } + }, +} diff --git a/oidc-fapi-conformance-tests/config/client_configs.py b/oidc-fapi-conformance-tests/config/client_configs.py new file mode 100644 index 0000000000..fe4727200f --- /dev/null +++ b/oidc-fapi-conformance-tests/config/client_configs.py @@ -0,0 +1,78 @@ +client_configs = { + "client": { + "jwks": { + "keys": [ + { + "p": "-fd5e1Hw-qC2oBHGvY-tFHgJwzc4CyAqsZ4f1yGRiAg_ec1ZklOlfVj6ljD0RRhZkvtT09KXd3IlJYj6RlJI1majVtZ0BjTSDhBIVaEaoTyTxZnsc2HazTr2kpbf9KCYwlEpBDcQTjERUBNYAAYboPLu3vby5kp-oracLzsg4kU", + "kty": "RSA", + "q": "wgpIpSK4XdI2mkcgk0zZvsvuDwuYXIkluYdX5YnAeak5vzUKgJM8xoMrCcMgu-C9xXT8e9ZP3H3P6NAkSmT8JnYQtegMehFAKN_vy64xJtY-tHsVNI_KKbtMYBe0rV_JAVsgIRheD-2iZySIeCTqWaOEjZGWDP6zzGuTCk-SC-c", + "d": "eRMplL0aeU3sCgGRqw6-ZtmaV3n3_Yluhy27pZBNPveQwwKWLvpqvYY-d-8VsJs948WXFV5wbHm5h3HvRcIQST080hlBdckj6l6liq3hipao48dxiOi62vy5EYvTb1_rLjW3NnEdZVd7bu_U9d-YewJiuC6dByjpwK7WOEyX4WLEx6p-CrylrY7WpsLJeoyNmVCKEQzm5gHf4G3trT_LsjiCHzX1ohTT1AU6mXqSgfYIdDcum1ZcTbL0RoSHlsaQDfFEfzh76LixWkS6NZlIU_E1nbQdOKtEVcVUWasMUe9Qd-Mvc6ATBo42ny9ZS_hz9-ir4dsTR5P8UsO4fszOQQ", + "e": "AQAB", + "use": "sig", + "kid": "ZR+iPrkP5j2j9sFIYfjr6RBNAxA=", + "qi": "QvkF44HxuJAytiUAev8hjPtbJ-CzGA1skqa0fkJqGLrWUeBT87JMrjTavDw9BXaRhDYqmRDJgeS0wQpE6QLYQknt6_iBolesyaKk9huvUeFFzp3Yf_goARlFc4nqGl03YZW193uP6BTnNwwdzSeevuPp-47eY5JML6EZXDYTavY", + "dp": "Bmlbm1cpuDQBGy_5igkyZlEB5xj2fgqQLcghhvIceoHDpwsmXgN5PjLXEp0k1tTeD1AVAVzod67c1w6hZMZdiepODD8p8LLQJH40bMXaGgKkw6_ECd2bsCHwCyiFjA_Ge017oNkMuQS3LfehDTca5FD24Y8yAcgtYV0PuoJowIU", + "alg": "PS256", + "dq": "CYHNe2HPHLGvATIY836hcIq3WD9eMLJ7ibKK6PEt4AgdjzO_Fvufz64fjrPjx7DEO4d20251rTzw0dxY2thFznCccv0yl0x_E0GdkyIuwFNg8SDSsieVZSQLIu4maRQ3Wfyn_1cdTLR874bHtPNCFDBBz1nhuzNDhfGDW874PeM", + "n": "vXeUqQt8sH0GdF8JzwDKlbAaHryPuAaHXQl0jX8czHAtvR9__DB41ebXDuZo8OUk0R_mvGceumCRidNxWiJEoREajK2fXVH4MnttWiWIiUShV2OlJJeeOC91mQsIh0hKztv-HkWozIlSDBlBzrwp06nrVI8eNSS7DveuFaG4ASXQBcOH6iO_sZjl-jCe8OgXa9xC8KcuOEIgV0j5t8ZZb2Jc_8y0OoVKUfUbIPTZ1J0nspK8oMR1dbLa9iW-BN10stoDKZP7qO3xOQWkYVywVtqOIAUY1o5OD-Vpgbvv-yvwN6184sTRRl5p_Jh9OafXxZ7Sp8bXn7MRuUpvGr8jQw" + }, + { + "p": "9C9WIB_G9nT4FvrK6J2UUItWcCihOrmfYsmM2zpbYjOTKSETVecFBMK6NOHh4J-w8xIzC_4I_IV-vRFTb1zVqNxAXP9OZgGKD3itCYXvOlkfpWohCB8sIGP9jvGusWjVukbGaT2GmDo-ljSPpOcF55Xg3E9ZhRl1y-nYR9K3mSs", + "kty": "RSA", + "q": "wNZUYJ8hFGIwsn_j9TwiZlNZ4NLbnBxB99gBNA9YCfYmtjVKP4DZYDoAStzR3CmfrRbBbUlbv7ZiHHNUP-iuHYQsfGGv2PnQwv5GByuT1mmVon-CT0vyHemKMk1iHUmTnMRP36CxCGtUbOGVuglAu6n_8FXXkjLTS5YrrGKqTeU", + "d": "sKa6yq0xqdDGgkLm-LkH_IFz8VP1SNn90YfnPuTBhquTvn8siBmuVQvZn6Kp5FAXfno3LwSNdD3IED7n4AbObwRxqIn3At6HTJmA74nnrKYMHYOlN4JyBOGqT0iIh4GSQiPhJaGD_vNYjorWYwZgLm50M3a-cKx4yyO8msIJ-qjjwCO9PMnASQzg_D8QYYmDBBUCxJGxip1ic4-R_WqxgnUB7gc8dceX5cjt9j2lpOjj5Yw5iwukIupczLy1WGhEUTFYBR6WdRVx3kSWAXuPJT1HvB8nFCAQ6WoYAFTxZbLk7Ccu50zX8TlpyND-BbJuJD9wVkilR2_UElb6C31gyQ", + "e": "AQAB", + "use": "enc", + "kid": "/HFQNRpvCQsnCGbEcrD0zED/45Y=", + "qi": "ETYcNNKbNa94tLcm8Svy9Cgg0PFCLBueGq_Kdj1FJECXyFG5wB9jcIqLFe7CJ7eoduc0tBFHKGmkRkaa8Ce75TrbILYRd2DpF0R6lWdWR1x88v-MMMVCmlNZCKsQPUte4FTZqDSLuntiWxm2yv4_ZsN4VyEWLjdOD5430j8-wzw", + "dp": "pD_PC-60E4Wcd0CdChks8Xy4WNtLkEfZyUZPhAtd-tE8VLcV3Xt26bveKsIiAMkJ6oa8ZX2sJ8fX929XExp78YitlqB46K5yYnPG2RgJUdMvMUIRE_h74xG_Nc6ltloRIaqHvbSP-4d6H-kdEkv_scYkKa0KRzhX_yC9xZocu18", + "alg": "RSA-OAEP", + "dq": "vOr9HO-oy7xqcuViIaX5GYA9BEnvBmCUrEe83hDOMmwpfsZ0aARGccRA9xagrm1E1qD9fKCqtwXp1m-SMU42DgqoPm7BhPMw92d-mZWyrxMfmmQK-L6xg2aBkkhHf-POLn4QtkhKR7JGsqjKM8jO10tzxzOCDuOmJ14GiUrcB5U", + "n": "t-_wpch7k6xcgLYfW3xeZnHk9rjaathVlyCo1j_I_dICNoFs-vpNsDua3lgg3BC5VTy8UjDh-4PFBB_GNRr_Hd6otR-fFnY6n15GKkl-MLV4RbqVECqIrtgG_-bY2CDvoMCkCEJijXdYRa20vPQh6Iav6yekworv1LW1ry1dmHTrTfCczdqMHmtKpzVAtnQwD8TbCHKs60tZIUjXLvktKkkPK0HE8s_ggSrSEFCiFCSsb9ckB4NiFZ18YrHza221bAUKT3Yj5G1P15QEfu_4UstDuqG9YfrAk46aEVlVnKYG1AQnIK8hQERyturXe-ZrxCMMyysHaVSe32sS49vydw" + } + ] + } + }, + "client2": { + "jwks": { + "keys": [ + { + "p": "4ErTZh9UOhmnKNaj6Q3DFcOxdkv2T8Tv37kkmsigZ4HDXjBe4ntLH5-r7Q64E4nRUetXg8euXrpwG_lj1Ye-hEqun7y88g-6fzfHUeoRaPcvVV8b5g2imfUrWtbMdu3h_NIqCVKiGiHgmccACW42HzWjy7YARSl2ur6hqOoLRM8", + "kty": "RSA", + "q": "pJgsWU7ElxLxHYZrXvk5LlowIX8N3UIHgO4apfjMcar6ZeQQbjna0IhejUQhnmLx7FBj9_QFgLapzwQBbSbdwUFnlWxxby33Uo2xb-2a-oKQJAW-gEJEPwQSlH9ciiDa19swVQ7w5QNf-jMQN5wTtG9UQT3nnQ3_7zLga791nYs", + "d": "OjJESdN0pDR7-lAsb5pMylo2EnEj1RUto_2FShPHm_SsQmDOYYrRokNqEJbH7obA56uH6de6Sljo92q3O-at1iEUiZNBakMA9DYi5m5RiE53xvom0r7HVPyOeiEzA8am_rMIGyNybUWGH-yaSEJHp3mUyZftIvTqHk0QwzrbK8YReojkMS9yDvH90gOJNZXac_ottCLf5pdnaTjB1mCK9tTGFA0AGE2bQ8iIF31bIYWvIQf8mBwoAn2YVRD7QtL-hi8y5raUghSF20Jpm0ZNGeLIFqdVmHTCZ8tJcZuUaQIW3fjuRkFaWSpYM-Snop0ivFwhiYb7564eIPy8orDnwQ", + "e": "AQAB", + "use": "sig", + "kid": "KZtSs4C20n0rxwT9tJBIn/gBzqQ=", + "qi": "t_G8D-8y8egZrPKhJscAGwNXNNaVjW0hhUvrlRV7Q9skwn0iHSPULPQsrrKODW_yOPnEuC7eAaP2ZGO5kg_JfRFC3gxkcLW3zgr49K_PQsJbUKGbdIxiLtlecEakBfLepSr99ksyN_GPn8_HNwuqmO5wn2BXNd-uPyqar9LLJAc", + "dp": "mbnvRlE2s1OdxFnIohUynpov6XqK6YV7OwV0Vzom5oqD0uyoO-ZxSM1xUtsBwzJ0awc4RjP4CSkTabj2egW9yII_SXBcuDObiVI7pKMfJ6gzovm672RtC8SD4JDUNrc4asXI1fby76JEhGFmXxBQu_dRum-Vo9GVdMzvlAxFiPU", + "alg": "PS256", + "dq": "d3Sp2zc7npl0vQTtsEQYp_d0rxrTKR3e7Efit0pWtrhser6G3jjHAkm2tbgY3ibZhbTimPZVeBEaNNCnaFyo3LVnRB8sRJCLyzJi8OT1cZKIolw44kU12rZN9dBpjSZDLh46ecEaPFuysJeW0YVJF88kyyTukCG0-hlmn0yZQ_k", + "n": "kDVCtgwQpS3_hhSCFq0cE5TViIKdt8NRPZdl0rX9Dg6jJXM7BzaLgnMIZteBrXEMrx-W5TQ7Li6l3m3KK1fnzzSaqdAYwVII5b2hL7Y0QVro0nymJTkW9Qq4NH9uKSJUnFxeTE7OI4rL3I-pyNVBS28BbK_Xry2A4gZXMdNhyYDzUJepsOGPVsI2mwVSzTW5I4BZ1CjDpwSFrFwCzJlPGieDHq-gXLH5ne8GotDkjnRrrNs1bFbMEekVcy_VYXfj6cLSGfDZqKRsLLHziIix13FwWRPR8ddPkhODNREOuwQR2fQeQdJ_yhRMaOuBsPmAQuzhLejdjsCX0KBB4OxPZQ" + }, + { + "p": "3q7Zspng5gYgacvQhYmLwEr70h1rjNCnRzyy6iCDH4v90kA-H8vB6Ug8mWC_f12XDif6vxPLhd4sktScicJNYQ4OoYOflwQyRLCk7TtyY86vWSK9Byxg8XvXhSChCUx7pSLqPFxgj0fnotyQEe63XqPYTNenpm7l_BCOUwIxbrc", + "kty": "RSA", + "q": "qeTmau4xOpm0_JTlaxnvSn4i1h4T6Bbg1Ntx2L1NaE9viufKykDse7wWieFx29gKGrqHgY6mUnFcgyuTyg_nEoGSzLyovTQod-WNYpwamuJOAyl4ewJZiszio317yWinlJGrdaDiXecypQRF80IU2Grqum0Ma9CWoqmjosCfZgk", + "d": "OQ_QtGE3yJhKkpT3I-zc3oqN7kggoBPzbhTzp63QvrB_DaAYyq8-MDRnE_BAxa3EiiGNvUZivI8TEZ1r_tCiZCG8x936-3e13GXF6kbVMPzyC-cbs0_nMRRNpvzJdaxYxnO3iwTo91VWCNumntIvGXsDTdUdy8HTeIvA5l_DvYxncBsgtLYrOcAapQhmXAFtA92WKCMnoM-WZlK6X2b0zEGO2OTNBx-TAXsHrprPSwu3B0U252Wfn3AdjTyrB4VUBYlv07UVcUmyqEElGikiXZT-EuMH0JiZenEnZ0er43hiRk5J7VLexS4IBABPgz8xcy3yH9In121410G0yEEGMQ", + "e": "AQAB", + "use": "enc", + "kid": "k6mOquqT8OLAXapY1u3+UC6pASs=", + "qi": "B9apz-JuPJsWkLyeVXZXZ41Q328rMteRdj4yDs_XJriJBU8-Z5QdKtZ5o-yvg0lvCZHbaV2MkRKBIswy6AYOv4UwHEcmwMD-zKHcotG3Tqw3J89Lh5XqFz-DVvIW9ggw_0vUpJnPnxRNh2uy8th3BvQgOzppS4yZdVABcjy-Vfw", + "dp": "IJaoJ1obrq4g2jrIpnJNsHOIzOQfVDlI8cTxThA6AEjleXqLXO3dz_0RWUTuYX5Z1-mD6ajE9S9hiI9y5To5RsXe4EY5-cjUbncHe-xiPjcGNF5AGael_FEqZkIzLwAxbCZi7zh5rR5RyOTMdMNhtIj3467_yvyZzbzCh2VfxJs", + "alg": "RSA-OAEP", + "dq": "mjXijIb-emGQrX_9qjSVRrmtp0sP6KfzlmJGOWTDAX6xEZWKNgGuZjX5A1h2gAXeThqn6wpvn_-z6IBCmrPKm7qvZFWOmIbigMoFhs-JOrNV-nrHGGiunFWTFrFNDimHi0yaJKBPOMsl5jE1yTNfNn396BAZqTdhs-5lUNKlHPk", + "n": "k8iJ3uXUfy-2Izij0Q8h39f7zMpcaCqGbe47jXRqbnchVdnN1GXczxeim3HO_p6W9WN-x3rdrxseaEQzt-U5N40qdIz5FbUCVoiTs9mXkyrQTxU14AHjBA04OxsBc4obHtm1bZPFz9kgLsvNpPqq8lA7fni6PBvfnEh9YtsV9McuUnvoURNpXzEmsf9zdc1XOCUpj1jjz-bK_SI1cCJLfmDmvH32VyU0emVMcxdYdNFYgN21RlauoYmuxlWUUPLxIwN2x-T0klfDB82_IKAMuvVItfVqv67XkP8NDelhyaq5afh3PQjHASlJH3iWuSVekRE-I2metmPeExhVyYLObw" + } + ] + } + }, + "mtls": { + "cert": "-----BEGIN CERTIFICATE-----\nMIIFODCCBCCgAwIBAgIEWca5LzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJH\nQjEUMBIGA1UEChMLT3BlbkJhbmtpbmcxLjAsBgNVBAMTJU9wZW5CYW5raW5nIFBy\nZS1Qcm9kdWN0aW9uIElzc3VpbmcgQ0EwHhcNMjMwNDE3MDQ1ODE2WhcNMjQwNTE3\nMDUyODE2WjBhMQswCQYDVQQGEwJHQjEUMBIGA1UEChMLT3BlbkJhbmtpbmcxGzAZ\nBgNVBAsTEjAwMTU4MDAwMDFIUVFyWkFBWDEfMB0GA1UEAxMWb1E0S29hYXZwT3Vv\nRTdydlFzWkVPVjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJeMaWpz\nmwYZ25cDHLSEWhcwaa/JJXgwupZJifByhaao8m/Hhx8PZpXOXz7GcfiNVmz3w1cA\nFXvfrNh4A7rc2vjp9shNQ6bQnbOKVjoN+rNxskYjxpvLOllCUaii5kjdRF5r0YE9\n7t3hH7GdATT56Js9aomykbeYodG1vN4eDcgArn1fO7q+6+0Ew2Mla5X+T/fsfu+1\n4tXMLx7AAQSCzGfsYnJp6fCJQ4uk1d5mlYWd+cM2gWf1eQ5sHeL1K9B+czos57NF\nhsVUBvPCPLmratanj78tN8O6zOxAs1UEckf+z1rLK3D2NCqv9FnfB7saLKhp58vQ\nqoRnOiW+lr1Z4bsCAwEAAaOCAgQwggIAMA4GA1UdDwEB/wQEAwIHgDAgBgNVHSUB\nAf8EFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwgeAGA1UdIASB2DCB1TCB0gYLKwYB\nBAGodYEGAWQwgcIwKgYIKwYBBQUHAgEWHmh0dHA6Ly9vYi50cnVzdGlzLmNvbS9w\nb2xpY2llczCBkwYIKwYBBQUHAgIwgYYMgYNVc2Ugb2YgdGhpcyBDZXJ0aWZpY2F0\nZSBjb25zdGl0dXRlcyBhY2NlcHRhbmNlIG9mIHRoZSBPcGVuQmFua2luZyBSb290\nIENBIENlcnRpZmljYXRpb24gUG9saWNpZXMgYW5kIENlcnRpZmljYXRlIFByYWN0\naWNlIFN0YXRlbWVudDBtBggrBgEFBQcBAQRhMF8wJgYIKwYBBQUHMAGGGmh0dHA6\nLy9vYi50cnVzdGlzLmNvbS9vY3NwMDUGCCsGAQUFBzAChilodHRwOi8vb2IudHJ1\nc3Rpcy5jb20vb2JfcHBfaXNzdWluZ2NhLmNydDA6BgNVHR8EMzAxMC+gLaArhilo\ndHRwOi8vb2IudHJ1c3Rpcy5jb20vb2JfcHBfaXNzdWluZ2NhLmNybDAfBgNVHSME\nGDAWgBRQc5HGIXLTd/T+ABIGgVx5eW4/UDAdBgNVHQ4EFgQUSoZfmnXGAPddPqfH\nWVOvkxD89MgwDQYJKoZIhvcNAQELBQADggEBABHzHOJzn4DPHay8xGzlWJIxxe+X\nsNtupR5V/ouEGCzJMUPmegYeK2Kiv+Z9nJKnbspgqLil52yauKWRmiXif4FWoOPR\nwspR9ijnynCgIp6z3EAOawbe28HkaGEfAi8PMqdNAYLKpXg35TUnbP+p2Q55Grq9\nEpSR2APQfJ4TjgLgKjqLRf/RjJAY9hJbQJIUl07esYf8hH7mX6uHDCywzic+UEQ3\ntUfo7PgWmnmtyUdFcW1qAl4P80a5fb8Wq0gNu6gN5tK2bg5TfSo3Gp2It8NVu/dY\n7q3ur7CAYTXrThjg4GXUQgVqYgV3pHbr1LTAiRtac7RBhMNPCklZA78RpTM=\n-----END CERTIFICATE-----", + "key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCXjGlqc5sGGduX\nAxy0hFoXMGmvySV4MLqWSYnwcoWmqPJvx4cfD2aVzl8+xnH4jVZs98NXABV736zY\neAO63Nr46fbITUOm0J2zilY6DfqzcbJGI8abyzpZQlGoouZI3URea9GBPe7d4R+x\nnQE0+eibPWqJspG3mKHRtbzeHg3IAK59Xzu6vuvtBMNjJWuV/k/37H7vteLVzC8e\nwAEEgsxn7GJyaenwiUOLpNXeZpWFnfnDNoFn9XkObB3i9SvQfnM6LOezRYbFVAbz\nwjy5q2rWp4+/LTfDuszsQLNVBHJH/s9ayytw9jQqr/RZ3we7GiyoaefL0KqEZzol\nvpa9WeG7AgMBAAECggEAF6Phw9HMd8tWe6boIOlZIQSTz/CO2uywW9uVMWq5RNyr\nk4U/+OKO9u/DisgrpNaYtJSCURLPHHeGqm6E7Rl2Ql2Yz05s2bDxkL1pIv2QQygJ\nY7NBUDHdDxRvJuELuYe5RQiznIy73g+368hulO7Q+qUTdpm3l7ivBsa/SLTOeIlp\nNHGZtilclW0jKF8s8yZRdCjIHTJeddKdNVD1uE0qiNhMLVk9mQBb5+sdEh+3+nTP\nYaWZJtc30/h8s1dKsV/NERmU34vb38jNydz3+WUQZQATsw3Qg0gCgNnKPiUroPLU\ndqb1uORaEEAM48RfWt5j6blqcREn6rwPLsE+emXx7QKBgQDKz2w2goXV6CfWs02B\n/Myl7KmZDIO/vlZS+f+UJWmmRnTmTsHk35eZ6V1VUtBUvRcQ8BDDDVuNP9cooA1h\nnvCbulD5wEZ5OqwuJKMmiBlJ7hLr67YjD2eWI3GvTJgwVMOGBgfsfDXUg3Q+iTUN\n0dg/cjRwly2fPh9HBUeISNtcjwKBgQC/S0zik7Nhg9uezN/dBQTKw9eaLzVvNTL3\nQr4amKssGT2hhJWadbtJvozf8SWKS61Sj+u3OQ7FgHQyHIv2iHwQZTyTl+L1bXUW\nLkeesTiriwtTbZfamjGsLk5AckL0Jf7H5d+80RDh0aAFiLbRvCPFHjPs9k2FQLbT\nYimSwakWFQKBgD34HS+WuEBBHJMs3F0AmqOEadn7CfK3vPbDrsVcnSiSVLveir5d\nV5xOwxcEI+YQlRC22dhPp47EWADwzsXqY8ihqIZ3qtD2DZBX34YxhdCiLyPpNREH\nbvZgRCd1Nvdlnwy/oQIwfd4+gHFvSUe8u9+/DKjueKE0wSfZRB8va9+fAoGAR3ry\nwuOgLvsfkMpLiII8lSvzH5kuZVzCq6RT9wo/RB42amlpoasGjfrXF0M73N6EaIqA\n8RkwDNsAEE2ce0Xq3hGGPjyOQVliXCR0/LaKT4eIdudHzBa8IvO2p9xKaoLcHoQm\nQ573qPo0ynyYm+yqjRjJHG247+cLqhdPI5ib/zUCgYB2LF4I/46XDyQw2sEJjEP/\nxTEKeo+TqNQTIGrkdiLqAvIKDYfyBxp7qtmwFkM1OOiqjWF0JpZD4haMH9HJFzA6\nmUZVVUB2UlEoknvXSKhbefPpNpCWYJcvQ24aEsE7IcxNasDgtY2dRHdg+/LdOtPY\n7bjFcMY3B6PzLER1Q9hyBw==\n-----END PRIVATE KEY-----" + }, + "mtls2": { + "cert": "-----BEGIN CERTIFICATE-----\nMIIFODCCBCCgAwIBAgIEWcbiiTANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJH\nQjEUMBIGA1UEChMLT3BlbkJhbmtpbmcxLjAsBgNVBAMTJU9wZW5CYW5raW5nIFBy\nZS1Qcm9kdWN0aW9uIElzc3VpbmcgQ0EwHhcNMjMxMTE1MDUxMDMxWhcNMjQxMjE1\nMDU0MDMxWjBhMQswCQYDVQQGEwJHQjEUMBIGA1UEChMLT3BlbkJhbmtpbmcxGzAZ\nBgNVBAsTEjAwMTU4MDAwMDFIUVFyWkFBWDEfMB0GA1UEAxMWakZRdVE0ZVFiTkNN\nU3FkQ29nMjFuRjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJslGjTm\n0tWwnnKgC7WNqUSYNxblURkJyoD5UuSmzpsM5nlUBAxYxBgztTo062LJELzUTzA/\n9kgLIMMgj+wG1OS475QCgeyoDmwf0SPuFRBl0G0AjxAvJzzs2aijzxiYRbKUa4gm\nO1KPU3Xlz89mi8lwjTZlxtGk3ABwBG4f5na5TY7uZMlgWPXDnTg7Cc1H4mrMbEFk\nUaXmb6ZhhGtp0JL04+4Lp16QWrgiHrlop+P8bd+pwmmOmLuglTIEh+v993j+7v8B\nXYqdmYQ3noiOhK9ynFPD1A7urrm71Pgkuq+Wk5HCvMiBK7zZ4Sn9FDovykDKZTFY\nMloVDXLhmfDQrmcCAwEAAaOCAgQwggIAMA4GA1UdDwEB/wQEAwIHgDAgBgNVHSUB\nAf8EFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwgeAGA1UdIASB2DCB1TCB0gYLKwYB\nBAGodYEGAWQwgcIwKgYIKwYBBQUHAgEWHmh0dHA6Ly9vYi50cnVzdGlzLmNvbS9w\nb2xpY2llczCBkwYIKwYBBQUHAgIwgYYMgYNVc2Ugb2YgdGhpcyBDZXJ0aWZpY2F0\nZSBjb25zdGl0dXRlcyBhY2NlcHRhbmNlIG9mIHRoZSBPcGVuQmFua2luZyBSb290\nIENBIENlcnRpZmljYXRpb24gUG9saWNpZXMgYW5kIENlcnRpZmljYXRlIFByYWN0\naWNlIFN0YXRlbWVudDBtBggrBgEFBQcBAQRhMF8wJgYIKwYBBQUHMAGGGmh0dHA6\nLy9vYi50cnVzdGlzLmNvbS9vY3NwMDUGCCsGAQUFBzAChilodHRwOi8vb2IudHJ1\nc3Rpcy5jb20vb2JfcHBfaXNzdWluZ2NhLmNydDA6BgNVHR8EMzAxMC+gLaArhilo\ndHRwOi8vb2IudHJ1c3Rpcy5jb20vb2JfcHBfaXNzdWluZ2NhLmNybDAfBgNVHSME\nGDAWgBRQc5HGIXLTd/T+ABIGgVx5eW4/UDAdBgNVHQ4EFgQU7T6cMtCSQTT5JWW3\nO6vifRUSdpkwDQYJKoZIhvcNAQELBQADggEBAE9jrd/AE65vy3SEWdmFKPS4su7u\nEHy+KH18PETV6jMF2UFIJAOx7jl+5a3O66NkcpxFPeyvSuH+6tAAr2ZjpoQwtW9t\nZ9k2KSOdNOiJeQgjavwQC6t/BHI3yXWOIQm445BUN1cV9pagcRJjRyL3SPdHVoRf\nIbF7VI/+ULHwWdZYPXxtwUoda1mQFf6a+2lO4ziUHb3U8iD90FBURzID7WJ1ODSe\nB5zE/hG9Sxd9wlSXvl1oNmc/ha5oG/7rJpRqrx5Dcq3LEoX9iZZ3knHLkCm/abIQ\n7Nff8GQytuGhnGZxmGFYKDXdKElcl9dAlZ3bIK2I+I6jD2z2XvSfrhFyRjU=\n-----END CERTIFICATE-----", + "key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCbJRo05tLVsJ5y\noAu1jalEmDcW5VEZCcqA+VLkps6bDOZ5VAQMWMQYM7U6NOtiyRC81E8wP/ZICyDD\nII/sBtTkuO+UAoHsqA5sH9Ej7hUQZdBtAI8QLyc87Nmoo88YmEWylGuIJjtSj1N1\n5c/PZovJcI02ZcbRpNwAcARuH+Z2uU2O7mTJYFj1w504OwnNR+JqzGxBZFGl5m+m\nYYRradCS9OPuC6dekFq4Ih65aKfj/G3fqcJpjpi7oJUyBIfr/fd4/u7/AV2KnZmE\nN56IjoSvcpxTw9QO7q65u9T4JLqvlpORwrzIgSu82eEp/RQ6L8pAymUxWDJaFQ1y\n4Znw0K5nAgMBAAECggEAIlg4UPW4Pq32pZFghjRyAEliCKODLHPKbHrFKvDyU8ir\nTLiYnNMZtfHccKI+aYPxPJwTW68NKi2sdwXCVpG8l1WJU86qTGV3q9/vQHD4IMUp\nOrgg84g8u3QDSx3YPq2W4E2S8HTmNQSdiT/ynQsqbtGHsT8eoFJKkZ46nNZbAnTy\ndW0ZEDNvNJ/F7KMxrnf4ibrQKx22rQtF5Go3G/Nm2WQZbcJ+0fkccXtunL3c4uuG\nxH/xdlOUlay4WOcmKw93HJOs7D2YEdZ2uTATgas74dK+RyXzyvVQoEpOSIJ2jQYb\nwEfcR85p5hvU9xR085Cp8+LAo2bKiQWGw0VUkIaTAQKBgQC3J/z2m6Wq5nvnIKE4\nvWg12jwvHhyP2zIZjKPncA0lKmUC7vBJ60KIm4O0fDVfSksgsLhxgZxchDfRjZN2\nolVqrqYTYi/OL4gSroSMK1VNyGj/n+4LTPIQxwiepGkbdj0vv+eKXG0CNZEOACyq\njhA97GaeY3aUTsE/EzSVaa6mYQKBgQDY2Sb1P8oTxFcfrNyLyr663N+uLPzEfu+S\nvFvvvG3ppcZjijmQVOJsW5R2F/7QCG2VdWft6Q6g2Ae/zWNSCvP2qYxJFhqtXPHx\nj33pU0sveQZdEy/dfiNfqqepd4EyZWFsLUQ2c4NMYvmT8PiArn6YUZ/nKcnwNGek\nt2hGItn5xwKBgC4j9sgxgdTB56jcQYFHxf77EXpVPaDH+aESGyBszAL24SeSBhq5\nF7POaamIOPHeh3qeynhdzIRKEr3JysJyNs5/XPs5Gw34T3FVYbLmH8FOoZT0N0XF\nhp3PZk6A4LG4YcWrtKrYieWqlPZP+PCmhVT0Pw3bxL2r8WbwcajKTONBAoGAWia9\nSYyBiIJB7ktBTWS2sp0M6S6Oz2ouU4S3fA2MbGyc89kNYXHIyF+ycv4602YNmOs8\nj+4qRLhLUHQk7IDdOBj415G2+YgAlfqf0Bbu1Qetm5hUd/Lu5eDe31dtgPQg8oZH\nK5QOa/1h4R13Upg4zT5yCfqXl6NRy2rskOwATxUCgYBbnpKY0sPyY1BxaxPChXuN\nhlIeRMwhurk+VylkvNOF3GvwJ9Z7Uzuf62PzXRu0anWtlGY9KFP494SX1YL7CQO9\nMws8Zo881MJr+oYzgwhv9CQhmNTtK7awjDizOlyuz7r4PJYIaKuqFxwiRBqNxJdA\nloFhm8Tc2RjLkAHcKjGgBQ==\n-----END PRIVATE KEY-----" + } +} diff --git a/oidc-fapi-conformance-tests/config/deployment-fapi-config.toml b/oidc-fapi-conformance-tests/config/deployment-fapi-config.toml new file mode 100644 index 0000000000..1d6d5cc0a2 --- /dev/null +++ b/oidc-fapi-conformance-tests/config/deployment-fapi-config.toml @@ -0,0 +1,99 @@ +[server] +hostname = "iam" +node_ip = "127.0.0.1" +base_path = "https://$ref{server.hostname}:${carbon.management.port}" + +[super_admin] +username = "admin" +password = "admin" +create_admin_account = true + +[user_store] +type = "database_unique_id" + +[database.identity_db] +type = "h2" +url = "jdbc:h2:./repository/database/WSO2IDENTITY_DB;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=60000" +username = "wso2carbon" +password = "wso2carbon" + +[database.shared_db] +type = "h2" +url = "jdbc:h2:./repository/database/WSO2SHARED_DB;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=60000" +username = "wso2carbon" +password = "wso2carbon" + +[keystore.primary] +file_name = "wso2carbon.jks" +password = "wso2carbon" + +[truststore] +file_name="client-truststore.jks" +password="wso2carbon" +type="JKS" + +[account_recovery.endpoint.auth] +hash= "66cd9688a2ae068244ea01e70f0e230f5623b7fa4cdecb65070a09ec06452262" + +[identity.auth_framework.endpoint] +app_password= "dashboard" + +# The KeyStore which is used for encrypting/decrypting internal data. By default the primary keystore is used as the internal keystore. + +#[keystore.internal] +#file_name = "$ref{keystore.primary.file_name}" +#type = "$ref{keystore.primary.type}" +#password = "$ref{keystore.primary.password}" +#alias = "$ref{keystore.primary.alias}" +#key_password = "$ref{keystore.primary.key_password}" + +# The KeyStore which is used for tls communication. By default the primary keystore is used as the tls keystore. + +#[keystore.tls] +#file_name = "$ref{keystore.primary.file_name}" +#type = "$ref{keystore.primary.type}" +#password = "$ref{keystore.primary.password}" +#alias = "$ref{keystore.primary.alias}" +#key_password = "$ref{keystore.primary.key_password}" + +#Google reCAPTCHA settings. + +#[recaptcha] +#enabled = true +#api_url = "https://www.google.com/recaptcha/api.js" +#verify_url = "https://www.google.com/recaptcha/api/siteverify" +#site_key = "" +#secret_key = "" + +# SMTP email sender settings. +#[output_adapter.email] +#from_address= "abcd@gmail.com" +#username= "abcd" +#password= "xxxx" +#hostname= "smtp.gmail.com" +#port= 587 + +# FAPI +[oauth.dcr] +enable_fapi_enforcement = true + +[oauth.oidc] +id_token.signature_algorithm="PS256" + +# Token endpoint related configurations +[oauth.oidc.token_endpoint] +signing_algorithms=["PS256","ES256"] + +# Userinfo response signing algorithm +[oauth.oidc.user_info] +jwt_signature_algorithm="PS256" + +[transport.https.sslHostConfig.properties] +ciphers="TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" + +[oauth.mutualtls] +client_certificate_header = "x-wso2-mtls-cert" + +[oauth.jwks_endpoint] +read_timeout = "10s" + diff --git a/oidc-fapi-conformance-tests/config/sandbox-certs/OB_SandBox_PP_Issuing_CA.cer b/oidc-fapi-conformance-tests/config/sandbox-certs/OB_SandBox_PP_Issuing_CA.cer new file mode 100644 index 0000000000..6ef427235e Binary files /dev/null and b/oidc-fapi-conformance-tests/config/sandbox-certs/OB_SandBox_PP_Issuing_CA.cer differ diff --git a/oidc-fapi-conformance-tests/config/sandbox-certs/OB_SandBox_PP_Root_CA.cer b/oidc-fapi-conformance-tests/config/sandbox-certs/OB_SandBox_PP_Root_CA.cer new file mode 100644 index 0000000000..064036c0bd Binary files /dev/null and b/oidc-fapi-conformance-tests/config/sandbox-certs/OB_SandBox_PP_Root_CA.cer differ diff --git a/oidc-fapi-conformance-tests/configure_is_fapi.py b/oidc-fapi-conformance-tests/configure_is_fapi.py new file mode 100644 index 0000000000..f2f0185a59 --- /dev/null +++ b/oidc-fapi-conformance-tests/configure_is_fapi.py @@ -0,0 +1,259 @@ +import re +import warnings +import psutil +import requests +import json +from urllib.parse import urlencode +from zipfile import ZipFile +import subprocess +import os +import sys +from requests.exceptions import HTTPError +import constants_fapi as constants +import base64 +from config import browser_configuration +from config.client_configs import client_configs + +# path to product is zip file +path_to_is_zip = str(sys.argv[1]) +print("Path to zip: ", path_to_is_zip) + +def decode_secret(secret): + decoded_string=base64.b64decode(secret+"=").decode("utf-8") + decoded_json = json.loads(decoded_string) + return decoded_json + +# use dcr to register a client +def dcr(app_json): + print(">>> Making DCR Request.") + print(">>> SP Name: " + app_json.get("client_name")) + DCR_BODY = constants.DCR_BODY + DCR_BODY['client_name'] = app_json.get("client_name") + DCR_BODY['redirect_uris'] = app_json.get("redirect_uris") + DCR_BODY['jwks_uri'] = app_json.get("jwks_uri") + DCR_BODY['token_endpoint_auth_method'] = app_json.get("token_endpoint_auth_method") + DCR_BODY['ext_param_client_id'] = app_json.get("client_id") + DCR_BODY['ext_param_client_secret'] = app_json.get("client_secret") + DCR_BODY['require_pushed_authorization_requests'] = app_json.get("require_pushed_authorization_requests") + try: + response = requests.post(url=constants.DCR_ENDPOINT, headers=constants.HEADERS_WITH_AUTH, + data=json.dumps(DCR_BODY), verify=False) + response.raise_for_status() + except HTTPError as http_error: + print(http_error) + print(response.text) + exit(1) + except Exception as error: + print("\nError occurred: " + str(error)) + exit(1) + else: + print(">>> ClientID: " + str(response.json()['client_id'])) + +# get application id of the service provider using the SP name +def get_application_id_by_sp_name(name): + try: + response = requests.get(url=constants.APPLICATION_ENDPOINT + "?filter=name+eq+" + name, + headers=constants.HEADERS_WITH_AUTH, verify=False) + response.raise_for_status() + response_json = json.loads(response.content) + application_id = response_json['applications'][0]['id'] + print(">>> Application ID: " + application_id) + return application_id + except HTTPError as http_error: + print(http_error) + print(response.text) + exit(1) + except Exception as error: + print("Error occurred: " + str(error)) + exit(1) + +# returns service provider details with given application id +def get_service_provider_details(application_id): + try: + response = requests.get(url=constants.APPLICATION_ENDPOINT + "/" + application_id + "/inbound-protocols/oidc", + headers=constants.HEADERS_WITH_AUTH, verify=False) + response.raise_for_status() + response_json = json.loads(response.content) + return response_json + except HTTPError as http_error: + print(http_error) + print(response.text) + exit(1) + except Exception as error: + print("Error occurred: " + str(error)) + exit(1) + +# perform advanced authentication configuration for given service provider +def configure_acr(application_id): + body = json.dumps(constants.ACR) + + print(">>> Setting up advanced authentication scripts...") + try: + response = requests.patch(url=constants.APPLICATION_ENDPOINT + "/" + application_id, + headers=constants.HEADERS_WITH_AUTH, data=body, verify=False) + response.raise_for_status() + except HTTPError as http_error: + print(http_error) + print(response.text) + exit(1) + except Exception as error: + print("\nError occurred: " + str(error)) + exit(1) + else: + print(">>> ACR script saved successfully.") + +def addCertsToKeystore(rootCertPath, issuerCertPath, ISPath): + print(">>> Adding certs to keystore...") + try: + # add root cert to keystore + os.system("keytool -import -noprompt -trustcacerts -alias obroot -file " + rootCertPath + " -storetype JKS -keystore " + ISPath + "/repository/resources/security/client-truststore.jks -storepass wso2carbon") + # add issuer cert to keystore + os.system("keytool -import -noprompt -trustcacerts -alias obissuer -file " + issuerCertPath + " -storetype JKS -keystore " + ISPath + "/repository/resources/security/client-truststore.jks -storepass wso2carbon") + except Exception as error: + print("\nError occurred: " + str(error)) + exit(1) + else: + print(">>> Certs added to keystore successfully.") + + +# unpack product-is zip file and run +def unpack_and_run(zip_file_name): + try: + # extract IS zip + with ZipFile(zip_file_name, 'r') as zip_file: + print(">>> Extracting " + zip_file_name) + zip_file.extractall() + + dir_name = '' + + # start identity server + print("\n>>> Starting Server") + dir_list = os.listdir() + r = re.compile('(?=^wso2is)(?=^((?!zip).)*$)') + for line in dir_list: + if r.match(line): + print(line) + dir_name = line + break + + os.chmod("./" + dir_name + "/bin/wso2server.sh", 0o777) + + # add root issuer certs to keystore + ISPath = "./" + dir_name + addCertsToKeystore("config/sandbox-certs/OB_SandBox_PP_Root_CA.cer", "config/sandbox-certs/OB_SandBox_PP_Issuing_CA.cer", ISPath) + + process = subprocess.Popen("./" + dir_name + "/bin/wso2server.sh", stdout=subprocess.PIPE) + while True: + output = process.stdout.readline() + if b'..................................' in output: + print("\n>>> Server Started") + break + if output: + print(output.strip()) + rc = process.poll() + return rc + except FileNotFoundError: + print() + raise + +# creates the IS_config.json file needed to run OIDC test plans and save in the given path +def json_config_builder(service_provider_1, service_provider_2, output_file_path, plan_name): + config = { + "alias": constants.ALIAS, + "description": plan_name, + "server": { + "discoveryUrl": constants.BASE_URL + "/oauth2/token/.well-known/openid-configuration" + }, + "resource": { + "resourceUrl": constants.RESOURCE_ENDPOINT_URL + }, + "client": { + "client_id": service_provider_1['clientId'], + "scope": "openid profile abc", + "jwks": client_configs['client']['jwks'] + }, + "client2": { + "client_id": service_provider_2['clientId'], + "scope": "openid profile abc", + "jwks": client_configs['client2']['jwks'] + }, + "mtls": client_configs['mtls'], + "mtls2": client_configs['mtls2'], + "browser": browser_configuration.CONFIG["basic"]["browser"], + "override": browser_configuration.CONFIG["basic"]["override"] + } + + json_config = json.dumps(config, indent=4) + f = open(output_file_path, "w") + f.write(json_config) + f.close() + +def createNewUser(username, password): + try: + body = {"userName":username,"password":password} + response = requests.post(url=constants.CREATE_USER_ENDPOINT, headers=constants.HEADERS_WITH_AUTH, + data=json.dumps(body), verify=False) + response.raise_for_status() + print("\n>>> User created successfully.") + except HTTPError as http_error: + print(http_error) + print(response.text) + exit(1) + except Exception as error: + print("\nError occurred: " + str(error)) + exit(1) + +# returns true if the process with given name is running +def is_process_running(process_name): + process_list = [] + + # Iterate over the all the running process + for proc in psutil.process_iter(): + try: + pinfo = proc.as_dict(attrs=['pid', 'name', 'create_time']) + # Check if process name contains the given name string. + if process_name.lower() in pinfo['name'].lower(): + process_list.append(pinfo) + except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): + pass + + if len(process_list) > 0: + return True + else: + return False + +warnings.filterwarnings("ignore") + +if not is_process_running("wso2server"): + unpack_and_run(path_to_is_zip) +else: + print("\n>>> IS already running ...") +print ("==============================================\n") + +# Create a new user to reject consent +createNewUser("user1", "User1@password") + +def createSPApp(app_json): + print("\n") + dcr(app_json) + app_id = get_application_id_by_sp_name(app_json.get("client_name")) + app_details = get_service_provider_details(app_id) + configure_acr(app_id) + return app_details + +def generateConfigForPlan(app1, app2, consfigOutputFilePath, plan_name): + app1_details = createSPApp(app1) + app2_details = createSPApp(app2) + json_config_builder(app1_details, app2_details, consfigOutputFilePath, plan_name) + +print("\n>>> Configs for pvtkeyjwt test plan") +generateConfigForPlan(constants.PVTKEYJWT_APP1, constants.PVTKEYJWT_APP2, "config/IS_config_fapi_pvtkeyjwt.json", "pvtkeyjwt") + +print("\n>>> Configs for mtls test plan") +generateConfigForPlan(constants.MTLS_APP1, constants.MTLS_APP2, "config/IS_config_fapi_mtls.json", "mtls") + +print("\n>>> Configs for pvtkeyjwt par test plan") +generateConfigForPlan(constants.PVTKEYJWT_PAR_APP1, constants.PVTKEYJWT_PAR_APP2, "config/IS_config_fapi_pvtkeyjwt_par.json", "pvtkeyjwt_par") + +print("\n>>> Configs for mtls par test plan") +generateConfigForPlan(constants.MTLS_PAR_APP1, constants.MTLS_PAR_APP2, "config/IS_config_fapi_mtls_par.json", "mtls_par") diff --git a/oidc-fapi-conformance-tests/constants_fapi.py b/oidc-fapi-conformance-tests/constants_fapi.py new file mode 100644 index 0000000000..603e2f51dc --- /dev/null +++ b/oidc-fapi-conformance-tests/constants_fapi.py @@ -0,0 +1,138 @@ + +ALIAS = "fapi-wso2is" + +BASE_URL = "https://iam:9443" +DCR_ENDPOINT = BASE_URL + "/api/identity/oauth2/dcr/v1.1/register" +TOKEN_ENDPOINT = BASE_URL + "/oauth2/token" +APPLICATION_ENDPOINT = BASE_URL + "/api/server/v1/applications" +CREATE_USER_ENDPOINT = BASE_URL + "/scim2/Users" +RESOURCE_ENDPOINT_URL = "https://iam/resource" + +SCOPES = "internal_user_mgt_update internal_application_mgt_create internal_application_mgt_view internal_login " \ + "internal_claim_meta_update internal_application_mgt_update internal_scope_mgt_create" + +HEADERS_WITH_AUTH = {'Content-Type': 'application/json', 'Connection': 'keep-alive', + 'Authorization': 'Basic YWRtaW46YWRtaW4='} + +# SP App common configs + +DCR_BODY = { + "grant_types": ["client_credentials", "authorization_code", "refresh_token"], + "backchannel_logout_uri": "https://www.google.com", + "backchannel_logout_session_required": "true", + "token_endpoint_auth_signing_alg" : "PS256", + "id_token_signed_response_alg" : "PS256", + "id_token_encrypted_response_alg" : "RSA-OAEP", + "id_token_encrypted_response_enc" : "A128GCM", + "request_object_signing_alg" : "PS256", + "require_signed_request_object" : "true", + "tls_client_certificate_bound_access_tokens":"true", + "request_object_encryption_alg" : "RSA-OAEP", + "request_object_encryption_enc" : "A128GCM", +} + +ACR = { + "authenticationSequence": { + "attributeStepId": 1, + "steps": [ + { + "id": 1, + "options": [ + { + "authenticator": "BasicAuthenticator", + "idp": "LOCAL" + } + ] + } + ], + "subjectStepId": 1, + "type": "USER_DEFINED", + "script": "var supportedAcrValues = ['acr1', 'urn:mace:incommon:iap:silver',];\n\nvar onLoginRequest = function(context) {\n var selectedAcr = selectAcrFrom(context, supportedAcrValues);\n Log.info('--------------- ACR selected: ' + selectedAcr);\n context.selectedAcr = selectedAcr;\n executeStep(1);\n};\n" + } +} + +# SP App configs + +JWKS_1 = "http://localhost:5002/jwks1" +JWKS_2 = "http://localhost:5002/jwks2" + + +PVTKEYJWT_APP1 = { + "client_name": "pvtkeyjwt_fapi1", + "token_endpoint_auth_method": "private_key_jwt", + "client_id": "pvtkeyjwt_fapi1_client_id", + "client_secret": "pvtkeyjwt_fapi1_client_secret", + "redirect_uris": ["https://localhost.emobix.co.uk:8443/test/a/fapi-wso2is/callback"], + "jwks_uri": JWKS_1, + "require_pushed_authorization_requests" : "false", +} + +PVTKEYJWT_APP2 = { + "client_name": "pvtkeyjwt_fapi2", + "token_endpoint_auth_method": "private_key_jwt", + "client_id": "pvtkeyjwt_fapi2_client_id", + "client_secret": "pvtkeyjwt_fapi2_client_secret", + "redirect_uris": ["https://localhost.emobix.co.uk:8443/test/a/fapi-wso2is/callback?dummy1=lorem&dummy2=ipsum"], + "jwks_uri": JWKS_2, + "require_pushed_authorization_requests" : "false", +} + +MTLS_APP1 = { + "client_name": "mtls_fapi1", + "token_endpoint_auth_method": "tls_client_auth", + "client_id": "mtls_fapi1_client_id", + "client_secret": "mtls_fapi1_client_secret", + "redirect_uris": ["https://localhost.emobix.co.uk:8443/test/a/fapi-wso2is/callback"], + "jwks_uri": JWKS_1, + "require_pushed_authorization_requests" : "false", +} + +MTLS_APP2 = { + "client_name": "mtls_fapi2", + "token_endpoint_auth_method": "tls_client_auth", + "client_id": "mtls_fapi2_client_id", + "client_secret": "mtls_fapi2_client_secret", + "redirect_uris": ["https://localhost.emobix.co.uk:8443/test/a/fapi-wso2is/callback?dummy1=lorem&dummy2=ipsum"], + "jwks_uri": JWKS_2, + "require_pushed_authorization_requests" : "false", +} + +PVTKEYJWT_PAR_APP1 = { + "client_name": "pvtkeyjwt_par_fapi1", + "token_endpoint_auth_method": "private_key_jwt", + "client_id": "pvtkeyjwt_par_fapi1_client_id", + "client_secret": "pvtkeyjwt_par_fapi1_client_secret", + "redirect_uris": ["https://localhost.emobix.co.uk:8443/test/a/fapi-wso2is/callback"], + "jwks_uri": JWKS_1, + "require_pushed_authorization_requests" : "true", +} + +PVTKEYJWT_PAR_APP2 = { + "client_name": "pvtkeyjwt_par_fapi2", + "token_endpoint_auth_method": "private_key_jwt", + "client_id": "pvtkeyjwt_par_fapi2_client_id", + "client_secret": "pvtkeyjwt_par_fapi2_client_secret", + "redirect_uris": ["https://localhost.emobix.co.uk:8443/test/a/fapi-wso2is/callback?dummy1=lorem&dummy2=ipsum"], + "jwks_uri": JWKS_2, + "require_pushed_authorization_requests" : "true", +} + +MTLS_PAR_APP1 = { + "client_name": "mtls_par_fapi1", + "token_endpoint_auth_method": "tls_client_auth", + "client_id": "mtls_par_fapi1_client_id", + "client_secret": "mtls_par_fapi1_client_secret", + "redirect_uris": ["https://localhost.emobix.co.uk:8443/test/a/fapi-wso2is/callback"], + "jwks_uri": JWKS_1, + "require_pushed_authorization_requests" : "true", +} + +MTLS_PAR_APP2 = { + "client_name": "mtls_par_fapi2", + "token_endpoint_auth_method": "tls_client_auth", + "client_id": "mtls_par_fapi2_client_id", + "client_secret": "mtls_par_fapi2_client_secret", + "redirect_uris": ["https://localhost.emobix.co.uk:8443/test/a/fapi-wso2is/callback?dummy1=lorem&dummy2=ipsum"], + "jwks_uri": JWKS_2, + "require_pushed_authorization_requests" : "true", +} diff --git a/oidc-fapi-conformance-tests/export_results_fapi.py b/oidc-fapi-conformance-tests/export_results_fapi.py new file mode 100644 index 0000000000..494ba7a7dc --- /dev/null +++ b/oidc-fapi-conformance-tests/export_results_fapi.py @@ -0,0 +1,75 @@ +import json +import warnings +import requests +import sys + +conformance_suite_url = sys.argv[1] + + +# export test results of a given test plan +def save_results(plan, i): + response = requests.get(url=conformance_suite_url + "/api/plan/exporthtml/" + plan['_id'], stream=True, verify=False) + with open("./" + plan['planName'] + "_"+ str(i) + "_test_results.zip", 'wb') as fileDir: + for chunk in response.iter_content(chunk_size=128): + fileDir.write(chunk) + + +# return names of all failed, warnings and other test cases of a given test plan +def get_failed_tests(plan): + test_fails = [] + test_warnings = [] + test_others = [] + test_log = json.loads(requests.get(url=conformance_suite_url + "/api/log?length=100&search=" + plan['_id'], verify=False).content) + for test in test_log['data']: + if "result" in test and test['result'] == "FAILED": + test_fails.append('Test Name: ' + test['testName'] + ' id: ' + test['testId']) + elif "result" in test and test['result'] == "WARNING": + test_warnings.append('Test Name: ' + test['testName'] + ' id: ' + test['testId']) + else: + test_others.append('Test Name: ' + test['testName'] + ' id: ' + test['testId']) + return { + 'fails': test_fails, + 'warnings': test_warnings, + 'others': test_others + } + + +if __name__ == '__main__': + failed_plan_details = dict() + contains_fails = False + warnings.filterwarnings("ignore") + plan_list = json.loads(requests.get(url=conformance_suite_url + "/api/plan?length=50", verify=False).content) + print("======================\nExporting test results\n======================") + i = 0 + for test_plan in plan_list['data']: + i += 1 + save_results(test_plan, i) + failed_tests_list = get_failed_tests(test_plan) + if len(failed_tests_list['fails']) > 0 or len(failed_tests_list['warnings']) > 0: + failed_plan_details[test_plan['planName']] = failed_tests_list + if len(failed_tests_list['fails']) > 0: + contains_fails = True + + if failed_plan_details: + print("Following tests have fails/warnings\n===========================") + for test_plan in failed_plan_details: + failed_count = len(failed_plan_details[test_plan]['fails']) + warnings_count = len(failed_plan_details[test_plan]['warnings']) + success_count = len(failed_plan_details[test_plan]['others']) + total_count = failed_count + warnings_count + success_count + print("\n"+test_plan+"\n-----------------------------------") + print("Total Test Cases: " + str(total_count)) + print("Successful: " + str(success_count)) + print("Warnings: " + str(warnings_count)) + print("Failures: " + str(failed_count)) + print("\nFailed Test Cases\n-----") + print(*failed_plan_details[test_plan]['fails'], sep="\n") + print("\nTest Cases with Warnings\n--------") + print(*failed_plan_details[test_plan]['warnings'], sep="\n") + if contains_fails: + sys.exit(1) + else: + sys.exit(0) + else: + print("\nAll test plans finished successfully") + sys.exit(0) diff --git a/oidc-fapi-conformance-tests/resource-server/jwks_to_host/serverjwks.py b/oidc-fapi-conformance-tests/resource-server/jwks_to_host/serverjwks.py new file mode 100644 index 0000000000..7b0eac27ac --- /dev/null +++ b/oidc-fapi-conformance-tests/resource-server/jwks_to_host/serverjwks.py @@ -0,0 +1,31 @@ +jwks = { + "keys": [ + { + "kty": "RSA", + "e": "AQAB", + "use": "enc", + "kid": "/HFQNRpvCQsnCGbEcrD0zED/45Y=", + "alg": "RSA-OAEP", + "n": "t-_wpch7k6xcgLYfW3xeZnHk9rjaathVlyCo1j_I_dICNoFs-vpNsDua3lgg3BC5VTy8UjDh-4PFBB_GNRr_Hd6otR-fFnY6n15GKkl-MLV4RbqVECqIrtgG_-bY2CDvoMCkCEJijXdYRa20vPQh6Iav6yekworv1LW1ry1dmHTrTfCczdqMHmtKpzVAtnQwD8TbCHKs60tZIUjXLvktKkkPK0HE8s_ggSrSEFCiFCSsb9ckB4NiFZ18YrHza221bAUKT3Yj5G1P15QEfu_4UstDuqG9YfrAk46aEVlVnKYG1AQnIK8hQERyturXe-ZrxCMMyysHaVSe32sS49vydw" + }, + { + "kty": "RSA", + "e": "AQAB", + "use": "sig", + "kid": "ZR+iPrkP5j2j9sFIYfjr6RBNAxA=", + "alg": "PS256", + "n": "vXeUqQt8sH0GdF8JzwDKlbAaHryPuAaHXQl0jX8czHAtvR9__DB41ebXDuZo8OUk0R_mvGceumCRidNxWiJEoREajK2fXVH4MnttWiWIiUShV2OlJJeeOC91mQsIh0hKztv-HkWozIlSDBlBzrwp06nrVI8eNSS7DveuFaG4ASXQBcOH6iO_sZjl-jCe8OgXa9xC8KcuOEIgV0j5t8ZZb2Jc_8y0OoVKUfUbIPTZ1J0nspK8oMR1dbLa9iW-BN10stoDKZP7qO3xOQWkYVywVtqOIAUY1o5OD-Vpgbvv-yvwN6184sTRRl5p_Jh9OafXxZ7Sp8bXn7MRuUpvGr8jQw" + }, + { + "kid" : "TKHsPIIGBAJoSqEEgAfLWK3iCMs", + "kty" : "RSA", + "n" : "l4xpanObBhnblwMctIRaFzBpr8kleDC6lkmJ8HKFpqjyb8eHHw9mlc5fPsZx-I1WbPfDVwAVe9-s2HgDutza-On2yE1DptCds4pWOg36s3GyRiPGm8s6WUJRqKLmSN1EXmvRgT3u3eEfsZ0BNPnomz1qibKRt5ih0bW83h4NyACufV87ur7r7QTDYyVrlf5P9-x-77Xi1cwvHsABBILMZ-xicmnp8IlDi6TV3maVhZ35wzaBZ_V5Dmwd4vUr0H5zOizns0WGxVQG88I8uatq1qePvy03w7rM7ECzVQRyR_7PWssrcPY0Kq_0Wd8HuxosqGnny9CqhGc6Jb6WvVnhuw", + "e" : "AQAB", + "use" : "tls", + "x5c" : [ "MIIFODCCBCCgAwIBAgIEWca5LzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJHQjEUMBIGA1UEChMLT3BlbkJhbmtpbmcxLjAsBgNVBAMTJU9wZW5CYW5raW5nIFByZS1Qcm9kdWN0aW9uIElzc3VpbmcgQ0EwHhcNMjMwNDE3MDQ1ODE2WhcNMjQwNTE3MDUyODE2WjBhMQswCQYDVQQGEwJHQjEUMBIGA1UEChMLT3BlbkJhbmtpbmcxGzAZBgNVBAsTEjAwMTU4MDAwMDFIUVFyWkFBWDEfMB0GA1UEAxMWb1E0S29hYXZwT3VvRTdydlFzWkVPVjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJeMaWpzmwYZ25cDHLSEWhcwaa/JJXgwupZJifByhaao8m/Hhx8PZpXOXz7GcfiNVmz3w1cAFXvfrNh4A7rc2vjp9shNQ6bQnbOKVjoN+rNxskYjxpvLOllCUaii5kjdRF5r0YE97t3hH7GdATT56Js9aomykbeYodG1vN4eDcgArn1fO7q+6+0Ew2Mla5X+T/fsfu+14tXMLx7AAQSCzGfsYnJp6fCJQ4uk1d5mlYWd+cM2gWf1eQ5sHeL1K9B+czos57NFhsVUBvPCPLmratanj78tN8O6zOxAs1UEckf+z1rLK3D2NCqv9FnfB7saLKhp58vQqoRnOiW+lr1Z4bsCAwEAAaOCAgQwggIAMA4GA1UdDwEB/wQEAwIHgDAgBgNVHSUBAf8EFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwgeAGA1UdIASB2DCB1TCB0gYLKwYBBAGodYEGAWQwgcIwKgYIKwYBBQUHAgEWHmh0dHA6Ly9vYi50cnVzdGlzLmNvbS9wb2xpY2llczCBkwYIKwYBBQUHAgIwgYYMgYNVc2Ugb2YgdGhpcyBDZXJ0aWZpY2F0ZSBjb25zdGl0dXRlcyBhY2NlcHRhbmNlIG9mIHRoZSBPcGVuQmFua2luZyBSb290IENBIENlcnRpZmljYXRpb24gUG9saWNpZXMgYW5kIENlcnRpZmljYXRlIFByYWN0aWNlIFN0YXRlbWVudDBtBggrBgEFBQcBAQRhMF8wJgYIKwYBBQUHMAGGGmh0dHA6Ly9vYi50cnVzdGlzLmNvbS9vY3NwMDUGCCsGAQUFBzAChilodHRwOi8vb2IudHJ1c3Rpcy5jb20vb2JfcHBfaXNzdWluZ2NhLmNydDA6BgNVHR8EMzAxMC+gLaArhilodHRwOi8vb2IudHJ1c3Rpcy5jb20vb2JfcHBfaXNzdWluZ2NhLmNybDAfBgNVHSMEGDAWgBRQc5HGIXLTd/T+ABIGgVx5eW4/UDAdBgNVHQ4EFgQUSoZfmnXGAPddPqfHWVOvkxD89MgwDQYJKoZIhvcNAQELBQADggEBABHzHOJzn4DPHay8xGzlWJIxxe+XsNtupR5V/ouEGCzJMUPmegYeK2Kiv+Z9nJKnbspgqLil52yauKWRmiXif4FWoOPRwspR9ijnynCgIp6z3EAOawbe28HkaGEfAi8PMqdNAYLKpXg35TUnbP+p2Q55Grq9EpSR2APQfJ4TjgLgKjqLRf/RjJAY9hJbQJIUl07esYf8hH7mX6uHDCywzic+UEQ3tUfo7PgWmnmtyUdFcW1qAl4P80a5fb8Wq0gNu6gN5tK2bg5TfSo3Gp2It8NVu/dY7q3ur7CAYTXrThjg4GXUQgVqYgV3pHbr1LTAiRtac7RBhMNPCklZA78RpTM=" ], + "x5t" : "m9PZfscobhscXz8oFMGQ6CDsxh0=", + "x5u" : "https://keystore.openbankingtest.org.uk/0015800001HQQrZAAX/TKHsPIIGBAJoSqEEgAfLWK3iCMs.pem", + "x5t#S256" : "k0p--ML7nfkE2pULKryszJRBx2ThBMaxHgJOePosits=" + } + ] +} diff --git a/oidc-fapi-conformance-tests/resource-server/jwks_to_host/serverjwks2.py b/oidc-fapi-conformance-tests/resource-server/jwks_to_host/serverjwks2.py new file mode 100644 index 0000000000..58d7fc1bdb --- /dev/null +++ b/oidc-fapi-conformance-tests/resource-server/jwks_to_host/serverjwks2.py @@ -0,0 +1,31 @@ +jwks = { + "keys": [ + { + "kty": "RSA", + "e": "AQAB", + "use": "enc", + "kid": "k6mOquqT8OLAXapY1u3+UC6pASs=", + "alg": "RSA-OAEP", + "n": "k8iJ3uXUfy-2Izij0Q8h39f7zMpcaCqGbe47jXRqbnchVdnN1GXczxeim3HO_p6W9WN-x3rdrxseaEQzt-U5N40qdIz5FbUCVoiTs9mXkyrQTxU14AHjBA04OxsBc4obHtm1bZPFz9kgLsvNpPqq8lA7fni6PBvfnEh9YtsV9McuUnvoURNpXzEmsf9zdc1XOCUpj1jjz-bK_SI1cCJLfmDmvH32VyU0emVMcxdYdNFYgN21RlauoYmuxlWUUPLxIwN2x-T0klfDB82_IKAMuvVItfVqv67XkP8NDelhyaq5afh3PQjHASlJH3iWuSVekRE-I2metmPeExhVyYLObw" + }, + { + "kty": "RSA", + "e": "AQAB", + "use": "sig", + "kid": "KZtSs4C20n0rxwT9tJBIn/gBzqQ=", + "alg": "PS256", + "n": "kDVCtgwQpS3_hhSCFq0cE5TViIKdt8NRPZdl0rX9Dg6jJXM7BzaLgnMIZteBrXEMrx-W5TQ7Li6l3m3KK1fnzzSaqdAYwVII5b2hL7Y0QVro0nymJTkW9Qq4NH9uKSJUnFxeTE7OI4rL3I-pyNVBS28BbK_Xry2A4gZXMdNhyYDzUJepsOGPVsI2mwVSzTW5I4BZ1CjDpwSFrFwCzJlPGieDHq-gXLH5ne8GotDkjnRrrNs1bFbMEekVcy_VYXfj6cLSGfDZqKRsLLHziIix13FwWRPR8ddPkhODNREOuwQR2fQeQdJ_yhRMaOuBsPmAQuzhLejdjsCX0KBB4OxPZQ" + }, + { + "kty": "RSA", + "e": "AQAB", + "kid": "a55e8a7b-86da-4056-aea5-dacdd167b917", + "n": "myUaNObS1bCecqALtY2pRJg3FuVRGQnKgPlS5KbOmwzmeVQEDFjEGDO1OjTrYskQvNRPMD_2SAsgwyCP7AbU5LjvlAKB7KgObB_RI-4VEGXQbQCPEC8nPOzZqKPPGJhFspRriCY7Uo9TdeXPz2aLyXCNNmXG0aTcAHAEbh_mdrlNju5kyWBY9cOdODsJzUfiasxsQWRRpeZvpmGEa2nQkvTj7gunXpBauCIeuWin4_xt36nCaY6Yu6CVMgSH6_33eP7u_wFdip2ZhDeeiI6Er3KcU8PUDu6uubvU-CS6r5aTkcK8yIErvNnhKf0UOi_KQMplMVgyWhUNcuGZ8NCuZw", + "use" : "tls", + "x5c" : ["MIIFODCCBCCgAwIBAgIEWcbiiTANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJHQjEUMBIGA1UEChMLT3BlbkJhbmtpbmcxLjAsBgNVBAMTJU9wZW5CYW5raW5nIFByZS1Qcm9kdWN0aW9uIElzc3VpbmcgQ0EwHhcNMjMxMTE1MDUxMDMxWhcNMjQxMjE1MDU0MDMxWjBhMQswCQYDVQQGEwJHQjEUMBIGA1UEChMLT3BlbkJhbmtpbmcxGzAZBgNVBAsTEjAwMTU4MDAwMDFIUVFyWkFBWDEfMB0GA1UEAxMWakZRdVE0ZVFiTkNNU3FkQ29nMjFuRjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJslGjTm0tWwnnKgC7WNqUSYNxblURkJyoD5UuSmzpsM5nlUBAxYxBgztTo062LJELzUTzA/9kgLIMMgj+wG1OS475QCgeyoDmwf0SPuFRBl0G0AjxAvJzzs2aijzxiYRbKUa4gmO1KPU3Xlz89mi8lwjTZlxtGk3ABwBG4f5na5TY7uZMlgWPXDnTg7Cc1H4mrMbEFkUaXmb6ZhhGtp0JL04+4Lp16QWrgiHrlop+P8bd+pwmmOmLuglTIEh+v993j+7v8BXYqdmYQ3noiOhK9ynFPD1A7urrm71Pgkuq+Wk5HCvMiBK7zZ4Sn9FDovykDKZTFYMloVDXLhmfDQrmcCAwEAAaOCAgQwggIAMA4GA1UdDwEB/wQEAwIHgDAgBgNVHSUBAf8EFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwgeAGA1UdIASB2DCB1TCB0gYLKwYBBAGodYEGAWQwgcIwKgYIKwYBBQUHAgEWHmh0dHA6Ly9vYi50cnVzdGlzLmNvbS9wb2xpY2llczCBkwYIKwYBBQUHAgIwgYYMgYNVc2Ugb2YgdGhpcyBDZXJ0aWZpY2F0ZSBjb25zdGl0dXRlcyBhY2NlcHRhbmNlIG9mIHRoZSBPcGVuQmFua2luZyBSb290IENBIENlcnRpZmljYXRpb24gUG9saWNpZXMgYW5kIENlcnRpZmljYXRlIFByYWN0aWNlIFN0YXRlbWVudDBtBggrBgEFBQcBAQRhMF8wJgYIKwYBBQUHMAGGGmh0dHA6Ly9vYi50cnVzdGlzLmNvbS9vY3NwMDUGCCsGAQUFBzAChilodHRwOi8vb2IudHJ1c3Rpcy5jb20vb2JfcHBfaXNzdWluZ2NhLmNydDA6BgNVHR8EMzAxMC+gLaArhilodHRwOi8vb2IudHJ1c3Rpcy5jb20vb2JfcHBfaXNzdWluZ2NhLmNybDAfBgNVHSMEGDAWgBRQc5HGIXLTd/T+ABIGgVx5eW4/UDAdBgNVHQ4EFgQU7T6cMtCSQTT5JWW3O6vifRUSdpkwDQYJKoZIhvcNAQELBQADggEBAE9jrd/AE65vy3SEWdmFKPS4su7uEHy+KH18PETV6jMF2UFIJAOx7jl+5a3O66NkcpxFPeyvSuH+6tAAr2ZjpoQwtW9tZ9k2KSOdNOiJeQgjavwQC6t/BHI3yXWOIQm445BUN1cV9pagcRJjRyL3SPdHVoRfIbF7VI/+ULHwWdZYPXxtwUoda1mQFf6a+2lO4ziUHb3U8iD90FBURzID7WJ1ODSeB5zE/hG9Sxd9wlSXvl1oNmc/ha5oG/7rJpRqrx5Dcq3LEoX9iZZ3knHLkCm/abIQ7Nff8GQytuGhnGZxmGFYKDXdKElcl9dAlZ3bIK2I+I6jD2z2XvSfrhFyRjU="], + "x5t" : "i_rXxQv8kzzoPawCeJN_KdafnDA=", + "x5u" : "https://keystore.openbankingtest.org.uk/0015800001HQQrZAAX/a55e8a7b-86da-4056-aea5-dacdd167b917.pem" + + } + ] +} diff --git a/oidc-fapi-conformance-tests/resource-server/nginx-proxy b/oidc-fapi-conformance-tests/resource-server/nginx-proxy new file mode 100644 index 0000000000..2a918d8b7d --- /dev/null +++ b/oidc-fapi-conformance-tests/resource-server/nginx-proxy @@ -0,0 +1,30 @@ +server { + listen 83; + server_name www.fapi-resource.com; + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl; + ssl_certificate /etc/nginx/ssl/serverCA.crt; + ssl_certificate_key /etc/nginx/ssl/serverCA.key; + + ssl_verify_client optional_no_ca; + + ssl_protocols TLSv1.2; + ssl_ciphers 'DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384'; + ssl_prefer_server_ciphers on; + + location /resource { + + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Prefix /; + proxy_set_header X-Forwarded-Port 443; + proxy_set_header X-Forwarded-Ssl on; + proxy_set_header X-SSL-CERT $ssl_client_escaped_cert; + + proxy_pass http://localhost:5002/resource; + } +} diff --git a/oidc-fapi-conformance-tests/resource-server/requirements.txt b/oidc-fapi-conformance-tests/resource-server/requirements.txt new file mode 100644 index 0000000000..07b0585ef5 --- /dev/null +++ b/oidc-fapi-conformance-tests/resource-server/requirements.txt @@ -0,0 +1,5 @@ +Flask==3.0.0 +cryptography==41.0.5 +urllib3==2.0.7 +requests==2.31.0 +PyJWT==2.8.0 \ No newline at end of file diff --git a/oidc-fapi-conformance-tests/resource-server/resource-server.py b/oidc-fapi-conformance-tests/resource-server/resource-server.py new file mode 100644 index 0000000000..3dd61efd98 --- /dev/null +++ b/oidc-fapi-conformance-tests/resource-server/resource-server.py @@ -0,0 +1,164 @@ +import base64 +import uuid +import requests +import jwt +from flask import request +from flask import Flask, jsonify +from cryptography.hazmat.primitives import hashes +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from urllib.parse import unquote +from jwks_to_host import serverjwks, serverjwks2 + + +app = Flask(__name__) +app.config['SECRET_KEY'] = 'secret' + +class Unauthorized(Exception): + status_code = 401 + + def __init__(self, message, status_code=None, payload=None): + Exception.__init__(self) + self.message = message + if status_code is not None: + self.status_code = status_code + self.payload = payload + + def to_dict(self): + rv = dict(self.payload or ()) + rv['message'] = self.message + return rv + +def getAccessToken(request): + authHeader = request.headers.get('Authorization') + if authHeader is None: + raise Unauthorized('Missing Authorization header.') + authHeaderParts = authHeader.split(" ") + if len(authHeaderParts) != 2 or authHeaderParts[0] != 'Bearer': + raise Unauthorized('Bearer token missing.') + accessToken = authHeaderParts[1] + if accessToken == None or accessToken == '': + raise Unauthorized('Bearer token is null.') + return accessToken + +def isJwt(accessToken): + try: + decodedToken = jwt.decode(accessToken, options={"verify_signature": False}, algorithms=['RS256']) + return True + except jwt.DecodeError: + return False + +def getCnfFromIntrospect(accessToken): + headers = {'Authorization': 'Basic ' + "YWRtaW46YWRtaW4=", 'Content-Type': 'application/x-www-form-urlencoded'} + data = {'token': accessToken} + response = requests.post('https://localhost:9443/oauth2/introspect', headers=headers, data=data, verify=False) + if response.status_code == 200: + introspect = response.json() + print ("[INFO] Introspect response: \n", introspect) + cnf = introspect.get('cnf') + print ("[INFO] Introspect cnf: ", cnf) + if cnf == None: + raise Unauthorized('No cnf in introspect response.') + return cnf + else: + raise Exception('Failed to introspect: ' + response.text) + +def getThumbprintFromAccessToken(accessToken): + if not isJwt(accessToken): + cnf = getCnfFromIntrospect(accessToken) + else: + try: + decodedToken = jwt.decode(accessToken, options={"verify_signature": False}, algorithms=['RS256']) + except jwt.DecodeError as e: + print ("Decode Error: ", e) + raise Unauthorized('Unable to decode Access Token.') + try: + cnf = decodedToken.get('cnf') + except Exception as e: + raise Unauthorized('Unable to get cnf from Access Token jwt.') + if cnf is not None: + thumbprint_b64 = cnf.get('x5t#S256') + if thumbprint_b64 is not None: + thumbprint_bytes = base64.urlsafe_b64decode(thumbprint_b64 + '==') + thumbprint_hex = thumbprint_bytes.hex() + print ("[INFO] Thumbprint from Access Token: ", thumbprint_hex) + return thumbprint_hex + else: + raise Unauthorized('Unable to get x5t#S256 from cnf.') + else: + raise Unauthorized('cnf is null.') + +def computeSHA256Thumbprint(clientCertificate): + try: + cert = unquote(clientCertificate) + cert_obj = x509.load_pem_x509_certificate(cert.encode('utf-8'), default_backend()) + thumbprint = cert_obj.fingerprint(hashes.SHA256()) + thumbprint_hex= thumbprint.hex() + print ("[INFO] Thumbprint from Client Certificate: ", thumbprint_hex) + return thumbprint_hex + except Exception as e: + raise Unauthorized('Unable to compute SHA256 thumbprint from client certificate.') + +def getThumbprintFromClientCertificate(request): + clientCertificate = request.headers.get('X-Ssl-Cert') + if clientCertificate is None: + raise Unauthorized('Missing X-Ssl-Cert header.') + clientCertificate = clientCertificate.strip().split(" ")[0] + if clientCertificate == None or clientCertificate == '': + raise Unauthorized('X-Ssl-Cert header is null.') + return computeSHA256Thumbprint(clientCertificate) + +def validateTokenWithIntrospect(accessToken): + headers = {'Authorization': 'Basic ' + "YWRtaW46YWRtaW4=", 'Content-Type': 'application/x-www-form-urlencoded'} + data = {'token': accessToken} + response = requests.post('https://localhost:9443/oauth2/introspect', headers=headers, data=data, verify=False) + if response.status_code == 200: + introspect = response.json() + activeState = introspect.get('active') + print ("[INFO] Introspect active state: ", activeState) + if activeState == False: + raise Unauthorized('Access token validation failed. Introspect active state is false.') + return activeState + else: + raise Exception('Failed to introspect token: ' + response.text) + +@app.route('/resource') +def resource(): + print ("[INFO] Resource endpoint...") + print ("[INFO] Headers : \n", request.headers) + print ("==================") + accesToekn = getAccessToken(request) + cnf = getThumbprintFromAccessToken(accesToekn) + clientCertificateHash = getThumbprintFromClientCertificate(request) + if cnf != clientCertificateHash: + raise Unauthorized('Thumbprints from Cleint Certificate and Access Token do not match.') + validateTokenWithIntrospect(accesToekn) + print ("==================") + + response_data = {'message': 'This is a mock resource endpoint with FAPI validations.'} + response = jsonify(response_data) + xFapi = request.headers.get('X-Fapi-Interaction-Id') + if xFapi is None: + xFapi = uuid.uuid4() + + response.headers['x-fapi-interaction-id'] = xFapi + return response + +@app.route('/jwks1') +def jwks1(): + print ("[INFO] JWKS endpoint 1") + return serverjwks.jwks + +@app.route('/jwks2') +def jwks2(): + print ("[INFO] JWKS endpoint 2") + return serverjwks2.jwks + +@app.errorhandler(Unauthorized) +def handleUnauthorized(error): + response = jsonify(error.to_dict()) + response.status_code = error.status_code + return response + +if __name__ == '__main__': + app.run(debug=False, port=5002, host='0.0.0.0') diff --git a/oidc-fapi-conformance-tests/start_conformance_suite.py b/oidc-fapi-conformance-tests/start_conformance_suite.py new file mode 100644 index 0000000000..9a01ac2789 --- /dev/null +++ b/oidc-fapi-conformance-tests/start_conformance_suite.py @@ -0,0 +1,26 @@ +import subprocess +import os +import sys + + +def start(file): + try: + os.chmod(file, 0o777) + process = subprocess.Popen("sudo -E docker-compose -f " + file + " up", shell=True, stdout=subprocess.PIPE) + while True: + output = process.stdout.readline() + if b'conformance-suite_server_1 exited with code 1' in output: + raise Exception("Conformance suite failed to start") + elif b'Starting application' in output: + print("Server Started") + break + elif output: + print(output.strip()) + rc = process.poll() + return rc + except FileNotFoundError: + print(file + " not found") + raise + +# takes OIDC conformance suite url as an argument +start(sys.argv[1]) diff --git a/oidc-fapi-conformance-tests/test_runner_fapi.sh b/oidc-fapi-conformance-tests/test_runner_fapi.sh new file mode 100644 index 0000000000..ccc9ad9f7b --- /dev/null +++ b/oidc-fapi-conformance-tests/test_runner_fapi.sh @@ -0,0 +1,53 @@ +#!/bin/bash +x + +CONFORMANCE_SUITE_PATH=./conformance-suite +PATH_TO_SCRIPTS=./product-is/oidc-fapi-conformance-tests +IS_SUCCESSFUL=false + +# sed -i '/^.*all_test_modules\ =.*/a \ \ \ \ print("All available OIDC test modules:")\n\ \ \ \ print("==============================================")\n\ \ \ \ print(sorted(all_test_modules.keys()))' $CONFORMANCE_SUITE_PATH/scripts/run-test-plan.py + +echo "========================" +echo "Running Tests" +echo "========================" + +echo +echo "FAPI Basic test plan - private_key_jwt, plain_fapi, plain_response, by_value" +echo "-----------------------------" +echo +python3 $CONFORMANCE_SUITE_PATH/scripts/run-test-plan.py fapi1-advanced-final-test-plan[client_auth_type=private_key_jwt][fapi_profile=plain_fapi][fapi_response_mode=plain_response][fapi_auth_request_method=by_value] $PATH_TO_SCRIPTS/config/IS_config_fapi_pvtkeyjwt.json 2>&1 | tee fapi-testplan-pvtkeyjwt-log.txt +echo + +echo +echo "FAPI Basic test plan - mtls, plain_fapi, plain_response, by_value" +echo "-----------------------------" +echo +python3 $CONFORMANCE_SUITE_PATH/scripts/run-test-plan.py fapi1-advanced-final-test-plan[client_auth_type=mtls][fapi_profile=plain_fapi][fapi_response_mode=plain_response][fapi_auth_request_method=by_value] $PATH_TO_SCRIPTS/config/IS_config_fapi_mtls.json 2>&1 | tee fapi-testplan-mtls-log.txt +echo + +# echo +# echo "FAPI Basic test plan - private_key_jwt, plain_fapi, plain_response, pushed" +# echo "-----------------------------" +# echo +# python3 $CONFORMANCE_SUITE_PATH/scripts/run-test-plan.py fapi1-advanced-final-test-plan[client_auth_type=private_key_jwt][fapi_profile=plain_fapi][fapi_response_mode=plain_response][fapi_auth_request_method=pushed] $PATH_TO_SCRIPTS/config/IS_config_fapi_pvtkeyjwt_par.json 2>&1 | tee fapi-testplan-pvtkeyjwt-par-log.txt +# echo + +# echo +# echo "FAPI Basic test plan - mtls, plain_fapi, plain_response, pushed" +# echo "-----------------------------" +# echo +# python3 $CONFORMANCE_SUITE_PATH/scripts/run-test-plan.py fapi1-advanced-final-test-plan[client_auth_type=mtls][fapi_profile=plain_fapi][fapi_response_mode=plain_response][fapi_auth_request_method=pushed] $PATH_TO_SCRIPTS/config/IS_config_fapi_mtls_par.json 2>&1 | tee fapi-testplan-mtls-par-log.txt +# echo + +# echo +# echo "FAPI Basic test plan - private_key_jwt, plain_fapi, jarm, by_value" +# echo "-----------------------------" +# echo +# python3 $CONFORMANCE_SUITE_PATH/scripts/run-test-plan.py fapi1-advanced-final-test-plan[client_auth_type=private_key_jwt][fapi_profile=plain_fapi][fapi_response_mode=jarm][fapi_auth_request_method=by_value] $PATH_TO_SCRIPTS/config/IS_config_fapi_pvtkeyjwt.json 2>&1 | tee fapi-testplan-pvtkeyjwt-jarm-log.txt +# echo + +# echo +# echo "FAPI Basic test plan - mtls, plain_fapi, jarm, by_value" +# echo "-----------------------------" +# echo +# python3 $CONFORMANCE_SUITE_PATH/scripts/run-test-plan.py fapi1-advanced-final-test-plan[client_auth_type=mtls][fapi_profile=plain_fapi][fapi_response_mode=jarm][fapi_auth_request_method=by_value] $PATH_TO_SCRIPTS/config/IS_config_fapi_mtls.json 2>&1 | tee fapi-testplan-mtls-jarm-log.txt +# echo