Skip to content

Commit

Permalink
Merge branch 'main' into pkg_not_found
Browse files Browse the repository at this point in the history
  • Loading branch information
rdimitrov authored May 20, 2024
2 parents c082a30 + df58820 commit 916b690
Show file tree
Hide file tree
Showing 9 changed files with 263 additions and 47 deletions.
4 changes: 2 additions & 2 deletions internal/crypto/algorithms/aes256cfb.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ var legacySalt = []byte("somesalt")

// Encrypt encrypts a row of data.
func (a *AES256CFBAlgorithm) Encrypt(plaintext []byte, key []byte) ([]byte, error) {
if len(plaintext) > maxSize {
return nil, status.Errorf(codes.InvalidArgument, "data is too large (>32MB)")
if len(plaintext) > maxPlaintextSize {
return nil, ErrExceedsMaxSize
}
block, err := aes.NewCipher(a.deriveKey(key))
if err != nil {
Expand Down
90 changes: 90 additions & 0 deletions internal/crypto/algorithms/aes256gcm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright 2024 Stacklok, Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Adapted from: https://github.com/gtank/cryptopasta/blob/bc3a108a5776376aa811eea34b93383837994340/encrypt.go
// cryptopasta - basic cryptography examples
//
// Written in 2015 by George Tankersley <george.tankersley@gmail.com>
//
// To the extent possible under law, the author(s) have dedicated all copyright
// and related and neighboring rights to this software to the public domain
// worldwide. This software is distributed without any warranty.
//
// You should have received a copy of the CC0 Public Domain Dedication along
// with this software. If not, see // <http://creativecommons.org/publicdomain/zero/1.0/>.

package algorithms

import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"errors"
"io"
)

// AES256GCMAlgorithm provides symmetric authenticated encryption using 256-bit AES-GCM with a random nonce.
type AES256GCMAlgorithm struct{}

// Encrypt encrypts data using 256-bit AES-GCM. This both hides the content of
// the data and provides a check that it hasn't been altered. Output takes the
// form nonce|ciphertext|tag where '|' indicates concatenation.
func (_ *AES256GCMAlgorithm) Encrypt(plaintext []byte, key []byte) ([]byte, error) {
if len(plaintext) > maxPlaintextSize {
return nil, ErrExceedsMaxSize
}

block, err := aes.NewCipher(key[:])
if err != nil {
return nil, err
}

gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}

nonce := make([]byte, gcm.NonceSize())
_, err = io.ReadFull(rand.Reader, nonce)
if err != nil {
return nil, err
}

return gcm.Seal(nonce, nonce, plaintext, nil), nil
}

// Decrypt decrypts data using 256-bit AES-GCM. This both hides the content of
// the data and provides a check that it hasn't been altered. Expects input
// form nonce|ciphertext|tag where '|' indicates concatenation.
func (_ *AES256GCMAlgorithm) Decrypt(ciphertext []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key[:])
if err != nil {
return nil, err
}

gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}

if len(ciphertext) < gcm.NonceSize() {
return nil, errors.New("malformed ciphertext")
}

return gcm.Open(nil,
ciphertext[:gcm.NonceSize()],
ciphertext[gcm.NonceSize():],
nil,
)
}
119 changes: 119 additions & 0 deletions internal/crypto/algorithms/aes256gcm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright 2024 Stacklok, Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package algorithms_test

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/stacklok/minder/internal/crypto/algorithms"
)

func TestGCMEncrypt(t *testing.T) {
t.Parallel()

scenarios := []struct {
Name string
Key []byte
Plaintext []byte
ExpectedError string
}{
{
Name: "GCM Encrypt rejects short key",
Key: []byte{0xFF},
Plaintext: []byte(plaintext),
ExpectedError: "invalid key size",
},
{
Name: "GCM Encrypt rejects oversized plaintext",
Key: key,
Plaintext: make([]byte, 33*1024*1024), // 33MiB
ExpectedError: algorithms.ErrExceedsMaxSize.Error(),
},
{
Name: "GCM encrypts plaintext",
Key: key,
Plaintext: []byte(plaintext),
},
}

for _, scenario := range scenarios {
t.Run(scenario.Name, func(t *testing.T) {
t.Parallel()

result, err := gcm.Encrypt(scenario.Plaintext, scenario.Key)
if scenario.ExpectedError == "" {
require.NoError(t, err)
// validate by decrypting
decrypted, err := gcm.Decrypt(result, key)
require.NoError(t, err)
require.Equal(t, scenario.Plaintext, decrypted)
} else {
require.ErrorContains(t, err, scenario.ExpectedError)
}
})
}
}

