diff --git a/CHANGELOG.md b/CHANGELOG.md index 79a9af2..51b4c65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - SSH server handles single `exec` requests correctly. (#483) +- Signing a certificate with `nebula-cert sign` now verifies that the supplied + ca-key matches the ca-crt. (#503) + ## [1.4.0] - 2021-05-11 ### Added diff --git a/cert/cert.go b/cert/cert.go index 4e0e02f..5eb4326 100644 --- a/cert/cert.go +++ b/cert/cert.go @@ -325,12 +325,25 @@ 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 { + 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") + } + + 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") + } + return nil + } + var dst, key32 [32]byte copy(key32[:], key) curve25519.ScalarBaseMult(&dst, &key32) if !bytes.Equal(dst[:], nc.Details.PublicKey) { return fmt.Errorf("public key in cert and private key supplied don't match") } + return nil } diff --git a/cert/cert_test.go b/cert/cert_test.go index dfb01d1..e5bdddc 100644 --- a/cert/cert_test.go +++ b/cert/cert_test.go @@ -375,9 +375,16 @@ func TestNebulaCertificate_Verify_Subnets(t *testing.T) { assert.Nil(t, err) } -func TestNebulaVerifyPrivateKey(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) + assert.Nil(t, err) + + _, _, caKey2, err := newTestCaCert(time.Time{}, time.Time{}, []*net.IPNet{}, []*net.IPNet{}, []string{}) + assert.Nil(t, err) + err = ca.VerifyPrivateKey(caKey2) + assert.NotNil(t, err) c, _, priv, err := newTestCert(ca, caKey, time.Time{}, time.Time{}, []*net.IPNet{}, []*net.IPNet{}, []string{}) err = c.VerifyPrivateKey(priv) diff --git a/cmd/nebula-cert/sign.go b/cmd/nebula-cert/sign.go index d122b2f..86de0b1 100644 --- a/cmd/nebula-cert/sign.go +++ b/cmd/nebula-cert/sign.go @@ -92,6 +92,10 @@ func signCert(args []string, out io.Writer, errOut io.Writer) error { return fmt.Errorf("error while parsing ca-crt: %s", err) } + if err := caCert.VerifyPrivateKey(caKey); err != nil { + return fmt.Errorf("refusing to sign, root certificate does not match private key") + } + issuer, err := caCert.Sha256Sum() if err != nil { return fmt.Errorf("error while getting -ca-crt fingerprint: %s", err) diff --git a/cmd/nebula-cert/sign_test.go b/cmd/nebula-cert/sign_test.go index 6227f3c..e1e9445 100644 --- a/cmd/nebula-cert/sign_test.go +++ b/cmd/nebula-cert/sign_test.go @@ -167,6 +167,20 @@ func Test_signCert(t *testing.T) { assert.Empty(t, ob.String()) assert.Empty(t, eb.String()) + // mismatched ca key + _, caPriv2, _ := ed25519.GenerateKey(rand.Reader) + caKeyF2, err := ioutil.TempFile("", "sign-cert-2.key") + assert.Nil(t, err) + defer os.Remove(caKeyF2.Name()) + caKeyF2.Write(cert.MarshalEd25519PrivateKey(caPriv2)) + + ob.Reset() + eb.Reset() + args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF2.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m", "-subnets", "a"} + assert.EqualError(t, signCert(args, ob, eb), "refusing to sign, root certificate does not match private key") + assert.Empty(t, ob.String()) + assert.Empty(t, eb.String()) + // failed key write ob.Reset() eb.Reset()