Browse Source

Identity management plumbing to Go

Adam Ierymenko 5 years ago
parent
commit
47a08ccbd4

+ 1 - 1
go/cmd/zerotier/cli/help.go

@@ -48,7 +48,7 @@ Commands:
     newdnskey                          Create a secure DNS name and secret
     getdns <key> <locator>             Create secure DNS TXT records
   identity <command> [args]            Identity management commands
-    new                                Create new identity (including secret)
+    new [c25519|p384]                  Create new identity (including secret)
     getpublic <identity>               Extract only public part of identity
     validate <identity>                Locally validate an identity
     sign <identity> <file>             Sign a file with an identity's key

+ 129 - 0
go/cmd/zerotier/cli/identity.go

@@ -13,6 +13,135 @@
 
 package cli
 
+import (
+	"encoding/hex"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"strings"
+
+	"zerotier/pkg/zerotier"
+)
+
+/*
+  identity <command> [args]            Identity management commands
+    new                                Create new identity (including secret)
+    getpublic <identity>               Extract only public part of identity
+    validate <identity>                Locally validate an identity
+    sign <identity> <file>             Sign a file with an identity's key
+    verify <identity> <file> <sig>     Verify a signature
+*/
+
 // Identity command
 func Identity(args []string) {
+	if len(args) > 0 {
+		switch args[0] {
+
+		case "new":
+			idType := zerotier.IdentityTypeC25519
+			if len(args) > 1 {
+				if len(args) > 2 {
+					Help()
+					os.Exit(1)
+				}
+				switch args[1] {
+				case "c25519":
+				case "p384":
+					idType = zerotier.IdentityTypeP384
+				default:
+					Help()
+					os.Exit(1)
+				}
+			}
+			id, err := zerotier.NewIdentity(idType)
+			if err != nil {
+				fmt.Printf("ERROR: internal error generating identity: %s\n", err.Error())
+				os.Exit(1)
+			}
+			fmt.Println(id.PrivateKeyString())
+			os.Exit(0)
+
+		case "getpublic":
+			if len(args) == 2 {
+				idData, err := ioutil.ReadFile(args[1])
+				if err != nil {
+					fmt.Printf("ERROR: unable to read identity: %s\n", err.Error())
+					os.Exit(1)
+				}
+				id, err := zerotier.NewIdentityFromString(string(idData))
+				if err != nil {
+					fmt.Printf("ERROR: identity in file '%s' invalid: %s\n", args[1], err.Error())
+					os.Exit(1)
+				}
+				fmt.Println(id.String())
+				os.Exit(0)
+			}
+
+		case "validate":
+			if len(args) == 2 {
+				idData, err := ioutil.ReadFile(args[1])
+				if err != nil {
+					fmt.Printf("ERROR: unable to read identity: %s\n", err.Error())
+					os.Exit(1)
+				}
+				id, err := zerotier.NewIdentityFromString(string(idData))
+				if err != nil {
+					fmt.Printf("ERROR: identity in file '%s' invalid: %s\n", args[1], err.Error())
+					os.Exit(1)
+				}
+				if id.LocallyValidate() {
+					fmt.Println("OK")
+					os.Exit(0)
+				}
+				fmt.Println("FAILED")
+				os.Exit(1)
+			}
+
+		case "sign", "verify":
+			if len(args) > 2 {
+				idData, err := ioutil.ReadFile(args[1])
+				if err != nil {
+					fmt.Printf("ERROR: unable to read identity: %s\n", err.Error())
+					os.Exit(1)
+				}
+				id, err := zerotier.NewIdentityFromString(string(idData))
+				if err != nil {
+					fmt.Printf("ERROR: identity in file '%s' invalid: %s\n", args[1], err.Error())
+					os.Exit(1)
+				}
+				msg, err := ioutil.ReadFile(args[2])
+				if err != nil {
+					fmt.Printf("ERROR: unable to read input file: %s\n", err.Error())
+					os.Exit(1)
+				}
+
+				if args[0] == "verify" {
+					if len(args) == 4 {
+						sig, err := hex.DecodeString(strings.TrimSpace(args[3]))
+						if err != nil {
+							fmt.Println("FAILED")
+							os.Exit(1)
+						}
+						if id.Verify(msg, sig) {
+							fmt.Println("OK")
+							os.Exit(0)
+						}
+					}
+					fmt.Println("FAILED")
+					os.Exit(1)
+				} else {
+					sig, err := id.Sign(msg)
+					if err != nil {
+						fmt.Printf("ERROR: internal error signing message: %s\n", err.Error())
+						os.Exit(1)
+					}
+					fmt.Println(hex.EncodeToString(sig))
+					os.Exit(0)
+				}
+			}
+
+		}
+	}
+	Help()
+	os.Exit(1)
 }

