1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package clearsign
17
18 import (
19 "bufio"
20 "bytes"
21 "crypto"
22 "fmt"
23 "hash"
24 "io"
25 "net/textproto"
26 "strconv"
27 "strings"
28
29 "golang.org/x/crypto/openpgp/armor"
30 "golang.org/x/crypto/openpgp/errors"
31 "golang.org/x/crypto/openpgp/packet"
32 )
33
34
35
36 type Block struct {
37 Headers textproto.MIMEHeader
38 Plaintext []byte
39 Bytes []byte
40 ArmoredSignature *armor.Block
41 }
42
43
44 var start = []byte("\n-----BEGIN PGP SIGNED MESSAGE-----")
45
46
47
48 var dashEscape = []byte("- ")
49
50
51
52 var endText = []byte("-----BEGIN PGP SIGNATURE-----")
53
54
55 var end = []byte("\n-----END PGP SIGNATURE-----")
56
57 var crlf = []byte("\r\n")
58 var lf = byte('\n')
59
60
61
62
63
64 func getLine(data []byte) (line, rest []byte) {
65 i := bytes.Index(data, []byte{'\n'})
66 var j int
67 if i < 0 {
68 i = len(data)
69 j = i
70 } else {
71 j = i + 1
72 if i > 0 && data[i-1] == '\r' {
73 i--
74 }
75 }
76 return data[0:i], data[j:]
77 }
78
79
80
81
82
83
84
85
86 func Decode(data []byte) (b *Block, rest []byte) {
87
88
89 rest = data
90 if bytes.HasPrefix(data, start[1:]) {
91 rest = rest[len(start)-1:]
92 } else if i := bytes.Index(data, start); i >= 0 {
93 rest = rest[i+len(start):]
94 } else {
95 return nil, data
96 }
97
98
99 suffix, rest := getLine(rest)
100 if len(suffix) != 0 {
101 return nil, data
102 }
103
104 var line []byte
105 b = &Block{
106 Headers: make(textproto.MIMEHeader),
107 }
108
109
110 for {
111
112
113 if len(rest) == 0 {
114 return nil, data
115 }
116
117 if line, rest = getLine(rest); len(line) == 0 {
118 break
119 }
120
121
122 if i := bytes.IndexFunc(line, func(r rune) bool {
123 return r < 0x20 || r > 0x7e
124 }); i != -1 {
125 return nil, data
126 }
127
128 i := bytes.Index(line, []byte{':'})
129 if i == -1 {
130 return nil, data
131 }
132
133 key, val := string(line[0:i]), string(line[i+1:])
134 key = strings.TrimSpace(key)
135 if key != "Hash" {
136 return nil, data
137 }
138 val = strings.TrimSpace(val)
139 b.Headers.Add(key, val)
140 }
141
142 firstLine := true
143 for {
144 start := rest
145
146 line, rest = getLine(rest)
147 if len(line) == 0 && len(rest) == 0 {
148
149 return nil, data
150 }
151 if bytes.Equal(line, endText) {
152
153
154 rest = start
155 break
156 }
157
158
159
160 if firstLine {
161 firstLine = false
162 } else {
163 b.Bytes = append(b.Bytes, crlf...)
164 }
165
166 if bytes.HasPrefix(line, dashEscape) {
167 line = line[2:]
168 }
169 line = bytes.TrimRight(line, " \t")
170 b.Bytes = append(b.Bytes, line...)
171
172 b.Plaintext = append(b.Plaintext, line...)
173 b.Plaintext = append(b.Plaintext, lf)
174 }
175
176
177
178 i := bytes.Index(rest, end)
179 if i == -1 {
180 return nil, data
181 }
182 i += len(end)
183 for i < len(rest) && (rest[i] == '\r' || rest[i] == '\n') {
184 i++
185 }
186 armored := rest[:i]
187 rest = rest[i:]
188
189 var err error
190 b.ArmoredSignature, err = armor.Decode(bytes.NewBuffer(armored))
191 if err != nil {
192 return nil, data
193 }
194
195 return b, rest
196 }
197
198
199
200
201
202
203
204 type dashEscaper struct {
205 buffered *bufio.Writer
206 hashers []hash.Hash
207 hashType crypto.Hash
208 toHash io.Writer
209
210 atBeginningOfLine bool
211 isFirstLine bool
212
213 whitespace []byte
214 byteBuf []byte
215
216 privateKeys []*packet.PrivateKey
217 config *packet.Config
218 }
219
220 func (d *dashEscaper) Write(data []byte) (n int, err error) {
221 for _, b := range data {
222 d.byteBuf[0] = b
223
224 if d.atBeginningOfLine {
225
226
227 if !d.isFirstLine {
228 d.toHash.Write(crlf)
229 }
230 d.isFirstLine = false
231 }
232
233
234
235 if b == ' ' || b == '\t' || b == '\r' {
236 d.whitespace = append(d.whitespace, b)
237 d.atBeginningOfLine = false
238 continue
239 }
240
241 if d.atBeginningOfLine {
242
243 if b == '-' {
244
245
246 if _, err = d.buffered.Write(dashEscape); err != nil {
247 return
248 }
249 d.toHash.Write(d.byteBuf)
250 d.atBeginningOfLine = false
251 } else if b == '\n' {
252
253 } else {
254 d.toHash.Write(d.byteBuf)
255 d.atBeginningOfLine = false
256 }
257 if err = d.buffered.WriteByte(b); err != nil {
258 return
259 }
260 } else {
261 if b == '\n' {
262
263
264 d.whitespace = d.whitespace[:0]
265
266
267 if err = d.buffered.WriteByte(b); err != nil {
268 return
269 }
270 d.atBeginningOfLine = true
271 } else {
272
273
274 if len(d.whitespace) > 0 {
275 d.toHash.Write(d.whitespace)
276 if _, err = d.buffered.Write(d.whitespace); err != nil {
277 return
278 }
279 d.whitespace = d.whitespace[:0]
280 }
281 d.toHash.Write(d.byteBuf)
282 if err = d.buffered.WriteByte(b); err != nil {
283 return
284 }
285 }
286 }
287 }
288
289 n = len(data)
290 return
291 }
292
293 func (d *dashEscaper) Close() (err error) {
294 if !d.atBeginningOfLine {
295 if err = d.buffered.WriteByte(lf); err != nil {
296 return
297 }
298 }
299
300 out, err := armor.Encode(d.buffered, "PGP SIGNATURE", nil)
301 if err != nil {
302 return
303 }
304
305 t := d.config.Now()
306 for i, k := range d.privateKeys {
307 sig := new(packet.Signature)
308 sig.SigType = packet.SigTypeText
309 sig.PubKeyAlgo = k.PubKeyAlgo
310 sig.Hash = d.hashType
311 sig.CreationTime = t
312 sig.IssuerKeyId = &k.KeyId
313
314 if err = sig.Sign(d.hashers[i], k, d.config); err != nil {
315 return
316 }
317 if err = sig.Serialize(out); err != nil {
318 return
319 }
320 }
321
322 if err = out.Close(); err != nil {
323 return
324 }
325 if err = d.buffered.Flush(); err != nil {
326 return
327 }
328 return
329 }
330
331
332
333 func Encode(w io.Writer, privateKey *packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) {
334 return EncodeMulti(w, []*packet.PrivateKey{privateKey}, config)
335 }
336
337
338
339
340 func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) {
341 for _, k := range privateKeys {
342 if k.Encrypted {
343 return nil, errors.InvalidArgumentError(fmt.Sprintf("signing key %s is encrypted", k.KeyIdString()))
344 }
345 }
346
347 hashType := config.Hash()
348 name := nameOfHash(hashType)
349 if len(name) == 0 {
350 return nil, errors.UnsupportedError("unknown hash type: " + strconv.Itoa(int(hashType)))
351 }
352
353 if !hashType.Available() {
354 return nil, errors.UnsupportedError("unsupported hash type: " + strconv.Itoa(int(hashType)))
355 }
356 var hashers []hash.Hash
357 var ws []io.Writer
358 for range privateKeys {
359 h := hashType.New()
360 hashers = append(hashers, h)
361 ws = append(ws, h)
362 }
363 toHash := io.MultiWriter(ws...)
364
365 buffered := bufio.NewWriter(w)
366
367 if _, err = buffered.Write(start[1:]); err != nil {
368 return
369 }
370 if err = buffered.WriteByte(lf); err != nil {
371 return
372 }
373 if _, err = buffered.WriteString("Hash: "); err != nil {
374 return
375 }
376 if _, err = buffered.WriteString(name); err != nil {
377 return
378 }
379 if err = buffered.WriteByte(lf); err != nil {
380 return
381 }
382 if err = buffered.WriteByte(lf); err != nil {
383 return
384 }
385
386 plaintext = &dashEscaper{
387 buffered: buffered,
388 hashers: hashers,
389 hashType: hashType,
390 toHash: toHash,
391
392 atBeginningOfLine: true,
393 isFirstLine: true,
394
395 byteBuf: make([]byte, 1),
396
397 privateKeys: privateKeys,
398 config: config,
399 }
400
401 return
402 }
403
404
405
406 func nameOfHash(h crypto.Hash) string {
407 switch h {
408 case crypto.MD5:
409 return "MD5"
410 case crypto.SHA1:
411 return "SHA1"
412 case crypto.RIPEMD160:
413 return "RIPEMD160"
414 case crypto.SHA224:
415 return "SHA224"
416 case crypto.SHA256:
417 return "SHA256"
418 case crypto.SHA384:
419 return "SHA384"
420 case crypto.SHA512:
421 return "SHA512"
422 }
423 return ""
424 }
425
View as plain text