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  }