]>
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 | ||
107c1cdb ND |
167 | // writeAndSign writes the data as a payload package and, optionally, signs |
168 | // it. hints contains optional information, that is also encrypted, | |
169 | // that aids the recipients in processing the message. The resulting | |
170 | // WriteCloser must be closed after the contents of the file have been | |
171 | // written. If config is nil, sensible defaults will be used. | |
172 | func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) { | |
c680a8e1 RS |
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 | ||
107c1cdb ND |
188 | var hash crypto.Hash |
189 | for _, hashId := range candidateHashes { | |
190 | if h, ok := s2k.HashIdToHash(hashId); ok && h.Available() { | |
191 | hash = h | |
192 | break | |
193 | } | |
194 | } | |
195 | ||
196 | // If the hash specified by config is a candidate, we'll use that. | |
197 | if configuredHash := config.Hash(); configuredHash.Available() { | |
198 | for _, hashId := range candidateHashes { | |
199 | if h, ok := s2k.HashIdToHash(hashId); ok && h == configuredHash { | |
200 | hash = h | |
201 | break | |
202 | } | |
203 | } | |
204 | } | |
205 | ||
206 | if hash == 0 { | |
207 | hashId := candidateHashes[0] | |
208 | name, ok := s2k.HashIdToString(hashId) | |
209 | if !ok { | |
210 | name = "#" + strconv.Itoa(int(hashId)) | |
211 | } | |
212 | return nil, errors.InvalidArgumentError("cannot encrypt because no candidate hash functions are compiled in. (Wanted " + name + " in this case.)") | |
213 | } | |
214 | ||
215 | if signer != nil { | |
216 | ops := &packet.OnePassSignature{ | |
217 | SigType: packet.SigTypeBinary, | |
218 | Hash: hash, | |
219 | PubKeyAlgo: signer.PubKeyAlgo, | |
220 | KeyId: signer.KeyId, | |
221 | IsLast: true, | |
222 | } | |
223 | if err := ops.Serialize(payload); err != nil { | |
224 | return nil, err | |
225 | } | |
226 | } | |
227 | ||
228 | if hints == nil { | |
229 | hints = &FileHints{} | |
230 | } | |
231 | ||
232 | w := payload | |
233 | if signer != nil { | |
234 | // If we need to write a signature packet after the literal | |
235 | // data then we need to stop literalData from closing | |
236 | // encryptedData. | |
237 | w = noOpCloser{w} | |
238 | ||
239 | } | |
240 | var epochSeconds uint32 | |
241 | if !hints.ModTime.IsZero() { | |
242 | epochSeconds = uint32(hints.ModTime.Unix()) | |
243 | } | |
244 | literalData, err := packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds) | |
245 | if err != nil { | |
246 | return nil, err | |
247 | } | |
248 | ||
249 | if signer != nil { | |
250 | return signatureWriter{payload, literalData, hash, hash.New(), signer, config}, nil | |
251 | } | |
252 | return literalData, nil | |
253 | } | |
254 | ||
255 | // Encrypt encrypts a message to a number of recipients and, optionally, signs | |
256 | // it. hints contains optional information, that is also encrypted, that aids | |
257 | // the recipients in processing the message. The resulting WriteCloser must | |
258 | // be closed after the contents of the file have been written. | |
259 | // If config is nil, sensible defaults will be used. | |
260 | func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) { | |
261 | if len(to) == 0 { | |
262 | return nil, errors.InvalidArgumentError("no encryption recipient provided") | |
263 | } | |
264 | ||
c680a8e1 RS |
265 | // These are the possible ciphers that we'll use for the message. |
266 | candidateCiphers := []uint8{ | |
267 | uint8(packet.CipherAES128), | |
268 | uint8(packet.CipherAES256), | |
269 | uint8(packet.CipherCAST5), | |
270 | } | |
271 | // These are the possible hash functions that we'll use for the signature. | |
272 | candidateHashes := []uint8{ | |
273 | hashToHashId(crypto.SHA256), | |
107c1cdb | 274 | hashToHashId(crypto.SHA384), |
c680a8e1 RS |
275 | hashToHashId(crypto.SHA512), |
276 | hashToHashId(crypto.SHA1), | |
277 | hashToHashId(crypto.RIPEMD160), | |
278 | } | |
279 | // In the event that a recipient doesn't specify any supported ciphers | |
280 | // or hash functions, these are the ones that we assume that every | |
281 | // implementation supports. | |
282 | defaultCiphers := candidateCiphers[len(candidateCiphers)-1:] | |
283 | defaultHashes := candidateHashes[len(candidateHashes)-1:] | |
284 | ||
285 | encryptKeys := make([]Key, len(to)) | |
286 | for i := range to { | |
287 | var ok bool | |
288 | encryptKeys[i], ok = to[i].encryptionKey(config.Now()) | |
289 | if !ok { | |
290 | return nil, errors.InvalidArgumentError("cannot encrypt a message to key id " + strconv.FormatUint(to[i].PrimaryKey.KeyId, 16) + " because it has no encryption keys") | |
291 | } | |
292 | ||
293 | sig := to[i].primaryIdentity().SelfSignature | |
294 | ||
295 | preferredSymmetric := sig.PreferredSymmetric | |
296 | if len(preferredSymmetric) == 0 { | |
297 | preferredSymmetric = defaultCiphers | |
298 | } | |
299 | preferredHashes := sig.PreferredHash | |
300 | if len(preferredHashes) == 0 { | |
301 | preferredHashes = defaultHashes | |
302 | } | |
303 | candidateCiphers = intersectPreferences(candidateCiphers, preferredSymmetric) | |
304 | candidateHashes = intersectPreferences(candidateHashes, preferredHashes) | |
305 | } | |
306 | ||
307 | if len(candidateCiphers) == 0 || len(candidateHashes) == 0 { | |
308 | return nil, errors.InvalidArgumentError("cannot encrypt because recipient set shares no common algorithms") | |
309 | } | |
310 | ||
311 | cipher := packet.CipherFunction(candidateCiphers[0]) | |
312 | // If the cipher specified by config is a candidate, we'll use that. | |
313 | configuredCipher := config.Cipher() | |
314 | for _, c := range candidateCiphers { | |
315 | cipherFunc := packet.CipherFunction(c) | |
316 | if cipherFunc == configuredCipher { | |
317 | cipher = cipherFunc | |
318 | break | |
319 | } | |
320 | } | |
321 | ||
c680a8e1 RS |
322 | symKey := make([]byte, cipher.KeySize()) |
323 | if _, err := io.ReadFull(config.Random(), symKey); err != nil { | |
324 | return nil, err | |
325 | } | |
326 | ||
327 | for _, key := range encryptKeys { | |
328 | if err := packet.SerializeEncryptedKey(ciphertext, key.PublicKey, cipher, symKey, config); err != nil { | |
329 | return nil, err | |
330 | } | |
331 | } | |
332 | ||
107c1cdb | 333 | payload, err := packet.SerializeSymmetricallyEncrypted(ciphertext, cipher, symKey, config) |
c680a8e1 RS |
334 | if err != nil { |
335 | return | |
336 | } | |
337 | ||
107c1cdb ND |
338 | return writeAndSign(payload, candidateHashes, signed, hints, config) |
339 | } | |
c680a8e1 | 340 | |
107c1cdb ND |
341 | // Sign signs a message. The resulting WriteCloser must be closed after the |
342 | // contents of the file have been written. hints contains optional information | |
343 | // that aids the recipients in processing the message. | |
344 | // If config is nil, sensible defaults will be used. | |
345 | func Sign(output io.Writer, signed *Entity, hints *FileHints, config *packet.Config) (input io.WriteCloser, err error) { | |
346 | if signed == nil { | |
347 | return nil, errors.InvalidArgumentError("no signer provided") | |
c680a8e1 RS |
348 | } |
349 | ||
107c1cdb ND |
350 | // These are the possible hash functions that we'll use for the signature. |
351 | candidateHashes := []uint8{ | |
352 | hashToHashId(crypto.SHA256), | |
353 | hashToHashId(crypto.SHA384), | |
354 | hashToHashId(crypto.SHA512), | |
355 | hashToHashId(crypto.SHA1), | |
356 | hashToHashId(crypto.RIPEMD160), | |
c680a8e1 | 357 | } |
107c1cdb ND |
358 | defaultHashes := candidateHashes[len(candidateHashes)-1:] |
359 | preferredHashes := signed.primaryIdentity().SelfSignature.PreferredHash | |
360 | if len(preferredHashes) == 0 { | |
361 | preferredHashes = defaultHashes | |
c680a8e1 | 362 | } |
107c1cdb ND |
363 | candidateHashes = intersectPreferences(candidateHashes, preferredHashes) |
364 | return writeAndSign(noOpCloser{output}, candidateHashes, signed, hints, config) | |
c680a8e1 RS |
365 | } |
366 | ||
367 | // signatureWriter hashes the contents of a message while passing it along to | |
368 | // literalData. When closed, it closes literalData, writes a signature packet | |
369 | // to encryptedData and then also closes encryptedData. | |
370 | type signatureWriter struct { | |
371 | encryptedData io.WriteCloser | |
372 | literalData io.WriteCloser | |
373 | hashType crypto.Hash | |
374 | h hash.Hash | |
375 | signer *packet.PrivateKey | |
376 | config *packet.Config | |
377 | } | |
378 | ||
379 | func (s signatureWriter) Write(data []byte) (int, error) { | |
380 | s.h.Write(data) | |
381 | return s.literalData.Write(data) | |
382 | } | |
383 | ||
384 | func (s signatureWriter) Close() error { | |
385 | sig := &packet.Signature{ | |
386 | SigType: packet.SigTypeBinary, | |
387 | PubKeyAlgo: s.signer.PubKeyAlgo, | |
388 | Hash: s.hashType, | |
389 | CreationTime: s.config.Now(), | |
390 | IssuerKeyId: &s.signer.KeyId, | |
391 | } | |
392 | ||
393 | if err := sig.Sign(s.h, s.signer, s.config); err != nil { | |
394 | return err | |
395 | } | |
396 | if err := s.literalData.Close(); err != nil { | |
397 | return err | |
398 | } | |
399 | if err := sig.Serialize(s.encryptedData); err != nil { | |
400 | return err | |
401 | } | |
402 | return s.encryptedData.Close() | |
403 | } | |
404 | ||
405 | // noOpCloser is like an ioutil.NopCloser, but for an io.Writer. | |
406 | // TODO: we have two of these in OpenPGP packages alone. This probably needs | |
407 | // to be promoted somewhere more common. | |
408 | type noOpCloser struct { | |
409 | w io.Writer | |
410 | } | |
411 | ||
412 | func (c noOpCloser) Write(data []byte) (n int, err error) { | |
413 | return c.w.Write(data) | |
414 | } | |
415 | ||
416 | func (c noOpCloser) Close() error { | |
417 | return nil | |
418 | } |