Skip to content
3 changes: 3 additions & 0 deletions lib/ocrypto/aes_gcm.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ func (aesGcm AesGcm) EncryptWithIVAndTagSize(iv, data []byte, authTagSize int) (
// NOTE: This method use nonce of 12 bytes and auth tag as aes block size(16 bytes)
// also expects IV as preamble of data.
func (aesGcm AesGcm) Decrypt(data []byte) ([]byte, error) { // extract nonce and cipherText
if len(data) < GcmStandardNonceSize+aes.BlockSize {
return nil, errors.New("ciphertext too short")
}
nonce, cipherText := data[:GcmStandardNonceSize], data[GcmStandardNonceSize:]

gcm, err := cipher.NewGCMWithNonceSize(aesGcm.block, GcmStandardNonceSize)
Expand Down
32 changes: 32 additions & 0 deletions lib/ocrypto/asym_encryption.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"errors"
"fmt"
"io"
"strconv"
"strings"

"golang.org/x/crypto/hkdf"
Expand All @@ -36,6 +37,9 @@ type PublicKeyEncryptor interface {
// Type required to use the scheme for encryption - notably, if it procduces extra metadata.
Type() SchemeType

// KeyType returns the key type, e.g. RSA or EC.
KeyType() KeyType

// For EC schemes, this method returns the public part of the ephemeral key.
// Otherwise, it returns nil.
EphemeralKey() []byte
Expand Down Expand Up @@ -139,10 +143,38 @@ func (e AsymEncryption) Type() SchemeType {
return RSA
}

func (e AsymEncryption) KeyType() KeyType {
switch e.PublicKey.Size() {
case RSA2048Size / 8: //nolint:mnd // standard key size in bytes
return RSA2048Key
case RSA4096Size / 8: //nolint:mnd // large key size in bytes
return RSA4096Key
default:
bitlen := e.PublicKey.Size() * 8 //nolint:mnd // convert to bits
return KeyType("rsa:" + strconv.Itoa(bitlen))
}
}

func (e ECEncryptor) Type() SchemeType {
return EC
}

func (e ECEncryptor) KeyType() KeyType {
switch e.pub.Curve() {
case ecdh.P256():
return EC256Key
case ecdh.P384():
return EC384Key
case ecdh.P521():
return EC521Key
default:
if n, ok := e.pub.Curve().(fmt.Stringer); ok {
return KeyType("ec:" + n.String())
}
return KeyType("ec:[unknown]")
}
}

func (e AsymEncryption) EphemeralKey() []byte {
return nil
}
Expand Down
137 changes: 137 additions & 0 deletions lib/ocrypto/asym_encryption_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package ocrypto

import (
"os"
"path/filepath"
"testing"

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

func TestFromPublicPEM(t *testing.T) {
testCases := []struct {
name string
filename string
expectedType KeyType
}{
{
name: "EC secp256r1 public key",
filename: "sample-ec-secp256r1-01-public.pem",
expectedType: EC256Key,
},
{
name: "EC secp384r1 public key",
filename: "sample-ec-secp384r1-01-public.pem",
expectedType: EC384Key,
},
{
name: "EC secp521r1 public key",
filename: "sample-ec-secp521r1-01-public.pem",
expectedType: EC521Key,
},
{
name: "RSA 2048 public key",
filename: "sample-rsa-2048-01-public.pem",
expectedType: RSA2048Key,
},
{
name: "RSA 4096 public key",
filename: "sample-rsa-4096-01-public.pem",
expectedType: RSA4096Key,
},
{
name: "Unsupported RSA 1024 public key",
filename: "sample-rsa-1024-01-public.pem",
expectedType: KeyType("rsa:1024"),
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Read the PEM file
testDataPath := filepath.Join("testdata", tc.filename)
pemData, err := os.ReadFile(testDataPath)
require.NoError(t, err, "Failed to read test file %s", tc.filename)

// Load the public key using FromPublicPEM
encryptor, err := FromPublicPEM(string(pemData))
require.NoError(t, err, "Failed to load public key from %s", tc.filename)
require.NotNil(t, encryptor, "Encryptor should not be nil") // Test that KeyType() returns the expected type
keyType := encryptor.KeyType()
assert.Equal(t, tc.expectedType, keyType, "KeyType() returned unexpected value for %s", tc.name)

// Also test that we can get the public key back in PEM format
pubKeyPEM, err := encryptor.PublicKeyInPemFormat()
require.NoError(t, err, "Failed to get public key in PEM format")
assert.NotEmpty(t, pubKeyPEM, "Public key PEM should not be empty")
})
}
}

func TestFromPublicPEM_UnsupportedFiles(t *testing.T) {
testCases := []struct {
name string
filename string
}{
{
name: "Unsupported EC secp256k1 public key",
filename: "sample-ec-secp256k1-01-public.pem",
},
{
name: "Unsupported EC brainpoolP160r1 public key",
filename: "sample-ec-brainpoolP160r1-01-public.pem",
},
{
name: "Loading a private key should fail",
filename: "sample-ec-secp256r1-01-private.pem",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Read the PEM file
testDataPath := filepath.Join("testdata", tc.filename)
pemData, err := os.ReadFile(testDataPath)
require.NoError(t, err, "Failed to read test file %s", tc.filename)

// Load the public key using FromPublicPEM - should fail for unsupported curves
_, err = FromPublicPEM(string(pemData))
assert.Error(t, err, "Expected error for unsupported curve %s", tc.name)
})
}
}

func TestFromPublicPEM_InvalidInput(t *testing.T) {
testCases := []struct {
name string
pemData string
errorMsg string
}{
{
name: "Empty string",
pemData: "",
errorMsg: "failed to parse PEM formatted public key",
},
{
name: "Invalid PEM format",
pemData: "not a pem file",
errorMsg: "failed to parse PEM formatted public key",
},
{
name: "Invalid PEM content",
pemData: `-----BEGIN PUBLIC KEY-----
invalid base64 content!!!
-----END PUBLIC KEY-----`,
errorMsg: "failed to parse PEM formatted public key",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, err := FromPublicPEM(tc.pemData)
require.Error(t, err)
assert.Contains(t, err.Error(), tc.errorMsg)
})
}
}
40 changes: 40 additions & 0 deletions lib/ocrypto/testdata/genkeys.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/bin/bash
# genkeys.sh
# Generate test keys for ocrypto unit tests

set -e

# Ensure we're in the correct directory
cd "$(dirname "$0")"

echo "Generating test keys..."

# Which EC curves we are using to generate keys
ec_curves=(
"secp256r1"
"secp384r1"
"secp521r1"
"secp256k1"
"brainpoolP160r1"
)

# Generate EC keys
for curve_name in "${ec_curves[@]}"; do
echo "Generating EC $curve_name keys..."
openssl ecparam -name "$curve_name" -genkey -noout -out "sample-ec-$curve_name-01-private.pem"
openssl ec -in "sample-ec-$curve_name-01-private.pem" -pubout -out "sample-ec-$curve_name-01-public.pem"
done

# What RSA bit lengths we want to test
rsa_bits=(2048 4096 1024)

# Generate RSA keys
for bits in "${rsa_bits[@]}"; do
echo "Generating RSA $bits keys..."
openssl genpkey -algorithm RSA -out "sample-rsa-$bits-01-private.pem" -pkeyopt "rsa_keygen_bits:$bits"
openssl rsa -in "sample-rsa-$bits-01-private.pem" -pubout -out "sample-rsa-$bits-01-public.pem"
done

echo "Test key generation complete!"
echo "Generated keys:"
ls -la sample-*.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN EC PRIVATE KEY-----
MFQCAQEEFHp8z00th6pcyzd45TD4mVz3iDRCoAsGCSskAwMCCAEBAaEsAyoABCSG
cu/mTV74q8bJUDDgA3gJ8nWojRgEt5WwkT9j0viJT2OLMWlf+7o=
-----END EC PRIVATE KEY-----
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MEIwFAYHKoZIzj0CAQYJKyQDAwIIAQEBAyoABCSGcu/mTV74q8bJUDDgA3gJ8nWo
jRgEt5WwkT9j0viJT2OLMWlf+7o=
-----END PUBLIC KEY-----
5 changes: 5 additions & 0 deletions lib/ocrypto/testdata/sample-ec-secp256k1-01-private.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIDmKI51/sEYpwoPhQSOzcIGI+e0VE7Kux9bCfC8p0qJroAcGBSuBBAAK
oUQDQgAEadV+Y82QMEpZyzFtdu5K5LA7eO+3Yu0Ms/4Gic7O0IEi6S1SZOCzALJb
e6mC5IcF1nm/dDC7xCQVgMHughWVPw==
-----END EC PRIVATE KEY-----
4 changes: 4 additions & 0 deletions lib/ocrypto/testdata/sample-ec-secp256k1-01-public.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEadV+Y82QMEpZyzFtdu5K5LA7eO+3Yu0M
s/4Gic7O0IEi6S1SZOCzALJbe6mC5IcF1nm/dDC7xCQVgMHughWVPw==
-----END PUBLIC KEY-----
5 changes: 5 additions & 0 deletions lib/ocrypto/testdata/sample-ec-secp256r1-01-private.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIP1yOQQkE1gy3ktmG3dsk/M3AcXYTf7DwRhFmSv8XJ4LoAoGCCqGSM49
AwEHoUQDQgAENM4HHVFcRnCBycNVgYo1ArhtVz65ddbkQCCdIwsi8NFSHza6tZyM
s3LzQ1n46CnnXsgg/V26pYouAKOUcuRaOA==
-----END EC PRIVATE KEY-----
4 changes: 4 additions & 0 deletions lib/ocrypto/testdata/sample-ec-secp256r1-01-public.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENM4HHVFcRnCBycNVgYo1ArhtVz65
ddbkQCCdIwsi8NFSHza6tZyMs3LzQ1n46CnnXsgg/V26pYouAKOUcuRaOA==
-----END PUBLIC KEY-----
6 changes: 6 additions & 0 deletions lib/ocrypto/testdata/sample-ec-secp384r1-01-private.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDAJxnVCC6ZJpuIDKz4S2sBV1TWMC8j4slIDKUWd+zueNPRxAINfACpP
r10ZCFofJxqgBwYFK4EEACKhZANiAATXFxgvsn7o3/kwlWTTP9Ue44ddtX173FwB
7q8WCs5THpnhHwG4uepfQWlyI5owHwIpO7aqGaJ0zyswikTH8E6wOv2W2iLYMAgW
L2S+8GNimGcGLwwdg9kTggUxf+BPOFI=
-----END EC PRIVATE KEY-----
5 changes: 5 additions & 0 deletions lib/ocrypto/testdata/sample-ec-secp384r1-01-public.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE1xcYL7J+6N/5MJVk0z/VHuOHXbV9e9xc
Ae6vFgrOUx6Z4R8BuLnqX0FpciOaMB8CKTu2qhmidM8rMIpEx/BOsDr9ltoi2DAI
Fi9kvvBjYphnBi8MHYPZE4IFMX/gTzhS
-----END PUBLIC KEY-----
7 changes: 7 additions & 0 deletions lib/ocrypto/testdata/sample-ec-secp521r1-01-private.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-----BEGIN EC PRIVATE KEY-----
MIHcAgEBBEIAdSyAcDXV5TQHs1Q1jk41srxicAmZdMY2z7uXqBOysp2hpEq13lYk
cfSfpFE3zzu/iFf808AouXiK6EsBIqXu6SCgBwYFK4EEACOhgYkDgYYABABainiG
YYOEHe6kgIKvfZfQ8GygjvSCE3sjLsEQvf0d3KH07sGiHgUYJLEyhKI/I4a1rHvw
Z8oYuyxyNisSKAT+fAE7Xf/JpWFFeon60N1+/f/Bm+xZ3YKqriEktJnz8tsPFedM
w94tyYFptM69fHrKij+JfTnYtVzIm+rPHQrCOs/JaA==
-----END EC PRIVATE KEY-----
6 changes: 6 additions & 0 deletions lib/ocrypto/testdata/sample-ec-secp521r1-01-public.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAWop4hmGDhB3upICCr32X0PBsoI70
ghN7Iy7BEL39Hdyh9O7Boh4FGCSxMoSiPyOGtax78GfKGLsscjYrEigE/nwBO13/
yaVhRXqJ+tDdfv3/wZvsWd2Cqq4hJLSZ8/LbDxXnTMPeLcmBabTOvXx6yoo/iX05
2LVcyJvqzx0KwjrPyWg=
-----END PUBLIC KEY-----
16 changes: 16 additions & 0 deletions lib/ocrypto/testdata/sample-rsa-1024-01-private.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANO/UmUeB9MY5z26
uT/Qwdn1tHEySzewCsWnnDHMgQeQlCjB+6go1XXxpOYv4PiArnp+vINmj1TKkL2L
pFS1/JvD6d564ecba7SgpcH/iToRi13kuXwrocNrsT4zxWO+lpoR1G+CzJ5JSH0H
b+NG4QQ6DwQT6Nj6A5Zd/j6V0GSnAgMBAAECgYBWVnJgLIiAOG1BLDuQm6wPFTJH
3Xvx7uPVh+wWGg6aaQcgP0g/Xrb66laUTP1sFfwOklKHOXBD4Hx37NJKgBHJKt3b
CXs9FCDIqJak+9LLVBgbSuzJ+XUN4HSEfgXveJ7PLEyrxVpwYzIybgTEbO2IA+51
fC67jnSw4pq7Eu10AQJBAP2xqKEfbdFHJ3FfLoGT0aSfst91sZFQUvQL8zs+2skJ
iWSpX+64QGyeVgKXqaevzDGmkMnIqXQdYyBGjJSG/wECQQDVrA3ZsxUu9Kma9oJ0
Pxs4aGNQJGrgOVeVOa4LEYwia/BmZEq06w9FKLWbfXpZGsEIoiW97ZrnohxoqMqv
ywunAkEAu43TcELvClBDbcKDfFKPI9jZAfFd9GNg4IHRMZS3ZPdC9wNtI+xd3K92
QPZk+86w9GgDFNrfxDNRrHPbzJa9AQJAZiBiTldGHLdcCXEhUSaIgCGEtl1xp9JA
hlaXVTsB28HzmTz+aBKhrdCTXMpQnB4pfVLi7zCOBYB6S5vBpNxLqQJBAJ7peIHi
8rWqOtSElK97nwtjrtnyeqHGXz2yI+awU9OyFDnMgBUuowwmIoApiMKTpZ+zfH8N
mtpo5D7DfLSqkNU=
-----END PRIVATE KEY-----
6 changes: 6 additions & 0 deletions lib/ocrypto/testdata/sample-rsa-1024-01-public.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDTv1JlHgfTGOc9urk/0MHZ9bRx
Mks3sArFp5wxzIEHkJQowfuoKNV18aTmL+D4gK56fryDZo9UypC9i6RUtfybw+ne
euHnG2u0oKXB/4k6EYtd5Ll8K6HDa7E+M8VjvpaaEdRvgsyeSUh9B2/jRuEEOg8E
E+jY+gOWXf4+ldBkpwIDAQAB
-----END PUBLIC KEY-----
28 changes: 28 additions & 0 deletions lib/ocrypto/testdata/sample-rsa-2048-01-private.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCzv7o3b1JcNFpM
pgUyIRatTqxTdCfA6PTltAQDFK37ZM/Zg1mC+QmNMaTXPxNXYlmNKzLnACKn/nEZ
15n+x/t2uHKSE2tM9hxQnBJkJrtm7zM6z5ztNO8fbqIFEB98bRzck7/WXFVfOrKS
Tc9tUku9xk8NRDSHKK0A6w/9Y51NnwwalLC8G66WonsXyRQOurcuxtcUHRKLOOo0
XZ65vKsZgGfYXQpsWb+N6sf7yDA28yn7aLX53qUFXZmeV8+7KLTopEFZdnNUJNzu
WqRY1dHC8DLi1/mzsSf9pg7/uzuV34J/mbiPkYcXPXuLnvRbFBOZ7Nn9YH2prWVA
0ck7tTqFAgMBAAECggEAE6TbKsZDe7ir+q71J+iWVBviON+bnZlH9FeTTavjpLL8
hSK19FqXmOLpRy3JRRZGP6eOMVEiOHZV8XNOzNmZqXyYZs7w/dDywLuBxgi2l9YB
5QY9+e18SZTbZ46+xigdjJyoDTr7iIP/cn5G8kVZajTDPGmtDO1c1Nobnf9WOF5/
raFPN8ouWLaBoL3EQ2CKH/rCPLZIDVWQoawcKgtHshLDy4q5Hi94foAifKvK8cbl
sP2MzuDYWvZ3Bp/0+C5S4W2qbJdo4bv/XAkxzO0hujiaX+hJTa4ZZC2k8/9JgEYz
2P31NloH7Hofy5wKNtDv6nP+v6vBCRmRZm1/nw9TwQKBgQDiTOAxgvMBNxhF0zuo
CX3J7mlU7YSkGJQGhjdltbB4OwcZSaC1pUwN0x7O4lATBZHJ2LeYj2r/mMmaY2a0
+XgF+eTwVzaiYv8GS6/G//ILnd6ufS8P1k1UmsT408OfMVtq9DrVNtAlaqLWl3Y5
SJVf7zpykj2YFSXwogikyqZpxQKBgQDLVteg+FMk2QtcrKqfsNH3RIiSJ90X2PH4
ZM4YaahuecT9BFRQPdCzkAICBjh17Bt0WigDGkZZ/I6izf91/E6chBa0TW59Dpq0
ojyRVePHBpg85uH6g2bOxkCYlXQ4SIozHPe1bshAkSkkTCxPtJ6cq4yJObQmF68o
M7QUPTJZwQKBgQCX1gTGs5ngQtsiXmw0fsnLZw99UDAi+eq3xe39bD6PLOvCZ8hQ
mCvDStfs76PSX3ZF/AaTcgbUn+sEj5Ul8Aw71kNpjtq1cb6ytq2l06zPZok2gf/F
nIAeOAnY+hzS/wbbaCrhS/m0YSwI128XWEABMj4BCWYSWH4wSkeKaf3mEQKBgBXW
J6XzxQoJ/Pxg1pn7pTDGvVvkyAuNkr64JKHehuYGUa9STbOoT8dYyb5p6JpRVslx
/SYIJlH3m2HEeZC0HcUVMlL+lcT8UoTff12kOaff/21a5h2/CsVd6QX51tdMgvrm
O3vSf9LfQ+nP/Fo67WWpzpfWCJCmrnrEwqwBvmyBAoGBAKZ3YWGpSPnLV2TZpZ8m
NY0EykcKoghDwGFFtSPLb5hzSTiSs5eBEzNpuNnWdePdg3rTYG7I1ojhTkYngDn9
2GPb7XHPADZFvQdsZ2V9D4ahio8sWUtlcZq+KLjt86KVBJdswgfuiUKGwITPZfOG
JJAivPZMBCoF0NFP2mCyibAj
-----END PRIVATE KEY-----
9 changes: 9 additions & 0 deletions lib/ocrypto/testdata/sample-rsa-2048-01-public.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs7+6N29SXDRaTKYFMiEW
rU6sU3QnwOj05bQEAxSt+2TP2YNZgvkJjTGk1z8TV2JZjSsy5wAip/5xGdeZ/sf7
drhykhNrTPYcUJwSZCa7Zu8zOs+c7TTvH26iBRAffG0c3JO/1lxVXzqykk3PbVJL
vcZPDUQ0hyitAOsP/WOdTZ8MGpSwvBuulqJ7F8kUDrq3LsbXFB0SizjqNF2eubyr
GYBn2F0KbFm/jerH+8gwNvMp+2i1+d6lBV2ZnlfPuyi06KRBWXZzVCTc7lqkWNXR
wvAy4tf5s7En/aYO/7s7ld+Cf5m4j5GHFz17i570WxQTmezZ/WB9qa1lQNHJO7U6
hQIDAQAB
-----END PUBLIC KEY-----
Loading
Loading