key.go

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