Public Release
This commit is contained in:
9
cert/Makefile
Normal file
9
cert/Makefile
Normal file
@ -0,0 +1,9 @@
|
||||
GO111MODULE = on
|
||||
export GO111MODULE
|
||||
|
||||
cert.pb.go: cert.proto .FORCE
|
||||
go build github.com/golang/protobuf/protoc-gen-go
|
||||
PATH="$(PWD):$(PATH)" protoc --go_out=. $<
|
||||
rm protoc-gen-go
|
||||
|
||||
.FORCE:
|
15
cert/README.md
Normal file
15
cert/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
## `cert`
|
||||
|
||||
This is a library for interacting with `nebula` style certificates and authorities.
|
||||
|
||||
A `protobuf` definition of the certificate format is also included
|
||||
|
||||
### Compiling the protobuf definition
|
||||
|
||||
Make sure you have `protoc` installed.
|
||||
|
||||
To compile for `go` with the same version of protobuf specified in go.mod:
|
||||
|
||||
```bash
|
||||
make
|
||||
```
|
120
cert/ca.go
Normal file
120
cert/ca.go
Normal file
@ -0,0 +1,120 @@
|
||||
package cert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type NebulaCAPool struct {
|
||||
CAs map[string]*NebulaCertificate
|
||||
certBlacklist map[string]struct{}
|
||||
}
|
||||
|
||||
// NewCAPool creates a CAPool
|
||||
func NewCAPool() *NebulaCAPool {
|
||||
ca := NebulaCAPool{
|
||||
CAs: make(map[string]*NebulaCertificate),
|
||||
certBlacklist: make(map[string]struct{}),
|
||||
}
|
||||
|
||||
return &ca
|
||||
}
|
||||
|
||||
func NewCAPoolFromBytes(caPEMs []byte) (*NebulaCAPool, error) {
|
||||
pool := NewCAPool()
|
||||
var err error
|
||||
for {
|
||||
caPEMs, err = pool.AddCACertificate(caPEMs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if caPEMs == nil || len(caPEMs) == 0 || strings.TrimSpace(string(caPEMs)) == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
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("provided certificate was not a CA; %s", c.Details.Name)
|
||||
}
|
||||
|
||||
if !c.CheckSignature(c.Details.PublicKey) {
|
||||
return pemBytes, fmt.Errorf("provided certificate was not self signed; %s", c.Details.Name)
|
||||
}
|
||||
|
||||
if c.Expired(time.Now()) {
|
||||
return pemBytes, fmt.Errorf("provided CA certificate is expired; %s", c.Details.Name)
|
||||
}
|
||||
|
||||
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
|
||||
return pemBytes, nil
|
||||
}
|
||||
|
||||
// BlacklistFingerprint adds a cert fingerprint to the blacklist
|
||||
func (ncp *NebulaCAPool) BlacklistFingerprint(f string) {
|
||||
ncp.certBlacklist[f] = struct{}{}
|
||||
}
|
||||
|
||||
// ResetCertBlacklist removes all previously blacklisted cert fingerprints
|
||||
func (ncp *NebulaCAPool) ResetCertBlacklist() {
|
||||
ncp.certBlacklist = make(map[string]struct{})
|
||||
}
|
||||
|
||||
// IsBlacklisted returns true if the fingerprint fails to generate or has been explicitly blacklisted
|
||||
func (ncp *NebulaCAPool) IsBlacklisted(c *NebulaCertificate) bool {
|
||||
h, err := c.Sha256Sum()
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if _, ok := ncp.certBlacklist[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
|
||||
}
|
445
cert/cert.go
Normal file
445
cert/cert.go
Normal file
@ -0,0 +1,445 @@
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
const publicKeyLen = 32
|
||||
|
||||
const (
|
||||
CertBanner = "NEBULA CERTIFICATE"
|
||||
X25519PrivateKeyBanner = "NEBULA X25519 PRIVATE KEY"
|
||||
X25519PublicKeyBanner = "NEBULA X25519 PUBLIC KEY"
|
||||
Ed25519PrivateKeyBanner = "NEBULA ED25519 PRIVATE KEY"
|
||||
Ed25519PublicKeyBanner = "NEBULA ED25519 PUBLIC KEY"
|
||||
)
|
||||
|
||||
type NebulaCertificate struct {
|
||||
Details NebulaCertificateDetails
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
type NebulaCertificateDetails struct {
|
||||
Name string
|
||||
Ips []*net.IPNet
|
||||
Subnets []*net.IPNet
|
||||
Groups []string
|
||||
NotBefore time.Time
|
||||
NotAfter time.Time
|
||||
PublicKey []byte
|
||||
IsCA bool
|
||||
Issuer string
|
||||
|
||||
// Map of groups for faster lookup
|
||||
InvertedGroups map[string]struct{}
|
||||
}
|
||||
|
||||
type m map[string]interface{}
|
||||
|
||||
// UnmarshalNebulaCertificate will unmarshal a protobuf byte representation of a nebula cert
|
||||
func UnmarshalNebulaCertificate(b []byte) (*NebulaCertificate, 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 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 := NebulaCertificate{
|
||||
Details: NebulaCertificateDetails{
|
||||
Name: rc.Details.Name,
|
||||
Groups: make([]string, len(rc.Details.Groups)),
|
||||
Ips: make([]*net.IPNet, len(rc.Details.Ips)/2),
|
||||
Subnets: make([]*net.IPNet, 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,
|
||||
InvertedGroups: make(map[string]struct{}),
|
||||
},
|
||||
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 {
|
||||
return nil, fmt.Errorf("Public key was fewer than 32 bytes; %v", len(rc.Details.PublicKey))
|
||||
}
|
||||
copy(nc.Details.PublicKey, rc.Details.PublicKey)
|
||||
|
||||
for i, rawIp := range rc.Details.Ips {
|
||||
if i%2 == 0 {
|
||||
nc.Details.Ips[i/2] = &net.IPNet{IP: int2ip(rawIp)}
|
||||
} else {
|
||||
nc.Details.Ips[i/2].Mask = net.IPMask(int2ip(rawIp))
|
||||
}
|
||||
}
|
||||
|
||||
for i, rawIp := range rc.Details.Subnets {
|
||||
if i%2 == 0 {
|
||||
nc.Details.Subnets[i/2] = &net.IPNet{IP: int2ip(rawIp)}
|
||||
} else {
|
||||
nc.Details.Subnets[i/2].Mask = net.IPMask(int2ip(rawIp))
|
||||
}
|
||||
}
|
||||
|
||||
for _, g := range rc.Details.Groups {
|
||||
nc.Details.InvertedGroups[g] = struct{}{}
|
||||
}
|
||||
|
||||
return &nc, nil
|
||||
}
|
||||
|
||||
// UnmarshalNebulaCertificateFromPEM will unmarshal the first pem block in a byte array, returning any non consumed data
|
||||
// or an error on failure
|
||||
func UnmarshalNebulaCertificateFromPEM(b []byte) (*NebulaCertificate, []byte, error) {
|
||||
p, r := pem.Decode(b)
|
||||
if p == nil {
|
||||
return nil, r, fmt.Errorf("input did not contain a valid PEM encoded block")
|
||||
}
|
||||
nc, err := UnmarshalNebulaCertificate(p.Bytes)
|
||||
return nc, r, err
|
||||
}
|
||||
|
||||
// MarshalX25519PrivateKey is a simple helper to PEM encode an X25519 private key
|
||||
func MarshalX25519PrivateKey(b []byte) []byte {
|
||||
return pem.EncodeToMemory(&pem.Block{Type: X25519PrivateKeyBanner, Bytes: b})
|
||||
}
|
||||
|
||||
// MarshalEd25519PrivateKey is a simple helper to PEM encode an Ed25519 private key
|
||||
func MarshalEd25519PrivateKey(key ed25519.PrivateKey) []byte {
|
||||
return pem.EncodeToMemory(&pem.Block{Type: Ed25519PrivateKeyBanner, Bytes: key})
|
||||
}
|
||||
|
||||
// UnmarshalX25519PrivateKey will try to pem decode an X25519 private key, returning any other bytes b
|
||||
// or an error on failure
|
||||
func UnmarshalX25519PrivateKey(b []byte) ([]byte, []byte, error) {
|
||||
k, r := pem.Decode(b)
|
||||
if k == nil {
|
||||
return nil, r, fmt.Errorf("input did not contain a valid PEM encoded block")
|
||||
}
|
||||
if k.Type != X25519PrivateKeyBanner {
|
||||
return nil, r, fmt.Errorf("bytes did not contain a proper nebula X25519 private key banner")
|
||||
}
|
||||
if len(k.Bytes) != publicKeyLen {
|
||||
return nil, r, fmt.Errorf("key was not 32 bytes, is invalid X25519 private key")
|
||||
}
|
||||
|
||||
return k.Bytes, r, nil
|
||||
}
|
||||
|
||||
// UnmarshalEd25519PrivateKey will try to pem decode an Ed25519 private key, returning any other bytes b
|
||||
// or an error on failure
|
||||
func UnmarshalEd25519PrivateKey(b []byte) (ed25519.PrivateKey, []byte, error) {
|
||||
k, r := pem.Decode(b)
|
||||
if k == nil {
|
||||
return nil, r, fmt.Errorf("input did not contain a valid PEM encoded block")
|
||||
}
|
||||
if k.Type != Ed25519PrivateKeyBanner {
|
||||
return nil, r, fmt.Errorf("bytes did not contain a proper nebula Ed25519 private key banner")
|
||||
}
|
||||
if len(k.Bytes) != ed25519.PrivateKeySize {
|
||||
return nil, r, fmt.Errorf("key was not 64 bytes, is invalid ed25519 private key")
|
||||
}
|
||||
|
||||
return k.Bytes, r, nil
|
||||
}
|
||||
|
||||
// MarshalX25519PublicKey is a simple helper to PEM encode an X25519 public key
|
||||
func MarshalX25519PublicKey(b []byte) []byte {
|
||||
return pem.EncodeToMemory(&pem.Block{Type: X25519PublicKeyBanner, Bytes: b})
|
||||
}
|
||||
|
||||
// MarshalEd25519PublicKey is a simple helper to PEM encode an Ed25519 public key
|
||||
func MarshalEd25519PublicKey(key ed25519.PublicKey) []byte {
|
||||
return pem.EncodeToMemory(&pem.Block{Type: Ed25519PublicKeyBanner, Bytes: key})
|
||||
}
|
||||
|
||||
// UnmarshalX25519PublicKey will try to pem decode an X25519 public key, returning any other bytes b
|
||||
// or an error on failure
|
||||
func UnmarshalX25519PublicKey(b []byte) ([]byte, []byte, error) {
|
||||
k, r := pem.Decode(b)
|
||||
if k == nil {
|
||||
return nil, r, fmt.Errorf("input did not contain a valid PEM encoded block")
|
||||
}
|
||||
if k.Type != X25519PublicKeyBanner {
|
||||
return nil, r, fmt.Errorf("bytes did not contain a proper nebula X25519 public key banner")
|
||||
}
|
||||
if len(k.Bytes) != publicKeyLen {
|
||||
return nil, r, fmt.Errorf("key was not 32 bytes, is invalid X25519 public key")
|
||||
}
|
||||
|
||||
return k.Bytes, r, nil
|
||||
}
|
||||
|
||||
// UnmarshalEd25519PublicKey will try to pem decode an Ed25519 public key, returning any other bytes b
|
||||
// or an error on failure
|
||||
func UnmarshalEd25519PublicKey(b []byte) (ed25519.PublicKey, []byte, error) {
|
||||
k, r := pem.Decode(b)
|
||||
if k == nil {
|
||||
return nil, r, fmt.Errorf("input did not contain a valid PEM encoded block")
|
||||
}
|
||||
if k.Type != Ed25519PublicKeyBanner {
|
||||
return nil, r, fmt.Errorf("bytes did not contain a proper nebula Ed25519 public key banner")
|
||||
}
|
||||
if len(k.Bytes) != ed25519.PublicKeySize {
|
||||
return nil, r, fmt.Errorf("key was not 32 bytes, is invalid ed25519 public key")
|
||||
}
|
||||
|
||||
return k.Bytes, r, nil
|
||||
}
|
||||
|
||||
// Sign signs a nebula cert with the provided private key
|
||||
func (nc *NebulaCertificate) Sign(key ed25519.PrivateKey) error {
|
||||
b, err := proto.Marshal(nc.getRawDetails())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sig, err := key.Sign(rand.Reader, b, crypto.Hash(0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nc.Signature = sig
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckSignature verifies the signature against the provided public key
|
||||
func (nc *NebulaCertificate) CheckSignature(key ed25519.PublicKey) bool {
|
||||
b, err := proto.Marshal(nc.getRawDetails())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return ed25519.Verify(key, b, nc.Signature)
|
||||
}
|
||||
|
||||
// Expired will return true if the nebula cert is too young or too old compared to the provided time, otherwise false
|
||||
func (nc *NebulaCertificate) Expired(t time.Time) bool {
|
||||
return nc.Details.NotBefore.After(t) || nc.Details.NotAfter.Before(t)
|
||||
}
|
||||
|
||||
// Verify will ensure a certificate is good in all respects (expiry, group membership, signature, cert blacklist, etc)
|
||||
func (nc *NebulaCertificate) Verify(t time.Time, ncp *NebulaCAPool) (bool, error) {
|
||||
if ncp.IsBlacklisted(nc) {
|
||||
return false, fmt.Errorf("certificate has been blacklisted")
|
||||
}
|
||||
|
||||
signer, err := ncp.GetCAForCert(nc)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if signer.Expired(t) {
|
||||
return false, fmt.Errorf("root certificate is expired")
|
||||
}
|
||||
|
||||
if nc.Expired(t) {
|
||||
return false, fmt.Errorf("certificate is expired")
|
||||
}
|
||||
|
||||
if !nc.CheckSignature(signer.Details.PublicKey) {
|
||||
return false, fmt.Errorf("certificate signature did not match")
|
||||
}
|
||||
|
||||
// If the signer has a limited set of groups make sure the cert only contains a subset
|
||||
if len(signer.Details.InvertedGroups) > 0 {
|
||||
for _, g := range nc.Details.Groups {
|
||||
if _, ok := signer.Details.InvertedGroups[g]; !ok {
|
||||
return false, fmt.Errorf("certificate contained a group not present on the signing ca; %s", g)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// VerifyPrivateKey checks that the public key in the Nebula certificate and a supplied private key match
|
||||
func (nc *NebulaCertificate) VerifyPrivateKey(key []byte) error {
|
||||
var dst, key32 [32]byte
|
||||
copy(key32[:], key)
|
||||
curve25519.ScalarBaseMult(&dst, &key32)
|
||||
if !bytes.Equal(dst[:], nc.Details.PublicKey) {
|
||||
return fmt.Errorf("public key in cert and private key supplied don't match")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String will return a pretty printed representation of a nebula cert
|
||||
func (nc *NebulaCertificate) String() string {
|
||||
if nc == nil {
|
||||
return "NebulaCertificate {}\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 += "\t}\n"
|
||||
fp, err := nc.Sha256Sum()
|
||||
if err == nil {
|
||||
s += fmt.Sprintf("\tFingerprint: %s\n", fp)
|
||||
}
|
||||
s += fmt.Sprintf("\tSignature: %x\n", nc.Signature)
|
||||
s += "}"
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// getRawDetails marshals the raw details into protobuf ready struct
|
||||
func (nc *NebulaCertificate) 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,
|
||||
}
|
||||
|
||||
for _, ipNet := range nc.Details.Ips {
|
||||
rd.Ips = append(rd.Ips, ip2int(ipNet.IP), ip2int(ipNet.Mask))
|
||||
}
|
||||
|
||||
for _, ipNet := range nc.Details.Subnets {
|
||||
rd.Subnets = append(rd.Subnets, ip2int(ipNet.IP), ip2int(ipNet.Mask))
|
||||
}
|
||||
|
||||
copy(rd.PublicKey, nc.Details.PublicKey[:])
|
||||
|
||||
// I know, this is terrible
|
||||
rd.Issuer, _ = hex.DecodeString(nc.Details.Issuer)
|
||||
|
||||
return rd
|
||||
}
|
||||
|
||||
// Marshal will marshal a nebula cert into a protobuf byte array
|
||||
func (nc *NebulaCertificate) Marshal() ([]byte, error) {
|
||||
rc := RawNebulaCertificate{
|
||||
Details: nc.getRawDetails(),
|
||||
Signature: nc.Signature,
|
||||
}
|
||||
|
||||
return proto.Marshal(&rc)
|
||||
}
|
||||
|
||||
// MarshalToPEM will marshal a nebula cert into a protobuf byte array and pem encode the result
|
||||
func (nc *NebulaCertificate) MarshalToPEM() ([]byte, error) {
|
||||
b, err := nc.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pem.EncodeToMemory(&pem.Block{Type: CertBanner, Bytes: b}), nil
|
||||
}
|
||||
|
||||
// Sha256Sum calculates a sha-256 sum of the marshaled certificate
|
||||
func (nc *NebulaCertificate) Sha256Sum() (string, error) {
|
||||
b, err := nc.Marshal()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
sum := sha256.Sum256(b)
|
||||
return hex.EncodeToString(sum[:]), nil
|
||||
}
|
||||
|
||||
func (nc *NebulaCertificate) MarshalJSON() ([]byte, error) {
|
||||
toString := func(ips []*net.IPNet) []string {
|
||||
s := []string{}
|
||||
for _, ip := range ips {
|
||||
s = append(s, ip.String())
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
fp, _ := nc.Sha256Sum()
|
||||
jc := m{
|
||||
"details": m{
|
||||
"name": nc.Details.Name,
|
||||
"ips": toString(nc.Details.Ips),
|
||||
"subnets": toString(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,
|
||||
},
|
||||
"fingerprint": fp,
|
||||
"signature": fmt.Sprintf("%x", nc.Signature),
|
||||
}
|
||||
return json.Marshal(jc)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
202
cert/cert.pb.go
Normal file
202
cert/cert.pb.go
Normal file
@ -0,0 +1,202 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: cert.proto
|
||||
|
||||
package cert
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type RawNebulaCertificate struct {
|
||||
Details *RawNebulaCertificateDetails `protobuf:"bytes,1,opt,name=Details,json=details,proto3" json:"Details,omitempty"`
|
||||
Signature []byte `protobuf:"bytes,2,opt,name=Signature,json=signature,proto3" json:"Signature,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *RawNebulaCertificate) Reset() { *m = RawNebulaCertificate{} }
|
||||
func (m *RawNebulaCertificate) String() string { return proto.CompactTextString(m) }
|
||||
func (*RawNebulaCertificate) ProtoMessage() {}
|
||||
func (*RawNebulaCertificate) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_a142e29cbef9b1cf, []int{0}
|
||||
}
|
||||
|
||||
func (m *RawNebulaCertificate) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_RawNebulaCertificate.Unmarshal(m, b)
|
||||
}
|
||||
func (m *RawNebulaCertificate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_RawNebulaCertificate.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *RawNebulaCertificate) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_RawNebulaCertificate.Merge(m, src)
|
||||
}
|
||||
func (m *RawNebulaCertificate) XXX_Size() int {
|
||||
return xxx_messageInfo_RawNebulaCertificate.Size(m)
|
||||
}
|
||||
func (m *RawNebulaCertificate) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_RawNebulaCertificate.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_RawNebulaCertificate proto.InternalMessageInfo
|
||||
|
||||
func (m *RawNebulaCertificate) GetDetails() *RawNebulaCertificateDetails {
|
||||
if m != nil {
|
||||
return m.Details
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *RawNebulaCertificate) GetSignature() []byte {
|
||||
if m != nil {
|
||||
return m.Signature
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type RawNebulaCertificateDetails struct {
|
||||
Name string `protobuf:"bytes,1,opt,name=Name,json=name,proto3" json:"Name,omitempty"`
|
||||
// Ips and Subnets are in big endian 32 bit pairs, 1st the ip, 2nd the mask
|
||||
Ips []uint32 `protobuf:"varint,2,rep,packed,name=Ips,json=ips,proto3" json:"Ips,omitempty"`
|
||||
Subnets []uint32 `protobuf:"varint,3,rep,packed,name=Subnets,json=subnets,proto3" json:"Subnets,omitempty"`
|
||||
Groups []string `protobuf:"bytes,4,rep,name=Groups,json=groups,proto3" json:"Groups,omitempty"`
|
||||
NotBefore int64 `protobuf:"varint,5,opt,name=NotBefore,json=notBefore,proto3" json:"NotBefore,omitempty"`
|
||||
NotAfter int64 `protobuf:"varint,6,opt,name=NotAfter,json=notAfter,proto3" json:"NotAfter,omitempty"`
|
||||
PublicKey []byte `protobuf:"bytes,7,opt,name=PublicKey,json=publicKey,proto3" json:"PublicKey,omitempty"`
|
||||
IsCA bool `protobuf:"varint,8,opt,name=IsCA,json=isCA,proto3" json:"IsCA,omitempty"`
|
||||
// sha-256 of the issuer certificate, if this field is blank the cert is self-signed
|
||||
Issuer []byte `protobuf:"bytes,9,opt,name=Issuer,json=issuer,proto3" json:"Issuer,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *RawNebulaCertificateDetails) Reset() { *m = RawNebulaCertificateDetails{} }
|
||||
func (m *RawNebulaCertificateDetails) String() string { return proto.CompactTextString(m) }
|
||||
func (*RawNebulaCertificateDetails) ProtoMessage() {}
|
||||
func (*RawNebulaCertificateDetails) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_a142e29cbef9b1cf, []int{1}
|
||||
}
|
||||
|
||||
func (m *RawNebulaCertificateDetails) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_RawNebulaCertificateDetails.Unmarshal(m, b)
|
||||
}
|
||||
func (m *RawNebulaCertificateDetails) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_RawNebulaCertificateDetails.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *RawNebulaCertificateDetails) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_RawNebulaCertificateDetails.Merge(m, src)
|
||||
}
|
||||
func (m *RawNebulaCertificateDetails) XXX_Size() int {
|
||||
return xxx_messageInfo_RawNebulaCertificateDetails.Size(m)
|
||||
}
|
||||
func (m *RawNebulaCertificateDetails) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_RawNebulaCertificateDetails.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_RawNebulaCertificateDetails proto.InternalMessageInfo
|
||||
|
||||
func (m *RawNebulaCertificateDetails) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *RawNebulaCertificateDetails) GetIps() []uint32 {
|
||||
if m != nil {
|
||||
return m.Ips
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *RawNebulaCertificateDetails) GetSubnets() []uint32 {
|
||||
if m != nil {
|
||||
return m.Subnets
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *RawNebulaCertificateDetails) GetGroups() []string {
|
||||
if m != nil {
|
||||
return m.Groups
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *RawNebulaCertificateDetails) GetNotBefore() int64 {
|
||||
if m != nil {
|
||||
return m.NotBefore
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *RawNebulaCertificateDetails) GetNotAfter() int64 {
|
||||
if m != nil {
|
||||
return m.NotAfter
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *RawNebulaCertificateDetails) GetPublicKey() []byte {
|
||||
if m != nil {
|
||||
return m.PublicKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *RawNebulaCertificateDetails) GetIsCA() bool {
|
||||
if m != nil {
|
||||
return m.IsCA
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *RawNebulaCertificateDetails) GetIssuer() []byte {
|
||||
if m != nil {
|
||||
return m.Issuer
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*RawNebulaCertificate)(nil), "cert.RawNebulaCertificate")
|
||||
proto.RegisterType((*RawNebulaCertificateDetails)(nil), "cert.RawNebulaCertificateDetails")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("cert.proto", fileDescriptor_a142e29cbef9b1cf) }
|
||||
|
||||
var fileDescriptor_a142e29cbef9b1cf = []byte{
|
||||
// 279 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x90, 0xcf, 0x4a, 0xf4, 0x30,
|
||||
0x14, 0xc5, 0xc9, 0xa4, 0x5f, 0xdb, 0xe4, 0x53, 0x90, 0x20, 0x12, 0xd4, 0x45, 0x9c, 0x55, 0x56,
|
||||
0xb3, 0xd0, 0xa5, 0xab, 0x71, 0x04, 0x29, 0x42, 0x91, 0xcc, 0x13, 0xa4, 0xf5, 0x76, 0x08, 0x74,
|
||||
0x9a, 0x9a, 0x3f, 0x88, 0x8f, 0xee, 0x4e, 0x9a, 0x4e, 0x77, 0xe2, 0xee, 0x9e, 0x5f, 0xce, 0x49,
|
||||
0x4e, 0x2e, 0xa5, 0x2d, 0xb8, 0xb0, 0x19, 0x9d, 0x0d, 0x96, 0x65, 0xd3, 0xbc, 0xfe, 0xa0, 0x97,
|
||||
0x4a, 0x7f, 0xd6, 0xd0, 0xc4, 0x5e, 0xef, 0xc0, 0x05, 0xd3, 0x99, 0x56, 0x07, 0x60, 0x8f, 0xb4,
|
||||
0x78, 0x86, 0xa0, 0x4d, 0xef, 0x39, 0x12, 0x48, 0xfe, 0xbf, 0xbf, 0xdb, 0xa4, 0xec, 0x6f, 0xe6,
|
||||
0x93, 0x51, 0x15, 0xef, 0xf3, 0xc0, 0x6e, 0x29, 0xd9, 0x9b, 0xc3, 0xa0, 0x43, 0x74, 0xc0, 0x57,
|
||||
0x02, 0xc9, 0x33, 0x45, 0xfc, 0x02, 0xd6, 0xdf, 0x88, 0xde, 0xfc, 0x71, 0x0d, 0x63, 0x34, 0xab,
|
||||
0xf5, 0x11, 0xd2, 0xbb, 0x44, 0x65, 0x83, 0x3e, 0x02, 0xbb, 0xa0, 0xb8, 0x1a, 0x3d, 0x5f, 0x09,
|
||||
0x2c, 0xcf, 0x15, 0x36, 0xa3, 0x67, 0x9c, 0x16, 0xfb, 0xd8, 0x0c, 0x10, 0x3c, 0xc7, 0x89, 0x16,
|
||||
0x7e, 0x96, 0xec, 0x8a, 0xe6, 0x2f, 0xce, 0xc6, 0xd1, 0xf3, 0x4c, 0x60, 0x49, 0x54, 0x7e, 0x48,
|
||||
0x6a, 0x6a, 0x55, 0xdb, 0xf0, 0x04, 0x9d, 0x75, 0xc0, 0xff, 0x09, 0x24, 0xb1, 0x22, 0xc3, 0x02,
|
||||
0xd8, 0x35, 0x2d, 0x6b, 0x1b, 0xb6, 0x5d, 0x00, 0xc7, 0xf3, 0x74, 0x58, 0x0e, 0x27, 0x3d, 0x25,
|
||||
0xdf, 0x62, 0xd3, 0x9b, 0xf6, 0x15, 0xbe, 0x78, 0x31, 0xff, 0x67, 0x5c, 0xc0, 0xd4, 0xb7, 0xf2,
|
||||
0xbb, 0x2d, 0x2f, 0x05, 0x92, 0xa5, 0xca, 0x8c, 0xdf, 0x6d, 0xa7, 0x0e, 0x95, 0xf7, 0x11, 0x1c,
|
||||
0x27, 0xc9, 0x9e, 0x9b, 0xa4, 0x9a, 0x3c, 0xed, 0xfe, 0xe1, 0x27, 0x00, 0x00, 0xff, 0xff, 0x2c,
|
||||
0xe3, 0x08, 0x37, 0x89, 0x01, 0x00, 0x00,
|
||||
}
|
27
cert/cert.proto
Normal file
27
cert/cert.proto
Normal file
@ -0,0 +1,27 @@
|
||||
syntax = "proto3";
|
||||
package cert;
|
||||
|
||||
//import "google/protobuf/timestamp.proto";
|
||||
|
||||
message RawNebulaCertificate {
|
||||
RawNebulaCertificateDetails Details = 1;
|
||||
bytes Signature = 2;
|
||||
}
|
||||
|
||||
message RawNebulaCertificateDetails {
|
||||
string Name = 1;
|
||||
|
||||
// Ips and Subnets are in big endian 32 bit pairs, 1st the ip, 2nd the mask
|
||||
repeated uint32 Ips = 2;
|
||||
repeated uint32 Subnets = 3;
|
||||
|
||||
repeated string Groups = 4;
|
||||
int64 NotBefore = 5;
|
||||
int64 NotAfter = 6;
|
||||
bytes PublicKey = 7;
|
||||
|
||||
bool IsCA = 8;
|
||||
|
||||
// sha-256 of the issuer certificate, if this field is blank the cert is self-signed
|
||||
bytes Issuer = 9;
|
||||
}
|
373
cert/cert_test.go
Normal file
373
cert/cert_test.go
Normal file
@ -0,0 +1,373 @@
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
func TestMarshalingNebulaCertificate(t *testing.T) {
|
||||
before := time.Now().Add(time.Second * -60).Round(time.Second)
|
||||
after := time.Now().Add(time.Second * 60).Round(time.Second)
|
||||
pubKey := []byte("1234567890abcedfghij1234567890ab")
|
||||
|
||||
nc := NebulaCertificate{
|
||||
Details: NebulaCertificateDetails{
|
||||
Name: "testing",
|
||||
Ips: []*net.IPNet{
|
||||
{IP: net.ParseIP("10.1.1.1"), Mask: net.IPMask(net.ParseIP("255.255.255.0"))},
|
||||
{IP: net.ParseIP("10.1.1.2"), Mask: net.IPMask(net.ParseIP("255.255.0.0"))},
|
||||
{IP: net.ParseIP("10.1.1.3"), Mask: net.IPMask(net.ParseIP("255.0.255.0"))},
|
||||
},
|
||||
Subnets: []*net.IPNet{
|
||||
{IP: net.ParseIP("9.1.1.1"), Mask: net.IPMask(net.ParseIP("255.0.255.0"))},
|
||||
{IP: net.ParseIP("9.1.1.2"), Mask: net.IPMask(net.ParseIP("255.255.255.0"))},
|
||||
{IP: net.ParseIP("9.1.1.3"), Mask: net.IPMask(net.ParseIP("255.255.0.0"))},
|
||||
},
|
||||
Groups: []string{"test-group1", "test-group2", "test-group3"},
|
||||
NotBefore: before,
|
||||
NotAfter: after,
|
||||
PublicKey: pubKey,
|
||||
IsCA: false,
|
||||
Issuer: "1234567890abcedfghij1234567890ab",
|
||||
},
|
||||
Signature: []byte("1234567890abcedfghij1234567890ab"),
|
||||
}
|
||||
|
||||
b, err := nc.Marshal()
|
||||
assert.Nil(t, err)
|
||||
t.Log("Cert size:", len(b))
|
||||
|
||||
nc2, err := UnmarshalNebulaCertificate(b)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, nc.Signature, nc2.Signature)
|
||||
assert.Equal(t, nc.Details.Name, nc2.Details.Name)
|
||||
assert.Equal(t, nc.Details.NotBefore, nc2.Details.NotBefore)
|
||||
assert.Equal(t, nc.Details.NotAfter, nc2.Details.NotAfter)
|
||||
assert.Equal(t, nc.Details.PublicKey, nc2.Details.PublicKey)
|
||||
assert.Equal(t, nc.Details.IsCA, nc2.Details.IsCA)
|
||||
|
||||
// IP byte arrays can be 4 or 16 in length so we have to go this route
|
||||
assert.Equal(t, len(nc.Details.Ips), len(nc2.Details.Ips))
|
||||
for i, wIp := range nc.Details.Ips {
|
||||
assert.Equal(t, wIp.String(), nc2.Details.Ips[i].String())
|
||||
}
|
||||
|
||||
assert.Equal(t, len(nc.Details.Subnets), len(nc2.Details.Subnets))
|
||||
for i, wIp := range nc.Details.Subnets {
|
||||
assert.Equal(t, wIp.String(), nc2.Details.Subnets[i].String())
|
||||
}
|
||||
|
||||
assert.EqualValues(t, nc.Details.Groups, nc2.Details.Groups)
|
||||
}
|
||||
|
||||
func TestNebulaCertificate_Sign(t *testing.T) {
|
||||
before := time.Now().Add(time.Second * -60).Round(time.Second)
|
||||
after := time.Now().Add(time.Second * 60).Round(time.Second)
|
||||
pubKey := []byte("1234567890abcedfghij1234567890ab")
|
||||
|
||||
nc := NebulaCertificate{
|
||||
Details: NebulaCertificateDetails{
|
||||
Name: "testing",
|
||||
Ips: []*net.IPNet{
|
||||
{IP: net.ParseIP("10.1.1.1"), Mask: net.IPMask(net.ParseIP("255.255.255.0"))},
|
||||
{IP: net.ParseIP("10.1.1.2"), Mask: net.IPMask(net.ParseIP("255.255.0.0"))},
|
||||
{IP: net.ParseIP("10.1.1.3"), Mask: net.IPMask(net.ParseIP("255.0.255.0"))},
|
||||
},
|
||||
Subnets: []*net.IPNet{
|
||||
{IP: net.ParseIP("9.1.1.1"), Mask: net.IPMask(net.ParseIP("255.0.255.0"))},
|
||||
{IP: net.ParseIP("9.1.1.2"), Mask: net.IPMask(net.ParseIP("255.255.255.0"))},
|
||||
{IP: net.ParseIP("9.1.1.3"), Mask: net.IPMask(net.ParseIP("255.255.0.0"))},
|
||||
},
|
||||
Groups: []string{"test-group1", "test-group2", "test-group3"},
|
||||
NotBefore: before,
|
||||
NotAfter: after,
|
||||
PublicKey: pubKey,
|
||||
IsCA: false,
|
||||
Issuer: "1234567890abcedfghij1234567890ab",
|
||||
},
|
||||
}
|
||||
|
||||
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, nc.CheckSignature(pub))
|
||||
assert.Nil(t, nc.Sign(priv))
|
||||
assert.True(t, nc.CheckSignature(pub))
|
||||
|
||||
b, err := nc.Marshal()
|
||||
assert.Nil(t, err)
|
||||
t.Log("Cert size:", len(b))
|
||||
}
|
||||
|
||||
func TestNebulaCertificate_Expired(t *testing.T) {
|
||||
nc := NebulaCertificate{
|
||||
Details: NebulaCertificateDetails{
|
||||
NotBefore: time.Now().Add(time.Second * -60).Round(time.Second),
|
||||
NotAfter: time.Now().Add(time.Second * 60).Round(time.Second),
|
||||
},
|
||||
}
|
||||
|
||||
assert.True(t, nc.Expired(time.Now().Add(time.Hour)))
|
||||
assert.True(t, nc.Expired(time.Now().Add(-time.Hour)))
|
||||
assert.False(t, nc.Expired(time.Now()))
|
||||
}
|
||||
|
||||
func TestNebulaCertificate_MarshalJSON(t *testing.T) {
|
||||
time.Local = time.UTC
|
||||
pubKey := []byte("1234567890abcedfghij1234567890ab")
|
||||
|
||||
nc := NebulaCertificate{
|
||||
Details: NebulaCertificateDetails{
|
||||
Name: "testing",
|
||||
Ips: []*net.IPNet{
|
||||
{IP: net.ParseIP("10.1.1.1"), Mask: net.IPMask(net.ParseIP("255.255.255.0"))},
|
||||
{IP: net.ParseIP("10.1.1.2"), Mask: net.IPMask(net.ParseIP("255.255.0.0"))},
|
||||
{IP: net.ParseIP("10.1.1.3"), Mask: net.IPMask(net.ParseIP("255.0.255.0"))},
|
||||
},
|
||||
Subnets: []*net.IPNet{
|
||||
{IP: net.ParseIP("9.1.1.1"), Mask: net.IPMask(net.ParseIP("255.0.255.0"))},
|
||||
{IP: net.ParseIP("9.1.1.2"), Mask: net.IPMask(net.ParseIP("255.255.255.0"))},
|
||||
{IP: net.ParseIP("9.1.1.3"), Mask: net.IPMask(net.ParseIP("255.255.0.0"))},
|
||||
},
|
||||
Groups: []string{"test-group1", "test-group2", "test-group3"},
|
||||
NotBefore: time.Date(1, 0, 0, 1, 0, 0, 0, time.UTC),
|
||||
NotAfter: time.Date(1, 0, 0, 2, 0, 0, 0, time.UTC),
|
||||
PublicKey: pubKey,
|
||||
IsCA: false,
|
||||
Issuer: "1234567890abcedfghij1234567890ab",
|
||||
},
|
||||
Signature: []byte("1234567890abcedfghij1234567890ab"),
|
||||
}
|
||||
|
||||
b, err := nc.MarshalJSON()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(
|
||||
t,
|
||||
"{\"details\":{\"groups\":[\"test-group1\",\"test-group2\",\"test-group3\"],\"ips\":[\"10.1.1.1/24\",\"10.1.1.2/16\",\"10.1.1.3/ff00ff00\"],\"isCa\":false,\"issuer\":\"1234567890abcedfghij1234567890ab\",\"name\":\"testing\",\"notAfter\":\"0000-11-30T02:00:00Z\",\"notBefore\":\"0000-11-30T01:00:00Z\",\"publicKey\":\"313233343536373839306162636564666768696a313233343536373839306162\",\"subnets\":[\"9.1.1.1/ff00ff00\",\"9.1.1.2/24\",\"9.1.1.3/16\"]},\"fingerprint\":\"26cb1c30ad7872c804c166b5150fa372f437aa3856b04edb4334b4470ec728e4\",\"signature\":\"313233343536373839306162636564666768696a313233343536373839306162\"}",
|
||||
string(b),
|
||||
)
|
||||
}
|
||||
|
||||
func TestNebulaCertificate_Verify(t *testing.T) {
|
||||
ca, _, caKey, err := newTestCaCert()
|
||||
assert.Nil(t, err)
|
||||
|
||||
c, _, _, err := newTestCert(ca, caKey)
|
||||
assert.Nil(t, err)
|
||||
|
||||
h, err := ca.Sha256Sum()
|
||||
assert.Nil(t, err)
|
||||
|
||||
caPool := NewCAPool()
|
||||
caPool.CAs[h] = ca
|
||||
|
||||
f, err := c.Sha256Sum()
|
||||
assert.Nil(t, err)
|
||||
caPool.BlacklistFingerprint(f)
|
||||
|
||||
v, err := c.Verify(time.Now(), caPool)
|
||||
assert.False(t, v)
|
||||
assert.EqualError(t, err, "certificate has been blacklisted")
|
||||
|
||||
caPool.ResetCertBlacklist()
|
||||
v, err = c.Verify(time.Now(), caPool)
|
||||
assert.True(t, v)
|
||||
assert.Nil(t, err)
|
||||
|
||||
v, err = c.Verify(time.Now().Add(time.Hour*1000), caPool)
|
||||
assert.False(t, v)
|
||||
assert.EqualError(t, err, "root certificate is expired")
|
||||
}
|
||||
|
||||
func TestNebulaVerifyPrivateKey(t *testing.T) {
|
||||
ca, _, caKey, err := newTestCaCert()
|
||||
assert.Nil(t, err)
|
||||
|
||||
c, _, priv, err := newTestCert(ca, caKey)
|
||||
err = c.VerifyPrivateKey(priv)
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, priv2 := x25519Keypair()
|
||||
err = c.VerifyPrivateKey(priv2)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
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-----
|
||||
|
||||
`
|
||||
|
||||
rootCA := NebulaCertificate{
|
||||
Details: NebulaCertificateDetails{
|
||||
Name: "nebula root ca",
|
||||
},
|
||||
}
|
||||
|
||||
rootCA01 := NebulaCertificate{
|
||||
Details: NebulaCertificateDetails{
|
||||
Name: "nebula root ca 01",
|
||||
},
|
||||
}
|
||||
|
||||
p, err := NewCAPoolFromBytes([]byte(noNewLines))
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, p.CAs[string("c9bfaf7ce8e84b2eeda2e27b469f4b9617bde192efd214b68891ecda6ed49522")].Details.Name, rootCA.Details.Name)
|
||||
assert.Equal(t, p.CAs[string("5c9c3f23e7ee7fe97637cbd3a0a5b854154d1d9aaaf7b566a51f4a88f76b64cd")].Details.Name, rootCA01.Details.Name)
|
||||
|
||||
pp, err := NewCAPoolFromBytes([]byte(withNewLines))
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, pp.CAs[string("c9bfaf7ce8e84b2eeda2e27b469f4b9617bde192efd214b68891ecda6ed49522")].Details.Name, rootCA.Details.Name)
|
||||
assert.Equal(t, pp.CAs[string("5c9c3f23e7ee7fe97637cbd3a0a5b854154d1d9aaaf7b566a51f4a88f76b64cd")].Details.Name, rootCA01.Details.Name)
|
||||
}
|
||||
|
||||
// Ensure that upgrading the protobuf library does not change how certificates
|
||||
// are marshalled, since this would break signature verification
|
||||
func TestMarshalingNebulaCertificateConsistency(t *testing.T) {
|
||||
before := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
|
||||
after := time.Date(2017, time.January, 18, 28, 40, 0, 0, time.UTC)
|
||||
pubKey := []byte("1234567890abcedfghij1234567890ab")
|
||||
|
||||
nc := NebulaCertificate{
|
||||
Details: NebulaCertificateDetails{
|
||||
Name: "testing",
|
||||
Ips: []*net.IPNet{
|
||||
{IP: net.ParseIP("10.1.1.1"), Mask: net.IPMask(net.ParseIP("255.255.255.0"))},
|
||||
{IP: net.ParseIP("10.1.1.2"), Mask: net.IPMask(net.ParseIP("255.255.0.0"))},
|
||||
{IP: net.ParseIP("10.1.1.3"), Mask: net.IPMask(net.ParseIP("255.0.255.0"))},
|
||||
},
|
||||
Subnets: []*net.IPNet{
|
||||
{IP: net.ParseIP("9.1.1.1"), Mask: net.IPMask(net.ParseIP("255.0.255.0"))},
|
||||
{IP: net.ParseIP("9.1.1.2"), Mask: net.IPMask(net.ParseIP("255.255.255.0"))},
|
||||
{IP: net.ParseIP("9.1.1.3"), Mask: net.IPMask(net.ParseIP("255.255.0.0"))},
|
||||
},
|
||||
Groups: []string{"test-group1", "test-group2", "test-group3"},
|
||||
NotBefore: before,
|
||||
NotAfter: after,
|
||||
PublicKey: pubKey,
|
||||
IsCA: false,
|
||||
Issuer: "1234567890abcedfghij1234567890ab",
|
||||
},
|
||||
Signature: []byte("1234567890abcedfghij1234567890ab"),
|
||||
}
|
||||
|
||||
b, err := nc.Marshal()
|
||||
assert.Nil(t, err)
|
||||
t.Log("Cert size:", len(b))
|
||||
assert.Equal(t, "0aa2010a0774657374696e67121b8182845080feffff0f828284508080fcff0f8382845080fe83f80f1a1b8182844880fe83f80f8282844880feffff0f838284488080fcff0f220b746573742d67726f757031220b746573742d67726f757032220b746573742d67726f75703328f0e0e7d70430a08681c4053a20313233343536373839306162636564666768696a3132333435363738393061624a081234567890abcedf1220313233343536373839306162636564666768696a313233343536373839306162", fmt.Sprintf("%x", b))
|
||||
|
||||
b, err = proto.Marshal(nc.getRawDetails())
|
||||
assert.Nil(t, err)
|
||||
t.Log("Raw cert size:", len(b))
|
||||
assert.Equal(t, "0a0774657374696e67121b8182845080feffff0f828284508080fcff0f8382845080fe83f80f1a1b8182844880fe83f80f8282844880feffff0f838284488080fcff0f220b746573742d67726f757031220b746573742d67726f757032220b746573742d67726f75703328f0e0e7d70430a08681c4053a20313233343536373839306162636564666768696a3132333435363738393061624a081234567890abcedf", fmt.Sprintf("%x", b))
|
||||
}
|
||||
|
||||
func newTestCaCert() (*NebulaCertificate, []byte, []byte, error) {
|
||||
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||
before := time.Now().Add(time.Second * -60).Round(time.Second)
|
||||
after := time.Now().Add(time.Second * 60).Round(time.Second)
|
||||
|
||||
nc := &NebulaCertificate{
|
||||
Details: NebulaCertificateDetails{
|
||||
Name: "test ca",
|
||||
NotBefore: before,
|
||||
NotAfter: after,
|
||||
PublicKey: pub,
|
||||
IsCA: true,
|
||||
},
|
||||
}
|
||||
|
||||
err = nc.Sign(priv)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return nc, pub, priv, nil
|
||||
}
|
||||
|
||||
func newTestCert(ca *NebulaCertificate, key []byte) (*NebulaCertificate, []byte, []byte, error) {
|
||||
issuer, err := ca.Sha256Sum()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
before := time.Now().Add(time.Second * -60).Round(time.Second)
|
||||
after := time.Now().Add(time.Second * 60).Round(time.Second)
|
||||
pub, rawPriv := x25519Keypair()
|
||||
|
||||
nc := &NebulaCertificate{
|
||||
Details: NebulaCertificateDetails{
|
||||
Name: "testing",
|
||||
Ips: []*net.IPNet{
|
||||
{IP: net.ParseIP("10.1.1.1"), Mask: net.IPMask(net.ParseIP("255.255.255.0"))},
|
||||
{IP: net.ParseIP("10.1.1.2"), Mask: net.IPMask(net.ParseIP("255.255.0.0"))},
|
||||
{IP: net.ParseIP("10.1.1.3"), Mask: net.IPMask(net.ParseIP("255.0.255.0"))},
|
||||
},
|
||||
Subnets: []*net.IPNet{
|
||||
{IP: net.ParseIP("9.1.1.1"), Mask: net.IPMask(net.ParseIP("255.0.255.0"))},
|
||||
{IP: net.ParseIP("9.1.1.2"), Mask: net.IPMask(net.ParseIP("255.255.255.0"))},
|
||||
{IP: net.ParseIP("9.1.1.3"), Mask: net.IPMask(net.ParseIP("255.255.0.0"))},
|
||||
},
|
||||
Groups: []string{"test-group1", "test-group2", "test-group3"},
|
||||
NotBefore: before,
|
||||
NotAfter: after,
|
||||
PublicKey: pub,
|
||||
IsCA: false,
|
||||
Issuer: issuer,
|
||||
},
|
||||
}
|
||||
|
||||
err = nc.Sign(key)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return nc, pub, rawPriv, nil
|
||||
}
|
||||
|
||||
func x25519Keypair() ([]byte, []byte) {
|
||||
var pubkey, privkey [32]byte
|
||||
if _, err := io.ReadFull(rand.Reader, privkey[:]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
curve25519.ScalarBaseMult(&pubkey, &privkey)
|
||||
return pubkey[:], privkey[:]
|
||||
}
|
Reference in New Issue
Block a user