github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/trust/key_load_test.go (about) 1 package trust 2 3 import ( 4 "encoding/pem" 5 "fmt" 6 "io" 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 config.SetDir(t.TempDir()) 60 61 for _, tc := range testCases { 62 cli := test.NewFakeCli(&fakeClient{}) 63 cmd := newKeyLoadCommand(cli) 64 cmd.SetArgs(tc.args) 65 cmd.SetOut(io.Discard) 66 assert.ErrorContains(t, cmd.Execute(), tc.expectedError) 67 assert.Check(t, is.Contains(cli.OutBuffer().String(), tc.expectedOutput)) 68 } 69 } 70 71 var rsaPrivKeyFixture = []byte(`-----BEGIN RSA PRIVATE KEY----- 72 MIIEpAIBAAKCAQEAs7yVMzCw8CBZPoN+QLdx3ZzbVaHnouHIKu+ynX60IZ3stpbb 73 6rowu78OWON252JcYJqe++2GmdIgbBhg+mZDwhX0ZibMVztJaZFsYL+Ch/2J9KqD 74 A5NtE1s/XdhYoX5hsv7W4ok9jLFXRYIMj+T4exJRlR4f4GP9p0fcqPWd9/enPnlJ 75 JFTmu0DXJTZUMVS1UrXUy5t/DPXdrwyl8pM7VCqO3bqK7jqE6mWawdTkEeiku1fJ 76 ydP0285uiYTbj1Q38VVhPwXzMuLbkaUgRJhCI4BcjfQIjtJLbWpS+VdhUEvtgMVx 77 XJMKxCVGG69qjXyj9TjI7pxanb/bWglhovJN9wIDAQABAoIBAQCSnMsLxbUfOxPx 78 RWuwOLN+NZxIvtfnastQEtSdWiRvo5Xa3zYmw5hLHa8DXRC57+cwug/jqr54LQpb 79 gotg1hiBck05In7ezTK2FXTVeoJskal91bUnLpP0DSOkVnz9xszFKNF6Wr7FTEfH 80 IC1FF16Fbcz0mW0hKg9X6+uYOzqPcKpQRwli5LAwhT18Alf9h4/3NCeKotiJyr2J 81 xvcEH1eY2m2c/jQZurBkys7qBC3+i8LJEOW8MBQt7mxajwfbU91wtP2YoqMcoYiS 82 zsPbYp7Ui2t4G9Yn+OJw+uj4RGP1Bo4nSyRxWDtg+8Zug/JYU6/s+8kVRpiGffd3 83 T1GvoxUhAoGBAOnPDWG/g1xlJf65Rh71CxMs638zhYbIloU2K4Rqr05DHe7GryTS 84 9hLVrwhHddK+KwfVbR8HFMPo1DC/NVbuKt8StTAadAu3HsC088gWd28nOiGAWuvH 85 Bo3x/DYQGYwGFfoo4rzCOgMj6DJjXmcWEXNv3NDMoXoYpkxa0g6zZDyHAoGBAMTL 86 t7EUneJT+Mm7wyL1I5bmaT/HFwqoUQB2ccBPVD8p1el62NgLdfhOa8iNlBVhMrlh 87 2aTjrMlSPcjr9sCgKrLcenSWw+2qFsf4+SmV01ntB9kWes2phXpnB0ynXIcbeG05 88 +BLxbqDTVV0Iqh4r/dGeplyV2WyL3mTpkT3hRq8RAoGAZ93degEUICWnHWO9LN97 89 Dge0joua0+ekRoVsC6VBP6k9UOfewqMdQfy/hxQH2Zk1kINVuKTyqp1yNj2bOoUP 90 co3jA/2cc9/jv4QjkE26vRxWDK/ytC90T/aiLno0fyns9XbYUzaNgvuemVPfijgZ 91 hIi7Nd7SFWWB6wWlr3YuH10CgYEAwh7JVa2mh8iZEjVaKTNyJbmmfDjgq6yYKkKr 92 ti0KRzv3O9Xn7ERx27tPaobtWaGFLYQt8g57NCMhuv23aw8Sz1fYmwTUw60Rx7P5 93 42FdF8lOAn/AJvpfJfxXIO+9v7ADPIr//3+TxqRwAdM4K4btWkaKh61wyTe26gfT 94 MxzyYmECgYAnlU5zsGyiZqwoXVktkhtZrE7Qu0SoztzFb8KpvFNmMTPF1kAAYmJY 95 GIhbizeGJ3h4cUdozKmt8ZWIt6uFDEYCqEA7XF4RH75dW25x86mpIPO7iRl9eisY 96 IsLeMYqTIwXAwGx6Ka9v5LOL1kzcHQ2iVj6+QX+yoptSft1dYa9jOA== 97 -----END RSA PRIVATE KEY-----`) 98 99 const rsaPrivKeyID = "ee69e8e07a14756ad5ff0aca2336b37f86b0ac1710d1f3e94440081e080aecd7" 100 101 var ecPrivKeyFixture = []byte(`-----BEGIN EC PRIVATE KEY----- 102 MHcCAQEEINfxKtDH3ug7ZIQPDyeAzujCdhw36D+bf9ToPE1A7YEyoAoGCCqGSM49 103 AwEHoUQDQgAEUIH9AYtrcDFzZrFJBdJZkn21d+4cH3nzy2O6Q/ct4BjOBKa+WCdR 104 tPo78bA+C/7t81ADQO8Jqaj59W50rwoqDQ== 105 -----END EC PRIVATE KEY-----`) 106 107 const ecPrivKeyID = "46157cb0becf9c72c3219e11d4692424fef9bf4460812ccc8a71a3dfcafc7e60" 108 109 var testKeys = map[string][]byte{ 110 ecPrivKeyID: ecPrivKeyFixture, 111 rsaPrivKeyID: rsaPrivKeyFixture, 112 } 113 114 func TestLoadKeyFromPath(t *testing.T) { 115 skip.If(t, runtime.GOOS == "windows") 116 for keyID, keyBytes := range testKeys { 117 keyID, keyBytes := keyID, keyBytes 118 t.Run(fmt.Sprintf("load-key-id-%s-from-path", keyID), func(t *testing.T) { 119 privKeyFilepath := filepath.Join(t.TempDir(), "privkey.pem") 120 assert.NilError(t, os.WriteFile(privKeyFilepath, keyBytes, notary.PrivNoExecPerms)) 121 122 keyStorageDir := t.TempDir() 123 124 const passwd = "password" 125 cannedPasswordRetriever := passphrase.ConstantRetriever(passwd) 126 keyFileStore, err := storage.NewPrivateKeyFileStorage(keyStorageDir, notary.KeyExtension) 127 assert.NilError(t, err) 128 privKeyImporters := []trustmanager.Importer{keyFileStore} 129 130 // get the privKeyBytes 131 privKeyBytes, err := getPrivKeyBytesFromPath(privKeyFilepath) 132 assert.NilError(t, err) 133 134 // import the key to our keyStorageDir 135 assert.Check(t, loadPrivKeyBytesToStore(privKeyBytes, privKeyImporters, privKeyFilepath, "signer-name", cannedPasswordRetriever)) 136 137 // check that the appropriate ~/<trust_dir>/private/<key_id>.key file exists 138 expectedImportKeyPath := filepath.Join(keyStorageDir, notary.PrivDir, keyID+"."+notary.KeyExtension) 139 _, err = os.Stat(expectedImportKeyPath) 140 assert.NilError(t, err) 141 142 // verify the key content 143 from, _ := os.OpenFile(expectedImportKeyPath, os.O_RDONLY, notary.PrivExecPerms) 144 defer from.Close() 145 fromBytes, _ := io.ReadAll(from) 146 keyPEM, _ := pem.Decode(fromBytes) 147 assert.Check(t, is.Equal("signer-name", keyPEM.Headers["role"])) 148 // the default GUN is empty 149 assert.Check(t, is.Equal("", keyPEM.Headers["gun"])) 150 // assert encrypted header 151 assert.Check(t, is.Equal("ENCRYPTED PRIVATE KEY", keyPEM.Type)) 152 153 decryptedKey, err := tufutils.ParsePKCS8ToTufKey(keyPEM.Bytes, []byte(passwd)) 154 assert.NilError(t, err) 155 fixturePEM, _ := pem.Decode(keyBytes) 156 assert.Check(t, is.DeepEqual(fixturePEM.Bytes, decryptedKey.Private())) 157 }) 158 } 159 } 160 161 func TestLoadKeyTooPermissive(t *testing.T) { 162 skip.If(t, runtime.GOOS == "windows") 163 for keyID, keyBytes := range testKeys { 164 keyID, keyBytes := keyID, keyBytes 165 t.Run(fmt.Sprintf("load-key-id-%s-too-permissive", keyID), func(t *testing.T) { 166 privKeyDir := t.TempDir() 167 privKeyFilepath := filepath.Join(privKeyDir, "privkey477.pem") 168 assert.NilError(t, os.WriteFile(privKeyFilepath, keyBytes, 0o477)) 169 170 // import the key to our keyStorageDir 171 _, err := getPrivKeyBytesFromPath(privKeyFilepath) 172 expected := fmt.Sprintf("private key file %s must not be readable or writable by others", privKeyFilepath) 173 assert.Error(t, err, expected) 174 175 privKeyFilepath = filepath.Join(privKeyDir, "privkey667.pem") 176 assert.NilError(t, os.WriteFile(privKeyFilepath, keyBytes, 0o677)) 177 178 _, err = getPrivKeyBytesFromPath(privKeyFilepath) 179 expected = fmt.Sprintf("private key file %s must not be readable or writable by others", privKeyFilepath) 180 assert.Error(t, err, expected) 181 182 privKeyFilepath = filepath.Join(privKeyDir, "privkey777.pem") 183 assert.NilError(t, os.WriteFile(privKeyFilepath, keyBytes, 0o777)) 184 185 _, err = getPrivKeyBytesFromPath(privKeyFilepath) 186 expected = fmt.Sprintf("private key file %s must not be readable or writable by others", privKeyFilepath) 187 assert.Error(t, err, expected) 188 189 privKeyFilepath = filepath.Join(privKeyDir, "privkey400.pem") 190 assert.NilError(t, os.WriteFile(privKeyFilepath, keyBytes, 0o400)) 191 192 _, err = getPrivKeyBytesFromPath(privKeyFilepath) 193 assert.NilError(t, err) 194 195 privKeyFilepath = filepath.Join(privKeyDir, "privkey600.pem") 196 assert.NilError(t, os.WriteFile(privKeyFilepath, keyBytes, 0o600)) 197 198 _, err = getPrivKeyBytesFromPath(privKeyFilepath) 199 assert.NilError(t, err) 200 }) 201 } 202 } 203 204 var pubKeyFixture = []byte(`-----BEGIN PUBLIC KEY----- 205 MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUIH9AYtrcDFzZrFJBdJZkn21d+4c 206 H3nzy2O6Q/ct4BjOBKa+WCdRtPo78bA+C/7t81ADQO8Jqaj59W50rwoqDQ== 207 -----END PUBLIC KEY-----`) 208 209 func TestLoadPubKeyFailure(t *testing.T) { 210 skip.If(t, runtime.GOOS == "windows") 211 pubKeyDir := t.TempDir() 212 pubKeyFilepath := filepath.Join(pubKeyDir, "pubkey.pem") 213 assert.NilError(t, os.WriteFile(pubKeyFilepath, pubKeyFixture, notary.PrivNoExecPerms)) 214 keyStorageDir := t.TempDir() 215 216 const passwd = "password" 217 cannedPasswordRetriever := passphrase.ConstantRetriever(passwd) 218 keyFileStore, err := storage.NewPrivateKeyFileStorage(keyStorageDir, notary.KeyExtension) 219 assert.NilError(t, err) 220 privKeyImporters := []trustmanager.Importer{keyFileStore} 221 222 pubKeyBytes, err := getPrivKeyBytesFromPath(pubKeyFilepath) 223 assert.NilError(t, err) 224 225 // import the key to our keyStorageDir - it should fail 226 err = loadPrivKeyBytesToStore(pubKeyBytes, privKeyImporters, pubKeyFilepath, "signer-name", cannedPasswordRetriever) 227 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) 228 assert.Error(t, err, expected) 229 }