// This doesn't test decryption - that is tested in the happy path of the encrypt test
func TestGCMDecrypt(t *testing.T) {
t.Parallel()

scenarios := []struct {
Name string
Key []byte
Ciphertext []byte
ExpectedError string
}{
{
Name: "GCM Decrypt rejects short key",
Key: []byte{0xFF},
Ciphertext: []byte(plaintext),
ExpectedError: "invalid key size",
},
{
Name: "GCM Decrypt rejects malformed ciphertext",
Key: key,
Ciphertext: make([]byte, 32), // 33MiB
ExpectedError: "message authentication failed",
},
{
Name: "GCM Decrypt rejects undersized key",
Key: key,
Ciphertext: []byte{0xFF},
ExpectedError: "malformed ciphertext",
},
}

for _, scenario := range scenarios {
t.Run(scenario.Name, func(t *testing.T) {
t.Parallel()

_, err := gcm.Decrypt(scenario.Ciphertext, scenario.Key)
require.ErrorContains(t, err, scenario.ExpectedError)
})
}
}

var (
key = []byte{
0x5, 0x94, 0x74, 0xfd, 0xb7, 0xf9, 0x85, 0x9, 0x67, 0x8, 0x2D, 0xe8, 0x46, 0x8c, 0x76, 0xe2,
0x7a, 0x85, 0x7f, 0xed, 0x67, 0xd4, 0xd5, 0x2c, 0x46, 0x00, 0xba, 0x44, 0x8d, 0x54, 0x20, 0xf1,
}
gcm = algorithms.AES256GCMAlgorithm{}
)

const plaintext = "Hello world"
25 changes: 18 additions & 7 deletions internal/crypto/algorithms/algorithm.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,29 +33,40 @@ type Type string
const (
// Aes256Cfb is the AES-256-CFB algorithm
Aes256Cfb Type = "aes-256-cfb"
// Aes256Gcm is the AES-256-GCM algorithm
Aes256Gcm Type = "aes-256-gcm"
)

const maxSize = 32 * 1024 * 1024
const maxPlaintextSize = 32 * 1024 * 1024

// ErrUnknownAlgorithm is used when an incorrect algorithm name is used.
var (
// ErrUnknownAlgorithm is returned when an incorrect algorithm name is used.
ErrUnknownAlgorithm = errors.New("unexpected encryption algorithm")
// ErrExceedsMaxSize is returned when the plaintext is too large.
ErrExceedsMaxSize = errors.New("plaintext is too large, limited to 32MiB")
)

