167 lines
6.4 KiB
C#
167 lines
6.4 KiB
C#
// Copyright (C) 2011, Kenneth Skovhede
|
|
|
|
// http://www.hexad.dk, opensource@hexad.dk
|
|
//
|
|
// This library is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as
|
|
// published by the Free Software Foundation; either version 2.1 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful, but
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using Newtonsoft.Json;
|
|
using System.Text;
|
|
using Duplicati.Library.Main.Database;
|
|
|
|
namespace Duplicati.Library.Main
|
|
{
|
|
public class Utility
|
|
{
|
|
/// <summary>
|
|
/// Implementation of the IMetahash interface
|
|
/// </summary>
|
|
private class Metahash : IMetahash
|
|
{
|
|
/// <summary>
|
|
/// The base64 encoded hash
|
|
/// </summary>
|
|
private readonly string m_hash;
|
|
/// <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;
|
|
var hasher = System.Security.Cryptography.HashAlgorithm.Create(options.BlockHashAlgorithm);
|
|
if (hasher == null)
|
|
throw new Exception(string.Format(Strings.Foresthash.InvalidHashAlgorithm, options.BlockHashAlgorithm));
|
|
if (!hasher.CanReuseTransform)
|
|
throw new Exception(string.Format(Strings.Foresthash.InvalidCryptoSystem, options.BlockHashAlgorithm));
|
|
|
|
using (var ms = new System.IO.MemoryStream())
|
|
using (var w = new StreamWriter(ms, Encoding.UTF8))
|
|
{
|
|
w.Write(JsonConvert.SerializeObject(values));
|
|
w.Flush();
|
|
|
|
m_blob = ms.ToArray();
|
|
|
|
ms.Position = 0;
|
|
m_hash = Convert.ToBase64String(hasher.ComputeHash(ms));
|
|
}
|
|
}
|
|
|
|
public string Hash
|
|
{
|
|
get { return m_hash; }
|
|
}
|
|
|
|
public long Size
|
|
{
|
|
get { return m_blob.Length; }
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
internal static void VerifyParameters(LocalDatabase db, Options options)
|
|
{
|
|
var newDict = new Dictionary<string, string>();
|
|
newDict.Add("blocksize", options.Blocksize.ToString());
|
|
newDict.Add("blockhash", options.BlockHashAlgorithm);
|
|
newDict.Add("filehash", options.FileHashAlgorithm);
|
|
var opts = db.GetDbOptions();
|
|
|
|
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)));
|
|
}
|
|
|
|
|
|
var needsUpdate = false;
|
|
foreach(var k in newDict)
|
|
if (!opts.ContainsKey(k.Key))
|
|
needsUpdate = true;
|
|
else if (opts[k.Key] != k.Value)
|
|
{
|
|
if (k.Key == "passphrase")
|
|
{
|
|
if (!options.AllowPassphraseChange)
|
|
{
|
|
if (newDict[k.Key] == "no-encryption")
|
|
throw new Exception("Unsupported removal of passphrase");
|
|
else if (opts[k.Key] == "no-encryption")
|
|
throw new Exception("Unsupported addition of passphrase");
|
|
else
|
|
throw new Exception("Unsupported change of passphrase");
|
|
}
|
|
}
|
|
else
|
|
throw new Exception(string.Format("Unsupported change of parameter \"{0}\" from \"{1}\" to \"{2}\"", k.Key, opts[k.Key], k.Value));
|
|
|
|
}
|
|
|
|
|
|
//Extra sanity check
|
|
if (db.GetBlocksLargerThan(options.Blocksize) > 0)
|
|
throw new Exception("Unsupported block-size change detected");
|
|
|
|
if (needsUpdate)
|
|
db.SetDbOptions(newDict);
|
|
}
|
|
}
|
|
}
|
|
|