github.com/prysmaticlabs/prysm@v1.4.4/validator/keymanager/remote/keymanager_test.go (about)

     1  package remote
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"os"
    11  	"strconv"
    12  	"testing"
    13  
    14  	"github.com/golang/mock/gomock"
    15  	validatorpb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
    16  	"github.com/prysmaticlabs/prysm/shared/bls"
    17  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    18  	"github.com/prysmaticlabs/prysm/shared/event"
    19  	"github.com/prysmaticlabs/prysm/shared/mock"
    20  	"github.com/prysmaticlabs/prysm/shared/params"
    21  	"github.com/prysmaticlabs/prysm/shared/testutil/assert"
    22  	"github.com/prysmaticlabs/prysm/shared/testutil/require"
    23  	"github.com/prysmaticlabs/prysm/validator/keymanager"
    24  	logTest "github.com/sirupsen/logrus/hooks/test"
    25  )
    26  
    27  var validClientCert = `-----BEGIN CERTIFICATE-----
    28  MIIEITCCAgmgAwIBAgIQXUJWQZgVO4IX+zlWGI1/mTANBgkqhkiG9w0BAQsFADAU
    29  MRIwEAYDVQQDEwlBdHRlc3RhbnQwHhcNMjAwMzE3MDgwNjU3WhcNMjEwOTE3MDc1
    30  OTUyWjASMRAwDgYDVQQDEwdjbGllbnQxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
    31  MIIBCgKCAQEAsc977g16Tan2j7YuA+zQOlDntb4Bkfs4sDOznOEvnozHwRZOgfcP
    32  jVcA9AS5eZOGIRrsTssptrgVNDPoIHWoKk7LAKyyLM3dGp5PWeyMBoQA5cq+yPAT
    33  4JkJpDnBFfwxXB99osJH0z3jSTRa62CSVvPRBisK4B9AlLQfcleEQlKJugy9tOAj
    34  G7zodwEi+J4AYQHmOiwL38ZsKq9We5y4HMQ0E7de0FoU5QHrtuPNrTuwVwrq825l
    35  cEAAFey6Btngx+sziysPHWHYOq4xOZ1UPBApeaAFLguzusc/4VwM7kzRNr4VOD8a
    36  eC3CtKLhBBVVxHI5ZlaHS+YylNGYD4+FxQIDAQABo3EwbzAOBgNVHQ8BAf8EBAMC
    37  A7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBQDGCE0
    38  3k4rHzB+Ycf3pt1MzeDPgzAfBgNVHSMEGDAWgBScIYZa4dQBIW/gVwR0ctGCuHhe
    39  9jANBgkqhkiG9w0BAQsFAAOCAgEAHG/EfvqIwbhYfci+zRCYC7aQPuvhivJblBwN
    40  mbXo2qsxvje1hcKm0ptJLOy/cjJzeLJYREhQlXDPRJC/xgELnbXRjgag82r35+pf
    41  wVJwP6Yw53VCM3o0QKsUrKyMm4sAijOBrJyqpB5untAieZsry5Bfj0S4YobbtdJa
    42  VsEioU07fVVczf5lYN0XrLgRnXq3LMkTiZ6drFiqLkwmXQZVxNujmcaFSm7yCALl
    43  EdhYNmaqedS5me5UOGxwPacrsZwWF9dvMsl3OswgTcaGdsUtx2/q+S2vbZUAM/Gw
    44  qaTanDfvVtVTF7KzVN9hiqKe4mO0HHHK2HWJYBLdRJjInOgRW+53hCmUhLxD+Dq+
    45  31jLKxn/Y4hyH9E+55b1sJHCFpsbEtVD53fojiH2C/uLbhq4Wr1PXgOoxzf2KeSQ
    46  B3ENu8C4b6AlNhqOnz5zeDcx8Ug0vMfVDAwf6RAYMG5b/MoWNKcLNXhk8H1nbVkt
    47  16ppjh6I27JqfNqfP2J/p3BF++ZugZuWfN9DRaJ6UPz+yyF7eW8fyDAQNl7LS0Kh
    48  8PlF5cYvyIIKVHe38Mn8ZAWboKUs0xNv2vhA9V/4Q1ZzAEkXjmbk8H26sjGvJnvg
    49  Lgm/+6LVWR4EnUlU8aEWASEpTWq2lSRF3ZOvNstHnufyiDfcwDcl/IKKQiVQQ3mX
    50  tw8Jf74=
    51  -----END CERTIFICATE-----`
    52  
    53  // skipcq: SCT-1000
    54  var validClientKey = `-----BEGIN RSA PRIVATE KEY-----
    55  MIIEpAIBAAKCAQEAsc977g16Tan2j7YuA+zQOlDntb4Bkfs4sDOznOEvnozHwRZO
    56  gfcPjVcA9AS5eZOGIRrsTssptrgVNDPoIHWoKk7LAKyyLM3dGp5PWeyMBoQA5cq+
    57  yPAT4JkJpDnBFfwxXB99osJH0z3jSTRa62CSVvPRBisK4B9AlLQfcleEQlKJugy9
    58  tOAjG7zodwEi+J4AYQHmOiwL38ZsKq9We5y4HMQ0E7de0FoU5QHrtuPNrTuwVwrq
    59  825lcEAAFey6Btngx+sziysPHWHYOq4xOZ1UPBApeaAFLguzusc/4VwM7kzRNr4V
    60  OD8aeC3CtKLhBBVVxHI5ZlaHS+YylNGYD4+FxQIDAQABAoIBAQCjV2MVcDQmHDhw
    61  FH95A5bVu3TgM8flfs64rwYU25iPIexuqDs+kOMsh/xMLfrkgGz7BGyIhYGwZLK1
    62  3ekjyHHPS8qYuAyFtCelSEDE7tRDOAhLEFDq7gCUloGQ561EsQP3CMa1OZwZpgSh
    63  PwM2ruRAFIK0E95NvOfqsv0gYN0Svo7hYjNsvW6ok/ZGMyN2ikcRR04wGOFOGjfT
    64  xTmfURc9ejnOjHAOqLTpToPwM1/gWWR2iMQefC4njy4MO2BXqOPUmHxmmR4PYhu2
    65  8EcKbyRs+/fvL3GgD3VAlOe5vnkfBzssQhHmexgSk5lHZrcSxUGXYGrYKPAeV2mk
    66  5HRBWp0RAoGBAOUn5w+NCAugcTGP0hfNlyGXsXqUZvnMyFWvUcxgzgPlJyEyDnKn
    67  aIb1DFOF2HckCfLZdrHqqgaF6K3TDvW9BgSKIsvISpo1S95ZPD6DKUo6YQ10CQRW
    68  q/ZZVbxtFksVgFRGYpCVmPNULmx7CiXDT1b/suwNMAwCZwiNPTSvKQVLAoGBAMaj
    69  zDo1/eepRslqnz5s8hh7dGEjfG/ZJcLgAJAxCyAgnIP4Tls7QkNhCVp9LcN6i1bc
    70  CnT6AIuZRXSJWEdp4k2QnVFUmh9Q5MGgwrKYSY5M/1puTISlF1yQ8J6FX8BlDVmy
    71  4dyaSyC0RIvgBzF9/KBDxxmJcHgGQ0awLeeyl4cvAoGBAN83FS3itLmOmXQrofyp
    72  uNNyDeFXeU9OmL5OPqGUkljc+Favib9JLtp3DIC3WfoD0uUJy0LXULN18QaRFnts
    73  mtYFMIvMGE9KJxL5XWOPI8M4Rp1yL+5X9r3Km2cl45dT5GMzBIPOFOTBVU86MtJC
    74  A6C9Bi5FUk4AcRi1a69MB+stAoGAWNiwoyS9IV38dGCFQ4W1LzAg2MXnhZuJoUVR
    75  2yykfkU33Gs2mOXDeKGxblDpJDLumfYnkzSzA72VbE92NdLtTqYtR1Bg8zraZqTC
    76  EOG+nLBh0o/dF8ND1LpbdXvQXRyVwRYaofI9Qi5/LlUQwplIYmKObiSkMnsSok5w
    77  6d5emi8CgYBjtUihOFaAmgqkTHOn4j4eKS1O7/H8QQSVe5M0bocmAIbgJ4At3GnI
    78  E1JcIY2SZtSwAWs6aQPGE42gwsNCCsQWdJNtViO23JbCwlcPToC4aDfc0JJNaYqp
    79  oVV7C5jmJh9VRd2tXIXIZMMNOfThfNf2qDQuJ1S2t5KugozFiRsHUg==
    80  -----END RSA PRIVATE KEY-----`
    81  
    82  func TestNewRemoteKeymanager(t *testing.T) {
    83  	tests := []struct {
    84  		name       string
    85  		opts       *KeymanagerOpts
    86  		clientCert string
    87  		clientKey  string
    88  		caCert     string
    89  		err        string
    90  	}{
    91  		{
    92  			name: "NoCertificates",
    93  			opts: &KeymanagerOpts{
    94  				RemoteCertificate: nil,
    95  			},
    96  			err: "certificate configuration is missing",
    97  		},
    98  		{
    99  			name: "NoClientCertificate",
   100  			opts: &KeymanagerOpts{
   101  				RemoteCertificate: &CertificateConfig{
   102  					RequireTls: true,
   103  				},
   104  			},
   105  			err: "client certificate is required",
   106  		},
   107  		{
   108  			name: "NoClientKey",
   109  			opts: &KeymanagerOpts{
   110  				RemoteCertificate: &CertificateConfig{
   111  					RequireTls:     true,
   112  					ClientCertPath: "/foo/client.crt",
   113  					ClientKeyPath:  "",
   114  				},
   115  			},
   116  			err: "client key is required",
   117  		},
   118  		{
   119  			name: "MissingClientKey",
   120  			opts: &KeymanagerOpts{
   121  				RemoteCertificate: &CertificateConfig{
   122  					RequireTls:     true,
   123  					ClientCertPath: "/foo/client.crt",
   124  					ClientKeyPath:  "/foo/client.key",
   125  					CACertPath:     "",
   126  				},
   127  			},
   128  			err: "failed to obtain client's certificate and/or key",
   129  		},
   130  		{
   131  			name:       "BadClientCert",
   132  			clientCert: `bad`,
   133  			clientKey:  validClientKey,
   134  			opts: &KeymanagerOpts{
   135  				RemoteCertificate: &CertificateConfig{
   136  					RequireTls: true,
   137  				},
   138  			},
   139  			err: "failed to obtain client's certificate and/or key: tls: failed to find any PEM data in certificate input",
   140  		},
   141  		{
   142  			name:       "BadClientKey",
   143  			clientCert: validClientCert,
   144  			clientKey:  `bad`,
   145  			opts: &KeymanagerOpts{
   146  				RemoteCertificate: &CertificateConfig{
   147  					RequireTls: true,
   148  				},
   149  			},
   150  			err: "failed to obtain client's certificate and/or key: tls: failed to find any PEM data in key input",
   151  		},
   152  		{
   153  			name:       "MissingCACert",
   154  			clientCert: validClientCert,
   155  			clientKey:  validClientKey,
   156  			opts: &KeymanagerOpts{
   157  				RemoteCertificate: &CertificateConfig{
   158  					RequireTls: true,
   159  					CACertPath: `bad`,
   160  				},
   161  			},
   162  			err: "failed to obtain server's CA certificate: open bad: no such file or directory",
   163  		},
   164  	}
   165  
   166  	for _, test := range tests {
   167  		t.Run(test.name, func(t *testing.T) {
   168  			if test.caCert != "" || test.clientCert != "" || test.clientKey != "" {
   169  				dir := fmt.Sprintf("%s/%s", t.TempDir(), test.name)
   170  				require.NoError(t, os.MkdirAll(dir, 0777))
   171  				if test.caCert != "" {
   172  					caCertPath := fmt.Sprintf("%s/ca.crt", dir)
   173  					err := ioutil.WriteFile(caCertPath, []byte(test.caCert), params.BeaconIoConfig().ReadWritePermissions)
   174  					require.NoError(t, err, "Failed to write CA certificate")
   175  					test.opts.RemoteCertificate.CACertPath = caCertPath
   176  				}
   177  				if test.clientCert != "" {
   178  					clientCertPath := fmt.Sprintf("%s/client.crt", dir)
   179  					err := ioutil.WriteFile(clientCertPath, []byte(test.clientCert), params.BeaconIoConfig().ReadWritePermissions)
   180  					require.NoError(t, err, "Failed to write client certificate")
   181  					test.opts.RemoteCertificate.ClientCertPath = clientCertPath
   182  				}
   183  				if test.clientKey != "" {
   184  					clientKeyPath := fmt.Sprintf("%s/client.key", dir)
   185  					err := ioutil.WriteFile(clientKeyPath, []byte(test.clientKey), params.BeaconIoConfig().ReadWritePermissions)
   186  					require.NoError(t, err, "Failed to write client key")
   187  					test.opts.RemoteCertificate.ClientKeyPath = clientKeyPath
   188  				}
   189  			}
   190  			_, err := NewKeymanager(context.Background(), &SetupConfig{Opts: test.opts, MaxMessageSize: 1})
   191  			if test.err == "" {
   192  				require.NoError(t, err)
   193  			} else {
   194  				require.ErrorContains(t, test.err, err)
   195  			}
   196  		})
   197  	}
   198  }
   199  
   200  func TestNewRemoteKeymanager_TlsDisabled(t *testing.T) {
   201  	opts := &KeymanagerOpts{
   202  		RemoteCertificate: &CertificateConfig{
   203  			RequireTls: false,
   204  		},
   205  	}
   206  	_, err := NewKeymanager(context.Background(), &SetupConfig{Opts: opts, MaxMessageSize: 1})
   207  	assert.NoError(t, err)
   208  }
   209  
   210  func TestRemoteKeymanager_Sign(t *testing.T) {
   211  	ctrl := gomock.NewController(t)
   212  	m := mock.NewMockRemoteSignerClient(ctrl)
   213  	k := &Keymanager{
   214  		client: m,
   215  	}
   216  
   217  	// Expect error handling to work.
   218  	m.EXPECT().Sign(
   219  		gomock.Any(), // ctx
   220  		gomock.Any(), // epoch
   221  	).Return(nil, errors.New("could not sign"))
   222  	_, err := k.Sign(context.Background(), nil)
   223  	require.ErrorContains(t, "could not sign", err)
   224  
   225  	// Expected proper error handling for signing response statuses.
   226  	m.EXPECT().Sign(
   227  		gomock.Any(), // ctx
   228  		gomock.Any(), // epoch
   229  	).Return(&validatorpb.SignResponse{
   230  		Status: validatorpb.SignResponse_FAILED,
   231  	}, nil /*err*/)
   232  	_, err = k.Sign(context.Background(), nil)
   233  	if err == nil {
   234  		t.Fatal(err)
   235  	}
   236  	if err != ErrSigningFailed {
   237  		t.Errorf("Expected %v, received %v", ErrSigningFailed, err)
   238  	}
   239  	m.EXPECT().Sign(
   240  		gomock.Any(), // ctx
   241  		gomock.Any(), // epoch
   242  	).Return(&validatorpb.SignResponse{
   243  		Status: validatorpb.SignResponse_DENIED,
   244  	}, nil /*err*/)
   245  	_, err = k.Sign(context.Background(), nil)
   246  	if err == nil {
   247  		t.Fatal(err)
   248  	}
   249  	if err != ErrSigningDenied {
   250  		t.Errorf("Expected %v, received %v", ErrSigningDenied, err)
   251  	}
   252  
   253  	// Expected signing success.
   254  	randKey, err := bls.RandKey()
   255  	require.NoError(t, err)
   256  	data := []byte("hello-world")
   257  	sig := randKey.Sign(data)
   258  	m.EXPECT().Sign(
   259  		gomock.Any(), // ctx
   260  		gomock.Any(), // epoch
   261  	).Return(&validatorpb.SignResponse{
   262  		Status:    validatorpb.SignResponse_SUCCEEDED,
   263  		Signature: sig.Marshal(),
   264  	}, nil /*err*/)
   265  	resp, err := k.Sign(context.Background(), nil)
   266  	require.NoError(t, err)
   267  	assert.DeepEqual(t, sig.Marshal(), resp.Marshal())
   268  }
   269  
   270  func TestRemoteKeymanager_FetchValidatingPublicKeys(t *testing.T) {
   271  	ctrl := gomock.NewController(t)
   272  	m := mock.NewMockRemoteSignerClient(ctrl)
   273  	k := &Keymanager{
   274  		client:              m,
   275  		accountsChangedFeed: new(event.Feed),
   276  	}
   277  
   278  	// Expect error handling to work.
   279  	m.EXPECT().ListValidatingPublicKeys(
   280  		gomock.Any(), // ctx
   281  		gomock.Any(), // epoch
   282  	).Return(nil, errors.New("could not fetch keys"))
   283  	_, err := k.FetchValidatingPublicKeys(context.Background())
   284  	require.ErrorContains(t, "could not fetch keys", err)
   285  
   286  	// Expect an empty response to return empty keys.
   287  	m.EXPECT().ListValidatingPublicKeys(
   288  		gomock.Any(), // ctx
   289  		gomock.Any(), // epoch
   290  	).Return(&validatorpb.ListPublicKeysResponse{
   291  		ValidatingPublicKeys: make([][]byte, 0),
   292  	}, nil /*err*/)
   293  	keys, err := k.FetchValidatingPublicKeys(context.Background())
   294  	require.NoError(t, err)
   295  	assert.Equal(t, 0, len(keys), "Expected empty response")
   296  
   297  	numKeys := 10
   298  	pubKeys := make([][]byte, numKeys)
   299  	for i := 0; i < numKeys; i++ {
   300  		key := make([]byte, 48)
   301  		copy(key, strconv.Itoa(i))
   302  		pubKeys[i] = key
   303  	}
   304  	m.EXPECT().ListValidatingPublicKeys(
   305  		gomock.Any(), // ctx
   306  		gomock.Any(), // epoch
   307  	).Return(&validatorpb.ListPublicKeysResponse{
   308  		ValidatingPublicKeys: pubKeys,
   309  	}, nil /*err*/)
   310  	keys, err = k.FetchValidatingPublicKeys(context.Background())
   311  	require.NoError(t, err)
   312  	rawKeys := make([][]byte, len(keys))
   313  	for i := 0; i < len(rawKeys); i++ {
   314  		rawKeys[i] = keys[i][:]
   315  	}
   316  	assert.DeepEqual(t, pubKeys, rawKeys)
   317  }
   318  
   319  func TestUnmarshalOptionsFile_DefaultRequireTls(t *testing.T) {
   320  	optsWithoutTls := struct {
   321  		RemoteCertificate struct {
   322  			ClientCertPath string
   323  			ClientKeyPath  string
   324  			CACertPath     string
   325  		}
   326  	}{}
   327  	var buffer bytes.Buffer
   328  	b, err := json.Marshal(optsWithoutTls)
   329  	require.NoError(t, err)
   330  	_, err = buffer.Write(b)
   331  	require.NoError(t, err)
   332  	r := ioutil.NopCloser(&buffer)
   333  
   334  	opts, err := UnmarshalOptionsFile(r)
   335  	assert.NoError(t, err)
   336  	assert.Equal(t, true, opts.RemoteCertificate.RequireTls)
   337  }
   338  
   339  func TestReloadPublicKeys(t *testing.T) {
   340  	hook := logTest.NewGlobal()
   341  	ctx := context.Background()
   342  	ctrl := gomock.NewController(t)
   343  	m := mock.NewMockRemoteSignerClient(ctrl)
   344  
   345  	k := &Keymanager{
   346  		client:              m,
   347  		accountsChangedFeed: new(event.Feed),
   348  		orderedPubKeys:      [][48]byte{bytesutil.ToBytes48([]byte("100"))},
   349  	}
   350  
   351  	// Add key
   352  	m.EXPECT().ListValidatingPublicKeys(
   353  		gomock.Any(), // ctx
   354  		gomock.Any(), // epoch
   355  	).Return(&validatorpb.ListPublicKeysResponse{
   356  		// Return keys in reverse order to verify ordering
   357  		ValidatingPublicKeys: [][]byte{[]byte("200"), []byte("100")},
   358  	}, nil /* err */)
   359  
   360  	keys, err := k.ReloadPublicKeys(ctx)
   361  	require.NoError(t, err)
   362  	assert.DeepEqual(t, [][48]byte{bytesutil.ToBytes48([]byte("100")), bytesutil.ToBytes48([]byte("200"))}, k.orderedPubKeys)
   363  	assert.DeepEqual(t, keys, k.orderedPubKeys)
   364  	assert.LogsContain(t, hook, keymanager.KeysReloaded)
   365  
   366  	hook.Reset()
   367  
   368  	// Remove key
   369  	m.EXPECT().ListValidatingPublicKeys(
   370  		gomock.Any(), // ctx
   371  		gomock.Any(), // epoch
   372  	).Return(&validatorpb.ListPublicKeysResponse{
   373  		ValidatingPublicKeys: [][]byte{[]byte("200")},
   374  	}, nil /* err */)
   375  
   376  	keys, err = k.ReloadPublicKeys(ctx)
   377  	require.NoError(t, err)
   378  	assert.DeepEqual(t, [][48]byte{bytesutil.ToBytes48([]byte("200"))}, k.orderedPubKeys)
   379  	assert.DeepEqual(t, keys, k.orderedPubKeys)
   380  	assert.LogsContain(t, hook, keymanager.KeysReloaded)
   381  
   382  	hook.Reset()
   383  
   384  	// Change key
   385  	m.EXPECT().ListValidatingPublicKeys(
   386  		gomock.Any(), // ctx
   387  		gomock.Any(), // epoch
   388  	).Return(&validatorpb.ListPublicKeysResponse{
   389  		ValidatingPublicKeys: [][]byte{[]byte("300")},
   390  	}, nil /* err */)
   391  
   392  	keys, err = k.ReloadPublicKeys(ctx)
   393  	require.NoError(t, err)
   394  	assert.DeepEqual(t, [][48]byte{bytesutil.ToBytes48([]byte("300"))}, k.orderedPubKeys)
   395  	assert.DeepEqual(t, keys, k.orderedPubKeys)
   396  	assert.LogsContain(t, hook, keymanager.KeysReloaded)
   397  
   398  	hook.Reset()
   399  
   400  	// No change
   401  	m.EXPECT().ListValidatingPublicKeys(
   402  		gomock.Any(), // ctx
   403  		gomock.Any(), // epoch
   404  	).Return(&validatorpb.ListPublicKeysResponse{
   405  		ValidatingPublicKeys: [][]byte{[]byte("300")},
   406  	}, nil /* err */)
   407  
   408  	keys, err = k.ReloadPublicKeys(ctx)
   409  	require.NoError(t, err)
   410  	assert.DeepEqual(t, [][48]byte{bytesutil.ToBytes48([]byte("300"))}, k.orderedPubKeys)
   411  	assert.DeepEqual(t, keys, k.orderedPubKeys)
   412  	assert.LogsDoNotContain(t, hook, keymanager.KeysReloaded)
   413  }