Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom PrefixByte Support #66

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package nkeys
// Errors
const (
ErrInvalidPrefixByte = nkeysError("nkeys: invalid prefix byte")
ErrDuplicatePrefixByte = nkeysError("nkeys: prefix byte already present")
ErrInvalidKey = nkeysError("nkeys: invalid key")
ErrInvalidPublicKey = nkeysError("nkeys: invalid public key")
ErrInvalidPrivateKey = nkeysError("nkeys: invalid private key")
Expand Down
75 changes: 73 additions & 2 deletions nkeys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ func TestValidateKeyPairRole(t *testing.T) {
t.Fatal(err)
}

var keyroles = []struct {
keyroles := []struct {
kp KeyPair
roles []PrefixByte
ok bool
Expand All @@ -685,7 +685,6 @@ func TestValidateKeyPairRole(t *testing.T) {
}
if err != nil && e.ok {
t.Fatalf("test %q should have not failed: %v", e.name, err)

}
if err != nil && !e.ok && err != ErrIncompatibleKey {
t.Fatalf("unexpected error type for %q: %v", e.name, err)
Expand Down Expand Up @@ -729,3 +728,75 @@ func TestSealOpen(t *testing.T) {
testSealOpen(t, PrefixByteAccount)
testSealOpen(t, PrefixByteUser)
}

func TestCustomPublicPrefix(t *testing.T) {
var modulePrefix PrefixByte = 12 << 3 // Base32-encodes to 'M...'

AddPublicPrefix(modulePrefix, "module")
if v := modulePrefix.String(); v != "module" {
t.Fatalf("Expected 'module', got %v", v)
}

testSealOpen(t, modulePrefix)

module, err := CreatePair(modulePrefix)
if err != nil {
t.Fatalf("Expected non-nill error on CreatePair with custom prefix, received %v", err)
}

if module == nil {
t.Fatal("Expect a non-nil keypair")
}

seed, err := module.Seed()
if err != nil {
t.Fatalf("Unexpected error retrieving seed: %v", err)
}

_, err = Decode(PrefixByteSeed, seed)
if err != nil {
t.Fatalf("Expected a proper seed string, got %s", seed)
}

// Check Public
public, err := module.PublicKey()
if err != nil {
t.Fatalf("Received an error retrieving public key: %v", err)
}
if public[0] != 'M' {
t.Fatalf("Expected a prefix of 'M' but got %c", public[0])
}

if _, err := Decode(modulePrefix, []byte(public)); err != nil {
t.Fatalf("Not a valid public key")
}

// Check Private
private, err := module.PrivateKey()
if err != nil {
t.Fatalf("Received an error retrieving private key: %v", err)
}
if private[0] != 'P' {
t.Fatalf("Expected a prefix of 'P' but got %v", private[0])
}

// Check Sign and Verify
data := []byte("Hello World")
sig, err := module.Sign(data)
if err != nil {
t.Fatalf("Unexpected error signing from custom prefix: %v", err)
}
if len(sig) != ed25519.SignatureSize {
t.Fatalf("Expected signature size of %d but got %d",
ed25519.SignatureSize, len(sig))
}
err = module.Verify(data, sig)
if err != nil {
t.Fatalf("Unexpected error verifying signature: %v", err)
}

RemovePublicPrefix(modulePrefix)
if v := modulePrefix.String(); v != "unknown" {
t.Fatalf("Expected 'unknown', got %v", v)
}
}
66 changes: 43 additions & 23 deletions strkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,41 @@ const (
PrefixByteUnknown PrefixByte = 25 << 3 // Base32-encodes to 'Z...'
)

var publicPrefixes = map[PrefixByte]string{
PrefixByteOperator: "operator",
PrefixByteServer: "server",
PrefixByteCluster: "cluster",
PrefixByteAccount: "account",
PrefixByteUser: "user",
PrefixByteCurve: "x25519",
}

var privatePrefixes = map[PrefixByte]string{
PrefixByteSeed: "seed",
PrefixBytePrivate: "private",
}

// Set our encoding to not include padding '=='
var b32Enc = base32.StdEncoding.WithPadding(base32.NoPadding)

// AddPublicPrefix adds a public prefix byte. Must not collide with existing prefixes.
func AddPublicPrefix(prefix PrefixByte, name string) error {
if _, ok := publicPrefixes[prefix]; ok {
return ErrDuplicatePrefixByte
}
publicPrefixes[prefix] = name
return nil
}

// RemovePublicPrefix removes a public prefix byte. Must be a valid prefix.
func RemovePublicPrefix(prefix PrefixByte) error {
if _, ok := publicPrefixes[prefix]; !ok {
return ErrInvalidPrefixByte
}
delete(publicPrefixes, prefix)
return nil
}

// Encode will encode a raw key or seed with the prefix and crc16 and then base32 encoded.
func Encode(prefix PrefixByte, src []byte) ([]byte, error) {
if err := checkValidPrefixByte(prefix); err != nil {
Expand Down Expand Up @@ -257,43 +289,31 @@ func IsValidPublicCurveKey(src string) bool {
// 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, PrefixByteCurve:
if _, ok := privatePrefixes[prefix]; ok {
return nil
}
return ErrInvalidPrefixByte

return checkValidPublicPrefixByte(prefix)
}

// checkValidPublicPrefixByte returns an error if the provided value
// is not one of the public defined valid prefix byte constants.
func checkValidPublicPrefixByte(prefix PrefixByte) error {
switch prefix {
case PrefixByteOperator, PrefixByteServer, PrefixByteCluster, PrefixByteAccount, PrefixByteUser, PrefixByteCurve:
if _, ok := publicPrefixes[prefix]; ok {
return nil
}
return ErrInvalidPrefixByte
}

func (p PrefixByte) String() string {
switch p {
case PrefixByteOperator:
return "operator"
case PrefixByteServer:
return "server"
case PrefixByteCluster:
return "cluster"
case PrefixByteAccount:
return "account"
case PrefixByteUser:
return "user"
case PrefixByteSeed:
return "seed"
case PrefixBytePrivate:
return "private"
case PrefixByteCurve:
return "x25519"
if v, ok := privatePrefixes[p]; ok {
return v
}

if v, ok := publicPrefixes[p]; ok {
return v
}

return "unknown"
}

Expand Down