123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062 |
- 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"
- "github.com/slackhq/nebula/pkclient"
- "golang.org/x/crypto/curve25519"
- "google.golang.org/protobuf/proto"
- )
- const publicKeyLen = 32
- const (
- CertBanner = "NEBULA CERTIFICATE"
- 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
- Pkcs11Backed bool
- 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 != 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
- }
- // SignPkcs11 signs a nebula cert with the provided private key
- func (nc *NebulaCertificate) SignPkcs11(curve Curve, client *pkclient.PKClient) error {
- if !nc.Pkcs11Backed {
- return fmt.Errorf("certificate is not PKCS#11 backed")
- }
- if curve != nc.Details.Curve {
- return fmt.Errorf("curve in cert and private key supplied don't match")
- }
- if curve != Curve_P256 {
- return fmt.Errorf("only P256 is supported by PKCS#11")
- }
- b, err := proto.Marshal(nc.getRawDetails())
- if err != nil {
- return err
- }
- sig, err := client.SignASN1(b)
- if err != nil {
- return err
- }
- 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 nc.Pkcs11Backed {
- return nil //todo!
- }
- 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
- }
|