1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package pgp
18
19
20
21 import (
22 "bufio"
23 "bytes"
24 "errors"
25 "fmt"
26 "io"
27 "io/ioutil"
28 "os"
29 "path/filepath"
30 "time"
31
32 "github.com/sassoftware/relic/lib/atomicfile"
33 "github.com/sassoftware/relic/lib/certloader"
34 "github.com/sassoftware/relic/lib/magic"
35 "github.com/sassoftware/relic/lib/pgptools"
36 "github.com/sassoftware/relic/signers"
37 "golang.org/x/crypto/openpgp"
38 "golang.org/x/crypto/openpgp/armor"
39 "golang.org/x/crypto/openpgp/packet"
40 )
41
42 var PgpSigner = &signers.Signer{
43 Name: "pgp",
44 Magic: magic.FileTypePGP,
45 CertTypes: signers.CertTypePgp,
46 AllowStdin: true,
47 Transform: transform,
48 Sign: sign,
49 VerifyStream: verify,
50 }
51
52 const maxStreamClearSignSize = 10 * 1000 * 1000
53
54 func init() {
55 PgpSigner.Flags().BoolP("armor", "a", false, "(PGP) Create ASCII armored output")
56 PgpSigner.Flags().Bool("inline", false, "(PGP) Create a signed message instead of a detached signature")
57 PgpSigner.Flags().Bool("clearsign", false, "(PGP) Create a cleartext signature")
58 PgpSigner.Flags().BoolP("textmode", "t", false, "(PGP) Sign in CRLF canonical text form")
59
60 PgpSigner.Flags().String("pgp", "", "")
61 PgpSigner.Flags().MarkHidden("pgp")
62 signers.Register(PgpSigner)
63 }
64
65 type pgpTransformer struct {
66 inline, clearsign, armor bool
67
68 filename string
69 stream io.ReadSeeker
70 closer io.Closer
71 }
72
73 func transform(f *os.File, opts signers.SignOpts) (signers.Transformer, error) {
74 armor := opts.Flags.GetBool("armor")
75 inline := opts.Flags.GetBool("inline")
76 if inline {
77
78 opts.Flags.Values["armor"] = "false"
79 }
80 clearsign := opts.Flags.GetBool("clearsign")
81 stream := io.ReadSeeker(f)
82 if _, err := f.Seek(0, 0); err != nil {
83
84 contents, err := ioutil.ReadAll(io.LimitReader(stream, maxStreamClearSignSize))
85 if err != nil {
86 return nil, err
87 } else if len(contents) == maxStreamClearSignSize {
88 return nil, errors.New("input stream is too big, try writing it to file first")
89 }
90 stream = bytes.NewReader(contents)
91 }
92 return &pgpTransformer{
93 inline: inline,
94 clearsign: clearsign,
95 armor: armor,
96 filename: filepath.Base(f.Name()),
97 stream: stream,
98 closer: f,
99 }, nil
100 }
101
102 func (t *pgpTransformer) GetReader() (io.Reader, error) {
103 if _, err := t.stream.Seek(0, 0); err != nil {
104 return nil, err
105 }
106 return t.stream, nil
107 }
108
109 func sign(r io.Reader, cert *certloader.Certificate, opts signers.SignOpts) ([]byte, error) {
110 armor := opts.Flags.GetBool("armor")
111 clearsign := opts.Flags.GetBool("clearsign")
112 textmode := opts.Flags.GetBool("textmode")
113 if pgpcompat := opts.Flags.GetString("pgp"); pgpcompat == "mini-clear" {
114 clearsign = true
115 }
116 var sf func(io.Writer, *openpgp.Entity, io.Reader, *packet.Config) error
117 if clearsign {
118 sf = pgptools.DetachClearSign
119 } else if armor {
120 if textmode {
121 sf = openpgp.ArmoredDetachSignText
122 } else {
123 sf = openpgp.ArmoredDetachSign
124 }
125 } else {
126 if textmode {
127 sf = openpgp.DetachSignText
128 } else {
129 sf = openpgp.DetachSign
130 }
131 }
132 var buf bytes.Buffer
133 config := &packet.Config{
134 DefaultHash: opts.Hash,
135 Time: func() time.Time { return opts.Time },
136 }
137 if err := sf(&buf, cert.PgpKey, r, config); err != nil {
138 return nil, err
139 } else if armor {
140 buf.WriteByte('\n')
141 }
142 return buf.Bytes(), nil
143 }
144
145 func (t *pgpTransformer) Apply(dest, mimeType string, result io.Reader) error {
146 outfile, err := atomicfile.WriteAny(dest)
147 if err != nil {
148 return err
149 }
150 defer outfile.Close()
151 if t.inline || t.clearsign {
152
153 if _, err := t.stream.Seek(0, 0); err != nil {
154 return err
155 }
156 sig, err := ioutil.ReadAll(result)
157 if err != nil {
158 return err
159 }
160 if t.clearsign {
161 err = pgptools.MergeClearSign(outfile, sig, t.stream)
162 } else {
163 err = pgptools.MergeSignature(outfile, sig, t.stream, t.armor, t.filename)
164 }
165 if err != nil {
166 return err
167 }
168 } else {
169 if _, err := io.Copy(outfile, result); err != nil {
170 return err
171 }
172 }
173 t.closer.Close()
174 return outfile.Commit()
175 }
176
177 func verify(r io.Reader, opts signers.VerifyOpts) ([]*signers.Signature, error) {
178 br := bufio.NewReader(r)
179
180 reader := io.Reader(br)
181 if x, _ := br.Peek(34); len(x) >= 1 && x[0] == '-' {
182 if bytes.HasPrefix(x, []byte("-----BEGIN PGP SIGNED MESSAGE-----")) {
183
184 sig, err := pgptools.VerifyClearSign(reader, nil, opts.TrustedPgp)
185 return verifyPgp(sig, opts.FileName, err)
186 }
187 block, err := armor.Decode(reader)
188 if err != nil {
189 return nil, err
190 }
191 reader = block.Body
192 }
193 if opts.Content != "" {
194
195 fc, err := os.Open(opts.Content)
196 if err != nil {
197 return nil, err
198 }
199 defer fc.Close()
200 sig, err := pgptools.VerifyDetached(reader, fc, opts.TrustedPgp)
201 return verifyPgp(sig, opts.FileName, err)
202 }
203
204 sig, err := pgptools.VerifyInline(reader, nil, opts.TrustedPgp)
205 return verifyPgp(sig, opts.FileName, err)
206 }
207
208 func verifyPgp(sig *pgptools.PgpSignature, name string, err error) ([]*signers.Signature, error) {
209 if err == nil {
210 return []*signers.Signature{&signers.Signature{
211 CreationTime: sig.CreationTime,
212 Hash: sig.Hash,
213 SignerPgp: sig.Key.Entity,
214 }}, nil
215 } else if sig != nil {
216 return nil, fmt.Errorf("bad signature from %s(%x) [%s]: %s", pgptools.EntityName(sig.Key.Entity), sig.Key.PublicKey.KeyId, sig.CreationTime, err)
217 }
218 return nil, err
219 }
220
View as plain text