| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033 |
- package cert
- import (
- "bytes"
- "crypto/ecdh"
- "crypto/ecdsa"
- "crypto/ed25519"
- "crypto/elliptic"
- "crypto/rand"
- "crypto/sha256"
- "encoding/binary"
- "encoding/hex"
- "encoding/json"
- "encoding/pem"
- "errors"
- "fmt"
- "math"
- "math/big"
- "net"
- "sync/atomic"
- "time"
- "golang.org/x/crypto/curve25519"
- "google.golang.org/protobuf/proto"
- )
- const publicKeyLen = 32
- const (
- CertBanner = "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"
- )
- type NebulaCertificate struct {
- Details NebulaCertificateDetails
- Signature []byte
- // the cached hex string of the calculated sha256sum
- // for VerifyWithCache
- sha256sum atomic.Pointer[string]
- // the cached public key bytes if they were verified as the signer
- // for VerifyWithCache
- signatureVerified atomic.Pointer[[]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{}
- Curve Curve
- }
- type NebulaEncryptedData struct {
- EncryptionMetadata NebulaEncryptionMetadata
- Ciphertext []byte
- }
- type NebulaEncryptionMetadata struct {
- EncryptionAlgorithm string
- Argon2Parameters Argon2Parameters
- }
- type m map[string]interface{}
- // Returned if we try to unmarshal an encrypted private key without a passphrase
- var ErrPrivateKeyEncrypted = errors.New("private key must be decrypted")
- // 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 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 := 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{}),
- 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 {
- 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")
- }
- if p.Type == CertificateV2Banner {
- return nil, r, fmt.Errorf("%w: %s", ErrInvalidPEMCertificateUnsupported, p.Type)
- }
- if p.Type != CertBanner {
- return nil, r, fmt.Errorf("bytes did not contain a proper nebula certificate banner")
- }
- nc, err := UnmarshalNebulaCertificate(p.Bytes)
- return nc, r, err
- }
- func MarshalPrivateKey(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 MarshalSigningPrivateKey(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
- }
- }
- // 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})
- }
- func UnmarshalPrivateKey(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 nebula 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 UnmarshalSigningPrivateKey(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 nebula Ed25519/ECDSA private key banner")
- }
- return k.Bytes, r, curve, 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)
- }
- }
- // 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 == EncryptedEd25519PrivateKeyBanner {
- return nil, r, ErrPrivateKeyEncrypted
- } else 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
- }
- // 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: rune(params.Version),
- Memory: uint32(params.Memory),
- Parallelism: uint8(params.Parallelism),
- Iterations: uint32(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
- }
- func MarshalPublicKey(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
- }
- }
- // 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})
- }
- func UnmarshalPublicKey(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:
- 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 nebula 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
- }
- // 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(curve Curve, key []byte) error {
- if curve != nc.Details.Curve {
- return fmt.Errorf("curve in cert and private key supplied don't match")
- }
- b, err := proto.Marshal(nc.getRawDetails())
- if err != nil {
- return err
- }
- var sig []byte
- switch curve {
- case Curve_CURVE25519:
- signer := ed25519.PrivateKey(key)
- sig = ed25519.Sign(signer, b)
- case Curve_P256:
- 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 err
- }
- default:
- return fmt.Errorf("invalid curve: %s", nc.Details.Curve)
- }
- nc.Signature = sig
- return nil
- }
- // CheckSignature verifies the signature against the provided public key
- func (nc *NebulaCertificate) CheckSignature(key []byte) bool {
- b, err := proto.Marshal(nc.getRawDetails())
- if err != nil {
- return false
- }
- switch nc.Details.Curve {
- case Curve_CURVE25519:
- return ed25519.Verify(ed25519.PublicKey(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
- }
- }
- // NOTE: This uses an internal cache that will not be invalidated automatically
- // if you manually change any fields in the NebulaCertificate.
- func (nc *NebulaCertificate) checkSignatureWithCache(key []byte, useCache bool) bool {
- if !useCache {
- return nc.CheckSignature(key)
- }
- if v := nc.signatureVerified.Load(); v != nil {
- return bytes.Equal(*v, key)
- }
- verified := nc.CheckSignature(key)
- if verified {
- keyCopy := make([]byte, len(key))
- copy(keyCopy, key)
- nc.signatureVerified.Store(&keyCopy)
- }
- return verified
- }
- // 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 blocklist, etc)
- func (nc *NebulaCertificate) Verify(t time.Time, ncp *NebulaCAPool) (bool, error) {
- return nc.verify(t, ncp, false)
- }
- // VerifyWithCache will ensure a certificate is good in all respects (expiry, group membership, signature, cert blocklist, etc)
- //
- // NOTE: This uses an internal cache that will not be invalidated automatically
- // if you manually change any fields in the NebulaCertificate.
- func (nc *NebulaCertificate) VerifyWithCache(t time.Time, ncp *NebulaCAPool) (bool, error) {
- return nc.verify(t, ncp, true)
- }
- // ResetCache resets the cache used by VerifyWithCache.
- func (nc *NebulaCertificate) ResetCache() {
- nc.sha256sum.Store(nil)
- nc.signatureVerified.Store(nil)
- }
- // Verify will ensure a certificate is good in all respects (expiry, group membership, signature, cert blocklist, etc)
- func (nc *NebulaCertificate) verify(t time.Time, ncp *NebulaCAPool, useCache bool) (bool, error) {
- if ncp.isBlocklistedWithCache(nc, useCache) {
- return false, ErrBlockListed
- }
- signer, err := ncp.GetCAForCert(nc)
- if err != nil {
- return false, err
- }
- if signer.Expired(t) {
- return false, ErrRootExpired
- }
- if nc.Expired(t) {
- return false, ErrExpired
- }
- if !nc.checkSignatureWithCache(signer.Details.PublicKey, useCache) {
- return false, ErrSignatureMismatch
- }
- if err := nc.CheckRootConstrains(signer); err != nil {
- return false, err
- }
- return true, nil
- }
- // CheckRootConstrains returns an error if the certificate violates constraints set on the root (groups, ips, subnets)
- func (nc *NebulaCertificate) CheckRootConstrains(signer *NebulaCertificate) error {
- // Make sure this cert wasn't valid before the root
- if signer.Details.NotAfter.Before(nc.Details.NotAfter) {
- return fmt.Errorf("certificate expires after signing certificate")
- }
- // Make sure this cert isn't valid after the root
- if signer.Details.NotBefore.After(nc.Details.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
- if len(signer.Details.InvertedGroups) > 0 {
- for _, g := range nc.Details.Groups {
- if _, ok := signer.Details.InvertedGroups[g]; !ok {
- 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
- if len(signer.Details.Ips) > 0 {
- for _, ip := range nc.Details.Ips {
- if !netMatch(ip, signer.Details.Ips) {
- return fmt.Errorf("certificate contained an ip assignment outside the limitations of the signing ca: %s", ip.String())
- }
- }
- }
- // If the signer has a limited set of subnet ranges to issue from make sure the cert only contains a subset
- if len(signer.Details.Subnets) > 0 {
- for _, subnet := range nc.Details.Subnets {
- if !netMatch(subnet, signer.Details.Subnets) {
- return fmt.Errorf("certificate contained a subnet assignment outside the limitations of the signing ca: %s", subnet)
- }
- }
- }
- return nil
- }
- // VerifyPrivateKey checks that the public key in the Nebula certificate and a supplied private key match
- func (nc *NebulaCertificate) VerifyPrivateKey(curve Curve, key []byte) error {
- if 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")
- }
- 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
- }
- // 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 += fmt.Sprintf("\t\tCurve: %s\n", nc.Details.Curve)
- 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,
- Curve: nc.Details.Curve,
- }
- 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
- }
- // NOTE: This uses an internal cache that will not be invalidated automatically
- // if you manually change any fields in the NebulaCertificate.
- func (nc *NebulaCertificate) sha256SumWithCache(useCache bool) (string, error) {
- if !useCache {
- return nc.Sha256Sum()
- }
- if s := nc.sha256sum.Load(); s != nil {
- return *s, nil
- }
- s, err := nc.Sha256Sum()
- if err != nil {
- return s, err
- }
- nc.sha256sum.Store(&s)
- return s, 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,
- "curve": nc.Details.Curve.String(),
- },
- "fingerprint": fp,
- "signature": fmt.Sprintf("%x", nc.Signature),
- }
- return json.Marshal(jc)
- }
- //func (nc *NebulaCertificate) Copy() *NebulaCertificate {
- // r, err := nc.Marshal()
- // if err != nil {
- // //TODO
- // return nil
- // }
- //
- // c, err := UnmarshalNebulaCertificate(r)
- // return c
- //}
- func (nc *NebulaCertificate) Copy() *NebulaCertificate {
- c := &NebulaCertificate{
- Details: NebulaCertificateDetails{
- Name: nc.Details.Name,
- Groups: make([]string, len(nc.Details.Groups)),
- Ips: make([]*net.IPNet, len(nc.Details.Ips)),
- Subnets: make([]*net.IPNet, 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,
- InvertedGroups: make(map[string]struct{}, len(nc.Details.InvertedGroups)),
- },
- 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] = &net.IPNet{
- IP: make(net.IP, len(p.IP)),
- Mask: make(net.IPMask, len(p.Mask)),
- }
- copy(c.Details.Ips[i].IP, p.IP)
- copy(c.Details.Ips[i].Mask, p.Mask)
- }
- for i, p := range nc.Details.Subnets {
- c.Details.Subnets[i] = &net.IPNet{
- IP: make(net.IP, len(p.IP)),
- Mask: make(net.IPMask, len(p.Mask)),
- }
- copy(c.Details.Subnets[i].IP, p.IP)
- copy(c.Details.Subnets[i].Mask, p.Mask)
- }
- for g := range nc.Details.InvertedGroups {
- c.Details.InvertedGroups[g] = struct{}{}
- }
- return c
- }
- func netMatch(certIp *net.IPNet, rootIps []*net.IPNet) bool {
- for _, net := range rootIps {
- if net.Contains(certIp.IP) && maskContains(net.Mask, certIp.Mask) {
- return true
- }
- }
- return false
- }
- func maskContains(caMask, certMask net.IPMask) bool {
- caM := maskTo4(caMask)
- cM := maskTo4(certMask)
- // Make sure forcing to ipv4 didn't nuke us
- if caM == nil || cM == nil {
- return false
- }
- // Make sure the cert mask is not greater than the ca mask
- for i := 0; i < len(caMask); i++ {
- if caM[i] > cM[i] {
- return false
- }
- }
- return true
- }
- func maskTo4(ip net.IPMask) net.IPMask {
- if len(ip) == net.IPv4len {
- return ip
- }
- if len(ip) == net.IPv6len && isZeros(ip[0:10]) && ip[10] == 0xff && ip[11] == 0xff {
- return ip[12:16]
- }
- return nil
- }
- func isZeros(b []byte) bool {
- for i := 0; i < len(b); i++ {
- if b[i] != 0 {
- return false
- }
- }
- return true
- }
- 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
- }
|