+ 10 - 0
go/cmd/zerotier/cli/locator.go

@@ -116,6 +116,16 @@ func locatorGetDNS(args []string) {
 		fmt.Printf("FATAL: locator invalid: %s", err.Error())
 		os.Exit(1)
 	}
+
+	txt, err := loc.MakeTXTRecords(&sk)
+	if err != nil {
+		fmt.Printf("FATAL: error creating TXT records: %s\n", err.Error())
+		os.Exit(1)
+	}
+	for _, t := range txt {
+		fmt.Println(t)
+	}
+	os.Exit(0)
 }
 
 // Locator CLI command

+ 38 - 0
go/native/GoGlue.cpp

@@ -723,6 +723,44 @@ extern "C" int ZT_GoTap_removeRoute(ZT_GoTap *tap,int targetAf,const void *targe
 
 /****************************************************************************/
 
+extern "C" const char *ZT_GoIdentity_generate(int type)
+{
+	Identity id;
+	id.generate((Identity::Type)type);
+	char *tmp = (char *)malloc(ZT_IDENTITY_STRING_BUFFER_LENGTH);
+	if (tmp)
+		id.toString(true,tmp);
+	return tmp;
+}
+
+extern "C" int ZT_GoIdentity_validate(const char *idStr)
+{
+	Identity id;
+	if (!id.fromString(idStr))
+		return 0;
+	if (!id.locallyValidate())
+		return 0;
+	return 1;
+}
+
+extern "C" int ZT_GoIdentity_sign(const char *idStr,const void *data,unsigned int len,void *sigbuf,unsigned int sigbuflen)
+{
+	Identity id;
+	if (!id.fromString(idStr))
+		return 0;
+	return (int)id.sign(data,len,sigbuf,sigbuflen);
+}
+
+extern "C" int ZT_GoIdentity_verify(const char *idStr,const void *data,unsigned int len,const void *sig,unsigned int siglen)
+{
+	Identity id;
+	if (!id.fromString(idStr))
+		return 0;
+	return id.verify(data,len,sig,siglen) ? 1 : 0;
+}
+
+/****************************************************************************/
+
 extern "C" int ZT_GoLocator_makeSecureDNSName(char *name,unsigned int nameBufSize,uint8_t *privateKey,unsigned int privateKeyBufSize)
 {
 	if ((privateKeyBufSize < ZT_ECC384_PRIVATE_KEY_SIZE)||(nameBufSize < 256))

+ 10 - 0
go/native/GoGlue.h

@@ -90,6 +90,16 @@ int ZT_GoTap_removeRoute(ZT_GoTap *tap,int targetAf,const void *targetIp,int tar
 
 /****************************************************************************/
 
+const char *ZT_GoIdentity_generate(int type);
+
+int ZT_GoIdentity_validate(const char *idStr);
+
+int ZT_GoIdentity_sign(const char *idStr,const void *data,unsigned int len,void *sigbuf,unsigned int sigbuflen);
+
+int ZT_GoIdentity_verify(const char *idStr,const void *data,unsigned int len,const void *sig,unsigned int siglen);
+
+/****************************************************************************/
+
 struct ZT_GoLocator_Info {
 	char id[1024];
 	unsigned int phyCount;

+ 57 - 4
go/pkg/zerotier/identity.go

@@ -13,11 +13,16 @@
 
 package zerotier
 
+//#cgo CFLAGS: -O3
+//#include "../../native/GoGlue.h"
+import "C"
+
 import (
 	"encoding/hex"
 	"encoding/json"
 	"fmt"
 	"strings"
+	"unsafe"
 )
 
 // IdentityTypeC25519 is a classic Curve25519/Ed25519 identity
@@ -42,6 +47,17 @@ type Identity struct {
 	privateKey []byte
 }
 
+// NewIdentity generates a new identity of the selected type
+func NewIdentity(identityType int) (*Identity, error) {
+	cIdStr := C.ZT_GoIdentity_generate(C.int(identityType))
+	if uintptr(unsafe.Pointer(cIdStr)) == 0 {
+		return nil, ErrInternal
+	}
+	id, err := NewIdentityFromString(C.GoString(cIdStr))
+	C.free(unsafe.Pointer(cIdStr))
+	return id, err
+}
+
 // 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) {
@@ -80,7 +96,7 @@ func NewIdentityFromString(s string) (*Identity, error) {
 		}
 
 	case 1:
-		id.publicKey, err = base32StdLowerCase.DecodeString(ss[2])
+		id.publicKey, err = Base32StdLowerCase.DecodeString(ss[2])
 		if err != nil {
 			return nil, err
 		}
@@ -88,7 +104,7 @@ func NewIdentityFromString(s string) (*Identity, error) {
 			return nil, ErrInvalidKey
 		}
 		if len(ss) >= 4 {
-			id.privateKey, err = base32StdLowerCase.DecodeString(ss[3])
+			id.privateKey, err = Base32StdLowerCase.DecodeString(ss[3])
 			if err != nil {
 				return nil, err
 			}
@@ -114,7 +130,7 @@ func (id *Identity) PrivateKeyString() string {
 		}
 	case IdentityTypeP384:
 		if len(id.publicKey) == IdentityTypeP384PublicKeySize && len(id.privateKey) == IdentityTypeP384PrivateKeySize {
-			return fmt.Sprintf("%.10x:1:%s:%s", uint64(id.address), base32StdLowerCase.EncodeToString(id.publicKey), base32StdLowerCase.EncodeToString(id.privateKey))
+			return fmt.Sprintf("%.10x:1:%s:%s", uint64(id.address), Base32StdLowerCase.EncodeToString(id.publicKey), Base32StdLowerCase.EncodeToString(id.privateKey))
 		}
 	}
 	return ""
@@ -130,12 +146,49 @@ func (id *Identity) String() string {
 		}
 	case IdentityTypeP384:
 		if len(id.publicKey) == IdentityTypeP384PublicKeySize {
-			return fmt.Sprintf("%.10x:1:%s", uint64(id.address), base32StdLowerCase.EncodeToString(id.publicKey))
+			return fmt.Sprintf("%.10x:1:%s", uint64(id.address), Base32StdLowerCase.EncodeToString(id.publicKey))
 		}
 	}
 	return ""
 }
 
+// LocallyValidate performs local self-validation of this identity
+func (id *Identity) LocallyValidate() bool {
+	idCStr := C.CString(id.String())
+	defer C.free(unsafe.Pointer(idCStr))
+	return C.ZT_GoIdentity_validate(idCStr) != 0
+}
+
+// Sign signs a message with this identity
+func (id *Identity) Sign(msg []byte) ([]byte, error) {
+	idCStr := C.CString(id.PrivateKeyString())
+	var sigbuf [96]byte
+	var dataP unsafe.Pointer
+	if len(msg) > 0 {
+		dataP = unsafe.Pointer(&msg[0])
+	}
+	siglen := C.ZT_GoIdentity_sign(idCStr, dataP, C.uint(len(msg)), unsafe.Pointer(&sigbuf[0]), C.uint(len(sigbuf)))
+	C.free(unsafe.Pointer(idCStr))
+	if siglen <= 0 {
+		return nil, ErrInvalidKey
+	}
+	return sigbuf[0:int(siglen)], nil
+}
+
+// Verify verifies a signature
+func (id *Identity) Verify(msg, sig []byte) bool {
+	if len(sig) == 0 {
+		return false
+	}
+	idCStr := C.CString(id.String())
+	defer C.free(unsafe.Pointer(idCStr))
+	var dataP unsafe.Pointer
+	if len(msg) > 0 {
+		dataP = unsafe.Pointer(&msg[0])
+	}
+	return C.ZT_GoIdentity_verify(idCStr, dataP, C.uint(len(msg)), unsafe.Pointer(&sig[0]), C.uint(len(sig))) != 0
+}
+
 // MarshalJSON marshals this Identity in its string format (private key is never included)
 func (id *Identity) MarshalJSON() ([]byte, error) {
 	return []byte("\"" + id.String() + "\""), nil

+ 2 - 1
go/pkg/zerotier/misc.go

@@ -24,7 +24,8 @@ import (
 // ZeroTierLogoChar is the unicode character that is ZeroTier's logo
 const ZeroTierLogoChar = "⏁"
 
-var base32StdLowerCase = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567").WithPadding(base32.NoPadding)
+// Base32StdLowerCase is a base32 encoder/decoder using a lower-case standard alphabet and no padding.
+var Base32StdLowerCase = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567").WithPadding(base32.NoPadding)
 
 // TimeMs returns the time in milliseconds since epoch.
 func TimeMs() int64 { return int64(time.Now().UnixNano()) / int64(1000000) }