mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 08:11:32 +01:00 
			
		
		
		
	And enable U1000 check in staticcheck. Updates #cleanup Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
		
			
				
	
	
		
			444 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			444 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // This file contains the implementation of Noise IK from
 | |
| // https://noiseexplorer.com/ . Unlike the rest of this repository,
 | |
| // this file is licensed under the terms of the GNU GPL v3. See
 | |
| // https://source.symbolic.software/noiseexplorer/noiseexplorer for
 | |
| // more information.
 | |
| //
 | |
| // This file is used here to verify that Tailscale's implementation of
 | |
| // Noise IK is interoperable with another implementation.
 | |
| //lint:file-ignore SA4006 not our code.
 | |
| 
 | |
| /*
 | |
| IK:
 | |
|   <- s
 | |
|   ...
 | |
|   -> e, es, s, ss
 | |
|   <- e, ee, se
 | |
|   ->
 | |
|   <-
 | |
| */
 | |
| 
 | |
| // Implementation Version: 1.0.2
 | |
| 
 | |
| /* ---------------------------------------------------------------- *
 | |
|  * PARAMETERS                                                       *
 | |
|  * ---------------------------------------------------------------- */
 | |
| 
 | |
| package controlbase
 | |
| 
 | |
| import (
 | |
| 	"crypto/rand"
 | |
| 	"crypto/subtle"
 | |
| 	"encoding/binary"
 | |
| 	"hash"
 | |
| 	"io"
 | |
| 
 | |
| 	"golang.org/x/crypto/blake2s"
 | |
| 	"golang.org/x/crypto/chacha20poly1305"
 | |
| 	"golang.org/x/crypto/curve25519"
 | |
| 	"golang.org/x/crypto/hkdf"
 | |
| )
 | |
| 
 | |
| /* ---------------------------------------------------------------- *
 | |
|  * TYPES                                                            *
 | |
|  * ---------------------------------------------------------------- */
 | |
| 
 | |
| type keypair struct {
 | |
| 	public_key  [32]byte
 | |
| 	private_key [32]byte
 | |
| }
 | |
| 
 | |
| type messagebuffer struct {
 | |
| 	ne         [32]byte
 | |
| 	ns         []byte
 | |
| 	ciphertext []byte
 | |
| }
 | |
| 
 | |
| type cipherstate struct {
 | |
| 	k [32]byte
 | |
| 	n uint32
 | |
| }
 | |
| 
 | |
| type symmetricstate struct {
 | |
| 	cs cipherstate
 | |
| 	ck [32]byte
 | |
| 	h  [32]byte
 | |
| }
 | |
| 
 | |
| type handshakestate struct {
 | |
| 	ss  symmetricstate
 | |
| 	s   keypair
 | |
| 	e   keypair
 | |
| 	rs  [32]byte
 | |
| 	re  [32]byte
 | |
| 	psk [32]byte
 | |
| }
 | |
| 
 | |
| type noisesession struct {
 | |
| 	hs  handshakestate
 | |
| 	h   [32]byte
 | |
| 	cs1 cipherstate
 | |
| 	cs2 cipherstate
 | |
| 	mc  uint64
 | |
| 	i   bool
 | |
| }
 | |
| 
 | |
| /* ---------------------------------------------------------------- *
 | |
|  * CONSTANTS                                                        *
 | |
|  * ---------------------------------------------------------------- */
 | |
| 
 | |
| var emptyKey = [32]byte{
 | |
| 	0x00, 0x00, 0x00, 0x00,
 | |
| 	0x00, 0x00, 0x00, 0x00,
 | |
| 	0x00, 0x00, 0x00, 0x00,
 | |
| 	0x00, 0x00, 0x00, 0x00,
 | |
| 	0x00, 0x00, 0x00, 0x00,
 | |
| 	0x00, 0x00, 0x00, 0x00,
 | |
| 	0x00, 0x00, 0x00, 0x00,
 | |
| 	0x00, 0x00, 0x00, 0x00,
 | |
| }
 | |
| 
 | |
| var minNonce = uint32(0)
 | |
| 
 | |
| /* ---------------------------------------------------------------- *
 | |
|  * UTILITY FUNCTIONS                                                *
 | |
|  * ---------------------------------------------------------------- */
 | |
| 
 | |
| func isEmptyKey(k [32]byte) bool {
 | |
| 	return subtle.ConstantTimeCompare(k[:], emptyKey[:]) == 1
 | |
| }
 | |
| 
 | |
