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

Add FIPS TLS encrypted private key tests #281

Merged
merged 9 commits into from
Feb 21, 2025
15 changes: 15 additions & 0 deletions .buildkite/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ steps:
artifact_paths:
- "junit-*.xml"

- label: ":linux: Test Linux FIPS"
key: test-lin-fips
command:
- ".buildkite/scripts/test.sh"
env:
FIPS: "true"
agents:
image: golang:${GO_VERSION}
cpu: "8"
memory: "4G"
artifact_paths:
- "junit-*.xml"

- label: ":windows: Test Windows"
key: test-win
command:
Expand Down Expand Up @@ -53,6 +66,8 @@ steps:
depends_on:
- step: "test-lin"
allow_failure: true
- step: "test-lin-fips"
allow_failure: true
- step: "test-win"
allow_failure: true
- step: "test-mac"
Expand Down
7 changes: 6 additions & 1 deletion .buildkite/scripts/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ go version
add_bin_path
with_go_junit_report

tags=integration
if [[ "${FIPS:-false}" == "true" ]]; then
tags="${tags},requirefips"
fi

echo "--- Go Test"
set +e
go test -tags integration -race -v ./... > tests-report.txt
go test -tags=${tags} -race -v ./... > tests-report.txt
exit_code=$?
set -e

Expand Down
3 changes: 2 additions & 1 deletion transport/tlscommon/diag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ import (
"encoding/pem"
"testing"

"github.com/elastic/elastic-agent-libs/transport/tlscommontest"
"github.com/stretchr/testify/require"

"github.com/elastic/elastic-agent-libs/transport/tlscommontest"
)

const verificationDefault = "verification_mode=full"
Expand Down
3 changes: 2 additions & 1 deletion transport/tlscommon/server_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ package tlscommon
import (
"testing"

"github.com/elastic/go-ucfg"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v2"

"github.com/elastic/go-ucfg"
)

// variables so we can use pointers in tests
Expand Down
7 changes: 5 additions & 2 deletions transport/tlscommon/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ func ReadPEMFile(log *logp.Logger, s, passphrase string) ([]byte, error) {
return nil, err
}

