github.com/cspotcode/docker-cli@v20.10.0-rc1.0.20201201121459-3faad7acc5b8+incompatible/cli/command/trust/key_load_test.go (about) 1 package trust 2 3 import ( 4 "encoding/pem" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "runtime" 10 "testing" 11 12 "github.com/docker/cli/cli/config" 13 "github.com/docker/cli/internal/test" 14 "github.com/theupdateframework/notary" 15 "github.com/theupdateframework/notary/passphrase" 16 "github.com/theupdateframework/notary/storage" 17 "github.com/theupdateframework/notary/trustmanager" 18 tufutils "github.com/theupdateframework/notary/tuf/utils" 19 "gotest.tools/v3/assert" 20 is "gotest.tools/v3/assert/cmp" 21 "gotest.tools/v3/skip" 22 ) 23 24 func TestTrustKeyLoadErrors(t *testing.T) { 25 noSuchFile := "stat iamnotakey: no such file or directory" 26 if runtime.GOOS == "windows" { 27 noSuchFile = "CreateFile iamnotakey: The system cannot find the file specified." 28 } 29 testCases := []struct { 30 name string 31 args []string 32 expectedError string 33 expectedOutput string 34 }{ 35 { 36 name: "not-enough-args", 37 expectedError: "exactly 1 argument", 38 expectedOutput: "", 39 }, 40 { 41 name: "too-many-args", 42 args: []string{"iamnotakey", "alsonotakey"}, 43 expectedError: "exactly 1 argument", 44 expectedOutput: "", 45 }, 46 { 47 name: "not-a-key", 48 args: []string{"iamnotakey"}, 49 expectedError: "refusing to load key from iamnotakey: " + noSuchFile, 50 expectedOutput: "Loading key from \"iamnotakey\"...\n", 51 }, 52 { 53 name: "bad-key-name", 54 args: []string{"iamnotakey", "--name", "KEYNAME"}, 55 expectedError: "key name \"KEYNAME\" must start with lowercase alphanumeric characters and can include \"-\" or \"_\" after the first character", 56 expectedOutput: "", 57 }, 58 } 59 tmpDir, err := ioutil.TempDir("", "docker-key-load-test-") 60 assert.NilError(t, err) 61 defer os.RemoveAll(tmpDir) 62 config.SetDir(tmpDir) 63 64 for _, tc := range testCases { 65 cli := test.NewFakeCli(&fakeClient{}) 66 cmd := newKeyLoadCommand(cli) 67 cmd.SetArgs(tc.args) 68 cmd.SetOut(ioutil.Discard) 69 assert.ErrorContains(t, cmd.Execute(), tc.expectedError) 70 assert.Check(t, is.Contains(cli.OutBuffer().String(), tc.expectedOutput)) 71 } 72 } 73 74 var rsaPrivKeyFixture = []byte(`-----BEGIN RSA PRIVATE KEY----- 75 MIIEpAIBAAKCAQEAs7yVMzCw8CBZPoN+QLdx3ZzbVaHnouHIKu+ynX60IZ3stpbb 76 6rowu78OWON252JcYJqe++2GmdIgbBhg+mZDwhX0ZibMVztJaZFsYL+Ch/2J9KqD 77 A5NtE1s/XdhYoX5hsv7W4ok9jLFXRYIMj+T4exJRlR4f4GP9p0fcqPWd9/enPnlJ 78 JFTmu0DXJTZUMVS1UrXUy5t/DPXdrwyl8pM7VCqO3bqK7jqE6mWawdTkEeiku1fJ 79 ydP0285uiYTbj1Q38VVhPwXzMuLbkaUgRJhCI4BcjfQIjtJLbWpS+VdhUEvtgMVx 80 XJMKxCVGG69qjXyj9TjI7pxanb/bWglhovJN9wIDAQABAoIBAQCSnMsLxbUfOxPx 81 RWuwOLN+NZxIvtfnastQEtSdWiRvo5Xa3zYmw5hLHa8DXRC57+cwug/jqr54LQpb 82 gotg1hiBck05In7ezTK2FXTVeoJskal91bUnLpP0DSOkVnz9xszFKNF6Wr7FTEfH 83 IC1FF16Fbcz0mW0hKg9X6+uYOzqPcKpQRwli5LAwhT18Alf9h4/3NCeKotiJyr2J 84 xvcEH1eY2m2c/jQZurBkys7qBC3+i8LJEOW8MBQt7mxajwfbU91wtP2YoqMcoYiS 85 zsPbYp7Ui2t4G9Yn+OJw+uj4RGP1Bo4nSyRxWDtg+8Zug/JYU6/s+8kVRpiGffd3 86 T1GvoxUhAoGBAOnPDWG/g1xlJf65Rh71CxMs638zhYbIloU2K4Rqr05DHe7GryTS 87 9hLVrwhHddK+KwfVbR8HFMPo1DC/NVbuKt8StTAadAu3HsC088gWd28nOiGAWuvH 88 Bo3x/DYQGYwGFfoo4rzCOgMj6DJjXmcWEXNv3NDMoXoYpkxa0g6zZDyHAoGBAMTL 89 t7EUneJT+Mm7wyL1I5bmaT/HFwqoUQB2ccBPVD8p1el62NgLdfhOa8iNlBVhMrlh 90 2aTjrMlSPcjr9sCgKrLcenSWw+2qFsf4+SmV01ntB9kWes2phXpnB0ynXIcbeG05 91 +BLxbqDTVV0Iqh4r/dGeplyV2WyL3mTpkT3hRq8RAoGAZ93degEUICWnHWO9LN97 92 Dge0joua0+ekRoVsC6VBP6k9UOfewqMdQfy/hxQH2Zk1kINVuKTyqp1yNj2bOoUP 93 co3jA/2cc9/jv4QjkE26vRxWDK/ytC90T/aiLno0fyns9XbYUzaNgvuemVPfijgZ 94 hIi7Nd7SFWWB6wWlr3YuH10CgYEAwh7JVa2mh8iZEjVaKTNyJbmmfDjgq6yYKkKr 95 ti0KRzv3O9Xn7ERx27tPaobtWaGFLYQt8g57NCMhuv23aw8Sz1fYmwTUw60Rx7P5 96 42FdF8lOAn/AJvpfJfxXIO+9v7ADPIr//3+TxqRwAdM4K4btWkaKh61wyTe26gfT 97 MxzyYmECgYAnlU5zsGyiZqwoXVktkhtZrE7Qu0SoztzFb8KpvFNmMTPF1kAAYmJY 98 GIhbizeGJ3h4cUdozKmt8ZWIt6uFDEYCqEA7XF4RH75dW25x86mpIPO7iRl9eisY 99 IsLeMYqTIwXAwGx6Ka9v5LOL1kzcHQ2iVj6+QX+yoptSft1dYa9jOA== 100 -----END RSA PRIVATE KEY-----`) 101 102 const rsaPrivKeyID = "ee69e8e07a14756ad5ff0aca2336b37f86b0ac1710d1f3e94440081e080aecd7" 103 104 var ecPrivKeyFixture = []byte(`-----BEGIN EC PRIVATE KEY----- 105 MHcCAQEEINfxKtDH3ug7ZIQPDyeAzujCdhw36D+bf9ToPE1A7YEyoAoGCCqGSM49 106 AwEHoUQDQgAEUIH9AYtrcDFzZrFJBdJZkn21d+4cH3nzy2O6Q/ct4BjOBKa+WCdR 107 tPo78bA+C/7t81ADQO8Jqaj59W50rwoqDQ== 108 -----END EC PRIVATE KEY-----`) 109 110 const ecPrivKeyID = "46157cb0becf9c72c3219e11d4692424fef9bf4460812ccc8a71a3dfcafc7e60" 111 112 var testKeys = map[string][]byte{ 113 ecPrivKeyID: ecPrivKeyFixture, 114 rsaPrivKeyID: rsaPrivKeyFixture, 115 } 116 117 func TestLoadKeyFromPath(t *testing.T) { 118 skip.If(t, runtime.GOOS == "windows") 119 for keyID, keyBytes := range testKeys { 120 keyID, keyBytes := keyID, keyBytes 121 t.Run(fmt.Sprintf("load-key-id-%s-from-path", keyID), func(t *testing.T) { 122 testLoadKeyFromPath(t, keyID, keyBytes) 123 }) 124 } 125 } 126 127 func testLoadKeyFromPath(t *testing.T, privKeyID string, privKeyFixture []byte) { 128 privKeyDir, err := ioutil.TempDir("", "key-load-test-") 129 assert.NilError(t, err) 130 defer os.RemoveAll(privKeyDir) 131 privKeyFilepath := filepath.Join(privKeyDir, "privkey.pem") 132 assert.NilError(t, ioutil.WriteFile(privKeyFilepath, privKeyFixture, notary.PrivNoExecPerms)) 133 134 keyStorageDir, err := ioutil.TempDir("", "loaded-keys-") 135 assert.NilError(t, err) 136 defer os.RemoveAll(keyStorageDir) 137 138 passwd := "password" 139 cannedPasswordRetriever := passphrase.ConstantRetriever(passwd) 140 keyFileStore, err := storage.NewPrivateKeyFileStorage(keyStorageDir, notary.KeyExtension) 141 assert.NilError(t, err) 142 privKeyImporters := []trustmanager.Importer{keyFileStore} 143 144 // get the privKeyBytes 145 privKeyBytes, err := getPrivKeyBytesFromPath(privKeyFilepath) 146 assert.NilError(t, err) 147 148 // import the key to our keyStorageDir 149 assert.Check(t, loadPrivKeyBytesToStore(privKeyBytes, privKeyImporters, privKeyFilepath, "signer-name", cannedPasswordRetriever)) 150 151 // check that the appropriate ~/<trust_dir>/private/<key_id>.key file exists 152 expectedImportKeyPath := filepath.Join(keyStorageDir, notary.PrivDir, privKeyID+"."+notary.KeyExtension) 153 _, err = os.Stat(expectedImportKeyPath) 154 assert.NilError(t, err) 155 156 // verify the key content 157 from, _ := os.OpenFile(expectedImportKeyPath, os.O_RDONLY, notary.PrivExecPerms) 158 defer from.Close() 159 fromBytes, _ := ioutil.ReadAll(from) 160 keyPEM, _ := pem.Decode(fromBytes) 161 assert.Check(t, is.Equal("signer-name", keyPEM.Headers["role"])) 162 // the default GUN is empty 163 assert.Check(t, is.Equal("", keyPEM.Headers["gun"])) 164 // assert encrypted header 165 assert.Check(t, is.Equal("ENCRYPTED PRIVATE KEY", keyPEM.Type)) 166 167 decryptedKey, err := tufutils.ParsePKCS8ToTufKey(keyPEM.Bytes, []byte(passwd)) 168 assert.NilError(t, err) 169 fixturePEM, _ := pem.Decode(privKeyFixture) 170 assert.Check(t, is.DeepEqual(fixturePEM.Bytes, decryptedKey.Private())) 171 } 172 173 func TestLoadKeyTooPermissive(t *testing.T) { 174 skip.If(t, runtime.GOOS == "windows") 175 for keyID, keyBytes := range testKeys { 176 keyID, keyBytes := keyID, keyBytes 177 t.Run(fmt.Sprintf("load-key-id-%s-too-permissive", keyID), func(t *testing.T) { 178 testLoadKeyTooPermissive(t, keyBytes) 179 }) 180 } 181 } 182 183 func testLoadKeyTooPermissive(t *testing.T, privKeyFixture []byte) { 184 privKeyDir, err := ioutil.TempDir("", "key-load-test-") 185 assert.NilError(t, err) 186 defer os.RemoveAll(privKeyDir) 187 privKeyFilepath := filepath.Join(privKeyDir, "privkey477.pem") 188 assert.NilError(t, ioutil.WriteFile(privKeyFilepath, privKeyFixture, 0477)) 189 190 keyStorageDir, err := ioutil.TempDir("", "loaded-keys-") 191 assert.NilError(t, err) 192 defer os.RemoveAll(keyStorageDir) 193 194 // import the key to our keyStorageDir 195 _, err = getPrivKeyBytesFromPath(privKeyFilepath) 196 expected := fmt.Sprintf("private key file %s must not be readable or writable by others", privKeyFilepath) 197 assert.Error(t, err, expected) 198 199 privKeyFilepath = filepath.Join(privKeyDir, "privkey667.pem") 200 assert.NilError(t, ioutil.WriteFile(privKeyFilepath, privKeyFixture, 0677)) 201 202 _, err = getPrivKeyBytesFromPath(privKeyFilepath) 203 expected = fmt.Sprintf("private key file %s must not be readable or writable by others", privKeyFilepath) 204 assert.Error(t, err, expected) 205 206 privKeyFilepath = filepath.Join(privKeyDir, "privkey777.pem") 207 assert.NilError(t, ioutil.WriteFile(privKeyFilepath, privKeyFixture, 0777)) 208 209 _, err = getPrivKeyBytesFromPath(privKeyFilepath) 210 expected = fmt.Sprintf("private key file %s must not be readable or writable by others", privKeyFilepath) 211 assert.Error(t, err, expected) 212 213 privKeyFilepath = filepath.Join(privKeyDir, "privkey400.pem") 214 assert.NilError(t, ioutil.WriteFile(privKeyFilepath, privKeyFixture, 0400)) 215 216 _, err = getPrivKeyBytesFromPath(privKeyFilepath) 217 assert.NilError(t, err) 218 219 privKeyFilepath = filepath.Join(privKeyDir, "privkey600.pem") 220 assert.NilError(t, ioutil.WriteFile(privKeyFilepath, privKeyFixture, 0600)) 221 222 _, err = getPrivKeyBytesFromPath(privKeyFilepath) 223 assert.NilError(t, err) 224 } 225 226 var pubKeyFixture = []byte(`-----BEGIN PUBLIC KEY----- 227 MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUIH9AYtrcDFzZrFJBdJZkn21d+4c 228 H3nzy2O6Q/ct4BjOBKa+WCdRtPo78bA+C/7t81ADQO8Jqaj59W50rwoqDQ== 229 -----END PUBLIC KEY-----`) 230 231 func TestLoadPubKeyFailure(t *testing.T) { 232 skip.If(t, runtime.GOOS == "windows") 233 pubKeyDir, err := ioutil.TempDir("", "key-load-test-pubkey-") 234 assert.NilError(t, err) 235 defer os.RemoveAll(pubKeyDir) 236 pubKeyFilepath := filepath.Join(pubKeyDir, "pubkey.pem") 237 assert.NilError(t, ioutil.WriteFile(pubKeyFilepath, pubKeyFixture, notary.PrivNoExecPerms)) 238 keyStorageDir, err := ioutil.TempDir("", "loaded-keys-") 239 assert.NilError(t, err) 240 defer os.RemoveAll(keyStorageDir) 241 242 passwd := "password" 243 cannedPasswordRetriever := passphrase.ConstantRetriever(passwd) 244 keyFileStore, err := storage.NewPrivateKeyFileStorage(keyStorageDir, notary.KeyExtension) 245 assert.NilError(t, err) 246 privKeyImporters := []trustmanager.Importer{keyFileStore} 247 248 pubKeyBytes, err := getPrivKeyBytesFromPath(pubKeyFilepath) 249 assert.NilError(t, err) 250 251 // import the key to our keyStorageDir - it should fail 252 err = loadPrivKeyBytesToStore(pubKeyBytes, privKeyImporters, pubKeyFilepath, "signer-name", cannedPasswordRetriever) 253 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) 254 assert.Error(t, err, expected) 255 }