diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index 18a62f1..e1b1d59 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -53,4 +53,12 @@ jobs: working-directory: ./.github/workflows/smoke run: ./smoke-relay.sh + - name: setup docker image for P256 + working-directory: ./.github/workflows/smoke + run: NAME="smoke-p256" CURVE=P256 ./build.sh + + - name: run smoke-p256 + working-directory: ./.github/workflows/smoke + run: NAME="smoke-p256" ./smoke.sh + timeout-minutes: 10 diff --git a/.github/workflows/smoke/build.sh b/.github/workflows/smoke/build.sh index 0c20b3f..00b2346 100755 --- a/.github/workflows/smoke/build.sh +++ b/.github/workflows/smoke/build.sh @@ -29,11 +29,11 @@ mkdir ./build OUTBOUND='[{"port": "any", "proto": "icmp", "group": "lighthouse"}]' \ ../genconfig.sh >host4.yml - ../../../../nebula-cert ca -name "Smoke Test" + ../../../../nebula-cert ca -curve "${CURVE:-25519}" -name "Smoke Test" ../../../../nebula-cert sign -name "lighthouse1" -groups "lighthouse,lighthouse1" -ip "192.168.100.1/24" ../../../../nebula-cert sign -name "host2" -groups "host,host2" -ip "192.168.100.2/24" ../../../../nebula-cert sign -name "host3" -groups "host,host3" -ip "192.168.100.3/24" ../../../../nebula-cert sign -name "host4" -groups "host,host4" -ip "192.168.100.4/24" ) -sudo docker build -t nebula:smoke . +sudo docker build -t "nebula:${NAME:-smoke}" . diff --git a/.github/workflows/smoke/smoke.sh b/.github/workflows/smoke/smoke.sh index 836e61a..4aa8029 100755 --- a/.github/workflows/smoke/smoke.sh +++ b/.github/workflows/smoke/smoke.sh @@ -20,18 +20,20 @@ cleanup() { trap cleanup EXIT -sudo docker run --name lighthouse1 --rm nebula:smoke -config lighthouse1.yml -test -sudo docker run --name host2 --rm nebula:smoke -config host2.yml -test -sudo docker run --name host3 --rm nebula:smoke -config host3.yml -test -sudo docker run --name host4 --rm nebula:smoke -config host4.yml -test +CONTAINER="nebula:${NAME:-smoke}" -sudo docker run --name lighthouse1 --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm nebula:smoke -config lighthouse1.yml 2>&1 | tee logs/lighthouse1 | sed -u 's/^/ [lighthouse1] /' & +sudo docker run --name lighthouse1 --rm "$CONTAINER" -config lighthouse1.yml -test +sudo docker run --name host2 --rm "$CONTAINER" -config host2.yml -test +sudo docker run --name host3 --rm "$CONTAINER" -config host3.yml -test +sudo docker run --name host4 --rm "$CONTAINER" -config host4.yml -test + +sudo docker run --name lighthouse1 --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm "$CONTAINER" -config lighthouse1.yml 2>&1 | tee logs/lighthouse1 | sed -u 's/^/ [lighthouse1] /' & sleep 1 -sudo docker run --name host2 --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm nebula:smoke -config host2.yml 2>&1 | tee logs/host2 | sed -u 's/^/ [host2] /' & +sudo docker run --name host2 --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm "$CONTAINER" -config host2.yml 2>&1 | tee logs/host2 | sed -u 's/^/ [host2] /' & sleep 1 -sudo docker run --name host3 --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm nebula:smoke -config host3.yml 2>&1 | tee logs/host3 | sed -u 's/^/ [host3] /' & +sudo docker run --name host3 --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm "$CONTAINER" -config host3.yml 2>&1 | tee logs/host3 | sed -u 's/^/ [host3] /' & sleep 1 -sudo docker run --name host4 --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm nebula:smoke -config host4.yml 2>&1 | tee logs/host4 | sed -u 's/^/ [host4] /' & +sudo docker run --name host4 --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN --rm "$CONTAINER" -config host4.yml 2>&1 | tee logs/host4 | sed -u 's/^/ [host4] /' & sleep 1 # grab tcpdump pcaps for debugging diff --git a/Makefile b/Makefile index 5e447a6..512fdc2 100644 --- a/Makefile +++ b/Makefile @@ -179,6 +179,8 @@ bin-docker: bin build/linux-amd64/nebula build/linux-amd64/nebula-cert smoke-docker: bin-docker cd .github/workflows/smoke/ && ./build.sh cd .github/workflows/smoke/ && ./smoke.sh + cd .github/workflows/smoke/ && NAME="smoke-p256" CURVE="P256" ./build.sh + cd .github/workflows/smoke/ && NAME="smoke-p256" ./smoke.sh smoke-relay-docker: bin-docker cd .github/workflows/smoke/ && ./build-relay.sh diff --git a/cert.go b/cert.go index be7bb6a..bbd29c6 100644 --- a/cert.go +++ b/cert.go @@ -66,7 +66,7 @@ func NewCertStateFromConfig(c *config.C) (*CertState, error) { } } - rawKey, _, err := cert.UnmarshalX25519PrivateKey(pemPrivateKey) + rawKey, _, curve, err := cert.UnmarshalPrivateKey(pemPrivateKey) if err != nil { return nil, fmt.Errorf("error while unmarshaling pki.key %s: %s", privPathOrPEM, err) } @@ -102,7 +102,7 @@ func NewCertStateFromConfig(c *config.C) (*CertState, error) { return nil, fmt.Errorf("no IPs encoded in certificate") } - if err = nebulaCert.VerifyPrivateKey(rawKey); err != nil { + if err = nebulaCert.VerifyPrivateKey(curve, rawKey); err != nil { return nil, fmt.Errorf("private key is not a pair with public key in nebula cert") } diff --git a/cert/cert.go b/cert/cert.go index afc6b05..04cde59 100644 --- a/cert/cert.go +++ b/cert/cert.go @@ -2,7 +2,10 @@ package cert import ( "bytes" - "crypto" + "crypto/ecdh" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" "crypto/rand" "crypto/sha256" "encoding/binary" @@ -12,11 +15,11 @@ import ( "errors" "fmt" "math" + "math/big" "net" "time" "golang.org/x/crypto/curve25519" - "golang.org/x/crypto/ed25519" "google.golang.org/protobuf/proto" ) @@ -29,6 +32,11 @@ const ( 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 { @@ -49,6 +57,8 @@ type NebulaCertificateDetails struct { // Map of groups for faster lookup InvertedGroups map[string]struct{} + + Curve Curve } type NebulaEncryptedData struct { @@ -100,6 +110,7 @@ func UnmarshalNebulaCertificate(b []byte) (*NebulaCertificate, error) { 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)), } @@ -150,6 +161,28 @@ func UnmarshalNebulaCertificateFromPEM(b []byte) (*NebulaCertificate, []byte, er 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}) @@ -160,8 +193,58 @@ func MarshalEd25519PrivateKey(key ed25519.PrivateKey) []byte { return pem.EncodeToMemory(&pem.Block{Type: Ed25519PrivateKeyBanner, Bytes: key}) } -// EncryptAndMarshalX25519PrivateKey is a simple helper to encrypt and PEM encode an X25519 private key -func EncryptAndMarshalEd25519PrivateKey(b []byte, passphrase []byte, kdfParams *Argon2Parameters) ([]byte, error) { +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 @@ -181,7 +264,14 @@ func EncryptAndMarshalEd25519PrivateKey(b []byte, passphrase []byte, kdfParams * Ciphertext: ciphertext, }) - return pem.EncodeToMemory(&pem.Block{Type: EncryptedEd25519PrivateKeyBanner, Bytes: b}), nil + 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 @@ -282,21 +372,28 @@ func unmarshalArgon2Parameters(params *RawNebulaArgon2Parameters) (*Argon2Parame } -// DecryptAndUnmarshalEd25519PrivateKey will try to pem decode and decrypt an Ed25519 private key with +// 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 DecryptAndUnmarshalEd25519PrivateKey(passphrase, b []byte) (ed25519.PrivateKey, []byte, error) { +func DecryptAndUnmarshalSigningPrivateKey(passphrase, b []byte) (Curve, []byte, []byte, error) { + var curve Curve + k, r := pem.Decode(b) if k == nil { - return nil, r, fmt.Errorf("input did not contain a valid PEM encoded block") + return curve, nil, r, fmt.Errorf("input did not contain a valid PEM encoded block") } - if k.Type != EncryptedEd25519PrivateKeyBanner { - return nil, r, fmt.Errorf("bytes did not contain a proper nebula encrypted Ed25519 private key banner") + 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 nil, r, err + return curve, nil, r, err } var bytes []byte @@ -304,17 +401,39 @@ func DecryptAndUnmarshalEd25519PrivateKey(passphrase, b []byte) (ed25519.Private case "AES-256-GCM": bytes, err = aes256Decrypt(passphrase, &ned.EncryptionMetadata.Argon2Parameters, ned.Ciphertext) if err != nil { - return nil, r, err + return curve, nil, r, err } default: - return nil, r, fmt.Errorf("unsupported encryption algorithm: %s", ned.EncryptionMetadata.EncryptionAlgorithm) + return curve, nil, r, fmt.Errorf("unsupported encryption algorithm: %s", ned.EncryptionMetadata.EncryptionAlgorithm) } if len(bytes) != ed25519.PrivateKeySize { - return nil, r, fmt.Errorf("key was not 64 bytes, is invalid ed25519 private key") + return curve, nil, r, fmt.Errorf("key was not 64 bytes, is invalid ed25519 private key") } - return bytes, r, nil + 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 @@ -327,6 +446,30 @@ 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) { @@ -362,27 +505,65 @@ func UnmarshalEd25519PublicKey(b []byte) (ed25519.PublicKey, []byte, error) { } // Sign signs a nebula cert with the provided private key -func (nc *NebulaCertificate) Sign(key ed25519.PrivateKey) error { +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 } - sig, err := key.Sign(rand.Reader, b, crypto.Hash(0)) - 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: + x, y := elliptic.Unmarshal(elliptic.P256(), nc.Details.PublicKey) + signer := &ecdsa.PrivateKey{ + PublicKey: ecdsa.PublicKey{ + Curve: elliptic.P256(), + X: x, Y: y, + }, + // ref: https://github.com/golang/go/blob/go1.19/src/crypto/x509/sec1.go#L95 + D: new(big.Int).SetBytes(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 ed25519.PublicKey) bool { +func (nc *NebulaCertificate) CheckSignature(key []byte) bool { b, err := proto.Marshal(nc.getRawDetails()) if err != nil { return false } - return ed25519.Verify(key, b, nc.Signature) + 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 + } } // Expired will return true if the nebula cert is too young or too old compared to the provided time, otherwise false @@ -463,22 +644,52 @@ func (nc *NebulaCertificate) CheckRootConstrains(signer *NebulaCertificate) erro } // VerifyPrivateKey checks that the public key in the Nebula certificate and a supplied private key match -func (nc *NebulaCertificate) VerifyPrivateKey(key []byte) error { +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 { - // 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") - } + 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") + 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 } - pub, err := curve25519.X25519(key, curve25519.Basepoint) - if err != nil { - return err + 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") @@ -532,6 +743,7 @@ func (nc *NebulaCertificate) String() string { 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 { @@ -552,6 +764,7 @@ func (nc *NebulaCertificate) getRawDetails() *RawNebulaCertificateDetails { 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 { @@ -621,6 +834,7 @@ func (nc *NebulaCertificate) MarshalJSON() ([]byte, error) { "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), diff --git a/cert/cert.pb.go b/cert/cert.pb.go index 1aa1b4b..825e22c 100644 --- a/cert/cert.pb.go +++ b/cert/cert.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.0 -// protoc v3.19.4 +// protoc-gen-go v1.29.0 +// protoc v3.20.0 // source: cert.proto package cert @@ -20,6 +20,52 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type Curve int32 + +const ( + Curve_CURVE25519 Curve = 0 + Curve_P256 Curve = 1 +) + +// Enum value maps for Curve. +var ( + Curve_name = map[int32]string{ + 0: "CURVE25519", + 1: "P256", + } + Curve_value = map[string]int32{ + "CURVE25519": 0, + "P256": 1, + } +) + +func (x Curve) Enum() *Curve { + p := new(Curve) + *p = x + return p +} + +func (x Curve) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Curve) Descriptor() protoreflect.EnumDescriptor { + return file_cert_proto_enumTypes[0].Descriptor() +} + +func (Curve) Type() protoreflect.EnumType { + return &file_cert_proto_enumTypes[0] +} + +func (x Curve) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Curve.Descriptor instead. +func (Curve) EnumDescriptor() ([]byte, []int) { + return file_cert_proto_rawDescGZIP(), []int{0} +} + type RawNebulaCertificate struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -91,6 +137,7 @@ type RawNebulaCertificateDetails struct { IsCA bool `protobuf:"varint,8,opt,name=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,proto3" json:"Issuer,omitempty"` + Curve Curve `protobuf:"varint,100,opt,name=curve,proto3,enum=cert.Curve" json:"curve,omitempty"` } func (x *RawNebulaCertificateDetails) Reset() { @@ -188,6 +235,13 @@ func (x *RawNebulaCertificateDetails) GetIssuer() []byte { return nil } +func (x *RawNebulaCertificateDetails) GetCurve() Curve { + if x != nil { + return x.Curve + } + return Curve_CURVE25519 +} + type RawNebulaEncryptedData struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -388,7 +442,7 @@ var file_cert_proto_rawDesc = []byte{ 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x07, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x53, 0x69, 0x67, 0x6e, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0xf9, 0x01, 0x0a, 0x1b, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x9c, 0x02, 0x0a, 0x1b, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x49, 0x70, 0x73, @@ -404,38 +458,43 @@ var file_cert_proto_rawDesc = []byte{ 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x49, 0x73, 0x43, 0x41, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x49, 0x73, 0x43, 0x41, 0x12, 0x16, 0x0a, 0x06, 0x49, 0x73, 0x73, 0x75, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x49, 0x73, 0x73, 0x75, 0x65, - 0x72, 0x22, 0x8b, 0x01, 0x0a, 0x16, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x45, - 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x51, 0x0a, 0x12, + 0x72, 0x12, 0x21, 0x0a, 0x05, 0x63, 0x75, 0x72, 0x76, 0x65, 0x18, 0x64, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x0b, 0x2e, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x43, 0x75, 0x72, 0x76, 0x65, 0x52, 0x05, 0x63, + 0x75, 0x72, 0x76, 0x65, 0x22, 0x8b, 0x01, 0x0a, 0x16, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, + 0x6c, 0x61, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, + 0x51, 0x0a, 0x12, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x63, 0x65, + 0x72, 0x74, 0x2e, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x45, 0x6e, 0x63, 0x72, + 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x12, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x63, 0x65, 0x72, 0x74, 0x2e, - 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x12, 0x45, 0x6e, 0x63, - 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, - 0x1e, 0x0a, 0x0a, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x22, - 0x9c, 0x01, 0x0a, 0x1b, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x45, 0x6e, 0x63, - 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, - 0x30, 0x0a, 0x13, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x6c, 0x67, - 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x45, 0x6e, - 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, - 0x6d, 0x12, 0x4b, 0x0a, 0x10, 0x41, 0x72, 0x67, 0x6f, 0x6e, 0x32, 0x50, 0x61, 0x72, 0x61, 0x6d, - 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x63, 0x65, - 0x72, 0x74, 0x2e, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x41, 0x72, 0x67, 0x6f, - 0x6e, 0x32, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x52, 0x10, 0x41, 0x72, - 0x67, 0x6f, 0x6e, 0x32, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x22, 0xa3, - 0x01, 0x0a, 0x19, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x41, 0x72, 0x67, 0x6f, - 0x6e, 0x32, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x12, 0x20, - 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x69, 0x73, 0x6d, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x69, 0x73, 0x6d, - 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x12, 0x12, 0x0a, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, - 0x73, 0x61, 0x6c, 0x74, 0x42, 0x20, 0x5a, 0x1e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x73, 0x6c, 0x61, 0x63, 0x6b, 0x68, 0x71, 0x2f, 0x6e, 0x65, 0x62, 0x75, 0x6c, - 0x61, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, + 0x78, 0x74, 0x22, 0x9c, 0x01, 0x0a, 0x1b, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, + 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x12, 0x30, 0x0a, 0x13, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x13, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x6c, 0x67, 0x6f, 0x72, + 0x69, 0x74, 0x68, 0x6d, 0x12, 0x4b, 0x0a, 0x10, 0x41, 0x72, 0x67, 0x6f, 0x6e, 0x32, 0x50, 0x61, + 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, + 0x2e, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x41, + 0x72, 0x67, 0x6f, 0x6e, 0x32, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x52, + 0x10, 0x41, 0x72, 0x67, 0x6f, 0x6e, 0x32, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, + 0x73, 0x22, 0xa3, 0x01, 0x0a, 0x19, 0x52, 0x61, 0x77, 0x4e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x41, + 0x72, 0x67, 0x6f, 0x6e, 0x32, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, + 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x6d, + 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, + 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x69, 0x73, 0x6d, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, + 0x69, 0x73, 0x6d, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x2a, 0x21, 0x0a, 0x05, 0x43, 0x75, 0x72, 0x76, 0x65, + 0x12, 0x0e, 0x0a, 0x0a, 0x43, 0x55, 0x52, 0x56, 0x45, 0x32, 0x35, 0x35, 0x31, 0x39, 0x10, 0x00, + 0x12, 0x08, 0x0a, 0x04, 0x50, 0x32, 0x35, 0x36, 0x10, 0x01, 0x42, 0x20, 0x5a, 0x1e, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6c, 0x61, 0x63, 0x6b, 0x68, 0x71, + 0x2f, 0x6e, 0x65, 0x62, 0x75, 0x6c, 0x61, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -450,23 +509,26 @@ func file_cert_proto_rawDescGZIP() []byte { return file_cert_proto_rawDescData } +var file_cert_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_cert_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_cert_proto_goTypes = []interface{}{ - (*RawNebulaCertificate)(nil), // 0: cert.RawNebulaCertificate - (*RawNebulaCertificateDetails)(nil), // 1: cert.RawNebulaCertificateDetails - (*RawNebulaEncryptedData)(nil), // 2: cert.RawNebulaEncryptedData - (*RawNebulaEncryptionMetadata)(nil), // 3: cert.RawNebulaEncryptionMetadata - (*RawNebulaArgon2Parameters)(nil), // 4: cert.RawNebulaArgon2Parameters + (Curve)(0), // 0: cert.Curve + (*RawNebulaCertificate)(nil), // 1: cert.RawNebulaCertificate + (*RawNebulaCertificateDetails)(nil), // 2: cert.RawNebulaCertificateDetails + (*RawNebulaEncryptedData)(nil), // 3: cert.RawNebulaEncryptedData + (*RawNebulaEncryptionMetadata)(nil), // 4: cert.RawNebulaEncryptionMetadata + (*RawNebulaArgon2Parameters)(nil), // 5: cert.RawNebulaArgon2Parameters } var file_cert_proto_depIdxs = []int32{ - 1, // 0: cert.RawNebulaCertificate.Details:type_name -> cert.RawNebulaCertificateDetails - 3, // 1: cert.RawNebulaEncryptedData.EncryptionMetadata:type_name -> cert.RawNebulaEncryptionMetadata - 4, // 2: cert.RawNebulaEncryptionMetadata.Argon2Parameters:type_name -> cert.RawNebulaArgon2Parameters - 3, // [3:3] is the sub-list for method output_type - 3, // [3:3] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 2, // 0: cert.RawNebulaCertificate.Details:type_name -> cert.RawNebulaCertificateDetails + 0, // 1: cert.RawNebulaCertificateDetails.curve:type_name -> cert.Curve + 4, // 2: cert.RawNebulaEncryptedData.EncryptionMetadata:type_name -> cert.RawNebulaEncryptionMetadata + 5, // 3: cert.RawNebulaEncryptionMetadata.Argon2Parameters:type_name -> cert.RawNebulaArgon2Parameters + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_cert_proto_init() } @@ -541,13 +603,14 @@ func file_cert_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_cert_proto_rawDesc, - NumEnums: 0, + NumEnums: 1, NumMessages: 5, NumExtensions: 0, NumServices: 0, }, GoTypes: file_cert_proto_goTypes, DependencyIndexes: file_cert_proto_depIdxs, + EnumInfos: file_cert_proto_enumTypes, MessageInfos: file_cert_proto_msgTypes, }.Build() File_cert_proto = out.File diff --git a/cert/cert.proto b/cert/cert.proto index be7b132..36d043b 100644 --- a/cert/cert.proto +++ b/cert/cert.proto @@ -5,6 +5,11 @@ option go_package = "github.com/slackhq/nebula/cert"; //import "google/protobuf/timestamp.proto"; +enum Curve { + CURVE25519 = 0; + P256 = 1; +} + message RawNebulaCertificate { RawNebulaCertificateDetails Details = 1; bytes Signature = 2; @@ -26,6 +31,8 @@ message RawNebulaCertificateDetails { // sha-256 of the issuer certificate, if this field is blank the cert is self-signed bytes Issuer = 9; + + Curve curve = 100; } message RawNebulaEncryptedData { diff --git a/cert/cert_test.go b/cert/cert_test.go index 0fe2f39..de7daa9 100644 --- a/cert/cert_test.go +++ b/cert/cert_test.go @@ -1,6 +1,9 @@ package cert import ( + "crypto/ecdh" + "crypto/ecdsa" + "crypto/elliptic" "crypto/rand" "fmt" "io" @@ -101,7 +104,49 @@ func TestNebulaCertificate_Sign(t *testing.T) { pub, priv, err := ed25519.GenerateKey(rand.Reader) assert.Nil(t, err) assert.False(t, nc.CheckSignature(pub)) - assert.Nil(t, nc.Sign(priv)) + assert.Nil(t, nc.Sign(Curve_CURVE25519, priv)) + assert.True(t, nc.CheckSignature(pub)) + + _, err = nc.Marshal() + assert.Nil(t, err) + //t.Log("Cert size:", len(b)) +} + +func TestNebulaCertificate_SignP256(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("01234567890abcedfghij1234567890ab1234567890abcedfghij1234567890ab") + + 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, + Curve: Curve_P256, + Issuer: "1234567890abcedfghij1234567890ab", + }, + } + + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + pub := elliptic.Marshal(elliptic.P256(), priv.PublicKey.X, priv.PublicKey.Y) + rawPriv := priv.D.FillBytes(make([]byte, 32)) + + assert.Nil(t, err) + assert.False(t, nc.CheckSignature(pub)) + assert.Nil(t, nc.Sign(Curve_P256, rawPriv)) assert.True(t, nc.CheckSignature(pub)) _, err = nc.Marshal() @@ -153,7 +198,7 @@ func TestNebulaCertificate_MarshalJSON(t *testing.T) { 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\"}", + "{\"details\":{\"curve\":\"CURVE25519\",\"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), ) } @@ -217,6 +262,65 @@ func TestNebulaCertificate_Verify(t *testing.T) { assert.Nil(t, err) } +func TestNebulaCertificate_VerifyP256(t *testing.T) { + ca, _, caKey, err := newTestCaCertP256(time.Now(), time.Now().Add(10*time.Minute), []*net.IPNet{}, []*net.IPNet{}, []string{}) + assert.Nil(t, err) + + c, _, _, err := newTestCert(ca, caKey, time.Now(), time.Now().Add(5*time.Minute), []*net.IPNet{}, []*net.IPNet{}, []string{}) + 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.BlocklistFingerprint(f) + + v, err := c.Verify(time.Now(), caPool) + assert.False(t, v) + assert.EqualError(t, err, "certificate has been blocked") + + caPool.ResetCertBlocklist() + 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") + + c, _, _, err = newTestCert(ca, caKey, time.Time{}, time.Time{}, []*net.IPNet{}, []*net.IPNet{}, []string{}) + assert.Nil(t, err) + v, err = c.Verify(time.Now().Add(time.Minute*6), caPool) + assert.False(t, v) + assert.EqualError(t, err, "certificate is expired") + + // Test group assertion + ca, _, caKey, err = newTestCaCertP256(time.Now(), time.Now().Add(10*time.Minute), []*net.IPNet{}, []*net.IPNet{}, []string{"test1", "test2"}) + assert.Nil(t, err) + + caPem, err := ca.MarshalToPEM() + assert.Nil(t, err) + + caPool = NewCAPool() + caPool.AddCACertificate(caPem) + + c, _, _, err = newTestCert(ca, caKey, time.Now(), time.Now().Add(5*time.Minute), []*net.IPNet{}, []*net.IPNet{}, []string{"test1", "bad"}) + assert.Nil(t, err) + v, err = c.Verify(time.Now(), caPool) + assert.False(t, v) + assert.EqualError(t, err, "certificate contained a group not present on the signing ca: bad") + + c, _, _, err = newTestCert(ca, caKey, time.Now(), time.Now().Add(5*time.Minute), []*net.IPNet{}, []*net.IPNet{}, []string{"test1"}) + assert.Nil(t, err) + v, err = c.Verify(time.Now(), caPool) + assert.True(t, v) + assert.Nil(t, err) +} + func TestNebulaCertificate_Verify_IPs(t *testing.T) { _, caIp1, _ := net.ParseCIDR("10.0.0.0/16") _, caIp2, _ := net.ParseCIDR("192.168.0.0/24") @@ -378,20 +482,40 @@ func TestNebulaCertificate_Verify_Subnets(t *testing.T) { func TestNebulaCertificate_VerifyPrivateKey(t *testing.T) { ca, _, caKey, err := newTestCaCert(time.Time{}, time.Time{}, []*net.IPNet{}, []*net.IPNet{}, []string{}) assert.Nil(t, err) - err = ca.VerifyPrivateKey(caKey) + err = ca.VerifyPrivateKey(Curve_CURVE25519, caKey) assert.Nil(t, err) _, _, caKey2, err := newTestCaCert(time.Time{}, time.Time{}, []*net.IPNet{}, []*net.IPNet{}, []string{}) assert.Nil(t, err) - err = ca.VerifyPrivateKey(caKey2) + err = ca.VerifyPrivateKey(Curve_CURVE25519, caKey2) assert.NotNil(t, err) c, _, priv, err := newTestCert(ca, caKey, time.Time{}, time.Time{}, []*net.IPNet{}, []*net.IPNet{}, []string{}) - err = c.VerifyPrivateKey(priv) + err = c.VerifyPrivateKey(Curve_CURVE25519, priv) assert.Nil(t, err) _, priv2 := x25519Keypair() - err = c.VerifyPrivateKey(priv2) + err = c.VerifyPrivateKey(Curve_CURVE25519, priv2) + assert.NotNil(t, err) +} + +func TestNebulaCertificate_VerifyPrivateKeyP256(t *testing.T) { + ca, _, caKey, err := newTestCaCertP256(time.Time{}, time.Time{}, []*net.IPNet{}, []*net.IPNet{}, []string{}) + assert.Nil(t, err) + err = ca.VerifyPrivateKey(Curve_P256, caKey) + assert.Nil(t, err) + + _, _, caKey2, err := newTestCaCertP256(time.Time{}, time.Time{}, []*net.IPNet{}, []*net.IPNet{}, []string{}) + assert.Nil(t, err) + err = ca.VerifyPrivateKey(Curve_P256, caKey2) + assert.NotNil(t, err) + + c, _, priv, err := newTestCert(ca, caKey, time.Time{}, time.Time{}, []*net.IPNet{}, []*net.IPNet{}, []string{}) + err = c.VerifyPrivateKey(Curve_P256, priv) + assert.Nil(t, err) + + _, priv2 := p256Keypair() + err = c.VerifyPrivateKey(Curve_P256, priv2) assert.NotNil(t, err) } @@ -438,6 +562,16 @@ CjkKB2V4cGlyZWQouPmWjQYwufmWjQY6ILCRaoCkJlqHgv5jfDN4lzLHBvDzaQm4 vZxfu144hmgjQAESQG4qlnZi8DncvD/LDZnLgJHOaX1DWCHHEh59epVsC+BNgTie WH1M9n4O7cFtGlM6sJJOS+rCVVEJ3ABS7+MPdQs= -----END NEBULA CERTIFICATE----- +` + + p256 := ` +# p256 certificate +-----BEGIN NEBULA CERTIFICATE----- +CmYKEG5lYnVsYSBQMjU2IHRlc3Qo4s+7mgYw4tXrsAc6QQRkaW2jFmllYvN4+/k2 +6tctO9sPT3jOx8ES6M1nIqOhpTmZeabF/4rELDqPV4aH5jfJut798DUXql0FlF8H +76gvQAGgBgESRzBFAiEAib0/te6eMiZOKD8gdDeloMTS0wGuX2t0C7TFdUhAQzgC +IBNWYMep3ysx9zCgknfG5dKtwGTaqF++BWKDYdyl34KX +-----END NEBULA CERTIFICATE----- ` rootCA := NebulaCertificate{ @@ -452,6 +586,12 @@ WH1M9n4O7cFtGlM6sJJOS+rCVVEJ3ABS7+MPdQs= }, } + rootCAP256 := NebulaCertificate{ + Details: NebulaCertificateDetails{ + Name: "nebula P256 test", + }, + } + p, err := NewCAPoolFromBytes([]byte(noNewLines)) assert.Nil(t, err) assert.Equal(t, p.CAs[string("c9bfaf7ce8e84b2eeda2e27b469f4b9617bde192efd214b68891ecda6ed49522")].Details.Name, rootCA.Details.Name) @@ -474,6 +614,11 @@ WH1M9n4O7cFtGlM6sJJOS+rCVVEJ3ABS7+MPdQs= assert.Equal(t, pppp.CAs[string("5c9c3f23e7ee7fe97637cbd3a0a5b854154d1d9aaaf7b566a51f4a88f76b64cd")].Details.Name, rootCA01.Details.Name) assert.Equal(t, pppp.CAs[string("152070be6bb19bc9e3bde4c2f0e7d8f4ff5448b4c9856b8eccb314fade0229b0")].Details.Name, "expired") assert.Equal(t, len(pppp.CAs), 3) + + ppppp, err := NewCAPoolFromBytes([]byte(p256)) + assert.Nil(t, err) + assert.Equal(t, ppppp.CAs[string("a7938893ec8c4ef769b06d7f425e5e46f7a7f5ffa49c3bcf4a86b608caba9159")].Details.Name, rootCAP256.Details.Name) + assert.Equal(t, len(ppppp.CAs), 1) } func appendByteSlices(b ...[]byte) []byte { @@ -529,11 +674,16 @@ bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB assert.EqualError(t, err, "input did not contain a valid PEM encoded block") } -func TestUnmarshalEd25519PrivateKey(t *testing.T) { +func TestUnmarshalSigningPrivateKey(t *testing.T) { privKey := []byte(`# A good key -----BEGIN NEBULA ED25519 PRIVATE KEY----- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== -----END NEBULA ED25519 PRIVATE KEY----- +`) + privP256Key := []byte(`# A good key +-----BEGIN NEBULA ECDSA P256 PRIVATE KEY----- +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= +-----END NEBULA ECDSA P256 PRIVATE KEY----- `) shortKey := []byte(`# A short key -----BEGIN NEBULA ED25519 PRIVATE KEY----- @@ -550,35 +700,43 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== -END NEBULA ED25519 PRIVATE KEY-----`) - keyBundle := appendByteSlices(privKey, shortKey, invalidBanner, invalidPem) + keyBundle := appendByteSlices(privKey, privP256Key, shortKey, invalidBanner, invalidPem) // Success test case - k, rest, err := UnmarshalEd25519PrivateKey(keyBundle) + k, rest, curve, err := UnmarshalSigningPrivateKey(keyBundle) assert.Len(t, k, 64) + assert.Equal(t, rest, appendByteSlices(privP256Key, shortKey, invalidBanner, invalidPem)) + assert.Equal(t, Curve_CURVE25519, curve) + assert.Nil(t, err) + + // Success test case + k, rest, curve, err = UnmarshalSigningPrivateKey(rest) + assert.Len(t, k, 32) assert.Equal(t, rest, appendByteSlices(shortKey, invalidBanner, invalidPem)) + assert.Equal(t, Curve_P256, curve) assert.Nil(t, err) // Fail due to short key - k, rest, err = UnmarshalEd25519PrivateKey(rest) + k, rest, curve, err = UnmarshalSigningPrivateKey(rest) assert.Nil(t, k) assert.Equal(t, rest, appendByteSlices(invalidBanner, invalidPem)) - assert.EqualError(t, err, "key was not 64 bytes, is invalid ed25519 private key") + assert.EqualError(t, err, "key was not 64 bytes, is invalid Ed25519 private key") // Fail due to invalid banner - k, rest, err = UnmarshalEd25519PrivateKey(rest) + k, rest, curve, err = UnmarshalSigningPrivateKey(rest) assert.Nil(t, k) assert.Equal(t, rest, invalidPem) - assert.EqualError(t, err, "bytes did not contain a proper nebula Ed25519 private key banner") + assert.EqualError(t, err, "bytes did not contain a proper nebula Ed25519/ECDSA private key banner") // Fail due to ivalid PEM format, because // it's missing the requisite pre-encapsulation boundary. - k, rest, err = UnmarshalEd25519PrivateKey(rest) + k, rest, curve, err = UnmarshalSigningPrivateKey(rest) assert.Nil(t, k) assert.Equal(t, rest, invalidPem) assert.EqualError(t, err, "input did not contain a valid PEM encoded block") } -func TestDecryptAndUnmarshalEd25519PrivateKey(t *testing.T) { +func TestDecryptAndUnmarshalSigningPrivateKey(t *testing.T) { passphrase := []byte("DO NOT USE THIS KEY") privKey := []byte(`# A good key -----BEGIN NEBULA ED25519 ENCRYPTED PRIVATE KEY----- @@ -614,60 +772,67 @@ qrlJ69wer3ZUHFXA keyBundle := appendByteSlices(privKey, shortKey, invalidBanner, invalidPem) // Success test case - k, rest, err := DecryptAndUnmarshalEd25519PrivateKey(passphrase, keyBundle) + curve, k, rest, err := DecryptAndUnmarshalSigningPrivateKey(passphrase, keyBundle) assert.Nil(t, err) + assert.Equal(t, Curve_CURVE25519, curve) assert.Len(t, k, 64) assert.Equal(t, rest, appendByteSlices(shortKey, invalidBanner, invalidPem)) // Fail due to short key - k, rest, err = DecryptAndUnmarshalEd25519PrivateKey(passphrase, rest) + curve, k, rest, err = DecryptAndUnmarshalSigningPrivateKey(passphrase, rest) assert.EqualError(t, err, "key was not 64 bytes, is invalid ed25519 private key") assert.Nil(t, k) assert.Equal(t, rest, appendByteSlices(invalidBanner, invalidPem)) // Fail due to invalid banner - k, rest, err = DecryptAndUnmarshalEd25519PrivateKey(passphrase, rest) - assert.EqualError(t, err, "bytes did not contain a proper nebula encrypted Ed25519 private key banner") + curve, k, rest, err = DecryptAndUnmarshalSigningPrivateKey(passphrase, rest) + assert.EqualError(t, err, "bytes did not contain a proper nebula encrypted Ed25519/ECDSA private key banner") assert.Nil(t, k) assert.Equal(t, rest, invalidPem) // Fail due to ivalid PEM format, because // it's missing the requisite pre-encapsulation boundary. - k, rest, err = DecryptAndUnmarshalEd25519PrivateKey(passphrase, rest) + curve, k, rest, err = DecryptAndUnmarshalSigningPrivateKey(passphrase, rest) assert.EqualError(t, err, "input did not contain a valid PEM encoded block") assert.Nil(t, k) assert.Equal(t, rest, invalidPem) // Fail due to invalid passphrase - k, rest, err = DecryptAndUnmarshalEd25519PrivateKey([]byte("invalid passphrase"), privKey) + curve, k, rest, err = DecryptAndUnmarshalSigningPrivateKey([]byte("invalid passphrase"), privKey) assert.EqualError(t, err, "invalid passphrase or corrupt private key") assert.Nil(t, k) assert.Equal(t, rest, []byte{}) } -func TestEncryptAndMarshalEd25519PrivateKey(t *testing.T) { +func TestEncryptAndMarshalSigningPrivateKey(t *testing.T) { // Having proved that decryption works correctly above, we can test the // encryption function produces a value which can be decrypted passphrase := []byte("passphrase") bytes := []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") kdfParams := NewArgon2Parameters(64*1024, 4, 3) - key, err := EncryptAndMarshalEd25519PrivateKey(bytes, passphrase, kdfParams) + key, err := EncryptAndMarshalSigningPrivateKey(Curve_CURVE25519, bytes, passphrase, kdfParams) assert.Nil(t, err) // Verify the "key" can be decrypted successfully - k, rest, err := DecryptAndUnmarshalEd25519PrivateKey(passphrase, key) + curve, k, rest, err := DecryptAndUnmarshalSigningPrivateKey(passphrase, key) assert.Len(t, k, 64) + assert.Equal(t, Curve_CURVE25519, curve) assert.Equal(t, rest, []byte{}) assert.Nil(t, err) // EncryptAndMarshalEd25519PrivateKey does not create any errors itself } -func TestUnmarshalX25519PrivateKey(t *testing.T) { +func TestUnmarshalPrivateKey(t *testing.T) { privKey := []byte(`# A good key -----BEGIN NEBULA X25519 PRIVATE KEY----- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= -----END NEBULA X25519 PRIVATE KEY----- +`) + privP256Key := []byte(`# A good key +-----BEGIN NEBULA P256 PRIVATE KEY----- +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= +-----END NEBULA P256 PRIVATE KEY----- `) shortKey := []byte(`# A short key -----BEGIN NEBULA X25519 PRIVATE KEY----- @@ -684,29 +849,37 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= -END NEBULA X25519 PRIVATE KEY-----`) - keyBundle := appendByteSlices(privKey, shortKey, invalidBanner, invalidPem) + keyBundle := appendByteSlices(privKey, privP256Key, shortKey, invalidBanner, invalidPem) // Success test case - k, rest, err := UnmarshalX25519PrivateKey(keyBundle) + k, rest, curve, err := UnmarshalPrivateKey(keyBundle) + assert.Len(t, k, 32) + assert.Equal(t, rest, appendByteSlices(privP256Key, shortKey, invalidBanner, invalidPem)) + assert.Equal(t, Curve_CURVE25519, curve) + assert.Nil(t, err) + + // Success test case + k, rest, curve, err = UnmarshalPrivateKey(rest) assert.Len(t, k, 32) assert.Equal(t, rest, appendByteSlices(shortKey, invalidBanner, invalidPem)) + assert.Equal(t, Curve_P256, curve) assert.Nil(t, err) // Fail due to short key - k, rest, err = UnmarshalX25519PrivateKey(rest) + k, rest, curve, err = UnmarshalPrivateKey(rest) assert.Nil(t, k) assert.Equal(t, rest, appendByteSlices(invalidBanner, invalidPem)) - assert.EqualError(t, err, "key was not 32 bytes, is invalid X25519 private key") + assert.EqualError(t, err, "key was not 32 bytes, is invalid CURVE25519 private key") // Fail due to invalid banner - k, rest, err = UnmarshalX25519PrivateKey(rest) + k, rest, curve, err = UnmarshalPrivateKey(rest) assert.Nil(t, k) assert.Equal(t, rest, invalidPem) - assert.EqualError(t, err, "bytes did not contain a proper nebula X25519 private key banner") + assert.EqualError(t, err, "bytes did not contain a proper nebula private key banner") // Fail due to ivalid PEM format, because // it's missing the requisite pre-encapsulation boundary. - k, rest, err = UnmarshalX25519PrivateKey(rest) + k, rest, curve, err = UnmarshalPrivateKey(rest) assert.Nil(t, k) assert.Equal(t, rest, invalidPem) assert.EqualError(t, err, "input did not contain a valid PEM encoded block") @@ -766,6 +939,12 @@ func TestUnmarshalX25519PublicKey(t *testing.T) { -----BEGIN NEBULA X25519 PUBLIC KEY----- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= -----END NEBULA X25519 PUBLIC KEY----- +`) + pubP256Key := []byte(`# A good key +-----BEGIN NEBULA P256 PUBLIC KEY----- +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAA= +-----END NEBULA P256 PUBLIC KEY----- `) shortKey := []byte(`# A short key -----BEGIN NEBULA X25519 PUBLIC KEY----- @@ -782,29 +961,37 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= -END NEBULA X25519 PUBLIC KEY-----`) - keyBundle := appendByteSlices(pubKey, shortKey, invalidBanner, invalidPem) + keyBundle := appendByteSlices(pubKey, pubP256Key, shortKey, invalidBanner, invalidPem) // Success test case - k, rest, err := UnmarshalX25519PublicKey(keyBundle) + k, rest, curve, err := UnmarshalPublicKey(keyBundle) assert.Equal(t, len(k), 32) assert.Nil(t, err) + assert.Equal(t, rest, appendByteSlices(pubP256Key, shortKey, invalidBanner, invalidPem)) + assert.Equal(t, Curve_CURVE25519, curve) + + // Success test case + k, rest, curve, err = UnmarshalPublicKey(rest) + assert.Equal(t, len(k), 65) + assert.Nil(t, err) assert.Equal(t, rest, appendByteSlices(shortKey, invalidBanner, invalidPem)) + assert.Equal(t, Curve_P256, curve) // Fail due to short key - k, rest, err = UnmarshalX25519PublicKey(rest) + k, rest, curve, err = UnmarshalPublicKey(rest) assert.Nil(t, k) assert.Equal(t, rest, appendByteSlices(invalidBanner, invalidPem)) - assert.EqualError(t, err, "key was not 32 bytes, is invalid X25519 public key") + assert.EqualError(t, err, "key was not 32 bytes, is invalid CURVE25519 public key") // Fail due to invalid banner - k, rest, err = UnmarshalX25519PublicKey(rest) + k, rest, curve, err = UnmarshalPublicKey(rest) assert.Nil(t, k) - assert.EqualError(t, err, "bytes did not contain a proper nebula X25519 public key banner") + assert.EqualError(t, err, "bytes did not contain a proper nebula public key banner") assert.Equal(t, rest, invalidPem) // Fail due to ivalid PEM format, because // it's missing the requisite pre-encapsulation boundary. - k, rest, err = UnmarshalX25519PublicKey(rest) + k, rest, curve, err = UnmarshalPublicKey(rest) assert.Nil(t, k) assert.Equal(t, rest, invalidPem) assert.EqualError(t, err, "input did not contain a valid PEM encoded block") @@ -901,13 +1088,56 @@ func newTestCaCert(before, after time.Time, ips, subnets []*net.IPNet, groups [] nc.Details.Groups = groups } - err = nc.Sign(priv) + err = nc.Sign(Curve_CURVE25519, priv) if err != nil { return nil, nil, nil, err } return nc, pub, priv, nil } +func newTestCaCertP256(before, after time.Time, ips, subnets []*net.IPNet, groups []string) (*NebulaCertificate, []byte, []byte, error) { + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + pub := elliptic.Marshal(elliptic.P256(), priv.PublicKey.X, priv.PublicKey.Y) + rawPriv := priv.D.FillBytes(make([]byte, 32)) + + if before.IsZero() { + before = time.Now().Add(time.Second * -60).Round(time.Second) + } + if after.IsZero() { + after = time.Now().Add(time.Second * 60).Round(time.Second) + } + + nc := &NebulaCertificate{ + Details: NebulaCertificateDetails{ + Name: "test ca", + NotBefore: time.Unix(before.Unix(), 0), + NotAfter: time.Unix(after.Unix(), 0), + PublicKey: pub, + IsCA: true, + Curve: Curve_P256, + InvertedGroups: make(map[string]struct{}), + }, + } + + if len(ips) > 0 { + nc.Details.Ips = ips + } + + if len(subnets) > 0 { + nc.Details.Subnets = subnets + } + + if len(groups) > 0 { + nc.Details.Groups = groups + } + + err = nc.Sign(Curve_P256, rawPriv) + if err != nil { + return nil, nil, nil, err + } + return nc, pub, rawPriv, nil +} + func newTestCert(ca *NebulaCertificate, key []byte, before, after time.Time, ips, subnets []*net.IPNet, groups []string) (*NebulaCertificate, []byte, []byte, error) { issuer, err := ca.Sha256Sum() if err != nil { @@ -941,7 +1171,16 @@ func newTestCert(ca *NebulaCertificate, key []byte, before, after time.Time, ips } } - pub, rawPriv := x25519Keypair() + var pub, rawPriv []byte + + switch ca.Details.Curve { + case Curve_CURVE25519: + pub, rawPriv = x25519Keypair() + case Curve_P256: + pub, rawPriv = p256Keypair() + default: + return nil, nil, nil, fmt.Errorf("unknown curve: %v", ca.Details.Curve) + } nc := &NebulaCertificate{ Details: NebulaCertificateDetails{ @@ -953,12 +1192,13 @@ func newTestCert(ca *NebulaCertificate, key []byte, before, after time.Time, ips NotAfter: time.Unix(after.Unix(), 0), PublicKey: pub, IsCA: false, + Curve: ca.Details.Curve, Issuer: issuer, InvertedGroups: make(map[string]struct{}), }, } - err = nc.Sign(key) + err = nc.Sign(ca.Details.Curve, key) if err != nil { return nil, nil, nil, err } @@ -979,3 +1219,12 @@ func x25519Keypair() ([]byte, []byte) { return pubkey, privkey } + +func p256Keypair() ([]byte, []byte) { + privkey, err := ecdh.P256().GenerateKey(rand.Reader) + if err != nil { + panic(err) + } + pubkey := privkey.PublicKey() + return pubkey.Bytes(), privkey.Bytes() +} diff --git a/cmd/nebula-cert/ca.go b/cmd/nebula-cert/ca.go index b4f25c9..e9ad3cb 100644 --- a/cmd/nebula-cert/ca.go +++ b/cmd/nebula-cert/ca.go @@ -1,6 +1,8 @@ package main import ( + "crypto/ecdsa" + "crypto/elliptic" "crypto/rand" "flag" "fmt" @@ -31,6 +33,8 @@ type caFlags struct { argonIterations *uint argonParallelism *uint encryption *bool + + curve *string } func newCaFlags() *caFlags { @@ -48,6 +52,7 @@ func newCaFlags() *caFlags { cf.argonParallelism = cf.set.Uint("argon-parallelism", 4, "Optional: Argon2 parallelism parameter used for encrypted private key passphrase") cf.argonIterations = cf.set.Uint("argon-iterations", 1, "Optional: Argon2 iterations parameter used for encrypted private key passphrase") cf.encryption = cf.set.Bool("encrypt", false, "Optional: prompt for passphrase and write out-key in an encrypted format") + cf.curve = cf.set.String("curve", "25519", "EdDSA/ECDSA Curve (25519, P256)") return &cf } @@ -160,9 +165,25 @@ func ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error } } - pub, rawPriv, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - return fmt.Errorf("error while generating ed25519 keys: %s", err) + var curve cert.Curve + var pub, rawPriv []byte + switch *cf.curve { + case "25519", "X25519", "Curve25519", "CURVE25519": + curve = cert.Curve_CURVE25519 + pub, rawPriv, err = ed25519.GenerateKey(rand.Reader) + if err != nil { + return fmt.Errorf("error while generating ed25519 keys: %s", err) + } + case "P256": + var key *ecdsa.PrivateKey + curve = cert.Curve_P256 + key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return fmt.Errorf("error while generating ecdsa keys: %s", err) + } + // ref: https://github.com/golang/go/blob/go1.19/src/crypto/x509/sec1.go#L60 + rawPriv = key.D.FillBytes(make([]byte, 32)) + pub = elliptic.Marshal(elliptic.P256(), key.X, key.Y) } nc := cert.NebulaCertificate{ @@ -175,6 +196,7 @@ func ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error NotAfter: time.Now().Add(*cf.duration), PublicKey: pub, IsCA: true, + Curve: curve, }, } @@ -186,20 +208,20 @@ func ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error return fmt.Errorf("refusing to overwrite existing CA cert: %s", *cf.outCertPath) } - err = nc.Sign(rawPriv) + err = nc.Sign(curve, rawPriv) if err != nil { return fmt.Errorf("error while signing: %s", err) } if *cf.encryption { - b, err := cert.EncryptAndMarshalEd25519PrivateKey(rawPriv, passphrase, kdfParams) + b, err := cert.EncryptAndMarshalSigningPrivateKey(curve, rawPriv, passphrase, kdfParams) if err != nil { return fmt.Errorf("error while encrypting out-key: %s", err) } err = ioutil.WriteFile(*cf.outKeyPath, b, 0600) } else { - err = ioutil.WriteFile(*cf.outKeyPath, cert.MarshalEd25519PrivateKey(rawPriv), 0600) + err = ioutil.WriteFile(*cf.outKeyPath, cert.MarshalSigningPrivateKey(curve, rawPriv), 0600) } if err != nil { diff --git a/cmd/nebula-cert/ca_test.go b/cmd/nebula-cert/ca_test.go index 0ce9182..ae79baf 100644 --- a/cmd/nebula-cert/ca_test.go +++ b/cmd/nebula-cert/ca_test.go @@ -35,6 +35,8 @@ func Test_caHelp(t *testing.T) { " \tOptional: Argon2 memory parameter (in KiB) used for encrypted private key passphrase (default 2097152)\n"+ " -argon-parallelism uint\n"+ " \tOptional: Argon2 parallelism parameter used for encrypted private key passphrase (default 4)\n"+ + " -curve string\n"+ + " \tEdDSA/ECDSA Curve (25519, P256) (default \"25519\")\n"+ " -duration duration\n"+ " \tOptional: amount of time the certificate should be valid for. Valid time units are seconds: \"s\", minutes: \"m\", hours: \"h\" (default 8760h0m0s)\n"+ " -encrypt\n"+ @@ -174,7 +176,9 @@ func Test_ca(t *testing.T) { assert.Equal(t, uint32(1), ned.EncryptionMetadata.Argon2Parameters.Iterations) // verify the key is valid and decrypt-able - lKey, b, err = cert.DecryptAndUnmarshalEd25519PrivateKey(passphrase, rb) + var curve cert.Curve + curve, lKey, b, err = cert.DecryptAndUnmarshalSigningPrivateKey(passphrase, rb) + assert.Equal(t, cert.Curve_CURVE25519, curve) assert.Nil(t, err) assert.Len(t, b, 0) assert.Len(t, lKey, 64) diff --git a/cmd/nebula-cert/keygen.go b/cmd/nebula-cert/keygen.go index 4f15af8..0016fc1 100644 --- a/cmd/nebula-cert/keygen.go +++ b/cmd/nebula-cert/keygen.go @@ -14,6 +14,8 @@ type keygenFlags struct { set *flag.FlagSet outKeyPath *string outPubPath *string + + curve *string } func newKeygenFlags() *keygenFlags { @@ -21,6 +23,7 @@ func newKeygenFlags() *keygenFlags { cf.set.Usage = func() {} cf.outPubPath = cf.set.String("out-pub", "", "Required: path to write the public key to") cf.outKeyPath = cf.set.String("out-key", "", "Required: path to write the private key to") + cf.curve = cf.set.String("curve", "25519", "ECDH Curve (25519, P256)") return &cf } @@ -38,14 +41,25 @@ func keygen(args []string, out io.Writer, errOut io.Writer) error { return err } - pub, rawPriv := x25519Keypair() + var pub, rawPriv []byte + var curve cert.Curve + switch *cf.curve { + case "25519", "X25519", "Curve25519", "CURVE25519": + pub, rawPriv = x25519Keypair() + curve = cert.Curve_CURVE25519 + case "P256": + pub, rawPriv = p256Keypair() + curve = cert.Curve_P256 + default: + return fmt.Errorf("invalid curve: %s", *cf.curve) + } - err = ioutil.WriteFile(*cf.outKeyPath, cert.MarshalX25519PrivateKey(rawPriv), 0600) + err = ioutil.WriteFile(*cf.outKeyPath, cert.MarshalPrivateKey(curve, rawPriv), 0600) if err != nil { return fmt.Errorf("error while writing out-key: %s", err) } - err = ioutil.WriteFile(*cf.outPubPath, cert.MarshalX25519PublicKey(pub), 0600) + err = ioutil.WriteFile(*cf.outPubPath, cert.MarshalPublicKey(curve, pub), 0600) if err != nil { return fmt.Errorf("error while writing out-pub: %s", err) } diff --git a/cmd/nebula-cert/keygen_test.go b/cmd/nebula-cert/keygen_test.go index 52f71f5..1480e5a 100644 --- a/cmd/nebula-cert/keygen_test.go +++ b/cmd/nebula-cert/keygen_test.go @@ -22,6 +22,8 @@ func Test_keygenHelp(t *testing.T) { assert.Equal( t, "Usage of "+os.Args[0]+" keygen : create a public/private key pair. the public key can be passed to `nebula-cert sign`\n"+ + " -curve string\n"+ + " \tECDH Curve (25519, P256) (default \"25519\")\n"+ " -out-key string\n"+ " \tRequired: path to write the private key to\n"+ " -out-pub string\n"+ diff --git a/cmd/nebula-cert/print_test.go b/cmd/nebula-cert/print_test.go index 5d6a38e..eb117f1 100644 --- a/cmd/nebula-cert/print_test.go +++ b/cmd/nebula-cert/print_test.go @@ -87,7 +87,7 @@ func Test_printCert(t *testing.T) { assert.Nil(t, err) assert.Equal( t, - "NebulaCertificate {\n\tDetails {\n\t\tName: test\n\t\tIps: []\n\t\tSubnets: []\n\t\tGroups: [\n\t\t\t\"hi\"\n\t\t]\n\t\tNot before: 0001-01-01 00:00:00 +0000 UTC\n\t\tNot After: 0001-01-01 00:00:00 +0000 UTC\n\t\tIs CA: false\n\t\tIssuer: \n\t\tPublic key: 0102030405060708090001020304050607080900010203040506070809000102\n\t}\n\tFingerprint: cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\n\tSignature: 0102030405060708090001020304050607080900010203040506070809000102\n}\nNebulaCertificate {\n\tDetails {\n\t\tName: test\n\t\tIps: []\n\t\tSubnets: []\n\t\tGroups: [\n\t\t\t\"hi\"\n\t\t]\n\t\tNot before: 0001-01-01 00:00:00 +0000 UTC\n\t\tNot After: 0001-01-01 00:00:00 +0000 UTC\n\t\tIs CA: false\n\t\tIssuer: \n\t\tPublic key: 0102030405060708090001020304050607080900010203040506070809000102\n\t}\n\tFingerprint: cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\n\tSignature: 0102030405060708090001020304050607080900010203040506070809000102\n}\nNebulaCertificate {\n\tDetails {\n\t\tName: test\n\t\tIps: []\n\t\tSubnets: []\n\t\tGroups: [\n\t\t\t\"hi\"\n\t\t]\n\t\tNot before: 0001-01-01 00:00:00 +0000 UTC\n\t\tNot After: 0001-01-01 00:00:00 +0000 UTC\n\t\tIs CA: false\n\t\tIssuer: \n\t\tPublic key: 0102030405060708090001020304050607080900010203040506070809000102\n\t}\n\tFingerprint: cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\n\tSignature: 0102030405060708090001020304050607080900010203040506070809000102\n}\n", + "NebulaCertificate {\n\tDetails {\n\t\tName: test\n\t\tIps: []\n\t\tSubnets: []\n\t\tGroups: [\n\t\t\t\"hi\"\n\t\t]\n\t\tNot before: 0001-01-01 00:00:00 +0000 UTC\n\t\tNot After: 0001-01-01 00:00:00 +0000 UTC\n\t\tIs CA: false\n\t\tIssuer: \n\t\tPublic key: 0102030405060708090001020304050607080900010203040506070809000102\n\t\tCurve: CURVE25519\n\t}\n\tFingerprint: cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\n\tSignature: 0102030405060708090001020304050607080900010203040506070809000102\n}\nNebulaCertificate {\n\tDetails {\n\t\tName: test\n\t\tIps: []\n\t\tSubnets: []\n\t\tGroups: [\n\t\t\t\"hi\"\n\t\t]\n\t\tNot before: 0001-01-01 00:00:00 +0000 UTC\n\t\tNot After: 0001-01-01 00:00:00 +0000 UTC\n\t\tIs CA: false\n\t\tIssuer: \n\t\tPublic key: 0102030405060708090001020304050607080900010203040506070809000102\n\t\tCurve: CURVE25519\n\t}\n\tFingerprint: cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\n\tSignature: 0102030405060708090001020304050607080900010203040506070809000102\n}\nNebulaCertificate {\n\tDetails {\n\t\tName: test\n\t\tIps: []\n\t\tSubnets: []\n\t\tGroups: [\n\t\t\t\"hi\"\n\t\t]\n\t\tNot before: 0001-01-01 00:00:00 +0000 UTC\n\t\tNot After: 0001-01-01 00:00:00 +0000 UTC\n\t\tIs CA: false\n\t\tIssuer: \n\t\tPublic key: 0102030405060708090001020304050607080900010203040506070809000102\n\t\tCurve: CURVE25519\n\t}\n\tFingerprint: cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\n\tSignature: 0102030405060708090001020304050607080900010203040506070809000102\n}\n", ob.String(), ) assert.Equal(t, "", eb.String()) @@ -115,7 +115,7 @@ func Test_printCert(t *testing.T) { assert.Nil(t, err) assert.Equal( t, - "{\"details\":{\"groups\":[\"hi\"],\"ips\":[],\"isCa\":false,\"issuer\":\"\",\"name\":\"test\",\"notAfter\":\"0001-01-01T00:00:00Z\",\"notBefore\":\"0001-01-01T00:00:00Z\",\"publicKey\":\"0102030405060708090001020304050607080900010203040506070809000102\",\"subnets\":[]},\"fingerprint\":\"cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\",\"signature\":\"0102030405060708090001020304050607080900010203040506070809000102\"}\n{\"details\":{\"groups\":[\"hi\"],\"ips\":[],\"isCa\":false,\"issuer\":\"\",\"name\":\"test\",\"notAfter\":\"0001-01-01T00:00:00Z\",\"notBefore\":\"0001-01-01T00:00:00Z\",\"publicKey\":\"0102030405060708090001020304050607080900010203040506070809000102\",\"subnets\":[]},\"fingerprint\":\"cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\",\"signature\":\"0102030405060708090001020304050607080900010203040506070809000102\"}\n{\"details\":{\"groups\":[\"hi\"],\"ips\":[],\"isCa\":false,\"issuer\":\"\",\"name\":\"test\",\"notAfter\":\"0001-01-01T00:00:00Z\",\"notBefore\":\"0001-01-01T00:00:00Z\",\"publicKey\":\"0102030405060708090001020304050607080900010203040506070809000102\",\"subnets\":[]},\"fingerprint\":\"cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\",\"signature\":\"0102030405060708090001020304050607080900010203040506070809000102\"}\n", + "{\"details\":{\"curve\":\"CURVE25519\",\"groups\":[\"hi\"],\"ips\":[],\"isCa\":false,\"issuer\":\"\",\"name\":\"test\",\"notAfter\":\"0001-01-01T00:00:00Z\",\"notBefore\":\"0001-01-01T00:00:00Z\",\"publicKey\":\"0102030405060708090001020304050607080900010203040506070809000102\",\"subnets\":[]},\"fingerprint\":\"cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\",\"signature\":\"0102030405060708090001020304050607080900010203040506070809000102\"}\n{\"details\":{\"curve\":\"CURVE25519\",\"groups\":[\"hi\"],\"ips\":[],\"isCa\":false,\"issuer\":\"\",\"name\":\"test\",\"notAfter\":\"0001-01-01T00:00:00Z\",\"notBefore\":\"0001-01-01T00:00:00Z\",\"publicKey\":\"0102030405060708090001020304050607080900010203040506070809000102\",\"subnets\":[]},\"fingerprint\":\"cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\",\"signature\":\"0102030405060708090001020304050607080900010203040506070809000102\"}\n{\"details\":{\"curve\":\"CURVE25519\",\"groups\":[\"hi\"],\"ips\":[],\"isCa\":false,\"issuer\":\"\",\"name\":\"test\",\"notAfter\":\"0001-01-01T00:00:00Z\",\"notBefore\":\"0001-01-01T00:00:00Z\",\"publicKey\":\"0102030405060708090001020304050607080900010203040506070809000102\",\"subnets\":[]},\"fingerprint\":\"cc3492c0e9c48f17547f5987ea807462ebb3451e622590a10bb3763c344c82bd\",\"signature\":\"0102030405060708090001020304050607080900010203040506070809000102\"}\n", ob.String(), ) assert.Equal(t, "", eb.String()) diff --git a/cmd/nebula-cert/sign.go b/cmd/nebula-cert/sign.go index 68104ad..9938401 100644 --- a/cmd/nebula-cert/sign.go +++ b/cmd/nebula-cert/sign.go @@ -1,7 +1,7 @@ package main import ( - "crypto/ed25519" + "crypto/ecdh" "crypto/rand" "flag" "fmt" @@ -78,10 +78,11 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) return fmt.Errorf("error while reading ca-key: %s", err) } - var caKey ed25519.PrivateKey + var curve cert.Curve + var caKey []byte // naively attempt to decode the private key as though it is not encrypted - caKey, _, err = cert.UnmarshalEd25519PrivateKey(rawCAKey) + caKey, _, curve, err = cert.UnmarshalSigningPrivateKey(rawCAKey) if err == cert.ErrPrivateKeyEncrypted { // ask for a passphrase until we get one var passphrase []byte @@ -103,7 +104,7 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) return fmt.Errorf("cannot open encrypted ca-key without passphrase") } - caKey, _, err = cert.DecryptAndUnmarshalEd25519PrivateKey(passphrase, rawCAKey) + curve, caKey, _, err = cert.DecryptAndUnmarshalSigningPrivateKey(passphrase, rawCAKey) if err != nil { return fmt.Errorf("error while parsing encrypted ca-key: %s", err) } @@ -121,7 +122,7 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) return fmt.Errorf("error while parsing ca-crt: %s", err) } - if err := caCert.VerifyPrivateKey(caKey); err != nil { + if err := caCert.VerifyPrivateKey(curve, caKey); err != nil { return fmt.Errorf("refusing to sign, root certificate does not match private key") } @@ -181,12 +182,16 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) if err != nil { return fmt.Errorf("error while reading in-pub: %s", err) } - pub, _, err = cert.UnmarshalX25519PublicKey(rawPub) + var pubCurve cert.Curve + pub, _, pubCurve, err = cert.UnmarshalPublicKey(rawPub) if err != nil { return fmt.Errorf("error while parsing in-pub: %s", err) } + if pubCurve != curve { + return fmt.Errorf("curve of in-pub does not match ca") + } } else { - pub, rawPriv = x25519Keypair() + pub, rawPriv = newKeypair(curve) } nc := cert.NebulaCertificate{ @@ -200,6 +205,7 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) PublicKey: pub, IsCA: false, Issuer: issuer, + Curve: curve, }, } @@ -219,7 +225,7 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) return fmt.Errorf("refusing to overwrite existing cert: %s", *sf.outCertPath) } - err = nc.Sign(caKey) + err = nc.Sign(curve, caKey) if err != nil { return fmt.Errorf("error while signing: %s", err) } @@ -229,7 +235,7 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) return fmt.Errorf("refusing to overwrite existing key: %s", *sf.outKeyPath) } - err = ioutil.WriteFile(*sf.outKeyPath, cert.MarshalX25519PrivateKey(rawPriv), 0600) + err = ioutil.WriteFile(*sf.outKeyPath, cert.MarshalPrivateKey(curve, rawPriv), 0600) if err != nil { return fmt.Errorf("error while writing out-key: %s", err) } @@ -260,6 +266,17 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) return nil } +func newKeypair(curve cert.Curve) ([]byte, []byte) { + switch curve { + case cert.Curve_CURVE25519: + return x25519Keypair() + case cert.Curve_P256: + return p256Keypair() + default: + return nil, nil + } +} + func x25519Keypair() ([]byte, []byte) { privkey := make([]byte, 32) if _, err := io.ReadFull(rand.Reader, privkey); err != nil { @@ -274,6 +291,15 @@ func x25519Keypair() ([]byte, []byte) { return pubkey, privkey } +func p256Keypair() ([]byte, []byte) { + privkey, err := ecdh.P256().GenerateKey(rand.Reader) + if err != nil { + panic(err) + } + pubkey := privkey.PublicKey() + return pubkey.Bytes(), privkey.Bytes() +} + func signSummary() string { return "sign : create and sign a certificate" } diff --git a/cmd/nebula-cert/sign_test.go b/cmd/nebula-cert/sign_test.go index afde357..7018fd2 100644 --- a/cmd/nebula-cert/sign_test.go +++ b/cmd/nebula-cert/sign_test.go @@ -359,7 +359,7 @@ func Test_signCert(t *testing.T) { // generate the encrypted key caPub, caPriv, _ = ed25519.GenerateKey(rand.Reader) kdfParams := cert.NewArgon2Parameters(64*1024, 4, 3) - b, _ = cert.EncryptAndMarshalEd25519PrivateKey(caPriv, passphrase, kdfParams) + b, _ = cert.EncryptAndMarshalSigningPrivateKey(cert.Curve_CURVE25519, caPriv, passphrase, kdfParams) caKeyF.Write(b) ca = cert.NebulaCertificate{ diff --git a/cmd/nebula-cert/verify_test.go b/cmd/nebula-cert/verify_test.go index f562433..25014fd 100644 --- a/cmd/nebula-cert/verify_test.go +++ b/cmd/nebula-cert/verify_test.go @@ -77,7 +77,7 @@ func Test_verify(t *testing.T) { IsCA: true, }, } - ca.Sign(caPriv) + ca.Sign(cert.Curve_CURVE25519, caPriv) b, _ := ca.MarshalToPEM() caFile.Truncate(0) caFile.Seek(0, 0) @@ -117,7 +117,7 @@ func Test_verify(t *testing.T) { }, } - crt.Sign(badPriv) + crt.Sign(cert.Curve_CURVE25519, badPriv) b, _ = crt.MarshalToPEM() certFile.Truncate(0) certFile.Seek(0, 0) @@ -129,7 +129,7 @@ func Test_verify(t *testing.T) { assert.EqualError(t, err, "certificate signature did not match") // verified cert at path - crt.Sign(caPriv) + crt.Sign(cert.Curve_CURVE25519, caPriv) b, _ = crt.MarshalToPEM() certFile.Truncate(0) certFile.Seek(0, 0) diff --git a/connection_manager_test.go b/connection_manager_test.go index 2ea906b..3a25611 100644 --- a/connection_manager_test.go +++ b/connection_manager_test.go @@ -220,7 +220,7 @@ func Test_NewConnectionManagerTest_DisconnectInvalid(t *testing.T) { PublicKey: pubCA, }, } - caCert.Sign(privCA) + caCert.Sign(cert.Curve_CURVE25519, privCA) ncp := &cert.NebulaCAPool{ CAs: cert.NewCAPool().CAs, } @@ -239,7 +239,7 @@ func Test_NewConnectionManagerTest_DisconnectInvalid(t *testing.T) { Issuer: "ca", }, } - peerCert.Sign(privCA) + peerCert.Sign(cert.Curve_CURVE25519, privCA) cs := &CertState{ rawCertificate: []byte{}, diff --git a/connection_state.go b/connection_state.go index 4f8a577..ab818c9 100644 --- a/connection_state.go +++ b/connection_state.go @@ -29,12 +29,23 @@ type ConnectionState struct { } func (f *Interface) newConnectionState(l *logrus.Logger, initiator bool, pattern noise.HandshakePattern, psk []byte, pskStage int) *ConnectionState { - cs := noise.NewCipherSuite(noise.DH25519, noiseutil.CipherAESGCM, noise.HashSHA256) + var dhFunc noise.DHFunc + curCertState := f.certState.Load() + + switch curCertState.certificate.Details.Curve { + case cert.Curve_CURVE25519: + dhFunc = noise.DH25519 + case cert.Curve_P256: + dhFunc = noiseutil.DHP256 + default: + l.Errorf("invalid curve: %s", curCertState.certificate.Details.Curve) + return nil + } + cs := noise.NewCipherSuite(dhFunc, noiseutil.CipherAESGCM, noise.HashSHA256) if f.cipher == "chachapoly" { - cs = noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashSHA256) + cs = noise.NewCipherSuite(dhFunc, noise.CipherChaChaPoly, noise.HashSHA256) } - curCertState := f.certState.Load() static := noise.DHKey{Private: curCertState.privateKey, Public: curCertState.publicKey} b := NewBits(ReplayWindow) diff --git a/e2e/helpers_test.go b/e2e/helpers_test.go index e143feb..f2e3128 100644 --- a/e2e/helpers_test.go +++ b/e2e/helpers_test.go @@ -141,7 +141,7 @@ func newTestCaCert(before, after time.Time, ips, subnets []*net.IPNet, groups [] nc.Details.Groups = groups } - err = nc.Sign(priv) + err = nc.Sign(cert.Curve_CURVE25519, priv) if err != nil { panic(err) } @@ -187,7 +187,7 @@ func newTestCert(ca *cert.NebulaCertificate, key []byte, name string, before, af }, } - err = nc.Sign(key) + err = nc.Sign(ca.Details.Curve, key) if err != nil { panic(err) } diff --git a/noiseutil/nist.go b/noiseutil/nist.go new file mode 100644 index 0000000..90e77ab --- /dev/null +++ b/noiseutil/nist.go @@ -0,0 +1,68 @@ +package noiseutil + +import ( + "crypto/ecdh" + "crypto/rand" + "fmt" + "io" + + "github.com/flynn/noise" +) + +// DHP256 is the NIST P-256 ECDH function +var DHP256 noise.DHFunc = newNISTCurve("P256", ecdh.P256(), 32) + +type nistCurve struct { + name string + curve ecdh.Curve + dhLen int + pubLen int +} + +func newNISTCurve(name string, curve ecdh.Curve, byteLen int) nistCurve { + return nistCurve{ + name: name, + curve: curve, + dhLen: byteLen, + // Standard uncompressed format, type (1 byte) plus both coordinates + pubLen: 1 + 2*byteLen, + } +} + +func (c nistCurve) GenerateKeypair(rng io.Reader) (noise.DHKey, error) { + if rng == nil { + rng = rand.Reader + } + privkey, err := c.curve.GenerateKey(rng) + if err != nil { + return noise.DHKey{}, err + } + pubkey := privkey.PublicKey() + return noise.DHKey{Private: privkey.Bytes(), Public: pubkey.Bytes()}, nil +} + +func (c nistCurve) DH(privkey, pubkey []byte) ([]byte, error) { + ecdhPubKey, err := c.curve.NewPublicKey(pubkey) + if err != nil { + return nil, fmt.Errorf("unable to unmarshal pubkey: %w", err) + } + ecdhPrivKey, err := c.curve.NewPrivateKey(privkey) + if err != nil { + return nil, fmt.Errorf("unable to unmarshal pubkey: %w", err) + } + + return ecdhPrivKey.ECDH(ecdhPubKey) +} + +func (c nistCurve) DHLen() int { + // NOTE: Noise Protocol specifies "DHLen" to represent two things: + // - The size of the public key + // - The return size of the DH() function + // But for standard NIST ECDH, the sizes of these are different. + // Luckily, the flynn/noise library actually only uses this DHLen() + // value to represent the public key size, so that is what we are + // returning here. The length of the DH() return bytes are unaffected by + // this value here. + return c.pubLen +} +func (c nistCurve) DHName() string { return c.name }