diff options
author | Rasmus Dahlberg <rasmus@rgdd.se> | 2022-10-13 17:47:14 +0200 |
---|---|---|
committer | Rasmus Dahlberg <rasmus@rgdd.se> | 2022-10-13 17:54:34 +0200 |
commit | 0f006662e14f0f3c863caab227832fede572b9a0 (patch) | |
tree | 19a678020b3999ff665f4590c10d9517fac793d9 /pkg | |
parent | 5ed1052e29b0eabb80bac387024c12b0a739a44d (diff) |
Add onion address parsing
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/oaddr/oaddr.go | 58 | ||||
-rw-r--r-- | pkg/oaddr/oaddr_test.go | 78 |
2 files changed, 136 insertions, 0 deletions
diff --git a/pkg/oaddr/oaddr.go b/pkg/oaddr/oaddr.go new file mode 100644 index 0000000..f065ec9 --- /dev/null +++ b/pkg/oaddr/oaddr.go @@ -0,0 +1,58 @@ +// Package oaddr provides onion address formatting +package oaddr + +import ( + "crypto" + "crypto/ed25519" + "encoding/base32" + "fmt" + "strings" + + "golang.org/x/crypto/sha3" +) + +// OnionAddress is an Ed25519 public key that represents a v3 onion address +type OnionAddress [ed25519.PublicKeySize]byte + +// New outputs an onion address from a public key as defined in RFC 8032 +func New(pub []byte) (addr OnionAddress, err error) { + if got, want := len(pub), ed25519.PublicKeySize; got != want { + return addr, fmt.Errorf("invalid public key size: %d", got) + } + + copy(addr[:], pub) + return addr, nil +} + +// NewFromSigner outputs an onion address for a given signer +func NewFromSigner(s crypto.Signer) (addr OnionAddress, err error) { + switch t := s.Public().(type) { + case ed25519.PublicKey: + addr, err = New(s.Public().(ed25519.PublicKey)) + default: + err = fmt.Errorf("unknown key type: %v", t) + } + return +} + +// String formats addr as defined in rend-spec-v3, see: +// https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt#n2160 +func (addr OnionAddress) String() string { + b := addr[:] + b = append(b, addr.checksum()...) + b = append(b, addr.version()...) + return strings.ToLower(base32.StdEncoding.EncodeToString(b)) + ".onion" +} + +func (addr OnionAddress) checksum() []byte { + h := sha3.New256() + h.Write([]byte(".onion checksum")) + h.Write(addr[:]) + h.Write(addr.version()) + sum := h.Sum(nil) + return sum[:2] +} + +func (addr OnionAddress) version() []byte { + return []byte{0x03} +} diff --git a/pkg/oaddr/oaddr_test.go b/pkg/oaddr/oaddr_test.go new file mode 100644 index 0000000..a37d4de --- /dev/null +++ b/pkg/oaddr/oaddr_test.go @@ -0,0 +1,78 @@ +package oaddr + +import ( + "crypto" + "crypto/ed25519" + "testing" + + "sauteed-onions.org/onion-csr/internal/testonly" +) + +const ( + testPriv = "a4007fabb23fae0f50fc45481553bf7d5d26b9fd8d76142c572606a6ebd7a2c1" + testPub = "da67efa9e06e724d999f8e0409b4a5a08aebd26005b63bef90d51a241d631cfd" + testAddr = "3jt67kpanzze3gm7rycatnffucfoxutaaw3dx34q2uncihlddt6tq3ad.onion" +) + +func TestNew(t *testing.T) { + for _, table := range []struct { + desc string + pub []byte + want OnionAddress + }{ + {"too short key", testonly.DecodeHex(t, testPub)[1:], newAddr(t, testPub)}, + {"too long key", append(testonly.DecodeHex(t, testPub), 0xff), newAddr(t, testPub)}, + {"valid", testonly.DecodeHex(t, testPub), newAddr(t, testPub)}, + } { + addr, err := New(table.pub) + if got, want := err != nil, table.desc != "valid"; got != want { + t.Errorf("%s: got error %v but wanted %v: %v", table.desc, got, want, err) + } + if err != nil { + continue + } + if got, want := addr, table.want; got != want { + t.Errorf("%s: got address\n%x\nbut wanted\n%x", table.desc, got[:], want[:]) + } + } +} + +func TestNewFromSigner(t *testing.T) { + for _, table := range []struct { + desc string + priv crypto.Signer + want OnionAddress + }{ + {"rsa key", testonly.RSAPriv(t), OnionAddress{}}, + {"valid", testonly.Ed25519Priv(t, testPriv), newAddr(t, testPub)}, + } { + addr, err := NewFromSigner(table.priv) + if got, want := err != nil, table.desc != "valid"; got != want { + t.Errorf("%s: got error %v but wanted %v: %v", table.desc, got, want, err) + } + if err != nil { + continue + } + if got, want := addr, table.want; got != want { + t.Errorf("%s: got address\n%x\nbut wanted\n%x", table.desc, got[:], want[:]) + } + } +} + +func TestString(t *testing.T) { + addr := newAddr(t, testPub) + want := testAddr + if got, want := addr.String(), want; got != want { + t.Errorf("got address\n%s\nbut wanted\n%s", got, want) + } +} + +func newAddr(t *testing.T, pub string) (addr OnionAddress) { + t.Helper() + b := testonly.DecodeHex(t, pub) + if got, want := len(b), ed25519.PublicKeySize; got != want { + t.Fatalf("invalid public key size: %d", got) + } + copy(addr[:], b) + return +} |