Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V2 certificate format #1216

Draft
wants to merge 32 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
bf2bfae
Add in v2 certificate
nbrownus Sep 13, 2024
81a4080
Ignore private key verification for pkcs11 private keys
nbrownus Sep 13, 2024
c744e94
Fixup nebula-cert tests for v1 certs
nbrownus Sep 16, 2024
7522a00
Initial dual cert version nebula-cert support
nbrownus Sep 18, 2024
4074163
Dual cert handling
nbrownus Sep 20, 2024
3098480
Fixes
nbrownus Sep 20, 2024
bf6c454
Fix v2 handshaking
nbrownus Sep 20, 2024
6fbf64e
Fix v2 cert copying
nbrownus Sep 20, 2024
c383c44
Copy darwin tun handling from other branch
nbrownus Sep 20, 2024
e910166
Copy device and route files over
nbrownus Sep 20, 2024
e39b8e7
Port over more of the ipv6 tun device changes
nbrownus Sep 20, 2024
debcc16
Fixup tests
nbrownus Sep 20, 2024
fd20fc3
Cert v2 + tun changes for Linux (#1224)
JackDoanRivian Sep 21, 2024
c9acce2
Shed the layers of indirection on udp listeners, get the full hostinf…
nbrownus Sep 21, 2024
554f849
Support multiple vpn addrs in lighthouse and hostmap
nbrownus Sep 22, 2024
35b67b0
Mostly working
nbrownus Sep 23, 2024
b3f2d49
Better e2e errors
nbrownus Sep 24, 2024
c00422f
Fix hostmap deletion and lighthouse version choice
nbrownus Oct 4, 2024
af0b881
Add TBSCertificate.SignWith to abstract out sources-of-signatures (#1…
JackDoanRivian Oct 8, 2024
d77278a
Cert-v2 submit v2 relays to lighthouse (#1244)
JackDoanRivian Oct 10, 2024
aea09f8
Cert V2 firewall fixes (#1242)
JackDoanRivian Oct 10, 2024
f7f246f
fix nebula-cert json printing (#1243)
JackDoanRivian Oct 10, 2024
d5f2619
cert-v2: backwards compatibility trickery for ipv6 (#1245)
JackDoanRivian Oct 11, 2024
d6f1b51
Fix flaky e2e test
nbrownus Oct 11, 2024
8ccdced
cert-v2: dns support for v4 and v6 addresses (#1249)
JackDoanRivian Oct 11, 2024
a56521e
cert-v2: more lighthousing (#1251)
JackDoanRivian Oct 11, 2024
5a72a56
Fix calculated remotes, clean up some todos
nbrownus Oct 17, 2024
df3b751
More ip -> addr
nbrownus Oct 17, 2024
1377b84
Fix firewall for assigned networks and more ip -> addr
nbrownus Oct 17, 2024
2067e52
Fixup hostinfo RemoteCIDR
nbrownus Oct 17, 2024
48b7f5a
Update documentation and add some new work todos
nbrownus Oct 17, 2024
aa23d5f
cert-v2 relay stuff (#1252)
JackDoanRivian Oct 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ bench-cpu-long:
go test -bench=. -benchtime=60s -cpuprofile=cpu.pprof
go tool pprof go-audit.test cpu.pprof

proto: nebula.pb.go cert/cert.pb.go
proto: nebula.pb.go cert/cert_v1.pb.go

nebula.pb.go: nebula.proto .FORCE
go build github.com/gogo/protobuf/protoc-gen-gogofaster
Expand Down
59 changes: 34 additions & 25 deletions calculated_remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ type calculatedRemote struct {
port uint32
}

func newCalculatedRemote(maskCidr netip.Prefix, port int) (*calculatedRemote, error) {
func newCalculatedRemote(cidr, maskCidr netip.Prefix, port int) (*calculatedRemote, error) {
if maskCidr.Addr().BitLen() != cidr.Addr().BitLen() {
return nil, fmt.Errorf("invalid mask: %s for cidr: %s", maskCidr, cidr)
}

masked := maskCidr.Masked()
if port < 0 || port > math.MaxUint16 {
return nil, fmt.Errorf("invalid port: %d", port)
Expand All @@ -38,32 +42,38 @@ func (c *calculatedRemote) String() string {
return fmt.Sprintf("CalculatedRemote(mask=%v port=%d)", c.ipNet, c.port)
}

func (c *calculatedRemote) Apply(ip netip.Addr) *Ip4AndPort {
// Combine the masked bytes of the "mask" IP with the unmasked bytes
// of the overlay IP
if c.ipNet.Addr().Is4() {
return c.apply4(ip)
}
return c.apply6(ip)
}

func (c *calculatedRemote) apply4(ip netip.Addr) *Ip4AndPort {
//TODO: IPV6-WORK this can be less crappy
func (c *calculatedRemote) ApplyV4(addr netip.Addr) *V4AddrPort {
// Combine the masked bytes of the "mask" IP with the unmasked bytes of the overlay IP
maskb := net.CIDRMask(c.mask.Bits(), c.mask.Addr().BitLen())
mask := binary.BigEndian.Uint32(maskb[:])

b := c.mask.Addr().As4()
maskIp := binary.BigEndian.Uint32(b[:])
maskAddr := binary.BigEndian.Uint32(b[:])

b = ip.As4()
intIp := binary.BigEndian.Uint32(b[:])
b = addr.As4()
intAddr := binary.BigEndian.Uint32(b[:])

return &Ip4AndPort{(maskIp & mask) | (intIp & ^mask), c.port}
return &V4AddrPort{(maskAddr & mask) | (intAddr & ^mask), c.port}
}

func (c *calculatedRemote) apply6(ip netip.Addr) *Ip4AndPort {
//TODO: IPV6-WORK
panic("Can not calculate ipv6 remote addresses")
func (c *calculatedRemote) ApplyV6(addr netip.Addr) *V6AddrPort {
mask := net.CIDRMask(c.mask.Bits(), c.mask.Addr().BitLen())
maskAddr := c.mask.Addr().As16()
calcAddr := addr.As16()

ap := V6AddrPort{Port: c.port}

maskb := binary.BigEndian.Uint64(mask[:8])
maskAddrb := binary.BigEndian.Uint64(maskAddr[:8])
calcAddrb := binary.BigEndian.Uint64(calcAddr[:8])
ap.Hi = (maskAddrb & maskb) | (calcAddrb & ^maskb)

maskb = binary.BigEndian.Uint64(mask[8:])
maskAddrb = binary.BigEndian.Uint64(maskAddr[8:])
calcAddrb = binary.BigEndian.Uint64(calcAddr[8:])
ap.Lo = (maskAddrb & maskb) | (calcAddrb & ^maskb)

return &ap
}

func NewCalculatedRemotesFromConfig(c *config.C, k string) (*bart.Table[[]*calculatedRemote], error) {
Expand All @@ -89,8 +99,7 @@ func NewCalculatedRemotesFromConfig(c *config.C, k string) (*bart.Table[[]*calcu
return nil, fmt.Errorf("config `%s` has invalid CIDR: %s", k, rawCIDR)
}

//TODO: IPV6-WORK this does not verify that rawValue contains the same bits as cidr here
entry, err := newCalculatedRemotesListFromConfig(rawValue)
entry, err := newCalculatedRemotesListFromConfig(cidr, rawValue)
if err != nil {
return nil, fmt.Errorf("config '%s.%s': %w", k, rawCIDR, err)
}
Expand All @@ -101,15 +110,15 @@ func NewCalculatedRemotesFromConfig(c *config.C, k string) (*bart.Table[[]*calcu
return calculatedRemotes, nil
}

func newCalculatedRemotesListFromConfig(raw any) ([]*calculatedRemote, error) {
func newCalculatedRemotesListFromConfig(cidr netip.Prefix, raw any) ([]*calculatedRemote, error) {
rawList, ok := raw.([]any)
if !ok {
return nil, fmt.Errorf("calculated_remotes entry has invalid type: %T", raw)
}

var l []*calculatedRemote
for _, e := range rawList {
c, err := newCalculatedRemotesEntryFromConfig(e)
c, err := newCalculatedRemotesEntryFromConfig(cidr, e)
if err != nil {
return nil, fmt.Errorf("calculated_remotes entry: %w", err)
}
Expand All @@ -119,7 +128,7 @@ func newCalculatedRemotesListFromConfig(raw any) ([]*calculatedRemote, error) {
return l, nil
}

func newCalculatedRemotesEntryFromConfig(raw any) (*calculatedRemote, error) {
func newCalculatedRemotesEntryFromConfig(cidr netip.Prefix, raw any) (*calculatedRemote, error) {
rawMap, ok := raw.(map[any]any)
if !ok {
return nil, fmt.Errorf("invalid type: %T", raw)
Expand Down Expand Up @@ -155,5 +164,5 @@ func newCalculatedRemotesEntryFromConfig(raw any) (*calculatedRemote, error) {
return nil, fmt.Errorf("invalid port (type %T): %v", rawValue, rawValue)
}

return newCalculatedRemote(maskCidr, port)
return newCalculatedRemote(cidr, maskCidr, port)
}
66 changes: 61 additions & 5 deletions calculated_remote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ import (
)

func TestCalculatedRemoteApply(t *testing.T) {
ipNet, err := netip.ParsePrefix("192.168.1.0/24")
require.NoError(t, err)

c, err := newCalculatedRemote(ipNet, 4242)
// Test v4 addresses
ipNet := netip.MustParsePrefix("192.168.1.0/24")
c, err := newCalculatedRemote(ipNet, ipNet, 4242)
require.NoError(t, err)

input, err := netip.ParseAddr("10.0.10.182")
Expand All @@ -21,5 +20,62 @@ func TestCalculatedRemoteApply(t *testing.T) {
expected, err := netip.ParseAddr("192.168.1.182")
assert.NoError(t, err)

assert.Equal(t, NewIp4AndPortFromNetIP(expected, 4242), c.Apply(input))
assert.Equal(t, netAddrToProtoV4AddrPort(expected, 4242), c.ApplyV4(input))

// Test v6 addresses
ipNet = netip.MustParsePrefix("ffff:ffff:ffff:ffff::0/64")
c, err = newCalculatedRemote(ipNet, ipNet, 4242)
require.NoError(t, err)

input, err = netip.ParseAddr("beef:beef:beef:beef:beef:beef:beef:beef")
assert.NoError(t, err)

expected, err = netip.ParseAddr("ffff:ffff:ffff:ffff:beef:beef:beef:beef")
assert.NoError(t, err)

assert.Equal(t, netAddrToProtoV6AddrPort(expected, 4242), c.ApplyV6(input))

// Test v6 addresses part 2
ipNet = netip.MustParsePrefix("ffff:ffff:ffff:ffff:ffff::0/80")
c, err = newCalculatedRemote(ipNet, ipNet, 4242)
require.NoError(t, err)

input, err = netip.ParseAddr("beef:beef:beef:beef:beef:beef:beef:beef")
assert.NoError(t, err)

expected, err = netip.ParseAddr("ffff:ffff:ffff:ffff:ffff:beef:beef:beef")
assert.NoError(t, err)

assert.Equal(t, netAddrToProtoV6AddrPort(expected, 4242), c.ApplyV6(input))

// Test v6 addresses part 2
ipNet = netip.MustParsePrefix("ffff:ffff:ffff::0/48")
c, err = newCalculatedRemote(ipNet, ipNet, 4242)
require.NoError(t, err)

input, err = netip.ParseAddr("beef:beef:beef:beef:beef:beef:beef:beef")
assert.NoError(t, err)

expected, err = netip.ParseAddr("ffff:ffff:ffff:beef:beef:beef:beef:beef")
assert.NoError(t, err)

assert.Equal(t, netAddrToProtoV6AddrPort(expected, 4242), c.ApplyV6(input))
}

func Test_newCalculatedRemote(t *testing.T) {
c, err := newCalculatedRemote(netip.MustParsePrefix("1::1/128"), netip.MustParsePrefix("1.0.0.0/32"), 4242)
require.EqualError(t, err, "invalid mask: 1.0.0.0/32 for cidr: 1::1/128")
require.Nil(t, c)

c, err = newCalculatedRemote(netip.MustParsePrefix("1.0.0.0/32"), netip.MustParsePrefix("1::1/128"), 4242)
require.EqualError(t, err, "invalid mask: 1::1/128 for cidr: 1.0.0.0/32")
require.Nil(t, c)

c, err = newCalculatedRemote(netip.MustParsePrefix("1.0.0.0/32"), netip.MustParsePrefix("1.0.0.0/32"), 4242)
require.NoError(t, err)
require.NotNil(t, c)

c, err = newCalculatedRemote(netip.MustParsePrefix("1::1/128"), netip.MustParsePrefix("1::1/128"), 4242)
require.NoError(t, err)
require.NotNil(t, c)
}
19 changes: 15 additions & 4 deletions cert/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,25 @@

This is a library for interacting with `nebula` style certificates and authorities.

A `protobuf` definition of the certificate format is also included
There are now 2 versions of `nebula` certificates:

### Compiling the protobuf definition
## v1

Make sure you have `protoc` installed.
This version is deprecated.

A `protobuf` definition of the certificate format is included at `cert_v1.proto`

To compile the definition you will need `protoc` installed.

To compile for `go` with the same version of protobuf specified in go.mod:

```bash
make
make proto
```

## v2

This is the latest version which uses asn.1 DER encoding. It can support ipv4 and ipv6 and tolerate
future certificate changes better than v1.

`cert_v2.asn1` defines the wire format and can be used to compile marshalers.
52 changes: 52 additions & 0 deletions cert/asn1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package cert

import (
"golang.org/x/crypto/cryptobyte"
"golang.org/x/crypto/cryptobyte/asn1"
)

// readOptionalASN1Boolean reads an asn.1 boolean with a specific tag instead of a asn.1 tag wrapping a boolean with a value
// https://github.com/golang/go/issues/64811#issuecomment-1944446920
func readOptionalASN1Boolean(b *cryptobyte.String, out *bool, tag asn1.Tag, defaultValue bool) bool {
var present bool
var child cryptobyte.String
if !b.ReadOptionalASN1(&child, &present, tag) {
return false
}

if !present {
*out = defaultValue
return true
}

// Ensure we have 1 byte
if len(child) == 1 {
*out = child[0] > 0
return true
}

return false
}

// readOptionalASN1Byte reads an asn.1 uint8 with a specific tag instead of a asn.1 tag wrapping a uint8 with a value
// Similar issue as with readOptionalASN1Boolean
func readOptionalASN1Byte(b *cryptobyte.String, out *byte, tag asn1.Tag, defaultValue byte) bool {
var present bool
var child cryptobyte.String
if !b.ReadOptionalASN1(&child, &present, tag) {
return false
}

if !present {
*out = defaultValue
return true
}

// Ensure we have 1 byte
if len(child) == 1 {
*out = child[0]
return true
}

return false
}
20 changes: 10 additions & 10 deletions cert/ca_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,31 +63,31 @@ IBNWYMep3ysx9zCgknfG5dKtwGTaqF++BWKDYdyl34KX

rootCA := certificateV1{
details: detailsV1{
Name: "nebula root ca",
name: "nebula root ca",
},
}

rootCA01 := certificateV1{
details: detailsV1{
Name: "nebula root ca 01",
name: "nebula root ca 01",
},
}

rootCAP256 := certificateV1{
details: detailsV1{
Name: "nebula P256 test",
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)
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)
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))
Expand All @@ -97,13 +97,13 @@ IBNWYMep3ysx9zCgknfG5dKtwGTaqF++BWKDYdyl34KX
// 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("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, ppppp.CAs[string("a7938893ec8c4ef769b06d7f425e5e46f7a7f5ffa49c3bcf4a86b608caba9159")].Certificate.Name(), rootCAP256.details.name)
assert.Equal(t, len(ppppp.CAs), 1)
}
Loading
Loading