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 }