File: key_load_test.go

package info (click to toggle)
docker.io 20.10.24%2Bdfsg1-1%2Bdeb12u1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bookworm-proposed-updates
  • size: 60,824 kB
  • sloc: sh: 5,621; makefile: 593; ansic: 179; python: 162; asm: 7
file content (237 lines) | stat: -rw-r--r-- 9,306 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
package trust

import (
	"encoding/pem"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"testing"

	"github.com/docker/cli/cli/config"
	"github.com/docker/cli/internal/test"
	"github.com/theupdateframework/notary"
	"github.com/theupdateframework/notary/passphrase"
	"github.com/theupdateframework/notary/storage"
	"github.com/theupdateframework/notary/trustmanager"
	tufutils "github.com/theupdateframework/notary/tuf/utils"
	"gotest.tools/v3/assert"
	is "gotest.tools/v3/assert/cmp"
	"gotest.tools/v3/skip"
)

func TestTrustKeyLoadErrors(t *testing.T) {
	noSuchFile := "stat iamnotakey: no such file or directory"
	if runtime.GOOS == "windows" {
		noSuchFile = "CreateFile iamnotakey: The system cannot find the file specified."
	}
	testCases := []struct {
		name           string
		args           []string
		expectedError  string
		expectedOutput string
	}{
		{
			name:           "not-enough-args",
			expectedError:  "exactly 1 argument",
			expectedOutput: "",
		},
		{
			name:           "too-many-args",
			args:           []string{"iamnotakey", "alsonotakey"},
			expectedError:  "exactly 1 argument",
			expectedOutput: "",
		},
		{
			name:           "not-a-key",
			args:           []string{"iamnotakey"},
			expectedError:  "refusing to load key from iamnotakey: " + noSuchFile,
			expectedOutput: "Loading key from \"iamnotakey\"...\n",
		},
		{
			name:           "bad-key-name",
			args:           []string{"iamnotakey", "--name", "KEYNAME"},
			expectedError:  "key name \"KEYNAME\" must start with lowercase alphanumeric characters and can include \"-\" or \"_\" after the first character",
			expectedOutput: "",
		},
	}
	config.SetDir(t.TempDir())

	for _, tc := range testCases {
		cli := test.NewFakeCli(&fakeClient{})
		cmd := newKeyLoadCommand(cli)
		cmd.SetArgs(tc.args)
		cmd.SetOut(io.Discard)
		assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
		assert.Check(t, is.Contains(cli.OutBuffer().String(), tc.expectedOutput))
	}
}