var errs error
for len(content) > 0 {
var block *pem.Block

Expand All @@ -117,13 +118,15 @@ func ReadPEMFile(log *logp.Logger, s, passphrase string) ([]byte, error) {
block, err := decryptPKCS1Key(*block, pass)
if err != nil {
log.Errorf("Dropping encrypted pem block with private key, block type '%s': %s", block.Type, err)
errs = errors.Join(errs, err)
continue
}
blocks = append(blocks, &block)
case block.Type == "ENCRYPTED PRIVATE KEY":
block, err := decryptPKCS8Key(*block, pass)
if err != nil {
log.Errorf("Dropping encrypted pem block with private key, block type '%s', could not decypt as PKCS8: %s", block.Type, err)
log.Errorf("Dropping encrypted pem block with private key, block type '%s', could not decrypt as PKCS8: %s", block.Type, err)
errs = errors.Join(errs, err)
continue
}
blocks = append(blocks, &block)
Expand All @@ -133,7 +136,7 @@ func ReadPEMFile(log *logp.Logger, s, passphrase string) ([]byte, error) {
}

if len(blocks) == 0 {
return nil, errors.New("no PEM blocks")
return nil, errors.Join(errors.New("no PEM blocks"), errs)
}

// re-encode available, decrypted blocks
Expand Down
3 changes: 2 additions & 1 deletion transport/tlscommon/tls_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ import (
"net/url"
"testing"

"github.com/elastic/elastic-agent-libs/transport/tlscommontest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/elastic/elastic-agent-libs/transport/tlscommontest"
)

func TestMakeVerifyServerConnection(t *testing.T) {
Expand Down
60 changes: 60 additions & 0 deletions transport/tlscommon/tls_fips_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

//go:build requirefips

package tlscommon

import (
"errors"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// TestFIPSCertifacteAndKeys tests that encrypted private keys fail in FIPS mode
func TestFIPSCertificateAndKeys(t *testing.T) {
t.Run("embed encrypted PKCS#1 key", func(t *testing.T) {
// Create a dummy configuration and append the CA after.
password := "abcd1234"
key, cert := makeKeyCertPair(t, blockTypePKCS1Encrypted, password)
cfg, err := load(`enabled: true`)
require.NoError(t, err)
cfg.Certificate.Certificate = cert
cfg.Certificate.Key = key
cfg.Certificate.Passphrase = password

_, err = LoadTLSConfig(cfg)
require.Error(t, err)
assert.ErrorIs(t, err, errors.ErrUnsupported, err)
})

t.Run("embed encrypted PKCS#8 key", func(t *testing.T) {
// Create a dummy configuration and append the CA after.
password := "abcd1234"
key, cert := makeKeyCertPair(t, blockTypePKCS8Encrypted, password)
cfg, err := load(`enabled: true`)
require.NoError(t, err)
cfg.Certificate.Certificate = cert
cfg.Certificate.Key = key
cfg.Certificate.Passphrase = password

_, err = LoadTLSConfig(cfg)
assert.ErrorIs(t, err, errors.ErrUnsupported)
})
}
117 changes: 117 additions & 0 deletions transport/tlscommon/tls_nofips_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

//go:build !requirefips

package tlscommon

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// TestNoFIPSCertificateAndKeys tests that encrypted private keys are supported in none FIPS mode
func TestNoFIPSCertificateAndKeys(t *testing.T) {
t.Run("embed encrypted PKCS#1 key", func(t *testing.T) {
// Create a dummy configuration and append the CA after.
password := "abcd1234"
key, cert := makeKeyCertPair(t, blockTypePKCS1Encrypted, password)
cfg, err := load(`enabled: true`)
require.NoError(t, err)
cfg.Certificate.Certificate = string(cert)

Check failure on line 38 in transport/tlscommon/tls_nofips_test.go

View workflow job for this annotation

GitHub Actions / lint (windows)

unnecessary conversion (unconvert)
cfg.Certificate.Key = string(key)

Check failure on line 39 in transport/tlscommon/tls_nofips_test.go

View workflow job for this annotation

GitHub Actions / lint (windows)

unnecessary conversion (unconvert)
cfg.Certificate.Passphrase = password

tlsC, err := LoadTLSConfig(cfg)
require.NoError(t, err)
assert.NotNil(t, tlsC)
})

t.Run("embed PKCS#8 key", func(t *testing.T) {
// Create a dummy configuration and append the CA after.
password := "abcd1234"
key, cert := makeKeyCertPair(t, blockTypePKCS8Encrypted, password)
cfg, err := load(`enabled: true`)
require.NoError(t, err)
cfg.Certificate.Certificate = string(cert)

Check failure on line 53 in transport/tlscommon/tls_nofips_test.go

View workflow job for this annotation

GitHub Actions / lint (windows)

unnecessary conversion (unconvert)
cfg.Certificate.Key = string(key)

Check failure on line 54 in transport/tlscommon/tls_nofips_test.go

View workflow job for this annotation

GitHub Actions / lint (windows)

unnecessary conversion (unconvert)
cfg.Certificate.Passphrase = password

tlsC, err := LoadTLSConfig(cfg)
require.NoError(t, err)
assert.NotNil(t, tlsC)
})
}

func TestEncryptedKeyPassphrase(t *testing.T) {
const passphrase = "Abcd1234!" // passphrase for testdata/ca.encrypted.key
t.Run("no passphrase", func(t *testing.T) {
_, err := LoadTLSConfig(mustLoad(t, `
enabled: true
certificate: testdata/ca.crt
key: testdata/ca.encrypted.key
`))
assert.ErrorContains(t, err, "no PEM blocks") // ReadPEMFile will generate an internal "no passphrase available" error that is logged and the no PEM blocks error is returned instead
})

t.Run("wrong passphrase", func(t *testing.T) {
_, err := LoadTLSConfig(mustLoad(t, `
enabled: true
certificate: testdata/ca.crt
key: testdata/ca.encrypted.key
key_passphrase: "abcd1234!"
`))
assert.ErrorContains(t, err, "no PEM blocks") // ReadPEMFile will fail decrypting with x509.IncorrectPasswordError that will be logged and a no PEM blocks error is returned instead
})

t.Run("passphrase value", func(t *testing.T) {
cfg, err := LoadTLSConfig(mustLoad(t, `
enabled: true
certificate: testdata/ca.crt
key: testdata/ca.encrypted.key
key_passphrase: Abcd1234!
`))
require.NoError(t, err)
assert.Equal(t, 1, len(cfg.Certificates), "expected 1 certificate to be loaded")
})

t.Run("passphrase file", func(t *testing.T) {
fileName := writeTestFile(t, passphrase)
cfg, err := LoadTLSConfig(mustLoad(t, fmt.Sprintf(`
enabled: true
certificate: testdata/ca.crt
key: testdata/ca.encrypted.key
key_passphrase_path: %s
`, fileName)))
require.NoError(t, err)
assert.Equal(t, 1, len(cfg.Certificates), "expected 1 certificate to be loaded")
})

t.Run("passphrase file empty", func(t *testing.T) {
fileName := writeTestFile(t, "")
_, err := LoadTLSConfig(mustLoad(t, fmt.Sprintf(`
enabled: true
certificate: testdata/ca.crt
key: testdata/ca.encrypted.key
key_passphrase_path: %s
`, fileName)))
assert.ErrorContains(t, err, "no PEM blocks") // ReadPEMFile will generate an internal "no passphrase available" error that is logged and the no PEM blocks error is returned instead
})
}
Loading
Loading