Repository: nats-io/nsc (go)

Issue:
adding user with a public key doesn't check if it already stores the private key - so creds creation fails.
If the key exists (created `nsc generate nkey --store`)

Pull request:
FIX #246 account/user with stored keys
creating a user or a managed account by specifying a public key that is stored in the keystore will fail generation of creds or creation of the account.

Patch:
diff --git a/cmd/adduser.go b/cmd/adduser.go
index 269f7ed..043f090 100644
--- a/cmd/adduser.go
+++ b/cmd/adduser.go
@@ -281,6 +281,8 @@ func (p *AddUserParams) Run(ctx ActionCtx) (store.Status, error) {
 	// if they gave us a seed, it stored - try to get it
 	ks := ctx.StoreCtx().KeyStore
 	if ks.HasPrivateKey(pk) {
+		// we may have it - but the key we got is possibly a pub only - resolve it from the store.
+		p.kp, _ = ks.GetKeyPair(pk)
 		d, err := GenerateConfig(ctx.StoreCtx().Store, p.AccountContextParams.Name, p.userName, p.kp)
 		if err != nil {
 			r.AddError("unable to save creds: %v", err)
diff --git a/cmd/signerparams.go b/cmd/signerparams.go
index 818c72b..c853e00 100644
--- a/cmd/signerparams.go
+++ b/cmd/signerparams.go
@@ -174,6 +174,28 @@ func (p *SignerParams) Resolve(ctx ActionCtx) error {
 
 func (p *SignerParams) ForceManagedAccountKey(ctx ActionCtx, kp nkeys.KeyPair) {
 	if ctx.StoreCtx().Store.IsManaged() && p.signerKP == nil {
+		// use the account as the signer
 		p.signerKP = kp
+		// check we have a private key available
+		pk, _ := p.signerKP.PrivateKey()
+		if pk == nil {
+			// try to load it
+			pub, _ := p.signerKP.PublicKey()
+			kp, err := ctx.StoreCtx().KeyStore.GetKeyPair(pub)
+			if err == nil {
+				pk, _ := kp.PrivateKey()
+				if pk != nil {
+					p.signerKP = kp
+				}
+			}
+		}
+	}
+}
+
+func (p *SignerParams) canSign(nk nkeys.KeyPair) bool {
+	if nk == nil {
+		return false
 	}
+	pk, _ := nk.PrivateKey()
+	return pk != nil
 }


Test patch:
diff --git a/cmd/addaccount_test.go b/cmd/addaccount_test.go
index 155e900..8d92d1f 100644
--- a/cmd/addaccount_test.go
+++ b/cmd/addaccount_test.go
@@ -18,6 +18,8 @@
 package cmd
 
 import (
+	"fmt"
+	"strings"
 	"testing"
 
 	"github.com/nats-io/jwt"
@@ -159,3 +161,33 @@ func Test_AddAccountNameArg(t *testing.T) {
 	_, err = ts.Store.ReadAccountClaim("A")
 	require.NoError(t, err)
 }
+
+func Test_AddAccountWithExistingKey(t *testing.T) {
+	ts := NewTestStore(t, "O")
+	defer ts.Done(t)
+	_, stderr, err := ExecuteCmd(createGenerateNKeyCmd(), "--account", "--store")
+	require.NoError(t, err)
+	pk := strings.Split(stderr, "\n")[1]
+	_, _, err = ExecuteCmd(CreateAddAccountCmd(), "A", "--public-key", pk)
+	require.NoError(t, err)
+}
+
+func Test_AddManagedAccountWithExistingKey(t *testing.T) {
+	as, m := RunTestAccountServer(t)
+	defer as.Close()
+
+	ts := NewTestStoreWithOperatorJWT(t, string(m["operator"]))
+	defer ts.Done(t)
+
+	_, stderr, err := ExecuteCmd(createGenerateNKeyCmd(), "--account", "--store")
+	require.NoError(t, err)
+	pk := strings.Split(stderr, "\n")[1]
+	_, _, err = ExecuteCmd(CreateAddAccountCmd(), "A", "--public-key", pk)
+	require.NoError(t, err)
+
+	// inspect the pushed JWT before it was resigned
+	ac, err := jwt.DecodeAccountClaims(string(m[fmt.Sprintf("SRC_%s", pk)]))
+	require.NoError(t, err)
+	require.Equal(t, pk, ac.Subject)
+	require.Equal(t, pk, ac.Issuer)
+}
diff --git a/cmd/adduser_test.go b/cmd/adduser_test.go
index 2b582d6..a778442 100644
--- a/cmd/adduser_test.go
+++ b/cmd/adduser_test.go
@@ -18,6 +18,7 @@ package cmd
 import (
 	"io/ioutil"
 	"path/filepath"
+	"strings"
 	"testing"
 	"time"
 
@@ -327,3 +328,15 @@ func Test_AddUserWithInteractiveCustomKey(t *testing.T) {
 	require.Empty(t, uc.IssuerAccount)
 	require.False(t, ts.KeyStore.HasPrivateKey(pk))
 }
+
+func Test_AddUserWithExistingNkey(t *testing.T) {
+	ts := NewTestStore(t, "O")
+	defer ts.Done(t)
+	ts.AddAccount(t, "A")
+
+	_, stderr, err := ExecuteCmd(createGenerateNKeyCmd(), "--user", "--store")
+	require.NoError(t, err)
+	pk := strings.Split(stderr, "\n")[1]
+	_, _, err = ExecuteCmd(CreateAddUserCmd(), "U", "--public-key", pk)
+	require.NoError(t, err)
+}
diff --git a/cmd/util_test.go b/cmd/util_test.go
index 26765cb..73fe4cc 100644
--- a/cmd/util_test.go
+++ b/cmd/util_test.go
@@ -701,6 +701,10 @@ func RunTestAccountServerWithOperatorKP(t *testing.T, okp nkeys.KeyPair) (*httpt
 				ok = ac.SigningKeys.Contains(ac.Issuer)
 			}
 
+			// store a copy of the source jwt that we can inspect
+			orig := fmt.Sprintf("SRC_%s", ac.Subject)
+			storage[orig] = body
+
 			if ok {
 				ac.Limits.Conn = -1
 				ac.Limits.Data = -1