mirror of https://github.com/tailscale/tailscale/
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
654 lines
18 KiB
Go
654 lines
18 KiB
Go
// Copyright (c) 2019 Tailscale Inc & AUTHORS All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package magicsock
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/hmac"
|
|
"crypto/subtle"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"hash"
|
|
"net"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/tailscale/wireguard-go/conn"
|
|
"github.com/tailscale/wireguard-go/tai64n"
|
|
"golang.org/x/crypto/blake2s"
|
|
"golang.org/x/crypto/chacha20poly1305"
|
|
"golang.org/x/crypto/poly1305"
|
|
"inet.af/netaddr"
|
|
"tailscale.com/ipn/ipnstate"
|
|
"tailscale.com/types/key"
|
|
"tailscale.com/types/logger"
|
|
"tailscale.com/types/wgkey"
|
|
"tailscale.com/wgengine/wgcfg"
|
|
)
|
|
|
|
var (
|
|
errNoDestinations = errors.New("magicsock: no destinations")
|
|
errDisabled = errors.New("magicsock: legacy networking disabled")
|
|
)
|
|
|
|
func (c *Conn) createLegacyEndpointLocked(pk key.Public, addrs string) (conn.Endpoint, error) {
|
|
if c.disableLegacy {
|
|
return nil, errDisabled
|
|
}
|
|
|
|
a := &addrSet{
|
|
Logf: c.logf,
|
|
publicKey: pk,
|
|
curAddr: -1,
|
|
}
|
|
|
|
if addrs != "" {
|
|
for _, ep := range strings.Split(addrs, ",") {
|
|
ipp, err := netaddr.ParseIPPort(ep)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("bogus address %q", ep)
|
|
}
|
|
a.ipPorts = append(a.ipPorts, ipp)
|
|
}
|
|
}
|
|
|
|
// If this endpoint is being updated, remember its old set of
|
|
// endpoints so we can remove any (from c.addrsByUDP) that are
|
|
// not in the new set.
|
|
var oldIPP []netaddr.IPPort
|
|
if preva, ok := c.addrsByKey[pk]; ok {
|
|
oldIPP = preva.ipPorts
|
|
}
|
|
c.addrsByKey[pk] = a
|
|
|
|
// Add entries to c.addrsByUDP.
|
|
for _, ipp := range a.ipPorts {
|
|
if ipp.IP == derpMagicIPAddr {
|
|
continue
|
|
}
|
|
c.addrsByUDP[ipp] = a
|
|
}
|
|
|
|
// Remove previous c.addrsByUDP entries that are no longer in the new set.
|
|
for _, ipp := range oldIPP {
|
|
if ipp.IP != derpMagicIPAddr && c.addrsByUDP[ipp] != a {
|
|
delete(c.addrsByUDP, ipp)
|
|
}
|
|
}
|
|
|
|
return a, nil
|
|
}
|
|
|
|
func (c *Conn) findLegacyEndpointLocked(ipp netaddr.IPPort, packet []byte) conn.Endpoint {
|
|
if c.disableLegacy {
|
|
return nil
|
|
}
|
|
|
|
// Pre-disco: look up their addrSet.
|
|
if as, ok := c.addrsByUDP[ipp]; ok {
|
|
as.updateDst(ipp)
|
|
return as
|
|
}
|
|
|
|
// We don't know who this peer is. It's possible that it's one of
|
|
// our legitimate peers and they've roamed to an address we don't
|
|
// know. If this is a handshake packet, we can try to identify the
|
|
// peer in question.
|
|
if as := c.peerFromPacketLocked(packet); as != nil {
|
|
as.updateDst(ipp)
|
|
return as
|
|
}
|
|
|
|
// We have no idea who this is, drop the packet.
|
|
//
|
|
// In the past, when this magicsock implementation was the main
|
|
// one, we tried harder to find a match here: we would pass the
|
|
// packet into wireguard-go with a "singleEndpoint" implementation
|
|
// that wrapped the UDPAddr. Then, a patch we added to
|
|
// wireguard-go would call UpdateDst on that singleEndpoint after
|
|
// decrypting the packet and identifying the peer (if any),
|
|
// allowing us to update the relevant addrSet.
|
|
//
|
|
// This was a significant out of tree patch to wireguard-go, so we
|
|
// got rid of it, and instead switched to this logic you're
|
|
// reading now, which makes a best effort to identify sources for
|
|
// handshake packets (because they're relatively easy to turn into
|
|
// a peer public key statelessly), but otherwise drops packets
|
|
// that come from "roaming" addresses that aren't known to
|
|
// magicsock.
|
|
//
|
|
// The practical consequence of this is that some complex NAT
|
|
// traversal cases will now fail between a very old Tailscale
|
|
// client (0.96 and earlier) and a very new Tailscale
|
|
// client. However, those scenarios were likely also failing on
|
|
// all-old clients, because the probabilistic NAT opening didn't
|
|
// work reliably. So, in practice, this simplification means
|
|
// connectivity looks like this:
|
|
//
|
|
// - old+old client: unchanged
|
|
// - old+new client (easy network topology): unchanged
|
|
// - old+new client (hard network topology): was bad, now a bit worse
|
|
// - new+new client: unchanged
|
|
//
|
|
// This degradation is acceptable in that it continues to support
|
|
// the incremental upgrade of old clients that currently work
|
|
// well, which is our primary goal for the <100 clients still left
|
|
// on the oldest pre-DERP versions (as of 2021-01-12).
|
|
return nil
|
|
}
|
|
|
|
func (c *Conn) resetAddrSetStatesLocked() {
|
|
for _, as := range c.addrsByKey {
|
|
as.curAddr = -1
|
|
as.stopSpray = as.timeNow().Add(sprayPeriod)
|
|
}
|
|
}
|
|
|
|
func (c *Conn) sendAddrSet(b []byte, as *addrSet) error {
|
|
if c.disableLegacy {
|
|
return errDisabled
|
|
}
|
|
|
|
var addrBuf [8]netaddr.IPPort
|
|
dsts, roamAddr := as.appendDests(addrBuf[:0], b)
|
|
|
|
if len(dsts) == 0 {
|
|
return errNoDestinations
|
|
}
|
|
|
|
var success bool
|
|
var ret error
|
|
for _, addr := range dsts {
|
|
sent, err := c.sendAddr(addr, as.publicKey, b)
|
|
if sent {
|
|
success = true
|
|
} else if ret == nil {
|
|
ret = err
|
|
}
|
|
if err != nil && addr != roamAddr && c.sendLogLimit.Allow() {
|
|
if c.connCtx.Err() == nil { // don't log if we're closed
|
|
c.logf("magicsock: Conn.Send(%v): %v", addr, err)
|
|
}
|
|
}
|
|
}
|
|
if success {
|
|
return nil
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// peerFromPacketLocked extracts returns the addrSet for the peer who sent
|
|
// packet, if derivable.
|
|
//
|
|
// The derived addrSet is a hint, not a cryptographically strong
|
|
// assertion. The returned value MUST NOT be used for any security
|
|
// critical function. Callers MUST assume that the addrset can be
|
|
// picked by a remote attacker.
|
|
func (c *Conn) peerFromPacketLocked(packet []byte) *addrSet {
|
|
if len(packet) < 4 {
|
|
return nil
|
|
}
|
|
msgType := binary.LittleEndian.Uint32(packet[:4])
|
|
if msgType != messageInitiationType {
|
|
// Can't get peer out of a non-handshake packet.
|
|
return nil
|
|
}
|
|
|
|
var msg messageInitiation
|
|
reader := bytes.NewReader(packet)
|
|
err := binary.Read(reader, binary.LittleEndian, &msg)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
// Process just enough of the handshake to extract the long-term
|
|
// peer public key. We don't verify the handshake all the way, so
|
|
// this may be a spoofed packet. The extracted peer MUST NOT be
|
|
// used for any security critical function. In our case, we use it
|
|
// as a hint for roaming addresses.
|
|
var (
|
|
pub = c.privateKey.Public()
|
|
hash [blake2s.Size]byte
|
|
chainKey [blake2s.Size]byte
|
|
peerPK key.Public
|
|
boxKey [chacha20poly1305.KeySize]byte
|
|
)
|
|
|
|
mixHash(&hash, &initialHash, pub[:])
|
|
mixHash(&hash, &hash, msg.Ephemeral[:])
|
|
mixKey(&chainKey, &initialChainKey, msg.Ephemeral[:])
|
|
|
|
ss := c.privateKey.SharedSecret(key.Public(msg.Ephemeral))
|
|
if isZero(ss[:]) {
|
|
return nil
|
|
}
|
|
|
|
kdf2(&chainKey, &boxKey, chainKey[:], ss[:])
|
|
aead, _ := chacha20poly1305.New(boxKey[:])
|
|
_, err = aead.Open(peerPK[:0], zeroNonce[:], msg.Static[:], hash[:])
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
return c.addrsByKey[peerPK]
|
|
}
|
|
|
|
func shouldSprayPacket(b []byte) bool {
|
|
if len(b) < 4 {
|
|
return false
|
|
}
|
|
msgType := binary.LittleEndian.Uint32(b[:4])
|
|
switch msgType {
|
|
case messageInitiationType,
|
|
messageResponseType,
|
|
messageCookieReplyType: // TODO: necessary?
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
const sprayPeriod = 3 * time.Second
|
|
|
|
// appendDests appends to dsts the destinations that b should be
|
|
// written to in order to reach as. Some of the returned IPPorts may
|
|
// be fake addrs representing DERP servers.
|
|
//
|
|
// It also returns as's current roamAddr, if any.
|
|
func (as *addrSet) appendDests(dsts []netaddr.IPPort, b []byte) (_ []netaddr.IPPort, roamAddr netaddr.IPPort) {
|
|
spray := shouldSprayPacket(b) // true for handshakes
|
|
now := as.timeNow()
|
|
|
|
as.mu.Lock()
|
|
defer as.mu.Unlock()
|
|
|
|
as.lastSend = now
|
|
|
|
// Spray logic.
|
|
//
|
|
// After exchanging a handshake with a peer, we send some outbound
|
|
// packets to every endpoint of that peer. These packets are spaced out
|
|
// over several seconds to make sure that our peer has an opportunity to
|
|
// send its own spray packet to us before we are done spraying.
|
|
//
|
|
// Multiple packets are necessary because we have to both establish the
|
|
// NAT mappings between two peers *and use* the mappings to switch away
|
|
// from DERP to a higher-priority UDP endpoint.
|
|
const sprayFreq = 250 * time.Millisecond
|
|
if spray {
|
|
as.lastSpray = now
|
|
as.stopSpray = now.Add(sprayPeriod)
|
|
|
|
// Reset our favorite route on new handshakes so we
|
|
// can downgrade to a worse path if our better path
|
|
// goes away. (https://github.com/tailscale/tailscale/issues/92)
|
|
as.curAddr = -1
|
|
} else if now.Before(as.stopSpray) {
|
|
// We are in the spray window. If it has been sprayFreq since we
|
|
// last sprayed a packet, spray this packet.
|
|
if now.Sub(as.lastSpray) >= sprayFreq {
|
|
spray = true
|
|
as.lastSpray = now
|
|
}
|
|
}
|
|
|
|
// Pick our destination address(es).
|
|
switch {
|
|
case spray:
|
|
// This packet is being sprayed to all addresses.
|
|
for i := range as.ipPorts {
|
|
dsts = append(dsts, as.ipPorts[i])
|
|
}
|
|
if as.roamAddr != nil {
|
|
dsts = append(dsts, *as.roamAddr)
|
|
}
|
|
case as.roamAddr != nil:
|
|
// We have a roaming address, prefer it over other addrs.
|
|
// TODO(danderson): this is not correct, there's no reason
|
|
// roamAddr should be special like this.
|
|
dsts = append(dsts, *as.roamAddr)
|
|
case as.curAddr != -1:
|
|
if as.curAddr >= len(as.ipPorts) {
|
|
as.Logf("[unexpected] magicsock bug: as.curAddr >= len(as.ipPorts): %d >= %d", as.curAddr, len(as.ipPorts))
|
|
break
|
|
}
|
|
// No roaming addr, but we've seen packets from a known peer
|
|
// addr, so keep using that one.
|
|
dsts = append(dsts, as.ipPorts[as.curAddr])
|
|
default:
|
|
// We know nothing about how to reach this peer, and we're not
|
|
// spraying. Use the first address in the array, which will
|
|
// usually be a DERP address that guarantees connectivity.
|
|
if len(as.ipPorts) > 0 {
|
|
dsts = append(dsts, as.ipPorts[0])
|
|
}
|
|
}
|
|
|
|
if logPacketDests {
|
|
as.Logf("spray=%v; roam=%v; dests=%v", spray, as.roamAddr, dsts)
|
|
}
|
|
if as.roamAddr != nil {
|
|
roamAddr = *as.roamAddr
|
|
}
|
|
return dsts, roamAddr
|
|
}
|
|
|
|
// addrSet is a set of UDP addresses that implements wireguard/conn.Endpoint.
|
|
//
|
|
// This is the legacy endpoint for peers that don't support discovery;
|
|
// it predates discoEndpoint.
|
|
type addrSet struct {
|
|
publicKey key.Public // peer public key used for DERP communication
|
|
|
|
// ipPorts is an ordered priority list provided by wgengine,
|
|
// sorted from expensive+slow+reliable at the begnining to
|
|
// fast+cheap at the end. More concretely, it's typically:
|
|
//
|
|
// [DERP fakeip:node, Global IP:port, LAN ip:port]
|
|
//
|
|
// But there could be multiple or none of each.
|
|
ipPorts []netaddr.IPPort
|
|
|
|
// clock, if non-nil, is used in tests instead of time.Now.
|
|
clock func() time.Time
|
|
Logf logger.Logf // must not be nil
|
|
|
|
mu sync.Mutex // guards following fields
|
|
|
|
lastSend time.Time
|
|
|
|
// roamAddr is non-nil if/when we receive a correctly signed
|
|
// WireGuard packet from an unexpected address. If so, we
|
|
// remember it and send responses there in the future, but
|
|
// this should hopefully never be used (or at least used
|
|
// rarely) in the case that all the components of Tailscale
|
|
// are correctly learning/sharing the network map details.
|
|
roamAddr *netaddr.IPPort
|
|
|
|
// curAddr is an index into addrs of the highest-priority
|
|
// address a valid packet has been received from so far.
|
|
// If no valid packet from addrs has been received, curAddr is -1.
|
|
curAddr int
|
|
|
|
// stopSpray is the time after which we stop spraying packets.
|
|
stopSpray time.Time
|
|
|
|
// lastSpray is the last time we sprayed a packet.
|
|
lastSpray time.Time
|
|
|
|
// loggedLogPriMask is a bit field of that tracks whether
|
|
// we've already logged about receiving a packet from a low
|
|
// priority ("low-pri") address when we already have curAddr
|
|
// set to a better one. This is only to suppress some
|
|
// redundant logs.
|
|
loggedLogPriMask uint32
|
|
}
|
|
|
|
// derpID returns this addrSet's home DERP node, or 0 if none is found.
|
|
func (as *addrSet) derpID() int {
|
|
for _, ua := range as.ipPorts {
|
|
if ua.IP == derpMagicIPAddr {
|
|
return int(ua.Port)
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (as *addrSet) timeNow() time.Time {
|
|
if as.clock != nil {
|
|
return as.clock()
|
|
}
|
|
return time.Now()
|
|
}
|
|
|
|
var noAddr, _ = netaddr.FromStdAddr(net.ParseIP("127.127.127.127"), 127, "")
|
|
|
|
func (a *addrSet) dst() netaddr.IPPort {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
|
|
if a.roamAddr != nil {
|
|
return *a.roamAddr
|
|
}
|
|
if len(a.ipPorts) == 0 {
|
|
return noAddr
|
|
}
|
|
i := a.curAddr
|
|
if i == -1 {
|
|
i = 0
|
|
}
|
|
return a.ipPorts[i]
|
|
}
|
|
|
|
func (a *addrSet) DstToBytes() []byte {
|
|
return packIPPort(a.dst())
|
|
}
|
|
func (a *addrSet) DstToString() string {
|
|
var addrs []string
|
|
for _, addr := range a.ipPorts {
|
|
addrs = append(addrs, addr.String())
|
|
}
|
|
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
if a.roamAddr != nil {
|
|
addrs = append(addrs, a.roamAddr.String())
|
|
}
|
|
return strings.Join(addrs, ",")
|
|
}
|
|
func (a *addrSet) DstIP() net.IP {
|
|
return a.dst().IP.IPAddr().IP // TODO: add netaddr accessor to cut an alloc here?
|
|
}
|
|
func (a *addrSet) SrcIP() net.IP { return nil }
|
|
func (a *addrSet) SrcToString() string { return "" }
|
|
func (a *addrSet) ClearSrc() {}
|
|
|
|
// updateDst records receipt of a packet from new. This is used to
|
|
// potentially update the transmit address used for this addrSet.
|
|
func (a *addrSet) updateDst(new netaddr.IPPort) error {
|
|
if new.IP == derpMagicIPAddr {
|
|
// Never consider DERP addresses as a viable candidate for
|
|
// either curAddr or roamAddr. It's only ever a last resort
|
|
// choice, never a preferred choice.
|
|
// This is a hot path for established connections.
|
|
return nil
|
|
}
|
|
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
|
|
if a.roamAddr != nil && new == *a.roamAddr {
|
|
// Packet from the current roaming address, no logging.
|
|
// This is a hot path for established connections.
|
|
return nil
|
|
}
|
|
if a.roamAddr == nil && a.curAddr >= 0 && new == a.ipPorts[a.curAddr] {
|
|
// Packet from current-priority address, no logging.
|
|
// This is a hot path for established connections.
|
|
return nil
|
|
}
|
|
|
|
index := -1
|
|
for i := range a.ipPorts {
|
|
if new == a.ipPorts[i] {
|
|
index = i
|
|
break
|
|
}
|
|
}
|
|
|
|
publicKey := wgkey.Key(a.publicKey)
|
|
pk := publicKey.ShortString()
|
|
old := "<none>"
|
|
if a.curAddr >= 0 {
|
|
old = a.ipPorts[a.curAddr].String()
|
|
}
|
|
|
|
switch {
|
|
case index == -1:
|
|
if a.roamAddr == nil {
|
|
a.Logf("[v1] magicsock: rx %s from roaming address %s, set as new priority", pk, new)
|
|
} else {
|
|
a.Logf("[v1] magicsock: rx %s from roaming address %s, replaces roaming address %s", pk, new, a.roamAddr)
|
|
}
|
|
a.roamAddr = &new
|
|
|
|
case a.roamAddr != nil:
|
|
a.Logf("[v1] magicsock: rx %s from known %s (%d), replaces roaming address %s", pk, new, index, a.roamAddr)
|
|
a.roamAddr = nil
|
|
a.curAddr = index
|
|
a.loggedLogPriMask = 0
|
|
|
|
case a.curAddr == -1:
|
|
a.Logf("[v1] magicsock: rx %s from %s (%d/%d), set as new priority", pk, new, index, len(a.ipPorts))
|
|
a.curAddr = index
|
|
a.loggedLogPriMask = 0
|
|
|
|
case index < a.curAddr:
|
|
if 1 <= index && index <= 32 && (a.loggedLogPriMask&1<<(index-1)) == 0 {
|
|
a.Logf("[v1] magicsock: rx %s from low-pri %s (%d), keeping current %s (%d)", pk, new, index, old, a.curAddr)
|
|
a.loggedLogPriMask |= 1 << (index - 1)
|
|
}
|
|
|
|
default: // index > a.curAddr
|
|
a.Logf("[v1] magicsock: rx %s from %s (%d/%d), replaces old priority %s", pk, new, index, len(a.ipPorts), old)
|
|
a.curAddr = index
|
|
a.loggedLogPriMask = 0
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *addrSet) String() string {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
|
|
buf := new(strings.Builder)
|
|
buf.WriteByte('[')
|
|
if a.roamAddr != nil {
|
|
buf.WriteString("roam:")
|
|
sbPrintAddr(buf, *a.roamAddr)
|
|
}
|
|
for i, addr := range a.ipPorts {
|
|
if i > 0 || a.roamAddr != nil {
|
|
buf.WriteString(", ")
|
|
}
|
|
sbPrintAddr(buf, addr)
|
|
if a.curAddr == i {
|
|
buf.WriteByte('*')
|
|
}
|
|
}
|
|
buf.WriteByte(']')
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
func (as *addrSet) populatePeerStatus(ps *ipnstate.PeerStatus) {
|
|
as.mu.Lock()
|
|
defer as.mu.Unlock()
|
|
|
|
ps.LastWrite = as.lastSend
|
|
for i, ua := range as.ipPorts {
|
|
if ua.IP == derpMagicIPAddr {
|
|
continue
|
|
}
|
|
uaStr := ua.String()
|
|
ps.Addrs = append(ps.Addrs, uaStr)
|
|
if as.curAddr == i {
|
|
ps.CurAddr = uaStr
|
|
}
|
|
}
|
|
if as.roamAddr != nil {
|
|
ps.CurAddr = ippDebugString(*as.roamAddr)
|
|
}
|
|
}
|
|
|
|
// Message types copied from wireguard-go/device/noise-protocol.go
|
|
const (
|
|
messageInitiationType = 1
|
|
messageResponseType = 2
|
|
messageCookieReplyType = 3
|
|
)
|
|
|
|
// Cryptographic constants copied from wireguard-go/device/noise-protocol.go
|
|
var (
|
|
noiseConstruction = "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s"
|
|
wgIdentifier = "WireGuard v1 zx2c4 Jason@zx2c4.com"
|
|
initialChainKey [blake2s.Size]byte
|
|
initialHash [blake2s.Size]byte
|
|
zeroNonce [chacha20poly1305.NonceSize]byte
|
|
)
|
|
|
|
func init() {
|
|
initialChainKey = blake2s.Sum256([]byte(noiseConstruction))
|
|
mixHash(&initialHash, &initialChainKey, []byte(wgIdentifier))
|
|
}
|
|
|
|
// messageInitiation is the same as wireguard-go's MessageInitiation,
|
|
// from wireguard-go/device/noise-protocol.go.
|
|
type messageInitiation struct {
|
|
Type uint32
|
|
Sender uint32
|
|
Ephemeral wgcfg.Key
|
|
Static [wgcfg.KeySize + poly1305.TagSize]byte
|
|
Timestamp [tai64n.TimestampSize + poly1305.TagSize]byte
|
|
MAC1 [blake2s.Size128]byte
|
|
MAC2 [blake2s.Size128]byte
|
|
}
|
|
|
|
func mixKey(dst *[blake2s.Size]byte, c *[blake2s.Size]byte, data []byte) {
|
|
kdf1(dst, c[:], data)
|
|
}
|
|
|
|
func mixHash(dst *[blake2s.Size]byte, h *[blake2s.Size]byte, data []byte) {
|
|
hash, _ := blake2s.New256(nil)
|
|
hash.Write(h[:])
|
|
hash.Write(data)
|
|
hash.Sum(dst[:0])
|
|
hash.Reset()
|
|
}
|
|
|
|
func hmac1(sum *[blake2s.Size]byte, key, in0 []byte) {
|
|
mac := hmac.New(func() hash.Hash {
|
|
h, _ := blake2s.New256(nil)
|
|
return h
|
|
}, key)
|
|
mac.Write(in0)
|
|
mac.Sum(sum[:0])
|
|
}
|
|
|
|
func hmac2(sum *[blake2s.Size]byte, key, in0, in1 []byte) {
|
|
mac := hmac.New(func() hash.Hash {
|
|
h, _ := blake2s.New256(nil)
|
|
return h
|
|
}, key)
|
|
mac.Write(in0)
|
|
mac.Write(in1)
|
|
mac.Sum(sum[:0])
|
|
}
|
|
|
|
func kdf1(t0 *[blake2s.Size]byte, key, input []byte) {
|
|
hmac1(t0, key, input)
|
|
hmac1(t0, t0[:], []byte{0x1})
|
|
}
|
|
|
|
func kdf2(t0, t1 *[blake2s.Size]byte, key, input []byte) {
|
|
var prk [blake2s.Size]byte
|
|
hmac1(&prk, key, input)
|
|
hmac1(t0, prk[:], []byte{0x1})
|
|
hmac2(t1, prk[:], t0[:], []byte{0x2})
|
|
for i := range prk[:] {
|
|
prk[i] = 0
|
|
}
|
|
}
|
|
|
|
func isZero(val []byte) bool {
|
|
acc := 1
|
|
for _, b := range val {
|
|
acc &= subtle.ConstantTimeByteEq(b, 0)
|
|
}
|
|
return acc == 1
|
|
}
|