Store the public key in the config to ensure one key policy.

Also make sure that RSA encrpytion works with the copy command.
This commit is contained in:
Gilbert Chen
2019-09-23 12:53:43 -04:00
parent 90833f9d86
commit 9a0d60ca84
4 changed files with 208 additions and 125 deletions

View File

@@ -208,6 +208,17 @@ func runScript(context *cli.Context, storageName string, phase string) bool {
return true return true
} }
func loadRSAPrivateKey(keyFile string, preference *duplicacy.Preference, backupManager *duplicacy.BackupManager, resetPasswords bool) {
if keyFile == "" {
return
}
prompt := fmt.Sprintf("Enter the passphrase for %s:", keyFile)
passphrase := duplicacy.GetPassword(*preference, "rsa_passphrase", prompt, false, resetPasswords)
backupManager.LoadRSAPrivateKey(keyFile, passphrase)
duplicacy.SavePassword(*preference, "rsa_passphrase", passphrase)
}
func initRepository(context *cli.Context) { func initRepository(context *cli.Context) {
configRepository(context, true) configRepository(context, true)
} }
@@ -319,6 +330,11 @@ func configRepository(context *cli.Context, init bool) {
if preference.Encrypted { if preference.Encrypted {
prompt := fmt.Sprintf("Enter storage password for %s:", preference.StorageURL) prompt := fmt.Sprintf("Enter storage password for %s:", preference.StorageURL)
storagePassword = duplicacy.GetPassword(preference, "password", prompt, false, true) storagePassword = duplicacy.GetPassword(preference, "password", prompt, false, true)
} else {
if context.String("key") != "" {
duplicacy.LOG_ERROR("STORAGE_CONFIG", "RSA encryption can't be enabled with an unencrypted storage")
return
}
} }
existingConfig, _, err := duplicacy.DownloadConfig(storage, storagePassword) existingConfig, _, err := duplicacy.DownloadConfig(storage, storagePassword)
@@ -434,7 +450,7 @@ func configRepository(context *cli.Context, init bool) {
iterations = duplicacy.CONFIG_DEFAULT_ITERATIONS iterations = duplicacy.CONFIG_DEFAULT_ITERATIONS
} }
duplicacy.ConfigStorage(storage, iterations, compressionLevel, averageChunkSize, maximumChunkSize, duplicacy.ConfigStorage(storage, iterations, compressionLevel, averageChunkSize, maximumChunkSize,
minimumChunkSize, storagePassword, otherConfig, bitCopy) minimumChunkSize, storagePassword, otherConfig, bitCopy, context.String("key"))
} }
duplicacy.Preferences = append(duplicacy.Preferences, preference) duplicacy.Preferences = append(duplicacy.Preferences, preference)
@@ -718,10 +734,6 @@ func backupRepository(context *cli.Context) {
backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, preference.NobackupFile) backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, preference.NobackupFile)
duplicacy.SavePassword(*preference, "password", password) duplicacy.SavePassword(*preference, "password", password)
if context.String("key") != "" {
backupManager.SetupRSAPublicKey(context.String("key"))
}
backupManager.SetupSnapshotCache(preference.Name) backupManager.SetupSnapshotCache(preference.Name)
backupManager.SetDryRun(dryRun) backupManager.SetDryRun(dryRun)
backupManager.Backup(repository, quickMode, threads, context.String("t"), showStatistics, enableVSS, vssTimeout, enumOnly) backupManager.Backup(repository, quickMode, threads, context.String("t"), showStatistics, enableVSS, vssTimeout, enumOnly)
@@ -799,12 +811,7 @@ func restoreRepository(context *cli.Context) {
backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, preference.NobackupFile) backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, preference.NobackupFile)
duplicacy.SavePassword(*preference, "password", password) duplicacy.SavePassword(*preference, "password", password)
if context.String("key") != "" { loadRSAPrivateKey(context.String("key"), preference, backupManager, false)
prompt := fmt.Sprintf("Enter the passphrase for %s:", context.String("key"))
passphrase := duplicacy.GetPassword(*preference, "rsa_passphrase", prompt, false, false)
backupManager.SetupRSAPrivateKey(context.String("key"), passphrase)
duplicacy.SavePassword(*preference, "rsa_passphrase", passphrase)
}
backupManager.SetupSnapshotCache(preference.Name) backupManager.SetupSnapshotCache(preference.Name)
backupManager.Restore(repository, revision, true, quickMode, threads, overwrite, deleteMode, setOwner, showStatistics, patterns) backupManager.Restore(repository, revision, true, quickMode, threads, overwrite, deleteMode, setOwner, showStatistics, patterns)
@@ -857,12 +864,7 @@ func listSnapshots(context *cli.Context) {
showChunks := context.Bool("chunks") showChunks := context.Bool("chunks")
// list doesn't need to decrypt file chunks; but we need -key here so we can reset the passphrase for the private key // list doesn't need to decrypt file chunks; but we need -key here so we can reset the passphrase for the private key
if context.String("key") != "" { loadRSAPrivateKey(context.String("key"), preference, backupManager, resetPassword)
prompt := fmt.Sprintf("Enter the passphrase for %s:", context.String("key"))
passphrase := duplicacy.GetPassword(*preference, "rsa_passphrase", prompt, false, resetPassword)
backupManager.SetupRSAPrivateKey(context.String("key"), passphrase)
duplicacy.SavePassword(*preference, "rsa_passphrase", passphrase)
}
backupManager.SetupSnapshotCache(preference.Name) backupManager.SetupSnapshotCache(preference.Name)
backupManager.SnapshotManager.ListSnapshots(id, revisions, tag, showFiles, showChunks) backupManager.SnapshotManager.ListSnapshots(id, revisions, tag, showFiles, showChunks)
@@ -902,12 +904,7 @@ func checkSnapshots(context *cli.Context) {
backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, preference.NobackupFile) backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, preference.NobackupFile)
duplicacy.SavePassword(*preference, "password", password) duplicacy.SavePassword(*preference, "password", password)
if context.String("key") != "" { loadRSAPrivateKey(context.String("key"), preference, backupManager, false)
prompt := fmt.Sprintf("Enter the passphrase for %s:", context.String("key"))
passphrase := duplicacy.GetPassword(*preference, "rsa_passphrase", prompt, false, false)
backupManager.SetupRSAPrivateKey(context.String("key"), passphrase)
duplicacy.SavePassword(*preference, "rsa_passphrase", passphrase)
}
id := preference.SnapshotID id := preference.SnapshotID
if context.Bool("all") { if context.Bool("all") {
@@ -964,12 +961,7 @@ func printFile(context *cli.Context) {
backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, preference.NobackupFile) backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, preference.NobackupFile)
duplicacy.SavePassword(*preference, "password", password) duplicacy.SavePassword(*preference, "password", password)
if context.String("key") != "" { loadRSAPrivateKey(context.String("key"), preference, backupManager, false)
prompt := fmt.Sprintf("Enter the passphrase for %s:", context.String("key"))
passphrase := duplicacy.GetPassword(*preference, "rsa_passphrase", prompt, false, false)
backupManager.SetupRSAPrivateKey(context.String("key"), passphrase)
duplicacy.SavePassword(*preference, "rsa_passphrase", passphrase)
}
backupManager.SetupSnapshotCache(preference.Name) backupManager.SetupSnapshotCache(preference.Name)
@@ -1027,12 +1019,7 @@ func diff(context *cli.Context) {
backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, preference.NobackupFile) backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, preference.NobackupFile)
duplicacy.SavePassword(*preference, "password", password) duplicacy.SavePassword(*preference, "password", password)
if context.String("key") != "" { loadRSAPrivateKey(context.String("key"), preference, backupManager, false)
prompt := fmt.Sprintf("Enter the passphrase for %s:", context.String("key"))
passphrase := duplicacy.GetPassword(*preference, "rsa_passphrase", prompt, false, false)
backupManager.SetupRSAPrivateKey(context.String("key"), passphrase)
duplicacy.SavePassword(*preference, "rsa_passphrase", passphrase)
}
backupManager.SetupSnapshotCache(preference.Name) backupManager.SetupSnapshotCache(preference.Name)
backupManager.SnapshotManager.Diff(repository, snapshotID, revisions, path, compareByHash, preference.NobackupFile) backupManager.SnapshotManager.Diff(repository, snapshotID, revisions, path, compareByHash, preference.NobackupFile)
@@ -1181,6 +1168,8 @@ func copySnapshots(context *cli.Context) {
sourceManager.SetupSnapshotCache(source.Name) sourceManager.SetupSnapshotCache(source.Name)
duplicacy.SavePassword(*source, "password", sourcePassword) duplicacy.SavePassword(*source, "password", sourcePassword)
loadRSAPrivateKey(context.String("key"), source, sourceManager, false)
_, destination := getRepositoryPreference(context, context.String("to")) _, destination := getRepositoryPreference(context, context.String("to"))
if destination.Name == source.Name { if destination.Name == source.Name {
@@ -1388,6 +1377,11 @@ func main() {
Usage: "initialize a new repository at the specified path rather than the current working directory", Usage: "initialize a new repository at the specified path rather than the current working directory",
Argument: "<path>", Argument: "<path>",
}, },
cli.StringFlag{
Name: "key",
Usage: "the RSA public key to encrypt file chunks",
Argument: "<public key>",
},
}, },
Usage: "Initialize the storage if necessary and the current directory as the repository", Usage: "Initialize the storage if necessary and the current directory as the repository",
ArgsUsage: "<snapshot id> <storage url>", ArgsUsage: "<snapshot id> <storage url>",
@@ -1444,11 +1438,6 @@ func main() {
Name: "enum-only", Name: "enum-only",
Usage: "enumerate the repository recursively and then exit", Usage: "enumerate the repository recursively and then exit",
}, },
cli.StringFlag{
Name: "key",
Usage: "the RSA public key to encrypt file chunks",
Argument: "<public key>",
},
}, },
Usage: "Save a snapshot of the repository to the storage", Usage: "Save a snapshot of the repository to the storage",
ArgsUsage: " ", ArgsUsage: " ",
@@ -1837,6 +1826,11 @@ func main() {
Usage: "specify the path of the repository (instead of the current working directory)", Usage: "specify the path of the repository (instead of the current working directory)",
Argument: "<path>", Argument: "<path>",
}, },
cli.StringFlag{
Name: "key",
Usage: "the RSA public key to encrypt file chunks",
Argument: "<public key>",
},
}, },
Usage: "Add an additional storage to be used for the existing repository", Usage: "Add an additional storage to be used for the existing repository",
ArgsUsage: "<storage name> <snapshot id> <storage url>", ArgsUsage: "<storage name> <snapshot id> <storage url>",
@@ -1935,6 +1929,11 @@ func main() {
Usage: "number of uploading threads", Usage: "number of uploading threads",
Argument: "<n>", Argument: "<n>",
}, },
cli.StringFlag{
Name: "key",
Usage: "the RSA private key to decrypt file chunks from the source storage",
Argument: "<public key>",
},
}, },
Usage: "Copy snapshots between compatible storages", Usage: "Copy snapshots between compatible storages",
ArgsUsage: " ", ArgsUsage: " ",

