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,382 @@
|
|
1
|
+
require 'jwt'
|
2
|
+
require 'faraday'
|
3
|
+
require 'time'
|
4
|
+
require 'rack/utils'
|
5
|
+
require_relative '../urls'
|
6
|
+
require_relative '../tags'
|
7
|
+
require_relative '../udap_jwt_builder'
|
8
|
+
|
9
|
+
module UDAPSecurityTestKit
|
10
|
+
module MockUDAPServer
|
11
|
+
SUPPORTED_SCOPES = ['openid', 'system/*.read', 'user/*.read', 'patient/*.read'].freeze
|
12
|
+
|
13
|
+
module_function
|
14
|
+
|
15
|
+
def udap_server_metadata(suite_id)
|
16
|
+
base_url = "#{Inferno::Application['base_url']}/custom/#{suite_id}"
|
17
|
+
response_body = {
|
18
|
+
udap_versions_supported: ['1'],
|
19
|
+
udap_profiles_supported: ['udap_dcr', 'udap_authn', 'udap_authz'],
|
20
|
+
udap_authorization_extensions_supported: ['hl7-b2b'],
|
21
|
+
udap_authorization_extensions_required: [],
|
22
|
+
udap_certifications_supported: [],
|
23
|
+
udap_certifications_required: [],
|
24
|
+
grant_types_supported: ['authorization_code', 'client_credentials', 'refresh_token'],
|
25
|
+
scopes_supported: SUPPORTED_SCOPES,
|
26
|
+
registration_endpoint: base_url + REGISTRATION_PATH,
|
27
|
+
registration_endpoint_jwt_signing_alg_values_supported: ['RS256', 'RS384', 'ES384'],
|
28
|
+
authorization_endpoint: base_url + AUTHORIZATION_PATH,
|
29
|
+
token_endpoint: base_url + TOKEN_PATH,
|
30
|
+
token_endpoint_auth_methods_supported: ['private_key_jwt'],
|
31
|
+
token_endpoint_auth_signing_alg_values_supported: ['RS256', 'RS384', 'ES384'],
|
32
|
+
signed_metadata: udap_signed_metadata_jwt(base_url)
|
33
|
+
}.to_json
|
34
|
+
|
35
|
+
[200, { 'Content-Type' => 'application/json', 'Access-Control-Allow-Origin' => '*' }, [response_body]]
|
36
|
+
end
|
37
|
+
|
38
|
+
def openid_connect_metadata(suite_id)
|
39
|
+
base_url = "#{Inferno::Application['base_url']}/custom/#{suite_id}"
|
40
|
+
response_body = {
|
41
|
+
issuer: base_url + FHIR_PATH,
|
42
|
+
authorization_endpoint: base_url + AUTHORIZATION_PATH,
|
43
|
+
token_endpoint: base_url + TOKEN_PATH,
|
44
|
+
jwks_uri: base_url + OIDC_JWKS_PATH,
|
45
|
+
response_types_supported: ['code', 'id_token', 'token id_token'],
|
46
|
+
subject_types_supported: ['pairwise', 'public'],
|
47
|
+
id_token_signing_alg_values_supported: ['RS256']
|
48
|
+
}.to_json
|
49
|
+
|
50
|
+
[200, { 'Content-Type' => 'application/json', 'Access-Control-Allow-Origin' => '*' }, [response_body]]
|
51
|
+
end
|
52
|
+
|
53
|
+
def udap_signed_metadata_jwt(base_url)
|
54
|
+
jwt_claim_hash = {
|
55
|
+
iss: base_url + FHIR_PATH,
|
56
|
+
sub: base_url + FHIR_PATH,
|
57
|
+
exp: 5.minutes.from_now.to_i,
|
58
|
+
iat: Time.now.to_i,
|
59
|
+
jti: SecureRandom.hex(32),
|
60
|
+
token_endpoint: base_url + TOKEN_PATH,
|
61
|
+
authorization_endpoint: base_url + AUTHORIZATION_PATH,
|
62
|
+
registration_endpoint: base_url + REGISTRATION_PATH
|
63
|
+
}.compact
|
64
|
+
|
65
|
+
UDAPJWTBuilder.encode_jwt_with_x5c_header(
|
66
|
+
jwt_claim_hash,
|
67
|
+
test_kit_private_key,
|
68
|
+
'RS256',
|
69
|
+
[test_kit_cert]
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
def root_ca_cert
|
74
|
+
File.read(
|
75
|
+
ENV.fetch('UDAP_ROOT_CA_CERT_FILE',
|
76
|
+
File.join(__dir__, '..',
|
77
|
+
'certs', 'infernoCA.pem'))
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
def root_ca_private_key
|
82
|
+
File.read(
|
83
|
+
ENV.fetch('UDAP_CA_PRIVATE_KEY_FILE',
|
84
|
+
File.join(__dir__, '..',
|
85
|
+
'certs', 'infernoCA.key'))
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_kit_cert
|
90
|
+
File.read(
|
91
|
+
ENV.fetch('UDAP_TEST_KIT_CERT_FILE',
|
92
|
+
File.join(__dir__, '..',
|
93
|
+
'certs', 'TestClient.pem'))
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_kit_private_key
|
98
|
+
File.read(
|
99
|
+
ENV.fetch('UDAP_TEST_KIT_PRIVATE_KEY_FILE',
|
100
|
+
File.join(__dir__, '..',
|
101
|
+
'certs', 'TestClientPrivateKey.key'))
|
102
|
+
)
|
103
|
+
end
|
104
|
+
|
105
|
+
def parsed_request_body(request)
|
106
|
+
JSON.parse(request.request_body)
|
107
|
+
rescue JSON::ParserError
|
108
|
+
nil
|
109
|
+
end
|
110
|
+
|
111
|
+
def parsed_io_body(request)
|
112
|
+
parsed_body = begin
|
113
|
+
JSON.parse(request.body.read)
|
114
|
+
rescue JSON::ParserError
|
115
|
+
nil
|
116
|
+
end
|
117
|
+
request.body.rewind
|
118
|
+
|
119
|
+
parsed_body
|
120
|
+
end
|
121
|
+
|
122
|
+
def jwt_claims(encoded_jwt)
|
123
|
+
JWT.decode(encoded_jwt, nil, false)[0]
|
124
|
+
end
|
125
|
+
|
126
|
+
def udap_client_uri_from_registration_payload(reg_body)
|
127
|
+
udap_claim_from_registration_payload(reg_body, 'iss')
|
128
|
+
end
|
129
|
+
|
130
|
+
def udap_claim_from_registration_payload(reg_body, claim_key)
|
131
|
+
software_statement_jwt = udap_software_statement_jwt(reg_body)
|
132
|
+
return unless software_statement_jwt.present?
|
133
|
+
|
134
|
+
jwt_claims(software_statement_jwt)&.dig(claim_key)
|
135
|
+
end
|
136
|
+
|
137
|
+
def udap_software_statement_jwt(reg_body)
|
138
|
+
reg_body&.dig('software_statement')
|
139
|
+
end
|
140
|
+
|
141
|
+
def client_uri_to_client_id(client_uri)
|
142
|
+
Base64.urlsafe_encode64(client_uri, padding: false)
|
143
|
+
end
|
144
|
+
|
145
|
+
def client_id_to_client_uri(client_id)
|
146
|
+
Base64.urlsafe_decode64(client_id)
|
147
|
+
end
|
148
|
+
|
149
|
+
def client_id_to_token(client_id, exp_min)
|
150
|
+
token_structure = {
|
151
|
+
client_id:,
|
152
|
+
expiration: exp_min.minutes.from_now.to_i,
|
153
|
+
nonce: SecureRandom.hex(8)
|
154
|
+
}.to_json
|
155
|
+
|
156
|
+
Base64.urlsafe_encode64(token_structure, padding: false)
|
157
|
+
end
|
158
|
+
|
159
|
+
def decode_token(token)
|
160
|
+
token_to_decode =
|
161
|
+
if issued_token_is_refresh_token(token)
|
162
|
+
refresh_token_to_authorization_code(token)
|
163
|
+
else
|
164
|
+
token
|
165
|
+
end
|
166
|
+
return unless token_to_decode.present?
|
167
|
+
|
168
|
+
JSON.parse(Base64.urlsafe_decode64(token))
|
169
|
+
rescue JSON::ParserError
|
170
|
+
nil
|
171
|
+
end
|
172
|
+
|
173
|
+
def issued_token_to_client_id(token)
|
174
|
+
decode_token(token)&.dig('client_id')
|
175
|
+
end
|
176
|
+
|
177
|
+
def issued_token_is_refresh_token(token)
|
178
|
+
token.end_with?('_rt')
|
179
|
+
end
|
180
|
+
|
181
|
+
def authorization_code_to_refresh_token(code)
|
182
|
+
"#{code}_rt"
|
183
|
+
end
|
184
|
+
|
185
|
+
def refresh_token_to_authorization_code(refresh_token)
|
186
|
+
refresh_token[..-4]
|
187
|
+
end
|
188
|
+
|
189
|
+
def request_has_expired_token?(request)
|
190
|
+
return false if request.params[:session_path].present?
|
191
|
+
|
192
|
+
token = request.headers['authorization']&.delete_prefix('Bearer ')
|
193
|
+
token_expired?(token)
|
194
|
+
end
|
195
|
+
|
196
|
+
def token_expired?(token, check_time = nil)
|
197
|
+
decoded_token = decode_token(token)
|
198
|
+
return false unless decoded_token&.dig('expiration').present?
|
199
|
+
|
200
|
+
check_time = Time.now.to_i unless check_time.present?
|
201
|
+
decoded_token['expiration'] < check_time
|
202
|
+
end
|
203
|
+
|
204
|
+
def update_response_for_expired_token(response, type)
|
205
|
+
response.status = 401
|
206
|
+
response.format = :json
|
207
|
+
response.body = FHIR::OperationOutcome.new(
|
208
|
+
issue: FHIR::OperationOutcome::Issue.new(severity: 'fatal', code: 'expired',
|
209
|
+
details: FHIR::CodeableConcept.new(text: "#{type}has expired"))
|
210
|
+
).to_json
|
211
|
+
end
|
212
|
+
|
213
|
+
def udap_reg_signature_verification(assertion_jwt)
|
214
|
+
assertion_body, assertion_header = JWT.decode(assertion_jwt, nil, false)
|
215
|
+
return 'missing `x5c` header' if assertion_header['x5c'].blank?
|
216
|
+
|
217
|
+
leaf_cert_der = Base64.decode64(assertion_header['x5c'].first)
|
218
|
+
leaf_cert = OpenSSL::X509::Certificate.new(leaf_cert_der)
|
219
|
+
|
220
|
+
signature_error = udap_assertion_signature_verification(assertion_jwt, leaf_cert, assertion_header['alg'])
|
221
|
+
return signature_error if signature_error.present?
|
222
|
+
|
223
|
+
# check the certificate's SAN extension for the issuer name
|
224
|
+
issuer = assertion_body['iss']
|
225
|
+
begin
|
226
|
+
alt_names =
|
227
|
+
leaf_cert.extensions
|
228
|
+
.find { |extension| extension.oid == 'subjectAltName' }.value
|
229
|
+
rescue NoMethodError
|
230
|
+
return 'Could not find Subject Alternative Name extension in leaf certificate'
|
231
|
+
end
|
232
|
+
return if alt_names.include?("URI:#{issuer}")
|
233
|
+
|
234
|
+
"`iss` claim `#{issuer}` not found in Subject Alternative Name extension " \
|
235
|
+
"from the `x5c` JWT header: `#{alt_names}`"
|
236
|
+
end
|
237
|
+
|
238
|
+
def udap_token_signature_verification(assertion_jwt, registration_jwt)
|
239
|
+
_assertion_body, assertion_header = JWT.decode(assertion_jwt, nil, false)
|
240
|
+
return 'missing `x5c` header' if assertion_header['x5c'].blank?
|
241
|
+
|
242
|
+
leaf_cert_der = Base64.decode64(assertion_header['x5c'].first)
|
243
|
+
leaf_cert = OpenSSL::X509::Certificate.new(leaf_cert_der)
|
244
|
+
|
245
|
+
signature_error = udap_assertion_signature_verification(assertion_jwt, leaf_cert, assertion_header['alg'])
|
246
|
+
return signature_error if signature_error.present?
|
247
|
+
return unless registration_jwt.present?
|
248
|
+
|
249
|
+
# check trust
|
250
|
+
_registration_body, registration_header = JWT.decode(registration_jwt, nil, false)
|
251
|
+
return if assertion_header['x5c'].first == registration_header['x5c'].first
|
252
|
+
|
253
|
+
'signing cert does not match registration cert'
|
254
|
+
end
|
255
|
+
|
256
|
+
def udap_assertion_signature_verification(assertion_jwt, signing_cert, algorithm)
|
257
|
+
return 'missing `alg` header' unless algorithm.present?
|
258
|
+
|
259
|
+
signature_validation_result = UDAPSecurityTestKit::UDAPJWTValidator.validate_signature(
|
260
|
+
assertion_jwt,
|
261
|
+
algorithm,
|
262
|
+
signing_cert
|
263
|
+
)
|
264
|
+
return if signature_validation_result[:success]
|
265
|
+
|
266
|
+
signature_validation_result[:error_message]
|
267
|
+
end
|
268
|
+
|
269
|
+
def udap_registration_software_statement(test_session_id)
|
270
|
+
registration_requests =
|
271
|
+
Inferno::Repositories::Requests.new.tagged_requests(test_session_id, [UDAP_TAG, REGISTRATION_TAG])
|
272
|
+
return unless registration_requests.present?
|
273
|
+
|
274
|
+
parsed_body = MockUDAPServer.parsed_request_body(registration_requests.last)
|
275
|
+
parsed_body&.dig('software_statement')
|
276
|
+
end
|
277
|
+
|
278
|
+
def update_response_for_error(response, error_message)
|
279
|
+
response.status = 401
|
280
|
+
response.format = :json
|
281
|
+
response.body = { error: 'invalid_client', error_description: error_message }.to_json
|
282
|
+
end
|
283
|
+
|
284
|
+
def client_id_from_client_assertion(client_assertion_jwt)
|
285
|
+
return unless client_assertion_jwt.present?
|
286
|
+
|
287
|
+
jwt_claims(client_assertion_jwt)&.dig('iss')
|
288
|
+
end
|
289
|
+
|
290
|
+
def check_jwt_timing(issue_claim, expiration_claim, request_time) # rubocop:disable Metrics/CyclomaticComplexity
|
291
|
+
add_message('error', 'Registration software statement `iat` claim is missing') unless issue_claim.present?
|
292
|
+
add_message('error', 'Registration software statement `exp` claim is missing') unless expiration_claim.present?
|
293
|
+
return unless issue_claim.present? && expiration_claim.present?
|
294
|
+
|
295
|
+
unless issue_claim.is_a?(Numeric)
|
296
|
+
add_message('error',
|
297
|
+
"Registration software statement `iat` claim is invalid: expected a number, got '#{issue_claim}'")
|
298
|
+
end
|
299
|
+
unless expiration_claim.is_a?(Numeric)
|
300
|
+
add_message('error',
|
301
|
+
'Registration software statement `exp` claim is invalid: ' \
|
302
|
+
"expected a number, got '#{expiration_claim}'")
|
303
|
+
end
|
304
|
+
return unless issue_claim.is_a?(Numeric) && expiration_claim.is_a?(Numeric)
|
305
|
+
|
306
|
+
issue_time = Time.at(issue_claim)
|
307
|
+
expiration_time = Time.at(expiration_claim)
|
308
|
+
unless expiration_time > issue_time
|
309
|
+
add_message('error',
|
310
|
+
'Registration software statement `exp` claim is invalid: ' \
|
311
|
+
'cannot be before the `iat` claim.')
|
312
|
+
end
|
313
|
+
unless expiration_time <= issue_time + 5.minutes
|
314
|
+
add_message('error',
|
315
|
+
'Registration software statement `exp` claim is invalid: ' \
|
316
|
+
'cannot be more than 5 minutes after the `iat` claim.')
|
317
|
+
end
|
318
|
+
unless issue_time <= request_time
|
319
|
+
add_message('error',
|
320
|
+
'Registration software statement `iat` claim is invalid: ' \
|
321
|
+
'cannot be after the request time.')
|
322
|
+
end
|
323
|
+
unless expiration_time > request_time
|
324
|
+
add_message('error',
|
325
|
+
'Registration software statement `exp` claim is invalid: ' \
|
326
|
+
'it has expired.')
|
327
|
+
end
|
328
|
+
|
329
|
+
nil
|
330
|
+
end
|
331
|
+
|
332
|
+
def pkce_error(verifier, challenge, method)
|
333
|
+
if verifier.blank?
|
334
|
+
'pkce check failed: no verifier provided'
|
335
|
+
elsif challenge.blank?
|
336
|
+
'pkce check failed: no challenge code provided'
|
337
|
+
elsif method == 'S256'
|
338
|
+
return nil unless challenge != calculate_s256_challenge(verifier)
|
339
|
+
|
340
|
+
"invalid S256 pkce verifier: got '#{calculate_s256_challenge(verifier)}' " \
|
341
|
+
"expected '#{challenge}'"
|
342
|
+
else
|
343
|
+
"invalid pkce challenge method '#{method}'"
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
def pkce_valid?(verifier, challenge, method, response)
|
348
|
+
pkce_error = pkce_error(verifier, challenge, method)
|
349
|
+
|
350
|
+
if pkce_error.present?
|
351
|
+
update_response_for_error(response, pkce_error)
|
352
|
+
false
|
353
|
+
else
|
354
|
+
true
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
def calculate_s256_challenge(verifier)
|
359
|
+
Base64.urlsafe_encode64(Digest::SHA256.digest(verifier), padding: false)
|
360
|
+
end
|
361
|
+
|
362
|
+
def authorization_request_for_code(code, test_session_id)
|
363
|
+
authorization_requests = Inferno::Repositories::Requests.new.tagged_requests(test_session_id, [AUTHORIZATION_TAG])
|
364
|
+
authorization_requests.find do |request|
|
365
|
+
location_header = request.response_headers.find { |header| header.name.downcase == 'location' }
|
366
|
+
if location_header.present? && location_header.value.present?
|
367
|
+
Rack::Utils.parse_query(URI(location_header.value)&.query)&.dig('code') == code
|
368
|
+
else
|
369
|
+
false
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
def authorization_code_request_details(inferno_request)
|
375
|
+
if inferno_request.verb.downcase == 'get'
|
376
|
+
Rack::Utils.parse_query(URI(inferno_request.url)&.query)
|
377
|
+
elsif inferno_request.verb.downcase == 'post'
|
378
|
+
Rack::Utils.parse_query(inferno_request.request_body)
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
382
|
+
end
|
@@ -5,23 +5,23 @@ module UDAPSecurityTestKit
|
|
5
5
|
id :udap_security
|
6
6
|
title 'UDAP Security Test Kit'
|
7
7
|
description <<~DESCRIPTION
|
8
|
-
This is a collection of tests to verify server conformance to the [HL7 UDAP Security
|
8
|
+
This is a collection of tests to verify client and server conformance to the [HL7 UDAP Security
|
9
9
|
STU 1.0 IG](https://hl7.org/fhir/us/udap-security/STU1/index.html)
|
10
10
|
<!-- break -->
|
11
11
|
Specifically, this test
|
12
|
-
kit assesses the required capabilities from the following sections:
|
12
|
+
kit assesses the required capabilities for clients and servers from the following sections:
|
13
13
|
- [JSON Web Token (JWT) Requirements](https://hl7.org/fhir/us/udap-security/STU1/index.html)
|
14
14
|
- [Discovery](https://hl7.org/fhir/us/udap-security/STU1/discovery.html)
|
15
15
|
- [Dynamic Client Registration](https://hl7.org/fhir/us/udap-security/STU1/registration.html)
|
16
16
|
- [Consumer-Facing Authorization & Authentication](https://hl7.org/fhir/us/udap-security/STU1/consumer.html)
|
17
|
+
(server only)
|
17
18
|
- [Business-to-Business (B2B) Authorization & Authentication](https://hl7.org/fhir/us/udap-security/STU1/b2b.html)
|
18
19
|
|
19
20
|
[Tiered OAuth for User
|
20
21
|
Authentication](https://hl7.org/fhir/us/udap-security/STU1/user.html) is not a
|
21
22
|
required capability and is not assessed.
|
22
|
-
This test kit also does not assess client conformance.
|
23
23
|
DESCRIPTION
|
24
|
-
suite_ids [:udap_security]
|
24
|
+
suite_ids [:udap_security, :udap_security_client]
|
25
25
|
tags ['UDAP Security']
|
26
26
|
last_updated LAST_UPDATED
|
27
27
|
version VERSION
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module UDAPSecurityTestKit
|
4
|
+
REGISTRATION_TAG = 'registration'
|
5
|
+
AUTHORIZATION_TAG = 'authorization'
|
6
|
+
TOKEN_TAG = 'token'
|
7
|
+
UDAP_TAG = 'udap'
|
8
|
+
ACCESS_TAG = 'access'
|
9
|
+
CLIENT_CREDENTIALS_TAG = 'client_credentials'
|
10
|
+
AUTHORIZATION_CODE_TAG = 'authorization_code'
|
11
|
+
REFRESH_TOKEN_TAG = 'refresh_token'
|
12
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module UDAPSecurityTestKit
|
4
|
+
FHIR_PATH = '/fhir'
|
5
|
+
OIDC_DISCOVERY_PATH = "#{FHIR_PATH}/.well-known/openid-configuration".freeze
|
6
|
+
OIDC_JWKS_PATH = "#{FHIR_PATH}/.well-known/jwks.json".freeze
|
7
|
+
UDAP_DISCOVERY_PATH = "#{FHIR_PATH}/.well-known/udap".freeze
|
8
|
+
AUTH_SERVER_PATH = '/auth'
|
9
|
+
REGISTRATION_PATH = "#{AUTH_SERVER_PATH}/register".freeze
|
10
|
+
AUTHORIZATION_PATH = "#{AUTH_SERVER_PATH}/authorization".freeze
|
11
|
+
TOKEN_PATH = "#{AUTH_SERVER_PATH}/token".freeze
|
12
|
+
RESUME_PASS_PATH = '/resume_pass'
|
13
|
+
RESUME_FAIL_PATH = '/resume_fail'
|
14
|
+
|
15
|
+
module URLs
|
16
|
+
def client_base_url
|
17
|
+
@client_base_url ||= "#{Inferno::Application['base_url']}/custom/#{client_suite_id}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def client_fhir_base_url
|
21
|
+
@client_fhir_base_url ||= client_base_url + FHIR_PATH
|
22
|
+
end
|
23
|
+
|
24
|
+
def client_resume_pass_url
|
25
|
+
@client_resume_pass_url ||= client_base_url + RESUME_PASS_PATH
|
26
|
+
end
|
27
|
+
|
28
|
+
def client_resume_fail_url
|
29
|
+
@client_resume_fail_url ||= client_base_url + RESUME_FAIL_PATH
|
30
|
+
end
|
31
|
+
|
32
|
+
def client_udap_discovery_url
|
33
|
+
@client_udap_discovery_url ||= client_base_url + UDAP_DISCOVERY_PATH
|
34
|
+
end
|
35
|
+
|
36
|
+
def client_registration_url
|
37
|
+
@client_registration_url ||= client_base_url + REGISTRATION_PATH
|
38
|
+
end
|
39
|
+
|
40
|
+
def client_authorization_url
|
41
|
+
@client_authorization_url ||= client_base_url + AUTHORIZATION_PATH
|
42
|
+
end
|
43
|
+
|
44
|
+
def client_token_url
|
45
|
+
@client_token_url ||= client_base_url + TOKEN_PATH
|
46
|
+
end
|
47
|
+
|
48
|
+
def client_suite_id
|
49
|
+
UDAPSecurityTestKit::UDAPSecurityClientTestSuite.id
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require_relative 'udap_security_test_kit/client_suite'
|
1
2
|
require_relative 'udap_security_test_kit/authorization_code_group'
|
2
3
|
require_relative 'udap_security_test_kit/client_credentials_group'
|
3
4
|
require_relative 'udap_security_test_kit/redirect_uri'
|
@@ -6,7 +7,7 @@ require_relative 'udap_security_test_kit/metadata'
|
|
6
7
|
module UDAPSecurityTestKit
|
7
8
|
class Suite < Inferno::TestSuite
|
8
9
|
id :udap_security
|
9
|
-
title 'UDAP Security'
|
10
|
+
title 'UDAP Security Server'
|
10
11
|
description %(
|
11
12
|
The User Data Access Protocol (UDAP) Security test kit verifies that systems correctly implement the
|
12
13
|
[HL7 UDAP Security IG](http://hl7.org/fhir/us/udap-security/STU1/)
|
@@ -28,7 +29,7 @@ module UDAPSecurityTestKit
|
|
28
29
|
Testers may test one or both flows based on their system under test.
|
29
30
|
|
30
31
|
This test suite does NOT assess [Tiered OAuth for User Authentication](https://hl7.org/fhir/us/udap-security/STU1/user.html)
|
31
|
-
(which is not a required capability)
|
32
|
+
(which is not a required capability).
|
32
33
|
)
|
33
34
|
|
34
35
|
input_instructions %(
|
@@ -74,6 +75,11 @@ module UDAPSecurityTestKit
|
|
74
75
|
type: 'download',
|
75
76
|
label: 'Download',
|
76
77
|
url: 'https://github.com/inferno-framework/udap-security-test-kit/releases/'
|
78
|
+
},
|
79
|
+
{
|
80
|
+
type: 'ig',
|
81
|
+
label: 'Implementation Guide',
|
82
|
+
url: 'https://hl7.org/fhir/us/udap-security/STU1/'
|
77
83
|
}
|
78
84
|
]
|
79
85
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: udap_security_test_kit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.11.
|
4
|
+
version: 0.11.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephen MacVicar
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2025-
|
12
|
+
date: 2025-05-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: inferno_core
|
@@ -48,6 +48,8 @@ extra_rdoc_files: []
|
|
48
48
|
files:
|
49
49
|
- LICENSE
|
50
50
|
- config/presets/SureFhirIdentityMatchingDemo.json
|
51
|
+
- config/presets/UDAP_RunClientAgainstServer.json.erb
|
52
|
+
- config/presets/UDAP_RunServerAgainstClient.json.erb
|
51
53
|
- lib/udap_security_test_kit.rb
|
52
54
|
- lib/udap_security_test_kit/authorization_code_authentication_group.rb
|
53
55
|
- lib/udap_security_test_kit/authorization_code_group.rb
|
@@ -62,10 +64,40 @@ files:
|
|
62
64
|
- lib/udap_security_test_kit/client_credentials_authentication_group.rb
|
63
65
|
- lib/udap_security_test_kit/client_credentials_group.rb
|
64
66
|
- lib/udap_security_test_kit/client_credentials_token_exchange_test.rb
|
67
|
+
- lib/udap_security_test_kit/client_suite.rb
|
68
|
+
- lib/udap_security_test_kit/client_suite/access_ac_group.rb
|
69
|
+
- lib/udap_security_test_kit/client_suite/access_ac_interaction_test.rb
|
70
|
+
- lib/udap_security_test_kit/client_suite/access_cc_group.rb
|
71
|
+
- lib/udap_security_test_kit/client_suite/access_cc_interaction_test.rb
|
72
|
+
- lib/udap_security_test_kit/client_suite/authorization_request_verification_test.rb
|
73
|
+
- lib/udap_security_test_kit/client_suite/client_descriptions.rb
|
74
|
+
- lib/udap_security_test_kit/client_suite/client_options.rb
|
75
|
+
- lib/udap_security_test_kit/client_suite/oidc_jwks.json
|
76
|
+
- lib/udap_security_test_kit/client_suite/oidc_jwks.rb
|
77
|
+
- lib/udap_security_test_kit/client_suite/registration_ac_group.rb
|
78
|
+
- lib/udap_security_test_kit/client_suite/registration_ac_verification_test.rb
|
79
|
+
- lib/udap_security_test_kit/client_suite/registration_cc_group.rb
|
80
|
+
- lib/udap_security_test_kit/client_suite/registration_cc_verification_test.rb
|
81
|
+
- lib/udap_security_test_kit/client_suite/registration_interaction_test.rb
|
82
|
+
- lib/udap_security_test_kit/client_suite/registration_request_verification.rb
|
83
|
+
- lib/udap_security_test_kit/client_suite/token_request_ac_verification_test.rb
|
84
|
+
- lib/udap_security_test_kit/client_suite/token_request_cc_verification_test.rb
|
85
|
+
- lib/udap_security_test_kit/client_suite/token_request_verification.rb
|
86
|
+
- lib/udap_security_test_kit/client_suite/token_use_verification_test.rb
|
65
87
|
- lib/udap_security_test_kit/common_assertions.rb
|
66
88
|
- lib/udap_security_test_kit/default_cert_file_loader.rb
|
67
89
|
- lib/udap_security_test_kit/discovery_group.rb
|
90
|
+
- lib/udap_security_test_kit/docs/demo/FHIR Request.postman_collection.json
|
91
|
+
- lib/udap_security_test_kit/docs/udap_client_suite_description.md
|
68
92
|
- lib/udap_security_test_kit/dynamic_client_registration_group.rb
|
93
|
+
- lib/udap_security_test_kit/endpoints/echoing_fhir_responder_endpoint.rb
|
94
|
+
- lib/udap_security_test_kit/endpoints/mock_udap_server.rb
|
95
|
+
- lib/udap_security_test_kit/endpoints/mock_udap_server/authorization_endpoint.rb
|
96
|
+
- lib/udap_security_test_kit/endpoints/mock_udap_server/registration_endpoint.rb
|
97
|
+
- lib/udap_security_test_kit/endpoints/mock_udap_server/token_endpoint.rb
|
98
|
+
- lib/udap_security_test_kit/endpoints/mock_udap_server/udap_authorization_response_creation.rb
|
99
|
+
- lib/udap_security_test_kit/endpoints/mock_udap_server/udap_registration_response_creation.rb
|
100
|
+
- lib/udap_security_test_kit/endpoints/mock_udap_server/udap_token_response_creation.rb
|
69
101
|
- lib/udap_security_test_kit/grant_types_supported_field_test.rb
|
70
102
|
- lib/udap_security_test_kit/igs/put_ig_package_dot_tgz_here
|
71
103
|
- lib/udap_security_test_kit/metadata.rb
|
@@ -81,6 +113,7 @@ files:
|
|
81
113
|
- lib/udap_security_test_kit/signed_metadata_field_test.rb
|
82
114
|
- lib/udap_security_test_kit/signed_metadata_trust_verification_test.rb
|
83
115
|
- lib/udap_security_test_kit/software_statement_builder.rb
|
116
|
+
- lib/udap_security_test_kit/tags.rb
|
84
117
|
- lib/udap_security_test_kit/token_endpoint_auth_methods_supported_field_test.rb
|
85
118
|
- lib/udap_security_test_kit/token_endpoint_auth_signing_alg_values_supported_field_test.rb
|
86
119
|
- lib/udap_security_test_kit/token_endpoint_field_test.rb
|
@@ -97,6 +130,7 @@ files:
|
|
97
130
|
- lib/udap_security_test_kit/udap_request_builder.rb
|
98
131
|
- lib/udap_security_test_kit/udap_versions_supported_field_test.rb
|
99
132
|
- lib/udap_security_test_kit/udap_x509_certificate.rb
|
133
|
+
- lib/udap_security_test_kit/urls.rb
|
100
134
|
- lib/udap_security_test_kit/version.rb
|
101
135
|
- lib/udap_security_test_kit/well_known_endpoint_test.rb
|
102
136
|
homepage: https://github.com/inferno-framework/udap-security-test-kit
|