Skip to content

Commit 3693e10

Browse files
Pranav GoyalGitHub Enterprise
authored andcommitted
Merge pull request #984 from mq-cloudpak/pranav-4611-go-sensitive-strings-sc2
Ensure sensitive strings/byte buffers are zeroed in memory before GC in SC2
2 parents d30a8d7 + 475913e commit 3693e10

File tree

9 files changed

+363
-49
lines changed

9 files changed

+363
-49
lines changed

internal/keystore/keystore.go

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,20 @@ import (
2727

2828
"github.com/ibm-messaging/mq-container/internal/command"
2929
"github.com/ibm-messaging/mq-container/internal/fips"
30+
"github.com/ibm-messaging/mq-container/internal/sensitive"
3031
)
3132

3233
// KeyStore describes information about a keystore file
3334
type KeyStore struct {
3435
Filename string
35-
Password string
36+
Password *sensitive.Sensitive
3637
keyStoreType string
3738
command string
3839
fipsEnabled bool
3940
}
4041

4142
// NewJKSKeyStore creates a new Java Key Store, managed by the runmqckm command
42-
func NewJKSKeyStore(filename, password string) *KeyStore {
43+
func NewJKSKeyStore(filename string, password *sensitive.Sensitive) *KeyStore {
4344
keyStore := &KeyStore{
4445
Filename: filename,
4546
Password: password,
@@ -52,7 +53,7 @@ func NewJKSKeyStore(filename, password string) *KeyStore {
5253
}
5354

5455
// NewCMSKeyStore creates a new MQ CMS Key Store, managed by the runmqakm command
55-
func NewCMSKeyStore(filename, password string) *KeyStore {
56+
func NewCMSKeyStore(filename string, password *sensitive.Sensitive) *KeyStore {
5657
keyStore := &KeyStore{
5758
Filename: filename,
5859
Password: password,
@@ -65,7 +66,7 @@ func NewCMSKeyStore(filename, password string) *KeyStore {
6566
}
6667

6768
// NewPKCS12KeyStore creates a new PKCS12 Key Store, managed by the runmqakm command
68-
func NewPKCS12KeyStore(filename, password string) *KeyStore {
69+
func NewPKCS12KeyStore(filename string, password *sensitive.Sensitive) *KeyStore {
6970
keyStore := &KeyStore{
7071
Filename: filename,
7172
Password: password,
@@ -111,7 +112,7 @@ func (ks *KeyStore) Create() error {
111112
}
112113

113114
// Create the keystore now we're sure it doesn't exist
114-
out, _, err := command.Run(ks.command, "-keydb", "-create", ks.getFipsEnabledFlag(), "-type", ks.keyStoreType, "-db", ks.Filename, "-pw", ks.Password, "-stash")
115+
out, _, err := command.Run(ks.command, "-keydb", "-create", ks.getFipsEnabledFlag(), "-type", ks.keyStoreType, "-db", ks.Filename, "-pw", ks.Password.String(), "-stash")
115116
if err != nil {
116117
return fmt.Errorf("error running \"%v -keydb -create\": %v %s", ks.command, err, out)
117118
}
@@ -126,7 +127,7 @@ func (ks *KeyStore) CreateStash() error {
126127
_, err := os.Stat(stashFile)
127128
if err != nil {
128129
if os.IsNotExist(err) {
129-
out, _, err := command.Run(ks.command, "-keydb", ks.getFipsEnabledFlag(), "-stashpw", "-type", ks.keyStoreType, "-db", ks.Filename, "-pw", ks.Password)
130+
out, _, err := command.Run(ks.command, "-keydb", ks.getFipsEnabledFlag(), "-stashpw", "-type", ks.keyStoreType, "-db", ks.Filename, "-pw", ks.Password.String())
130131
if err != nil {
131132
return fmt.Errorf("error running \"%v -keydb -stashpw\": %v %s", ks.command, err, out)
132133
}
@@ -137,8 +138,8 @@ func (ks *KeyStore) CreateStash() error {
137138
}
138139

139140
// Import imports a certificate file in the keystore
140-
func (ks *KeyStore) Import(inputFile, password string) error {
141-
out, _, err := command.Run(ks.command, "-cert", "-import", ks.getFipsEnabledFlag(), "-file", inputFile, "-pw", password, "-target", ks.Filename, "-target_pw", ks.Password, "-target_type", ks.keyStoreType)
141+
func (ks *KeyStore) Import(inputFile string, password *sensitive.Sensitive) error {
142+
out, _, err := command.Run(ks.command, "-cert", "-import", ks.getFipsEnabledFlag(), "-file", inputFile, "-pw", password.String(), "-target", ks.Filename, "-target_pw", ks.Password.String(), "-target_type", ks.keyStoreType)
142143
if err != nil {
143144
return fmt.Errorf("error running \"%v -cert -import\": %v %s", ks.command, err, out)
144145
}
@@ -147,7 +148,7 @@ func (ks *KeyStore) Import(inputFile, password string) error {
147148

148149
// CreateSelfSignedCertificate creates a self-signed certificate in the keystore
149150
func (ks *KeyStore) CreateSelfSignedCertificate(label, dn, hostname string) error {
150-
out, _, err := command.Run(ks.command, "-cert", "-create", ks.getFipsEnabledFlag(), "-db", ks.Filename, "-pw", ks.Password, "-label", label, "-dn", dn, "-san_dnsname", hostname, "-size 2048 -sig_alg sha512 -eku serverAuth")
151+
out, _, err := command.Run(ks.command, "-cert", "-create", ks.getFipsEnabledFlag(), "-db", ks.Filename, "-pw", ks.Password.String(), "-label", label, "-dn", dn, "-san_dnsname", hostname, "-size 2048 -sig_alg sha512 -eku serverAuth")
151152
if err != nil {
152153
return fmt.Errorf("error running \"%v -cert -create\": %v %s", ks.command, err, out)
153154
}
@@ -156,7 +157,7 @@ func (ks *KeyStore) CreateSelfSignedCertificate(label, dn, hostname string) erro
156157

157158
// Add adds a CA certificate to the keystore
158159
func (ks *KeyStore) Add(inputFile, label string) error {
159-
out, _, err := command.Run(ks.command, "-cert", "-add", ks.getFipsEnabledFlag(), "-db", ks.Filename, "-type", ks.keyStoreType, "-pw", ks.Password, "-file", inputFile, "-label", label)
160+
out, _, err := command.Run(ks.command, "-cert", "-add", ks.getFipsEnabledFlag(), "-db", ks.Filename, "-type", ks.keyStoreType, "-pw", ks.Password.String(), "-file", inputFile, "-label", label)
160161
if err != nil {
161162
return fmt.Errorf("error running \"%v -cert -add\": %v %s", ks.command, err, out)
162163
}
@@ -165,7 +166,7 @@ func (ks *KeyStore) Add(inputFile, label string) error {
165166

166167
// Add adds a CA certificate to the keystore
167168
func (ks *KeyStore) AddNoLabel(inputFile string) error {
168-
out, _, err := command.Run(ks.command, "-cert", "-add", ks.getFipsEnabledFlag(), "-db", ks.Filename, "-type", ks.keyStoreType, "-pw", ks.Password, "-file", inputFile)
169+
out, _, err := command.Run(ks.command, "-cert", "-add", ks.getFipsEnabledFlag(), "-db", ks.Filename, "-type", ks.keyStoreType, "-pw", ks.Password.String(), "-file", inputFile)
169170
if err != nil {
170171
return fmt.Errorf("error running \"%v -cert -add\": %v %s", ks.command, err, out)
171172
}
@@ -174,7 +175,7 @@ func (ks *KeyStore) AddNoLabel(inputFile string) error {
174175

175176
// GetCertificateLabels returns the labels of all certificates in the key store
176177
func (ks *KeyStore) GetCertificateLabels() ([]string, error) {
177-
out, _, err := command.Run(ks.command, "-cert", "-list", ks.getFipsEnabledFlag(), "-type", ks.keyStoreType, "-db", ks.Filename, "-pw", ks.Password)
178+
out, _, err := command.Run(ks.command, "-cert", "-list", ks.getFipsEnabledFlag(), "-type", ks.keyStoreType, "-db", ks.Filename, "-pw", ks.Password.String())
178179
if err != nil {
179180
return nil, fmt.Errorf("error running \"%v -cert -list\": %v %s", ks.command, err, out)
180181
}
@@ -200,14 +201,14 @@ func (ks *KeyStore) RenameCertificate(from, to string) error {
200201
// runmqakm can't handle certs with ' in them so just use capicmd
201202
// Overriding gosec here as this function is in an internal package and only callable by our internal functions.
202203
// #nosec G204
203-
cmd := exec.Command("/opt/mqm/gskit8/bin/gsk8capicmd_64", "-cert", "-rename", "-db", ks.Filename, "-pw", ks.Password, "-label", from, "-new_label", to)
204+
cmd := exec.Command("/opt/mqm/gskit8/bin/gsk8capicmd_64", "-cert", "-rename", "-db", ks.Filename, "-pw", ks.Password.String(), "-label", from, "-new_label", to)
204205
cmd.Env = append(os.Environ(), "LD_LIBRARY_PATH=/opt/mqm/gskit8/lib64/:/opt/mqm/gskit8/lib")
205206
out, err := cmd.CombinedOutput()
206207
if err != nil {
207208
return fmt.Errorf("error running \"%v -cert -rename\": %v %s", "/opt/mqm/gskit8/bin/gsk8capicmd_64", err, out)
208209
}
209210
} else {
210-
out, _, err := command.Run(ks.command, "-cert", "-rename", "-db", ks.Filename, "-pw", ks.Password, "-label", from, "-new_label", to)
211+
out, _, err := command.Run(ks.command, "-cert", "-rename", "-db", ks.Filename, "-pw", ks.Password.String(), "-label", from, "-new_label", to)
211212
if err != nil {
212213
return fmt.Errorf("error running \"%v -cert -rename\": %v %s", ks.command, err, out)
213214
}
@@ -218,7 +219,7 @@ func (ks *KeyStore) RenameCertificate(from, to string) error {
218219

219220
// ListAllCertificates Lists all certificates in the keystore
220221
func (ks *KeyStore) ListAllCertificates() ([]string, error) {
221-
out, _, err := command.Run(ks.command, "-cert", "-list", ks.getFipsEnabledFlag(), "-type", ks.keyStoreType, "-db", ks.Filename, "-pw", ks.Password)
222+
out, _, err := command.Run(ks.command, "-cert", "-list", ks.getFipsEnabledFlag(), "-type", ks.keyStoreType, "-db", ks.Filename, "-pw", ks.Password.String())
222223
if err != nil {
223224
return nil, fmt.Errorf("error running \"%v -cert -list\": %v %s", ks.command, err, out)
224225
}

internal/securityutility/securityutility.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,30 @@ import (
2424
"os"
2525
"os/exec"
2626
"strings"
27+
28+
"github.com/ibm-messaging/mq-container/internal/sensitive"
2729
)
2830

2931
// EncodeSecrets takes a secret/password as an input and encodes the password using securityUtility
3032
// and returns the encoded password
31-
func EncodeSecrets(secret string) (string, error) {
33+
func EncodeSecrets(secret *sensitive.Sensitive) (string, error) {
3234
_, err := os.Stat("/opt/mqm/web/bin/securityUtility")
3335
if err != nil && os.IsNotExist(err) {
3436
return "", err
3537
}
36-
if len(secret) > 256 {
37-
return "", fmt.Errorf("length of password is greater than the maximum length of 256 characters, length of password is %d", len(secret))
38+
if secret.Len() > 256 {
39+
return "", fmt.Errorf("length of password is greater than the maximum length of 256 characters, length of password is %d", secret.Len())
40+
}
41+
script := []byte("source setmqenv -s;/opt/mqm/web/bin/server; /opt/mqm/web/bin/securityUtility encode --encoding=aes ")
42+
scriptSecret := sensitive.New(script)
43+
err = scriptSecret.Append(secret)
44+
if err != nil {
45+
return "", err
3846
}
3947
// Set the java environment required for running securityUtility tool and then run the securityUtility tool
4048
// to encode the password using "aes" encoding
4149
// #nosec G204
42-
cmd := exec.Command("/bin/sh", "-c", "source setmqenv -s;/opt/mqm/web/bin/server; /opt/mqm/web/bin/securityUtility encode --encoding=aes "+secret)
50+
cmd := exec.Command("/bin/sh", "-c", scriptSecret.String())
4351
cmd.Env = os.Environ()
4452
cmd.Env = append(cmd.Env, "JAVA_HOME=/opt/mqm/java/jre64/jre")
4553

internal/sensitive/sensitive.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
© Copyright IBM Corporation 2025
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package sensitive
18+
19+
import (
20+
"runtime"
21+
"unsafe"
22+
)
23+
24+
// Sensitive ensures that memory used for sensitive strings/byte buffers is cleared (overwritten with zeroes) after garbage collection occurs
25+
type Sensitive struct {
26+
*meta
27+
}
28+
29+
type meta struct {
30+
buf []byte
31+
pin *runtime.Pinner
32+
}
33+
34+
// New creates a new Sensitive object
35+
//
36+
// NOTE: Ownership of the buffer should transfer to the Sensitive object and the buffer passed should not continue to be used by the caller
37+
func New(buf []byte) *Sensitive {
38+
s := &Sensitive{}
39+
s.setMeta(buf)
40+
return s
41+
}
42+
43+
// Write appends the byte slice to the underlying buffer.
44+
// If the buffer needs to grow and is moved, the original location will be zeroed as part of the Write call.
45+
func (s *Sensitive) Write(b []byte) error {
46+
newBuf := append(s.buf, b...)
47+
if &newBuf[0] != &s.buf[0] {
48+
// buffer starts at a new address - must have moved to a new memory block
49+
zeroMeta(s.meta)
50+
s.setMeta(newBuf)
51+
return nil
52+
}
53+
// buffer hasn't moved, don't zero out current buffer as it's still in use, but do update the buffer to capture new length etc.
54+
s.buf = newBuf
55+
return nil
56+
}
57+
58+
func (s Sensitive) Len() int {
59+
return len(s.buf)
60+
}
61+
62+
func (s *Sensitive) Append(other *Sensitive) error {
63+
return s.Write(other.buf)
64+
}
65+
66+
// Clear triggers an immediate clear of the underlying buffer without waiting for garbage collection to occur
67+
func (s *Sensitive) Clear() {
68+
zeroMeta(s.meta)
69+
}
70+
71+
// String returns a string that points to the underlying managed buffer
72+
//
73+
// NOTE: if the Sensitive object is garbage collected, or Clear() is called, this string will be zeroed even if it remains in scope
74+
func (s *Sensitive) String() string {
75+
// #nosec G103 - unsafe package is required in order to prevent memory copy during type conversion to string
76+
return unsafe.String(unsafe.SliceData(s.meta.buf), len(s.buf))
77+
}
78+
79+
// setMeta creates a new metadata object, pinning its location in memory and registering finalizers to zero the underlying buffer on garbage collection
80+
func (s *Sensitive) setMeta(buf []byte) {
81+
m := &meta{
82+
buf: buf,
83+
pin: &runtime.Pinner{},
84+
}
85+
m.pin.Pin(&buf)
86+
s.meta = m
87+
runtime.SetFinalizer(m, zeroMeta)
88+
}
89+
90+
// zeroMeta zeroes the underlying buffer and removes all finalizers and memory pins
91+
func zeroMeta(m *meta) {
92+
for i := range len(m.buf) {
93+
m.buf[i] = 0
94+
}
95+
runtime.KeepAlive(m.buf)
96+
m.pin.Unpin()
97+
runtime.SetFinalizer(m, nil)
98+
}

0 commit comments

Comments
 (0)