2024-02-28 15:45:30 +01:00
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
2013-05-08 21:29:59 +02:00
using System ;
using System.Collections.Generic ;
using System.IO ;
2018-09-09 14:42:40 +02:00
using System.Security.Cryptography ;
2013-05-08 21:29:59 +02:00
using Newtonsoft.Json ;
using System.Text ;
using Duplicati.Library.Main.Database ;
2021-04-04 11:17:13 -07:00
using Duplicati.Library.Utility ;
2013-05-08 21:29:59 +02:00
namespace Duplicati.Library.Main
{
public class Utility
{
/// <summary>
/// Implementation of the IMetahash interface
/// </summary>
private class Metahash : IMetahash
{
/// <summary>
/// The base64 encoded hash
/// </summary>
2016-04-03 23:10:47 +02:00
private readonly string m_filehash ;
2013-05-08 21:29:59 +02:00
/// <summary>
/// The UTF-8 encoded json element with the metadata
/// </summary>
private readonly byte [] m_blob ;
/// <summary>
/// The lookup table with elements
/// </summary>
private readonly Dictionary < string , string > m_values ;
public Metahash ( Dictionary < string , string > values , Options options )
{
m_values = values ;
using ( var ms = new System . IO . MemoryStream ())
using ( var w = new StreamWriter ( ms , Encoding . UTF8 ))
2021-04-04 11:17:13 -07:00
using ( var filehasher = HashFactory . CreateHasher ( options . FileHashAlgorithm ))
2013-05-08 21:29:59 +02:00
{
2016-04-03 23:10:47 +02:00
if ( filehasher == null )
2018-03-12 14:07:11 +01:00
throw new Duplicati . Library . Interface . UserInformationException ( Strings . Common . InvalidHashAlgorithm ( options . FileHashAlgorithm ), "FileHashAlgorithmNotSupported" );
2016-04-03 23:10:47 +02:00
2013-05-08 21:29:59 +02:00
w . Write ( JsonConvert . SerializeObject ( values ));
w . Flush ();
m_blob = ms . ToArray ();
ms . Position = 0 ;
2016-04-03 23:10:47 +02:00
m_filehash = Convert . ToBase64String ( filehasher . ComputeHash ( ms ));
2013-05-08 21:29:59 +02:00
}
}
2016-04-03 23:10:47 +02:00
public string FileHash
2013-05-08 21:29:59 +02:00
{
2016-04-03 23:10:47 +02:00
get { return m_filehash ; }
2013-05-08 21:29:59 +02:00
}
2016-04-03 23:10:47 +02:00
2013-05-08 21:29:59 +02:00
public byte [] Blob
{
get { return m_blob ; }
}
public Dictionary < string , string > Values
{
get { return m_values ; }
}
}
/// <summary>
/// Constructs a container for a given metadata dictionary
/// </summary>
/// <param name="values">The metadata values to wrap</param>
/// <returns>A IMetahash instance</returns>
public static IMetahash WrapMetadata ( Dictionary < string , string > values , Options options )
{
return new Metahash ( values , options );
}
2015-04-08 20:33:30 +02:00
internal static void UpdateOptionsFromDb ( LocalDatabase db , Options options , System . Data . IDbTransaction transaction = null )
{
string n = null ;
var opts = db . GetDbOptions ( transaction );
if ( opts . ContainsKey ( "blocksize" ) && (! options . RawOptions . TryGetValue ( "blocksize" , out n ) || string . IsNullOrEmpty ( n )))
options . RawOptions [ "blocksize" ] = opts [ "blocksize" ] + "b" ;
if ( opts . ContainsKey ( "blockhash" ) && (! options . RawOptions . TryGetValue ( "block-hash-algorithm" , out n ) || string . IsNullOrEmpty ( n )))
options . RawOptions [ "block-hash-algorithm" ] = opts [ "blockhash" ];
if ( opts . ContainsKey ( "filehash" ) && (! options . RawOptions . TryGetValue ( "file-hash-algorithm" , out n ) || string . IsNullOrEmpty ( n )))
options . RawOptions [ "file-hash-algorithm" ] = opts [ "filehash" ];
}
internal static void VerifyParameters ( LocalDatabase db , Options options , System . Data . IDbTransaction transaction = null )
2013-05-08 21:29:59 +02:00
{
2018-11-20 20:08:39 +01:00
var newDict = new Dictionary < string , string >
{
{ "blocksize" , options . Blocksize . ToString () },
{ "blockhash" , options . BlockHashAlgorithm },
{ "filehash" , options . FileHashAlgorithm }
};
2015-04-08 20:33:30 +02:00
var opts = db . GetDbOptions ( transaction );
2013-08-17 22:05:40 +02:00
if ( options . NoEncryption )
{
newDict . Add ( "passphrase" , "no-encryption" );
}
else
{
string salt ;
opts . TryGetValue ( "passphrase-salt" , out salt );
if ( string . IsNullOrEmpty ( salt ))
{
// Not Crypto-class PRNG salts
var buf = new byte [ 32 ];
new Random (). NextBytes ( buf );
//Add version so we can detect and change the algorithm
salt = "v1:" + Library . Utility . Utility . ByteArrayAsHexString ( buf );
}
newDict [ "passphrase-salt" ] = salt ;
// We avoid storing the passphrase directly,
// instead we salt and rehash repeatedly
newDict . Add ( "passphrase" , Library . Utility . Utility . ByteArrayAsHexString ( Library . Utility . Utility . RepeatedHashWithSalt ( options . Passphrase , salt , 1200 )));
}
2013-05-08 21:29:59 +02:00
var needsUpdate = false ;
foreach ( var k in newDict )
if (! opts . ContainsKey ( k . Key ))
needsUpdate = true ;
else if ( opts [ k . Key ] != k . Value )
2013-08-24 21:17:12 +02:00
{
if ( k . Key == "passphrase" )
{
2013-10-29 11:43:44 +01:00
if (! options . AllowPassphraseChange )
{
if ( newDict [ k . Key ] == "no-encryption" )
2018-03-12 14:07:11 +01:00
throw new Duplicati . Library . Interface . UserInformationException ( "You have attempted to remove the passphrase on an existing backup, which is not supported. Please configure a new clean backup if you want to remove the passphrase." , "PassphraseRemovalNotSupported" );
2013-10-29 11:43:44 +01:00
else if ( opts [ k . Key ] == "no-encryption" )
2018-03-12 14:07:11 +01:00
throw new Duplicati . Library . Interface . UserInformationException ( "You have attempted to add a passphrase to an existing backup, which is not supported. Please configure a new clean backup if you want to add a passphrase." , "PassphraseAdditionNotSupported" );
2013-10-29 11:43:44 +01:00
else
2018-03-12 14:07:11 +01:00
throw new Duplicati . Library . Interface . UserInformationException ( "You have attempted to change a passphrase to an existing backup, which is not supported. Please configure a new clean backup if you want to change the passphrase." , "PassphraseChangeNotSupported" );
2013-10-29 11:43:44 +01:00
}
2013-08-24 21:17:12 +02:00
}
else
2018-03-12 14:07:11 +01:00
throw new Duplicati . Library . Interface . UserInformationException ( string . Format ( "You have attempted to change the parameter \"{0}\" from \"{1}\" to \"{2}\", which is not supported. Please configure a new clean backup if you want to change the parameter." , k . Key , opts [ k . Key ], k . Value ), "ParameterChangeNotSupported" );
2013-08-24 21:17:12 +02:00
}
2016-03-18 13:26:12 +01:00
2013-05-08 21:29:59 +02:00
//Extra sanity check
if ( db . GetBlocksLargerThan ( options . Blocksize ) > 0 )
2018-03-12 14:07:11 +01:00
throw new Duplicati . Library . Interface . UserInformationException ( "You have attempted to change the block-size on an existing backup, which is not supported. Please configure a new clean backup if you want to change the block-size." , "BlockSizeChangeNotSupported" );
2013-05-08 21:29:59 +02:00
if ( needsUpdate )
2016-03-18 13:26:12 +01:00
{
2017-11-05 13:32:01 -05:00
// Make sure we do not lose values
2016-03-18 13:26:12 +01:00
foreach ( var k in opts )
if (! newDict . ContainsKey ( k . Key ))
newDict [ k . Key ] = k . Value ;
2015-04-08 20:33:30 +02:00
db . SetDbOptions ( newDict , transaction );
2016-03-18 13:26:12 +01:00
}
2015-12-22 02:15:49 +01:00
}
2013-05-08 21:29:59 +02:00
}
}