Cert interface (#1212)

This commit is contained in:
Nate Brown 2024-10-10 18:00:22 -05:00 committed by GitHub
parent 16eaae306a
commit 08ac65362e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
49 changed files with 2862 additions and 2833 deletions

View File

@ -1,7 +1,7 @@
GO111MODULE = on GO111MODULE = on
export GO111MODULE export GO111MODULE
cert.pb.go: cert.proto .FORCE cert_v1.pb.go: cert_v1.proto .FORCE
go build google.golang.org/protobuf/cmd/protoc-gen-go go build google.golang.org/protobuf/cmd/protoc-gen-go
PATH="$(CURDIR):$(PATH)" protoc --go_out=. --go_opt=paths=source_relative $< PATH="$(CURDIR):$(PATH)" protoc --go_out=. --go_opt=paths=source_relative $<
rm protoc-gen-go rm protoc-gen-go

View File

@ -1,140 +0,0 @@
package cert
import (
"errors"
"fmt"
"strings"
"time"
)
type NebulaCAPool struct {
CAs map[string]*NebulaCertificate
certBlocklist map[string]struct{}
}
// NewCAPool creates a CAPool
func NewCAPool() *NebulaCAPool {
ca := NebulaCAPool{
CAs: make(map[string]*NebulaCertificate),
certBlocklist: make(map[string]struct{}),
}
return &ca
}
// NewCAPoolFromBytes will create a new CA pool from the provided
// input bytes, which must be a PEM-encoded set of nebula certificates.
// If the pool contains any expired certificates, an ErrExpired will be
// returned along with the pool. The caller must handle any such errors.
func NewCAPoolFromBytes(caPEMs []byte) (*NebulaCAPool, error) {
pool := NewCAPool()
var err error
var expired bool
for {
caPEMs, err = pool.AddCACertificate(caPEMs)
if errors.Is(err, ErrExpired) {
expired = true
err = nil
}
if err != nil {
return nil, err
}
if len(caPEMs) == 0 || strings.TrimSpace(string(caPEMs)) == "" {
break
}
}
if expired {
return pool, ErrExpired
}
return pool, nil
}
// AddCACertificate verifies a Nebula CA certificate and adds it to the pool
// Only the first pem encoded object will be consumed, any remaining bytes are returned.
// Parsed certificates will be verified and must be a CA
func (ncp *NebulaCAPool) AddCACertificate(pemBytes []byte) ([]byte, error) {
c, pemBytes, err := UnmarshalNebulaCertificateFromPEM(pemBytes)
if err != nil {
return pemBytes, err
}
if !c.Details.IsCA {
return pemBytes, fmt.Errorf("%s: %w", c.Details.Name, ErrNotCA)
}
if !c.CheckSignature(c.Details.PublicKey) {
return pemBytes, fmt.Errorf("%s: %w", c.Details.Name, ErrNotSelfSigned)
}
sum, err := c.Sha256Sum()
if err != nil {
return pemBytes, fmt.Errorf("could not calculate shasum for provided CA; error: %s; %s", err, c.Details.Name)
}
ncp.CAs[sum] = c
if c.Expired(time.Now()) {
return pemBytes, fmt.Errorf("%s: %w", c.Details.Name, ErrExpired)
}
return pemBytes, nil
}
// BlocklistFingerprint adds a cert fingerprint to the blocklist
func (ncp *NebulaCAPool) BlocklistFingerprint(f string) {
ncp.certBlocklist[f] = struct{}{}
}
// ResetCertBlocklist removes all previously blocklisted cert fingerprints
func (ncp *NebulaCAPool) ResetCertBlocklist() {
ncp.certBlocklist = make(map[string]struct{})
}
// NOTE: This uses an internal cache for Sha256Sum() that will not be invalidated
// automatically if you manually change any fields in the NebulaCertificate.
func (ncp *NebulaCAPool) IsBlocklisted(c *NebulaCertificate) bool {
return ncp.isBlocklistedWithCache(c, false)
}
// IsBlocklisted returns true if the fingerprint fails to generate or has been explicitly blocklisted
func (ncp *NebulaCAPool) isBlocklistedWithCache(c *NebulaCertificate, useCache bool) bool {
h, err := c.sha256SumWithCache(useCache)
if err != nil {
return true
}
if _, ok := ncp.certBlocklist[h]; ok {
return true
}
return false
}
// GetCAForCert attempts to return the signing certificate for the provided certificate.
// No signature validation is performed
func (ncp *NebulaCAPool) GetCAForCert(c *NebulaCertificate) (*NebulaCertificate, error) {
if c.Details.Issuer == "" {
return nil, fmt.Errorf("no issuer in certificate")
}
signer, ok := ncp.CAs[c.Details.Issuer]
if ok {
return signer, nil
}
return nil, fmt.Errorf("could not find ca for the certificate")
}
// GetFingerprints returns an array of trusted CA fingerprints
func (ncp *NebulaCAPool) GetFingerprints() []string {
fp := make([]string, len(ncp.CAs))
i := 0
for k := range ncp.CAs {
fp[i] = k
i++
}
return fp
}

296
cert/ca_pool.go Normal file
View File

@ -0,0 +1,296 @@
package cert
import (
"errors"
"fmt"
"net/netip"
"slices"
"strings"
"time"
)
type CAPool struct {
CAs map[string]*CachedCertificate
certBlocklist map[string]struct{}
}
// NewCAPool creates an empty CAPool
func NewCAPool() *CAPool {
ca := CAPool{
CAs: make(map[string]*CachedCertificate),
certBlocklist: make(map[string]struct{}),
}
return &ca
}
// NewCAPoolFromPEM will create a new CA pool from the provided
// input bytes, which must be a PEM-encoded set of nebula certificates.
// If the pool contains any expired certificates, an ErrExpired will be
// returned along with the pool. The caller must handle any such errors.
func NewCAPoolFromPEM(caPEMs []byte) (*CAPool, error) {
pool := NewCAPool()
var err error
var expired bool
for {
caPEMs, err = pool.AddCAFromPEM(caPEMs)
if errors.Is(err, ErrExpired) {
expired = true
err = nil
}
if err != nil {
return nil, err
}
if len(caPEMs) == 0 || strings.TrimSpace(string(caPEMs)) == "" {
break
}
}
if expired {
return pool, ErrExpired
}
return pool, nil
}
// AddCAFromPEM verifies a Nebula CA certificate and adds it to the pool.
// Only the first pem encoded object will be consumed, any remaining bytes are returned.
// Parsed certificates will be verified and must be a CA
func (ncp *CAPool) AddCAFromPEM(pemBytes []byte) ([]byte, error) {
c, pemBytes, err := UnmarshalCertificateFromPEM(pemBytes)
if err != nil {
return pemBytes, err
}
err = ncp.AddCA(c)
if err != nil {
return pemBytes, err
}
return pemBytes, nil
}
// AddCA verifies a Nebula CA certificate and adds it to the pool.
func (ncp *CAPool) AddCA(c Certificate) error {
if !c.IsCA() {
return fmt.Errorf("%s: %w", c.Name(), ErrNotCA)
}
if !c.CheckSignature(c.PublicKey()) {
return fmt.Errorf("%s: %w", c.Name(), ErrNotSelfSigned)
}
sum, err := c.Fingerprint()
if err != nil {
return fmt.Errorf("could not calculate fingerprint for provided CA; error: %w; %s", err, c.Name())
}
cc := &CachedCertificate{
Certificate: c,
Fingerprint: sum,
InvertedGroups: make(map[string]struct{}),
}
for _, g := range c.Groups() {
cc.InvertedGroups[g] = struct{}{}
}
ncp.CAs[sum] = cc
if c.Expired(time.Now()) {
return fmt.Errorf("%s: %w", c.Name(), ErrExpired)
}
return nil
}
// BlocklistFingerprint adds a cert fingerprint to the blocklist
func (ncp *CAPool) BlocklistFingerprint(f string) {
ncp.certBlocklist[f] = struct{}{}
}
// ResetCertBlocklist removes all previously blocklisted cert fingerprints
func (ncp *CAPool) ResetCertBlocklist() {
ncp.certBlocklist = make(map[string]struct{})
}
// IsBlocklisted tests the provided fingerprint against the pools blocklist.
// Returns true if the fingerprint is blocked.
func (ncp *CAPool) IsBlocklisted(fingerprint string) bool {
if _, ok := ncp.certBlocklist[fingerprint]; ok {
return true
}
return false
}
// VerifyCertificate verifies the certificate is valid and is signed by a trusted CA in the pool.
// If the certificate is valid then the returned CachedCertificate can be used in subsequent verification attempts
// to increase performance.
func (ncp *CAPool) VerifyCertificate(now time.Time, c Certificate) (*CachedCertificate, error) {
if c == nil {
return nil, fmt.Errorf("no certificate")
}
fp, err := c.Fingerprint()
if err != nil {
return nil, fmt.Errorf("could not calculate fingerprint to verify: %w", err)
}
signer, err := ncp.verify(c, now, fp, "")
if err != nil {
return nil, err
}
cc := CachedCertificate{
Certificate: c,
InvertedGroups: make(map[string]struct{}),
Fingerprint: fp,
signerFingerprint: signer.Fingerprint,
}
for _, g := range c.Groups() {
cc.InvertedGroups[g] = struct{}{}
}
return &cc, nil
}
// VerifyCachedCertificate is the same as VerifyCertificate other than it operates on a pre-verified structure and
// is a cheaper operation to perform as a result.
func (ncp *CAPool) VerifyCachedCertificate(now time.Time, c *CachedCertificate) error {
_, err := ncp.verify(c.Certificate, now, c.Fingerprint, c.signerFingerprint)
return err
}
func (ncp *CAPool) verify(c Certificate, now time.Time, certFp string, signerFp string) (*CachedCertificate, error) {
if ncp.IsBlocklisted(certFp) {
return nil, ErrBlockListed
}
signer, err := ncp.GetCAForCert(c)
if err != nil {
return nil, err
}
if signer.Certificate.Expired(now) {
return nil, ErrRootExpired
}
if c.Expired(now) {
return nil, ErrExpired
}
// If we are checking a cached certificate then we can bail early here
// Either the root is no longer trusted or everything is fine
if len(signerFp) > 0 {
if signerFp != signer.Fingerprint {
return nil, ErrFingerprintMismatch
}
return signer, nil
}
if !c.CheckSignature(signer.Certificate.PublicKey()) {
return nil, ErrSignatureMismatch
}
err = CheckCAConstraints(signer.Certificate, c)
if err != nil {
return nil, err
}
return signer, nil
}
// GetCAForCert attempts to return the signing certificate for the provided certificate.
// No signature validation is performed
func (ncp *CAPool) GetCAForCert(c Certificate) (*CachedCertificate, error) {
issuer := c.Issuer()
if issuer == "" {
return nil, fmt.Errorf("no issuer in certificate")
}
signer, ok := ncp.CAs[issuer]
if ok {
return signer, nil
}
return nil, fmt.Errorf("could not find ca for the certificate")
}
// GetFingerprints returns an array of trusted CA fingerprints
func (ncp *CAPool) GetFingerprints() []string {
fp := make([]string, len(ncp.CAs))
i := 0
for k := range ncp.CAs {
fp[i] = k
i++
}
return fp
}
// CheckCAConstraints returns an error if the sub certificate violates constraints present in the signer certificate.
func CheckCAConstraints(signer Certificate, sub Certificate) error {
return checkCAConstraints(signer, sub.NotBefore(), sub.NotAfter(), sub.Groups(), sub.Networks(), sub.UnsafeNetworks())
}
// checkCAConstraints is a very generic function allowing both Certificates and TBSCertificates to be tested.
func checkCAConstraints(signer Certificate, notBefore, notAfter time.Time, groups []string, networks, unsafeNetworks []netip.Prefix) error {
// Make sure this cert isn't valid after the root
if notAfter.After(signer.NotAfter()) {
return fmt.Errorf("certificate expires after signing certificate")
}
// Make sure this cert wasn't valid before the root
if notBefore.Before(signer.NotBefore()) {
return fmt.Errorf("certificate is valid before the signing certificate")
}
// If the signer has a limited set of groups make sure the cert only contains a subset
signerGroups := signer.Groups()
if len(signerGroups) > 0 {
for _, g := range groups {
if !slices.Contains(signerGroups, g) {
return fmt.Errorf("certificate contained a group not present on the signing ca: %s", g)
}
}
}
// If the signer has a limited set of ip ranges to issue from make sure the cert only contains a subset
signingNetworks := signer.Networks()
if len(signingNetworks) > 0 {
for _, certNetwork := range networks {
found := false
for _, signingNetwork := range signingNetworks {
if signingNetwork.Contains(certNetwork.Addr()) && signingNetwork.Bits() <= certNetwork.Bits() {
found = true
break
}
}
if !found {
return fmt.Errorf("certificate contained a network assignment outside the limitations of the signing ca: %s", certNetwork.String())
}
}
}
// If the signer has a limited set of subnet ranges to issue from make sure the cert only contains a subset
signingUnsafeNetworks := signer.UnsafeNetworks()
if len(signingUnsafeNetworks) > 0 {
for _, certUnsafeNetwork := range unsafeNetworks {
found := false
for _, caNetwork := range signingUnsafeNetworks {
if caNetwork.Contains(certUnsafeNetwork.Addr()) && caNetwork.Bits() <= certUnsafeNetwork.Bits() {
found = true
break
}
}
if !found {
return fmt.Errorf("certificate contained an unsafe network assignment outside the limitations of the signing ca: %s", certUnsafeNetwork.String())
}
}
}
return nil
}

109
cert/ca_pool_test.go Normal file
View File

@ -0,0 +1,109 @@
package cert
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewCAPoolFromBytes(t *testing.T) {
noNewLines := `
# Current provisional, Remove once everything moves over to the real root.
-----BEGIN NEBULA CERTIFICATE-----
CkAKDm5lYnVsYSByb290IGNhKJfap9AFMJfg1+YGOiCUQGByMuNRhIlQBOyzXWbL
vcKBwDhov900phEfJ5DN3kABEkDCq5R8qBiu8sl54yVfgRcQXEDt3cHr8UTSLszv
bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB
-----END NEBULA CERTIFICATE-----
# root-ca01
-----BEGIN NEBULA CERTIFICATE-----
CkMKEW5lYnVsYSByb290IGNhIDAxKJL2u9EFMJL86+cGOiDPXMH4oU6HZTk/CqTG
BVG+oJpAoqokUBbI4U0N8CSfpUABEkB/Pm5A2xyH/nc8mg/wvGUWG3pZ7nHzaDMf
8/phAUt+FLzqTECzQKisYswKvE3pl9mbEYKbOdIHrxdIp95mo4sF
-----END NEBULA CERTIFICATE-----
`
withNewLines := `
# Current provisional, Remove once everything moves over to the real root.
-----BEGIN NEBULA CERTIFICATE-----
CkAKDm5lYnVsYSByb290IGNhKJfap9AFMJfg1+YGOiCUQGByMuNRhIlQBOyzXWbL
vcKBwDhov900phEfJ5DN3kABEkDCq5R8qBiu8sl54yVfgRcQXEDt3cHr8UTSLszv
bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB
-----END NEBULA CERTIFICATE-----
# root-ca01
-----BEGIN NEBULA CERTIFICATE-----
CkMKEW5lYnVsYSByb290IGNhIDAxKJL2u9EFMJL86+cGOiDPXMH4oU6HZTk/CqTG
BVG+oJpAoqokUBbI4U0N8CSfpUABEkB/Pm5A2xyH/nc8mg/wvGUWG3pZ7nHzaDMf
8/phAUt+FLzqTECzQKisYswKvE3pl9mbEYKbOdIHrxdIp95mo4sF
-----END NEBULA CERTIFICATE-----
`
expired := `
# expired certificate
-----BEGIN NEBULA CERTIFICATE-----
CjkKB2V4cGlyZWQouPmWjQYwufmWjQY6ILCRaoCkJlqHgv5jfDN4lzLHBvDzaQm4
vZxfu144hmgjQAESQG4qlnZi8DncvD/LDZnLgJHOaX1DWCHHEh59epVsC+BNgTie
WH1M9n4O7cFtGlM6sJJOS+rCVVEJ3ABS7+MPdQs=
-----END NEBULA CERTIFICATE-----
`
p256 := `
# p256 certificate
-----BEGIN NEBULA CERTIFICATE-----
CmYKEG5lYnVsYSBQMjU2IHRlc3Qo4s+7mgYw4tXrsAc6QQRkaW2jFmllYvN4+/k2
6tctO9sPT3jOx8ES6M1nIqOhpTmZeabF/4rELDqPV4aH5jfJut798DUXql0FlF8H
76gvQAGgBgESRzBFAiEAib0/te6eMiZOKD8gdDeloMTS0wGuX2t0C7TFdUhAQzgC
IBNWYMep3ysx9zCgknfG5dKtwGTaqF++BWKDYdyl34KX
-----END NEBULA CERTIFICATE-----
`
rootCA := certificateV1{
details: detailsV1{
Name: "nebula root ca",
},
}
rootCA01 := certificateV1{
details: detailsV1{
Name: "nebula root ca 01",
},
}
rootCAP256 := certificateV1{
details: detailsV1{
Name: "nebula P256 test",
},
}
p, err := NewCAPoolFromPEM([]byte(noNewLines))
assert.Nil(t, err)
assert.Equal(t, p.CAs[string("c9bfaf7ce8e84b2eeda2e27b469f4b9617bde192efd214b68891ecda6ed49522")].Certificate.Name(), rootCA.details.Name)
assert.Equal(t, p.CAs[string("5c9c3f23e7ee7fe97637cbd3a0a5b854154d1d9aaaf7b566a51f4a88f76b64cd")].Certificate.Name(), rootCA01.details.Name)
pp, err := NewCAPoolFromPEM([]byte(withNewLines))
assert.Nil(t, err)
assert.Equal(t, pp.CAs[string("c9bfaf7ce8e84b2eeda2e27b469f4b9617bde192efd214b68891ecda6ed49522")].Certificate.Name(), rootCA.details.Name)
assert.Equal(t, pp.CAs[string("5c9c3f23e7ee7fe97637cbd3a0a5b854154d1d9aaaf7b566a51f4a88f76b64cd")].Certificate.Name(), rootCA01.details.Name)
// expired cert, no valid certs
ppp, err := NewCAPoolFromPEM([]byte(expired))
assert.Equal(t, ErrExpired, err)
assert.Equal(t, ppp.CAs[string("152070be6bb19bc9e3bde4c2f0e7d8f4ff5448b4c9856b8eccb314fade0229b0")].Certificate.Name(), "expired")
// expired cert, with valid certs
pppp, err := NewCAPoolFromPEM(append([]byte(expired), noNewLines...))
assert.Equal(t, ErrExpired, err)
assert.Equal(t, pppp.CAs[string("c9bfaf7ce8e84b2eeda2e27b469f4b9617bde192efd214b68891ecda6ed49522")].Certificate.Name(), rootCA.details.Name)
assert.Equal(t, pppp.CAs[string("5c9c3f23e7ee7fe97637cbd3a0a5b854154d1d9aaaf7b566a51f4a88f76b64cd")].Certificate.Name(), rootCA01.details.Name)
assert.Equal(t, pppp.CAs[string("152070be6bb19bc9e3bde4c2f0e7d8f4ff5448b4c9856b8eccb314fade0229b0")].Certificate.Name(), "expired")
assert.Equal(t, len(pppp.CAs), 3)
ppppp, err := NewCAPoolFromPEM([]byte(p256))
assert.Nil(t, err)
assert.Equal(t, ppppp.CAs[string("a7938893ec8c4ef769b06d7f425e5e46f7a7f5ffa49c3bcf4a86b608caba9159")].Certificate.Name(), rootCAP256.details.Name)
assert.Equal(t, len(ppppp.CAs), 1)
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

496
cert/cert_v1.go Normal file
View File

@ -0,0 +1,496 @@
package cert
import (
"bytes"
"crypto/ecdh"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"encoding/json"
"encoding/pem"
"fmt"
"math/big"
"net"
"net/netip"
"time"
"github.com/slackhq/nebula/pkclient"
"golang.org/x/crypto/curve25519"
"google.golang.org/protobuf/proto"
)
const publicKeyLen = 32
type certificateV1 struct {
details detailsV1
signature []byte
}
type detailsV1 struct {
Name string
Ips []netip.Prefix
Subnets []netip.Prefix
Groups []string
NotBefore time.Time
NotAfter time.Time
PublicKey []byte
IsCA bool
Issuer string
Curve Curve
}
type m map[string]interface{}
func (nc *certificateV1) Version() Version {
return Version1
}
func (nc *certificateV1) Curve() Curve {
return nc.details.Curve
}
func (nc *certificateV1) Groups() []string {
return nc.details.Groups
}
func (nc *certificateV1) IsCA() bool {
return nc.details.IsCA
}
func (nc *certificateV1) Issuer() string {
return nc.details.Issuer
}
func (nc *certificateV1) Name() string {
return nc.details.Name
}
func (nc *certificateV1) Networks() []netip.Prefix {
return nc.details.Ips
}
func (nc *certificateV1) NotAfter() time.Time {
return nc.details.NotAfter
}
func (nc *certificateV1) NotBefore() time.Time {
return nc.details.NotBefore
}
func (nc *certificateV1) PublicKey() []byte {
return nc.details.PublicKey
}
func (nc *certificateV1) Signature() []byte {
return nc.signature
}
func (nc *certificateV1) UnsafeNetworks() []netip.Prefix {
return nc.details.Subnets
}
func (nc *certificateV1) Fingerprint() (string, error) {
b, err := nc.Marshal()
if err != nil {
return "", err
}
sum := sha256.Sum256(b)
return hex.EncodeToString(sum[:]), nil
}
func (nc *certificateV1) CheckSignature(key []byte) bool {
b, err := proto.Marshal(nc.getRawDetails())
if err != nil {
return false
}
switch nc.details.Curve {
case Curve_CURVE25519:
return ed25519.Verify(key, b, nc.signature)
case Curve_P256:
x, y := elliptic.Unmarshal(elliptic.P256(), key)
pubKey := &ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y}
hashed := sha256.Sum256(b)
return ecdsa.VerifyASN1(pubKey, hashed[:], nc.signature)
default:
return false
}
}
func (nc *certificateV1) Expired(t time.Time) bool {
return nc.details.NotBefore.After(t) || nc.details.NotAfter.Before(t)
}
func (nc *certificateV1) VerifyPrivateKey(curve Curve, key []byte) error {
if curve != nc.details.Curve {
return fmt.Errorf("curve in cert and private key supplied don't match")
}
if nc.details.IsCA {
switch curve {
case Curve_CURVE25519:
// the call to PublicKey below will panic slice bounds out of range otherwise
if len(key) != ed25519.PrivateKeySize {
return fmt.Errorf("key was not 64 bytes, is invalid ed25519 private key")
}
if !ed25519.PublicKey(nc.details.PublicKey).Equal(ed25519.PrivateKey(key).Public()) {
return fmt.Errorf("public key in cert and private key supplied don't match")
}
case Curve_P256:
privkey, err := ecdh.P256().NewPrivateKey(key)
if err != nil {
return fmt.Errorf("cannot parse private key as P256: %w", err)
}
pub := privkey.PublicKey().Bytes()
if !bytes.Equal(pub, nc.details.PublicKey) {
return fmt.Errorf("public key in cert and private key supplied don't match")
}
default:
return fmt.Errorf("invalid curve: %s", curve)
}
return nil
}
var pub []byte
switch curve {
case Curve_CURVE25519:
var err error
pub, err = curve25519.X25519(key, curve25519.Basepoint)
if err != nil {
return err
}
case Curve_P256:
privkey, err := ecdh.P256().NewPrivateKey(key)
if err != nil {
return err
}
pub = privkey.PublicKey().Bytes()
default:
return fmt.Errorf("invalid curve: %s", curve)
}
if !bytes.Equal(pub, nc.details.PublicKey) {
return fmt.Errorf("public key in cert and private key supplied don't match")
}
return nil
}
// getRawDetails marshals the raw details into protobuf ready struct
func (nc *certificateV1) getRawDetails() *RawNebulaCertificateDetails {
rd := &RawNebulaCertificateDetails{
Name: nc.details.Name,
Groups: nc.details.Groups,
NotBefore: nc.details.NotBefore.Unix(),
NotAfter: nc.details.NotAfter.Unix(),
PublicKey: make([]byte, len(nc.details.PublicKey)),
IsCA: nc.details.IsCA,
Curve: nc.details.Curve,
}
for _, ipNet := range nc.details.Ips {
mask := net.CIDRMask(ipNet.Bits(), ipNet.Addr().BitLen())
rd.Ips = append(rd.Ips, addr2int(ipNet.Addr()), ip2int(mask))
}
for _, ipNet := range nc.details.Subnets {
mask := net.CIDRMask(ipNet.Bits(), ipNet.Addr().BitLen())
rd.Subnets = append(rd.Subnets, addr2int(ipNet.Addr()), ip2int(mask))
}
copy(rd.PublicKey, nc.details.PublicKey[:])
// I know, this is terrible
rd.Issuer, _ = hex.DecodeString(nc.details.Issuer)
return rd
}
func (nc *certificateV1) String() string {
if nc == nil {
return "Certificate {}\n"
}
s := "NebulaCertificate {\n"
s += "\tDetails {\n"
s += fmt.Sprintf("\t\tName: %v\n", nc.details.Name)
if len(nc.details.Ips) > 0 {
s += "\t\tIps: [\n"
for _, ip := range nc.details.Ips {
s += fmt.Sprintf("\t\t\t%v\n", ip.String())
}
s += "\t\t]\n"
} else {
s += "\t\tIps: []\n"
}
if len(nc.details.Subnets) > 0 {
s += "\t\tSubnets: [\n"
for _, ip := range nc.details.Subnets {
s += fmt.Sprintf("\t\t\t%v\n", ip.String())
}
s += "\t\t]\n"
} else {
s += "\t\tSubnets: []\n"
}
if len(nc.details.Groups) > 0 {
s += "\t\tGroups: [\n"
for _, g := range nc.details.Groups {
s += fmt.Sprintf("\t\t\t\"%v\"\n", g)
}
s += "\t\t]\n"
} else {
s += "\t\tGroups: []\n"
}
s += fmt.Sprintf("\t\tNot before: %v\n", nc.details.NotBefore)
s += fmt.Sprintf("\t\tNot After: %v\n", nc.details.NotAfter)
s += fmt.Sprintf("\t\tIs CA: %v\n", nc.details.IsCA)
s += fmt.Sprintf("\t\tIssuer: %s\n", nc.details.Issuer)
s += fmt.Sprintf("\t\tPublic key: %x\n", nc.details.PublicKey)
s += fmt.Sprintf("\t\tCurve: %s\n", nc.details.Curve)
s += "\t}\n"
fp, err := nc.Fingerprint()
if err == nil {
s += fmt.Sprintf("\tFingerprint: %s\n", fp)
}
s += fmt.Sprintf("\tSignature: %x\n", nc.Signature())
s += "}"
return s
}
func (nc *certificateV1) MarshalForHandshakes() ([]byte, error) {
pubKey := nc.details.PublicKey
nc.details.PublicKey = nil
rawCertNoKey, err := nc.Marshal()
if err != nil {
return nil, err
}
nc.details.PublicKey = pubKey
return rawCertNoKey, nil
}
func (nc *certificateV1) Marshal() ([]byte, error) {
rc := RawNebulaCertificate{
Details: nc.getRawDetails(),
Signature: nc.signature,
}
return proto.Marshal(&rc)
}
func (nc *certificateV1) MarshalPEM() ([]byte, error) {
b, err := nc.Marshal()
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{Type: CertificateBanner, Bytes: b}), nil
}
func (nc *certificateV1) MarshalJSON() ([]byte, error) {
fp, _ := nc.Fingerprint()
jc := m{
"details": m{
"name": nc.details.Name,
"ips": nc.details.Ips,
"subnets": nc.details.Subnets,
"groups": nc.details.Groups,
"notBefore": nc.details.NotBefore,
"notAfter": nc.details.NotAfter,
"publicKey": fmt.Sprintf("%x", nc.details.PublicKey),
"isCa": nc.details.IsCA,
"issuer": nc.details.Issuer,
"curve": nc.details.Curve.String(),
},
"fingerprint": fp,
"signature": fmt.Sprintf("%x", nc.Signature()),
}
return json.Marshal(jc)
}
func (nc *certificateV1) Copy() Certificate {
c := &certificateV1{
details: detailsV1{
Name: nc.details.Name,
Groups: make([]string, len(nc.details.Groups)),
Ips: make([]netip.Prefix, len(nc.details.Ips)),
Subnets: make([]netip.Prefix, len(nc.details.Subnets)),
NotBefore: nc.details.NotBefore,
NotAfter: nc.details.NotAfter,
PublicKey: make([]byte, len(nc.details.PublicKey)),
IsCA: nc.details.IsCA,
Issuer: nc.details.Issuer,
},
signature: make([]byte, len(nc.signature)),
}
copy(c.signature, nc.signature)
copy(c.details.Groups, nc.details.Groups)
copy(c.details.PublicKey, nc.details.PublicKey)
for i, p := range nc.details.Ips {
c.details.Ips[i] = p
}
for i, p := range nc.details.Subnets {
c.details.Subnets[i] = p
}
return c
}
// unmarshalCertificateV1 will unmarshal a protobuf byte representation of a nebula cert
func unmarshalCertificateV1(b []byte, assertPublicKey bool) (*certificateV1, error) {
if len(b) == 0 {
return nil, fmt.Errorf("nil byte array")
}
var rc RawNebulaCertificate
err := proto.Unmarshal(b, &rc)
if err != nil {
return nil, err
}
if rc.Details == nil {
return nil, fmt.Errorf("encoded Details was nil")
}
if len(rc.Details.Ips)%2 != 0 {
return nil, fmt.Errorf("encoded IPs should be in pairs, an odd number was found")
}
if len(rc.Details.Subnets)%2 != 0 {
return nil, fmt.Errorf("encoded Subnets should be in pairs, an odd number was found")
}
nc := certificateV1{
details: detailsV1{
Name: rc.Details.Name,
Groups: make([]string, len(rc.Details.Groups)),
Ips: make([]netip.Prefix, len(rc.Details.Ips)/2),
Subnets: make([]netip.Prefix, len(rc.Details.Subnets)/2),
NotBefore: time.Unix(rc.Details.NotBefore, 0),
NotAfter: time.Unix(rc.Details.NotAfter, 0),
PublicKey: make([]byte, len(rc.Details.PublicKey)),
IsCA: rc.Details.IsCA,
Curve: rc.Details.Curve,
},
signature: make([]byte, len(rc.Signature)),
}
copy(nc.signature, rc.Signature)
copy(nc.details.Groups, rc.Details.Groups)
nc.details.Issuer = hex.EncodeToString(rc.Details.Issuer)
if len(rc.Details.PublicKey) < publicKeyLen && assertPublicKey {
return nil, fmt.Errorf("public key was fewer than 32 bytes; %v", len(rc.Details.PublicKey))
}
copy(nc.details.PublicKey, rc.Details.PublicKey)
var ip netip.Addr
for i, rawIp := range rc.Details.Ips {
if i%2 == 0 {
ip = int2addr(rawIp)
} else {
ones, _ := net.IPMask(int2ip(rawIp)).Size()
nc.details.Ips[i/2] = netip.PrefixFrom(ip, ones)
}
}
for i, rawIp := range rc.Details.Subnets {
if i%2 == 0 {
ip = int2addr(rawIp)
} else {
ones, _ := net.IPMask(int2ip(rawIp)).Size()
nc.details.Subnets[i/2] = netip.PrefixFrom(ip, ones)
}
}
return &nc, nil
}
func signV1(t *TBSCertificate, curve Curve, key []byte, client *pkclient.PKClient) (*certificateV1, error) {
c := &certificateV1{
details: detailsV1{
Name: t.Name,
Ips: t.Networks,
Subnets: t.UnsafeNetworks,
Groups: t.Groups,
NotBefore: t.NotBefore,
NotAfter: t.NotAfter,
PublicKey: t.PublicKey,
IsCA: t.IsCA,
Curve: t.Curve,
Issuer: t.issuer,
},
}
b, err := proto.Marshal(c.getRawDetails())
if err != nil {
return nil, err
}
var sig []byte
switch curve {
case Curve_CURVE25519:
signer := ed25519.PrivateKey(key)
sig = ed25519.Sign(signer, b)
case Curve_P256:
if client != nil {
sig, err = client.SignASN1(b)
} else {
signer := &ecdsa.PrivateKey{
PublicKey: ecdsa.PublicKey{
Curve: elliptic.P256(),
},
// ref: https://github.com/golang/go/blob/go1.19/src/crypto/x509/sec1.go#L95
D: new(big.Int).SetBytes(key),
}
// ref: https://github.com/golang/go/blob/go1.19/src/crypto/x509/sec1.go#L119
signer.X, signer.Y = signer.Curve.ScalarBaseMult(key)
// We need to hash first for ECDSA
// - https://pkg.go.dev/crypto/ecdsa#SignASN1
hashed := sha256.Sum256(b)
sig, err = ecdsa.SignASN1(rand.Reader, signer, hashed[:])
if err != nil {
return nil, err
}
}
default:
return nil, fmt.Errorf("invalid curve: %s", c.details.Curve)
}
c.signature = sig
return c, nil
}
func ip2int(ip []byte) uint32 {
if len(ip) == 16 {
return binary.BigEndian.Uint32(ip[12:16])
}
return binary.BigEndian.Uint32(ip)
}
func int2ip(nn uint32) net.IP {
ip := make(net.IP, net.IPv4len)
binary.BigEndian.PutUint32(ip, nn)
return ip
}
func addr2int(addr netip.Addr) uint32 {
b := addr.Unmap().As4()
return binary.BigEndian.Uint32(b[:])
}
func int2addr(nn uint32) netip.Addr {
ip := [4]byte{}
binary.BigEndian.PutUint32(ip[:], nn)
return netip.AddrFrom4(ip).Unmap()
}

