Skip to content
Open
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
92 changes: 70 additions & 22 deletions git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,12 @@ limitations under the License.
package git

import (
"bytes"
"errors"
"fmt"
"strings"
"time"

"github.com/ProtonMail/go-crypto/openpgp"
"github.com/fluxcd/pkg/git/signatures"
)

const (
Expand Down Expand Up @@ -113,19 +112,37 @@ func (c *Commit) AbsoluteReference() string {
return c.Hash.Digest()
}

// Deprecated: Verify is deprecated, use VerifySSH or VerifyGPG
// wrapper function to ensure backwards compatibility
func (c *Commit) Verify(keyRings ...string) (string, error) {
return c.VerifyGPG(keyRings...)
}

// Verify the Signature of the commit with the given key rings.
// It returns the fingerprint of the key the signature was verified
// with, or an error. It does not verify the signature of the referencing
// tag (if present). Users are expected to explicitly verify the referencing
// tag's signature using `c.ReferencingTag.Verify()`
func (c *Commit) Verify(keyRings ...string) (string, error) {
fingerprint, err := verifySignature(c.Signature, c.Encoded, keyRings...)
func (c *Commit) VerifyGPG(keyRings ...string) (string, error) {
fingerprint, err := signatures.VerifyPGPSignature(c.Signature, c.Encoded, keyRings...)
if err != nil {
return "", fmt.Errorf("unable to verify Git commit: %w", err)
}
return fingerprint, nil
}

// VerifySSH verifies the SSH signature of the commit with the given authorized keys.
// It returns the fingerprint of the key the signature was verified with, or an error.
// It does not verify the signature of the referencing tag (if present). Users are
// expected to explicitly verify the referencing tag's signature using `c.ReferencingTag.VerifySSH()`
func (c *Commit) VerifySSH(authorizedKeys ...string) (string, error) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think having a new func per type is sub-optimal as that violates OCP. From an user perspective that feels a bit awkward, as you need to iterate through commits, check each type and then make the verification. I'd rather that is abstracted away.

That being said, we can keep this as is and revisit it during the implementation upstream in go-git.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @pjbgf,

I could rework this but didn't want to break any current implementation in the source-controller. Not actually sure which other component uses this part of the pkg.

I would also like to see most of the functionality implemented in go-git and only abstract here where needed.

Maybe we can continue with the discussions over there.

fingerprint, err := signatures.VerifySSHSignature(c.Signature, c.Encoded, authorizedKeys...)
if err != nil {
return "", fmt.Errorf("unable to verify Git commit SSH signature: %w", err)
}
return fingerprint, nil
}

// ShortMessage returns the first 50 characters of a commit subject.
func (c *Commit) ShortMessage() string {
subject := strings.Split(c.Message, "\n")[0]
Expand All @@ -152,17 +169,33 @@ type Tag struct {
Message string
}

// Deprecated: Verify is deprecated, use VerifySSH or VerifyGPG
// wrapper function to ensure backwards compatibility
func (t *Tag) Verify(keyRings ...string) (string, error) {
return t.VerifyGPG(keyRings...)
}

// Verify the Signature of the tag with the given key rings.
// It returns the fingerprint of the key the signature was verified
// with, or an error.
func (t *Tag) Verify(keyRings ...string) (string, error) {
fingerprint, err := verifySignature(t.Signature, t.Encoded, keyRings...)
func (t *Tag) VerifyGPG(keyRings ...string) (string, error) {
fingerprint, err := signatures.VerifyPGPSignature(t.Signature, t.Encoded, keyRings...)
if err != nil {
return "", fmt.Errorf("unable to verify Git tag: %w", err)
}
return fingerprint, nil
}

// VerifySSH verifies the SSH signature of the tag with the given authorized keys.
// It returns the fingerprint of the key the signature was verified with, or an error.
func (t *Tag) VerifySSH(authorizedKeys ...string) (string, error) {
fingerprint, err := signatures.VerifySSHSignature(t.Signature, t.Encoded, authorizedKeys...)
if err != nil {
return "", fmt.Errorf("unable to verify Git tag SSH signature: %w", err)
}
return fingerprint, nil
}

// String returns a short string representation of the tag in the format
// of <name@hash>, for eg: "1.0.0@a0c14dc8580a23f79bc654faa79c4f62b46c2c22"
// If the tag is lightweight, it won't have a hash, so it'll simply return
Expand Down Expand Up @@ -210,21 +243,36 @@ func IsSignedTag(t Tag) bool {
return t.Signature != ""
}

func verifySignature(sig string, payload []byte, keyRings ...string) (string, error) {
if sig == "" {
return "", fmt.Errorf("unable to verify payload as the provided signature is empty")
}
// IsPGPSigned returns true if the commit has a PGP signature.
func (c *Commit) IsPGPSigned() bool {
return signatures.IsPGPSignature(c.Signature)
}

for _, r := range keyRings {
reader := strings.NewReader(r)
keyring, err := openpgp.ReadArmoredKeyRing(reader)
if err != nil {
return "", fmt.Errorf("unable to read armored key ring: %w", err)
}
signer, err := openpgp.CheckArmoredDetachedSignature(keyring, bytes.NewBuffer(payload), bytes.NewBufferString(sig), nil)
if err == nil {
return signer.PrimaryKey.KeyIdString(), nil
}
}
return "", fmt.Errorf("unable to verify payload with any of the given key rings")
// IsSSHSigned returns true if the commit has an SSH signature.
func (c *Commit) IsSSHSigned() bool {
return signatures.IsSSHSignature(c.Signature)
}

// SignatureType returns the type of the commit signature as a string.
// It returns "pgp" for PGP signatures, "ssh" for SSH signatures,
// and "unknown" for unrecognized or empty signatures.
func (c *Commit) SignatureType() string {
return signatures.GetSignatureType(c.Signature)
}

// IsPGPSigned returns true if the tag has a PGP signature.
func (t *Tag) IsPGPSigned() bool {
return signatures.IsPGPSignature(t.Signature)
}

// IsSSHSigned returns true if the tag has an SSH signature.
func (t *Tag) IsSSHSigned() bool {
return signatures.IsSSHSignature(t.Signature)
}

// SignatureType returns the type of the tag signature as a string.
// It returns "pgp" for PGP signatures, "ssh" for SSH signatures,
// and "unknown" for unrecognized or empty signatures.
func (t *Tag) SignatureType() string {
return signatures.GetSignatureType(t.Signature)
}
Loading