shc_vaccination_test_kit 0.1.0 → 0.3.0

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.
@@ -1,109 +0,0 @@
1
- require_relative 'vc_fhir_validation'
2
- require_relative 'vc_headers'
3
- require_relative 'vc_payload_verification'
4
- require_relative 'vc_signature_verification'
5
-
6
- module Covid19VCI
7
- class FileDownload < Inferno::TestGroup
8
- id 'vci_file_download'
9
- title 'Download and validate a health card via file download'
10
-
11
- input :file_download_url
12
-
13
- test do
14
- id 'vci-file-01'
15
- title 'Health card can be downloaded'
16
- description 'The health card can be downloaded and is a valid JSON object'
17
- makes_request :vci_file_download
18
-
19
- run do
20
- get(file_download_url, name: :vci_file_download)
21
-
22
- assert_response_status(200)
23
- assert_valid_json(response[:body])
24
- end
25
- end
26
-
27
- test do
28
- id 'vci-file-02'
29
- title 'Response contains correct Content-Type of application/smart-health-card'
30
- uses_request :vci_file_download
31
-
32
- run do
33
- skip_if request.status != 200, 'Health card not successfully downloaded'
34
-
35
- content_type = request.response_header('Content-Type')
36
-
37
- assert content_type.present?, 'Response did not include a Content-Type header'
38
- assert content_type.value.match?(%r{\Aapplication/smart-health-card(\z|\W)}),
39
- "Content-Type header was '#{content_type.value}' instead of 'application/smart-health-card'"
40
- end
41
- end
42
-
43
- test do
44
- id 'vci-file-03'
45
- title 'Health card is provided as a file download with a .smart-health-card extension'
46
- uses_request :vci_file_download
47
-
48
- run do
49
- skip_if request.status != 200, 'Health card not successfully downloaded'
50
-
51
- pass_if request.url.ends_with?('.smart-health-card')
52
-
53
- content_disposition = request.response_header('Content-Disposition')
54
- assert content_disposition.present?,
55
- "Url did not end with '.smart-health-card' and response did not include a Content-Disposition header"
56
-
57
- attachment_pattern = /\Aattachment;/
58
- assert content_disposition.value.match?(attachment_pattern),
59
- "Url did not end with '.smart-health-card' and " \
60
- "Content-Disposition header does not indicate file should be downloaded: '#{content_disposition}'"
61
-
62
- extension_pattern = /filename=".*\.smart-health-card"/
63
- assert content_disposition.value.match?(extension_pattern),
64
- "Url did not end with '.smart-health-card' and Content-Disposition header does not indicate " \
65
- "file should have a '.smart-health-card' extension: '#{content_disposition}'"
66
- end
67
- end
68
-
69
- test do
70
- id 'vci-file-04'
71
- title 'Response contains an array of Verifiable Credential strings'
72
- uses_request :vci_file_download
73
- output :credential_strings
74
-
75
- run do
76
- skip_if request.status != 200, 'Health card not successfully downloaded'
77
-
78
- body = JSON.parse(response[:body])
79
- assert body.include?('verifiableCredential'),
80
- "Health card does not contain 'verifiableCredential' field"
81
-
82
- vc = body['verifiableCredential']
83
-
84
- assert vc.is_a?(Array), "'verifiableCredential' field must contain an Array"
85
- assert vc.length.positive?, "'verifiableCredential' field must contain at least one verifiable credential"
86
-
87
- output credential_strings: vc.join(',')
88
-
89
- pass "Received #{vc.length} verifiable #{'credential'.pluralize(vc.length)}"
90
- end
91
- end
92
-
93
- test from: :vc_headers do
94
- id 'vci-file-05'
95
- end
96
-
97
- test from: :vc_signature_verification do
98
- id 'vci-file-06'
99
- end
100
-
101
- test from: :vc_payload_verification do
102
- id 'vci-file-07'
103
- end
104
-
105
- test from: :vc_fhir_verification do
106
- id 'vci-file-08'
107
- end
108
- end
109
- end
@@ -1,75 +0,0 @@
1
- module Covid19VCI
2
- class VCFHIRVerification < Inferno::Test
3
- title 'Health Card payloads conform to the Vaccination Credential Bundle Profiles'
4
- input :credential_strings
5
-
6
- id :vc_fhir_verification
7
-
8
- run do
9
- skip_if credential_strings.blank?, 'No Verifiable Credentials received'
10
-
11
- credential_strings.split(',').each do |credential|
12
- raw_payload = HealthCards::JWS.from_jws(credential).payload
13
- assert raw_payload&.length&.positive?, 'No payload found'
14
-
15
- decompressed_payload =
16
- begin
17
- Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(raw_payload)
18
- rescue Zlib::DataError
19
- assert false, 'Payload compression error. Unable to inflate payload.'
20
- end
21
-
22
- assert decompressed_payload.length.positive?, 'Payload compression error. Unable to inflate payload.'
23
-
24
- payload_length = decompressed_payload.length
25
- health_card = HealthCards::COVIDHealthCard.from_jws(credential)
26
- health_card_length = health_card.to_json.length
27
-
28
- assert_valid_json decompressed_payload, 'Payload is not valid JSON'
29
-
30
- payload = JSON.parse(decompressed_payload)
31
- vc = payload['vc']
32
- assert vc.is_a?(Hash), "Expected 'vc' claim to be a JSON object, but found #{vc.class}"
33
-
34
- subject = vc['credentialSubject']
35
- assert subject.is_a?(Hash), "Expected 'vc.credentialSubject' to be a JSON object, but found #{subject.class}"
36
-
37
- raw_bundle = subject['fhirBundle']
38
- assert raw_bundle.is_a?(Hash), "Expected 'vc.fhirBundle' to be a JSON object, but found #{raw_bundle.class}"
39
-
40
- bundle = FHIR::Bundle.new(raw_bundle)
41
-
42
- # assert bundle.entry.any? { |r| r.resource.is_a?(FHIR::Immunization) } || bundle.entry.any? { |r| r.resource.is_a?(FHIR::Observation) },
43
- # "Bundle must have either Immunization entries or Observation entries"
44
-
45
- # if bundle.entry.any? { |r| r.resource.is_a?(FHIR::Immunization) }
46
- assert_valid_resource(
47
- resource: bundle,
48
- profile_url: 'http://hl7.org/fhir/uv/smarthealthcards-vaccination/StructureDefinition/vaccination-credential-bundle'
49
- )
50
-
51
- warning do
52
- assert_valid_resource(
53
- resource: bundle,
54
- profile_url: 'http://hl7.org/fhir/uv/smarthealthcards-vaccination/StructureDefinition/vaccination-credential-bundle-dm'
55
- )
56
- end
57
- # end
58
-
59
- if bundle.entry.any? { |r| r.resource.is_a?(FHIR::Observation) }
60
- assert_valid_resource(
61
- resource: bundle,
62
- profile_url: 'http://hl7.org/fhir/uv/smarthealthcards-vaccination/StructureDefinition/covid19-laboratory-bundle'
63
- )
64
-
65
- warning do
66
- assert_valid_resource(
67
- resource: bundle,
68
- profile_url: 'http://hl7.org/fhir/uv/smarthealthcards-vaccination/StructureDefinition/covid19-laboratory-bundle-dm'
69
- )
70
- end
71
- end
72
- end
73
- end
74
- end
75
- end
@@ -1,21 +0,0 @@
1
- module Covid19VCI
2
- class VCHeaders < Inferno::Test
3
- title 'Health Cards contain the correct headers'
4
- input :credential_strings
5
-
6
- id :vc_headers
7
-
8
- run do
9
- skip_if credential_strings.blank?, 'No Verifiable Credentials received'
10
- credential_strings.split(',').each do |credential|
11
- header = HealthCards::JWS.from_jws(credential).header
12
-
13
- assert header['zip'] == 'DEF', "Expected 'zip' header to equal 'DEF', but found '#{header['zip']}'"
14
- assert header['alg'] == 'ES256', "Expected 'alg' header to equal 'ES256', but found '#{header['alg']}'"
15
- assert header['kid'].present?, "No 'kid' header was present"
16
- rescue StandardError => e
17
- assert false, "Error decoding credential: #{e.message}"
18
- end
19
- end
20
- end
21
- end
@@ -1,115 +0,0 @@
1
- module Covid19VCI
2
- class VCPayloadVerification < Inferno::Test
3
- title 'Health Card payloads follow the spec requirements'
4
- input :credential_strings
5
-
6
- id :vc_payload_verification
7
-
8
- run do
9
- skip_if credential_strings.blank?, 'No Verifiable Credentials received'
10
-
11
- credential_strings.split(',').each do |credential|
12
- raw_payload = HealthCards::JWS.from_jws(credential).payload
13
- assert raw_payload&.length&.positive?, 'No payload found'
14
-
15
- decompressed_payload =
16
- begin
17
- Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(raw_payload)
18
- rescue Zlib::DataError
19
- assert false, 'Payload compression error. Unable to inflate payload.'
20
- end
21
-
22
- assert decompressed_payload.length.positive?, 'Payload compression error. Unable to inflate payload.'
23
-
24
- payload_length = decompressed_payload.length
25
- health_card = HealthCards::HealthCard.from_jws(credential)
26
- health_card_length = health_card.to_json.length
27
-
28
- warning do
29
- assert payload_length <= health_card_length,
30
- "Payload may not be properly minified. Received a payload with length #{payload_length}, " \
31
- "but was able to generate a payload with length #{health_card_length}"
32
- end
33
-
34
- assert_valid_json decompressed_payload, 'Payload is not valid JSON'
35
-
36
- payload = JSON.parse(decompressed_payload)
37
-
38
- warning do
39
- nbf = payload['nbf']
40
- assert nbf.present?, "Payload does not include an 'nbf' claim"
41
- assert nbf.is_a?(Numeric), "Expected 'nbf' claim to be Numeric, but found #{nbf.class}"
42
- issue_time = Time.at(nbf).to_datetime
43
- assert issue_time < DateTime.now, "'nbf' is in the future: #{issue_time.rfc822}"
44
- end
45
-
46
- vc = payload['vc']
47
- assert vc.is_a?(Hash), "Expected 'vc' claim to be a JSON object, but found #{vc.class}"
48
- type = vc['type']
49
-
50
- warning do
51
- assert type.is_a?(Array), "Expected 'vc.type' to be an array, but found #{type.class}"
52
- assert type.include?('https://smarthealth.cards#health-card'),
53
- "'vc.type' does not include 'https://smarthealth.cards#health-card'"
54
- end
55
-
56
- subject = vc['credentialSubject']
57
- assert subject.is_a?(Hash), "Expected 'vc.credentialSubject' to be a JSON object, but found #{subject.class}"
58
-
59
- warning do
60
- assert subject['fhirVersion'].present?, "'vc.credentialSubject.fhirVersion' not provided"
61
- end
62
-
63
- raw_bundle = subject['fhirBundle']
64
- assert raw_bundle.is_a?(Hash), "Expected 'vc.fhirBundle' to be a JSON object, but found #{raw_bundle.class}"
65
-
66
- resource_scheme_regex = /\Aresource:\d+\z/
67
- warning do
68
- urls = raw_bundle['entry'].map { |entry| entry['fullUrl'] }
69
- bad_urls = urls.reject { |url| url.match?(resource_scheme_regex) }
70
- assert bad_urls.empty?,
71
- "The following Bundle entry urls do not use short resource-scheme URIs: #{bad_urls.join(', ')}"
72
- end
73
-
74
- bundle = FHIR::Bundle.new(raw_bundle)
75
- resources = bundle.entry.map(&:resource)
76
- bundle.entry.each { |entry| entry.resource = nil }
77
- resources << bundle
78
-
79
- resources.each do |resource|
80
- warning { assert resource.id.nil?, "#{resource.resourceType} resource should not have an 'id' element" }
81
-
82
- if resource.respond_to? :text
83
- warning { assert resource.text.nil?, "#{resource.resourceType} resource should not have a 'text' element" }
84
- end
85
-
86
- resource.each_element(resource) do |value, meta, path|
87
- case meta['type']
88
- when 'CodeableConcept'
89
- warning { assert value.text.nil?, "#{resource.resourceType} should not have a #{path}.text element" }
90
- when 'Coding'
91
- warning do
92
- assert value.display.nil?, "#{resource.resourceType} should not have a #{path}.display element"
93
- end
94
- when 'Reference'
95
- warning do
96
- next if value.reference.nil?
97
-
98
- assert value.reference.match?(resource_scheme_regex),
99
- "#{resource.resourceType}.#{path}.reference is not using the short resource URI scheme: " \
100
- "#{value.reference}"
101
- end
102
- when 'Meta'
103
- hash = value.to_hash
104
- warning do
105
- assert hash.length == 1 && hash.include?('security'),
106
- "If present, Bundle 'meta' field should only include 'security', " \
107
- "but found: #{hash.keys.join(', ')}"
108
- end
109
- end
110
- end
111
- end
112
- end
113
- end
114
- end
115
- end
@@ -1,69 +0,0 @@
1
- module Covid19VCI
2
- class VCSignatureVerification < Inferno::Test
3
- title 'Verifiable Credential signatures can be verified'
4
- input :credential_strings
5
-
6
- id :vc_signature_verification
7
-
8
- run do
9
- skip_if credential_strings.blank?, 'No Verifiable Credentials received'
10
- credential_strings.split(',').each do |credential|
11
- card = HealthCards::HealthCard.from_jws(credential)
12
- iss = card.issuer
13
-
14
- jws = HealthCards::JWS.from_jws(credential)
15
-
16
- assert iss.present?, 'Credential contains no `iss`'
17
- warning { assert iss.start_with?('https://'), "`iss` SHALL use the `https` scheme: #{iss}" }
18
- assert !iss.end_with?('/'), "`iss` SHALL NOT include a trailing `/`: #{iss}"
19
-
20
- key_set_url = "#{card.issuer}/.well-known/jwks.json"
21
-
22
- get(key_set_url)
23
-
24
- assert_response_status(200)
25
- assert_valid_json(response[:body])
26
-
27
- cors_header = request.response_header('Control-Allow-Origin')
28
- warning do
29
- assert cors_header.present?,
30
- 'No CORS header received. Issuers SHALL publish their public keys with CORS enabled'
31
- assert cors_header.value == '*',
32
- "Expected CORS header value of `*`, but actual value was `#{cors_header.value}`"
33
- end
34
-
35
- key_set = JSON.parse(response[:body])
36
-
37
- public_key = key_set['keys'].find { |key| key['kid'] == jws.kid }
38
- key_object = HealthCards::Key.from_jwk(public_key)
39
-
40
- assert public_key.present?, "Key set did not contain a key with a `kid` of #{jws.kid}"
41
-
42
- warning { assert public_key['kty'] == 'EC', "Key had a `kty` value of `#{public_key['kty']}` instead of `EC`" }
43
- warning do
44
- assert public_key['use'] == 'sig', "Key had a `use` value of `#{public_key['use']}` instead of `sig`"
45
- end
46
- warning do
47
- assert public_key['alg'] == 'ES256', "Key had an `alg` value of `#{public_key['alg']}` instead of `ES256`"
48
- end
49
- warning do
50
- assert public_key['crv'] == 'P-256', "Key had a `crv` value of `#{public_key['crv']}` instead of `P-256`"
51
- end
52
- warning { assert !public_key.include?('d'), 'Key SHALL NOT have the private key parameter `d`' }
53
- warning do
54
- assert public_key['kid'] == key_object.kid,
55
- "'kid' SHALL be equal to the base64url-encoded SHA-256 JWK Thumbprint of the key. " \
56
- "Received: '#{public_key['kid']}', Expected: '#{key_object.kid}'"
57
- end
58
-
59
- verifier = HealthCards::Verifier.new(keys: key_object, resolve_keys: false)
60
-
61
- begin
62
- assert verifier.verify(jws), 'JWS signature invalid'
63
- rescue StandardError => e
64
- assert false, "Error decoding credential: #{e.message}"
65
- end
66
- end
67
- end
68
- end
69
- end
@@ -1,3 +0,0 @@
1
- module Covid19VCI
2
- VERSION = '0.1.0'.freeze
3
- end