key.go

  1package identity
  2
  3import (
  4	"bytes"
  5	"encoding/json"
  6	"fmt"
  7	"strings"
  8	"time"
  9
 10	"github.com/ProtonMail/go-crypto/openpgp"
 11	"github.com/ProtonMail/go-crypto/openpgp/armor"
 12	"github.com/ProtonMail/go-crypto/openpgp/packet"
 13	"github.com/pkg/errors"
 14
 15	"github.com/git-bug/git-bug/repository"
 16)
 17
 18var errNoPrivateKey = fmt.Errorf("no private key")
 19
 20type Key struct {
 21	public *packet.PublicKey
 22	entity *openpgp.Entity
 23}
 24
 25type keyConfig struct {
 26	time time.Time
 27}
 28
 29type KeyOption func(*keyConfig)
 30
 31// WithTime sets a specific time for key generation.
 32// Useful for testing to ensure consistent, deterministic keys.
 33func WithTime(t time.Time) KeyOption {
 34	return func(cfg *keyConfig) {
 35		cfg.time = t
 36	}
 37}
 38
 39// GenerateKey generate a key pair (public+private) with identity metadata.
 40// The type and configuration of the key is determined by the default value in go's OpenPGP.
 41func GenerateKey(id Interface, opts ...KeyOption) *Key {
 42	cfg := &keyConfig{
 43		time: time.Now(),
 44	}
 45	for _, opt := range opts {
 46		opt(cfg)
 47	}
 48
 49	entity, err := openpgp.NewEntity(id.Name(), id.Login(), id.Email(), &packet.Config{
 50		Time: func() time.Time {
 51			return cfg.time
 52		},
 53	})
 54	if err != nil {
 55		panic(err)
 56	}
 57
 58	return &Key{
 59		public: entity.PrimaryKey,
 60		entity: entity,
 61	}
 62}
 63
 64// generatePublicKey generate only a public key (only useful for testing)
 65// See GenerateKey for the details.
 66func generatePublicKey(id Interface) *Key {
 67	k := GenerateKey(id, WithTime(time.Time{}))
 68	// k.entity = nil
 69	k.entity.PrivateKey = nil
 70	return k
 71}
 72
 73func (k *Key) Public() *packet.PublicKey {
 74	return k.entity.PrimaryKey
 75}
 76
 77func (k *Key) Version() string {
 78	return "new"
 79}
 80
 81func (k *Key) Private() *packet.PrivateKey {
 82	return k.entity.PrivateKey
 83}
 84
 85func (k *Key) Validate() error {
 86	if k.public == nil {
 87		return fmt.Errorf("nil public key")
 88	}
 89	if !k.public.CanSign() {
 90		return fmt.Errorf("public key can't sign")
 91	}
 92
 93	if k.entity != nil && k.entity.PrivateKey != nil {
 94		if !k.entity.PrivateKey.CanSign() {
 95			return fmt.Errorf("private key can't sign")
 96		}
 97	}
 98
 99	return nil
100}
101
102func (k *Key) Clone() *Key {
103	clone := &Key{}
104
105	pub := *k.public
106	clone.public = &pub
107
108	if k.entity != nil {
109		entity := *k.entity
110		clone.entity = &entity
111	}
112
113	return clone
114}
115
116func (k *Key) MarshalJSON() ([]byte, error) {
117	// Serialize only the public key, in the armored format.
118	var buf bytes.Buffer
119	w, err := armor.Encode(&buf, openpgp.PublicKeyType, nil)
120	if err != nil {
121		return nil, err
122	}
123
124	err = k.entity.Serialize(w)
125	if err != nil {
126		return nil, err
127	}
128	err = w.Close()
129	if err != nil {
130		return nil, err
131	}
132	return json.Marshal(buf.String())
133}
134
135func (k *Key) UnmarshalJSON(data []byte) error {
136	// De-serialize the entity using the armored format.
137	var armored string
138	err := json.Unmarshal(data, &armored)
139	if err != nil {
140		return err
141	}
142
143	entities, err := openpgp.ReadArmoredKeyRing(strings.NewReader(armored))
144	if err != nil {
145		return errors.Wrap(err, "failed to read armored key ring")
146	}
147
148	if len(entities) != 1 {
149		return fmt.Errorf("exactly one entity should be present - got %d", len(entities))
150	}
151
152	entity := entities[0]
153
154	// The armored format (RFC 4880) doesn't preserve key creation timestamps.
155	// We don't care about the creation time, so we reset it to the zero value to ensure consistency.
156	entity.PrimaryKey.CreationTime = time.Time{}
157
158	k.public = entity.PrimaryKey
159	k.entity = entity
160	return nil
161}
162
163func (k *Key) loadPrivate(repo repository.RepoKeyring) error {
164	item, err := repo.Keyring().Get(k.public.KeyIdString())
165	if err == repository.ErrKeyringKeyNotFound {
166		return errNoPrivateKey
167	}
168	if err != nil {
169		return err
170	}
171
172	entities, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(item.Data))
173	if err != nil {
174		return err
175	}
176
177	if len(entities) != 1 {
178		return fmt.Errorf("examtly one entity should be stored - got %d", len(entities))
179	}
180
181	// The armored format (RFC 4880) doesn't preserve key creation timestamps.
182	// We don't care about the creation time, so we reset it to the zero value to ensure consistency.
183	entities[0].PrivateKey.CreationTime = time.Time{}
184	k.entity = entities[0]
185	k.public = entities[0].PrimaryKey
186
187	return nil
188}
189
190// ensurePrivateKey attempt to load the corresponding private key if it is not loaded already.
191// If no private key is found, returns errNoPrivateKey
192func (k *Key) ensurePrivateKey(repo repository.RepoKeyring) error {
193	if k.entity != nil && k.entity.PrivateKey != nil {
194		return nil
195	}
196
197	return k.loadPrivate(repo)
198}
199
200func (k *Key) storePrivate(repo repository.RepoKeyring) error {
201	var buf bytes.Buffer
202	w, err := armor.Encode(&buf, openpgp.PrivateKeyType, nil)
203	if err != nil {
204		return err
205	}
206	err = k.entity.SerializePrivate(w, nil)
207	if err != nil {
208		return err
209	}
210	err = w.Close()
211	if err != nil {
212		return err
213	}
214
215	return repo.Keyring().Set(repository.Item{
216		Key:  k.entity.PrimaryKey.KeyIdString(),
217		Data: buf.Bytes(),
218	})
219}
220
221func (k *Key) PGPEntity() *openpgp.Entity {
222	return k.entity
223}