about summary refs log tree commit diff
path: root/third_party/go/git-appraise/review/gpg/signable.go
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/go/git-appraise/review/gpg/signable.go')
-rw-r--r--third_party/go/git-appraise/review/gpg/signable.go129
1 files changed, 129 insertions, 0 deletions
diff --git a/third_party/go/git-appraise/review/gpg/signable.go b/third_party/go/git-appraise/review/gpg/signable.go
new file mode 100644
index 0000000000..776764c6fc
--- /dev/null
+++ b/third_party/go/git-appraise/review/gpg/signable.go
@@ -0,0 +1,129 @@
+// Package gpg provides an interface and an abstraction with which to sign and
+// verify review requests and comments.
+package gpg
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+)
+
+const placeholder = "gpgsig"
+
+// Sig provides an abstraction around shelling out to GPG to sign the
+// content it's given.
+type Sig struct {
+	// Sig holds an object's content's signature.
+	Sig string `json:"signature,omitempty"`
+}
+
+// Signable is an interfaces which provides the pointer to the signable
+// object's stringified signature.
+//
+// This pointer is used by `Sign` and `Verify` to replace its contents with
+// `placeholder` or the signature itself for the purposes of signing or
+// verifying.
+type Signable interface {
+	Signature() *string
+}
+
+// Signature is `Sig`'s implementation of `Signable`. Through this function, an
+// object which needs to implement `Signable` need only embed `Sig`
+// anonymously. See, e.g., review/request.go.
+func (s *Sig) Signature() *string {
+	return &s.Sig
+}
+
+// Sign uses gpg to sign the contents of a request and deposit it into the
+// signature key of the request.
+func Sign(key string, s Signable) error {
+	// First we retrieve the pointer and write `placeholder` as its value.
+	sigPtr := s.Signature()
+	*sigPtr = placeholder
+
+	// Marshal the content and sign it.
+	content, err := json.Marshal(s)
+	if err != nil {
+		return err
+	}
+	sig, err := signContent(key, content)
+	if err != nil {
+		return err
+	}
+
+	// Write the signature as the new value at the pointer.
+	*sigPtr = sig.String()
+	return nil
+}
+
+func signContent(key string, content []byte) (*bytes.Buffer,
+	error) {
+	var stdout, stderr bytes.Buffer
+	cmd := exec.Command("gpg", "-u", key, "--detach-sign", "--armor")
+	cmd.Stdin = bytes.NewReader(content)
+	cmd.Stdout = &stdout
+	cmd.Stderr = &stderr
+	err := cmd.Run()
+	return &stdout, err
+}
+
+// Verify verifies the signatures on the request and its comments with the
+// given key.
+func Verify(s Signable) error {
+	// Retrieve the pointer.
+	sigPtr := s.Signature()
+	// Copy its contents.
+	sig := *sigPtr
+	// Overwrite the value with the placeholder.
+	*sigPtr = placeholder
+
+	defer func() { *sigPtr = sig }()
+
+	// 1. Marshal the content into JSON.
+	// 2. Write the signature and the content to temp files.
+	// 3. Use gpg to verify the signature.
+	content, err := json.Marshal(s)
+	if err != nil {
+		return err
+	}
+	sigFile, err := ioutil.TempFile("", "sig")
+	if err != nil {
+		return err
+	}
+	defer os.Remove(sigFile.Name())
+	_, err = sigFile.Write([]byte(sig))
+	if err != nil {
+		return err
+	}
+	err = sigFile.Close()
+	if err != nil {
+		return err
+	}
+
+	contentFile, err := ioutil.TempFile("", "content")
+	if err != nil {
+		return err
+	}
+	defer os.Remove(contentFile.Name())
+	_, err = contentFile.Write(content)
+	if err != nil {
+		return err
+	}
+	err = contentFile.Close()
+	if err != nil {
+		return err
+	}
+
+	var stdout, stderr bytes.Buffer
+	cmd := exec.Command("gpg", "--verify", sigFile.Name(), contentFile.Name())
+	cmd.Stdout = &stdout
+	cmd.Stderr = &stderr
+	err = cmd.Run()
+	if err != nil {
+		return fmt.Errorf("%s", stderr.String())
+	}
+	return nil
+}