Skip to content
This repository has been archived by the owner on Jul 11, 2023. It is now read-only.

Possible leak of private keys #103

Closed
emanuelelaface opened this issue Oct 26, 2021 · 102 comments
Closed

Possible leak of private keys #103

emanuelelaface opened this issue Oct 26, 2021 · 102 comments

Comments

@emanuelelaface
Copy link

emanuelelaface commented Oct 26, 2021

On various groups (Telegram mainly) are circulating several forged Green Pass with valid signature, I attach two here.

<< image redacted - available from the security team >>

<< image redacted - available from the security team >>

I verified with my application and found that these two certificates are signed with the keys corresponding to these two public keys:

kid: 53FOjX/4aJs=
key: <EllipticCurvePublicNumbers(curve=secp256r1, x=59224424711316661084877973301841821584140021680113528472675651838972371380627, y=54841068689176540860306147861276004028606373898471432794562118907413910993957>

kid: HhkeqvrtQ0U=
key: <EllipticCurvePublicNumbers(curve=secp256r1, x=58474994431552591028397454866551462597403245555156280299836700796569353760692, y=94160389532428263599765213184431546448511972729790083405292977908540518398962>

There is the possibility that a database of private keys is compromised and this may ends up in a break of the chain of trust in the Green Pass architecture. I am not sure to who this should be reported, so I write this here.

@emanuelelaface emanuelelaface changed the title Leak of keys Possible leak of private keys Oct 26, 2021
@dirkx
Copy link
Member

dirkx commented Oct 27, 2021

Image in above have been redacted - available from the security team at the EU; or contact the team of the Kingdom of The Netherlands via security at rdobeheer dot nl.

@emanuelelaface
Copy link
Author

Why the Netherlands? The two passes here are signed one with a French key and the other with a Polish key. Is Netherland responsible for the emission of these keys?

@dirkx
Copy link
Member

dirkx commented Oct 27, 2021

We are passing these on to Poland and France. Unfortunately here in NL we only have the addresses of the project lead / lead architects at those countries; and not their generic security reporting address.

And I did not want to delete these keys without leaving at least one option for those that need them to get them again; nor did I want to disclose their names here either without getting their OK.

@emanuelelaface
Copy link
Author

emanuelelaface commented Oct 27, 2021

Sure, it is perfectly understandable. What worries me is that is unclear how the keys were obtained, and I think that a leak in two different countries is very unlucky event. On some forum there is a script for brute forcing the keys from the public. I don't know if that was the method used but in that case it means that also the other keys are in danger.

I attach here the brute force code that I found, to allow you to inspect it. If you don't want to have it public feel free to remove it, but consider that it is already on several social networks/


from __future__ import annotations
from base64 import b64decode
from optparse import OptionParser
from cose.messages import Sign1Message, CoseMessage
from cose.headers import Algorithm, KID
from cose.algorithms import Es256
from cose.keys import EC2Key
from cryptography.hazmat.backends.openssl.ec import _EllipticCurvePublicKey
from cryptography.hazmat.primitives.serialization import load_der_public_key
from concurrent.futures import (ThreadPoolExecutor, FIRST_COMPLETED, as_completed as completed_futures, wait as wait_futures)

DEFAULT_KID = "53FOjX/4aJs="      #kid FR
DEFAULT_PUBK = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgu/WJBn1Q+RCOfQx3NLT5oIGUCHsqSRXuu7EZsqfqZN5PvHk6/E++88wvj2fMrfmAptk5tVld2xBH4P4tRs8JQ=="

class EncodedPoint:
    def __init__(self, xy: tuple) -> None:
        self.x, self.y = xy[0], xy[1]

    @staticmethod
    def from_public_key(pubkey: _EllipticCurvePublicKey) -> EncodedPoint:
        return EncodedPoint((
            pubkey.public_numbers().x.to_bytes(32, 'big'),
            pubkey.public_numbers().y.to_bytes(32, 'big')
        ))

class Recovery:
    def __init__(self, b64_kid: str,
                      ec_point: EncodedPoint,
                      verbose: bool = False) -> None:
        self.pubk_ec_point = ec_point
        self.pubk_id = b64decode(b64_kid)
        self.print_k = lambda k: print(k.d.hex()) if verbose else lambda _: None

    def run(self) -> bytes:
        while 1:
            private_k = self.generate_key()
            self.print_k(private_k)

            k_is_valid = self.verify_signature(
                ec_point=self.pubk_ec_point,
                encoded_message=self.sign_message(self.pubk_id, private_k, 'This is a test message')
            )
            if k_is_valid:
                return private_k.d

    @staticmethod
    def generate_key() -> EC2Key:
        return EC2Key.generate_key(crv='P_256', optional_params={'ALG': 'ES256'})

    @staticmethod
    def sign_message(kid: str, private_key: EC2Key, message: str) -> bytes:
        msg = Sign1Message(
            phdr={KID: kid, Algorithm: Es256},
            payload=message.encode('utf-8')
        )
        msg.key = private_key
        return msg.encode()

    @staticmethod
    def verify_signature(ec_point: EncodedPoint, encoded_message: bytes) -> bool:
        msg = CoseMessage.decode(encoded_message)
        msg.key = EC2Key(
            crv='P_256',
            x=ec_point.x,
            y=ec_point.y,
            optional_params={'ALG': 'ES256'}
        )
        return msg.verify_signature()

    @staticmethod
    def log_once(private_key: bytes) -> None:
        with open('ec_key.txt', 'w') as out:
            out.write(private_key.hex())

def main():
    parser = OptionParser()
    parser.add_option('-t', '--tasks', dest='TASKS', type='int', default=1,
                            help='number of tasks in parallel (default: 1)')
    parser.add_option('-v', '--verbose', dest='VERBOSE', action='store_true',
                            default=False, help='print generated keys')
    parser.add_option('-k', '--kid', dest='KID', default=DEFAULT_KID,
                            help='key ID in BASE64 format')
    parser.add_option('-p', '--public-key', dest='PUBK', default=DEFAULT_PUBK,
                            help='public key in DER format')
    (options, _) = parser.parse_args()

    recovery = Recovery(options.KID,
        EncodedPoint.from_public_key( load_der_public_key(b64decode(options.PUBK)) ),
        verbose=options.VERBOSE
    )
    with ThreadPoolExecutor(max_workers=options.TASKS) as executor:
        results = [executor.submit(recovery.run) for _ in range(options.TASKS)]
        wait_futures(results, return_when=FIRST_COMPLETED)
        for future in completed_futures(results):
            private_key = future.result()
            if private_key:
                recovery.log_once(private_key)
            else:
                executor.shutdown(wait=False, cancel_futures=True)

if __name__ == '__main__':
    main()```

@dirkx
Copy link
Member

dirkx commented Oct 27, 2021

Thanks - that is good information - I'll try to ge the EHN technical team to investigate (as they do not monitor this space closely/it at all).

Of course - brute forcing is always an option -- and that is what this scripts seems to do in a very simple, un-systematic way. Which ought to take forever. But the existence of this may suggest it is too easy; which implies a weak key or similar issue - and that needs to be checked.

And will also try to get the security contact on this repository updated - to provide a secure channel. If there are things you want to share that are (more) sensitive -- feel free to use https://english.ncsc.nl/report-vulnerability for now.

@emanuelelaface
Copy link
Author

Perfect, thanks, I will contact you if I have other information.

@ThiefMaster
Copy link

Out of curiosity, why was what I guess is the Hitler certificate removed from the original post? It's already circulating, and won't go away. It should ideally be revoked ASAP (but AFAIU revoking individual certs isn't supported everywhere?), but hiding it won't look good - newspapers will report about this anyway.

@daniel-eder
Copy link

Out of curiosity, why was what I guess is the Hitler certificate removed from the original post? It's already circulating, and won't go away. It should ideally be revoked ASAP (but AFAIU revoking individual certs isn't supported everywhere?), but hiding it won't look good - newspapers will report about this anyway.

While I cannot speak to the actions of my colleagues in the e-Health-Network, removing these fake certificates in other project-related places is not a matter of hiding this from the press (that is why all other information that cannot be actively abused remains), but a best-practices policy to aim to reduce the spread. Of course, there is no way to prevent this from happening on social media, but nobody wants their support channel to become a spreading factor on top of that.

@emanuelelaface
Copy link
Author

I fully agree with this policy and I apologise for posting them here in the first place but I had no idea how to report this and I think it was important to have an "official" place to discuss this issue.

@Flaburgan
Copy link

You have all the information to contact the French security agency, including PGP key, here: https://www.cert.ssi.gouv.fr/contact/

@dirkx
Copy link
Member

dirkx commented Oct 27, 2021 via email

@Valeri0p
Copy link

Valeri0p commented Oct 27, 2021

There's another forged QR code from the same forum that seems to have been issued by the German health authority, with some text in Finnish in the name area; is there a simple way for me to extract the correlated public key without publishing the QR here?
Thanks.

@HenkPoley
Copy link

@Valeri0p The 'DCC Scanner App' by the government of The Netherlands gives advanced info under the results.

Such as:

  • Publisher: Centrum e-Zdrowia
  • Cert nr.: URN:UVCI:01:PL:1/AF2AA5873FAE45DFA826B8A01237BDC4

@ryanbnl
Copy link
Contributor

ryanbnl commented Oct 27, 2021

Valeri0p, you can use the scripts here to verify the key: https://github.com/ehn-dcc-development/ehn-sign-verify-python-trivial
I also have some Python scripts that I use for diagnostics, I can share them with you. Give me a few minutes and I'll push them to GitHub.

@fejiso
Copy link

fejiso commented Oct 27, 2021

I attach here the brute force code that I found, to allow you to inspect it. If you don't want to have it public feel free to remove it, but consider that it is already on several social networks/

While it seems to be correct in what it does, the fact that they use ThreadPool in Python expecting to gain any performance is pretty funny.

@ryanbnl
Copy link
Contributor

ryanbnl commented Oct 27, 2021

Here are my diagnostic scripts, they're a bit hacky but yet :) Mail me if you need a hand (see my profile) https://github.com/ryanbnl/eu-dcc-diagnostics

@aneutron
Copy link

While it seems to be correct in what it does, the fact that they use ThreadPool in Python expecting to gain any performance is pretty funny.

Well, not if they're using the nogil patches ! (sorry for the irrelevant comment)

@owlstead
Copy link

Beware that ECDSA signatures can leak the private key if the ECDSA signature is created using a bad RNG. See the Sony hack and the Chaos computer club.

@intUnderflow
Copy link

intUnderflow commented Oct 27, 2021

Code for the mis-issued german certificate:

QR Code:

HC1:..

Output from verification attempt:

ehn-sign-verify-python-trivial % cat key.txt | python3 ./hc1_verify.py -v -U -p -A
KID in the unprotected header.
Signature           : ... = @ ES256
Correct signature againt known key (kid=XkVWZqUeeFc=)
Issuer              : DE
Experation time     : ...
Issued At           : ...
Health payload      : {
    "dob": "...",
    "nam": {
        "fn": "...",
        "fnt": "...",
        "gn": "...",
        "gnt": "..."
    },
    "v": [
        {
            "ci": "URN:UVCI:...",
            "co": "DE",
            "dn": 1,
            "dt": "2021-09-22",
            "is": "Robert Koch-Institut",
            "ma": "ORG-100001417",
            "mp": "EU/1/20/1525",
            "sd": 1,
            "tg": "840539006",
            "vp": "1119305005"
        }
    ],
    "ver": "1.3.0"
}

@ryanbnl
Copy link
Contributor

ryanbnl commented Oct 27, 2021

@intUnderflow I've hid your post for now, I need to confirm that it does not contain any personal information :)

@Ein-Tim

This comment has been minimized.

@RichiH
Copy link

RichiH commented Oct 27, 2021

@ryanbnl the comment is still visible if you click the "show comment" button to the right. From what I can see, @intUnderflow 's paste contains fake personal data.

@ryanbnl
Copy link
Contributor

ryanbnl commented Oct 27, 2021

Thanks, I've removed anything that can be PII.

@Ein-Tim

This comment has been minimized.

@ryanbnl
Copy link
Contributor

ryanbnl commented Oct 27, 2021

I would also re-iterate: the news reports that a DSC - the SIGNING key - has been leaked do not appear to in any way reflect reality.
I believe that they are the result of certain journalists not understanding the difference between a DCC and a DSC.

@ryanbnl
Copy link
Contributor

ryanbnl commented Oct 27, 2021

Thanks @Ein-Tim & @RichiH :-) Doing this whilst changing a diaper, I appreciate the extra set of eyes :)

@RichiH
Copy link

RichiH commented Oct 27, 2021

@ryanbnl : From what I could find in this issue and in other places, it seems that only a few wrong vaccination passports are in circulation, not an actual signing key? @emanuelelaface does this match what you saw?

I just had someone creating vaccination certificates explain the process to me again, and there is very little security built into the system of creating vaccination certificates on site. As such, isn't the more likely scenario the creation of rogue vaccination certificates, not leaking/brute forcing of the signing keys?

@pdehaye
Copy link

pdehaye commented Oct 27, 2021

You might want to remove the key ids as well as the originals are a Google search away...

@Xiloe
Copy link

Xiloe commented Oct 28, 2021

So everything is about a issuer server with default configuration? No private(s) key leak?

Quite disappointing 😆

No key leaks, nothing confirmed yet at least.

@PLTorrent
Copy link

PLTorrent commented Oct 28, 2021

I've reported it, but I would like to kindly request that people follow the advice in SECURITY.md:
https://github.com/eu-digital-green-certificates/dgca-issuance-web/blob/main/SECURITY.md
EDIT: thanks, looks like some of you have sent the mail to the official security address. The team responsible are in the loop.
:-)

It's only an issue if someone reuses this in their implementation and does not change or delete the credentials used in repo so TBH I do not think this was the way.
I guess @Xiloe could comment if Macedonian site used those credentials or was not secured at all...

None of all the sites I found are secured by anything, there is just one, ONE server hosted in germany that is auth protected by "pin.health"

We definitely need a facepalm reaction on github...

@manfred-kaiser
Copy link

The password is a salted MD5 hash in APR1 format:

"$apr1$" + the result of an Apache-specific algorithm using an iterated (1,000 times) MD5 digest 
of various combinations of a random 32-bit salt and the password. See the APR 
source file apr_md5.c for the details of the algorithm.

The pull request was opened on 21 may. The plain text value of the password was not commited. The pull request has only one commit.

It should only possible to bruteforce the password, if a simple password was used. 🤔

@PLTorrent
Copy link

PLTorrent commented Oct 28, 2021

The password is a salted MD5 hash in APR1 format:

"$apr1$" + the result of an Apache-specific algorithm using an iterated (1,000 times) MD5 digest 
of various combinations of a random 32-bit salt and the password. See the APR 
source file apr_md5.c for the details of the algorithm.

The pull request was opened on 21 may. The plain text value of the password was not commited. The pull request has only one commit.

It should only possible to bruteforce the password, if a simple password was used. 🤔

As @Xiloe stated:

None of all the sites I found are secured by anything, there is just one, ONE server hosted in germany that is auth protected by "pin.health"

So no need to bother with those creds... @manfred-kaiser simply not needed 🤦

@manfred-kaiser
Copy link

So no need to bother with those creds...

That's a really bad 😞

@ryanbnl
Copy link
Contributor

ryanbnl commented Oct 28, 2021

@PLTorrent would you mind sharing the endpoints and details? Via the security.md instructions here, or if that for some reason is not possible, then share with my via Telegram (@bunchofcoconutz) so that I can share with my colleges in the EU.

@manfred-kaiser
Copy link

@PLTorrent would you mind sharing the endpoints and details?

I think it's better not to share details about the attack vector on a public channel, when most systems are not protected.

Those information should be shared in a private group or sent to the certs.

@owlstead
Copy link

I would strongly suggest that an upgrade to bcrypt as password hash as well, although the default password is of course a bigger issue. 1000 rounds of MD5 and a 48 bit random salt is not considered very secure; it won't add much protection to weaker passwords.

@ryanbnl
Copy link
Contributor

ryanbnl commented Oct 28, 2021

@PLTorrent would you mind sharing the endpoints and details?

I think it's better not to share details about the attack vector on a public channel, when most systems are not protected.

Those information should be shared in a private group or sent to the certs.

Exactly, via the contact address in security.md or to me via Telegram (so with e2e encryption.

Thanks for the clarification: please share responsibly - so via a secure channel, with people who you can verify/identify.

@ThiefMaster
Copy link

If this public hash was really the source of the bogus certificates I hope the responsible people make sure this gets framed as "human mistake, can happen, we'll make sure that this does not happen again" (ideally with some automatisms that try to detect credentials in PRs or git commits in general) and not "this is why such things should not be done in the open" - I'm sure some newspapers would love to frame it as the latter. "Open Source destroys trust in COVID certs" or crap like that.

@russss
Copy link

russss commented Oct 28, 2021

Thanks for the clarification: please share responsibly - so via a secure channel, with people who you can verify/identify.

In case nobody else has got there already, I've just sent some details over via the ncsc.nl form.

@PLTorrent
Copy link

PLTorrent commented Oct 28, 2021

@PLTorrent would you mind sharing the endpoints and details? Via the security.md instructions here, or if that for some reason is not possible, then share with my via Telegram (@bunchofcoconutz) so that I can share with my colleges in the EU.

@ryanbnl
You should ask @Xiloe

I have simply found the .htpasswd in the repo, nothing more.

If this public hash was really the source of the bogus certificates I hope the responsible people make sure this gets framed as "human mistake, can happen, we'll make sure that this does not happen again" (ideally with some automatisms that try to detect credentials in PRs or git commits in general) and not "this is why such things should not be done in the open" - I'm sure some newspapers would love to frame it as the latter. "Open Source destroys trust in COVID certs" or crap like that.

@ThiefMaster It was not. @Xiloe already confirmed that the endpoints he found were not protected in any way bar one in DE that had "pin.health" authorization

@czz
Copy link

czz commented Oct 28, 2021

I think it's better to share details about the attack vector, so everyone can check, this is how opensource works

@ryanbnl
Copy link
Contributor

ryanbnl commented Oct 28, 2021

That is not how responsible disclosure works.

@manfred-kaiser
Copy link

I think it's better to share details about the attack vector, so everyone can check, this is how opensource works

As long as the systems are unprotected, this can result in more fake certificates.

Perhaps it's possible, that each country can provide some contacts (e.g national certs).

It's common for open source software not providing public information before vulnerabilities are closed.

@ryanbnl
Copy link
Contributor

ryanbnl commented Oct 28, 2021

Thanks for the clarification: please share responsibly - so via a secure channel, with people who you can verify/identify.

In case nobody else has got there already, I've just sent some details over via the ncsc.nl form.

Can you send it directly to our security team?

security@rdobeheer.nl

There is a process @ NCSC.nl that will cause a delay.

@manfred-kaiser
Copy link

Please also send information to austrian certs:

@dirkx
Copy link
Member

dirkx commented Oct 28, 2021

I think it's better to share details about the attack vector, so everyone can check, this is how opensource works

Most mature open source organisation use something called Responsible Disclousre - where details of attack vectors are kept confidential while the issue is fixed. And generally made public when enough of the estate is protected. A good example is at https://www.apache.org/security/

The EHN network (and most EU countries) follow substantially the same appraoch.

@czz
Copy link

czz commented Oct 28, 2021

Yes but someone can post it anyway and your risk is to loose credibility

@J08nY
Copy link

J08nY commented Oct 28, 2021

It seems pretty clear now, given the public information and (https://twitter.com/Xiloeee/status/1453493664806735876), that some unsecured dgca-issuance-web instances are running somewhere and are the source of the fake DGCs. I think this should be enough info for the eHN to urge members to go and secure all of the issuance-web instances (the members should know where they run these instances).

@Xiloe
Copy link

Xiloe commented Oct 28, 2021

@PLTorrent would you mind sharing the endpoints and details? Via the security.md instructions here, or if that for some reason is not possible, then share with my via Telegram (@bunchofcoconutz) so that I can share with my colleges in the EU.

https://github.com/eu-digital-green-certificates/dgca-issuance-web

here :)

@Ein-Tim
Copy link

Ein-Tim commented Oct 28, 2021

Could we please stay focused here and refrain from insults?

Also, it won't help anyone bringing politics into this discussion and only makes it more likely that this issue gets locked.

I really hope this issue does not get locked, as this issue seems like a very good place where people with knowledge share their investigations.

So please, come down everyone and stay focused!

Thank you!

@no-identd
Copy link

@BLeQuerrec I know it's not quite relevant anymore since—as @Xiloe pointed out—the page is down now, however, 2 seconds of Google yields @nicolasjms @mamillmsft @snf @JosephBialekMsft @axsdnied @saaramarmsft as MSRC contacts easily taggable on github

@Xiloe
Copy link

Xiloe commented Oct 28, 2021

it now redirects to https://vakcinacija.mk/

@PLTorrent
Copy link

it now redirects to https://vakcinacija.mk/

@Xiloe how about the other ones?

@no-identd
Copy link

+their contact entry point for vulnerabilities is https://msrc.microsoft.com/create-report, but for issues like this one, one should instead check https://cert.microsoft.com/, which currently redirects to https://msrc.microsoft.com/report/abuse, and to where I've just submitted a report pointing to this thread here (don't worry, I didn't put the URL to the thread into the source URL field, I put example.invalid in there & put the URL in the comments, lest someone briefly thinks the issue this generates in their system relates to Github itself, which'd be bad, given that they own & run it.) but I'd recommend you submit the URL you found to there anyway @BLeQuerrec @Xiloe or whoever has it, despite the fact that it's already down by now I think they'll need all the help which they can get.

Also, good morning y'all.

@jschlyter
Copy link
Contributor

This is not an issue with the technical specification, I will move this to discussions.

@ehn-dcc-development ehn-dcc-development locked and limited conversation to collaborators Oct 28, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests