Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Format certificates properly (rfc7468) with a trailing new line (#10411) #10556

Merged
merged 3 commits into from
Jul 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/10411.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
ca: Fixed a bug that returned a malformed certificate chain when the certificate did not having a trailing newline.
```
13 changes: 13 additions & 0 deletions agent/connect/ca/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"crypto/x509"
"fmt"
"strings"

"github.com/hashicorp/consul/agent/connect"
)
Expand Down Expand Up @@ -89,3 +90,15 @@ func validateSignIntermediate(csr *x509.CertificateRequest, spiffeID *connect.Sp
}
return nil
}

// EnsureTrailingNewline this is used to fix a case where the provider do not return a new line after
// the certificate as per the specification see GH-8178 for more context
func EnsureTrailingNewline(cert string) string {
if cert == "" {
return cert
}
if strings.HasSuffix(cert, "\n") {
return cert
}
return fmt.Sprintf("%s\n", cert)
}
1 change: 1 addition & 0 deletions agent/connect/ca/provider_aws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func testSignAndValidate(t *testing.T, p Provider, rootPEM string, intermediateP

err = connect.ValidateLeaf(rootPEM, leafPEM, intermediatePEMs)
require.NoError(t, err)
requireTrailingNewline(t, leafPEM)
}

func TestAWSBootstrapAndSignSecondary(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion agent/connect/ca/provider_consul_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func TestConsulCAProvider_SignLeaf(t *testing.T) {

cert, err := provider.Sign(csr)
require.NoError(err)

requireTrailingNewline(t, cert)
parsed, err := connect.ParseCert(cert)
require.NoError(err)
require.Equal(spiffeService.URI(), parsed.URIs[0])
Expand Down
8 changes: 4 additions & 4 deletions agent/connect/ca/provider_vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ func (v *VaultProvider) getCA(path string) (string, error) {
return "", err
}

root := string(bytes)
root := EnsureTrailingNewline(string(bytes))
if root == "" {
return "", ErrBackendNotInitialized
}
Expand Down Expand Up @@ -437,7 +437,7 @@ func (v *VaultProvider) Sign(csr *x509.CertificateRequest) (string, error) {
return "", fmt.Errorf("issuing_ca was not a string")
}

return fmt.Sprintf("%s\n%s", cert, ca), nil
return EnsureTrailingNewline(cert) + EnsureTrailingNewline(ca), nil
}

// SignIntermediate returns a signed CA certificate with a path length constraint
Expand Down Expand Up @@ -474,7 +474,7 @@ func (v *VaultProvider) SignIntermediate(csr *x509.CertificateRequest) (string,
return "", fmt.Errorf("signed intermediate result is not a string")
}

return intermediate, nil
return EnsureTrailingNewline(intermediate), nil
}

// CrossSignCA takes a CA certificate and cross-signs it to form a trust chain
Expand Down Expand Up @@ -514,7 +514,7 @@ func (v *VaultProvider) CrossSignCA(cert *x509.Certificate) (string, error) {
return "", fmt.Errorf("certificate was not a string")
}

return xcCert, nil
return EnsureTrailingNewline(xcCert), nil
}

// SupportsCrossSigning implements Provider
Expand Down
12 changes: 3 additions & 9 deletions agent/connect/ca/provider_vault_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ func TestVaultCAProvider_VaultTLSConfig(t *testing.T) {
}

func TestVaultCAProvider_SecondaryActiveIntermediate(t *testing.T) {
t.Parallel()

SkipIfVaultNotPresent(t)

Expand All @@ -50,7 +49,7 @@ func TestVaultCAProvider_SecondaryActiveIntermediate(t *testing.T) {
}

func TestVaultCAProvider_RenewToken(t *testing.T) {
t.Parallel()

SkipIfVaultNotPresent(t)

testVault, err := runTestVault(t)
Expand Down Expand Up @@ -86,7 +85,6 @@ func TestVaultCAProvider_RenewToken(t *testing.T) {
}

func TestVaultCAProvider_Bootstrap(t *testing.T) {
t.Parallel()

SkipIfVaultNotPresent(t)

Expand Down Expand Up @@ -119,7 +117,7 @@ func TestVaultCAProvider_Bootstrap(t *testing.T) {
require.NoError(err)
bytes, err := ioutil.ReadAll(resp.Body)
require.NoError(err)
require.Equal(cert, string(bytes))
require.Equal(cert, string(bytes)+"\n")

// Should be a valid CA cert
parsed, err := connect.ParseCert(cert)
Expand Down Expand Up @@ -147,7 +145,6 @@ func assertCorrectKeyType(t *testing.T, want, certPEM string) {
}

func TestVaultCAProvider_SignLeaf(t *testing.T) {
t.Parallel()

SkipIfVaultNotPresent(t)

Expand Down Expand Up @@ -200,6 +197,7 @@ func TestVaultCAProvider_SignLeaf(t *testing.T) {

// Make sure we can validate the cert as expected.
require.NoError(connect.ValidateLeaf(rootPEM, cert, []string{intPEM}))
requireTrailingNewline(t, cert)
}

// Generate a new cert for another service and make sure
Expand Down Expand Up @@ -231,7 +229,6 @@ func TestVaultCAProvider_SignLeaf(t *testing.T) {
}

func TestVaultCAProvider_CrossSignCA(t *testing.T) {
t.Parallel()

SkipIfVaultNotPresent(t)

Expand Down Expand Up @@ -286,7 +283,6 @@ func TestVaultCAProvider_CrossSignCA(t *testing.T) {
}

func TestVaultProvider_SignIntermediate(t *testing.T) {
t.Parallel()

SkipIfVaultNotPresent(t)

Expand Down Expand Up @@ -315,7 +311,6 @@ func TestVaultProvider_SignIntermediate(t *testing.T) {
}

func TestVaultProvider_SignIntermediateConsul(t *testing.T) {
t.Parallel()

SkipIfVaultNotPresent(t)

Expand Down Expand Up @@ -360,7 +355,6 @@ func TestVaultProvider_SignIntermediateConsul(t *testing.T) {
}

func TestVaultProvider_Cleanup(t *testing.T) {
t.Parallel()

SkipIfVaultNotPresent(t)

Expand Down
9 changes: 9 additions & 0 deletions agent/connect/ca/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,12 @@ func ApplyCARequestToStore(store *state.Store, req *structs.CARequest) (interfac
return nil, fmt.Errorf("Invalid CA operation '%s'", req.Op)
}
}
func requireTrailingNewline(t testing.T, leafPEM string) {
t.Helper()
if len(leafPEM) == 0 {
t.Fatalf("cert is empty")
}
if '\n' != rune(leafPEM[len(leafPEM)-1]) {
t.Fatalf("cert do not end with a new line")
}
}
6 changes: 3 additions & 3 deletions agent/connect_ca_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func (s *HTTPServer) ConnectCARoots(resp http.ResponseWriter, req *http.Request)

// defined in RFC 8555 and registered with the IANA
resp.Header().Set("Content-Type", "application/pem-certificate-chain")

for _, root := range reply.Roots {
if _, err := resp.Write([]byte(root.RootCert)); err != nil {
return nil, err
Expand All @@ -47,7 +48,6 @@ func (s *HTTPServer) ConnectCARoots(resp http.ResponseWriter, req *http.Request)
}
}
}

return nil, nil
}

Expand All @@ -58,7 +58,7 @@ func (s *HTTPServer) ConnectCAConfiguration(resp http.ResponseWriter, req *http.
return s.ConnectCAConfigurationGet(resp, req)

case "PUT":
return s.ConnectCAConfigurationSet(resp, req)
return s.ConnectCAConfigurationSet(req)

default:
return nil, MethodNotAllowedError{req.Method, []string{"GET", "POST"}}
Expand All @@ -83,7 +83,7 @@ func (s *HTTPServer) ConnectCAConfigurationGet(resp http.ResponseWriter, req *ht
}

// PUT /v1/connect/ca/configuration
func (s *HTTPServer) ConnectCAConfigurationSet(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
func (s *HTTPServer) ConnectCAConfigurationSet(req *http.Request) (interface{}, error) {
// Method is tested in ConnectCAConfiguration

var args structs.CARequest
Expand Down
28 changes: 12 additions & 16 deletions agent/connect_ca_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,21 @@ import (
func TestConnectCARoots_empty(t *testing.T) {
t.Parallel()

require := require.New(t)
a := NewTestAgent(t, "connect { enabled = false }")
defer a.Shutdown()
testrpc.WaitForTestAgent(t, a.RPC, "dc1")

req, _ := http.NewRequest("GET", "/v1/connect/ca/roots", nil)
resp := httptest.NewRecorder()
_, err := a.srv.ConnectCARoots(resp, req)
require.Error(err)
require.Contains(err.Error(), "Connect must be enabled")
require.Error(t, err)
require.Contains(t, err.Error(), "Connect must be enabled")
}

func TestConnectCARoots_list(t *testing.T) {
t.Parallel()

assert := assert.New(t)
assertion := assert.New(t)
a := NewTestAgent(t, "")
defer a.Shutdown()
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
Expand All @@ -48,16 +47,16 @@ func TestConnectCARoots_list(t *testing.T) {
req, _ := http.NewRequest("GET", "/v1/connect/ca/roots", nil)
resp := httptest.NewRecorder()
obj, err := a.srv.ConnectCARoots(resp, req)
assert.NoError(err)
assertion.NoError(err)

value := obj.(structs.IndexedCARoots)
assert.Equal(value.ActiveRootID, ca2.ID)
assert.Len(value.Roots, 2)
assertion.Equal(value.ActiveRootID, ca2.ID)
assertion.Len(value.Roots, 2)

// We should never have the secret information
for _, r := range value.Roots {
assert.Equal("", r.SigningCert)
assert.Equal("", r.SigningKey)
assertion.Equal("", r.SigningCert)
assertion.Equal("", r.SigningKey)
}
}

Expand Down Expand Up @@ -213,7 +212,6 @@ func TestConnectCAConfig(t *testing.T) {
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)
hcl := ""
if tc.initialState != "" {
hcl = `
Expand All @@ -239,23 +237,23 @@ func TestConnectCAConfig(t *testing.T) {
resp := httptest.NewRecorder()
_, err := a.srv.ConnectCAConfiguration(resp, req)
if tc.wantErr {
require.Error(err)
require.Error(t, err)
return
}
require.NoError(err)
require.NoError(t, err)
}
// The config should be updated now.
{
req, _ := http.NewRequest("GET", "/v1/connect/ca/configuration", nil)
resp := httptest.NewRecorder()
obj, err := a.srv.ConnectCAConfiguration(resp, req)
require.NoError(err)
require.NoError(t, err)

got := obj.(structs.CAConfiguration)
// Reset Raft indexes to make it non flaky
got.CreateIndex = 0
got.ModifyIndex = 0
require.Equal(tc.wantCfg, got)
require.Equal(t, tc.wantCfg, got)
}
})
}
Expand Down Expand Up @@ -284,9 +282,7 @@ func TestConnectCARoots_PEMEncoding(t *testing.T) {

data, err := ioutil.ReadAll(resp.Body)
require.NoError(t, err)

pool := x509.NewCertPool()

require.True(t, pool.AppendCertsFromPEM(data))
// expecting the root cert from dc1 and an intermediate in dc2
require.Len(t, pool.Subjects(), 2)
Expand Down
8 changes: 6 additions & 2 deletions agent/consul/leader_connect_ca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ type mockCAProvider struct {
func (m *mockCAProvider) Configure(cfg ca.ProviderConfig) error { return nil }
func (m *mockCAProvider) State() (map[string]string, error) { return nil, nil }
func (m *mockCAProvider) GenerateRoot() error { return nil }
func (m *mockCAProvider) ActiveRoot() (string, error) { return m.rootPEM, nil }
func (m *mockCAProvider) ActiveRoot() (string, error) {
return m.rootPEM, nil
}
func (m *mockCAProvider) GenerateIntermediateCSR() (string, error) {
m.callbackCh <- "provider/GenerateIntermediateCSR"
return "", nil
Expand All @@ -134,7 +136,9 @@ func (m *mockCAProvider) SetIntermediate(intermediatePEM, rootPEM string) error
m.callbackCh <- "provider/SetIntermediate"
return nil
}
func (m *mockCAProvider) ActiveIntermediate() (string, error) { return m.rootPEM, nil }
func (m *mockCAProvider) ActiveIntermediate() (string, error) {
return m.rootPEM, nil
}
func (m *mockCAProvider) GenerateIntermediate() (string, error) { return "", nil }
func (m *mockCAProvider) Sign(*x509.CertificateRequest) (string, error) { return "", nil }
func (m *mockCAProvider) SignIntermediate(*x509.CertificateRequest) (string, error) { return "", nil }
Expand Down
17 changes: 11 additions & 6 deletions agent/consul/server_connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"crypto/x509"
"fmt"
"net/url"
"strings"
"sync"

"github.com/hashicorp/consul/agent/connect"
Expand Down Expand Up @@ -104,6 +103,13 @@ func (s *Server) getCARoots(ws memdb.WatchSet, state *state.Store) (*structs.Ind
// data such as key material stored within the struct. So here we
// pull out some of the fields and copy them into
for i, r := range indexedRoots.Roots {
var intermediates []string
if r.IntermediateCerts != nil {
intermediates = make([]string, len(r.IntermediateCerts))
for i, intermediate := range r.IntermediateCerts {
intermediates[i] = intermediate
}
}
// IMPORTANT: r must NEVER be modified, since it is a pointer
// directly to the structure in the memdb store.

Expand All @@ -115,8 +121,8 @@ func (s *Server) getCARoots(ws memdb.WatchSet, state *state.Store) (*structs.Ind
ExternalTrustDomain: r.ExternalTrustDomain,
NotBefore: r.NotBefore,
NotAfter: r.NotAfter,
RootCert: r.RootCert,
IntermediateCerts: r.IntermediateCerts,
RootCert: ca.EnsureTrailingNewline(r.RootCert),
IntermediateCerts: intermediates,
RaftIndex: r.RaftIndex,
Active: r.Active,
PrivateKeyType: r.PrivateKeyType,
Expand Down Expand Up @@ -219,7 +225,7 @@ func (s *Server) SignCertificate(csr *x509.CertificateRequest, spiffeID connect.

// Append any intermediates needed by this root.
for _, p := range caRoot.IntermediateCerts {
pem = strings.TrimSpace(pem) + "\n" + p
pem = pem + ca.EnsureTrailingNewline(p)
}

// Append our local CA's intermediate if there is one.
Expand All @@ -231,9 +237,8 @@ func (s *Server) SignCertificate(csr *x509.CertificateRequest, spiffeID connect.
if err != nil {
return nil, err
}

if inter != root {
pem = strings.TrimSpace(pem) + "\n" + inter
pem = pem + ca.EnsureTrailingNewline(inter)
}

// TODO(banks): when we implement IssuedCerts table we can use the insert to
Expand Down
Loading