Skip to content

Commit

Permalink
Added seed import. Preparing for integration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
cypherhat committed Dec 19, 2018
1 parent 361c2f3 commit d095ffe
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 29 deletions.
1 change: 1 addition & 0 deletions backend.go
Expand Up @@ -56,6 +56,7 @@ func Backend() *backend {
Help: "",
Paths: framework.PathAppend(
configPaths(&b),
importPaths(&b),
namesPaths(&b),
keysPaths(&b),
identitiesPaths(&b),
Expand Down
71 changes: 42 additions & 29 deletions path_identities.go
Expand Up @@ -243,40 +243,22 @@ func (b *backend) pathIdentitiesCreate(ctx context.Context, req *logical.Request
if err != nil {
return nil, err
}
publickey, err := pair.PublicKey()
if err != nil {
return nil, err
}
privatekey, err := pair.PrivateKey()
if err != nil {
return nil, err
}
seed, err := pair.Seed()
if err != nil {
return nil, err
}
identityJSON := &Identity{
PublicKey: publickey,
TrustedKeys: trustedKeys,
PrivateKey: string(privatekey),
Seed: string(seed),
}
defer pair.Wipe()
entry, err := logical.StorageEntryJSON(req.Path, identityJSON)

identity, err := b.storeIdentity(ctx, req, name, pair, trustedKeys)
if err != nil {
return nil, err
}

err = req.Storage.Put(ctx, entry)
err = b.crossReference(ctx, req, name, identity.PublicKey)
if err != nil {
return nil, err
}
b.crossReference(ctx, req, name, identityJSON.PublicKey)

return &logical.Response{
Data: map[string]interface{}{
"type": nkeys.Prefix(identityJSON.PublicKey).String(),
"public_key": identityJSON.PublicKey,
"trusted_keys": identityJSON.TrustedKeys,
"type": nkeys.Prefix(identity.PublicKey).String(),
"public_key": identity.PublicKey,
"trusted_keys": identity.TrustedKeys,
},
}, nil
}
Expand Down Expand Up @@ -426,13 +408,11 @@ func (b *backend) pathIdentitiesUpdate(ctx context.Context, req *logical.Request
if trustedKeysRaw, ok := data.GetOk("trusted_keys"); ok {
trustedKeys = trustedKeysRaw.([]string)
}
identity.TrustedKeys = trustedKeys
entry, err := logical.StorageEntryJSON(req.Path, identity)
pair, err := nkeys.FromSeed([]byte(identity.Seed))
if err != nil {
return nil, err
}

err = req.Storage.Put(ctx, entry)
identity, err = b.storeIdentity(ctx, req, name, pair, trustedKeys)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -557,3 +537,36 @@ func (b *backend) pathVerifySignatureByName(ctx context.Context, req *logical.Re
}, nil

}

func (b *backend) storeIdentity(ctx context.Context, req *logical.Request, name string, pair nkeys.KeyPair, trustedKeys []string) (*Identity, error) {
publickey, err := pair.PublicKey()
if err != nil {
return nil, err
}
privatekey, err := pair.PrivateKey()
if err != nil {
return nil, err
}
seed, err := pair.Seed()
if err != nil {
return nil, err
}
identity := &Identity{
PublicKey: publickey,
TrustedKeys: trustedKeys,
PrivateKey: string(privatekey),
Seed: string(seed),
}
path := fmt.Sprintf("identities/%s", name)

entry, err := logical.StorageEntryJSON(path, identity)
if err != nil {
return nil, err
}

err = req.Storage.Put(ctx, entry)
if err != nil {
return nil, err
}
return identity, nil
}
124 changes: 124 additions & 0 deletions path_import.go
@@ -0,0 +1,124 @@
// Copyright © 2018 Immutability, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"context"
"fmt"
"io/ioutil"
"path/filepath"

"github.com/nats-io/nkeys"

"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
)

const (
// TypeNkey is a file with the .nk extension
TypeNkey string = ".nk"
// TypeCreds is a file with the .creds extension
TypeCreds string = ".creds"
)

func importPaths(b *backend) []*framework.Path {
return []*framework.Path{
&framework.Path{
Pattern: "import/" + framework.GenericNameRegex("name"),
HelpSynopsis: "Import an nkey from file.",
HelpDescription: `
Reads an nkey seed from file.
`,
Fields: map[string]*framework.FieldSchema{
"name": &framework.FieldSchema{Type: framework.TypeString},
"path": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Absolute path to the keystore file - not the parent directory.",
},
},
ExistenceCheck: b.pathExistenceCheck,
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.CreateOperation: b.pathImportCreate,
},
},
}
}

