Skip to content

Commit

Permalink
Merge pull request #8 from MGTheTrain/feature/secure-file-storage
Browse files Browse the repository at this point in the history
Feature/secure file storage
  • Loading branch information
MGTheTrain authored Nov 18, 2024
2 parents a263f8f + b41a4d0 commit 072f894
Show file tree
Hide file tree
Showing 15 changed files with 347 additions and 4 deletions.
Empty file removed docs/.gitkeep
Empty file.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ require (
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.22.1 // indirect
github.com/go-playground/validator/v10 v10.23.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
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/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
Expand Down
1 change: 0 additions & 1 deletion internal/domain/.gitkeep

This file was deleted.

21 changes: 21 additions & 0 deletions internal/domain/contracts/blob_management.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package contracts

import (
"crypto_vault_service/internal/domain/model"
"mime/multipart"
)

// BlobManagement defines methods for managing blob operations.
type BlobManagement interface {
// Upload handles the upload of a blob from a multipart form.
// Returns the created Blob metadata and any error encountered.
Upload(form *multipart.Form) (*model.Blob, error)

// Download retrieves a blob by its ID, returning the metadata and file data.
// Returns the Blob metadata, file data as a byte slice, and any error.
Download(blobId string) (*model.Blob, []byte, error)

// Delete removes a blob by its ID.
// Returns any error encountered.
Delete(blobId string) error
}
21 changes: 21 additions & 0 deletions internal/domain/contracts/key_management.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package contracts

import (
"crypto_vault_service/internal/domain/model"
"mime/multipart"
)

// KeyManagement defines methods for managing cryptographic key operations.
type KeyManagement interface {
// Upload handles the upload of a cryptographic key from a multipart form.
// Returns the created key metadata and any error encountered.
Upload(form *multipart.Form) (*model.CryptographicKey, error)

// Download retrieves a cryptographic key by its ID, returning the metadata and key data.
// Returns the key metadata, key data as a byte slice, and any error.
Download(keyId string) (*model.CryptographicKey, []byte, error)

// Delete removes a cryptographic key by its ID.
// Returns any error encountered.
Delete(keyId string) error
}
47 changes: 47 additions & 0 deletions internal/domain/contracts/key_operations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package contracts

// KeyOperations defines methods for key management, encryption, signing, and PKCS#11 operations.
type KeyOperations interface {

// ---Key generation---

// GenerateKey generates keys for specified type and size (e.g., AES, RSA, ECDSA)
GenerateKey(keyType string, keySize int) ([]byte, error)

// ---Key storage and retrieval---

// SaveKey saves a key to a file
SaveKey(key []byte, filename string) error
// LoadKey loads a key from a file
LoadKey(filename string) ([]byte, error)

// ---Encryption and Decryption (Symmetric algorithms like AES)---

// EncryptWithSymmetricKey encrypts data with symmetric keys (e.g. AES)
EncryptWithSymmetricKey(plainText []byte, key []byte) ([]byte, error)
// DecryptWithSymmetricKey decrypts data with symmetric keys (e.g. AES)
DecryptWithSymmetricKey(cipherText []byte, key []byte) ([]byte, error)

// ---Asymmetric Encryption (RSA, ECDSA, PKCS#11)---

// EncryptWithPublicKey encrypts with public key using asymmetric algorithms (RSA, ECDSA) and optionally a PKCS#11 interface
EncryptWithPublicKey(plainText []byte, publicKey interface{}) ([]byte, error)
// DecryptWithPrivateKey decrypt with private key using asymmetric algorithms (RSA, ECDSA) and optionally a PKCS#11 interface
DecryptWithPrivateKey(cipherText []byte, privateKey interface{}) ([]byte, error)

// ---Signing and Verification (For RSA, ECDSA)---

// SignWithPrivateKey signs message with private key using asymmetric algorithms (RSA, ECDSA) and optionally a PKCS#11 interface
SignWithPrivateKey(message []byte, privateKey interface{}) ([]byte, error)
// VerifyWithPublicKey verifies signatures with public key using asymmetric algorithms (RSA, ECDSA) and optionally a PKCS#11 interface
VerifyWithPublicKey(message []byte, signature []byte, publicKey interface{}) (bool, error)

// ---PKCS#11 Operations---

// InitializeToken initializes PKCS#11 token in the specified slot
InitializeToken(slot string) error
// AddKeyToToken adds key to the PKCS#11 token
AddKeyToToken() error
// DeleteKeyFromToken deletes key from PKCS#11 token by type and label
DeleteKeyFromToken(objectType, objectLabel string) error
}
30 changes: 30 additions & 0 deletions internal/domain/contracts/metadata_management.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package contracts

import (
"crypto_vault_service/internal/domain/model"
)

// MetadataManagement defines the methods for managing Blob and CryptographicKey metadata
type MetadataManagement interface {
// ---CRUD operations for Blob metadata---

// CreateBlob creates a new blob
CreateBlob(blob *model.Blob) (*model.Blob, error)
// GetBlob retrieves blob by ID
GetBlob(blobID string) (*model.Blob, error)
// UpdateBlob updates a blob's metadata
UpdateBlob(blobID string, updates *model.Blob) (*model.Blob, error)
// DeleteBlob deletes a blob by ID
DeleteBlob(blobID string) error

// ---CRUD operations for CryptographicKey metadata---

// CreateCryptographicKey creates a new cryptographic key
CreateCryptographicKey(key *model.CryptographicKey) (*model.CryptographicKey, error)
// GetCryptographicKey retrieves cryptographic key by ID
GetCryptographicKey(keyID string) (*model.CryptographicKey, error)
// UpdateCryptographicKey updates cryptographic key metadata
UpdateCryptographicKey(keyID string, updates *model.CryptographicKey) (*model.CryptographicKey, error)
// DeleteCryptographicKey deletes a cryptographic key by ID
DeleteCryptographicKey(keyID string) error
}
7 changes: 7 additions & 0 deletions internal/domain/contracts/permission_management.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package contracts

// PoC with OpenFGA required for signatures

// PermissionManagement Interface
type PermissionManagement interface {
}
43 changes: 43 additions & 0 deletions internal/domain/model/blob.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package model

import (
"fmt"
"time"

"github.com/go-playground/validator/v10"
)

// Blob represents metadata on the actual blob being stored
type Blob struct {
BlobID string `gorm:"primaryKey" validate:"required,uuid4"` // BlobID is required and must be a valid UUID
BlobStoragePath string `validate:"required"` // BlobStoragePath is required
UploadTime time.Time `validate:"required"` // UploadTime is required
UserID string `validate:"required,uuid4"` // UserID is required and must be a valid UUID
BlobName string `validate:"required,min=1,max=255"` // BlobName is required, and its length must be between 1 and 255 characters
BlobSize int `validate:"required,min=1"` // BlobSize must be greater than 0
BlobType string `validate:"required,min=1,max=50"` // BlobType is required, and its length must be between 1 and 50 characters
EncryptionAlgorithm string `validate:"omitempty,oneof=AES RSA ECDSA"` // EncryptionAlgorithm is optional and must be one of the listed algorithms
HashAlgorithm string `validate:"omitempty,oneof=SHA256 SHA512 MD5"` // HashAlgorithm is optional and must be one of the listed algorithms
IsEncrypted bool `validate:"-"` // IsEncrypted is required (true/false)
IsSigned bool `validate:"-"` // IsSigned is required (true/false)
CryptographicKey CryptographicKey `gorm:"foreignKey:KeyID" validate:"required"` // CryptographicKey is required
KeyID string `validate:"omitempty,uuid4"` // KeyID is optional and must be a valid UUID
}

// Validate for validating Blob struct
func (b *Blob) Validate() error {
// Initialize the validator
validate := validator.New()

// Validate the struct
err := validate.Struct(b)
if err != nil {
// If validation fails, return a formatted error
var validationErrors []string
for _, err := range err.(validator.ValidationErrors) {
validationErrors = append(validationErrors, fmt.Sprintf("Field: %s, Tag: %s", err.Field(), err.Tag()))
}
return fmt.Errorf("Validation failed: %v", validationErrors)
}
return nil // Return nil if validation passes
}
35 changes: 35 additions & 0 deletions internal/domain/model/key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package model

import (
"fmt"
"time"

"github.com/go-playground/validator/v10"
)

// CryptographicKey represents the encryption key entity
type CryptographicKey struct {
KeyID string `gorm:"primaryKey" validate:"required,uuid4"` // KeyID is required and must be a valid UUID
KeyType string `validate:"required,oneof=AES RSA ECDSA"` // KeyType is required and must be one of the listed types
CreatedAt time.Time `validate:"required"` // CreatedAt is required
ExpiresAt time.Time `validate:"required,gtefield=CreatedAt"` // ExpiresAt is required and must be after CreatedAt
UserID string `gorm:"index" validate:"required,uuid4"` // UserID is required and must be a valid UUID
}

// Validate for validating CryptographicKey struct
func (k *CryptographicKey) Validate() error {
// Initialize the validator
validate := validator.New()

// Validate the struct
err := validate.Struct(k)
if err != nil {
// If validation fails, return a formatted error
var validationErrors []string
for _, err := range err.(validator.ValidationErrors) {
validationErrors = append(validationErrors, fmt.Sprintf("Field: %s, Tag: %s", err.Field(), err.Tag()))
}
return fmt.Errorf("Validation failed: %v", validationErrors)
}
return nil // Return nil if validation passes
}
1 change: 0 additions & 1 deletion test/integration/domain/.gitkeep

This file was deleted.

1 change: 0 additions & 1 deletion test/unit/domain/.gitkeep

This file was deleted.

82 changes: 82 additions & 0 deletions test/unit/domain/blob_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package model

import (
"crypto_vault_service/internal/domain/model"
"testing"
"time"

"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)

// TestBlobValidation tests the Validator method for Blob
func TestBlobValidation(t *testing.T) {
// Valid Blob
validBlob := model.Blob{
BlobID: uuid.New().String(), // Valid UUID
BlobStoragePath: "/path/to/blob",
UploadTime: time.Now(),
UserID: uuid.New().String(), // Valid UUID
BlobName: "test_blob.txt",
BlobSize: 12345,
BlobType: "text",
EncryptionAlgorithm: "AES",
HashAlgorithm: "SHA256",
IsEncrypted: true,
IsSigned: false, // Explicitly set to false
CryptographicKey: model.CryptographicKey{KeyID: uuid.New().String(), KeyType: "AES", CreatedAt: time.Now(), ExpiresAt: time.Now().Add(24 * time.Hour), UserID: uuid.New().String()},
KeyID: uuid.New().String(), // Use valid UUID here
}

// Validate the valid Blob
err := validBlob.Validate()
assert.Nil(t, err, "Expected no validation errors for valid Blob")

// Invalid Blob (empty BlobID, invalid BlobSize)
invalidBlob := model.Blob{
BlobID: "", // Invalid empty BlobID
BlobStoragePath: "/path/to/blob",
UploadTime: time.Now(),
UserID: "invalid-uuid", // Invalid UserID
BlobName: "test_blob.txt",
BlobSize: -12345, // Invalid BlobSize (negative)
BlobType: "text",
EncryptionAlgorithm: "AES",
HashAlgorithm: "SHA256",
IsEncrypted: true,
IsSigned: false,
CryptographicKey: model.CryptographicKey{KeyID: uuid.New().String(), KeyType: "AES", CreatedAt: time.Now(), ExpiresAt: time.Now().Add(24 * time.Hour), UserID: uuid.New().String()},
KeyID: uuid.New().String(), // Use valid UUID
}

// Validate the invalid Blob
err = invalidBlob.Validate()
assert.NotNil(t, err, "Expected validation errors for invalid Blob")
assert.Contains(t, err.Error(), "Field: BlobID, Tag: required")
assert.Contains(t, err.Error(), "Field: BlobSize, Tag: min")
assert.Contains(t, err.Error(), "Field: UserID, Tag: uuid4")
}

// TestBlobValidationEdgeCases tests validation edge cases for Blob
func TestBlobValidationEdgeCases(t *testing.T) {
// Test missing BlobName (should fail)
invalidBlob := model.Blob{
BlobID: uuid.New().String(), // Valid UUID
BlobStoragePath: "/path/to/blob",
UploadTime: time.Now(),
UserID: uuid.New().String(), // Valid UUID
BlobName: "", // Invalid empty BlobName
BlobSize: 12345,
BlobType: "text",
EncryptionAlgorithm: "AES",
HashAlgorithm: "SHA256",
IsEncrypted: true,
IsSigned: false,
CryptographicKey: model.CryptographicKey{KeyID: uuid.New().String(), KeyType: "AES", CreatedAt: time.Now(), ExpiresAt: time.Now().Add(24 * time.Hour), UserID: uuid.New().String()},
KeyID: uuid.New().String(),
}

err := invalidBlob.Validate()
assert.NotNil(t, err, "Expected validation error for missing BlobName")
assert.Contains(t, err.Error(), "Field: BlobName, Tag: required")
}
58 changes: 58 additions & 0 deletions test/unit/domain/key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package model

import (
"crypto_vault_service/internal/domain/model"
"testing"
"time"

"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)

// TestCryptographicKeyValidation tests the Validator method for CryptographicKey
func TestCryptographicKeyValidation(t *testing.T) {
// Valid CryptographicKey
validKey := model.CryptographicKey{
KeyID: uuid.New().String(), // Valid UUID
KeyType: "AES", // Valid KeyType
CreatedAt: time.Now(),
ExpiresAt: time.Now().Add(time.Hour * 24), // Valid ExpiresAt
UserID: uuid.New().String(), // Valid UserID
}

// Validate the valid CryptographicKey
err := validKey.Validate()
assert.Nil(t, err, "Expected no validation errors for valid CryptographicKey")

// Invalid CryptographicKey (empty KeyID, invalid KeyType, expired)
invalidKey := model.CryptographicKey{
KeyID: "", // Invalid empty KeyID
KeyType: "InvalidType", // Invalid KeyType
CreatedAt: time.Now(),
ExpiresAt: time.Now().Add(-time.Hour * 24), // Invalid ExpiresAt (before CreatedAt)
UserID: "invalid-user-id", // Invalid UserID
}

// Validate the invalid CryptographicKey
err = invalidKey.Validate()
assert.NotNil(t, err, "Expected validation errors for invalid CryptographicKey")
assert.Contains(t, err.Error(), "Field: KeyID, Tag: required")
assert.Contains(t, err.Error(), "Field: KeyType, Tag: oneof")
assert.Contains(t, err.Error(), "Field: ExpiresAt, Tag: gtefield")
}

// TestCryptographicKeyValidations tests the validation edge cases for CryptographicKey
func TestCryptographicKeyValidations(t *testing.T) {
// Test missing UserID (should fail)
invalidKey := model.CryptographicKey{
KeyID: uuid.New().String(), // Valid UUID
KeyType: "AES", // Valid KeyType
CreatedAt: time.Now(),
ExpiresAt: time.Now().Add(time.Hour * 24), // Valid ExpiresAt
UserID: "", // Invalid empty UserID
}

err := invalidKey.Validate()
assert.NotNil(t, err, "Expected validation error for missing UserID")
assert.Contains(t, err.Error(), "Field: UserID, Tag: required")
}

0 comments on commit 072f894

Please sign in to comment.