smart_health_cards_test_kit 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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: []