Closed wkornewald closed 3 years ago
The `kid´ in the HCERT refers to the kid on the trusted list - see https://github.com/ehn-digital-green-development/hcert-spec/blob/main/hcert_spec.md#a1-the-key-identifier-kids. The note is important as verifiers do not need to know about how the kid came about, they only need to match between the kid in the HCERT with the ones on the trusted list.
And how do you get each kid from the trusted list? Currently, the kid seems to be the shortened cert digest and this means the verifier needs to know how to derive the kid for the trusted list and you don’t get the flexibility of a public key based kid. Or did I misunderstand something?
Or will the kid be the (shortened?) Subject Key Identifier in the final spec?
BTW, does JWKS have any advantage over a standard PEM file? PEM seems simpler and has ready made parsers in many languages.
To be useful, a trusted list must contain information about the issuer (name, country etc), public key parameters as well as the key identifiers. If this is communicated using certificates (PEM), in which case the receiver of the trusted list must calculate the kid
as described in Appendix A.1.
I would personally recommend that the trusted list is constructed as a (signed if required) JWKS. An example is provided in the testdata repository. The upside of that is that kid
, public key parameters and additional data about the keys (e.g., issuer) is easily consumed by modern applications. YMMV and PEM might fit your bill better.
@wkornewald just to avoid confusion - we are trying to keep the QR as small as possible (100's of bytes) - so there is only room for a couple of bytes really for this certificate reference. And hence the shortened KID.
The reasoning behind shortening is clear.
I just wasn’t expecting that we’d want to avoid X.509 and go with JWKS. That’s the reason for my confusion because it thought we’d just read the SKID extension from the X.509 and use that (shortened) as the kid. But with JWKS there is no X.509 of course unless everyone additionally creates them, but why use both if just one is sufficient?
Also, every language should have APIs for working with X.509 and parsing PEM and you can construct cert chains and validate them with existing tools in case you want to have root and intermediate certs. The advantage of JWKS isn’t quite clear to me because it’s not a real replacement for X.509. Our team is currently using cert chains and we’d have to discuss the implications of going with JWKS.
So you can still use the whole PEM/X509 and validate the chain. It is just that the (leaf or intermediate) certs are not in the QR. Nor is the public key.
In theory - you could even ignore the KID and simply validate against all your known trusted public keys.
you could even ignore the KID and simply validate against all your known trusted public keys.
That is not practical. We may get a potential larger number of certificates that would need to be tested each time.
You mean even with JWKS all countries will additionally issue X.509 certs and the JWKS is just derived from that? With the JWKS suggestion in the spec I’m afraid that countries will only issue a JWKS with public keys and then we can’t have X.509 anymore (we definitely don’t want to implement JWKS as a fallback).
It is up to each member state to construct their list of of trusted keys. At the end of the day, the trusted list still needs to contain a list of keys with name and kid. The format of the trusted list of out of scope of the HCERT specification.
Per A.3.1 the secretariat will maintain a public, integrity (secure) protected, single, up to date, aggregated, list of all CSCAs (DGCG). You can always this list as source for your own trusted list.
@wkornewald no pure JWKS with public keys is NOT a forseen option - the Europan DGCG will only handle and allow the upload and management of a CSCA in X.509 form that signs a DSC in X.509 format - and distribute these as X.509s - closely aligned to the current ICAO model for eMRTD and the WHO model for this.
See the https://ec.europa.eu/health/sites/health/files/ehealth/docs/trust-framework_interoperability_certificates_en.pdf, the trust managment section in https://ec.europa.eu/health/sites/health/files/ehealth/docs/digital-green-certificates_v1_en.pdf https://ec.europa.eu/health/sites/health/files/ehealth/docs/digital-green-certificates_v2_en.pdf and https://www.who.int/publications/m/item/interim-guidance-for-developing-a-smart-vaccination-certificate
Alright, this clarifies it. And would the kid be the X.509‘s shortened SKID? This way a PEM could contain all information without needing a JWKS and you still wouldn’t have to know how the SKID came about. Just the shortening would be the only detail for verifiers to take into account.
No - it is the first 8 bytes of the SHA256 fingerprint. The reason for not using SKIDs is that some countries may use existing eMRTD certificates, such as BCS certs that do not have SKIDs due to size limits.
Could we then please require using the first 8 bytes of the SKID if one exists and fall back to the first 8 bytes of the SHA-256 fingerprint? Then we can still have the advantages of using the SKID where possible. I hope this doesn't add too much complexity to the spec.
Could we then please require using the first 8 bytes of the SKID if one exists and fall back to the first 8 bytes of the SHA-256 fingerprint? Then we can still have the advantages of using the SKID where possible. I hope this doesn't add too much complexity to the spec.
If you have the certificate, why not simply calculate the hash? It is a one time operation when one import the list of certificate into the verifier.
Because the hash refers to the whole X.509 certificate instead of just the public key. This is the whole reason why cert pinning was considered a failure. The new best practice is to pin the public key, not the certificate. This allows for e.g. extending the validity of an existing X.509 by simply creating a new X.509 and reusing the previous public key. This way all existing HCERTs stay valid because the kid doesn't change. However, if the kid refers to the hash of the X.509 you can't do that because the hash changes, so all existing HCERTs become invalid.
Oh we can simplify this some more: The spec could require the kid to be the first 8 bytes of the hash of the X.509's public key (instead of hashing the whole X.509). This way we have a single, simple rule like before, but at the same time more flexibility (and mistakes are less fatal). In most certs the SKID would just happen to have the same value as the public key hash, but that wouldn't be relevant to us.
@wkornewald all good ideas, but I think the ship has sailed. Could it be better? Yes, but people are already implementing and we need to move on.
Since you are considering a change of the encoding, could you please also consider this really trivially tiny change, too?
@wkornewald while agree on your analysis on pinning and cert renewal, one opinion and one question: (except for the fact, that like @jschlyter has said, this cannot be changed anymore)
This
Think of it this way: Replacing a certificate currently leads to invalidating tens of millions of HCerts. We should do everything to avoid unnecessary HCert invalidation because so many people are affected and it would be a non-trivial effort to correct the situation (by issuing tens of millions of new HCerts). Such an effort would dwarf the tiny implemenation effort we could do now as a preparation. You might not see the need right now, but with so many countries and people involved, mistakes are more likely.
Regarding your question: If we use the (shortened?) SKID it wouldn't really matter which key format is used. The issuer of the cert decides how the SKID is computed and our HCert's kid would only identify based on SKID. When issuing a new cert the SKID just needs to be computed in the same way.
there are multiple perspectives/topics right now, but regarding the answer to my question, let me check if we have the same perspective here:
do you have the same view on this?
Yes, I think this looks right.
IIRC we had a discussion about this earlier and the choice to hash the certificate very deliberate. Perhaps @dirkx, @martin-lindstrom or @Razumain can recap?
As to the original reasoning:
So first - in the eMRTD/mDOC world it is common for (barcode) signing certificates to be very small - and hence not have a SKI or any such data.
E.g. see table 4 in https://www.icao.int/publications/Documents/9303_p12_cons_en.pdf and the appendix (x means 'do not use — the field MUST NOT be present').
So effectively the lowest common denominator are things like the fingerprint of the certificate or some hash over the public key (or the public key itself).
The fingerprint (i.e. the SHA256 of the DER encoded x.509) is very standard - and very interoperable between tooling (And this also sidesteps the window/tooling 2-byte ASN.1 prefix that also plagues cut-and-paste of the fingerprint/SKI & the debate if the SKI its its value or the oncted string)).
So hence the choise for this thing which is the easiest to get interoperable.
Size matters - so hence the 8 bytes cutoff.
So, it's a matter of simplicity of creating a cert hash vs. all the benefits of using the public key hash? I think the priority should be on the benefits of public key pinning.
The priority should be making sure the kid, whatever it is, is resolvable at the verification time. The solution could be very short, and very informative.
Let's take Austria as an example. Today, WTOXYrYS47o
is one of their KIDs. A structure like this forces verifiers to not only have a cached trust list somewhere but also to build their own servers to update such trust lists and push to their apps from time to time. Each verifying vendor can then use their own key servers to trace users around.
A second option would be to force the ISSUING COUNTRY inside the payload to be the same as the country of the KEY. As you know, the issuing country can be different than the country of test/immunization. In this case, verifiers first unpack the payload to look for the co
field, and then hard code that AT
to Austria's Master List. This still requires an intermediary server that is managing trust list updates for all countries for each verifying vendor, but at least there are no assumptions on which country the signature is coming from.
A third option would be to add the Issuer to the kid as: AT:WTOXYrYS47o
. In this option, verifiers would hardcode the AT
from the kid itself to the Master List from Austria. They can also hardcode a simpler AT
-> dgc.a-sit.at/ehn/
and resolve the kid from there. In this case, verifying vendors don't need to build a key management server (less tracing of users) and master lists are not even needed.
A fourth option is to add the full URL directly in the kid as: dgc.a-sit.at/ehn/WTOXYrYS47o
. In this case, verifying vendors don't need to worry about master lists at all or building their own key-updating servers, and will simply call the address if the app doesn't have a cached version of that public key.
We can also have a forwarder service available in a shorter domain: dgc.go/AT/WTOXYrYS47o
-> dgc.a-sit.at/ehn/WTOXYrYS47o
I would easily pay 10 bytes to avoid all the added complications, lack of privacy and potential bugs of the first 3 options.
Of course, the best options are DID:DGC:AT:WTOXYrYS47o
which uses a universal DID resolver or a WTOXYrYS47o.dgc.a-sit.at
, which uses a DNS Lookup. But there might be way too much convincing to get these 2 options to be considered.
@vitorpamplona not clear where you are aiming at ... leaving policy aside and sticking to technical/privacy/sec arguments:
since the central EU GW is not publicly available (also due to policy reasons) the member states will compile such trust-lists containing all DSCs in some or another way
@vitorpamplona please also remember that any online lookup of keys, without additional security requirement like DNSSEC, effectively moves trust from the official trusted list to DNS (via WebPKI). Although applications like TLSA or CERT RR (application keys in DNS with DNSSEC) could be used to remove the WebPKI intermediate step, it would still be a non-official key distribution mechanism.
The discussion here is just to make the kid automatically resolvable if verifiers want to resolve. It doesn't affect policies, or any other security concern. It's on how to create the ID for the key, not on if I can trust the link in that ID.
Every verifier has risk management policies of its own. It's up to them to decide what to do with the ID.
My point is that a well specified, more resolvable, unique ID is better than an ambiguous ID that is implemented today.
you do not want online calls (this can lead to privacy problems in certain cases) and goes against the offline requirements
Online calls will happen, either to my key server or directly to the country's website, in bulk or individually. But they will happen. There will be millions of keys out there. Apps won't store everything on devices. There's no other way.
I have my own mechanisms to make sure these calls are private. None of which needs to be in the spec.
problem of trust
As a verifier, I have my own trust policies. I am not saying to kill the master lists or certificate chain. I am proposing to change the ID on how to find them. Trust policy is dependent on the use case. Verifying signatures of the types of extremely low-risk certificates within the DGC context, for instance, can use many existing solutions out there. Outside the DGC context, there are way more threatening payloads that I have to worry about extreme security. The DGC is not one of them.
no individual certificate will be downloaded
My apps will, no matter what you do. My question to you is, do you wish my apps to download them from a private provider like myself (or a partnering company), or do you wish me to download directly from the original source?
without additional security requirement like DNSSEC, effectively moves trust from the official trusted list to DNS (via WebPKI)
Correct. But the manual download of the master list as of today MUST ALSO trust the DNS. There's nothing you can do to avoid the trust on the DNS resolver. If the Master list is transferred online, in any way, you are trusting the underlying architecture in the same way you would have to trust the DNS to download an individual key directly to the verifying app.
Correct. But the manual download of the master list as of today MUST ALSO trust the DNS. There's nothing you can do to avoid the trust on the DNS resolver. If the Master list is transferred online, in any way, you are trusting the underlying architecture in the same way you would have to trust the DNS to download an individual key directly to the verifying app.
This is why I claim that the trusted list should be signed out of band, e.g with JWS, or by validating each DSC against its CSCA (that is fetched out of band).
This is why I claim that the trusted list should be signed out of band, e.g with JWS, or by validating each DSC against its CSCA (that is fetched out of band).
You are still trusting the DNS to get the original CSCA.
You are still trusting the DNS to get the original CSCA.
No, they can be exchanged out of band are are long-term stable. The key (pun intended) is to validate the list of DSC with something that is not fetch from the DNS.
You are still trusting the DNS to get the original CSCA.
No, they can be exchanged out of band are are long-term stable. The key (pun intended) is to validate the list of DSC with something that is not fetch from the DNS.
If it follows what happened with the Passport Master List (black market on day 2), they will never be exchanged out of band. There are a lot of lessons from that model.
But the discussion here is not on what to use to download the certificates. It's on how to define the key to find those certificates. My proposal is to make it so that, if verifiers want, they could download it directly.
We can call the individual download of keys a semi-secure verification that happens only temporarily between updates on the master lists. The app checks with the official master list first and if it's not there, it tries to find the key while reporting to the user that the verification was not double-checked by the master list. In this way, the app doesn't block service just because the cached list is outdated.
If verifiers don't want to download it directly via the web (their decision), they already have the option of going out of band, or explore other ways. That hasn't changed.
You can do dgc.go/AT/WTOXYrYS47o
and still download the entire key list offline if you think security is a concern.
The spec doesn't clearly state what the kid refers to. In the Kotlin implementation it seems to refer to the shortened SHA-256 digest of the DSC.
Also, instead of referring to a DSC digest, you can increase the flexibility by referring to a public key digest (i.e. the key within the DSC). This allows for extending the validity of an existing DSC without invalidating existing HCERTs. This can reduce the impact of mistakes and increase security with shorter lived DSCs. You'd simply reuse the existing key pair to create a new DSC. If you want to explicitly invalidate existing HCERTs you can still generate a new key pair.