View File

@ -1,8 +1,8 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.30.0 // protoc-gen-go v1.34.2
// protoc v3.21.5 // protoc v3.21.5
// source: cert.proto // source: cert_v1.proto
package cert package cert
@ -50,11 +50,11 @@ func (x Curve) String() string {
} }
func (Curve) Descriptor() protoreflect.EnumDescriptor { func (Curve) Descriptor() protoreflect.EnumDescriptor {
return file_cert_proto_enumTypes[0].Descriptor() return file_cert_v1_proto_enumTypes[0].Descriptor()
} }
func (Curve) Type() protoreflect.EnumType { func (Curve) Type() protoreflect.EnumType {
return &file_cert_proto_enumTypes[0] return &file_cert_v1_proto_enumTypes[0]
} }
func (x Curve) Number() protoreflect.EnumNumber { func (x Curve) Number() protoreflect.EnumNumber {
@ -63,7 +63,7 @@ func (x Curve) Number() protoreflect.EnumNumber {
// Deprecated: Use Curve.Descriptor instead. // Deprecated: Use Curve.Descriptor instead.
func (Curve) EnumDescriptor() ([]byte, []int) { func (Curve) EnumDescriptor() ([]byte, []int) {
return file_cert_proto_rawDescGZIP(), []int{0} return file_cert_v1_proto_rawDescGZIP(), []int{0}
} }
type RawNebulaCertificate struct { type RawNebulaCertificate struct {
@ -78,7 +78,7 @@ type RawNebulaCertificate struct {
func (x *RawNebulaCertificate) Reset() { func (x *RawNebulaCertificate) Reset() {
*x = RawNebulaCertificate{} *x = RawNebulaCertificate{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_cert_proto_msgTypes[0] mi := &file_cert_v1_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -91,7 +91,7 @@ func (x *RawNebulaCertificate) String() string {
func (*RawNebulaCertificate) ProtoMessage() {} func (*RawNebulaCertificate) ProtoMessage() {}
func (x *RawNebulaCertificate) ProtoReflect() protoreflect.Message { func (x *RawNebulaCertificate) ProtoReflect() protoreflect.Message {
mi := &file_cert_proto_msgTypes[0] mi := &file_cert_v1_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -104,7 +104,7 @@ func (x *RawNebulaCertificate) ProtoReflect() protoreflect.Message {
// Deprecated: Use RawNebulaCertificate.ProtoReflect.Descriptor instead. // Deprecated: Use RawNebulaCertificate.ProtoReflect.Descriptor instead.
func (*RawNebulaCertificate) Descriptor() ([]byte, []int) { func (*RawNebulaCertificate) Descriptor() ([]byte, []int) {
return file_cert_proto_rawDescGZIP(), []int{0} return file_cert_v1_proto_rawDescGZIP(), []int{0}
} }
func (x *RawNebulaCertificate) GetDetails() *RawNebulaCertificateDetails { func (x *RawNebulaCertificate) GetDetails() *RawNebulaCertificateDetails {
@ -143,7 +143,7 @@ type RawNebulaCertificateDetails struct {
func (x *RawNebulaCertificateDetails) Reset() { func (x *RawNebulaCertificateDetails) Reset() {
*x = RawNebulaCertificateDetails{} *x = RawNebulaCertificateDetails{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_cert_proto_msgTypes[1] mi := &file_cert_v1_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -156,7 +156,7 @@ func (x *RawNebulaCertificateDetails) String() string {
func (*RawNebulaCertificateDetails) ProtoMessage() {} func (*RawNebulaCertificateDetails) ProtoMessage() {}
func (x *RawNebulaCertificateDetails) ProtoReflect() protoreflect.Message { func (x *RawNebulaCertificateDetails) ProtoReflect() protoreflect.Message {
mi := &file_cert_proto_msgTypes[1] mi := &file_cert_v1_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -169,7 +169,7 @@ func (x *RawNebulaCertificateDetails) ProtoReflect() protoreflect.Message {
// Deprecated: Use RawNebulaCertificateDetails.ProtoReflect.Descriptor instead. // Deprecated: Use RawNebulaCertificateDetails.ProtoReflect.Descriptor instead.
func (*RawNebulaCertificateDetails) Descriptor() ([]byte, []int) { func (*RawNebulaCertificateDetails) Descriptor() ([]byte, []int) {
return file_cert_proto_rawDescGZIP(), []int{1} return file_cert_v1_proto_rawDescGZIP(), []int{1}
} }
func (x *RawNebulaCertificateDetails) GetName() string { func (x *RawNebulaCertificateDetails) GetName() string {
@ -254,7 +254,7 @@ type RawNebulaEncryptedData struct {
func (x *RawNebulaEncryptedData) Reset() { func (x *RawNebulaEncryptedData) Reset() {
*x = RawNebulaEncryptedData{} *x = RawNebulaEncryptedData{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_cert_proto_msgTypes[2] mi := &file_cert_v1_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -267,7 +267,7 @@ func (x *RawNebulaEncryptedData) String() string {
func (*RawNebulaEncryptedData) ProtoMessage() {} func (*RawNebulaEncryptedData) ProtoMessage() {}
func (x *RawNebulaEncryptedData) ProtoReflect() protoreflect.Message { func (x *RawNebulaEncryptedData) ProtoReflect() protoreflect.Message {
mi := &file_cert_proto_msgTypes[2] mi := &file_cert_v1_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -280,7 +280,7 @@ func (x *RawNebulaEncryptedData) ProtoReflect() protoreflect.Message {
// Deprecated: Use RawNebulaEncryptedData.ProtoReflect.Descriptor instead. // Deprecated: Use RawNebulaEncryptedData.ProtoReflect.Descriptor instead.
func (*RawNebulaEncryptedData) Descriptor() ([]byte, []int) { func (*RawNebulaEncryptedData) Descriptor() ([]byte, []int) {
return file_cert_proto_rawDescGZIP(), []int{2} return file_cert_v1_proto_rawDescGZIP(), []int{2}
} }
func (x *RawNebulaEncryptedData) GetEncryptionMetadata() *RawNebulaEncryptionMetadata { func (x *RawNebulaEncryptedData) GetEncryptionMetadata() *RawNebulaEncryptionMetadata {
@ -309,7 +309,7 @@ type RawNebulaEncryptionMetadata struct {
func (x *RawNebulaEncryptionMetadata) Reset() { func (x *RawNebulaEncryptionMetadata) Reset() {
*x = RawNebulaEncryptionMetadata{} *x = RawNebulaEncryptionMetadata{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_cert_proto_msgTypes[3] mi := &file_cert_v1_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -322,7 +322,7 @@ func (x *RawNebulaEncryptionMetadata) String() string {
func (*RawNebulaEncryptionMetadata) ProtoMessage() {} func (*RawNebulaEncryptionMetadata) ProtoMessage() {}
func (x *RawNebulaEncryptionMetadata) ProtoReflect() protoreflect.Message { func (x *RawNebulaEncryptionMetadata) ProtoReflect() protoreflect.Message {
mi := &file_cert_proto_msgTypes[3] mi := &file_cert_v1_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -335,7 +335,7 @@ func (x *RawNebulaEncryptionMetadata) ProtoReflect() protoreflect.Message {
// Deprecated: Use RawNebulaEncryptionMetadata.ProtoReflect.Descriptor instead. // Deprecated: Use RawNebulaEncryptionMetadata.ProtoReflect.Descriptor instead.
func (*RawNebulaEncryptionMetadata) Descriptor() ([]byte, []int) { func (*RawNebulaEncryptionMetadata) Descriptor() ([]byte, []int) {
return file_cert_proto_rawDescGZIP(), []int{3} return file_cert_v1_proto_rawDescGZIP(), []int{3}
} }
func (x *RawNebulaEncryptionMetadata) GetEncryptionAlgorithm() string { func (x *RawNebulaEncryptionMetadata) GetEncryptionAlgorithm() string {
@ -367,7 +367,7 @@ type RawNebulaArgon2Parameters struct {
func (x *RawNebulaArgon2Parameters) Reset() { func (x *RawNebulaArgon2Parameters) Reset() {
*x = RawNebulaArgon2Parameters{} *x = RawNebulaArgon2Parameters{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_cert_proto_msgTypes[4] mi := &file_cert_v1_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -380,7 +380,7 @@ func (x *RawNebulaArgon2Parameters) String() string {
func (*RawNebulaArgon2Parameters) ProtoMessage() {} func (*RawNebulaArgon2Parameters) ProtoMessage() {}
func (x *RawNebulaArgon2Parameters) ProtoReflect() protoreflect.Message { func (x *RawNebulaArgon2Parameters) ProtoReflect() protoreflect.Message {
mi := &file_cert_proto_msgTypes[4] mi := &file_cert_v1_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -393,7 +393,7 @@ func (x *RawNebulaArgon2Parameters) ProtoReflect() protoreflect.Message {
// Deprecated: Use RawNebulaArgon2Parameters.ProtoReflect.Descriptor instead. // Deprecated: Use RawNebulaArgon2Parameters.ProtoReflect.Descriptor instead.
func (*RawNebulaArgon2Parameters) Descriptor() ([]byte, []int) { func (*RawNebulaArgon2Parameters) Descriptor() ([]byte, []int) {
return file_cert_proto_rawDescGZIP(), []int{4} return file_cert_v1_proto_rawDescGZIP(), []int{4}
} }
func (x *RawNebulaArgon2Parameters) GetVersion() int32 { func (x *RawNebulaArgon2Parameters) GetVersion() int32 {
@ -431,87 +431,87 @@ func (x *RawNebulaArgon2Parameters) GetSalt() []byte {
return nil return nil
} }
var File_cert_proto protoreflect.FileDescriptor var File_cert_v1_proto protoreflect.FileDescriptor
var file_cert_proto_rawDesc = []byte{ var file_cert_v1_proto_rawDesc = []byte{
0x0a, 0x0a, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x63, 0x65, 0x0a, 0x0d, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x76, 0x31, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
0x72, 0x74, 0x22, 0x71, 0x0a, 0x14, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x43, 0x04, 0x63, 0x65, 0x72, 0x74, 0x22, 0x71, 0x0a, 0x14, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75,
0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x3b, 0x0a, 0x07, 0x44, 0x65, 0x6c, 0x61, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x3b, 0x0a,
0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x63, 0x65, 0x07, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21,
0x72, 0x74, 0x2e, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x43, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x43,
0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x07, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c,
0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x73, 0x52, 0x07, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x53, 0x69,
0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x53, 0x69, 0x67, 0x6e, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x53,
0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x9c, 0x02, 0x0a, 0x1b, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x9c, 0x02, 0x0a, 0x1b, 0x52, 0x61, 0x77,
0x75, 0x6c, 0x61, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x44, 0x65, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65,
0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x49, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03,
0x18, 0x02, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x03, 0x49, 0x70, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x53, 0x49, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x03, 0x49, 0x70, 0x73, 0x12, 0x18,
0x75, 0x62, 0x6e, 0x65, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x07, 0x53, 0x75, 0x0a, 0x07, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0d, 0x52,
0x62, 0x6e, 0x65, 0x74, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x07, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x47, 0x72, 0x6f, 0x75,
0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x1c, 0x0a, 0x70, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73,
0x09, 0x4e, 0x6f, 0x74, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x12, 0x1c, 0x0a, 0x09, 0x4e, 0x6f, 0x74, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x18, 0x05, 0x20,
0x52, 0x09, 0x4e, 0x6f, 0x74, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x4e, 0x01, 0x28, 0x03, 0x52, 0x09, 0x4e, 0x6f, 0x74, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x12, 0x1a,
0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x4e, 0x0a, 0x08, 0x4e, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03,
0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x52, 0x08, 0x4e, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x75,
0x63, 0x4b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x50, 0x75, 0x62, 0x6c, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x50,
0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x49, 0x73, 0x43, 0x41, 0x18, 0x08, 0x20, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x49, 0x73, 0x43, 0x41,
0x01, 0x28, 0x08, 0x52, 0x04, 0x49, 0x73, 0x43, 0x41, 0x12, 0x16, 0x0a, 0x06, 0x49, 0x73, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x49, 0x73, 0x43, 0x41, 0x12, 0x16, 0x0a, 0x06,
0x75, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x49, 0x73, 0x73, 0x75, 0x65, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x49, 0x73,
0x72, 0x12, 0x21, 0x0a, 0x05, 0x63, 0x75, 0x72, 0x76, 0x65, 0x18, 0x64, 0x20, 0x01, 0x28, 0x0e, 0x73, 0x75, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x05, 0x63, 0x75, 0x72, 0x76, 0x65, 0x18, 0x64, 0x20,
0x32, 0x0b, 0x2e, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x43, 0x75, 0x72, 0x76, 0x65, 0x52, 0x05, 0x63, 0x01, 0x28, 0x0e, 0x32, 0x0b, 0x2e, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x43, 0x75, 0x72, 0x76, 0x65,
0x75, 0x72, 0x76, 0x65, 0x22, 0x8b, 0x01, 0x0a, 0x16, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x52, 0x05, 0x63, 0x75, 0x72, 0x76, 0x65, 0x22, 0x8b, 0x01, 0x0a, 0x16, 0x52, 0x61, 0x77, 0x4e,
0x6c, 0x61, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61,
0x51, 0x0a, 0x12, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x74, 0x61, 0x12, 0x51, 0x0a, 0x12, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e,
0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21,
0x72, 0x74, 0x2e, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x45, 0x6e, 0x63, 0x72, 0x2e, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x45,
0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x12, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x61, 0x52, 0x12, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74,
0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x43, 0x69, 0x70, 0x68, 0x65,
0x78, 0x74, 0x22, 0x9c, 0x01, 0x0a, 0x1b, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x72, 0x74, 0x65, 0x78, 0x74, 0x22, 0x9c, 0x01, 0x0a, 0x1b, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62,
0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x75, 0x6c, 0x61, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74,
0x74, 0x61, 0x12, 0x30, 0x0a, 0x13, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x30, 0x0a, 0x13, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74,
0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x69, 0x6f, 0x6e, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x18, 0x01, 0x20, 0x01,
0x13, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x28, 0x09, 0x52, 0x13, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x6c,
0x69, 0x74, 0x68, 0x6d, 0x12, 0x4b, 0x0a, 0x10, 0x41, 0x72, 0x67, 0x6f, 0x6e, 0x32, 0x50, 0x61, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x4b, 0x0a, 0x10, 0x41, 0x72, 0x67, 0x6f, 0x6e,
0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x32, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
0x2e, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x41, 0x0b, 0x32, 0x1f, 0x2e, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75,
0x72, 0x67, 0x6f, 0x6e, 0x32, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x52, 0x6c, 0x61, 0x41, 0x72, 0x67, 0x6f, 0x6e, 0x32, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65,
0x10, 0x41, 0x72, 0x67, 0x6f, 0x6e, 0x32, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x72, 0x73, 0x52, 0x10, 0x41, 0x72, 0x67, 0x6f, 0x6e, 0x32, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65,
0x73, 0x22, 0xa3, 0x01, 0x0a, 0x19, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x41, 0x74, 0x65, 0x72, 0x73, 0x22, 0xa3, 0x01, 0x0a, 0x19, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75,
0x72, 0x67, 0x6f, 0x6e, 0x32, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x6c, 0x61, 0x41, 0x72, 0x67, 0x6f, 0x6e, 0x32, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65,
0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20,
0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x6d, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06,
0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6d, 0x65,
0x79, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x69, 0x73, 0x6d, 0x6d, 0x6f, 0x72, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c,
0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x69, 0x73, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x61, 0x6c,
0x69, 0x73, 0x6d, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x6c, 0x65, 0x6c, 0x69, 0x73, 0x6d, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74,
0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x69, 0x74, 0x65, 0x72,
0x6f, 0x6e, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x18, 0x05,
0x0c, 0x52, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x2a, 0x21, 0x0a, 0x05, 0x43, 0x75, 0x72, 0x76, 0x65, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x2a, 0x21, 0x0a, 0x05, 0x43, 0x75,
0x12, 0x0e, 0x0a, 0x0a, 0x43, 0x55, 0x52, 0x56, 0x45, 0x32, 0x35, 0x35, 0x31, 0x39, 0x10, 0x00, 0x72, 0x76, 0x65, 0x12, 0x0e, 0x0a, 0x0a, 0x43, 0x55, 0x52, 0x56, 0x45, 0x32, 0x35, 0x35, 0x31,
0x12, 0x08, 0x0a, 0x04, 0x50, 0x32, 0x35, 0x36, 0x10, 0x01, 0x42, 0x20, 0x5a, 0x1e, 0x67, 0x69, 0x39, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x32, 0x35, 0x36, 0x10, 0x01, 0x42, 0x20, 0x5a,
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6c, 0x61, 0x63, 0x6b, 0x68, 0x71, 0x1e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6c, 0x61, 0x63,
0x2f, 0x6e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6b, 0x68, 0x71, 0x2f, 0x6e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x62,
0x6f, 0x74, 0x6f, 0x33, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (
file_cert_proto_rawDescOnce sync.Once file_cert_v1_proto_rawDescOnce sync.Once
file_cert_proto_rawDescData = file_cert_proto_rawDesc file_cert_v1_proto_rawDescData = file_cert_v1_proto_rawDesc
) )
func file_cert_proto_rawDescGZIP() []byte { func file_cert_v1_proto_rawDescGZIP() []byte {
file_cert_proto_rawDescOnce.Do(func() { file_cert_v1_proto_rawDescOnce.Do(func() {
file_cert_proto_rawDescData = protoimpl.X.CompressGZIP(file_cert_proto_rawDescData) file_cert_v1_proto_rawDescData = protoimpl.X.CompressGZIP(file_cert_v1_proto_rawDescData)
}) })
return file_cert_proto_rawDescData return file_cert_v1_proto_rawDescData
} }
var file_cert_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_cert_v1_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_cert_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_cert_v1_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_cert_proto_goTypes = []interface{}{ var file_cert_v1_proto_goTypes = []any{
(Curve)(0), // 0: cert.Curve (Curve)(0), // 0: cert.Curve
(*RawNebulaCertificate)(nil), // 1: cert.RawNebulaCertificate (*RawNebulaCertificate)(nil), // 1: cert.RawNebulaCertificate
(*RawNebulaCertificateDetails)(nil), // 2: cert.RawNebulaCertificateDetails (*RawNebulaCertificateDetails)(nil), // 2: cert.RawNebulaCertificateDetails
@ -519,7 +519,7 @@ var file_cert_proto_goTypes = []interface{}{
(*RawNebulaEncryptionMetadata)(nil), // 4: cert.RawNebulaEncryptionMetadata (*RawNebulaEncryptionMetadata)(nil), // 4: cert.RawNebulaEncryptionMetadata
(*RawNebulaArgon2Parameters)(nil), // 5: cert.RawNebulaArgon2Parameters (*RawNebulaArgon2Parameters)(nil), // 5: cert.RawNebulaArgon2Parameters
} }
var file_cert_proto_depIdxs = []int32{ var file_cert_v1_proto_depIdxs = []int32{
2, // 0: cert.RawNebulaCertificate.Details:type_name -> cert.RawNebulaCertificateDetails 2, // 0: cert.RawNebulaCertificate.Details:type_name -> cert.RawNebulaCertificateDetails
0, // 1: cert.RawNebulaCertificateDetails.curve:type_name -> cert.Curve 0, // 1: cert.RawNebulaCertificateDetails.curve:type_name -> cert.Curve
4, // 2: cert.RawNebulaEncryptedData.EncryptionMetadata:type_name -> cert.RawNebulaEncryptionMetadata 4, // 2: cert.RawNebulaEncryptedData.EncryptionMetadata:type_name -> cert.RawNebulaEncryptionMetadata
@ -531,13 +531,13 @@ var file_cert_proto_depIdxs = []int32{
0, // [0:4] is the sub-list for field type_name 0, // [0:4] is the sub-list for field type_name
} }
func init() { file_cert_proto_init() } func init() { file_cert_v1_proto_init() }
func file_cert_proto_init() { func file_cert_v1_proto_init() {
if File_cert_proto != nil { if File_cert_v1_proto != nil {
return return
} }
if !protoimpl.UnsafeEnabled { if !protoimpl.UnsafeEnabled {
file_cert_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { file_cert_v1_proto_msgTypes[0].Exporter = func(v any, i int) any {
switch v := v.(*RawNebulaCertificate); i { switch v := v.(*RawNebulaCertificate); i {
case 0: case 0:
return &v.state return &v.state
@ -549,7 +549,7 @@ func file_cert_proto_init() {
return nil return nil
} }
} }
file_cert_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { file_cert_v1_proto_msgTypes[1].Exporter = func(v any, i int) any {
switch v := v.(*RawNebulaCertificateDetails); i { switch v := v.(*RawNebulaCertificateDetails); i {
case 0: case 0:
return &v.state return &v.state
@ -561,7 +561,7 @@ func file_cert_proto_init() {
return nil return nil
} }
} }
file_cert_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { file_cert_v1_proto_msgTypes[2].Exporter = func(v any, i int) any {
switch v := v.(*RawNebulaEncryptedData); i { switch v := v.(*RawNebulaEncryptedData); i {
case 0: case 0:
return &v.state return &v.state
@ -573,7 +573,7 @@ func file_cert_proto_init() {
return nil return nil
} }
} }
file_cert_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { file_cert_v1_proto_msgTypes[3].Exporter = func(v any, i int) any {
switch v := v.(*RawNebulaEncryptionMetadata); i { switch v := v.(*RawNebulaEncryptionMetadata); i {
case 0: case 0:
return &v.state return &v.state
@ -585,7 +585,7 @@ func file_cert_proto_init() {
return nil return nil
} }
} }
file_cert_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { file_cert_v1_proto_msgTypes[4].Exporter = func(v any, i int) any {
switch v := v.(*RawNebulaArgon2Parameters); i { switch v := v.(*RawNebulaArgon2Parameters); i {
case 0: case 0:
return &v.state return &v.state
@ -602,19 +602,19 @@ func file_cert_proto_init() {
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_cert_proto_rawDesc, RawDescriptor: file_cert_v1_proto_rawDesc,
NumEnums: 1, NumEnums: 1,
NumMessages: 5, NumMessages: 5,
NumExtensions: 0, NumExtensions: 0,
NumServices: 0, NumServices: 0,
}, },
GoTypes: file_cert_proto_goTypes, GoTypes: file_cert_v1_proto_goTypes,
DependencyIndexes: file_cert_proto_depIdxs, DependencyIndexes: file_cert_v1_proto_depIdxs,
EnumInfos: file_cert_proto_enumTypes, EnumInfos: file_cert_v1_proto_enumTypes,
MessageInfos: file_cert_proto_msgTypes, MessageInfos: file_cert_v1_proto_msgTypes,
}.Build() }.Build()
File_cert_proto = out.File File_cert_v1_proto = out.File
file_cert_proto_rawDesc = nil file_cert_v1_proto_rawDesc = nil
file_cert_proto_goTypes = nil file_cert_v1_proto_goTypes = nil
file_cert_proto_depIdxs = nil file_cert_v1_proto_depIdxs = nil
} }

View File

@ -3,14 +3,28 @@ package cert
import ( import (
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/ed25519"
"crypto/rand" "crypto/rand"
"encoding/pem"
"fmt" "fmt"
"io" "io"
"math"
"golang.org/x/crypto/argon2" "golang.org/x/crypto/argon2"
"google.golang.org/protobuf/proto"
) )
// KDF factors type NebulaEncryptedData struct {
EncryptionMetadata NebulaEncryptionMetadata
Ciphertext []byte
}
type NebulaEncryptionMetadata struct {
EncryptionAlgorithm string
Argon2Parameters Argon2Parameters
}
// Argon2Parameters KDF factors
type Argon2Parameters struct { type Argon2Parameters struct {
version rune version rune
Memory uint32 // KiB Memory uint32 // KiB
@ -19,7 +33,7 @@ type Argon2Parameters struct {
salt []byte salt []byte
} }
// Returns a new Argon2Parameters object with current version set // NewArgon2Parameters Returns a new Argon2Parameters object with current version set
func NewArgon2Parameters(memory uint32, parallelism uint8, iterations uint32) *Argon2Parameters { func NewArgon2Parameters(memory uint32, parallelism uint8, iterations uint32) *Argon2Parameters {
return &Argon2Parameters{ return &Argon2Parameters{
version: argon2.Version, version: argon2.Version,
@ -141,3 +155,146 @@ func splitNonceCiphertext(blob []byte, nonceSize int) ([]byte, []byte, error) {
return blob[:nonceSize], blob[nonceSize:], nil return blob[:nonceSize], blob[nonceSize:], nil
} }
// EncryptAndMarshalSigningPrivateKey is a simple helper to encrypt and PEM encode a private key
func EncryptAndMarshalSigningPrivateKey(curve Curve, b []byte, passphrase []byte, kdfParams *Argon2Parameters) ([]byte, error) {
ciphertext, err := aes256Encrypt(passphrase, kdfParams, b)
if err != nil {
return nil, err
}
b, err = proto.Marshal(&RawNebulaEncryptedData{
EncryptionMetadata: &RawNebulaEncryptionMetadata{
EncryptionAlgorithm: "AES-256-GCM",
Argon2Parameters: &RawNebulaArgon2Parameters{
Version: kdfParams.version,
Memory: kdfParams.Memory,
Parallelism: uint32(kdfParams.Parallelism),
Iterations: kdfParams.Iterations,
Salt: kdfParams.salt,
},
},
Ciphertext: ciphertext,
})
if err != nil {
return nil, err
}
switch curve {
case Curve_CURVE25519:
return pem.EncodeToMemory(&pem.Block{Type: EncryptedEd25519PrivateKeyBanner, Bytes: b}), nil
case Curve_P256:
return pem.EncodeToMemory(&pem.Block{Type: EncryptedECDSAP256PrivateKeyBanner, Bytes: b}), nil
default:
return nil, fmt.Errorf("invalid curve: %v", curve)
}
}
// UnmarshalNebulaEncryptedData will unmarshal a protobuf byte representation of a nebula cert into its
// protobuf-generated struct.
func UnmarshalNebulaEncryptedData(b []byte) (*NebulaEncryptedData, error) {
if len(b) == 0 {
return nil, fmt.Errorf("nil byte array")
}
var rned RawNebulaEncryptedData
err := proto.Unmarshal(b, &rned)
if err != nil {
return nil, err
}
if rned.EncryptionMetadata == nil {
return nil, fmt.Errorf("encoded EncryptionMetadata was nil")
}
if rned.EncryptionMetadata.Argon2Parameters == nil {
return nil, fmt.Errorf("encoded Argon2Parameters was nil")
}
params, err := unmarshalArgon2Parameters(rned.EncryptionMetadata.Argon2Parameters)
if err != nil {
return nil, err
}
ned := NebulaEncryptedData{
EncryptionMetadata: NebulaEncryptionMetadata{
EncryptionAlgorithm: rned.EncryptionMetadata.EncryptionAlgorithm,
Argon2Parameters: *params,
},
Ciphertext: rned.Ciphertext,
}
return &ned, nil
}
func unmarshalArgon2Parameters(params *RawNebulaArgon2Parameters) (*Argon2Parameters, error) {
if params.Version < math.MinInt32 || params.Version > math.MaxInt32 {
return nil, fmt.Errorf("Argon2Parameters Version must be at least %d and no more than %d", math.MinInt32, math.MaxInt32)
}
if params.Memory <= 0 || params.Memory > math.MaxUint32 {
return nil, fmt.Errorf("Argon2Parameters Memory must be be greater than 0 and no more than %d KiB", uint32(math.MaxUint32))
}
if params.Parallelism <= 0 || params.Parallelism > math.MaxUint8 {
return nil, fmt.Errorf("Argon2Parameters Parallelism must be be greater than 0 and no more than %d", math.MaxUint8)
}
if params.Iterations <= 0 || params.Iterations > math.MaxUint32 {
return nil, fmt.Errorf("-argon-iterations must be be greater than 0 and no more than %d", uint32(math.MaxUint32))
}
return &Argon2Parameters{
version: params.Version,
Memory: params.Memory,
Parallelism: uint8(params.Parallelism),
Iterations: params.Iterations,
salt: params.Salt,
}, nil
}
// DecryptAndUnmarshalSigningPrivateKey will try to pem decode and decrypt an Ed25519/ECDSA private key with
// the given passphrase, returning any other bytes b or an error on failure
func DecryptAndUnmarshalSigningPrivateKey(passphrase, b []byte) (Curve, []byte, []byte, error) {
var curve Curve
k, r := pem.Decode(b)
if k == nil {
return curve, nil, r, fmt.Errorf("input did not contain a valid PEM encoded block")
}
switch k.Type {
case EncryptedEd25519PrivateKeyBanner:
curve = Curve_CURVE25519
case EncryptedECDSAP256PrivateKeyBanner:
curve = Curve_P256
default:
return curve, nil, r, fmt.Errorf("bytes did not contain a proper nebula encrypted Ed25519/ECDSA private key banner")
}
ned, err := UnmarshalNebulaEncryptedData(k.Bytes)
if err != nil {
return curve, nil, r, err
}
var bytes []byte
switch ned.EncryptionMetadata.EncryptionAlgorithm {
case "AES-256-GCM":
bytes, err = aes256Decrypt(passphrase, &ned.EncryptionMetadata.Argon2Parameters, ned.Ciphertext)
if err != nil {
return curve, nil, r, err
}
default:
return curve, nil, r, fmt.Errorf("unsupported encryption algorithm: %s", ned.EncryptionMetadata.EncryptionAlgorithm)
}
switch curve {
case Curve_CURVE25519:
if len(bytes) != ed25519.PrivateKeySize {
return curve, nil, r, fmt.Errorf("key was not %d bytes, is invalid ed25519 private key", ed25519.PrivateKeySize)
}
case Curve_P256:
if len(bytes) != 32 {
return curve, nil, r, fmt.Errorf("key was not 32 bytes, is invalid ECDSA P256 private key")
}
}
return curve, bytes, r, nil
}

View File

@ -23,3 +23,90 @@ func TestNewArgon2Parameters(t *testing.T) {
Iterations: 1, Iterations: 1,
}, p) }, p)
} }
func TestDecryptAndUnmarshalSigningPrivateKey(t *testing.T) {
passphrase := []byte("DO NOT USE THIS KEY")
privKey := []byte(`# A good key
-----BEGIN NEBULA ED25519 ENCRYPTED PRIVATE KEY-----
CjwKC0FFUy0yNTYtR0NNEi0IExCAgIABGAEgBCognnjujd67Vsv99p22wfAjQaDT
oCMW1mdjkU3gACKNW4MSXOWR9Sts4C81yk1RUku2gvGKs3TB9LYoklLsIizSYOLl
+Vs//O1T0I1Xbml2XBAROsb/VSoDln/6LMqR4B6fn6B3GOsLBBqRI8daDl9lRMPB
qrlJ69wer3ZUHFXA
-----END NEBULA ED25519 ENCRYPTED PRIVATE KEY-----
`)
shortKey := []byte(`# A key which, once decrypted, is too short
-----BEGIN NEBULA ED25519 ENCRYPTED PRIVATE KEY-----
CjwKC0FFUy0yNTYtR0NNEi0IExCAgIABGAEgBCoga5h8owMEBWRSMMJKzuUvWce7
k0qlBkQmCxiuLh80MuASW70YcKt8jeEIS2axo2V6zAKA9TSMcCsJW1kDDXEtL/xe
GLF5T7sDl5COp4LU3pGxpV+KoeQ/S3gQCAAcnaOtnJQX+aSDnbO3jCHyP7U9CHbs
rQr3bdH3Oy/WiYU=
-----END NEBULA ED25519 ENCRYPTED PRIVATE KEY-----
`)
invalidBanner := []byte(`# Invalid banner (not encrypted)
-----BEGIN NEBULA ED25519 PRIVATE KEY-----
bWRp2CTVFhW9HD/qCd28ltDgK3w8VXSeaEYczDWos8sMUBqDb9jP3+NYwcS4lURG
XgLvodMXZJuaFPssp+WwtA==
-----END NEBULA ED25519 PRIVATE KEY-----
`)
invalidPem := []byte(`# Not a valid PEM format
-BEGIN NEBULA ED25519 ENCRYPTED PRIVATE KEY-----
CjwKC0FFUy0yNTYtR0NNEi0IExCAgIABGAEgBCognnjujd67Vsv99p22wfAjQaDT
oCMW1mdjkU3gACKNW4MSXOWR9Sts4C81yk1RUku2gvGKs3TB9LYoklLsIizSYOLl
+Vs//O1T0I1Xbml2XBAROsb/VSoDln/6LMqR4B6fn6B3GOsLBBqRI8daDl9lRMPB
qrlJ69wer3ZUHFXA
-END NEBULA ED25519 ENCRYPTED PRIVATE KEY-----
`)
keyBundle := appendByteSlices(privKey, shortKey, invalidBanner, invalidPem)
// Success test case
curve, k, rest, err := DecryptAndUnmarshalSigningPrivateKey(passphrase, keyBundle)
assert.Nil(t, err)
assert.Equal(t, Curve_CURVE25519, curve)
assert.Len(t, k, 64)
assert.Equal(t, rest, appendByteSlices(shortKey, invalidBanner, invalidPem))
// Fail due to short key
curve, k, rest, err = DecryptAndUnmarshalSigningPrivateKey(passphrase, rest)
assert.EqualError(t, err, "key was not 64 bytes, is invalid ed25519 private key")
assert.Nil(t, k)
assert.Equal(t, rest, appendByteSlices(invalidBanner, invalidPem))
// Fail due to invalid banner
curve, k, rest, err = DecryptAndUnmarshalSigningPrivateKey(passphrase, rest)
assert.EqualError(t, err, "bytes did not contain a proper nebula encrypted Ed25519/ECDSA private key banner")
assert.Nil(t, k)
assert.Equal(t, rest, invalidPem)
// Fail due to ivalid PEM format, because
// it's missing the requisite pre-encapsulation boundary.
curve, k, rest, err = DecryptAndUnmarshalSigningPrivateKey(passphrase, rest)
assert.EqualError(t, err, "input did not contain a valid PEM encoded block")
assert.Nil(t, k)
assert.Equal(t, rest, invalidPem)
// Fail due to invalid passphrase
curve, k, rest, err = DecryptAndUnmarshalSigningPrivateKey([]byte("invalid passphrase"), privKey)
assert.EqualError(t, err, "invalid passphrase or corrupt private key")
assert.Nil(t, k)
assert.Equal(t, rest, []byte{})
}
func TestEncryptAndMarshalSigningPrivateKey(t *testing.T) {
// Having proved that decryption works correctly above, we can test the
// encryption function produces a value which can be decrypted
passphrase := []byte("passphrase")
bytes := []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
kdfParams := NewArgon2Parameters(64*1024, 4, 3)
key, err := EncryptAndMarshalSigningPrivateKey(Curve_CURVE25519, bytes, passphrase, kdfParams)
assert.Nil(t, err)
// Verify the "key" can be decrypted successfully
curve, k, rest, err := DecryptAndUnmarshalSigningPrivateKey(passphrase, key)
assert.Len(t, k, 64)
assert.Equal(t, Curve_CURVE25519, curve)
assert.Equal(t, rest, []byte{})
assert.Nil(t, err)
// EncryptAndMarshalEd25519PrivateKey does not create any errors itself
}

View File

@ -5,10 +5,23 @@ import (
) )
var ( var (
ErrBadFormat = errors.New("bad wire format")
ErrRootExpired = errors.New("root certificate is expired") ErrRootExpired = errors.New("root certificate is expired")
ErrExpired = errors.New("certificate is expired") ErrExpired = errors.New("certificate is expired")
ErrNotCA = errors.New("certificate is not a CA") ErrNotCA = errors.New("certificate is not a CA")
ErrNotSelfSigned = errors.New("certificate is not self-signed") ErrNotSelfSigned = errors.New("certificate is not self-signed")
ErrBlockListed = errors.New("certificate is in the block list") ErrBlockListed = errors.New("certificate is in the block list")
ErrFingerprintMismatch = errors.New("certificate fingerprint did not match")
ErrSignatureMismatch = errors.New("certificate signature did not match") ErrSignatureMismatch = errors.New("certificate signature did not match")
ErrInvalidPublicKeyLength = errors.New("invalid public key length")
ErrInvalidPrivateKeyLength = errors.New("invalid private key length")
ErrPrivateKeyEncrypted = errors.New("private key must be decrypted")
ErrInvalidPEMBlock = errors.New("input did not contain a valid PEM encoded block")
ErrInvalidPEMCertificateBanner = errors.New("bytes did not contain a proper certificate banner")
ErrInvalidPEMX25519PublicKeyBanner = errors.New("bytes did not contain a proper X25519 public key banner")
ErrInvalidPEMX25519PrivateKeyBanner = errors.New("bytes did not contain a proper X25519 private key banner")
ErrInvalidPEMEd25519PublicKeyBanner = errors.New("bytes did not contain a proper Ed25519 public key banner")
ErrInvalidPEMEd25519PrivateKeyBanner = errors.New("bytes did not contain a proper Ed25519 private key banner")
) )

155
cert/pem.go Normal file
View File

@ -0,0 +1,155 @@
package cert
import (
"encoding/pem"
"fmt"
"golang.org/x/crypto/ed25519"
)
const (
CertificateBanner = "NEBULA CERTIFICATE"
CertificateV2Banner = "NEBULA CERTIFICATE V2"
X25519PrivateKeyBanner = "NEBULA X25519 PRIVATE KEY"
X25519PublicKeyBanner = "NEBULA X25519 PUBLIC KEY"
EncryptedEd25519PrivateKeyBanner = "NEBULA ED25519 ENCRYPTED PRIVATE KEY"
Ed25519PrivateKeyBanner = "NEBULA ED25519 PRIVATE KEY"
Ed25519PublicKeyBanner = "NEBULA ED25519 PUBLIC KEY"
P256PrivateKeyBanner = "NEBULA P256 PRIVATE KEY"
P256PublicKeyBanner = "NEBULA P256 PUBLIC KEY"
EncryptedECDSAP256PrivateKeyBanner = "NEBULA ECDSA P256 ENCRYPTED PRIVATE KEY"
ECDSAP256PrivateKeyBanner = "NEBULA ECDSA P256 PRIVATE KEY"
)
// UnmarshalCertificateFromPEM will try to unmarshal the first pem block in a byte array, returning any non consumed
// data or an error on failure
func UnmarshalCertificateFromPEM(b []byte) (Certificate, []byte, error) {
p, r := pem.Decode(b)
if p == nil {
return nil, r, ErrInvalidPEMBlock
}
switch p.Type {
case CertificateBanner:
c, err := unmarshalCertificateV1(p.Bytes, true)
if err != nil {
return nil, nil, err
}
return c, r, nil
case CertificateV2Banner:
//TODO
panic("TODO")
default:
return nil, r, ErrInvalidPEMCertificateBanner
}
}
func MarshalPublicKeyToPEM(curve Curve, b []byte) []byte {
switch curve {
case Curve_CURVE25519:
return pem.EncodeToMemory(&pem.Block{Type: X25519PublicKeyBanner, Bytes: b})
case Curve_P256:
return pem.EncodeToMemory(&pem.Block{Type: P256PublicKeyBanner, Bytes: b})
default:
return nil
}
}
func UnmarshalPublicKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {
k, r := pem.Decode(b)
if k == nil {
return nil, r, 0, fmt.Errorf("input did not contain a valid PEM encoded block")
}
var expectedLen int
var curve Curve
switch k.Type {
case X25519PublicKeyBanner, Ed25519PublicKeyBanner:
expectedLen = 32
curve = Curve_CURVE25519
case P256PublicKeyBanner:
// Uncompressed
expectedLen = 65
curve = Curve_P256
default:
return nil, r, 0, fmt.Errorf("bytes did not contain a proper public key banner")
}
if len(k.Bytes) != expectedLen {
return nil, r, 0, fmt.Errorf("key was not %d bytes, is invalid %s public key", expectedLen, curve)
}
return k.Bytes, r, curve, nil
}
func MarshalPrivateKeyToPEM(curve Curve, b []byte) []byte {
switch curve {
case Curve_CURVE25519:
return pem.EncodeToMemory(&pem.Block{Type: X25519PrivateKeyBanner, Bytes: b})
case Curve_P256:
return pem.EncodeToMemory(&pem.Block{Type: P256PrivateKeyBanner, Bytes: b})
default:
return nil
}
}
func MarshalSigningPrivateKeyToPEM(curve Curve, b []byte) []byte {
switch curve {
case Curve_CURVE25519:
return pem.EncodeToMemory(&pem.Block{Type: Ed25519PrivateKeyBanner, Bytes: b})
case Curve_P256:
return pem.EncodeToMemory(&pem.Block{Type: ECDSAP256PrivateKeyBanner, Bytes: b})
default:
return nil
}
}
// UnmarshalPrivateKeyFromPEM will try to unmarshal the first pem block in a byte array, returning any non
// consumed data or an error on failure
func UnmarshalPrivateKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {
k, r := pem.Decode(b)
if k == nil {
return nil, r, 0, fmt.Errorf("input did not contain a valid PEM encoded block")
}
var expectedLen int
var curve Curve
switch k.Type {
case X25519PrivateKeyBanner:
expectedLen = 32
curve = Curve_CURVE25519
case P256PrivateKeyBanner:
expectedLen = 32
curve = Curve_P256
default:
return nil, r, 0, fmt.Errorf("bytes did not contain a proper private key banner")
}
if len(k.Bytes) != expectedLen {
return nil, r, 0, fmt.Errorf("key was not %d bytes, is invalid %s private key", expectedLen, curve)
}
return k.Bytes, r, curve, nil
}
func UnmarshalSigningPrivateKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {
k, r := pem.Decode(b)
if k == nil {
return nil, r, 0, fmt.Errorf("input did not contain a valid PEM encoded block")
}
var curve Curve
switch k.Type {
case EncryptedEd25519PrivateKeyBanner:
return nil, nil, Curve_CURVE25519, ErrPrivateKeyEncrypted
case EncryptedECDSAP256PrivateKeyBanner:
return nil, nil, Curve_P256, ErrPrivateKeyEncrypted
case Ed25519PrivateKeyBanner:
curve = Curve_CURVE25519
if len(k.Bytes) != ed25519.PrivateKeySize {
return nil, r, 0, fmt.Errorf("key was not %d bytes, is invalid Ed25519 private key", ed25519.PrivateKeySize)
}
case ECDSAP256PrivateKeyBanner:
curve = Curve_P256
if len(k.Bytes) != 32 {
return nil, r, 0, fmt.Errorf("key was not 32 bytes, is invalid ECDSA P256 private key")
}
default:
return nil, r, 0, fmt.Errorf("bytes did not contain a proper Ed25519/ECDSA private key banner")
}
return k.Bytes, r, curve, nil
}

292
cert/pem_test.go Normal file
View File

@ -0,0 +1,292 @@
package cert
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestUnmarshalCertificateFromPEM(t *testing.T) {
goodCert := []byte(`
# A good cert
-----BEGIN NEBULA CERTIFICATE-----
CkAKDm5lYnVsYSByb290IGNhKJfap9AFMJfg1+YGOiCUQGByMuNRhIlQBOyzXWbL
vcKBwDhov900phEfJ5DN3kABEkDCq5R8qBiu8sl54yVfgRcQXEDt3cHr8UTSLszv
bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB
-----END NEBULA CERTIFICATE-----
`)
badBanner := []byte(`# A bad banner
-----BEGIN NOT A NEBULA CERTIFICATE-----
CkAKDm5lYnVsYSByb290IGNhKJfap9AFMJfg1+YGOiCUQGByMuNRhIlQBOyzXWbL
vcKBwDhov900phEfJ5DN3kABEkDCq5R8qBiu8sl54yVfgRcQXEDt3cHr8UTSLszv
bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB
-----END NOT A NEBULA CERTIFICATE-----
`)
invalidPem := []byte(`# Not a valid PEM format
-BEGIN NEBULA CERTIFICATE-----
CkAKDm5lYnVsYSByb290IGNhKJfap9AFMJfg1+YGOiCUQGByMuNRhIlQBOyzXWbL
vcKBwDhov900phEfJ5DN3kABEkDCq5R8qBiu8sl54yVfgRcQXEDt3cHr8UTSLszv
bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB
-END NEBULA CERTIFICATE----`)
certBundle := appendByteSlices(goodCert, badBanner, invalidPem)
// Success test case
cert, rest, err := UnmarshalCertificateFromPEM(certBundle)
assert.NotNil(t, cert)
assert.Equal(t, rest, append(badBanner, invalidPem...))
assert.Nil(t, err)
// Fail due to invalid banner.
cert, rest, err = UnmarshalCertificateFromPEM(rest)
assert.Nil(t, cert)
assert.Equal(t, rest, invalidPem)
assert.EqualError(t, err, "bytes did not contain a proper certificate banner")
// Fail due to ivalid PEM format, because
// it's missing the requisite pre-encapsulation boundary.
cert, rest, err = UnmarshalCertificateFromPEM(rest)
assert.Nil(t, cert)
assert.Equal(t, rest, invalidPem)
assert.EqualError(t, err, "input did not contain a valid PEM encoded block")
}
func TestUnmarshalSigningPrivateKeyFromPEM(t *testing.T) {
privKey := []byte(`# A good key
-----BEGIN NEBULA ED25519 PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-----END NEBULA ED25519 PRIVATE KEY-----
`)
privP256Key := []byte(`# A good key
-----BEGIN NEBULA ECDSA P256 PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-----END NEBULA ECDSA P256 PRIVATE KEY-----
`)
shortKey := []byte(`# A short key
-----BEGIN NEBULA ED25519 PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-----END NEBULA ED25519 PRIVATE KEY-----
`)
invalidBanner := []byte(`# Invalid banner
-----BEGIN NOT A NEBULA PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-----END NOT A NEBULA PRIVATE KEY-----
`)
invalidPem := []byte(`# Not a valid PEM format
-BEGIN NEBULA ED25519 PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-END NEBULA ED25519 PRIVATE KEY-----`)
keyBundle := appendByteSlices(privKey, privP256Key, shortKey, invalidBanner, invalidPem)
// Success test case
k, rest, curve, err := UnmarshalSigningPrivateKeyFromPEM(keyBundle)
assert.Len(t, k, 64)
assert.Equal(t, rest, appendByteSlices(privP256Key, shortKey, invalidBanner, invalidPem))
assert.Equal(t, Curve_CURVE25519, curve)
assert.Nil(t, err)
// Success test case
k, rest, curve, err = UnmarshalSigningPrivateKeyFromPEM(rest)
assert.Len(t, k, 32)
assert.Equal(t, rest, appendByteSlices(shortKey, invalidBanner, invalidPem))
assert.Equal(t, Curve_P256, curve)
assert.Nil(t, err)
// Fail due to short key
k, rest, curve, err = UnmarshalSigningPrivateKeyFromPEM(rest)
assert.Nil(t, k)
assert.Equal(t, rest, appendByteSlices(invalidBanner, invalidPem))
assert.EqualError(t, err, "key was not 64 bytes, is invalid Ed25519 private key")
// Fail due to invalid banner
k, rest, curve, err = UnmarshalSigningPrivateKeyFromPEM(rest)
assert.Nil(t, k)
assert.Equal(t, rest, invalidPem)
assert.EqualError(t, err, "bytes did not contain a proper Ed25519/ECDSA private key banner")
// Fail due to ivalid PEM format, because
// it's missing the requisite pre-encapsulation boundary.
k, rest, curve, err = UnmarshalSigningPrivateKeyFromPEM(rest)
assert.Nil(t, k)
assert.Equal(t, rest, invalidPem)
assert.EqualError(t, err, "input did not contain a valid PEM encoded block")
}
func TestUnmarshalPrivateKeyFromPEM(t *testing.T) {
privKey := []byte(`# A good key
-----BEGIN NEBULA X25519 PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-----END NEBULA X25519 PRIVATE KEY-----
`)
privP256Key := []byte(`# A good key
-----BEGIN NEBULA P256 PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-----END NEBULA P256 PRIVATE KEY-----
`)
shortKey := []byte(`# A short key
-----BEGIN NEBULA X25519 PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-----END NEBULA X25519 PRIVATE KEY-----
`)
invalidBanner := []byte(`# Invalid banner
-----BEGIN NOT A NEBULA PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-----END NOT A NEBULA PRIVATE KEY-----
`)
invalidPem := []byte(`# Not a valid PEM format
-BEGIN NEBULA X25519 PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-END NEBULA X25519 PRIVATE KEY-----`)
keyBundle := appendByteSlices(privKey, privP256Key, shortKey, invalidBanner, invalidPem)
// Success test case
k, rest, curve, err := UnmarshalPrivateKeyFromPEM(keyBundle)
assert.Len(t, k, 32)
assert.Equal(t, rest, appendByteSlices(privP256Key, shortKey, invalidBanner, invalidPem))
assert.Equal(t, Curve_CURVE25519, curve)
assert.Nil(t, err)
// Success test case
k, rest, curve, err = UnmarshalPrivateKeyFromPEM(rest)
assert.Len(t, k, 32)
assert.Equal(t, rest, appendByteSlices(shortKey, invalidBanner, invalidPem))
assert.Equal(t, Curve_P256, curve)
assert.Nil(t, err)
// Fail due to short key
k, rest, curve, err = UnmarshalPrivateKeyFromPEM(rest)
assert.Nil(t, k)
assert.Equal(t, rest, appendByteSlices(invalidBanner, invalidPem))
assert.EqualError(t, err, "key was not 32 bytes, is invalid CURVE25519 private key")
// Fail due to invalid banner
k, rest, curve, err = UnmarshalPrivateKeyFromPEM(rest)
assert.Nil(t, k)
assert.Equal(t, rest, invalidPem)
assert.EqualError(t, err, "bytes did not contain a proper private key banner")
// Fail due to ivalid PEM format, because
// it's missing the requisite pre-encapsulation boundary.
k, rest, curve, err = UnmarshalPrivateKeyFromPEM(rest)
assert.Nil(t, k)
assert.Equal(t, rest, invalidPem)
assert.EqualError(t, err, "input did not contain a valid PEM encoded block")
}
func TestUnmarshalPublicKeyFromPEM(t *testing.T) {
pubKey := []byte(`# A good key
-----BEGIN NEBULA ED25519 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-----END NEBULA ED25519 PUBLIC KEY-----
`)
shortKey := []byte(`# A short key
-----BEGIN NEBULA ED25519 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-----END NEBULA ED25519 PUBLIC KEY-----
`)
invalidBanner := []byte(`# Invalid banner
-----BEGIN NOT A NEBULA PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-----END NOT A NEBULA PUBLIC KEY-----
`)
invalidPem := []byte(`# Not a valid PEM format
-BEGIN NEBULA ED25519 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-END NEBULA ED25519 PUBLIC KEY-----`)
keyBundle := appendByteSlices(pubKey, shortKey, invalidBanner, invalidPem)
// Success test case
k, rest, curve, err := UnmarshalPublicKeyFromPEM(keyBundle)
assert.Equal(t, 32, len(k))
assert.Equal(t, Curve_CURVE25519, curve)
assert.Nil(t, err)
assert.Equal(t, rest, appendByteSlices(shortKey, invalidBanner, invalidPem))
// Fail due to short key
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
assert.Nil(t, k)
assert.Equal(t, Curve_CURVE25519, curve)
assert.Equal(t, rest, appendByteSlices(invalidBanner, invalidPem))
assert.EqualError(t, err, "key was not 32 bytes, is invalid CURVE25519 public key")
// Fail due to invalid banner
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
assert.Nil(t, k)
assert.Equal(t, Curve_CURVE25519, curve)
assert.EqualError(t, err, "bytes did not contain a proper public key banner")
assert.Equal(t, rest, invalidPem)
// Fail due to ivalid PEM format, because
// it's missing the requisite pre-encapsulation boundary.
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
assert.Nil(t, k)
assert.Equal(t, Curve_CURVE25519, curve)
assert.Equal(t, rest, invalidPem)
assert.EqualError(t, err, "input did not contain a valid PEM encoded block")
}
func TestUnmarshalX25519PublicKey(t *testing.T) {
pubKey := []byte(`# A good key
-----BEGIN NEBULA X25519 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-----END NEBULA X25519 PUBLIC KEY-----
`)
pubP256Key := []byte(`# A good key
-----BEGIN NEBULA P256 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAA=
-----END NEBULA P256 PUBLIC KEY-----
`)
shortKey := []byte(`# A short key
-----BEGIN NEBULA X25519 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-----END NEBULA X25519 PUBLIC KEY-----
`)
invalidBanner := []byte(`# Invalid banner
-----BEGIN NOT A NEBULA PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-----END NOT A NEBULA PUBLIC KEY-----
`)
invalidPem := []byte(`# Not a valid PEM format
-BEGIN NEBULA X25519 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-END NEBULA X25519 PUBLIC KEY-----`)
keyBundle := appendByteSlices(pubKey, pubP256Key, shortKey, invalidBanner, invalidPem)
// Success test case
k, rest, curve, err := UnmarshalPublicKeyFromPEM(keyBundle)
assert.Equal(t, 32, len(k))
assert.Nil(t, err)
assert.Equal(t, rest, appendByteSlices(pubP256Key, shortKey, invalidBanner, invalidPem))
assert.Equal(t, Curve_CURVE25519, curve)
// Success test case
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
assert.Equal(t, 65, len(k))
assert.Nil(t, err)
assert.Equal(t, rest, appendByteSlices(shortKey, invalidBanner, invalidPem))
assert.Equal(t, Curve_P256, curve)
// Fail due to short key
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
assert.Nil(t, k)
assert.Equal(t, rest, appendByteSlices(invalidBanner, invalidPem))
assert.EqualError(t, err, "key was not 32 bytes, is invalid CURVE25519 public key")
// Fail due to invalid banner
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
assert.Nil(t, k)
assert.EqualError(t, err, "bytes did not contain a proper public key banner")
assert.Equal(t, rest, invalidPem)
// Fail due to ivalid PEM format, because
// it's missing the requisite pre-encapsulation boundary.
k, rest, curve, err = UnmarshalPublicKeyFromPEM(rest)
assert.Nil(t, k)
assert.Equal(t, rest, invalidPem)
assert.EqualError(t, err, "input did not contain a valid PEM encoded block")
}

76
cert/sign.go Normal file
View File

@ -0,0 +1,76 @@
package cert
import (
"fmt"
"net/netip"
"time"
"github.com/slackhq/nebula/pkclient"
)
// TBSCertificate represents a certificate intended to be signed.
// It is invalid to use this structure as a Certificate.
type TBSCertificate struct {
Version Version
Name string
Networks []netip.Prefix
UnsafeNetworks []netip.Prefix
Groups []string
IsCA bool
NotBefore time.Time
NotAfter time.Time
PublicKey []byte
Curve Curve
issuer string
}
// Sign will create a sealed certificate using details provided by the TBSCertificate as long as those
// details do not violate constraints of the signing certificate.
// If the TBSCertificate is a CA then signer must be nil.
func (t *TBSCertificate) Sign(signer Certificate, curve Curve, key []byte) (Certificate, error) {
return t.sign(signer, curve, key, nil)
}
func (t *TBSCertificate) SignPkcs11(signer Certificate, curve Curve, client *pkclient.PKClient) (Certificate, error) {
if curve != Curve_P256 {
return nil, fmt.Errorf("only P256 is supported by PKCS#11")
}
return t.sign(signer, curve, nil, client)
}
func (t *TBSCertificate) sign(signer Certificate, curve Curve, key []byte, client *pkclient.PKClient) (Certificate, error) {
if curve != t.Curve {
return nil, fmt.Errorf("curve in cert and private key supplied don't match")
}
//TODO: make sure we have all minimum properties to sign, like a public key
if signer != nil {
if t.IsCA {
return nil, fmt.Errorf("can not sign a CA certificate with another")
}
err := checkCAConstraints(signer, t.NotBefore, t.NotAfter, t.Groups, t.Networks, t.UnsafeNetworks)
if err != nil {
return nil, err
}
issuer, err := signer.Fingerprint()
if err != nil {
return nil, fmt.Errorf("error computing issuer: %v", err)
}
t.issuer = issuer
} else {
if !t.IsCA {
return nil, fmt.Errorf("self signed certificates must have IsCA set to true")
}
}
switch t.Version {
case Version1:
return signV1(t, curve, key, client)
default:
return nil, fmt.Errorf("unknown cert version %d", t.Version)
}
}

View File

@ -4,12 +4,11 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rand"
"flag" "flag"
"fmt" "fmt"
"io" "io"
"math" "math"
"net" "net/netip"
"os" "os"
"strings" "strings"
"time" "time"
@ -114,38 +113,36 @@ func ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error
} }
} }
var ips []*net.IPNet var ips []netip.Prefix
if *cf.ips != "" { if *cf.ips != "" {
for _, rs := range strings.Split(*cf.ips, ",") { for _, rs := range strings.Split(*cf.ips, ",") {
rs := strings.Trim(rs, " ") rs := strings.Trim(rs, " ")
if rs != "" { if rs != "" {
ip, ipNet, err := net.ParseCIDR(rs) n, err := netip.ParsePrefix(rs)
if err != nil { if err != nil {
return newHelpErrorf("invalid ip definition: %s", err) return newHelpErrorf("invalid ip definition: %s", err)
} }
if ip.To4() == nil { if !n.Addr().Is4() {
return newHelpErrorf("invalid ip definition: can only be ipv4, have %s", rs) return newHelpErrorf("invalid ip definition: can only be ipv4, have %s", rs)
} }
ips = append(ips, n)
ipNet.IP = ip
ips = append(ips, ipNet)
} }
} }
} }
var subnets []*net.IPNet var subnets []netip.Prefix
if *cf.subnets != "" { if *cf.subnets != "" {
for _, rs := range strings.Split(*cf.subnets, ",") { for _, rs := range strings.Split(*cf.subnets, ",") {
rs := strings.Trim(rs, " ") rs := strings.Trim(rs, " ")
if rs != "" { if rs != "" {
_, s, err := net.ParseCIDR(rs) n, err := netip.ParsePrefix(rs)
if err != nil { if err != nil {
return newHelpErrorf("invalid subnet definition: %s", err) return newHelpErrorf("invalid subnet definition: %s", err)
} }
if s.IP.To4() == nil { if !n.Addr().Is4() {
return newHelpErrorf("invalid subnet definition: can only be ipv4, have %s", rs) return newHelpErrorf("invalid subnet definition: can only be ipv4, have %s", rs)
} }
subnets = append(subnets, s) subnets = append(subnets, n)
} }
} }
} }
@ -224,19 +221,17 @@ func ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error
} }
} }
nc := cert.NebulaCertificate{ t := &cert.TBSCertificate{
Details: cert.NebulaCertificateDetails{ Version: cert.Version1,
Name: *cf.name, Name: *cf.name,
Groups: groups, Groups: groups,
Ips: ips, Networks: ips,
Subnets: subnets, UnsafeNetworks: subnets,
NotBefore: time.Now(), NotBefore: time.Now(),
NotAfter: time.Now().Add(*cf.duration), NotAfter: time.Now().Add(*cf.duration),
PublicKey: pub, PublicKey: pub,
IsCA: true, IsCA: true,
Curve: curve, Curve: curve,
},
Pkcs11Backed: isP11,
} }
if !isP11 { if !isP11 {
@ -249,15 +244,16 @@ func ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error
return fmt.Errorf("refusing to overwrite existing CA cert: %s", *cf.outCertPath) return fmt.Errorf("refusing to overwrite existing CA cert: %s", *cf.outCertPath)
} }
var c cert.Certificate
var b []byte var b []byte
if isP11 { if isP11 {
err = nc.SignPkcs11(curve, p11Client) c, err = t.SignPkcs11(nil, curve, p11Client)
if err != nil { if err != nil {
return fmt.Errorf("error while signing with PKCS#11: %w", err) return fmt.Errorf("error while signing with PKCS#11: %w", err)
} }
} else { } else {
err = nc.Sign(curve, rawPriv) c, err = t.Sign(nil, curve, rawPriv)
if err != nil { if err != nil {
return fmt.Errorf("error while signing: %s", err) return fmt.Errorf("error while signing: %s", err)
} }
@ -268,19 +264,16 @@ func ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error
return fmt.Errorf("error while encrypting out-key: %s", err) return fmt.Errorf("error while encrypting out-key: %s", err)
} }
} else { } else {
b = cert.MarshalSigningPrivateKey(curve, rawPriv) b = cert.MarshalSigningPrivateKeyToPEM(curve, rawPriv)
} }
err = os.WriteFile(*cf.outKeyPath, b, 0600) err = os.WriteFile(*cf.outKeyPath, b, 0600)
if err != nil { if err != nil {
return fmt.Errorf("error while writing out-key: %s", err) 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() b, err = c.MarshalPEM()
if err != nil { if err != nil {
return fmt.Errorf("error while marshalling certificate: %s", err) return fmt.Errorf("error while marshalling certificate: %s", err)
} }

View File

@ -109,7 +109,7 @@ func Test_ca(t *testing.T) {
// create temp key file // create temp key file
keyF, err := os.CreateTemp("", "test.key") keyF, err := os.CreateTemp("", "test.key")
assert.Nil(t, err) assert.Nil(t, err)
os.Remove(keyF.Name()) assert.Nil(t, os.Remove(keyF.Name()))
// failed cert write // failed cert write
ob.Reset() ob.Reset()
@ -122,8 +122,8 @@ func Test_ca(t *testing.T) {
// create temp cert file // create temp cert file
crtF, err := os.CreateTemp("", "test.crt") crtF, err := os.CreateTemp("", "test.crt")
assert.Nil(t, err) assert.Nil(t, err)
os.Remove(crtF.Name()) assert.Nil(t, os.Remove(crtF.Name()))
os.Remove(keyF.Name()) assert.Nil(t, os.Remove(keyF.Name()))
// test proper cert with removed empty groups and subnets // test proper cert with removed empty groups and subnets
ob.Reset() ob.Reset()
@ -135,25 +135,26 @@ func Test_ca(t *testing.T) {
// read cert and key files // read cert and key files
rb, _ := os.ReadFile(keyF.Name()) rb, _ := os.ReadFile(keyF.Name())
lKey, b, err := cert.UnmarshalEd25519PrivateKey(rb) lKey, b, c, err := cert.UnmarshalSigningPrivateKeyFromPEM(rb)
assert.Equal(t, cert.Curve_CURVE25519, c)
assert.Len(t, b, 0) assert.Len(t, b, 0)
assert.Nil(t, err) assert.Nil(t, err)
assert.Len(t, lKey, 64) assert.Len(t, lKey, 64)
rb, _ = os.ReadFile(crtF.Name()) rb, _ = os.ReadFile(crtF.Name())
lCrt, b, err := cert.UnmarshalNebulaCertificateFromPEM(rb) lCrt, b, err := cert.UnmarshalCertificateFromPEM(rb)
assert.Len(t, b, 0) assert.Len(t, b, 0)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "test", lCrt.Details.Name) assert.Equal(t, "test", lCrt.Name())
assert.Len(t, lCrt.Details.Ips, 0) assert.Len(t, lCrt.Networks(), 0)
assert.True(t, lCrt.Details.IsCA) assert.True(t, lCrt.IsCA())
assert.Equal(t, []string{"1", "2", "3", "4", "5"}, lCrt.Details.Groups) assert.Equal(t, []string{"1", "2", "3", "4", "5"}, lCrt.Groups())
assert.Len(t, lCrt.Details.Subnets, 0) assert.Len(t, lCrt.UnsafeNetworks(), 0)
assert.Len(t, lCrt.Details.PublicKey, 32) assert.Len(t, lCrt.PublicKey(), 32)
assert.Equal(t, time.Duration(time.Minute*100), lCrt.Details.NotAfter.Sub(lCrt.Details.NotBefore)) assert.Equal(t, time.Duration(time.Minute*100), lCrt.NotAfter().Sub(lCrt.NotBefore()))
assert.Equal(t, "", lCrt.Details.Issuer) assert.Equal(t, "", lCrt.Issuer())
assert.True(t, lCrt.CheckSignature(lCrt.Details.PublicKey)) assert.True(t, lCrt.CheckSignature(lCrt.PublicKey()))
// test encrypted key // test encrypted key
os.Remove(keyF.Name()) os.Remove(keyF.Name())

View File

@ -82,12 +82,12 @@ func keygen(args []string, out io.Writer, errOut io.Writer) error {
return fmt.Errorf("error while getting public key: %w", err) return fmt.Errorf("error while getting public key: %w", err)
} }
} else { } else {
err = os.WriteFile(*cf.outKeyPath, cert.MarshalPrivateKey(curve, rawPriv), 0600) err = os.WriteFile(*cf.outKeyPath, cert.MarshalPrivateKeyToPEM(curve, rawPriv), 0600)
if err != nil { if err != nil {
return fmt.Errorf("error while writing out-key: %s", err) return fmt.Errorf("error while writing out-key: %s", err)
} }
} }
err = os.WriteFile(*cf.outPubPath, cert.MarshalPublicKey(curve, pub), 0600) err = os.WriteFile(*cf.outPubPath, cert.MarshalPublicKeyToPEM(curve, pub), 0600)
if err != nil { if err != nil {
return fmt.Errorf("error while writing out-pub: %s", err) return fmt.Errorf("error while writing out-pub: %s", err)
} }

View File

@ -81,13 +81,15 @@ func Test_keygen(t *testing.T) {
// read cert and key files // read cert and key files
rb, _ := os.ReadFile(keyF.Name()) rb, _ := os.ReadFile(keyF.Name())
lKey, b, err := cert.UnmarshalX25519PrivateKey(rb) lKey, b, curve, err := cert.UnmarshalPrivateKeyFromPEM(rb)
assert.Equal(t, cert.Curve_CURVE25519, curve)
assert.Len(t, b, 0) assert.Len(t, b, 0)
assert.Nil(t, err) assert.Nil(t, err)
assert.Len(t, lKey, 32) assert.Len(t, lKey, 32)
rb, _ = os.ReadFile(pubF.Name()) rb, _ = os.ReadFile(pubF.Name())
lPub, b, err := cert.UnmarshalX25519PublicKey(rb) lPub, b, curve, err := cert.UnmarshalPublicKeyFromPEM(rb)
assert.Equal(t, cert.Curve_CURVE25519, curve)
assert.Len(t, b, 0) assert.Len(t, b, 0)
assert.Nil(t, err) assert.Nil(t, err)
assert.Len(t, lPub, 32) assert.Len(t, lPub, 32)

View File

@ -45,12 +45,12 @@ func printCert(args []string, out io.Writer, errOut io.Writer) error {
return fmt.Errorf("unable to read cert; %s", err) return fmt.Errorf("unable to read cert; %s", err)
} }
var c *cert.NebulaCertificate var c cert.Certificate
var qrBytes []byte var qrBytes []byte
part := 0 part := 0
for { for {
c, rawCert, err = cert.UnmarshalNebulaCertificateFromPEM(rawCert) c, rawCert, err = cert.UnmarshalCertificateFromPEM(rawCert)
if err != nil { if err != nil {
return fmt.Errorf("error while unmarshaling cert: %s", err) return fmt.Errorf("error while unmarshaling cert: %s", err)
} }
@ -66,7 +66,7 @@ func printCert(args []string, out io.Writer, errOut io.Writer) error {
} }
if *pf.outQRPath != "" { if *pf.outQRPath != "" {
b, err := c.MarshalToPEM() b, err := c.MarshalPEM()
if err != nil { if err != nil {
return fmt.Errorf("error while marshalling cert to PEM: %s", err) return fmt.Errorf("error while marshalling cert to PEM: %s", err)
} }

View File

@ -2,6 +2,10 @@ package main
import ( import (
"bytes" "bytes"
"crypto/ed25519"
"crypto/rand"
"encoding/hex"
"net/netip"
"os" "os"
"testing" "testing"
"time" "time"
@ -68,25 +72,22 @@ func Test_printCert(t *testing.T) {
eb.Reset() eb.Reset()
tf.Truncate(0) tf.Truncate(0)
tf.Seek(0, 0) tf.Seek(0, 0)
c := cert.NebulaCertificate{ ca, caKey := NewTestCaCert("test ca", nil, nil, time.Time{}, time.Time{}, nil, nil, nil)
Details: cert.NebulaCertificateDetails{ c, _ := NewTestCert(ca, caKey, "test", time.Time{}, time.Time{}, nil, nil, []string{"hi"})
Name: "test",
Groups: []string{"hi"},
PublicKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2},
},
Signature: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2},
}
p, _ := c.MarshalToPEM() p, _ := c.MarshalPEM()
tf.Write(p) tf.Write(p)
tf.Write(p) tf.Write(p)
tf.Write(p) tf.Write(p)
err = printCert([]string{"-path", tf.Name()}, ob, eb) err = printCert([]string{"-path", tf.Name()}, ob, eb)
fp, _ := c.Fingerprint()
pk := hex.EncodeToString(c.PublicKey())
sig := hex.EncodeToString(c.Signature())
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal( assert.Equal(
t, t,
"NebulaCertificate {\n\tDetails {\n\t\tName: test\n\t\tIps: []\n\t\tSubnets: []\n\t\tGroups: [\n\t\t\t\"hi\"\n\t\t]\n\t\tNot before: 0001-01-01 00:00:00 +0000 UTC\n\t\tNot After: 0001-01-01 00:00:00 +0000 UTC\n\t\tIs CA: false\n\t\tIssuer: \n\t\tPublic key: 0102030405060708090001020304050607080900010203040506070809000102\n\t\tCurve: CURVE25519\n\t}\n\tFingerprint: cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\n\tSignature: 0102030405060708090001020304050607080900010203040506070809000102\n}\nNebulaCertificate {\n\tDetails {\n\t\tName: test\n\t\tIps: []\n\t\tSubnets: []\n\t\tGroups: [\n\t\t\t\"hi\"\n\t\t]\n\t\tNot before: 0001-01-01 00:00:00 +0000 UTC\n\t\tNot After: 0001-01-01 00:00:00 +0000 UTC\n\t\tIs CA: false\n\t\tIssuer: \n\t\tPublic key: 0102030405060708090001020304050607080900010203040506070809000102\n\t\tCurve: CURVE25519\n\t}\n\tFingerprint: cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\n\tSignature: 0102030405060708090001020304050607080900010203040506070809000102\n}\nNebulaCertificate {\n\tDetails {\n\t\tName: test\n\t\tIps: []\n\t\tSubnets: []\n\t\tGroups: [\n\t\t\t\"hi\"\n\t\t]\n\t\tNot before: 0001-01-01 00:00:00 +0000 UTC\n\t\tNot After: 0001-01-01 00:00:00 +0000 UTC\n\t\tIs CA: false\n\t\tIssuer: \n\t\tPublic key: 0102030405060708090001020304050607080900010203040506070809000102\n\t\tCurve: CURVE25519\n\t}\n\tFingerprint: cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\n\tSignature: 0102030405060708090001020304050607080900010203040506070809000102\n}\n", "NebulaCertificate {\n\tDetails {\n\t\tName: test\n\t\tIps: []\n\t\tSubnets: []\n\t\tGroups: [\n\t\t\t\"hi\"\n\t\t]\n\t\tNot before: 0001-01-01 00:00:00 +0000 UTC\n\t\tNot After: 0001-01-01 00:00:00 +0000 UTC\n\t\tIs CA: false\n\t\tIssuer: "+c.Issuer()+"\n\t\tPublic key: "+pk+"\n\t\tCurve: CURVE25519\n\t}\n\tFingerprint: "+fp+"\n\tSignature: "+sig+"\n}\nNebulaCertificate {\n\tDetails {\n\t\tName: test\n\t\tIps: []\n\t\tSubnets: []\n\t\tGroups: [\n\t\t\t\"hi\"\n\t\t]\n\t\tNot before: 0001-01-01 00:00:00 +0000 UTC\n\t\tNot After: 0001-01-01 00:00:00 +0000 UTC\n\t\tIs CA: false\n\t\tIssuer: "+c.Issuer()+"\n\t\tPublic key: "+pk+"\n\t\tCurve: CURVE25519\n\t}\n\tFingerprint: "+fp+"\n\tSignature: "+sig+"\n}\nNebulaCertificate {\n\tDetails {\n\t\tName: test\n\t\tIps: []\n\t\tSubnets: []\n\t\tGroups: [\n\t\t\t\"hi\"\n\t\t]\n\t\tNot before: 0001-01-01 00:00:00 +0000 UTC\n\t\tNot After: 0001-01-01 00:00:00 +0000 UTC\n\t\tIs CA: false\n\t\tIssuer: "+c.Issuer()+"\n\t\tPublic key: "+pk+"\n\t\tCurve: CURVE25519\n\t}\n\tFingerprint: "+fp+"\n\tSignature: "+sig+"\n}\n",
ob.String(), ob.String(),
) )
assert.Equal(t, "", eb.String()) assert.Equal(t, "", eb.String())
@ -96,26 +97,79 @@ func Test_printCert(t *testing.T) {
eb.Reset() eb.Reset()
tf.Truncate(0) tf.Truncate(0)
tf.Seek(0, 0) tf.Seek(0, 0)
c = cert.NebulaCertificate{
Details: cert.NebulaCertificateDetails{
Name: "test",
Groups: []string{"hi"},
PublicKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2},
},
Signature: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2},
}
p, _ = c.MarshalToPEM()
tf.Write(p) tf.Write(p)
tf.Write(p) tf.Write(p)
tf.Write(p) tf.Write(p)
err = printCert([]string{"-json", "-path", tf.Name()}, ob, eb) err = printCert([]string{"-json", "-path", tf.Name()}, ob, eb)
fp, _ = c.Fingerprint()
pk = hex.EncodeToString(c.PublicKey())
sig = hex.EncodeToString(c.Signature())
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal( assert.Equal(
t, t,
"{\"details\":{\"curve\":\"CURVE25519\",\"groups\":[\"hi\"],\"ips\":[],\"isCa\":false,\"issuer\":\"\",\"name\":\"test\",\"notAfter\":\"0001-01-01T00:00:00Z\",\"notBefore\":\"0001-01-01T00:00:00Z\",\"publicKey\":\"0102030405060708090001020304050607080900010203040506070809000102\",\"subnets\":[]},\"fingerprint\":\"cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\",\"signature\":\"0102030405060708090001020304050607080900010203040506070809000102\"}\n{\"details\":{\"curve\":\"CURVE25519\",\"groups\":[\"hi\"],\"ips\":[],\"isCa\":false,\"issuer\":\"\",\"name\":\"test\",\"notAfter\":\"0001-01-01T00:00:00Z\",\"notBefore\":\"0001-01-01T00:00:00Z\",\"publicKey\":\"0102030405060708090001020304050607080900010203040506070809000102\",\"subnets\":[]},\"fingerprint\":\"cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\",\"signature\":\"0102030405060708090001020304050607080900010203040506070809000102\"}\n{\"details\":{\"curve\":\"CURVE25519\",\"groups\":[\"hi\"],\"ips\":[],\"isCa\":false,\"issuer\":\"\",\"name\":\"test\",\"notAfter\":\"0001-01-01T00:00:00Z\",\"notBefore\":\"0001-01-01T00:00:00Z\",\"publicKey\":\"0102030405060708090001020304050607080900010203040506070809000102\",\"subnets\":[]},\"fingerprint\":\"cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\",\"signature\":\"0102030405060708090001020304050607080900010203040506070809000102\"}\n", "{\"details\":{\"curve\":\"CURVE25519\",\"groups\":[\"hi\"],\"ips\":[],\"isCa\":false,\"issuer\":\""+c.Issuer()+"\",\"name\":\"test\",\"notAfter\":\"0001-01-01T00:00:00Z\",\"notBefore\":\"0001-01-01T00:00:00Z\",\"publicKey\":\""+pk+"\",\"subnets\":[]},\"fingerprint\":\""+fp+"\",\"signature\":\""+sig+"\"}\n{\"details\":{\"curve\":\"CURVE25519\",\"groups\":[\"hi\"],\"ips\":[],\"isCa\":false,\"issuer\":\""+c.Issuer()+"\",\"name\":\"test\",\"notAfter\":\"0001-01-01T00:00:00Z\",\"notBefore\":\"0001-01-01T00:00:00Z\",\"publicKey\":\""+pk+"\",\"subnets\":[]},\"fingerprint\":\""+fp+"\",\"signature\":\""+sig+"\"}\n{\"details\":{\"curve\":\"CURVE25519\",\"groups\":[\"hi\"],\"ips\":[],\"isCa\":false,\"issuer\":\""+c.Issuer()+"\",\"name\":\"test\",\"notAfter\":\"0001-01-01T00:00:00Z\",\"notBefore\":\"0001-01-01T00:00:00Z\",\"publicKey\":\""+pk+"\",\"subnets\":[]},\"fingerprint\":\""+fp+"\",\"signature\":\""+sig+"\"}\n",
ob.String(), ob.String(),
) )
assert.Equal(t, "", eb.String()) assert.Equal(t, "", eb.String())
} }
// NewTestCaCert will generate a CA cert
func NewTestCaCert(name string, pubKey, privKey []byte, before, after time.Time, networks, unsafeNetworks []netip.Prefix, groups []string) (cert.Certificate, []byte) {
var err error
if pubKey == nil || privKey == nil {
pubKey, privKey, err = ed25519.GenerateKey(rand.Reader)
if err != nil {
panic(err)
}
}
t := &cert.TBSCertificate{
Version: cert.Version1,
Name: name,
NotBefore: time.Unix(before.Unix(), 0),
NotAfter: time.Unix(after.Unix(), 0),
PublicKey: pubKey,
Networks: networks,
UnsafeNetworks: unsafeNetworks,
Groups: groups,
IsCA: true,
}
c, err := t.Sign(nil, cert.Curve_CURVE25519, privKey)
if err != nil {
panic(err)
}
return c, privKey
}
func NewTestCert(ca cert.Certificate, signerKey []byte, name string, before, after time.Time, networks, unsafeNetworks []netip.Prefix, groups []string) (cert.Certificate, []byte) {
if before.IsZero() {
before = ca.NotBefore()
}
if after.IsZero() {
after = ca.NotAfter()
}
pub, rawPriv := x25519Keypair()
nc := &cert.TBSCertificate{
Version: cert.Version1,
Name: name,
Networks: networks,
UnsafeNetworks: unsafeNetworks,
Groups: groups,
NotBefore: time.Unix(before.Unix(), 0),
NotAfter: time.Unix(after.Unix(), 0),
PublicKey: pub,
IsCA: false,
}
c, err := nc.Sign(ca, ca.Curve(), signerKey)
if err != nil {
panic(err)
}
return c, rawPriv
}

View File

@ -6,7 +6,7 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
"net" "net/netip"
"os" "os"
"strings" "strings"
"time" "time"
@ -80,15 +80,17 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
var curve cert.Curve var curve cert.Curve
var caKey []byte var caKey []byte
if !isP11 { if !isP11 {
var rawCAKey []byte var rawCAKey []byte
rawCAKey, err := os.ReadFile(*sf.caKeyPath) rawCAKey, err := os.ReadFile(*sf.caKeyPath)
if err != nil { if err != nil {
return fmt.Errorf("error while reading 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 // naively attempt to decode the private key as though it is not encrypted
caKey, _, curve, err = cert.UnmarshalSigningPrivateKey(rawCAKey) caKey, _, curve, err = cert.UnmarshalSigningPrivateKeyFromPEM(rawCAKey)
if err == cert.ErrPrivateKeyEncrypted { if err == cert.ErrPrivateKeyEncrypted {
// ask for a passphrase until we get one // ask for a passphrase until we get one
var passphrase []byte var passphrase []byte
@ -124,7 +126,7 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
return fmt.Errorf("error while reading ca-crt: %s", err) return fmt.Errorf("error while reading ca-crt: %s", err)
} }
caCert, _, err := cert.UnmarshalNebulaCertificateFromPEM(rawCACert) caCert, _, err := cert.UnmarshalCertificateFromPEM(rawCACert)
if err != nil { if err != nil {
return fmt.Errorf("error while parsing ca-crt: %s", err) return fmt.Errorf("error while parsing ca-crt: %s", err)
} }
@ -135,30 +137,24 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
} }
} }
issuer, err := caCert.Sha256Sum()
if err != nil {
return fmt.Errorf("error while getting -ca-crt fingerprint: %s", err)
}
if caCert.Expired(time.Now()) { if caCert.Expired(time.Now()) {
return fmt.Errorf("ca certificate is expired") return fmt.Errorf("ca certificate is expired")
} }
// if no duration is given, expire one second before the root expires // if no duration is given, expire one second before the root expires
if *sf.duration <= 0 { if *sf.duration <= 0 {
*sf.duration = time.Until(caCert.Details.NotAfter) - time.Second*1 *sf.duration = time.Until(caCert.NotAfter()) - time.Second*1
} }
ip, ipNet, err := net.ParseCIDR(*sf.ip) network, err := netip.ParsePrefix(*sf.ip)
if err != nil { if err != nil {
return newHelpErrorf("invalid ip definition: %s", err) return newHelpErrorf("invalid ip definition: %s", *sf.ip)
} }
if ip.To4() == nil { if !network.Addr().Is4() {
return newHelpErrorf("invalid ip definition: can only be ipv4, have %s", *sf.ip) return newHelpErrorf("invalid ip definition: can only be ipv4, have %s", *sf.ip)
} }
ipNet.IP = ip
groups := []string{} var groups []string
if *sf.groups != "" { if *sf.groups != "" {
for _, rg := range strings.Split(*sf.groups, ",") { for _, rg := range strings.Split(*sf.groups, ",") {
g := strings.TrimSpace(rg) g := strings.TrimSpace(rg)
@ -168,16 +164,16 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
} }
} }
subnets := []*net.IPNet{} var subnets []netip.Prefix
if *sf.subnets != "" { if *sf.subnets != "" {
for _, rs := range strings.Split(*sf.subnets, ",") { for _, rs := range strings.Split(*sf.subnets, ",") {
rs := strings.Trim(rs, " ") rs := strings.Trim(rs, " ")
if rs != "" { if rs != "" {
_, s, err := net.ParseCIDR(rs) s, err := netip.ParsePrefix(rs)
if err != nil { if err != nil {
return newHelpErrorf("invalid subnet definition: %s", err) return newHelpErrorf("invalid subnet definition: %s", rs)
} }
if s.IP.To4() == nil { if !s.Addr().Is4() {
return newHelpErrorf("invalid subnet definition: can only be ipv4, have %s", rs) return newHelpErrorf("invalid subnet definition: can only be ipv4, have %s", rs)
} }
subnets = append(subnets, s) subnets = append(subnets, s)
@ -205,7 +201,8 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
if err != nil { if err != nil {
return fmt.Errorf("error while reading in-pub: %s", err) return fmt.Errorf("error while reading in-pub: %s", err)
} }
pub, _, pubCurve, err = cert.UnmarshalPublicKey(rawPub)
pub, _, pubCurve, err = cert.UnmarshalPublicKeyFromPEM(rawPub)
if err != nil { if err != nil {
return fmt.Errorf("error while parsing in-pub: %s", err) return fmt.Errorf("error while parsing in-pub: %s", err)
} }
@ -221,36 +218,17 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
pub, rawPriv = newKeypair(curve) pub, rawPriv = newKeypair(curve)
} }
nc := cert.NebulaCertificate{ t := &cert.TBSCertificate{
Details: cert.NebulaCertificateDetails{ Version: cert.Version1,
Name: *sf.name, Name: *sf.name,
Ips: []*net.IPNet{ipNet}, Networks: []netip.Prefix{network},
Groups: groups, Groups: groups,
Subnets: subnets, UnsafeNetworks: subnets,
NotBefore: time.Now(), NotBefore: time.Now(),
NotAfter: time.Now().Add(*sf.duration), NotAfter: time.Now().Add(*sf.duration),
PublicKey: pub, PublicKey: pub,
IsCA: false, IsCA: false,
Issuer: issuer,
Curve: curve, 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 {
return fmt.Errorf("refusing to sign, root certificate constraints violated: %s", err)
} }
if *sf.outKeyPath == "" { if *sf.outKeyPath == "" {
@ -265,18 +243,32 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
return fmt.Errorf("refusing to overwrite existing cert: %s", *sf.outCertPath) return fmt.Errorf("refusing to overwrite existing cert: %s", *sf.outCertPath)
} }
var c cert.Certificate
if p11Client == nil {
c, err = t.Sign(caCert, curve, caKey)
if err != nil {
return fmt.Errorf("error while signing: %w", err)
}
} else {
c, err = t.SignPkcs11(caCert, curve, p11Client)
if err != nil {
return fmt.Errorf("error while signing with PKCS#11: %w", err)
}
}
if !isP11 && *sf.inPubPath == "" { if !isP11 && *sf.inPubPath == "" {
if _, err := os.Stat(*sf.outKeyPath); err == nil { if _, err := os.Stat(*sf.outKeyPath); err == nil {
return fmt.Errorf("refusing to overwrite existing key: %s", *sf.outKeyPath) return fmt.Errorf("refusing to overwrite existing key: %s", *sf.outKeyPath)
} }
err = os.WriteFile(*sf.outKeyPath, cert.MarshalPrivateKey(curve, rawPriv), 0600) err = os.WriteFile(*sf.outKeyPath, cert.MarshalPrivateKeyToPEM(curve, rawPriv), 0600)
if err != nil { if err != nil {
return fmt.Errorf("error while writing out-key: %s", err) return fmt.Errorf("error while writing out-key: %s", err)
} }
} }
b, err := nc.MarshalToPEM() b, err := c.MarshalPEM()
if err != nil { if err != nil {
return fmt.Errorf("error while marshalling certificate: %s", err) return fmt.Errorf("error while marshalling certificate: %s", err)
} }

View File

@ -117,7 +117,7 @@ func Test_signCert(t *testing.T) {
ob.Reset() ob.Reset()
eb.Reset() eb.Reset()
caPub, caPriv, _ := ed25519.GenerateKey(rand.Reader) caPub, caPriv, _ := ed25519.GenerateKey(rand.Reader)
caKeyF.Write(cert.MarshalEd25519PrivateKey(caPriv)) caKeyF.Write(cert.MarshalSigningPrivateKeyToPEM(cert.Curve_CURVE25519, caPriv))
// failed to read cert // failed to read cert
args = []string{"-ca-crt", "./nope", "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m"} args = []string{"-ca-crt", "./nope", "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m"}
@ -138,16 +138,8 @@ func Test_signCert(t *testing.T) {
assert.Empty(t, eb.String()) assert.Empty(t, eb.String())
// write a proper ca cert for later // write a proper ca cert for later
ca := cert.NebulaCertificate{ ca, _ := NewTestCaCert("ca", caPub, caPriv, time.Now(), time.Now().Add(time.Minute*200), nil, nil, nil)
Details: cert.NebulaCertificateDetails{ b, _ := ca.MarshalPEM()
Name: "ca",
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Minute * 200),
PublicKey: caPub,
IsCA: true,
},
}
b, _ := ca.MarshalToPEM()
caCrtF.Write(b) caCrtF.Write(b)
// failed to read pub // failed to read pub
@ -172,13 +164,13 @@ func Test_signCert(t *testing.T) {
ob.Reset() ob.Reset()
eb.Reset() eb.Reset()
inPub, _ := x25519Keypair() inPub, _ := x25519Keypair()
inPubF.Write(cert.MarshalX25519PublicKey(inPub)) inPubF.Write(cert.MarshalPublicKeyToPEM(cert.Curve_CURVE25519, inPub))
// bad ip cidr // bad ip cidr
ob.Reset() ob.Reset()
eb.Reset() eb.Reset()
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "a1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m"} args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "a1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m"}
assertHelpError(t, signCert(args, ob, eb, nopw), "invalid ip definition: invalid CIDR address: a1.1.1.1/24") assertHelpError(t, signCert(args, ob, eb, nopw), "invalid ip definition: a1.1.1.1/24")
assert.Empty(t, ob.String()) assert.Empty(t, ob.String())
assert.Empty(t, eb.String()) assert.Empty(t, eb.String())
@ -193,7 +185,7 @@ func Test_signCert(t *testing.T) {
ob.Reset() ob.Reset()
eb.Reset() eb.Reset()
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m", "-subnets", "a"} args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m", "-subnets", "a"}
assertHelpError(t, signCert(args, ob, eb, nopw), "invalid subnet definition: invalid CIDR address: a") assertHelpError(t, signCert(args, ob, eb, nopw), "invalid subnet definition: a")
assert.Empty(t, ob.String()) assert.Empty(t, ob.String())
assert.Empty(t, eb.String()) assert.Empty(t, eb.String())
@ -209,7 +201,7 @@ func Test_signCert(t *testing.T) {
caKeyF2, err := os.CreateTemp("", "sign-cert-2.key") caKeyF2, err := os.CreateTemp("", "sign-cert-2.key")
assert.Nil(t, err) assert.Nil(t, err)
defer os.Remove(caKeyF2.Name()) defer os.Remove(caKeyF2.Name())
caKeyF2.Write(cert.MarshalEd25519PrivateKey(caPriv2)) caKeyF2.Write(cert.MarshalSigningPrivateKeyToPEM(cert.Curve_CURVE25519, caPriv2))
ob.Reset() ob.Reset()
eb.Reset() eb.Reset()
@ -255,33 +247,34 @@ func Test_signCert(t *testing.T) {
// read cert and key files // read cert and key files
rb, _ := os.ReadFile(keyF.Name()) rb, _ := os.ReadFile(keyF.Name())
lKey, b, err := cert.UnmarshalX25519PrivateKey(rb) lKey, b, curve, err := cert.UnmarshalPrivateKeyFromPEM(rb)
assert.Equal(t, cert.Curve_CURVE25519, curve)
assert.Len(t, b, 0) assert.Len(t, b, 0)
assert.Nil(t, err) assert.Nil(t, err)
assert.Len(t, lKey, 32) assert.Len(t, lKey, 32)
rb, _ = os.ReadFile(crtF.Name()) rb, _ = os.ReadFile(crtF.Name())
lCrt, b, err := cert.UnmarshalNebulaCertificateFromPEM(rb) lCrt, b, err := cert.UnmarshalCertificateFromPEM(rb)
assert.Len(t, b, 0) assert.Len(t, b, 0)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "test", lCrt.Details.Name) assert.Equal(t, "test", lCrt.Name())
assert.Equal(t, "1.1.1.1/24", lCrt.Details.Ips[0].String()) assert.Equal(t, "1.1.1.1/24", lCrt.Networks()[0].String())
assert.Len(t, lCrt.Details.Ips, 1) assert.Len(t, lCrt.Networks(), 1)
assert.False(t, lCrt.Details.IsCA) assert.False(t, lCrt.IsCA())
assert.Equal(t, []string{"1", "2", "3", "4", "5"}, lCrt.Details.Groups) assert.Equal(t, []string{"1", "2", "3", "4", "5"}, lCrt.Groups())
assert.Len(t, lCrt.Details.Subnets, 3) assert.Len(t, lCrt.UnsafeNetworks(), 3)
assert.Len(t, lCrt.Details.PublicKey, 32) assert.Len(t, lCrt.PublicKey(), 32)
assert.Equal(t, time.Duration(time.Minute*100), lCrt.Details.NotAfter.Sub(lCrt.Details.NotBefore)) assert.Equal(t, time.Duration(time.Minute*100), lCrt.NotAfter().Sub(lCrt.NotBefore()))
sns := []string{} sns := []string{}
for _, sn := range lCrt.Details.Subnets { for _, sn := range lCrt.UnsafeNetworks() {
sns = append(sns, sn.String()) sns = append(sns, sn.String())
} }
assert.Equal(t, []string{"10.1.1.1/32", "10.2.2.2/32", "10.5.5.5/32"}, sns) assert.Equal(t, []string{"10.1.1.1/32", "10.2.2.2/32", "10.5.5.5/32"}, sns)
issuer, _ := ca.Sha256Sum() issuer, _ := ca.Fingerprint()
assert.Equal(t, issuer, lCrt.Details.Issuer) assert.Equal(t, issuer, lCrt.Issuer())
assert.True(t, lCrt.CheckSignature(caPub)) assert.True(t, lCrt.CheckSignature(caPub))
@ -297,16 +290,18 @@ func Test_signCert(t *testing.T) {
// read cert file and check pub key matches in-pub // read cert file and check pub key matches in-pub
rb, _ = os.ReadFile(crtF.Name()) rb, _ = os.ReadFile(crtF.Name())
lCrt, b, err = cert.UnmarshalNebulaCertificateFromPEM(rb) lCrt, b, err = cert.UnmarshalCertificateFromPEM(rb)
assert.Len(t, b, 0) assert.Len(t, b, 0)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, lCrt.Details.PublicKey, inPub) assert.Equal(t, lCrt.PublicKey(), inPub)
// test refuse to sign cert with duration beyond root // test refuse to sign cert with duration beyond root
ob.Reset() ob.Reset()
eb.Reset() eb.Reset()
os.Remove(keyF.Name())
os.Remove(crtF.Name())
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", crtF.Name(), "-out-key", keyF.Name(), "-duration", "1000m", "-subnets", "10.1.1.1/32, , 10.2.2.2/32 , , ,, 10.5.5.5/32", "-groups", "1,, 2 , ,,,3,4,5"} args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", crtF.Name(), "-out-key", keyF.Name(), "-duration", "1000m", "-subnets", "10.1.1.1/32, , 10.2.2.2/32 , , ,, 10.5.5.5/32", "-groups", "1,, 2 , ,,,3,4,5"}
assert.EqualError(t, signCert(args, ob, eb, nopw), "refusing to sign, root certificate constraints violated: certificate expires after signing certificate") assert.EqualError(t, signCert(args, ob, eb, nopw), "error while signing: certificate expires after signing certificate")
assert.Empty(t, ob.String()) assert.Empty(t, ob.String())
assert.Empty(t, eb.String()) assert.Empty(t, eb.String())
@ -362,16 +357,8 @@ func Test_signCert(t *testing.T) {
b, _ = cert.EncryptAndMarshalSigningPrivateKey(cert.Curve_CURVE25519, caPriv, passphrase, kdfParams) b, _ = cert.EncryptAndMarshalSigningPrivateKey(cert.Curve_CURVE25519, caPriv, passphrase, kdfParams)
caKeyF.Write(b) caKeyF.Write(b)
ca = cert.NebulaCertificate{ ca, _ = NewTestCaCert("ca", caPub, caPriv, time.Now(), time.Now().Add(time.Minute*200), nil, nil, nil)
Details: cert.NebulaCertificateDetails{ b, _ = ca.MarshalPEM()
Name: "ca",
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Minute * 200),
PublicKey: caPub,
IsCA: true,
},
}
b, _ = ca.MarshalToPEM()
caCrtF.Write(b) caCrtF.Write(b)
// test with the proper password // test with the proper password

View File

@ -46,7 +46,7 @@ func verify(args []string, out io.Writer, errOut io.Writer) error {
caPool := cert.NewCAPool() caPool := cert.NewCAPool()
for { for {
rawCACert, err = caPool.AddCACertificate(rawCACert) rawCACert, err = caPool.AddCAFromPEM(rawCACert)
if err != nil { if err != nil {
return fmt.Errorf("error while adding ca cert to pool: %s", err) return fmt.Errorf("error while adding ca cert to pool: %s", err)
} }
@ -61,13 +61,13 @@ func verify(args []string, out io.Writer, errOut io.Writer) error {
return fmt.Errorf("unable to read crt; %s", err) return fmt.Errorf("unable to read crt; %s", err)
} }
c, _, err := cert.UnmarshalNebulaCertificateFromPEM(rawCert) c, _, err := cert.UnmarshalCertificateFromPEM(rawCert)
if err != nil { if err != nil {
return fmt.Errorf("error while parsing crt: %s", err) return fmt.Errorf("error while parsing crt: %s", err)
} }
good, err := c.Verify(time.Now(), caPool) _, err = caPool.VerifyCertificate(time.Now(), c)
if !good { if err != nil {
return err return err
} }

View File

@ -7,7 +7,6 @@ import (
"testing" "testing"
"time" "time"
"github.com/slackhq/nebula/cert"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"golang.org/x/crypto/ed25519" "golang.org/x/crypto/ed25519"
) )
@ -67,17 +66,8 @@ func Test_verify(t *testing.T) {
// make a ca for later // make a ca for later
caPub, caPriv, _ := ed25519.GenerateKey(rand.Reader) caPub, caPriv, _ := ed25519.GenerateKey(rand.Reader)
ca := cert.NebulaCertificate{ ca, _ := NewTestCaCert("test-ca", caPub, caPriv, time.Now().Add(time.Hour*-1), time.Now().Add(time.Hour*2), nil, nil, nil)
Details: cert.NebulaCertificateDetails{ b, _ := ca.MarshalPEM()
Name: "test-ca",
NotBefore: time.Now().Add(time.Hour * -1),
NotAfter: time.Now().Add(time.Hour * 2),
PublicKey: caPub,
IsCA: true,
},
}
ca.Sign(cert.Curve_CURVE25519, caPriv)
b, _ := ca.MarshalToPEM()
caFile.Truncate(0) caFile.Truncate(0)
caFile.Seek(0, 0) caFile.Seek(0, 0)
caFile.Write(b) caFile.Write(b)
@ -102,22 +92,13 @@ func Test_verify(t *testing.T) {
assert.EqualError(t, err, "error while parsing crt: input did not contain a valid PEM encoded block") assert.EqualError(t, err, "error while parsing crt: input did not contain a valid PEM encoded block")
// unverifiable cert at path // unverifiable cert at path
_, badPriv, _ := ed25519.GenerateKey(rand.Reader) crt, _ := NewTestCert(ca, caPriv, "test-cert", time.Now().Add(time.Hour*-1), time.Now().Add(time.Hour), nil, nil, nil)
certPub, _ := x25519Keypair() // Slightly evil hack to modify the certificate after it was sealed to generate an invalid signature
signer, _ := ca.Sha256Sum() pub := crt.PublicKey()
crt := cert.NebulaCertificate{ for i, _ := range pub {
Details: cert.NebulaCertificateDetails{ pub[i] = 0
Name: "test-cert",
NotBefore: time.Now().Add(time.Hour * -1),
NotAfter: time.Now().Add(time.Hour),
PublicKey: certPub,
IsCA: false,
Issuer: signer,
},
} }
b, _ = crt.MarshalPEM()
crt.Sign(cert.Curve_CURVE25519, badPriv)
b, _ = crt.MarshalToPEM()
certFile.Truncate(0) certFile.Truncate(0)
certFile.Seek(0, 0) certFile.Seek(0, 0)
certFile.Write(b) certFile.Write(b)
@ -128,8 +109,8 @@ func Test_verify(t *testing.T) {
assert.EqualError(t, err, "certificate signature did not match") assert.EqualError(t, err, "certificate signature did not match")
// verified cert at path // verified cert at path
crt.Sign(cert.Curve_CURVE25519, caPriv) crt, _ = NewTestCert(ca, caPriv, "test-cert", time.Now().Add(time.Hour*-1), time.Now().Add(time.Hour), nil, nil, nil)
b, _ = crt.MarshalToPEM() b, _ = crt.MarshalPEM()
certFile.Truncate(0) certFile.Truncate(0)
certFile.Seek(0, 0) certFile.Seek(0, 0)
certFile.Write(b) certFile.Write(b)

View File

@ -415,7 +415,7 @@ func (n *connectionManager) shouldSwapPrimary(current, primary *HostInfo) bool {
} }
certState := n.intf.pki.GetCertState() certState := n.intf.pki.GetCertState()
return bytes.Equal(current.ConnectionState.myCert.Signature, certState.Certificate.Signature) return bytes.Equal(current.ConnectionState.myCert.Signature(), certState.Certificate.Signature())
} }
func (n *connectionManager) swapPrimary(current, primary *HostInfo) { func (n *connectionManager) swapPrimary(current, primary *HostInfo) {
@ -436,8 +436,9 @@ func (n *connectionManager) isInvalidCertificate(now time.Time, hostinfo *HostIn
return false return false
} }
valid, err := remoteCert.VerifyWithCache(now, n.intf.pki.GetCAPool()) caPool := n.intf.pki.GetCAPool()
if valid { err := caPool.VerifyCachedCertificate(now, remoteCert)
if err == nil {
return false return false
} }
@ -446,9 +447,8 @@ func (n *connectionManager) isInvalidCertificate(now time.Time, hostinfo *HostIn
return false return false
} }
fingerprint, _ := remoteCert.Sha256Sum()
hostinfo.logger(n.l).WithError(err). hostinfo.logger(n.l).WithError(err).
WithField("fingerprint", fingerprint). WithField("fingerprint", remoteCert.Fingerprint).
Info("Remote certificate is no longer valid, tearing down the tunnel") Info("Remote certificate is no longer valid, tearing down the tunnel")
return true return true
@ -474,7 +474,7 @@ func (n *connectionManager) sendPunch(hostinfo *HostInfo) {
func (n *connectionManager) tryRehandshake(hostinfo *HostInfo) { func (n *connectionManager) tryRehandshake(hostinfo *HostInfo) {
certState := n.intf.pki.GetCertState() certState := n.intf.pki.GetCertState()
if bytes.Equal(hostinfo.ConnectionState.myCert.Signature, certState.Certificate.Signature) { if bytes.Equal(hostinfo.ConnectionState.myCert.Signature(), certState.Certificate.Signature()) {
return return
} }

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"crypto/ed25519" "crypto/ed25519"
"crypto/rand" "crypto/rand"
"net"
"net/netip" "net/netip"
"testing" "testing"
"time" "time"
@ -47,7 +46,7 @@ func Test_NewConnectionManagerTest(t *testing.T) {
cs := &CertState{ cs := &CertState{
RawCertificate: []byte{}, RawCertificate: []byte{},
PrivateKey: []byte{}, PrivateKey: []byte{},
Certificate: &cert.NebulaCertificate{}, Certificate: &dummyCert{},
RawCertificateNoKey: []byte{}, RawCertificateNoKey: []byte{},
} }
@ -80,7 +79,7 @@ func Test_NewConnectionManagerTest(t *testing.T) {
remoteIndexId: 9901, remoteIndexId: 9901,
} }
hostinfo.ConnectionState = &ConnectionState{ hostinfo.ConnectionState = &ConnectionState{
myCert: &cert.NebulaCertificate{}, myCert: &dummyCert{},
H: &noise.HandshakeState{}, H: &noise.HandshakeState{},
} }
nc.hostMap.unlockedAddHostInfo(hostinfo, ifce) nc.hostMap.unlockedAddHostInfo(hostinfo, ifce)
@ -130,7 +129,7 @@ func Test_NewConnectionManagerTest2(t *testing.T) {
cs := &CertState{ cs := &CertState{
RawCertificate: []byte{}, RawCertificate: []byte{},
PrivateKey: []byte{}, PrivateKey: []byte{},
Certificate: &cert.NebulaCertificate{}, Certificate: &dummyCert{},
RawCertificateNoKey: []byte{}, RawCertificateNoKey: []byte{},
} }
@ -163,7 +162,7 @@ func Test_NewConnectionManagerTest2(t *testing.T) {
remoteIndexId: 9901, remoteIndexId: 9901,
} }
hostinfo.ConnectionState = &ConnectionState{ hostinfo.ConnectionState = &ConnectionState{
myCert: &cert.NebulaCertificate{}, myCert: &dummyCert{},
H: &noise.HandshakeState{}, H: &noise.HandshakeState{},
} }
nc.hostMap.unlockedAddHostInfo(hostinfo, ifce) nc.hostMap.unlockedAddHostInfo(hostinfo, ifce)
@ -206,10 +205,7 @@ func Test_NewConnectionManagerTest2(t *testing.T) {
func Test_NewConnectionManagerTest_DisconnectInvalid(t *testing.T) { func Test_NewConnectionManagerTest_DisconnectInvalid(t *testing.T) {
now := time.Now() now := time.Now()
l := test.NewLogger() l := test.NewLogger()
ipNet := net.IPNet{
IP: net.IPv4(172, 1, 1, 2),
Mask: net.IPMask{255, 255, 255, 0},
}
vpncidr := netip.MustParsePrefix("172.1.1.1/24") vpncidr := netip.MustParsePrefix("172.1.1.1/24")
localrange := netip.MustParsePrefix("10.1.1.1/24") localrange := netip.MustParsePrefix("10.1.1.1/24")
vpnIp := netip.MustParseAddr("172.1.1.2") vpnIp := netip.MustParseAddr("172.1.1.2")
@ -219,41 +215,38 @@ func Test_NewConnectionManagerTest_DisconnectInvalid(t *testing.T) {
// Generate keys for CA and peer's cert. // Generate keys for CA and peer's cert.
pubCA, privCA, _ := ed25519.GenerateKey(rand.Reader) pubCA, privCA, _ := ed25519.GenerateKey(rand.Reader)
caCert := cert.NebulaCertificate{ tbs := &cert.TBSCertificate{
Details: cert.NebulaCertificateDetails{ Version: 1,
Name: "ca", Name: "ca",
IsCA: true,
NotBefore: now, NotBefore: now,
NotAfter: now.Add(1 * time.Hour), NotAfter: now.Add(1 * time.Hour),
IsCA: true,
PublicKey: pubCA, PublicKey: pubCA,
},
} }
assert.NoError(t, caCert.Sign(cert.Curve_CURVE25519, privCA)) caCert, err := tbs.Sign(nil, cert.Curve_CURVE25519, privCA)
ncp := &cert.NebulaCAPool{ assert.NoError(t, err)
CAs: cert.NewCAPool().CAs, ncp := cert.NewCAPool()
} assert.NoError(t, ncp.AddCA(caCert))
ncp.CAs["ca"] = &caCert
pubCrt, _, _ := ed25519.GenerateKey(rand.Reader) pubCrt, _, _ := ed25519.GenerateKey(rand.Reader)
peerCert := cert.NebulaCertificate{ tbs = &cert.TBSCertificate{
Details: cert.NebulaCertificateDetails{ Version: 1,
Name: "host", Name: "host",
Ips: []*net.IPNet{&ipNet}, Networks: []netip.Prefix{vpncidr},
Subnets: []*net.IPNet{},
NotBefore: now, NotBefore: now,
NotAfter: now.Add(60 * time.Second), NotAfter: now.Add(60 * time.Second),
PublicKey: pubCrt, PublicKey: pubCrt,
IsCA: false,
Issuer: "ca",
},
} }
assert.NoError(t, peerCert.Sign(cert.Curve_CURVE25519, privCA)) peerCert, err := tbs.Sign(caCert, cert.Curve_CURVE25519, privCA)
assert.NoError(t, err)
cachedPeerCert, err := ncp.VerifyCertificate(now.Add(time.Second), peerCert)
cs := &CertState{ cs := &CertState{
RawCertificate: []byte{}, RawCertificate: []byte{},
PrivateKey: []byte{}, PrivateKey: []byte{},
Certificate: &cert.NebulaCertificate{}, Certificate: &dummyCert{},
RawCertificateNoKey: []byte{}, RawCertificateNoKey: []byte{},
} }
@ -282,8 +275,8 @@ func Test_NewConnectionManagerTest_DisconnectInvalid(t *testing.T) {
hostinfo := &HostInfo{ hostinfo := &HostInfo{
vpnIp: vpnIp, vpnIp: vpnIp,
ConnectionState: &ConnectionState{ ConnectionState: &ConnectionState{
myCert: &cert.NebulaCertificate{}, myCert: &dummyCert{},
peerCert: &peerCert, peerCert: cachedPeerCert,
H: &noise.HandshakeState{}, H: &noise.HandshakeState{},
}, },
} }
@ -303,3 +296,114 @@ func Test_NewConnectionManagerTest_DisconnectInvalid(t *testing.T) {
invalid = nc.isInvalidCertificate(nextTick, hostinfo) invalid = nc.isInvalidCertificate(nextTick, hostinfo)
assert.True(t, invalid) assert.True(t, invalid)
} }
type dummyCert struct {
version cert.Version
curve cert.Curve
groups []string
isCa bool
issuer string
name string
networks []netip.Prefix
notAfter time.Time
notBefore time.Time
publicKey []byte
signature []byte
unsafeNetworks []netip.Prefix
}
func (d *dummyCert) Version() cert.Version {
return d.version
}
func (d *dummyCert) Curve() cert.Curve {
return d.curve
}
func (d *dummyCert) Groups() []string {
return d.groups
}
func (d *dummyCert) IsCA() bool {
return d.isCa
}
func (d *dummyCert) Issuer() string {
return d.issuer
}
func (d *dummyCert) Name() string {
return d.name
}
func (d *dummyCert) Networks() []netip.Prefix {
return d.networks
}
func (d *dummyCert) NotAfter() time.Time {
return d.notAfter
}
func (d *dummyCert) NotBefore() time.Time {
return d.notBefore
}
func (d *dummyCert) PublicKey() []byte {
return d.publicKey
}
func (d *dummyCert) Signature() []byte {
return d.signature
}
func (d *dummyCert) UnsafeNetworks() []netip.Prefix {
return d.unsafeNetworks
}
func (d *dummyCert) MarshalForHandshakes() ([]byte, error) {
return nil, nil
}
func (d *dummyCert) Sign(curve cert.Curve, key []byte) error {
return nil
}
func (d *dummyCert) CheckSignature(key []byte) bool {
return true
}
func (d *dummyCert) Expired(t time.Time) bool {
return false
}
func (d *dummyCert) CheckRootConstraints(signer cert.Certificate) error {
return nil
}
func (d *dummyCert) VerifyPrivateKey(curve cert.Curve, key []byte) error {
return nil
}
func (d *dummyCert) String() string {
return ""
}
func (d *dummyCert) Marshal() ([]byte, error) {
return nil, nil
}
func (d *dummyCert) MarshalPEM() ([]byte, error) {
return nil, nil
}
func (d *dummyCert) Fingerprint() (string, error) {
return "", nil
}
func (d *dummyCert) MarshalJSON() ([]byte, error) {
return nil, nil
}
func (d *dummyCert) Copy() cert.Certificate {
return d
}

View File

@ -18,8 +18,8 @@ type ConnectionState struct {
eKey *NebulaCipherState eKey *NebulaCipherState
dKey *NebulaCipherState dKey *NebulaCipherState
H *noise.HandshakeState H *noise.HandshakeState
myCert *cert.NebulaCertificate myCert cert.Certificate
peerCert *cert.NebulaCertificate peerCert *cert.CachedCertificate
initiator bool initiator bool
messageCounter atomic.Uint64 messageCounter atomic.Uint64
window *Bits window *Bits
@ -28,17 +28,17 @@ type ConnectionState struct {
func NewConnectionState(l *logrus.Logger, cipher string, certState *CertState, initiator bool, pattern noise.HandshakePattern, psk []byte, pskStage int) *ConnectionState { func NewConnectionState(l *logrus.Logger, cipher string, certState *CertState, initiator bool, pattern noise.HandshakePattern, psk []byte, pskStage int) *ConnectionState {
var dhFunc noise.DHFunc var dhFunc noise.DHFunc
switch certState.Certificate.Details.Curve { switch certState.Certificate.Curve() {
case cert.Curve_CURVE25519: case cert.Curve_CURVE25519:
dhFunc = noise.DH25519 dhFunc = noise.DH25519
case cert.Curve_P256: case cert.Curve_P256:
if certState.Certificate.Pkcs11Backed { if certState.pkcs11Backed {
dhFunc = noiseutil.DHP256PKCS11 dhFunc = noiseutil.DHP256PKCS11
} else { } else {
dhFunc = noiseutil.DHP256 dhFunc = noiseutil.DHP256
} }
default: default:
l.Errorf("invalid curve: %s", certState.Certificate.Details.Curve) l.Errorf("invalid curve: %s", certState.Certificate.Curve())
return nil return nil
} }

View File

@ -41,7 +41,7 @@ type ControlHostInfo struct {
LocalIndex uint32 `json:"localIndex"` LocalIndex uint32 `json:"localIndex"`
RemoteIndex uint32 `json:"remoteIndex"` RemoteIndex uint32 `json:"remoteIndex"`
RemoteAddrs []netip.AddrPort `json:"remoteAddrs"` RemoteAddrs []netip.AddrPort `json:"remoteAddrs"`
Cert *cert.NebulaCertificate `json:"cert"` Cert cert.Certificate `json:"cert"`
MessageCounter uint64 `json:"messageCounter"` MessageCounter uint64 `json:"messageCounter"`
CurrentRemote netip.AddrPort `json:"currentRemote"` CurrentRemote netip.AddrPort `json:"currentRemote"`
CurrentRelaysToMe []netip.Addr `json:"currentRelaysToMe"` CurrentRelaysToMe []netip.Addr `json:"currentRelaysToMe"`
@ -130,15 +130,15 @@ func (c *Control) ListHostmapIndexes(pendingMap bool) []ControlHostInfo {
} }
// GetCertByVpnIp returns the authenticated certificate of the given vpn IP, or nil if not found // GetCertByVpnIp returns the authenticated certificate of the given vpn IP, or nil if not found
func (c *Control) GetCertByVpnIp(vpnIp netip.Addr) *cert.NebulaCertificate { func (c *Control) GetCertByVpnIp(vpnIp netip.Addr) cert.Certificate {
if c.f.myVpnNet.Addr() == vpnIp { if c.f.myVpnNet.Addr() == vpnIp {
return c.f.pki.GetCertState().Certificate return c.f.pki.GetCertState().Certificate.Copy()
} }
hi := c.f.hostMap.QueryVpnIp(vpnIp) hi := c.f.hostMap.QueryVpnIp(vpnIp)
if hi == nil { if hi == nil {
return nil return nil
} }
return hi.GetCert() return hi.GetCert().Certificate.Copy()
} }
// CreateTunnel creates a new tunnel to the given vpn ip. // CreateTunnel creates a new tunnel to the given vpn ip.
@ -290,7 +290,7 @@ func copyHostInfo(h *HostInfo, preferredRanges []netip.Prefix) ControlHostInfo {
} }
if c := h.GetCert(); c != nil { if c := h.GetCert(); c != nil {
chi.Cert = c.Copy() chi.Cert = c.Certificate.Copy()
} }
return chi return chi

View File

@ -5,7 +5,6 @@ import (
"net/netip" "net/netip"
"reflect" "reflect"
"testing" "testing"
"time"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/slackhq/nebula/cert" "github.com/slackhq/nebula/cert"
@ -14,6 +13,9 @@ import (
) )
func TestControl_GetHostInfoByVpnIp(t *testing.T) { func TestControl_GetHostInfoByVpnIp(t *testing.T) {
//TODO: with multiple certificate versions we have a problem with this test
// Some certs versions have different characteristics and each version implements their own Copy() func
// which means this is not a good place to test for exposing memory
l := test.NewLogger() l := test.NewLogger()
// Special care must be taken to re-use all objects provided to the hostmap and certificate in the expectedInfo object // Special care must be taken to re-use all objects provided to the hostmap and certificate in the expectedInfo object
// To properly ensure we are not exposing core memory to the caller // To properly ensure we are not exposing core memory to the caller
@ -33,22 +35,6 @@ func TestControl_GetHostInfoByVpnIp(t *testing.T) {
Mask: net.IPMask{255, 255, 255, 0}, Mask: net.IPMask{255, 255, 255, 0},
} }
crt := &cert.NebulaCertificate{
Details: cert.NebulaCertificateDetails{
Name: "test",
Ips: []*net.IPNet{&ipNet},
Subnets: []*net.IPNet{},
Groups: []string{"default-group"},
NotBefore: time.Unix(1, 0),
NotAfter: time.Unix(2, 0),
PublicKey: []byte{5, 6, 7, 8},
IsCA: false,
Issuer: "the-issuer",
InvertedGroups: map[string]struct{}{"default-group": {}},
},
Signature: []byte{1, 2, 1, 2, 1, 3},
}
remotes := NewRemoteList(nil) remotes := NewRemoteList(nil)
remotes.unlockedPrependV4(netip.IPv4Unspecified(), NewIp4AndPortFromNetIP(remote1.Addr(), remote1.Port())) remotes.unlockedPrependV4(netip.IPv4Unspecified(), NewIp4AndPortFromNetIP(remote1.Addr(), remote1.Port()))
remotes.unlockedPrependV6(netip.IPv4Unspecified(), NewIp6AndPortFromNetIP(remote2.Addr(), remote2.Port())) remotes.unlockedPrependV6(netip.IPv4Unspecified(), NewIp6AndPortFromNetIP(remote2.Addr(), remote2.Port()))
@ -56,11 +42,12 @@ func TestControl_GetHostInfoByVpnIp(t *testing.T) {
vpnIp, ok := netip.AddrFromSlice(ipNet.IP) vpnIp, ok := netip.AddrFromSlice(ipNet.IP)
assert.True(t, ok) assert.True(t, ok)
crt := &dummyCert{}
hm.unlockedAddHostInfo(&HostInfo{ hm.unlockedAddHostInfo(&HostInfo{
remote: remote1, remote: remote1,
remotes: remotes, remotes: remotes,
ConnectionState: &ConnectionState{ ConnectionState: &ConnectionState{
peerCert: crt, peerCert: &cert.CachedCertificate{Certificate: crt},
}, },
remoteIndexId: 200, remoteIndexId: 200,
localIndexId: 201, localIndexId: 201,
@ -115,8 +102,7 @@ func TestControl_GetHostInfoByVpnIp(t *testing.T) {
// Make sure we don't have any unexpected fields // Make sure we don't have any unexpected fields
assertFields(t, []string{"VpnIp", "LocalIndex", "RemoteIndex", "RemoteAddrs", "Cert", "MessageCounter", "CurrentRemote", "CurrentRelaysToMe", "CurrentRelaysThroughMe"}, thi) assertFields(t, []string{"VpnIp", "LocalIndex", "RemoteIndex", "RemoteAddrs", "Cert", "MessageCounter", "CurrentRemote", "CurrentRelaysToMe", "CurrentRelaysThroughMe"}, thi)
assert.EqualValues(t, &expectedInfo, thi) assert.EqualValues(t, &expectedInfo, thi)
//TODO: netip.Addr reuses global memory for zone identifiers which breaks our "no reused memory check" here test.AssertDeepCopyEqual(t, &expectedInfo, thi)
//test.AssertDeepCopyEqual(t, &expectedInfo, thi)
// Make sure we don't panic if the host info doesn't have a cert yet // Make sure we don't panic if the host info doesn't have a cert yet
assert.NotPanics(t, func() { assert.NotPanics(t, func() {

View File

@ -153,7 +153,7 @@ func (c *Control) GetHostmap() *HostMap {
return c.f.hostMap return c.f.hostMap
} }
func (c *Control) GetCert() *cert.NebulaCertificate { func (c *Control) GetCert() cert.Certificate {
return c.f.pki.GetCertState().Certificate return c.f.pki.GetCertState().Certificate
} }

View File

@ -57,9 +57,11 @@ func (d *dnsRecords) QueryCert(data string) string {
return "" return ""
} }
cert := q.Details b, err := q.Certificate.MarshalJSON()
c := fmt.Sprintf("\"Name: %s\" \"Ips: %s\" \"Subnets %s\" \"Groups %s\" \"NotBefore %s\" \"NotAfter %s\" \"PublicKey %x\" \"IsCA %t\" \"Issuer %s\"", cert.Name, cert.Ips, cert.Subnets, cert.Groups, cert.NotBefore, cert.NotAfter, cert.PublicKey, cert.IsCA, cert.Issuer) if err != nil {
return c return ""
}
return string(b)
} }
func (d *dnsRecords) Add(host, data string) { func (d *dnsRecords) Add(host, data string) {

View File

@ -6,6 +6,7 @@ package e2e
import ( import (
"fmt" "fmt"
"net/netip" "net/netip"
"slices"
"testing" "testing"
"time" "time"
@ -538,9 +539,9 @@ func TestRehandshakingRelays(t *testing.T) {
// When I update the certificate for the relay, both me and them will have 2 host infos for the relay, // When I update the certificate for the relay, both me and them will have 2 host infos for the relay,
// and the main host infos will not have any relay state to handle the me<->relay<->them tunnel. // and the main host infos will not have any relay state to handle the me<->relay<->them tunnel.
r.Log("Renew relay certificate and spin until me and them sees it") r.Log("Renew relay certificate and spin until me and them sees it")
_, _, myNextPrivKey, myNextPEM := NewTestCert(ca, caKey, "relay", time.Now(), time.Now().Add(5*time.Minute), relayVpnIpNet, nil, []string{"new group"}) _, _, myNextPrivKey, myNextPEM := NewTestCert(ca, caKey, "relay", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{relayVpnIpNet}, nil, []string{"new group"})
caB, err := ca.MarshalToPEM() caB, err := ca.MarshalPEM()
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -558,7 +559,7 @@ func TestRehandshakingRelays(t *testing.T) {
r.Log("Assert the tunnel works between myVpnIpNet and relayVpnIpNet") r.Log("Assert the tunnel works between myVpnIpNet and relayVpnIpNet")
assertTunnel(t, myVpnIpNet.Addr(), relayVpnIpNet.Addr(), myControl, relayControl, r) assertTunnel(t, myVpnIpNet.Addr(), relayVpnIpNet.Addr(), myControl, relayControl, r)
c := myControl.GetHostInfoByVpnIp(relayVpnIpNet.Addr(), false) c := myControl.GetHostInfoByVpnIp(relayVpnIpNet.Addr(), false)
if len(c.Cert.Details.Groups) != 0 { if len(c.Cert.Groups()) != 0 {
// We have a new certificate now // We have a new certificate now
r.Log("Certificate between my and relay is updated!") r.Log("Certificate between my and relay is updated!")
break break
@ -571,7 +572,7 @@ func TestRehandshakingRelays(t *testing.T) {
r.Log("Assert the tunnel works between theirVpnIpNet and relayVpnIpNet") r.Log("Assert the tunnel works between theirVpnIpNet and relayVpnIpNet")
assertTunnel(t, theirVpnIpNet.Addr(), relayVpnIpNet.Addr(), theirControl, relayControl, r) assertTunnel(t, theirVpnIpNet.Addr(), relayVpnIpNet.Addr(), theirControl, relayControl, r)
c := theirControl.GetHostInfoByVpnIp(relayVpnIpNet.Addr(), false) c := theirControl.GetHostInfoByVpnIp(relayVpnIpNet.Addr(), false)
if len(c.Cert.Details.Groups) != 0 { if len(c.Cert.Groups()) != 0 {
// We have a new certificate now // We have a new certificate now
r.Log("Certificate between their and relay is updated!") r.Log("Certificate between their and relay is updated!")
break break
@ -642,9 +643,9 @@ func TestRehandshakingRelaysPrimary(t *testing.T) {
// When I update the certificate for the relay, both me and them will have 2 host infos for the relay, // When I update the certificate for the relay, both me and them will have 2 host infos for the relay,
// and the main host infos will not have any relay state to handle the me<->relay<->them tunnel. // and the main host infos will not have any relay state to handle the me<->relay<->them tunnel.
r.Log("Renew relay certificate and spin until me and them sees it") r.Log("Renew relay certificate and spin until me and them sees it")
_, _, myNextPrivKey, myNextPEM := NewTestCert(ca, caKey, "relay", time.Now(), time.Now().Add(5*time.Minute), relayVpnIpNet, nil, []string{"new group"}) _, _, myNextPrivKey, myNextPEM := NewTestCert(ca, caKey, "relay", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{relayVpnIpNet}, nil, []string{"new group"})
caB, err := ca.MarshalToPEM() caB, err := ca.MarshalPEM()
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -662,7 +663,7 @@ func TestRehandshakingRelaysPrimary(t *testing.T) {
r.Log("Assert the tunnel works between myVpnIpNet and relayVpnIpNet") r.Log("Assert the tunnel works between myVpnIpNet and relayVpnIpNet")
assertTunnel(t, myVpnIpNet.Addr(), relayVpnIpNet.Addr(), myControl, relayControl, r) assertTunnel(t, myVpnIpNet.Addr(), relayVpnIpNet.Addr(), myControl, relayControl, r)
c := myControl.GetHostInfoByVpnIp(relayVpnIpNet.Addr(), false) c := myControl.GetHostInfoByVpnIp(relayVpnIpNet.Addr(), false)
if len(c.Cert.Details.Groups) != 0 { if len(c.Cert.Groups()) != 0 {
// We have a new certificate now // We have a new certificate now
r.Log("Certificate between my and relay is updated!") r.Log("Certificate between my and relay is updated!")
break break
@ -675,7 +676,7 @@ func TestRehandshakingRelaysPrimary(t *testing.T) {
r.Log("Assert the tunnel works between theirVpnIpNet and relayVpnIpNet") r.Log("Assert the tunnel works between theirVpnIpNet and relayVpnIpNet")
assertTunnel(t, theirVpnIpNet.Addr(), relayVpnIpNet.Addr(), theirControl, relayControl, r) assertTunnel(t, theirVpnIpNet.Addr(), relayVpnIpNet.Addr(), theirControl, relayControl, r)
c := theirControl.GetHostInfoByVpnIp(relayVpnIpNet.Addr(), false) c := theirControl.GetHostInfoByVpnIp(relayVpnIpNet.Addr(), false)
if len(c.Cert.Details.Groups) != 0 { if len(c.Cert.Groups()) != 0 {
// We have a new certificate now // We have a new certificate now
r.Log("Certificate between their and relay is updated!") r.Log("Certificate between their and relay is updated!")
break break
@ -737,9 +738,9 @@ func TestRehandshaking(t *testing.T) {
r.RenderHostmaps("Starting hostmaps", myControl, theirControl) r.RenderHostmaps("Starting hostmaps", myControl, theirControl)
r.Log("Renew my certificate and spin until their sees it") r.Log("Renew my certificate and spin until their sees it")
_, _, myNextPrivKey, myNextPEM := NewTestCert(ca, caKey, "me", time.Now(), time.Now().Add(5*time.Minute), myVpnIpNet, nil, []string{"new group"}) _, _, myNextPrivKey, myNextPEM := NewTestCert(ca, caKey, "me", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{myVpnIpNet}, nil, []string{"new group"})
caB, err := ca.MarshalToPEM() caB, err := ca.MarshalPEM()
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -756,7 +757,7 @@ func TestRehandshaking(t *testing.T) {
for { for {
assertTunnel(t, myVpnIpNet.Addr(), theirVpnIpNet.Addr(), myControl, theirControl, r) assertTunnel(t, myVpnIpNet.Addr(), theirVpnIpNet.Addr(), myControl, theirControl, r)
c := theirControl.GetHostInfoByVpnIp(myVpnIpNet.Addr(), false) c := theirControl.GetHostInfoByVpnIp(myVpnIpNet.Addr(), false)
if len(c.Cert.Details.Groups) != 0 { if len(c.Cert.Groups()) != 0 {
// We have a new certificate now // We have a new certificate now
break break
} }
@ -764,6 +765,7 @@ func TestRehandshaking(t *testing.T) {
time.Sleep(time.Second) time.Sleep(time.Second)
} }
r.Log("Got the new cert")
// Flip their firewall to only allowing the new group to catch the tunnels reverting incorrectly // Flip their firewall to only allowing the new group to catch the tunnels reverting incorrectly
rc, err = yaml.Marshal(theirConfig.Settings) rc, err = yaml.Marshal(theirConfig.Settings)
assert.NoError(t, err) assert.NoError(t, err)
@ -794,7 +796,7 @@ func TestRehandshaking(t *testing.T) {
// Make sure the correct tunnel won // Make sure the correct tunnel won
c := theirControl.GetHostInfoByVpnIp(myVpnIpNet.Addr(), false) c := theirControl.GetHostInfoByVpnIp(myVpnIpNet.Addr(), false)
assert.Contains(t, c.Cert.Details.Groups, "new group") assert.Contains(t, c.Cert.Groups(), "new group")
// We should only have a single tunnel now on both sides // We should only have a single tunnel now on both sides
assert.Len(t, myFinalHostmapHosts, 1) assert.Len(t, myFinalHostmapHosts, 1)
@ -837,9 +839,9 @@ func TestRehandshakingLoser(t *testing.T) {
r.RenderHostmaps("Starting hostmaps", myControl, theirControl) r.RenderHostmaps("Starting hostmaps", myControl, theirControl)
r.Log("Renew their certificate and spin until mine sees it") r.Log("Renew their certificate and spin until mine sees it")
_, _, theirNextPrivKey, theirNextPEM := NewTestCert(ca, caKey, "them", time.Now(), time.Now().Add(5*time.Minute), theirVpnIpNet, nil, []string{"their new group"}) _, _, theirNextPrivKey, theirNextPEM := NewTestCert(ca, caKey, "them", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{theirVpnIpNet}, nil, []string{"their new group"})
caB, err := ca.MarshalToPEM() caB, err := ca.MarshalPEM()
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -857,8 +859,7 @@ func TestRehandshakingLoser(t *testing.T) {
assertTunnel(t, myVpnIpNet.Addr(), theirVpnIpNet.Addr(), myControl, theirControl, r) assertTunnel(t, myVpnIpNet.Addr(), theirVpnIpNet.Addr(), myControl, theirControl, r)
theirCertInMe := myControl.GetHostInfoByVpnIp(theirVpnIpNet.Addr(), false) theirCertInMe := myControl.GetHostInfoByVpnIp(theirVpnIpNet.Addr(), false)
_, theirNewGroup := theirCertInMe.Cert.Details.InvertedGroups["their new group"] if slices.Contains(theirCertInMe.Cert.Groups(), "their new group") {
if theirNewGroup {
break break
} }
@ -895,7 +896,7 @@ func TestRehandshakingLoser(t *testing.T) {
// Make sure the correct tunnel won // Make sure the correct tunnel won
theirCertInMe := myControl.GetHostInfoByVpnIp(theirVpnIpNet.Addr(), false) theirCertInMe := myControl.GetHostInfoByVpnIp(theirVpnIpNet.Addr(), false)
assert.Contains(t, theirCertInMe.Cert.Details.Groups, "their new group") assert.Contains(t, theirCertInMe.Cert.Groups(), "their new group")
// We should only have a single tunnel now on both sides // We should only have a single tunnel now on both sides
assert.Len(t, myFinalHostmapHosts, 1) assert.Len(t, myFinalHostmapHosts, 1)

View File

@ -3,7 +3,6 @@ package e2e
import ( import (
"crypto/rand" "crypto/rand"
"io" "io"
"net"
"net/netip" "net/netip"
"time" "time"
@ -13,7 +12,7 @@ import (
) )
// NewTestCaCert will generate a CA cert // NewTestCaCert will generate a CA cert
func NewTestCaCert(before, after time.Time, ips, subnets []netip.Prefix, groups []string) (*cert.NebulaCertificate, []byte, []byte, []byte) { func NewTestCaCert(before, after time.Time, networks, unsafeNetworks []netip.Prefix, groups []string) (cert.Certificate, []byte, []byte, []byte) {
pub, priv, err := ed25519.GenerateKey(rand.Reader) pub, priv, err := ed25519.GenerateKey(rand.Reader)
if before.IsZero() { if before.IsZero() {
before = time.Now().Add(time.Second * -60).Round(time.Second) before = time.Now().Add(time.Second * -60).Round(time.Second)
@ -22,56 +21,34 @@ func NewTestCaCert(before, after time.Time, ips, subnets []netip.Prefix, groups
after = time.Now().Add(time.Second * 60).Round(time.Second) after = time.Now().Add(time.Second * 60).Round(time.Second)
} }
nc := &cert.NebulaCertificate{ t := &cert.TBSCertificate{
Details: cert.NebulaCertificateDetails{ Version: cert.Version1,
Name: "test ca", Name: "test ca",
NotBefore: time.Unix(before.Unix(), 0), NotBefore: time.Unix(before.Unix(), 0),
NotAfter: time.Unix(after.Unix(), 0), NotAfter: time.Unix(after.Unix(), 0),
PublicKey: pub, PublicKey: pub,
Networks: networks,
UnsafeNetworks: unsafeNetworks,
Groups: groups,
IsCA: true, IsCA: true,
InvertedGroups: make(map[string]struct{}),
},
} }
if len(ips) > 0 { c, err := t.Sign(nil, cert.Curve_CURVE25519, priv)
nc.Details.Ips = make([]*net.IPNet, len(ips))
for i, ip := range ips {
nc.Details.Ips[i] = &net.IPNet{IP: ip.Addr().AsSlice(), Mask: net.CIDRMask(ip.Bits(), ip.Addr().BitLen())}
}
}
if len(subnets) > 0 {
nc.Details.Subnets = make([]*net.IPNet, len(subnets))
for i, ip := range subnets {
nc.Details.Ips[i] = &net.IPNet{IP: ip.Addr().AsSlice(), Mask: net.CIDRMask(ip.Bits(), ip.Addr().BitLen())}
}
}
if len(groups) > 0 {
nc.Details.Groups = groups
}
err = nc.Sign(cert.Curve_CURVE25519, priv)
if err != nil { if err != nil {
panic(err) panic(err)
} }
pem, err := nc.MarshalToPEM() pem, err := c.MarshalPEM()
if err != nil { if err != nil {
panic(err) panic(err)
} }
return nc, pub, priv, pem return c, pub, priv, pem
} }
// NewTestCert will generate a signed certificate with the provided details. // NewTestCert will generate a signed certificate with the provided details.
// Expiry times are defaulted if you do not pass them in // Expiry times are defaulted if you do not pass them in
func NewTestCert(ca *cert.NebulaCertificate, key []byte, name string, before, after time.Time, ip netip.Prefix, subnets []netip.Prefix, groups []string) (*cert.NebulaCertificate, []byte, []byte, []byte) { func NewTestCert(ca cert.Certificate, key []byte, name string, before, after time.Time, networks, unsafeNetworks []netip.Prefix, groups []string) (cert.Certificate, []byte, []byte, []byte) {
issuer, err := ca.Sha256Sum()
if err != nil {
panic(err)
}
if before.IsZero() { if before.IsZero() {
before = time.Now().Add(time.Second * -60).Round(time.Second) before = time.Now().Add(time.Second * -60).Round(time.Second)
} }
@ -81,33 +58,29 @@ func NewTestCert(ca *cert.NebulaCertificate, key []byte, name string, before, af
} }
pub, rawPriv := x25519Keypair() pub, rawPriv := x25519Keypair()
ipb := ip.Addr().AsSlice() nc := &cert.TBSCertificate{
nc := &cert.NebulaCertificate{ Version: cert.Version1,
Details: cert.NebulaCertificateDetails{
Name: name, Name: name,
Ips: []*net.IPNet{{IP: ipb[:], Mask: net.CIDRMask(ip.Bits(), ip.Addr().BitLen())}}, Networks: networks,
//Subnets: subnets, UnsafeNetworks: unsafeNetworks,
Groups: groups, Groups: groups,
NotBefore: time.Unix(before.Unix(), 0), NotBefore: time.Unix(before.Unix(), 0),
NotAfter: time.Unix(after.Unix(), 0), NotAfter: time.Unix(after.Unix(), 0),
PublicKey: pub, PublicKey: pub,
IsCA: false, IsCA: false,
Issuer: issuer,
InvertedGroups: make(map[string]struct{}),
},
} }
err = nc.Sign(ca.Details.Curve, key) c, err := nc.Sign(ca, ca.Curve(), key)
if err != nil { if err != nil {
panic(err) panic(err)
} }
pem, err := nc.MarshalToPEM() pem, err := c.MarshalPEM()
if err != nil { if err != nil {
panic(err) panic(err)
} }
return nc, pub, cert.MarshalX25519PrivateKey(rawPriv), pem return c, pub, cert.MarshalPrivateKeyToPEM(cert.Curve_CURVE25519, rawPriv), pem
} }
func x25519Keypair() ([]byte, []byte) { func x25519Keypair() ([]byte, []byte) {

View File

@ -26,7 +26,7 @@ import (
type m map[string]interface{} type m map[string]interface{}
// newSimpleServer creates a nebula instance with many assumptions // newSimpleServer creates a nebula instance with many assumptions
func newSimpleServer(caCrt *cert.NebulaCertificate, caKey []byte, name string, sVpnIpNet string, overrides m) (*nebula.Control, netip.Prefix, netip.AddrPort, *config.C) { func newSimpleServer(caCrt cert.Certificate, caKey []byte, name string, sVpnIpNet string, overrides m) (*nebula.Control, netip.Prefix, netip.AddrPort, *config.C) {
l := NewTestLogger() l := NewTestLogger()
vpnIpNet, err := netip.ParsePrefix(sVpnIpNet) vpnIpNet, err := netip.ParsePrefix(sVpnIpNet)
@ -44,9 +44,9 @@ func newSimpleServer(caCrt *cert.NebulaCertificate, caKey []byte, name string, s
budpIp[13] -= 128 budpIp[13] -= 128
udpAddr = netip.AddrPortFrom(netip.AddrFrom16(budpIp), 4242) udpAddr = netip.AddrPortFrom(netip.AddrFrom16(budpIp), 4242)
} }
_, _, myPrivKey, myPEM := NewTestCert(caCrt, caKey, name, time.Now(), time.Now().Add(5*time.Minute), vpnIpNet, nil, []string{}) _, _, myPrivKey, myPEM := NewTestCert(caCrt, caKey, name, time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{vpnIpNet}, nil, []string{})
caB, err := caCrt.MarshalToPEM() caB, err := caCrt.MarshalPEM()
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -58,8 +58,8 @@ func renderHostmap(c *nebula.Control) (string, []*edge) {
var lines []string var lines []string
var globalLines []*edge var globalLines []*edge
clusterName := strings.Trim(c.GetCert().Details.Name, " ") clusterName := strings.Trim(c.GetCert().Name(), " ")
clusterVpnIp := c.GetCert().Details.Ips[0].IP clusterVpnIp := c.GetCert().Networks()[0].Addr()
r := fmt.Sprintf("\tsubgraph %s[\"%s (%s)\"]\n", clusterName, clusterName, clusterVpnIp) r := fmt.Sprintf("\tsubgraph %s[\"%s (%s)\"]\n", clusterName, clusterName, clusterVpnIp)
hm := c.GetHostmap() hm := c.GetHostmap()
@ -102,7 +102,7 @@ func renderHostmap(c *nebula.Control) (string, []*edge) {
hi, ok := hm.Indexes[idx] hi, ok := hm.Indexes[idx]
if ok { if ok {
r += fmt.Sprintf("\t\t\t%v.%v[\"%v (%v)\"]\n", clusterName, idx, idx, hi.GetVpnIp()) r += fmt.Sprintf("\t\t\t%v.%v[\"%v (%v)\"]\n", clusterName, idx, idx, hi.GetVpnIp())
remoteClusterName := strings.Trim(hi.GetCert().Details.Name, " ") remoteClusterName := strings.Trim(hi.GetCert().Certificate.Name(), " ")
globalLines = append(globalLines, &edge{from: fmt.Sprintf("%v.%v", clusterName, idx), to: fmt.Sprintf("%v.%v", remoteClusterName, hi.GetRemoteIndex())}) globalLines = append(globalLines, &edge{from: fmt.Sprintf("%v.%v", clusterName, idx), to: fmt.Sprintf("%v.%v", remoteClusterName, hi.GetRemoteIndex())})
_ = hi _ = hi
} }

View File

@ -54,7 +54,7 @@ type Firewall struct {
// Used to ensure we don't emit local packets for ips we don't own // Used to ensure we don't emit local packets for ips we don't own
localIps *bart.Table[struct{}] localIps *bart.Table[struct{}]
assignedCIDR netip.Prefix assignedCIDR netip.Prefix
hasSubnets bool hasUnsafeNetworks bool
rules string rules string
rulesVersion uint16 rulesVersion uint16
@ -126,7 +126,7 @@ type firewallLocalCIDR struct {
} }
// NewFirewall creates a new Firewall object. A TimerWheel is created for you from the provided timeouts. // NewFirewall creates a new Firewall object. A TimerWheel is created for you from the provided timeouts.
func NewFirewall(l *logrus.Logger, tcpTimeout, UDPTimeout, defaultTimeout time.Duration, c *cert.NebulaCertificate) *Firewall { func NewFirewall(l *logrus.Logger, tcpTimeout, UDPTimeout, defaultTimeout time.Duration, c cert.Certificate) *Firewall {
//TODO: error on 0 duration //TODO: error on 0 duration
var min, max time.Duration var min, max time.Duration
@ -147,11 +147,8 @@ func NewFirewall(l *logrus.Logger, tcpTimeout, UDPTimeout, defaultTimeout time.D
localIps := new(bart.Table[struct{}]) localIps := new(bart.Table[struct{}])
var assignedCIDR netip.Prefix var assignedCIDR netip.Prefix
var assignedSet bool var assignedSet bool
for _, ip := range c.Details.Ips { for _, network := range c.Networks() {
//TODO: IPV6-WORK the unmap is a bit unfortunate nprefix := netip.PrefixFrom(network.Addr(), network.Addr().BitLen())
nip, _ := netip.AddrFromSlice(ip.IP)
nip = nip.Unmap()
nprefix := netip.PrefixFrom(nip, nip.BitLen())
localIps.Insert(nprefix, struct{}{}) localIps.Insert(nprefix, struct{}{})
if !assignedSet { if !assignedSet {
@ -161,11 +158,10 @@ func NewFirewall(l *logrus.Logger, tcpTimeout, UDPTimeout, defaultTimeout time.D
} }
} }
for _, n := range c.Details.Subnets { hasUnsafeNetworks := false
nip, _ := netip.AddrFromSlice(n.IP) for _, n := range c.UnsafeNetworks() {
ones, _ := n.Mask.Size() localIps.Insert(n, struct{}{})
nip = nip.Unmap() hasUnsafeNetworks = true
localIps.Insert(netip.PrefixFrom(nip, ones), struct{}{})
} }
return &Firewall{ return &Firewall{
@ -180,7 +176,7 @@ func NewFirewall(l *logrus.Logger, tcpTimeout, UDPTimeout, defaultTimeout time.D
DefaultTimeout: defaultTimeout, DefaultTimeout: defaultTimeout,
localIps: localIps, localIps: localIps,
assignedCIDR: assignedCIDR, assignedCIDR: assignedCIDR,
hasSubnets: len(c.Details.Subnets) > 0, hasUnsafeNetworks: hasUnsafeNetworks,
l: l, l: l,
incomingMetrics: firewallMetrics{ incomingMetrics: firewallMetrics{
@ -196,7 +192,7 @@ func NewFirewall(l *logrus.Logger, tcpTimeout, UDPTimeout, defaultTimeout time.D
} }
} }
func NewFirewallFromConfig(l *logrus.Logger, nc *cert.NebulaCertificate, c *config.C) (*Firewall, error) { func NewFirewallFromConfig(l *logrus.Logger, nc cert.Certificate, c *config.C) (*Firewall, error) {
fw := NewFirewall( fw := NewFirewall(
l, l,
c.GetDuration("firewall.conntrack.tcp_timeout", time.Minute*12), c.GetDuration("firewall.conntrack.tcp_timeout", time.Minute*12),
@ -421,7 +417,7 @@ var ErrNoMatchingRule = errors.New("no matching rule in firewall table")
// Drop returns an error if the packet should be dropped, explaining why. It // Drop returns an error if the packet should be dropped, explaining why. It
// returns nil if the packet should not be dropped. // returns nil if the packet should not be dropped.
func (f *Firewall) Drop(fp firewall.Packet, incoming bool, h *HostInfo, caPool *cert.NebulaCAPool, localCache firewall.ConntrackCache) error { func (f *Firewall) Drop(fp firewall.Packet, incoming bool, h *HostInfo, caPool *cert.CAPool, localCache firewall.ConntrackCache) error {
// Check if we spoke to this tuple, if we did then allow this packet // Check if we spoke to this tuple, if we did then allow this packet
if f.inConns(fp, h, caPool, localCache) { if f.inConns(fp, h, caPool, localCache) {
return nil return nil
@ -492,7 +488,7 @@ func (f *Firewall) EmitStats() {
metrics.GetOrRegisterGauge("firewall.rules.hash", nil).Update(int64(f.GetRuleHashFNV())) metrics.GetOrRegisterGauge("firewall.rules.hash", nil).Update(int64(f.GetRuleHashFNV()))
} }
func (f *Firewall) inConns(fp firewall.Packet, h *HostInfo, caPool *cert.NebulaCAPool, localCache firewall.ConntrackCache) bool { func (f *Firewall) inConns(fp firewall.Packet, h *HostInfo, caPool *cert.CAPool, localCache firewall.ConntrackCache) bool {
if localCache != nil { if localCache != nil {
if _, ok := localCache[fp]; ok { if _, ok := localCache[fp]; ok {
return true return true
@ -619,7 +615,7 @@ func (f *Firewall) evict(p firewall.Packet) {
delete(conntrack.Conns, p) delete(conntrack.Conns, p)
} }
func (ft *FirewallTable) match(p firewall.Packet, incoming bool, c *cert.NebulaCertificate, caPool *cert.NebulaCAPool) bool { func (ft *FirewallTable) match(p firewall.Packet, incoming bool, c *cert.CachedCertificate, caPool *cert.CAPool) bool {
if ft.AnyProto.match(p, incoming, c, caPool) { if ft.AnyProto.match(p, incoming, c, caPool) {
return true return true
} }
@ -663,7 +659,7 @@ func (fp firewallPort) addRule(f *Firewall, startPort int32, endPort int32, grou
return nil return nil
} }
func (fp firewallPort) match(p firewall.Packet, incoming bool, c *cert.NebulaCertificate, caPool *cert.NebulaCAPool) bool { func (fp firewallPort) match(p firewall.Packet, incoming bool, c *cert.CachedCertificate, caPool *cert.CAPool) bool {
// We don't have any allowed ports, bail // We don't have any allowed ports, bail
if fp == nil { if fp == nil {
return false return false
@ -726,7 +722,7 @@ func (fc *FirewallCA) addRule(f *Firewall, groups []string, host string, ip, loc
return nil return nil
} }
func (fc *FirewallCA) match(p firewall.Packet, c *cert.NebulaCertificate, caPool *cert.NebulaCAPool) bool { func (fc *FirewallCA) match(p firewall.Packet, c *cert.CachedCertificate, caPool *cert.CAPool) bool {
if fc == nil { if fc == nil {
return false return false
} }
@ -735,18 +731,18 @@ func (fc *FirewallCA) match(p firewall.Packet, c *cert.NebulaCertificate, caPool
return true return true
} }
if t, ok := fc.CAShas[c.Details.Issuer]; ok { if t, ok := fc.CAShas[c.Certificate.Issuer()]; ok {
if t.match(p, c) { if t.match(p, c) {
return true return true
} }
} }
s, err := caPool.GetCAForCert(c) s, err := caPool.GetCAForCert(c.Certificate)
if err != nil { if err != nil {
return false return false
} }
return fc.CANames[s.Details.Name].match(p, c) return fc.CANames[s.Certificate.Name()].match(p, c)
} }
func (fr *FirewallRule) addRule(f *Firewall, groups []string, host string, ip, localCIDR netip.Prefix) error { func (fr *FirewallRule) addRule(f *Firewall, groups []string, host string, ip, localCIDR netip.Prefix) error {
@ -826,7 +822,7 @@ func (fr *FirewallRule) isAny(groups []string, host string, ip netip.Prefix) boo
return false return false
} }
func (fr *FirewallRule) match(p firewall.Packet, c *cert.NebulaCertificate) bool { func (fr *FirewallRule) match(p firewall.Packet, c *cert.CachedCertificate) bool {
if fr == nil { if fr == nil {
return false return false
} }
@ -841,7 +837,7 @@ func (fr *FirewallRule) match(p firewall.Packet, c *cert.NebulaCertificate) bool
found := false found := false
for _, g := range sg.Groups { for _, g := range sg.Groups {
if _, ok := c.Details.InvertedGroups[g]; !ok { if _, ok := c.InvertedGroups[g]; !ok {
found = false found = false
break break
} }
@ -855,7 +851,7 @@ func (fr *FirewallRule) match(p firewall.Packet, c *cert.NebulaCertificate) bool
} }
if fr.Hosts != nil { if fr.Hosts != nil {
if flc, ok := fr.Hosts[c.Details.Name]; ok { if flc, ok := fr.Hosts[c.Certificate.Name()]; ok {
if flc.match(p, c) { if flc.match(p, c) {
return true return true
} }
@ -876,7 +872,7 @@ func (fr *FirewallRule) match(p firewall.Packet, c *cert.NebulaCertificate) bool
func (flc *firewallLocalCIDR) addRule(f *Firewall, localIp netip.Prefix) error { func (flc *firewallLocalCIDR) addRule(f *Firewall, localIp netip.Prefix) error {
if !localIp.IsValid() { if !localIp.IsValid() {
if !f.hasSubnets || f.defaultLocalCIDRAny { if !f.hasUnsafeNetworks || f.defaultLocalCIDRAny {
flc.Any = true flc.Any = true
return nil return nil
} }
@ -890,7 +886,7 @@ func (flc *firewallLocalCIDR) addRule(f *Firewall, localIp netip.Prefix) error {
return nil return nil
} }
func (flc *firewallLocalCIDR) match(p firewall.Packet, c *cert.NebulaCertificate) bool { func (flc *firewallLocalCIDR) match(p firewall.Packet, c *cert.CachedCertificate) bool {
if flc == nil { if flc == nil {
return false return false
} }

View File

@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"errors" "errors"
"math" "math"
"net"
"net/netip" "net/netip"
"testing" "testing"
"time" "time"
@ -18,7 +17,7 @@ import (
func TestNewFirewall(t *testing.T) { func TestNewFirewall(t *testing.T) {
l := test.NewLogger() l := test.NewLogger()
c := &cert.NebulaCertificate{} c := &dummyCert{}
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c) fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c)
conntrack := fw.Conntrack conntrack := fw.Conntrack
assert.NotNil(t, conntrack) assert.NotNil(t, conntrack)
@ -60,7 +59,7 @@ func TestFirewall_AddRule(t *testing.T) {
ob := &bytes.Buffer{} ob := &bytes.Buffer{}
l.SetOutput(ob) l.SetOutput(ob)
c := &cert.NebulaCertificate{} c := &dummyCert{}
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c) fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c)
assert.NotNil(t, fw.InRules) assert.NotNil(t, fw.InRules)
assert.NotNil(t, fw.OutRules) assert.NotNil(t, fw.OutRules)
@ -137,23 +136,18 @@ func TestFirewall_Drop(t *testing.T) {
Fragment: false, Fragment: false,
} }
ipNet := net.IPNet{ c := dummyCert{
IP: net.IPv4(1, 2, 3, 4), name: "host1",
Mask: net.IPMask{255, 255, 255, 0}, networks: []netip.Prefix{netip.MustParsePrefix("1.2.3.4/24")},
} groups: []string{"default-group"},
issuer: "signer-shasum",
c := cert.NebulaCertificate{
Details: cert.NebulaCertificateDetails{
Name: "host1",
Ips: []*net.IPNet{&ipNet},
Groups: []string{"default-group"},
InvertedGroups: map[string]struct{}{"default-group": {}},
Issuer: "signer-shasum",
},
} }
h := HostInfo{ h := HostInfo{
ConnectionState: &ConnectionState{ ConnectionState: &ConnectionState{
peerCert: &c, peerCert: &cert.CachedCertificate{
Certificate: &c,
InvertedGroups: map[string]struct{}{"default-group": {}},
},
}, },
vpnIp: netip.MustParseAddr("1.2.3.4"), vpnIp: netip.MustParseAddr("1.2.3.4"),
} }
@ -190,14 +184,14 @@ func TestFirewall_Drop(t *testing.T) {
assert.NoError(t, fw.Drop(p, true, &h, cp, nil)) assert.NoError(t, fw.Drop(p, true, &h, cp, nil))
// ensure ca name doesn't get in the way of group checks // ensure ca name doesn't get in the way of group checks
cp.CAs["signer-shasum"] = &cert.NebulaCertificate{Details: cert.NebulaCertificateDetails{Name: "ca-good"}} cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c) fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
assert.Nil(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", netip.Prefix{}, netip.Prefix{}, "ca-good", "")) assert.Nil(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", netip.Prefix{}, netip.Prefix{}, "ca-good", ""))
assert.Nil(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", netip.Prefix{}, netip.Prefix{}, "ca-good-bad", "")) assert.Nil(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", netip.Prefix{}, netip.Prefix{}, "ca-good-bad", ""))
assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule) assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
// test caName doesn't drop on match // test caName doesn't drop on match
cp.CAs["signer-shasum"] = &cert.NebulaCertificate{Details: cert.NebulaCertificateDetails{Name: "ca-good"}} cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c) fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
assert.Nil(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", netip.Prefix{}, netip.Prefix{}, "ca-good-bad", "")) assert.Nil(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", netip.Prefix{}, netip.Prefix{}, "ca-good-bad", ""))
assert.Nil(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", netip.Prefix{}, netip.Prefix{}, "ca-good", "")) assert.Nil(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", netip.Prefix{}, netip.Prefix{}, "ca-good", ""))
@ -217,7 +211,9 @@ func BenchmarkFirewallTable_match(b *testing.B) {
b.Run("fail on proto", func(b *testing.B) { b.Run("fail on proto", func(b *testing.B) {
// This benchmark is showing us the cost of failing to match the protocol // This benchmark is showing us the cost of failing to match the protocol
c := &cert.NebulaCertificate{} c := &cert.CachedCertificate{
Certificate: &dummyCert{},
}
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoUDP}, true, c, cp)) assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoUDP}, true, c, cp))
} }
@ -225,14 +221,18 @@ func BenchmarkFirewallTable_match(b *testing.B) {
b.Run("pass proto, fail on port", func(b *testing.B) { b.Run("pass proto, fail on port", func(b *testing.B) {
// This benchmark is showing us the cost of matching a specific protocol but failing to match the port // This benchmark is showing us the cost of matching a specific protocol but failing to match the port
c := &cert.NebulaCertificate{} c := &cert.CachedCertificate{
Certificate: &dummyCert{},
}
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 1}, true, c, cp)) assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 1}, true, c, cp))
} }
}) })
b.Run("pass proto, port, fail on local CIDR", func(b *testing.B) { b.Run("pass proto, port, fail on local CIDR", func(b *testing.B) {
c := &cert.NebulaCertificate{} c := &cert.CachedCertificate{
Certificate: &dummyCert{},
}
ip := netip.MustParsePrefix("9.254.254.254/32") ip := netip.MustParsePrefix("9.254.254.254/32")
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalIP: ip.Addr()}, true, c, cp)) assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalIP: ip.Addr()}, true, c, cp))
@ -240,13 +240,12 @@ func BenchmarkFirewallTable_match(b *testing.B) {
}) })
b.Run("pass proto, port, any local CIDR, fail all group, name, and cidr", func(b *testing.B) { b.Run("pass proto, port, any local CIDR, fail all group, name, and cidr", func(b *testing.B) {
_, ip, _ := net.ParseCIDR("9.254.254.254/32") c := &cert.CachedCertificate{
c := &cert.NebulaCertificate{ Certificate: &dummyCert{
Details: cert.NebulaCertificateDetails{ name: "nope",
InvertedGroups: map[string]struct{}{"nope": {}}, networks: []netip.Prefix{netip.MustParsePrefix("9.254.254.245/32")},
Name: "nope",
Ips: []*net.IPNet{ip},
}, },
InvertedGroups: map[string]struct{}{"nope": {}},
} }
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 10}, true, c, cp)) assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 10}, true, c, cp))
@ -254,13 +253,12 @@ func BenchmarkFirewallTable_match(b *testing.B) {
}) })
b.Run("pass proto, port, specific local CIDR, fail all group, name, and cidr", func(b *testing.B) { b.Run("pass proto, port, specific local CIDR, fail all group, name, and cidr", func(b *testing.B) {
_, ip, _ := net.ParseCIDR("9.254.254.254/32") c := &cert.CachedCertificate{
c := &cert.NebulaCertificate{ Certificate: &dummyCert{
Details: cert.NebulaCertificateDetails{ name: "nope",
InvertedGroups: map[string]struct{}{"nope": {}}, networks: []netip.Prefix{netip.MustParsePrefix("9.254.254.245/32")},
Name: "nope",
Ips: []*net.IPNet{ip},
}, },
InvertedGroups: map[string]struct{}{"nope": {}},
} }
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalIP: pfix.Addr()}, true, c, cp)) assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalIP: pfix.Addr()}, true, c, cp))
@ -268,11 +266,11 @@ func BenchmarkFirewallTable_match(b *testing.B) {
}) })
b.Run("pass on group on any local cidr", func(b *testing.B) { b.Run("pass on group on any local cidr", func(b *testing.B) {
c := &cert.NebulaCertificate{ c := &cert.CachedCertificate{
Details: cert.NebulaCertificateDetails{ Certificate: &dummyCert{
InvertedGroups: map[string]struct{}{"good-group": {}}, name: "nope",
Name: "nope",
}, },
InvertedGroups: map[string]struct{}{"good-group": {}},
} }
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
assert.True(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 10}, true, c, cp)) assert.True(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 10}, true, c, cp))
@ -280,11 +278,11 @@ func BenchmarkFirewallTable_match(b *testing.B) {
}) })
b.Run("pass on group on specific local cidr", func(b *testing.B) { b.Run("pass on group on specific local cidr", func(b *testing.B) {
c := &cert.NebulaCertificate{ c := &cert.CachedCertificate{
Details: cert.NebulaCertificateDetails{ Certificate: &dummyCert{
InvertedGroups: map[string]struct{}{"good-group": {}}, name: "nope",
Name: "nope",
}, },
InvertedGroups: map[string]struct{}{"good-group": {}},
} }
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
assert.True(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalIP: pfix.Addr()}, true, c, cp)) assert.True(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalIP: pfix.Addr()}, true, c, cp))
@ -292,70 +290,16 @@ func BenchmarkFirewallTable_match(b *testing.B) {
}) })
b.Run("pass on name", func(b *testing.B) { b.Run("pass on name", func(b *testing.B) {
c := &cert.NebulaCertificate{ c := &cert.CachedCertificate{
Details: cert.NebulaCertificateDetails{ Certificate: &dummyCert{
InvertedGroups: map[string]struct{}{"nope": {}}, name: "good-host",
Name: "good-host",
}, },
InvertedGroups: map[string]struct{}{"nope": {}},
} }
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 10}, true, c, cp) ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 10}, true, c, cp)
} }
}) })
//
//b.Run("pass on ip", func(b *testing.B) {
// ip := iputil.Ip2VpnIp(net.IPv4(172, 1, 1, 1))
// c := &cert.NebulaCertificate{
// Details: cert.NebulaCertificateDetails{
// InvertedGroups: map[string]struct{}{"nope": {}},
// Name: "good-host",
// },
// }
// for n := 0; n < b.N; n++ {
// ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 10, RemoteIP: ip}, true, c, cp)
// }
//})
//
//b.Run("pass on local ip", func(b *testing.B) {
// ip := iputil.Ip2VpnIp(net.IPv4(172, 1, 1, 1))
// c := &cert.NebulaCertificate{
// Details: cert.NebulaCertificateDetails{
// InvertedGroups: map[string]struct{}{"nope": {}},
// Name: "good-host",
// },
// }
// for n := 0; n < b.N; n++ {
// ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 10, LocalIP: ip}, true, c, cp)
// }
//})
//
//_ = ft.TCP.addRule(0, 0, []string{"good-group"}, "good-host", n, n, "", "")
//
//b.Run("pass on ip with any port", func(b *testing.B) {
// ip := iputil.Ip2VpnIp(net.IPv4(172, 1, 1, 1))
// c := &cert.NebulaCertificate{
// Details: cert.NebulaCertificateDetails{
// InvertedGroups: map[string]struct{}{"nope": {}},
// Name: "good-host",
// },
// }
// for n := 0; n < b.N; n++ {
// ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, RemoteIP: ip}, true, c, cp)
// }
//})
//
//b.Run("pass on local ip with any port", func(b *testing.B) {
// ip := iputil.Ip2VpnIp(net.IPv4(172, 1, 1, 1))
// c := &cert.NebulaCertificate{
// Details: cert.NebulaCertificateDetails{
// InvertedGroups: map[string]struct{}{"nope": {}},
// Name: "good-host",
// },
// }
// for n := 0; n < b.N; n++ {
// ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalIP: ip}, true, c, cp)
// }
//})
} }
func TestFirewall_Drop2(t *testing.T) { func TestFirewall_Drop2(t *testing.T) {
@ -372,41 +316,38 @@ func TestFirewall_Drop2(t *testing.T) {
Fragment: false, Fragment: false,
} }
ipNet := net.IPNet{ network := netip.MustParsePrefix("1.2.3.4/24")
IP: net.IPv4(1, 2, 3, 4),
Mask: net.IPMask{255, 255, 255, 0},
}
c := cert.NebulaCertificate{ c := cert.CachedCertificate{
Details: cert.NebulaCertificateDetails{ Certificate: &dummyCert{
Name: "host1", name: "host1",
Ips: []*net.IPNet{&ipNet}, networks: []netip.Prefix{network},
InvertedGroups: map[string]struct{}{"default-group": {}, "test-group": {}},
}, },
InvertedGroups: map[string]struct{}{"default-group": {}, "test-group": {}},
} }
h := HostInfo{ h := HostInfo{
ConnectionState: &ConnectionState{ ConnectionState: &ConnectionState{
peerCert: &c, peerCert: &c,
}, },
vpnIp: netip.MustParseAddr(ipNet.IP.String()), vpnIp: network.Addr(),
} }
h.CreateRemoteCIDR(&c) h.CreateRemoteCIDR(c.Certificate)
c1 := cert.NebulaCertificate{ c1 := cert.CachedCertificate{
Details: cert.NebulaCertificateDetails{ Certificate: &dummyCert{
Name: "host1", name: "host1",
Ips: []*net.IPNet{&ipNet}, networks: []netip.Prefix{network},
InvertedGroups: map[string]struct{}{"default-group": {}, "test-group-not": {}},
}, },
InvertedGroups: map[string]struct{}{"default-group": {}, "test-group-not": {}},
} }
h1 := HostInfo{ h1 := HostInfo{
ConnectionState: &ConnectionState{ ConnectionState: &ConnectionState{
peerCert: &c1, peerCert: &c1,
}, },
} }
h1.CreateRemoteCIDR(&c1) h1.CreateRemoteCIDR(c1.Certificate)
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, &c) fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
assert.Nil(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group", "test-group"}, "", netip.Prefix{}, netip.Prefix{}, "", "")) assert.Nil(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group", "test-group"}, "", netip.Prefix{}, netip.Prefix{}, "", ""))
cp := cert.NewCAPool() cp := cert.NewCAPool()
@ -431,64 +372,60 @@ func TestFirewall_Drop3(t *testing.T) {
Fragment: false, Fragment: false,
} }
ipNet := net.IPNet{ network := netip.MustParsePrefix("1.2.3.4/24")
IP: net.IPv4(1, 2, 3, 4), c := cert.CachedCertificate{
Mask: net.IPMask{255, 255, 255, 0}, Certificate: &dummyCert{
} name: "host-owner",
networks: []netip.Prefix{network},
c := cert.NebulaCertificate{
Details: cert.NebulaCertificateDetails{
Name: "host-owner",
Ips: []*net.IPNet{&ipNet},
}, },
} }
c1 := cert.NebulaCertificate{ c1 := cert.CachedCertificate{
Details: cert.NebulaCertificateDetails{ Certificate: &dummyCert{
Name: "host1", name: "host1",
Ips: []*net.IPNet{&ipNet}, networks: []netip.Prefix{network},
Issuer: "signer-sha-bad", issuer: "signer-sha-bad",
}, },
} }
h1 := HostInfo{ h1 := HostInfo{
ConnectionState: &ConnectionState{ ConnectionState: &ConnectionState{
peerCert: &c1, peerCert: &c1,
}, },
vpnIp: netip.MustParseAddr(ipNet.IP.String()), vpnIp: network.Addr(),
} }
h1.CreateRemoteCIDR(&c1) h1.CreateRemoteCIDR(c1.Certificate)
c2 := cert.NebulaCertificate{ c2 := cert.CachedCertificate{
Details: cert.NebulaCertificateDetails{ Certificate: &dummyCert{
Name: "host2", name: "host2",
Ips: []*net.IPNet{&ipNet}, networks: []netip.Prefix{network},
Issuer: "signer-sha", issuer: "signer-sha",
}, },
} }
h2 := HostInfo{ h2 := HostInfo{
ConnectionState: &ConnectionState{ ConnectionState: &ConnectionState{
peerCert: &c2, peerCert: &c2,
}, },
vpnIp: netip.MustParseAddr(ipNet.IP.String()), vpnIp: network.Addr(),
} }
h2.CreateRemoteCIDR(&c2) h2.CreateRemoteCIDR(c2.Certificate)
c3 := cert.NebulaCertificate{ c3 := cert.CachedCertificate{
Details: cert.NebulaCertificateDetails{ Certificate: &dummyCert{
Name: "host3", name: "host3",
Ips: []*net.IPNet{&ipNet}, networks: []netip.Prefix{network},
Issuer: "signer-sha-bad", issuer: "signer-sha-bad",
}, },
} }
h3 := HostInfo{ h3 := HostInfo{
ConnectionState: &ConnectionState{ ConnectionState: &ConnectionState{
peerCert: &c3, peerCert: &c3,
}, },
vpnIp: netip.MustParseAddr(ipNet.IP.String()), vpnIp: network.Addr(),
} }
h3.CreateRemoteCIDR(&c3) h3.CreateRemoteCIDR(c3.Certificate)
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, &c) fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
assert.Nil(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "host1", netip.Prefix{}, netip.Prefix{}, "", "")) assert.Nil(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "host1", netip.Prefix{}, netip.Prefix{}, "", ""))
assert.Nil(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", netip.Prefix{}, netip.Prefix{}, "", "signer-sha")) assert.Nil(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", netip.Prefix{}, netip.Prefix{}, "", "signer-sha"))
cp := cert.NewCAPool() cp := cert.NewCAPool()
@ -516,30 +453,26 @@ func TestFirewall_DropConntrackReload(t *testing.T) {
Protocol: firewall.ProtoUDP, Protocol: firewall.ProtoUDP,
Fragment: false, Fragment: false,
} }
network := netip.MustParsePrefix("1.2.3.4/24")
ipNet := net.IPNet{ c := cert.CachedCertificate{
IP: net.IPv4(1, 2, 3, 4), Certificate: &dummyCert{
Mask: net.IPMask{255, 255, 255, 0}, name: "host1",
} networks: []netip.Prefix{network},
groups: []string{"default-group"},
c := cert.NebulaCertificate{ issuer: "signer-shasum",
Details: cert.NebulaCertificateDetails{
Name: "host1",
Ips: []*net.IPNet{&ipNet},
Groups: []string{"default-group"},
InvertedGroups: map[string]struct{}{"default-group": {}},
Issuer: "signer-shasum",
}, },
InvertedGroups: map[string]struct{}{"default-group": {}},
} }
h := HostInfo{ h := HostInfo{
ConnectionState: &ConnectionState{ ConnectionState: &ConnectionState{
peerCert: &c, peerCert: &c,
}, },
vpnIp: netip.MustParseAddr(ipNet.IP.String()), vpnIp: network.Addr(),
} }
h.CreateRemoteCIDR(&c) h.CreateRemoteCIDR(c.Certificate)
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, &c) fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
assert.Nil(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", netip.Prefix{}, netip.Prefix{}, "", "")) assert.Nil(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", netip.Prefix{}, netip.Prefix{}, "", ""))
cp := cert.NewCAPool() cp := cert.NewCAPool()
@ -552,7 +485,7 @@ func TestFirewall_DropConntrackReload(t *testing.T) {
assert.NoError(t, fw.Drop(p, false, &h, cp, nil)) assert.NoError(t, fw.Drop(p, false, &h, cp, nil))
oldFw := fw oldFw := fw
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c) fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
assert.Nil(t, fw.AddRule(true, firewall.ProtoAny, 10, 10, []string{"any"}, "", netip.Prefix{}, netip.Prefix{}, "", "")) assert.Nil(t, fw.AddRule(true, firewall.ProtoAny, 10, 10, []string{"any"}, "", netip.Prefix{}, netip.Prefix{}, "", ""))
fw.Conntrack = oldFw.Conntrack fw.Conntrack = oldFw.Conntrack
fw.rulesVersion = oldFw.rulesVersion + 1 fw.rulesVersion = oldFw.rulesVersion + 1
@ -561,7 +494,7 @@ func TestFirewall_DropConntrackReload(t *testing.T) {
assert.NoError(t, fw.Drop(p, false, &h, cp, nil)) assert.NoError(t, fw.Drop(p, false, &h, cp, nil))
oldFw = fw oldFw = fw
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c) fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
assert.Nil(t, fw.AddRule(true, firewall.ProtoAny, 11, 11, []string{"any"}, "", netip.Prefix{}, netip.Prefix{}, "", "")) assert.Nil(t, fw.AddRule(true, firewall.ProtoAny, 11, 11, []string{"any"}, "", netip.Prefix{}, netip.Prefix{}, "", ""))
fw.Conntrack = oldFw.Conntrack fw.Conntrack = oldFw.Conntrack
fw.rulesVersion = oldFw.rulesVersion + 1 fw.rulesVersion = oldFw.rulesVersion + 1
@ -688,7 +621,7 @@ func Test_parsePort(t *testing.T) {
func TestNewFirewallFromConfig(t *testing.T) { func TestNewFirewallFromConfig(t *testing.T) {
l := test.NewLogger() l := test.NewLogger()
// Test a bad rule definition // Test a bad rule definition
c := &cert.NebulaCertificate{} c := &dummyCert{}
conf := config.NewC(l) conf := config.NewC(l)
conf.Settings["firewall"] = map[interface{}]interface{}{"outbound": "asdf"} conf.Settings["firewall"] = map[interface{}]interface{}{"outbound": "asdf"}
_, err := NewFirewallFromConfig(l, c, conf) _, err := NewFirewallFromConfig(l, c, conf)

View File

@ -99,8 +99,7 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
return return
} }
vpnIp, ok := netip.AddrFromSlice(remoteCert.Details.Ips[0].IP) if len(remoteCert.Certificate.Networks()) == 0 {
if !ok {
e := f.l.WithError(err).WithField("udpAddr", addr). e := f.l.WithError(err).WithField("udpAddr", addr).
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}) WithField("handshake", m{"stage": 1, "style": "ix_psk0"})
@ -112,10 +111,10 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
return return
} }
vpnIp = vpnIp.Unmap() vpnIp := remoteCert.Certificate.Networks()[0].Addr().Unmap()
certName := remoteCert.Details.Name certName := remoteCert.Certificate.Name()
fingerprint, _ := remoteCert.Sha256Sum() fingerprint := remoteCert.Fingerprint
issuer := remoteCert.Details.Issuer issuer := remoteCert.Certificate.Issuer()
if vpnIp == f.myVpnNet.Addr() { if vpnIp == f.myVpnNet.Addr() {
f.l.WithField("vpnIp", vpnIp).WithField("udpAddr", addr). f.l.WithField("vpnIp", vpnIp).WithField("udpAddr", addr).
@ -216,7 +215,7 @@ func ixHandshakeStage1(f *Interface, addr netip.AddrPort, via *ViaSender, packet
hostinfo.remotes = f.lightHouse.QueryCache(vpnIp) hostinfo.remotes = f.lightHouse.QueryCache(vpnIp)
hostinfo.SetRemote(addr) hostinfo.SetRemote(addr)
hostinfo.CreateRemoteCIDR(remoteCert) hostinfo.CreateRemoteCIDR(remoteCert.Certificate)
existing, err := f.handshakeManager.CheckAndComplete(hostinfo, 0, f) existing, err := f.handshakeManager.CheckAndComplete(hostinfo, 0, f)
if err != nil { if err != nil {
@ -402,8 +401,7 @@ func ixHandshakeStage2(f *Interface, addr netip.AddrPort, via *ViaSender, hh *Ha
return true return true
} }
vpnIp, ok := netip.AddrFromSlice(remoteCert.Details.Ips[0].IP) if len(remoteCert.Certificate.Networks()) == 0 {
if !ok {
e := f.l.WithError(err).WithField("udpAddr", addr). e := f.l.WithError(err).WithField("udpAddr", addr).
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}) WithField("handshake", m{"stage": 2, "style": "ix_psk0"})
@ -415,10 +413,10 @@ func ixHandshakeStage2(f *Interface, addr netip.AddrPort, via *ViaSender, hh *Ha
return true return true
} }
vpnIp = vpnIp.Unmap() vpnIp := remoteCert.Certificate.Networks()[0].Addr().Unmap()
certName := remoteCert.Details.Name certName := remoteCert.Certificate.Name()
fingerprint, _ := remoteCert.Sha256Sum() fingerprint := remoteCert.Fingerprint
issuer := remoteCert.Details.Issuer issuer := remoteCert.Certificate.Issuer()
// Ensure the right host responded // Ensure the right host responded
if vpnIp != hostinfo.vpnIp { if vpnIp != hostinfo.vpnIp {
@ -486,7 +484,7 @@ func ixHandshakeStage2(f *Interface, addr netip.AddrPort, via *ViaSender, hh *Ha
} }
// Build up the radix for the firewall if we have subnets in the cert // Build up the radix for the firewall if we have subnets in the cert
hostinfo.CreateRemoteCIDR(remoteCert) hostinfo.CreateRemoteCIDR(remoteCert.Certificate)
// Complete our handshake and update metrics, this will replace any existing tunnels for this vpnIp // Complete our handshake and update metrics, this will replace any existing tunnels for this vpnIp
f.handshakeManager.Complete(hostinfo, f) f.handshakeManager.Complete(hostinfo, f)

View File

@ -7,6 +7,7 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"net/netip" "net/netip"
"slices"
"sync" "sync"
"time" "time"
@ -14,7 +15,6 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/slackhq/nebula/header" "github.com/slackhq/nebula/header"
"github.com/slackhq/nebula/udp" "github.com/slackhq/nebula/udp"
"golang.org/x/exp/slices"
) )
const ( const (

View File

@ -5,7 +5,6 @@ import (
"testing" "testing"
"time" "time"
"github.com/slackhq/nebula/cert"
"github.com/slackhq/nebula/header" "github.com/slackhq/nebula/header"
"github.com/slackhq/nebula/test" "github.com/slackhq/nebula/test"
"github.com/slackhq/nebula/udp" "github.com/slackhq/nebula/udp"
@ -27,7 +26,7 @@ func Test_NewHandshakeManagerVpnIp(t *testing.T) {
cs := &CertState{ cs := &CertState{
RawCertificate: []byte{}, RawCertificate: []byte{},
PrivateKey: []byte{}, PrivateKey: []byte{},
Certificate: &cert.NebulaCertificate{}, Certificate: &dummyCert{},
RawCertificateNoKey: []byte{}, RawCertificateNoKey: []byte{},
} }

View File

@ -491,7 +491,7 @@ func (hm *HostMap) queryVpnIp(vpnIp netip.Addr, promoteIfce *Interface) *HostInf
func (hm *HostMap) unlockedAddHostInfo(hostinfo *HostInfo, f *Interface) { func (hm *HostMap) unlockedAddHostInfo(hostinfo *HostInfo, f *Interface) {
if f.serveDns { if f.serveDns {
remoteCert := hostinfo.ConnectionState.peerCert remoteCert := hostinfo.ConnectionState.peerCert
dnsR.Add(remoteCert.Details.Name+".", remoteCert.Details.Ips[0].IP.String()) dnsR.Add(remoteCert.Certificate.Name()+".", remoteCert.Certificate.Networks()[0].Addr().String())
} }
existing := hm.Hosts[hostinfo.vpnIp] existing := hm.Hosts[hostinfo.vpnIp]
@ -585,7 +585,7 @@ func (i *HostInfo) TryPromoteBest(preferredRanges []netip.Prefix, ifce *Interfac
} }
} }
func (i *HostInfo) GetCert() *cert.NebulaCertificate { func (i *HostInfo) GetCert() *cert.CachedCertificate {
if i.ConnectionState != nil { if i.ConnectionState != nil {
return i.ConnectionState.peerCert return i.ConnectionState.peerCert
} }
@ -647,27 +647,19 @@ func (i *HostInfo) RecvErrorExceeded() bool {
return true return true
} }
func (i *HostInfo) CreateRemoteCIDR(c *cert.NebulaCertificate) { func (i *HostInfo) CreateRemoteCIDR(c cert.Certificate) {
if len(c.Details.Ips) == 1 && len(c.Details.Subnets) == 0 { if len(c.Networks()) == 1 && len(c.UnsafeNetworks()) == 0 {
// Simple case, no CIDRTree needed // Simple case, no CIDRTree needed
return return
} }
remoteCidr := new(bart.Table[struct{}]) remoteCidr := new(bart.Table[struct{}])
for _, ip := range c.Details.Ips { for _, network := range c.Networks() {
//TODO: IPV6-WORK what to do when ip is invalid? remoteCidr.Insert(network, struct{}{})
nip, _ := netip.AddrFromSlice(ip.IP)
nip = nip.Unmap()
bits, _ := ip.Mask.Size()
remoteCidr.Insert(netip.PrefixFrom(nip, bits), struct{}{})
} }
for _, n := range c.Details.Subnets { for _, network := range c.UnsafeNetworks() {
//TODO: IPV6-WORK what to do when ip is invalid? remoteCidr.Insert(network, struct{}{})
nip, _ := netip.AddrFromSlice(n.IP)
nip = nip.Unmap()
bits, _ := n.Mask.Size()
remoteCidr.Insert(netip.PrefixFrom(nip, bits), struct{}{})
} }
i.remoteCidr = remoteCidr i.remoteCidr = remoteCidr
} }
@ -683,7 +675,7 @@ func (i *HostInfo) logger(l *logrus.Logger) *logrus.Entry {
if connState := i.ConnectionState; connState != nil { if connState := i.ConnectionState; connState != nil {
if peerCert := connState.peerCert; peerCert != nil { if peerCert := connState.peerCert; peerCert != nil {
li = li.WithField("certName", peerCert.Details.Name) li = li.WithField("certName", peerCert.Certificate.Name())
} }
} }

View File

@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"net"
"net/netip" "net/netip"
"os" "os"
"runtime" "runtime"
@ -157,26 +158,6 @@ func NewInterface(ctx context.Context, c *InterfaceConfig) (*Interface, error) {
certificate := c.pki.GetCertState().Certificate certificate := c.pki.GetCertState().Certificate
myVpnAddr, ok := netip.AddrFromSlice(certificate.Details.Ips[0].IP)
if !ok {
return nil, fmt.Errorf("invalid ip address in certificate: %s", certificate.Details.Ips[0].IP)
}
myVpnMask, ok := netip.AddrFromSlice(certificate.Details.Ips[0].Mask)
if !ok {
return nil, fmt.Errorf("invalid ip mask in certificate: %s", certificate.Details.Ips[0].Mask)
}
myVpnAddr = myVpnAddr.Unmap()
myVpnMask = myVpnMask.Unmap()
if myVpnAddr.BitLen() != myVpnMask.BitLen() {
return nil, fmt.Errorf("ip address and mask are different lengths in certificate")
}
ones, _ := certificate.Details.Ips[0].Mask.Size()
myVpnNet := netip.PrefixFrom(myVpnAddr, ones)
ifce := &Interface{ ifce := &Interface{
pki: c.pki, pki: c.pki,
hostMap: c.HostMap, hostMap: c.HostMap,
@ -194,7 +175,7 @@ func NewInterface(ctx context.Context, c *InterfaceConfig) (*Interface, error) {
version: c.version, version: c.version,
writers: make([]udp.Conn, c.routines), writers: make([]udp.Conn, c.routines),
readers: make([]io.ReadWriteCloser, c.routines), readers: make([]io.ReadWriteCloser, c.routines),
myVpnNet: myVpnNet, myVpnNet: certificate.Networks()[0],
relayManager: c.relayManager, relayManager: c.relayManager,
conntrackCacheTimeout: c.ConntrackCacheTimeout, conntrackCacheTimeout: c.ConntrackCacheTimeout,
@ -209,9 +190,11 @@ func NewInterface(ctx context.Context, c *InterfaceConfig) (*Interface, error) {
l: c.l, l: c.l,
} }
if myVpnAddr.Is4() { if ifce.myVpnNet.Addr().Is4() {
addr := myVpnNet.Masked().Addr().As4() maskedAddr := certificate.Networks()[0].Masked()
binary.BigEndian.PutUint32(addr[:], binary.BigEndian.Uint32(addr[:])|^binary.BigEndian.Uint32(certificate.Details.Ips[0].Mask)) addr := maskedAddr.Addr().As4()
mask := net.CIDRMask(maskedAddr.Bits(), maskedAddr.Addr().BitLen())
binary.BigEndian.PutUint32(addr[:], binary.BigEndian.Uint32(addr[:])|^binary.BigEndian.Uint32(mask))
ifce.myBroadcastAddr = netip.AddrFrom4(addr) ifce.myBroadcastAddr = netip.AddrFrom4(addr)
} }
@ -434,7 +417,7 @@ func (f *Interface) emitStats(ctx context.Context, i time.Duration) {
f.firewall.EmitStats() f.firewall.EmitStats()
f.handshakeManager.EmitStats() f.handshakeManager.EmitStats()
udpStats() udpStats()
certExpirationGauge.Update(int64(f.pki.GetCertState().Certificate.Details.NotAfter.Sub(time.Now()) / time.Second)) certExpirationGauge.Update(int64(f.pki.GetCertState().Certificate.NotAfter().Sub(time.Now()) / time.Second))
} }
} }
} }

12
main.go
View File

@ -68,17 +68,7 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg
} }
l.WithField("firewallHashes", fw.GetRuleHashes()).Info("Firewall started") l.WithField("firewallHashes", fw.GetRuleHashes()).Info("Firewall started")
ones, _ := certificate.Details.Ips[0].Mask.Size() tunCidr := certificate.Networks()[0]
addr, ok := netip.AddrFromSlice(certificate.Details.Ips[0].IP)
if !ok {
err = util.NewContextualError(
"Invalid ip address in certificate",
m{"vpnIp": certificate.Details.Ips[0].IP},
nil,
)
return nil, err
}
tunCidr := netip.PrefixFrom(addr, ones)
ssh, err := sshd.NewSSHServer(l.WithField("subsystem", "sshd")) ssh, err := sshd.NewSSHServer(l.WithField("subsystem", "sshd"))
if err != nil { if err != nil {

View File

@ -14,7 +14,6 @@ import (
"github.com/slackhq/nebula/header" "github.com/slackhq/nebula/header"
"github.com/slackhq/nebula/udp" "github.com/slackhq/nebula/udp"
"golang.org/x/net/ipv4" "golang.org/x/net/ipv4"
"google.golang.org/protobuf/proto"
) )
const ( const (
@ -494,7 +493,7 @@ func (f *Interface) sendMeta(ci *ConnectionState, endpoint *net.UDPAddr, meta *N
} }
*/ */
func RecombineCertAndValidate(h *noise.HandshakeState, rawCertBytes []byte, caPool *cert.NebulaCAPool) (*cert.NebulaCertificate, error) { func RecombineCertAndValidate(h *noise.HandshakeState, rawCertBytes []byte, caPool *cert.CAPool) (*cert.CachedCertificate, error) {
pk := h.PeerStatic() pk := h.PeerStatic()
if pk == nil { if pk == nil {
@ -505,31 +504,15 @@ func RecombineCertAndValidate(h *noise.HandshakeState, rawCertBytes []byte, caPo
return nil, errors.New("provided payload was empty") return nil, errors.New("provided payload was empty")
} }
r := &cert.RawNebulaCertificate{} c, err := cert.UnmarshalCertificateFromHandshake(rawCertBytes, pk)
err := proto.Unmarshal(rawCertBytes, r)
if err != nil { if err != nil {
return nil, fmt.Errorf("error unmarshaling cert: %s", err) return nil, fmt.Errorf("error unmarshaling cert: %w", err)
} }
// If the Details are nil, just exit to avoid crashing cc, err := caPool.VerifyCertificate(time.Now(), c)
if r.Details == nil {
return nil, fmt.Errorf("certificate did not contain any details")
}
r.Details.PublicKey = pk
recombined, err := proto.Marshal(r)
if err != nil { if err != nil {
return nil, fmt.Errorf("error while recombining certificate: %s", err) return nil, fmt.Errorf("certificate validation failed: %w", err)
} }
c, _ := cert.UnmarshalNebulaCertificate(recombined) return cc, nil
isValid, err := c.Verify(time.Now(), caPool)
if err != nil {
return c, fmt.Errorf("certificate validation failed: %s", err)
} else if !isValid {
// This case should never happen but here's to defensive programming!
return c, errors.New("certificate validation failed but did not return an error")
}
return c, nil
} }

46
pki.go
View File

@ -16,16 +16,17 @@ import (
type PKI struct { type PKI struct {
cs atomic.Pointer[CertState] cs atomic.Pointer[CertState]
caPool atomic.Pointer[cert.NebulaCAPool] caPool atomic.Pointer[cert.CAPool]
l *logrus.Logger l *logrus.Logger
} }
type CertState struct { type CertState struct {
Certificate *cert.NebulaCertificate Certificate cert.Certificate
RawCertificate []byte RawCertificate []byte
RawCertificateNoKey []byte RawCertificateNoKey []byte
PublicKey []byte PublicKey []byte
PrivateKey []byte PrivateKey []byte
pkcs11Backed bool
} }
func NewPKIFromConfig(l *logrus.Logger, c *config.C) (*PKI, error) { func NewPKIFromConfig(l *logrus.Logger, c *config.C) (*PKI, error) {
@ -49,7 +50,7 @@ func (p *PKI) GetCertState() *CertState {
return p.cs.Load() return p.cs.Load()
} }
func (p *PKI) GetCAPool() *cert.NebulaCAPool { func (p *PKI) GetCAPool() *cert.CAPool {
return p.caPool.Load() return p.caPool.Load()
} }
@ -84,12 +85,12 @@ func (p *PKI) reloadCert(c *config.C, initial bool) *util.ContextualError {
// did IP in cert change? if so, don't set // did IP in cert change? if so, don't set
currentCert := p.cs.Load().Certificate currentCert := p.cs.Load().Certificate
oldIPs := currentCert.Details.Ips oldIPs := currentCert.Networks()
newIPs := cs.Certificate.Details.Ips newIPs := cs.Certificate.Networks()
if len(oldIPs) > 0 && len(newIPs) > 0 && oldIPs[0].String() != newIPs[0].String() { if len(oldIPs) > 0 && len(newIPs) > 0 && oldIPs[0].String() != newIPs[0].String() {
return util.NewContextualError( return util.NewContextualError(
"IP in new cert was different from old", "Networks in new cert was different from old",
m{"new_ip": newIPs[0], "old_ip": oldIPs[0]}, m{"new_network": newIPs[0], "old_network": oldIPs[0]},
nil, nil,
) )
} }
@ -115,29 +116,28 @@ func (p *PKI) reloadCAPool(c *config.C) *util.ContextualError {
return nil return nil
} }
func newCertState(certificate *cert.NebulaCertificate, privateKey []byte) (*CertState, error) { func newCertState(certificate cert.Certificate, pkcs11backed bool, privateKey []byte) (*CertState, error) {
// Marshal the certificate to ensure it is valid // Marshal the certificate to ensure it is valid
rawCertificate, err := certificate.Marshal() rawCertificate, err := certificate.Marshal()
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid nebula certificate on interface: %s", err) return nil, fmt.Errorf("invalid nebula certificate on interface: %s", err)
} }
publicKey := certificate.Details.PublicKey publicKey := certificate.PublicKey()
cs := &CertState{ cs := &CertState{
RawCertificate: rawCertificate, RawCertificate: rawCertificate,
Certificate: certificate, Certificate: certificate,
PrivateKey: privateKey, PrivateKey: privateKey,
PublicKey: publicKey, PublicKey: publicKey,
pkcs11Backed: pkcs11backed,
} }
cs.Certificate.Details.PublicKey = nil rawCertNoKey, err := cs.Certificate.MarshalForHandshakes()
rawCertNoKey, err := cs.Certificate.Marshal()
if err != nil { if err != nil {
return nil, fmt.Errorf("error marshalling certificate no key: %s", err) return nil, fmt.Errorf("error marshalling certificate no key: %s", err)
} }
cs.RawCertificateNoKey = rawCertNoKey cs.RawCertificateNoKey = rawCertNoKey
// put public key back
cs.Certificate.Details.PublicKey = cs.PublicKey
return cs, nil return cs, nil
} }
@ -146,7 +146,7 @@ func loadPrivateKey(privPathOrPEM string) (rawKey []byte, curve cert.Curve, isPk
if strings.Contains(privPathOrPEM, "-----BEGIN") { if strings.Contains(privPathOrPEM, "-----BEGIN") {
pemPrivateKey = []byte(privPathOrPEM) pemPrivateKey = []byte(privPathOrPEM)
privPathOrPEM = "<inline>" privPathOrPEM = "<inline>"
rawKey, _, curve, err = cert.UnmarshalPrivateKey(pemPrivateKey) rawKey, _, curve, err = cert.UnmarshalPrivateKeyFromPEM(pemPrivateKey)
if err != nil { if err != nil {
return nil, curve, false, fmt.Errorf("error while unmarshaling pki.key %s: %s", privPathOrPEM, err) return nil, curve, false, fmt.Errorf("error while unmarshaling pki.key %s: %s", privPathOrPEM, err)
} }
@ -158,7 +158,7 @@ func loadPrivateKey(privPathOrPEM string) (rawKey []byte, curve cert.Curve, isPk
if err != nil { if err != nil {
return nil, curve, false, fmt.Errorf("unable to read pki.key file %s: %s", privPathOrPEM, err) return nil, curve, false, fmt.Errorf("unable to read pki.key file %s: %s", privPathOrPEM, err)
} }
rawKey, _, curve, err = cert.UnmarshalPrivateKey(pemPrivateKey) rawKey, _, curve, err = cert.UnmarshalPrivateKeyFromPEM(pemPrivateKey)
if err != nil { if err != nil {
return nil, curve, false, fmt.Errorf("error while unmarshaling pki.key %s: %s", privPathOrPEM, err) return nil, curve, false, fmt.Errorf("error while unmarshaling pki.key %s: %s", privPathOrPEM, err)
} }
@ -198,27 +198,27 @@ func newCertStateFromConfig(c *config.C) (*CertState, error) {
} }
} }
nebulaCert, _, err := cert.UnmarshalNebulaCertificateFromPEM(rawCert) nebulaCert, _, err := cert.UnmarshalCertificateFromPEM(rawCert)
if err != nil { if err != nil {
return nil, fmt.Errorf("error while unmarshaling pki.cert %s: %s", pubPathOrPEM, err) return nil, fmt.Errorf("error while unmarshaling pki.cert %s: %s", pubPathOrPEM, err)
} }
nebulaCert.Pkcs11Backed = isPkcs11
if nebulaCert.Expired(time.Now()) { if nebulaCert.Expired(time.Now()) {
return nil, fmt.Errorf("nebula certificate for this host is expired") return nil, fmt.Errorf("nebula certificate for this host is expired")
} }
if len(nebulaCert.Details.Ips) == 0 { if len(nebulaCert.Networks()) == 0 {
return nil, fmt.Errorf("no IPs encoded in certificate") return nil, fmt.Errorf("no networks encoded in certificate")
} }
if err = nebulaCert.VerifyPrivateKey(curve, rawKey); err != nil { if err = nebulaCert.VerifyPrivateKey(curve, rawKey); err != nil {
return nil, fmt.Errorf("private key is not a pair with public key in nebula cert") return nil, fmt.Errorf("private key is not a pair with public key in nebula cert")
} }
return newCertState(nebulaCert, rawKey) return newCertState(nebulaCert, isPkcs11, rawKey)
} }
func loadCAPoolFromConfig(l *logrus.Logger, c *config.C) (*cert.NebulaCAPool, error) { func loadCAPoolFromConfig(l *logrus.Logger, c *config.C) (*cert.CAPool, error) {
var rawCA []byte var rawCA []byte
var err error var err error
@ -237,11 +237,11 @@ func loadCAPoolFromConfig(l *logrus.Logger, c *config.C) (*cert.NebulaCAPool, er
} }
} }
caPool, err := cert.NewCAPoolFromBytes(rawCA) caPool, err := cert.NewCAPoolFromPEM(rawCA)
if errors.Is(err, cert.ErrExpired) { if errors.Is(err, cert.ErrExpired) {
var expired int var expired int
for _, crt := range caPool.CAs { for _, crt := range caPool.CAs {
if crt.Expired(time.Now()) { if crt.Certificate.Expired(time.Now()) {
expired++ expired++
l.WithField("cert", crt).Warn("expired certificate present in CA pool") l.WithField("cert", crt).Warn("expired certificate present in CA pool")
} }

View File

@ -18,9 +18,9 @@ import (
type m map[string]interface{} type m map[string]interface{}
func newSimpleService(caCrt *cert.NebulaCertificate, caKey []byte, name string, udpIp netip.Addr, overrides m) *Service { func newSimpleService(caCrt cert.Certificate, caKey []byte, name string, udpIp netip.Addr, overrides m) *Service {
_, _, myPrivKey, myPEM := e2e.NewTestCert(caCrt, caKey, "a", time.Now(), time.Now().Add(5*time.Minute), netip.PrefixFrom(udpIp, 24), nil, []string{}) _, _, myPrivKey, myPEM := e2e.NewTestCert(caCrt, caKey, "a", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{netip.PrefixFrom(udpIp, 24)}, nil, []string{})
caB, err := caCrt.MarshalToPEM() caB, err := caCrt.MarshalPEM()
if err != nil { if err != nil {
panic(err) panic(err)
} }

4
ssh.go
View File

@ -801,7 +801,7 @@ func sshPrintCert(ifce *Interface, fs interface{}, a []string, w sshd.StringWrit
return w.WriteLine(fmt.Sprintf("Could not find tunnel for vpn ip: %v", a[0])) return w.WriteLine(fmt.Sprintf("Could not find tunnel for vpn ip: %v", a[0]))
} }
cert = hostInfo.GetCert() cert = hostInfo.GetCert().Certificate
} }
if args.Json || args.Pretty { if args.Json || args.Pretty {
@ -825,7 +825,7 @@ func sshPrintCert(ifce *Interface, fs interface{}, a []string, w sshd.StringWrit
} }
if args.Raw { if args.Raw {
b, err := cert.MarshalToPEM() b, err := cert.MarshalPEM()
if err != nil { if err != nil {
//TODO: handle it //TODO: handle it
return nil return nil

View File

@ -2,6 +2,7 @@ package test
import ( import (
"fmt" "fmt"
"net/netip"
"reflect" "reflect"
"testing" "testing"
"time" "time"
@ -24,6 +25,11 @@ func AssertDeepCopyEqual(t *testing.T, a interface{}, b interface{}) {
} }
func traverseDeepCopy(t *testing.T, v1 reflect.Value, v2 reflect.Value, name string) bool { func traverseDeepCopy(t *testing.T, v1 reflect.Value, v2 reflect.Value, name string) bool {
if v1.Type() == v2.Type() && v1.Type() == reflect.TypeOf(netip.Addr{}) {
// Ignore netip.Addr types since they reuse an interned global value
return false
}
switch v1.Kind() { switch v1.Kind() {
case reflect.Array: case reflect.Array:
for i := 0; i < v1.Len(); i++ { for i := 0; i < v1.Len(); i++ {