aboutsummaryrefslogtreecommitdiffstats
path: root/identity
diff options
context:
space:
mode:
authorMichael Muré <batolettre@gmail.com>2021-01-24 19:45:21 +0100
committerMichael Muré <batolettre@gmail.com>2021-02-14 12:19:00 +0100
commitdc5059bc3372941e2908739831188768335ac50b (patch)
tree7294aed90cf5f04809d7a99b4967b513bdb409d5 /identity
parent8d63c983c982f93cc48d3996d6bd097ddeeb327f (diff)
downloadgit-bug-dc5059bc3372941e2908739831188768335ac50b.tar.gz
entity: more progress on merging and signing
Diffstat (limited to 'identity')
-rw-r--r--identity/identity.go15
-rw-r--r--identity/identity_actions.go6
-rw-r--r--identity/identity_stub.go3
-rw-r--r--identity/interface.go3
-rw-r--r--identity/key.go163
-rw-r--r--identity/key_test.go45
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))
+}