From b2a5a4bb290bed48a6270ade556f4f9f243ee99f Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Mon, 11 Nov 2024 16:44:36 +0100 Subject: [PATCH 01/23] provide interface specification for web api v1 --- docs/architecture-decision-record/api-v1.md | 20 ++++++++++++++++++++ docs/diagrams/.gitkeep | 0 2 files changed, 20 insertions(+) create mode 100644 docs/architecture-decision-record/api-v1.md create mode 100644 docs/diagrams/.gitkeep diff --git a/docs/architecture-decision-record/api-v1.md b/docs/architecture-decision-record/api-v1.md new file mode 100644 index 0000000..dd38546 --- /dev/null +++ b/docs/architecture-decision-record/api-v1.md @@ -0,0 +1,20 @@ +# api-v1 + +- **Encryption and Decryption of files**: For encryption or decryption, we expect the file to be uploaded as multipart/form-data and the file content will be processed based on the specified encryption/decryption algorithm (e.g. AES or RSA). +- **Hashing of files**: Hashing a file is useful for ensuring file integrity. This can be done using algorithms like SHA-256 or MD5. The resulting hash can be used to verify if the file was modified or corrupted. +- **Signature Verification**: When verifying a file signature, the system compares the provided signature (signed by a private key) with the file content using a public key (e.g. RSA). + +--- + +| **Method** | **Endpoint** | **Description** | **Request Body** (Multipart/Form-Data) | **Response** | +|------------|-------------------------------------|----------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------| +| **POST** | `/api/v1/blobs` | Upload a blob with optional encryption and/or hashing enabled. | `encryption_algorithm: AES
encryption_key_id: encryptionKey123
hash_algorithm: SHA-256
hash_key_id: hashKey123
file: ` | `{ "blob_id": "123", "name": "file1.txt", "date_time_created": "2024-11-01T10:00:00Z", "date_time_updated": "2024-11-01T10:00:00Z", "encryption_key_id": "encryptionKey123", "hash_key_id": "hashKey123" }` | +| **GET** | `/api/v1/blobs/{blob_id}` | Download the blob by its ID. | None | `{ "file": }` | +| **GET** | `/api/v1/blobs/{blob_id}/meta` | Retrieve metadata associated with a blob by its ID. | None | `{ "blob_id": "123", "name": "file1.txt", "date_time_created": "2024-11-01T10:00:00Z", "date_time_updated": "2024-11-01T10:00:00Z", "encryption_key_id": "encryptionKey123", "hash_key_id": "hashKey123" }` | +| **DELETE** | `/api/v1/blobs/{blob_id}` | Delete a blob by its ID. | None | `{ "message": "Blob deleted successfully" }` | +| **PUT** | `/api/v1/blobs/{blob_id}` | Update an existing blob with new content, metadata, or re-encrypt it. | `encryption_algorithm: AES
encryption_key_id: encryptionKey123
hash_algorithm: SHA-256
hash_key_id: hashKey123
file: ` | `{ "blob_id": "123", "name": "file1.txt", "date_time_created": "2024-11-01T10:00:00Z", "date_time_updated": "2024-11-01T10:30:00Z", "encryption_key_id": "encryptionKey123", "hash_key_id": "hashKey123" }` | +| **POST** | `/api/v1/keys` | Create a new key in the KeyVault. | `name: example-key
algorithm: RSA
key_size: 2048` | `{ "key_id": "key123", "name": "example-key", "status": "created" }` | +| **GET** | `/api/v1/keys/{key_id}` | Retrieve an existing key from the KeyVault by its ID. | None | `{ "key_id": "key123", "name": "example-key", "algorithm": "RSA", "key_size": 2048 }` | +| **GET** | `/api/v1/keys` | List all keys stored in the KeyVault. | None | `{ "keys": [ { "key_id": "key123", "name": "example-key", "algorithm": "RSA", "key_size": 2048 }, ... ] }` | +| **PUT** | `/api/v1/keys/{key_id}/rotate` | Rotate (update) the key with a new version in the KeyVault. | None | `{ "key_id": "key123", "status": "rotated", "new_version": "2" }` | +| **DELETE** | `/api/v1/keys/{key_id}` | Delete a key from the KeyVault by its ID. | None | `{ "message": "Key deleted successfully" }` | \ No newline at end of file diff --git a/docs/diagrams/.gitkeep b/docs/diagrams/.gitkeep new file mode 100644 index 0000000..e69de29 From 631b20cf873f910d2696b813e75d03c8105769ff Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Tue, 12 Nov 2024 13:34:56 +0100 Subject: [PATCH 02/23] add configs folder --- configs/dev.yml | 1 + configs/prd.yml | 1 + configs/qas.yml | 1 + 3 files changed, 3 insertions(+) create mode 100644 configs/dev.yml create mode 100644 configs/prd.yml create mode 100644 configs/qas.yml diff --git a/configs/dev.yml b/configs/dev.yml new file mode 100644 index 0000000..d3f5a12 --- /dev/null +++ b/configs/dev.yml @@ -0,0 +1 @@ + diff --git a/configs/prd.yml b/configs/prd.yml new file mode 100644 index 0000000..16e3853 --- /dev/null +++ b/configs/prd.yml @@ -0,0 +1 @@ +# diff --git a/configs/qas.yml b/configs/qas.yml new file mode 100644 index 0000000..d3f5a12 --- /dev/null +++ b/configs/qas.yml @@ -0,0 +1 @@ + From 304acd4aec7b510e9e6e824b0a08a422996d7834 Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Tue, 12 Nov 2024 13:36:35 +0100 Subject: [PATCH 03/23] add cobra and viper dependencies --- go.mod | 18 ++++++++++++++++++ go.sum | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/go.mod b/go.mod index 7d7b685..0532410 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/bytedance/sonic/loader v0.2.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.6 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-gonic/gin v1.10.0 // indirect @@ -14,20 +15,37 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.22.1 // indirect github.com/goccy/go-json v0.10.3 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/cobra v1.8.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.19.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect golang.org/x/arch v0.12.0 // indirect golang.org/x/crypto v0.29.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/net v0.31.0 // indirect golang.org/x/sys v0.27.0 // indirect golang.org/x/text v0.20.0 // indirect google.golang.org/protobuf v1.35.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e94b718..fab9a15 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,11 @@ github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/ github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc= github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -24,6 +27,10 @@ github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaC github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -32,8 +39,12 @@ github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -42,6 +53,23 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -51,14 +79,22 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg= golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -69,6 +105,8 @@ golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 4a0edc8beaf1cd0ce6016585b7e136051e37d551 Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Tue, 12 Nov 2024 13:48:24 +0100 Subject: [PATCH 04/23] decouple tests from lib --- pkg/cryptography/{decrypt.go => encryption.go} | 0 pkg/cryptography/{decrypt_test.go => hashing.go} | 0 .../integration-tests/cryptography/encryption_test.go | 0 .../integration-tests/cryptography/hashing_test.go | 0 {pkg => test/integration-tests}/storage/az_blob_test.go | 0 {pkg => test/integration-tests}/storage/az_postgres_test.go | 0 {pkg => test/integration-tests}/storage/az_vault_test.go | 0 .../hash.go => test/unit-tests/cryptography/encryption_test.go | 0 .../hash_test.go => test/unit-tests/cryptography/hashing_test.go | 0 .../unit-tests/storage/az_blob_test.go | 0 .../unit-tests/storage/az_postgres_test.go | 0 test/unit-tests/storage/az_vault_test.go | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename pkg/cryptography/{decrypt.go => encryption.go} (100%) rename pkg/cryptography/{decrypt_test.go => hashing.go} (100%) rename pkg/cryptography/encrypt.go => test/integration-tests/cryptography/encryption_test.go (100%) rename pkg/cryptography/encrypt_test.go => test/integration-tests/cryptography/hashing_test.go (100%) rename {pkg => test/integration-tests}/storage/az_blob_test.go (100%) rename {pkg => test/integration-tests}/storage/az_postgres_test.go (100%) rename {pkg => test/integration-tests}/storage/az_vault_test.go (100%) rename pkg/cryptography/hash.go => test/unit-tests/cryptography/encryption_test.go (100%) rename pkg/cryptography/hash_test.go => test/unit-tests/cryptography/hashing_test.go (100%) rename pkg/cryptography/verify_signature.go => test/unit-tests/storage/az_blob_test.go (100%) rename pkg/cryptography/verify_signature_test.go => test/unit-tests/storage/az_postgres_test.go (100%) create mode 100644 test/unit-tests/storage/az_vault_test.go diff --git a/pkg/cryptography/decrypt.go b/pkg/cryptography/encryption.go similarity index 100% rename from pkg/cryptography/decrypt.go rename to pkg/cryptography/encryption.go diff --git a/pkg/cryptography/decrypt_test.go b/pkg/cryptography/hashing.go similarity index 100% rename from pkg/cryptography/decrypt_test.go rename to pkg/cryptography/hashing.go diff --git a/pkg/cryptography/encrypt.go b/test/integration-tests/cryptography/encryption_test.go similarity index 100% rename from pkg/cryptography/encrypt.go rename to test/integration-tests/cryptography/encryption_test.go diff --git a/pkg/cryptography/encrypt_test.go b/test/integration-tests/cryptography/hashing_test.go similarity index 100% rename from pkg/cryptography/encrypt_test.go rename to test/integration-tests/cryptography/hashing_test.go diff --git a/pkg/storage/az_blob_test.go b/test/integration-tests/storage/az_blob_test.go similarity index 100% rename from pkg/storage/az_blob_test.go rename to test/integration-tests/storage/az_blob_test.go diff --git a/pkg/storage/az_postgres_test.go b/test/integration-tests/storage/az_postgres_test.go similarity index 100% rename from pkg/storage/az_postgres_test.go rename to test/integration-tests/storage/az_postgres_test.go diff --git a/pkg/storage/az_vault_test.go b/test/integration-tests/storage/az_vault_test.go similarity index 100% rename from pkg/storage/az_vault_test.go rename to test/integration-tests/storage/az_vault_test.go diff --git a/pkg/cryptography/hash.go b/test/unit-tests/cryptography/encryption_test.go similarity index 100% rename from pkg/cryptography/hash.go rename to test/unit-tests/cryptography/encryption_test.go diff --git a/pkg/cryptography/hash_test.go b/test/unit-tests/cryptography/hashing_test.go similarity index 100% rename from pkg/cryptography/hash_test.go rename to test/unit-tests/cryptography/hashing_test.go diff --git a/pkg/cryptography/verify_signature.go b/test/unit-tests/storage/az_blob_test.go similarity index 100% rename from pkg/cryptography/verify_signature.go rename to test/unit-tests/storage/az_blob_test.go diff --git a/pkg/cryptography/verify_signature_test.go b/test/unit-tests/storage/az_postgres_test.go similarity index 100% rename from pkg/cryptography/verify_signature_test.go rename to test/unit-tests/storage/az_postgres_test.go diff --git a/test/unit-tests/storage/az_vault_test.go b/test/unit-tests/storage/az_vault_test.go new file mode 100644 index 0000000..e69de29 From be814daa8a3a14c54b7d138d95017c1b39e199f9 Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Tue, 12 Nov 2024 13:52:49 +0100 Subject: [PATCH 05/23] rename folder --- {test => tests}/integration-tests/cryptography/encryption_test.go | 0 {test => tests}/integration-tests/cryptography/hashing_test.go | 0 {test => tests}/integration-tests/storage/az_blob_test.go | 0 {test => tests}/integration-tests/storage/az_postgres_test.go | 0 {test => tests}/integration-tests/storage/az_vault_test.go | 0 {test => tests}/unit-tests/cryptography/encryption_test.go | 0 {test => tests}/unit-tests/cryptography/hashing_test.go | 0 {test => tests}/unit-tests/storage/az_blob_test.go | 0 {test => tests}/unit-tests/storage/az_postgres_test.go | 0 {test => tests}/unit-tests/storage/az_vault_test.go | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename {test => tests}/integration-tests/cryptography/encryption_test.go (100%) rename {test => tests}/integration-tests/cryptography/hashing_test.go (100%) rename {test => tests}/integration-tests/storage/az_blob_test.go (100%) rename {test => tests}/integration-tests/storage/az_postgres_test.go (100%) rename {test => tests}/integration-tests/storage/az_vault_test.go (100%) rename {test => tests}/unit-tests/cryptography/encryption_test.go (100%) rename {test => tests}/unit-tests/cryptography/hashing_test.go (100%) rename {test => tests}/unit-tests/storage/az_blob_test.go (100%) rename {test => tests}/unit-tests/storage/az_postgres_test.go (100%) rename {test => tests}/unit-tests/storage/az_vault_test.go (100%) diff --git a/test/integration-tests/cryptography/encryption_test.go b/tests/integration-tests/cryptography/encryption_test.go similarity index 100% rename from test/integration-tests/cryptography/encryption_test.go rename to tests/integration-tests/cryptography/encryption_test.go diff --git a/test/integration-tests/cryptography/hashing_test.go b/tests/integration-tests/cryptography/hashing_test.go similarity index 100% rename from test/integration-tests/cryptography/hashing_test.go rename to tests/integration-tests/cryptography/hashing_test.go diff --git a/test/integration-tests/storage/az_blob_test.go b/tests/integration-tests/storage/az_blob_test.go similarity index 100% rename from test/integration-tests/storage/az_blob_test.go rename to tests/integration-tests/storage/az_blob_test.go diff --git a/test/integration-tests/storage/az_postgres_test.go b/tests/integration-tests/storage/az_postgres_test.go similarity index 100% rename from test/integration-tests/storage/az_postgres_test.go rename to tests/integration-tests/storage/az_postgres_test.go diff --git a/test/integration-tests/storage/az_vault_test.go b/tests/integration-tests/storage/az_vault_test.go similarity index 100% rename from test/integration-tests/storage/az_vault_test.go rename to tests/integration-tests/storage/az_vault_test.go diff --git a/test/unit-tests/cryptography/encryption_test.go b/tests/unit-tests/cryptography/encryption_test.go similarity index 100% rename from test/unit-tests/cryptography/encryption_test.go rename to tests/unit-tests/cryptography/encryption_test.go diff --git a/test/unit-tests/cryptography/hashing_test.go b/tests/unit-tests/cryptography/hashing_test.go similarity index 100% rename from test/unit-tests/cryptography/hashing_test.go rename to tests/unit-tests/cryptography/hashing_test.go diff --git a/test/unit-tests/storage/az_blob_test.go b/tests/unit-tests/storage/az_blob_test.go similarity index 100% rename from test/unit-tests/storage/az_blob_test.go rename to tests/unit-tests/storage/az_blob_test.go diff --git a/test/unit-tests/storage/az_postgres_test.go b/tests/unit-tests/storage/az_postgres_test.go similarity index 100% rename from test/unit-tests/storage/az_postgres_test.go rename to tests/unit-tests/storage/az_postgres_test.go diff --git a/test/unit-tests/storage/az_vault_test.go b/tests/unit-tests/storage/az_vault_test.go similarity index 100% rename from test/unit-tests/storage/az_vault_test.go rename to tests/unit-tests/storage/az_vault_test.go From a1d2c20f038e6e565d2d4653c55aba66942636cc Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Tue, 12 Nov 2024 13:55:32 +0100 Subject: [PATCH 06/23] add bullet point --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 03be907..5e15dc8 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ TBD - [ ] **Provide RESTful API for cryptographic operations**: Expose endpoints for managing cryptographic material and securing data (files, metadata) at rest. - [ ] **Asymmetric encryption and decryption**: Support RSA, ECC and other asymmetric encryption algorithms for data protection. +- [ ] **PKCS#11 integration**: Enable key management in FIPS-compliant hardware or software environments. - [ ] **Symmetric encryption**: Support for symmetric key encryption (e.g. AES) for data protection. - [ ] **Manage cryptographic material**: Enable management of X.509 certificates, private/public key pairs and symmetric keys (generation, import/export, rotation, etc.). - [ ] **Hashing and signature verification**: Support hashing algorithms (e.g. SHA-256, SHA-512) and verify signatures using asymmetric keys (RSA, ECDSA, etc.). From 35364b5dc56305d624789ccc28a197068d4d6e14 Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Tue, 12 Nov 2024 13:57:11 +0100 Subject: [PATCH 07/23] do not consider x.509 certs --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5e15dc8..3cff7ed 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ## Summary -RESTful Web API for managing cryptographic material (x.509 certs and keys) and securing data at rest (metadata, BLOB) +RESTful Web API for managing cryptographic keys and securing data at rest (metadata, BLOB) ## References @@ -23,7 +23,7 @@ TBD - [ ] **Asymmetric encryption and decryption**: Support RSA, ECC and other asymmetric encryption algorithms for data protection. - [ ] **PKCS#11 integration**: Enable key management in FIPS-compliant hardware or software environments. - [ ] **Symmetric encryption**: Support for symmetric key encryption (e.g. AES) for data protection. -- [ ] **Manage cryptographic material**: Enable management of X.509 certificates, private/public key pairs and symmetric keys (generation, import/export, rotation, etc.). +- [ ] **Manage cryptographic material**: Enable management of private/public key pairs and symmetric keys (generation, import/export, rotation, etc.). - [ ] **Hashing and signature verification**: Support hashing algorithms (e.g. SHA-256, SHA-512) and verify signatures using asymmetric keys (RSA, ECDSA, etc.). - [ ] **Key management lifecycle**: Implement key lifecycle management (generation, rotation, revocation, expiration). - [ ] **Secure file storage integration**: Provide mechanisms to securely store encrypted files in BLOB storage (e.g. AWS S3, Azure Blob Storage, Google Cloud Storage). From 05daa07dc6813223d4cbe10b492cdb63596e48b8 Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Tue, 12 Nov 2024 14:49:01 +0100 Subject: [PATCH 08/23] add crypto-cli app utilizing RSA or AES --- cmd/crypto-vault-cli/README.md | 54 ++++ cmd/crypto-vault-cli/crypto-cli.go | 452 +++++++++++++++++++++++++++ cmd/crypto-vault-cli/data/.gitignore | 3 + cmd/crypto-vault-cli/data/input.txt | 1 + 4 files changed, 510 insertions(+) create mode 100644 cmd/crypto-vault-cli/README.md create mode 100644 cmd/crypto-vault-cli/crypto-cli.go create mode 100644 cmd/crypto-vault-cli/data/.gitignore create mode 100644 cmd/crypto-vault-cli/data/input.txt diff --git a/cmd/crypto-vault-cli/README.md b/cmd/crypto-vault-cli/README.md new file mode 100644 index 0000000..ef80b17 --- /dev/null +++ b/cmd/crypto-vault-cli/README.md @@ -0,0 +1,54 @@ +# crypto-vault-cli + +`crypto-vault-cli` is a command-line tool for file encryption and decryption using AES, RSA and EC algorithms. It provides an easy interface to securely encrypt and decrypt files using symmetric (AES) and asymmetric (RSA, EC) cryptography. + +## Prerequisites + +Before you begin, ensure you have the following tools installed: + +- Install Go from the official Go website, or use this [devcontainer.json](../../.devcontainer/devcontainer.json) with the [DevContainer extensions in VS Code or other IDE supporting DevContainers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) + +## Getting Started + +## AES example + +```sh +# Encryption +go run crypto-cli.go encrypt-aes --input data/input.txt --output data/output.enc --keySize 16 --keyDir data/ +# Decryption +go run crypto-cli.go decrypt-aes --input data/output.enc --output data/decrypted.txt --keyDir data/ +``` + +## RSA Example + +### External key generation + +```sh +# Generate private and public keys externally +cd assets +openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048 +openssl rsa -pubout -in private_key.pem -out public_key.pem +cd - + +# Encryption +go run crypto-cli.go encrypt-rsa --input data/input.txt --output data/encryptedII.txt --publicKey data/public_key.pem + +# Decryption +go run crypto-cli.go decrypt-rsa --input data/encryptedII.txt --output data/decryptedII.txt --privateKey data/private_key.pem +``` + +### Internal key generation + +```sh +# Encryption +go run crypto-cli.go encrypt-rsa --input data/input.txt --output data/encryptedII.txt + +# Decryption +go run crypto-cli.go decrypt-rsa --input data/encryptedII.txt --output data/decryptedII.txt --privateKey data/private_key.pem +``` + +## EC Example + +```sh +TBD +``` \ No newline at end of file diff --git a/cmd/crypto-vault-cli/crypto-cli.go b/cmd/crypto-vault-cli/crypto-cli.go new file mode 100644 index 0000000..7dce99c --- /dev/null +++ b/cmd/crypto-vault-cli/crypto-cli.go @@ -0,0 +1,452 @@ +package main + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + + "github.com/spf13/cobra" +) + +// AES Functions +// GenerateRandomAESKey generates a random AES key of the specified size +func generateRandomAESKey(keySize int) ([]byte, error) { + key := make([]byte, keySize) + _, err := rand.Read(key) + if err != nil { + return nil, fmt.Errorf("failed to generate AES key: %v", err) + } + return key, nil +} + +// Pad data to make it a multiple of AES block size +func pkcs7Pad(data []byte, blockSize int) []byte { + padding := blockSize - len(data)%blockSize + padded := append(data, bytes.Repeat([]byte{byte(padding)}, padding)...) + return padded +} + +// Unpad data after decryption +func pkcs7Unpad(data []byte, blockSize int) ([]byte, error) { + length := len(data) + padding := int(data[length-1]) + + if padding > length || padding > blockSize { + return nil, fmt.Errorf("invalid padding size") + } + return data[:length-padding], nil +} + +// Encrypts data using AES in CBC mode +func encryptAES(plainText, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + plainText = pkcs7Pad(plainText, aes.BlockSize) + + ciphertext := make([]byte, aes.BlockSize+len(plainText)) + iv := ciphertext[:aes.BlockSize] + if _, err := rand.Read(iv); err != nil { + return nil, err + } + + mode := cipher.NewCBCEncrypter(block, iv) + mode.CryptBlocks(ciphertext[aes.BlockSize:], plainText) + + return ciphertext, nil +} + +// Decrypts data using AES in CBC mode +func decryptAES(ciphertext, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + if len(ciphertext) < aes.BlockSize { + return nil, fmt.Errorf("ciphertext too short") + } + + iv := ciphertext[:aes.BlockSize] + ciphertext = ciphertext[aes.BlockSize:] + + mode := cipher.NewCBCDecrypter(block, iv) + mode.CryptBlocks(ciphertext, ciphertext) + + return pkcs7Unpad(ciphertext, aes.BlockSize) +} + +// RSA Functions +func generateRSAKeys(bits int) (*rsa.PrivateKey, *rsa.PublicKey, error) { + privateKey, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return nil, nil, fmt.Errorf("failed to generate RSA keys: %v", err) + } + + publicKey := &privateKey.PublicKey + return privateKey, publicKey, nil +} + +func savePrivateKeyToFile(privateKey *rsa.PrivateKey, filename string) error { + privKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey) + privKeyPem := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: privKeyBytes, + } + + file, err := os.Create(filename) + if err != nil { + return fmt.Errorf("failed to create private key file: %v", err) + } + defer file.Close() + + err = pem.Encode(file, privKeyPem) + if err != nil { + return fmt.Errorf("failed to encode private key: %v", err) + } + + return nil +} + +func savePublicKeyToFile(publicKey *rsa.PublicKey, filename string) error { + pubKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return fmt.Errorf("failed to marshal public key: %v", err) + } + + pubKeyPem := &pem.Block{ + Type: "PUBLIC KEY", + Bytes: pubKeyBytes, + } + + file, err := os.Create(filename) + if err != nil { + return fmt.Errorf("failed to create public key file: %v", err) + } + defer file.Close() + + err = pem.Encode(file, pubKeyPem) + if err != nil { + return fmt.Errorf("failed to encode public key: %v", err) + } + + return nil +} + +// Encrypt data with RSA public key +func encryptWithRSA(publicKey *rsa.PublicKey, data []byte) ([]byte, error) { + encryptedData, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, data) + if err != nil { + return nil, fmt.Errorf("failed to encrypt data: %v", err) + } + return encryptedData, nil +} + +// Decrypt data with RSA private key +func decryptWithRSA(privateKey *rsa.PrivateKey, encryptedData []byte) ([]byte, error) { + decryptedData, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, encryptedData) + if err != nil { + return nil, fmt.Errorf("failed to decrypt data: %v", err) + } + return decryptedData, nil +} + +// File Operations +func readFile(filePath string) ([]byte, error) { + return ioutil.ReadFile(filePath) +} + +func writeFile(filePath string, data []byte) error { + return ioutil.WriteFile(filePath, data, 0644) +} + +// AES Command + +// Read RSA private key from PEM file +func readPrivateKey(privateKeyPath string) (*rsa.PrivateKey, error) { + privKeyPEM, err := ioutil.ReadFile(privateKeyPath) + if err != nil { + return nil, fmt.Errorf("unable to read private key file: %v", err) + } + + block, _ := pem.Decode(privKeyPEM) + if block == nil { + return nil, fmt.Errorf("failed to parse PEM block containing the private key") + } + + privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("unable to parse private key: %v", err) + } + + return privateKey, nil +} + +// Read RSA public key from PEM file +func readPublicKey(publicKeyPath string) (*rsa.PublicKey, error) { + pubKeyPEM, err := ioutil.ReadFile(publicKeyPath) + if err != nil { + return nil, fmt.Errorf("unable to read public key file: %v", err) + } + + block, _ := pem.Decode(pubKeyPEM) + if block == nil { + return nil, fmt.Errorf("failed to parse PEM block containing the public key") + } + + publicKey, err := x509.ParsePKCS1PublicKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("unable to parse public key: %v", err) + } + + return publicKey, nil +} + +// Encrypts a file using AES and saves the encryption key +func encryptAESCmd(cmd *cobra.Command, args []string) { + inputFile, _ := cmd.Flags().GetString("input") + outputFile, _ := cmd.Flags().GetString("output") + keySize, _ := cmd.Flags().GetInt("keySize") + keyDir, _ := cmd.Flags().GetString("keyDir") + + // Validate input arguments + if inputFile == "" || outputFile == "" || keyDir == "" { + log.Fatalf("Error: input, output, and keyDir flags are required\n") + } + + // Generate AES Key + key, err := generateRandomAESKey(keySize) + if err != nil { + log.Fatalf("Error generating AES key: %v\n", err) + } + + // Encrypt the file + plainText, err := readFile(inputFile) + if err != nil { + log.Fatalf("Error reading input file: %v\n", err) + } + + encryptedData, err := encryptAES(plainText, key) + if err != nil { + log.Fatalf("Error encrypting data: %v\n", err) + } + + // Save encrypted file + err = writeFile(outputFile, encryptedData) + if err != nil { + log.Fatalf("Error writing encrypted file: %v\n", err) + } + fmt.Printf("Encrypted data saved to %s\n", outputFile) + + // Save the AES key to the specified key directory + keyFilePath := filepath.Join(keyDir, "encryption_key.bin") + err = writeFile(keyFilePath, key) + if err != nil { + log.Fatalf("Error writing AES key to file: %v\n", err) + } + fmt.Printf("AES key saved to %s\n", keyFilePath) +} + +func decryptAESCmd(cmd *cobra.Command, args []string) { + inputFile, _ := cmd.Flags().GetString("input") + outputFile, _ := cmd.Flags().GetString("output") + keyDir, _ := cmd.Flags().GetString("keyDir") + + // Validate input arguments + if inputFile == "" || outputFile == "" || keyDir == "" { + log.Fatalf("Error: input, output, and keyDir flags are required\n") + } + + // Read the encryption key from the specified directory + keyFilePath := filepath.Join(keyDir, "encryption_key.bin") + key, err := ioutil.ReadFile(keyFilePath) + if err != nil { + log.Fatalf("Error reading encryption key from file: %v\n", err) + } + + // Decrypt the file + encryptedData, err := readFile(inputFile) + if err != nil { + log.Fatalf("Error reading encrypted file: %v\n", err) + } + + decryptedData, err := decryptAES(encryptedData, key) + if err != nil { + log.Fatalf("Error decrypting data: %v\n", err) + } + + // Save decrypted file + err = writeFile(outputFile, decryptedData) + if err != nil { + log.Fatalf("Error writing decrypted file: %v\n", err) + } + fmt.Printf("Decrypted data saved to %s\n", outputFile) +} + +// RSA Command +func encryptRSACmd(cmd *cobra.Command, args []string) { + inputFile, _ := cmd.Flags().GetString("input") + outputFile, _ := cmd.Flags().GetString("output") + publicKeyPath, _ := cmd.Flags().GetString("publicKey") + + // Generate RSA keys if no public key is provided + var publicKey *rsa.PublicKey + var err error + if publicKeyPath == "" { + // Generate RSA keys + privateKey, pubKey, genErr := generateRSAKeys(2048) + if genErr != nil { + log.Fatalf("Error generating RSA keys: %v\n", genErr) + } + publicKey = pubKey + + // Optionally save the private and public keys + err = savePrivateKeyToFile(privateKey, "data/private_key.pem") + if err != nil { + log.Fatalf("Error saving private key: %v\n", err) + } + err = savePublicKeyToFile(publicKey, "data/public_key.pem") + if err != nil { + log.Fatalf("Error saving public key: %v\n", err) + } + fmt.Println("Generated and saved RSA keys.") + } else { + // Read the provided public key + publicKey, err = readPublicKey(publicKeyPath) + if err != nil { + log.Fatalf("Error reading public key: %v\n", err) + } + } + + // Encrypt the file + plainText, err := readFile(inputFile) + if err != nil { + log.Fatalf("Error reading input file: %v\n", err) + } + + encryptedData, err := encryptWithRSA(publicKey, plainText) + if err != nil { + log.Fatalf("Error encrypting data: %v\n", err) + } + + // Save encrypted file + err = writeFile(outputFile, encryptedData) + if err != nil { + log.Fatalf("Error writing encrypted file: %v\n", err) + } + fmt.Printf("Encrypted data saved to %s\n", outputFile) +} + +func decryptRSACmd(cmd *cobra.Command, args []string) { + inputFile, _ := cmd.Flags().GetString("input") + outputFile, _ := cmd.Flags().GetString("output") + privateKeyPath, _ := cmd.Flags().GetString("privateKey") + + // Generate RSA keys if no private key is provided + var privateKey *rsa.PrivateKey + var err error + if privateKeyPath == "" { + // Generate RSA keys + privKey, _, genErr := generateRSAKeys(2048) + if genErr != nil { + log.Fatalf("Error generating RSA keys: %v\n", genErr) + } + privateKey = privKey + + // Optionally save the private and public keys + err = savePrivateKeyToFile(privateKey, "private_key.pem") + if err != nil { + log.Fatalf("Error saving private key: %v\n", err) + } + fmt.Println("Generated and saved private key.") + } else { + // Read the provided private key + privateKey, err = readPrivateKey(privateKeyPath) + if err != nil { + log.Fatalf("Error reading private key: %v\n", err) + } + } + + // Decrypt the file + encryptedData, err := readFile(inputFile) + if err != nil { + log.Fatalf("Error reading encrypted file: %v\n", err) + } + + decryptedData, err := decryptWithRSA(privateKey, encryptedData) + if err != nil { + log.Fatalf("Error decrypting data: %v\n", err) + } + + // Save decrypted file + err = writeFile(outputFile, decryptedData) + if err != nil { + log.Fatalf("Error writing decrypted file: %v\n", err) + } + fmt.Printf("Decrypted data saved to %s\n", outputFile) +} + +// Main function +func main() { + var rootCmd = &cobra.Command{Use: "crypto-cli"} + + // AES Commands + var encryptAESFileCmd = &cobra.Command{ + Use: "encrypt-aes", + Short: "Encrypt a file using AES", + Run: encryptAESCmd, + } + encryptAESFileCmd.Flags().StringP("input", "i", "", "Input file path") + encryptAESFileCmd.Flags().StringP("output", "o", "", "Output encrypted file path") + encryptAESFileCmd.Flags().IntP("keySize", "k", 16, "AES key size (default 16 bytes for AES-128)") + encryptAESFileCmd.Flags().StringP("keyDir", "d", "", "Directory to store the encryption key") + rootCmd.AddCommand(encryptAESFileCmd) + + var decryptAESFileCmd = &cobra.Command{ + Use: "decrypt-aes", + Short: "Decrypt a file using AES", + Run: decryptAESCmd, + } + decryptAESFileCmd.Flags().StringP("input", "i", "", "Input encrypted file path") + decryptAESFileCmd.Flags().StringP("output", "o", "", "Output decrypted file path") + decryptAESFileCmd.Flags().StringP("keyDir", "d", "", "Directory to read the encryption key from") + rootCmd.AddCommand(decryptAESFileCmd) + + // RSA Commands + var encryptRSAFileCmd = &cobra.Command{ + Use: "encrypt-rsa", + Short: "Encrypt a file using RSA", + Run: encryptRSACmd, + } + encryptRSAFileCmd.Flags().StringP("input", "i", "", "Input file path") + encryptRSAFileCmd.Flags().StringP("output", "o", "", "Output encrypted file path") + encryptRSAFileCmd.Flags().StringP("publicKey", "p", "", "Path to RSA public key") + rootCmd.AddCommand(encryptRSAFileCmd) + + var decryptRSAFileCmd = &cobra.Command{ + Use: "decrypt-rsa", + Short: "Decrypt a file using RSA", + Run: decryptRSACmd, + } + decryptRSAFileCmd.Flags().StringP("input", "i", "", "Input encrypted file path") + decryptRSAFileCmd.Flags().StringP("output", "o", "", "Output decrypted file path") + decryptRSAFileCmd.Flags().StringP("privateKey", "r", "", "Path to RSA private key") + rootCmd.AddCommand(decryptRSAFileCmd) + + // Execute the root command + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/cmd/crypto-vault-cli/data/.gitignore b/cmd/crypto-vault-cli/data/.gitignore new file mode 100644 index 0000000..8007d75 --- /dev/null +++ b/cmd/crypto-vault-cli/data/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!input.txt \ No newline at end of file diff --git a/cmd/crypto-vault-cli/data/input.txt b/cmd/crypto-vault-cli/data/input.txt new file mode 100644 index 0000000..793aa68 --- /dev/null +++ b/cmd/crypto-vault-cli/data/input.txt @@ -0,0 +1 @@ +This is a test \ No newline at end of file From 9ed3091a1466897785524800465624821bceb3b6 Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Tue, 12 Nov 2024 14:50:20 +0100 Subject: [PATCH 09/23] modify README --- cmd/crypto-vault-cli/README.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/cmd/crypto-vault-cli/README.md b/cmd/crypto-vault-cli/README.md index ef80b17..5e6da1a 100644 --- a/cmd/crypto-vault-cli/README.md +++ b/cmd/crypto-vault-cli/README.md @@ -24,7 +24,6 @@ go run crypto-cli.go decrypt-aes --input data/output.enc --output data/decrypted ### External key generation ```sh -# Generate private and public keys externally cd assets openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048 openssl rsa -pubout -in private_key.pem -out public_key.pem @@ -49,6 +48,28 @@ go run crypto-cli.go decrypt-rsa --input data/encryptedII.txt --output data/decr ## EC Example +### External key generation + +```sh +TBD +``` + +### Internal key generation + +```sh +TBD +``` + +## RSA with PKCS#11 + +### External key generation + +```sh +TBD +``` + +### Internal key generation + ```sh TBD ``` \ No newline at end of file From f42303f75e6041adfd05d2563f9289d1f8b88439 Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Tue, 12 Nov 2024 14:53:51 +0100 Subject: [PATCH 10/23] rename file --- cmd/crypto-vault-service/{main.go => crypto-vault-service.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cmd/crypto-vault-service/{main.go => crypto-vault-service.go} (100%) diff --git a/cmd/crypto-vault-service/main.go b/cmd/crypto-vault-service/crypto-vault-service.go similarity index 100% rename from cmd/crypto-vault-service/main.go rename to cmd/crypto-vault-service/crypto-vault-service.go From b1806aaea4678db760be5bcffeaf14c7c3facc52 Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Tue, 12 Nov 2024 15:07:15 +0100 Subject: [PATCH 11/23] move functions to cryptography package --- cmd/crypto-vault-cli/crypto-cli.go | 246 +++-------------------------- pkg/cryptography/encryption.go | 210 ++++++++++++++++++++++++ pkg/cryptography/hashing.go | 1 + 3 files changed, 234 insertions(+), 223 deletions(-) diff --git a/cmd/crypto-vault-cli/crypto-cli.go b/cmd/crypto-vault-cli/crypto-cli.go index 7dce99c..b6ea689 100644 --- a/cmd/crypto-vault-cli/crypto-cli.go +++ b/cmd/crypto-vault-cli/crypto-cli.go @@ -1,13 +1,7 @@ package main import ( - "bytes" - "crypto/aes" - "crypto/cipher" - "crypto/rand" "crypto/rsa" - "crypto/x509" - "encoding/pem" "fmt" "io/ioutil" "log" @@ -15,203 +9,9 @@ import ( "path/filepath" "github.com/spf13/cobra" -) - -// AES Functions -// GenerateRandomAESKey generates a random AES key of the specified size -func generateRandomAESKey(keySize int) ([]byte, error) { - key := make([]byte, keySize) - _, err := rand.Read(key) - if err != nil { - return nil, fmt.Errorf("failed to generate AES key: %v", err) - } - return key, nil -} - -// Pad data to make it a multiple of AES block size -func pkcs7Pad(data []byte, blockSize int) []byte { - padding := blockSize - len(data)%blockSize - padded := append(data, bytes.Repeat([]byte{byte(padding)}, padding)...) - return padded -} - -// Unpad data after decryption -func pkcs7Unpad(data []byte, blockSize int) ([]byte, error) { - length := len(data) - padding := int(data[length-1]) - - if padding > length || padding > blockSize { - return nil, fmt.Errorf("invalid padding size") - } - return data[:length-padding], nil -} - -// Encrypts data using AES in CBC mode -func encryptAES(plainText, key []byte) ([]byte, error) { - block, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - - plainText = pkcs7Pad(plainText, aes.BlockSize) - - ciphertext := make([]byte, aes.BlockSize+len(plainText)) - iv := ciphertext[:aes.BlockSize] - if _, err := rand.Read(iv); err != nil { - return nil, err - } - - mode := cipher.NewCBCEncrypter(block, iv) - mode.CryptBlocks(ciphertext[aes.BlockSize:], plainText) - - return ciphertext, nil -} - -// Decrypts data using AES in CBC mode -func decryptAES(ciphertext, key []byte) ([]byte, error) { - block, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - - if len(ciphertext) < aes.BlockSize { - return nil, fmt.Errorf("ciphertext too short") - } - - iv := ciphertext[:aes.BlockSize] - ciphertext = ciphertext[aes.BlockSize:] - - mode := cipher.NewCBCDecrypter(block, iv) - mode.CryptBlocks(ciphertext, ciphertext) - - return pkcs7Unpad(ciphertext, aes.BlockSize) -} - -// RSA Functions -func generateRSAKeys(bits int) (*rsa.PrivateKey, *rsa.PublicKey, error) { - privateKey, err := rsa.GenerateKey(rand.Reader, bits) - if err != nil { - return nil, nil, fmt.Errorf("failed to generate RSA keys: %v", err) - } - - publicKey := &privateKey.PublicKey - return privateKey, publicKey, nil -} - -func savePrivateKeyToFile(privateKey *rsa.PrivateKey, filename string) error { - privKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey) - privKeyPem := &pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: privKeyBytes, - } - - file, err := os.Create(filename) - if err != nil { - return fmt.Errorf("failed to create private key file: %v", err) - } - defer file.Close() - - err = pem.Encode(file, privKeyPem) - if err != nil { - return fmt.Errorf("failed to encode private key: %v", err) - } - - return nil -} - -func savePublicKeyToFile(publicKey *rsa.PublicKey, filename string) error { - pubKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey) - if err != nil { - return fmt.Errorf("failed to marshal public key: %v", err) - } - - pubKeyPem := &pem.Block{ - Type: "PUBLIC KEY", - Bytes: pubKeyBytes, - } - - file, err := os.Create(filename) - if err != nil { - return fmt.Errorf("failed to create public key file: %v", err) - } - defer file.Close() - - err = pem.Encode(file, pubKeyPem) - if err != nil { - return fmt.Errorf("failed to encode public key: %v", err) - } - - return nil -} -// Encrypt data with RSA public key -func encryptWithRSA(publicKey *rsa.PublicKey, data []byte) ([]byte, error) { - encryptedData, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, data) - if err != nil { - return nil, fmt.Errorf("failed to encrypt data: %v", err) - } - return encryptedData, nil -} - -// Decrypt data with RSA private key -func decryptWithRSA(privateKey *rsa.PrivateKey, encryptedData []byte) ([]byte, error) { - decryptedData, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, encryptedData) - if err != nil { - return nil, fmt.Errorf("failed to decrypt data: %v", err) - } - return decryptedData, nil -} - -// File Operations -func readFile(filePath string) ([]byte, error) { - return ioutil.ReadFile(filePath) -} - -func writeFile(filePath string, data []byte) error { - return ioutil.WriteFile(filePath, data, 0644) -} - -// AES Command - -// Read RSA private key from PEM file -func readPrivateKey(privateKeyPath string) (*rsa.PrivateKey, error) { - privKeyPEM, err := ioutil.ReadFile(privateKeyPath) - if err != nil { - return nil, fmt.Errorf("unable to read private key file: %v", err) - } - - block, _ := pem.Decode(privKeyPEM) - if block == nil { - return nil, fmt.Errorf("failed to parse PEM block containing the private key") - } - - privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - return nil, fmt.Errorf("unable to parse private key: %v", err) - } - - return privateKey, nil -} - -// Read RSA public key from PEM file -func readPublicKey(publicKeyPath string) (*rsa.PublicKey, error) { - pubKeyPEM, err := ioutil.ReadFile(publicKeyPath) - if err != nil { - return nil, fmt.Errorf("unable to read public key file: %v", err) - } - - block, _ := pem.Decode(pubKeyPEM) - if block == nil { - return nil, fmt.Errorf("failed to parse PEM block containing the public key") - } - - publicKey, err := x509.ParsePKCS1PublicKey(block.Bytes) - if err != nil { - return nil, fmt.Errorf("unable to parse public key: %v", err) - } - - return publicKey, nil -} + cryptography "crypto_vault_service/pkg/cryptography" +) // Encrypts a file using AES and saves the encryption key func encryptAESCmd(cmd *cobra.Command, args []string) { @@ -226,24 +26,24 @@ func encryptAESCmd(cmd *cobra.Command, args []string) { } // Generate AES Key - key, err := generateRandomAESKey(keySize) + key, err := cryptography.GenerateRandomAESKey(keySize) if err != nil { log.Fatalf("Error generating AES key: %v\n", err) } // Encrypt the file - plainText, err := readFile(inputFile) + plainText, err := cryptography.ReadFile(inputFile) if err != nil { log.Fatalf("Error reading input file: %v\n", err) } - encryptedData, err := encryptAES(plainText, key) + encryptedData, err := cryptography.EncryptAES(plainText, key) if err != nil { log.Fatalf("Error encrypting data: %v\n", err) } // Save encrypted file - err = writeFile(outputFile, encryptedData) + err = cryptography.WriteFile(outputFile, encryptedData) if err != nil { log.Fatalf("Error writing encrypted file: %v\n", err) } @@ -251,7 +51,7 @@ func encryptAESCmd(cmd *cobra.Command, args []string) { // Save the AES key to the specified key directory keyFilePath := filepath.Join(keyDir, "encryption_key.bin") - err = writeFile(keyFilePath, key) + err = cryptography.WriteFile(keyFilePath, key) if err != nil { log.Fatalf("Error writing AES key to file: %v\n", err) } @@ -276,18 +76,18 @@ func decryptAESCmd(cmd *cobra.Command, args []string) { } // Decrypt the file - encryptedData, err := readFile(inputFile) + encryptedData, err := cryptography.ReadFile(inputFile) if err != nil { log.Fatalf("Error reading encrypted file: %v\n", err) } - decryptedData, err := decryptAES(encryptedData, key) + decryptedData, err := cryptography.DecryptAES(encryptedData, key) if err != nil { log.Fatalf("Error decrypting data: %v\n", err) } // Save decrypted file - err = writeFile(outputFile, decryptedData) + err = cryptography.WriteFile(outputFile, decryptedData) if err != nil { log.Fatalf("Error writing decrypted file: %v\n", err) } @@ -305,43 +105,43 @@ func encryptRSACmd(cmd *cobra.Command, args []string) { var err error if publicKeyPath == "" { // Generate RSA keys - privateKey, pubKey, genErr := generateRSAKeys(2048) + privateKey, pubKey, genErr := cryptography.GenerateRSAKeys(2048) if genErr != nil { log.Fatalf("Error generating RSA keys: %v\n", genErr) } publicKey = pubKey // Optionally save the private and public keys - err = savePrivateKeyToFile(privateKey, "data/private_key.pem") + err = cryptography.SavePrivateKeyToFile(privateKey, "data/private_key.pem") if err != nil { log.Fatalf("Error saving private key: %v\n", err) } - err = savePublicKeyToFile(publicKey, "data/public_key.pem") + err = cryptography.SavePublicKeyToFile(publicKey, "data/public_key.pem") if err != nil { log.Fatalf("Error saving public key: %v\n", err) } fmt.Println("Generated and saved RSA keys.") } else { // Read the provided public key - publicKey, err = readPublicKey(publicKeyPath) + publicKey, err = cryptography.ReadPublicKey(publicKeyPath) if err != nil { log.Fatalf("Error reading public key: %v\n", err) } } // Encrypt the file - plainText, err := readFile(inputFile) + plainText, err := cryptography.ReadFile(inputFile) if err != nil { log.Fatalf("Error reading input file: %v\n", err) } - encryptedData, err := encryptWithRSA(publicKey, plainText) + encryptedData, err := cryptography.EncryptWithRSA(publicKey, plainText) if err != nil { log.Fatalf("Error encrypting data: %v\n", err) } // Save encrypted file - err = writeFile(outputFile, encryptedData) + err = cryptography.WriteFile(outputFile, encryptedData) if err != nil { log.Fatalf("Error writing encrypted file: %v\n", err) } @@ -358,39 +158,39 @@ func decryptRSACmd(cmd *cobra.Command, args []string) { var err error if privateKeyPath == "" { // Generate RSA keys - privKey, _, genErr := generateRSAKeys(2048) + privKey, _, genErr := cryptography.GenerateRSAKeys(2048) if genErr != nil { log.Fatalf("Error generating RSA keys: %v\n", genErr) } privateKey = privKey // Optionally save the private and public keys - err = savePrivateKeyToFile(privateKey, "private_key.pem") + err = cryptography.SavePrivateKeyToFile(privateKey, "private_key.pem") if err != nil { log.Fatalf("Error saving private key: %v\n", err) } fmt.Println("Generated and saved private key.") } else { // Read the provided private key - privateKey, err = readPrivateKey(privateKeyPath) + privateKey, err = cryptography.ReadPrivateKey(privateKeyPath) if err != nil { log.Fatalf("Error reading private key: %v\n", err) } } // Decrypt the file - encryptedData, err := readFile(inputFile) + encryptedData, err := cryptography.ReadFile(inputFile) if err != nil { log.Fatalf("Error reading encrypted file: %v\n", err) } - decryptedData, err := decryptWithRSA(privateKey, encryptedData) + decryptedData, err := cryptography.DecryptWithRSA(privateKey, encryptedData) if err != nil { log.Fatalf("Error decrypting data: %v\n", err) } // Save decrypted file - err = writeFile(outputFile, decryptedData) + err = cryptography.WriteFile(outputFile, decryptedData) if err != nil { log.Fatalf("Error writing decrypted file: %v\n", err) } diff --git a/pkg/cryptography/encryption.go b/pkg/cryptography/encryption.go index e69de29..0748871 100644 --- a/pkg/cryptography/encryption.go +++ b/pkg/cryptography/encryption.go @@ -0,0 +1,210 @@ +package cryptography + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "fmt" + "io/ioutil" + "os" +) + +// AES Functions +// GenerateRandomAESKey generates a random AES key of the specified size +func GenerateRandomAESKey(keySize int) ([]byte, error) { + key := make([]byte, keySize) + _, err := rand.Read(key) + if err != nil { + return nil, fmt.Errorf("failed to generate AES key: %v", err) + } + return key, nil +} + +// Pad data to make it a multiple of AES block size +func pkcs7Pad(data []byte, blockSize int) []byte { + padding := blockSize - len(data)%blockSize + padded := append(data, bytes.Repeat([]byte{byte(padding)}, padding)...) + return padded +} + +// Unpad data after decryption +func pkcs7Unpad(data []byte, blockSize int) ([]byte, error) { + length := len(data) + padding := int(data[length-1]) + + if padding > length || padding > blockSize { + return nil, fmt.Errorf("invalid padding size") + } + return data[:length-padding], nil +} + +// Encrypts data using AES in CBC mode +func EncryptAES(plainText, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + plainText = pkcs7Pad(plainText, aes.BlockSize) + + ciphertext := make([]byte, aes.BlockSize+len(plainText)) + iv := ciphertext[:aes.BlockSize] + if _, err := rand.Read(iv); err != nil { + return nil, err + } + + mode := cipher.NewCBCEncrypter(block, iv) + mode.CryptBlocks(ciphertext[aes.BlockSize:], plainText) + + return ciphertext, nil +} + +// Decrypts data using AES in CBC mode +func DecryptAES(ciphertext, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + if len(ciphertext) < aes.BlockSize { + return nil, fmt.Errorf("ciphertext too short") + } + + iv := ciphertext[:aes.BlockSize] + ciphertext = ciphertext[aes.BlockSize:] + + mode := cipher.NewCBCDecrypter(block, iv) + mode.CryptBlocks(ciphertext, ciphertext) + + return pkcs7Unpad(ciphertext, aes.BlockSize) +} + +// RSA Functions +func GenerateRSAKeys(bits int) (*rsa.PrivateKey, *rsa.PublicKey, error) { + privateKey, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return nil, nil, fmt.Errorf("failed to generate RSA keys: %v", err) + } + + publicKey := &privateKey.PublicKey + return privateKey, publicKey, nil +} + +func SavePrivateKeyToFile(privateKey *rsa.PrivateKey, filename string) error { + privKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey) + privKeyPem := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: privKeyBytes, + } + + file, err := os.Create(filename) + if err != nil { + return fmt.Errorf("failed to create private key file: %v", err) + } + defer file.Close() + + err = pem.Encode(file, privKeyPem) + if err != nil { + return fmt.Errorf("failed to encode private key: %v", err) + } + + return nil +} + +func SavePublicKeyToFile(publicKey *rsa.PublicKey, filename string) error { + pubKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return fmt.Errorf("failed to marshal public key: %v", err) + } + + pubKeyPem := &pem.Block{ + Type: "PUBLIC KEY", + Bytes: pubKeyBytes, + } + + file, err := os.Create(filename) + if err != nil { + return fmt.Errorf("failed to create public key file: %v", err) + } + defer file.Close() + + err = pem.Encode(file, pubKeyPem) + if err != nil { + return fmt.Errorf("failed to encode public key: %v", err) + } + + return nil +} + +// Encrypt data with RSA public key +func EncryptWithRSA(publicKey *rsa.PublicKey, data []byte) ([]byte, error) { + encryptedData, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, data) + if err != nil { + return nil, fmt.Errorf("failed to encrypt data: %v", err) + } + return encryptedData, nil +} + +// Decrypt data with RSA private key +func DecryptWithRSA(privateKey *rsa.PrivateKey, encryptedData []byte) ([]byte, error) { + decryptedData, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, encryptedData) + if err != nil { + return nil, fmt.Errorf("failed to decrypt data: %v", err) + } + return decryptedData, nil +} + +// File Operations +func ReadFile(filePath string) ([]byte, error) { + return ioutil.ReadFile(filePath) +} + +func WriteFile(filePath string, data []byte) error { + return ioutil.WriteFile(filePath, data, 0644) +} + +// AES Command + +// Read RSA private key from PEM file +func ReadPrivateKey(privateKeyPath string) (*rsa.PrivateKey, error) { + privKeyPEM, err := ioutil.ReadFile(privateKeyPath) + if err != nil { + return nil, fmt.Errorf("unable to read private key file: %v", err) + } + + block, _ := pem.Decode(privKeyPEM) + if block == nil { + return nil, fmt.Errorf("failed to parse PEM block containing the private key") + } + + privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("unable to parse private key: %v", err) + } + + return privateKey, nil +} + +// Read RSA public key from PEM file +func ReadPublicKey(publicKeyPath string) (*rsa.PublicKey, error) { + pubKeyPEM, err := ioutil.ReadFile(publicKeyPath) + if err != nil { + return nil, fmt.Errorf("unable to read public key file: %v", err) + } + + block, _ := pem.Decode(pubKeyPEM) + if block == nil { + return nil, fmt.Errorf("failed to parse PEM block containing the public key") + } + + publicKey, err := x509.ParsePKCS1PublicKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("unable to parse public key: %v", err) + } + + return publicKey, nil +} diff --git a/pkg/cryptography/hashing.go b/pkg/cryptography/hashing.go index e69de29..b63edf2 100644 --- a/pkg/cryptography/hashing.go +++ b/pkg/cryptography/hashing.go @@ -0,0 +1 @@ +package cryptography From 23012c4e861a01d4bd93b4a1b981e40e9057a57a Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Tue, 12 Nov 2024 15:15:46 +0100 Subject: [PATCH 12/23] fix typo --- cmd/crypto-vault-cli/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/crypto-vault-cli/README.md b/cmd/crypto-vault-cli/README.md index 5e6da1a..efdf6bb 100644 --- a/cmd/crypto-vault-cli/README.md +++ b/cmd/crypto-vault-cli/README.md @@ -24,7 +24,7 @@ go run crypto-cli.go decrypt-aes --input data/output.enc --output data/decrypted ### External key generation ```sh -cd assets +cd data openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048 openssl rsa -pubout -in private_key.pem -out public_key.pem cd - From 5a7a3ba3cc21504af617de1fbe668d46966a0c21 Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Tue, 12 Nov 2024 15:16:09 +0100 Subject: [PATCH 13/23] support pkcs#1 and pkcs#8 formats --- pkg/cryptography/encryption.go | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/pkg/cryptography/encryption.go b/pkg/cryptography/encryption.go index 0748871..c7897a9 100644 --- a/pkg/cryptography/encryption.go +++ b/pkg/cryptography/encryption.go @@ -181,9 +181,22 @@ func ReadPrivateKey(privateKeyPath string) (*rsa.PrivateKey, error) { return nil, fmt.Errorf("failed to parse PEM block containing the private key") } + // First try to parse as PKCS#1 format privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err == nil { + return privateKey, nil + } + + // If PKCS#1 parsing fails, try parsing as PKCS#8 format + privateKeyInterface, err := x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { - return nil, fmt.Errorf("unable to parse private key: %v", err) + return nil, fmt.Errorf("unable to parse private key in either PKCS#1 or PKCS#8 format: %v", err) + } + + // Type assertion to *rsa.PrivateKey if it is indeed an RSA key + privateKey, ok := privateKeyInterface.(*rsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("private key is not of type RSA") } return privateKey, nil @@ -201,9 +214,22 @@ func ReadPublicKey(publicKeyPath string) (*rsa.PublicKey, error) { return nil, fmt.Errorf("failed to parse PEM block containing the public key") } + // Try to parse as PKCS#1 format first publicKey, err := x509.ParsePKCS1PublicKey(block.Bytes) + if err == nil { + return publicKey, nil + } + + // If PKCS#1 parsing fails, try parsing as PKCS#8 format + pubKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { - return nil, fmt.Errorf("unable to parse public key: %v", err) + return nil, fmt.Errorf("unable to parse public key in either PKCS#1 or PKCS#8 format: %v", err) + } + + // Type assertion to *rsa.PublicKey if it is indeed an RSA key + publicKey, ok := pubKeyInterface.(*rsa.PublicKey) + if !ok { + return nil, fmt.Errorf("public key is not of type RSA") } return publicKey, nil From 96c90473a854f8694975afeadc01485518e2288a Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Tue, 12 Nov 2024 15:25:07 +0100 Subject: [PATCH 14/23] modularize --- cmd/crypto-vault-cli/crypto-cli.go | 21 ++++-- pkg/cryptography/aes.go | 88 ++++++++++++++++++++++++ pkg/cryptography/encryption.go | 105 ----------------------------- pkg/cryptography/rsa.go | 45 +++++++++++++ pkg/cryptography/utils.go | 0 5 files changed, 147 insertions(+), 112 deletions(-) create mode 100644 pkg/cryptography/aes.go create mode 100644 pkg/cryptography/rsa.go create mode 100644 pkg/cryptography/utils.go diff --git a/cmd/crypto-vault-cli/crypto-cli.go b/cmd/crypto-vault-cli/crypto-cli.go index b6ea689..bc1db2c 100644 --- a/cmd/crypto-vault-cli/crypto-cli.go +++ b/cmd/crypto-vault-cli/crypto-cli.go @@ -25,8 +25,10 @@ func encryptAESCmd(cmd *cobra.Command, args []string) { log.Fatalf("Error: input, output, and keyDir flags are required\n") } + aes := &cryptography.AESImpl{} + // Generate AES Key - key, err := cryptography.GenerateRandomAESKey(keySize) + key, err := aes.GenerateKey(keySize) if err != nil { log.Fatalf("Error generating AES key: %v\n", err) } @@ -37,7 +39,7 @@ func encryptAESCmd(cmd *cobra.Command, args []string) { log.Fatalf("Error reading input file: %v\n", err) } - encryptedData, err := cryptography.EncryptAES(plainText, key) + encryptedData, err := aes.Encrypt(plainText, key) if err != nil { log.Fatalf("Error encrypting data: %v\n", err) } @@ -81,7 +83,9 @@ func decryptAESCmd(cmd *cobra.Command, args []string) { log.Fatalf("Error reading encrypted file: %v\n", err) } - decryptedData, err := cryptography.DecryptAES(encryptedData, key) + aes := &cryptography.AESImpl{} + + decryptedData, err := aes.Decrypt(encryptedData, key) if err != nil { log.Fatalf("Error decrypting data: %v\n", err) } @@ -103,9 +107,11 @@ func encryptRSACmd(cmd *cobra.Command, args []string) { // Generate RSA keys if no public key is provided var publicKey *rsa.PublicKey var err error + rsa := &cryptography.RSAImpl{} if publicKeyPath == "" { // Generate RSA keys - privateKey, pubKey, genErr := cryptography.GenerateRSAKeys(2048) + + privateKey, pubKey, genErr := rsa.GenerateKeys(2048) if genErr != nil { log.Fatalf("Error generating RSA keys: %v\n", genErr) } @@ -135,7 +141,7 @@ func encryptRSACmd(cmd *cobra.Command, args []string) { log.Fatalf("Error reading input file: %v\n", err) } - encryptedData, err := cryptography.EncryptWithRSA(publicKey, plainText) + encryptedData, err := rsa.Encrypt(plainText, publicKey) if err != nil { log.Fatalf("Error encrypting data: %v\n", err) } @@ -156,9 +162,10 @@ func decryptRSACmd(cmd *cobra.Command, args []string) { // Generate RSA keys if no private key is provided var privateKey *rsa.PrivateKey var err error + rsa := &cryptography.RSAImpl{} if privateKeyPath == "" { // Generate RSA keys - privKey, _, genErr := cryptography.GenerateRSAKeys(2048) + privKey, _, genErr := rsa.GenerateKeys(2048) if genErr != nil { log.Fatalf("Error generating RSA keys: %v\n", genErr) } @@ -184,7 +191,7 @@ func decryptRSACmd(cmd *cobra.Command, args []string) { log.Fatalf("Error reading encrypted file: %v\n", err) } - decryptedData, err := cryptography.DecryptWithRSA(privateKey, encryptedData) + decryptedData, err := rsa.Decrypt(encryptedData, privateKey) if err != nil { log.Fatalf("Error decrypting data: %v\n", err) } diff --git a/pkg/cryptography/aes.go b/pkg/cryptography/aes.go new file mode 100644 index 0000000..c5dc0fe --- /dev/null +++ b/pkg/cryptography/aes.go @@ -0,0 +1,88 @@ +package cryptography + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "fmt" +) + +// AES Interface +type AES interface { + Encrypt(plainText []byte, key []byte) ([]byte, error) + Decrypt(ciphertext []byte, key []byte) ([]byte, error) + GenerateKey(keySize int) ([]byte, error) +} + +// AESImpl struct that implements the AES interface +type AESImpl struct{} + +// Pad data to make it a multiple of AES block size +func pkcs7Pad(data []byte, blockSize int) []byte { + padding := blockSize - len(data)%blockSize + padded := append(data, bytes.Repeat([]byte{byte(padding)}, padding)...) + return padded +} + +// Unpad data after decryption +func pkcs7Unpad(data []byte, blockSize int) ([]byte, error) { + length := len(data) + padding := int(data[length-1]) + + if padding > length || padding > blockSize { + return nil, fmt.Errorf("invalid padding size") + } + return data[:length-padding], nil +} + +// GenerateRandomAESKey generates a random AES key of the specified size +func (a *AESImpl) GenerateKey(keySize int) ([]byte, error) { + key := make([]byte, keySize) + _, err := rand.Read(key) + if err != nil { + return nil, fmt.Errorf("failed to generate AES key: %v", err) + } + return key, nil +} + +// Encrypt data using AES in CBC mode +func (a *AESImpl) Encrypt(plainText, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + plainText = pkcs7Pad(plainText, aes.BlockSize) + + ciphertext := make([]byte, aes.BlockSize+len(plainText)) + iv := ciphertext[:aes.BlockSize] + if _, err := rand.Read(iv); err != nil { + return nil, err + } + + mode := cipher.NewCBCEncrypter(block, iv) + mode.CryptBlocks(ciphertext[aes.BlockSize:], plainText) + + return ciphertext, nil +} + +// Decrypt data using AES in CBC mode +func (a *AESImpl) Decrypt(ciphertext, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + if len(ciphertext) < aes.BlockSize { + return nil, fmt.Errorf("ciphertext too short") + } + + iv := ciphertext[:aes.BlockSize] + ciphertext = ciphertext[aes.BlockSize:] + + mode := cipher.NewCBCDecrypter(block, iv) + mode.CryptBlocks(ciphertext, ciphertext) + + return pkcs7Unpad(ciphertext, aes.BlockSize) +} diff --git a/pkg/cryptography/encryption.go b/pkg/cryptography/encryption.go index c7897a9..c4e4dd3 100644 --- a/pkg/cryptography/encryption.go +++ b/pkg/cryptography/encryption.go @@ -1,10 +1,6 @@ package cryptography import ( - "bytes" - "crypto/aes" - "crypto/cipher" - "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" @@ -13,87 +9,6 @@ import ( "os" ) -// AES Functions -// GenerateRandomAESKey generates a random AES key of the specified size -func GenerateRandomAESKey(keySize int) ([]byte, error) { - key := make([]byte, keySize) - _, err := rand.Read(key) - if err != nil { - return nil, fmt.Errorf("failed to generate AES key: %v", err) - } - return key, nil -} - -// Pad data to make it a multiple of AES block size -func pkcs7Pad(data []byte, blockSize int) []byte { - padding := blockSize - len(data)%blockSize - padded := append(data, bytes.Repeat([]byte{byte(padding)}, padding)...) - return padded -} - -// Unpad data after decryption -func pkcs7Unpad(data []byte, blockSize int) ([]byte, error) { - length := len(data) - padding := int(data[length-1]) - - if padding > length || padding > blockSize { - return nil, fmt.Errorf("invalid padding size") - } - return data[:length-padding], nil -} - -// Encrypts data using AES in CBC mode -func EncryptAES(plainText, key []byte) ([]byte, error) { - block, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - - plainText = pkcs7Pad(plainText, aes.BlockSize) - - ciphertext := make([]byte, aes.BlockSize+len(plainText)) - iv := ciphertext[:aes.BlockSize] - if _, err := rand.Read(iv); err != nil { - return nil, err - } - - mode := cipher.NewCBCEncrypter(block, iv) - mode.CryptBlocks(ciphertext[aes.BlockSize:], plainText) - - return ciphertext, nil -} - -// Decrypts data using AES in CBC mode -func DecryptAES(ciphertext, key []byte) ([]byte, error) { - block, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - - if len(ciphertext) < aes.BlockSize { - return nil, fmt.Errorf("ciphertext too short") - } - - iv := ciphertext[:aes.BlockSize] - ciphertext = ciphertext[aes.BlockSize:] - - mode := cipher.NewCBCDecrypter(block, iv) - mode.CryptBlocks(ciphertext, ciphertext) - - return pkcs7Unpad(ciphertext, aes.BlockSize) -} - -// RSA Functions -func GenerateRSAKeys(bits int) (*rsa.PrivateKey, *rsa.PublicKey, error) { - privateKey, err := rsa.GenerateKey(rand.Reader, bits) - if err != nil { - return nil, nil, fmt.Errorf("failed to generate RSA keys: %v", err) - } - - publicKey := &privateKey.PublicKey - return privateKey, publicKey, nil -} - func SavePrivateKeyToFile(privateKey *rsa.PrivateKey, filename string) error { privKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey) privKeyPem := &pem.Block{ @@ -140,24 +55,6 @@ func SavePublicKeyToFile(publicKey *rsa.PublicKey, filename string) error { return nil } -// Encrypt data with RSA public key -func EncryptWithRSA(publicKey *rsa.PublicKey, data []byte) ([]byte, error) { - encryptedData, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, data) - if err != nil { - return nil, fmt.Errorf("failed to encrypt data: %v", err) - } - return encryptedData, nil -} - -// Decrypt data with RSA private key -func DecryptWithRSA(privateKey *rsa.PrivateKey, encryptedData []byte) ([]byte, error) { - decryptedData, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, encryptedData) - if err != nil { - return nil, fmt.Errorf("failed to decrypt data: %v", err) - } - return decryptedData, nil -} - // File Operations func ReadFile(filePath string) ([]byte, error) { return ioutil.ReadFile(filePath) @@ -167,8 +64,6 @@ func WriteFile(filePath string, data []byte) error { return ioutil.WriteFile(filePath, data, 0644) } -// AES Command - // Read RSA private key from PEM file func ReadPrivateKey(privateKeyPath string) (*rsa.PrivateKey, error) { privKeyPEM, err := ioutil.ReadFile(privateKeyPath) diff --git a/pkg/cryptography/rsa.go b/pkg/cryptography/rsa.go new file mode 100644 index 0000000..ea46efd --- /dev/null +++ b/pkg/cryptography/rsa.go @@ -0,0 +1,45 @@ +package cryptography + +import ( + "crypto/rand" + "crypto/rsa" + "fmt" +) + +// RSA Interface +type RSA interface { + Encrypt(plainText []byte, publicKey *rsa.PublicKey) ([]byte, error) + Decrypt(ciphertext []byte, privateKey *rsa.PrivateKey) ([]byte, error) + GenerateKeys(bits int) (*rsa.PrivateKey, *rsa.PublicKey, error) +} + +// RSAImpl struct that implements the RSA interface +type RSAImpl struct{} + +// GenerateRSAKeys generates RSA key pair +func (r *RSAImpl) GenerateKeys(bits int) (*rsa.PrivateKey, *rsa.PublicKey, error) { + privateKey, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return nil, nil, fmt.Errorf("failed to generate RSA keys: %v", err) + } + publicKey := &privateKey.PublicKey + return privateKey, publicKey, nil +} + +// Encrypt data using RSA public key +func (r *RSAImpl) Encrypt(plainText []byte, publicKey *rsa.PublicKey) ([]byte, error) { + encryptedData, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, plainText) + if err != nil { + return nil, fmt.Errorf("failed to encrypt data: %v", err) + } + return encryptedData, nil +} + +// Decrypt data using RSA private key +func (r *RSAImpl) Decrypt(ciphertext []byte, privateKey *rsa.PrivateKey) ([]byte, error) { + decryptedData, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, ciphertext) + if err != nil { + return nil, fmt.Errorf("failed to decrypt data: %v", err) + } + return decryptedData, nil +} diff --git a/pkg/cryptography/utils.go b/pkg/cryptography/utils.go new file mode 100644 index 0000000..e69de29 From 10c8f9832831b3fd4175861844abfa21a758b8cf Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Tue, 12 Nov 2024 15:27:34 +0100 Subject: [PATCH 15/23] remove and rename --- pkg/cryptography/{encryption.go => io.go} | 18 +++++++++--------- pkg/cryptography/utils.go | 0 2 files changed, 9 insertions(+), 9 deletions(-) rename pkg/cryptography/{encryption.go => io.go} (96%) delete mode 100644 pkg/cryptography/utils.go diff --git a/pkg/cryptography/encryption.go b/pkg/cryptography/io.go similarity index 96% rename from pkg/cryptography/encryption.go rename to pkg/cryptography/io.go index c4e4dd3..3348a88 100644 --- a/pkg/cryptography/encryption.go +++ b/pkg/cryptography/io.go @@ -9,6 +9,15 @@ import ( "os" ) +// File Operations +func ReadFile(filePath string) ([]byte, error) { + return ioutil.ReadFile(filePath) +} + +func WriteFile(filePath string, data []byte) error { + return ioutil.WriteFile(filePath, data, 0644) +} + func SavePrivateKeyToFile(privateKey *rsa.PrivateKey, filename string) error { privKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey) privKeyPem := &pem.Block{ @@ -55,15 +64,6 @@ func SavePublicKeyToFile(publicKey *rsa.PublicKey, filename string) error { return nil } -// File Operations -func ReadFile(filePath string) ([]byte, error) { - return ioutil.ReadFile(filePath) -} - -func WriteFile(filePath string, data []byte) error { - return ioutil.WriteFile(filePath, data, 0644) -} - // Read RSA private key from PEM file func ReadPrivateKey(privateKeyPath string) (*rsa.PrivateKey, error) { privKeyPEM, err := ioutil.ReadFile(privateKeyPath) diff --git a/pkg/cryptography/utils.go b/pkg/cryptography/utils.go deleted file mode 100644 index e69de29..0000000 From f713cdebdecb20ce3421a04df3ba541e4c9f8457 Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Tue, 12 Nov 2024 15:30:23 +0100 Subject: [PATCH 16/23] add gitignore --- tests/data/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 tests/data/.gitignore diff --git a/tests/data/.gitignore b/tests/data/.gitignore new file mode 100644 index 0000000..a3a0c8b --- /dev/null +++ b/tests/data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file From 84071182ab0f5d51526b54e895976953cd87c386 Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Tue, 12 Nov 2024 15:43:35 +0100 Subject: [PATCH 17/23] move logic to RSAImpl --- cmd/crypto-vault-cli/crypto-cli.go | 10 +-- pkg/cryptography/io.go | 117 ---------------------------- pkg/cryptography/rsa.go | 120 +++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 122 deletions(-) diff --git a/cmd/crypto-vault-cli/crypto-cli.go b/cmd/crypto-vault-cli/crypto-cli.go index bc1db2c..76a612b 100644 --- a/cmd/crypto-vault-cli/crypto-cli.go +++ b/cmd/crypto-vault-cli/crypto-cli.go @@ -118,18 +118,18 @@ func encryptRSACmd(cmd *cobra.Command, args []string) { publicKey = pubKey // Optionally save the private and public keys - err = cryptography.SavePrivateKeyToFile(privateKey, "data/private_key.pem") + err = rsa.SavePrivateKeyToFile(privateKey, "data/private_key.pem") if err != nil { log.Fatalf("Error saving private key: %v\n", err) } - err = cryptography.SavePublicKeyToFile(publicKey, "data/public_key.pem") + err = rsa.SavePublicKeyToFile(publicKey, "data/public_key.pem") if err != nil { log.Fatalf("Error saving public key: %v\n", err) } fmt.Println("Generated and saved RSA keys.") } else { // Read the provided public key - publicKey, err = cryptography.ReadPublicKey(publicKeyPath) + publicKey, err = rsa.ReadPublicKey(publicKeyPath) if err != nil { log.Fatalf("Error reading public key: %v\n", err) } @@ -172,14 +172,14 @@ func decryptRSACmd(cmd *cobra.Command, args []string) { privateKey = privKey // Optionally save the private and public keys - err = cryptography.SavePrivateKeyToFile(privateKey, "private_key.pem") + err = rsa.SavePrivateKeyToFile(privateKey, "private_key.pem") if err != nil { log.Fatalf("Error saving private key: %v\n", err) } fmt.Println("Generated and saved private key.") } else { // Read the provided private key - privateKey, err = cryptography.ReadPrivateKey(privateKeyPath) + privateKey, err = rsa.ReadPrivateKey(privateKeyPath) if err != nil { log.Fatalf("Error reading private key: %v\n", err) } diff --git a/pkg/cryptography/io.go b/pkg/cryptography/io.go index 3348a88..6aa0b25 100644 --- a/pkg/cryptography/io.go +++ b/pkg/cryptography/io.go @@ -1,12 +1,7 @@ package cryptography import ( - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "fmt" "io/ioutil" - "os" ) // File Operations @@ -17,115 +12,3 @@ func ReadFile(filePath string) ([]byte, error) { func WriteFile(filePath string, data []byte) error { return ioutil.WriteFile(filePath, data, 0644) } - -func SavePrivateKeyToFile(privateKey *rsa.PrivateKey, filename string) error { - privKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey) - privKeyPem := &pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: privKeyBytes, - } - - file, err := os.Create(filename) - if err != nil { - return fmt.Errorf("failed to create private key file: %v", err) - } - defer file.Close() - - err = pem.Encode(file, privKeyPem) - if err != nil { - return fmt.Errorf("failed to encode private key: %v", err) - } - - return nil -} - -func SavePublicKeyToFile(publicKey *rsa.PublicKey, filename string) error { - pubKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey) - if err != nil { - return fmt.Errorf("failed to marshal public key: %v", err) - } - - pubKeyPem := &pem.Block{ - Type: "PUBLIC KEY", - Bytes: pubKeyBytes, - } - - file, err := os.Create(filename) - if err != nil { - return fmt.Errorf("failed to create public key file: %v", err) - } - defer file.Close() - - err = pem.Encode(file, pubKeyPem) - if err != nil { - return fmt.Errorf("failed to encode public key: %v", err) - } - - return nil -} - -// Read RSA private key from PEM file -func ReadPrivateKey(privateKeyPath string) (*rsa.PrivateKey, error) { - privKeyPEM, err := ioutil.ReadFile(privateKeyPath) - if err != nil { - return nil, fmt.Errorf("unable to read private key file: %v", err) - } - - block, _ := pem.Decode(privKeyPEM) - if block == nil { - return nil, fmt.Errorf("failed to parse PEM block containing the private key") - } - - // First try to parse as PKCS#1 format - privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) - if err == nil { - return privateKey, nil - } - - // If PKCS#1 parsing fails, try parsing as PKCS#8 format - privateKeyInterface, err := x509.ParsePKCS8PrivateKey(block.Bytes) - if err != nil { - return nil, fmt.Errorf("unable to parse private key in either PKCS#1 or PKCS#8 format: %v", err) - } - - // Type assertion to *rsa.PrivateKey if it is indeed an RSA key - privateKey, ok := privateKeyInterface.(*rsa.PrivateKey) - if !ok { - return nil, fmt.Errorf("private key is not of type RSA") - } - - return privateKey, nil -} - -// Read RSA public key from PEM file -func ReadPublicKey(publicKeyPath string) (*rsa.PublicKey, error) { - pubKeyPEM, err := ioutil.ReadFile(publicKeyPath) - if err != nil { - return nil, fmt.Errorf("unable to read public key file: %v", err) - } - - block, _ := pem.Decode(pubKeyPEM) - if block == nil { - return nil, fmt.Errorf("failed to parse PEM block containing the public key") - } - - // Try to parse as PKCS#1 format first - publicKey, err := x509.ParsePKCS1PublicKey(block.Bytes) - if err == nil { - return publicKey, nil - } - - // If PKCS#1 parsing fails, try parsing as PKCS#8 format - pubKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes) - if err != nil { - return nil, fmt.Errorf("unable to parse public key in either PKCS#1 or PKCS#8 format: %v", err) - } - - // Type assertion to *rsa.PublicKey if it is indeed an RSA key - publicKey, ok := pubKeyInterface.(*rsa.PublicKey) - if !ok { - return nil, fmt.Errorf("public key is not of type RSA") - } - - return publicKey, nil -} diff --git a/pkg/cryptography/rsa.go b/pkg/cryptography/rsa.go index ea46efd..e898fb9 100644 --- a/pkg/cryptography/rsa.go +++ b/pkg/cryptography/rsa.go @@ -3,7 +3,11 @@ package cryptography import ( "crypto/rand" "crypto/rsa" + "crypto/x509" + "encoding/pem" "fmt" + "io/ioutil" + "os" ) // RSA Interface @@ -11,6 +15,10 @@ type RSA interface { Encrypt(plainText []byte, publicKey *rsa.PublicKey) ([]byte, error) Decrypt(ciphertext []byte, privateKey *rsa.PrivateKey) ([]byte, error) GenerateKeys(bits int) (*rsa.PrivateKey, *rsa.PublicKey, error) + SavePrivateKeyToFile(privateKey *rsa.PrivateKey, filename string) error + SavePublicKeyToFile(publicKey *rsa.PublicKey, filename string) error + ReadPrivateKey(privateKeyPath string) (*rsa.PrivateKey, error) + ReadPublicKey(publicKeyPath string) (*rsa.PublicKey, error) } // RSAImpl struct that implements the RSA interface @@ -43,3 +51,115 @@ func (r *RSAImpl) Decrypt(ciphertext []byte, privateKey *rsa.PrivateKey) ([]byte } return decryptedData, nil } + +func (r *RSAImpl) SavePrivateKeyToFile(privateKey *rsa.PrivateKey, filename string) error { + privKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey) + privKeyPem := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: privKeyBytes, + } + + file, err := os.Create(filename) + if err != nil { + return fmt.Errorf("failed to create private key file: %v", err) + } + defer file.Close() + + err = pem.Encode(file, privKeyPem) + if err != nil { + return fmt.Errorf("failed to encode private key: %v", err) + } + + return nil +} + +func (r *RSAImpl) SavePublicKeyToFile(publicKey *rsa.PublicKey, filename string) error { + pubKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return fmt.Errorf("failed to marshal public key: %v", err) + } + + pubKeyPem := &pem.Block{ + Type: "PUBLIC KEY", + Bytes: pubKeyBytes, + } + + file, err := os.Create(filename) + if err != nil { + return fmt.Errorf("failed to create public key file: %v", err) + } + defer file.Close() + + err = pem.Encode(file, pubKeyPem) + if err != nil { + return fmt.Errorf("failed to encode public key: %v", err) + } + + return nil +} + +// Read RSA private key from PEM file +func (r *RSAImpl) ReadPrivateKey(privateKeyPath string) (*rsa.PrivateKey, error) { + privKeyPEM, err := ioutil.ReadFile(privateKeyPath) + if err != nil { + return nil, fmt.Errorf("unable to read private key file: %v", err) + } + + block, _ := pem.Decode(privKeyPEM) + if block == nil { + return nil, fmt.Errorf("failed to parse PEM block containing the private key") + } + + // First try to parse as PKCS#1 format + privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err == nil { + return privateKey, nil + } + + // If PKCS#1 parsing fails, try parsing as PKCS#8 format + privateKeyInterface, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("unable to parse private key in either PKCS#1 or PKCS#8 format: %v", err) + } + + // Type assertion to *rsa.PrivateKey if it is indeed an RSA key + privateKey, ok := privateKeyInterface.(*rsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("private key is not of type RSA") + } + + return privateKey, nil +} + +// Read RSA public key from PEM file +func (r *RSAImpl) ReadPublicKey(publicKeyPath string) (*rsa.PublicKey, error) { + pubKeyPEM, err := ioutil.ReadFile(publicKeyPath) + if err != nil { + return nil, fmt.Errorf("unable to read public key file: %v", err) + } + + block, _ := pem.Decode(pubKeyPEM) + if block == nil { + return nil, fmt.Errorf("failed to parse PEM block containing the public key") + } + + // Try to parse as PKCS#1 format first + publicKey, err := x509.ParsePKCS1PublicKey(block.Bytes) + if err == nil { + return publicKey, nil + } + + // If PKCS#1 parsing fails, try parsing as PKCS#8 format + pubKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("unable to parse public key in either PKCS#1 or PKCS#8 format: %v", err) + } + + // Type assertion to *rsa.PublicKey if it is indeed an RSA key + publicKey, ok := pubKeyInterface.(*rsa.PublicKey) + if !ok { + return nil, fmt.Errorf("public key is not of type RSA") + } + + return publicKey, nil +} From 96043a211583bf6d3843391fd520db7632492d5c Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Tue, 12 Nov 2024 15:55:30 +0100 Subject: [PATCH 18/23] modify README --- cmd/crypto-vault-cli/README.md | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/cmd/crypto-vault-cli/README.md b/cmd/crypto-vault-cli/README.md index efdf6bb..45fad57 100644 --- a/cmd/crypto-vault-cli/README.md +++ b/cmd/crypto-vault-cli/README.md @@ -4,13 +4,13 @@ ## Prerequisites -Before you begin, ensure you have the following tools installed: - - Install Go from the official Go website, or use this [devcontainer.json](../../.devcontainer/devcontainer.json) with the [DevContainer extensions in VS Code or other IDE supporting DevContainers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) ## Getting Started -## AES example +### Encryption/Decryption + +**AES example** ```sh # Encryption @@ -19,9 +19,7 @@ go run crypto-cli.go encrypt-aes --input data/input.txt --output data/output.enc go run crypto-cli.go decrypt-aes --input data/output.enc --output data/decrypted.txt --keyDir data/ ``` -## RSA Example - -### External key generation +**RSA Example considering external key generation** ```sh cd data @@ -36,7 +34,7 @@ go run crypto-cli.go encrypt-rsa --input data/input.txt --output data/encryptedI go run crypto-cli.go decrypt-rsa --input data/encryptedII.txt --output data/decryptedII.txt --privateKey data/private_key.pem ``` -### Internal key generation +**RSA Example considering internal key generation** ```sh # Encryption @@ -46,29 +44,27 @@ go run crypto-cli.go encrypt-rsa --input data/input.txt --output data/encryptedI go run crypto-cli.go decrypt-rsa --input data/encryptedII.txt --output data/decryptedII.txt --privateKey data/private_key.pem ``` -## EC Example - -### External key generation +**RSA with PKCS#11 Example considering external key generation** ```sh TBD ``` -### Internal key generation +**RSA with PKCS#11 Example considering internal key generation** ```sh TBD ``` -## RSA with PKCS#11 +### Hashing / Verifying signatures -### External key generation +**ECC Example considering external key generation** ```sh TBD ``` -### Internal key generation +**ECC Example considering internal key generation** ```sh TBD From 3dc1b9b756caf602de012df550f49325c7ecccbd Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Tue, 12 Nov 2024 15:58:50 +0100 Subject: [PATCH 19/23] update bullet points --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3cff7ed..c33a5df 100644 --- a/README.md +++ b/README.md @@ -20,11 +20,11 @@ TBD ### Functional - [ ] **Provide RESTful API for cryptographic operations**: Expose endpoints for managing cryptographic material and securing data (files, metadata) at rest. -- [ ] **Asymmetric encryption and decryption**: Support RSA, ECC and other asymmetric encryption algorithms for data protection. -- [ ] **PKCS#11 integration**: Enable key management in FIPS-compliant hardware or software environments. +- [ ] **Asymmetric encryption and decryption**: Support RSA and other asymmetric encryption algorithms for data protection. - [ ] **Symmetric encryption**: Support for symmetric key encryption (e.g. AES) for data protection. -- [ ] **Manage cryptographic material**: Enable management of private/public key pairs and symmetric keys (generation, import/export, rotation, etc.). - [ ] **Hashing and signature verification**: Support hashing algorithms (e.g. SHA-256, SHA-512) and verify signatures using asymmetric keys (RSA, ECDSA, etc.). +- [ ] **PKCS#11 integration**: Enable key management in FIPS-compliant hardware or software. +- [ ] **Manage cryptographic material**: Enable management of private/public key pairs and symmetric keys (generation, import/export, rotation, etc.). - [ ] **Key management lifecycle**: Implement key lifecycle management (generation, rotation, revocation, expiration). - [ ] **Secure file storage integration**: Provide mechanisms to securely store encrypted files in BLOB storage (e.g. AWS S3, Azure Blob Storage, Google Cloud Storage). - [ ] **Access control**: Implement role-based access control (RBAC) for APIs ensuring that only authorized users can perform operations on cryptographic material. From 903fc616fcd22bd19333122eb7ef04ca76732e42 Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Tue, 12 Nov 2024 16:38:22 +0100 Subject: [PATCH 20/23] add ECDS interface and implementation --- cmd/crypto-vault-cli/README.md | 12 +- cmd/crypto-vault-cli/crypto-cli.go | 140 +++++++++++++++++++++ pkg/cryptography/ecdsa.go | 193 +++++++++++++++++++++++++++++ 3 files changed, 338 insertions(+), 7 deletions(-) create mode 100644 pkg/cryptography/ecdsa.go diff --git a/cmd/crypto-vault-cli/README.md b/cmd/crypto-vault-cli/README.md index 45fad57..3855754 100644 --- a/cmd/crypto-vault-cli/README.md +++ b/cmd/crypto-vault-cli/README.md @@ -58,14 +58,12 @@ TBD ### Hashing / Verifying signatures -**ECC Example considering external key generation** +**ECDSA Example considering internal key generation** ```sh -TBD -``` - -**ECC Example considering internal key generation** +# Sign a file with a newly generated ECC key pair (internally generated) +go run crypto-cli.go sign-ecc --input data/input.txt --keyDir data -```sh -TBD +# Verify the signature using the generated public key +go run crypto-cli.go verify-ecc --input data/input.txt --publicKey data/public_key.pem --signature data/signature.sig ``` \ No newline at end of file diff --git a/cmd/crypto-vault-cli/crypto-cli.go b/cmd/crypto-vault-cli/crypto-cli.go index 76a612b..258e421 100644 --- a/cmd/crypto-vault-cli/crypto-cli.go +++ b/cmd/crypto-vault-cli/crypto-cli.go @@ -1,7 +1,10 @@ package main import ( + "crypto/ecdsa" + "crypto/elliptic" "crypto/rsa" + "encoding/hex" "fmt" "io/ioutil" "log" @@ -204,6 +207,121 @@ func decryptRSACmd(cmd *cobra.Command, args []string) { fmt.Printf("Decrypted data saved to %s\n", outputFile) } +// ECDSA command +// signECCCmd signs the contents of a file with ECDSA +func signECCCmd(cmd *cobra.Command, args []string) { + inputFile, _ := cmd.Flags().GetString("input") // File to sign + keyDir, _ := cmd.Flags().GetString("keyDir") // Directory to save keys + + // ECC implementation + ecdsaImpl := &cryptography.ECDSAImpl{} + var privateKey *ecdsa.PrivateKey + var publicKey *ecdsa.PublicKey + var err error + + // Generate new ECC keys if no private key is provided + privateKey, publicKey, err = ecdsaImpl.GenerateKeys(elliptic.P256()) + if err != nil { + log.Fatalf("Error generating ECC keys: %v\n", err) + } + + // Read the file content + fileContent, err := cryptography.ReadFile(inputFile) + if err != nil { + log.Fatalf("Error reading input file: %v\n", err) + } + + // Sign the file content (hash the content before signing) + signature, err := ecdsaImpl.Sign(fileContent, privateKey) + if err != nil { + log.Fatalf("Error signing file content: %v\n", err) + } + + // Output the signature + fmt.Printf("Signature: %x\n", signature) + + // Save the private and public keys to files (if they were generated) + if privateKey != nil && keyDir != "" { + privateKeyFilePath := fmt.Sprintf("%s/private_key.pem", keyDir) + err = ecdsaImpl.SavePrivateKeyToFile(privateKey, privateKeyFilePath) + if err != nil { + log.Fatalf("Error saving private key: %v\n", err) + } + fmt.Printf("Private key saved to: %s\n", privateKeyFilePath) + } + + if publicKey != nil && keyDir != "" { + publicKeyFilePath := fmt.Sprintf("%s/public_key.pem", keyDir) + err = ecdsaImpl.SavePublicKeyToFile(publicKey, publicKeyFilePath) + if err != nil { + log.Fatalf("Error saving public key: %v\n", err) + } + fmt.Printf("Public key saved to: %s\n", publicKeyFilePath) + } + + // Save the signature to a file in the data folder (optional, based on the input file) + if keyDir != "" { + signatureFilePath := fmt.Sprintf("%s/signature.sig", keyDir) + err = ecdsaImpl.SaveSignatureToFile(signatureFilePath, signature) + if err != nil { + log.Fatalf("Error saving signature: %v\n", err) + } + fmt.Printf("Signature saved to: %s\n", signatureFilePath) + } +} + +// verifyECCCmd verifies the signature of a file's content using ECDSA +func verifyECCCmd(cmd *cobra.Command, args []string) { + publicKeyPath, _ := cmd.Flags().GetString("publicKey") // Path to public key + inputFile, _ := cmd.Flags().GetString("input") // Input file to verify + signatureFile, _ := cmd.Flags().GetString("signature") // Path to signature file + + // ECC implementation + ecdsaImpl := &cryptography.ECDSAImpl{} + var publicKey *ecdsa.PublicKey + var err error + + // Read the public key + if publicKeyPath == "" { + log.Fatalf("Public key is required for ECC signature verification.\n") + } else { + publicKey, err = ecdsaImpl.ReadPublicKey(publicKeyPath) + if err != nil { + log.Fatalf("Error reading public key: %v\n", err) + } + } + + // Read the file content (optional: you can also hash the content before verifying) + fileContent, err := cryptography.ReadFile(inputFile) + if err != nil { + log.Fatalf("Error reading input file: %v\n", err) + } + + // Read the signature (from hex file) + signatureHex, err := ioutil.ReadFile(signatureFile) + if err != nil { + log.Fatalf("Error reading signature file: %v\n", err) + } + + // Decode the hex string back to bytes + signature, err := hex.DecodeString(string(signatureHex)) + if err != nil { + log.Fatalf("Error decoding signature hex: %v\n", err) + } + + // Verify the signature + valid, err := ecdsaImpl.Verify(fileContent, signature, publicKey) + if err != nil { + log.Fatalf("Error verifying signature: %v\n", err) + } + + if valid { + fmt.Println("Signature is valid.") + } else { + fmt.Println("Signature is invalid.") + } +} + // Main function func main() { var rootCmd = &cobra.Command{Use: "crypto-cli"} @@ -251,6 +369,28 @@ func main() { decryptRSAFileCmd.Flags().StringP("privateKey", "r", "", "Path to RSA private key") rootCmd.AddCommand(decryptRSAFileCmd) + // ECDSA Command + var signECCMessageCmd = &cobra.Command{ + Use: "sign-ecc", + Short: "Sign a message using ECC", + Run: signECCCmd, + } + + // Rename the input flag to messageFile for clarity + signECCMessageCmd.Flags().StringP("input", "i", "", "Path to file that needs to be signed") + signECCMessageCmd.Flags().StringP("keyDir", "d", "", "Directory to save generated keys (optional)") + rootCmd.AddCommand(signECCMessageCmd) + + var verifyECCSignatureCmd = &cobra.Command{ + Use: "verify-ecc", + Short: "Verify a signature using ECC", + Run: verifyECCCmd, + } + verifyECCSignatureCmd.Flags().StringP("input", "i", "", "Path to ECC public key") + verifyECCSignatureCmd.Flags().StringP("publicKey", "p", "", "The public key used to verify the signature") + verifyECCSignatureCmd.Flags().StringP("signature", "s", "", "Signature to verify (hex format)") + rootCmd.AddCommand(verifyECCSignatureCmd) + // Execute the root command if err := rootCmd.Execute(); err != nil { fmt.Println(err) diff --git a/pkg/cryptography/ecdsa.go b/pkg/cryptography/ecdsa.go new file mode 100644 index 0000000..e7079cb --- /dev/null +++ b/pkg/cryptography/ecdsa.go @@ -0,0 +1,193 @@ +package cryptography + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "encoding/pem" + "fmt" + "io/ioutil" + "math/big" + "os" +) + +// ECDSA Interface +type ECDSA interface { + GenerateKeys(curve elliptic.Curve) (*ecdsa.PrivateKey, *ecdsa.PublicKey, error) + Sign(message []byte, privateKey *ecdsa.PrivateKey) ([]byte, error) + Verify(message, signature []byte, publicKey *ecdsa.PublicKey) (bool, error) + SaveSignatureToFile(filename string, data []byte) error + SavePrivateKeyToFile(privateKey *ecdsa.PrivateKey, filename string) error + SavePublicKeyToFile(publicKey *ecdsa.PublicKey, filename string) error + ReadPrivateKey(privateKeyPath string) (*ecdsa.PrivateKey, error) + ReadPublicKey(publicKeyPath string) (*ecdsa.PublicKey, error) +} + +// ECDSAImpl struct that implements the ECDSA interface +type ECDSAImpl struct{} + +// GenerateKeys generates an elliptic curve key pair +func (e *ECDSAImpl) GenerateKeys(curve elliptic.Curve) (*ecdsa.PrivateKey, *ecdsa.PublicKey, error) { + privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) + if err != nil { + return nil, nil, fmt.Errorf("failed to generate elliptic curve keys: %v", err) + } + + publicKey := &privateKey.PublicKey + return privateKey, publicKey, nil +} + +// Sign signs a message with the private key +func (e *ECDSAImpl) Sign(message []byte, privateKey *ecdsa.PrivateKey) ([]byte, error) { + // Hash the message before signing it + hash := sha256.Sum256(message) + r, s, err := ecdsa.Sign(rand.Reader, privateKey, hash[:]) + if err != nil { + return nil, fmt.Errorf("failed to sign message: %v", err) + } + + // Encode the signature as r and s + signature := append(r.Bytes(), s.Bytes()...) + return signature, nil +} + +// Verify verifies the signature of a message with the public key +func (e *ECDSAImpl) Verify(message, signature []byte, publicKey *ecdsa.PublicKey) (bool, error) { + // Hash the message before verifying it + hash := sha256.Sum256(message) + + // Split the signature into r and s + r, s := signature[:len(signature)/2], signature[len(signature)/2:] + rInt := new(big.Int).SetBytes(r) + sInt := new(big.Int).SetBytes(s) + + // Verify the signature + valid := ecdsa.Verify(publicKey, hash[:], rInt, sInt) + return valid, nil +} + +// SavePrivateKeyToFile saves the private key to a PEM file using encoding/pem +func (e *ECDSAImpl) SavePrivateKeyToFile(privateKey *ecdsa.PrivateKey, filename string) error { + // Marshal private key components (private key 'D' and public key components 'X' and 'Y') + privKeyBytes := append(privateKey.D.Bytes(), privateKey.PublicKey.X.Bytes()...) + privKeyBytes = append(privKeyBytes, privateKey.PublicKey.Y.Bytes()...) + + // Prepare the PEM block + privKeyPem := &pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: privKeyBytes, + } + + // Write the PEM block to a file + file, err := os.Create(filename) + if err != nil { + return fmt.Errorf("failed to create private key file: %v", err) + } + defer file.Close() + + // Encode and write the private key in PEM format + err = pem.Encode(file, privKeyPem) + if err != nil { + return fmt.Errorf("failed to encode private key: %v", err) + } + + return nil +} + +// SavePublicKeyToFile saves the public key to a PEM file using encoding/pem +func (e *ECDSAImpl) SavePublicKeyToFile(publicKey *ecdsa.PublicKey, filename string) error { + pubKeyBytes := append(publicKey.X.Bytes(), publicKey.Y.Bytes()...) + + // Prepare the PEM block for the public key + pubKeyPem := &pem.Block{ + Type: "PUBLIC KEY", + Bytes: pubKeyBytes, + } + + // Write the PEM block to a file + file, err := os.Create(filename) + if err != nil { + return fmt.Errorf("failed to create public key file: %v", err) + } + defer file.Close() + + // Encode and write the public key in PEM format + err = pem.Encode(file, pubKeyPem) + if err != nil { + return fmt.Errorf("failed to encode public key: %v", err) + } + + return nil +} + +// SaveSignatureToFile can be used for storing signature files in hex format +func (e *ECDSAImpl) SaveSignatureToFile(filename string, data []byte) error { + hexData := hex.EncodeToString(data) + err := ioutil.WriteFile(filename, []byte(hexData), 0644) + if err != nil { + return fmt.Errorf("failed to write data to file %s: %v", filename, err) + } + return nil +} + +// ReadPrivateKey reads an ECDSA private key from a PEM file using encoding/pem +func (e *ECDSAImpl) ReadPrivateKey(privateKeyPath string) (*ecdsa.PrivateKey, error) { + privKeyPEM, err := ioutil.ReadFile(privateKeyPath) + if err != nil { + return nil, fmt.Errorf("unable to read private key file: %v", err) + } + + // Decode the PEM block + block, _ := pem.Decode(privKeyPEM) + if block == nil { + return nil, fmt.Errorf("failed to parse PEM block containing the private key") + } + + // Extract the private key (first part is 'D', followed by 'X' and 'Y' of the public key) + privKeyBytes := block.Bytes + privKeyD := new(big.Int).SetBytes(privKeyBytes[:32]) // first 32 bytes for D + pubKeyX := new(big.Int).SetBytes(privKeyBytes[32:64]) // next 32 bytes for X + pubKeyY := new(big.Int).SetBytes(privKeyBytes[64:96]) // last 32 bytes for Y + + publicKey := &ecdsa.PublicKey{ + Curve: elliptic.P256(), // Assume P256 curve, you can make it dynamic + X: pubKeyX, + Y: pubKeyY, + } + + privateKey := &ecdsa.PrivateKey{ + D: privKeyD, + PublicKey: *publicKey, + } + + return privateKey, nil +} + +// ReadPublicKey reads an ECDSA public key from a PEM file using encoding/pem +func (e *ECDSAImpl) ReadPublicKey(publicKeyPath string) (*ecdsa.PublicKey, error) { + pubKeyPEM, err := ioutil.ReadFile(publicKeyPath) + if err != nil { + return nil, fmt.Errorf("unable to read public key file: %v", err) + } + + // Decode the PEM block + block, _ := pem.Decode(pubKeyPEM) + if block == nil { + return nil, fmt.Errorf("failed to parse PEM block containing the public key") + } + + // Extract the public key (first 32 bytes for 'X' and next 32 bytes for 'Y') + pubKeyBytes := block.Bytes + pubKeyX := new(big.Int).SetBytes(pubKeyBytes[:32]) // first 32 bytes for X + pubKeyY := new(big.Int).SetBytes(pubKeyBytes[32:64]) // next 32 bytes for Y + + publicKey := &ecdsa.PublicKey{ + Curve: elliptic.P256(), // Assume P256 curve, you can make it dynamic + X: pubKeyX, + Y: pubKeyY, + } + + return publicKey, nil +} From 3511e273912cbd5fc47d173fbb15f68d160cdb0d Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Tue, 12 Nov 2024 16:40:42 +0100 Subject: [PATCH 21/23] rename file --- cmd/crypto-vault-cli/README.md | 16 ++++++++-------- .../{crypto-cli.go => crypto-vault-cli.go} | 0 2 files changed, 8 insertions(+), 8 deletions(-) rename cmd/crypto-vault-cli/{crypto-cli.go => crypto-vault-cli.go} (100%) diff --git a/cmd/crypto-vault-cli/README.md b/cmd/crypto-vault-cli/README.md index 3855754..006ddca 100644 --- a/cmd/crypto-vault-cli/README.md +++ b/cmd/crypto-vault-cli/README.md @@ -14,9 +14,9 @@ ```sh # Encryption -go run crypto-cli.go encrypt-aes --input data/input.txt --output data/output.enc --keySize 16 --keyDir data/ +go run crypto-vault-cli.go encrypt-aes --input data/input.txt --output data/output.enc --keySize 16 --keyDir data/ # Decryption -go run crypto-cli.go decrypt-aes --input data/output.enc --output data/decrypted.txt --keyDir data/ +go run crypto-vault-cli.go decrypt-aes --input data/output.enc --output data/decrypted.txt --keyDir data/ ``` **RSA Example considering external key generation** @@ -28,20 +28,20 @@ openssl rsa -pubout -in private_key.pem -out public_key.pem cd - # Encryption -go run crypto-cli.go encrypt-rsa --input data/input.txt --output data/encryptedII.txt --publicKey data/public_key.pem +go run crypto-vault-cli.go encrypt-rsa --input data/input.txt --output data/encryptedII.txt --publicKey data/public_key.pem # Decryption -go run crypto-cli.go decrypt-rsa --input data/encryptedII.txt --output data/decryptedII.txt --privateKey data/private_key.pem +go run crypto-vault-cli.go decrypt-rsa --input data/encryptedII.txt --output data/decryptedII.txt --privateKey data/private_key.pem ``` **RSA Example considering internal key generation** ```sh # Encryption -go run crypto-cli.go encrypt-rsa --input data/input.txt --output data/encryptedII.txt +go run crypto-vault-cli.go encrypt-rsa --input data/input.txt --output data/encryptedII.txt # Decryption -go run crypto-cli.go decrypt-rsa --input data/encryptedII.txt --output data/decryptedII.txt --privateKey data/private_key.pem +go run crypto-vault-cli.go decrypt-rsa --input data/encryptedII.txt --output data/decryptedII.txt --privateKey data/private_key.pem ``` **RSA with PKCS#11 Example considering external key generation** @@ -62,8 +62,8 @@ TBD ```sh # Sign a file with a newly generated ECC key pair (internally generated) -go run crypto-cli.go sign-ecc --input data/input.txt --keyDir data +go run crypto-vault-cli.go sign-ecc --input data/input.txt --keyDir data # Verify the signature using the generated public key -go run crypto-cli.go verify-ecc --input data/input.txt --publicKey data/public_key.pem --signature data/signature.sig +go run crypto-vault-cli.go verify-ecc --input data/input.txt --publicKey data/public_key.pem --signature data/signature.sig ``` \ No newline at end of file diff --git a/cmd/crypto-vault-cli/crypto-cli.go b/cmd/crypto-vault-cli/crypto-vault-cli.go similarity index 100% rename from cmd/crypto-vault-cli/crypto-cli.go rename to cmd/crypto-vault-cli/crypto-vault-cli.go From db4c47bea0e6d0920ce8146358917049ae5b2e65 Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Tue, 12 Nov 2024 16:42:36 +0100 Subject: [PATCH 22/23] remove obsolete file --- pkg/cryptography/hashing.go | 1 - 1 file changed, 1 deletion(-) delete mode 100644 pkg/cryptography/hashing.go diff --git a/pkg/cryptography/hashing.go b/pkg/cryptography/hashing.go deleted file mode 100644 index b63edf2..0000000 --- a/pkg/cryptography/hashing.go +++ /dev/null @@ -1 +0,0 @@ -package cryptography From 2a4f20d080d31a8aab10a93ca0850e21c07c3860 Mon Sep 17 00:00:00 2001 From: Marvin Gajek Date: Tue, 12 Nov 2024 16:48:10 +0100 Subject: [PATCH 23/23] check boxes and add entries in CHANGELOG.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c33a5df..b2ca834 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,9 @@ TBD ### Functional - [ ] **Provide RESTful API for cryptographic operations**: Expose endpoints for managing cryptographic material and securing data (files, metadata) at rest. -- [ ] **Asymmetric encryption and decryption**: Support RSA and other asymmetric encryption algorithms for data protection. -- [ ] **Symmetric encryption**: Support for symmetric key encryption (e.g. AES) for data protection. -- [ ] **Hashing and signature verification**: Support hashing algorithms (e.g. SHA-256, SHA-512) and verify signatures using asymmetric keys (RSA, ECDSA, etc.). +- [x] **Asymmetric encryption and decryption**: Support RSA and other asymmetric encryption algorithms for data protection. +- [x] **Symmetric encryption**: Support for symmetric key encryption (e.g. AES) for data protection. +- [x] **Hashing and signature verification**: Support hashing algorithms (e.g. SHA-256, SHA-512) and verify signatures using asymmetric keys (RSA, ECDSA, etc.). - [ ] **PKCS#11 integration**: Enable key management in FIPS-compliant hardware or software. - [ ] **Manage cryptographic material**: Enable management of private/public key pairs and symmetric keys (generation, import/export, rotation, etc.). - [ ] **Key management lifecycle**: Implement key lifecycle management (generation, rotation, revocation, expiration).