1package identity
  2
  3import (
  4	"bytes"
  5	"encoding/json"
  6	"fmt"
  7	"io"
  8	"strings"
  9	"time"
 10
 11	"github.com/ProtonMail/go-crypto/openpgp"
 12	"github.com/ProtonMail/go-crypto/openpgp/armor"
 13	"github.com/ProtonMail/go-crypto/openpgp/packet"
 14	"github.com/pkg/errors"
 15
 16	"github.com/MichaelMure/git-bug/repository"
 17)
 18
 19var errNoPrivateKey = fmt.Errorf("no private key")
 20
 21type Key struct {
 22	public  *packet.PublicKey
 23	private *packet.PrivateKey
 24}
 25
 26// GenerateKey generate a keypair (public+private)
 27// The type and configuration of the key is determined by the default value in go's OpenPGP.
 28func GenerateKey() *Key {
 29	entity, err := openpgp.NewEntity("", "", "", &packet.Config{
 30		// The armored format doesn't include the creation time, which makes the round-trip data not being fully equal.
 31		// We don't care about the creation time so we can set it to the zero value.
 32		Time: func() time.Time {
 33			return time.Time{}
 34		},
 35	})
 36	if err != nil {
 37		panic(err)
 38	}
 39	return &Key{
 40		public:  entity.PrimaryKey,
 41		private: entity.PrivateKey,
 42	}
 43}
 44
 45// generatePublicKey generate only a public key (only useful for testing)
 46// See GenerateKey for the details.
 47func generatePublicKey() *Key {
 48	k := GenerateKey()
 49	k.private = nil
 50	return k
 51}
 52
 53func (k *Key) Public() *packet.PublicKey {
 54	return k.public
 55}
 56
 57func (k *Key) Private() *packet.PrivateKey {
 58	return k.private
 59}
 60
 61func (k *Key) Validate() error {
 62	if k.public == nil {
 63		return fmt.Errorf("nil public key")
 64	}
 65	if !k.public.CanSign() {
 66		return fmt.Errorf("public key can't sign")
 67	}
 68
 69	if k.private != nil {
 70		if !k.private.CanSign() {
 71			return fmt.Errorf("private key can't sign")
 72		}
 73	}
 74
 75	return nil
 76}
 77
 78func (k *Key) Clone() *Key {
 79	clone := &Key{}
 80
 81	pub := *k.public
 82	clone.public = &pub
 83
 84	if k.private != nil {
 85		priv := *k.private
 86		clone.private = &priv
 87	}
 88
 89	return clone
 90}
 91
 92func (k *Key) MarshalJSON() ([]byte, error) {
 93	// Serialize only the public key, in the armored format.
 94	var buf bytes.Buffer
 95	w, err := armor.Encode(&buf, openpgp.PublicKeyType, nil)
 96	if err != nil {
 97		return nil, err
 98	}
 99
100	err = k.public.Serialize(w)
101	if err != nil {
102		return nil, err
103	}
104	err = w.Close()
105	if err != nil {
106		return nil, err
107	}
108	return json.Marshal(buf.String())
109}
110
111func (k *Key) UnmarshalJSON(data []byte) error {
112	// De-serialize only the public key, in the armored format.
113	var armored string
114	err := json.Unmarshal(data, &armored)
115	if err != nil {
116		return err
117	}
118
119	block, err := armor.Decode(strings.NewReader(armored))
120	if err == io.EOF {
121		return fmt.Errorf("no armored data found")
122	}
123	if err != nil {
124		return err
125	}
126
127	if block.Type != openpgp.PublicKeyType {
128		return fmt.Errorf("invalid key type")
129	}
130
131	p, err := packet.Read(block.Body)
132	if err != nil {
133		return errors.Wrap(err, "failed to read public key packet")
134	}
135
136	public, ok := p.(*packet.PublicKey)
137	if !ok {
138		return errors.New("got no packet.publicKey")
139	}
140
141	// The armored format doesn't include the creation time, which makes the round-trip data not being fully equal.
142	// We don't care about the creation time so we can set it to the zero value.
143	public.CreationTime = time.Time{}
144
145	k.public = public
146	return nil
147}
148
149func (k *Key) loadPrivate(repo repository.RepoKeyring) error {
150	item, err := repo.Keyring().Get(k.public.KeyIdString())
151	if err == repository.ErrKeyringKeyNotFound {
152		return errNoPrivateKey
153	}
154	if err != nil {
155		return err
156	}
157
158	block, err := armor.Decode(bytes.NewReader(item.Data))
159	if err == io.EOF {
160		return fmt.Errorf("no armored data found")
161	}
162	if err != nil {
163		return err
164	}
165
166	if block.Type != openpgp.PrivateKeyType {
167		return fmt.Errorf("invalid key type")
168	}
169
170	p, err := packet.Read(block.Body)
171	if err != nil {
172		return errors.Wrap(err, "failed to read private key packet")
173	}
174
175	private, ok := p.(*packet.PrivateKey)
176	if !ok {
177		return errors.New("got no packet.privateKey")
178	}
179
180	// The armored format doesn't include the creation time, which makes the round-trip data not being fully equal.
181	// We don't care about the creation time so we can set it to the zero value.
182	private.CreationTime = time.Time{}
183
184	k.private = private
185	return nil
186}
187
188// ensurePrivateKey attempt to load the corresponding private key if it is not loaded already.
189// If no private key is found, returns errNoPrivateKey
190func (k *Key) ensurePrivateKey(repo repository.RepoKeyring) error {
191	if k.private != nil {
192		return nil
193	}
194
195	return k.loadPrivate(repo)
196}
197
198func (k *Key) storePrivate(repo repository.RepoKeyring) error {
199	var buf bytes.Buffer
200	w, err := armor.Encode(&buf, openpgp.PrivateKeyType, nil)
201	if err != nil {
202		return err
203	}
204	err = k.private.Serialize(w)
205	if err != nil {
206		return err
207	}
208	err = w.Close()
209	if err != nil {
210		return err
211	}
212
213	return repo.Keyring().Set(repository.Item{
214		Key:  k.public.KeyIdString(),
215		Data: buf.Bytes(),
216	})
217}
218
219func (k *Key) PGPEntity() *openpgp.Entity {
220	uid := packet.NewUserId("", "", "")
221	return &openpgp.Entity{
222		PrimaryKey: k.public,
223		PrivateKey: k.private,
224		Identities: map[string]*openpgp.Identity{
225			uid.Id: {
226				Name:   uid.Id,
227				UserId: uid,
228				SelfSignature: &packet.Signature{
229					IsPrimaryId: func() *bool { b := true; return &b }(),
230				},
231			},
232		},
233	}
234}