diff --git a/cmd/runmqserver/main.go b/cmd/runmqserver/main.go index 0ba885f..a338346 100644 --- a/cmd/runmqserver/main.go +++ b/cmd/runmqserver/main.go @@ -126,19 +126,19 @@ func doMain() error { // Print out versioning information logVersionInfo() - keylabel, cmsDB, p12Trust, _, err := tls.ConfigureTLSKeystores(keyDir, trustDir, keyStoreDir) + keyLabel, cmsKeystore, p12Truststore, err := tls.ConfigureTLSKeystores() if err != nil { logTermination(err) return err } - err = configureTLS(keylabel, cmsDB, *devFlag) + err = tls.ConfigureTLS(keyLabel, cmsKeystore, *devFlag, log) if err != nil { logTermination(err) return err } - err = postInit(name, keylabel, p12Trust) + err = postInit(name, keyLabel, p12Truststore) if err != nil { logTermination(err) return err diff --git a/cmd/runmqserver/post_init.go b/cmd/runmqserver/post_init.go index aa4b119..7128e1e 100644 --- a/cmd/runmqserver/post_init.go +++ b/cmd/runmqserver/post_init.go @@ -22,23 +22,23 @@ import ( ) // postInit is run after /var/mqm is set up -func postInit(name, keylabel string, p12Trust tls.KeyStoreData) error { +func postInit(name, keyLabel string, p12Truststore tls.KeyStoreData) error { enableWebServer := os.Getenv("MQ_ENABLE_EMBEDDED_WEB_SERVER") if enableWebServer == "true" || enableWebServer == "1" { // Configure the web server (if enabled) - keystore, err := configureWebServer(keylabel, p12Trust) + webKeystore, err := configureWebServer(keyLabel, p12Truststore) if err != nil { return err } - // If trust-store is empty, set reference to point to the key-store - p12TrustStoreRef := "MQWebTrustStore" - if len(p12Trust.TrustedCerts) == 0 { - p12TrustStoreRef = "MQWebKeyStore" + // If trust-store is empty, set reference to point to the keystore + webTruststoreRef := "MQWebTrustStore" + if len(p12Truststore.TrustedCerts) == 0 { + webTruststoreRef = "MQWebKeyStore" } // Start the web server, in the background (if installed) // WARNING: No error handling or health checking available for the web server go func() { - err = startWebServer(keystore, p12Trust.Password, p12TrustStoreRef) + err = startWebServer(webKeystore, p12Truststore.Password, webTruststoreRef) if err != nil { log.Printf("Error starting web server: %v", err) } diff --git a/cmd/runmqserver/tls.go b/cmd/runmqserver/tls.go deleted file mode 100644 index 6c68ba3..0000000 --- a/cmd/runmqserver/tls.go +++ /dev/null @@ -1,163 +0,0 @@ -/* -© Copyright IBM Corporation 2018, 2019 - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -package main - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/ibm-messaging/mq-container/internal/command" - "github.com/ibm-messaging/mq-container/internal/keystore" - "github.com/ibm-messaging/mq-container/internal/mqtemplate" - "github.com/ibm-messaging/mq-container/internal/tls" -) - -// Location to store the keystores -const keyStoreDir = "/run/runmqserver/tls/" - -// KeyDir is the location of the certificate keys to import -const keyDir = "/etc/mqm/pki/keys" - -// TrustDir is the location of the Certifates to add -const trustDir = "/etc/mqm/pki/trust" - -// configureWebTLS configures TLS for Web Console -func configureWebTLS(label string) error { - // Return immediately if we have no certificate to use as identity - if label == "" && os.Getenv("MQ_GENERATE_CERTIFICATE_HOSTNAME") == "" { - return nil - } - - webConfigDir := "/etc/mqm/web/installations/Installation1/servers/mqweb" - tls := "tls.xml" - - tlsConfig := filepath.Join(webConfigDir, tls) - newTLSConfig := filepath.Join(webConfigDir, tls+".tpl") - err := os.Remove(tlsConfig) - if err != nil { - return fmt.Errorf("Could not delete file %s: %v", tlsConfig, err) - } - // we symlink here to prevent issues on restart - err = os.Symlink(newTLSConfig, tlsConfig) - if err != nil { - return fmt.Errorf("Could not create symlink %s->%s: %v", newTLSConfig, tlsConfig, err) - } - mqmUID, mqmGID, err := command.LookupMQM() - if err != nil { - return fmt.Errorf("Could not find mqm user or group: %v", err) - } - err = os.Chown(tlsConfig, mqmUID, mqmGID) - if err != nil { - return fmt.Errorf("Could change ownership of %s to mqm: %v", tlsConfig, err) - } - - return nil -} - -// configureTLSDev configures TLS for developer defaults -func configureTLSDev() error { - const mqsc string = "/etc/mqm/20-dev-tls.mqsc" - const mqscTemplate string = mqsc + ".tpl" - - if os.Getenv("MQ_DEV") == "true" { - err := mqtemplate.ProcessTemplateFile(mqscTemplate, mqsc, map[string]string{}, log) - if err != nil { - return err - } - } else { - _, err := os.Stat(mqsc) - if !os.IsNotExist(err) { - err = os.Remove(mqsc) - if err != nil { - log.Errorf("Error removing file %s: %v", mqsc, err) - return err - } - } - } - - return nil -} - -// configureTLS configures TLS for queue manager -func configureTLS(certLabel string, cmsKeystore tls.KeyStoreData, devmode bool) error { - log.Debug("Configuring TLS") - - const mqsc string = "/etc/mqm/15-tls.mqsc" - const mqscTemplate string = mqsc + ".tpl" - - err := mqtemplate.ProcessTemplateFile(mqscTemplate, mqsc, map[string]string{ - "SSLKeyR": strings.TrimSuffix(cmsKeystore.Keystore.Filename, ".kdb"), - "CertificateLabel": certLabel, - }, log) - if err != nil { - return err - } - - if devmode && certLabel != "" { - err = configureTLSDev() - if err != nil { - return err - } - } - - return nil -} - -// configureWebKeyStore configures the key stores for the web console -func configureWebKeyStore(p12TrustStore tls.KeyStoreData) (string, error) { - // TODO find way to supply this - // Override the webstore variables to hard coded defaults - webKeyStoreName := tls.WebDefaultLabel + ".p12" - - // Check keystore exists - ks := filepath.Join(keyStoreDir, webKeyStoreName) - _, err := os.Stat(ks) - // Now we know if the file exists let's check whether we should have it or not. - // Check if we're being told to generate the certificate - genHostName := os.Getenv("MQ_GENERATE_CERTIFICATE_HOSTNAME") - if genHostName != "" { - // We've got to generate the certificate with the hostname given - if err == nil { - log.Printf("Replacing existing keystore %s - generating new certificate", ks) - } - // Keystore doesn't exist so create it and populate a certificate - newKS := keystore.NewPKCS12KeyStore(ks, p12TrustStore.Password) - err = newKS.Create() - if err != nil { - return "", fmt.Errorf("Failed to create keystore %s: %v", ks, err) - } - - err = newKS.CreateSelfSignedCertificate("default", fmt.Sprintf("CN=%s", genHostName), genHostName) - if err != nil { - return "", fmt.Errorf("Failed to generate certificate in keystore %s with DN of 'CN=%s': %v", ks, genHostName, err) - } - } else { - // Keystore should already exist - if err != nil { - return "", fmt.Errorf("Failed to find existing keystore %s: %v", ks, err) - } - } - - // Check truststore exists - _, err = os.Stat(p12TrustStore.Keystore.Filename) - if err != nil { - return "", fmt.Errorf("Failed to find existing truststore %s: %v", p12TrustStore.Keystore.Filename, err) - } - - return webKeyStoreName, nil -} diff --git a/cmd/runmqserver/webserver.go b/cmd/runmqserver/webserver.go index 6318451..593bdd1 100644 --- a/cmd/runmqserver/webserver.go +++ b/cmd/runmqserver/webserver.go @@ -31,7 +31,7 @@ import ( "github.com/ibm-messaging/mq-container/internal/tls" ) -func startWebServer(keystore, keystorepw, p12TrustStoreRef string) error { +func startWebServer(webKeystore, webkeystorePW, webTruststoreRef string) error { _, err := os.Stat("/opt/mqm/bin/strmqweb") if err != nil && os.IsNotExist(err) { log.Debug("Skipping web server, because it's not installed") @@ -50,10 +50,10 @@ func startWebServer(keystore, keystorepw, p12TrustStoreRef string) error { } // TLS enabled - if keystore != "" { - cmd.Env = append(cmd.Env, "AMQ_WEBKEYSTORE="+keystore) - cmd.Env = append(cmd.Env, "AMQ_WEBKEYSTOREPW="+keystorepw) - cmd.Env = append(cmd.Env, "AMQ_WEBTRUSTSTOREREF="+p12TrustStoreRef) + if webKeystore != "" { + cmd.Env = append(cmd.Env, "AMQ_WEBKEYSTORE="+webKeystore) + cmd.Env = append(cmd.Env, "AMQ_WEBKEYSTOREPW="+webkeystorePW) + cmd.Env = append(cmd.Env, "AMQ_WEBTRUSTSTOREREF="+webTruststoreRef) } uid, gid, err := command.LookupMQM() @@ -119,50 +119,53 @@ func configureSSO(p12TrustStore tls.KeyStoreData) (string, error) { } // Configure SSO TLS - return configureWebKeyStore(p12TrustStore) + return tls.ConfigureWebKeystore(p12TrustStore) } -func configureWebServer(keyLabel string, p12Trust tls.KeyStoreData) (string, error) { - var keystore string +func configureWebServer(keyLabel string, p12Truststore tls.KeyStoreData) (string, error) { + var webKeystore string // Configure TLS for Web Console first if we have a certificate to use - err := configureWebTLS(keyLabel) + err := tls.ConfigureWebTLS(keyLabel) if err != nil { - return keystore, err + return "", err } if keyLabel != "" { - keystore = keyLabel + ".p12" + webKeystore = keyLabel + ".p12" } // Configure Single-Sign-On for the web server (if enabled) enableSSO := os.Getenv("MQ_BETA_ENABLE_SSO") if enableSSO == "true" || enableSSO == "1" { - keystore, err = configureSSO(p12Trust) + webKeystore, err = configureSSO(p12Truststore) if err != nil { - return keystore, err + return "", err } } else if keyLabel == "" && os.Getenv("MQ_GENERATE_CERTIFICATE_HOSTNAME") != "" { - keystore, err = configureWebKeyStore(p12Trust) + webKeystore, err = tls.ConfigureWebKeystore(p12Truststore) + if err != nil { + return "", err + } } _, err = os.Stat("/opt/mqm/bin/strmqweb") if err != nil { if os.IsNotExist(err) { - return keystore, nil + return "", nil } - return keystore, err + return "", err } const webConfigDir string = "/etc/mqm/web" _, err = os.Stat(webConfigDir) if err != nil { if os.IsNotExist(err) { - return keystore, nil + return "", nil } - return keystore, err + return "", err } uid, gid, err := command.LookupMQM() if err != nil { - return keystore, err + return "", err } const prefix string = "/etc/mqm/web" err = filepath.Walk(prefix, func(from string, info os.FileInfo, err error) error { @@ -206,5 +209,6 @@ func configureWebServer(keyLabel string, p12Trust tls.KeyStoreData) (string, err } return nil }) - return keystore, err + + return webKeystore, err } diff --git a/docs/developer-config.md b/docs/developer-config.md index 2580fce..180c090 100644 --- a/docs/developer-config.md +++ b/docs/developer-config.md @@ -9,8 +9,6 @@ The MQ Developer Defaults supports some customization options, these are all con * **MQ_DEV** - Set this to `false` to stop the default objects being created. * **MQ_ADMIN_PASSWORD** - Changes the password of the `admin` user. Must be at least 8 characters long. * **MQ_APP_PASSWORD** - Changes the password of the app user. If set, this will cause the `DEV.APP.SVRCONN` channel to become secured and only allow connections that supply a valid userid and password. Must be at least 8 characters long. -* **MQ_TLS_KEYSTORE** - **DEPRECATED**. See section `Supplying TLS certificates` in [usage document](usage.md). Allows you to supply the location of a PKCS#12 keystore containing a single certificate which you want to use in both the web console and the queue manager. Requires `MQ_TLS_PASSPHRASE`. When enabled the channels created will be secured using the `TLS_RSA_WITH_AES_128_CBC_SHA256` CipherSpec. *Note*: you will need to make the keystore available inside your container, this can be done by mounting a volume to your container. -* **MQ_TLS_PASSPHRASE** - **DEPRECATED**. See section `Supplying TLS certificates` in [usage document](usage.md). Passphrase for the keystore referenced in `MQ_TLS_KEYSTORE`. ## Details of the default configuration @@ -45,6 +43,6 @@ If you choose to accept the security warning, you will be presented with the log * **User:** admin * **Password:** passw0rd -If you wish to change the password for the admin user, this can be done using the `MQ_ADMIN_PASSWORD` environment variable. If you supply a PKCS#12 keystore using the `MQ_TLS_KEYSTORE` environment variable, then the web console will be configured to use the certificate inside the keystore for HTTPS operations. +If you wish to change the password for the admin user, this can be done using the `MQ_ADMIN_PASSWORD` environment variable. If you do not wish the web console to run, you can disable it by setting the environment variable `MQ_ENABLE_EMBEDDED_WEB_SERVER` to `false`. diff --git a/internal/tls/tls.go b/internal/tls/tls.go index fb2e535..614e696 100644 --- a/internal/tls/tls.go +++ b/internal/tls/tls.go @@ -30,19 +30,27 @@ import ( "crypto/x509" "encoding/pem" - "github.com/ibm-messaging/mq-container/internal/filecheck" - "github.com/ibm-messaging/mq-container/internal/keystore" pkcs "software.sslmate.com/src/go-pkcs12" + + "github.com/ibm-messaging/mq-container/internal/keystore" + "github.com/ibm-messaging/mq-container/internal/mqtemplate" + "github.com/ibm-messaging/mq-container/pkg/logger" ) -// WebDefaultLabel is the default certificate label for the web console -const WebDefaultLabel = "default" +// cmsKeystoreName is the name of the CMS Keystore +const cmsKeystoreName = "key.kdb" -// P12TrustStoreName is the name of the PKCS#12 truststore used by the webconsole -const P12TrustStoreName = "trust.p12" +// p12TruststoreName is the name of the PKCS#12 Truststore +const p12TruststoreName = "trust.p12" -// CMSKeyStoreName is the name of the CMS Keystore used by the queue manager -const CMSKeyStoreName = "key.kdb" +// keystoreDir is the location for the CMS Keystore & PKCS#12 Truststore +const keystoreDir = "/run/runmqserver/tls/" + +// keyDir is the location of the keys to import +const keyDir = "/etc/mqm/pki/keys" + +// trustDir is the location of the trust certificates to import +const trustDir = "/etc/mqm/pki/trust" type KeyStoreData struct { Keystore *keystore.KeyStore @@ -57,45 +65,445 @@ type P12KeyFiles struct { Password string } -func getCertFingerPrint(block *pem.Block) (string, error) { - // Add to future truststore and known certs (if not already there) - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return "", fmt.Errorf("could not parse x509 certificate: %v", err) - } - sha512Sum := sha512.Sum512(cert.Raw) - sha512str := string(sha512Sum[:]) +// ConfigureTLSKeystores configures the CMS Keystore & PKCS#12 Truststore +func ConfigureTLSKeystores() (string, KeyStoreData, KeyStoreData, error) { - return sha512str, nil + // Create the CMS Keystore & PKCS#12 Truststore + cmsKeystore, p12Truststore, err := generateAllKeystores() + if err != nil { + return "", cmsKeystore, p12Truststore, err + } + + // Process all keys - add them to the CMS KeyStore + keyLabel, err := processKeys(&cmsKeystore, &p12Truststore) + if err != nil { + return "", cmsKeystore, p12Truststore, err + } + + // Process all trust certificates - add them to the CMS KeyStore & PKCS#12 Truststore + err = processTrustCertificates(&cmsKeystore, &p12Truststore) + if err != nil { + return "", cmsKeystore, p12Truststore, err + } + + return keyLabel, cmsKeystore, p12Truststore, err } -// Add to Keystores known certs (if not already there) and add to the -// Keystore if "addToKeystore" is true. -func addCertToKeyData(block *pem.Block, keyData *KeyStoreData, addToKeystore bool) error { - sha512str, err := getCertFingerPrint(block) +// ConfigureTLS configures TLS for the queue manager +func ConfigureTLS(keyLabel string, cmsKeystore KeyStoreData, devMode bool, log *logger.Logger) error { + + const mqsc string = "/etc/mqm/15-tls.mqsc" + const mqscTemplate string = mqsc + ".tpl" + + err := mqtemplate.ProcessTemplateFile(mqscTemplate, mqsc, map[string]string{ + "SSLKeyR": strings.TrimSuffix(cmsKeystore.Keystore.Filename, ".kdb"), + "CertificateLabel": keyLabel, + }, log) if err != nil { return err } - known := false - for _, fingerprint := range keyData.KnownFingerPrints { - if fingerprint == sha512str { - known = true + + if devMode && keyLabel != "" { + err = configureTLSDev(log) + if err != nil { + return err + } + } + + return nil +} + +// configureTLSDev configures TLS for the developer defaults +func configureTLSDev(log *logger.Logger) error { + + const mqsc string = "/etc/mqm/20-dev-tls.mqsc" + const mqscTemplate string = mqsc + ".tpl" + + if os.Getenv("MQ_DEV") == "true" { + err := mqtemplate.ProcessTemplateFile(mqscTemplate, mqsc, map[string]string{}, log) + if err != nil { + return err + } + } else { + _, err := os.Stat(mqsc) + if !os.IsNotExist(err) { + err = os.Remove(mqsc) + if err != nil { + return fmt.Errorf("Failed to remove file %s: %v", mqsc, err) + } + } + } + + return nil +} + +// generateAllKeystores creates the CMS Keystore & PKCS#12 Truststore +func generateAllKeystores() (KeyStoreData, KeyStoreData, error) { + var cmsKeystore, p12Truststore KeyStoreData + + // Generate a pasword for use with both the CMS Keystore & PKCS#12 Truststore + pw := generateRandomPassword() + cmsKeystore.Password = pw + p12Truststore.Password = pw + + // Create the Keystore directory - if it does not already exist + // #nosec G301 - write group permissions are required + err := os.MkdirAll(keystoreDir, 0770) + if err != nil { + return cmsKeystore, p12Truststore, fmt.Errorf("Failed to create Keystore directory: %v", err) + } + + // Create the CMS Keystore + cmsKeystore.Keystore = keystore.NewCMSKeyStore(filepath.Join(keystoreDir, cmsKeystoreName), cmsKeystore.Password) + err = cmsKeystore.Keystore.Create() + if err != nil { + return cmsKeystore, p12Truststore, fmt.Errorf("Failed to create CMS Keystore: %v", err) + } + + // Create the PKCS#12 Truststore + p12Truststore.Keystore = keystore.NewPKCS12KeyStore(filepath.Join(keystoreDir, p12TruststoreName), p12Truststore.Password) + err = p12Truststore.Keystore.Create() + if err != nil { + return cmsKeystore, p12Truststore, fmt.Errorf("Failed to create PKCS#12 Truststore: %v", err) + } + + return cmsKeystore, p12Truststore, nil +} + +// processKeys processes all keys - adding them to the CMS KeyStore +func processKeys(cmsKeystore, p12Truststore *KeyStoreData) (string, error) { + + // Key label - will be set to the label of the first set of keys + keyLabel := "" + + // Process all keys + keyList, err := ioutil.ReadDir(keyDir) + if err == nil && len(keyList) > 0 { + + // Process each set of keys - each set should contain files: *.key & *.crt + for _, keySet := range keyList { + keys, _ := ioutil.ReadDir(filepath.Join(keyDir, keySet.Name())) + + // Ensure the label of the set of keys does not match the name of the PKCS#12 Truststore + if keySet.Name() == p12TruststoreName[0:len(p12TruststoreName)-len(filepath.Ext(p12TruststoreName))] { + return "", fmt.Errorf("Key label cannot be set to the Truststore name: %v", keySet.Name()) + } + + // Process private key (*.key) + privateKey, keyPrefix, err := processPrivateKey(keySet.Name(), keys) + if err != nil { + return "", err + } + + // If private key does not exist - skip this set of keys + if privateKey == nil { + continue + } + + // Process certificates (*.crt) - public certificate & optional CA certificate + publicCertificate, caCertificate, err := processCertificates(keySet.Name(), keyPrefix, keys, cmsKeystore, p12Truststore) + if err != nil { + return "", err + } + + // Create a new PKCS#12 Keystore - containing private key, public certificate & optional CA certificate + file, err := pkcs.Encode(rand.Reader, privateKey, publicCertificate, caCertificate, cmsKeystore.Password) + if err != nil { + return "", fmt.Errorf("Failed to encode PKCS#12 Keystore %s: %v", keySet.Name()+".p12", err) + } + err = ioutil.WriteFile(filepath.Join(keystoreDir, keySet.Name()+".p12"), file, 0644) + if err != nil { + return "", fmt.Errorf("Failed to write PKCS#12 Keystore %s: %v", filepath.Join(keystoreDir, keySet.Name()+".p12"), err) + } + + // Import the new PKCS#12 Keystore into the CMS Keystore + err = cmsKeystore.Keystore.Import(filepath.Join(keystoreDir, keySet.Name()+".p12"), cmsKeystore.Password) + if err != nil { + return "", fmt.Errorf("Failed tp import keys from %s into CMS Keystore: %v", filepath.Join(keystoreDir, keySet.Name()+".p12"), err) + } + + // Relabel the certificate in the CMS Keystore + err = relabelCertificate(keySet.Name(), cmsKeystore) + if err != nil { + return "", err + } + + // Set key label - for first set of keys only + if keyLabel == "" { + keyLabel = keySet.Name() + } + } + } + + return keyLabel, nil +} + +// processTrustCertificates processes all trust certificates - adding them to the CMS KeyStore & PKCS#12 Truststore +func processTrustCertificates(cmsKeystore, p12Truststore *KeyStoreData) error { + + // Process all trust certiifcates + trustList, err := ioutil.ReadDir(trustDir) + if err == nil && len(trustList) > 0 { + + // Process each set of keys + for _, trustSet := range trustList { + keys, _ := ioutil.ReadDir(filepath.Join(trustDir, trustSet.Name())) + + for _, key := range keys { + if strings.HasSuffix(key.Name(), ".crt") { + // #nosec G304 - filename variable is derived from contents of 'trustDir' which is a defined constant + file, err := ioutil.ReadFile(filepath.Join(trustDir, trustSet.Name(), key.Name())) + if err != nil { + return fmt.Errorf("Failed to read file %s: %v", filepath.Join(trustDir, trustSet.Name(), key.Name()), err) + } + + for string(file) != "" { + var block *pem.Block + block, file = pem.Decode(file) + if block == nil { + break + } + + // Add to known certificates for the CMS Keystore + err = addToKnownCertificates(block, cmsKeystore, true) + if err != nil { + return fmt.Errorf("Failed to add to know certificates for CMS Keystore") + } + + // Add to known certificates for the PKCS#12 Truststore + err = addToKnownCertificates(block, p12Truststore, true) + if err != nil { + return fmt.Errorf("Failed to add to know certificates for PKCS#12 Truststore") + } + } + } + } + } + } + + // Add all trust certificates to PKCS#12 Truststore + if len(p12Truststore.TrustedCerts) > 0 { + err = addCertificatesToTruststore(p12Truststore) + if err != nil { + return err + } + } + + // Add all trust certificates to CMS Keystore + if len(cmsKeystore.TrustedCerts) > 0 { + err = addCertificatesToCMSKeystore(cmsKeystore) + if err != nil { + return err + } + } + + return nil +} + +// processPrivateKey processes the private key (*.key) from a set of keys +func processPrivateKey(keySetName string, keys []os.FileInfo) (interface{}, string, error) { + + var privateKey interface{} + keyPrefix := "" + + for _, key := range keys { + + if strings.HasSuffix(key.Name(), ".key") { + // #nosec G304 - filename variable is derived from contents of 'keyDir' which is a defined constant + file, err := ioutil.ReadFile(filepath.Join(keyDir, keySetName, key.Name())) + if err != nil { + return nil, "", fmt.Errorf("Failed to read private key %s: %v", filepath.Join(keyDir, keySetName, key.Name()), err) + } + block, _ := pem.Decode(file) + if block == nil { + return nil, "", fmt.Errorf("Failed to decode private key %s: pem.Decode returned nil", filepath.Join(keyDir, keySetName, key.Name())) + } + + // Check if the private key is PKCS1 + privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + // Check if the private key is PKCS8 + privateKey, err = x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, "", fmt.Errorf("Failed to parse private key %s: %v", filepath.Join(keyDir, keySetName, key.Name()), err) + } + } + keyPrefix = key.Name()[0 : len(key.Name())-len(filepath.Ext(key.Name()))] + } + } + + return privateKey, keyPrefix, nil +} + +// processCertificates processes the certificates (*.crt) from a set of keys +func processCertificates(keySetName, keyPrefix string, keys []os.FileInfo, cmsKeystore, p12Truststore *KeyStoreData) (*x509.Certificate, []*x509.Certificate, error) { + + var publicCertificate *x509.Certificate + var caCertificate []*x509.Certificate + + for _, key := range keys { + + if strings.HasPrefix(key.Name(), keyPrefix) && strings.HasSuffix(key.Name(), ".crt") { + // #nosec G304 - filename variable is derived from contents of 'keyDir' which is a defined constant + file, err := ioutil.ReadFile(filepath.Join(keyDir, keySetName, key.Name())) + if err != nil { + return nil, nil, fmt.Errorf("Failed to read public certificate %s: %v", filepath.Join(keyDir, keySetName, key.Name()), err) + } + block, _ := pem.Decode(file) + if block == nil { + return nil, nil, fmt.Errorf("Failed to decode public certificate %s: pem.Decode returned nil", filepath.Join(keyDir, keySetName, key.Name())) + } + publicCertificate, err = x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, nil, fmt.Errorf("Failed to parse public certificate %s: %v", filepath.Join(keyDir, keySetName, key.Name()), err) + } + + // Add to known certificates for the CMS Keystore + err = addToKnownCertificates(block, cmsKeystore, false) + if err != nil { + return nil, nil, fmt.Errorf("Failed to add to know certificates for CMS Keystore") + } + + } else if strings.HasSuffix(key.Name(), ".crt") { + // #nosec G304 - filename variable is derived from contents of 'keyDir' which is a defined constant + file, err := ioutil.ReadFile(filepath.Join(keyDir, keySetName, key.Name())) + if err != nil { + return nil, nil, fmt.Errorf("Failed to read CA certificate %s: %v", filepath.Join(keyDir, keySetName, key.Name()), err) + } + + for string(file) != "" { + var block *pem.Block + block, file = pem.Decode(file) + if block == nil { + break + } + + // Add to known certificates for the CMS Keystore + err = addToKnownCertificates(block, cmsKeystore, false) + if err != nil { + return nil, nil, fmt.Errorf("Failed to add to know certificates for CMS Keystore") + } + + // Add to known certificates for the PKCS#12 Truststore + err = addToKnownCertificates(block, p12Truststore, true) + if err != nil { + return nil, nil, fmt.Errorf("Failed to add to know certificates for PKCS#12 Truststore") + } + + certificate, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, nil, fmt.Errorf("Failed to parse CA certificate %s: %v", filepath.Join(keyDir, keySetName, key.Name()), err) + } + caCertificate = append(caCertificate, certificate) + } + } + } + + return publicCertificate, caCertificate, nil +} + +// relabelCertificate sets a new label for a certificate in the CMS Keystore +func relabelCertificate(newLabel string, cmsKeystore *KeyStoreData) error { + + allLabels, err := cmsKeystore.Keystore.GetCertificateLabels() + if err != nil { + return fmt.Errorf("Failed to get list of all certificate labels from CMS Keystore: %v", err) + } + relabelled := false + for _, label := range allLabels { + found := false + for _, keyLabel := range cmsKeystore.KeyLabels { + if strings.Trim(label, "\"") == keyLabel { + found = true + break + } + } + if !found { + err = cmsKeystore.Keystore.RenameCertificate(strings.Trim(label, "\""), newLabel) + if err != nil { + return err + } + relabelled = true + cmsKeystore.KeyLabels = append(cmsKeystore.KeyLabels, newLabel) break } } - if !known { - // Sometimes we don't want to add to the keystore trust here. - // For example if it will be imported with the key later. - if addToKeystore { - keyData.TrustedCerts = append(keyData.TrustedCerts, block) - } - keyData.KnownFingerPrints = append(keyData.KnownFingerPrints, sha512str) + if !relabelled { + return fmt.Errorf("Failed to relabel certificate for %s in CMS keystore", newLabel) } + return nil } -// Generates a random 12 character password from the characters a-z, A-Z, 0-9. +// addCertificatesToTruststore adds trust certificates to the PKCS#12 Truststore +func addCertificatesToTruststore(p12Truststore *KeyStoreData) error { + + temporaryPemFile := filepath.Join("/tmp", "trust.pem") + _, err := os.Stat(temporaryPemFile) + if err == nil { + err = os.Remove(temporaryPemFile) + if err != nil { + return fmt.Errorf("Failed to remove file %v: %v", temporaryPemFile, err) + } + } + + err = writeCertificatesToFile(temporaryPemFile, p12Truststore.TrustedCerts) + if err != nil { + return err + } + + err = p12Truststore.Keystore.AddNoLabel(temporaryPemFile) + if err != nil { + return fmt.Errorf("Failed to add certificates to PKCS#12 Truststore: %v", err) + } + + // Relabel all certiifcates + allCertificates, err := p12Truststore.Keystore.ListAllCertificates() + if err != nil || len(allCertificates) <= 0 { + return fmt.Errorf("Failed to get any certificates from PKCS#12 Truststore: %v", err) + } + + for i, certificate := range allCertificates { + certificate = strings.Trim(certificate, "\"") + certificate = strings.TrimSpace(certificate) + newLabel := fmt.Sprintf("Trust%d", i) + + err = p12Truststore.Keystore.RenameCertificate(certificate, newLabel) + if err != nil || len(allCertificates) <= 0 { + return fmt.Errorf("Failed to rename certificate %s to %s in PKCS#12 Truststore: %v", certificate, newLabel, err) + } + } + + return nil +} + +// addCertificatesToCMSKeystore adds trust certificates to the CMS keystore +func addCertificatesToCMSKeystore(cmsKeystore *KeyStoreData) error { + + temporaryPemFile := filepath.Join("/tmp", "cmsTrust.pem") + _, err := os.Stat(temporaryPemFile) + if err == nil { + err = os.Remove(temporaryPemFile) + if err != nil { + return fmt.Errorf("Failed to remove file %v: %v", temporaryPemFile, err) + } + } + + err = writeCertificatesToFile(temporaryPemFile, cmsKeystore.TrustedCerts) + if err != nil { + return err + } + + err = cmsKeystore.Keystore.AddNoLabel(temporaryPemFile) + if err != nil { + return fmt.Errorf("Failed to add certificates to CMS keystore: %v", err) + } + + return nil +} + +// generateRandomPassword generates a random 12 character password from the characters a-z, A-Z, 0-9 func generateRandomPassword() string { pwr.Seed(time.Now().Unix()) validChars := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" @@ -108,503 +516,61 @@ func generateRandomPassword() string { return password } -// Creates the PKCS#12 Truststore and the CMS Keystore. -func generateAllStores(dir string) (KeyStoreData, KeyStoreData, error) { - var cmsKeystore, p12TrustStore KeyStoreData - pw := generateRandomPassword() - - cmsKeystore.Password = pw - p12TrustStore.Password = pw - - // Create the keystore Directory (if it doesn't already exist) - // #nosec G301 - write group permissions are required - err := os.MkdirAll(dir, 0770) +// addToKnownCertificates adds to the list of known certificates for a Keystore +func addToKnownCertificates(block *pem.Block, keyData *KeyStoreData, addToKeystore bool) error { + sha512str, err := getCertificateFingerprint(block) if err != nil { - return cmsKeystore, p12TrustStore, fmt.Errorf("Failed to create keystore directory: %v", err) + return err } - - p12TrustStore.Keystore = keystore.NewPKCS12KeyStore(filepath.Join(dir, P12TrustStoreName), p12TrustStore.Password) - err = p12TrustStore.Keystore.Create() - if err != nil { - return cmsKeystore, p12TrustStore, fmt.Errorf("Failed to create PKCS#12 TrustStore: %v", err) - } - - cmsKeystore.Keystore = keystore.NewCMSKeyStore(filepath.Join(dir, CMSKeyStoreName), cmsKeystore.Password) - err = cmsKeystore.Keystore.Create() - if err != nil { - return cmsKeystore, p12TrustStore, fmt.Errorf("Failed to create CMS KeyStore: %v", err) - } - - return cmsKeystore, p12TrustStore, nil -} - -// processKeys walks through the keyDir directory and imports any keys it finds to individual PKCS#12 keystores -// and the CMS KeyStore. The label it uses is the name of the directory if finds the keys in. -func processKeys(keyDir, outputDir string, cmsKeyDB, p12TrustDB *KeyStoreData) (string, P12KeyFiles, error) { - var p12s P12KeyFiles - var firstLabel string - - pwToUse := cmsKeyDB.Password - p12s.Password = pwToUse - trustStoreReserveredName := P12TrustStoreName[0 : len(P12TrustStoreName)-len(filepath.Ext(P12TrustStoreName))] - keyList, err := ioutil.ReadDir(keyDir) - - if err == nil && len(keyList) > 0 { - // Found some keys, verify the contents - for _, key := range keyList { - keys, _ := ioutil.ReadDir(filepath.Join(keyDir, key.Name())) - keyLabel := key.Name() - if keyLabel == trustStoreReserveredName { - return firstLabel, p12s, fmt.Errorf("Found key with same label set as same name as truststore(%s). This is not allowed", trustStoreReserveredName) - } - - keyfilename := "" - var keyfile interface{} - var certFile *x509.Certificate - var caFile []*x509.Certificate - - // find the keyfile name - for _, a := range keys { - if strings.HasSuffix(a.Name(), ".key") { - // #nosec G304 - filename variable is derived from contents of 'keyDir' which is a defined constant - keyFile, err := ioutil.ReadFile(filepath.Join(keyDir, key.Name(), a.Name())) - if err != nil { - return firstLabel, p12s, fmt.Errorf("Could not read keyfile %s: %v", filepath.Join(keyDir, key.Name(), a.Name()), err) - } - block, _ := pem.Decode(keyFile) - if block == nil { - return firstLabel, p12s, fmt.Errorf("Could not decode keyfile %s: pem.Decode returned nil", filepath.Join(keyDir, key.Name(), a.Name())) - } - - //Test whether it is PKCS1 - keyfile, err = x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - // Before we fail check whether it is PKCS8 - keyfile, err = x509.ParsePKCS8PrivateKey(block.Bytes) - if err != nil { - fmt.Printf("key %s ParsePKCS1/8PrivateKey ERR: %v\n", filepath.Join(keyDir, key.Name(), a.Name()), err) - return firstLabel, p12s, err - } - //It was PKCS8 afterall - } - keyfilename = a.Name() - } - } - if keyfile == nil { - continue - } - - // Find out what the keyfile was called without the extension - prefix := keyfilename[0 : len(keyfilename)-len(filepath.Ext(keyfilename))] - - for _, a := range keys { - if strings.HasSuffix(a.Name(), ".key") { - continue - } - if strings.HasPrefix(a.Name(), prefix) && strings.HasSuffix(a.Name(), ".crt") { - // #nosec G304 - filename variable is derived from contents of 'keyDir' which is a defined constant - cert, err := ioutil.ReadFile(filepath.Join(keyDir, key.Name(), a.Name())) - if err != nil { - return firstLabel, p12s, fmt.Errorf("Could not read file %s: %v", filepath.Join(keyDir, key.Name(), a.Name()), err) - } - block, _ := pem.Decode(cert) - if block == nil { - return firstLabel, p12s, fmt.Errorf("Could not decode certificate %s: pem.Decode returned nil", filepath.Join(keyDir, key.Name(), a.Name())) - } - certFile, err = x509.ParseCertificate(block.Bytes) - if err != nil { - return firstLabel, p12s, fmt.Errorf("Could not parse certificate %s: %v", filepath.Join(keyDir, key.Name(), a.Name()), err) - } - // Add to the dup list for the CMS keystore but not the PKCS#12 Truststore - err = addCertToKeyData(block, cmsKeyDB, false) - - } else if strings.HasSuffix(a.Name(), ".crt") { - // #nosec G304 - filename variable is derived from contents of 'keyDir' which is a defined constant - remainder, err := ioutil.ReadFile(filepath.Join(keyDir, key.Name(), a.Name())) - if err != nil { - return firstLabel, p12s, fmt.Errorf("Could not read file %s: %v", filepath.Join(keyDir, key.Name(), a.Name()), err) - } - - for string(remainder) != "" { - var block *pem.Block - block, remainder = pem.Decode(remainder) - // If we can't decode the CA certificate then just exit. - if block == nil { - break - } - - // Add to the dup list for the CMS keystore - err = addCertToKeyData(block, cmsKeyDB, false) - - // Add to the p12 truststore - err = addCertToKeyData(block, p12TrustDB, true) - - caCert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return firstLabel, p12s, fmt.Errorf("Could not parse CA certificate %s: %v", filepath.Join(keyDir, key.Name(), a.Name()), err) - } - - caFile = append(caFile, caCert) - } - } - } - - // Create p12 keystore - file, err := pkcs.Encode(rand.Reader, keyfile, certFile, caFile, pwToUse) - if err != nil { - return firstLabel, p12s, fmt.Errorf("Could not encode PKCS#12 Keystore %s: %v", keyLabel+".p12", err) - } - - err = ioutil.WriteFile(filepath.Join(outputDir, keyLabel+".p12"), file, 0644) - if err != nil { - return firstLabel, p12s, fmt.Errorf("Could not write PKCS#12 Keystore %s: %v", filepath.Join(outputDir, keyLabel+".p12"), err) - } - - p12s.Keystores = append(p12s.Keystores, keyLabel+".p12") - - // Add to the CMS keystore - err = cmsKeyDB.Keystore.Import(filepath.Join(outputDir, keyLabel+".p12"), pwToUse) - if err != nil { - return firstLabel, p12s, fmt.Errorf("Could not import keys from %s into CMS Keystore: %v", filepath.Join(outputDir, keyLabel+".p12"), err) - } - - // Relabel it - allLabels, err := cmsKeyDB.Keystore.GetCertificateLabels() - if err != nil { - return firstLabel, p12s, fmt.Errorf("Could not list keys in CMS Keystore: %v", err) - } - relabelled := false - for _, cl := range allLabels { - found := false - for _, kl := range cmsKeyDB.KeyLabels { - if strings.Trim(cl, "\"") == kl { - found = true - break - } - } - if !found { - // This is the one to rename - err = cmsKeyDB.Keystore.RenameCertificate(strings.Trim(cl, "\""), keyLabel) - if err != nil { - return firstLabel, p12s, err - } - relabelled = true - cmsKeyDB.KeyLabels = append(cmsKeyDB.KeyLabels, keyLabel) - break - } - } - - if !relabelled { - return firstLabel, p12s, fmt.Errorf("Unable to find the added key for %s in CMS keystore", keyLabel) - } - - // First key found so mark it as the one to use with the queue manager. - if firstLabel == "" { - firstLabel = keyLabel - } - } - } - return firstLabel, p12s, nil -} - -// processTrustCertificates walks through the trustDir directory and adds any certificates it finds -// to the PKCS#12 Truststore and the CMS KeyStore as long as has not already been added. -func processTrustCertificates(trustDir string, cmsKeyDB, p12TrustDB *KeyStoreData) error { - certList, err := ioutil.ReadDir(trustDir) - if err == nil && len(certList) > 0 { - // Found some keys, verify the contents - for _, cert := range certList { - certs, _ := ioutil.ReadDir(filepath.Join(trustDir, cert.Name())) - for _, a := range certs { - if strings.HasSuffix(a.Name(), ".crt") { - // #nosec G304 - filename variable is derived from contents of 'trustDir' which is a defined constant - remainder, err := ioutil.ReadFile(filepath.Join(trustDir, cert.Name(), a.Name())) - if err != nil { - return fmt.Errorf("Could not read file %s: %v", filepath.Join(trustDir, cert.Name(), a.Name()), err) - } - - for string(remainder) != "" { - var block *pem.Block - block, remainder = pem.Decode(remainder) - if block == nil { - break - } - - // Add to the CMS keystore - err = addCertToKeyData(block, cmsKeyDB, true) - - // Add to the p12 truststore - err = addCertToKeyData(block, p12TrustDB, true) - } - } - } - } - } - // We've potentially created two lists of certificates to import. Add them both to relevant Truststores - if len(p12TrustDB.TrustedCerts) > 0 { - // Do P12 TrustStore first - temporaryPemFile := filepath.Join("/tmp", "trust.pem") - _, err := os.Stat(temporaryPemFile) - if err == nil { - err = os.Remove(temporaryPemFile) - if err != nil { - return fmt.Errorf("Could not remove file %v: %v", temporaryPemFile, err) - } - } - - err = writeCertsToFile(temporaryPemFile, p12TrustDB.TrustedCerts) - if err != nil { - return err - } - - err = p12TrustDB.Keystore.AddNoLabel(temporaryPemFile) - if err != nil { - return fmt.Errorf("Could not add certificates to PKCS#12 Truststore: %v", err) - } - - // We need to relabel everything because liberty doesn't play nicely with autolabelled certs - allCerts, err := p12TrustDB.Keystore.ListAllCertificates() - if err != nil || len(allCerts) <= 0 { - return fmt.Errorf("Could not get any certificates from PKCS#12 Truststore: %v", err) - } - - for i, cert := range allCerts { - cert = strings.Trim(cert, "\"") - cert = strings.TrimSpace(cert) - newLabel := fmt.Sprintf("Trust%d", i) - - err = p12TrustDB.Keystore.RenameCertificate(cert, newLabel) - if err != nil || len(allCerts) <= 0 { - return fmt.Errorf("Could not rename certificate %s to %s in PKCS#12 Truststore: %v", cert, newLabel, err) - } + known := false + for _, fingerprint := range keyData.KnownFingerPrints { + if fingerprint == sha512str { + known = true + break } } - if len(cmsKeyDB.TrustedCerts) > 0 { - // Now the CMS Keystore - temporaryPemFile := filepath.Join("/tmp", "cmsTrust.pem") - _, err := os.Stat(temporaryPemFile) - if err == nil { - err = os.Remove(temporaryPemFile) - if err != nil { - return fmt.Errorf("Could not remove file %v: %v", temporaryPemFile, err) - } - } - - err = writeCertsToFile(temporaryPemFile, cmsKeyDB.TrustedCerts) - if err != nil { - return err - } - - err = cmsKeyDB.Keystore.AddNoLabel(temporaryPemFile) - if err != nil { - return fmt.Errorf("Could not add certificates to CMS keystore: %v", err) + if !known { + if addToKeystore { + keyData.TrustedCerts = append(keyData.TrustedCerts, block) } + keyData.KnownFingerPrints = append(keyData.KnownFingerPrints, sha512str) } + return nil } -// Writes a given list of certificates to a file. -func writeCertsToFile(file string, certs []*pem.Block) error { +// getCertificateFingerprint returns a fingerprint for a certificate +func getCertificateFingerprint(block *pem.Block) (string, error) { + certificate, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return "", fmt.Errorf("Failed to parse x509 certificate: %v", err) + } + sha512Sum := sha512.Sum512(certificate.Raw) + sha512str := string(sha512Sum[:]) + + return sha512str, nil +} + +// writeCertificatesToFile writes a list of certificates to a file +func writeCertificatesToFile(file string, certificates []*pem.Block) error { f, err := os.Create(file) if err != nil { - return fmt.Errorf("writeCertsToFile: Could not create file %s: %v", file, err) + return fmt.Errorf("Failed to create file %s: %v", file, err) } defer f.Close() w := bufio.NewWriter(f) - for i, c := range certs { + for i, c := range certificates { err := pem.Encode(w, c) if err != nil { - return fmt.Errorf("writeCertsToFile: Could not encode certificate number %d: %v", i, err) + return fmt.Errorf("Failed to encode certificate number %d: %v", i, err) } err = w.Flush() if err != nil { - return fmt.Errorf("writeCertsToFile: Failed to write to file %s: %v", file, err) + return fmt.Errorf("Failed to write certificate to file %s: %v", file, err) } } return nil } - -// ConfigureTLSKeystores sets up the TLS Trust and Keystores for use -func ConfigureTLSKeystores(keyDir, certDir, outputDir string) (string, KeyStoreData, KeyStoreData, P12KeyFiles, error) { - var returnLabel, label string - var cmsKeyDB, p12TrustDB KeyStoreData - var keyFiles P12KeyFiles - var err error - - cmsKeyDB, p12TrustDB, err = generateAllStores(outputDir) - if err != nil { - return returnLabel, cmsKeyDB, p12TrustDB, keyFiles, err - } - - returnLabel, err = expandOldTLSVariable(keyDir, outputDir, &cmsKeyDB, &p12TrustDB) - if err != nil { - return returnLabel, cmsKeyDB, p12TrustDB, keyFiles, err - } - - label, keyFiles, err = processKeys(keyDir, outputDir, &cmsKeyDB, &p12TrustDB) - if err != nil { - return returnLabel, cmsKeyDB, p12TrustDB, keyFiles, err - } - if returnLabel == "" { - returnLabel = label - } - - err = processTrustCertificates(certDir, &cmsKeyDB, &p12TrustDB) - if err != nil { - return returnLabel, cmsKeyDB, p12TrustDB, keyFiles, err - } - - return returnLabel, cmsKeyDB, p12TrustDB, keyFiles, err -} - -// This function supports the old mechanism of importing certificates supplied by the MQ_TLS_KEYSTORE envvar -func expandOldTLSVariable(keyDir, outputDir string, cmsKeyDB, p12TrustDB *KeyStoreData) (string, error) { - // TODO: Change this or find a way to set it - outputDirName := "acopiedcertificate" - - // Check whether the old variable is set. If not exit quietly - keyfile := os.Getenv("MQ_TLS_KEYSTORE") - if keyfile == "" { - return "", nil - } - - // There is a file to read and process - keyfilepw := os.Getenv("MQ_TLS_PASSPHRASE") - - if !strings.HasSuffix(keyfile, ".p12") { - return "", fmt.Errorf("MQ_TLS_KEYSTORE (%s) does not point to a PKCS#12 file ending with the suffix .p12", keyfile) - } - - _, err := os.Stat(keyfile) - if err != nil { - return "", fmt.Errorf("File %s referenced by MQ_TLS_KEYSTORE does not exist", keyfile) - } - - err = filecheck.CheckFileSource(keyfile) - if err != nil { - return "", fmt.Errorf("File %s referenced by MQ_TLS_KEYSTORE is invalid: %v", keyfile, err) - } - - // #nosec G304 - filename variable 'keyfile' is checked above to ensure it is valid - readkey, err := ioutil.ReadFile(keyfile) - if err != nil { - return "", fmt.Errorf("Failed to read %s: %v", keyfile, err) - } - - // File has been checked and read, decode it. - pk, cert, cas, err := pkcs.DecodeChain(readkey, keyfilepw) - if err != nil { - return "", fmt.Errorf("Failed to decode %s: %v", keyfile, err) - } - - // Find a directory name that doesn't exist - for { - _, err := os.Stat(filepath.Join(keyDir, outputDirName)) - if err == nil { - outputDirName = outputDirName + "0" - } else { - break - } - } - - //Bceause they supplied this certificate using the old method we should use this for qm & webconsole - overrideLabel := outputDirName - - // Write out the certificate for the private key - if cert != nil { - block := pem.Block{ - Type: "CERTIFICATE", - Headers: nil, - Bytes: cert.Raw, - } - err = addCertToKeyData(&block, cmsKeyDB, false) - if err != nil { - return "", fmt.Errorf("expandOldTLSVariable: Failed to add cert to CMS Keystore duplicate list: %v", err) - } - err = addCertToKeyData(&block, p12TrustDB, true) - if err != nil { - return "", fmt.Errorf("expandOldTLSVariable: Failed to add cert to P12 Truststore duplicate list: %v", err) - } - } - - // now write out all the ca certificates - if cas != nil || len(cas) > 0 { - for i, c := range cas { - block := pem.Block{ - Type: "CERTIFICATE", - Headers: nil, - Bytes: c.Raw, - } - - // Add to the dup list for the CMS keystore - err = addCertToKeyData(&block, cmsKeyDB, false) - if err != nil { - return "", fmt.Errorf("expandOldTLSVariable: Failed to add CA cert %d to CMS Keystore duplicate list: %v", i, err) - } - - // Add to the p12 truststore - err = addCertToKeyData(&block, p12TrustDB, true) - if err != nil { - return "", fmt.Errorf("expandOldTLSVariable: Failed to add CA cert %d to P12 Truststore duplicate list: %v", i, err) - } - } - } - - // Now we've handled the certificates copy the keystore into place - destination := filepath.Join(outputDir, outputDirName+".p12") - - // Create p12 keystore - file, err := pkcs.Encode(rand.Reader, pk, cert, cas, p12TrustDB.Password) - if err != nil { - return "", fmt.Errorf("Failed to re-encode p12 keystore: %v", err) - } - - err = ioutil.WriteFile(destination, file, 0644) - if err != nil { - return "", fmt.Errorf("Failed to write p12 keystore: %v", err) - } - - // Add to the CMS keystore - err = cmsKeyDB.Keystore.Import(destination, p12TrustDB.Password) - if err != nil { - return "", fmt.Errorf("Failed to import p12 keystore %s: %v", destination, err) - } - - if pk != nil { - // Relabel the key - allLabels, err := cmsKeyDB.Keystore.GetCertificateLabels() - if err != nil { - fmt.Printf("cms GetCertificateLabels: %v\n", err) - return "", err - } - relabelled := false - for _, cl := range allLabels { - found := false - for _, kl := range cmsKeyDB.KeyLabels { - if strings.Trim(cl, "\"") == kl { - found = true - break - } - } - if !found { - // This is the one to rename - err = cmsKeyDB.Keystore.RenameCertificate(strings.Trim(cl, "\""), outputDirName) - if err != nil { - return "", err - } - relabelled = true - cmsKeyDB.KeyLabels = append(cmsKeyDB.KeyLabels, outputDirName) - break - } - } - - if !relabelled { - return "", fmt.Errorf("Unable to find the added key in CMS keystore") - } - } - - return overrideLabel, nil -} diff --git a/internal/tls/tls_web.go b/internal/tls/tls_web.go new file mode 100644 index 0000000..e281fec --- /dev/null +++ b/internal/tls/tls_web.go @@ -0,0 +1,102 @@ +/* +© Copyright IBM Corporation 2019 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package tls + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/ibm-messaging/mq-container/internal/command" + "github.com/ibm-messaging/mq-container/internal/keystore" +) + +// webServerKeystoreName is the name of the web server Keystore +const webServerKeystoreName = "default.p12" + +// ConfigureWebTLS configures TLS for the web server +func ConfigureWebTLS(keyLabel string) error { + + // Return immediately if we have no certificate to use as identity + if keyLabel == "" && os.Getenv("MQ_GENERATE_CERTIFICATE_HOSTNAME") == "" { + return nil + } + + webConfigDir := "/etc/mqm/web/installations/Installation1/servers/mqweb" + tls := "tls.xml" + + tlsConfig := filepath.Join(webConfigDir, tls) + newTLSConfig := filepath.Join(webConfigDir, tls+".tpl") + + err := os.Remove(tlsConfig) + if err != nil { + return fmt.Errorf("Failed to delete file %s: %v", tlsConfig, err) + } + + // Symlink here to prevent issues on restart + err = os.Symlink(newTLSConfig, tlsConfig) + if err != nil { + return fmt.Errorf("Failed to create symlink %s->%s: %v", newTLSConfig, tlsConfig, err) + } + mqmUID, mqmGID, err := command.LookupMQM() + if err != nil { + return fmt.Errorf("Failed to find mqm user or group: %v", err) + } + err = os.Chown(tlsConfig, mqmUID, mqmGID) + if err != nil { + return fmt.Errorf("Failed to change ownership of %s to mqm: %v", tlsConfig, err) + } + + return nil +} + +// ConfigureWebKeyStore configures the Web Keystore +func ConfigureWebKeystore(p12Truststore KeyStoreData) (string, error) { + webKeystore := filepath.Join(keystoreDir, webServerKeystoreName) + + // Check if a new self-signed certificate should be generated + genHostName := os.Getenv("MQ_GENERATE_CERTIFICATE_HOSTNAME") + if genHostName != "" { + + // Create the Web Keystore + newWebKeystore := keystore.NewPKCS12KeyStore(webKeystore, p12Truststore.Password) + err := newWebKeystore.Create() + if err != nil { + return "", fmt.Errorf("Failed to create Web Keystore %s: %v", webKeystore, err) + } + + // Generate a new self-signed certificate in the Web Keystore + err = newWebKeystore.CreateSelfSignedCertificate("default", fmt.Sprintf("CN=%s", genHostName), genHostName) + if err != nil { + return "", fmt.Errorf("Failed to generate certificate in Web Keystore %s with DN of 'CN=%s': %v", webKeystore, genHostName, err) + } + + } else { + // Check Web Keystore already exists + _, err := os.Stat(webKeystore) + if err != nil { + return "", fmt.Errorf("Failed to find existing Web Keystore %s: %v", webKeystore, err) + } + } + + // Check Web Truststore already exists + _, err := os.Stat(p12Truststore.Keystore.Filename) + if err != nil { + return "", fmt.Errorf("Failed to find existing Web Truststore %s: %v", p12Truststore.Keystore.Filename, err) + } + + return webServerKeystoreName, nil +} diff --git a/test/docker/devconfig_test.go b/test/docker/devconfig_test.go index f39d8ae..94c8fdd 100644 --- a/test/docker/devconfig_test.go +++ b/test/docker/devconfig_test.go @@ -81,8 +81,6 @@ func TestDevSecure(t *testing.T) { "LICENSE=accept", "MQ_QMGR_NAME=" + qm, "MQ_APP_PASSWORD=" + appPassword, - "MQ_TLS_KEYSTORE=/var/tls/server.p12", - "MQ_TLS_PASSPHRASE=" + tlsPassPhrase, "DEBUG=1", }, Image: imageName(), @@ -90,7 +88,7 @@ func TestDevSecure(t *testing.T) { hostConfig := container.HostConfig{ Binds: []string{ coverageBind(t), - tlsDir(t, false) + ":/var/tls", + tlsDir(t, false) + ":/etc/mqm/pki/keys/default", }, // Assign a random port for the web server on the host // TODO: Don't do this for all tests diff --git a/test/docker/devconfig_test_util.go b/test/docker/devconfig_test_util.go index f202d38..6b13579 100644 --- a/test/docker/devconfig_test_util.go +++ b/test/docker/devconfig_test_util.go @@ -82,7 +82,7 @@ func tlsDir(t *testing.T, unixPath bool) string { // runJMSTests runs a container with a JMS client, which connects to the queue manager container with the specified ID func runJMSTests(t *testing.T, cli *client.Client, ID string, tls bool, user, password string) { containerConfig := container.Config{ - // -e MQ_PORT_1414_TCP_ADDR=9.145.14.173 -e MQ_USERNAME=app -e MQ_PASSWORD=passw0rd -e MQ_CHANNEL=DEV.APP.SVRCONN -e MQ_TLS_KEYSTORE=/tls/test.p12 -e MQ_TLS_PASSPHRASE=passw0rd -v /Users/arthurbarr/go/src/github.com/ibm-messaging/mq-container/test/tls:/tls msgtest + // -e MQ_PORT_1414_TCP_ADDR=9.145.14.173 -e MQ_USERNAME=app -e MQ_PASSWORD=passw0rd -e MQ_CHANNEL=DEV.APP.SVRCONN -e MQ_TLS_TRUSTSTORE=/tls/test.p12 -e MQ_TLS_PASSPHRASE=passw0rd -v /Users/arthurbarr/go/src/github.com/ibm-messaging/mq-container/test/tls:/tls msgtest Env: []string{ "MQ_PORT_1414_TCP_ADDR=" + getIPAddress(t, cli, ID), "MQ_USERNAME=" + user,