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 }