smart_health_cards_test_kit 0.9.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.
Files changed (27) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/lib/smart_health_cards_test_kit/fhir_operation_group.rb +105 -0
  4. data/lib/smart_health_cards_test_kit/file_download_group.rb +108 -0
  5. data/lib/smart_health_cards_test_kit/health_card.rb +33 -0
  6. data/lib/smart_health_cards_test_kit/javascript/jsQR.js +10100 -0
  7. data/lib/smart_health_cards_test_kit/javascript/qr-scanner-worker.min.js +98 -0
  8. data/lib/smart_health_cards_test_kit/javascript/qr-scanner.min.js +31 -0
  9. data/lib/smart_health_cards_test_kit/qr_code_group.rb +107 -0
  10. data/lib/smart_health_cards_test_kit/shc_fhir_validation.rb +39 -0
  11. data/lib/smart_health_cards_test_kit/shc_header_verification.rb +36 -0
  12. data/lib/smart_health_cards_test_kit/shc_payload_verification.rb +123 -0
  13. data/lib/smart_health_cards_test_kit/shc_signature_verification.rb +95 -0
  14. data/lib/smart_health_cards_test_kit/utils/chunking_utils.rb +71 -0
  15. data/lib/smart_health_cards_test_kit/utils/encoding.rb +26 -0
  16. data/lib/smart_health_cards_test_kit/utils/jws.rb +132 -0
  17. data/lib/smart_health_cards_test_kit/utils/key.rb +82 -0
  18. data/lib/smart_health_cards_test_kit/utils/key_set.rb +88 -0
  19. data/lib/smart_health_cards_test_kit/utils/private_key.rb +53 -0
  20. data/lib/smart_health_cards_test_kit/utils/public_key.rb +33 -0
  21. data/lib/smart_health_cards_test_kit/utils/verification.rb +41 -0
  22. data/lib/smart_health_cards_test_kit/utils/verifier.rb +71 -0
  23. data/lib/smart_health_cards_test_kit/version.rb +3 -0
  24. data/lib/smart_health_cards_test_kit/views/scan_qr_code.html +207 -0
  25. data/lib/smart_health_cards_test_kit/views/upload_qr_code.html +130 -0
  26. data/lib/smart_health_cards_test_kit.rb +67 -0
  27. metadata +168 -0
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require_relative 'key_set'
5
+ require_relative 'verification'
6
+
7
+ module SmartHealthCardsTestKit
8
+ module Utils
9
+ # Verifiers can validate HealthCards using public keys
10
+ class Verifier
11
+ attr_reader :keys
12
+ attr_accessor :resolve_keys
13
+
14
+ include SmartHealthCardsTestKit::Utils::Verification
15
+ extend SmartHealthCardsTestKit::Utils::Verification
16
+
17
+ # Verify a Payload
18
+ #
19
+ # This method _always_ uses key resolution and does not depend on any cached keys
20
+ #
21
+ # @param verifiable [SmartHealthCardsTestKit::Utils::JWS, String] the health card to verify
22
+ # @return [Boolean]
23
+ def self.verify(verifiable)
24
+ verify_using_key_set(verifiable)
25
+ end
26
+
27
+ # Create a new Verifier
28
+ #
29
+ # @param keys [SmartHealthCardsTestKit::Utils::KeySet, SmartHealthCardsTestKit::Utils::Key, nil] keys to use when verifying Health Cards
30
+ # @param resolve_keys [Boolean] Enables or disables key resolution
31
+ def initialize(keys: nil, resolve_keys: true)
32
+ @keys = case keys
33
+ when KeySet
34
+ keys
35
+ when Key
36
+ KeySet.new(keys)
37
+ else
38
+ KeySet.new
39
+ end
40
+
41
+ self.resolve_keys = resolve_keys
42
+ end
43
+
44
+ # # Add a key to use when verifying
45
+ # #
46
+ # # @param key [SmartHealthCardsTestKit::Utils::Key, SmartHealthCardsTestKit::Utils::KeySet] the key to add
47
+ # def add_keys(key)
48
+ # @keys.add_keys(key)
49
+ # end
50
+
51
+ # # Remove a key to use when verifying
52
+ # #
53
+ # # @param key [SmartHealthCardsTestKit::Utils::Key] the key to remove
54
+ # def remove_keys(key)
55
+ # @keys.remove_keys(key)
56
+ # end
57
+
58
+ # Verify a Payload
59
+ #
60
+ # @param verifiable [SmartHealthCardsTestKit::Utils::JWS, String] the health card to verify
61
+ # @return [Boolean]
62
+ def verify(verifiable)
63
+ verify_using_key_set(verifiable, keys, resolve_keys: resolve_keys?)
64
+ end
65
+
66
+ def resolve_keys?
67
+ resolve_keys
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,3 @@
1
+ module SmartHealthCardsTestKit
2
+ VERSION = '0.9.0'
3
+ end
@@ -0,0 +1,207 @@
1
+ <html>
2
+ <head>
3
+ <meta charset="utf-8">
4
+ <meta name="viewport" content="width=device-width, initial-scale=1">
5
+ <title>Scan a QR Code</title>
6
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
7
+ </head>
8
+ <body class="m-3" onload="onLoad()">
9
+ <section class="hero is-small is-success">
10
+ <h3>Scan a QR Code</h3>
11
+ <p class="subtitle">Use device's camera to scan a QR code representing a Smart Health Card</p>
12
+ </section>
13
+
14
+ <div>
15
+ <div class="field is-grouped">
16
+ <button id="start" class="btn btn-primary" onclick="disableStartButton()">Start</button>
17
+ <button id="stop" class="btn btn-primary" onclick="enableStartButton()" disabled>Stop</button>
18
+ </div>
19
+ <div class="level-right" id="multi-status-container">
20
+ </div>
21
+
22
+ <video id="preview"></video>
23
+
24
+ <div class="notification is-success is-light" hidden id="success-notification">
25
+ <div class="level">
26
+ <div class="level-left">
27
+ <div class="level-item">
28
+ <span class="is-size-4">QR code scanned successfully.</span>
29
+ </div>
30
+ </div>
31
+ </div>
32
+ </div>
33
+
34
+ <div class="notification is-success is-light">
35
+ <form id="upload-qr-code" action="post_qr_code" accept-charset="UTF-8" method="post"></form>
36
+ <div class="level">
37
+ <div class="level-left">
38
+ <div class="level-item">
39
+ <span class="is-size-4" id="qr-contents"></span>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ </form>
44
+ </div>
45
+
46
+ <div class="notification is-danger is-light" hidden id="error-notification">
47
+ <div class="level">
48
+ <div class="level-left">
49
+ <div class="level-item">
50
+ <span class="is-size-4">QR code does not contain a SMART Health Card.</span>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ </div>
55
+ </div>
56
+
57
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
58
+
59
+ <script type="module">
60
+ import QrScanner from './qr-scanner.min.js';
61
+
62
+ const healthCardPattern = /^shc:\/(?<multipleChunks>(?<chunkIndex>[0-9]+)\/(?<chunkCount>[0-9]+)\/)?[0-9]+$/;
63
+
64
+ let qrScanner;
65
+ let startButton;
66
+ let stopButton;
67
+ let successNotification;
68
+ let errorNotification;
69
+ let inputField;
70
+ let multiStatusContainer;
71
+ let form
72
+
73
+ let scannedCodes = [];
74
+
75
+ window.onLoad = function() {
76
+ const videoElement = document.getElementById('preview');
77
+
78
+ qrScanner = new QrScanner(videoElement, handleScan);
79
+
80
+ startButton = document.getElementById('start');
81
+ stopButton = document.getElementById('stop');
82
+ successNotification = document.getElementById('success-notification');
83
+ errorNotification = document.getElementById('error-notification');
84
+ inputField = document.getElementById('qr-contents');
85
+ multiStatusContainer = document.getElementById('multi-status-container');
86
+ form = document.getElementById('upload-qr-code');
87
+
88
+ startButton.addEventListener('click', startScanning);
89
+ stopButton.addEventListener('click', stopScanning);
90
+ }
91
+
92
+ window.startScanning = function() {
93
+ disableStartButton();
94
+ hideSuccessNotification();
95
+ hideErrorNotification();
96
+ qrScanner.start();
97
+ };
98
+
99
+ window.stopScanning = function() {
100
+ enableStartButton();
101
+ qrScanner.stop();
102
+ };
103
+
104
+ window.disableStartButton = function() {
105
+ startButton.setAttribute('disabled', true);
106
+ stopButton.removeAttribute('disabled');
107
+ };
108
+
109
+ window.enableStartButton = function() {
110
+ stopButton.setAttribute('disabled', true);
111
+ startButton.removeAttribute('disabled');
112
+ };
113
+
114
+ window.hideSuccessNotification = function() {
115
+ successNotification.setAttribute('hidden', true);
116
+ };
117
+
118
+ window.showSuccessNotification = function() {
119
+ hideErrorNotification();
120
+ successNotification.removeAttribute('hidden');
121
+ };
122
+
123
+ window.hideErrorNotification = function() {
124
+ errorNotification.setAttribute('hidden', true);
125
+ };
126
+
127
+ window.showErrorNotification = function() {
128
+ hideSuccessNotification();
129
+ errorNotification.removeAttribute('hidden');
130
+ };
131
+
132
+ window.handleScan = function(result) {
133
+ console.log(result);
134
+
135
+ if (healthCardPattern.test(result)) {
136
+ const match = result.match(healthCardPattern);
137
+ if (match.groups.multipleChunks) {
138
+ hideErrorNotification();
139
+ const chunkCount = +match.groups.chunkCount;
140
+ const currentChunkIndex = +match.groups.chunkIndex;
141
+ if (scannedCodes.length !== chunkCount) {
142
+ scannedCodes = new Array(chunkCount);
143
+ scannedCodes.fill(null, 0, chunkCount);
144
+ }
145
+ scannedCodes[currentChunkIndex - 1] = result;
146
+
147
+ multiStatusContainer.innerHTML = scannedCodes
148
+ .map((code, index) => {
149
+ return code
150
+ ? multiPresentElement(index + 1, chunkCount)
151
+ : multiMissingElement(index + 1, chunkCount);
152
+ })
153
+ .join('\n');
154
+
155
+ if (scannedCodes.every(code => code)) {
156
+ stopScanning();
157
+
158
+ inputField.textContent = JSON.stringify(scannedCodes);
159
+ showSuccessNotification();
160
+
161
+ //TODO: do something here. Post result to endpoint? Need to understand the logic in this nested if statement
162
+
163
+ }
164
+ } else {
165
+ stopScanning();
166
+
167
+ multiStatusContainer.innerHTML = '';
168
+ inputField.textContent = JSON.stringify([result]);
169
+ showSuccessNotification();
170
+
171
+ //QR code scanned successfully. Post to endpoint (code borrowed from upload_qr_code.html)
172
+ const urlParams = new URLSearchParams(window.location.search);
173
+ const requestId = urlParams.get('id');
174
+ if (requestId) {
175
+ form.action += '?id=' + encodeURIComponent(requestId);
176
+ }
177
+
178
+ const postUrl = `${form.action}`;
179
+ fetch(postUrl, {
180
+ method: 'POST',
181
+ headers: {
182
+ 'Content-Type': 'application/json',
183
+ },
184
+ body: JSON.stringify({ qr_code_content: result }),
185
+ })
186
+ .then(response => {
187
+ if (response.ok) {
188
+ inputField.textContent += " - QR Code successfully posted to server.";
189
+ window.history.back(); // Go back to the previous page
190
+ } else {
191
+ inputField.textContent += " - Error posting QR code to server.";
192
+ }
193
+ })
194
+ .catch(error => {
195
+ inputField.textContent += ` - Network error: ${error}`;
196
+ });
197
+
198
+ }
199
+ } else {
200
+ stopScanning();
201
+
202
+ showErrorNotification();
203
+ }
204
+ };
205
+ </script>
206
+ </body>
207
+ </html>
@@ -0,0 +1,130 @@
1
+ <html>
2
+ <head>
3
+ <meta charset="utf-8">
4
+ <meta name="viewport" content="width=device-width, initial-scale=1">
5
+ <title>Upload a QR image file</title>
6
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
7
+ </head>
8
+ <body class="m-3">
9
+ <div>
10
+ <h3>Upload a QR image file</h3>
11
+ <p class="subtitle">Manually upload a pre saved QR image file. The file shall have .png, .jpg, or .jpeg file extension.</p>
12
+ </div>
13
+ <div>
14
+ <form id="upload-qr-code" action="post_qr_code" accept-charset="UTF-8" method="post">
15
+ <div class="field is-grouped">
16
+ <div class="file has-name">
17
+ <label class="file-label">
18
+ <input type="file" name="qr_file" id="qr_file" accept=".png, .jpg, .jpeg" class="file-input" style="display: none;">
19
+ <label for="qr_file" class="btn btn-primary">Choose File</label>
20
+ <span class="file-name">No file uploaded</span>
21
+ </label>
22
+ </div>
23
+ <p></p>
24
+ <p id="qr-content" style="margin-top: 10px;"></p>
25
+ <p class="control">
26
+ <input type="submit" class="btn btn-secondary" id="upload-button" disabled>
27
+ </p>
28
+ </div>
29
+ </form>
30
+ <canvas id="qr-canvas" style="display: none;"></canvas>
31
+
32
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
33
+ <script src="jsqr.js"></script>
34
+ <script>
35
+ document.addEventListener('DOMContentLoaded', function () {
36
+ const fileInput = document.getElementById('qr_file');
37
+ const fileName = document.querySelector('.file-name');
38
+ const uploadButton = document.getElementById('upload-button');
39
+ const qrCanvas = document.getElementById('qr-canvas');
40
+ const qrContent = document.getElementById('qr-content');
41
+ const form = document.getElementById('upload-qr-code');
42
+
43
+ let qrCodeContent = null;
44
+
45
+ // Update form action if `id` query param is present
46
+ const urlParams = new URLSearchParams(window.location.search);
47
+ const requestId = urlParams.get('id');
48
+ if (requestId) {
49
+ form.action += '?id=' + encodeURIComponent(requestId);
50
+ }
51
+
52
+ fileInput.addEventListener('change', function () {
53
+ const file = fileInput.files[0];
54
+ if (file) {
55
+ fileName.textContent = file.name;
56
+
57
+ // Read the image file and decode QR code
58
+ const reader = new FileReader();
59
+ reader.onload = function (e) {
60
+ const img = new Image();
61
+ img.onload = function () {
62
+ // Draw the image on the canvas
63
+ const ctx = qrCanvas.getContext('2d');
64
+ qrCanvas.width = img.width;
65
+ qrCanvas.height = img.height;
66
+ ctx.drawImage(img, 0, 0, img.width, img.height);
67
+
68
+ // Get image data from the canvas
69
+ const imageData = ctx.getImageData(0, 0, qrCanvas.width, qrCanvas.height);
70
+
71
+ // Decode QR code using jsQR
72
+ const qrCode = jsQR(imageData.data, imageData.width, imageData.height);
73
+ if (qrCode) {
74
+ qrContent.textContent = `QR Code Content: ${qrCode.data}`;
75
+ qrCodeContent = qrCode.data; // Store the decoded content
76
+ uploadButton.disabled = false; // Enable the submit button
77
+ // Change button style to "btn-primary"
78
+ uploadButton.classList.remove('btn-secondary');
79
+ uploadButton.classList.add('btn-primary');
80
+ } else {
81
+ qrContent.textContent = 'No QR code detected in the image.';
82
+ uploadButton.disabled = true;
83
+ // Reset button style to "btn-secondary"
84
+ uploadButton.classList.remove('btn-primary');
85
+ uploadButton.classList.add('btn-secondary');
86
+ }
87
+ };
88
+ img.src = e.target.result;
89
+ };
90
+ reader.readAsDataURL(file);
91
+ } else {
92
+ fileName.textContent = 'No file uploaded';
93
+ uploadButton.disabled = true;
94
+ qrContent.textContent = '';
95
+ // Reset button style to "btn-secondary"
96
+ uploadButton.classList.remove('btn-primary');
97
+ uploadButton.classList.add('btn-secondary');
98
+ }
99
+ });
100
+
101
+ form.addEventListener('submit', function (event) {
102
+ event.preventDefault(); // Prevent default form submission
103
+
104
+ if (qrCodeContent) {
105
+ const postUrl = `${form.action}`;
106
+ fetch(postUrl, {
107
+ method: 'POST',
108
+ headers: {
109
+ 'Content-Type': 'application/json',
110
+ },
111
+ body: JSON.stringify({ qr_code_content: qrCodeContent }),
112
+ })
113
+ .then(response => {
114
+ if (response.ok) {
115
+ qrContent.textContent += " - QR Code successfully posted to server.";
116
+ window.history.back(); // Go back to the previous page
117
+ } else {
118
+ qrContent.textContent += " - Error posting QR code to server.";
119
+ }
120
+ })
121
+ .catch(error => {
122
+ qrContent.textContent += ` - Network error: ${error}`;
123
+ });
124
+ }
125
+ });
126
+ });
127
+ </script>
128
+ </div>
129
+ </body>
130
+ </html>
@@ -0,0 +1,67 @@
1
+ require 'json'
2
+
3
+ require_relative 'smart_health_cards_test_kit/version'
4
+
5
+ require_relative 'smart_health_cards_test_kit/utils/jws'
6
+ require_relative 'smart_health_cards_test_kit/health_card'
7
+
8
+ require_relative 'smart_health_cards_test_kit/shc_fhir_validation'
9
+ require_relative 'smart_health_cards_test_kit/shc_header_verification'
10
+ require_relative 'smart_health_cards_test_kit/shc_payload_verification'
11
+ require_relative 'smart_health_cards_test_kit/shc_signature_verification'
12
+
13
+ require_relative 'smart_health_cards_test_kit/file_download_group'
14
+ require_relative 'smart_health_cards_test_kit/qr_code_group'
15
+ require_relative 'smart_health_cards_test_kit/fhir_operation_group'
16
+
17
+ module SmartHealthCardsTestKit
18
+ class SmartHealthCardsTestSuite < Inferno::TestSuite
19
+ id :smart_health_cards
20
+ title 'SMART Health Cards'
21
+ description %(
22
+ The US Core Test Kit tests systems for their conformance to the
23
+ [SMART Health Cards Framework v1.4.0](https://spec.smarthealth.cards/)
24
+ )
25
+ version VERSION
26
+
27
+ # All FHIR validation requsets will use this FHIR validator
28
+ fhir_resource_validator do
29
+ # igs 'identifier#version' # Use this method for published IGs/versions
30
+ # igs 'igs/filename.tgz' # Use this otherwise
31
+
32
+ exclude_message do |message|
33
+ message.message.match?(/\A\S+: \S+: URL value '.*' does not resolve/)
34
+ end
35
+ end
36
+
37
+ # HTTP Routes
38
+ resume_test_route :post, '/post_qr_code' do |request|
39
+ request.query_parameters['id']
40
+ end
41
+
42
+ scan_qr_code_html = File.read(File.join(__dir__, './smart_health_cards_test_kit/views/scan_qr_code.html'))
43
+ scan_qr_code_html_route_handler = proc { [200, { 'Content-Type' => 'text/html' }, [scan_qr_code_html]] }
44
+ route(:get, '/scan_qr_code', scan_qr_code_html_route_handler)
45
+
46
+ qr_scanner = File.read(File.join(__dir__, './smart_health_cards_test_kit/javascript/qr-scanner.min.js'))
47
+ qr_scanner_route_handler = proc { [200, { 'Content-Type' => 'text/javascript' }, [qr_scanner]] }
48
+ route(:get, '/qr-scanner.min.js', qr_scanner_route_handler)
49
+
50
+ qr_scanner_worker = File.read(File.join(__dir__, './smart_health_cards_test_kit/javascript/qr-scanner-worker.min.js'))
51
+ qr_scanner_worker_route_handler = proc { [200, { 'Content-Type' => 'text/javascript' }, [qr_scanner_worker]] }
52
+ route(:get, '/qr-scanner-worker.min.js', qr_scanner_worker_route_handler)
53
+
54
+ js_qr = File.read(File.join(__dir__, './smart_health_cards_test_kit/javascript/jsQR.js'))
55
+ js_qr_route_handler = proc { [200, { 'Content-Type' => 'text/javascript' }, [js_qr]] }
56
+ route(:get, '/jsqr.js', js_qr_route_handler)
57
+
58
+ upload_html = File.read(File.join(__dir__, './smart_health_cards_test_kit/views/upload_qr_code.html'))
59
+ upload_html_route_handler = proc { [200, { 'Content-Type' => 'text/html' }, [upload_html]] }
60
+ route(:get, '/upload_qr_code', upload_html_route_handler)
61
+
62
+ # Tests and TestGroups
63
+ group from: :shc_file_download_group
64
+ group from: :shc_fhir_operation_group
65
+ group from: :shc_qr_code_group
66
+ end
67
+ end
metadata ADDED
@@ -0,0 +1,168 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: smart_health_cards_test_kit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Yunwei Wang
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-01-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: inferno_core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.5.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.5.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rqrcode
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rqrcode_core
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 1.2.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 1.2.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: database_cleaner-sequel
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.8'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.8'
69
+ - !ruby/object:Gem::Dependency
70
+ name: factory_bot
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '6.1'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '6.1'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.10'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.10'
97
+ - !ruby/object:Gem::Dependency
98
+ name: webmock
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.11'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.11'
111
+ description: Smart Health Cards test kit
112
+ email:
113
+ - inferno@groups.mitre.org
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - LICENSE
119
+ - lib/smart_health_cards_test_kit.rb
120
+ - lib/smart_health_cards_test_kit/fhir_operation_group.rb
121
+ - lib/smart_health_cards_test_kit/file_download_group.rb
122
+ - lib/smart_health_cards_test_kit/health_card.rb
123
+ - lib/smart_health_cards_test_kit/javascript/jsQR.js
124
+ - lib/smart_health_cards_test_kit/javascript/qr-scanner-worker.min.js
125
+ - lib/smart_health_cards_test_kit/javascript/qr-scanner.min.js
126
+ - lib/smart_health_cards_test_kit/qr_code_group.rb
127
+ - lib/smart_health_cards_test_kit/shc_fhir_validation.rb
128
+ - lib/smart_health_cards_test_kit/shc_header_verification.rb
129
+ - lib/smart_health_cards_test_kit/shc_payload_verification.rb
130
+ - lib/smart_health_cards_test_kit/shc_signature_verification.rb
131
+ - lib/smart_health_cards_test_kit/utils/chunking_utils.rb
132
+ - lib/smart_health_cards_test_kit/utils/encoding.rb
133
+ - lib/smart_health_cards_test_kit/utils/jws.rb
134
+ - lib/smart_health_cards_test_kit/utils/key.rb
135
+ - lib/smart_health_cards_test_kit/utils/key_set.rb
136
+ - lib/smart_health_cards_test_kit/utils/private_key.rb
137
+ - lib/smart_health_cards_test_kit/utils/public_key.rb
138
+ - lib/smart_health_cards_test_kit/utils/verification.rb
139
+ - lib/smart_health_cards_test_kit/utils/verifier.rb
140
+ - lib/smart_health_cards_test_kit/version.rb
141
+ - lib/smart_health_cards_test_kit/views/scan_qr_code.html
142
+ - lib/smart_health_cards_test_kit/views/upload_qr_code.html
143
+ homepage: https://github.com/inferno-framework/smart-health-cards-test-kit
144
+ licenses:
145
+ - Apache-2.0
146
+ metadata:
147
+ homepage_uri: https://github.com/inferno-framework/smart-health-cards-test-kit
148
+ source_code_uri: https://github.com/inferno-framework/smart-health-cards-test-kit
149
+ post_install_message:
150
+ rdoc_options: []
151
+ require_paths:
152
+ - lib
153
+ required_ruby_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: 3.1.2
158
+ required_rubygems_version: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ requirements: []
164
+ rubygems_version: 3.5.7
165
+ signing_key:
166
+ specification_version: 4
167
+ summary: Smart Health Cards test kit
168
+ test_files: []