Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Git files hidden in plain sight (tylercipriani.com)
163 points by thcipriani on Aug 1, 2023 | hide | past | favorite | 47 comments


I know this isn’t about the signing really, but it is my understanding that you sign things with your private key and people can verify that it was you who signed it with the matching public key.

Does this work differently with git?


Nope, it’s the same. I believe the blog post is trying to say that including the key in the git object storage is effectively the same thing as self-signing, since there’s no independent key discovery or trust mechanism. In other words: it’s not because it’s a private key, but because putting a public key in the same channel that it’s meant to verify is essentially “useless” from a verification perspective.


This is not a security issue. The key is just hard to update when it expires. Private is private, public is public, even in git.


It’s a security issue in the sense that it defeats the signing scheme: rather than negotiating the key out-of-band, the user is trusting the thing they’re trying to verify to supply the key. In other words “trust me because I’m signed, and also trust me to give you the key I’m signed with.”

On the other hand, approximately nobody actually verifies git commit signatures, so it doesn’t matter all that much :-)


I'm not really advocating for it, but GitHub does verify out of band - it uses keys on the profile. (If it says 'unverified', it doesn't match any that are present. If it says neither unverified nor verified, the commit wasn't signed.)


I do. Deploys can only be rolled out if the tag was signed by a well known list of people (git pull --verify-signatures).


The name `junio-gpg-key` is a lightweight tag in the git git repo. It's not that hard to update, though it requires both force push and force pull (to an existing clone, new ones get the latest value no matter what).

It is something of a security risk because there is no audit log for force pushes and anyone with force push access to that repo could update that tag at any time.

Also, because it is a lightweight tag, it is not itself signed so could potentially be man-in-the-middled.

That said, yes it is a public key and public keys are meant to be public. In many threat models this isn't a "huge deal". The issue, as often the case, with public keys is that you still need a chain of trust somehow defining if you should trust that public key. A public key that you get from the repo itself that anyone could force push an update to is possibly not one you should trust without additional verification channels involved.


Why does it need to be public? By revealing your public key you also reveal the algo used for your public/private keypair. If you are a renowned developer and have an insecure algo, you might be a victim for targeted attacks


If your goal is to have others verify your signatures, then they need to have your public key.

The solution to using weak algorithms is to not use them, not to hide your public key. If you don’t reveal your public key, then there’s effectively no value in the signatures made by its private half.


Fun fact, just as SSH public keys for a user are readily publicly available on GitHub, gpg keys are as well

    curl https://github.com/${username}.keys
    curl https://github.com/${username}.gpg | gpg --list-packets
Interestingly enough, since changing the GH keys is subject to GH authentication (possibly involving 2FA), the owner's key is kind of validated (by GH) so it _kind of_ makes GH a gpg key notary.

Having `git log --show-signatures` validate _all_ signatures is then a matter of:

    curl https://github.com/${username}.gpg | gpg --import
Now I kind of wish GH would sign such user pub keys _at the time they're added_ (thus at the time GH authentication is deemed valid), thus ensuring they've not been tampered with in between.

With a bit of UI GH could present a UI for a user to validate another user's key, and sign that assertion (with a GH private key, not the user's of course) as well.

