package identity import ( "bytes" "encoding/json" "fmt" "io" "strings" "time" "github.com/pkg/errors" "golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp/armor" "golang.org/x/crypto/openpgp/packet" "github.com/MichaelMure/git-bug/repository" ) var errNoPrivateKey = fmt.Errorf("no private key") type Key struct { public *packet.PublicKey private *packet.PrivateKey } // GenerateKey generate a keypair (public+private) // The type and configuration of the key is determined by the default value in go's OpenPGP. func GenerateKey() *Key { entity, err := openpgp.NewEntity("", "", "", &packet.Config{ // The armored format doesn't include the creation time, which makes the round-trip data not being fully equal. // We don't care about the creation time so we can set it to the zero value. Time: func() time.Time { return time.Time{} }, }) if err != nil { panic(err) } return &Key{ public: entity.PrimaryKey, private: entity.PrivateKey, } } // generatePublicKey generate only a public key (only useful for testing) // See GenerateKey for the details. func generatePublicKey() *Key { k := GenerateKey() k.private = nil return k } func (k *Key) Public() *packet.PublicKey { return k.public } func (k *Key) Private() *packet.PrivateKey { return k.private } func (k *Key) Validate() error { if k.public == nil { return fmt.Errorf("nil public key") } if !k.public.CanSign() { return fmt.Errorf("public key can't sign") } if k.private != nil { if !k.private.CanSign() { return fmt.Errorf("private key can't sign") } } return nil } func (k *Key) Clone() *Key { clone := &Key{} pub := *k.public clone.public = &pub if k.private != nil { priv := *k.private clone.private = &priv } return clone } func (k *Key) MarshalJSON() ([]byte, error) { // Serialize only the public key, in the armored format. var buf bytes.Buffer w, err := armor.Encode(&buf, openpgp.PublicKeyType, nil) if err != nil { return nil, err } err = k.public.Serialize(w) if err != nil { return nil, err } err = w.Close() if err != nil { return nil, err } return json.Marshal(buf.String()) } func (k *Key) UnmarshalJSON(data []byte) error { // De-serialize only the public key, in the armored format. var armored string err := json.Unmarshal(data, &armored) if err != nil { return err } block, err := armor.Decode(strings.NewReader(armored)) if err == io.EOF { return fmt.Errorf("no armored data found") } if err != nil { return err } if block.Type != openpgp.PublicKeyType { return fmt.Errorf("invalid key type") } p, err := packet.Read(block.Body) if err != nil { return errors.Wrap(err, "failed to read public key packet") } public, ok := p.(*packet.PublicKey) if !ok { return errors.New("got no packet.publicKey") } // The armored format doesn't include the creation time, which makes the round-trip data not being fully equal. // We don't care about the creation time so we can set it to the zero value. public.CreationTime = time.Time{} k.public = public return nil } func (k *Key) loadPrivate(repo repository.RepoKeyring) error { item, err := repo.Keyring().Get(k.public.KeyIdString()) if err == repository.ErrKeyringKeyNotFound { return errNoPrivateKey } if err != nil { return err } block, err := armor.Decode(bytes.NewReader(item.Data)) if err == io.EOF { return fmt.Errorf("no armored data found") } if err != nil { return err } if block.Type != openpgp.PrivateKeyType { return fmt.Errorf("invalid key type") } p, err := packet.Read(block.Body) if err != nil { return errors.Wrap(err, "failed to read private key packet") } private, ok := p.(*packet.PrivateKey) if !ok { return errors.New("got no packet.privateKey") } // The armored format doesn't include the creation time, which makes the round-trip data not being fully equal. // We don't care about the creation time so we can set it to the zero value. private.CreationTime = time.Time{} k.private = private return nil } // ensurePrivateKey attempt to load the corresponding private key if it is not loaded already. // If no private key is found, returns errNoPrivateKey func (k *Key) ensurePrivateKey(repo repository.RepoKeyring) error { if k.private != nil { return nil } return k.loadPrivate(repo) } func (k *Key) storePrivate(repo repository.RepoKeyring) error { var buf bytes.Buffer w, err := armor.Encode(&buf, openpgp.PrivateKeyType, nil) if err != nil { return err } err = k.private.Serialize(w) if err != nil { return err } err = w.Close() if err != nil { return err } return repo.Keyring().Set(repository.Item{ Key: k.public.KeyIdString(), Data: buf.Bytes(), }) } func (k *Key) PGPEntity() *openpgp.Entity { return &openpgp.Entity{ PrimaryKey: k.public, PrivateKey: k.private, } }