diff options
author | Michael Muré <batolettre@gmail.com> | 2021-01-24 19:45:21 +0100 |
---|---|---|
committer | Michael Muré <batolettre@gmail.com> | 2021-02-14 12:19:00 +0100 |
commit | dc5059bc3372941e2908739831188768335ac50b (patch) | |
tree | 7294aed90cf5f04809d7a99b4967b513bdb409d5 /identity | |
parent | 8d63c983c982f93cc48d3996d6bd097ddeeb327f (diff) | |
download | git-bug-dc5059bc3372941e2908739831188768335ac50b.tar.gz |
entity: more progress on merging and signing
Diffstat (limited to 'identity')
-rw-r--r-- | identity/identity.go | 15 | ||||
-rw-r--r-- | identity/identity_actions.go | 6 | ||||
-rw-r--r-- | identity/identity_stub.go | 3 | ||||
-rw-r--r-- | identity/interface.go | 3 | ||||
-rw-r--r-- | identity/key.go | 163 | ||||
-rw-r--r-- | identity/key_test.go | 45 |
6 files changed, 157 insertions, 78 deletions
diff --git a/identity/identity.go b/identity/identity.go index 65019041..ad5f1efd 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -519,12 +519,19 @@ func (i *Identity) Keys() []*Key { } // SigningKey return the key that should be used to sign new messages. If no key is available, return nil. -func (i *Identity) SigningKey() *Key { +func (i *Identity) SigningKey(repo repository.RepoKeyring) (*Key, error) { keys := i.Keys() - if len(keys) > 0 { - return keys[0] + for _, key := range keys { + err := key.ensurePrivateKey(repo) + if err == errNoPrivateKey { + continue + } + if err != nil { + return nil, err + } + return key, nil } - return nil + return nil, nil } // ValidKeysAtTime return the set of keys valid at a given lamport time diff --git a/identity/identity_actions.go b/identity/identity_actions.go index 2e804533..21ce3fa6 100644 --- a/identity/identity_actions.go +++ b/identity/identity_actions.go @@ -102,7 +102,7 @@ func MergeAll(repo repository.ClockedRepo, remote string) <-chan entity.MergeRes return } - out <- entity.NewMergeStatus(entity.MergeStatusNew, id, remoteIdentity) + out <- entity.NewMergeNewStatus(id, remoteIdentity) continue } @@ -121,9 +121,9 @@ func MergeAll(repo repository.ClockedRepo, remote string) <-chan entity.MergeRes } if updated { - out <- entity.NewMergeStatus(entity.MergeStatusUpdated, id, localIdentity) + out <- entity.NewMergeUpdatedStatus(id, localIdentity) } else { - out <- entity.NewMergeStatus(entity.MergeStatusNothing, id, localIdentity) + out <- entity.NewMergeNothingStatus(id) } } }() diff --git a/identity/identity_stub.go b/identity/identity_stub.go index 91945378..fb5c90a5 100644 --- a/identity/identity_stub.go +++ b/identity/identity_stub.go @@ -4,6 +4,7 @@ import ( "encoding/json" "github.com/MichaelMure/git-bug/entity" + "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/lamport" "github.com/MichaelMure/git-bug/util/timestamp" ) @@ -71,7 +72,7 @@ func (IdentityStub) Keys() []*Key { panic("identities needs to be properly loaded with identity.ReadLocal()") } -func (i *IdentityStub) SigningKey() *Key { +func (i *IdentityStub) SigningKey(repo repository.RepoKeyring) (*Key, error) { panic("identities needs to be properly loaded with identity.ReadLocal()") } diff --git a/identity/interface.go b/identity/interface.go index 528cb067..5b14295b 100644 --- a/identity/interface.go +++ b/identity/interface.go @@ -2,6 +2,7 @@ package identity import ( "github.com/MichaelMure/git-bug/entity" + "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/lamport" "github.com/MichaelMure/git-bug/util/timestamp" ) @@ -37,7 +38,7 @@ type Interface interface { Keys() []*Key // SigningKey return the key that should be used to sign new messages. If no key is available, return nil. - SigningKey() *Key + SigningKey(repo repository.RepoKeyring) (*Key, error) // ValidKeysAtTime return the set of keys valid at a given lamport time for a given clock of another entity // Can be empty. diff --git a/identity/key.go b/identity/key.go index 8dd5e8c1..daa66b0e 100644 --- a/identity/key.go +++ b/identity/key.go @@ -16,12 +16,15 @@ import ( "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. @@ -47,12 +50,53 @@ func generatePublicKey() *Key { 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 @@ -65,6 +109,7 @@ func (k *Key) MarshalJSON() ([]byte, error) { } 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 { @@ -83,8 +128,7 @@ func (k *Key) UnmarshalJSON(data []byte) error { return fmt.Errorf("invalid key type") } - reader := packet.NewReader(block.Body) - p, err := reader.Next() + p, err := packet.Read(block.Body) if err != nil { return errors.Wrap(err, "failed to read public key packet") } @@ -102,53 +146,74 @@ func (k *Key) UnmarshalJSON(data []byte) error { return nil } -func (k *Key) Validate() error { - if k.public == nil { - return fmt.Errorf("nil public key") +func (k *Key) loadPrivate(repo repository.RepoKeyring) error { + item, err := repo.Keyring().Get(k.public.KeyIdString()) + if err == repository.ErrKeyringKeyNotFound { + return errNoPrivateKey } - if !k.public.CanSign() { - return fmt.Errorf("public key can't sign") + if err != nil { + return err } - if k.private != nil { - if !k.private.CanSign() { - return fmt.Errorf("private key can't sign") - } + block, err := armor.Decode(bytes.NewReader(item.Data)) + if err == io.EOF { + return fmt.Errorf("no armored data found") + } + if err != nil { + return err } - return nil -} - -func (k *Key) Clone() *Key { - clone := &Key{} + if block.Type != openpgp.PrivateKeyType { + return fmt.Errorf("invalid key type") + } - pub := *k.public - clone.public = &pub + p, err := packet.Read(block.Body) + if err != nil { + return errors.Wrap(err, "failed to read private key packet") + } - if k.private != nil { - priv := *k.private - clone.private = &priv + private, ok := p.(*packet.PrivateKey) + if !ok { + return errors.New("got no packet.privateKey") } - return clone + // 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 } -func (k *Key) EnsurePrivateKey(repo repository.RepoKeyring) error { +// 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 } - // item, err := repo.Keyring().Get(k.Fingerprint()) - // if err != nil { - // return fmt.Errorf("no private key found for %s", k.Fingerprint()) - // } - // - - panic("TODO") + return k.loadPrivate(repo) } -func (k *Key) Fingerprint() string { - return string(k.public.Fingerprint[:]) +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 { @@ -157,37 +222,3 @@ func (k *Key) PGPEntity() *openpgp.Entity { PrivateKey: k.private, } } - -var _ openpgp.KeyRing = &PGPKeyring{} - -// PGPKeyring implement a openpgp.KeyRing from an slice of Key -type PGPKeyring []*Key - -func (pk PGPKeyring) KeysById(id uint64) []openpgp.Key { - var result []openpgp.Key - for _, key := range pk { - if key.public.KeyId == id { - result = append(result, openpgp.Key{ - PublicKey: key.public, - PrivateKey: key.private, - }) - } - } - return result -} - -func (pk PGPKeyring) KeysByIdUsage(id uint64, requiredUsage byte) []openpgp.Key { - // the only usage we care about is the ability to sign, which all keys should already be capable of - return pk.KeysById(id) -} - -func (pk PGPKeyring) DecryptionKeys() []openpgp.Key { - result := make([]openpgp.Key, len(pk)) - for i, key := range pk { - result[i] = openpgp.Key{ - PublicKey: key.public, - PrivateKey: key.private, - } - } - return result -} diff --git a/identity/key_test.go b/identity/key_test.go index 3206c34e..6e320dc2 100644 --- a/identity/key_test.go +++ b/identity/key_test.go @@ -1,21 +1,60 @@ package identity import ( + "crypto/rsa" "encoding/json" "testing" "github.com/stretchr/testify/require" + + "github.com/MichaelMure/git-bug/repository" ) -func TestKeyJSON(t *testing.T) { +func TestPublicKeyJSON(t *testing.T) { k := generatePublicKey() - data, err := json.Marshal(k) + dataJSON, err := json.Marshal(k) require.NoError(t, err) var read Key - err = json.Unmarshal(data, &read) + err = json.Unmarshal(dataJSON, &read) require.NoError(t, err) require.Equal(t, k, &read) } + +func TestStoreLoad(t *testing.T) { + repo := repository.NewMockRepoKeyring() + + // public + private + k := GenerateKey() + + // Store + + dataJSON, err := json.Marshal(k) + require.NoError(t, err) + + err = k.storePrivate(repo) + require.NoError(t, err) + + // Load + + var read Key + err = json.Unmarshal(dataJSON, &read) + require.NoError(t, err) + + err = read.ensurePrivateKey(repo) + require.NoError(t, err) + + require.Equal(t, k.public, read.public) + + require.IsType(t, (*rsa.PrivateKey)(nil), k.private.PrivateKey) + + // See https://github.com/golang/crypto/pull/175 + rsaPriv := read.private.PrivateKey.(*rsa.PrivateKey) + back := rsaPriv.Primes[0] + rsaPriv.Primes[0] = rsaPriv.Primes[1] + rsaPriv.Primes[1] = back + + require.True(t, k.private.PrivateKey.(*rsa.PrivateKey).Equal(read.private.PrivateKey)) +} |