Browse Source

Go F yourself

Adam Ierymenko 5 years ago
parent
commit
24904c5083
7 changed files with 405 additions and 22 deletions
  1. 4 1
      go/go.mod
  2. 7 0
      go/go.sum
  3. 109 0
      go/pkg/zerotier/base62.go
  4. 56 0
      go/pkg/zerotier/blob.go
  5. 6 3
      go/pkg/zerotier/errors.go
  6. 192 0
      go/pkg/zerotier/identity.go
  7. 31 18
      go/pkg/zerotier/node.go

+ 4 - 1
go/go.mod

@@ -2,4 +2,7 @@ module zerotier
 
 
 go 1.13
 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
+)

+ 7 - 0
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 h1:S4qyfL2sEm5Budr4KVMyEniCy+PbS55651I/a+Kn/NQ=
 github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95/go.mod h1:QiyDdbZLaJ/mZP4Zwc9g2QsfaEA4o7XvvgZegSci5/E=
 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 h1:so6Hr/LodwSZ5UQDu/7PmQiDeS112WwtLvU3lpSPZTU=
 golang.org/x/sys v0.0.0-20190529164535-6a60838ec259/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 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=

+ 109 - 0
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) }

+ 56 - 0
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<base62> 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
+}

+ 6 - 3
go/pkg/zerotier/errors.go

@@ -20,7 +20,10 @@ func (e Err) Error() string { return (string)(e) }
 
 
 // Simple ZeroTier Errors
 // Simple ZeroTier Errors
 const (
 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"
 )
 )

+ 192 - 0
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 ""
+}

+ 31 - 18
go/pkg/zerotier/node.go

@@ -64,6 +64,11 @@ type Node struct {
 
 
 // NewNode creates and initializes a new instance of the ZeroTier node service
 // NewNode creates and initializes a new instance of the ZeroTier node service
 func NewNode(path string) (*Node, error) {
 func NewNode(path string) (*Node, error) {
+	os.MkdirAll(path, 0755)
+	if _, err := os.Stat(path); err != nil {
+		return nil, err
+	}
+
 	n := new(Node)
 	n := new(Node)
 	n.path = path
 	n.path = path
 	n.networks = make(map[uint64]*Network)
 	n.networks = make(map[uint64]*Network)
@@ -72,7 +77,7 @@ func NewNode(path string) (*Node, error) {
 	n.gn = C.ZT_GoNode_new(cpath)
 	n.gn = C.ZT_GoNode_new(cpath)
 	C.free(unsafe.Pointer(cpath))
 	C.free(unsafe.Pointer(cpath))
 	if n.gn == nil {
 	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))
 	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()
 	n.networksLock.RUnlock()
 
 
 	if tap != nil {
 	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))
 	ntap := C.ZT_GoNode_join(n.gn, C.uint64_t(nwid))
 	if ntap == nil {
 	if ntap == nil {
-		return nil, errors.New("unable to initialize native tap (check device driver or permissions)")
+		return nil, ErrTapInitFailed
 	}
 	}
 
 
 	nw := &Network{
 	nw := &Network{
@@ -133,6 +138,10 @@ func (n *Node) Join(nwid uint64, tap Tap) (*Network, error) {
 
 
 // Leave leaves a network
 // Leave leaves a network
 func (n *Node) Leave(nwid uint64) error {
 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
 	return nil
 }
 }
 
 
@@ -157,7 +166,7 @@ func (n *Node) makeStateObjectPath(objType int, id [2]uint64) (string, bool) {
 		secret = true
 		secret = true
 	case C.ZT_STATE_OBJECT_PEER:
 	case C.ZT_STATE_OBJECT_PEER:
 		fp = path.Join(n.path, "peers.d")
 		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]))
 		fp = path.Join(fp, fmt.Sprintf("%.10x.peer", id[0]))
 		secret = true
 		secret = true
 	case C.ZT_STATE_OBJECT_NETWORK_CONFIG:
 	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) {
 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) {
 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) {
 func (n *Node) stateObjectGet(objType int, id [2]uint64) ([]byte, bool) {