udap_security_test_kit 0.11.2 → 0.11.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/config/presets/UDAP_RunClientAgainstServer.json.erb +20 -0
- data/config/presets/UDAP_RunServerAgainstClient.json.erb +272 -0
- data/lib/udap_security_test_kit/client_credentials_token_exchange_test.rb +1 -1
- data/lib/udap_security_test_kit/client_suite/access_ac_group.rb +25 -0
- data/lib/udap_security_test_kit/client_suite/access_ac_interaction_test.rb +59 -0
- data/lib/udap_security_test_kit/client_suite/access_cc_group.rb +23 -0
- data/lib/udap_security_test_kit/client_suite/access_cc_interaction_test.rb +49 -0
- data/lib/udap_security_test_kit/client_suite/authorization_request_verification_test.rb +83 -0
- data/lib/udap_security_test_kit/client_suite/client_descriptions.rb +70 -0
- data/lib/udap_security_test_kit/client_suite/client_options.rb +20 -0
- data/lib/udap_security_test_kit/client_suite/oidc_jwks.json +32 -0
- data/lib/udap_security_test_kit/client_suite/oidc_jwks.rb +27 -0
- data/lib/udap_security_test_kit/client_suite/registration_ac_group.rb +18 -0
- data/lib/udap_security_test_kit/client_suite/registration_ac_verification_test.rb +38 -0
- data/lib/udap_security_test_kit/client_suite/registration_cc_group.rb +18 -0
- data/lib/udap_security_test_kit/client_suite/registration_cc_verification_test.rb +38 -0
- data/lib/udap_security_test_kit/client_suite/registration_interaction_test.rb +57 -0
- data/lib/udap_security_test_kit/client_suite/registration_request_verification.rb +242 -0
- data/lib/udap_security_test_kit/client_suite/token_request_ac_verification_test.rb +49 -0
- data/lib/udap_security_test_kit/client_suite/token_request_cc_verification_test.rb +49 -0
- data/lib/udap_security_test_kit/client_suite/token_request_verification.rb +223 -0
- data/lib/udap_security_test_kit/client_suite/token_use_verification_test.rb +40 -0
- data/lib/udap_security_test_kit/client_suite.rb +107 -0
- data/lib/udap_security_test_kit/docs/demo/FHIR Request.postman_collection.json +81 -0
- data/lib/udap_security_test_kit/docs/udap_client_suite_description.md +163 -0
- data/lib/udap_security_test_kit/endpoints/echoing_fhir_responder_endpoint.rb +96 -0
- data/lib/udap_security_test_kit/endpoints/mock_udap_server/authorization_endpoint.rb +28 -0
- data/lib/udap_security_test_kit/endpoints/mock_udap_server/registration_endpoint.rb +31 -0
- data/lib/udap_security_test_kit/endpoints/mock_udap_server/token_endpoint.rb +56 -0
- data/lib/udap_security_test_kit/endpoints/mock_udap_server/udap_authorization_response_creation.rb +63 -0
- data/lib/udap_security_test_kit/endpoints/mock_udap_server/udap_registration_response_creation.rb +28 -0
- data/lib/udap_security_test_kit/endpoints/mock_udap_server/udap_token_response_creation.rb +218 -0
- data/lib/udap_security_test_kit/endpoints/mock_udap_server.rb +382 -0
- data/lib/udap_security_test_kit/metadata.rb +4 -4
- data/lib/udap_security_test_kit/tags.rb +12 -0
- data/lib/udap_security_test_kit/urls.rb +52 -0
- data/lib/udap_security_test_kit/version.rb +2 -2
- data/lib/udap_security_test_kit.rb +8 -2
- metadata +36 -2
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../tags'
|
4
|
+
|
5
|
+
module UDAPSecurityTestKit
|
6
|
+
module UDAPClientOptions
|
7
|
+
module_function
|
8
|
+
|
9
|
+
UDAP_AUTHORIZATION_CODE = "#{UDAP_TAG},#{AUTHORIZATION_CODE_TAG}".freeze
|
10
|
+
UDAP_CLIENT_CREDENTIALS = "#{UDAP_TAG},#{CLIENT_CREDENTIALS_TAG}".freeze
|
11
|
+
|
12
|
+
def oauth_flow(suite_options)
|
13
|
+
if suite_options[:client_type].include?(AUTHORIZATION_CODE_TAG)
|
14
|
+
AUTHORIZATION_CODE_TAG
|
15
|
+
elsif suite_options[:client_type].include?(CLIENT_CREDENTIALS_TAG)
|
16
|
+
CLIENT_CREDENTIALS_TAG
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
{
|
2
|
+
"keys": [
|
3
|
+
{
|
4
|
+
"kty": "RSA",
|
5
|
+
"use": "sig",
|
6
|
+
"key_ops": [
|
7
|
+
"verify"
|
8
|
+
],
|
9
|
+
"alg": "RS256",
|
10
|
+
"kid": "618e76fc7eb7c3401c7fd47b9af5e94d",
|
11
|
+
"n": "uoYzdYZQDp7yHTfY4XL1PFCFPfijG1rP2dNgMmsqKbm9qpMjiUuXCpdoOXWE838pcu3Ahi6L3dGgGS3Aeys3YrnF6h9uRTD1DHf-UWlOq4iPCzk26a-bBuyVZDVqS7Y5HsyF7i_UvYV1W0EaHSpAF911Vo_W_FVyiSkAZP1yN0ECbbjnSqxhaa44SGw90oh6XnyNBLu7a11aLCURkaRJOKXHj_vf8UyaD-IA9YkvM80St7QF6u6LRVYmCShbpfWRmejh_Swf4zdqcPl-duUsZE-OBysoTq92FaXr605igM30Rdkx8hs-HTwZtjn5hB3XNTiG2Wka1cUXKNyH59u8Yw",
|
12
|
+
"e": "AQAB"
|
13
|
+
},
|
14
|
+
{
|
15
|
+
"kty": "RSA",
|
16
|
+
"use": "sig",
|
17
|
+
"key_ops": [
|
18
|
+
"sign"
|
19
|
+
],
|
20
|
+
"alg": "RS256",
|
21
|
+
"kid": "618e76fc7eb7c3401c7fd47b9af5e94d",
|
22
|
+
"d": "k2SxDVHRuXwIvuX-0EjTWZIXeF0eJuOgE_VgsvbUHpzUMBKNplTBSnFSvvUK1o_J5TPTSzVE-UhJRxxMWghQgAdlShkEPlDtk6jOou6gaBRFVQ0lQ4ys6M_TTZiYIrQgdyIPQ6Uwa4MmtbHAPQPCGhm6O2j27fdnxtNLqIJO2zF9OzF4VP5_v63bbMfwbKECvB_bbcGLmJrq2ClI1iTOw-GawQ3I8sfwf52D3mb-qDJyQwtomNxf7EhvmGBC9u_8JVY48qsCCYWr2KAauID02WtCVepxM4ltKOLJb6BL5QpKmJ8svYLWjSZvNJSdk-EoB9grqechyDOIa2DuU9g-QQ",
|
23
|
+
"n": "uoYzdYZQDp7yHTfY4XL1PFCFPfijG1rP2dNgMmsqKbm9qpMjiUuXCpdoOXWE838pcu3Ahi6L3dGgGS3Aeys3YrnF6h9uRTD1DHf-UWlOq4iPCzk26a-bBuyVZDVqS7Y5HsyF7i_UvYV1W0EaHSpAF911Vo_W_FVyiSkAZP1yN0ECbbjnSqxhaa44SGw90oh6XnyNBLu7a11aLCURkaRJOKXHj_vf8UyaD-IA9YkvM80St7QF6u6LRVYmCShbpfWRmejh_Swf4zdqcPl-duUsZE-OBysoTq92FaXr605igM30Rdkx8hs-HTwZtjn5hB3XNTiG2Wka1cUXKNyH59u8Yw",
|
24
|
+
"e": "AQAB",
|
25
|
+
"p": "x3syoDXXDJne_-aSeHMm3muQ1KfIGewMhklvGNZ6LVTIJWUE5UYiGshABmgn86GfS-uZc_oUo3WcXiCLUlZSBqZFTIyXgy9y979VWA0whPCYcVSLzSGOiUtv3Ys4VGYCmGSuJnyLDp0CNN63Gf_iGpjCRbaPCPdRnyBgfSEvETs",
|
26
|
+
"q": "718z8hC_AQXrhQxvTsCEv7FXSX0Ev10Tjdq94DcsD91g34KTbrF9K9wtPfBUQaUx5Z2rMOTdLxbtHKvP-YQ4YMoQioA9qsclcnwdvKoRcRWim9oBnLa3Iuqttdwc9U2FWEQ4wqv16rsQw7URU4qlYkiVjrHvRJb8Nx_AJsA8Tvk",
|
27
|
+
"dp": "waDGDVj1exfIq-ClgCFWM0N5-9E4nGDR729MVXGqemH3PMUHsX0YEaMa8p0bWpMhStJPy5GNgvTgaUVxtuRvDmFKlvlJAF-IWw7vyl5TIFdhwW_tm5nc_0uoNAW1EcdK8Z2YpWbym6avw54DYUtNr79jo8OGp49ZPPpybkNNqo0",
|
28
|
+
"dq": "ge3mL02Br9d7yKNAQ7niFH75Ry1yB0FJXOVPzUWFSDM84vVoe1wh-k2vzQAHa_50ABO-GXMQz_-cwsRLxj9LrtXfdp43Wtxv6h2OspqJjx1UP05tM5hF_dDua1lH6qqiZ4_YU2qtuDTD28cL2ZHXRWrqqyLQIiXmTzGPxjjwQ1k",
|
29
|
+
"qi": "n3fMznRlCCwuoCq0kv4tL0cravVhcg33rA-CQXlIxgqj3h1yiBJ4p76pHceV6tvYsPyX3YsUKNOBP9HcxuHK3I3kFqMN5Y4C4r8dTE4fbJK9QT_guo_k_GKijNp0NDNCCeKxU78SRYvA527VY-3Z6TUC_K6O-5Pd6aPJN1QDtoA"
|
30
|
+
}
|
31
|
+
]
|
32
|
+
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'jwt'
|
2
|
+
|
3
|
+
module UDAPSecurityTestKit
|
4
|
+
class OIDCJWKS
|
5
|
+
class << self
|
6
|
+
def jwks_json
|
7
|
+
@jwks_json ||=
|
8
|
+
JSON.pretty_generate(
|
9
|
+
{ keys: jwks.export[:keys].select { |key| key[:key_ops]&.include?('verify') } }
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
def default_jwks_path
|
14
|
+
@default_jwks_path ||= File.join(__dir__, 'oidc_jwks.json')
|
15
|
+
end
|
16
|
+
|
17
|
+
def jwks_path
|
18
|
+
@jwks_path ||=
|
19
|
+
ENV.fetch('SIMULATED_OIDC_JWKS_PATH', default_jwks_path)
|
20
|
+
end
|
21
|
+
|
22
|
+
def jwks
|
23
|
+
@jwks ||= JWT::JWK::Set.new(JSON.parse(File.read(jwks_path)))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative 'registration_interaction_test'
|
2
|
+
require_relative 'registration_ac_verification_test'
|
3
|
+
|
4
|
+
module UDAPSecurityTestKit
|
5
|
+
class UDAPClientRegistrationAuthorizationCode < Inferno::TestGroup
|
6
|
+
id :udap_client_registration_ac
|
7
|
+
title 'Client Registration'
|
8
|
+
description %(
|
9
|
+
During these tests, the client system will dynamically register with Inferno's
|
10
|
+
simulated UDAP Server to use the authorization_code flow. At any time, the client
|
11
|
+
may perform UDAP discovery on the simulated Inferno UDAP server.
|
12
|
+
)
|
13
|
+
run_as_group
|
14
|
+
|
15
|
+
test from: :udap_client_registration_interaction
|
16
|
+
test from: :udap_client_registration_ac_verification
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative '../tags'
|
2
|
+
require_relative '../urls'
|
3
|
+
require_relative '../endpoints/mock_udap_server'
|
4
|
+
require_relative 'registration_request_verification'
|
5
|
+
|
6
|
+
module UDAPSecurityTestKit
|
7
|
+
class UDAPClientRegistrationAuthorizationCodeVerification < Inferno::Test
|
8
|
+
include URLs
|
9
|
+
include RegistrationRequestVerification
|
10
|
+
|
11
|
+
id :udap_client_registration_ac_verification
|
12
|
+
title 'Verify UDAP Authorization Code Registration'
|
13
|
+
description %(
|
14
|
+
During this test, Inferno will verify that the client's UDAP
|
15
|
+
registration request is conformant.
|
16
|
+
)
|
17
|
+
input :udap_client_uri
|
18
|
+
output :udap_registration_jwt
|
19
|
+
|
20
|
+
def client_suite_id
|
21
|
+
return config.options[:endpoint_suite_id] if config.options[:endpoint_suite_id].present?
|
22
|
+
|
23
|
+
UDAPSecurityTestKit::UDAPSecurityClientTestSuite.id
|
24
|
+
end
|
25
|
+
|
26
|
+
run do
|
27
|
+
client_registration_requests = load_registration_requests_for_client_uri(udap_client_uri)
|
28
|
+
skip_if client_registration_requests.empty?,
|
29
|
+
"No UDAP Registration Requests made for client uri '#{udap_client_uri}'."
|
30
|
+
|
31
|
+
verify_registration_request(AUTHORIZATION_CODE_TAG, client_registration_requests.last) # most recent if several
|
32
|
+
|
33
|
+
assert messages.none? { |msg|
|
34
|
+
msg[:type] == 'error'
|
35
|
+
}, 'Invalid registration request. See messages for details.'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative 'registration_interaction_test'
|
2
|
+
require_relative 'registration_cc_verification_test'
|
3
|
+
|
4
|
+
module UDAPSecurityTestKit
|
5
|
+
class UDAPClientRegistrationClientCredentials < Inferno::TestGroup
|
6
|
+
id :udap_client_registration_cc
|
7
|
+
title 'Client Registration'
|
8
|
+
description %(
|
9
|
+
During these tests, the client system will dynamically register with Inferno's
|
10
|
+
simulated UDAP Server to use the client_credentials flow. At any time, the client
|
11
|
+
may perform UDAP discovery on the simulated Inferno UDAP server.
|
12
|
+
)
|
13
|
+
run_as_group
|
14
|
+
|
15
|
+
test from: :udap_client_registration_interaction
|
16
|
+
test from: :udap_client_registration_cc_verification
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative '../tags'
|
2
|
+
require_relative '../urls'
|
3
|
+
require_relative '../endpoints/mock_udap_server'
|
4
|
+
require_relative 'registration_request_verification'
|
5
|
+
|
6
|
+
module UDAPSecurityTestKit
|
7
|
+
class UDAPClientRegistrationClientCredentialsVerification < Inferno::Test
|
8
|
+
include URLs
|
9
|
+
include RegistrationRequestVerification
|
10
|
+
|
11
|
+
id :udap_client_registration_cc_verification
|
12
|
+
title 'Verify UDAP Client Credentials Registration'
|
13
|
+
description %(
|
14
|
+
During this test, Inferno will verify that the client's UDAP
|
15
|
+
registration request is conformant.
|
16
|
+
)
|
17
|
+
input :udap_client_uri
|
18
|
+
output :udap_registration_jwt
|
19
|
+
|
20
|
+
def client_suite_id
|
21
|
+
return config.options[:endpoint_suite_id] if config.options[:endpoint_suite_id].present?
|
22
|
+
|
23
|
+
UDAPSecurityTestKit::UDAPSecurityClientTestSuite.id
|
24
|
+
end
|
25
|
+
|
26
|
+
run do
|
27
|
+
client_registration_requests = load_registration_requests_for_client_uri(udap_client_uri)
|
28
|
+
skip_if client_registration_requests.empty?,
|
29
|
+
"No UDAP Registration Requests made for client uri '#{udap_client_uri}'."
|
30
|
+
|
31
|
+
verify_registration_request(CLIENT_CREDENTIALS_TAG, client_registration_requests.last) # most recent if several
|
32
|
+
|
33
|
+
assert messages.none? { |msg|
|
34
|
+
msg[:type] == 'error'
|
35
|
+
}, 'Invalid registration request. See messages for details.'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require_relative '../urls'
|
2
|
+
require_relative '../endpoints/mock_udap_server'
|
3
|
+
|
4
|
+
module UDAPSecurityTestKit
|
5
|
+
class UDAPClientRegistrationInteraction < Inferno::Test
|
6
|
+
include URLs
|
7
|
+
|
8
|
+
id :udap_client_registration_interaction
|
9
|
+
title 'Perform UDAP Registration'
|
10
|
+
description %(
|
11
|
+
During this test, Inferno will wait for the client to register
|
12
|
+
themselves as a UDAP client with Inferno's simulated UDAP server
|
13
|
+
using UDAP dynamic registration.
|
14
|
+
)
|
15
|
+
input :udap_client_uri,
|
16
|
+
title: 'UDAP Client URI',
|
17
|
+
type: 'text',
|
18
|
+
description: %(
|
19
|
+
The UDAP Client URI that will be used to register with Inferno's simulated UDAP server.
|
20
|
+
)
|
21
|
+
|
22
|
+
output :client_id
|
23
|
+
|
24
|
+
def client_suite_id
|
25
|
+
return config.options[:endpoint_suite_id] if config.options[:endpoint_suite_id].present?
|
26
|
+
|
27
|
+
UDAPSecurityTestKit::UDAPSecurityClientTestSuite.id
|
28
|
+
end
|
29
|
+
|
30
|
+
run do
|
31
|
+
generated_client_id = MockUDAPServer.client_uri_to_client_id(udap_client_uri)
|
32
|
+
output client_id: generated_client_id
|
33
|
+
|
34
|
+
wait(
|
35
|
+
identifier: generated_client_id,
|
36
|
+
message: %(
|
37
|
+
**UDAP Registration**
|
38
|
+
|
39
|
+
Make a UDAP dyanmic registration request to the UDAP-protected FHIR Server at
|
40
|
+
|
41
|
+
`#{client_fhir_base_url}`
|
42
|
+
|
43
|
+
For Client URI
|
44
|
+
|
45
|
+
`#{udap_client_uri}`
|
46
|
+
|
47
|
+
Metadata on Inferno's simulated UDAP server can be found at
|
48
|
+
|
49
|
+
`#{client_udap_discovery_url}`
|
50
|
+
|
51
|
+
[Click here](#{client_resume_pass_url}?token=#{generated_client_id}) once you have
|
52
|
+
succesfully completed the registration.
|
53
|
+
)
|
54
|
+
)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,242 @@
|
|
1
|
+
require_relative '../tags'
|
2
|
+
require_relative '../urls'
|
3
|
+
require_relative '../endpoints/mock_udap_server'
|
4
|
+
|
5
|
+
module UDAPSecurityTestKit
|
6
|
+
module RegistrationRequestVerification
|
7
|
+
def load_registration_requests_for_client_uri(client_uri)
|
8
|
+
load_tagged_requests(UDAP_TAG, REGISTRATION_TAG)
|
9
|
+
requests.select do |reg_request|
|
10
|
+
registered_uri = MockUDAPServer.udap_client_uri_from_registration_payload(
|
11
|
+
MockUDAPServer.parsed_request_body(reg_request)
|
12
|
+
)
|
13
|
+
client_uri == registered_uri
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def verify_registration_request(oauth_flow, verified_request)
|
18
|
+
parsed_body = MockUDAPServer.parsed_request_body(verified_request)
|
19
|
+
assert parsed_body.present?, 'Registration request body is not valid JSON.'
|
20
|
+
|
21
|
+
check_request_body(parsed_body)
|
22
|
+
check_software_statement(oauth_flow, parsed_body['software_statement'], verified_request.created_at)
|
23
|
+
output udap_registration_jwt: parsed_body['software_statement']
|
24
|
+
end
|
25
|
+
|
26
|
+
def check_request_body(request_body)
|
27
|
+
if request_body['udap'].blank?
|
28
|
+
add_message('error', '`udap` key with a value of `1` missing in the registration request')
|
29
|
+
elsif request_body['udap'] != '1'
|
30
|
+
add_message('error',
|
31
|
+
'The registration request contained an incorrect `udap` value: expected `1`, ' \
|
32
|
+
"got `#{request_body['udap']}`")
|
33
|
+
end
|
34
|
+
|
35
|
+
return unless request_body['certifications'].present?
|
36
|
+
|
37
|
+
request_body['certifications'].each_with_index do |certification_jwt, index|
|
38
|
+
JWT.decode(certification_jwt, nil, false)
|
39
|
+
rescue StandardError => e
|
40
|
+
add_message('error',
|
41
|
+
"Certification #{index + 1} in the registration request is not a valid signed jwt: #{e}")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def check_software_statement(oauth_flow, software_statement_jwt, request_time)
|
46
|
+
unless software_statement_jwt.present?
|
47
|
+
add_message('error',
|
48
|
+
'Registration is missing a `software_statement` key')
|
49
|
+
return
|
50
|
+
end
|
51
|
+
|
52
|
+
claims, _headers = begin
|
53
|
+
JWT.decode(software_statement_jwt, nil, false)
|
54
|
+
rescue StandardError => e
|
55
|
+
add_message('error',
|
56
|
+
"Registration software statement does not follow the jwt structure: #{e}")
|
57
|
+
return
|
58
|
+
end
|
59
|
+
|
60
|
+
# headers checked with signature
|
61
|
+
check_software_statement_claims(oauth_flow, claims, request_time)
|
62
|
+
check_jwt_signature(software_statement_jwt)
|
63
|
+
end
|
64
|
+
|
65
|
+
def check_software_statement_claims(oauth_flow, claims, request_time) # rubocop:disable Metrics/CyclomaticComplexity
|
66
|
+
unless claims['iss'] == udap_client_uri
|
67
|
+
add_message('error',
|
68
|
+
'Registration software statement `iss` claim is incorrect: ' \
|
69
|
+
"expected '#{udap_client_uri}', got '#{claims['iss']}'")
|
70
|
+
end
|
71
|
+
unless claims['sub'] == udap_client_uri
|
72
|
+
add_message('error',
|
73
|
+
'Registration software statement `sub` claim is incorrect: ' \
|
74
|
+
"expected '#{udap_client_uri}', got '#{claims['sub']}'")
|
75
|
+
end
|
76
|
+
unless claims['aud'] == client_registration_url
|
77
|
+
add_message('error',
|
78
|
+
'Registration software statement `aud` claim is incorrect: ' \
|
79
|
+
"expected '#{client_registration_url}', got '#{claims['aud']}'")
|
80
|
+
end
|
81
|
+
|
82
|
+
check_software_statement_grant_types(oauth_flow, claims)
|
83
|
+
MockUDAPServer.check_jwt_timing(claims['iat'], claims['exp'], request_time)
|
84
|
+
|
85
|
+
add_message('error', 'Registration software statement `jti` claim is missing.') unless claims['jti'].present?
|
86
|
+
unless claims['client_name'].present?
|
87
|
+
add_message('error', 'Registration software statement `client_name` claim is missing.')
|
88
|
+
end
|
89
|
+
check_software_statement_contacts(claims['contacts'])
|
90
|
+
unless claims['token_endpoint_auth_method'] == 'private_key_jwt'
|
91
|
+
add_message('error', 'Registration software statement `token_endpoint_auth_method` claim is incorrect: ' \
|
92
|
+
"expected `token_endpoint_auth_method`, got #{claims['token_endpoint_auth_method']}.")
|
93
|
+
end
|
94
|
+
add_message('error', 'Registration software statement `scope` claim is missing.') unless claims['scope'].present?
|
95
|
+
|
96
|
+
nil
|
97
|
+
end
|
98
|
+
|
99
|
+
def check_software_statement_contacts(contacts)
|
100
|
+
unless contacts.present?
|
101
|
+
add_message('error', 'Registration software statement `contacts` claim is missing.')
|
102
|
+
return
|
103
|
+
end
|
104
|
+
unless contacts.is_a?(Array)
|
105
|
+
add_message('error', 'Registration software statement `contacts` claim is missing.')
|
106
|
+
return
|
107
|
+
end
|
108
|
+
unless contacts.find { |contact| valid_uri?(contact, required_scheme: 'mailto') }.present?
|
109
|
+
add_message('error', 'Registration software statement `contacts` claim has no ' \
|
110
|
+
'valid `mailto` uri entry.')
|
111
|
+
end
|
112
|
+
|
113
|
+
nil
|
114
|
+
end
|
115
|
+
|
116
|
+
def check_software_statement_grant_types(oauth_flow, claims) # rubocop:disable Metrics/CyclomaticComplexity
|
117
|
+
unless claims['grant_types'].present?
|
118
|
+
add_message('error', 'Registration software statement `grant_types` claim is missing')
|
119
|
+
return
|
120
|
+
end
|
121
|
+
|
122
|
+
unless claims['grant_types'].is_a?(Array)
|
123
|
+
add_message('error', 'Registration software statement `grant_types` claim must be a list.')
|
124
|
+
return
|
125
|
+
end
|
126
|
+
|
127
|
+
has_client_credentials = claims['grant_types'].include?('client_credentials')
|
128
|
+
has_authorization_code = claims['grant_types'].include?('authorization_code')
|
129
|
+
|
130
|
+
unless has_client_credentials || has_authorization_code
|
131
|
+
add_message('error', 'Registration software statement `grant_types` claim must contain one of ' \
|
132
|
+
"'authorization_code' or 'client_credentials'")
|
133
|
+
return
|
134
|
+
end
|
135
|
+
|
136
|
+
if has_client_credentials && has_authorization_code
|
137
|
+
add_message('error', 'Registration software statement `grant_types` claim cannot contain both ' \
|
138
|
+
"'authorization_code' and 'client_credentials'")
|
139
|
+
end
|
140
|
+
|
141
|
+
extra_grants = claims['grant_types'].reject do |grant|
|
142
|
+
['client_credentials', 'authorization_code', 'refresh_token'].include?(grant)
|
143
|
+
end
|
144
|
+
unless extra_grants.blank?
|
145
|
+
add_message('error', 'Registration software statement `grant_types` claim cannot contain values beyond ' \
|
146
|
+
"'authorization_code', 'client_credentials', and 'refresh_token")
|
147
|
+
end
|
148
|
+
|
149
|
+
if oauth_flow == CLIENT_CREDENTIALS_TAG && !has_client_credentials
|
150
|
+
add_message('error', 'Registration software statement `grant_types` must contain ' \
|
151
|
+
"''client_credentials' when testing the client credentials flow.")
|
152
|
+
end
|
153
|
+
if oauth_flow == AUTHORIZATION_CODE_TAG && !has_authorization_code
|
154
|
+
add_message('error', 'Registration software statement `grant_types` must contain ' \
|
155
|
+
"''authorization_code' when testing the authorization code flow.")
|
156
|
+
end
|
157
|
+
check_client_credentials_software_statement(claims) if has_client_credentials
|
158
|
+
check_authorization_code_software_statement(claims) if has_authorization_code
|
159
|
+
|
160
|
+
nil
|
161
|
+
end
|
162
|
+
|
163
|
+
def check_authorization_code_software_statement(claims) # rubocop:disable Metrics/CyclomaticComplexity
|
164
|
+
if claims['redirect_uris'].blank?
|
165
|
+
add_message('error', 'Registration software statement `redirect_uris` must be present when ' \
|
166
|
+
"the 'authorization_code' `grant_type` is requested.")
|
167
|
+
elsif !claims['redirect_uris'].is_a?(Array)
|
168
|
+
add_message('error', 'Registration software statement `redirect_uris` must be a list when ' \
|
169
|
+
"the 'authorization_code' `grant_type` is requested.")
|
170
|
+
else
|
171
|
+
claims['redirect_uris'].each_with_index do |redirect_uri, index|
|
172
|
+
unless valid_uri?(redirect_uri, required_scheme: 'https')
|
173
|
+
add_message('error', "Registration software statement `redirect_uris` entry #{index + 1} is invalid: " \
|
174
|
+
"'#{redirect_uri}' is not a valid https uri.")
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
if claims['logo_uri'].blank?
|
180
|
+
add_message('error', 'Registration software statement `logo_uri` must be present when ' \
|
181
|
+
"the 'authorization_code' `grant_type` is requested.")
|
182
|
+
else
|
183
|
+
unless valid_uri?(claims['logo_uri'], required_scheme: 'https')
|
184
|
+
add_message('error', 'Registration software statement `logo_uri` is invalid: ' \
|
185
|
+
"'#{claims['logo_uri']}' is not a valid https uri.")
|
186
|
+
end
|
187
|
+
unless ['gif', 'jpg', 'jpeg', 'png'].include?(claims['logo_uri'].split('.').last.downcase)
|
188
|
+
add_message('error', 'Registration software statement `logo_uri` is invalid: it must point to a ' \
|
189
|
+
'PNG, JPG, or GIF file.')
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
if claims['response_types'].blank?
|
194
|
+
add_message('error', 'Registration software statement `response_types` must be present when ' \
|
195
|
+
"the 'authorization_code' `grant_type` is requested.")
|
196
|
+
else
|
197
|
+
unless claims['response_types'].is_a?(Array) &&
|
198
|
+
claims['response_types'].size == 1 &&
|
199
|
+
claims['response_types'][0] == 'code'
|
200
|
+
add_message('error', 'Registration software statement `response_types` claim is invalid: ' \
|
201
|
+
"must contain exactly one entry with the value 'code'.")
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
nil
|
206
|
+
end
|
207
|
+
|
208
|
+
def check_client_credentials_software_statement(claims)
|
209
|
+
unless claims['redirect_uris'].nil?
|
210
|
+
add_message('error', 'Registration software statement `redirect_uris` must not be present when ' \
|
211
|
+
"the 'client_credentials' `grant_type` is requested.")
|
212
|
+
end
|
213
|
+
|
214
|
+
unless claims['response_types'].nil?
|
215
|
+
add_message('error', 'Registration software statement `response_types` must not be present when ' \
|
216
|
+
"the 'client_credentials' `grant_type` is requested.")
|
217
|
+
end
|
218
|
+
|
219
|
+
if claims['grant_types'].include?('refresh_token')
|
220
|
+
add_message('error', "Registration software statement `response_types` cannot contain 'refresh_token' when " \
|
221
|
+
"the 'client_credentials' `grant_type` is requested.")
|
222
|
+
end
|
223
|
+
|
224
|
+
nil
|
225
|
+
end
|
226
|
+
|
227
|
+
def check_jwt_signature(jwt)
|
228
|
+
error = MockUDAPServer.udap_reg_signature_verification(jwt)
|
229
|
+
|
230
|
+
return unless error.present?
|
231
|
+
|
232
|
+
add_message('error', "Signature validation failed on registration request: #{error}")
|
233
|
+
end
|
234
|
+
|
235
|
+
def valid_uri?(url, required_scheme: nil)
|
236
|
+
uri = URI.parse(url)
|
237
|
+
required_scheme.blank? || uri.scheme == required_scheme
|
238
|
+
rescue URI::InvalidURIError
|
239
|
+
false
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require_relative '../tags'
|
2
|
+
require_relative '../urls'
|
3
|
+
require_relative '../endpoints/mock_udap_server'
|
4
|
+
require_relative 'client_descriptions'
|
5
|
+
require_relative 'client_options'
|
6
|
+
require_relative 'token_request_verification'
|
7
|
+
|
8
|
+
module UDAPSecurityTestKit
|
9
|
+
class UDAPClientTokenRequestAuthorizationCodeVerification < Inferno::Test
|
10
|
+
include URLs
|
11
|
+
include TokenRequestVerification
|
12
|
+
|
13
|
+
id :udap_client_token_request_ac_verification
|
14
|
+
title 'Verify UDAP Authorization Code Token Requests'
|
15
|
+
description %(
|
16
|
+
Check that UDAP token requests are conformant.
|
17
|
+
)
|
18
|
+
|
19
|
+
input :client_id,
|
20
|
+
title: 'Client Id',
|
21
|
+
type: 'text',
|
22
|
+
locked: true,
|
23
|
+
description: INPUT_CLIENT_ID_DESCRIPTION_LOCKED
|
24
|
+
input :udap_registration_jwt,
|
25
|
+
title: 'Registered UDAP Software Statement',
|
26
|
+
type: 'textarea',
|
27
|
+
locked: 'true',
|
28
|
+
description: INPUT_UDAP_REGISTRATION_JWT_DESCRIPTION_LOCKED
|
29
|
+
output :udap_tokens
|
30
|
+
|
31
|
+
def client_suite_id
|
32
|
+
return config.options[:endpoint_suite_id] if config.options[:endpoint_suite_id].present?
|
33
|
+
|
34
|
+
UDAPSecurityTestKit::UDAPSecurityClientTestSuite.id
|
35
|
+
end
|
36
|
+
|
37
|
+
run do
|
38
|
+
load_tagged_requests(TOKEN_TAG, UDAP_TAG, AUTHORIZATION_CODE_TAG)
|
39
|
+
skip_if requests.blank?, 'No UDAP token requests made.'
|
40
|
+
load_tagged_requests(TOKEN_TAG, UDAP_TAG, REFRESH_TOKEN_TAG) # verify refresh_requests as well
|
41
|
+
|
42
|
+
verify_token_requests(AUTHORIZATION_CODE_TAG)
|
43
|
+
|
44
|
+
assert messages.none? { |msg|
|
45
|
+
msg[:type] == 'error'
|
46
|
+
}, 'Invalid token requests received. See messages for details.'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require_relative '../tags'
|
2
|
+
require_relative '../urls'
|
3
|
+
require_relative '../endpoints/mock_udap_server'
|
4
|
+
require_relative 'client_descriptions'
|
5
|
+
require_relative 'client_options'
|
6
|
+
require_relative 'token_request_verification'
|
7
|
+
|
8
|
+
module UDAPSecurityTestKit
|
9
|
+
class UDAPClientTokenRequestClientCredentialsVerification < Inferno::Test
|
10
|
+
include URLs
|
11
|
+
include TokenRequestVerification
|
12
|
+
|
13
|
+
id :udap_client_token_request_cc_verification
|
14
|
+
title 'Verify UDAP Client Credentials Token Requests'
|
15
|
+
description %(
|
16
|
+
Check that UDAP token requests are conformant.
|
17
|
+
)
|
18
|
+
|
19
|
+
input :client_id,
|
20
|
+
title: 'Client Id',
|
21
|
+
type: 'text',
|
22
|
+
locked: true,
|
23
|
+
description: INPUT_CLIENT_ID_DESCRIPTION_LOCKED
|
24
|
+
input :udap_registration_jwt,
|
25
|
+
title: 'Registered UDAP Software Statement',
|
26
|
+
type: 'textarea',
|
27
|
+
locked: 'true',
|
28
|
+
description: INPUT_UDAP_REGISTRATION_JWT_DESCRIPTION_LOCKED
|
29
|
+
output :udap_tokens
|
30
|
+
|
31
|
+
def client_suite_id
|
32
|
+
return config.options[:endpoint_suite_id] if config.options[:endpoint_suite_id].present?
|
33
|
+
|
34
|
+
UDAPSecurityTestKit::UDAPSecurityClientTestSuite.id
|
35
|
+
end
|
36
|
+
|
37
|
+
run do
|
38
|
+
load_tagged_requests(TOKEN_TAG, UDAP_TAG, CLIENT_CREDENTIALS_TAG)
|
39
|
+
skip_if requests.blank?, 'No UDAP token requests made.'
|
40
|
+
load_tagged_requests(TOKEN_TAG, UDAP_TAG, REFRESH_TOKEN_TAG) # verify refresh_requests as well (shouldn't be any)
|
41
|
+
|
42
|
+
verify_token_requests(CLIENT_CREDENTIALS_TAG)
|
43
|
+
|
44
|
+
assert messages.none? { |msg|
|
45
|
+
msg[:type] == 'error'
|
46
|
+
}, 'Invalid token requests received. See messages for details.'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|