| func validatePublicKey(k []byte) bool {
 | |
| 	forbiddenCurveValues := [12][]byte{
 | |
| 		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
 | |
| 		{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
 | |
| 		{224, 235, 122, 124, 59, 65, 184, 174, 22, 86, 227, 250, 241, 159, 196, 106, 218, 9, 141, 235, 156, 50, 177, 253, 134, 98, 5, 22, 95, 73, 184, 0},
 | |
| 		{95, 156, 149, 188, 163, 80, 140, 36, 177, 208, 177, 85, 156, 131, 239, 91, 4, 68, 92, 196, 88, 28, 142, 134, 216, 34, 78, 221, 208, 159, 17, 87},
 | |
| 		{236, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127},
 | |
| 		{237, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127},
 | |
| 		{238, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127},
 | |
| 		{205, 235, 122, 124, 59, 65, 184, 174, 22, 86, 227, 250, 241, 159, 196, 106, 218, 9, 141, 235, 156, 50, 177, 253, 134, 98, 5, 22, 95, 73, 184, 128},
 | |
| 		{76, 156, 149, 188, 163, 80, 140, 36, 177, 208, 177, 85, 156, 131, 239, 91, 4, 68, 92, 196, 88, 28, 142, 134, 216, 34, 78, 221, 208, 159, 17, 215},
 | |
| 		{217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
 | |
| 		{218, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
 | |
| 		{219, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 25},
 | |
| 	}
 | |
| 
 | |
| 	for _, testValue := range forbiddenCurveValues {
 | |
| 		if subtle.ConstantTimeCompare(k[:], testValue[:]) == 1 {
 | |
| 			panic("Invalid public key")
 | |
| 		}
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| /* ---------------------------------------------------------------- *
 | |
|  * PRIMITIVES                                                       *
 | |
|  * ---------------------------------------------------------------- */
 | |
| 
 | |
| func incrementNonce(n uint32) uint32 {
 | |
| 	return n + 1
 | |
| }
 | |
| 
 | |
| func dh(private_key [32]byte, public_key [32]byte) [32]byte {
 | |
| 	var ss [32]byte
 | |
| 	curve25519.ScalarMult(&ss, &private_key, &public_key)
 | |
| 	return ss
 | |
| }
 | |
| 
 | |
| func generateKeypair() keypair {
 | |
| 	var public_key [32]byte
 | |
| 	var private_key [32]byte
 | |
| 	_, _ = rand.Read(private_key[:])
 | |
| 	curve25519.ScalarBaseMult(&public_key, &private_key)
 | |
| 	if validatePublicKey(public_key[:]) {
 | |
| 		return keypair{public_key, private_key}
 | |
| 	}
 | |
| 	return generateKeypair()
 | |
| }
 | |
| 
 | |
| func encrypt(k [32]byte, n uint32, ad []byte, plaintext []byte) []byte {
 | |
| 	var nonce [12]byte
 | |
| 	var ciphertext []byte
 | |
| 	enc, _ := chacha20poly1305.New(k[:])
 | |
| 	binary.LittleEndian.PutUint32(nonce[4:], n)
 | |
| 	ciphertext = enc.Seal(nil, nonce[:], plaintext, ad)
 | |
| 	return ciphertext
 | |
| }
 | |
| 
 | |
| func decrypt(k [32]byte, n uint32, ad []byte, ciphertext []byte) (bool, []byte, []byte) {
 | |
| 	var nonce [12]byte
 | |
| 	var plaintext []byte
 | |
| 	enc, err := chacha20poly1305.New(k[:])
 | |
| 	binary.LittleEndian.PutUint32(nonce[4:], n)
 | |
| 	plaintext, err = enc.Open(nil, nonce[:], ciphertext, ad)
 | |
| 	return (err == nil), ad, plaintext
 | |
| }
 | |
| 
 | |
| func getHash(a []byte, b []byte) [32]byte {
 | |
| 	return blake2s.Sum256(append(a, b...))
 | |
| }
 | |
| 
 | |
| func hashProtocolName(protocolName []byte) [32]byte {
 | |
| 	var h [32]byte
 | |
| 	if len(protocolName) <= 32 {
 | |
| 		copy(h[:], protocolName)
 | |
| 	} else {
 | |
| 		h = getHash(protocolName, []byte{})
 | |
| 	}
 | |
| 	return h
 | |
| }
 | |
| 
 | |
| func blake2HkdfInterface() hash.Hash {
 | |
| 	h, _ := blake2s.New256([]byte{})
 | |
| 	return h
 | |
| }
 | |
| 
 | |
| func getHkdf(ck [32]byte, ikm []byte) ([32]byte, [32]byte, [32]byte) {
 | |
| 	var k1 [32]byte
 | |
| 	var k2 [32]byte
 | |
| 	var k3 [32]byte
 | |
| 	output := hkdf.New(blake2HkdfInterface, ikm[:], ck[:], []byte{})
 | |
| 	io.ReadFull(output, k1[:])
 | |
| 	io.ReadFull(output, k2[:])
 | |
| 	io.ReadFull(output, k3[:])
 | |
| 	return k1, k2, k3
 | |
| }
 | |
| 
 | |
| /* ---------------------------------------------------------------- *
 | |
|  * STATE MANAGEMENT                                                 *
 | |
|  * ---------------------------------------------------------------- */
 | |
| 
 | |
| /* CipherState */
 | |
| func initializeKey(k [32]byte) cipherstate {
 | |
| 	return cipherstate{k, minNonce}
 | |
| }
 | |
| 
 | |
| func hasKey(cs *cipherstate) bool {
 | |
| 	return !isEmptyKey(cs.k)
 | |
| }
 | |
| 
 | |
| func setNonce(cs *cipherstate, newNonce uint32) *cipherstate {
 | |
| 	cs.n = newNonce
 | |
| 	return cs
 | |
| }
 | |
| 
 | |
| func encryptWithAd(cs *cipherstate, ad []byte, plaintext []byte) (*cipherstate, []byte) {
 | |
| 	e := encrypt(cs.k, cs.n, ad, plaintext)
 | |
| 	cs = setNonce(cs, incrementNonce(cs.n))
 | |
| 	return cs, e
 | |
| }
 | |
| 
 | |
| func decryptWithAd(cs *cipherstate, ad []byte, ciphertext []byte) (*cipherstate, []byte, bool) {
 | |
| 	valid, ad, plaintext := decrypt(cs.k, cs.n, ad, ciphertext)
 | |
| 	cs = setNonce(cs, incrementNonce(cs.n))
 | |
| 	return cs, plaintext, valid
 | |
| }
 | |
| 
 | |
| /* SymmetricState */
 | |
| 
 | |
| func initializeSymmetric(protocolName []byte) symmetricstate {
 | |
| 	h := hashProtocolName(protocolName)
 | |
| 	ck := h
 | |
| 	cs := initializeKey(emptyKey)
 | |
| 	return symmetricstate{cs, ck, h}
 | |
| }
 | |
| 
 | |
| func mixKey(ss *symmetricstate, ikm [32]byte) *symmetricstate {
 | |
| 	ck, tempK, _ := getHkdf(ss.ck, ikm[:])
 | |
| 	ss.cs = initializeKey(tempK)
 | |
| 	ss.ck = ck
 | |
| 	return ss
 | |
| }
 | |
| 
 | |
| func mixHash(ss *symmetricstate, data []byte) *symmetricstate {
 | |
| 	ss.h = getHash(ss.h[:], data)
 | |
| 	return ss
 | |
| }
 | |
| 
 | |
| func encryptAndHash(ss *symmetricstate, plaintext []byte) (*symmetricstate, []byte) {
 | |
| 	var ciphertext []byte
 | |
| 	if hasKey(&ss.cs) {
 | |
| 		_, ciphertext = encryptWithAd(&ss.cs, ss.h[:], plaintext)
 | |
| 	} else {
 | |
| 		ciphertext = plaintext
 | |
| 	}
 | |
| 	ss = mixHash(ss, ciphertext)
 | |
| 	return ss, ciphertext
 | |
| }
 | |
| 
 | |
| func decryptAndHash(ss *symmetricstate, ciphertext []byte) (*symmetricstate, []byte, bool) {
 | |
| 	var plaintext []byte
 | |
| 	var valid bool
 | |
| 	if hasKey(&ss.cs) {
 | |
| 		_, plaintext, valid = decryptWithAd(&ss.cs, ss.h[:], ciphertext)
 | |
| 	} else {
 | |
| 		plaintext, valid = ciphertext, true
 | |
| 	}
 | |
| 	ss = mixHash(ss, ciphertext)
 | |
| 	return ss, plaintext, valid
 | |
| }
 | |
| 
 | |
| func split(ss *symmetricstate) (cipherstate, cipherstate) {
 | |
| 	tempK1, tempK2, _ := getHkdf(ss.ck, []byte{})
 | |
| 	cs1 := initializeKey(tempK1)
 | |
| 	cs2 := initializeKey(tempK2)
 | |
| 	return cs1, cs2
 | |
| }
 | |
| 
 | |
| /* HandshakeState */
 | |
| 
 | |
| func initializeInitiator(prologue []byte, s keypair, rs [32]byte, psk [32]byte) handshakestate {
 | |
| 	var ss symmetricstate
 | |
| 	var e keypair
 | |
| 	var re [32]byte
 | |
| 	name := []byte("Noise_IK_25519_ChaChaPoly_BLAKE2s")
 | |
| 	ss = initializeSymmetric(name)
 | |
| 	mixHash(&ss, prologue)
 | |
| 	mixHash(&ss, rs[:])
 | |
| 	return handshakestate{ss, s, e, rs, re, psk}
 | |
| }
 | |
| 
 | |
| func initializeResponder(prologue []byte, s keypair, rs [32]byte, psk [32]byte) handshakestate {
 | |
| 	var ss symmetricstate
 | |
| 	var e keypair
 | |
| 	var re [32]byte
 | |
| 	name := []byte("Noise_IK_25519_ChaChaPoly_BLAKE2s")
 | |
| 	ss = initializeSymmetric(name)
 | |
| 	mixHash(&ss, prologue)
 | |
| 	mixHash(&ss, s.public_key[:])
 | |
| 	return handshakestate{ss, s, e, rs, re, psk}
 | |
| }
 | |
| 
 | |
| func writeMessageA(hs *handshakestate, payload []byte) (*handshakestate, messagebuffer) {
 | |
| 	ne, ns, ciphertext := emptyKey, []byte{}, []byte{}
 | |
| 	hs.e = generateKeypair()
 | |
| 	ne = hs.e.public_key
 | |
| 	mixHash(&hs.ss, ne[:])
 | |
| 	/* No PSK, so skipping mixKey */
 | |
| 	mixKey(&hs.ss, dh(hs.e.private_key, hs.rs))
 | |
| 	spk := make([]byte, len(hs.s.public_key))
 | |
| 	copy(spk[:], hs.s.public_key[:])
 | |
| 	_, ns = encryptAndHash(&hs.ss, spk)
 | |
| 	mixKey(&hs.ss, dh(hs.s.private_key, hs.rs))
 | |
| 	_, ciphertext = encryptAndHash(&hs.ss, payload)
 | |
| 	messageBuffer := messagebuffer{ne, ns, ciphertext}
 | |
| 	return hs, messageBuffer
 | |
| }
 | |
| 
 | |
| func writeMessageB(hs *handshakestate, payload []byte) ([32]byte, messagebuffer, cipherstate, cipherstate) {
 | |
| 	ne, ns, ciphertext := emptyKey, []byte{}, []byte{}
 | |
| 	hs.e = generateKeypair()
 | |
| 	ne = hs.e.public_key
 | |
| 	mixHash(&hs.ss, ne[:])
 | |
| 	/* No PSK, so skipping mixKey */
 | |
| 	mixKey(&hs.ss, dh(hs.e.private_key, hs.re))
 | |
| 	mixKey(&hs.ss, dh(hs.e.private_key, hs.rs))
 | |
| 	_, ciphertext = encryptAndHash(&hs.ss, payload)
 | |
| 	messageBuffer := messagebuffer{ne, ns, ciphertext}
 | |
| 	cs1, cs2 := split(&hs.ss)
 | |
| 	return hs.ss.h, messageBuffer, cs1, cs2
 | |
| }
 | |
| 
 | |
| func writeMessageRegular(cs *cipherstate, payload []byte) (*cipherstate, messagebuffer) {
 | |
| 	ne, ns, ciphertext := emptyKey, []byte{}, []byte{}
 | |
| 	cs, ciphertext = encryptWithAd(cs, []byte{}, payload)
 | |
| 	messageBuffer := messagebuffer{ne, ns, ciphertext}
 | |
| 	return cs, messageBuffer
 | |
| }
 | |
| 
 | |
| func readMessageA(hs *handshakestate, message *messagebuffer) (*handshakestate, []byte, bool) {
 | |
| 	valid1 := true
 | |
| 	if validatePublicKey(message.ne[:]) {
 | |
| 		hs.re = message.ne
 | |
| 	}
 | |
| 	mixHash(&hs.ss, hs.re[:])
 | |
| 	/* No PSK, so skipping mixKey */
 | |
| 	mixKey(&hs.ss, dh(hs.s.private_key, hs.re))
 | |
| 	_, ns, valid1 := decryptAndHash(&hs.ss, message.ns)
 | |
| 	if valid1 && len(ns) == 32 && validatePublicKey(message.ns[:]) {
 | |
| 		copy(hs.rs[:], ns)
 | |
| 	}
 | |
| 	mixKey(&hs.ss, dh(hs.s.private_key, hs.rs))
 | |
| 	_, plaintext, valid2 := decryptAndHash(&hs.ss, message.ciphertext)
 | |
| 	return hs, plaintext, (valid1 && valid2)
 | |
| }
 | |
| 
 | |
| func readMessageB(hs *handshakestate, message *messagebuffer) ([32]byte, []byte, bool, cipherstate, cipherstate) {
 | |
| 	valid1 := true
 | |
| 	if validatePublicKey(message.ne[:]) {
 | |
| 		hs.re = message.ne
 | |
| 	}
 | |
| 	mixHash(&hs.ss, hs.re[:])
 | |
| 	/* No PSK, so skipping mixKey */
 | |
| 	mixKey(&hs.ss, dh(hs.e.private_key, hs.re))
 | |
| 	mixKey(&hs.ss, dh(hs.s.private_key, hs.re))
 | |
| 	_, plaintext, valid2 := decryptAndHash(&hs.ss, message.ciphertext)
 | |
| 	cs1, cs2 := split(&hs.ss)
 | |
| 	return hs.ss.h, plaintext, (valid1 && valid2), cs1, cs2
 | |
| }
 | |
| 
 | |
| func readMessageRegular(cs *cipherstate, message *messagebuffer) (*cipherstate, []byte, bool) {
 | |
| 	/* No encrypted keys */
 | |
| 	_, plaintext, valid2 := decryptWithAd(cs, []byte{}, message.ciphertext)
 | |
| 	return cs, plaintext, valid2
 | |
| }
 | |
| 
 | |
| /* ---------------------------------------------------------------- *
 | |
|  * PROCESSES                                                        *
 | |
|  * ---------------------------------------------------------------- */
 | |
| 
 | |
| func InitSession(initiator bool, prologue []byte, s keypair, rs [32]byte) noisesession {
 | |
| 	var session noisesession
 | |
| 	psk := emptyKey
 | |
| 	if initiator {
 | |
| 		session.hs = initializeInitiator(prologue, s, rs, psk)
 | |
| 	} else {
 | |
| 		session.hs = initializeResponder(prologue, s, rs, psk)
 | |
| 	}
 | |
| 	session.i = initiator
 | |
| 	session.mc = 0
 | |
| 	return session
 | |
| }
 | |
| 
 | |
| func SendMessage(session *noisesession, message []byte) (*noisesession, messagebuffer) {
 | |
| 	var messageBuffer messagebuffer
 | |
| 	if session.mc == 0 {
 | |
| 		_, messageBuffer = writeMessageA(&session.hs, message)
 | |
| 	}
 | |
| 	if session.mc == 1 {
 | |
| 		session.h, messageBuffer, session.cs1, session.cs2 = writeMessageB(&session.hs, message)
 | |
| 		session.hs = handshakestate{}
 | |
| 	}
 | |
| 	if session.mc > 1 {
 | |
| 		if session.i {
 | |
| 			_, messageBuffer = writeMessageRegular(&session.cs1, message)
 | |
| 		} else {
 | |
| 			_, messageBuffer = writeMessageRegular(&session.cs2, message)
 | |
| 		}
 | |
| 	}
 | |
| 	session.mc = session.mc + 1
 | |
| 	return session, messageBuffer
 | |
| }
 | |
| 
 | |
| func RecvMessage(session *noisesession, message *messagebuffer) (*noisesession, []byte, bool) {
 | |
| 	var plaintext []byte
 | |
| 	var valid bool
 | |
| 	if session.mc == 0 {
 | |
| 		_, plaintext, valid = readMessageA(&session.hs, message)
 | |
| 	}
 | |
| 	if session.mc == 1 {
 | |
| 		session.h, plaintext, valid, session.cs1, session.cs2 = readMessageB(&session.hs, message)
 | |
| 		session.hs = handshakestate{}
 | |
| 	}
 | |
| 	if session.mc > 1 {
 | |
| 		if session.i {
 | |
| 			_, plaintext, valid = readMessageRegular(&session.cs2, message)
 | |
| 		} else {
 | |
| 			_, plaintext, valid = readMessageRegular(&session.cs1, message)
 | |
| 		}
 | |
| 	}
 | |
| 	session.mc = session.mc + 1
 | |
| 	return session, plaintext, valid
 | |
| }
 |