0455389534
Co-authored-by: James Rasell <jrasell@users.noreply.github.com>
272 lines
8.1 KiB
Go
272 lines
8.1 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package tlsutil
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"io"
|
|
"net"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/nomad/ci"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestSerialNumber(t *testing.T) {
|
|
n1, err := GenerateSerialNumber()
|
|
require.Nil(t, err)
|
|
|
|
n2, err := GenerateSerialNumber()
|
|
require.Nil(t, err)
|
|
require.NotEqual(t, n1, n2)
|
|
|
|
n3, err := GenerateSerialNumber()
|
|
require.Nil(t, err)
|
|
require.NotEqual(t, n1, n3)
|
|
require.NotEqual(t, n2, n3)
|
|
|
|
}
|
|
|
|
func TestGeneratePrivateKey(t *testing.T) {
|
|
ci.Parallel(t)
|
|
_, p, err := GeneratePrivateKey()
|
|
require.Nil(t, err)
|
|
require.NotEmpty(t, p)
|
|
require.Contains(t, p, "BEGIN EC PRIVATE KEY")
|
|
require.Contains(t, p, "END EC PRIVATE KEY")
|
|
|
|
block, _ := pem.Decode([]byte(p))
|
|
pk, err := x509.ParseECPrivateKey(block.Bytes)
|
|
|
|
require.Nil(t, err)
|
|
require.NotNil(t, pk)
|
|
require.Equal(t, 256, pk.Params().BitSize)
|
|
}
|
|
|
|
type TestSigner struct {
|
|
public interface{}
|
|
}
|
|
|
|
func (s *TestSigner) Public() crypto.PublicKey {
|
|
return s.public
|
|
}
|
|
|
|
func (s *TestSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
|
return []byte{}, nil
|
|
}
|
|
|
|
func TestGenerateCA(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
t.Run("no signer", func(t *testing.T) {
|
|
ca, pk, err := GenerateCA(CAOpts{Signer: &TestSigner{}})
|
|
require.Error(t, err)
|
|
require.Empty(t, ca)
|
|
require.Empty(t, pk)
|
|
})
|
|
|
|
t.Run("wrong key", func(t *testing.T) {
|
|
ca, pk, err := GenerateCA(CAOpts{Signer: &TestSigner{public: &rsa.PublicKey{}}})
|
|
require.Error(t, err)
|
|
require.Empty(t, ca)
|
|
require.Empty(t, pk)
|
|
})
|
|
|
|
t.Run("valid key", func(t *testing.T) {
|
|
ca, pk, err := GenerateCA(CAOpts{})
|
|
require.Nil(t, err)
|
|
require.NotEmpty(t, ca)
|
|
require.NotEmpty(t, pk)
|
|
|
|
cert, err := parseCert(ca)
|
|
require.Nil(t, err)
|
|
require.True(t, strings.HasPrefix(cert.Subject.CommonName, "Nomad Agent CA"))
|
|
require.Equal(t, true, cert.IsCA)
|
|
require.Equal(t, true, cert.BasicConstraintsValid)
|
|
|
|
require.WithinDuration(t, cert.NotBefore, time.Now(), time.Minute)
|
|
require.WithinDuration(t, cert.NotAfter, time.Now().AddDate(0, 0, 1825), time.Minute)
|
|
|
|
require.Equal(t, x509.KeyUsageCertSign|x509.KeyUsageCRLSign|x509.KeyUsageDigitalSignature, cert.KeyUsage)
|
|
})
|
|
|
|
t.Run("RSA key", func(t *testing.T) {
|
|
ca, pk, err := GenerateCA(CAOpts{})
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, ca)
|
|
require.NotEmpty(t, pk)
|
|
|
|
cert, err := parseCert(ca)
|
|
require.NoError(t, err)
|
|
require.True(t, strings.HasPrefix(cert.Subject.CommonName, "Nomad Agent CA"))
|
|
require.Equal(t, true, cert.IsCA)
|
|
require.Equal(t, true, cert.BasicConstraintsValid)
|
|
|
|
require.WithinDuration(t, cert.NotBefore, time.Now(), time.Minute)
|
|
require.WithinDuration(t, cert.NotAfter, time.Now().AddDate(0, 0, 1825), time.Minute)
|
|
|
|
require.Equal(t, x509.KeyUsageCertSign|x509.KeyUsageCRLSign|x509.KeyUsageDigitalSignature, cert.KeyUsage)
|
|
})
|
|
|
|
t.Run("Custom CA", func(t *testing.T) {
|
|
ca, pk, err := GenerateCA(CAOpts{
|
|
Days: 6,
|
|
PermittedDNSDomains: []string{"domain1.com"},
|
|
Country: "ZZ",
|
|
PostalCode: "0000",
|
|
Province: "CustProvince",
|
|
Locality: "CustLocality",
|
|
StreetAddress: "CustStreet",
|
|
Organization: "CustOrg",
|
|
OrganizationalUnit: "CustUnit",
|
|
Name: "Custom CA",
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, ca)
|
|
require.NotEmpty(t, pk)
|
|
|
|
cert, err := parseCert(ca)
|
|
require.NoError(t, err)
|
|
require.True(t, strings.HasPrefix(cert.Subject.CommonName, "Custom CA"))
|
|
require.True(t, strings.Contains(cert.PermittedDNSDomains[0], "domain1.com"))
|
|
require.True(t, strings.Contains(cert.Subject.Country[0], "ZZ"))
|
|
require.True(t, strings.Contains(cert.Subject.PostalCode[0], "0000"))
|
|
require.True(t, strings.Contains(cert.Subject.Province[0], "CustProvince"))
|
|
require.True(t, strings.Contains(cert.Subject.Locality[0], "CustLocality"))
|
|
require.True(t, strings.Contains(cert.Subject.StreetAddress[0], "CustStreet"))
|
|
require.True(t, strings.Contains(cert.Subject.Organization[0], "CustOrg"))
|
|
require.True(t, strings.Contains(cert.Subject.OrganizationalUnit[0], "CustUnit"))
|
|
require.Equal(t, true, cert.IsCA)
|
|
require.Equal(t, true, cert.BasicConstraintsValid)
|
|
|
|
require.WithinDuration(t, cert.NotBefore, time.Now(), time.Minute)
|
|
require.WithinDuration(t, cert.NotAfter, time.Now().AddDate(0, 0, 6), time.Minute)
|
|
|
|
require.Equal(t, x509.KeyUsageCertSign|x509.KeyUsageCRLSign|x509.KeyUsageDigitalSignature, cert.KeyUsage)
|
|
})
|
|
|
|
t.Run("Custom CA Custom Date", func(t *testing.T) {
|
|
ca, pk, err := GenerateCA(CAOpts{
|
|
Days: 365,
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, ca)
|
|
require.NotEmpty(t, pk)
|
|
|
|
cert, err := parseCert(ca)
|
|
require.WithinDuration(t, cert.NotAfter, time.Now().AddDate(0, 0, 365), time.Minute)
|
|
})
|
|
|
|
t.Run("Custom CA No CN", func(t *testing.T) {
|
|
ca, pk, err := GenerateCA(CAOpts{
|
|
Days: 6,
|
|
PermittedDNSDomains: []string{"domain1.com"},
|
|
Locality: "CustLocality",
|
|
})
|
|
require.ErrorContains(t, err, "common name value not provided")
|
|
require.Empty(t, ca)
|
|
require.Empty(t, pk)
|
|
})
|
|
|
|
t.Run("Custom CA No Country", func(t *testing.T) {
|
|
ca, pk, err := GenerateCA(CAOpts{
|
|
Days: 6,
|
|
PermittedDNSDomains: []string{"domain1.com"},
|
|
Name: "Custom CA",
|
|
Locality: "CustLocality",
|
|
})
|
|
require.ErrorContains(t, err, "country value not provided")
|
|
require.Empty(t, ca)
|
|
require.Empty(t, pk)
|
|
})
|
|
|
|
t.Run("Custom CA No Organization", func(t *testing.T) {
|
|
ca, pk, err := GenerateCA(CAOpts{
|
|
Days: 6,
|
|
PermittedDNSDomains: []string{"domain1.com"},
|
|
Name: "Custom CA",
|
|
Country: "ZZ",
|
|
Locality: "CustLocality",
|
|
})
|
|
require.ErrorContains(t, err, "organization value not provided")
|
|
// require.NoError(t, err)
|
|
require.Empty(t, ca)
|
|
require.Empty(t, pk)
|
|
})
|
|
|
|
t.Run("Custom CA No Organizational Unit", func(t *testing.T) {
|
|
ca, pk, err := GenerateCA(CAOpts{
|
|
Days: 6,
|
|
PermittedDNSDomains: []string{"domain1.com"},
|
|
Name: "Custom CA",
|
|
Country: "ZZ",
|
|
Locality: "CustLocality",
|
|
Organization: "CustOrg",
|
|
})
|
|
require.ErrorContains(t, err, "organizational unit value not provided")
|
|
require.Empty(t, ca)
|
|
require.Empty(t, pk)
|
|
})
|
|
}
|
|
|
|
func TestGenerateCert(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
require.Nil(t, err)
|
|
ca, _, err := GenerateCA(CAOpts{
|
|
Name: "Custom CA",
|
|
Country: "ZZ",
|
|
Organization: "CustOrg",
|
|
OrganizationalUnit: "CustOrgUnit",
|
|
Signer: signer},
|
|
)
|
|
require.Nil(t, err)
|
|
|
|
DNSNames := []string{"server.dc1.nomad"}
|
|
IPAddresses := []net.IP{net.ParseIP("123.234.243.213")}
|
|
extKeyUsage := []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
|
|
name := "Cert Name"
|
|
certificate, pk, err := GenerateCert(CertOpts{
|
|
Signer: signer, CA: ca, Name: name, Days: 365,
|
|
DNSNames: DNSNames, IPAddresses: IPAddresses, ExtKeyUsage: extKeyUsage,
|
|
})
|
|
require.Nil(t, err)
|
|
require.NotEmpty(t, certificate)
|
|
require.NotEmpty(t, pk)
|
|
|
|
cert, err := parseCert(certificate)
|
|
require.Nil(t, err)
|
|
require.Equal(t, name, cert.Subject.CommonName)
|
|
require.Equal(t, true, cert.BasicConstraintsValid)
|
|
signee, err := ParseSigner(pk)
|
|
require.Nil(t, err)
|
|
certID, err := keyID(signee.Public())
|
|
require.Nil(t, err)
|
|
require.Equal(t, certID, cert.SubjectKeyId)
|
|
caID, err := keyID(signer.Public())
|
|
require.Nil(t, err)
|
|
require.Equal(t, caID, cert.AuthorityKeyId)
|
|
require.Contains(t, cert.Issuer.CommonName, "Custom CA")
|
|
require.Equal(t, false, cert.IsCA)
|
|
|
|
require.WithinDuration(t, cert.NotBefore, time.Now(), time.Minute)
|
|
require.WithinDuration(t, cert.NotAfter, time.Now().AddDate(0, 0, 365), time.Minute)
|
|
|
|
require.Equal(t, x509.KeyUsageDigitalSignature|x509.KeyUsageKeyEncipherment, cert.KeyUsage)
|
|
require.Equal(t, extKeyUsage, cert.ExtKeyUsage)
|
|
|
|
// https://github.com/golang/go/blob/10538a8f9e2e718a47633ac5a6e90415a2c3f5f1/src/crypto/x509/verify.go#L414
|
|
require.Equal(t, DNSNames, cert.DNSNames)
|
|
require.True(t, IPAddresses[0].Equal(cert.IPAddresses[0]))
|
|
}
|