2
0
Эх сурвалжийг харах

Check CA cert and key match in nebula-cert sign (#503)

`func (nc *NebulaCertificate) VerifyPrivateKey(key []byte) error` would
previously return an error even if passed the correct private key for a
CA certificate `nc`.

That function has been updated to support CA certificates, and
nebula-cert now calls it before signing a new certificate. Previously,
it would perform all constraint checks against the CA certificate
provided, take a SHA256 fingerprint of the provided certificate, insert
it into the new node certificate, and then finally sign it with the
mismatching private key provided.
John Maguire 3 жил өмнө
parent
commit
34d002d695

+ 3 - 0
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)
 - 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
 ## [1.4.0] - 2021-05-11
 
 
 ### Added
 ### Added

+ 13 - 0
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
 // 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(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
 	var dst, key32 [32]byte
 	copy(key32[:], key)
 	copy(key32[:], key)
 	curve25519.ScalarBaseMult(&dst, &key32)
 	curve25519.ScalarBaseMult(&dst, &key32)
 	if !bytes.Equal(dst[:], nc.Details.PublicKey) {
 	if !bytes.Equal(dst[:], nc.Details.PublicKey) {
 		return fmt.Errorf("public key in cert and private key supplied don't match")
 		return fmt.Errorf("public key in cert and private key supplied don't match")
 	}
 	}
+
 	return nil
 	return nil
 }
 }
 
 

+ 8 - 1
cert/cert_test.go

@@ -375,9 +375,16 @@ func TestNebulaCertificate_Verify_Subnets(t *testing.T) {
 	assert.Nil(t, err)
 	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{})
 	ca, _, caKey, err := newTestCaCert(time.Time{}, time.Time{}, []*net.IPNet{}, []*net.IPNet{}, []string{})
 	assert.Nil(t, err)
 	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{})
 	c, _, priv, err := newTestCert(ca, caKey, time.Time{}, time.Time{}, []*net.IPNet{}, []*net.IPNet{}, []string{})
 	err = c.VerifyPrivateKey(priv)
 	err = c.VerifyPrivateKey(priv)

+ 4 - 0
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)
 		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()
 	issuer, err := caCert.Sha256Sum()
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("error while getting -ca-crt fingerprint: %s", err)
 		return fmt.Errorf("error while getting -ca-crt fingerprint: %s", err)

+ 14 - 0
cmd/nebula-cert/sign_test.go

@@ -167,6 +167,20 @@ func Test_signCert(t *testing.T) {
 	assert.Empty(t, ob.String())
 	assert.Empty(t, ob.String())
 	assert.Empty(t, eb.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
 	// failed key write
 	ob.Reset()
 	ob.Reset()
 	eb.Reset()
 	eb.Reset()