8000 Calculate correct SHA for signed Tags. by wlynch · Pull Request #89 · sigstore/gitsign · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Calculate correct SHA for signed Tags. #89

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 41 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,30 +60,55 @@ To learn more about these options, see

## Usage

### Signing Commits

Once configured, you can sign commits as usual with `git commit -S` (or
`git config --global commit.gpgsign true` to enable signing for all commits).

```sh
$ git commit --allow-empty --message="Signed commit"
[main cb6eee1] Signed commit
Your browser will now be opened to:
https://oauth2.sigstore.dev/auth/auth?access_type=online&client_id=sigstore&...
[main 040b9af] Signed commit
```

This will redirect you through the Sigstore Keyless flow to authenticate and
sign the commit.

Commits can then be verified using `git log`:
Commits can then be verified using `git verify-commit`:

```sh
$ git --no-pager log --show-signature -1
commit 227e796042fdd170e58b7e3b7627a1badd320224 (HEAD -> main)
searching tlog for commit: 227e796042fdd170e58b7e3b7627a1badd320224
tlog index: 2212633
smimesign: Signature made using certificate ID 0x815ada5516906a862af8f528d69d3c86e4774b4f | CN=sigstore,O=sigstore.dev
smimesign: Good signature from "" ([billy@chainguard.dev])
Author: Billy Lynch <billy@chainguard.dev>
Date: Mon May 2 16:51:44 2022 -0400

Signed commit
$ git verify-commit HEAD
tlog index: 2801760
gitsign: Signature made using certificate ID 0xf805288664f2e851dcb34e6a03b1a5232eb574ae | CN=sigstore-intermediate,O=sigstore.dev
gitsign: Good signature from [billy@chainguard.dev]
Validated Git signature: true
Validated Rekor entry: true
```

### Signing Tags

Once configured, you can sign commits as usual with `git tag -s` (or
`git config --global tag.gpgsign true` to enable signing for all tags).

```sh
$ git tag v0.0.1
Your browser will now be opened to:
https://oauth2.sigstore.dev/auth/auth?access_type=online&client_id=sigstore&...
```

This will redirect you through the Sigstore Keyless flow to authenticate and
sign the tag.

Tags can then be verified using `git verify-tag`:

```sh
$ git verify-tag v0.0.1
tlog index: 2802961
gitsign: Signature made using certificate ID 0xe56a5a962ed59f9e3730d6696137eceb8b4ee8ea | CN=sigstore-intermediate,O=sigstore.dev
gitsign: Good signature from [billy@chainguard.dev]
Validated Git signature: true
Validated Rekor entry: true
```

## Limitations
Expand Down Expand Up @@ -456,3 +481,7 @@ nPkp+Sy1EwIwdOulWop3oJV/Qo7fau0mlsy0MCm3lBgyxo2lpAaI4gFRxGE2GhpV
Notice that **the Rekor entry uses the same cert that was used to generate the
git commit signature**. This can be used to correlate the 2 messages, even
though they signed different content!

Note that for Git tags, the annotated tag object SHA is what is used (i.e. the
output of `git rev-parse <tag>`), **not** the SHA of the underlying tagged
commit.
74 changes: 62 additions & 12 deletions internal/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"bytes"
"context"
"crypto/x509"
"errors"
"fmt"

"github.com/go-git/go-git/v5/plumbing"
Expand All @@ -43,7 +44,7 @@ func Sign(ctx context.Context, rekor rekor.Writer, ident *fulcio.Identity, data
// using the same key, this is probably okay? e.g. even if you could cause a SHA1 collision,
// you would still need the underlying commit to be valid and using the same key which seems hard.

commit, err := commitHash(data, sig)
commit, err := objectHash(data, sig)
if err != nil {
return nil, nil, fmt.Errorf("error generating commit hash: %w", err)
}
Expand Down Expand Up @@ -110,7 +111,7 @@ func Verify(ctx context.Context, rekor rekor.Verifier, data, sig []byte, detache
}
claims = append(claims, NewClaim(ClaimValidatedSignature, true))

commit, err := commitHash(data, sig)
commit, err := objectHash(data, sig)
if err != nil {
return nil, err
}
Expand All @@ -128,26 +129,75 @@ func Verify(ctx context.Context, rekor rekor.Verifier, data, sig []byte, detache
}, nil
}

