Skip to content

Commit

Permalink
Merge pull request #37 from nats-io/xkeys
Browse files Browse the repository at this point in the history
Added in xkeys support, which are encoded x25519 keys.
  • Loading branch information
derekcollison authored Dec 5, 2022
2 parents 1269927 + 4f089af commit 5d8a673
Show file tree
Hide file tree
Showing 9 changed files with 374 additions and 37 deletions.
3 changes: 0 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
language: go
arch:
- amd64
- ppc64le
go:
- 1.19.x
- 1.18.x
Expand Down
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
14 changes: 11 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,16 @@ 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")
ErrInvalidEncVersion = nkeysError("nkeys: encrypted input wrong version")
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 [user|account|server|cluster|operator|curve|x25519]
-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 5d8a673

Please sign in to comment.