1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16
17 package jsonsign
18
19 import (
20 "bytes"
21 "context"
22 "encoding/json"
23 "errors"
24 "fmt"
25 "strings"
26 "sync"
27 "time"
28 "unicode"
29
30 "perkeep.org/internal/osutil"
31 "perkeep.org/pkg/blob"
32
33 "go4.org/wkfs"
34 "golang.org/x/crypto/openpgp"
35 "golang.org/x/crypto/openpgp/packet"
36 )
37
38 type EntityFetcher interface {
39 FetchEntity(fingerprint string) (*openpgp.Entity, error)
40 }
41
42 type FileEntityFetcher struct {
43 File string
44 }
45
46 func FlagEntityFetcher() *FileEntityFetcher {
47 return &FileEntityFetcher{File: osutil.SecretRingFile()}
48 }
49
50 type CachingEntityFetcher struct {
51 Fetcher EntityFetcher
52
53 lk sync.Mutex
54 m map[string]*openpgp.Entity
55 }
56
57 func (ce *CachingEntityFetcher) FetchEntity(fingerprint string) (*openpgp.Entity, error) {
58 ce.lk.Lock()
59 if ce.m != nil {
60 e := ce.m[fingerprint]
61 if e != nil {
62 ce.lk.Unlock()
63 return e, nil
64 }
65 }
66 ce.lk.Unlock()
67
68 e, err := ce.Fetcher.FetchEntity(fingerprint)
69 if err == nil {
70 ce.lk.Lock()
71 defer ce.lk.Unlock()
72 if ce.m == nil {
73 ce.m = make(map[string]*openpgp.Entity)
74 }
75 ce.m[fingerprint] = e
76 }
77
78 return e, err
79 }
80
81 func (fe *FileEntityFetcher) FetchEntity(fingerprint string) (*openpgp.Entity, error) {
82 f, err := wkfs.Open(fe.File)
83 if err != nil {
84 return nil, fmt.Errorf("jsonsign: FetchEntity: %v", err)
85 }
86 defer f.Close()
87 el, err := readKeyRing(f)
88 if err != nil {
89 return nil, fmt.Errorf("jsonsign: readKeyRing of %q: %v", fe.File, err)
90 }
91 for _, e := range el {
92 pubk := &e.PrivateKey.PublicKey
93 if fingerprintString(pubk) != fingerprint {
94 continue
95 }
96 if e.PrivateKey.Encrypted {
97 if err := fe.decryptEntity(e); err == nil {
98 return e, nil
99 }
100 return nil, err
101 }
102 return e, nil
103 }
104 return nil, fmt.Errorf("jsonsign: entity for fingerprint %q not found in %q", fingerprint, fe.File)
105 }
106
107 type SignRequest struct {
108 UnsignedJSON string
109 Fetcher blob.Fetcher
110 ServerMode bool
111
112
113 SignatureTime time.Time
114
115
116
117 EntityFetcher EntityFetcher
118
119
120
121
122 SecretKeyringPath string
123 }
124
125 func (sr *SignRequest) secretRingPath() string {
126 if sr.SecretKeyringPath != "" {
127 return sr.SecretKeyringPath
128 }
129 return osutil.SecretRingFile()
130 }
131
132 func (sr *SignRequest) Sign(ctx context.Context) (signedJSON string, err error) {
133 trimmedJSON := strings.TrimRightFunc(sr.UnsignedJSON, unicode.IsSpace)
134
135
136 inputfail := func(msg string) (string, error) {
137 return "", errors.New(msg)
138 }
139 execfail := func(msg string) (string, error) {
140 return "", errors.New(msg)
141 }
142
143 jmap := make(map[string]interface{})
144 if err := json.Unmarshal([]byte(trimmedJSON), &jmap); err != nil {
145 return inputfail("json parse error")
146 }
147
148 camliSigner, hasSigner := jmap["camliSigner"]
149 if !hasSigner {
150 return inputfail("json lacks \"camliSigner\" key with public key blobref")
151 }
152
153 camliSignerStr, _ := camliSigner.(string)
154 signerBlob, ok := blob.Parse(camliSignerStr)
155 if !ok {
156 return inputfail("json \"camliSigner\" key is malformed or unsupported")
157 }
158
159 pubkeyReader, _, err := sr.Fetcher.Fetch(ctx, signerBlob)
160 if err != nil {
161
162
163 return execfail(fmt.Sprintf("failed to find public key %s: %v", signerBlob.String(), err))
164 }
165
166 pubk, err := openArmoredPublicKeyFile(pubkeyReader)
167 pubkeyReader.Close()
168 if err != nil {
169 return execfail(fmt.Sprintf("failed to parse public key from blobref %s: %v", signerBlob.String(), err))
170 }
171
172
173
174 if len(trimmedJSON) == 0 || trimmedJSON[len(trimmedJSON)-1] != '}' {
175 return inputfail("json parameter lacks trailing '}'")
176 }
177 trimmedJSON = trimmedJSON[0 : len(trimmedJSON)-1]
178
179
180 entityFetcher := sr.EntityFetcher
181 if entityFetcher == nil {
182 file := sr.secretRingPath()
183 if file == "" {
184 return "", errors.New("jsonsign: no EntityFetcher, and no secret ring file defined")
185 }
186 secring, err := wkfs.Open(sr.secretRingPath())
187 if err != nil {
188 return "", fmt.Errorf("jsonsign: failed to open secret ring file %q: %v", sr.secretRingPath(), err)
189 }
190 secring.Close()
191 entityFetcher = &FileEntityFetcher{File: file}
192 }
193 signer, err := entityFetcher.FetchEntity(fingerprintString(pubk))
194 if err != nil {
195 return "", err
196 }
197
198 var buf bytes.Buffer
199 err = openpgp.ArmoredDetachSign(
200 &buf,
201 signer,
202 strings.NewReader(trimmedJSON),
203 &packet.Config{Time: func() time.Time { return sr.SignatureTime }},
204 )
205 if err != nil {
206 return "", err
207 }
208
209 output := buf.String()
210
211 index1 := strings.Index(output, "\n\n")
212 index2 := strings.Index(output, "\n-----")
213 if index1 == -1 || index2 == -1 {
214 return execfail("Failed to parse signature from gpg.")
215 }
216 inner := output[index1+2 : index2]
217 signature := strings.Replace(inner, "\n", "", -1)
218
219 return fmt.Sprintf("%s,\"camliSig\":\"%s\"}\n", trimmedJSON, signature), nil
220 }