ci: add golangci-lint config and fix formatting
- Add .golangci.yml with minimal linters (govet, ineffassign) - Run gofmt -s and goimports on all files to fix formatting - Disable fieldalignment and copylocks checks in govet
This commit is contained in:
@@ -14,38 +14,38 @@ import (
|
||||
const (
|
||||
// AES-256 requires 32-byte keys
|
||||
KeySize = 32
|
||||
|
||||
|
||||
// Nonce size for GCM
|
||||
NonceSize = 12
|
||||
|
||||
|
||||
// Salt size for key derivation
|
||||
SaltSize = 32
|
||||
|
||||
|
||||
// PBKDF2 iterations (100,000 is recommended minimum)
|
||||
PBKDF2Iterations = 100000
|
||||
|
||||
|
||||
// Magic header to identify encrypted files
|
||||
EncryptedFileMagic = "DBBACKUP_ENCRYPTED_V1"
|
||||
)
|
||||
|
||||
// EncryptionHeader stores metadata for encrypted files
|
||||
type EncryptionHeader struct {
|
||||
Magic [22]byte // "DBBACKUP_ENCRYPTED_V1" (21 bytes + null)
|
||||
Version uint8 // Version number (1)
|
||||
Algorithm uint8 // Algorithm ID (1 = AES-256-GCM)
|
||||
Salt [32]byte // Salt for key derivation
|
||||
Nonce [12]byte // GCM nonce
|
||||
Reserved [32]byte // Reserved for future use
|
||||
Magic [22]byte // "DBBACKUP_ENCRYPTED_V1" (21 bytes + null)
|
||||
Version uint8 // Version number (1)
|
||||
Algorithm uint8 // Algorithm ID (1 = AES-256-GCM)
|
||||
Salt [32]byte // Salt for key derivation
|
||||
Nonce [12]byte // GCM nonce
|
||||
Reserved [32]byte // Reserved for future use
|
||||
}
|
||||
|
||||
// EncryptionOptions configures encryption behavior
|
||||
type EncryptionOptions struct {
|
||||
// Key is the encryption key (32 bytes for AES-256)
|
||||
Key []byte
|
||||
|
||||
|
||||
// Passphrase for key derivation (alternative to direct key)
|
||||
Passphrase string
|
||||
|
||||
|
||||
// Salt for key derivation (if empty, will be generated)
|
||||
Salt []byte
|
||||
}
|
||||
@@ -79,7 +79,7 @@ func NewEncryptionWriter(w io.Writer, opts EncryptionOptions) (*EncryptionWriter
|
||||
// Derive or validate key
|
||||
var key []byte
|
||||
var salt []byte
|
||||
|
||||
|
||||
if opts.Passphrase != "" {
|
||||
// Derive key from passphrase
|
||||
if len(opts.Salt) == 0 {
|
||||
@@ -106,25 +106,25 @@ func NewEncryptionWriter(w io.Writer, opts EncryptionOptions) (*EncryptionWriter
|
||||
} else {
|
||||
return nil, fmt.Errorf("either Key or Passphrase must be provided")
|
||||
}
|
||||
|
||||
|
||||
// Create AES cipher
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create cipher: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// Create GCM mode
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create GCM: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// Generate nonce
|
||||
nonce := make([]byte, NonceSize)
|
||||
if _, err := rand.Read(nonce); err != nil {
|
||||
return nil, fmt.Errorf("failed to generate nonce: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// Write header
|
||||
header := EncryptionHeader{
|
||||
Version: 1,
|
||||
@@ -133,11 +133,11 @@ func NewEncryptionWriter(w io.Writer, opts EncryptionOptions) (*EncryptionWriter
|
||||
copy(header.Magic[:], []byte(EncryptedFileMagic))
|
||||
copy(header.Salt[:], salt)
|
||||
copy(header.Nonce[:], nonce)
|
||||
|
||||
|
||||
if err := writeHeader(w, &header); err != nil {
|
||||
return nil, fmt.Errorf("failed to write header: %w", err)
|
||||
}
|
||||
|
||||
|
||||
return &EncryptionWriter{
|
||||
writer: w,
|
||||
gcm: gcm,
|
||||
@@ -160,16 +160,16 @@ func (ew *EncryptionWriter) Write(p []byte) (n int, err error) {
|
||||
if ew.closed {
|
||||
return 0, fmt.Errorf("writer is closed")
|
||||
}
|
||||
|
||||
|
||||
// Accumulate data in buffer
|
||||
ew.buffer = append(ew.buffer, p...)
|
||||
|
||||
|
||||
// If buffer is large enough, encrypt and write
|
||||
const chunkSize = 64 * 1024 // 64KB chunks
|
||||
for len(ew.buffer) >= chunkSize {
|
||||
chunk := ew.buffer[:chunkSize]
|
||||
encrypted := ew.gcm.Seal(nil, ew.nonce, chunk, nil)
|
||||
|
||||
|
||||
// Write encrypted chunk size (4 bytes) then chunk
|
||||
size := uint32(len(encrypted))
|
||||
sizeBytes := []byte{
|
||||
@@ -184,15 +184,15 @@ func (ew *EncryptionWriter) Write(p []byte) (n int, err error) {
|
||||
if _, err := ew.writer.Write(encrypted); err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
|
||||
// Move remaining data to start of buffer
|
||||
ew.buffer = ew.buffer[chunkSize:]
|
||||
n += chunkSize
|
||||
|
||||
|
||||
// Increment nonce for next chunk
|
||||
incrementNonce(ew.nonce)
|
||||
}
|
||||
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
@@ -202,11 +202,11 @@ func (ew *EncryptionWriter) Close() error {
|
||||
return nil
|
||||
}
|
||||
ew.closed = true
|
||||
|
||||
|
||||
// Encrypt and write remaining buffer
|
||||
if len(ew.buffer) > 0 {
|
||||
encrypted := ew.gcm.Seal(nil, ew.nonce, ew.buffer, nil)
|
||||
|
||||
|
||||
size := uint32(len(encrypted))
|
||||
sizeBytes := []byte{
|
||||
byte(size >> 24),
|
||||
@@ -221,12 +221,12 @@ func (ew *EncryptionWriter) Close() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Write final zero-length chunk to signal end
|
||||
if _, err := ew.writer.Write([]byte{0, 0, 0, 0}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -237,22 +237,22 @@ func NewDecryptionReader(r io.Reader, opts EncryptionOptions) (*DecryptionReader
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read header: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// Verify magic
|
||||
if string(header.Magic[:len(EncryptedFileMagic)]) != EncryptedFileMagic {
|
||||
return nil, fmt.Errorf("not an encrypted backup file")
|
||||
}
|
||||
|
||||
|
||||
// Verify version
|
||||
if header.Version != 1 {
|
||||
return nil, fmt.Errorf("unsupported encryption version: %d", header.Version)
|
||||
}
|
||||
|
||||
|
||||
// Verify algorithm
|
||||
if header.Algorithm != 1 {
|
||||
return nil, fmt.Errorf("unsupported encryption algorithm: %d", header.Algorithm)
|
||||
}
|
||||
|
||||
|
||||
// Derive or validate key
|
||||
var key []byte
|
||||
if opts.Passphrase != "" {
|
||||
@@ -265,22 +265,22 @@ func NewDecryptionReader(r io.Reader, opts EncryptionOptions) (*DecryptionReader
|
||||
} else {
|
||||
return nil, fmt.Errorf("either Key or Passphrase must be provided")
|
||||
}
|
||||
|
||||
|
||||
// Create AES cipher
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create cipher: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// Create GCM mode
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create GCM: %w", err)
|
||||
}
|
||||
|
||||
|
||||
nonce := make([]byte, NonceSize)
|
||||
copy(nonce, header.Nonce[:])
|
||||
|
||||
|
||||
return &DecryptionReader{
|
||||
reader: r,
|
||||
gcm: gcm,
|
||||
@@ -306,12 +306,12 @@ func (dr *DecryptionReader) Read(p []byte) (n int, err error) {
|
||||
dr.buffer = dr.buffer[n:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
|
||||
// If EOF reached, return EOF
|
||||
if dr.eof {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
|
||||
// Read next chunk size
|
||||
sizeBytes := make([]byte, 4)
|
||||
if _, err := io.ReadFull(dr.reader, sizeBytes); err != nil {
|
||||
@@ -321,36 +321,36 @@ func (dr *DecryptionReader) Read(p []byte) (n int, err error) {
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
|
||||
size := uint32(sizeBytes[0])<<24 | uint32(sizeBytes[1])<<16 | uint32(sizeBytes[2])<<8 | uint32(sizeBytes[3])
|
||||
|
||||
|
||||
// Zero-length chunk signals end of stream
|
||||
if size == 0 {
|
||||
dr.eof = true
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
|
||||
// Read encrypted chunk
|
||||
encrypted := make([]byte, size)
|
||||
if _, err := io.ReadFull(dr.reader, encrypted); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
|
||||
// Decrypt chunk
|
||||
decrypted, err := dr.gcm.Open(nil, dr.nonce, encrypted, nil)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("decryption failed (wrong key?): %w", err)
|
||||
}
|
||||
|
||||
|
||||
// Increment nonce for next chunk
|
||||
incrementNonce(dr.nonce)
|
||||
|
||||
|
||||
// Return as much as fits in p, buffer the rest
|
||||
n = copy(p, decrypted)
|
||||
if n < len(decrypted) {
|
||||
dr.buffer = decrypted[n:]
|
||||
}
|
||||
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
@@ -364,7 +364,7 @@ func writeHeader(w io.Writer, h *EncryptionHeader) error {
|
||||
copy(data[24:56], h.Salt[:])
|
||||
copy(data[56:68], h.Nonce[:])
|
||||
copy(data[68:100], h.Reserved[:])
|
||||
|
||||
|
||||
_, err := w.Write(data)
|
||||
return err
|
||||
}
|
||||
@@ -374,7 +374,7 @@ func readHeader(r io.Reader) (*EncryptionHeader, error) {
|
||||
if _, err := io.ReadFull(r, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
header := &EncryptionHeader{
|
||||
Version: data[22],
|
||||
Algorithm: data[23],
|
||||
@@ -383,7 +383,7 @@ func readHeader(r io.Reader) (*EncryptionHeader, error) {
|
||||
copy(header.Salt[:], data[24:56])
|
||||
copy(header.Nonce[:], data[56:68])
|
||||
copy(header.Reserved[:], data[68:100])
|
||||
|
||||
|
||||
return header, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user