diff --git a/go/cmd/zerotier/cli/help.go b/go/cmd/zerotier/cli/help.go index 5e8b5dd6c..57186f1b2 100644 --- a/go/cmd/zerotier/cli/help.go +++ b/go/cmd/zerotier/cli/help.go @@ -48,7 +48,7 @@ Commands: newdnskey Create a secure DNS name and secret getdns Create secure DNS TXT records identity [args] Identity management commands - new Create new identity (including secret) + new [c25519|p384] Create new identity (including secret) getpublic Extract only public part of identity validate Locally validate an identity sign Sign a file with an identity's key diff --git a/go/cmd/zerotier/cli/identity.go b/go/cmd/zerotier/cli/identity.go index 836b927a8..716561cfd 100644 --- a/go/cmd/zerotier/cli/identity.go +++ b/go/cmd/zerotier/cli/identity.go @@ -13,6 +13,135 @@ package cli +import ( + "encoding/hex" + "fmt" + "io/ioutil" + "os" + "strings" + + "zerotier/pkg/zerotier" +) + +/* + identity [args] Identity management commands + new Create new identity (including secret) + getpublic Extract only public part of identity + validate Locally validate an identity + sign Sign a file with an identity's key + verify 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) } diff --git a/go/cmd/zerotier/cli/locator.go b/go/cmd/zerotier/cli/locator.go index 10a66f50d..036d65fff 100644 --- a/go/cmd/zerotier/cli/locator.go +++ b/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 diff --git a/go/native/GoGlue.cpp b/go/native/GoGlue.cpp index 37fe739f7..07837086f 100644 --- a/go/native/GoGlue.cpp +++ b/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)) diff --git a/go/native/GoGlue.h b/go/native/GoGlue.h index 63f04fbca..5560751be 100644 --- a/go/native/GoGlue.h +++ b/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; diff --git a/go/pkg/zerotier/identity.go b/go/pkg/zerotier/identity.go index bde3b43ae..281efb550 100644 --- a/go/pkg/zerotier/identity.go +++ b/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 diff --git a/go/pkg/zerotier/misc.go b/go/pkg/zerotier/misc.go index 8a0b839b8..4ff0ceefe 100644 --- a/go/pkg/zerotier/misc.go +++ b/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) }