github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/ca/keyreadwriter_test.go (about) 1 package ca_test 2 3 import ( 4 "encoding/pem" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "testing" 10 11 "github.com/docker/swarmkit/ca" 12 "github.com/docker/swarmkit/ca/keyutils" 13 "github.com/docker/swarmkit/ca/pkcs8" 14 "github.com/docker/swarmkit/ca/testutils" 15 "github.com/stretchr/testify/require" 16 ) 17 18 // can read and write tls keys that aren't encrypted, and that are encrypted. without 19 // a pem header manager, the headers are all preserved and not overwritten 20 func TestKeyReadWriter(t *testing.T) { 21 cert, key, err := testutils.CreateRootCertAndKey("cn") 22 require.NoError(t, err) 23 24 expectedKey := key 25 26 tempdir, err := ioutil.TempDir("", "KeyReadWriter") 27 require.NoError(t, err) 28 defer os.RemoveAll(tempdir) 29 30 path := ca.NewConfigPaths(filepath.Join(tempdir, "subdir")) // to make sure subdirectories are created 31 32 checkCanReadWithKEK := func(kek []byte) *ca.KeyReadWriter { 33 k := ca.NewKeyReadWriter(path.Node, kek, nil) 34 readCert, readKey, err := k.Read() 35 require.NoError(t, err) 36 require.Equal(t, cert, readCert) 37 require.Equal(t, expectedKey, readKey, "Expected %s, Got %s", string(expectedKey), string(readKey)) 38 return k 39 } 40 41 k := ca.NewKeyReadWriter(path.Node, nil, nil) 42 43 // can't read things that don't exist 44 _, _, err = k.Read() 45 require.Error(t, err) 46 47 // can write an unencrypted key with no updates 48 require.NoError(t, k.Write(cert, expectedKey, nil)) 49 50 // can read unencrypted 51 k = checkCanReadWithKEK(nil) 52 _, kekData := k.GetCurrentState() 53 require.EqualValues(t, 0, kekData.Version) // the first version was 0 54 55 // write a key with headers to the key to make sure they're cleaned 56 keyBlock, _ := pem.Decode(expectedKey) 57 require.NotNil(t, keyBlock) 58 keyBlock.Headers = map[string]string{"hello": "world"} 59 expectedKey = pem.EncodeToMemory(keyBlock) 60 // write a version, but that's not what we'd expect back once we read 61 keyBlock.Headers["kek-version"] = "8" 62 require.NoError(t, ioutil.WriteFile(path.Node.Key, pem.EncodeToMemory(keyBlock), 0600)) 63 64 // if a kek is provided, we can still read unencrypted keys, and read 65 // the provided version 66 k = checkCanReadWithKEK([]byte("original kek")) 67 _, kekData = k.GetCurrentState() 68 require.EqualValues(t, 8, kekData.Version) 69 70 // we can update the kek and write at the same time 71 require.NoError(t, k.Write(cert, key, &ca.KEKData{KEK: []byte("new kek!"), Version: 3})) 72 73 // the same kek can still read, and will continue to write with this key if 74 // no further kek updates are provided 75 _, _, err = k.Read() 76 require.NoError(t, err) 77 require.NoError(t, k.Write(cert, expectedKey, nil)) 78 79 expectedKey = key 80 81 // without the right kek, we can't read 82 k = ca.NewKeyReadWriter(path.Node, []byte("original kek"), nil) 83 _, _, err = k.Read() 84 require.Error(t, err) 85 86 // same new key, just for sanity 87 k = checkCanReadWithKEK([]byte("new kek!")) 88 _, kekData = k.GetCurrentState() 89 require.EqualValues(t, 3, kekData.Version) 90 91 // we can also change the kek back to nil, which means the key is unencrypted 92 require.NoError(t, k.Write(cert, key, &ca.KEKData{KEK: nil})) 93 k = checkCanReadWithKEK(nil) 94 _, kekData = k.GetCurrentState() 95 require.EqualValues(t, 0, kekData.Version) 96 } 97 98 type testHeaders struct { 99 setHeaders func(map[string]string, ca.KEKData) (ca.PEMKeyHeaders, error) 100 newHeaders func(ca.KEKData) (map[string]string, error) 101 } 102 103 func (p testHeaders) UnmarshalHeaders(h map[string]string, k ca.KEKData) (ca.PEMKeyHeaders, error) { 104 if p.setHeaders != nil { 105 return p.setHeaders(h, k) 106 } 107 return nil, fmt.Errorf("set header error") 108 } 109 110 func (p testHeaders) MarshalHeaders(k ca.KEKData) (map[string]string, error) { 111 if p.newHeaders != nil { 112 return p.newHeaders(k) 113 } 114 return nil, fmt.Errorf("update header error") 115 } 116 117 func (p testHeaders) UpdateKEK(ca.KEKData, ca.KEKData) ca.PEMKeyHeaders { 118 return p 119 } 120 121 // KeyReaderWriter makes a call to a get headers updater, if write is called, 122 // and set headers, if read is called. The KEK version header is always preserved 123 // no matter what. 124 func TestKeyReadWriterWithPemHeaderManager(t *testing.T) { 125 cert, key, err := testutils.CreateRootCertAndKey("cn") 126 require.NoError(t, err) 127 128 // write a key with headers to the key to make sure it gets overwritten 129 keyBlock, _ := pem.Decode(key) 130 require.NotNil(t, keyBlock) 131 keyBlock.Headers = map[string]string{"hello": "world"} 132 key = pem.EncodeToMemory(keyBlock) 133 134 tempdir, err := ioutil.TempDir("", "KeyReadWriter") 135 require.NoError(t, err) 136 defer os.RemoveAll(tempdir) 137 138 path := ca.NewConfigPaths(filepath.Join(tempdir, "subdir")) // to make sure subdirectories are created 139 140 // if if getting new headers fail, writing a key fails, and the key does not rotate 141 var count int 142 badKEKData := ca.KEKData{KEK: []byte("failed kek"), Version: 3} 143 k := ca.NewKeyReadWriter(path.Node, nil, testHeaders{newHeaders: func(k ca.KEKData) (map[string]string, error) { 144 if count == 0 { 145 count++ 146 require.Equal(t, badKEKData, k) 147 return nil, fmt.Errorf("fail") 148 } 149 require.Equal(t, ca.KEKData{}, k) 150 return nil, nil 151 }}) 152 // first write will fail 153 require.Error(t, k.Write(cert, key, &badKEKData)) 154 // the stored kek data will be not be updated because the write failed 155 _, kekData := k.GetCurrentState() 156 require.Equal(t, ca.KEKData{}, kekData) 157 // second write will succeed, using the original kek (nil) 158 require.NoError(t, k.Write(cert, key, nil)) 159 160 var ( 161 headers map[string]string 162 kek ca.KEKData 163 ) 164 165 // if setting headers fail, reading fails 166 k = ca.NewKeyReadWriter(path.Node, nil, testHeaders{setHeaders: func(map[string]string, ca.KEKData) (ca.PEMKeyHeaders, error) { 167 return nil, fmt.Errorf("nope") 168 }}) 169 _, _, err = k.Read() 170 require.Error(t, err) 171 172 k = ca.NewKeyReadWriter(path.Node, nil, testHeaders{setHeaders: func(h map[string]string, k ca.KEKData) (ca.PEMKeyHeaders, error) { 173 headers = h 174 kek = k 175 return testHeaders{}, nil 176 }}) 177 178 _, _, err = k.Read() 179 require.NoError(t, err) 180 require.Equal(t, ca.KEKData{}, kek) 181 require.Equal(t, keyBlock.Headers, headers) 182 183 // writing new headers is called with existing headers, and will write a key that has the headers 184 // returned by the header update function 185 k = ca.NewKeyReadWriter(path.Node, []byte("oldKek"), testHeaders{newHeaders: func(kek ca.KEKData) (map[string]string, error) { 186 require.Equal(t, []byte("newKEK"), kek.KEK) 187 return map[string]string{"updated": "headers"}, nil 188 }}) 189 require.NoError(t, k.Write(cert, key, &ca.KEKData{KEK: []byte("newKEK"), Version: 2})) 190 191 // make sure headers were correctly set 192 k = ca.NewKeyReadWriter(path.Node, []byte("newKEK"), testHeaders{setHeaders: func(h map[string]string, k ca.KEKData) (ca.PEMKeyHeaders, error) { 193 headers = h 194 kek = k 195 return testHeaders{}, nil 196 }}) 197 _, _, err = k.Read() 198 require.NoError(t, err) 199 require.Equal(t, ca.KEKData{KEK: []byte("newKEK"), Version: 2}, kek) 200 201 _, kekData = k.GetCurrentState() 202 require.Equal(t, kek, kekData) 203 require.Equal(t, map[string]string{"updated": "headers"}, headers) 204 } 205 206 func TestKeyReadWriterViewAndUpdateHeaders(t *testing.T) { 207 cert, key, err := testutils.CreateRootCertAndKey("cn") 208 require.NoError(t, err) 209 210 tempdir, err := ioutil.TempDir("", "KeyReadWriter") 211 require.NoError(t, err) 212 defer os.RemoveAll(tempdir) 213 214 path := ca.NewConfigPaths(filepath.Join(tempdir)) 215 216 // write a key with headers to the key to make sure it gets passed when reading/writing headers 217 keyBlock, _ := pem.Decode(key) 218 require.NotNil(t, keyBlock) 219 keyBlock.Headers = map[string]string{"hello": "world"} 220 key = pem.EncodeToMemory(keyBlock) 221 require.NoError(t, ioutil.WriteFile(path.Node.Cert, cert, 0644)) 222 require.NoError(t, ioutil.WriteFile(path.Node.Key, key, 0600)) 223 224 // if the update headers callback function fails, updating headers fails 225 k := ca.NewKeyReadWriter(path.Node, nil, nil) 226 err = k.ViewAndUpdateHeaders(func(h ca.PEMKeyHeaders) (ca.PEMKeyHeaders, error) { 227 require.Nil(t, h) 228 return nil, fmt.Errorf("nope") 229 }) 230 require.Error(t, err) 231 require.Equal(t, "nope", err.Error()) 232 233 // updating headers succeed and is called with the latest kek data 234 err = k.ViewAndUpdateHeaders(func(h ca.PEMKeyHeaders) (ca.PEMKeyHeaders, error) { 235 require.Nil(t, h) 236 return testHeaders{newHeaders: func(kek ca.KEKData) (map[string]string, error) { 237 return map[string]string{"updated": "headers"}, nil 238 }}, nil 239 }) 240 require.NoError(t, err) 241 242 k = ca.NewKeyReadWriter(path.Node, nil, testHeaders{setHeaders: func(h map[string]string, k ca.KEKData) (ca.PEMKeyHeaders, error) { 243 require.Equal(t, map[string]string{"updated": "headers"}, h) 244 require.Equal(t, ca.KEKData{}, k) 245 return testHeaders{}, nil 246 }}) 247 _, _, err = k.Read() 248 require.NoError(t, err) 249 250 // we can also update headers on an encrypted key 251 k = ca.NewKeyReadWriter(path.Node, []byte("kek"), nil) 252 require.NoError(t, k.Write(cert, key, nil)) 253 254 err = k.ViewAndUpdateHeaders(func(h ca.PEMKeyHeaders) (ca.PEMKeyHeaders, error) { 255 require.Nil(t, h) 256 return testHeaders{newHeaders: func(kek ca.KEKData) (map[string]string, error) { 257 require.Equal(t, ca.KEKData{KEK: []byte("kek")}, kek) 258 return map[string]string{"updated": "headers"}, nil 259 }}, nil 260 }) 261 require.NoError(t, err) 262 263 k = ca.NewKeyReadWriter(path.Node, []byte("kek"), testHeaders{setHeaders: func(h map[string]string, k ca.KEKData) (ca.PEMKeyHeaders, error) { 264 require.Equal(t, map[string]string{"updated": "headers"}, h) 265 require.Equal(t, ca.KEKData{KEK: []byte("kek")}, k) 266 return testHeaders{}, nil 267 }}) 268 _, _, err = k.Read() 269 require.NoError(t, err) 270 } 271 272 func TestKeyReadWriterViewAndRotateKEK(t *testing.T) { 273 cert, key, err := testutils.CreateRootCertAndKey("cn") 274 require.NoError(t, err) 275 276 tempdir, err := ioutil.TempDir("", "KeyReadWriter") 277 require.NoError(t, err) 278 defer os.RemoveAll(tempdir) 279 280 path := ca.NewConfigPaths(filepath.Join(tempdir)) 281 282 // write a key with headers to the key to make sure it gets passed when reading/writing headers 283 keyBlock, _ := pem.Decode(key) 284 require.NotNil(t, keyBlock) 285 keyBlock.Headers = map[string]string{"hello": "world"} 286 key = pem.EncodeToMemory(keyBlock) 287 require.NoError(t, ca.NewKeyReadWriter(path.Node, nil, nil).Write(cert, key, nil)) 288 289 // if if getting new kek and headers fail, rotating a KEK fails, and the kek does not rotate 290 k := ca.NewKeyReadWriter(path.Node, nil, nil) 291 require.Error(t, k.ViewAndRotateKEK(func(k ca.KEKData, h ca.PEMKeyHeaders) (ca.KEKData, ca.PEMKeyHeaders, error) { 292 require.Equal(t, ca.KEKData{}, k) 293 require.Nil(t, h) 294 return ca.KEKData{}, nil, fmt.Errorf("Nope") 295 })) 296 297 // writing new headers will write a key that has the headers returned by the header update function 298 k = ca.NewKeyReadWriter(path.Node, []byte("oldKEK"), nil) 299 require.NoError(t, k.ViewAndRotateKEK(func(k ca.KEKData, h ca.PEMKeyHeaders) (ca.KEKData, ca.PEMKeyHeaders, error) { 300 require.Equal(t, ca.KEKData{KEK: []byte("oldKEK")}, k) 301 require.Nil(t, h) 302 return ca.KEKData{KEK: []byte("newKEK"), Version: uint64(2)}, 303 testHeaders{newHeaders: func(kek ca.KEKData) (map[string]string, error) { 304 require.Equal(t, []byte("newKEK"), kek.KEK) 305 return map[string]string{"updated": "headers"}, nil 306 }}, nil 307 })) 308 309 // ensure the key has been re-encrypted and we can read it 310 k = ca.NewKeyReadWriter(path.Node, nil, nil) 311 _, _, err = k.Read() 312 require.Error(t, err) 313 314 var headers map[string]string 315 316 k = ca.NewKeyReadWriter(path.Node, []byte("newKEK"), testHeaders{setHeaders: func(h map[string]string, _ ca.KEKData) (ca.PEMKeyHeaders, error) { 317 headers = h 318 return testHeaders{}, nil 319 }}) 320 _, _, err = k.Read() 321 require.NoError(t, err) 322 require.Equal(t, map[string]string{"updated": "headers"}, headers) 323 } 324 325 // If we abort in the middle of writing the key and cert, such that only the key is written 326 // to the final location, when we read we can still read the cert from the temporary 327 // location. 328 func TestTwoPhaseReadWrite(t *testing.T) { 329 cert1, _, err := testutils.CreateRootCertAndKey("cn") 330 require.NoError(t, err) 331 332 cert2, key2, err := testutils.CreateRootCertAndKey("cn") 333 require.NoError(t, err) 334 335 tempdir, err := ioutil.TempDir("", "KeyReadWriter") 336 require.NoError(t, err) 337 defer os.RemoveAll(tempdir) 338 339 path := ca.NewConfigPaths(filepath.Join(tempdir)) 340 krw := ca.NewKeyReadWriter(path.Node, nil, nil) 341 342 // put a directory in the location where the cert goes, so we can't actually move 343 // the cert from the temporary location to the final location. 344 require.NoError(t, os.Mkdir(filepath.Join(path.Node.Cert), 0755)) 345 require.Error(t, krw.Write(cert2, key2, nil)) 346 347 // the temp cert file should exist 348 tempCertPath := filepath.Join(filepath.Dir(path.Node.Cert), "."+filepath.Base(path.Node.Cert)) 349 readCert, err := ioutil.ReadFile(tempCertPath) 350 require.NoError(t, err) 351 require.Equal(t, cert2, readCert) 352 353 // remove the directory, to simulate it failing to write the first time 354 os.RemoveAll(path.Node.Cert) 355 readCert, readKey, err := krw.Read() 356 require.NoError(t, err) 357 require.Equal(t, cert2, readCert) 358 require.Equal(t, key2, readKey) 359 // the cert should have been moved to its proper location 360 _, err = os.Stat(tempCertPath) 361 require.True(t, os.IsNotExist(err)) 362 363 // If the cert in the proper location doesn't match the key, the temp location is checked 364 require.NoError(t, ioutil.WriteFile(tempCertPath, cert2, 0644)) 365 require.NoError(t, ioutil.WriteFile(path.Node.Cert, cert1, 0644)) 366 readCert, readKey, err = krw.Read() 367 require.NoError(t, err) 368 require.Equal(t, cert2, readCert) 369 require.Equal(t, key2, readKey) 370 // the cert should have been moved to its proper location 371 _, err = os.Stat(tempCertPath) 372 require.True(t, os.IsNotExist(err)) 373 374 // If the cert in the temp location also doesn't match, the failure matching the 375 // correctly-located cert is returned 376 require.NoError(t, os.Remove(path.Node.Cert)) 377 require.NoError(t, ioutil.WriteFile(tempCertPath, cert1, 0644)) // mismatching cert 378 _, _, err = krw.Read() 379 require.True(t, os.IsNotExist(err)) 380 // the cert should have been removed 381 _, err = os.Stat(tempCertPath) 382 require.True(t, os.IsNotExist(err)) 383 } 384 385 func TestKeyReadWriterMigrate(t *testing.T) { 386 cert, key, err := testutils.CreateRootCertAndKey("cn") 387 require.NoError(t, err) 388 389 tempdir, err := ioutil.TempDir("", "KeyReadWriter") 390 require.NoError(t, err) 391 defer os.RemoveAll(tempdir) 392 393 path := ca.NewConfigPaths(filepath.Join(tempdir)) 394 395 // if the key exists in an old location, migrate it from there. 396 tempKeyPath := filepath.Join(filepath.Dir(path.Node.Key), "."+filepath.Base(path.Node.Key)) 397 require.NoError(t, ioutil.WriteFile(path.Node.Cert, cert, 0644)) 398 require.NoError(t, ioutil.WriteFile(tempKeyPath, key, 0600)) 399 400 krw := ca.NewKeyReadWriter(path.Node, nil, nil) 401 require.NoError(t, krw.Migrate()) 402 _, err = os.Stat(tempKeyPath) 403 require.True(t, os.IsNotExist(err)) // it's been moved to the right place 404 _, _, err = krw.Read() 405 require.NoError(t, err) 406 407 // migrate does not affect any existing files 408 dirList, err := ioutil.ReadDir(filepath.Dir(path.Node.Key)) 409 require.NoError(t, err) 410 require.NoError(t, krw.Migrate()) 411 dirList2, err := ioutil.ReadDir(filepath.Dir(path.Node.Key)) 412 require.NoError(t, err) 413 require.Equal(t, dirList, dirList2) 414 _, _, err = krw.Read() 415 require.NoError(t, err) 416 } 417 418 type downgradeTestCase struct { 419 encrypted bool 420 pkcs8 bool 421 errorStr string 422 } 423 424 func testKeyReadWriterDowngradeKeyCase(t *testing.T, tc downgradeTestCase) error { 425 cert, key, err := testutils.CreateRootCertAndKey("cn") 426 require.NoError(t, err) 427 428 if !tc.pkcs8 { 429 key, err = pkcs8.ConvertToECPrivateKeyPEM(key) 430 require.NoError(t, err) 431 } 432 433 var kek []byte 434 if tc.encrypted { 435 block, _ := pem.Decode(key) 436 require.NotNil(t, block) 437 438 kek = []byte("kek") 439 block, err = keyutils.Default.EncryptPEMBlock(block.Bytes, kek) 440 require.NoError(t, err) 441 442 key = pem.EncodeToMemory(block) 443 } 444 445 tempdir, err := ioutil.TempDir("", "KeyReadWriterDowngrade") 446 require.NoError(t, err) 447 defer os.RemoveAll(tempdir) 448 449 path := ca.NewConfigPaths(filepath.Join(tempdir)) 450 451 block, _ := pem.Decode(key) 452 require.NotNil(t, block) 453 454 // add kek-version to later check if it is still there 455 block.Headers["kek-version"] = "5" 456 457 key = pem.EncodeToMemory(block) 458 require.NoError(t, ioutil.WriteFile(path.Node.Cert, cert, 0644)) 459 require.NoError(t, ioutil.WriteFile(path.Node.Key, key, 0600)) 460 461 // if the update headers callback function fails, updating headers fails 462 k := ca.NewKeyReadWriter(path.Node, kek, nil) 463 if err := k.DowngradeKey(); err != nil { 464 return err 465 } 466 467 // read the key directly from fs so we can check if key 468 key, err = ioutil.ReadFile(path.Node.Key) 469 require.NoError(t, err) 470 471 keyBlock, _ := pem.Decode(key) 472 require.NotNil(t, block) 473 require.False(t, keyutils.IsPKCS8(keyBlock.Bytes)) 474 475 if tc.encrypted { 476 require.True(t, keyutils.IsEncryptedPEMBlock(keyBlock)) 477 } 478 require.Equal(t, "5", keyBlock.Headers["kek-version"]) 479 480 // check if KeyReaderWriter can read the key 481 _, _, err = k.Read() 482 require.NoError(t, err) 483 return nil 484 } 485 486 func TestKeyReadWriterDowngradeKey(t *testing.T) { 487 invalid := []downgradeTestCase{ 488 { 489 encrypted: false, 490 pkcs8: false, 491 errorStr: "key is already downgraded to PKCS#1", 492 }, { 493 encrypted: true, 494 pkcs8: false, 495 errorStr: "key is already downgraded to PKCS#1", 496 }, 497 } 498 499 for _, c := range invalid { 500 err := testKeyReadWriterDowngradeKeyCase(t, c) 501 require.Error(t, err) 502 require.EqualError(t, err, c.errorStr) 503 } 504 505 valid := []downgradeTestCase{ 506 { 507 encrypted: false, 508 pkcs8: true, 509 }, { 510 encrypted: true, 511 pkcs8: true, 512 }, 513 } 514 515 for _, c := range valid { 516 err := testKeyReadWriterDowngradeKeyCase(t, c) 517 require.NoError(t, err) 518 } 519 } 520 521 // In FIPS mode, when reading a PKCS1 encrypted key, a PKCS1 error is returned as opposed 522 // to any other type of invalid KEK error 523 func TestKeyReadWriterReadNonFIPS(t *testing.T) { 524 t.Parallel() 525 cert, key, err := testutils.CreateRootCertAndKey("cn") 526 require.NoError(t, err) 527 528 key, err = pkcs8.ConvertToECPrivateKeyPEM(key) 529 require.NoError(t, err) 530 531 tempdir, err := ioutil.TempDir("", "KeyReadWriter") 532 require.NoError(t, err) 533 defer os.RemoveAll(tempdir) 534 535 path := ca.NewConfigPaths(filepath.Join(tempdir, "subdir")) // to make sure subdirectories are created 536 537 k := ca.NewKeyReadWriter(path.Node, nil, nil) 538 k.SetKeyFormatter(keyutils.FIPS) 539 540 // can write an unencrypted PKCS1 key with no issues 541 require.NoError(t, k.Write(cert, key, nil)) 542 // can read the unencrypted key with no issues 543 readCert, readKey, err := k.Read() 544 require.NoError(t, err) 545 require.Equal(t, cert, readCert) 546 require.Equal(t, key, readKey) 547 548 // cannot write an encrypted PKCS1 key 549 passphrase := []byte("passphrase") 550 require.Equal(t, keyutils.ErrFIPSUnsupportedKeyFormat, k.Write(cert, key, &ca.KEKData{KEK: passphrase})) 551 552 k.SetKeyFormatter(keyutils.Default) 553 require.NoError(t, k.Write(cert, key, &ca.KEKData{KEK: passphrase})) 554 555 // cannot read an encrypted PKCS1 key 556 k.SetKeyFormatter(keyutils.FIPS) 557 _, _, err = k.Read() 558 require.Equal(t, keyutils.ErrFIPSUnsupportedKeyFormat, err) 559 560 k.SetKeyFormatter(keyutils.Default) 561 _, _, err = k.Read() 562 require.NoError(t, err) 563 }