// TypeFromString attempts to map a string to a `Type` value.
func TypeFromString(name string) (Type, error) {
// TODO: use switch when we support more than once type.
if name == string(Aes256Cfb) {
switch name {
case string(Aes256Cfb):
return Aes256Cfb, nil
case string(Aes256Gcm):
return Aes256Gcm, nil
default:
return "", fmt.Errorf("%w: %s", ErrUnknownAlgorithm, name)
}
return "", fmt.Errorf("%w: %s", ErrUnknownAlgorithm, name)
}

// NewFromType instantiates an encryption algorithm by name
func NewFromType(algoType Type) (EncryptionAlgorithm, error) {
// TODO: use switch when we support more than once type.
if algoType == Aes256Cfb {
switch algoType {
case Aes256Cfb:
return &AES256CFBAlgorithm{}, nil
case Aes256Gcm:
return &AES256GCMAlgorithm{}, nil
default:
return nil, fmt.Errorf("%w: %s", ErrUnknownAlgorithm, algoType)
}
return nil, fmt.Errorf("%w: %s", ErrUnknownAlgorithm, algoType)
}
4 changes: 2 additions & 2 deletions internal/crypto/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func (e *engine) DecryptString(encryptedString EncryptedData) (string, error) {
return string(decrypted), nil
}

func (e *engine) encrypt(data []byte) (EncryptedData, error) {
func (e *engine) encrypt(plaintext []byte) (EncryptedData, error) {
// Neither of these lookups should ever fail.
algorithm, ok := e.supportedAlgorithms[e.defaultAlgorithm]
if !ok {
Expand All @@ -168,7 +168,7 @@ func (e *engine) encrypt(data []byte) (EncryptedData, error) {
return EncryptedData{}, fmt.Errorf("unable to find preferred key with ID: %s", e.defaultKeyID)
}

encrypted, err := algorithm.Encrypt(data, key)
encrypted, err := algorithm.Encrypt(plaintext, key)
if err != nil {
return EncryptedData{}, errors.Join(ErrEncrypt, err)
}
Expand Down
4 changes: 1 addition & 3 deletions internal/crypto/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/oauth2"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

"github.com/stacklok/minder/internal/config/server"
"github.com/stacklok/minder/internal/crypto/algorithms"
Expand Down Expand Up @@ -137,7 +135,7 @@ func TestEncryptTooLarge(t *testing.T) {
require.NoError(t, err)
large := make([]byte, 34000000) // More than 32 MB
_, err = engine.EncryptString(string(large))
assert.ErrorIs(t, err, status.Error(codes.InvalidArgument, "data is too large (>32MB)"))
assert.ErrorIs(t, err, algorithms.ErrExceedsMaxSize)
}

func TestEncryptDecryptOAuthToken(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ func NewHomoglyphsEvaluator(
}

// evaluateHomoglyphs is a helper function to evaluate the homoglyphs rule type
// Return parameters:
// - bool: whether the evaluation has found violations
// - error: an error if the evaluation failed
func evaluateHomoglyphs(
ctx context.Context,
processor domain.HomoglyphProcessor,
Expand Down Expand Up @@ -112,14 +115,9 @@ func evaluateHomoglyphs(
}
}

var reviewText string
var hasFoundViolations bool
if len(reviewHandler.GetComments()) > 0 {
reviewText = processor.GetFailedReviewText()
hasFoundViolations = true
} else {
reviewText = processor.GetPassedReviewText()
return true, reviewHandler.SubmitReview(ctx, processor.GetFailedReviewText())
}

return hasFoundViolations, reviewHandler.SubmitReview(ctx, reviewText)
return false, nil
}
12 changes: 6 additions & 6 deletions tools/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/daixiang0/gci v0.13.4
github.com/deepmap/oapi-codegen/v2 v2.1.0
github.com/go-critic/go-critic v0.11.3
github.com/golangci/golangci-lint v1.58.1
github.com/golangci/golangci-lint v1.58.2
github.com/gotesttools/gotestfmt/v2 v2.5.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0
github.com/mikefarah/yq/v4 v4.44.1
Expand All @@ -36,7 +36,7 @@ require (
github.com/4meepo/tagalign v1.3.4 // indirect
github.com/Abirdcfly/dupword v0.0.14 // indirect
github.com/Antonboom/errname v0.1.13 // indirect
github.com/Antonboom/nilnil v0.1.8 // indirect
github.com/Antonboom/nilnil v0.1.9 // indirect
github.com/Antonboom/testifylint v1.2.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
Expand Down Expand Up @@ -99,7 +99,7 @@ require (
github.com/elliotchance/orderedmap v1.5.1 // indirect
github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect
github.com/ettle/strcase v0.2.0 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/felixge/fgprof v0.9.4 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
Expand Down Expand Up @@ -183,7 +183,7 @@ require (
github.com/kulti/thelper v0.6.3 // indirect
github.com/kunwardeep/paralleltest v1.0.10 // indirect
github.com/kyoh86/exportloopref v0.1.11 // indirect
github.com/lasiar/canonicalheader v1.0.6 // indirect
github.com/lasiar/canonicalheader v1.1.1 // indirect
github.com/ldez/gomoddirectives v0.2.4 // indirect
github.com/ldez/tagliatelle v0.5.0 // indirect
github.com/leonklingele/grouper v1.1.2 // indirect
Expand Down Expand Up @@ -261,7 +261,7 @@ require (
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
github.com/sashamelentyev/usestdlibvars v1.25.0 // indirect
github.com/securego/gosec/v2 v2.19.0 // indirect
github.com/securego/gosec/v2 v2.20.0 // indirect
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
Expand Down Expand Up @@ -300,7 +300,7 @@ require (
github.com/ykadowak/zerologlint v0.1.5 // indirect
github.com/yuin/gopher-lua v1.1.1 // indirect
gitlab.com/bosi/decorder v0.4.2 // indirect
go-simpler.org/musttag v0.12.1 // indirect
go-simpler.org/musttag v0.12.2 // indirect
go-simpler.org/sloglint v0.6.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
go.opentelemetry.io/otel v1.26.0 // indirect
Expand Down
Loading

0 comments on commit 916b690

Please sign in to comment.