Skip to content

Commit

Permalink
Added in xkeys support, which are encoded x25519 keys.
Browse files Browse the repository at this point in the history
Also added in support for nacl.Box compatibility for Seal and Open. We pick a random nonce for the user via a random source.
We may add in other ways to encrypt data for larger messages and multiple recipients in the future.

Signed-off-by: Derek Collison <derek@nats.io>
  • Loading branch information
derekcollison committed Dec 3, 2022
1 parent 31a0779 commit bd43791
Show file tree
Hide file tree
Showing 8 changed files with 361 additions and 34 deletions.
2 changes: 1 addition & 1 deletion creds_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func Test_ParseDecoratedJWTBad(t *testing.T) {
func Test_ParseDecoratedSeedBad(t *testing.T) {
if _, err := ParseDecoratedNKey([]byte("foo")); err == nil {
t.Fatal("Expected error")
} else if err.Error() != "no nkey seed found" {
} else if err.Error() != "nkeys: no nkey seed found" {
t.Fatal(err)
}
}
Expand Down
13 changes: 10 additions & 3 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
ErrInvalidPrefixByte = nkeysError("nkeys: invalid prefix byte")
ErrInvalidKey = nkeysError("nkeys: invalid key")
ErrInvalidPublicKey = nkeysError("nkeys: invalid public key")
ErrInvalidPrivateKey = nkeysError("nkeys: invalid private key")
ErrInvalidSeedLen = nkeysError("nkeys: invalid seed length")
ErrInvalidSeed = nkeysError("nkeys: invalid seed")
ErrInvalidEncoding = nkeysError("nkeys: invalid encoded key")
Expand All @@ -26,9 +27,15 @@ const (
ErrPublicKeyOnly = nkeysError("nkeys: no seed or private key available")
ErrIncompatibleKey = nkeysError("nkeys: incompatible key")
ErrInvalidChecksum = nkeysError("nkeys: invalid checksum")
ErrNoSeedFound = nkeysError("no nkey seed found")
ErrInvalidNkeySeed = nkeysError("doesn't contain a seed nkey")
ErrInvalidUserSeed = nkeysError("doesn't contain an user seed nkey")
ErrNoSeedFound = nkeysError("nkeys: no nkey seed found")
ErrInvalidNkeySeed = nkeysError("nkeys: doesn't contain a seed nkey")
ErrInvalidUserSeed = nkeysError("nkeys: doesn't contain an user seed nkey")
ErrInvalidRecipient = nkeysError("nkeys: not a valid recipient public curve key")
ErrInvalidSender = nkeysError("nkeys: not a valid sender public curve key")
ErrInvalidCurveKey = nkeysError("nkeys: not a valid curve key")
ErrInvalidCurveSeed = nkeysError("nkeys: not a valid curve seed")
ErrInvalidEncrypted = nkeysError("nkeys: encrypted input is not valid")
ErrCouldNotDecrypt = nkeysError("nkeys: could not decrypt input")
)

type nkeysError string
Expand Down
19 changes: 15 additions & 4 deletions keypair.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2018 The NATS Authors
// Copyright 2018-2022 The NATS Authors
// 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
Expand Down Expand Up @@ -26,11 +26,22 @@ type kp struct {
seed []byte
}

// CreatePair will create a KeyPair based on the rand entropy and a type/prefix byte. rand can be nil.
// All seeds are 32 bytes long.
const seedLen = 32

// CreatePair will create a KeyPair based on the rand entropy and a type/prefix byte.
func CreatePair(prefix PrefixByte) (KeyPair, error) {
var rawSeed [32]byte
return CreatePairWithRand(prefix, rand.Reader)
}

// CreatePair will create a KeyPair based on the rand reader and a type/prefix byte. rand can be nil.
func CreatePairWithRand(prefix PrefixByte, rr io.Reader) (KeyPair, error) {
if rr == nil {
rr = rand.Reader
}
var rawSeed [seedLen]byte

_, err := io.ReadFull(rand.Reader, rawSeed[:])
_, err := io.ReadFull(rr, rawSeed[:])
if err != nil {
return nil, err
}
Expand Down
62 changes: 44 additions & 18 deletions nk/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"encoding/base64"
"flag"
"fmt"
"io"
"log"
"os"
"runtime"
Expand All @@ -32,16 +31,35 @@ import (
// this will be set during compilation when a release is made on tools
var Version string

const defaultVanMax = 10_000_000

func usage() {
log.Fatalf("Usage: nk [-v] [-gen type] [-sign file] [-verify file] [-inkey keyfile] [-pubin keyfile] [-sigfile file] [-pubout] [-e entropy] [-pre vanity]\n")
log.Fatalf(`Usage: nk [options]
-v Show version
-gen <type> Generate key for <type>, e.g. nk -gen user
-sign <file> Sign <file> with -inkey <keyfile>
-verify <file> Verfify <file> with -inkey <keyfile> or -pubin <public> and -sigfile <file>
-inkey <file> Input key file (seed/private key)
-pubin <file> Public key file
-sigfile <file> Signature file
-pubout Output public key
-e Entropy file, e.g. /dev/urandom
-pre <vanity> Attempt to generate public key given prefix, e.g. nk -gen user -pre derek
-maxpre <N> Maximum attempts at generating the correct key prefix, default is 10,000,000
`)
}

type KeyPair interface {
Seed() ([]byte, error)
PublicKey() (string, error)
}

func main() {
var entropy = flag.String("e", "", "Entropy file, e.g. /dev/urandom")
var keyFile = flag.String("inkey", "", "Input key file (seed/private key)")
var pubFile = flag.String("pubin", "", "Public key file")

var signFile = flag.String("sign", "", "Sign <file> with -inkey <key>")
var signFile = flag.String("sign", "", "Sign <file> with -inkey <keyfile>")
var sigFile = flag.String("sigfile", "", "Signature file")

var verifyFile = flag.String("verify", "", "Verfify <file> with -inkey <keyfile> or -pubin <public> and -sigfile <file>")
Expand All @@ -51,7 +69,7 @@ func main() {

var version = flag.Bool("v", false, "Show version")
var vanPre = flag.String("pre", "", "Attempt to generate public key given prefix, e.g. nk -gen user -pre derek")
var vanMax = flag.Int("maxpre", 10000000, "Maximum attempts at generating the correct key prefix")
var vanMax = flag.Int("maxpre", defaultVanMax, "Maximum attempts at generating the correct key prefix")

log.SetFlags(0)
log.SetOutput(os.Stdout)
Expand All @@ -65,7 +83,7 @@ func main() {

// Create Key
if *keyType != "" {
var kp nkeys.KeyPair
var kp KeyPair
// Check to see if we are trying to do a vanity public key.
if *vanPre != "" {
kp = createVanityKey(*keyType, *vanPre, *entropy, *vanMax)
Expand All @@ -82,6 +100,7 @@ func main() {
log.Printf("%s", pub)
}
return

}

if *entropy != "" {
Expand Down Expand Up @@ -202,13 +221,16 @@ func preForType(keyType string) nkeys.PrefixByte {
return nkeys.PrefixByteCluster
case "operator":
return nkeys.PrefixByteOperator
case "curve", "x25519":
return nkeys.PrefixByteCurve

default:
log.Fatalf("Usage: nk -gen [user|account|server|cluster|operator]\n")
log.Fatalf("Usage: nk -gen [user|account|server|cluster|operator|curve|x25519]\n")
}
return nkeys.PrefixByte(0)
}

func genKeyPair(pre nkeys.PrefixByte, entropy string) nkeys.KeyPair {
func genKeyPair(pre nkeys.PrefixByte, entropy string) KeyPair {
// See if we override entropy.
ef := rand.Reader
if entropy != "" {
Expand All @@ -219,22 +241,26 @@ func genKeyPair(pre nkeys.PrefixByte, entropy string) nkeys.KeyPair {
ef = r
}

// Create raw seed from source or random.
var rawSeed [32]byte
_, err := io.ReadFull(ef, rawSeed[:]) // Or some other random source.
if err != nil {
log.Fatalf("Error reading from %s: %v", ef, err)
}
kp, err := nkeys.FromRawSeed(pre, rawSeed[:])
if err != nil {
log.Fatalf("Error creating %c: %v", pre, err)
var kp KeyPair
var err error

if pre == nkeys.PrefixByteCurve {
kp, err = nkeys.CreateCurveKeysWithRand(ef)
if err != nil {
log.Fatalf("Error creating %c: %v", pre, err)
}
} else {
kp, err = nkeys.CreatePairWithRand(pre, ef)
if err != nil {
log.Fatalf("Error creating %c: %v", pre, err)
}
}
return kp
}

var b32Enc = base32.StdEncoding.WithPadding(base32.NoPadding)

func createVanityKey(keyType, vanity, entropy string, max int) nkeys.KeyPair {
func createVanityKey(keyType, vanity, entropy string, max int) KeyPair {
spinners := []rune(`⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏`)
pre := preForType(keyType)
vanity = strings.ToUpper(vanity)
Expand All @@ -251,7 +277,7 @@ func createVanityKey(keyType, vanity, entropy string, max int) nkeys.KeyPair {
defer close(wch)

// Found solution
found := make(chan nkeys.KeyPair)
found := make(chan KeyPair)

// Start NumCPU go routines.
for i := 0; i < ncpu; i++ {
Expand Down
3 changes: 2 additions & 1 deletion nkeys.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@

// Package nkeys is an Ed25519 based public-key signature system that simplifies keys and seeds
// and performs signing and verification.
// It also supports encryption via x25519 keys and is compatible with https://pkg.go.dev/golang.org/x/crypto/nacl/box.
package nkeys

// Version is our current version
const Version = "0.3.0"
const Version = "0.4.0-beta"

// KeyPair provides the central interface to nkeys.
type KeyPair interface {
Expand Down
26 changes: 19 additions & 7 deletions strkey.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2018 The NATS Authors
// Copyright 2018-2022 The NATS Authors
// 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
Expand All @@ -17,7 +17,6 @@ import (
"bytes"
"encoding/base32"
"encoding/binary"
"golang.org/x/crypto/ed25519"
)

// PrefixByte is a lead byte representing the type.
Expand Down Expand Up @@ -45,8 +44,11 @@ const (
// PrefixByteUser is the version byte used for encoded NATS Users
PrefixByteUser PrefixByte = 20 << 3 // Base32-encodes to 'U...'

// PrefixByteCurve is the version byte used for encoded CurveKeys (X25519)
PrefixByteCurve PrefixByte = 23 << 3 // Base32-encodes to 'X...'

// PrefixByteUnknown is for unknown prefixes.
PrefixByteUnknown PrefixByte = 23 << 3 // Base32-encodes to 'X...'
PrefixByteUnknown PrefixByte = 25 << 3 // Base32-encodes to 'Z...'
)

// Set our encoding to not include padding '=='
Expand Down Expand Up @@ -83,12 +85,13 @@ func Encode(prefix PrefixByte, src []byte) ([]byte, error) {
}

// EncodeSeed will encode a raw key with the prefix and then seed prefix and crc16 and then base32 encoded.
// `src` must be 32 bytes long (ed25519.SeedSize).
func EncodeSeed(public PrefixByte, src []byte) ([]byte, error) {
if err := checkValidPublicPrefixByte(public); err != nil {
return nil, err
}

if len(src) != ed25519.SeedSize {
if len(src) != seedLen {
return nil, ErrInvalidSeedLen
}

Expand Down Expand Up @@ -161,7 +164,8 @@ func Decode(expectedPrefix PrefixByte, src []byte) ([]byte, error) {
if err != nil {
return nil, err
}
if prefix := PrefixByte(raw[0]); prefix != expectedPrefix {
b1 := raw[0] & 248 // 248 = 11111000
if prefix := PrefixByte(b1); prefix != expectedPrefix {
return nil, ErrInvalidPrefixByte
}
return raw[1:], nil
Expand Down Expand Up @@ -248,12 +252,18 @@ func IsValidPublicOperatorKey(src string) bool {
return err == nil
}

// IsValidPublicCurveKey will decode and verify the string is a valid encoded Public Curve Key.
func IsValidPublicCurveKey(src string) bool {
_, err := Decode(PrefixByteCurve, []byte(src))
return err == nil
}

// checkValidPrefixByte returns an error if the provided value
// is not one of the defined valid prefix byte constants.
func checkValidPrefixByte(prefix PrefixByte) error {
switch prefix {
case PrefixByteOperator, PrefixByteServer, PrefixByteCluster,
PrefixByteAccount, PrefixByteUser, PrefixByteSeed, PrefixBytePrivate:
PrefixByteAccount, PrefixByteUser, PrefixByteSeed, PrefixBytePrivate, PrefixByteCurve:
return nil
}
return ErrInvalidPrefixByte
Expand All @@ -263,7 +273,7 @@ func checkValidPrefixByte(prefix PrefixByte) error {
// is not one of the public defined valid prefix byte constants.
func checkValidPublicPrefixByte(prefix PrefixByte) error {
switch prefix {
case PrefixByteServer, PrefixByteCluster, PrefixByteOperator, PrefixByteAccount, PrefixByteUser:
case PrefixByteOperator, PrefixByteServer, PrefixByteCluster, PrefixByteAccount, PrefixByteUser, PrefixByteCurve:
return nil
}
return ErrInvalidPrefixByte
Expand All @@ -285,6 +295,8 @@ func (p PrefixByte) String() string {
return "seed"
case PrefixBytePrivate:
return "private"
case PrefixByteCurve:
return "x25519"
}
return "unknown"
}
Expand Down
Loading

0 comments on commit bd43791

Please sign in to comment.