github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/providers/security/pkcs11sec/pkcs11_security_test.go (about)

     1  // Copyright (c) 2020-2022, R.I. Pienaar and the Choria Project contributors
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package pkcs11sec
     6  
     7  import (
     8  	"encoding/base64"
     9  	"fmt"
    10  	"os"
    11  	"path/filepath"
    12  	"testing"
    13  
    14  	"github.com/choria-io/go-choria/config"
    15  	. "github.com/onsi/ginkgo/v2"
    16  	. "github.com/onsi/gomega"
    17  	"github.com/sirupsen/logrus"
    18  )
    19  
    20  func TestFileSecurity(t *testing.T) {
    21  	RegisterFailHandler(Fail)
    22  	if runPkcs11 := os.Getenv("RUN_PKCS11_TESTS"); runPkcs11 == "1" {
    23  		RunSpecs(t, "Security/Pkcs11Security")
    24  	}
    25  }
    26  
    27  var _ = Describe("Pkcs11Security", func() {
    28  	var err error
    29  	var prov *Pkcs11Security
    30  	var l *logrus.Logger
    31  	var lib string
    32  	var pin = "1234"
    33  	var c *config.Config
    34  	var testSlot = 374292918
    35  
    36  	BeforeEach(func() {
    37  		lib = "/usr/lib/softhsm/libsofthsm2.so"
    38  		if envLib := os.Getenv("SOFTHSM_LIB"); envLib != "" {
    39  			lib = envLib
    40  		}
    41  
    42  		wd, _ := os.Getwd()
    43  		err = os.Setenv("SOFTHSM2_CONF", wd+"/softhsm2.conf")
    44  		Expect(err).ToNot(HaveOccurred())
    45  
    46  		l = logrus.New()
    47  		l.Out = GinkgoWriter
    48  
    49  		c, err = config.NewDefaultConfig()
    50  		Expect(err).ToNot(HaveOccurred())
    51  
    52  		c.Choria.FileSecurityCA = filepath.Join("..", "testdata", "good", "certs", "ca.pem")
    53  		c.Choria.PKCS11Slot = testSlot
    54  		c.Choria.PKCS11DriverFile = lib
    55  
    56  		prov, err = New(WithChoriaConfig(c), WithLog(l.WithFields(logrus.Fields{})), WithPin(pin))
    57  		Expect(err).ToNot(HaveOccurred())
    58  	})
    59  
    60  	Describe("WithChoriaConfig", func() {
    61  		It("Should copy all the relevant settings", func() {
    62  			c, err := config.NewDefaultConfig()
    63  			Expect(err).ToNot(HaveOccurred())
    64  
    65  			c.Choria.FileSecurityCA = filepath.Join("..", "testdata", "good", "certs", "ca.pem")
    66  			c.Choria.PKCS11Slot = testSlot
    67  			c.Choria.PKCS11DriverFile = lib
    68  			c.DisableTLSVerify = true
    69  
    70  			prov, err := New(WithChoriaConfig(c), WithLog(l.WithFields(logrus.Fields{})), WithPin("1234"))
    71  			Expect(err).ToNot(HaveOccurred())
    72  
    73  			Expect(prov.conf.AllowList).To(Equal([]string{"\\.mcollective$", "\\.choria$"}))
    74  			Expect(prov.conf.PrivilegedUsers).To(Equal([]string{"\\.privileged.mcollective$", "\\.privileged.choria$"}))
    75  			Expect(prov.conf.CAFile).To(Equal("../testdata/good/certs/ca.pem"))
    76  			Expect(prov.conf.DisableTLSVerify).To(BeTrue())
    77  		})
    78  	})
    79  
    80  	Describe("Validate", func() {
    81  		It("Should return true if provider was successfully initialized", func() {
    82  			errs, ok := prov.Validate()
    83  			Expect(errs).To(BeEmpty())
    84  			Expect(ok).To(BeTrue())
    85  		})
    86  		It("Should handle missing files", func() {
    87  			c, err := config.NewDefaultConfig()
    88  			Expect(err).ToNot(HaveOccurred())
    89  
    90  			c.Choria.FileSecurityCA = "stub/ca.pem"
    91  			c.Choria.PKCS11Slot = testSlot
    92  			c.Choria.PKCS11DriverFile = lib
    93  
    94  			prov, err = New(WithChoriaConfig(c), WithLog(l.WithFields(logrus.Fields{})), WithPin(pin))
    95  			Expect(err).ToNot(HaveOccurred())
    96  
    97  			errs, ok := prov.Validate()
    98  			Expect(ok).To(BeFalse())
    99  			Expect(errs).To(HaveLen(2))
   100  			Expect(errs[0]).To(Equal(fmt.Sprintf("stat %s: no such file or directory", prov.conf.CAFile)))
   101  		})
   102  	})
   103  
   104  	Describe("Identity", func() {
   105  		It("Should return the identity", func() {
   106  			Expect(prov.Identity()).To(Equal("joeuser"))
   107  		})
   108  	})
   109  
   110  	Describe("CallerName", func() {
   111  		It("Should return the right caller name", func() {
   112  			Expect(prov.CallerName()).To(Equal("choria=joeuser"))
   113  		})
   114  	})
   115  
   116  	Describe("CallerIdentity", func() {
   117  		It("Should return the right caller ident, no matter the input", func() {
   118  			Expect(prov.CallerIdentity("choria=test.choria")).To(Equal("test.choria"))
   119  			Expect(prov.CallerIdentity("foo=test1.choria")).To(Equal("test1.choria"))
   120  		})
   121  	})
   122  
   123  	Describe("SignBytes", func() {
   124  		It("Should produce the right signature", func() {
   125  			sig, err := prov.SignBytes([]byte("too many secrets"))
   126  			Expect(err).ToNot(HaveOccurred())
   127  			Expect(base64.StdEncoding.EncodeToString(sig)).To(Equal("PQlGnXt8jQ9N2WbghvKhH4qNTJcmTpbfspkT+9aSabivRbMGNIlMwDGMg8PQEC5AMF9eoxdaXuR/t2rbgUfqQrB3oI2YMD2clUtdVI1MIJ81ww90o0KHZa3C0N/OlshJVCDg1mUiget7rdfE5K3HARKbPZZbQFe/q5yPnjA7FGHEb1K+qnPyLGKD8WKIDTjHza16O6QWAcbyAWk2CP9ziLH5flVGMP0zMkdXQPiFfzexUG6iTIi64zVJ2k6E3k1JOGzRLeQfvUDNEQnmekH4w0iK0+uTZzBsQPr3jbd8xraTInv+v1CzrpBwoIP36Qlr296vxKngaqDSN2K3uSyKWg=="))
   128  		})
   129  	})
   130  
   131  	Describe("VerifySignatureBytes", func() {
   132  		It("Should validate correctly", func() {
   133  			sig, err := base64.StdEncoding.DecodeString("PQlGnXt8jQ9N2WbghvKhH4qNTJcmTpbfspkT+9aSabivRbMGNIlMwDGMg8PQEC5AMF9eoxdaXuR/t2rbgUfqQrB3oI2YMD2clUtdVI1MIJ81ww90o0KHZa3C0N/OlshJVCDg1mUiget7rdfE5K3HARKbPZZbQFe/q5yPnjA7FGHEb1K+qnPyLGKD8WKIDTjHza16O6QWAcbyAWk2CP9ziLH5flVGMP0zMkdXQPiFfzexUG6iTIi64zVJ2k6E3k1JOGzRLeQfvUDNEQnmekH4w0iK0+uTZzBsQPr3jbd8xraTInv+v1CzrpBwoIP36Qlr296vxKngaqDSN2K3uSyKWg==")
   134  			Expect(err).ToNot(HaveOccurred())
   135  
   136  			valid, _ := prov.VerifySignatureBytes([]byte("too many secrets"), sig, nil)
   137  			Expect(valid).To(BeTrue())
   138  		})
   139  
   140  		It("Should fail for invalid sigs", func() {
   141  			valid, _ := prov.VerifySignatureBytes([]byte("too many secrets"), []byte("meh"), nil)
   142  			Expect(valid).To(BeFalse())
   143  		})
   144  
   145  		It("Should support cached certificates", func() {
   146  			prov, err = New(WithChoriaConfig(c), WithLog(l.WithFields(logrus.Fields{})), WithPin(pin))
   147  			Expect(err).ToNot(HaveOccurred())
   148  
   149  			sig, err := base64.StdEncoding.DecodeString("Zq1F2bdXOAvB5Ca+iYCZ/BLYz2ZzbQP/V8kwQY0E3cuDrBDArX7UhUnBakzN+Msr7UyF+EkYmzvIi4KHnFBrgi7otM8Q5YMh5IT+IPaoHj3Rj/jorqD4g8ltZINqCUBWDN4wvSG98SxLyawV69gAK4SnP+oy7SU7zxuQiPwIMJ7lVoiQ3t+tiQAHUxeykQPw7WElLb+wPTb1k4DM3yRkijA9OeUk+3SVyl2sTCu5h/Lg0lcI372bkLDESlnhnvw7yuLD2SSncrEQrBdv/N2yEpY2fx1UKGlTrn9GH4MGA1GuzE1F87RH9P8ieeul6vI13BkBAlMk5KaGlmWpgiGri5UjCHHXMxEnXfwUcKFE+E6yVg4SbrJknkuJzNJduypMIep7YOnPHVLNIBZLuOUdJrRgBQ+Yb9mxPnEQHhOHeN0XHUcseRJEISqPkagpNx1xhOb7g3hsNyEvqibT/DZsc/2hyU2I/wG9fl26CnN9c12r1zInyCQYsU/wuIvjDtRZvTpLGJSJdgjSmTPzGmA/fKpAfOWObdsoLeorjF/pNweuc0x0JZMsBrZauldLL53wnnvllsFEmIAxs+RusoJ2UfW7WugZ7lXGISHTef6IHjukHgDBSbeGawVCnAgPbPz1dy42x04koUW3Bmz89fJ4/j+e49ijz7z3W/IercNeke4=")
   150  			Expect(err).ToNot(HaveOccurred())
   151  
   152  			cert, err := os.ReadFile("../testdata/good/certs/2.mcollective.pem")
   153  			Expect(err).ToNot(HaveOccurred())
   154  
   155  			valid, _ := prov.VerifySignatureBytes([]byte("too many secrets"), sig, cert)
   156  			Expect(valid).To(BeTrue())
   157  		})
   158  	})
   159  
   160  	Describe("ChecksumBytes", func() {
   161  		It("Should produce the right checksum", func() {
   162  			sum, err := base64.StdEncoding.DecodeString("Yk+jdKdZ3v8E2p6dmbfn+ZN9lBBAHEIcOMp4lzuYKTo=")
   163  			Expect(err).ToNot(HaveOccurred())
   164  
   165  			Expect(prov.ChecksumBytes([]byte("too many secrets"))).To(Equal(sum))
   166  		})
   167  	})
   168  
   169  	Describe("TLSConfig", func() {
   170  		It("Should produce a valid TLS Config", func() {
   171  			c, err := config.NewDefaultConfig()
   172  			Expect(err).ToNot(HaveOccurred())
   173  
   174  			c.Choria.FileSecurityCA = filepath.Join("test_data", "ssl_dir", "certs", "ca.pem")
   175  			c.Choria.PKCS11Slot = testSlot
   176  			c.Choria.PKCS11DriverFile = lib
   177  
   178  			prov, err = New(WithChoriaConfig(c), WithLog(l.WithFields(logrus.Fields{})), WithPin(pin))
   179  
   180  			Expect(err).ToNot(HaveOccurred())
   181  			tlsC, err := prov.TLSConfig()
   182  			Expect(err).ToNot(HaveOccurred())
   183  
   184  			Expect(tlsC.InsecureSkipVerify).To(BeFalse())
   185  			Expect(err).ToNot(HaveOccurred())
   186  
   187  			cert := prov.cert
   188  
   189  			Expect(tlsC.Certificates).To(HaveLen(1))
   190  			Expect(tlsC.Certificates[0].Certificate).To(Equal(cert.Certificate))
   191  		})
   192  
   193  		It("Should support disabling tls verify", func() {
   194  			c, err := config.NewDefaultConfig()
   195  			Expect(err).ToNot(HaveOccurred())
   196  
   197  			c.Choria.FileSecurityCA = filepath.Join("test_data", "ssl_dir", "certs", "ca.pem")
   198  			c.DisableTLSVerify = true
   199  			c.Choria.PKCS11Slot = testSlot
   200  			c.Choria.PKCS11DriverFile = lib
   201  
   202  			prov, err = New(WithChoriaConfig(c), WithLog(l.WithFields(logrus.Fields{})), WithPin(pin))
   203  
   204  			Expect(err).ToNot(HaveOccurred())
   205  
   206  			tlsC, err := prov.TLSConfig()
   207  			Expect(err).ToNot(HaveOccurred())
   208  
   209  			Expect(tlsC.InsecureSkipVerify).To(BeTrue())
   210  
   211  		})
   212  	})
   213  
   214  	Describe("VerifyCertificate", func() {
   215  		var pem []byte
   216  		var inter *config.Config
   217  
   218  		BeforeEach(func() {
   219  			inter, err = config.NewDefaultConfig()
   220  			Expect(err).ToNot(HaveOccurred())
   221  
   222  			inter.Choria.FileSecurityCA = filepath.Join("..", "testdata", "intermediate", "certs", "ca_chain_ca.pem")
   223  			inter.Choria.PKCS11Slot = testSlot
   224  			inter.Choria.PKCS11DriverFile = lib
   225  
   226  			pem, err = prov.PublicCertBytes()
   227  			Expect(err).ToNot(HaveOccurred())
   228  		})
   229  
   230  		It("Should fail for foreign certs", func() {
   231  			pem, err = os.ReadFile(filepath.Join("..", "testdata", "foreign.pem"))
   232  			Expect(err).ToNot(HaveOccurred())
   233  			err := prov.VerifyCertificate(pem, "rip.mcollective")
   234  			Expect(err).To(MatchError("x509: certificate signed by unknown authority"))
   235  
   236  		})
   237  
   238  		It("Should fail for invalid names", func() {
   239  			prov, err = New(WithChoriaConfig(c), WithLog(l.WithFields(logrus.Fields{})), WithPin(pin))
   240  			Expect(err).ToNot(HaveOccurred())
   241  
   242  			err := prov.VerifyCertificate(pem, "bob")
   243  			Expect(err).To(MatchError("x509: certificate is valid for joeuser, not bob"))
   244  		})
   245  
   246  		It("Should accept valid certs", func() {
   247  			c, err := config.NewDefaultConfig()
   248  			Expect(err).ToNot(HaveOccurred())
   249  
   250  			c.Choria.FileSecurityCA = filepath.Join("test_data", "ssl_dir", "certs", "ca.pem")
   251  			c.Choria.PKCS11Slot = testSlot
   252  			c.Choria.PKCS11DriverFile = lib
   253  
   254  			prov, err = New(WithChoriaConfig(c), WithLog(l.WithFields(logrus.Fields{})), WithPin(pin))
   255  			Expect(err).ToNot(HaveOccurred())
   256  
   257  			err = prov.VerifyCertificate(pem, "joeuser")
   258  			Expect(err).ToNot(HaveOccurred())
   259  		})
   260  
   261  		It("Should work with client provided intermediate chains", func() {
   262  			c, err := config.NewDefaultConfig()
   263  			Expect(err).ToNot(HaveOccurred())
   264  
   265  			c.Choria.FileSecurityCA = filepath.Join("..", "testdata", "intermediate", "certs", "ca.pem")
   266  			c.Choria.SSLDir = filepath.Join("..", "testdata", "intermediate")
   267  			c.Choria.PKCS11Slot = testSlot
   268  			c.Choria.PKCS11DriverFile = lib
   269  
   270  			prov, err := New(WithChoriaConfig(c), WithLog(l.WithFields(logrus.Fields{})), WithPin("1234"))
   271  			Expect(err).ToNot(HaveOccurred())
   272  
   273  			pem, err = os.ReadFile(filepath.Join("..", "testdata", "intermediate", "certs", "rip.mcollective.pem"))
   274  			Expect(err).ToNot(HaveOccurred())
   275  
   276  			err = prov.VerifyCertificate(pem, "rip.mcollective")
   277  			Expect(err).ToNot(HaveOccurred())
   278  		})
   279  
   280  		It("Should work with server side ca intermediate chains", func() {
   281  			prov, err := New(WithChoriaConfig(inter), WithLog(l.WithFields(logrus.Fields{})), WithPin("1234"))
   282  			Expect(err).ToNot(HaveOccurred())
   283  
   284  			pem, err = os.ReadFile(filepath.Join("..", "testdata", "intermediate", "certs", "ca_chain_rip.mcollective.pem"))
   285  			Expect(err).ToNot(HaveOccurred())
   286  
   287  			err = prov.VerifyCertificate(pem, "rip.mcollective")
   288  			Expect(err).ToNot(HaveOccurred())
   289  		})
   290  
   291  		It("Should work with email addresses", func() {
   292  			prov, err := New(WithChoriaConfig(inter), WithLog(l.WithFields(logrus.Fields{})), WithPin(pin))
   293  			Expect(err).ToNot(HaveOccurred())
   294  
   295  			pem, err = os.ReadFile(filepath.Join("..", "testdata", "intermediate", "certs", "email-chain-rip.mcollective.pem"))
   296  			Expect(err).ToNot(HaveOccurred())
   297  
   298  			err = prov.VerifyCertificate(pem, "email:test@choria-io.com")
   299  			Expect(err).ToNot(HaveOccurred())
   300  		})
   301  
   302  		It("Should not work with wrong addresses", func() {
   303  			prov, err := New(WithChoriaConfig(inter), WithLog(l.WithFields(logrus.Fields{})), WithPin(pin))
   304  			Expect(err).ToNot(HaveOccurred())
   305  
   306  			pem, err = os.ReadFile(filepath.Join("..", "testdata", "intermediate", "certs", "email-chain-rip.mcollective.pem"))
   307  			Expect(err).ToNot(HaveOccurred())
   308  
   309  			err = prov.VerifyCertificate(pem, "email:bad@choria-io.com")
   310  			Expect(err).To(HaveOccurred())
   311  		})
   312  	})
   313  })