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}