From b1af716edc321ab8ae1c9c80663ef9e84a94777d Mon Sep 17 00:00:00 2001 From: Lucas Fontes Date: Fri, 19 Jul 2024 12:08:07 -0300 Subject: [PATCH 1/3] Custom Public Prefix Byte Support Signed-off-by: Lucas Fontes --- errors.go | 1 + strkey.go | 37 +++++++++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/errors.go b/errors.go index a30bb96..0b52f61 100644 --- a/errors.go +++ b/errors.go @@ -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") diff --git a/strkey.go b/strkey.go index 8ae3311..9d78313 100644 --- a/strkey.go +++ b/strkey.go @@ -51,9 +51,29 @@ const ( PrefixByteUnknown PrefixByte = 25 << 3 // Base32-encodes to 'Z...' ) +var publicPrefixes = []PrefixByte{ + PrefixByteOperator, PrefixByteServer, PrefixByteCluster, PrefixByteAccount, PrefixByteUser, PrefixByteCurve, +} + +var privatePrefixes = []PrefixByte{ + PrefixByteSeed, PrefixBytePrivate, +} + // Set our encoding to not include padding '==' var b32Enc = base32.StdEncoding.WithPadding(base32.NoPadding) +func AllowPublicPrefix(prefix PrefixByte) error { + for _, p := range publicPrefixes { + if prefix == p { + return ErrDuplicatePrefixByte + } + } + + publicPrefixes = append(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 { @@ -257,20 +277,21 @@ 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: - return nil + for _, p := range privatePrefixes { + if prefix == p { + 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: - return nil + for _, p := range publicPrefixes { + if prefix == p { + return nil + } } return ErrInvalidPrefixByte } From 05b5a997fd25646ace30b7ffc9e506e89c8cddc9 Mon Sep 17 00:00:00 2001 From: Lucas Fontes Date: Fri, 19 Jul 2024 12:32:44 -0300 Subject: [PATCH 2/3] Tests & Making it dynamic Signed-off-by: Lucas Fontes --- nkeys_test.go | 19 ++++++++++++-- strkey.go | 71 +++++++++++++++++++++++++-------------------------- 2 files changed, 52 insertions(+), 38 deletions(-) diff --git a/nkeys_test.go b/nkeys_test.go index e0db328..16abfd1 100644 --- a/nkeys_test.go +++ b/nkeys_test.go @@ -659,7 +659,7 @@ func TestValidateKeyPairRole(t *testing.T) { t.Fatal(err) } - var keyroles = []struct { + keyroles := []struct { kp KeyPair roles []PrefixByte ok bool @@ -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) @@ -729,3 +728,19 @@ func TestSealOpen(t *testing.T) { testSealOpen(t, PrefixByteAccount) testSealOpen(t, PrefixByteUser) } + +func TestCustomPublicPrefix(t *testing.T) { + var prefixModule PrefixByte = 12 << 3 // Base32-encodes to 'M...' + AddPublicPrefix(prefixModule, "module") + testSealOpen(t, prefixModule) + + if v := prefixModule.String(); v != "module" { + t.Fatalf("Expected 'module', got %v", v) + } + + RemovePublicPrefix(prefixModule) + + if v := prefixModule.String(); v != "unknown" { + t.Fatalf("Expected 'unknown', got %v", v) + } +} diff --git a/strkey.go b/strkey.go index 9d78313..e5eca01 100644 --- a/strkey.go +++ b/strkey.go @@ -51,26 +51,38 @@ const ( PrefixByteUnknown PrefixByte = 25 << 3 // Base32-encodes to 'Z...' ) -var publicPrefixes = []PrefixByte{ - PrefixByteOperator, PrefixByteServer, PrefixByteCluster, PrefixByteAccount, PrefixByteUser, PrefixByteCurve, +var publicPrefixes = map[PrefixByte]string{ + PrefixByteOperator: "operator", + PrefixByteServer: "server", + PrefixByteCluster: "cluster", + PrefixByteAccount: "account", + PrefixByteUser: "user", + PrefixByteCurve: "x25519", } -var privatePrefixes = []PrefixByte{ - PrefixByteSeed, PrefixBytePrivate, +var privatePrefixes = map[PrefixByte]string{ + PrefixByteSeed: "seed", + PrefixBytePrivate: "private", } // Set our encoding to not include padding '==' var b32Enc = base32.StdEncoding.WithPadding(base32.NoPadding) -func AllowPublicPrefix(prefix PrefixByte) error { - for _, p := range publicPrefixes { - if prefix == p { - return ErrDuplicatePrefixByte - } +// 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 +} - publicPrefixes = append(publicPrefixes, prefix) - +// 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 } @@ -277,44 +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 { - for _, p := range privatePrefixes { - if prefix == p { - return nil - } + if _, ok := privatePrefixes[prefix]; ok { + return nil } + 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 { - for _, p := range publicPrefixes { - if prefix == p { - return nil - } + 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" } From bfbb520af89abcc6a9777a759bcbe9ac313ec63b Mon Sep 17 00:00:00 2001 From: Lucas Fontes Date: Fri, 19 Jul 2024 14:51:57 -0300 Subject: [PATCH 3/3] Full tests Signed-off-by: Lucas Fontes --- nkeys_test.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/nkeys_test.go b/nkeys_test.go index 16abfd1..1693617 100644 --- a/nkeys_test.go +++ b/nkeys_test.go @@ -730,17 +730,73 @@ func TestSealOpen(t *testing.T) { } func TestCustomPublicPrefix(t *testing.T) { - var prefixModule PrefixByte = 12 << 3 // Base32-encodes to 'M...' - AddPublicPrefix(prefixModule, "module") - testSealOpen(t, prefixModule) + var modulePrefix PrefixByte = 12 << 3 // Base32-encodes to 'M...' - if v := prefixModule.String(); v != "module" { + AddPublicPrefix(modulePrefix, "module") + if v := modulePrefix.String(); v != "module" { t.Fatalf("Expected 'module', got %v", v) } - RemovePublicPrefix(prefixModule) + testSealOpen(t, modulePrefix) - if v := prefixModule.String(); v != "unknown" { + 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) } }