github.com/hernad/nomad@v1.6.112/helper/tlsutil/generate_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package tlsutil
     5  
     6  import (
     7  	"crypto"
     8  	"crypto/ecdsa"
     9  	"crypto/elliptic"
    10  	"crypto/rand"
    11  	"crypto/rsa"
    12  	"crypto/x509"
    13  	"encoding/pem"
    14  	"io"
    15  	"net"
    16  	"strings"
    17  	"testing"
    18  	"time"
    19  
    20  	"github.com/hernad/nomad/ci"
    21  	"github.com/stretchr/testify/require"
    22  )
    23  
    24  func TestSerialNumber(t *testing.T) {
    25  	n1, err := GenerateSerialNumber()
    26  	require.Nil(t, err)
    27  
    28  	n2, err := GenerateSerialNumber()
    29  	require.Nil(t, err)
    30  	require.NotEqual(t, n1, n2)
    31  
    32  	n3, err := GenerateSerialNumber()
    33  	require.Nil(t, err)
    34  	require.NotEqual(t, n1, n3)
    35  	require.NotEqual(t, n2, n3)
    36  
    37  }
    38  
    39  func TestGeneratePrivateKey(t *testing.T) {
    40  	ci.Parallel(t)
    41  	_, p, err := GeneratePrivateKey()
    42  	require.Nil(t, err)
    43  	require.NotEmpty(t, p)
    44  	require.Contains(t, p, "BEGIN EC PRIVATE KEY")
    45  	require.Contains(t, p, "END EC PRIVATE KEY")
    46  
    47  	block, _ := pem.Decode([]byte(p))
    48  	pk, err := x509.ParseECPrivateKey(block.Bytes)
    49  
    50  	require.Nil(t, err)
    51  	require.NotNil(t, pk)
    52  	require.Equal(t, 256, pk.Params().BitSize)
    53  }
    54  
    55  type TestSigner struct {
    56  	public interface{}
    57  }
    58  
    59  func (s *TestSigner) Public() crypto.PublicKey {
    60  	return s.public
    61  }
    62  
    63  func (s *TestSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
    64  	return []byte{}, nil
    65  }
    66  
    67  func TestGenerateCA(t *testing.T) {
    68  	ci.Parallel(t)
    69  
    70  	t.Run("no signer", func(t *testing.T) {
    71  		ca, pk, err := GenerateCA(CAOpts{Signer: &TestSigner{}})
    72  		require.Error(t, err)
    73  		require.Empty(t, ca)
    74  		require.Empty(t, pk)
    75  	})
    76  
    77  	t.Run("wrong key", func(t *testing.T) {
    78  		ca, pk, err := GenerateCA(CAOpts{Signer: &TestSigner{public: &rsa.PublicKey{}}})
    79  		require.Error(t, err)
    80  		require.Empty(t, ca)
    81  		require.Empty(t, pk)
    82  	})
    83  
    84  	t.Run("valid key", func(t *testing.T) {
    85  		ca, pk, err := GenerateCA(CAOpts{})
    86  		require.Nil(t, err)
    87  		require.NotEmpty(t, ca)
    88  		require.NotEmpty(t, pk)
    89  
    90  		cert, err := parseCert(ca)
    91  		require.Nil(t, err)
    92  		require.True(t, strings.HasPrefix(cert.Subject.CommonName, "Nomad Agent CA"))
    93  		require.Equal(t, true, cert.IsCA)
    94  		require.Equal(t, true, cert.BasicConstraintsValid)
    95  
    96  		require.WithinDuration(t, cert.NotBefore, time.Now(), time.Minute)
    97  		require.WithinDuration(t, cert.NotAfter, time.Now().AddDate(0, 0, 1825), time.Minute)
    98  
    99  		require.Equal(t, x509.KeyUsageCertSign|x509.KeyUsageCRLSign|x509.KeyUsageDigitalSignature, cert.KeyUsage)
   100  	})
   101  
   102  	t.Run("RSA key", func(t *testing.T) {
   103  		ca, pk, err := GenerateCA(CAOpts{})
   104  		require.NoError(t, err)
   105  		require.NotEmpty(t, ca)
   106  		require.NotEmpty(t, pk)
   107  
   108  		cert, err := parseCert(ca)
   109  		require.NoError(t, err)
   110  		require.True(t, strings.HasPrefix(cert.Subject.CommonName, "Nomad Agent CA"))
   111  		require.Equal(t, true, cert.IsCA)
   112  		require.Equal(t, true, cert.BasicConstraintsValid)
   113  
   114  		require.WithinDuration(t, cert.NotBefore, time.Now(), time.Minute)
   115  		require.WithinDuration(t, cert.NotAfter, time.Now().AddDate(0, 0, 1825), time.Minute)
   116  
   117  		require.Equal(t, x509.KeyUsageCertSign|x509.KeyUsageCRLSign|x509.KeyUsageDigitalSignature, cert.KeyUsage)
   118  	})
   119  
   120  	t.Run("Custom CA", func(t *testing.T) {
   121  		ca, pk, err := GenerateCA(CAOpts{
   122  			Days:                6,
   123  			PermittedDNSDomains: []string{"domain1.com"},
   124  			Country:             "ZZ",
   125  			PostalCode:          "0000",
   126  			Province:            "CustProvince",
   127  			Locality:            "CustLocality",
   128  			StreetAddress:       "CustStreet",
   129  			Organization:        "CustOrg",
   130  			OrganizationalUnit:  "CustUnit",
   131  			Name:                "Custom CA",
   132  		})
   133  		require.NoError(t, err)
   134  		require.NotEmpty(t, ca)
   135  		require.NotEmpty(t, pk)
   136  
   137  		cert, err := parseCert(ca)
   138  		require.NoError(t, err)
   139  		require.True(t, strings.HasPrefix(cert.Subject.CommonName, "Custom CA"))
   140  		require.True(t, strings.Contains(cert.PermittedDNSDomains[0], "domain1.com"))
   141  		require.True(t, strings.Contains(cert.Subject.Country[0], "ZZ"))
   142  		require.True(t, strings.Contains(cert.Subject.PostalCode[0], "0000"))
   143  		require.True(t, strings.Contains(cert.Subject.Province[0], "CustProvince"))
   144  		require.True(t, strings.Contains(cert.Subject.Locality[0], "CustLocality"))
   145  		require.True(t, strings.Contains(cert.Subject.StreetAddress[0], "CustStreet"))
   146  		require.True(t, strings.Contains(cert.Subject.Organization[0], "CustOrg"))
   147  		require.True(t, strings.Contains(cert.Subject.OrganizationalUnit[0], "CustUnit"))
   148  		require.Equal(t, true, cert.IsCA)
   149  		require.Equal(t, true, cert.BasicConstraintsValid)
   150  
   151  		require.WithinDuration(t, cert.NotBefore, time.Now(), time.Minute)
   152  		require.WithinDuration(t, cert.NotAfter, time.Now().AddDate(0, 0, 6), time.Minute)
   153  
   154  		require.Equal(t, x509.KeyUsageCertSign|x509.KeyUsageCRLSign|x509.KeyUsageDigitalSignature, cert.KeyUsage)
   155  	})
   156  
   157  	t.Run("Custom CA Custom Date", func(t *testing.T) {
   158  		ca, pk, err := GenerateCA(CAOpts{
   159  			Days: 365,
   160  		})
   161  		require.NoError(t, err)
   162  		require.NotEmpty(t, ca)
   163  		require.NotEmpty(t, pk)
   164  
   165  		cert, err := parseCert(ca)
   166  		require.WithinDuration(t, cert.NotAfter, time.Now().AddDate(0, 0, 365), time.Minute)
   167  	})
   168  
   169  	t.Run("Custom CA No CN", func(t *testing.T) {
   170  		ca, pk, err := GenerateCA(CAOpts{
   171  			Days:                6,
   172  			PermittedDNSDomains: []string{"domain1.com"},
   173  			Locality:            "CustLocality",
   174  		})
   175  		require.ErrorContains(t, err, "common name value not provided")
   176  		require.Empty(t, ca)
   177  		require.Empty(t, pk)
   178  	})
   179  
   180  	t.Run("Custom CA No Country", func(t *testing.T) {
   181  		ca, pk, err := GenerateCA(CAOpts{
   182  			Days:                6,
   183  			PermittedDNSDomains: []string{"domain1.com"},
   184  			Name:                "Custom CA",
   185  			Locality:            "CustLocality",
   186  		})
   187  		require.ErrorContains(t, err, "country value not provided")
   188  		require.Empty(t, ca)
   189  		require.Empty(t, pk)
   190  	})
   191  
   192  	t.Run("Custom CA No Organization", func(t *testing.T) {
   193  		ca, pk, err := GenerateCA(CAOpts{
   194  			Days:                6,
   195  			PermittedDNSDomains: []string{"domain1.com"},
   196  			Name:                "Custom CA",
   197  			Country:             "ZZ",
   198  			Locality:            "CustLocality",
   199  		})
   200  		require.ErrorContains(t, err, "organization value not provided")
   201  		// require.NoError(t, err)
   202  		require.Empty(t, ca)
   203  		require.Empty(t, pk)
   204  	})
   205  
   206  	t.Run("Custom CA No Organizational Unit", func(t *testing.T) {
   207  		ca, pk, err := GenerateCA(CAOpts{
   208  			Days:                6,
   209  			PermittedDNSDomains: []string{"domain1.com"},
   210  			Name:                "Custom CA",
   211  			Country:             "ZZ",
   212  			Locality:            "CustLocality",
   213  			Organization:        "CustOrg",
   214  		})
   215  		require.ErrorContains(t, err, "organizational unit value not provided")
   216  		require.Empty(t, ca)
   217  		require.Empty(t, pk)
   218  	})
   219  }
   220  
   221  func TestGenerateCert(t *testing.T) {
   222  	ci.Parallel(t)
   223  
   224  	signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
   225  	require.Nil(t, err)
   226  	ca, _, err := GenerateCA(CAOpts{
   227  		Name:               "Custom CA",
   228  		Country:            "ZZ",
   229  		Organization:       "CustOrg",
   230  		OrganizationalUnit: "CustOrgUnit",
   231  		Signer:             signer},
   232  	)
   233  	require.Nil(t, err)
   234  
   235  	DNSNames := []string{"server.dc1.nomad"}
   236  	IPAddresses := []net.IP{net.ParseIP("123.234.243.213")}
   237  	extKeyUsage := []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
   238  	name := "Cert Name"
   239  	certificate, pk, err := GenerateCert(CertOpts{
   240  		Signer: signer, CA: ca, Name: name, Days: 365,
   241  		DNSNames: DNSNames, IPAddresses: IPAddresses, ExtKeyUsage: extKeyUsage,
   242  	})
   243  	require.Nil(t, err)
   244  	require.NotEmpty(t, certificate)
   245  	require.NotEmpty(t, pk)
   246  
   247  	cert, err := parseCert(certificate)
   248  	require.Nil(t, err)
   249  	require.Equal(t, name, cert.Subject.CommonName)
   250  	require.Equal(t, true, cert.BasicConstraintsValid)
   251  	signee, err := ParseSigner(pk)
   252  	require.Nil(t, err)
   253  	certID, err := keyID(signee.Public())
   254  	require.Nil(t, err)
   255  	require.Equal(t, certID, cert.SubjectKeyId)
   256  	caID, err := keyID(signer.Public())
   257  	require.Nil(t, err)
   258  	require.Equal(t, caID, cert.AuthorityKeyId)
   259  	require.Contains(t, cert.Issuer.CommonName, "Custom CA")
   260  	require.Equal(t, false, cert.IsCA)
   261  
   262  	require.WithinDuration(t, cert.NotBefore, time.Now(), time.Minute)
   263  	require.WithinDuration(t, cert.NotAfter, time.Now().AddDate(0, 0, 365), time.Minute)
   264  
   265  	require.Equal(t, x509.KeyUsageDigitalSignature|x509.KeyUsageKeyEncipherment, cert.KeyUsage)
   266  	require.Equal(t, extKeyUsage, cert.ExtKeyUsage)
   267  
   268  	// https://github.com/golang/go/blob/10538a8f9e2e718a47633ac5a6e90415a2c3f5f1/src/crypto/x509/verify.go#L414
   269  	require.Equal(t, DNSNames, cert.DNSNames)
   270  	require.True(t, IPAddresses[0].Equal(cert.IPAddresses[0]))
   271  }