func commitHash(data, sig []byte) (string, error) {
type encoder interface {
Encode(o plumbing.EncodedObject) error
}

func objectHash(data, sig []byte) (string, error) {
// Precompute commit hash to store in tlog
obj := &plumbing.MemoryObject{}
_, _ = obj.Write(data)
if _, err := obj.Write(data); err != nil {
return "", err
}

var (
encoder encoder
err error
)
// We're making big assumptions here about the ordering of fields
// in Git objects. Unfortunately go-git does loose parsing of objects,
// so it will happily decode objects that don't match the unmarshal type.
// We should see if there's a better way to detect object types.
switch {
case bytes.HasPrefix(data, []byte("tree ")):
encoder, err = commit(obj, sig)
case bytes.HasPrefix(data, []byte("object ")):
encoder, err = tag(obj, sig)
default:
return "", errors.New("could not determine Git object type")
}
if err != nil {
return "", err
}

// go-git will compute a hash on decode and preserve even if we alter the
// object data. To work around this, re-encode the object into a new object
// to force a new hash to be computed.
out := &plumbing.MemoryObject{}
err = encoder.Encode(out)
return out.Hash().String(), err
}

func commit(obj *plumbing.MemoryObject, sig []byte) (*object.Commit, error) {
obj.SetType(plumbing.CommitObject)

// go-git will compute a hash on decode and preserve that. To work around this,
// decode into one object then copy everything but the commit into a separate object.
base := object. 10000 Commit{}
_ = base.Decode(obj)
c := object.Commit{
if err := base.Decode(obj); err != nil {
return nil, err
}
return &object.Commit{
Author: base.Author,
Committer: base.Committer,
PGPSignature: string(sig),
Message: base.Message,
TreeHash: base.TreeHash,
ParentHashes: base.ParentHashes,
}
out := &plumbing.MemoryObject{}
err := c.Encode(out)
}, nil
}

return out.Hash().String(), err
func tag(obj *plumbing.MemoryObject, sig []byte) (*object.Tag, error) {
obj.SetType(plumbing.TagObject)

base := object.Tag{}
if err := base.Decode(obj); err != nil {
return nil, err
}
return &object.Tag{
Tagger: base.Tagger,
Name: base.Name,
TargetType: base.TargetType,
Target: base.Target,
Message: base.Message,
PGPSignature: string(sig),
}, nil
}
123 changes: 123 additions & 0 deletions internal/git/git_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2022 The Sigstore Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package git

import "testing"

const (
// These are real commit values generated in a test repo that were manually verified.

// Rekor index: 2802961
tagBody = `object 040b9af339e69d18848b7bbe05cb27ee42bb0161
type commit
tag signed-tag2
tagger Billy Lynch <billy@chainguard.dev> 1656531453 -0400

asdf
`
tagSig = `-----BEGIN SIGNED MESSAGE-----
MIIEBQYJKoZIhvcNAQcCoIID9jCCA/ICAQExDTALBglghkgBZQMEAgEwCwYJKoZI
hvcNAQcBoIICpjCCAqIwggIooAMCAQICFGc8V7+B2VlJeFLpglonkbyb2kVeMAoG
CCqGSM49BAMDMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2ln
c3RvcmUtaW50ZXJtZWRpYXRlMB4XDTIyMDYyOTE5MzczOVoXDTIyMDYyOTE5NDcz
OVowADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABP8JBFjhGqQsQCBmZqyuSHcG
KZpDDRdpq7cl8Bhwuvu9A2bDz0gcuA/Nv18fKtikguBw6YBmEPi8S/YMYgMctVyj
ggFHMIIBQzAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHQYD
VR0OBBYEFMhi60DZPBYkwhDEuiltjyvxYYTDMB8GA1UdIwQYMBaAFN/T6c9WJBGW
+ajY6ShVosYuGGQ/MCIGA1UdEQEB/wQYMBaBFGJpbGx5QGNoYWluZ3VhcmQuZGV2
MCwGCisGAQQBg78wAQEEHmh0dHBzOi8vZ2l0aHViLmNvbS9sb2dpbi9vYXV0aDCB
iQYKKwYBBAHWeQIEAgR7BHkAdwB1AAhgkvAoUv9oRdHRayeEnEVnGKwWPcM40m3m
vCIGNm9yAAABgbD4HlAAAAQDAEYwRAIgON4g6BzdFgOIcCFk+8EXKpEw1XD0/DZ2
7gcb9Q/Jeg0CIGozxLGJS71uA2OU3JD6pGWCdnpYVsiG44/Em5w34SHmMAoGCCqG
SM49BAMDA2gAMGUCMQDjLNl6Zaj5HbfLqqUvWNgz/R1VoQ3QG88kzu3GY0PodO8K
QDcgt8bcGXzEdKkSFg4CMHIkGGLrG3bOYsjyIqZxiO6ess1jJxsFnM+GzvjwNRJk
eWF9g96u/pNN8KA5VhveljGCASUwggEhAgEBME8wNzEVMBMGA1UEChMMc2lnc3Rv
cmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUCFGc8V7+B2VlJ
eFLpglonkbyb2kVeMAsGCWCGSAFlAwQCAaBpMBgGCSqGSIb3DQEJAzELBgkqhkiG
9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTIyMDYyOTE5MzczOVowLwYJKoZIhvcNAQkE
MSIEINZzCK5apWIVIKK26tVflr6zNoFkJm8SXQC5T65qwF1BMAoGCCqGSM49BAMC
BEcwRQIgfAl7Elc0DB8UEMOXo3ZxKmN7zTrMO/tvhu1Himgc9IYCIQCxf06wWHVw
YKHxU2tY8MNGomLVk0LyA/QaHQnoo34t8A==
-----END SIGNED MESSAGE-----
`
tagSHA = "ed092bb8688d6e37185bcdb58900940703c1a292"

// Rekor index: 2801760
commitBody = `tree b333504b8cf3d9c314fed2cc242c5c38e89534a5
parent 2dc0ab59d7f0a7a62423bd181d9e2ab3adb7b56d
author Billy Lynch <billy@chainguard.dev> 1656524971 -0400
committer Billy Lynch <billy@chainguard.dev> 1656524971 -0400

foo
`
commitSig = `-----BEGIN SIGNED MESSAGE-----
MIIEBwYJKoZIhvcNAQcCoIID+DCCA/QCAQExDTALBglghkgBZQMEAgEwCwYJKoZI
hvcNAQcBoIICqDCCAqQwggIqoAMCAQICFHtMvZZL50P5bLkgDxwMf2MN4jdAMAoG
CCqGSM49BAMDMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2ln
c3RvcmUtaW50ZXJtZWRpYXRlMB4XDTIyMDYyOTE3NDkzNFoXDTIyMDYyOTE3NTkz
NFowADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNf9io+JonCZhwe/dSkSoJ/Y
eRun8C7xhPVF3FhoPnPVWdywaAEIkniA2WSHXLHt5aQN/08bV65haMZA/Luhmhaj
ggFJMIIBRTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHQYD
VR0OBBYEFGzhjCzFUI0caspJJfD4bToYxfDhMB8GA1UdIwQYMBaAFN/T6c9WJBGW
+ajY6ShVosYuGGQ/MCIGA1UdEQEB/wQYMBaBFGJpbGx5QGNoYWluZ3VhcmQuZGV2
MCwGCisGAQQBg78wAQEEHmh0dHBzOi8vZ2l0aHViLmNvbS9sb2dpbi9vYXV0aDCB
iwYKKwYBBAHWeQIEAgR9BHsAeQB3AAhgkvAoUv9oRdHRayeEnEVnGKwWPcM40m3m
vCIGNm9yAAABgbCVKBkAAAQDAEgwRgIhAJHJalxdErw5icNqfgWtyrv75XGXxAZz
F/J4b7B8ikQAAiEAj8g8ZiSIGmePmES19Y/yFeGj6Fz0NGE2Rk5uJdKyAGEwCgYI
KoZIzj0EAwMDaAAwZQIxAKpQFL9D5s1YVEmNWBoEQ1oo6gBESGhd5L1Kcdq52Ltt
KWXKKB7tpVRwC0lfof2ILgIwU1LTaKeKWb0vToMY9InoS2+hAVljbEh3oxKm/JoX
hiRx2GiDe2OyLCs76/kbH6C/MYIBJTCCASECAQEwTzA3MRUwEwYDVQQKEwxzaWdz
dG9yZS5kZXYxHjAcBgNVBAMTFXNpZ3N0b3JlLWludGVybWVkaWF0ZQIUe0y9lkvn
Q/lsuSAPHAx/Yw3iN0AwCwYJYIZIAWUDBAIBoGkwGAYJKoZIhvcNAQkDMQsGCSqG
SIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjIwNjI5MTc0OTM0WjAvBgkqhkiG9w0B
CQQxIgQgSbThfvXoc6INDxPzRtlUu0TTBjFLm4XmwuxXAzfsZmkwCgYIKoZIzj0E
AwIERzBFAiBeNZewVOFI5aa7bPUXa05HDgz5yevQ9aPclDX6U+koTAIhAMbyysil
7I/UWLzhwM+9iusn3JXy71akUTcrqi2MNPaO
-----END SIGNED MESSAGE-----
`
commitSHA = "040b9af339e69d18848b7bbe05cb27ee42bb0161"
)

func TestObjectHash(t *testing.T) {
for _, tc := range []struct {
name string
body string
sig string
sha string
}{
{
name: "tag",
body: tagBody,
sig: tagSig,
sha: tagSHA,
},
{
name: "commit",
body: commitBody,
sig: commitSig,
sha: commitSHA,
},
} {
t.Run(tc.name, func(t *testing.T) {
got, err := objectHash([]byte(tc.body), []byte(tc.sig))
if err != nil {
t.Fatal(err)
}
if got != tc.sha {
t.Errorf("want %s, got %s", tc.sha, got)
}
})
}
}
0