Of course it hinges on trusting GH, but GH already signs e.g UI borne merge commits on your behalf (via https://github.com/web-flow.gpg) so there's that.

Then next step would be for GH to host an actual key server to accept off-band signed keys containing a magic comment that bind the keys and sigs to GH usernames...


Isn't the best way to verify the integrity to use git signed commits? GitHub has great support for this


You’re mixing up integrity and authenticity: git provides integrity through its basic design; commit signing additionally adds authenticity (sort of).

But again: you don’t actually get authenticity if you obtain the public key from the same untrusted channel as the signed material. The key has to come from another channel (another simplification, but true in PGP and git’s case). That channel can be the user’s GitHub profile, website, etc., but it can’t be the repository itself.


It doesn't necessarily need to be public in the global sense, but it is public in the sense of "you can't rely on keeping it secret as a security boundary". Since its used to validate the signature, it will usually need to be (at least) as public as the signed thing.


Public key cryptography is pretty pointless if you don’t take advantage of the public part. One, you can’t send me any encrypted messages without my public key. Two, you can’t verify my signatures without my public key.

Insecure algorithms are a red herring here too. Either I know what algo you used and can interact with you, or I don’t.


>If you are a renowned developer and have an insecure algo, ...

Example of such an insecure algorithm that might be relevant here?


Low key sizes (<= 1024) of RSA are considered vulnerable, especially to quantum computer attacks. Some ECC curves are considered vulnerable, especially to NSA backdoors.


Nope, that's how it works with Git.


`junio-gpg-pub` uses a git feature called annotated tags [1]. These are one of two types of tags that git supports. Many people are only aware of the default lightweight tags. They are not stored in git's object database, instead they're just simple refs, which you can think of as a symlink in `refs/tags` that contains nothing but a pointer to the object id of the commit you are tagging.

  $ git tag v1.2.3.4.5
  $ cat .git/refs/tags/v1.2.3.4.5
  ee48e70a829d1fa2da82f14787051ad8e7c45b71
But annotated tags are full git objects hashed and stored in the object database (one of four things that can be stored, alongside blobs, trees, and commits). An annotated tag can store a tag message and records the user that created it plus a timestamp. All the metadata in an annotated tag can be GPG-signed. It can also point to any type of object, which is the feature being "abused" here, where we have a tag (currently oid dd20f6ea5) that points to a blob (currently debb772bf) instead of a commit. Normally blobs are only used within trees which are pointed to by other trees or commits.

  $ git show-ref -d junio-gpg-pub
  dd20f6ea53bf6828baba3e2f279bf633eaae6815 refs/tags/junio-gpg-pub
  debb772bfc2bfedbfd5830dbe2c1c149dbf054e9 refs/tags/junio-gpg-pub^{}

  $ git cat-file -t junio-gpg-pub # i.e. dd20f6ea5
  tag
  $ git cat-file -t junio-gpg-pub^{} # i.e. debb772bf
  blob
Interestingly, it's perfectly legal to have chains of annotated tags (and lightweight tags) which eventually resolve to a non-reference object. This process is called unwrapping and needs to be done carefully to avoid circular or excessively long reference chains. It's a very common thing to get wrong in git implementations that handle the plumbing themselves.

You can see the tag object itself including the message using `git cat-file -p junio-gpg-pub` and the key it points to with the command in the article.

[1] https://git-scm.com/book/en/v2/Git-Basics-Tagging#_creating_...


> Many people are only aware of the default lightweight tags

I don't know if that's true anymore, if only because the ongoing popularity of the Git Flow branching model; the `git-flow` tools use annotated tags by default for every command that creates a tag (e.g. releases).


"Hiding" a file as a raw blob with a tag pointing to it, isn't bad if the thing should be able to expire.

If a thing is truly expired, then why have it fill-up the commit graph.

That said, a public key - even an expired one - may have value in keeping around: Verifying older historic releases.


By default, most things assume that tags are immutable and won't check for updates to a tag. So tags aren't great for things that can expire or otherwise need to be changed.


Huh, is that true? If you push a new version of a tag, Github will ignore it? Or if you 'git fetch', git does not update tags? I would be pretty surprised at either of those.


All tag updates require a force push and any existing clones also need a force pull.


I agree. It is not the case that "most things ... won't check for updates to a tag," which is what GP claimed and I responded to. Most things do check for updates! Without checking, they could not warn you about / reject updates.


The trick to remember though is that new clones don't have any "update" check, they only see and fetch the latest value. There's no force push audit log for git refs in most git server software, there's just "local and remote refs disagree". That's not really an "update check" from the perspective of "having an update notification or update event".


It's the second one


I don't think so. In both cases, there was visible UI of either the new tag or that the tag was changed.

Github shows the updated tag.

git pull --tags shows:

  ! [rejected]        <tag>   -> <tag>  (would clobber existing tag)
git pull --tags --force updates the tag.


You asked about `git fetch`. If you change your question to `git pull --tags` then yes, the answer is different.


git fetch --tags presents the exact same warning/error.


That's right.

> if you 'git fetch', git does not update tags? I would be pretty surprised

This is the comment you replied to:

> By default, most things assume that tags are immutable and won't check for updates to a tag.

You can use options to change from the default. I don't know what point you are now trying to make, but this conversation is getting very laborious.


Is this a git issue or a github issue? I know git is being (mis)used, but the issues seem to be with github. Maybe a better title for those of us with no interest in github would be "Github hides git files in plain sight" ?


It's not an issue with either, they are both working "as intended". Both examples are storing data in git's object database. Git and GitHub clone the object databases as fully as they can. To keep the objects "live" in the database both examples use non-standard git refs. Git's raw object DB format was designed to be extensible so it supports non-standard refs that don't reflect (aren't reflected in) high-level git concepts (such as branches and commits). Because these examples use such non-standard refs there's not a standard UI for them in GitHub.

It's probably not a great idea to use or rely on non-standard refs in general because it may conflict with other tools or future git capabilities, even if there were a "standard UI for unknown refs".


I think there is no issue. But, yes, it seems like if there is one then it's with the github UI.


that functionality is pretty useful -- we use hidden refs (but more garden variety, pointing to commits) for all sorts of things, such as source revision for every CI run and past revisions of PRs.

Works really great -- the UI is not cluttered with thousands of rarely-used entries, but all the data is still backed up and usable witth git tools. Now if only github actions could support this...


[deleted]


The `junio-gpg-pub` key is a blob in the git repo for git, ie

  ssh://git.kernel.org/pub/scm/git/git.git
You won’t see it in any repo, just that one. This is not an easter egg in the git binary, just a quirky way to transmit a signing pubkey via a non-tree git sidechannel within the git source repo.


The concept of hidden files lurking within a repository, even after deletion, is both intriguing and concerning. The detailed walkthrough of how these hidden files can be discovered and the explanation of how they persist even after being removed from the working directory provide a clear understanding of the issue.


The first example involved a signing key that expired. What would be the point of expiring a signing key? What would that mean? In a paper context, a signature or seal is still considered valid even if the technical means to create such marks no longer exists. Even if you lose your pen or stamp, signatures made with the pen or stamp are still binding.

Implementations should probably just ignore the expiry of a key used for signing when checking a signature...


If the signature creation date is before the key expiration date, the signature is valid. If the signature creation date is after expiration, it is possible (more likely than before) that someone else discovered the private key and has signed the document.

Signing key expiration exists for the same reason certificate expiration exists: we create keys that are infeasible to discover with current methods, and create replacements as techniques advance. This encourages us to create stronger keys over time.

Had you failed to notice a key expiration from the 80s, you would assume the signature is valid - but most computing devices these days could have just generated the signature without the original key.


Why would an attacker with access to the private key specify a signature creation date after key expiry? They can specify whatever date they want.

>Had you failed to notice a key expiration from the 80s, you would assume the signature is valid ...

If the algorithm is broken then the implementation should not support verification using that algorithm no matter the expiration date. An attacker can update the key's expiration date to whatever they want if the algorithm is broken.


For long-term signatures, the designated way to handle this is using cryptographic timestamps that establish when the signature was created. That proof-of-existence time is then compared to the validity period of the certificate associated with the key. This allows successfully validating signatures even after certificate expiration.

Certificates have an expiration date because keys can be compromised, and cryptographic algorithms can become weak and be broken. The expiration date imposes a time limit for possible fraudulent use of the key.

When using certificates, it’s also the certificate that expires, not the key. A new certificate for the same key can be issued (certificate renewal).


But I want to know how they used figlet to generate ANSI colored image. Is that a custom font?


These are standard blocks available in any font. Just search for ANSI or ASCI art image converter and you will find many cli-tools and webapps allowing you to convert images to those ascii blocks.


I misread it, it’s just cating a text file. Thanks.


I think you meant ascii art instead of ansi art?


That's ANSI art, since it uses ANSI escape codes for colors. The two terms are often used interchangeably though.


Also, at this point for the most/easiest portability most ANSI art today is Unicode encoded (most often UTF-8) rather than ASCII. (The block drawing characters in "Extended ASCII" aren't very portable, don't work on every OS, and also exist in a nice Unicode block that is portable.)




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: