Skip to content
Merged
65 changes: 65 additions & 0 deletions github/git_commits.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@
package github

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

"golang.org/x/crypto/openpgp"
)

// SignatureVerification represents GPG signature verification.
Expand Down Expand Up @@ -37,6 +42,11 @@ type Commit struct {
// is only populated for requests that fetch GitHub data like
// Pulls.ListCommits, Repositories.ListCommits, etc.
CommentCount *int `json:"comment_count,omitempty"`

// SigningKey denotes a key to sign the commit with. If not nil this key will
// be used to sign the commit. The private key must be present and already
// decrypted. Ignored if Verification.Signature is defined.
SigningKey *openpgp.Entity `json:"-"`
}

func (c Commit) String() string {
Expand Down Expand Up @@ -116,6 +126,13 @@ func (s *GitService) CreateCommit(ctx context.Context, owner string, repo string
if commit.Tree != nil {
body.Tree = commit.Tree.SHA
}
if commit.SigningKey != nil {
signature, err := createSignature(commit.SigningKey, body)
if err != nil {
return nil, nil, err
}
body.Signature = &signature
}
if commit.Verification != nil {
body.Signature = commit.Verification.Signature
}
Expand All @@ -133,3 +150,51 @@ func (s *GitService) CreateCommit(ctx context.Context, owner string, repo string

return c, resp, nil
}

func createSignature(signingKey *openpgp.Entity, commit *createCommit) (string, error) {
if signingKey == nil || commit == nil {
return "", errors.New("createSignature: invalid parameters")
}

message, err := createSignatureMessage(commit)
if err != nil {
return "", err
}

writer := new(bytes.Buffer)
reader := bytes.NewReader([]byte(message))
if err := openpgp.ArmoredDetachSign(writer, signingKey, reader, nil); err != nil {
return "", err
}

return writer.String(), nil
}

func createSignatureMessage(commit *createCommit) (string, error) {
if commit == nil || commit.Message == nil || *commit.Message == "" || commit.Author == nil {
return "", errors.New("createSignatureMessage: invalid parameters")
}

var message []string

if commit.Tree != nil {
message = append(message, fmt.Sprintf("tree %s", *commit.Tree))
}

for _, parent := range commit.Parents {
message = append(message, fmt.Sprintf("parent %s", parent))
}

message = append(message, fmt.Sprintf("author %s <%s> %d %s", commit.Author.GetName(), commit.Author.GetEmail(), commit.Author.GetDate().Unix(), commit.Author.GetDate().Format("-0700")))

committer := commit.Committer
if committer == nil {
committer = commit.Author
}

// There needs to be a double newline after committer
message = append(message, fmt.Sprintf("committer %s <%s> %d %s\n", committer.GetName(), committer.GetEmail(), committer.GetDate().Unix(), committer.GetDate().Format("-0700")))
message = append(message, *commit.Message)

return strings.Join(message, "\n"), nil
}
Loading