summary refs log tree commit diff
path: root/third_party/go/git-appraise/review/gpg/signable.go
blob: 776764c6fc107a6976de268e0fc880dca00f22b1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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
}