ZeroTierOne/go/pkg/zerotier/identity.go
2019-09-21 11:56:31 -07:00

193 lines
4.9 KiB
Go

/*
* 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 ""
}