first commit
This commit is contained in:
745
internal/tls/tls.go
Normal file
745
internal/tls/tls.go
Normal file
@@ -0,0 +1,745 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2019, 2024
|
||||
|
||||
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 (
|
||||
"bufio"
|
||||
"fmt"
|
||||
pwr "math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
|
||||
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/internal/pathutils"
|
||||
"github.com/ibm-messaging/mq-container/pkg/logger"
|
||||
)
|
||||
|
||||
// cmsKeystoreName is the name of the CMS Keystore
|
||||
const cmsKeystoreName = "key.kdb"
|
||||
|
||||
// p12TruststoreName is the name of the PKCS#12 Truststore
|
||||
const p12TruststoreName = "trust.p12"
|
||||
|
||||
// keystoreDirDefault is the location for the default CMS Keystore & PKCS#12 Truststore
|
||||
const keystoreDirDefault = "/run/runmqserver/tls/"
|
||||
|
||||
// keystoreDirHA is the location for the HA CMS Keystore
|
||||
const keystoreDirHA = "/run/runmqserver/ha/tls/"
|
||||
|
||||
// keyDirDefault is the location of the default keys to import
|
||||
const keyDirDefault = "/etc/mqm/pki/keys"
|
||||
|
||||
// keyDirHA is the location of the HA keys to import
|
||||
const keyDirHA = "/etc/mqm/ha/pki/keys"
|
||||
|
||||
// keyDirGroupHA is the location of the GroupHA keys to import
|
||||
const keyDirGroupHA = "/etc/mqm/groupha/pki/keys"
|
||||
|
||||
// trustDirDefault is the location of the trust certificates to import
|
||||
const trustDirDefault = "/etc/mqm/pki/trust"
|
||||
|
||||
// trustDirGroupDefault is the location of the GroupHA trust certificates to import
|
||||
const trustDirGroupHA = "/etc/mqm/groupha/pki/trust"
|
||||
|
||||
type KeyStoreData struct {
|
||||
Keystore *keystore.KeyStore
|
||||
Password string
|
||||
TrustedCerts []*pem.Block
|
||||
KnownFingerPrints []string
|
||||
KeyLabels []string
|
||||
}
|
||||
|
||||
type P12KeyFiles struct {
|
||||
Keystores []string
|
||||
Password string
|
||||
}
|
||||
|
||||
type TLSStore struct {
|
||||
Keystore KeyStoreData
|
||||
Truststore KeyStoreData
|
||||
}
|
||||
|
||||
func configureTLSKeystores(keystoreDir string, keyDirs, trustDirs []string, p12TruststoreRequired bool, nativeTLSHA bool) ([]string, KeyStoreData, KeyStoreData, error) {
|
||||
var keyLabel string
|
||||
|
||||
cmsKeystoreRequired := false
|
||||
allDirs := append([]string{}, keyDirs...)
|
||||
allDirs = append(allDirs, trustDirs...)
|
||||
for _, dir := range allDirs {
|
||||
if haveKeysAndCerts(dir) {
|
||||
cmsKeystoreRequired = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// Create the CMS Keystore & PKCS#12 Truststore (if required)
|
||||
tlsStore, err := generateAllKeystores(keystoreDir, cmsKeystoreRequired, p12TruststoreRequired, nativeTLSHA)
|
||||
if err != nil {
|
||||
return nil, tlsStore.Keystore, tlsStore.Truststore, err
|
||||
}
|
||||
|
||||
keyLabels := make([]string, len(keyDirs))
|
||||
if tlsStore.Keystore.Keystore != nil {
|
||||
for idx, keyDir := range keyDirs {
|
||||
// Process all keys - add them to the CMS KeyStore
|
||||
keyLabel, err = processKeys(&tlsStore, keystoreDir, keyDir)
|
||||
if err != nil {
|
||||
return nil, tlsStore.Keystore, tlsStore.Truststore, err
|
||||
}
|
||||
keyLabels[idx] = keyLabel
|
||||
}
|
||||
}
|
||||
|
||||
for _, trustDir := range trustDirs {
|
||||
// Process all trust certificates - add them to the CMS KeyStore & PKCS#12 Truststore (if required)
|
||||
err = processTrustCertificates(&tlsStore, trustDir)
|
||||
if err != nil {
|
||||
return nil, tlsStore.Keystore, tlsStore.Truststore, err
|
||||
}
|
||||
}
|
||||
|
||||
return keyLabels, tlsStore.Keystore, tlsStore.Truststore, err
|
||||
}
|
||||
|
||||
// ConfigureDefaultTLSKeystores configures the CMS Keystore & PKCS#12 Truststore
|
||||
func ConfigureDefaultTLSKeystores() (string, KeyStoreData, KeyStoreData, error) {
|
||||
certLabels, keyStore, trustStore, err := configureTLSKeystores(keystoreDirDefault, []string{keyDirDefault}, []string{trustDirDefault}, true, false)
|
||||
if err != nil {
|
||||
return "", keyStore, trustStore, err
|
||||
}
|
||||
certLabel := ""
|
||||
if len(certLabels) > 0 {
|
||||
certLabel = certLabels[0]
|
||||
}
|
||||
return certLabel, keyStore, trustStore, err
|
||||
}
|
||||
|
||||
// ConfigureHATLSKeystore configures the CMS Keystore & PKCS#12 Truststore
|
||||
func ConfigureHATLSKeystore() (string, string, KeyStoreData, KeyStoreData, error) {
|
||||
// *.crt files mounted to the HA TLS dir keyDirHA will be processed as trusted in the CMS keystore
|
||||
keyDirs := []string{keyDirHA, keyDirGroupHA}
|
||||
haCertLabels, haKeystore, haTruststore, err := configureTLSKeystores(keystoreDirHA, keyDirs, keyDirs, false, true)
|
||||
if err != nil {
|
||||
return "", "", haKeystore, haTruststore, err
|
||||
}
|
||||
if len(haCertLabels) != len(keyDirs) {
|
||||
return "", "", haKeystore, haTruststore, fmt.Errorf("incorrect number of certificate labels returned (expected %d, got %d)", len(keyDirs), len(haCertLabels))
|
||||
}
|
||||
|
||||
return haCertLabels[0], haCertLabels[1], haKeystore, haTruststore, err
|
||||
}
|
||||
|
||||
// ConfigureTLS configures TLS for the queue manager
|
||||
func ConfigureTLS(keyLabel string, cmsKeystore KeyStoreData, devMode bool, log *logger.Logger) error {
|
||||
|
||||
const mqscLink string = "/run/15-tls.mqsc"
|
||||
const mqscTemplate string = "/etc/mqm/15-tls.mqsc.tpl"
|
||||
sslKeyRing := ""
|
||||
var fipsEnabled = "NO"
|
||||
|
||||
// Don't set SSLKEYR if no keys or crts are not supplied
|
||||
// Key label will be blank if no private keys were added during processing keys and certs.
|
||||
if cmsKeystore.Keystore != nil && len(keyLabel) > 0 {
|
||||
certList, _ := cmsKeystore.Keystore.ListAllCertificates()
|
||||
if len(certList) > 0 {
|
||||
sslKeyRing = strings.TrimSuffix(cmsKeystore.Keystore.Filename, ".kdb")
|
||||
}
|
||||
|
||||
if cmsKeystore.Keystore.IsFIPSEnabled() {
|
||||
fipsEnabled = "YES"
|
||||
}
|
||||
}
|
||||
err := mqtemplate.ProcessTemplateFile(mqscTemplate, mqscLink, map[string]string{
|
||||
"SSLKeyR": sslKeyRing,
|
||||
"CertificateLabel": keyLabel,
|
||||
"SSLFips": fipsEnabled,
|
||||
}, log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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 mqscLink string = "/run/20-dev-tls.mqsc"
|
||||
const mqscTemplate string = "/etc/mqm/20-dev-tls.mqsc.tpl"
|
||||
|
||||
if os.Getenv("MQ_DEV") == "true" {
|
||||
err := mqtemplate.ProcessTemplateFile(mqscTemplate, mqscLink, map[string]string{}, log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateAllKeystores creates the CMS Keystore & PKCS#12 Truststore (if required)
|
||||
func generateAllKeystores(keystoreDir string, createCMSKeystore bool, p12TruststoreRequired bool, nativeTLSHA bool) (TLSStore, 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 TLSStore{cmsKeystore, p12Truststore}, fmt.Errorf("Failed to create Keystore directory: %v", err)
|
||||
}
|
||||
|
||||
// Create the CMS Keystore if we have been provided keys and certificates
|
||||
if createCMSKeystore {
|
||||
cmsKeystore.Keystore = keystore.NewCMSKeyStore(pathutils.CleanPath(keystoreDir, cmsKeystoreName), cmsKeystore.Password)
|
||||
err = cmsKeystore.Keystore.Create()
|
||||
if err != nil {
|
||||
return TLSStore{cmsKeystore, p12Truststore}, fmt.Errorf("Failed to create CMS Keystore: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the PKCS#12 Truststore (if required)
|
||||
if p12TruststoreRequired {
|
||||
p12Truststore.Keystore = keystore.NewPKCS12KeyStore(pathutils.CleanPath(keystoreDir, p12TruststoreName), p12Truststore.Password)
|
||||
err = p12Truststore.Keystore.Create()
|
||||
if err != nil {
|
||||
return TLSStore{cmsKeystore, p12Truststore}, fmt.Errorf("Failed to create PKCS#12 Truststore: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return TLSStore{cmsKeystore, p12Truststore}, nil
|
||||
}
|
||||
|
||||
// processKeys processes all keys - adding them to the CMS KeyStore
|
||||
func processKeys(tlsStore *TLSStore, keystoreDir string, keyDir string) (string, error) {
|
||||
|
||||
// Key label - will be set to the label of the first set of keys
|
||||
keyLabel := ""
|
||||
|
||||
// Process all keys
|
||||
keyList, err := os.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, _ := os.ReadDir(pathutils.CleanPath(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(keyDir, 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(keyDir, keySet.Name(), keyPrefix, keys, &tlsStore.Keystore, &tlsStore.Truststore)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Return an error if corresponding public certificate was not found. Both private key and
|
||||
// it's corresponding public certificate are required.
|
||||
if publicCertificate == nil {
|
||||
return "", fmt.Errorf("Failed to find public certificate in directory %s", keyDir)
|
||||
}
|
||||
|
||||
// Validate certificates for duplicate Subject DNs
|
||||
if len(caCertificate) > 0 {
|
||||
errCertValid := validateCertificates(publicCertificate, caCertificate)
|
||||
if errCertValid != nil {
|
||||
return "", errCertValid
|
||||
}
|
||||
}
|
||||
// Create a new PKCS#12 Keystore - containing private key, public certificate & optional CA certificate
|
||||
file, err := pkcs.Modern.Encode(privateKey, publicCertificate, caCertificate, tlsStore.Keystore.Password)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to encode PKCS#12 Keystore %s: %v", keySet.Name()+".p12", err)
|
||||
}
|
||||
keystorePath := pathutils.CleanPath(keystoreDir, keySet.Name()+".p12")
|
||||
// #nosec G306 - this gives permissions to owner/s group only.
|
||||
err = os.WriteFile(keystorePath, file, 0644)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to write PKCS#12 Keystore %s: %v", keystorePath, err)
|
||||
}
|
||||
|
||||
// Import the new PKCS#12 Keystore into the CMS Keystore
|
||||
err = tlsStore.Keystore.Keystore.Import(keystorePath, tlsStore.Keystore.Password)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to import keys from %s into CMS Keystore: %v", keystorePath, err)
|
||||
}
|
||||
|
||||
// Relabel the certificate in the CMS Keystore
|
||||
err = relabelCertificate(keySet.Name(), &tlsStore.Keystore)
|
||||
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 (if required)
|
||||
func processTrustCertificates(tlsStore *TLSStore, trustDir string) error {
|
||||
|
||||
// Process all trust certiifcates
|
||||
trustList, err := os.ReadDir(trustDir)
|
||||
if err == nil && len(trustList) > 0 {
|
||||
|
||||
// Process each set of keys
|
||||
for _, trustSet := range trustList {
|
||||
keys, _ := os.ReadDir(pathutils.CleanPath(trustDir, trustSet.Name()))
|
||||
|
||||
for _, key := range keys {
|
||||
if strings.HasSuffix(key.Name(), ".crt") {
|
||||
trustSetPath := pathutils.CleanPath(trustDir, trustSet.Name(), key.Name())
|
||||
// #nosec G304 - filename variable is derived from contents of 'trustDir' which is a defined constant
|
||||
file, err := os.ReadFile(trustSetPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to read file %s: %v", trustSetPath, 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, &tlsStore.Keystore, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to add to know certificates for CMS Keystore")
|
||||
}
|
||||
|
||||
if tlsStore.Truststore.Keystore != nil {
|
||||
// Add to known certificates for the PKCS#12 Truststore
|
||||
err = addToKnownCertificates(block, &tlsStore.Truststore, 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 required)
|
||||
if tlsStore.Truststore.Keystore != nil && len(tlsStore.Truststore.TrustedCerts) > 0 {
|
||||
err = addCertificatesToTruststore(&tlsStore.Truststore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Add all trust certificates to CMS Keystore
|
||||
if len(tlsStore.Keystore.TrustedCerts) > 0 {
|
||||
err = addCertificatesToCMSKeystore(&tlsStore.Keystore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// processPrivateKey processes the private key (*.key) from a set of keys
|
||||
func processPrivateKey(keyDir string, keySetName string, keys []os.DirEntry) (interface{}, string, error) {
|
||||
|
||||
var privateKey interface{}
|
||||
keyPrefix := ""
|
||||
|
||||
for _, key := range keys {
|
||||
|
||||
privateKeyPath := pathutils.CleanPath(keyDir, keySetName, key.Name())
|
||||
if strings.HasSuffix(key.Name(), ".key") {
|
||||
// #nosec G304 - filename variable is derived from contents of 'keyDir' which is a defined constant
|
||||
file, err := os.ReadFile(privateKeyPath)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("Failed to read private key %s: %v", privateKeyPath, err)
|
||||
}
|
||||
block, _ := pem.Decode(file)
|
||||
if block == nil {
|
||||
return nil, "", fmt.Errorf("Failed to decode private key %s: pem.Decode returned nil", privateKeyPath)
|
||||
}
|
||||
|
||||
// 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", privateKeyPath, 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(keyDir string, keySetName, keyPrefix string, keys []os.DirEntry, cmsKeystore, p12Truststore *KeyStoreData) (*x509.Certificate, []*x509.Certificate, error) {
|
||||
|
||||
var publicCertificate *x509.Certificate
|
||||
var caCertificate []*x509.Certificate
|
||||
|
||||
for _, key := range keys {
|
||||
|
||||
keystorePath := pathutils.CleanPath(keyDir, keySetName, key.Name())
|
||||
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 := os.ReadFile(keystorePath)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Failed to read public certificate %s: %v", keystorePath, err)
|
||||
}
|
||||
block, file := pem.Decode(file)
|
||||
if block == nil {
|
||||
return nil, nil, fmt.Errorf("Failed to decode public certificate %s: pem.Decode returned nil", keystorePath)
|
||||
}
|
||||
publicCertificate, err = x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Failed to parse public certificate %s: %v", keystorePath, 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 known certificates for CMS Keystore")
|
||||
}
|
||||
|
||||
// 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 known certificates for CMS Keystore")
|
||||
}
|
||||
|
||||
// Pick up any other intermediate certificates
|
||||
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 known certificates for CMS Keystore")
|
||||
}
|
||||
|
||||
if p12Truststore.Keystore != nil {
|
||||
// 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 known 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", keystorePath, err)
|
||||
}
|
||||
caCertificate = append(caCertificate, certificate)
|
||||
}
|
||||
|
||||
} else if strings.HasSuffix(key.Name(), ".crt") {
|
||||
// #nosec G304 - filename variable is derived from contents of 'keyDir' which is a defined constant
|
||||
file, err := os.ReadFile(keystorePath)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Failed to read CA certificate %s: %v", keystorePath, 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 known certificates for CMS Keystore")
|
||||
}
|
||||
|
||||
if p12Truststore.Keystore != nil {
|
||||
// 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 known 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", keystorePath, 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 !relabelled {
|
||||
return fmt.Errorf("Failed to relabel certificate for %s in CMS keystore", newLabel)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addCertificatesToTruststore adds trust certificates to the PKCS#12 Truststore
|
||||
func addCertificatesToTruststore(p12Truststore *KeyStoreData) error {
|
||||
|
||||
temporaryPemFile := pathutils.CleanPath("/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 := pathutils.CleanPath("/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"
|
||||
validcharArray := []byte(validChars)
|
||||
password := ""
|
||||
for i := 0; i < 12; i++ {
|
||||
// #nosec G404 - this is only for internal keystore and using math/rand pose no harm.
|
||||
password = password + string(validcharArray[pwr.Intn(len(validcharArray))])
|
||||
}
|
||||
|
||||
return password
|
||||
}
|
||||
|
||||
// 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 err
|
||||
}
|
||||
known := false
|
||||
for _, fingerprint := range keyData.KnownFingerPrints {
|
||||
if fingerprint == sha512str {
|
||||
known = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !known {
|
||||
if addToKeystore {
|
||||
keyData.TrustedCerts = append(keyData.TrustedCerts, block)
|
||||
}
|
||||
keyData.KnownFingerPrints = append(keyData.KnownFingerPrints, sha512str)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
// #nosec G304 - this is a temporary pem file to write certs.
|
||||
f, err := os.Create(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to create file %s: %v", file, err)
|
||||
}
|
||||
// #nosec G307 - local to this function, pose no harm.
|
||||
defer f.Close()
|
||||
|
||||
w := bufio.NewWriter(f)
|
||||
|
||||
for i, c := range certificates {
|
||||
err := pem.Encode(w, c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to encode certificate number %d: %v", i, err)
|
||||
}
|
||||
err = w.Flush()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to write certificate to file %s: %v", file, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Search the specified directory for .key and .crt files.
|
||||
// Return true if at least one .key or .crt file is found else false
|
||||
func haveKeysAndCerts(keyDir string) bool {
|
||||
fileList, err := os.ReadDir(keyDir)
|
||||
if err == nil && len(fileList) > 0 {
|
||||
for _, fileInfo := range fileList {
|
||||
// Keys and certs will be supplied in an user defined subdirectory.
|
||||
// Do a listing of the subdirectory and then search for .key and .cert files
|
||||
keys, _ := os.ReadDir(pathutils.CleanPath(keyDir, fileInfo.Name()))
|
||||
for _, key := range keys {
|
||||
if strings.HasSuffix(key.Name(), ".key") || strings.HasSuffix(key.Name(), ".crt") {
|
||||
// We found at least one key/crt file.
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Iterate through the certificates to ensure there are no two certificates with same Subject DN.
|
||||
// GSKit does not allow two certificates with same Subject DN/Friendly Names
|
||||
func validateCertificates(personalCert *x509.Certificate, caCertificates []*x509.Certificate) error {
|
||||
// Check if we have been asked to override certificate validation by setting
|
||||
// MQ_ENABLE_CERT_VALIDATION to false
|
||||
enableValidation, enableValidationSet := os.LookupEnv("MQ_ENABLE_CERT_VALIDATION")
|
||||
if !enableValidationSet || (enableValidationSet && !strings.EqualFold(strings.Trim(enableValidation, ""), "false")) {
|
||||
for _, caCert := range caCertificates {
|
||||
if strings.EqualFold(personalCert.Subject.String(), caCert.Subject.String()) {
|
||||
return fmt.Errorf("Error: The Subject DN of the Issuer Certificate and the Queue Manager are same")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
99
internal/tls/tls_web.go
Normal file
99
internal/tls/tls_web.go
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2019, 2023
|
||||
|
||||
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"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/internal/keystore"
|
||||
"github.com/ibm-messaging/mq-container/internal/mqtemplate"
|
||||
"github.com/ibm-messaging/mq-container/internal/pathutils"
|
||||
"github.com/ibm-messaging/mq-container/internal/securityutility"
|
||||
"github.com/ibm-messaging/mq-container/pkg/logger"
|
||||
)
|
||||
|
||||
// webKeystoreDefault is the name of the default web server Keystore
|
||||
const webKeystoreDefault = "default.p12"
|
||||
|
||||
// ConfigureWebTLS configures TLS for the web server
|
||||
func ConfigureWebTLS(keyLabel string, log *logger.Logger, password string) error {
|
||||
|
||||
// Return immediately if we have no certificate to use as identity
|
||||
if keyLabel == "" && os.Getenv("MQ_GENERATE_CERTIFICATE_HOSTNAME") == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
tlsConfigLink := "/run/tls.xml"
|
||||
tlsConfigTemplate := "/etc/mqm/web/installations/Installation1/servers/mqweb/tls.xml.tpl"
|
||||
encryptedPassword, err := securityutility.EncodeSecrets(password)
|
||||
if err != nil {
|
||||
log.Printf("Password encoding for Web Keystore failed with error %v", err)
|
||||
// We couldn't encode the passwords so using an empty string as password
|
||||
encryptedPassword = ""
|
||||
}
|
||||
// Password successfully encoded using securityUtility use the encoded password the template
|
||||
templateErr := mqtemplate.ProcessTemplateFile(tlsConfigTemplate, tlsConfigLink, map[string]string{"password": encryptedPassword}, log)
|
||||
if templateErr != nil {
|
||||
return templateErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigureWebKeyStore configures the Web Keystore
|
||||
func ConfigureWebKeystore(p12Truststore KeyStoreData, keyLabel string) (string, error) {
|
||||
|
||||
webKeystore := webKeystoreDefault
|
||||
if keyLabel != "" {
|
||||
webKeystore = keyLabel + ".p12"
|
||||
}
|
||||
webKeystoreFile := pathutils.CleanPath(keystoreDirDefault, webKeystore)
|
||||
|
||||
// Check if a new self-signed certificate should be generated
|
||||
if keyLabel == "" {
|
||||
|
||||
// Get hostname to use for self-signed certificate
|
||||
genHostName := os.Getenv("MQ_GENERATE_CERTIFICATE_HOSTNAME")
|
||||
|
||||
// Create the Web Keystore
|
||||
newWebKeystore := keystore.NewPKCS12KeyStore(webKeystoreFile, p12Truststore.Password)
|
||||
err := newWebKeystore.Create()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create Web Keystore %s: %v", webKeystoreFile, 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", webKeystoreFile, genHostName, err)
|
||||
}
|
||||
} else {
|
||||
// Check Web Keystore already exists
|
||||
_, err := os.Stat(webKeystoreFile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to find existing Web Keystore %s: %v", webKeystoreFile, 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 webKeystore, nil
|
||||
}
|
||||
Reference in New Issue
Block a user