mirror of
https://codeberg.org/cunicu/cunicu.git
synced 2025-09-26 21:01:14 +08:00
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:
@@ -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
2
go.mod
@@ -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
5
go.sum
@@ -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=
|
||||
|
@@ -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,
|
||||
|
@@ -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"`
|
||||
|
17
pkg/daemon/feature/pske/handlers.go
Normal file
17
pkg/daemon/feature/pske/handlers.go
Normal 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) {}
|
128
pkg/daemon/feature/pske/peer.go
Normal file
128
pkg/daemon/feature/pske/peer.go
Normal 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))
|
||||
}
|
||||
}
|
130
pkg/daemon/feature/pske/piv.go_
Normal file
130
pkg/daemon/feature/pske/piv.go_
Normal 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))
|
||||
}
|
43
pkg/daemon/feature/pske/pske.go
Normal file
43
pkg/daemon/feature/pske/pske.go
Normal 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
|
||||
}
|
7
pkg/daemon/feature/pske/types.go
Normal file
7
pkg/daemon/feature/pske/types.go
Normal 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
|
@@ -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
|
||||
|
Reference in New Issue
Block a user