mirror of
https://github.com/jkl1337/duplicacy.git
synced 2026-01-02 19:54:54 -06:00
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:
@@ -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: " ",
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user