diff --git a/commands/user_key.go b/commands/user_key.go index 0cc8681fbcc8f84a15e5f2a6fb06d36832e1cb71..05892f53f8eedb1bb43a6cf5f3dd8c1d468c1561 100644 --- a/commands/user_key.go +++ b/commands/user_key.go @@ -3,10 +3,10 @@ package commands import ( "fmt" + "github.com/spf13/cobra" + "github.com/MichaelMure/git-bug/cache" - "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/util/interrupt" - "github.com/spf13/cobra" ) func ResolveUser(repo *cache.RepoCache, args []string) (*cache.IdentityCache, []string, error) { @@ -39,11 +39,7 @@ func runKey(cmd *cobra.Command, args []string) error { } for _, key := range id.Keys() { - pubkey, err := key.GetPublicKey() - if err != nil { - return err - } - fmt.Println(identity.EncodeKeyFingerprint(pubkey.Fingerprint)) + fmt.Println(key.Fingerprint()) } return nil diff --git a/commands/user_key_add.go b/commands/user_key_add.go index 3699ca336569b779ef122d7704d8942ecf0785ed..72e0ca24a4b1d480a42a96a808b7b2617e5ecf68 100644 --- a/commands/user_key_add.go +++ b/commands/user_key_add.go @@ -13,8 +13,8 @@ import ( ) var ( - keyAddArmoredFile string - keyAddArmored string + keyAddArmoredFile string + keyAddArmored string ) func runKeyAdd(cmd *cobra.Command, args []string) error { @@ -52,7 +52,7 @@ func runKeyAdd(cmd *cobra.Command, args []string) error { } } - key, err := identity.NewKey(keyAddArmored) + key, err := identity.NewKeyFromArmored(keyAddArmored) if err != nil { return err } @@ -61,9 +61,9 @@ func runKeyAdd(cmd *cobra.Command, args []string) error { if err != nil { return errors.Wrap(err, "failed to create validator") } - commitHash := validator.KeyCommitHash(key.PublicKey.KeyId) + commitHash := validator.KeyCommitHash(key.publicKey().KeyId) if commitHash != "" { - return fmt.Errorf("key id %d is already used by the key introduced in commit %s", key.PublicKey.KeyId, commitHash) + return fmt.Errorf("key id %d is already used by the key introduced in commit %s", key.publicKey.KeyId, commitHash) } err = id.Mutate(func(mutator identity.Mutator) identity.Mutator { diff --git a/commands/user_key_rm.go b/commands/user_key_rm.go index f35d0a89394ea7bde87add04ca4480649e1405fc..ff581cde05622763854a8d817b96e7016953e8c2 100644 --- a/commands/user_key_rm.go +++ b/commands/user_key_rm.go @@ -3,11 +3,12 @@ package commands import ( "fmt" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/util/interrupt" - "github.com/pkg/errors" - "github.com/spf13/cobra" ) func runKeyRm(cmd *cobra.Command, args []string) error { @@ -22,7 +23,7 @@ func runKeyRm(cmd *cobra.Command, args []string) error { return errors.New("missing key fingerprint") } - keyFingerprint := args[0] + fingerprint := args[0] args = args[1:] id, args, err := ResolveUser(backend, args) @@ -34,21 +35,10 @@ func runKeyRm(cmd *cobra.Command, args []string) error { return fmt.Errorf("unexpected arguments: %s", args) } - fingerprint, err := identity.DecodeKeyFingerprint(keyFingerprint) - if err != nil { - return errors.Wrap(err, "invalid key fingerprint") - } - var removedKey *identity.Key err = id.Mutate(func(mutator identity.Mutator) identity.Mutator { for j, key := range mutator.Keys { - pubkey, err := key.GetPublicKey() - if err != nil { - fmt.Printf("Warning: failed to decode public key: %s", err) - continue - } - - if pubkey.Fingerprint == fingerprint { + if key.Fingerprint() == fingerprint { removedKey = key copy(mutator.Keys[j:], mutator.Keys[j+1:]) mutator.Keys = mutator.Keys[:len(mutator.Keys)-1] diff --git a/commands/validate.go b/commands/validate.go index 6e4a43c52e1ebc13354d93639e61d270fdad365f..99de0e4316e6c3bdcf160a18c0b5cd575335bbda 100644 --- a/commands/validate.go +++ b/commands/validate.go @@ -24,7 +24,7 @@ func runValidate(cmd *cobra.Command, args []string) error { return err } - fmt.Printf("first commit signed with key: %s\n", identity.EncodeKeyFingerprint(validator.FirstKey.PublicKey.Fingerprint)) + fmt.Printf("first commit signed with key: %s\n", identity.encodeKeyFingerprint(validator.FirstKey.publicKey.Fingerprint)) var refErr error for _, ref := range args { diff --git a/identity/identity_test.go b/identity/identity_test.go index 05b5de2b053fb2a213765ea0561ad4323d8c3248..1d437f055f37c85620337576a6a433c8ffe649c8 100644 --- a/identity/identity_test.go +++ b/identity/identity_test.go @@ -45,7 +45,7 @@ func TestIdentityCommitLoad(t *testing.T) { name: "René Descartes", email: "rene.descartes@example.com", keys: []*Key{ - {ArmoredPublicKey: repository.CreatePubkey(t)}, + {armoredPublicKey: repository.CreatePubkey(t)}, }, }, { @@ -53,7 +53,7 @@ func TestIdentityCommitLoad(t *testing.T) { name: "René Descartes", email: "rene.descartes@example.com", keys: []*Key{ - {ArmoredPublicKey: repository.CreatePubkey(t)}, + {armoredPublicKey: repository.CreatePubkey(t)}, }, }, { @@ -61,7 +61,7 @@ func TestIdentityCommitLoad(t *testing.T) { name: "René Descartes", email: "rene.descartes@example.com", keys: []*Key{ - {ArmoredPublicKey: repository.CreatePubkey(t)}, + {armoredPublicKey: repository.CreatePubkey(t)}, }, }, }, @@ -90,7 +90,7 @@ func TestIdentityCommitLoad(t *testing.T) { name: "René Descartes", email: "rene.descartes@example.com", keys: []*Key{ - {ArmoredPublicKey: repository.CreatePubkey(t)}, + {armoredPublicKey: repository.CreatePubkey(t)}, }, }) @@ -99,7 +99,7 @@ func TestIdentityCommitLoad(t *testing.T) { name: "René Descartes", email: "rene.descartes@example.com", keys: []*Key{ - {ArmoredPublicKey: repository.CreatePubkey(t)}, + {armoredPublicKey: repository.CreatePubkey(t)}, }, }) @@ -118,7 +118,7 @@ func TestIdentityCommitLoad(t *testing.T) { func loadKeys(identity *Identity) { for _, v := range identity.versions { for _, k := range v.keys { - k.GetPublicKey() + k.publicKey() } } } @@ -139,7 +139,7 @@ func TestIdentity_ValidKeysAtTime(t *testing.T) { name: "René Descartes", email: "rene.descartes@example.com", keys: []*Key{ - {ArmoredPublicKey: "pubkeyA"}, + {armoredPublicKey: "pubkeyA"}, }, }, { @@ -147,7 +147,7 @@ func TestIdentity_ValidKeysAtTime(t *testing.T) { name: "René Descartes", email: "rene.descartes@example.com", keys: []*Key{ - {ArmoredPublicKey: "pubkeyB"}, + {armoredPublicKey: "pubkeyB"}, }, }, { @@ -155,7 +155,7 @@ func TestIdentity_ValidKeysAtTime(t *testing.T) { name: "René Descartes", email: "rene.descartes@example.com", keys: []*Key{ - {ArmoredPublicKey: "pubkeyC"}, + {armoredPublicKey: "pubkeyC"}, }, }, { @@ -163,7 +163,7 @@ func TestIdentity_ValidKeysAtTime(t *testing.T) { name: "René Descartes", email: "rene.descartes@example.com", keys: []*Key{ - {ArmoredPublicKey: "pubkeyD"}, + {armoredPublicKey: "pubkeyD"}, }, }, { @@ -171,20 +171,20 @@ func TestIdentity_ValidKeysAtTime(t *testing.T) { name: "René Descartes", email: "rene.descartes@example.com", keys: []*Key{ - {ArmoredPublicKey: "pubkeyE"}, + {armoredPublicKey: "pubkeyE"}, }, }, }, } assert.Nil(t, identity.ValidKeysAtTime(10)) - assert.Equal(t, identity.ValidKeysAtTime(100), []*Key{{ArmoredPublicKey: "pubkeyA"}}) - assert.Equal(t, identity.ValidKeysAtTime(140), []*Key{{ArmoredPublicKey: "pubkeyA"}}) - assert.Equal(t, identity.ValidKeysAtTime(200), []*Key{{ArmoredPublicKey: "pubkeyB"}}) - assert.Equal(t, identity.ValidKeysAtTime(201), []*Key{{ArmoredPublicKey: "pubkeyD"}}) - assert.Equal(t, identity.ValidKeysAtTime(202), []*Key{{ArmoredPublicKey: "pubkeyD"}}) - assert.Equal(t, identity.ValidKeysAtTime(300), []*Key{{ArmoredPublicKey: "pubkeyE"}}) - assert.Equal(t, identity.ValidKeysAtTime(3000), []*Key{{ArmoredPublicKey: "pubkeyE"}}) + assert.Equal(t, identity.ValidKeysAtTime(100), []*Key{{armoredPublicKey: "pubkeyA"}}) + assert.Equal(t, identity.ValidKeysAtTime(140), []*Key{{armoredPublicKey: "pubkeyA"}}) + assert.Equal(t, identity.ValidKeysAtTime(200), []*Key{{armoredPublicKey: "pubkeyB"}}) + assert.Equal(t, identity.ValidKeysAtTime(201), []*Key{{armoredPublicKey: "pubkeyD"}}) + assert.Equal(t, identity.ValidKeysAtTime(202), []*Key{{armoredPublicKey: "pubkeyD"}}) + assert.Equal(t, identity.ValidKeysAtTime(300), []*Key{{armoredPublicKey: "pubkeyE"}}) + assert.Equal(t, identity.ValidKeysAtTime(3000), []*Key{{armoredPublicKey: "pubkeyE"}}) } // Test the immutable or mutable metadata search diff --git a/identity/key.go b/identity/key.go index 5be1e3bb8f0703eba67e4d609daca97aba9edd1b..f5d85d0e55b06950cb96418073c25663fce3694d 100644 --- a/identity/key.go +++ b/identity/key.go @@ -2,6 +2,7 @@ package identity import ( "encoding/hex" + "encoding/json" "fmt" "strings" @@ -10,14 +11,20 @@ import ( "golang.org/x/crypto/openpgp/packet" ) +// Key hold a cryptographic public key type Key struct { // PubKey is the armored PGP public key. - ArmoredPublicKey string `json:"pub_key"` + armoredPublicKey string - PublicKey *packet.PublicKey `json:"-"` + // memoized decoded public key + publicKey *packet.PublicKey } -func NewKey(armoredPGPKey string) (*Key, error) { +type keyJSON struct { + ArmoredPublicKey string `json:"armored_pub_key"` +} + +func NewKeyFromArmored(armoredPGPKey string) (*Key, error) { publicKey, err := parsePublicKey(armoredPGPKey) if err != nil { return nil, err @@ -26,10 +33,70 @@ func NewKey(armoredPGPKey string) (*Key, error) { return &Key{armoredPGPKey, publicKey}, nil } +func (k *Key) MarshalJSON() ([]byte, error) { + return json.Marshal(keyJSON{ + ArmoredPublicKey: k.armoredPublicKey, + }) +} + +func (k *Key) UnmarshalJSON(data []byte) error { + var aux keyJSON + + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + k.armoredPublicKey = aux.ArmoredPublicKey + + return nil +} + +func (k *Key) Validate() error { + if k.publicKey != nil { + return nil + } + + publicKey, err := parsePublicKey(k.armoredPublicKey) + if err != nil { + return errors.Wrap(err, "invalid public key") + } + k.publicKey = publicKey + + return nil +} + +func (k *Key) Clone() *Key { + clone := *k + return &clone +} + +func (k *Key) publicKey() *packet.PublicKey { + if k.publicKey != nil { + return k.publicKey + } + + publicKey, err := parsePublicKey(k.armoredPublicKey) + if err != nil { + // Coding problem, a key should be validated before use + panic("invalid key: " + err.Error()) + } + + k.publicKey = publicKey + return k.publicKey +} + +func (k Key) Armored() string { + return k.armoredPublicKey +} + +func (k Key) Fingerprint() string { + return encodeKeyFingerprint(k.publicKey().Fingerprint) +} + func parsePublicKey(armoredPublicKey string) (*packet.PublicKey, error) { block, err := armor.Decode(strings.NewReader(armoredPublicKey)) if err != nil { - return nil, errors.Wrap(err, "failed to dearmor public key") + return nil, errors.Wrap(err, "failed to decode armored public key") } reader := packet.NewReader(block.Body) @@ -40,14 +107,14 @@ func parsePublicKey(armoredPublicKey string) (*packet.PublicKey, error) { publicKey, ok := p.(*packet.PublicKey) if !ok { - return nil, errors.New("got no packet.PublicKey") + return nil, errors.New("got no packet.publicKey") } return publicKey, nil } -// DecodeKeyFingerprint decodes a 40 hex digits long fingerprint into bytes. -func DecodeKeyFingerprint(keyFingerprint string) ([20]byte, error) { +// decodeKeyFingerprint decodes a 40 hex digits long fingerprint into bytes. +func decodeKeyFingerprint(keyFingerprint string) ([20]byte, error) { var fingerprint [20]byte fingerprintBytes, err := hex.DecodeString(keyFingerprint) if err != nil { @@ -60,24 +127,7 @@ func DecodeKeyFingerprint(keyFingerprint string) ([20]byte, error) { return fingerprint, nil } -func EncodeKeyFingerprint(fingerprint [20]byte) string { +// encodeKeyFingerprint encode a byte representation of a fingerprint into a 40 hex digits string. +func encodeKeyFingerprint(fingerprint [20]byte) string { return strings.ToUpper(hex.EncodeToString(fingerprint[:])) } - -func (k *Key) Validate() error { - _, err := k.GetPublicKey() - return err -} - -func (k *Key) Clone() *Key { - clone := *k - return &clone -} - -func (k *Key) GetPublicKey() (*packet.PublicKey, error) { - var err error - if k.PublicKey == nil { - k.PublicKey, err = parsePublicKey(k.ArmoredPublicKey) - } - return k.PublicKey, err -} diff --git a/identity/key_test.go b/identity/key_test.go index 7cb75e4c3f31092417a320823e3b7ed2c85d1500..d366545f4c1b7dda90a363125e7ab245a131b03d 100644 --- a/identity/key_test.go +++ b/identity/key_test.go @@ -1,9 +1,11 @@ package identity import ( + "encoding/json" "strings" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -14,8 +16,45 @@ func TestDecodeKeyFingerprint(t *testing.T) { } func checkEncodeDecodeKeyFingerprint(t *testing.T, fingerprint string) { - decoded, err := DecodeKeyFingerprint(fingerprint) + decoded, err := decodeKeyFingerprint(fingerprint) require.NoError(t, err) - require.Equal(t, fingerprint, EncodeKeyFingerprint(decoded)) + require.Equal(t, fingerprint, encodeKeyFingerprint(decoded)) } +func TestKeySerialize(t *testing.T) { + armored := `-----BEGIN PGP PUBLIC KEY BLOCK----- + +mI0EXrVFzwEEAL7roW5pBs7PYhnW8XdHuMBUrOqx+TR8JPsTLzlFFKHniJ7Cxm24 +rj+nCiVAC3yI5hEWbLYLp6HoSCAEJim+ac+LsoH0Rxz325l+EYz7nAq44rebfNuy +A5LD9/KVzLAu0FO27pgCiH9RpsFVYveHYtR1jDDvag6MLdlTZaQfqCGnABEBAAG0 +LFJlbsOpIERlc2NhcnRlcyA8cmVuZS5kZXNjYXJ0ZXNAZXhhbXBsZS5jb20+iM4E +EwEKADgWIQQpwni46BlhwjZt/3bXoSG7jO2rwwUCXrVFzwIbAwULCQgHAgYVCgkI +CwIEFgIDAQIeAQIXgAAKCRDXoSG7jO2rw5LcBACPp+2cwUcYCiHZVUAnAHokY7R0 +msjA/YryCy+rcW86TcV7KuyZjg3wCHi7DrDuvYIDXr83W2XaCoJktAW/+aENj8QH +6r7Tini3ENmNT8caqiCJGE0iY0QRXZomxAoZc5kvDq596ifoUA08ILncGla7Uq04 ++3Da+JBLWoDQvVP3xbiNBF61Rc8BBADBYKVgB1eHgXOorCeKYLCDSNwklkkdCN5u +WZygmr/EMpT7YGuAvW70WKHcd0zo+MX/3fWvJeTQDVmReNF0zJv0OSjcAsamcOQ9 +G9rdL3bKWMGJRtTeXmtZ6BkP4YU227VkFTFXvQzt8WD5CXGQJtEZRXQqHKNjNNIY +JUxF6VfJtQARAQABiLYEGAEKACAWIQQpwni46BlhwjZt/3bXoSG7jO2rwwUCXrVF +zwIbDAAKCRDXoSG7jO2rw7xEA/9TJD/M6vO160zNr7fCB31rqGUvkHWOKaeSHJmG +AvFBrNiBG+nGRjc2XbZqSaykO7ApcmLzgh8zzlB3gxZjorbEGRoEUsYD5pmZhfFl +kZyE/aXEbuTIXXcR9fyuDGvP2eF4RPth8P4ew9ycXl89IUdbapD3JKg/ptkgw8dy +y8TVdw== +=01qL +-----END PGP PUBLIC KEY BLOCK-----` + + before, err := NewKeyFromArmored(armored) + assert.NoError(t, err) + assert.NoError(t, before.Validate()) + + data, err := json.Marshal(before) + assert.NoError(t, err) + + var after Key + err = json.Unmarshal(data, &after) + assert.NoError(t, err) + assert.NoError(t, after.Validate()) + + assert.NotEmpty(t, after.Fingerprint()) + assert.Equal(t, before.Fingerprint(), after.Fingerprint()) +} diff --git a/identity/version_test.go b/identity/version_test.go index dcfe73afc93f5371d8c83635353ffbec57bb2967..1734327a632934ae36dd48bd49d6e93e254e2e96 100644 --- a/identity/version_test.go +++ b/identity/version_test.go @@ -14,10 +14,10 @@ func TestVersionSerialize(t *testing.T) { avatarURL: "avatarUrl", keys: []*Key{ { - ArmoredPublicKey: "pubkey1", + armoredPublicKey: "pubkey1", }, { - ArmoredPublicKey: "pubkey2", + armoredPublicKey: "pubkey2", }, }, nonce: makeNonce(20),