View File

@@ -20,11 +20,6 @@ import (
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"reflect"
) )
// BackupManager performs the two major operations, backup and restore, and passes other operations, mostly related to // BackupManager performs the two major operations, backup and restore, and passes other operations, mostly related to
@@ -81,6 +76,11 @@ func CreateBackupManager(snapshotID string, storage Storage, top string, passwor
return backupManager return backupManager
} }
// loadRSAPrivateKey loads the specifed private key file for decrypting file chunks
func (manager *BackupManager) LoadRSAPrivateKey(keyFile string, passphrase string) {
manager.config.loadRSAPrivateKey(keyFile, passphrase)
}
// SetupSnapshotCache creates the snapshot cache, which is merely a local storage under the default .duplicacy // SetupSnapshotCache creates the snapshot cache, which is merely a local storage under the default .duplicacy
// directory // directory
func (manager *BackupManager) SetupSnapshotCache(storageName string) bool { func (manager *BackupManager) SetupSnapshotCache(storageName string) bool {
@@ -108,80 +108,6 @@ func (manager *BackupManager) SetupSnapshotCache(storageName string) bool {
return true return true
} }
// SetupRSAPublicKey loads the specifed public key file for encrypting file chunks
func (manager *BackupManager) SetupRSAPublicKey(keyFile string) {
encodedKey, err := ioutil.ReadFile(keyFile)
if err != nil {
LOG_ERROR("BACKUP_KEY", "Failed to read the public key file: %v", err)
return
}
decodedKey, _ := pem.Decode(encodedKey)
if decodedKey == nil {
LOG_ERROR("RESTORE_KEY", "unrecognized public key in %s", keyFile)
return
}
if decodedKey.Type != "PUBLIC KEY" {
LOG_ERROR("BACKUP_KEY", "Unsupported public key type %s in %s", decodedKey.Type, keyFile)
return
}
parsedKey, err := x509.ParsePKIXPublicKey(decodedKey.Bytes)
if err != nil {
LOG_ERROR("BACKUP_KEY", "Failed to parse the public key in %s: %v", keyFile, err)
return
}
key, ok := parsedKey.(*rsa.PublicKey)
if !ok {
LOG_ERROR("BACKUP_KEY", "Unsupported public key type %s in %s", reflect.TypeOf(parsedKey), keyFile)
return
}
manager.config.rsaPublicKey = key
}
// SetupRSAPrivateKey loads the specifed private key file for decrypting file chunks
func (manager *BackupManager) SetupRSAPrivateKey(keyFile string, passphrase string) {
encodedKey, err := ioutil.ReadFile(keyFile)
if err != nil {
LOG_ERROR("RESTORE_KEY", "Failed to read the private key file: %v", err)
}
decodedKey, _ := pem.Decode(encodedKey)
if decodedKey == nil {
LOG_ERROR("RESTORE_KEY", "unrecognized private key in %s", keyFile)
return
}
if decodedKey.Type != "RSA PRIVATE KEY" {
LOG_ERROR("RESTORE_KEY", "Unsupported private key type %s in %s", decodedKey.Type, keyFile)
return
}
var decodedKeyBytes []byte
if passphrase != "" {
decodedKeyBytes, err = x509.DecryptPEMBlock(decodedKey, []byte(passphrase))
} else {
decodedKeyBytes = decodedKey.Bytes
}
var parsedKey interface{}
if parsedKey, err = x509.ParsePKCS1PrivateKey(decodedKeyBytes); err != nil {
if parsedKey, err = x509.ParsePKCS8PrivateKey(decodedKeyBytes); err != nil {
LOG_ERROR("RESTORE_KEY", "Failed to parse the private key in %s: %v", keyFile, err)
return
}
}
key, ok := parsedKey.(*rsa.PrivateKey)
if !ok {
LOG_ERROR("RESTORE_KEY", "Unsupported private key type %s in %s", reflect.TypeOf(parsedKey), keyFile)
return
}
manager.config.rsaPrivateKey = key
}
// setEntryContent sets the 4 content pointers for each entry in 'entries'. 'offset' indicates the value // setEntryContent sets the 4 content pointers for each entry in 'entries'. 'offset' indicates the value
// to be added to the StartChunk and EndChunk points, used when intending to append 'entries' to the // to be added to the StartChunk and EndChunk points, used when intending to append 'entries' to the
@@ -256,6 +182,10 @@ func (manager *BackupManager) Backup(top string, quickMode bool, threads int, ta
LOG_DEBUG("BACKUP_PARAMETERS", "top: %s, quick: %t, tag: %s", top, quickMode, tag) LOG_DEBUG("BACKUP_PARAMETERS", "top: %s, quick: %t, tag: %s", top, quickMode, tag)
if manager.config.rsaPublicKey != nil && len(manager.config.FileKey) > 0 {
LOG_INFO("BACKUP_KEY", "RSA encryption is enabled" )
}
remoteSnapshot := manager.SnapshotManager.downloadLatestSnapshot(manager.snapshotID) remoteSnapshot := manager.SnapshotManager.downloadLatestSnapshot(manager.snapshotID)
if remoteSnapshot == nil { if remoteSnapshot == nil {
remoteSnapshot = CreateEmptySnapshot(manager.snapshotID) remoteSnapshot = CreateEmptySnapshot(manager.snapshotID)
@@ -1796,6 +1726,7 @@ func (manager *BackupManager) CopySnapshots(otherManager *BackupManager, snapsho
newChunk := otherManager.config.GetChunk() newChunk := otherManager.config.GetChunk()
newChunk.Reset(true) newChunk.Reset(true)
newChunk.Write(chunk.GetBytes()) newChunk.Write(chunk.GetBytes())
newChunk.encryptionVersion = chunk.encryptionVersion
chunkUploader.StartChunk(newChunk, chunkIndex) chunkUploader.StartChunk(newChunk, chunkIndex)
totalCopied++ totalCopied++
} else { } else {

View File

@@ -62,6 +62,8 @@ type Chunk struct {
config *Config // Every chunk is associated with a Config object. Which hashing algorithm to use is determined config *Config // Every chunk is associated with a Config object. Which hashing algorithm to use is determined
// by the config // by the config
encryptionVersion byte // The version type in the encrytion header
} }
// Magic word to identify a duplicacy format encrypted file, plus a version number. // Magic word to identify a duplicacy format encrypted file, plus a version number.
@@ -191,7 +193,7 @@ func (chunk *Chunk) Encrypt(encryptionKey []byte, derivationKey string, isSnapsh
key := encryptionKey key := encryptionKey
usingRSA := false usingRSA := false
if !isSnapshot && chunk.config.rsaPublicKey != nil { if chunk.config.rsaPublicKey != nil && (!isSnapshot || chunk.encryptionVersion == ENCRYPTION_VERSION_RSA) {
// If the chunk is not a snpashot chunk, we attempt to encrypt it with the RSA publick key if there is one // If the chunk is not a snpashot chunk, we attempt to encrypt it with the RSA publick key if there is one
randomKey := make([]byte, 32) randomKey := make([]byte, 32)
_, err := rand.Read(randomKey) _, err := rand.Read(randomKey)
@@ -319,6 +321,8 @@ func (chunk *Chunk) Decrypt(encryptionKey []byte, derivationKey string) (err err
chunk.buffer, encryptedBuffer = encryptedBuffer, chunk.buffer chunk.buffer, encryptedBuffer = encryptedBuffer, chunk.buffer
headerLength := len(ENCRYPTION_HEADER) headerLength := len(ENCRYPTION_HEADER)
chunk.encryptionVersion = 0
if len(encryptionKey) > 0 { if len(encryptionKey) > 0 {
key := encryptionKey key := encryptionKey
@@ -343,12 +347,12 @@ func (chunk *Chunk) Decrypt(encryptionKey []byte, derivationKey string) (err err
return fmt.Errorf("The storage doesn't seem to be encrypted") return fmt.Errorf("The storage doesn't seem to be encrypted")
} }
version := encryptedBuffer.Bytes()[headerLength-1] chunk.encryptionVersion = encryptedBuffer.Bytes()[headerLength-1]
if version != 0 && version != ENCRYPTION_VERSION_RSA { if chunk.encryptionVersion != 0 && chunk.encryptionVersion != ENCRYPTION_VERSION_RSA {
return fmt.Errorf("Unsupported encryption version %d", version) return fmt.Errorf("Unsupported encryption version %d", chunk.encryptionVersion)
} }
if version == ENCRYPTION_VERSION_RSA { if chunk.encryptionVersion == ENCRYPTION_VERSION_RSA {
if chunk.config.rsaPrivateKey == nil { if chunk.config.rsaPrivateKey == nil {
LOG_ERROR("CHUNK_DECRYPT", "An RSA private key is required to decrypt the chunk") LOG_ERROR("CHUNK_DECRYPT", "An RSA private key is required to decrypt the chunk")
return fmt.Errorf("An RSA private key is required to decrypt the chunk") return fmt.Errorf("An RSA private key is required to decrypt the chunk")

View File

@@ -10,15 +10,19 @@ import (
"crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/sha256"
"crypto/rsa" "crypto/rsa"
"crypto/x509"
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"encoding/pem"
"fmt" "fmt"
"hash" "hash"
"os" "os"
"runtime" "runtime"
"runtime/debug" "runtime/debug"
"sync/atomic" "sync/atomic"
"io/ioutil"
"reflect"
blake2 "github.com/minio/blake2b-simd" blake2 "github.com/minio/blake2b-simd"
) )
@@ -85,10 +89,15 @@ type jsonableConfig struct {
IDKey string `json:"id-key"` IDKey string `json:"id-key"`
ChunkKey string `json:"chunk-key"` ChunkKey string `json:"chunk-key"`
FileKey string `json:"file-key"` FileKey string `json:"file-key"`
RSAPublicKey string `json:"rsa-public-key"`
} }
func (config *Config) MarshalJSON() ([]byte, error) { func (config *Config) MarshalJSON() ([]byte, error) {
publicKey := []byte {}
if config.rsaPublicKey != nil {
publicKey, _ = x509.MarshalPKIXPublicKey(config.rsaPublicKey)
}
return json.Marshal(&jsonableConfig{ return json.Marshal(&jsonableConfig{
aliasedConfig: (*aliasedConfig)(config), aliasedConfig: (*aliasedConfig)(config),
ChunkSeed: hex.EncodeToString(config.ChunkSeed), ChunkSeed: hex.EncodeToString(config.ChunkSeed),
@@ -96,6 +105,7 @@ func (config *Config) MarshalJSON() ([]byte, error) {
IDKey: hex.EncodeToString(config.IDKey), IDKey: hex.EncodeToString(config.IDKey),
ChunkKey: hex.EncodeToString(config.ChunkKey), ChunkKey: hex.EncodeToString(config.ChunkKey),
FileKey: hex.EncodeToString(config.FileKey), FileKey: hex.EncodeToString(config.FileKey),
RSAPublicKey: hex.EncodeToString(publicKey),
}) })
} }
@@ -125,6 +135,19 @@ func (config *Config) UnmarshalJSON(description []byte) (err error) {
return fmt.Errorf("Invalid representation of the file key in the config") return fmt.Errorf("Invalid representation of the file key in the config")
} }
if publicKey, err := hex.DecodeString(aliased.RSAPublicKey); err != nil {
return fmt.Errorf("Invalid hex encoding of the RSA public key in the config")
} else if len(publicKey) > 0 {
parsedKey, err := x509.ParsePKIXPublicKey(publicKey)
if err != nil {
return fmt.Errorf("Invalid RSA public key in the config: %v", err)
}
config.rsaPublicKey = parsedKey.(*rsa.PublicKey)
if config.rsaPublicKey == nil {
return fmt.Errorf("Unsupported public key type %s in the config", reflect.TypeOf(parsedKey))
}
}
return nil return nil
} }
@@ -145,6 +168,29 @@ func (config *Config) Print() {
LOG_INFO("CONFIG_INFO", "Maximum chunk size: %d", config.MaximumChunkSize) LOG_INFO("CONFIG_INFO", "Maximum chunk size: %d", config.MaximumChunkSize)
LOG_INFO("CONFIG_INFO", "Minimum chunk size: %d", config.MinimumChunkSize) LOG_INFO("CONFIG_INFO", "Minimum chunk size: %d", config.MinimumChunkSize)
LOG_INFO("CONFIG_INFO", "Chunk seed: %x", config.ChunkSeed) LOG_INFO("CONFIG_INFO", "Chunk seed: %x", config.ChunkSeed)
LOG_TRACE("CONFIG_INFO", "Hash key: %x", config.HashKey)
LOG_TRACE("CONFIG_INFO", "ID key: %x", config.IDKey)
if len(config.ChunkKey) >= 0 {
LOG_TRACE("CONFIG_INFO", "File chunks are encrypted")
}
if len(config.FileKey) >= 0 {
LOG_TRACE("CONFIG_INFO", "Metadata chunks are encrypted")
}
if config.rsaPublicKey != nil {
pkisPublicKey, _ := x509.MarshalPKIXPublicKey(config.rsaPublicKey)
publicKey := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: pkisPublicKey,
})
LOG_TRACE("CONFIG_INFO", "RSA public key: %s", publicKey)
}
} }
func CreateConfigFromParameters(compressionLevel int, averageChunkSize int, maximumChunkSize int, mininumChunkSize int, func CreateConfigFromParameters(compressionLevel int, averageChunkSize int, maximumChunkSize int, mininumChunkSize int,
@@ -482,7 +528,7 @@ func UploadConfig(storage Storage, config *Config, password string, iterations i
// it simply creates a file named 'config' that stores various parameters as well as a set of keys if encryption // it simply creates a file named 'config' that stores various parameters as well as a set of keys if encryption
// is enabled. // is enabled.
func ConfigStorage(storage Storage, iterations int, compressionLevel int, averageChunkSize int, maximumChunkSize int, func ConfigStorage(storage Storage, iterations int, compressionLevel int, averageChunkSize int, maximumChunkSize int,
minimumChunkSize int, password string, copyFrom *Config, bitCopy bool) bool { minimumChunkSize int, password string, copyFrom *Config, bitCopy bool, keyFile string) bool {
exist, _, _, err := storage.GetFileInfo(0, "config") exist, _, _, err := storage.GetFileInfo(0, "config")
if err != nil { if err != nil {
@@ -501,5 +547,108 @@ func ConfigStorage(storage Storage, iterations int, compressionLevel int, averag
return false return false
} }
if keyFile != "" {
config.loadRSAPublicKey(keyFile)
}
return UploadConfig(storage, config, password, iterations) return UploadConfig(storage, config, password, iterations)
} }
func (config *Config) loadRSAPublicKey(keyFile string) {
encodedKey, err := ioutil.ReadFile(keyFile)
if err != nil {
LOG_ERROR("BACKUP_KEY", "Failed to read the public key file: %v", err)
return
}
decodedKey, _ := pem.Decode(encodedKey)
if decodedKey == nil {
LOG_ERROR("RSA_PUBLIC", "unrecognized public key in %s", keyFile)
return
}
if decodedKey.Type != "PUBLIC KEY" {
LOG_ERROR("RSA_PUBLIC", "Unsupported public key type %s in %s", decodedKey.Type, keyFile)
return
}
parsedKey, err := x509.ParsePKIXPublicKey(decodedKey.Bytes)
if err != nil {
LOG_ERROR("RSA_PUBLIC", "Failed to parse the public key in %s: %v", keyFile, err)
return
}
key, ok := parsedKey.(*rsa.PublicKey)
if !ok {
LOG_ERROR("RSA_PUBLIC", "Unsupported public key type %s in %s", reflect.TypeOf(parsedKey), keyFile)
return
}
config.rsaPublicKey = key
}
// loadRSAPrivateKey loads the specifed private key file for decrypting file chunks
func (config *Config) loadRSAPrivateKey(keyFile string, passphrase string) {
encodedKey, err := ioutil.ReadFile(keyFile)
if err != nil {
LOG_ERROR("RSA_PRIVATE", "Failed to read the private key file: %v", err)
return
}
decodedKey, _ := pem.Decode(encodedKey)
if decodedKey == nil {
LOG_ERROR("RSA_PRIVATE", "unrecognized private key in %s", keyFile)
return
}
if decodedKey.Type != "RSA PRIVATE KEY" {
LOG_ERROR("RSA_PRIVATE", "Unsupported private key type %s in %s", decodedKey.Type, keyFile)
return
}
var decodedKeyBytes []byte
if passphrase != "" {
decodedKeyBytes, err = x509.DecryptPEMBlock(decodedKey, []byte(passphrase))
} else {
decodedKeyBytes = decodedKey.Bytes
}
var parsedKey interface{}
if parsedKey, err = x509.ParsePKCS1PrivateKey(decodedKeyBytes); err != nil {
if parsedKey, err = x509.ParsePKCS8PrivateKey(decodedKeyBytes); err != nil {
LOG_ERROR("RSA_PRIVATE", "Failed to parse the private key in %s: %v", keyFile, err)
return
}
}
key, ok := parsedKey.(*rsa.PrivateKey)
if !ok {
LOG_ERROR("RSA_PRIVATE", "Unsupported private key type %s in %s", reflect.TypeOf(parsedKey), keyFile)
return
}
data := make([]byte, 32)
_, err = rand.Read(data)
if err != nil {
LOG_ERROR("RSA_PRIVATE", "Failed to generate random data for testing the private key: %v", err)
return
}
// Now test if the private key matches the public key
encryptedData, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, config.rsaPublicKey, data, nil)
if err != nil {
LOG_ERROR("RSA_PRIVATE", "Failed to encrypt random data with the public key: %v", err)
return
}
decryptedData, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, key, encryptedData, nil)
if err != nil {
LOG_ERROR("RSA_PRIVATE", "Incorrect private key: %v", err)
return
}
if !bytes.Equal(data, decryptedData) {
LOG_ERROR("RSA_PRIVATE", "Decrypted data do not match the original data")
return
}
config.rsaPrivateKey = key
}