mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-06-06 20:43:44 +02:00
Identity management plumbing to Go
This commit is contained in:
parent
7fc78129f4
commit
47a08ccbd4
7 changed files with 247 additions and 6 deletions
|
@ -48,7 +48,7 @@ Commands:
|
||||||
newdnskey Create a secure DNS name and secret
|
newdnskey Create a secure DNS name and secret
|
||||||
getdns <key> <locator> Create secure DNS TXT records
|
getdns <key> <locator> Create secure DNS TXT records
|
||||||
identity <command> [args] Identity management commands
|
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
|
getpublic <identity> Extract only public part of identity
|
||||||
validate <identity> Locally validate an identity
|
validate <identity> Locally validate an identity
|
||||||
sign <identity> <file> Sign a file with an identity's key
|
sign <identity> <file> Sign a file with an identity's key
|
||||||
|
|
|
@ -13,6 +13,135 @@
|
||||||
|
|
||||||
package cli
|
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
|
// Identity command
|
||||||
func Identity(args []string) {
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,6 +116,16 @@ func locatorGetDNS(args []string) {
|
||||||
fmt.Printf("FATAL: locator invalid: %s", err.Error())
|
fmt.Printf("FATAL: locator invalid: %s", err.Error())
|
||||||
os.Exit(1)
|
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
|
// Locator CLI command
|
||||||
|
|
|
@ -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)
|
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))
|
if ((privateKeyBufSize < ZT_ECC384_PRIVATE_KEY_SIZE)||(nameBufSize < 256))
|
||||||
|
|
|
@ -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 {
|
struct ZT_GoLocator_Info {
|
||||||
char id[1024];
|
char id[1024];
|
||||||
unsigned int phyCount;
|
unsigned int phyCount;
|
||||||
|
|
|
@ -13,11 +13,16 @@
|
||||||
|
|
||||||
package zerotier
|
package zerotier
|
||||||
|
|
||||||
|
//#cgo CFLAGS: -O3
|
||||||
|
//#include "../../native/GoGlue.h"
|
||||||
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IdentityTypeC25519 is a classic Curve25519/Ed25519 identity
|
// IdentityTypeC25519 is a classic Curve25519/Ed25519 identity
|
||||||
|
@ -42,6 +47,17 @@ type Identity struct {
|
||||||
privateKey []byte
|
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.
|
// NewIdentityFromString generates a new identity from its string representation.
|
||||||
// The private key is imported as well if it is present.
|
// The private key is imported as well if it is present.
|
||||||
func NewIdentityFromString(s string) (*Identity, error) {
|
func NewIdentityFromString(s string) (*Identity, error) {
|
||||||
|
@ -80,7 +96,7 @@ func NewIdentityFromString(s string) (*Identity, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
id.publicKey, err = base32StdLowerCase.DecodeString(ss[2])
|
id.publicKey, err = Base32StdLowerCase.DecodeString(ss[2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -88,7 +104,7 @@ func NewIdentityFromString(s string) (*Identity, error) {
|
||||||
return nil, ErrInvalidKey
|
return nil, ErrInvalidKey
|
||||||
}
|
}
|
||||||
if len(ss) >= 4 {
|
if len(ss) >= 4 {
|
||||||
id.privateKey, err = base32StdLowerCase.DecodeString(ss[3])
|
id.privateKey, err = Base32StdLowerCase.DecodeString(ss[3])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -114,7 +130,7 @@ func (id *Identity) PrivateKeyString() string {
|
||||||
}
|
}
|
||||||
case IdentityTypeP384:
|
case IdentityTypeP384:
|
||||||
if len(id.publicKey) == IdentityTypeP384PublicKeySize && len(id.privateKey) == IdentityTypeP384PrivateKeySize {
|
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 ""
|
return ""
|
||||||
|
@ -130,12 +146,49 @@ func (id *Identity) String() string {
|
||||||
}
|
}
|
||||||
case IdentityTypeP384:
|
case IdentityTypeP384:
|
||||||
if len(id.publicKey) == IdentityTypeP384PublicKeySize {
|
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 ""
|
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)
|
// MarshalJSON marshals this Identity in its string format (private key is never included)
|
||||||
func (id *Identity) MarshalJSON() ([]byte, error) {
|
func (id *Identity) MarshalJSON() ([]byte, error) {
|
||||||
return []byte("\"" + id.String() + "\""), nil
|
return []byte("\"" + id.String() + "\""), nil
|
||||||
|
|
|
@ -24,7 +24,8 @@ import (
|
||||||
// ZeroTierLogoChar is the unicode character that is ZeroTier's logo
|
// ZeroTierLogoChar is the unicode character that is ZeroTier's logo
|
||||||
const ZeroTierLogoChar = "⏁"
|
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.
|
// TimeMs returns the time in milliseconds since epoch.
|
||||||
func TimeMs() int64 { return int64(time.Now().UnixNano()) / int64(1000000) }
|
func TimeMs() int64 { return int64(time.Now().UnixNano()) / int64(1000000) }
|
||||||
|
|
Loading…
Add table
Reference in a new issue