Skip to content

Commit

Permalink
IPsec key rotation with algorithm change support.
Browse files Browse the repository at this point in the history
Signed-off-by: viktor-kurchenko <[email protected]>
  • Loading branch information
viktor-kurchenko authored and michi-covalent committed Feb 15, 2024
1 parent 60bd84d commit 1007419
Show file tree
Hide file tree
Showing 7 changed files with 425 additions and 208 deletions.
2 changes: 2 additions & 0 deletions encrypt/encrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ type Parameters struct {
AgentPodSelector string
NodeName string
PerNodeDetails bool
IPsecKeyAuthAlgo string
IPsecKeyPerNode string
Writer io.Writer
WaitDuration time.Duration
Output string
Expand Down
73 changes: 73 additions & 0 deletions encrypt/ipsec_key_rotator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium

package encrypt

var rotators = map[string]func(key ipsecKey) (ipsecKey, error){
"": func(key ipsecKey) (ipsecKey, error) { return key.rotate() },
"gcm-aes": newGcmAesKey,
"hmac-md5": newHmacMD5Key,
"hmac-sha1": newHmacSHA1Key,
"hmac-sha256": newHmacSHA256Key,
"hmac-sha512": newHmacSHA512Key,
}

func IsIPsecAlgoSupported(algo string) bool {
_, ok := rotators[algo]
return ok
}

func rotateIPsecKey(key ipsecKey, algo string) (ipsecKey, error) {
return rotators[algo](key)
}

func newGcmAesKey(key ipsecKey) (ipsecKey, error) {
authKey, err := generateRandomHex(40)
if err != nil {
return ipsecKey{}, err
}
newKey := ipsecKey{
spi: key.nextSPI(),
spiSuffix: key.spiSuffix,
algo: "rfc4106(gcm(aes))",
key: authKey,
size: 128,
}
return newKey, nil
}

func newHmacMD5Key(key ipsecKey) (ipsecKey, error) {
return newCbcAesKey(key, "hmac(md5)", 16, 32)
}

func newHmacSHA1Key(key ipsecKey) (ipsecKey, error) {
return newCbcAesKey(key, "hmac(sha1)", 20, 32)
}

func newHmacSHA256Key(key ipsecKey) (ipsecKey, error) {
return newCbcAesKey(key, "hmac(sha256)", 32, 32)
}

func newHmacSHA512Key(key ipsecKey) (ipsecKey, error) {
return newCbcAesKey(key, "hmac(sha512)", 64, 32)
}

func newCbcAesKey(key ipsecKey, algo string, authKeylen int, cipherKeyLen int) (ipsecKey, error) {
authKey, err := generateRandomHex(authKeylen)
if err != nil {
return ipsecKey{}, err
}
cipherKey, err := generateRandomHex(cipherKeyLen)
if err != nil {
return ipsecKey{}, err
}
newKey := ipsecKey{
spi: key.nextSPI(),
spiSuffix: key.spiSuffix,
algo: algo,
key: authKey,
cipherMode: "cbc(aes)",
cipherKey: cipherKey,
}
return newKey, nil
}
303 changes: 303 additions & 0 deletions encrypt/ipsec_key_rotator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium

package encrypt

import (
"testing"

"github.com/stretchr/testify/require"
)

func Test_IsIPsecAlgoSupported(t *testing.T) {
testCases := []struct {
have string
expected bool
}{
{
have: "",
expected: true,
},
{
have: "gcm-aes",
expected: true,
},
{
have: "hmac-md5",
expected: true,
},
{
have: "hmac-sha1",
expected: true,
},
{
have: "hmac-sha256",
expected: true,
},
{
have: "hmac-sha512",
expected: true,
},
{
have: "bla-bla",
expected: false,
},
}

for _, tt := range testCases {
// function to test
actual := IsIPsecAlgoSupported(tt.have)

require.Equal(t, tt.expected, actual)
}
}

func Test_rotateIPsecKey(t *testing.T) {
testCases := []struct {
haveKey ipsecKey
haveAlgo string
expected ipsecKey
}{
{
haveAlgo: "",
haveKey: ipsecKey{
spi: 3,
spiSuffix: false,
algo: "rfc4106(gcm(aes))",
key: "41049390e1e2b5d6543901daab6435f4042155fe",
size: 128,
},
expected: ipsecKey{
spi: 4,
spiSuffix: false,
algo: "rfc4106(gcm(aes))",
key: "41049390e1e2b5d6543901daab6435f4042155fe",
size: 128,
},
},
{
haveAlgo: "",
haveKey: ipsecKey{
spi: 16,
spiSuffix: false,
algo: "rfc4106(gcm(aes))",
key: "41049390e1e2b5d6543901daab6435f4042155fe",
size: 128,
},
expected: ipsecKey{
spi: 1,
spiSuffix: false,
algo: "rfc4106(gcm(aes))",
key: "41049390e1e2b5d6543901daab6435f4042155fe",
size: 128,
},
},
{
haveAlgo: "",
haveKey: ipsecKey{
spi: 3,
spiSuffix: true,
algo: "rfc4106(gcm(aes))",
key: "41049390e1e2b5d6543901daab6435f4042155fe",
size: 128,
},
expected: ipsecKey{
spi: 4,
spiSuffix: true,
algo: "rfc4106(gcm(aes))",
key: "41049390e1e2b5d6543901daab6435f4042155fe",
size: 128,
},
},
{
haveAlgo: "",
haveKey: ipsecKey{
spi: 16,
spiSuffix: true,
algo: "rfc4106(gcm(aes))",
key: "41049390e1e2b5d6543901daab6435f4042155fe",
size: 128,
},
expected: ipsecKey{
spi: 1,
spiSuffix: true,
algo: "rfc4106(gcm(aes))",
key: "41049390e1e2b5d6543901daab6435f4042155fe",
size: 128,
},
},
{
haveAlgo: "",
haveKey: ipsecKey{
spi: 3,
spiSuffix: false,
algo: "hmac(sha256)",
key: "e6b4bab427cd37bb64b39cd66a8476a62963174b78bc544fb525f4c2f548342b",
cipherMode: "cbc(aes)",
cipherKey: "0f12337d9ee75095ff21402dc98476f5f9107261073b70bb37747237d2691d3e",
},
expected: ipsecKey{
spi: 4,
spiSuffix: false,
algo: "hmac(sha256)",
key: "e6b4bab427cd37bb64b39cd66a8476a62963174b78bc544fb525f4c2f548342b",
cipherMode: "cbc(aes)",
cipherKey: "0f12337d9ee75095ff21402dc98476f5f9107261073b70bb37747237d2691d3e",
},
},
{
haveAlgo: "",
haveKey: ipsecKey{
spi: 16,
spiSuffix: false,
algo: "hmac(sha256)",
key: "e6b4bab427cd37bb64b39cd66a8476a62963174b78bc544fb525f4c2f548342b",
cipherMode: "cbc(aes)",
cipherKey: "0f12337d9ee75095ff21402dc98476f5f9107261073b70bb37747237d2691d3e",
},
expected: ipsecKey{
spi: 1,
spiSuffix: false,
algo: "hmac(sha256)",
key: "e6b4bab427cd37bb64b39cd66a8476a62963174b78bc544fb525f4c2f548342b",
cipherMode: "cbc(aes)",
cipherKey: "0f12337d9ee75095ff21402dc98476f5f9107261073b70bb37747237d2691d3e",
},
},
{
haveAlgo: "",
haveKey: ipsecKey{
spi: 3,
spiSuffix: true,
algo: "hmac(sha256)",
key: "e6b4bab427cd37bb64b39cd66a8476a62963174b78bc544fb525f4c2f548342b",
cipherMode: "cbc(aes)",
cipherKey: "0f12337d9ee75095ff21402dc98476f5f9107261073b70bb37747237d2691d3e",
},
expected: ipsecKey{
spi: 4,
spiSuffix: true,
algo: "hmac(sha256)",
key: "e6b4bab427cd37bb64b39cd66a8476a62963174b78bc544fb525f4c2f548342b",
cipherMode: "cbc(aes)",
cipherKey: "0f12337d9ee75095ff21402dc98476f5f9107261073b70bb37747237d2691d3e",
},
},
{
haveAlgo: "",
haveKey: ipsecKey{
spi: 16,
spiSuffix: true,
algo: "hmac(sha256)",
key: "e6b4bab427cd37bb64b39cd66a8476a62963174b78bc544fb525f4c2f548342b",
cipherMode: "cbc(aes)",
cipherKey: "0f12337d9ee75095ff21402dc98476f5f9107261073b70bb37747237d2691d3e",
},
expected: ipsecKey{
spi: 1,
spiSuffix: true,
algo: "hmac(sha256)",
key: "e6b4bab427cd37bb64b39cd66a8476a62963174b78bc544fb525f4c2f548342b",
cipherMode: "cbc(aes)",
cipherKey: "0f12337d9ee75095ff21402dc98476f5f9107261073b70bb37747237d2691d3e",
},
},
{
haveAlgo: "gcm-aes",
haveKey: ipsecKey{
spi: 16,
spiSuffix: true,
},
expected: ipsecKey{
spi: 1,
spiSuffix: true,
algo: "rfc4106(gcm(aes))",
key: "41049390e1e2b5d6543901daab6435f4042155fe",
size: 128,
},
},
{
haveAlgo: "hmac-md5",
haveKey: ipsecKey{
spi: 1,
spiSuffix: true,
},
expected: ipsecKey{
spi: 2,
spiSuffix: true,
algo: "hmac(md5)",
key: "1286b7f6f9f61a4f",
cipherMode: "cbc(aes)",
cipherKey: "efbeeb4230992f76a6e4cc2ff995b756",
},
},
{
haveAlgo: "hmac-sha1",
haveKey: ipsecKey{
spi: 2,
spiSuffix: true,
},
expected: ipsecKey{
spi: 3,
spiSuffix: true,
algo: "hmac(sha1)",
key: "5448dd20e4528a9c2d5b",
cipherMode: "cbc(aes)",
cipherKey: "123d17f2bbbae8009d952b4d0d656f06",
},
},
{
haveAlgo: "hmac-sha256",
haveKey: ipsecKey{
spi: 3,
spiSuffix: true,
},
expected: ipsecKey{
spi: 4,
spiSuffix: true,
algo: "hmac(sha256)",
key: "a9d204b6c2df6f0b707bbfdb71b4bd44",
cipherMode: "cbc(aes)",
cipherKey: "9bd24c14452783bb6f3c9335aff2ed2e",
},
},
{
haveAlgo: "hmac-sha512",
haveKey: ipsecKey{
spi: 4,
spiSuffix: true,
},
expected: ipsecKey{
spi: 5,
spiSuffix: true,
algo: "hmac(sha512)",
key: "8b4d92bf9396e7febb4d51e87394bb158ebcc0d9d57e4da8e938b0e931223ec7",
cipherMode: "cbc(aes)",
cipherKey: "0151a41da39e3310d4f58b3930788dc4",
},
},
}

for _, tt := range testCases {
// function to test
actual, err := rotateIPsecKey(tt.haveKey, tt.haveAlgo)

require.NoError(t, err)
require.Equal(t, tt.expected.spi, actual.spi)
require.Equal(t, tt.expected.spiSuffix, actual.spiSuffix)
require.Equal(t, tt.expected.algo, actual.algo)
require.Equal(t, len(tt.expected.key), len(actual.key))
require.Equal(t, len(tt.expected.cipherKey), len(actual.cipherKey))
require.Equal(t, tt.expected.size, actual.size)
require.Equal(t, tt.expected.cipherMode, actual.cipherMode)
if tt.expected.cipherMode == "" {
// this field will be randomly generated, `require.NotEqual` used for verification
require.NotEqual(t, tt.expected.key, actual.key)
require.Equal(t, tt.expected.cipherKey, actual.cipherKey)
} else {
// the following fields will be randomly generated, `require.NotEqual` used for verification
require.NotEqual(t, tt.expected.key, actual.key)
require.NotEqual(t, tt.expected.cipherKey, actual.cipherKey)
}
}
}
Loading

0 comments on commit 1007419

Please sign in to comment.