Merge remote-tracking branch 'origin/master' into holepunch-remote-allow-list

This commit is contained in:
Wade Simmons 2024-09-13 16:16:17 -04:00
commit c87e2cbc70
27 changed files with 867 additions and 184 deletions

View File

@ -67,6 +67,24 @@ jobs:
- name: End 2 end
run: make e2evv GOEXPERIMENT=boringcrypto CGO_ENABLED=1
test-linux-pkcs11:
name: Build and test on linux with pkcs11
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
check-latest: true
- name: Build
run: make bin-pkcs11
- name: Test
run: make test-pkcs11
test:
name: Build and test on ${{ matrix.os }}
runs-on: ${{ matrix.os }}

1
.gitignore vendored
View File

@ -13,5 +13,6 @@
**.crt
**.key
**.pem
**.pub
!/examples/quickstart-vagrant/ansible/roles/nebula/files/vagrant-test-ca.key
!/examples/quickstart-vagrant/ansible/roles/nebula/files/vagrant-test-ca.crt

View File

@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [1.9.4] - 2024-09-09
### Added
- Support UDP dialing with gVisor. (#1181)
### Changed
- Make some Nebula state programmatically available via control object. (#1188)
- Switch internal representation of IPs to netip, to prepare for IPv6 support
in the overlay. (#1173)
- Minor build and cleanup changes. (#1171, #1164, #1162)
- Various dependency updates. (#1195, #1190, #1174, #1168, #1167, #1161, #1147, #1146)
### Fixed
- Fix a bug on big endian hosts, like mips. (#1194)
- Fix a rare panic if a local index collision happens. (#1191)
- Fix integer wraparound in the calculation of handshake timeouts on 32-bit targets. (#1185)
## [1.9.3] - 2024-06-06
### Fixed
@ -644,7 +664,8 @@ created.)
- Initial public release.
[Unreleased]: https://github.com/slackhq/nebula/compare/v1.9.3...HEAD
[Unreleased]: https://github.com/slackhq/nebula/compare/v1.9.4...HEAD
[1.9.4]: https://github.com/slackhq/nebula/releases/tag/v1.9.4
[1.9.3]: https://github.com/slackhq/nebula/releases/tag/v1.9.3
[1.9.2]: https://github.com/slackhq/nebula/releases/tag/v1.9.2
[1.9.1]: https://github.com/slackhq/nebula/releases/tag/v1.9.1

View File

@ -40,7 +40,7 @@ ALL_LINUX = linux-amd64 \
linux-mips64le \
linux-mips-softfloat \
linux-riscv64 \
linux-loong64
linux-loong64
ALL_FREEBSD = freebsd-amd64 \
freebsd-arm64
@ -63,7 +63,7 @@ ALL = $(ALL_LINUX) \
e2e:
$(TEST_ENV) go test -tags=e2e_testing -count=1 $(TEST_FLAGS) ./e2e
e2ev: TEST_FLAGS = -v
e2ev: TEST_FLAGS += -v
e2ev: e2e
e2evv: TEST_ENV += TEST_LOGS=1
@ -96,7 +96,7 @@ release-netbsd: $(ALL_NETBSD:%=build/nebula-%.tar.gz)
release-boringcrypto: build/nebula-linux-$(shell go env GOARCH)-boringcrypto.tar.gz
BUILD_ARGS = -trimpath
BUILD_ARGS += -trimpath
bin-windows: build/windows-amd64/nebula.exe build/windows-amd64/nebula-cert.exe
mv $? .
@ -116,6 +116,10 @@ bin-freebsd-arm64: build/freebsd-arm64/nebula build/freebsd-arm64/nebula-cert
bin-boringcrypto: build/linux-$(shell go env GOARCH)-boringcrypto/nebula build/linux-$(shell go env GOARCH)-boringcrypto/nebula-cert
mv $? .
bin-pkcs11: BUILD_ARGS += -tags pkcs11
bin-pkcs11: CGO_ENABLED = 1
bin-pkcs11: bin
bin:
go build $(BUILD_ARGS) -ldflags "$(LDFLAGS)" -o ./nebula${NEBULA_CMD_SUFFIX} ${NEBULA_CMD_PATH}
go build $(BUILD_ARGS) -ldflags "$(LDFLAGS)" -o ./nebula-cert${NEBULA_CMD_SUFFIX} ./cmd/nebula-cert
@ -168,6 +172,9 @@ test:
test-boringcrypto:
GOEXPERIMENT=boringcrypto CGO_ENABLED=1 go test -v ./...
test-pkcs11:
CGO_ENABLED=1 go test -v -tags pkcs11 ./...
test-cov-html:
go test -coverprofile=coverage.out
go tool cover -html=coverage.out

View File

@ -20,6 +20,7 @@ import (
"sync/atomic"
"time"
"github.com/slackhq/nebula/pkclient"
"golang.org/x/crypto/curve25519"
"google.golang.org/protobuf/proto"
)
@ -41,8 +42,9 @@ const (
)
type NebulaCertificate struct {
Details NebulaCertificateDetails
Signature []byte
Details NebulaCertificateDetails
Pkcs11Backed bool
Signature []byte
// the cached hex string of the calculated sha256sum
// for VerifyWithCache
@ -555,6 +557,34 @@ func (nc *NebulaCertificate) Sign(curve Curve, key []byte) error {
return nil
}
// SignPkcs11 signs a nebula cert with the provided private key
func (nc *NebulaCertificate) SignPkcs11(curve Curve, client *pkclient.PKClient) error {
if !nc.Pkcs11Backed {
return fmt.Errorf("certificate is not PKCS#11 backed")
}
if curve != nc.Details.Curve {
return fmt.Errorf("curve in cert and private key supplied don't match")
}
if curve != Curve_P256 {
return fmt.Errorf("only P256 is supported by PKCS#11")
}
b, err := proto.Marshal(nc.getRawDetails())
if err != nil {
return err
}
sig, err := client.SignASN1(b)
if err != nil {
return err
}
nc.Signature = sig
return nil
}
// CheckSignature verifies the signature against the provided public key
func (nc *NebulaCertificate) CheckSignature(key []byte) bool {
b, err := proto.Marshal(nc.getRawDetails())
@ -693,6 +723,9 @@ func (nc *NebulaCertificate) CheckRootConstrains(signer *NebulaCertificate) erro
// VerifyPrivateKey checks that the public key in the Nebula certificate and a supplied private key match
func (nc *NebulaCertificate) VerifyPrivateKey(curve Curve, key []byte) error {
if nc.Pkcs11Backed {
return nil //todo!
}
if curve != nc.Details.Curve {
return fmt.Errorf("curve in cert and private key supplied don't match")
}

View File

@ -4,6 +4,7 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"flag"
"fmt"
"io"
@ -15,6 +16,7 @@ import (
"github.com/skip2/go-qrcode"
"github.com/slackhq/nebula/cert"
"github.com/slackhq/nebula/pkclient"
"golang.org/x/crypto/ed25519"
)
@ -33,7 +35,8 @@ type caFlags struct {
argonParallelism *uint
encryption *bool
curve *string
curve *string
p11url *string
}
func newCaFlags() *caFlags {
@ -52,6 +55,7 @@ func newCaFlags() *caFlags {
cf.argonIterations = cf.set.Uint("argon-iterations", 1, "Optional: Argon2 iterations parameter used for encrypted private key passphrase")
cf.encryption = cf.set.Bool("encrypt", false, "Optional: prompt for passphrase and write out-key in an encrypted format")
cf.curve = cf.set.String("curve", "25519", "EdDSA/ECDSA Curve (25519, P256)")
cf.p11url = p11Flag(cf.set)
return &cf
}
@ -76,17 +80,21 @@ func ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error
return err
}
isP11 := len(*cf.p11url) > 0
if err := mustFlagString("name", cf.name); err != nil {
return err
}
if err := mustFlagString("out-key", cf.outKeyPath); err != nil {
return err
if !isP11 {
if err = mustFlagString("out-key", cf.outKeyPath); err != nil {
return err
}
}
if err := mustFlagString("out-crt", cf.outCertPath); err != nil {
return err
}
var kdfParams *cert.Argon2Parameters
if *cf.encryption {
if !isP11 && *cf.encryption {
if kdfParams, err = parseArgonParameters(*cf.argonMemory, *cf.argonParallelism, *cf.argonIterations); err != nil {
return err
}
@ -143,7 +151,7 @@ func ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error
}
var passphrase []byte
if *cf.encryption {
if !isP11 && *cf.encryption {
for i := 0; i < 5; i++ {
out.Write([]byte("Enter passphrase: "))
passphrase, err = pr.ReadPassword()
@ -166,29 +174,54 @@ func ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error
var curve cert.Curve
var pub, rawPriv []byte
switch *cf.curve {
case "25519", "X25519", "Curve25519", "CURVE25519":
curve = cert.Curve_CURVE25519
pub, rawPriv, err = ed25519.GenerateKey(rand.Reader)
if err != nil {
return fmt.Errorf("error while generating ed25519 keys: %s", err)
}
case "P256":
var key *ecdsa.PrivateKey
curve = cert.Curve_P256
key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return fmt.Errorf("error while generating ecdsa keys: %s", err)
var p11Client *pkclient.PKClient
if isP11 {
switch *cf.curve {
case "P256":
curve = cert.Curve_P256
default:
return fmt.Errorf("invalid curve for PKCS#11: %s", *cf.curve)
}
// ecdh.PrivateKey lets us get at the encoded bytes, even though
// we aren't using ECDH here.
eKey, err := key.ECDH()
p11Client, err = pkclient.FromUrl(*cf.p11url)
if err != nil {
return fmt.Errorf("error while converting ecdsa key: %s", err)
return fmt.Errorf("error while creating PKCS#11 client: %w", err)
}
defer func(client *pkclient.PKClient) {
_ = client.Close()
}(p11Client)
pub, err = p11Client.GetPubKey()
if err != nil {
return fmt.Errorf("error while getting public key with PKCS#11: %w", err)
}
} else {
switch *cf.curve {
case "25519", "X25519", "Curve25519", "CURVE25519":
curve = cert.Curve_CURVE25519
pub, rawPriv, err = ed25519.GenerateKey(rand.Reader)
if err != nil {
return fmt.Errorf("error while generating ed25519 keys: %s", err)
}
case "P256":
var key *ecdsa.PrivateKey
curve = cert.Curve_P256
key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return fmt.Errorf("error while generating ecdsa keys: %s", err)
}
// ecdh.PrivateKey lets us get at the encoded bytes, even though
// we aren't using ECDH here.
eKey, err := key.ECDH()
if err != nil {
return fmt.Errorf("error while converting ecdsa key: %s", err)
}
rawPriv = eKey.Bytes()
pub = eKey.PublicKey().Bytes()
default:
return fmt.Errorf("invalid curve: %s", *cf.curve)
}
rawPriv = eKey.Bytes()
pub = eKey.PublicKey().Bytes()
}
nc := cert.NebulaCertificate{
@ -203,34 +236,48 @@ func ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error
IsCA: true,
Curve: curve,
},
Pkcs11Backed: isP11,
}
if _, err := os.Stat(*cf.outKeyPath); err == nil {
return fmt.Errorf("refusing to overwrite existing CA key: %s", *cf.outKeyPath)
if !isP11 {
if _, err := os.Stat(*cf.outKeyPath); err == nil {
return fmt.Errorf("refusing to overwrite existing CA key: %s", *cf.outKeyPath)
}
}
if _, err := os.Stat(*cf.outCertPath); err == nil {
return fmt.Errorf("refusing to overwrite existing CA cert: %s", *cf.outCertPath)
}
err = nc.Sign(curve, rawPriv)
if err != nil {
return fmt.Errorf("error while signing: %s", err)
}
var b []byte
if *cf.encryption {
b, err = cert.EncryptAndMarshalSigningPrivateKey(curve, rawPriv, passphrase, kdfParams)
if isP11 {
err = nc.SignPkcs11(curve, p11Client)
if err != nil {
return fmt.Errorf("error while encrypting out-key: %s", err)
return fmt.Errorf("error while signing with PKCS#11: %w", err)
}
} else {
b = cert.MarshalSigningPrivateKey(curve, rawPriv)
}
err = nc.Sign(curve, rawPriv)
if err != nil {
return fmt.Errorf("error while signing: %s", err)
}
err = os.WriteFile(*cf.outKeyPath, b, 0600)
if err != nil {
return fmt.Errorf("error while writing out-key: %s", err)
if *cf.encryption {
b, err = cert.EncryptAndMarshalSigningPrivateKey(curve, rawPriv, passphrase, kdfParams)
if err != nil {
return fmt.Errorf("error while encrypting out-key: %s", err)
}
} else {
b = cert.MarshalSigningPrivateKey(curve, rawPriv)
}
err = os.WriteFile(*cf.outKeyPath, b, 0600)
if err != nil {
return fmt.Errorf("error while writing out-key: %s", err)
}
if _, err := os.Stat(*cf.outCertPath); err == nil {
return fmt.Errorf("refusing to overwrite existing CA cert: %s", *cf.outCertPath)
}
}
b, err = nc.MarshalToPEM()

View File

@ -52,6 +52,7 @@ func Test_caHelp(t *testing.T) {
" \tOptional: path to write the private key to (default \"ca.key\")\n"+
" -out-qr string\n"+
" \tOptional: output a qr code image (png) of the certificate\n"+
optionalPkcs11String(" -pkcs11 string\n \tOptional: PKCS#11 URI to an existing private key\n")+
" -subnets string\n"+
" \tOptional: comma separated list of ipv4 address and network in CIDR notation. This will limit which ipv4 addresses and networks subordinate certs can use in subnets\n",
ob.String(),

View File

@ -6,6 +6,8 @@ import (
"io"
"os"
"github.com/slackhq/nebula/pkclient"
"github.com/slackhq/nebula/cert"
)
@ -13,8 +15,8 @@ type keygenFlags struct {
set *flag.FlagSet
outKeyPath *string
outPubPath *string
curve *string
curve *string
p11url *string
}
func newKeygenFlags() *keygenFlags {
@ -23,6 +25,7 @@ func newKeygenFlags() *keygenFlags {
cf.outPubPath = cf.set.String("out-pub", "", "Required: path to write the public key to")
cf.outKeyPath = cf.set.String("out-key", "", "Required: path to write the private key to")
cf.curve = cf.set.String("curve", "25519", "ECDH Curve (25519, P256)")
cf.p11url = p11Flag(cf.set)
return &cf
}
@ -33,31 +36,57 @@ func keygen(args []string, out io.Writer, errOut io.Writer) error {
return err
}
if err := mustFlagString("out-key", cf.outKeyPath); err != nil {
return err
isP11 := len(*cf.p11url) > 0
if !isP11 {
if err = mustFlagString("out-key", cf.outKeyPath); err != nil {
return err
}
}
if err := mustFlagString("out-pub", cf.outPubPath); err != nil {
if err = mustFlagString("out-pub", cf.outPubPath); err != nil {
return err
}
var pub, rawPriv []byte
var curve cert.Curve
switch *cf.curve {
case "25519", "X25519", "Curve25519", "CURVE25519":
pub, rawPriv = x25519Keypair()
curve = cert.Curve_CURVE25519
case "P256":
pub, rawPriv = p256Keypair()
curve = cert.Curve_P256
default:
return fmt.Errorf("invalid curve: %s", *cf.curve)
if isP11 {
switch *cf.curve {
case "P256":
curve = cert.Curve_P256
default:
return fmt.Errorf("invalid curve for PKCS#11: %s", *cf.curve)
}
} else {
switch *cf.curve {
case "25519", "X25519", "Curve25519", "CURVE25519":
pub, rawPriv = x25519Keypair()
curve = cert.Curve_CURVE25519
case "P256":
pub, rawPriv = p256Keypair()
curve = cert.Curve_P256
default:
return fmt.Errorf("invalid curve: %s", *cf.curve)
}
}
err = os.WriteFile(*cf.outKeyPath, cert.MarshalPrivateKey(curve, rawPriv), 0600)
if err != nil {
return fmt.Errorf("error while writing out-key: %s", err)
if isP11 {
p11Client, err := pkclient.FromUrl(*cf.p11url)
if err != nil {
return fmt.Errorf("error while creating PKCS#11 client: %w", err)
}
defer func(client *pkclient.PKClient) {
_ = client.Close()
}(p11Client)
pub, err = p11Client.GetPubKey()
if err != nil {
return fmt.Errorf("error while getting public key: %w", err)
}
} else {
err = os.WriteFile(*cf.outKeyPath, cert.MarshalPrivateKey(curve, rawPriv), 0600)
if err != nil {
return fmt.Errorf("error while writing out-key: %s", err)
}
}
err = os.WriteFile(*cf.outPubPath, cert.MarshalPublicKey(curve, pub), 0600)
if err != nil {
return fmt.Errorf("error while writing out-pub: %s", err)
@ -72,7 +101,7 @@ func keygenSummary() string {
func keygenHelp(out io.Writer) {
cf := newKeygenFlags()
out.Write([]byte("Usage of " + os.Args[0] + " " + keygenSummary() + "\n"))
_, _ = out.Write([]byte("Usage of " + os.Args[0] + " " + keygenSummary() + "\n"))
cf.set.SetOutput(out)
cf.set.PrintDefaults()
}

View File

@ -26,7 +26,8 @@ func Test_keygenHelp(t *testing.T) {
" -out-key string\n"+
" \tRequired: path to write the private key to\n"+
" -out-pub string\n"+
" \tRequired: path to write the public key to\n",
" \tRequired: path to write the public key to\n"+
optionalPkcs11String(" -pkcs11 string\n \tOptional: PKCS#11 URI to an existing private key\n"),
ob.String(),
)
}

View File

@ -3,6 +3,7 @@ package main
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"testing"
@ -77,8 +78,16 @@ func assertHelpError(t *testing.T, err error, msg string) {
case *helpError:
// good
default:
t.Fatal("err was not a helpError")
t.Fatal(fmt.Sprintf("err was not a helpError: %q, expected %q", err, msg))
}
assert.EqualError(t, err, msg)
}
func optionalPkcs11String(msg string) string {
if p11Supported() {
return msg
} else {
return ""
}
}

View File

@ -0,0 +1,15 @@
//go:build cgo && pkcs11
package main
import (
"flag"
)
func p11Supported() bool {
return true
}
func p11Flag(set *flag.FlagSet) *string {
return set.String("pkcs11", "", "Optional: PKCS#11 URI to an existing private key")
}

View File

@ -0,0 +1,16 @@
//go:build !cgo || !pkcs11
package main
import (
"flag"
)
func p11Supported() bool {
return false
}
func p11Flag(set *flag.FlagSet) *string {
var ret = ""
return &ret
}

View File

@ -13,6 +13,7 @@ import (
"github.com/skip2/go-qrcode"
"github.com/slackhq/nebula/cert"
"github.com/slackhq/nebula/pkclient"
"golang.org/x/crypto/curve25519"
)
@ -29,6 +30,7 @@ type signFlags struct {
outQRPath *string
groups *string
subnets *string
p11url *string
}
func newSignFlags() *signFlags {
@ -45,8 +47,8 @@ func newSignFlags() *signFlags {
sf.outQRPath = sf.set.String("out-qr", "", "Optional: output a qr code image (png) of the certificate")
sf.groups = sf.set.String("groups", "", "Optional: comma separated list of groups")
sf.subnets = sf.set.String("subnets", "", "Optional: comma separated list of ipv4 address and network in CIDR notation. Subnets this cert can serve for")
sf.p11url = p11Flag(sf.set)
return &sf
}
func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error {
@ -56,8 +58,12 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
return err
}
if err := mustFlagString("ca-key", sf.caKeyPath); err != nil {
return err
isP11 := len(*sf.p11url) > 0
if !isP11 {
if err := mustFlagString("ca-key", sf.caKeyPath); err != nil {
return err
}
}
if err := mustFlagString("ca-crt", sf.caCertPath); err != nil {
return err
@ -68,47 +74,49 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
if err := mustFlagString("ip", sf.ip); err != nil {
return err
}
if *sf.inPubPath != "" && *sf.outKeyPath != "" {
if !isP11 && *sf.inPubPath != "" && *sf.outKeyPath != "" {
return newHelpErrorf("cannot set both -in-pub and -out-key")
}
rawCAKey, err := os.ReadFile(*sf.caKeyPath)
if err != nil {
return fmt.Errorf("error while reading ca-key: %s", err)
}
var curve cert.Curve
var caKey []byte
// naively attempt to decode the private key as though it is not encrypted
caKey, _, curve, err = cert.UnmarshalSigningPrivateKey(rawCAKey)
if err == cert.ErrPrivateKeyEncrypted {
// ask for a passphrase until we get one
var passphrase []byte
for i := 0; i < 5; i++ {
out.Write([]byte("Enter passphrase: "))
passphrase, err = pr.ReadPassword()
if err == ErrNoTerminal {
return fmt.Errorf("ca-key is encrypted and must be decrypted interactively")
} else if err != nil {
return fmt.Errorf("error reading password: %s", err)
}
if len(passphrase) > 0 {
break
}
}
if len(passphrase) == 0 {
return fmt.Errorf("cannot open encrypted ca-key without passphrase")
}
curve, caKey, _, err = cert.DecryptAndUnmarshalSigningPrivateKey(passphrase, rawCAKey)
if !isP11 {
var rawCAKey []byte
rawCAKey, err := os.ReadFile(*sf.caKeyPath)
if err != nil {
return fmt.Errorf("error while parsing encrypted ca-key: %s", err)
return fmt.Errorf("error while reading ca-key: %s", err)
}
// naively attempt to decode the private key as though it is not encrypted
caKey, _, curve, err = cert.UnmarshalSigningPrivateKey(rawCAKey)
if err == cert.ErrPrivateKeyEncrypted {
// ask for a passphrase until we get one
var passphrase []byte
for i := 0; i < 5; i++ {
out.Write([]byte("Enter passphrase: "))
passphrase, err = pr.ReadPassword()
if err == ErrNoTerminal {
return fmt.Errorf("ca-key is encrypted and must be decrypted interactively")
} else if err != nil {
return fmt.Errorf("error reading password: %s", err)
}
if len(passphrase) > 0 {
break
}
}
if len(passphrase) == 0 {
return fmt.Errorf("cannot open encrypted ca-key without passphrase")
}
curve, caKey, _, err = cert.DecryptAndUnmarshalSigningPrivateKey(passphrase, rawCAKey)
if err != nil {
return fmt.Errorf("error while parsing encrypted ca-key: %s", err)
}
} else if err != nil {
return fmt.Errorf("error while parsing ca-key: %s", err)
}
} else if err != nil {
return fmt.Errorf("error while parsing ca-key: %s", err)
}
rawCACert, err := os.ReadFile(*sf.caCertPath)
@ -121,8 +129,10 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
return fmt.Errorf("error while parsing ca-crt: %s", err)
}
if err := caCert.VerifyPrivateKey(curve, caKey); err != nil {
return fmt.Errorf("refusing to sign, root certificate does not match private key")
if !isP11 {
if err := caCert.VerifyPrivateKey(curve, caKey); err != nil {
return fmt.Errorf("refusing to sign, root certificate does not match private key")
}
}
issuer, err := caCert.Sha256Sum()
@ -176,12 +186,25 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
}
var pub, rawPriv []byte
var p11Client *pkclient.PKClient
if isP11 {
curve = cert.Curve_P256
p11Client, err = pkclient.FromUrl(*sf.p11url)
if err != nil {
return fmt.Errorf("error while creating PKCS#11 client: %w", err)
}
defer func(client *pkclient.PKClient) {
_ = client.Close()
}(p11Client)
}
if *sf.inPubPath != "" {
var pubCurve cert.Curve
rawPub, err := os.ReadFile(*sf.inPubPath)
if err != nil {
return fmt.Errorf("error while reading in-pub: %s", err)
}
var pubCurve cert.Curve
pub, _, pubCurve, err = cert.UnmarshalPublicKey(rawPub)
if err != nil {
return fmt.Errorf("error while parsing in-pub: %s", err)
@ -189,6 +212,11 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
if pubCurve != curve {
return fmt.Errorf("curve of in-pub does not match ca")
}
} else if isP11 {
pub, err = p11Client.GetPubKey()
if err != nil {
return fmt.Errorf("error while getting public key with PKCS#11: %w", err)
}
} else {
pub, rawPriv = newKeypair(curve)
}
@ -206,6 +234,19 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
Issuer: issuer,
Curve: curve,
},
Pkcs11Backed: isP11,
}
if p11Client == nil {
err = nc.Sign(curve, caKey)
if err != nil {
return fmt.Errorf("error while signing: %w", err)
}
} else {
err = nc.SignPkcs11(curve, p11Client)
if err != nil {
return fmt.Errorf("error while signing with PKCS#11: %w", err)
}
}
if err := nc.CheckRootConstrains(caCert); err != nil {
@ -224,12 +265,7 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
return fmt.Errorf("refusing to overwrite existing cert: %s", *sf.outCertPath)
}
err = nc.Sign(curve, caKey)
if err != nil {
return fmt.Errorf("error while signing: %s", err)
}
if *sf.inPubPath == "" {
if !isP11 && *sf.inPubPath == "" {
if _, err := os.Stat(*sf.outKeyPath); err == nil {
return fmt.Errorf("refusing to overwrite existing key: %s", *sf.outKeyPath)
}

View File

@ -48,6 +48,7 @@ func Test_signHelp(t *testing.T) {
" \tOptional (if in-pub not set): path to write the private key to\n"+
" -out-qr string\n"+
" \tOptional: output a qr code image (png) of the certificate\n"+
optionalPkcs11String(" -pkcs11 string\n \tOptional: PKCS#11 URI to an existing private key\n")+
" -subnets string\n"+
" \tOptional: comma separated list of ipv4 address and network in CIDR notation. Subnets this cert can serve for\n",
ob.String(),

View File

@ -32,7 +32,11 @@ func NewConnectionState(l *logrus.Logger, cipher string, certState *CertState, i
case cert.Curve_CURVE25519:
dhFunc = noise.DH25519
case cert.Curve_P256:
dhFunc = noiseutil.DHP256
if certState.Certificate.Pkcs11Backed {
dhFunc = noiseutil.DHP256PKCS11
} else {
dhFunc = noiseutil.DHP256
}
default:
l.Errorf("invalid curve: %s", certState.Certificate.Details.Curve)
return nil

View File

@ -4,6 +4,7 @@ import (
"bufio"
"fmt"
"log"
"net"
"github.com/slackhq/nebula/config"
"github.com/slackhq/nebula/service"
@ -54,16 +55,16 @@ pki:
cert: /home/rice/Developer/nebula-config/app.crt
key: /home/rice/Developer/nebula-config/app.key
`
var config config.C
if err := config.LoadString(configStr); err != nil {
var cfg config.C
if err := cfg.LoadString(configStr); err != nil {
return err
}
service, err := service.New(&config)
svc, err := service.New(&cfg)
if err != nil {
return err
}
ln, err := service.Listen("tcp", ":1234")
ln, err := svc.Listen("tcp", ":1234")
if err != nil {
return err
}
@ -73,16 +74,24 @@ pki:
log.Printf("accept error: %s", err)
break
}
defer conn.Close()
defer func(conn net.Conn) {
_ = conn.Close()
}(conn)
log.Printf("got connection")
conn.Write([]byte("hello world\n"))
_, err = conn.Write([]byte("hello world\n"))
if err != nil {
log.Printf("write error: %s", err)
}
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
message := scanner.Text()
fmt.Fprintf(conn, "echo: %q\n", message)
_, err = fmt.Fprintf(conn, "echo: %q\n", message)
if err != nil {
log.Printf("write error: %s", err)
}
log.Printf("got message %q", message)
}
@ -92,8 +101,8 @@ pki:
}
}
service.Close()
if err := service.Wait(); err != nil {
_ = svc.Close()
if err := svc.Wait(); err != nil {
return err
}
return nil

16
go.mod
View File

@ -5,29 +5,32 @@ go 1.22.0
toolchain go1.22.2
require (
dario.cat/mergo v1.0.0
dario.cat/mergo v1.0.1
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
github.com/armon/go-radix v1.0.0
github.com/cyberdelia/go-metrics-graphite v0.0.0-20161219230853-39f87cc3b432
github.com/flynn/noise v1.1.0
github.com/gaissmai/bart v0.11.1
github.com/gogo/protobuf v1.3.2
github.com/google/gopacket v1.1.19
github.com/kardianos/service v1.2.2
github.com/miekg/dns v1.1.61
github.com/miekg/pkcs11 v1.1.2-0.20231115102856-9078ad6b9d4b
github.com/nbrownus/go-metrics-prometheus v0.0.0-20210712211119-974a6260965f
github.com/prometheus/client_golang v1.19.1
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
github.com/sirupsen/logrus v1.9.3
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6
github.com/stretchr/testify v1.9.0
github.com/vishvananda/netlink v1.2.1-beta.2
golang.org/x/crypto v0.25.0
golang.org/x/crypto v0.26.0
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090
golang.org/x/net v0.27.0
golang.org/x/sync v0.7.0
golang.org/x/sys v0.22.0
golang.org/x/term v0.22.0
golang.org/x/net v0.28.0
golang.org/x/sync v0.8.0
golang.org/x/sys v0.24.0
golang.org/x/term v0.23.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b
golang.zx2c4.com/wireguard/windows v0.5.3
@ -41,7 +44,6 @@ require (
github.com/bits-and-blooms/bitset v1.13.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gaissmai/bart v0.11.1 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect

30
go.sum
View File

@ -1,6 +1,6 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@ -26,8 +26,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg=
github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
github.com/gaissmai/bart v0.10.0 h1:yCZCYF8xzcRnqDe4jMk14NlJjL1WmMsE7ilBzvuHtiI=
github.com/gaissmai/bart v0.10.0/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg=
github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc=
github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@ -85,6 +83,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
github.com/miekg/pkcs11 v1.1.2-0.20231115102856-9078ad6b9d4b h1:J/AzCvg5z0Hn1rqZUJjpbzALUmkKX0Zwbc/i4fw7Sfk=
github.com/miekg/pkcs11 v1.1.2-0.20231115102856-9078ad6b9d4b/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
@ -133,6 +133,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 h1:pnnLyeX7o/5aX8qUQ69P/mLojDqwda8hFOCBTmP/6hw=
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6/go.mod h1:39R/xuhNgVhi+K0/zst4TLrJrVmbm6LVgl4A0+ZFS5M=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@ -153,8 +155,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
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.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
@ -173,8 +175,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -182,8 +184,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -201,11 +203,11 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

View File

@ -35,7 +35,7 @@ var (
type HandshakeConfig struct {
tryInterval time.Duration
retries int
retries int64
triggerBuffer int
useRelays bool
@ -69,7 +69,7 @@ type HandshakeHostInfo struct {
startTime time.Time // Time that we first started trying with this handshake
ready bool // Is the handshake ready
counter int // How many attempts have we made so far
counter int64 // How many attempts have we made so far
lastRemotes []netip.AddrPort // Remotes that we sent to during the previous attempt
packetStore []*cachedPacket // A set of packets to be transmitted once the handshake completes
@ -665,6 +665,6 @@ func generateIndex(l *logrus.Logger) (uint32, error) {
return index, nil
}
func hsTimeout(tries int, interval time.Duration) time.Duration {
return time.Duration(tries / 2 * ((2 * int(interval)) + (tries-1)*int(interval)))
func hsTimeout(tries int64, interval time.Duration) time.Duration {
return time.Duration(tries / 2 * ((2 * int64(interval)) + (tries-1)*int64(interval)))
}

View File

@ -215,7 +215,7 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg
handshakeConfig := HandshakeConfig{
tryInterval: c.GetDuration("handshakes.try_interval", DefaultHandshakeTryInterval),
retries: c.GetInt("handshakes.retries", DefaultHandshakeRetries),
retries: int64(c.GetInt("handshakes.retries", DefaultHandshakeRetries)),
triggerBuffer: c.GetInt("handshakes.trigger_buffer", DefaultHandshakeTriggerBuffer),
useRelays: useRelays,

50
noiseutil/pkcs11.go Normal file
View File

@ -0,0 +1,50 @@
package noiseutil
import (
"crypto/ecdh"
"fmt"
"strings"
"github.com/slackhq/nebula/pkclient"
"github.com/flynn/noise"
)
// DHP256PKCS11 is the NIST P-256 ECDH function
var DHP256PKCS11 noise.DHFunc = newNISTP11Curve("P256", ecdh.P256(), 32)
type nistP11Curve struct {
nistCurve
}
func newNISTP11Curve(name string, curve ecdh.Curve, byteLen int) nistP11Curve {
return nistP11Curve{
newNISTCurve(name, curve, byteLen),
}
}
func (c nistP11Curve) DH(privkey, pubkey []byte) ([]byte, error) {
//for this function "privkey" is actually a pkcs11 URI
pkStr := string(privkey)
//to set up a handshake, we need to also do non-pkcs11-DH. Handle that here.
if !strings.HasPrefix(pkStr, "pkcs11:") {
return DHP256.DH(privkey, pubkey)
}
ecdhPubKey, err := c.curve.NewPublicKey(pubkey)
if err != nil {
return nil, fmt.Errorf("unable to unmarshal pubkey: %w", err)
}
//this is not the most performant way to do this (a long-lived client would be better)
//but, it works, and helps avoid problems with stale sessions and HSMs used by multiple users.
client, err := pkclient.FromUrl(pkStr)
if err != nil {
return nil, err
}
defer func(client *pkclient.PKClient) {
_ = client.Close()
}(client)
return client.DeriveNoise(ecdhPubKey.Bytes())
}

87
pkclient/pkclient.go Normal file
View File

@ -0,0 +1,87 @@
package pkclient
import (
"crypto/ecdsa"
"crypto/x509"
"fmt"
"io"
"strconv"
"github.com/stefanberger/go-pkcs11uri"
)
type Client interface {
io.Closer
GetPubKey() ([]byte, error)
DeriveNoise(peerPubKey []byte) ([]byte, error)
Test() error
}
const NoiseKeySize = 32
func FromUrl(pkurl string) (*PKClient, error) {
uri := pkcs11uri.New()
uri.SetAllowAnyModule(true) //todo
err := uri.Parse(pkurl)
if err != nil {
return nil, err
}
module, err := uri.GetModule()
if err != nil {
return nil, err
}
slotid := 0
slot, ok := uri.GetPathAttribute("slot-id", false)
if !ok {
slotid = 0
} else {
slotid, err = strconv.Atoi(slot)
if err != nil {
return nil, err
}
}
pin, _ := uri.GetPIN()
id, _ := uri.GetPathAttribute("id", false)
label, _ := uri.GetPathAttribute("object", false)
return New(module, uint(slotid), pin, id, label)
}
func ecKeyToArray(key *ecdsa.PublicKey) []byte {
x := make([]byte, 32)
y := make([]byte, 32)
key.X.FillBytes(x)
key.Y.FillBytes(y)
return append([]byte{0x04}, append(x, y...)...)
}
func formatPubkeyFromPublicKeyInfoAttr(d []byte) ([]byte, error) {
e, err := x509.ParsePKIXPublicKey(d)
if err != nil {
return nil, err
}
switch t := e.(type) {
case *ecdsa.PublicKey:
return ecKeyToArray(e.(*ecdsa.PublicKey)), nil
default:
return nil, fmt.Errorf("unknown public key type: %T", t)
}
}
func (c *PKClient) Test() error {
pub, err := c.GetPubKey()
if err != nil {
return fmt.Errorf("failed to get public key: %w", err)
}
out, err := c.DeriveNoise(pub) //do an ECDH with ourselves as a quick test
if err != nil {
return err
}
if len(out) != NoiseKeySize {
return fmt.Errorf("got a key of %d bytes, expected %d", len(out), NoiseKeySize)
}
return nil
}

229
pkclient/pkclient_cgo.go Normal file
View File

@ -0,0 +1,229 @@
//go:build cgo && pkcs11
package pkclient
import (
"encoding/asn1"
"errors"
"fmt"
"log"
"math/big"
"github.com/miekg/pkcs11"
"github.com/miekg/pkcs11/p11"
)
type PKClient struct {
module p11.Module
session p11.Session
id []byte
label []byte
privKeyObj p11.Object
pubKeyObj p11.Object
}
type ecdsaSignature struct {
R, S *big.Int
}
// New tries to open a session with the HSM, select the slot and login to it
func New(hsmPath string, slotId uint, pin string, id string, label string) (*PKClient, error) {
module, err := p11.OpenModule(hsmPath)
if err != nil {
return nil, fmt.Errorf("failed to load module library: %s", hsmPath)
}
slots, err := module.Slots()
if err != nil {
module.Destroy()
return nil, err
}
// Try to open a session on the slot
slotIdx := 0
for i, slot := range slots {
if slot.ID() == slotId {
slotIdx = i
break
}
}
client := &PKClient{
module: module,
id: []byte(id),
label: []byte(label),
}
client.session, err = slots[slotIdx].OpenWriteSession()
if err != nil {
module.Destroy()
return nil, fmt.Errorf("failed to open session on slot %d", slotId)
}
if len(pin) != 0 {
err = client.session.Login(pin)
if err != nil {
// ignore "already logged in"
if !errors.Is(err, pkcs11.Error(256)) {
_ = client.session.Close()
return nil, fmt.Errorf("unable to login. error: %w", err)
}
}
}
// Make sure the hsm has a private key for deriving
client.privKeyObj, err = client.findDeriveKey(client.id, client.label, true)
if err != nil {
_ = client.Close() //log out, close session, destroy module
return nil, fmt.Errorf("failed to find private key for deriving: %w", err)
}
return client, nil
}
// Close cleans up properly and logs out
func (c *PKClient) Close() error {
var err error = nil
if c.session != nil {
_ = c.session.Logout() //if logout fails, we still want to close
err = c.session.Close()
}
c.module.Destroy()
return err
}
// Try to find a suitable key on the hsm for key derivation
// parameter GET_PUB_KEY sets the search pattern for a public or private key
func (c *PKClient) findDeriveKey(id []byte, label []byte, private bool) (key p11.Object, err error) {
keyClass := pkcs11.CKO_PRIVATE_KEY
if !private {
keyClass = pkcs11.CKO_PUBLIC_KEY
}
keyAttrs := []*pkcs11.Attribute{
//todo, not all HSMs seem to report this, even if its true: pkcs11.NewAttribute(pkcs11.CKA_DERIVE, true),
pkcs11.NewAttribute(pkcs11.CKA_CLASS, keyClass),
}
if id != nil && len(id) != 0 {
keyAttrs = append(keyAttrs, pkcs11.NewAttribute(pkcs11.CKA_ID, id))
}
if label != nil && len(label) != 0 {
keyAttrs = append(keyAttrs, pkcs11.NewAttribute(pkcs11.CKA_LABEL, label))
}
return c.session.FindObject(keyAttrs)
}
func (c *PKClient) listDeriveKeys(id []byte, label []byte, private bool) {
keyClass := pkcs11.CKO_PRIVATE_KEY
if !private {
keyClass = pkcs11.CKO_PUBLIC_KEY
}
keyAttrs := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_CLASS, keyClass),
}
if id != nil && len(id) != 0 {
keyAttrs = append(keyAttrs, pkcs11.NewAttribute(pkcs11.CKA_ID, id))
}
if label != nil && len(label) != 0 {
keyAttrs = append(keyAttrs, pkcs11.NewAttribute(pkcs11.CKA_LABEL, label))
}
objects, err := c.session.FindObjects(keyAttrs)
if err != nil {
return
}
for _, obj := range objects {
l, err := obj.Label()
log.Printf("%s, %v", l, err)
a, err := obj.Attribute(pkcs11.CKA_DERIVE)
log.Printf("DERIVE: %s %v, %v", l, a, err)
}
}
// SignASN1 signs some data. Returns the ASN.1 encoded signature.
func (c *PKClient) SignASN1(data []byte) ([]byte, error) {
mech := pkcs11.NewMechanism(pkcs11.CKM_ECDSA_SHA256, nil)
sk := p11.PrivateKey(c.privKeyObj)
rawSig, err := sk.Sign(*mech, data)
if err != nil {
return nil, err
}
// PKCS #11 Mechanisms v2.30:
// "The signature octets correspond to the concatenation of the ECDSA values r and s,
// both represented as an octet string of equal length of at most nLen with the most
// significant byte first. If r and s have different octet length, the shorter of both
// must be padded with leading zero octets such that both have the same octet length.
// Loosely spoken, the first half of the signature is r and the second half is s."
r := new(big.Int).SetBytes(rawSig[:len(rawSig)/2])
s := new(big.Int).SetBytes(rawSig[len(rawSig)/2:])
return asn1.Marshal(ecdsaSignature{r, s})
}
// DeriveNoise derives a shared secret using the input public key against the private key that was found during setup.
// Returns a fixed 32 byte array.
func (c *PKClient) DeriveNoise(peerPubKey []byte) ([]byte, error) {
// Before we call derive, we need to have an array of attributes which specify the type of
// key to be returned, in our case, it's the shared secret key, produced via deriving
// This template pulled from OpenSC pkclient-tool.c line 4038
attrTemplate := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, false),
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_SECRET_KEY),
pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_GENERIC_SECRET),
pkcs11.NewAttribute(pkcs11.CKA_SENSITIVE, false),
pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, true),
pkcs11.NewAttribute(pkcs11.CKA_ENCRYPT, true),
pkcs11.NewAttribute(pkcs11.CKA_DECRYPT, true),
pkcs11.NewAttribute(pkcs11.CKA_WRAP, true),
pkcs11.NewAttribute(pkcs11.CKA_UNWRAP, true),
}
// Set up the parameters which include the peer's public key
ecdhParams := pkcs11.NewECDH1DeriveParams(pkcs11.CKD_NULL, nil, peerPubKey)
mech := pkcs11.NewMechanism(pkcs11.CKM_ECDH1_DERIVE, ecdhParams)
sk := p11.PrivateKey(c.privKeyObj)
tmpKey, err := sk.Derive(*mech, attrTemplate)
if err != nil {
return nil, err
}
if tmpKey == nil || len(tmpKey) == 0 {
return nil, fmt.Errorf("got an empty secret key")
}
secret := make([]byte, NoiseKeySize)
copy(secret[:], tmpKey[:NoiseKeySize])
return secret, nil
}
func (c *PKClient) GetPubKey() ([]byte, error) {
d, err := c.privKeyObj.Attribute(pkcs11.CKA_PUBLIC_KEY_INFO)
if err != nil {
return nil, err
}
if d != nil && len(d) > 0 {
return formatPubkeyFromPublicKeyInfoAttr(d)
}
c.pubKeyObj, err = c.findDeriveKey(c.id, c.label, false)
if err != nil {
return nil, fmt.Errorf("pkcs11 module gave us a nil CKA_PUBLIC_KEY_INFO, and looking up the public key also failed: %w", err)
}
d, err = c.pubKeyObj.Attribute(pkcs11.CKA_EC_POINT)
if err != nil {
return nil, fmt.Errorf("pkcs11 module gave us a nil CKA_PUBLIC_KEY_INFO, and reading CKA_EC_POINT also failed: %w", err)
}
if d == nil || len(d) < 1 {
return nil, fmt.Errorf("pkcs11 module gave us a nil or empty CKA_EC_POINT")
}
switch len(d) {
case 65: //length of 0x04 + len(X) + len(Y)
return d, nil
case 67: //as above, DER-encoded IIRC?
return d[2:], nil
default:
return nil, fmt.Errorf("unknown public key length: %d", len(d))
}
}

30
pkclient/pkclient_stub.go Normal file
View File

@ -0,0 +1,30 @@
//go:build !cgo || !pkcs11
package pkclient
import "errors"
type PKClient struct {
}
var notImplemented = errors.New("not implemented")
func New(hsmPath string, slotId uint, pin string, id string, label string) (*PKClient, error) {
return nil, notImplemented
}
func (c *PKClient) Close() error {
return nil
}
func (c *PKClient) SignASN1(data []byte) ([]byte, error) {
return nil, notImplemented
}
func (c *PKClient) DeriveNoise(_ []byte) ([]byte, error) {
return nil, notImplemented
}
func (c *PKClient) GetPubKey() ([]byte, error) {
return nil, notImplemented
}

44
pki.go
View File

@ -141,8 +141,33 @@ func newCertState(certificate *cert.NebulaCertificate, privateKey []byte) (*Cert
return cs, nil
}
func newCertStateFromConfig(c *config.C) (*CertState, error) {
func loadPrivateKey(privPathOrPEM string) (rawKey []byte, curve cert.Curve, isPkcs11 bool, err error) {
var pemPrivateKey []byte
if strings.Contains(privPathOrPEM, "-----BEGIN") {
pemPrivateKey = []byte(privPathOrPEM)
privPathOrPEM = "<inline>"
rawKey, _, curve, err = cert.UnmarshalPrivateKey(pemPrivateKey)
if err != nil {
return nil, curve, false, fmt.Errorf("error while unmarshaling pki.key %s: %s", privPathOrPEM, err)
}
} else if strings.HasPrefix(privPathOrPEM, "pkcs11:") {
rawKey = []byte(privPathOrPEM)
return rawKey, cert.Curve_P256, true, nil
} else {
pemPrivateKey, err = os.ReadFile(privPathOrPEM)
if err != nil {
return nil, curve, false, fmt.Errorf("unable to read pki.key file %s: %s", privPathOrPEM, err)
}
rawKey, _, curve, err = cert.UnmarshalPrivateKey(pemPrivateKey)
if err != nil {
return nil, curve, false, fmt.Errorf("error while unmarshaling pki.key %s: %s", privPathOrPEM, err)
}
}
return
}
func newCertStateFromConfig(c *config.C) (*CertState, error) {
var err error
privPathOrPEM := c.GetString("pki.key", "")
@ -150,20 +175,9 @@ func newCertStateFromConfig(c *config.C) (*CertState, error) {
return nil, errors.New("no pki.key path or PEM data provided")
}
if strings.Contains(privPathOrPEM, "-----BEGIN") {
pemPrivateKey = []byte(privPathOrPEM)
privPathOrPEM = "<inline>"
} else {
pemPrivateKey, err = os.ReadFile(privPathOrPEM)
if err != nil {
return nil, fmt.Errorf("unable to read pki.key file %s: %s", privPathOrPEM, err)
}
}
rawKey, _, curve, err := cert.UnmarshalPrivateKey(pemPrivateKey)
rawKey, curve, isPkcs11, err := loadPrivateKey(privPathOrPEM)
if err != nil {
return nil, fmt.Errorf("error while unmarshaling pki.key %s: %s", privPathOrPEM, err)
return nil, err
}
var rawCert []byte
@ -188,7 +202,7 @@ func newCertStateFromConfig(c *config.C) (*CertState, error) {
if err != nil {
return nil, fmt.Errorf("error while unmarshaling pki.cert %s: %s", pubPathOrPEM, err)
}
nebulaCert.Pkcs11Backed = isPkcs11
if nebulaCert.Expired(time.Now()) {
return nil, fmt.Errorf("nebula certificate for this host is expired")
}

View File

@ -8,6 +8,7 @@ import (
"log"
"math"
"net"
"net/netip"
"os"
"strings"
"sync"
@ -153,24 +154,48 @@ func New(config *config.C) (*Service, error) {
return &s, nil
}
// DialContext dials the provided address. Currently only TCP is supported.
func getProtocolNumber(addr netip.Addr) tcpip.NetworkProtocolNumber {
if addr.Is6() {
return ipv6.ProtocolNumber
}
return ipv4.ProtocolNumber
}
// DialContext dials the provided address.
func (s *Service) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
if network != "tcp" && network != "tcp4" {
return nil, errors.New("only tcp is supported")
switch network {
case "udp", "udp4", "udp6":
addr, err := net.ResolveUDPAddr(network, address)
if err != nil {
return nil, err
}
fullAddr := tcpip.FullAddress{
NIC: nicID,
Addr: tcpip.AddrFromSlice(addr.IP),
Port: uint16(addr.Port),
}
num := getProtocolNumber(addr.AddrPort().Addr())
return gonet.DialUDP(s.ipstack, nil, &fullAddr, num)
case "tcp", "tcp4", "tcp6":
addr, err := net.ResolveTCPAddr(network, address)
if err != nil {
return nil, err
}
fullAddr := tcpip.FullAddress{
NIC: nicID,
Addr: tcpip.AddrFromSlice(addr.IP),
Port: uint16(addr.Port),
}
num := getProtocolNumber(addr.AddrPort().Addr())
return gonet.DialContextTCP(ctx, s.ipstack, fullAddr, num)
default:
return nil, fmt.Errorf("unknown network type: %s", network)
}
}
addr, err := net.ResolveTCPAddr(network, address)
if err != nil {
return nil, err
}
fullAddr := tcpip.FullAddress{
NIC: nicID,
Addr: tcpip.AddrFromSlice(addr.IP),
Port: uint16(addr.Port),
}
return gonet.DialContextTCP(ctx, s.ipstack, fullAddr, ipv4.ProtocolNumber)
// Dial dials the provided address
func (s *Service) Dial(network, address string) (net.Conn, error) {
return s.DialContext(context.Background(), network, address)
}
// Listen listens on the provided address. Currently only TCP with wildcard

View File

@ -218,9 +218,7 @@ func (u *StdConn) writeTo6(b []byte, ip netip.AddrPort) error {
var rsa unix.RawSockaddrInet6
rsa.Family = unix.AF_INET6
rsa.Addr = ip.Addr().As16()
port := ip.Port()
// Little Endian -> Network Endian
rsa.Port = (port >> 8) | ((port & 0xff) << 8)
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&rsa.Port))[:], ip.Port())
for {
_, _, err := unix.Syscall6(
@ -251,9 +249,7 @@ func (u *StdConn) writeTo4(b []byte, ip netip.AddrPort) error {
var rsa unix.RawSockaddrInet4
rsa.Family = unix.AF_INET
rsa.Addr = ip.Addr().As4()
port := ip.Port()
// Little Endian -> Network Endian
rsa.Port = (port >> 8) | ((port & 0xff) << 8)
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&rsa.Port))[:], ip.Port())
for {
_, _, err := unix.Syscall6(