1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package libkb
5
6import (
7	"bytes"
8	"fmt"
9	"io"
10	"io/ioutil"
11	"time"
12
13	"github.com/keybase/go-crypto/openpgp"
14	"github.com/keybase/go-crypto/openpgp/armor"
15	"github.com/keybase/go-crypto/openpgp/clearsign"
16	"github.com/keybase/go-crypto/openpgp/errors"
17)
18
19type SignatureStatus struct {
20	IsSigned        bool
21	Verified        bool
22	SignatureError  error
23	KeyID           uint64
24	Entity          *openpgp.Entity
25	SignatureTime   time.Time
26	RecipientKeyIDs []uint64
27	Warnings        HashSecurityWarnings
28}
29
30func PGPDecryptWithBundles(g *GlobalContext, source io.Reader, sink io.Writer, keys []*PGPKeyBundle) (*SignatureStatus, error) {
31	opkr := make(openpgp.EntityList, len(keys))
32	for i, k := range keys {
33		opkr[i] = k.Entity
34	}
35	return PGPDecrypt(g, source, sink, opkr)
36}
37
38// PGPDecrypt only generates warnings about insecure _message_ signatures, not
39// _key_ signatures - that is handled by engine.PGPDecrypt.
40func PGPDecrypt(g *GlobalContext, source io.Reader, sink io.Writer, kr openpgp.KeyRing) (*SignatureStatus, error) {
41
42	var sc StreamClassification
43	var err error
44
45	sc, source, err = ClassifyStream(source)
46	if err != nil {
47		return nil, err
48	}
49
50	if sc.Format != CryptoMessageFormatPGP {
51		return nil, WrongCryptoFormatError{
52			Wanted:    CryptoMessageFormatPGP,
53			Received:  sc.Format,
54			Operation: "decrypt",
55		}
56	}
57
58	if sc.Type == CryptoMessageTypeClearSignature {
59		return pgpDecryptClearsign(g, source, sink, kr)
60	}
61
62	if sc.Armored {
63		b, err := armor.Decode(source)
64		if err != nil {
65			return nil, err
66		}
67		source = b.Body
68	}
69
70	g.Log.Debug("Calling into openpgp ReadMessage for decryption")
71	md, err := openpgp.ReadMessage(source, kr, nil, nil)
72	if err != nil {
73		if err == errors.ErrKeyIncorrect {
74			return nil, NoDecryptionKeyError{Msg: "unable to find a PGP decryption key for this message"}
75		}
76		return nil, err
77	}
78
79	if md.IsSigned {
80		g.Log.Debug("message is signed (SignedByKeyId: %+v) (have key? %v)", md.SignedByKeyId, md.SignedBy != nil)
81	}
82
83	n, err := io.Copy(sink, md.UnverifiedBody)
84	if err != nil {
85		return nil, err
86	}
87	g.Log.Debug("PGPDecrypt: copied %d bytes to writer", n)
88
89	var status SignatureStatus
90	if md.IsSigned {
91		status.IsSigned = true
92		status.KeyID = md.SignedByKeyId
93		if md.Signature != nil {
94			status.SignatureTime = md.Signature.CreationTime
95
96			if !IsHashSecure(md.Signature.Hash) {
97				status.Warnings = append(
98					status.Warnings,
99					NewHashSecurityWarning(
100						HashSecurityWarningSignatureHash,
101						md.Signature.Hash,
102						nil,
103					),
104				)
105			}
106		}
107		if md.SignedBy != nil {
108			status.Entity = md.SignedBy.Entity
109		}
110		if md.SignatureError != nil {
111			status.SignatureError = md.SignatureError
112		} else {
113			status.Verified = true
114		}
115	}
116
117	status.RecipientKeyIDs = md.EncryptedToKeyIds
118
119	return &status, nil
120}
121
122func pgpDecryptClearsign(g *GlobalContext, source io.Reader, sink io.Writer, kr openpgp.KeyRing) (*SignatureStatus, error) {
123	// clearsign decode only works with the whole data slice, not a reader
124	// so have to read it all here:
125	msg, err := ioutil.ReadAll(source)
126	if err != nil {
127		return nil, err
128	}
129	b, _ := clearsign.Decode(msg)
130	if b == nil {
131		return nil, fmt.Errorf("Unable to decode clearsigned message")
132	}
133
134	sigBytes, err := ioutil.ReadAll(b.ArmoredSignature.Body)
135	if err != nil {
136		return nil, err
137	}
138
139	signer, err := openpgp.CheckDetachedSignature(kr, bytes.NewReader(b.Bytes), bytes.NewReader(sigBytes))
140	if err != nil {
141		return nil, fmt.Errorf("Check sig error: %s", err)
142	}
143
144	n, err := io.Copy(sink, bytes.NewReader(b.Plaintext))
145	if err != nil {
146		return nil, err
147	}
148	g.Log.Debug("PGPDecrypt: copied %d bytes to writer", n)
149
150	var status SignatureStatus
151	if signer == nil {
152		return &status, nil
153	}
154
155	// Reexamine the signature to figure out its hash
156	digestHash, signerKeyID, err := ExtractPGPSignatureHashMethod(kr, sigBytes)
157	if err != nil {
158		return nil, err
159	}
160	if !IsHashSecure(digestHash) {
161		status.Warnings = append(
162			status.Warnings,
163			NewHashSecurityWarning(
164				HashSecurityWarningSignatureHash,
165				digestHash,
166				nil,
167			),
168		)
169	}
170
171	status.IsSigned = true
172	status.Verified = true
173	status.Entity = signer
174	status.KeyID = signerKeyID
175
176	return &status, nil
177}
178