pske: add first version of post-quantum safe preshared key estasblishment feature

Signed-off-by: Steffen Vogel <post@steffenvogel.de>
This commit is contained in:
Steffen Vogel
2022-08-21 00:06:47 +02:00
parent d2b82e0bb7
commit a82aff3140
11 changed files with 347 additions and 12 deletions

View File

@@ -13,5 +13,6 @@ import (
_ "github.com/stv0g/cunicu/pkg/daemon/feature/hooks"
_ "github.com/stv0g/cunicu/pkg/daemon/feature/hsync"
_ "github.com/stv0g/cunicu/pkg/daemon/feature/pdisc"
_ "github.com/stv0g/cunicu/pkg/daemon/feature/pske"
_ "github.com/stv0g/cunicu/pkg/daemon/feature/rtsync"
)

2
go.mod
View File

@@ -8,6 +8,7 @@ require (
github.com/fsnotify/fsnotify v1.5.4
github.com/go-logr/logr v1.2.3
github.com/go-logr/zapr v1.2.3
github.com/go-piv/piv-go v1.10.0
github.com/google/nftables v0.0.0-20221002140148-535f5eb8da79
github.com/imdario/mergo v0.3.13
github.com/jpillora/backoff v1.0.0
@@ -21,6 +22,7 @@ require (
github.com/pion/stun v0.3.5
github.com/spf13/cobra v1.5.0
github.com/spf13/pflag v1.0.5
github.com/symbolicsoft/kyber-k2so v0.2.0
github.com/vishvananda/netlink v1.2.1-beta.2
go.uber.org/zap v1.23.0
golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2

5
go.sum
View File

@@ -101,6 +101,8 @@ github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
github.com/go-piv/piv-go v1.10.0 h1:P1Y1VjBI5DnXW0+YkKmTuh5opWnMIrKriUaIOblee9Q=
github.com/go-piv/piv-go v1.10.0/go.mod h1:NZ2zmjVkfFaL/CF8cVQ/pXdXtuj110zEKGdJM6fJZZM=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
@@ -379,6 +381,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stv0g/gont v1.6.3 h1:7cIU2+F7iaGOHN9Mu2QKlKqZ4Dzk98Fu/P9gUPtjNFc=
github.com/stv0g/gont v1.6.3/go.mod h1:Cl7BLHOE7YhWCPURgG0mvYBQBGGaNNQJeXSWNw+DJXs=
github.com/symbolicsoft/kyber-k2so v0.2.0 h1:JHb3OzwV9xjSH/Sib4jd2swZzp2GNot8gUpdydqQBfs=
github.com/symbolicsoft/kyber-k2so v0.2.0/go.mod h1:sfb01VbnBnAF3NJ0+k+Vh15N2tb0otGDxNfgE8al1is=
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
@@ -411,6 +415,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2 h1:x8vtB3zMecnlqZIwJNUUpwYKYSqCz5jXbiyv0ZJJZeI=
golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=

View File

@@ -51,12 +51,13 @@ var (
}
DefaultInterfaceSettings = InterfaceSettings{
DiscoverPeers: true,
DiscoverEndpoints: true,
SyncConfig: true,
SyncHosts: true,
SyncRoutes: true,
WatchRoutes: true,
DiscoverPeers: true,
DiscoverEndpoints: true,
SyncConfig: true,
SyncHosts: true,
SyncRoutes: true,
WatchRoutes: true,
EstablishPresharedKeys: true,
ICE: ICESettings{
URLs: DefaultICEURLs,

View File

@@ -117,11 +117,12 @@ type InterfaceSettings struct {
Peers map[string]PeerSettings `koanf:"peers,omitempty"`
// Feature flags
DiscoverEndpoints bool `koanf:"discover_endpoints,omitempty"`
DiscoverPeers bool `koanf:"discover_peers,omitempty"`
SyncConfig bool `koanf:"sync_config,omitempty"`
SyncRoutes bool `koanf:"sync_routes,omitempty"`
SyncHosts bool `koanf:"sync_hosts,omitempty"`
DiscoverEndpoints bool `koanf:"discover_endpoints,omitempty"`
DiscoverPeers bool `koanf:"discover_peers,omitempty"`
SyncConfig bool `koanf:"sync_config,omitempty"`
SyncRoutes bool `koanf:"sync_routes,omitempty"`
SyncHosts bool `koanf:"sync_hosts,omitempty"`
EstablishPresharedKeys bool `koanf:"establish_psk,omitempty"`
WatchConfig bool `koanf:"watch_config,omitempty"`
WatchRoutes bool `koanf:"watch_routes,omitempty"`

View File

@@ -0,0 +1,17 @@
package pske
import (
"github.com/stv0g/cunicu/pkg/core"
"go.uber.org/zap"
)
func (i *Interface) OnPeerAdded(cp *core.Peer) {
if cp.PresharedKey().IsSet() {
i.logger.Debug("Ignoring peer as it already has a PSK configured", zap.Any("peer", cp))
return
}
i.Peers[cp] = i.NewPeer(cp, i)
}
func (i *Interface) OnPeerRemoved(p *core.Peer) {}

View File

@@ -0,0 +1,128 @@
package pske
import (
"context"
kyber "github.com/symbolicsoft/kyber-k2so"
"go.uber.org/zap"
"github.com/stv0g/cunicu/pkg/core"
"github.com/stv0g/cunicu/pkg/crypto"
"github.com/stv0g/cunicu/pkg/signaling"
pskeproto "github.com/stv0g/cunicu/pkg/proto/feature/pske"
)
type Peer struct {
*core.Peer
Interface *Interface
SecretKey KyberSecretKey
logger *zap.Logger
}
func (i *Interface) NewPeer(cp *core.Peer, kem *Interface) *Peer {
p := &Peer{
Peer: cp,
Interface: i,
logger: i.logger.With(zap.String("peer", cp.String())),
}
go p.EstablishPSK()
return p
}
func (p *Peer) EstablishPSK() {
var err error
ctx := context.Background()
kp := p.PublicPrivateKeyPair()
if _, err := p.Interface.Daemon.Backend.Subscribe(ctx, kp, p); err != nil {
p.logger.Error("Failed to subscribe to message", zap.Error(err))
return
}
if p.IsControlling() {
var pk KyberPublicKey
// Generate new key pair
if p.SecretKey, pk, err = kyber.KemKeypair1024(); err != nil {
p.logger.Error("Failed to generate Kyber key pair", zap.Error(err))
}
// Send public key
msg := signaling.Message{
Pske: &pskeproto.PresharedKeyEstablishment{
PublicKey: pk[:],
},
}
if err := p.Interface.Daemon.Backend.Publish(ctx, kp, &msg); err != nil {
p.logger.Error("Failed to publish public key", zap.Error(err))
}
}
}
func (p *Peer) OnSignalingMessage(kp *crypto.PublicKeyPair, msg *signaling.Message) {
if msg.Pske == nil {
return
}
var err error
var psk crypto.Key
if p.IsControlling() {
if msg.Pske.CipherText == nil {
p.logger.Error("Expected cipher text")
return
}
if ctLen := len(msg.Pske.CipherText); ctLen != kyber.Kyber1024CTBytes {
p.logger.Error("Invalid cipher text length", zap.Int("len", ctLen))
return
}
// Decrypt cipher text
ct := *(*KyberCipherText)(msg.Pske.CipherText)
if psk, err = kyber.KemDecrypt1024(ct, p.SecretKey); err != nil {
p.logger.Error("Failed to decrypt PSK", zap.Error(err))
return
}
} else {
if msg.Pske.PublicKey == nil {
p.logger.Error("Expected public key")
return
}
var ct KyberCipherText
// Encrypt cipher text
pk := *(*KyberPublicKey)(msg.Pske.PublicKey)
if ct, psk, err = kyber.KemEncrypt1024(pk); err != nil {
p.logger.Error("Failed to encrypt cipher text", zap.Error(err))
return
}
// Publish cipher text
ctx := context.Background()
kp := p.PublicPrivateKeyPair()
msg := &signaling.Message{
Pske: &pskeproto.PresharedKeyEstablishment{
CipherText: ct[:],
},
}
if err := p.Interface.Daemon.Backend.Publish(ctx, kp, msg); err != nil {
p.logger.Error("Failed to publish cipher text", zap.Error(err))
}
}
if err := p.SetPresharedKey(&psk); err != nil {
p.logger.Error("Failed to update preshared key", zap.Error(err))
}
}

View File

@@ -0,0 +1,130 @@
package pske
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"io"
"os"
"strings"
"github.com/go-piv/piv-go/piv"
)
func main() {
if len(os.Args) < 2 {
return
}
// List all smart cards connected to the system.
cards, err := piv.Cards()
if err != nil {
panic(err)
}
// Find a YubiKey and open the reader.
var yk *piv.YubiKey
for _, card := range cards {
if strings.Contains(strings.ToLower(card), "yubikey") {
if yk, err = piv.Open(card); err != nil {
panic(err)
}
break
}
}
if yk == nil {
panic("no yubikey found")
}
defer yk.Close()
switch os.Args[1] {
case "cert":
cert(yk)
case "decrypt":
decrypt(yk)
case "encrypt":
encrypt(yk)
}
}
func cert(yk *piv.YubiKey) {
// sn, _ := yk.Serial()
// fmt.Printf("Version: %d.%d.%d\n", yk.Version().Major, yk.Version().Minor, yk.Version().Patch)
// fmt.Printf("Serial: %d\n", sn)
crt, err := yk.Certificate(piv.SlotSignature)
if err != nil {
panic(err)
}
// Print the certificate
// result, err := certinfo.CertificateText(crt)
// if err != nil {
// log.Fatal(err)
// }
// fmt.Print(result)
pemPayload, _ := x509.MarshalPKIXPublicKey(crt.PublicKey.(*rsa.PublicKey))
pem.Encode(os.Stdout, &pem.Block{
Type: "PUBLIC KEY",
Bytes: pemPayload,
})
}
func encrypt(yk *piv.YubiKey) {
pt, err := io.ReadAll(os.Stdin)
if err != nil {
panic(err)
}
crt, err := yk.Certificate(piv.SlotSignature)
if err != nil {
panic(err)
}
pk := crt.PublicKey.(*rsa.PublicKey)
ct, err := rsa.EncryptPKCS1v15(rand.Reader, pk, pt)
if err != nil {
panic(err)
}
os.Stdout.Write(ct)
}
func decrypt(yk *piv.YubiKey) {
ct, err := io.ReadAll(os.Stdin)
if err != nil {
panic(err)
}
crt, err := yk.Certificate(piv.SlotSignature)
if err != nil {
panic(err)
}
sk, err := yk.PrivateKey(piv.SlotSignature, crt.PublicKey, piv.KeyAuth{
PIN: "0000",
})
if err != nil {
panic(err)
}
dec, ok := sk.(crypto.Decrypter)
if !ok {
panic("no rsa key?")
}
pt, err := dec.Decrypt(rand.Reader, ct, nil)
if err != nil {
panic(err)
}
fmt.Print(string(pt))
}

View File

@@ -0,0 +1,43 @@
// Package pske uses the Kyber Key Establishment Mechanism (KEM) to establish Preshared Keys (PSKs) between two WireGuard peers
package pske
import (
"github.com/stv0g/cunicu/pkg/core"
"github.com/stv0g/cunicu/pkg/daemon"
"go.uber.org/zap"
)
func init() {
daemon.RegisterFeature("pske", "Preshared key establishment ", New, 110)
}
type Interface struct {
*daemon.Interface
Peers map[*core.Peer]*Peer
logger *zap.Logger
}
func New(i *daemon.Interface) (daemon.Feature, error) {
if !i.Settings.EstablishPresharedKeys {
return nil, nil
}
p := &Interface{
Interface: i,
Peers: map[*core.Peer]*Peer{},
logger: zap.L().Named("pske").With(zap.String("intf", i.Name())),
}
i.OnPeer(p)
return p, nil
}
func (i *Interface) Start() error {
i.logger.Info("Started post-quantum preshared key establishment")
return nil
}

View File

@@ -0,0 +1,7 @@
package pske
import kyber "github.com/symbolicsoft/kyber-k2so"
type KyberCipherText [kyber.Kyber1024CTBytes]byte
type KyberPublicKey [kyber.Kyber1024PKBytes]byte
type KyberSecretKey [kyber.Kyber1024SKBytes]byte

View File

@@ -5,7 +5,7 @@ package proto
//go:generate protoc --proto_path=../../proto --go_out=. --go_opt=paths=import,module=github.com/stv0g/cunicu/pkg/proto core/peer.proto core/interface.proto core/net.proto
//go:generate protoc --proto_path=../../proto --go_out=. --go_opt=paths=import,module=github.com/stv0g/cunicu/pkg/proto signaling/signaling.proto signaling/relay.proto
//go:generate protoc --proto_path=../../proto --go_out=. --go_opt=paths=import,module=github.com/stv0g/cunicu/pkg/proto rpc/daemon.proto rpc/epdisc.proto rpc/event.proto rpc/signaling.proto rpc/invitation.proto
//go:generate protoc --proto_path=../../proto --go_out=. --go_opt=paths=import,module=github.com/stv0g/cunicu/pkg/proto feature/epdisc.proto feature/epdisc_candidate.proto feature/pdisc.proto feature/pske.proto feature/hooks.proto
//go:generate protoc --proto_path=../../proto --go_out=. --go_opt=paths=import,module=github.com/stv0g/cunicu/pkg/proto feature/epdisc.proto feature/epdisc_candidate.proto feature/pdisc.proto feature/pske.proto feature/hooks.proto feature/pske.proto
//go:generate protoc --proto_path=../../proto --go-grpc_out=. --go-grpc_opt=paths=import,module=github.com/stv0g/cunicu/pkg/proto rpc/daemon.proto rpc/epdisc.proto rpc/signaling.proto
//go:generate protoc --proto_path=../../proto --go-grpc_out=. --go-grpc_opt=paths=import,module=github.com/stv0g/cunicu/pkg/proto signaling/signaling.proto signaling/relay.proto