func (b *backend) pathImportCreate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
_, err := b.configured(ctx, req)
if err != nil {
return nil, err
}
name := data.Get("name").(string)
var identity *Identity
identity, err = b.readIdentity(ctx, req, name)
if err != nil {
return nil, fmt.Errorf("Error reading identity")
}
if identity == nil {
keystorePath := data.Get("path").(string)
fileType := filepath.Ext(keystorePath)
switch fileType {
case TypeNkey:
seed, err := ioutil.ReadFile(keystorePath)
if err != nil {
return nil, err
}
pair, err := nkeys.FromSeed(seed)
if err != nil {
return nil, err
}
identity, err = b.storeIdentity(ctx, req, name, pair, nil)
if err != nil {
return nil, err
}
err = b.crossReference(ctx, req, name, identity.PublicKey)
if err != nil {
return nil, err
}

case TypeCreds:
_, seed, err := credsFromNkeyFile(keystorePath)
if err != nil {
return nil, err
}
pair, err := nkeys.FromSeed([]byte(seed))
if err != nil {
return nil, err
}
identity, err = b.storeIdentity(ctx, req, name, pair, nil)
if err != nil {
return nil, err
}
err = b.crossReference(ctx, req, name, identity.PublicKey)
if err != nil {
return nil, err
}

default:
return nil, fmt.Errorf("unknown file type")
}
return &logical.Response{
Data: map[string]interface{}{
"type": nkeys.Prefix(identity.PublicKey).String(),
"trusted_keys": identity.TrustedKeys,
"public_key": identity.PublicKey,
},
}, nil
}
return nil, fmt.Errorf("account %s exists", name)
}
27 changes: 27 additions & 0 deletions tests/claims.bats
Expand Up @@ -54,6 +54,33 @@
[ "$trusted_keys" = "$account_key" ]
}

@test "import ngs account" {
path=$HOME"/.nkeys/synadia/accounts/ngs/ngs.nk"
user="$(vault write -format=json nkey/import/ngs-account path=$path | jq .data)"
type="$(echo $user | jq -r .type)"
[ "$type" = "account" ]
}

@test "import ngs user" {
path=$HOME"/.nkeys/synadia/accounts/ngs/users/ngs.nk"
account_key="$(vault read -format=json nkey/identities/ngs-account | jq -r .data.public_key)"
user="$(vault write -format=json nkey/import/ngs-user path=$path | jq .data)"
user_update="$(vault write -format=json nkey/identities/ngs-user trusted_keys=$account_key | jq .data)"
type="$(echo $user | jq -r .type)"
[ "$type" = "user" ]
}

@test "create ngs user claim" {
issuer="$(vault read -format=json nkey/identities/ngs-account | jq -r .data.public_key)"
subject="$(vault read -format=json nkey/identities/ngs-user | jq -r .data.public_key)"
token="$(vault write -format=json nkey/identities/ngs-account/sign-claim subject=$subject type="user" claims=@user.json | jq -r .data.token)"
response="$(vault write -format=json nkey/identities/ngs-user/verify-claim token=$token | jq .data)"
response_issuer="$(echo $response | jq -r .issuer)"
response_subject="$(echo $response | jq -r .public_key)"
[ "$issuer" = "$response_issuer" ]
[ "$subject" = "$response_subject" ]
}

@test "create account claim" {
issuer="$(vault read -format=json nkey/identities/operator | jq -r .data.public_key)"
subject="$(vault read -format=json nkey/identities/account | jq -r .data.public_key)"
Expand Down
48 changes: 48 additions & 0 deletions util.go
Expand Up @@ -17,11 +17,18 @@ package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"regexp"

"github.com/nats-io/jwt"
"github.com/nats-io/nkeys"
)

const (
// Empty is an empty string
Empty string = ""
)

// PrefixByteFromString returns a PrefixByte from the stringified value
func PrefixByteFromString(p string) nkeys.PrefixByte {
switch p {
Expand Down Expand Up @@ -106,3 +113,44 @@ func encodeClaim(claimsType, claimsData, subject string, keyPair nkeys.KeyPair)
}
return token, nil
}

var nscDecoratedRe = regexp.MustCompile(`\s*(?:(?:[-]{3,}[^\n]*[-]{3,}\n)(.+)(?:\n\s*[-]{3,}[^\n]*[-]{3,}\n))`)

func credsFromNkeyFile(userFile string) (string, string, error) {
contents, err := ioutil.ReadFile(userFile)
if err != nil {
return "", Empty, fmt.Errorf("nats: %v", err)
}
defer wipeSlice(contents)

items := nscDecoratedRe.FindAllSubmatch(contents, -1)
if len(items) == 0 {
return "", string(contents), nil
}
// First result should be the user JWT.
// We copy here so that if the file contained a seed file too we wipe appropriately.
var jwt []byte
var nkey []byte
for i, item := range items {
switch i {
case 0:
if len(item) == 2 {
jwt = make([]byte, len(item[1]))
copy(jwt, item[1])
}
case 1:
if len(item) == 2 {
nkey = make([]byte, len(item[1]))
copy(nkey, item[1])
}
}
}
return string(jwt), string(nkey), nil
}

// Just wipe slice with 'x', for clearing contents of nkey seed file.
func wipeSlice(buf []byte) {
for i := range buf {
buf[i] = 'x'
}
}

0 comments on commit d095ffe

Please sign in to comment.