[SEC] Add `keying` module
The keying modules tries to solve two problems, the lack of key
separation and the lack of AEAD being used for encryption. The currently
used `secrets` doesn't provide this and is hard to adjust to provide
this functionality.
For encryption, the additional data is now a parameter that can be used,
as the underlying primitive is an AEAD constructions. This allows for
context binding to happen and can be seen as defense-in-depth; it
ensures that if a value X is encrypted for context Y (e.g. ID=3,
Column="private_key") it will only decrypt if that context Y is also
given in the Decrypt function. This makes confused deputy attack harder
to exploit.[^1]
For key separation, HKDF is used to derives subkeys from some IKM, which
is the value of the `[service].SECRET_KEY` config setting. The context
for subkeys are hardcoded, any variable should be shuffled into the the
additional data parameter when encrypting.
[^1]: This is still possible, because the used AEAD construction is not
key-comitting. For Forgejo's current use-case this risk is negligible,
because the subkeys aren't known to a malicious user (which is required
for such attack), unless they also have access to the IKM (at which
point you can assume the whole system is compromised). See
https://scottarc.blog/2022/10/17/lucid-multi-key-deputies-require-commitment/
2024-08-21 00:13:04 +03:00
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package keying_test
import (
2024-08-04 21:46:05 +03:00
"math"
[SEC] Add `keying` module
The keying modules tries to solve two problems, the lack of key
separation and the lack of AEAD being used for encryption. The currently
used `secrets` doesn't provide this and is hard to adjust to provide
this functionality.
For encryption, the additional data is now a parameter that can be used,
as the underlying primitive is an AEAD constructions. This allows for
context binding to happen and can be seen as defense-in-depth; it
ensures that if a value X is encrypted for context Y (e.g. ID=3,
Column="private_key") it will only decrypt if that context Y is also
given in the Decrypt function. This makes confused deputy attack harder
to exploit.[^1]
For key separation, HKDF is used to derives subkeys from some IKM, which
is the value of the `[service].SECRET_KEY` config setting. The context
for subkeys are hardcoded, any variable should be shuffled into the the
additional data parameter when encrypting.
[^1]: This is still possible, because the used AEAD construction is not
key-comitting. For Forgejo's current use-case this risk is negligible,
because the subkeys aren't known to a malicious user (which is required
for such attack), unless they also have access to the IKM (at which
point you can assume the whole system is compromised). See
https://scottarc.blog/2022/10/17/lucid-multi-key-deputies-require-commitment/
2024-08-21 00:13:04 +03:00
"testing"
"code.gitea.io/gitea/modules/keying"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/chacha20poly1305"
)
func TestKeying ( t * testing . T ) {
2024-11-28 13:35:55 +03:00
t . Run ( "Not initialized" , func ( t * testing . T ) {
[SEC] Add `keying` module
The keying modules tries to solve two problems, the lack of key
separation and the lack of AEAD being used for encryption. The currently
used `secrets` doesn't provide this and is hard to adjust to provide
this functionality.
For encryption, the additional data is now a parameter that can be used,
as the underlying primitive is an AEAD constructions. This allows for
context binding to happen and can be seen as defense-in-depth; it
ensures that if a value X is encrypted for context Y (e.g. ID=3,
Column="private_key") it will only decrypt if that context Y is also
given in the Decrypt function. This makes confused deputy attack harder
to exploit.[^1]
For key separation, HKDF is used to derives subkeys from some IKM, which
is the value of the `[service].SECRET_KEY` config setting. The context
for subkeys are hardcoded, any variable should be shuffled into the the
additional data parameter when encrypting.
[^1]: This is still possible, because the used AEAD construction is not
key-comitting. For Forgejo's current use-case this risk is negligible,
because the subkeys aren't known to a malicious user (which is required
for such attack), unless they also have access to the IKM (at which
point you can assume the whole system is compromised). See
https://scottarc.blog/2022/10/17/lucid-multi-key-deputies-require-commitment/
2024-08-21 00:13:04 +03:00
assert . Panics ( t , func ( ) {
keying . DeriveKey ( keying . Context ( "TESTING" ) )
} )
} )
t . Run ( "Initialization" , func ( t * testing . T ) {
keying . Init ( [ ] byte { 0x00 , 0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 , 0x07 } )
} )
2024-11-28 13:35:55 +03:00
t . Run ( "Context separation" , func ( t * testing . T ) {
[SEC] Add `keying` module
The keying modules tries to solve two problems, the lack of key
separation and the lack of AEAD being used for encryption. The currently
used `secrets` doesn't provide this and is hard to adjust to provide
this functionality.
For encryption, the additional data is now a parameter that can be used,
as the underlying primitive is an AEAD constructions. This allows for
context binding to happen and can be seen as defense-in-depth; it
ensures that if a value X is encrypted for context Y (e.g. ID=3,
Column="private_key") it will only decrypt if that context Y is also
given in the Decrypt function. This makes confused deputy attack harder
to exploit.[^1]
For key separation, HKDF is used to derives subkeys from some IKM, which
is the value of the `[service].SECRET_KEY` config setting. The context
for subkeys are hardcoded, any variable should be shuffled into the the
additional data parameter when encrypting.
[^1]: This is still possible, because the used AEAD construction is not
key-comitting. For Forgejo's current use-case this risk is negligible,
because the subkeys aren't known to a malicious user (which is required
for such attack), unless they also have access to the IKM (at which
point you can assume the whole system is compromised). See
https://scottarc.blog/2022/10/17/lucid-multi-key-deputies-require-commitment/
2024-08-21 00:13:04 +03:00
key1 := keying . DeriveKey ( keying . Context ( "TESTING" ) )
key2 := keying . DeriveKey ( keying . Context ( "TESTING2" ) )
ciphertext := key1 . Encrypt ( [ ] byte ( "This is for context TESTING" ) , nil )
plaintext , err := key2 . Decrypt ( ciphertext , nil )
require . Error ( t , err )
assert . Empty ( t , plaintext )
plaintext , err = key1 . Decrypt ( ciphertext , nil )
require . NoError ( t , err )
assert . EqualValues ( t , "This is for context TESTING" , plaintext )
} )
context := keying . Context ( "TESTING PURPOSES" )
plainText := [ ] byte ( "Forgejo is run by [Redacted]" )
var cipherText [ ] byte
t . Run ( "Encrypt" , func ( t * testing . T ) {
key := keying . DeriveKey ( context )
cipherText = key . Encrypt ( plainText , [ ] byte { 0x05 , 0x06 } )
cipherText2 := key . Encrypt ( plainText , [ ] byte { 0x05 , 0x06 } )
2024-11-28 13:35:55 +03:00
// Ensure ciphertexts don't have an deterministic output.
[SEC] Add `keying` module
The keying modules tries to solve two problems, the lack of key
separation and the lack of AEAD being used for encryption. The currently
used `secrets` doesn't provide this and is hard to adjust to provide
this functionality.
For encryption, the additional data is now a parameter that can be used,
as the underlying primitive is an AEAD constructions. This allows for
context binding to happen and can be seen as defense-in-depth; it
ensures that if a value X is encrypted for context Y (e.g. ID=3,
Column="private_key") it will only decrypt if that context Y is also
given in the Decrypt function. This makes confused deputy attack harder
to exploit.[^1]
For key separation, HKDF is used to derives subkeys from some IKM, which
is the value of the `[service].SECRET_KEY` config setting. The context
for subkeys are hardcoded, any variable should be shuffled into the the
additional data parameter when encrypting.
[^1]: This is still possible, because the used AEAD construction is not
key-comitting. For Forgejo's current use-case this risk is negligible,
because the subkeys aren't known to a malicious user (which is required
for such attack), unless they also have access to the IKM (at which
point you can assume the whole system is compromised). See
https://scottarc.blog/2022/10/17/lucid-multi-key-deputies-require-commitment/
2024-08-21 00:13:04 +03:00
assert . NotEqualValues ( t , cipherText , cipherText2 )
} )
t . Run ( "Decrypt" , func ( t * testing . T ) {
key := keying . DeriveKey ( context )
2024-11-28 13:35:55 +03:00
t . Run ( "Successful" , func ( t * testing . T ) {
[SEC] Add `keying` module
The keying modules tries to solve two problems, the lack of key
separation and the lack of AEAD being used for encryption. The currently
used `secrets` doesn't provide this and is hard to adjust to provide
this functionality.
For encryption, the additional data is now a parameter that can be used,
as the underlying primitive is an AEAD constructions. This allows for
context binding to happen and can be seen as defense-in-depth; it
ensures that if a value X is encrypted for context Y (e.g. ID=3,
Column="private_key") it will only decrypt if that context Y is also
given in the Decrypt function. This makes confused deputy attack harder
to exploit.[^1]
For key separation, HKDF is used to derives subkeys from some IKM, which
is the value of the `[service].SECRET_KEY` config setting. The context
for subkeys are hardcoded, any variable should be shuffled into the the
additional data parameter when encrypting.
[^1]: This is still possible, because the used AEAD construction is not
key-comitting. For Forgejo's current use-case this risk is negligible,
because the subkeys aren't known to a malicious user (which is required
for such attack), unless they also have access to the IKM (at which
point you can assume the whole system is compromised). See
https://scottarc.blog/2022/10/17/lucid-multi-key-deputies-require-commitment/
2024-08-21 00:13:04 +03:00
convertedPlainText , err := key . Decrypt ( cipherText , [ ] byte { 0x05 , 0x06 } )
require . NoError ( t , err )
assert . EqualValues ( t , plainText , convertedPlainText )
} )
2024-11-28 13:35:55 +03:00
t . Run ( "Not enough additional data" , func ( t * testing . T ) {
[SEC] Add `keying` module
The keying modules tries to solve two problems, the lack of key
separation and the lack of AEAD being used for encryption. The currently
used `secrets` doesn't provide this and is hard to adjust to provide
this functionality.
For encryption, the additional data is now a parameter that can be used,
as the underlying primitive is an AEAD constructions. This allows for
context binding to happen and can be seen as defense-in-depth; it
ensures that if a value X is encrypted for context Y (e.g. ID=3,
Column="private_key") it will only decrypt if that context Y is also
given in the Decrypt function. This makes confused deputy attack harder
to exploit.[^1]
For key separation, HKDF is used to derives subkeys from some IKM, which
is the value of the `[service].SECRET_KEY` config setting. The context
for subkeys are hardcoded, any variable should be shuffled into the the
additional data parameter when encrypting.
[^1]: This is still possible, because the used AEAD construction is not
key-comitting. For Forgejo's current use-case this risk is negligible,
because the subkeys aren't known to a malicious user (which is required
for such attack), unless they also have access to the IKM (at which
point you can assume the whole system is compromised). See
https://scottarc.blog/2022/10/17/lucid-multi-key-deputies-require-commitment/
2024-08-21 00:13:04 +03:00
plainText , err := key . Decrypt ( cipherText , [ ] byte { 0x05 } )
require . Error ( t , err )
assert . Empty ( t , plainText )
} )
t . Run ( "Too much additional data" , func ( t * testing . T ) {
plainText , err := key . Decrypt ( cipherText , [ ] byte { 0x05 , 0x06 , 0x07 } )
require . Error ( t , err )
assert . Empty ( t , plainText )
} )
t . Run ( "Incorrect nonce" , func ( t * testing . T ) {
// Flip the first byte of the nonce.
cipherText [ 0 ] = ^ cipherText [ 0 ]
plainText , err := key . Decrypt ( cipherText , [ ] byte { 0x05 , 0x06 } )
require . Error ( t , err )
assert . Empty ( t , plainText )
} )
t . Run ( "Incorrect ciphertext" , func ( t * testing . T ) {
assert . Panics ( t , func ( ) {
key . Decrypt ( nil , nil )
} )
assert . Panics ( t , func ( ) {
cipherText := make ( [ ] byte , chacha20poly1305 . NonceSizeX )
key . Decrypt ( cipherText , nil )
} )
} )
} )
}
2024-08-04 21:46:05 +03:00
func TestKeyingColumnAndID ( t * testing . T ) {
assert . EqualValues ( t , [ ] byte { 0x74 , 0x61 , 0x62 , 0x6c , 0x65 , 0x3a , 0x80 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 } , keying . ColumnAndID ( "table" , math . MinInt64 ) )
assert . EqualValues ( t , [ ] byte { 0x74 , 0x61 , 0x62 , 0x6c , 0x65 , 0x3a , 0xff , 0xff , 0xff , 0xff , 0xff , 0xff , 0xff , 0xff } , keying . ColumnAndID ( "table" , - 1 ) )
assert . EqualValues ( t , [ ] byte { 0x74 , 0x61 , 0x62 , 0x6c , 0x65 , 0x3a , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 } , keying . ColumnAndID ( "table" , 0 ) )
assert . EqualValues ( t , [ ] byte { 0x74 , 0x61 , 0x62 , 0x6c , 0x65 , 0x3a , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x01 } , keying . ColumnAndID ( "table" , 1 ) )
assert . EqualValues ( t , [ ] byte { 0x74 , 0x61 , 0x62 , 0x6c , 0x65 , 0x3a , 0x7f , 0xff , 0xff , 0xff , 0xff , 0xff , 0xff , 0xff } , keying . ColumnAndID ( "table" , math . MaxInt64 ) )
assert . EqualValues ( t , [ ] byte { 0x74 , 0x61 , 0x62 , 0x6c , 0x65 , 0x32 , 0x3a , 0x80 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 } , keying . ColumnAndID ( "table2" , math . MinInt64 ) )
assert . EqualValues ( t , [ ] byte { 0x74 , 0x61 , 0x62 , 0x6c , 0x65 , 0x32 , 0x3a , 0xff , 0xff , 0xff , 0xff , 0xff , 0xff , 0xff , 0xff } , keying . ColumnAndID ( "table2" , - 1 ) )
assert . EqualValues ( t , [ ] byte { 0x74 , 0x61 , 0x62 , 0x6c , 0x65 , 0x32 , 0x3a , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 } , keying . ColumnAndID ( "table2" , 0 ) )
assert . EqualValues ( t , [ ] byte { 0x74 , 0x61 , 0x62 , 0x6c , 0x65 , 0x32 , 0x3a , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x01 } , keying . ColumnAndID ( "table2" , 1 ) )
assert . EqualValues ( t , [ ] byte { 0x74 , 0x61 , 0x62 , 0x6c , 0x65 , 0x32 , 0x3a , 0x7f , 0xff , 0xff , 0xff , 0xff , 0xff , 0xff , 0xff } , keying . ColumnAndID ( "table2" , math . MaxInt64 ) )
}