]>
Commit | Line | Data |
---|---|---|
c680a8e1 RS |
1 | // Copyright 2011 The Go Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style | |
3 | // license that can be found in the LICENSE file. | |
4 | ||
5 | package openpgp | |
6 | ||
7 | import ( | |
8 | "crypto" | |
9 | "hash" | |
10 | "io" | |
11 | "strconv" | |
12 | "time" | |
13 | ||
14 | "golang.org/x/crypto/openpgp/armor" | |
15 | "golang.org/x/crypto/openpgp/errors" | |
16 | "golang.org/x/crypto/openpgp/packet" | |
17 | "golang.org/x/crypto/openpgp/s2k" | |
18 | ) | |
19 | ||
20 | // DetachSign signs message with the private key from signer (which must | |
21 | // already have been decrypted) and writes the signature to w. | |
22 | // If config is nil, sensible defaults will be used. | |
23 | func DetachSign(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error { | |
24 | return detachSign(w, signer, message, packet.SigTypeBinary, config) | |
25 | } | |
26 | ||
27 | // ArmoredDetachSign signs message with the private key from signer (which | |
28 | // must already have been decrypted) and writes an armored signature to w. | |
29 | // If config is nil, sensible defaults will be used. | |
30 | func ArmoredDetachSign(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) (err error) { | |
31 | return armoredDetachSign(w, signer, message, packet.SigTypeBinary, config) | |
32 | } | |
33 | ||
34 | // DetachSignText signs message (after canonicalising the line endings) with | |
35 | // the private key from signer (which must already have been decrypted) and | |
36 | // writes the signature to w. | |
37 | // If config is nil, sensible defaults will be used. | |
38 | func DetachSignText(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error { | |
39 | return detachSign(w, signer, message, packet.SigTypeText, config) | |
40 | } | |
41 | ||
42 | // ArmoredDetachSignText signs message (after canonicalising the line endings) | |
43 | // with the private key from signer (which must already have been decrypted) | |
44 | // and writes an armored signature to w. | |
45 | // If config is nil, sensible defaults will be used. | |
46 | func ArmoredDetachSignText(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error { | |
47 | return armoredDetachSign(w, signer, message, packet.SigTypeText, config) | |
48 | } | |
49 | ||
50 | func armoredDetachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) { | |
51 | out, err := armor.Encode(w, SignatureType, nil) | |
52 | if err != nil { | |
53 | return | |
54 | } | |
55 | err = detachSign(out, signer, message, sigType, config) | |
56 | if err != nil { | |
57 | return | |
58 | } | |
59 | return out.Close() | |
60 | } | |
61 | ||
62 | func detachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) { | |
63 | if signer.PrivateKey == nil { | |
64 | return errors.InvalidArgumentError("signing key doesn't have a private key") | |
65 | } | |
66 | if signer.PrivateKey.Encrypted { | |
67 | return errors.InvalidArgumentError("signing key is encrypted") | |
68 | } | |
69 | ||
70 | sig := new(packet.Signature) | |
71 | sig.SigType = sigType | |
72 | sig.PubKeyAlgo = signer.PrivateKey.PubKeyAlgo | |
73 | sig.Hash = config.Hash() | |
74 | sig.CreationTime = config.Now() | |
75 | sig.IssuerKeyId = &signer.PrivateKey.KeyId | |
76 | ||
77 | h, wrappedHash, err := hashForSignature(sig.Hash, sig.SigType) | |
78 | if err != nil { | |
79 | return | |
80 | } | |
81 | io.Copy(wrappedHash, message) | |
82 | ||
83 | err = sig.Sign(h, signer.PrivateKey, config) | |
84 | if err != nil { | |
85 | return | |
86 | } | |
87 | ||
88 | return sig.Serialize(w) | |
89 | } | |
90 | ||
91 | // FileHints contains metadata about encrypted files. This metadata is, itself, | |
92 | // encrypted. | |
93 | type FileHints struct { | |
94 | // IsBinary can be set to hint that the contents are binary data. | |
95 | IsBinary bool | |
96 | // FileName hints at the name of the file that should be written. It's | |
97 | // truncated to 255 bytes if longer. It may be empty to suggest that the | |
98 | // file should not be written to disk. It may be equal to "_CONSOLE" to | |
99 | // suggest the data should not be written to disk. | |
100 | FileName string | |
101 | // ModTime contains the modification time of the file, or the zero time if not applicable. | |
102 | ModTime time.Time | |
103 | } | |
104 | ||
105 | // SymmetricallyEncrypt acts like gpg -c: it encrypts a file with a passphrase. | |
106 | // The resulting WriteCloser must be closed after the contents of the file have | |
107 | // been written. | |
108 | // If config is nil, sensible defaults will be used. | |
109 | func SymmetricallyEncrypt(ciphertext io.Writer, passphrase []byte, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) { | |
110 | if hints == nil { | |
111 | hints = &FileHints{} | |
112 | } | |
113 | ||
114 | key, err := packet.SerializeSymmetricKeyEncrypted(ciphertext, passphrase, config) | |
115 | if err != nil { | |
116 | return | |
117 | } | |
118 | w, err := packet.SerializeSymmetricallyEncrypted(ciphertext, config.Cipher(), key, config) | |
119 | if err != nil { | |
120 | return | |
121 | } | |
122 | ||
123 | literaldata := w | |
124 | if algo := config.Compression(); algo != packet.CompressionNone { | |
125 | var compConfig *packet.CompressionConfig | |
126 | if config != nil { | |
127 | compConfig = config.CompressionConfig | |
128 | } | |
129 | literaldata, err = packet.SerializeCompressed(w, algo, compConfig) | |
130 | if err != nil { | |
131 | return | |
132 | } | |
133 | } | |
134 | ||
135 | var epochSeconds uint32 | |
136 | if !hints.ModTime.IsZero() { | |
137 | epochSeconds = uint32(hints.ModTime.Unix()) | |
138 | } | |
139 | return packet.SerializeLiteral(literaldata, hints.IsBinary, hints.FileName, epochSeconds) | |
140 | } | |
141 | ||
142 | // intersectPreferences mutates and returns a prefix of a that contains only | |
143 | // the values in the intersection of a and b. The order of a is preserved. | |
144 | func intersectPreferences(a []uint8, b []uint8) (intersection []uint8) { | |
145 | var j int | |
146 | for _, v := range a { | |
147 | for _, v2 := range b { | |
148 | if v == v2 { | |
149 | a[j] = v | |
150 | j++ | |
151 | break | |
152 | } | |
153 | } | |
154 | } | |
155 | ||
156 | return a[:j] | |
157 | } | |
158 | ||
159 | func hashToHashId(h crypto.Hash) uint8 { | |
160 | v, ok := s2k.HashToHashId(h) | |
161 | if !ok { | |
162 | panic("tried to convert unknown hash") | |
163 | } | |
164 | return v | |
165 | } | |
166 | ||
167 | // Encrypt encrypts a message to a number of recipients and, optionally, signs | |
168 | // it. hints contains optional information, that is also encrypted, that aids | |
169 | // the recipients in processing the message. The resulting WriteCloser must | |
170 | // be closed after the contents of the file have been written. | |
171 | // If config is nil, sensible defaults will be used. | |
172 | func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) { | |
173 | var signer *packet.PrivateKey | |
174 | if signed != nil { | |
175 | signKey, ok := signed.signingKey(config.Now()) | |
176 | if !ok { | |
177 | return nil, errors.InvalidArgumentError("no valid signing keys") | |
178 | } | |
179 | signer = signKey.PrivateKey | |
180 | if signer == nil { | |
181 | return nil, errors.InvalidArgumentError("no private key in signing key") | |
182 | } | |
183 | if signer.Encrypted { | |
184 | return nil, errors.InvalidArgumentError("signing key must be decrypted") | |
185 | } | |
186 | } | |
187 | ||
188 | // These are the possible ciphers that we'll use for the message. | |
189 | candidateCiphers := []uint8{ | |
190 | uint8(packet.CipherAES128), | |
191 | uint8(packet.CipherAES256), | |
192 | uint8(packet.CipherCAST5), | |
193 | } | |
194 | // These are the possible hash functions that we'll use for the signature. | |
195 | candidateHashes := []uint8{ | |
196 | hashToHashId(crypto.SHA256), | |
197 | hashToHashId(crypto.SHA512), | |
198 | hashToHashId(crypto.SHA1), | |
199 | hashToHashId(crypto.RIPEMD160), | |
200 | } | |
201 | // In the event that a recipient doesn't specify any supported ciphers | |
202 | // or hash functions, these are the ones that we assume that every | |
203 | // implementation supports. | |
204 | defaultCiphers := candidateCiphers[len(candidateCiphers)-1:] | |
205 | defaultHashes := candidateHashes[len(candidateHashes)-1:] | |
206 | ||
207 | encryptKeys := make([]Key, len(to)) | |
208 | for i := range to { | |
209 | var ok bool | |
210 | encryptKeys[i], ok = to[i].encryptionKey(config.Now()) | |
211 | if !ok { | |
212 | return nil, errors.InvalidArgumentError("cannot encrypt a message to key id " + strconv.FormatUint(to[i].PrimaryKey.KeyId, 16) + " because it has no encryption keys") | |
213 | } | |
214 | ||
215 | sig := to[i].primaryIdentity().SelfSignature | |
216 | ||
217 | preferredSymmetric := sig.PreferredSymmetric | |
218 | if len(preferredSymmetric) == 0 { | |
219 | preferredSymmetric = defaultCiphers | |
220 | } | |
221 | preferredHashes := sig.PreferredHash | |
222 | if len(preferredHashes) == 0 { | |
223 | preferredHashes = defaultHashes | |
224 | } | |
225 | candidateCiphers = intersectPreferences(candidateCiphers, preferredSymmetric) | |
226 | candidateHashes = intersectPreferences(candidateHashes, preferredHashes) | |
227 | } | |
228 | ||
229 | if len(candidateCiphers) == 0 || len(candidateHashes) == 0 { | |
230 | return nil, errors.InvalidArgumentError("cannot encrypt because recipient set shares no common algorithms") | |
231 | } | |
232 | ||
233 | cipher := packet.CipherFunction(candidateCiphers[0]) | |
234 | // If the cipher specified by config is a candidate, we'll use that. | |
235 | configuredCipher := config.Cipher() | |
236 | for _, c := range candidateCiphers { | |
237 | cipherFunc := packet.CipherFunction(c) | |
238 | if cipherFunc == configuredCipher { | |
239 | cipher = cipherFunc | |
240 | break | |
241 | } | |
242 | } | |
243 | ||
244 | var hash crypto.Hash | |
245 | for _, hashId := range candidateHashes { | |
246 | if h, ok := s2k.HashIdToHash(hashId); ok && h.Available() { | |
247 | hash = h | |
248 | break | |
249 | } | |
250 | } | |
251 | ||
252 | // If the hash specified by config is a candidate, we'll use that. | |
253 | if configuredHash := config.Hash(); configuredHash.Available() { | |
254 | for _, hashId := range candidateHashes { | |
255 | if h, ok := s2k.HashIdToHash(hashId); ok && h == configuredHash { | |
256 | hash = h | |
257 | break | |
258 | } | |
259 | } | |
260 | } | |
261 | ||
262 | if hash == 0 { | |
263 | hashId := candidateHashes[0] | |
264 | name, ok := s2k.HashIdToString(hashId) | |
265 | if !ok { | |
266 | name = "#" + strconv.Itoa(int(hashId)) | |
267 | } | |
268 | return nil, errors.InvalidArgumentError("cannot encrypt because no candidate hash functions are compiled in. (Wanted " + name + " in this case.)") | |
269 | } | |
270 | ||
271 | symKey := make([]byte, cipher.KeySize()) | |
272 | if _, err := io.ReadFull(config.Random(), symKey); err != nil { | |
273 | return nil, err | |
274 | } | |
275 | ||
276 | for _, key := range encryptKeys { | |
277 | if err := packet.SerializeEncryptedKey(ciphertext, key.PublicKey, cipher, symKey, config); err != nil { | |
278 | return nil, err | |
279 | } | |
280 | } | |
281 | ||
282 | encryptedData, err := packet.SerializeSymmetricallyEncrypted(ciphertext, cipher, symKey, config) | |
283 | if err != nil { | |
284 | return | |
285 | } | |
286 | ||
287 | if signer != nil { | |
288 | ops := &packet.OnePassSignature{ | |
289 | SigType: packet.SigTypeBinary, | |
290 | Hash: hash, | |
291 | PubKeyAlgo: signer.PubKeyAlgo, | |
292 | KeyId: signer.KeyId, | |
293 | IsLast: true, | |
294 | } | |
295 | if err := ops.Serialize(encryptedData); err != nil { | |
296 | return nil, err | |
297 | } | |
298 | } | |
299 | ||
300 | if hints == nil { | |
301 | hints = &FileHints{} | |
302 | } | |
303 | ||
304 | w := encryptedData | |
305 | if signer != nil { | |
306 | // If we need to write a signature packet after the literal | |
307 | // data then we need to stop literalData from closing | |
308 | // encryptedData. | |
309 | w = noOpCloser{encryptedData} | |
310 | ||
311 | } | |
312 | var epochSeconds uint32 | |
313 | if !hints.ModTime.IsZero() { | |
314 | epochSeconds = uint32(hints.ModTime.Unix()) | |
315 | } | |
316 | literalData, err := packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds) | |
317 | if err != nil { | |
318 | return nil, err | |
319 | } | |
320 | ||
321 | if signer != nil { | |
322 | return signatureWriter{encryptedData, literalData, hash, hash.New(), signer, config}, nil | |
323 | } | |
324 | return literalData, nil | |
325 | } | |
326 | ||
327 | // signatureWriter hashes the contents of a message while passing it along to | |
328 | // literalData. When closed, it closes literalData, writes a signature packet | |
329 | // to encryptedData and then also closes encryptedData. | |
330 | type signatureWriter struct { | |
331 | encryptedData io.WriteCloser | |
332 | literalData io.WriteCloser | |
333 | hashType crypto.Hash | |
334 | h hash.Hash | |
335 | signer *packet.PrivateKey | |
336 | config *packet.Config | |
337 | } | |
338 | ||
339 | func (s signatureWriter) Write(data []byte) (int, error) { | |
340 | s.h.Write(data) | |
341 | return s.literalData.Write(data) | |
342 | } | |
343 | ||
344 | func (s signatureWriter) Close() error { | |
345 | sig := &packet.Signature{ | |
346 | SigType: packet.SigTypeBinary, | |
347 | PubKeyAlgo: s.signer.PubKeyAlgo, | |
348 | Hash: s.hashType, | |
349 | CreationTime: s.config.Now(), | |
350 | IssuerKeyId: &s.signer.KeyId, | |
351 | } | |
352 | ||
353 | if err := sig.Sign(s.h, s.signer, s.config); err != nil { | |
354 | return err | |
355 | } | |
356 | if err := s.literalData.Close(); err != nil { | |
357 | return err | |
358 | } | |
359 | if err := sig.Serialize(s.encryptedData); err != nil { | |
360 | return err | |
361 | } | |
362 | return s.encryptedData.Close() | |
363 | } | |
364 | ||
365 | // noOpCloser is like an ioutil.NopCloser, but for an io.Writer. | |
366 | // TODO: we have two of these in OpenPGP packages alone. This probably needs | |
367 | // to be promoted somewhere more common. | |
368 | type noOpCloser struct { | |
369 | w io.Writer | |
370 | } | |
371 | ||
372 | func (c noOpCloser) Write(data []byte) (n int, err error) { | |
373 | return c.w.Write(data) | |
374 | } | |
375 | ||
376 | func (c noOpCloser) Close() error { | |
377 | return nil | |
378 | } |