Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only implement the supported AES extra mode interfaces #186

Merged
merged 8 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
225 changes: 190 additions & 35 deletions aes.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,11 @@ import (
"errors"
)

type extraModes interface {
// Copied out of crypto/aes/modes.go.
NewCBCEncrypter(iv []byte) cipher.BlockMode
NewCBCDecrypter(iv []byte) cipher.BlockMode
NewCTR(iv []byte) cipher.Stream
NewGCM(nonceSize, tagSize int) (cipher.AEAD, error)
qmuntal marked this conversation as resolved.
Show resolved Hide resolved

// Invented for BoringCrypto.
NewGCMTLS() (cipher.AEAD, error)
}

var _ extraModes = (*aesCipher)(nil)

// NewAESCipher creates and returns a new AES cipher.Block.
// The key argument should be the AES key, either 16, 24, or 32 bytes to select
// AES-128, AES-192, or AES-256.
// The returned cipher.Block implements the CBC, CTR, and/or GCM modes if
// the underlying OpenSSL library supports them.
func NewAESCipher(key []byte) (cipher.Block, error) {
var kind cipherKind
switch len(key) * 8 {
Expand All @@ -38,63 +30,226 @@ func NewAESCipher(key []byte) (cipher.Block, error) {
if err != nil {
return nil, err
}
return &aesCipher{c}, nil
var block cipher.Block
cbcSupported := loadCipher(kind, cipherModeCBC) != nil
ctrSupported := loadCipher(kind, cipherModeCTR) != nil
gcmSupported := loadCipher(kind, cipherModeGCM) != nil
aes := aesCipher{c}
switch {
case cbcSupported && ctrSupported && gcmSupported:
qmuntal marked this conversation as resolved.
Show resolved Hide resolved
block = cipherWithCBC_CTR_GCM{aes,
cipherWithCBC{aes},
cipherWithCTR{aes},
cipherWithGCM{aes},
}
case cbcSupported && ctrSupported && !gcmSupported:
block = cipherWithCBC_CTR{aes,
cipherWithCBC{aes},
cipherWithCTR{aes},
}
case cbcSupported && !ctrSupported && gcmSupported:
block = cipherWithCBC_GCM{aes,
cipherWithCBC{aes},
cipherWithGCM{aes},
}
case cbcSupported && !ctrSupported && !gcmSupported:
block = cipherWithCBC{aes}
case !cbcSupported && ctrSupported && gcmSupported:
block = cipherWithCTR_GCM{aes,
cipherWithCTR{aes},
cipherWithGCM{aes},
}
case !cbcSupported && ctrSupported && !gcmSupported:
block = cipherWithCTR{aes}
case !cbcSupported && !ctrSupported && gcmSupported:
block = cipherWithGCM{aes}
case !cbcSupported && !ctrSupported && !gcmSupported:
block = aes
default:
panic("unreachable")
}

return block, nil
}

// NewGCMTLS returns a GCM cipher specific to TLS
// and should not be used for non-TLS purposes.
func NewGCMTLS(c cipher.Block) (cipher.AEAD, error) {
return c.(*aesCipher).NewGCMTLS()
if c, ok := c.(interface {
NewGCMTLS() (cipher.AEAD, error)
}); ok {
return c.NewGCMTLS()
}
return nil, errors.New("GCM not supported")
}

// NewGCMTLS13 returns a GCM cipher specific to TLS 1.3 and should not be used
// for non-TLS purposes.
func NewGCMTLS13(c cipher.Block) (cipher.AEAD, error) {
return c.(*aesCipher).NewGCMTLS13()
if c, ok := c.(interface {
NewGCMTLS13() (cipher.AEAD, error)
}); ok {
return c.NewGCMTLS13()
}
return nil, errors.New("GCM not supported")
}

// aesCipher implements the cipher.Block interface.
type aesCipher struct {
*evpCipher
cipher *evpCipher
}

func (c *aesCipher) BlockSize() int {
return c.blockSize
func (c aesCipher) BlockSize() int {
return c.cipher.blockSize
}

func (c *aesCipher) Encrypt(dst, src []byte) {
if err := c.encrypt(dst, src); err != nil {
func (c aesCipher) Encrypt(dst, src []byte) {
if err := c.cipher.encrypt(dst, src); err != nil {
// crypto/aes expects that the panic message starts with "crypto/aes: ".
panic("crypto/aes: " + err.Error())
}
}

func (c *aesCipher) Decrypt(dst, src []byte) {
if err := c.decrypt(dst, src); err != nil {
func (c aesCipher) Decrypt(dst, src []byte) {
if err := c.cipher.decrypt(dst, src); err != nil {
// crypto/aes expects that the panic message starts with "crypto/aes: ".
panic("crypto/aes: " + err.Error())
}
}

func (c *aesCipher) NewCBCEncrypter(iv []byte) cipher.BlockMode {
return c.newCBC(iv, cipherOpEncrypt)
// cipherWithCBC implements the cipher.Block, aes.cbcEncAble and aes.cbcDecAble interfaces.
type cipherWithCBC struct {
aesCipher
}

func (c cipherWithCBC) NewCBCEncrypter(iv []byte) cipher.BlockMode {
return c.cipher.newCBC(iv, cipherOpEncrypt)
}

func (c cipherWithCBC) NewCBCDecrypter(iv []byte) cipher.BlockMode {
return c.cipher.newCBC(iv, cipherOpDecrypt)
}

// cipherWithCTR implements the cipher.Block and aes.ctrAble interfaces.
type cipherWithCTR struct {
aesCipher
}

func (c cipherWithCTR) NewCTR(iv []byte) cipher.Stream {
return c.cipher.newCTR(iv)
}

// cipherWithGCM implements the cipher.Block and aes.gcmAble interface.
type cipherWithGCM struct {
aesCipher
}

func (c cipherWithGCM) NewGCM(nonceSize, tagSize int) (cipher.AEAD, error) {
return c.cipher.newGCMChecked(nonceSize, tagSize)
}

func (c *aesCipher) NewCBCDecrypter(iv []byte) cipher.BlockMode {
return c.newCBC(iv, cipherOpDecrypt)
func (c cipherWithGCM) NewGCMTLS() (cipher.AEAD, error) {
return c.cipher.newGCM(cipherGCMTLS12)
}

func (c *aesCipher) NewCTR(iv []byte) cipher.Stream {
return c.newCTR(iv)
func (c cipherWithGCM) NewGCMTLS13() (cipher.AEAD, error) {
return c.cipher.newGCM(cipherGCMTLS13)
}

func (c *aesCipher) NewGCM(nonceSize, tagSize int) (cipher.AEAD, error) {
return c.newGCMChecked(nonceSize, tagSize)
// cipherWithCBC_CTR implements the cipher.Block, aes.cbcEncAble, aes.cbcDecAble,
// and aes.ctrAble interfaces.
type cipherWithCBC_CTR struct {
aesCipher
cipherWithCBC
cipherWithCTR
}

func (c *aesCipher) NewGCMTLS() (cipher.AEAD, error) {
return c.newGCM(cipherGCMTLS12)
// cipherWithCBC_GCM implements the cipher.Block, aes.cbcEncAble, aes.cbcDecAble,
// and aes.gcmAble interfaces.
type cipherWithCBC_GCM struct {
aesCipher
cipherWithCBC
cipherWithGCM
}

func (c *aesCipher) NewGCMTLS13() (cipher.AEAD, error) {
return c.newGCM(cipherGCMTLS13)
// cipherWithCTR_GCM implements the cipher.Block, aes.ctrAble, and aes.gcmAble interfaces.
type cipherWithCTR_GCM struct {
aesCipher
cipherWithCTR
cipherWithGCM
}

// cipherWithCBC_CTR_GCM implements the cipher.Block, aes.cbcEncAble, aes.cbcDecAble,
// aes.ctrAble, and aes.gcmAble interfaces.
type cipherWithCBC_CTR_GCM struct {
aesCipher
cipherWithCBC
cipherWithCTR
cipherWithGCM
}

// The following interfaces have been copied out of crypto/aes/modes.go.

// gcmAble is implemented by cipher.Blocks that can provide an optimized
// implementation of GCM through the AEAD interface.
// See crypto/cipher/gcm.go.
type gcmAble interface {
NewGCM(nonceSize, tagSize int) (cipher.AEAD, error)
}

// cbcEncAble is implemented by cipher.Blocks that can provide an optimized
// implementation of CBC encryption through the cipher.BlockMode interface.
// See crypto/cipher/cbc.go.
type cbcEncAble interface {
NewCBCEncrypter(iv []byte) cipher.BlockMode
}

// cbcDecAble is implemented by cipher.Blocks that can provide an optimized
// implementation of CBC decryption through the cipher.BlockMode interface.
// See crypto/cipher/cbc.go.
type cbcDecAble interface {
NewCBCDecrypter(iv []byte) cipher.BlockMode
}

// ctrAble is implemented by cipher.Blocks that can provide an optimized
// implementation of CTR through the cipher.Stream interface.
// See crypto/cipher/ctr.go.
type ctrAble interface {
NewCTR(iv []byte) cipher.Stream
}

// Test that the interfaces are implemented.

var (
_ cipher.Block = (*aesCipher)(nil)

_ cipher.Block = (*cipherWithCBC)(nil)
_ cbcEncAble = (*cipherWithCBC)(nil)
_ cbcDecAble = (*cipherWithCBC)(nil)

_ cipher.Block = (*cipherWithCTR)(nil)
_ ctrAble = (*cipherWithCTR)(nil)

_ cipher.Block = (*cipherWithGCM)(nil)
_ gcmAble = (*cipherWithGCM)(nil)

_ cipher.Block = (*cipherWithCBC_CTR)(nil)
_ cbcEncAble = (*cipherWithCBC_CTR)(nil)
_ cbcDecAble = (*cipherWithCBC_CTR)(nil)
_ ctrAble = (*cipherWithCBC_CTR)(nil)

_ cipher.Block = (*cipherWithCBC_GCM)(nil)
_ cbcEncAble = (*cipherWithCBC_GCM)(nil)
_ cbcDecAble = (*cipherWithCBC_GCM)(nil)
_ gcmAble = (*cipherWithCBC_GCM)(nil)

_ cipher.Block = (*cipherWithCTR_GCM)(nil)
_ ctrAble = (*cipherWithCTR_GCM)(nil)
_ gcmAble = (*cipherWithCTR_GCM)(nil)

_ cipher.Block = (*cipherWithCBC_CTR_GCM)(nil)
_ cbcEncAble = (*cipherWithCBC_CTR_GCM)(nil)
_ cbcDecAble = (*cipherWithCBC_CTR_GCM)(nil)
_ ctrAble = (*cipherWithCBC_CTR_GCM)(nil)
_ gcmAble = (*cipherWithCBC_CTR_GCM)(nil)
)
5 changes: 4 additions & 1 deletion aes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ func TestNewGCMNonce(t *testing.T) {
gcmStandardNonceSize = 12
)

c := ci.(interface {
c, ok := ci.(interface {
NewGCM(nonceSize, tagSize int) (cipher.AEAD, error)
})
if !ok {
t.Fatal("cipher does not support NewGCM")
}
g, err := c.NewGCM(gcmStandardNonceSize, gcmTagSize)
if err != nil {
t.Errorf("expected no error for standard nonce size with standard tag size, got: %#v", err)
Expand Down
Loading