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 17 commits into
base: cert-interface
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
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
}
63 changes: 53 additions & 10 deletions cert/cert.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package cert

import (
"fmt"
"net/netip"
"time"
)

type Version int
type Version uint32

const (
Version1 Version = 1
Version2 Version = 2
VersionPre1 Version = 0
Version1 Version = 1
Version2 Version = 2
)

type Certificate interface {
Expand Down Expand Up @@ -109,21 +111,62 @@ type CachedCertificate struct {

// UnmarshalCertificate will attempt to unmarshal a wire protocol level certificate.
func UnmarshalCertificate(b []byte) (Certificate, error) {
c, err := unmarshalCertificateV1(b, true)
if err != nil {
return nil, err
//TODO: you left off here, no one uses this function but it might be beneficial to export _something_ that someone can use, maybe the Versioned unmarshallsers?
var c Certificate
c, err := unmarshalCertificateV2(b, nil)
if err == nil {
return c, nil
}
return c, nil

c, err = unmarshalCertificateV1(b, nil)
if err == nil {
return c, nil
}

return nil, fmt.Errorf("could not unmarshal certificate")
}

// UnmarshalCertificateFromHandshake will attempt to unmarshal a certificate received in a handshake.
// Handshakes save space by placing the peers public key in a different part of the packet, we have to
// reassemble the actual certificate structure with that in mind.
func UnmarshalCertificateFromHandshake(b []byte, publicKey []byte) (Certificate, error) {
c, err := unmarshalCertificateV1(b, false)
func UnmarshalCertificateFromHandshake(v Version, b []byte, publicKey []byte) (Certificate, error) {
var c Certificate
var err error

switch v {
case VersionPre1, Version1:
c, err = unmarshalCertificateV1(b, publicKey)
case Version2:
c, err = unmarshalCertificateV2(b, publicKey)
default:
//TODO: make a static var
return nil, fmt.Errorf("unknown certificate version %d", v)
}

if err != nil {
return nil, err
}
c.details.PublicKey = publicKey
return c, nil
}

func RecombineAndValidate(v Version, rawCertBytes, publicKey []byte, caPool *CAPool) (*CachedCertificate, error) {
if publicKey == nil {
return nil, ErrNoPeerStaticKey
}

if rawCertBytes == nil {
return nil, ErrNoPayload
}

c, err := UnmarshalCertificateFromHandshake(v, rawCertBytes, publicKey)
if err != nil {
return nil, fmt.Errorf("error unmarshaling cert: %w", err)
}

cc, err := caPool.VerifyCertificate(time.Now(), c)
if err != nil {
return nil, fmt.Errorf("certificate validation failed: %w", err)
}

return cc, nil
}
4 changes: 2 additions & 2 deletions cert/cert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func TestMarshalingNebulaCertificate(t *testing.T) {
assert.Nil(t, err)
//t.Log("Cert size:", len(b))

nc2, err := unmarshalCertificateV1(b, true)
nc2, err := unmarshalCertificateV1(b, nil)
assert.Nil(t, err)

assert.Equal(t, nc.signature, nc2.Signature())
Expand Down Expand Up @@ -534,7 +534,7 @@ func TestNebulaCertificate_Copy(t *testing.T) {
func TestUnmarshalNebulaCertificate(t *testing.T) {
// Test that we don't panic with an invalid certificate (#332)
data := []byte("\x98\x00\x00")
_, err := unmarshalCertificateV1(data, true)
_, err := unmarshalCertificateV1(data, nil)
assert.EqualError(t, err, "encoded Details was nil")
}

Expand Down
Loading
Loading