diff --git a/internal/edge.go b/internal/edge.go index ff7a92f..dcc71c5 100644 --- a/internal/edge.go +++ b/internal/edge.go @@ -14,8 +14,10 @@ package feature import ( "encoding/json" + "net/url" "github.com/eclipse-kanto/software-update/internal/logger" + "github.com/eclipse-kanto/software-update/util/tls" MQTT "github.com/eclipse/paho.mqtt.golang" "github.com/google/uuid" @@ -58,6 +60,17 @@ func newEdgeConnector(scriptSUPConfig *ScriptBasedSoftwareUpdatableConfig, ecl e if len(scriptSUPConfig.Username) > 0 { opts = opts.SetUsername(scriptSUPConfig.Username).SetPassword(scriptSUPConfig.Password) } + u, err := url.Parse(scriptSUPConfig.Broker) + if err != nil { + return nil, err + } + if isConnectionSecure(u.Scheme) { + tlsConfig, err := tls.NewTLSConfig(scriptSUPConfig.CACert, scriptSUPConfig.Cert, scriptSUPConfig.Key) + if err != nil { + return nil, err + } + opts.SetTLSConfig(tlsConfig) + } p := &EdgeConnector{mqttClient: MQTT.NewClient(opts), edgeClient: ecl} if token := p.mqttClient.Connect(); token.Wait() && token.Error() != nil { @@ -98,6 +111,15 @@ func newEdgeConnector(scriptSUPConfig *ScriptBasedSoftwareUpdatableConfig, ecl e return p, nil } +func isConnectionSecure(schema string) bool { + switch schema { + case "wss", "ssl", "tls", "mqtts", "mqtt+ssl", "tcps": + return true + default: + } + return false +} + // Close the EdgeConnector func (p *EdgeConnector) Close() { if p.cfg != nil { diff --git a/internal/feature.go b/internal/feature.go index 7f86b44..d6b6d66 100644 --- a/internal/feature.go +++ b/internal/feature.go @@ -39,6 +39,9 @@ const ( defaultBroker = "tcp://localhost:1883" defaultUsername = "" defaultPassword = "" + defaultCACert = "" + defaultCert = "" + defaultKey = "" defaultStorageLocation = "." defaultFeatureID = "SoftwareUpdatable" defaultModuleType = "software" @@ -70,6 +73,9 @@ type ScriptBasedSoftwareUpdatableConfig struct { Broker string `json:"broker,omitempty"` Username string `json:"username,omitempty"` Password string `json:"password,omitempty"` + CACert string `json:"caCert,omitempty"` + Cert string `json:"cert,omitempty"` + Key string `json:"key,omitempty"` StorageLocation string `json:"storageLocation,omitempty"` FeatureID string `json:"featureId,omitempty"` ModuleType string `json:"moduleType,omitempty"` @@ -117,6 +123,9 @@ func NewDefaultConfig() *BasicConfig { Broker: defaultBroker, Username: defaultUsername, Password: defaultPassword, + CACert: defaultCACert, + Cert: defaultCert, + Key: defaultKey, StorageLocation: defaultStorageLocation, FeatureID: defaultFeatureID, ModuleType: defaultModuleType, diff --git a/internal/flags.go b/internal/flags.go index ea58465..a2e364b 100644 --- a/internal/flags.go +++ b/internal/flags.go @@ -51,6 +51,9 @@ func InitFlags(flagSet *flag.FlagSet, cfg *BasicConfig) { flagSet.StringVar(&cfg.Broker, "broker", cfg.Broker, "Local MQTT broker address") flagSet.StringVar(&cfg.Username, "username", cfg.Username, "Username that is a part of the credentials") flagSet.StringVar(&cfg.Password, "password", cfg.Password, "Password that is a part of the credentials") + flagSet.StringVar(&cfg.CACert, "caCert", cfg.CACert, "A PEM encoded CA certificates file for MQTT broker connection") + flagSet.StringVar(&cfg.Cert, "cert", cfg.Cert, "A PEM encoded certificate file to authenticate to the MQTT server/broker") + flagSet.StringVar(&cfg.Key, "key", cfg.Key, "A PEM encoded unencrypted private key file to authenticate to the MQTT server/broker") flagSet.StringVar(&cfg.StorageLocation, "storageLocation", cfg.StorageLocation, "Location of the storage") flagSet.StringVar(&cfg.FeatureID, "featureId", cfg.FeatureID, "Feature identifier of SoftwareUpdatable") flagSet.StringVar(&cfg.ModuleType, "moduleType", cfg.ModuleType, "Module type of SoftwareUpdatable") @@ -62,7 +65,7 @@ func InitFlags(flagSet *flag.FlagSet, cfg *BasicConfig) { flagSet.StringVar(&cfg.Mode, "mode", cfg.Mode, modeDescription) flagSet.Var(&cfg.InstallCommand, flagInstall, "Defines the absolute path to install script") - flagSet.Var(NewPathArgs(&cfg.InstallDirs), "installDirs", "Local file system directories, where to search for module artifacts") + flagSet.Var(newPathArgs(&cfg.InstallDirs), "installDirs", "Local file system directories, where to search for module artifacts") flagSet.StringVar(&cfg.ConfigFile, flagConfigFile, cfg.ConfigFile, "Defines the configuration file") } diff --git a/internal/flags_test.go b/internal/flags_test.go index e7726c6..e62e168 100644 --- a/internal/flags_test.go +++ b/internal/flags_test.go @@ -131,6 +131,9 @@ func TestFlagsHasHigherPriority(t *testing.T) { // 3. Test with all flags are applied instead of default values if no configuration JSON is provided expectedFlagBroker := "host:1234" expectedArtifact := "TestArchive" + expectedCACert := "TestCaCert.crt" + expectedCert := "TestCert.cert" + expectedKey := "TestKey.key" expectedFeatureID := "TestFeature" expectedInstall := "TestInstall" expectedServerCert := "TestCert" @@ -152,6 +155,9 @@ func TestFlagsHasHigherPriority(t *testing.T) { setFlags([]string{ c(flagBroker, expectedFlagBroker), c(flagArtifactType, expectedArtifact), + c(flagCACert, expectedCACert), + c(flagCert, expectedCert), + c(flagKey, expectedKey), c(flagFeatureID, expectedFeatureID), c(flagInstall, expectedInstall), c(flagServerCert, expectedServerCert), @@ -180,6 +186,9 @@ func TestFlagsHasHigherPriority(t *testing.T) { Broker: expectedFlagBroker, Username: expectedUsername, Password: expectedPassword, + CACert: expectedCACert, + Cert: expectedCert, + Key: expectedKey, ServerCert: expectedServerCert, StorageLocation: expectedStorageLocation, InstallCommand: command{cmd: expectedInstall}, @@ -353,6 +362,9 @@ func assertSoftwareUpdatable(t *testing.T, actual, expected ScriptBasedSoftwareU assertString(t, actual.Broker, expected.Broker) assertString(t, actual.Username, expected.Username) assertString(t, actual.Password, expected.Password) + assertString(t, actual.CACert, expected.CACert) + assertString(t, actual.Cert, expected.Cert) + assertString(t, actual.Key, expected.Key) assertString(t, actual.ServerCert, expected.ServerCert) assertString(t, actual.StorageLocation, expected.StorageLocation) assertInt(t, actual.DownloadRetryCount, expected.DownloadRetryCount) diff --git a/internal/path_args.go b/internal/path_args.go index 3565056..e44fd6d 100644 --- a/internal/path_args.go +++ b/internal/path_args.go @@ -36,8 +36,8 @@ func (a *pathArgs) Set(value string) error { return nil } -// NewPathArgs creates new flag variable for slice of strings definition. -func NewPathArgs(setter *[]string) *pathArgs { +// newPathArgs creates new flag variable for slice of strings definition. +func newPathArgs(setter *[]string) *pathArgs { return &pathArgs{ args: setter, } diff --git a/internal/path_args_test.go b/internal/path_args_test.go index 268b854..de66313 100644 --- a/internal/path_args_test.go +++ b/internal/path_args_test.go @@ -23,7 +23,7 @@ func TestPathArgsIsSet(t *testing.T) { f := flag.NewFlagSet("testing", flag.ContinueOnError) var s []string - v := NewPathArgs(&s) + v := newPathArgs(&s) f.Var(v, "S", "S") args := []string{"-S=a b"} @@ -47,7 +47,7 @@ func TestPathArgsInvalid(t *testing.T) { f.SetOutput(io.Discard) var s []string - v := NewPathArgs(&s) + v := newPathArgs(&s) f.Var(v, "S", "S") args := []string{"-S="} diff --git a/internal/utils_test.go b/internal/utils_test.go index 27e0bc2..b2474ab 100644 --- a/internal/utils_test.go +++ b/internal/utils_test.go @@ -41,6 +41,9 @@ const ( flagBroker = "broker" flagUsername = "username" flagPassword = "password" + flagCACert = "caCert" + flagCert = "cert" + flagKey = "key" flagStorageLocation = "storageLocation" flagFeatureID = "featureId" flagModuleType = "moduleType" diff --git a/util/tls/testdata/ca.crt b/util/tls/testdata/ca.crt new file mode 100644 index 0000000..59609aa --- /dev/null +++ b/util/tls/testdata/ca.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDkzCCAnugAwIBAgIUbD0mjn2x1H5VKi54xgjfPsEugzAwDQYJKoZIhvcNAQEL +BQAwWTELMAkGA1UEBhMCQkcxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X +DTIyMTAyNzE1MDk0OFoXDTI3MTAyNzE1MDk0OFowWTELMAkGA1UEBhMCQkcxEzAR +BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5 +IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAqRZyG9gywFvplpfPq60X7JYtkbj7OF+tOfixF9FkGDJ9Hhh4LoMv +W+rdLnxEHfI4Vicn0Wj3r/Ra1bd9yo8hzGiYdNliH9kegSbjB2Xx8N4yTCqaiZ9E +JQLcstQeXHEP3YwPXLnNfTOmbQPAbC2T9J+USlmolG1qpkuU5rQVC/sjW5M8MOmN +TuKEU6pds/j8GQKhQmsIHddwfypnBDilpYOotgeMwDqsyM4+zdSXbFaNmYoh3Tjb +FSN0WJTdPe7uv+nG03NZe6dHvN4C/8Z5uBIkeLw8yrO/2Wb7aAxwtpK3Wswzlya9 +TAwALiOI+hWuIfUmkoQapskIMhcRAnpMxwIDAQABo1MwUTAdBgNVHQ4EFgQUH2fo +QuQyEoszAL3vBOBqs8OLg7YwHwYDVR0jBBgwFoAUH2foQuQyEoszAL3vBOBqs8OL +g7YwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAYNY+8FNt9Pr/ +I6NYzY56opMIXmMErRMBN4i5WkuZxXp7gaqPNrx42O7gaWJz7dwwGUmrb0eHyOIm +XetphU6cW1vNgaXPOggacp2wRJ6AGJn1+v/hfWU6sPt0XWPM9p5umoCeYJeZ1UlE +uodsUGEQc7b/ODROObPHAFc/18nChoiPylXtB5TcgdyzalzhL/d8B/c4QZJwvT+W +L+8IoChNQzeH4yCgoDZXaQpRfrnGjLyrpx4dojNBYd/rJsGNZa+wMwzhFZU3f3QY +jeZnnp+nVBw+/L3q/FVwCee/RsYiR797OL7wyPAJPGd4iqbh09Hv0B2YgIih86X6 +giPwyRzg7g== +-----END CERTIFICATE----- diff --git a/util/tls/testdata/certificate.pem b/util/tls/testdata/certificate.pem new file mode 100644 index 0000000..7697626 --- /dev/null +++ b/util/tls/testdata/certificate.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEjzCCAncCFAEWn/QU1vzg07IeNzhaX/6pNlTeMA0GCSqGSIb3DQEBCwUAMIGD +MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2Fu +IEZyYW5jaXNjbzEVMBMGA1UECgwMRXhhbXBsZSBJbmMuMRYwFAYDVQQLDA1JVCBE +ZXBhcnRtZW50MRgwFgYDVQQDDA93d3cuZXhhbXBsZS5jb20wHhcNMjEwNTEzMDg0 +NTU3WhcNMjExMDEwMDg0NTU3WjCBgzELMAkGA1UEBhMCVVMxFjAUBgNVBAgMDU1h +c3NhY2h1c2V0dHMxDzANBgNVBAcMBkJvc3RvbjEVMBMGA1UECgwMRXhhbXBsZSBJ +bmMuMRYwFAYDVQQLDA1IUiBEZXBhcnRtZW50MRwwGgYDVQQDDBN3d3cuZXhhbXBs +ZS1pbmMuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApmF7J4WB +BujYYNt86YHSWrv3ffX3Odt57y4kqhWuMM8VcA2RXTlJO3SXX8xLF/+lsaZZJmfg +xR1tJ0hKfBYt78H03bjrylWLOQoRqlyVuz0SF3ueR9vx3mPIO0F0E3Q2mC4SLHbr +5kW4aj3/NszLzgvZPbOchfcktdCd1vME+pM8lPPY6z8qWJzlWjOYdymWUV8z9qlA +c3VCnYQ+UwJY51vTcMPpalByrMPWkiNic+9Onl8KHCik27vNMIVVLYLa6763UwU8 +qCKT+jZj6nlCT8w5oqoJNsaSd/EGGFtGR+qZj6V2TrsI2cNknyY7Qf/QN0+zH0nm +XsOxK6jW8q7RGwIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQBe/kcT2L54PxZqb3GU +liwYJGjB+9fkqTyMwglt8dAm3it9F/POyXtoKB8a1AuaZ/FJlR+AUOFv+f3i0ZnE +Ek0OAsllVPclv7HhywD1HzrbLh0PreGsBnYgyrW7qZKAfevus0U0GrjhcrY7zCoA +EBFWWqcWqhRCFXYwgI13ZNLhYl7r+NIWLza1bPcnWVfY2g19/nctR53ZFFiVkvlk +FCYGat0SPQWvjFIKaCNQQL6IZSxqk95W87kEWrac9A+bQzENpWLfwu86O5r6vKNK +pHmd47Sy8hyhf75/SEOQuWBEkgT+sPXU7TykvFB8kzO1Wmsz7D2/d5pvkjZF31dQ +m4ZHIuclPOETtAwiY13dI94vAhgruK0FRFn7jyfePn20CFqUCOO9cEQytysCO/n7 +4xJPbIVcvUO825Kbos71OWfNkLEi1tlLkFpe73/rSXnZRWweqAThrY7jxGxhveI4 +iYrOOYEqGdM6VfLvVhYXhsc/MDqiqJLdbhQAS/lE8bJrnVnVRYnnb0ExfNTwqVU3 +8YZB6JbT+j9556c675j0sfa0J8qgDRHsWR7EG5u5wENnDH/s142dYcjJ5S6tCumM +K/GExzmrJFHmLfqKTSxAquMYvDVufICcklL067DacRJKHkyx5KvKkgc3R8rTaX2o +D+0ioElFJXVQ7rULiWzxZs+Gdw== +-----END CERTIFICATE----- diff --git a/util/tls/testdata/empty.crt b/util/tls/testdata/empty.crt new file mode 100644 index 0000000..e69de29 diff --git a/util/tls/testdata/invalid.pem b/util/tls/testdata/invalid.pem new file mode 100644 index 0000000..eeb0277 --- /dev/null +++ b/util/tls/testdata/invalid.pem @@ -0,0 +1 @@ +---invalid data--- \ No newline at end of file diff --git a/util/tls/testdata/key.pem b/util/tls/testdata/key.pem new file mode 100644 index 0000000..e0b52a6 --- /dev/null +++ b/util/tls/testdata/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEApmF7J4WBBujYYNt86YHSWrv3ffX3Odt57y4kqhWuMM8VcA2R +XTlJO3SXX8xLF/+lsaZZJmfgxR1tJ0hKfBYt78H03bjrylWLOQoRqlyVuz0SF3ue +R9vx3mPIO0F0E3Q2mC4SLHbr5kW4aj3/NszLzgvZPbOchfcktdCd1vME+pM8lPPY +6z8qWJzlWjOYdymWUV8z9qlAc3VCnYQ+UwJY51vTcMPpalByrMPWkiNic+9Onl8K +HCik27vNMIVVLYLa6763UwU8qCKT+jZj6nlCT8w5oqoJNsaSd/EGGFtGR+qZj6V2 +TrsI2cNknyY7Qf/QN0+zH0nmXsOxK6jW8q7RGwIDAQABAoIBAH09m6qgQAOnellO +XrSW2HUcUKwsXjDbGOoF3et57mknOIfkbqux14I9vUSLT2t9MIiNI0ZZo0Q9ZlDP +heHqACId6eiMrlDcG7SP88Q9dShATEII95g349T3X13bYzjRndbntx5pViE8EhlH +GblyZ2duW9SqQwREiQmjQ2zt+a1zuKUAGdysS+4101UHUj6tC/RjiNN/TCXXRJIX +GOJ4WFLY+f2bXgSmqbK7wqN9nPxmxl/+bv4hO32Gsv06ejuy/6+GFJZa+n3ASU2r +/ptr4vgCK+t6I0OWVTpvYUboEwAXam4JfAu12zLtczfXVFJjzklUvSClIG9aJ6DP +2B3LEuECgYEA1RenCuVl6sM2o858X8iOLTMuCZWX5VFKY1+vvLwB/+CBfy7/dDYw +lbv+xaots0rY9Wn784ewi9zJbdnXE1YNgj0utIMHzylXvTolnDYsoO7SYpIwqtpa +PyzPcAV3Khkd5LGe1hf9VmOJfTF/563ztLXip0HUeIvzgB/maqfAQW8CgYEAx+H4 +GZ3ycdL03x7Zvp5g5yDPZGvxqVIEIFmliagEFyBgqogbXuOvsvZDTg+gKV5/QyVg +FWokz5VtC6U9UcWfF7LJses/Hsedh9IeICd9UIzkey8UmS2mDBeGfTHVTNuczml5 +VzmTK8jGUO5aRVRyOt0tqVf6Oozo8ImdI1fOPRUCgYBI4p4wC+agNcUqoiXIXUDE +FQ1aGeCqfvOCqefiFixY6OFiLyERDrfvfy3VTi/zc1ZiGq4izfaE4C/Fcw0tf/F+ +6o5fD7JMGUf5YTocBCufoBA1xur+hVD46srI9hWcQJsI7ff2Ip50Pfd46sVk6QrC +dLPhoZKa6MOQv1iAgoAv4QKBgQDAfiO6N9vmNizQOxujcU8NBxHzOekvEOccaHj9 +Cqt1wh6V3CHPziHEjVjf8jhh3rlcZsATn3b32oV7c5SMDW9bGTkYeN7+u2pABOAy +QxVx312iLAMASW/hsT45jyZFsDFgrz7F+5J51g72nbSdk+e2PI7eyPUYMd+a1kxY +XxUkyQKBgBde+jg2UjGHAfW6ZWSPHWvi74oD42VzbJg6/o/KFaZGEyHdKWgrwBW7 +8wNyNTMOMSHyuvtMnc5oLd492aO2a+yurgzt7yIelVuphSKda0wd/0PljKb0lwlb +VzldR4bXolbyjNXky1KWmvSNkmbPFNGSCeyHmKQgSLXKGuWbj4ic +-----END RSA PRIVATE KEY----- diff --git a/util/tls/tls_config.go b/util/tls/tls_config.go new file mode 100644 index 0000000..e9fd9ad --- /dev/null +++ b/util/tls/tls_config.go @@ -0,0 +1,60 @@ +// Copyright (c) 2023 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + +package tls + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "os" +) + +// NewTLSConfig initializes the TLS. +func NewTLSConfig(rootCert, cert, key string) (*tls.Config, error) { + fmt.Println("NewTLS CONFIG!", rootCert) + caCert, err := os.ReadFile(rootCert) + if err != nil { + return nil, fmt.Errorf("failed to load CA: %s", err) + } + caCertPool := x509.NewCertPool() + if !caCertPool.AppendCertsFromPEM(caCert) { + return nil, fmt.Errorf("failed to parse CA %s", rootCert) + } + + tlsConfig := &tls.Config{ + InsecureSkipVerify: false, + RootCAs: caCertPool, + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS13, + CipherSuites: supportedCipherSuites(), + } + + if len(cert) > 0 || len(key) > 0 { + cert, err := tls.LoadX509KeyPair(cert, key) + if err != nil { + return nil, fmt.Errorf("failed to load X509 key pair: %s", err) + } + tlsConfig.Certificates = []tls.Certificate{cert} + } + + return tlsConfig, nil +} + +func supportedCipherSuites() []uint16 { + cs := tls.CipherSuites() + cid := make([]uint16, len(cs)) + for i := range cs { + cid[i] = cs[i].ID + } + return cid +} diff --git a/util/tls/tls_config_test.go b/util/tls/tls_config_test.go new file mode 100644 index 0000000..aa8b875 --- /dev/null +++ b/util/tls/tls_config_test.go @@ -0,0 +1,104 @@ +// Copyright (c) 2023 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + +package tls + +import ( + "crypto/tls" + "errors" + "fmt" + "path/filepath" + "testing" +) + +const ( + nonExisting = "nonexisting.test" + invalidFile = "testdata/invalid.pem" + caCertPath = "testdata/ca.crt" + certPath = "testdata/certificate.pem" + keyPath = "testdata/key.pem" + caError = "failed to load CA: open %s: no such file or directory" + keyPairError = "failed to load X509 key pair: open %s: no such file or directory" + invalidError = "failed to load X509 key pair: tls: failed to find any PEM data in key input" +) + +func TestNewTLSConfig(t *testing.T) { + dirAbsPath, _ := filepath.Abs("./") + + tests := map[string]struct { + CACert string + Cert string + Key string + ExpectedError error + }{ + "valid_config_with_credentials": {CACert: caCertPath, Cert: certPath, Key: keyPath, ExpectedError: nil}, + "valid_config_no_credentials": {CACert: caCertPath, Cert: "", Key: "", ExpectedError: nil}, + "no_files_provided": {CACert: "", Cert: "", Key: "", ExpectedError: fmt.Errorf(caError, "")}, + "non_existing_ca_file": {CACert: nonExisting, Cert: "", Key: "", ExpectedError: fmt.Errorf(caError, nonExisting)}, + "invalid_ca_file": {CACert: invalidFile, Cert: certPath, Key: keyPath, ExpectedError: fmt.Errorf("failed to parse CA %s", invalidFile)}, + "invalid_ca_file_arg": {CACert: "\\\000", Cert: certPath, Key: keyPath, ExpectedError: errors.New("failed to load CA: open \\\000: invalid argument")}, + "not_abs_cert_file_provided": {CACert: caCertPath, Cert: nonExisting, Key: "", ExpectedError: fmt.Errorf(keyPairError, nonExisting)}, + "cert_is_directory": {CACert: caCertPath, Cert: dirAbsPath, Key: "", ExpectedError: fmt.Errorf("failed to load X509 key pair: read %s: is a directory", dirAbsPath)}, + "no_key_file_provided": {CACert: caCertPath, Cert: certPath, Key: "", ExpectedError: fmt.Errorf(keyPairError, "")}, + "not_abs_key_file_provided": {CACert: caCertPath, Cert: certPath, Key: nonExisting, ExpectedError: fmt.Errorf(keyPairError, nonExisting)}, + "empty_key_file_provided": {CACert: caCertPath, Cert: certPath, Key: "testdata/empty.crt", ExpectedError: errors.New(invalidError)}, + "cert_file_instead_key": {CACert: caCertPath, Cert: certPath, Key: caCertPath, ExpectedError: fmt.Errorf("failed to load X509 key pair: tls: found a certificate rather than a key in the PEM for the private key")}, + "invalid_key_file_provided": {CACert: caCertPath, Cert: certPath, Key: invalidFile, ExpectedError: errors.New(invalidError)}, + // "real_files": {CACert: "/home/antonia/tony/kanto/mosquitto_new_certs/ca.crt", Cert: "/home/antonia/tony/kanto/mosquitto_new_certs/client.crt", Key: "/home/antonia/tony/kanto/mosquitto_new_certs/client.key", ExpectedError: nil}, + } + + for testName, testCase := range tests { + t.Run(testName, func(t *testing.T) { + cfg, err := NewTLSConfig(testCase.CACert, testCase.Cert, testCase.Key) + if testCase.ExpectedError != nil { + if testCase.ExpectedError.Error() != err.Error() { + t.Fatalf("expected error : %s, got: %s", testCase.ExpectedError, err) + } + if cfg != nil { + t.Fatalf("expected nil, got: %v", cfg) + } + } else { + if err != nil { + t.Fatal(err) + } + if len(cfg.Certificates) == 0 && testCase.Cert != "" && testCase.Key != "" { + t.Fatal("certificates length must not be 0") + } + if len(cfg.CipherSuites) == 0 { + t.Fatal("cipher suites length must not be 0") + } + // assert that cipher suites identifiers are contained in tls.CipherSuites + for _, csID := range cfg.CipherSuites { + if !func() bool { + for _, cs := range tls.CipherSuites() { + if cs.ID == csID { + return true + } + } + return false + }() { + t.Fatalf("cipher suite %d is not implemented", csID) + } + } + if cfg.InsecureSkipVerify { + t.Fatal("skip verify is set to true") + } + if cfg.MinVersion != tls.VersionTLS12 { + t.Fatalf("invalid min TLS version %d", cfg.MinVersion) + } + if cfg.MaxVersion != tls.VersionTLS13 { + t.Fatalf("invalid max TLS version %d", cfg.MaxVersion) + } + } + }) + } +}