forked from edgexfoundry/edgex-go
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add key-related APIs in security-proxy-auth
Resolves edgexfoundry#5038. Add key-related APIs in security-proxy-auth to enable support for external JWT verification. Signed-off-by: Lindsey Cheng <[email protected]>
- Loading branch information
1 parent
0ef5369
commit b41f93f
Showing
27 changed files
with
1,283 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
-- | ||
-- Copyright (C) 2024 IOTech Ltd | ||
-- | ||
-- SPDX-License-Identifier: Apache-2.0 | ||
|
||
-- schema for proxy-auth related tables | ||
CREATE SCHEMA IF NOT EXISTS security_proxy_auth; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
-- | ||
-- Copyright (C) 2024 IOTech Ltd | ||
-- | ||
-- SPDX-License-Identifier: Apache-2.0 | ||
|
||
-- security_proxy_auth.key_store is used to store the key file | ||
CREATE TABLE IF NOT EXISTS security_proxy_auth.key_store ( | ||
id UUID PRIMARY KEY, | ||
name TEXT NOT NULL UNIQUE, | ||
content TEXT NOT NULL, | ||
created timestamp NOT NULL DEFAULT (now() AT TIME ZONE 'utc'), | ||
modified timestamp NOT NULL DEFAULT (now() AT TIME ZONE 'utc') | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// | ||
// Copyright (C) 2024 IOTech Ltd | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package postgres | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"time" | ||
|
||
pgClient "github.com/edgexfoundry/edgex-go/internal/pkg/db/postgres" | ||
|
||
"github.com/edgexfoundry/go-mod-core-contracts/v4/errors" | ||
|
||
"github.com/google/uuid" | ||
) | ||
|
||
// AddKey adds a new key to the database | ||
func (c *Client) AddKey(name, content string) errors.EdgeX { | ||
exists, err := c.KeyExists(name) | ||
if err != nil { | ||
return errors.NewCommonEdgeXWrapper(err) | ||
} else if exists { | ||
return errors.NewCommonEdgeX(errors.KindDuplicateName, fmt.Sprintf("key '%s' already exists", name), nil) | ||
} | ||
|
||
_, pgxErr := c.ConnPool.Exec( | ||
context.Background(), sqlInsert(keyStoreTableName, idCol, nameCol, contentCol), | ||
uuid.New().String(), name, content) | ||
if pgxErr != nil { | ||
return pgClient.WrapDBError("failed to insert row to key_store table", pgxErr) | ||
} | ||
return nil | ||
} | ||
|
||
// UpdateKey updates the key by name | ||
func (c *Client) UpdateKey(name, content string) errors.EdgeX { | ||
_, pgxErr := c.ConnPool.Exec( | ||
context.Background(), sqlUpdateColsByCondCol(keyStoreTableName, nameCol, contentCol, modifiedCol), content, time.Now().UTC(), name) | ||
if pgxErr != nil { | ||
return pgClient.WrapDBError("failed to update row to key_store table", pgxErr) | ||
} | ||
return nil | ||
} | ||
|
||
// ReadKeyContent reads key content from the database | ||
func (c *Client) ReadKeyContent(name string) (string, errors.EdgeX) { | ||
var fileContent string | ||
row := c.ConnPool.QueryRow(context.Background(), | ||
fmt.Sprintf("SELECT %s FROM %s WHERE %s = $1", contentCol, keyStoreTableName, nameCol), name) | ||
if err := row.Scan(&fileContent); err != nil { | ||
return fileContent, pgClient.WrapDBError("failed to query key content", err) | ||
} | ||
return fileContent, nil | ||
} | ||
|
||
// KeyExists check whether the key file exits | ||
func (c *Client) KeyExists(filename string) (bool, errors.EdgeX) { | ||
var exists bool | ||
err := c.ConnPool.QueryRow(context.Background(), sqlCheckExistsByCol(keyStoreTableName, nameCol), filename).Scan(&exists) | ||
if err != nil { | ||
return false, pgClient.WrapDBError(fmt.Sprintf("failed to check key by name '%s' from %s table", nameCol, keyStoreTableName), err) | ||
} | ||
return exists, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
// | ||
// Copyright (C) 2024 IOTech Ltd | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package utils | ||
|
||
import ( | ||
"bytes" | ||
"crypto/aes" | ||
"crypto/cipher" | ||
"crypto/rand" | ||
"encoding/base64" | ||
"io" | ||
|
||
"github.com/edgexfoundry/go-mod-core-contracts/v4/errors" | ||
) | ||
|
||
const aesKey = "6NKEwDmK9Jz2aa6IqcTS6jNlA3mQP53B" | ||
|
||
// AESCryptor defined the AES cryptor struct | ||
type AESCryptor struct { | ||
key []byte | ||
} | ||
|
||
func NewAESCryptor() *AESCryptor { | ||
return &AESCryptor{ | ||
key: []byte(aesKey), | ||
} | ||
} | ||
|
||
// Encrypt encrypts the given plaintext with AES-CBC mode and returns a string in base64 encoding | ||
func (c AESCryptor) Encrypt(plaintext string) (string, errors.EdgeX) { | ||
bytePlaintext := []byte(plaintext) | ||
block, err := aes.NewCipher(c.key) | ||
if err != nil { | ||
return "", errors.NewCommonEdgeX(errors.KindServerError, "encrypt failed", err) | ||
} | ||
|
||
// CBC mode works on blocks so plaintexts may need to be padded to the next whole block | ||
paddedPlaintext := pkcs7Pad(bytePlaintext, block.BlockSize()) | ||
|
||
ciphertext := make([]byte, aes.BlockSize+len(paddedPlaintext)) | ||
// attach a random iv ahead of the ciphertext | ||
iv := ciphertext[:aes.BlockSize] | ||
if _, err := io.ReadFull(rand.Reader, iv); err != nil { | ||
return "", errors.NewCommonEdgeX(errors.KindServerError, "encrypt failed", err) | ||
} | ||
|
||
mode := cipher.NewCBCEncrypter(block, iv) | ||
mode.CryptBlocks(ciphertext[aes.BlockSize:], paddedPlaintext) | ||
|
||
return base64.StdEncoding.EncodeToString(ciphertext), nil | ||
} | ||
|
||
// Decrypt decrypts the given ciphertext with AES-CBC mode and returns the original value as string | ||
func (c AESCryptor) Decrypt(ciphertext string) ([]byte, errors.EdgeX) { | ||
decodedCipherText, err := base64.StdEncoding.DecodeString(ciphertext) | ||
if err != nil { | ||
return nil, errors.NewCommonEdgeX(errors.KindServerError, "decrypt failed", err) | ||
} | ||
|
||
block, err := aes.NewCipher(c.key) | ||
if err != nil { | ||
return nil, errors.NewCommonEdgeX(errors.KindServerError, "decrypt failed", err) | ||
} | ||
|
||
if len(decodedCipherText) < aes.BlockSize { | ||
return nil, errors.NewCommonEdgeX(errors.KindServerError, "decrypt failed", err) | ||
} | ||
|
||
// get the iv from the cipher text | ||
iv := decodedCipherText[:aes.BlockSize] | ||
decodedCipherText = decodedCipherText[aes.BlockSize:] | ||
|
||
mode := cipher.NewCBCDecrypter(block, iv) | ||
mode.CryptBlocks(decodedCipherText, decodedCipherText) | ||
|
||
// If the original plaintext lengths are not a multiple of the block | ||
// size, padding would have to be added when encrypting, which would be | ||
// removed at this point | ||
plaintext, e := pkcs7Unpad(decodedCipherText) | ||
if e != nil { | ||
return nil, errors.NewCommonEdgeXWrapper(err) | ||
} | ||
|
||
return plaintext, nil | ||
} | ||
|
||
// pkcs7Pad implements the PKCS7 padding | ||
func pkcs7Pad(data []byte, blockSize int) []byte { | ||
padding := blockSize - (len(data) % blockSize) | ||
padText := bytes.Repeat([]byte{byte(padding)}, padding) | ||
return append(data, padText...) | ||
} | ||
|
||
// pkcs7Unpad implements the PKCS7 unpadding | ||
func pkcs7Unpad(data []byte) ([]byte, errors.EdgeX) { | ||
length := len(data) | ||
unpadding := int(data[length-1]) | ||
if unpadding > length { | ||
return nil, errors.NewCommonEdgeX(errors.KindServerError, "invalid padding", nil) | ||
} | ||
return data[:(length - unpadding)], nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// | ||
// Copyright (C) 2024 IOTech Ltd | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package utils | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestAESCryptor_Encryption(t *testing.T) { | ||
testData := "test data" | ||
aesCryptor := NewAESCryptor() | ||
|
||
encrypted, err := aesCryptor.Encrypt(testData) | ||
require.NoError(t, err) | ||
|
||
decrypted, err := aesCryptor.Decrypt(encrypted) | ||
require.NoError(t, err) | ||
require.Equal(t, testData, string(decrypted)) | ||
} |
Oops, something went wrong.