From 24904c5083274afbd49af827ed02de6f308283ff Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Sat, 21 Sep 2019 11:56:31 -0700 Subject: [PATCH] Go F yourself --- go/go.mod | 5 +- go/go.sum | 7 ++ go/pkg/zerotier/base62.go | 109 ++++++++++++++++++++ go/pkg/zerotier/blob.go | 56 +++++++++++ go/pkg/zerotier/errors.go | 9 +- go/pkg/zerotier/identity.go | 192 ++++++++++++++++++++++++++++++++++++ go/pkg/zerotier/node.go | 49 +++++---- 7 files changed, 405 insertions(+), 22 deletions(-) create mode 100644 go/pkg/zerotier/base62.go create mode 100644 go/pkg/zerotier/blob.go create mode 100644 go/pkg/zerotier/identity.go diff --git a/go/go.mod b/go/go.mod index 8c49ab486..bd0aaff0e 100644 --- a/go/go.mod +++ b/go/go.mod @@ -2,4 +2,7 @@ module zerotier go 1.13 -require github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95 +require ( + github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95 + golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 +) diff --git a/go/go.sum b/go/go.sum index ad7bf1bb3..56484479a 100644 --- a/go/go.sum +++ b/go/go.sum @@ -1,4 +1,11 @@ github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95 h1:S4qyfL2sEm5Budr4KVMyEniCy+PbS55651I/a+Kn/NQ= github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95/go.mod h1:QiyDdbZLaJ/mZP4Zwc9g2QsfaEA4o7XvvgZegSci5/E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 h1:0hQKqeLdqlt5iIwVOBErRisrHJAN57yOiPRQItI20fU= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190529164535-6a60838ec259 h1:so6Hr/LodwSZ5UQDu/7PmQiDeS112WwtLvU3lpSPZTU= golang.org/x/sys v0.0.0-20190529164535-6a60838ec259/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/go/pkg/zerotier/base62.go b/go/pkg/zerotier/base62.go new file mode 100644 index 000000000..1ddbb5ee5 --- /dev/null +++ b/go/pkg/zerotier/base62.go @@ -0,0 +1,109 @@ +/* + * Copyright (c)2019 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2023-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +package zerotier + +import ( + "bytes" + "errors" +) + +// Base62Alphabet is the alphabet used for LF's Base62 encoding. +const Base62Alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + +var base62Encoding, _ = newBaseXEncoding(Base62Alphabet) + +type baseXEncoding struct { + base int + alphabet []rune + alphabetMap map[rune]int +} + +func newBaseXEncoding(alphabet string) (*baseXEncoding, error) { + runes := []rune(alphabet) + runeMap := make(map[rune]int) + for i := 0; i < len(runes); i++ { + if _, ok := runeMap[runes[i]]; ok { + return nil, errors.New("bad alphabet") + } + runeMap[runes[i]] = i + } + return &baseXEncoding{ + base: len(runes), + alphabet: runes, + alphabetMap: runeMap, + }, nil +} + +func (e *baseXEncoding) encode(source []byte) string { + if len(source) == 0 { + return "" + } + digits := []int{0} + for i := 0; i < len(source); i++ { + carry := int(source[i]) + for j := 0; j < len(digits); j++ { + carry += digits[j] << 8 + digits[j] = carry % e.base + carry = carry / e.base + } + for carry > 0 { + digits = append(digits, carry%e.base) + carry = carry / e.base + } + } + var res bytes.Buffer + for k := 0; source[k] == 0 && k < len(source)-1; k++ { + res.WriteRune(e.alphabet[0]) + } + for q := len(digits) - 1; q >= 0; q-- { + res.WriteRune(e.alphabet[digits[q]]) + } + return res.String() +} + +func (e *baseXEncoding) decode(source string) []byte { + if len(source) == 0 { + return nil + } + runes := []rune(source) + bytes := []byte{0} + for i := 0; i < len(source); i++ { + value, ok := e.alphabetMap[runes[i]] + if ok { // ignore non-base characters + carry := int(value) + for j := 0; j < len(bytes); j++ { + carry += int(bytes[j]) * e.base + bytes[j] = byte(carry & 0xff) + carry >>= 8 + } + for carry > 0 { + bytes = append(bytes, byte(carry&0xff)) + carry >>= 8 + } + } + } + for k := 0; runes[k] == e.alphabet[0] && k < len(runes)-1; k++ { + bytes = append(bytes, 0) + } + for i, j := 0, len(bytes)-1; i < j; i, j = i+1, j-1 { + bytes[i], bytes[j] = bytes[j], bytes[i] + } + return bytes +} + +// Base62Encode encodes a byte array in base62 form +func Base62Encode(in []byte) string { return base62Encoding.encode(in) } + +// Base62Decode decodes a base62 string into a byte array, ignoring non-base62 characters +func Base62Decode(in string) []byte { return base62Encoding.decode(in) } diff --git a/go/pkg/zerotier/blob.go b/go/pkg/zerotier/blob.go new file mode 100644 index 000000000..e12fb43a4 --- /dev/null +++ b/go/pkg/zerotier/blob.go @@ -0,0 +1,56 @@ +/* + * Copyright (c)2019 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2023-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +package zerotier + +// This is copied from the LF code base to make JSON blob encoding uniform + +import ( + "encoding/json" + "unicode/utf8" +) + +// Blob is a byte array that serializes to a string or a base62 string prefixed by \b (binary) +type Blob []byte + +// MarshalJSON returns this blob marshaled as a string using \b for non-UTF8 binary data. +func (b Blob) MarshalJSON() ([]byte, error) { + if utf8.Valid(b) { + return json.Marshal(string(b)) + } + return []byte("\"\\b" + Base62Encode(b) + "\""), nil +} + +// UnmarshalJSON unmarshals this blob from a string or byte array. +func (b *Blob) UnmarshalJSON(j []byte) error { + var s string + err := json.Unmarshal(j, &s) + if err == nil { + if len(s) == 0 { + *b = nil + } else if s[0] == '\b' { + *b = Base62Decode(s[1:]) + return nil + } + *b = []byte(s) + return nil + } + + // Byte arrays are also accepted + var bb []byte + if json.Unmarshal(j, &bb) != nil { + return err + } + *b = bb + return nil +} diff --git a/go/pkg/zerotier/errors.go b/go/pkg/zerotier/errors.go index 79b12f752..54c4772f2 100644 --- a/go/pkg/zerotier/errors.go +++ b/go/pkg/zerotier/errors.go @@ -20,7 +20,10 @@ func (e Err) Error() string { return (string)(e) } // Simple ZeroTier Errors const ( - ErrInvalidMACAddress Err = "invalid MAC address" - ErrInvalidZeroTierAddress Err = "invalid ZeroTier address" - ErrInvalidParameter Err = "invalid parameter" + ErrNodeInitFailed Err = "unable to initialize core Node instance" + ErrInvalidMACAddress Err = "invalid MAC address" + ErrInvalidZeroTierAddress Err = "invalid ZeroTier address" + ErrInvalidParameter Err = "invalid parameter" + ErrTapInitFailed Err = "unable to create native Tap instance" + ErrUncrecognizedIdentityType Err = "unrecognized identity type" ) diff --git a/go/pkg/zerotier/identity.go b/go/pkg/zerotier/identity.go new file mode 100644 index 000000000..7eaaf9615 --- /dev/null +++ b/go/pkg/zerotier/identity.go @@ -0,0 +1,192 @@ +/* + * Copyright (c)2019 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2023-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +package zerotier + +import ( + secrand "crypto/rand" + "crypto/sha512" + "encoding/binary" + "encoding/hex" + "fmt" + "strings" + + "golang.org/x/crypto/salsa20/salsa" + + "golang.org/x/crypto/curve25519" + + "golang.org/x/crypto/ed25519" +) + +const ztIdentityGenMemory = 2097152 +const ztIdentityHashCashFirstByteLessThan = 17 + +// IdentityTypeC25519 is a classic Curve25519/Ed25519 identity +const IdentityTypeC25519 = 0 + +// IdentityTypeP384 is an identity containing both NIST P-384 and Curve25519/Ed25519 key types and leveraging both when possible +const IdentityTypeP384 = 1 + +func computeZeroTierIdentityMemoryHardHash(publicKey []byte) []byte { + s512 := sha512.Sum512(publicKey) + + var genmem [ztIdentityGenMemory]byte + var s20key [32]byte + var s20ctr [16]byte + var s20ctri uint64 + copy(s20key[:], s512[0:32]) + copy(s20ctr[0:8], s512[32:40]) + salsa.XORKeyStream(genmem[0:64], genmem[0:64], &s20ctr, &s20key) + s20ctri++ + for i := 64; i < ztIdentityGenMemory; i += 64 { + binary.LittleEndian.PutUint64(s20ctr[8:16], s20ctri) + salsa.XORKeyStream(genmem[i:i+64], genmem[i-64:i], &s20ctr, &s20key) + s20ctri++ + } + + var tmp [8]byte + for i := 0; i < ztIdentityGenMemory; { + idx1 := uint(binary.BigEndian.Uint64(genmem[i:])&7) * 8 + i += 8 + idx2 := (uint(binary.BigEndian.Uint64(genmem[i:])) % uint(ztIdentityGenMemory/8)) * 8 + i += 8 + gm := genmem[idx2 : idx2+8] + d := s512[idx1 : idx1+8] + copy(tmp[:], gm) + copy(gm, d) + copy(d, tmp[:]) + binary.LittleEndian.PutUint64(s20ctr[8:16], s20ctri) + salsa.XORKeyStream(s512[:], s512[:], &s20ctr, &s20key) + s20ctri++ + } + + return s512[:] +} + +// generateDualPair generates a key pair containing two pairs: one for curve25519 and one for ed25519. +func generateDualPair() (pub [64]byte, priv [64]byte) { + k0pub, k0priv, _ := ed25519.GenerateKey(secrand.Reader) + var k1pub, k1priv [32]byte + secrand.Read(k1priv[:]) + curve25519.ScalarBaseMult(&k1pub, &k1priv) + copy(pub[0:32], k1pub[:]) + copy(pub[32:64], k0pub[0:32]) + copy(priv[0:32], k1priv[:]) + copy(priv[32:64], k0priv[0:32]) + return +} + +// Identity is precisely what it sounds like: the address and associated keys for a ZeroTier node +type Identity struct { + // Address is this identity's 40-bit short address + Address Address + + // Type is either IdentityTypeC25519 or IdentityTypeP384 + Type int + + // PublicKey is this identity's public key bytes + PublicKey Blob + + privateKey []byte +} + +// NewIdentity generates a new ZeroTier Identity. +// This can be a little bit time consuming due to one way proof of work requirements (usually a few hundred milliseconds). +func NewIdentity() (id Identity) { + for { + pub, priv := generateDualPair() + dig := computeZeroTierIdentityMemoryHardHash(pub[:]) + if dig[0] < ztIdentityHashCashFirstByteLessThan && dig[59] != 0xff { + addr := uint64(dig[59]) + addr <<= 8 + addr |= uint64(dig[60]) + addr <<= 8 + addr |= uint64(dig[61]) + addr <<= 8 + addr |= uint64(dig[62]) + addr <<= 8 + addr |= uint64(dig[63]) + if addr != 0 { + id.Address = Address(addr) + id.PublicKey = pub[:] + id.privateKey = priv[:] + break + } + } + } + return +} + +// NewIdentityFromString generates a new identity from its string representation. +// The private key is imported as well if it is present. +func NewIdentityFromString(s string) (*Identity, error) { + ss := strings.Split(s, ":") + if len(ss) < 3 { + return nil, ErrInvalidParameter + } + + var err error + var id Identity + id.Address, err = NewAddressFromString(ss[0]) + if err != nil { + return nil, err + } + + if ss[1] == "0" { + id.Type = 0 + } else if ss[1] == "1" { + id.Type = 1 + } else { + return nil, ErrUncrecognizedIdentityType + } + + switch id.Type { + case 0: + id.PublicKey, err = hex.DecodeString(ss[2]) + if err != nil { + return nil, err + } + if len(ss) >= 4 { + id.privateKey, err = hex.DecodeString(ss[3]) + if err != nil { + return nil, err + } + } + case 1: + + } + + return &id, nil +} + +// HasPrivate returns true if this identity has its own private portion. +func (id *Identity) HasPrivate() bool { return len(id.privateKey) > 0 } + +// PrivateKeyString returns the full identity.secret if the private key is set, or an empty string if no private key is set. +func (id *Identity) PrivateKeyString() string { + if len(id.privateKey) == 64 { + s := fmt.Sprintf("%.10x:0:%x:%x", id.Address, id.PublicKey, id.privateKey) + return s + } + return "" +} + +// PublicKeyString returns the address and public key (identity.public contents). +// An empty string is returned if this identity is invalid or not initialized. +func (id *Identity) String() string { + if len(id.PublicKey) == 64 { + s := fmt.Sprintf("%.10x:0:%x", id.Address, id.PublicKey) + return s + } + return "" +} diff --git a/go/pkg/zerotier/node.go b/go/pkg/zerotier/node.go index 5cbd79fde..c36529cba 100644 --- a/go/pkg/zerotier/node.go +++ b/go/pkg/zerotier/node.go @@ -64,6 +64,11 @@ type Node struct { // NewNode creates and initializes a new instance of the ZeroTier node service func NewNode(path string) (*Node, error) { + os.MkdirAll(path, 0755) + if _, err := os.Stat(path); err != nil { + return nil, err + } + n := new(Node) n.path = path n.networks = make(map[uint64]*Network) @@ -72,7 +77,7 @@ func NewNode(path string) (*Node, error) { n.gn = C.ZT_GoNode_new(cpath) C.free(unsafe.Pointer(cpath)) if n.gn == nil { - return nil, errors.New("unable to create new Node instance") + return nil, ErrNodeInitFailed } n.zn = (*C.ZT_Node)(C.ZT_GoNode_getNode(n.gn)) @@ -107,11 +112,11 @@ func (n *Node) Join(nwid uint64, tap Tap) (*Network, error) { n.networksLock.RUnlock() if tap != nil { - return nil, errors.New("not implemented yet") + return nil, errors.New("non-native taps not implemented yet") } ntap := C.ZT_GoNode_join(n.gn, C.uint64_t(nwid)) if ntap == nil { - return nil, errors.New("unable to initialize native tap (check device driver or permissions)") + return nil, ErrTapInitFailed } nw := &Network{ @@ -133,6 +138,10 @@ func (n *Node) Join(nwid uint64, tap Tap) (*Network, error) { // Leave leaves a network func (n *Node) Leave(nwid uint64) error { + C.ZT_GoNode_leave(n.gn, C.uint64_t(nwid)) + n.networksLock.Lock() + delete(n.networks, nwid) + n.networksLock.Unlock() return nil } @@ -157,7 +166,7 @@ func (n *Node) makeStateObjectPath(objType int, id [2]uint64) (string, bool) { secret = true case C.ZT_STATE_OBJECT_PEER: fp = path.Join(n.path, "peers.d") - os.Mkdir(fp, 0755) + os.Mkdir(fp, 0700) fp = path.Join(fp, fmt.Sprintf("%.10x.peer", id[0])) secret = true case C.ZT_STATE_OBJECT_NETWORK_CONFIG: @@ -171,24 +180,28 @@ func (n *Node) makeStateObjectPath(objType int, id [2]uint64) (string, bool) { } func (n *Node) stateObjectPut(objType int, id [2]uint64, data []byte) { - fp, secret := n.makeStateObjectPath(objType, id) - if len(fp) > 0 { - fileMode := os.FileMode(0644) - if secret { - fileMode = os.FileMode(0600) + go func() { + fp, secret := n.makeStateObjectPath(objType, id) + if len(fp) > 0 { + fileMode := os.FileMode(0644) + if secret { + fileMode = os.FileMode(0600) + } + ioutil.WriteFile(fp, data, fileMode) + if secret { + acl.Chmod(fp, 0600) // this emulates Unix chmod on Windows and uses os.Chmod on Unix-type systems + } } - ioutil.WriteFile(fp, data, fileMode) - if secret { - acl.Chmod(fp, 0600) // this emulates Unix chmod on Windows and uses os.Chmod on Unix-type systems - } - } + }() } func (n *Node) stateObjectDelete(objType int, id [2]uint64) { - fp, _ := n.makeStateObjectPath(objType, id) - if len(fp) > 0 { - os.Remove(fp) - } + go func() { + fp, _ := n.makeStateObjectPath(objType, id) + if len(fp) > 0 { + os.Remove(fp) + } + }() } func (n *Node) stateObjectGet(objType int, id [2]uint64) ([]byte, bool) {