1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
|
# Green Pass - התו הירוק
Signature verification for the Green Pass (התו הירוק).
As (not really) specified in https://github.com/MohGovIL/Ramzor
## Verification Script
A pythonic implementation of the verification process can be found in [`verify.py`](verify.py).
### Setup
Install [`pipenv`](https://pipenv.pypa.io/en/latest/) and sync the required dependencies:
```bash
$ pipenv sync
```
### Usage
Generate a Green Pass or Vaccination Certificate at https://corona.health.gov.il/green-pass/
#### Option 1 - from PDF
Directly verify your `GreenPass.pdf` or `VaccinationCertificate.pdf`:
```bash
$ pipenv run verify -p GreenPass.pdf
```
#### Option 2 - from QR code image
Save the QR code as a `.png` file (e.g. from screenshot) and execute:
```bash
$ pipenv run verify -i green_pass_image.png
```
#### Option 3 - from QR code decoded textual content
Decode the QR code payload yourself, put it in a txt file and then execute:
```bash
$ pipenv run verify -t green_pass_payload.txt
```
### Output example
```
Israeli ID Number 012345678
ID valid by 2021-01-01
Cert Unique ID 01/IL/ABCD1234ABCD1234ABCD1234ABCD1234#ABCD1234
✅ Valid signature!
```
## Verification Process Details
We describe in detail the signature verification steps using `openssl` commands.
### QR Code Data
Scan a Green Pass QR code, the encoded data has the following format:
```json
Base64EncodedSignature#{"id":"01/IL/ABCD1234ABCD1234ABCD1234ABCD1234#ABCD1234","et":1,"ct":1,"c":"IL MOH","cn":null,"fn":null,"g":null,"f":null,"gl":null,"fl":null,"idp":null,"idl":null,"b":"0001-01-01","e":"0001-01-01","a":"0001-01-01","p":[{"idl":"0123456789","e":"2021-01-01"}]}
```
Where `Base64EncodedSignature` are 256 bytes of an RSA signature signed with a 2048-bit public key and PKCS#1 v1.5 padding, followed by a `#` delimiter, and then the signed JSON payload as defined in https://github.com/MohGovIL/Ramzor#minimal-dataset
⚠️ **The current MOH implementation has a pitfall.** ⚠️ The JSON payload is not signed as is, but rather the **SHA256 hash** of the data is signed. This effectively means the payload is hashed twice, once manually, and once as part of the signature verification scheme.
Some certificates now have `ct=2` which is not double hashed. This implementation supports both versions.
### Certificates
The Ministry of Healthy RSA certificate seems to be available at https://ramzorfiles.z6.web.core.windows.net/RamzorQRPubKey.der
```bash
$ openssl x509 -pubkey -noout -inform der -in certs/RamzorQRPubKey.der -text
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw4MJrQWgRnYakBsoU/eV
RxpvDnrGvtidQtfU0o0OGKU+p3H16ufPusBzKLHQPGAoZB33lU8wvfP01xUJTvod
qoi6KEKXGXC+XreQ1YJDKhIglYfPxJOOcauWf/tmV+w0xph6O3L5/2JrhxEjIbdu
E8zP8FvZ+KxVFA9LOFQzX7zbbiDUBLCRtIBhwtLCPIiy960O+lVZkMPXg5BrBWjc
NBrDN62PgOxGXvP3iF0bOlz1+m63q9cFzdKqVfOyl8jZRr3GzYD8SVSXO9EbfYId
8DEP+HMmqd4StD2X6OMDc9UrBBHx3nGbRpi2D9QuHA/kq/QAjQqnrd+iuzdSwQi+
mQIDAQAB
-----END PUBLIC KEY-----
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
09:77:f3:e6:67:08:85:38:25:c4:ad:f6:88:5a:02:8a
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert SHA2 Assured ID Code Signing CA
Validity
Not Before: Feb 19 00:00:00 2021 GMT
Not After : Feb 27 23:59:59 2024 GMT
Subject: C = IL, L = Jerusalem, O = Ministry of Health, CN = Ministry of Health
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:c3:83:09:ad:05:a0:46:76:1a:90:1b:28:53:f7:
95:47:1a:6f:0e:7a:c6:be:d8:9d:42:d7:d4:d2:8d:
0e:18:a5:3e:a7:71:f5:ea:e7:cf:ba:c0:73:28:b1:
d0:3c:60:28:64:1d:f7:95:4f:30:bd:f3:f4:d7:15:
09:4e:fa:1d:aa:88:ba:28:42:97:19:70:be:5e:b7:
90:d5:82:43:2a:12:20:95:87:cf:c4:93:8e:71:ab:
96:7f:fb:66:57:ec:34:c6:98:7a:3b:72:f9:ff:62:
6b:87:11:23:21:b7:6e:13:cc:cf:f0:5b:d9:f8:ac:
55:14:0f:4b:38:54:33:5f:bc:db:6e:20:d4:04:b0:
91:b4:80:61:c2:d2:c2:3c:88:b2:f7:ad:0e:fa:55:
59:90:c3:d7:83:90:6b:05:68:dc:34:1a:c3:37:ad:
8f:80:ec:46:5e:f3:f7:88:5d:1b:3a:5c:f5:fa:6e:
b7:ab:d7:05:cd:d2:aa:55:f3:b2:97:c8:d9:46:bd:
c6:cd:80:fc:49:54:97:3b:d1:1b:7d:82:1d:f0:31:
0f:f8:73:26:a9:de:12:b4:3d:97:e8:e3:03:73:d5:
2b:04:11:f1:de:71:9b:46:98:b6:0f:d4:2e:1c:0f:
e4:ab:f4:00:8d:0a:a7:ad:df:a2:bb:37:52:c1:08:
be:99
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Authority Key Identifier:
keyid:5A:C4:B9:7B:2A:0A:A3:A5:EA:71:03:C0:60:F9:2D:F6:65:75:0E:58
X509v3 Subject Key Identifier:
87:3F:27:58:83:96:F0:67:2D:E5:E0:B5:9A:A7:B5:2A:50:A1:1E:A8
X509v3 Key Usage: critical
Digital Signature
X509v3 Extended Key Usage:
Code Signing
X509v3 CRL Distribution Points:
Full Name:
URI:http://crl3.digicert.com/sha2-assured-cs-g1.crl
Full Name:
URI:http://crl4.digicert.com/sha2-assured-cs-g1.crl
X509v3 Certificate Policies:
Policy: 2.16.840.1.114412.3.1
CPS: http://www.digicert.com/CPS
Policy: 2.23.140.1.4.1
Authority Information Access:
OCSP - URI:http://ocsp.digicert.com
CA Issuers - URI:http://cacerts.digicert.com/DigiCertSHA2AssuredIDCodeSigningCA.crt
X509v3 Basic Constraints: critical
CA:FALSE
Signature Algorithm: sha256WithRSAEncryption
4a:44:6c:e6:24:56:69:72:54:59:d0:f0:29:5a:ca:89:38:71:
68:56:6b:42:17:bd:ee:f4:e9:2c:7a:86:1d:4d:b7:f0:44:90:
ec:fb:e9:38:0a:d7:72:38:5e:0b:3b:61:7d:96:36:b2:b4:44:
b1:2e:fd:68:c2:8b:ec:12:66:37:06:81:bb:f5:bd:83:72:49:
8c:e5:ec:53:56:d5:24:c1:35:56:d2:a3:8d:e1:a7:a0:69:ca:
46:e3:f5:3b:dd:53:89:4a:27:ab:ee:94:bc:9e:46:60:b4:f8:
f9:2a:88:87:fd:7d:46:6e:86:21:df:fe:56:d4:1c:61:6b:22:
3c:ea:84:85:cd:fd:c6:f1:c6:e6:b9:5e:5c:df:23:da:e9:bc:
6d:8a:bf:1c:c4:11:c1:26:c6:e6:84:21:6d:55:d6:63:c0:c3:
dd:67:db:73:4b:ce:35:d7:72:0b:26:09:f3:20:1a:cf:6a:c1:
98:b8:bc:48:3a:6f:a8:b1:89:35:55:85:2f:00:26:7d:9c:ee:
46:b2:58:eb:9f:9d:4f:42:7e:03:52:24:27:88:50:dc:fc:c0:
f2:48:ab:6b:2c:c7:0a:d0:c6:99:af:12:a5:3d:ea:dd:70:94:
1c:fe:06:56:d9:b7:cf:b2:e7:7b:8b:13:d6:8b:1c:75:c6:6c:
30:06:49:99
```
Convert the DER formatted certificate to a PEM file:
```bash
$ openssl x509 -pubkey -noout -inform der -in certs/RamzorQRPubKey.der > certs/RamzorQRPubKey.pem
```
Assumed the signed JSON data (without any trailing whitespace) is in `data.json`. Before we verify the data we need to hash the signed JSON:
```bash
$ openssl dgst -binary -sha256 data.json > data.hash
```
This redundant hash step should be skipped in case the `ct` value in the payload is equal to `2`.
Assuming the Base64-decoded signature bytes are in `sig.bin` we can now run:
```bash
$ openssl dgst -verify certs/RamzorQRPubKey.pem -keyform PEM -sha256 -signature sig.bin data.hash
Verified OK
```
and receive successful verification.
## License
[GPLv3](LICENSE)
|