var rsaPrivKeyFixture = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAs7yVMzCw8CBZPoN+QLdx3ZzbVaHnouHIKu+ynX60IZ3stpbb
6rowu78OWON252JcYJqe++2GmdIgbBhg+mZDwhX0ZibMVztJaZFsYL+Ch/2J9KqD
A5NtE1s/XdhYoX5hsv7W4ok9jLFXRYIMj+T4exJRlR4f4GP9p0fcqPWd9/enPnlJ
JFTmu0DXJTZUMVS1UrXUy5t/DPXdrwyl8pM7VCqO3bqK7jqE6mWawdTkEeiku1fJ
ydP0285uiYTbj1Q38VVhPwXzMuLbkaUgRJhCI4BcjfQIjtJLbWpS+VdhUEvtgMVx
XJMKxCVGG69qjXyj9TjI7pxanb/bWglhovJN9wIDAQABAoIBAQCSnMsLxbUfOxPx
RWuwOLN+NZxIvtfnastQEtSdWiRvo5Xa3zYmw5hLHa8DXRC57+cwug/jqr54LQpb
gotg1hiBck05In7ezTK2FXTVeoJskal91bUnLpP0DSOkVnz9xszFKNF6Wr7FTEfH
IC1FF16Fbcz0mW0hKg9X6+uYOzqPcKpQRwli5LAwhT18Alf9h4/3NCeKotiJyr2J
xvcEH1eY2m2c/jQZurBkys7qBC3+i8LJEOW8MBQt7mxajwfbU91wtP2YoqMcoYiS
zsPbYp7Ui2t4G9Yn+OJw+uj4RGP1Bo4nSyRxWDtg+8Zug/JYU6/s+8kVRpiGffd3
T1GvoxUhAoGBAOnPDWG/g1xlJf65Rh71CxMs638zhYbIloU2K4Rqr05DHe7GryTS
9hLVrwhHddK+KwfVbR8HFMPo1DC/NVbuKt8StTAadAu3HsC088gWd28nOiGAWuvH
Bo3x/DYQGYwGFfoo4rzCOgMj6DJjXmcWEXNv3NDMoXoYpkxa0g6zZDyHAoGBAMTL
t7EUneJT+Mm7wyL1I5bmaT/HFwqoUQB2ccBPVD8p1el62NgLdfhOa8iNlBVhMrlh
2aTjrMlSPcjr9sCgKrLcenSWw+2qFsf4+SmV01ntB9kWes2phXpnB0ynXIcbeG05
+BLxbqDTVV0Iqh4r/dGeplyV2WyL3mTpkT3hRq8RAoGAZ93degEUICWnHWO9LN97
Dge0joua0+ekRoVsC6VBP6k9UOfewqMdQfy/hxQH2Zk1kINVuKTyqp1yNj2bOoUP
co3jA/2cc9/jv4QjkE26vRxWDK/ytC90T/aiLno0fyns9XbYUzaNgvuemVPfijgZ
hIi7Nd7SFWWB6wWlr3YuH10CgYEAwh7JVa2mh8iZEjVaKTNyJbmmfDjgq6yYKkKr
ti0KRzv3O9Xn7ERx27tPaobtWaGFLYQt8g57NCMhuv23aw8Sz1fYmwTUw60Rx7P5
42FdF8lOAn/AJvpfJfxXIO+9v7ADPIr//3+TxqRwAdM4K4btWkaKh61wyTe26gfT
MxzyYmECgYAnlU5zsGyiZqwoXVktkhtZrE7Qu0SoztzFb8KpvFNmMTPF1kAAYmJY
GIhbizeGJ3h4cUdozKmt8ZWIt6uFDEYCqEA7XF4RH75dW25x86mpIPO7iRl9eisY
IsLeMYqTIwXAwGx6Ka9v5LOL1kzcHQ2iVj6+QX+yoptSft1dYa9jOA==
-----END RSA PRIVATE KEY-----`)

const rsaPrivKeyID = "ee69e8e07a14756ad5ff0aca2336b37f86b0ac1710d1f3e94440081e080aecd7"

var ecPrivKeyFixture = []byte(`-----BEGIN EC PRIVATE KEY-----
MHcCAQEEINfxKtDH3ug7ZIQPDyeAzujCdhw36D+bf9ToPE1A7YEyoAoGCCqGSM49
AwEHoUQDQgAEUIH9AYtrcDFzZrFJBdJZkn21d+4cH3nzy2O6Q/ct4BjOBKa+WCdR
tPo78bA+C/7t81ADQO8Jqaj59W50rwoqDQ==
-----END EC PRIVATE KEY-----`)

const ecPrivKeyID = "46157cb0becf9c72c3219e11d4692424fef9bf4460812ccc8a71a3dfcafc7e60"

var testKeys = map[string][]byte{
	ecPrivKeyID:  ecPrivKeyFixture,
	rsaPrivKeyID: rsaPrivKeyFixture,
}

func TestLoadKeyFromPath(t *testing.T) {
	skip.If(t, runtime.GOOS == "windows")
	for keyID, keyBytes := range testKeys {
		keyID, keyBytes := keyID, keyBytes
		t.Run(fmt.Sprintf("load-key-id-%s-from-path", keyID), func(t *testing.T) {
			testLoadKeyFromPath(t, keyID, keyBytes)
		})
	}
}

func testLoadKeyFromPath(t *testing.T, privKeyID string, privKeyFixture []byte) {
	privKeyFilepath := filepath.Join(t.TempDir(), "privkey.pem")
	assert.NilError(t, os.WriteFile(privKeyFilepath, privKeyFixture, notary.PrivNoExecPerms))

	keyStorageDir := t.TempDir()

	const passwd = "password"
	cannedPasswordRetriever := passphrase.ConstantRetriever(passwd)
	keyFileStore, err := storage.NewPrivateKeyFileStorage(keyStorageDir, notary.KeyExtension)
	assert.NilError(t, err)
	privKeyImporters := []trustmanager.Importer{keyFileStore}

	// get the privKeyBytes
	privKeyBytes, err := getPrivKeyBytesFromPath(privKeyFilepath)
	assert.NilError(t, err)

	// import the key to our keyStorageDir
	assert.Check(t, loadPrivKeyBytesToStore(privKeyBytes, privKeyImporters, privKeyFilepath, "signer-name", cannedPasswordRetriever))

	// check that the appropriate ~/<trust_dir>/private/<key_id>.key file exists
	expectedImportKeyPath := filepath.Join(keyStorageDir, notary.PrivDir, privKeyID+"."+notary.KeyExtension)
	_, err = os.Stat(expectedImportKeyPath)
	assert.NilError(t, err)

	// verify the key content
	from, _ := os.OpenFile(expectedImportKeyPath, os.O_RDONLY, notary.PrivExecPerms)
	defer from.Close()
	fromBytes, _ := io.ReadAll(from)
	keyPEM, _ := pem.Decode(fromBytes)
	assert.Check(t, is.Equal("signer-name", keyPEM.Headers["role"]))
	// the default GUN is empty
	assert.Check(t, is.Equal("", keyPEM.Headers["gun"]))
	// assert encrypted header
	assert.Check(t, is.Equal("ENCRYPTED PRIVATE KEY", keyPEM.Type))

	decryptedKey, err := tufutils.ParsePKCS8ToTufKey(keyPEM.Bytes, []byte(passwd))
	assert.NilError(t, err)
	fixturePEM, _ := pem.Decode(privKeyFixture)
	assert.Check(t, is.DeepEqual(fixturePEM.Bytes, decryptedKey.Private()))
}

func TestLoadKeyTooPermissive(t *testing.T) {
	skip.If(t, runtime.GOOS == "windows")
	for keyID, keyBytes := range testKeys {
		keyID, keyBytes := keyID, keyBytes
		t.Run(fmt.Sprintf("load-key-id-%s-too-permissive", keyID), func(t *testing.T) {
			testLoadKeyTooPermissive(t, keyBytes)
		})
	}
}

func testLoadKeyTooPermissive(t *testing.T, privKeyFixture []byte) {
	privKeyDir := t.TempDir()
	privKeyFilepath := filepath.Join(privKeyDir, "privkey477.pem")
	assert.NilError(t, os.WriteFile(privKeyFilepath, privKeyFixture, 0477))

	// import the key to our keyStorageDir
	_, err := getPrivKeyBytesFromPath(privKeyFilepath)
	expected := fmt.Sprintf("private key file %s must not be readable or writable by others", privKeyFilepath)
	assert.Error(t, err, expected)

	privKeyFilepath = filepath.Join(privKeyDir, "privkey667.pem")
	assert.NilError(t, os.WriteFile(privKeyFilepath, privKeyFixture, 0677))

	_, err = getPrivKeyBytesFromPath(privKeyFilepath)
	expected = fmt.Sprintf("private key file %s must not be readable or writable by others", privKeyFilepath)
	assert.Error(t, err, expected)

	privKeyFilepath = filepath.Join(privKeyDir, "privkey777.pem")
	assert.NilError(t, os.WriteFile(privKeyFilepath, privKeyFixture, 0777))

	_, err = getPrivKeyBytesFromPath(privKeyFilepath)
	expected = fmt.Sprintf("private key file %s must not be readable or writable by others", privKeyFilepath)
	assert.Error(t, err, expected)

	privKeyFilepath = filepath.Join(privKeyDir, "privkey400.pem")
	assert.NilError(t, os.WriteFile(privKeyFilepath, privKeyFixture, 0400))

	_, err = getPrivKeyBytesFromPath(privKeyFilepath)
	assert.NilError(t, err)

	privKeyFilepath = filepath.Join(privKeyDir, "privkey600.pem")
	assert.NilError(t, os.WriteFile(privKeyFilepath, privKeyFixture, 0600))

	_, err = getPrivKeyBytesFromPath(privKeyFilepath)
	assert.NilError(t, err)
}

var pubKeyFixture = []byte(`-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUIH9AYtrcDFzZrFJBdJZkn21d+4c
H3nzy2O6Q/ct4BjOBKa+WCdRtPo78bA+C/7t81ADQO8Jqaj59W50rwoqDQ==
-----END PUBLIC KEY-----`)

func TestLoadPubKeyFailure(t *testing.T) {
	skip.If(t, runtime.GOOS == "windows")
	pubKeyDir := t.TempDir()
	pubKeyFilepath := filepath.Join(pubKeyDir, "pubkey.pem")
	assert.NilError(t, os.WriteFile(pubKeyFilepath, pubKeyFixture, notary.PrivNoExecPerms))
	keyStorageDir := t.TempDir()

	const passwd = "password"
	cannedPasswordRetriever := passphrase.ConstantRetriever(passwd)
	keyFileStore, err := storage.NewPrivateKeyFileStorage(keyStorageDir, notary.KeyExtension)
	assert.NilError(t, err)
	privKeyImporters := []trustmanager.Importer{keyFileStore}

	pubKeyBytes, err := getPrivKeyBytesFromPath(pubKeyFilepath)
	assert.NilError(t, err)

	// import the key to our keyStorageDir - it should fail
	err = loadPrivKeyBytesToStore(pubKeyBytes, privKeyImporters, pubKeyFilepath, "signer-name", cannedPasswordRetriever)
	expected := fmt.Sprintf("provided file %s is not a supported private key - to add a signer's public key use docker trust signer add", pubKeyFilepath)
	assert.Error(t, err, expected)
}