Pārlūkot izejas kodu

Add support for writing and validating checksum files

Add libraries to read/write and sign/validate checksum files, and add command-line
utility to expose this functionality
Antony Male 10 gadi atpakaļ
vecāks
revīzija
4f7f22f465

+ 0 - 129
src/ChecksumCreator/Program.cs

@@ -1,129 +0,0 @@
-using Org.BouncyCastle.Bcpg;
-using Org.BouncyCastle.Bcpg.OpenPgp;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace ChecksumCreator
-{
-    // From the SignedFileProcessor sample, and http://stackoverflow.com/a/18796555/1086121
-    class Program
-    {
-        static void Main(string[] args)
-        {
-            //using (var key = File.OpenRead("secret_key.gpg"))
-            //using (var outputStream = File.OpenWrite("testfile2.txt.asc"))
-            //{
-            //    SignFile(File.ReadAllBytes("testfile2.txt"), outputStream, key, "password".ToCharArray());
-            //}
-
-            using (var key = File.OpenRead("certificate.asc"))
-            using (var input = File.OpenRead("testfile2.txt.asc"))
-            {
-                VerifyFile(input, key);
-            }
-        }
-
-        private static PgpSecretKey ReadSecretKey(Stream inputStream)
-        {
-            var decodedInputStream = PgpUtilities.GetDecoderStream(inputStream);
-            PgpSecretKeyRingBundle pgpSec = new PgpSecretKeyRingBundle(decodedInputStream);
-
-            // we just loop through the collection till we find a key suitable for encryption, in the real
-            // world you would probably want to be a bit smarter about this.
-            foreach (PgpSecretKeyRing keyRing in pgpSec.GetKeyRings())
-            {
-                foreach (PgpSecretKey key in keyRing.GetSecretKeys())
-                {
-                    if (key.IsSigningKey)
-                        return key;
-                }
-            }
-
-            throw new ArgumentException("Can't find signing key in key ring.");
-        }
-
-        private static void SignFile(byte[] input, Stream outputStream, Stream keyIn, char[] pass)
-        {
-            var secretKey = ReadSecretKey(keyIn);
-            var privateKey = secretKey.ExtractPrivateKey(pass);
-
-            PgpSignatureGenerator signatureGenerator = new PgpSignatureGenerator(secretKey.PublicKey.Algorithm, HashAlgorithmTag.Sha1);
-            PgpSignatureSubpacketGenerator subpacketGenerator = new PgpSignatureSubpacketGenerator();
-
-            signatureGenerator.InitSign(PgpSignature.CanonicalTextDocument, privateKey);
-            foreach (string userId in secretKey.PublicKey.GetUserIds())
-            {
-                PgpSignatureSubpacketGenerator spGen = new PgpSignatureSubpacketGenerator();
-                spGen.SetSignerUserId(false, userId);
-                signatureGenerator.SetHashedSubpackets(spGen.Generate());
-                // Just the first one!
-                break;
-            }
-
-            var armouredOutputStream = new ArmoredOutputStream(outputStream);
-
-            BcpgOutputStream bcpgOutputStream = new BcpgOutputStream(armouredOutputStream);
-
-            armouredOutputStream.BeginClearText(HashAlgorithmTag.Sha1);
-
-            signatureGenerator.Update(input);
-            bcpgOutputStream.Write(input);
-            bcpgOutputStream.Write((byte)'\n'); // For some reason this needs adding
-
-            armouredOutputStream.EndClearText();
-
-            signatureGenerator.Generate().Encode(bcpgOutputStream);
-        }
-
-        private static byte[] VerifyFile(Stream inputStream, Stream keyIn)
-        {
-            byte[] cleartext;
-            var armouredInputStream = new ArmoredInputStream(inputStream);
-
-            using (var cleartextStream = new MemoryStream())
-            {
-                int ch = 0;
-                while ((ch = armouredInputStream.ReadByte()) >= 0 && armouredInputStream.IsClearText())
-                {
-                    cleartextStream.WriteByte((byte)ch);
-                }
-
-                // Strip the trailing newline if set...
-                cleartextStream.Seek(-1, SeekOrigin.End);
-                if (cleartextStream.ReadByte() == '\n')
-                    cleartextStream.SetLength(cleartextStream.Length - 1);
-
-                cleartext = cleartextStream.ToArray();
-            }
-
-            using (var decoderStream = PgpUtilities.GetDecoderStream(inputStream))
-            {
-                PgpObjectFactory pgpObjectFactory = new PgpObjectFactory(decoderStream);
-
-                PgpSignatureList signatureList = (PgpSignatureList)pgpObjectFactory.NextPgpObject();
-                PgpSignature signature = signatureList[0];
-
-                PgpPublicKeyRingBundle publicKeyRing = new PgpPublicKeyRingBundle(PgpUtilities.GetDecoderStream(keyIn));
-                PgpPublicKey publicKey = publicKeyRing.GetPublicKey(signature.KeyId);
-
-                signature.InitVerify(publicKey);
-                signature.Update(cleartext);
-
-                if (signature.Verify())
-                {
-                    Console.WriteLine("signature verified.");
-                }
-                else
-                {
-                    Console.WriteLine("signature verification failed.");
-                }
-            }
-
-            return cleartext;
-        }
-    }
-}

+ 0 - 0
src/ChecksumCreator/App.config → src/ChecksumUtil/App.config


+ 11 - 5
src/ChecksumCreator/ChecksumCreator.csproj → src/ChecksumUtil/ChecksumUtil.csproj

@@ -4,11 +4,11 @@
   <PropertyGroup>
     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
     <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProjectGuid>{CFD4B44A-328E-4294-89A0-6A2962DEFB33}</ProjectGuid>
+    <ProjectGuid>{58EFC3AF-A52F-4492-A26A-D006890BE508}</ProjectGuid>
     <OutputType>Exe</OutputType>
     <AppDesignerFolder>Properties</AppDesignerFolder>
-    <RootNamespace>ChecksumCreator</RootNamespace>
-    <AssemblyName>ChecksumCreator</AssemblyName>
+    <RootNamespace>ChecksumUtil</RootNamespace>
+    <AssemblyName>ChecksumUtil</AssemblyName>
     <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
   </PropertyGroup>
@@ -17,7 +17,7 @@
     <DebugSymbols>true</DebugSymbols>
     <DebugType>full</DebugType>
     <Optimize>false</Optimize>
-    <OutputPath>bin\Debug\</OutputPath>
+    <OutputPath>..\..\bin\ChecksumUtil\Debug\</OutputPath>
     <DefineConstants>DEBUG;TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
@@ -26,7 +26,7 @@
     <PlatformTarget>AnyCPU</PlatformTarget>
     <DebugType>pdbonly</DebugType>
     <Optimize>true</Optimize>
-    <OutputPath>bin\Release\</OutputPath>
+    <OutputPath>..\..\bin\ChecksumUtil\Release\</OutputPath>
     <DefineConstants>TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
@@ -45,6 +45,12 @@
     <Reference Include="System.Xml" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="..\SyncTrayzor\Utils\ChecksumFileUtilities.cs">
+      <Link>ChecksumFileUtilities.cs</Link>
+    </Compile>
+    <Compile Include="..\SyncTrayzor\Utils\PgpClearsignUtilities.cs">
+      <Link>PgpClearsignUtilities.cs</Link>
+    </Compile>
     <Compile Include="Program.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>

+ 128 - 0
src/ChecksumUtil/Program.cs

@@ -0,0 +1,128 @@
+using SyncTrayzor.Utils;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ChecksumUtil
+{
+    class Program
+    {
+        public static void Main(string[] args)
+        {
+            if (args.Length < 1)
+            {
+                ShowHelp();
+                return;
+            }
+
+            try
+            {
+                var subcommand = args[0];
+                switch (subcommand)
+                {
+                    case "create":
+                        Create(args);
+                        break;
+
+                    case "verify":
+                        Verify(args);
+                        break;
+
+                    default:
+                        ShowHelp();
+                        return;
+                }
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine("Error: {0}", e.Message);
+                Environment.Exit(1);
+            }
+        }
+
+        private static void Create(string[] args)
+        {
+            if (args.Length < 5)
+            {
+                ShowHelp();
+                return;
+            }
+
+            var checksumFileName = args[1];
+            var privateKeyName = args[2];
+            var passphrase = args[3];
+            var inputFileNames = args.Skip(4).ToArray();
+
+            using (var checksumFileTemp = new MemoryStream())
+            {
+                using (var hashAlgorithm = new SHA1Managed())
+                {
+                    foreach (var inputFileName in inputFileNames)
+                    {
+                        using (var inputFile = File.OpenRead(inputFileName))
+                        {
+                            ChecksumFileUtilities.WriteChecksumToFile(hashAlgorithm, checksumFileTemp, Path.GetFileName(inputFileName), inputFile);
+                        }
+                    }
+                }
+
+                checksumFileTemp.Position = 0;
+
+                using (var checksumFile = File.Create(checksumFileName))
+                using (var privateKey = File.OpenRead(privateKeyName))
+                {
+                    PgpClearsignUtilities.SignFile(checksumFileTemp, checksumFile, privateKey, passphrase.ToCharArray());
+                }
+            }
+
+            Console.WriteLine("{0} created", checksumFileName);
+        }
+
+        private static void Verify(string[] args)
+        {
+            if (args.Length < 4)
+            {
+                ShowHelp();
+                return;
+            }
+
+            var checksumFileName = args[1];
+            var certificateFileName = args[2];
+            var inputFileNames = args.Skip(3).ToArray();
+
+            // Signature first, then hash
+            using (var checksumFile = File.OpenRead(checksumFileName))
+            using (var certificate = File.OpenRead(certificateFileName))
+            using (var cleartext = PgpClearsignUtilities.ReadAndVerifyFile(checksumFile, certificate))
+            {
+                if (cleartext == null)
+                    throw new Exception("Signature verification failed");
+
+                using (var hashAlgorithm = new SHA1Managed())
+                {
+                    foreach (var inputFileName in inputFileNames)
+                    {
+                        using (var inputFile = File.OpenRead(inputFileName))
+                        {
+                            var valid = ChecksumFileUtilities.ValidateChecksum(hashAlgorithm, cleartext, Path.GetFileName(inputFileName), inputFile);
+                            if (!valid)
+                                throw new Exception(String.Format("File {0} failed checksum", inputFileName));
+                        }
+                    }
+                }
+            }
+
+            Console.WriteLine("All files successfully verified");
+        }
+
+        private static void ShowHelp()
+        {
+            Console.WriteLine("Usage : ChecksumUtil.exe create checksumfile privatekey passphrase inputfile [inputfile ...]");
+            Console.WriteLine("        ChecksumUtil.exe verify checksumfile certificate inputfile [inputfile ...]");
+        }
+    }
+}

+ 5 - 5
src/ChecksumCreator/Properties/AssemblyInfo.cs → src/ChecksumUtil/Properties/AssemblyInfo.cs

@@ -5,11 +5,11 @@ using System.Runtime.InteropServices;
 // General Information about an assembly is controlled through the following 
 // set of attributes. Change these attribute values to modify the information
 // associated with an assembly.
-[assembly: AssemblyTitle("ChecksumCreator")]
+[assembly: AssemblyTitle("ChecksumUtil")]
 [assembly: AssemblyDescription("")]
 [assembly: AssemblyConfiguration("")]
 [assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("ChecksumCreator")]
+[assembly: AssemblyProduct("ChecksumUtil")]
 [assembly: AssemblyCopyright("Copyright ©  2015")]
 [assembly: AssemblyTrademark("")]
 [assembly: AssemblyCulture("")]
@@ -20,7 +20,7 @@ using System.Runtime.InteropServices;
 [assembly: ComVisible(false)]
 
 // The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("e54696bf-ab8f-4176-89a7-13f4d3e3072b")]
+[assembly: Guid("d796924f-d24e-4059-81e5-e450cb63dbd7")]
 
 // Version information for an assembly consists of the following four values:
 //
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
 // You can specify all the values or you can default the Build and Revision Numbers 
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: AssemblyVersion("1.0.22.0")]
+[assembly: AssemblyFileVersion("1.0.22.0")]

+ 0 - 0
src/ChecksumCreator/packages.config → src/ChecksumUtil/packages.config


+ 6 - 29
src/SyncTrayzor.sln

@@ -10,60 +10,37 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SyncTrayzor", "SyncTrayzor\
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProcessRunner", "ProcessRunner\ProcessRunner.csproj", "{692BB2F9-CD24-482F-B13A-4335F27F6EC2}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChecksumCreator", "ChecksumCreator\ChecksumCreator.csproj", "{CFD4B44A-328E-4294-89A0-6A2962DEFB33}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChecksumUtil", "ChecksumUtil\ChecksumUtil.csproj", "{58EFC3AF-A52F-4492-A26A-D006890BE508}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|Any CPU = Debug|Any CPU
-		Debug|Mixed Platforms = Debug|Mixed Platforms
 		Debug|x64 = Debug|x64
 		Debug|x86 = Debug|x86
-		Release|Any CPU = Release|Any CPU
-		Release|Mixed Platforms = Release|Mixed Platforms
 		Release|x64 = Release|x64
 		Release|x86 = Release|x86
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{D1F89B3D-7967-4DC6-AE45-50A7817FE54F}.Debug|Any CPU.ActiveCfg = Debug|x86
-		{D1F89B3D-7967-4DC6-AE45-50A7817FE54F}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
-		{D1F89B3D-7967-4DC6-AE45-50A7817FE54F}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{D1F89B3D-7967-4DC6-AE45-50A7817FE54F}.Debug|x64.ActiveCfg = Debug|x64
 		{D1F89B3D-7967-4DC6-AE45-50A7817FE54F}.Debug|x64.Build.0 = Debug|x64
 		{D1F89B3D-7967-4DC6-AE45-50A7817FE54F}.Debug|x86.ActiveCfg = Debug|x86
 		{D1F89B3D-7967-4DC6-AE45-50A7817FE54F}.Debug|x86.Build.0 = Debug|x86
-		{D1F89B3D-7967-4DC6-AE45-50A7817FE54F}.Release|Any CPU.ActiveCfg = Release|x86
-		{D1F89B3D-7967-4DC6-AE45-50A7817FE54F}.Release|Mixed Platforms.ActiveCfg = Release|x86
-		{D1F89B3D-7967-4DC6-AE45-50A7817FE54F}.Release|Mixed Platforms.Build.0 = Release|x86
 		{D1F89B3D-7967-4DC6-AE45-50A7817FE54F}.Release|x64.ActiveCfg = Release|x64
 		{D1F89B3D-7967-4DC6-AE45-50A7817FE54F}.Release|x64.Build.0 = Release|x64
 		{D1F89B3D-7967-4DC6-AE45-50A7817FE54F}.Release|x86.ActiveCfg = Release|x86
 		{D1F89B3D-7967-4DC6-AE45-50A7817FE54F}.Release|x86.Build.0 = Release|x86
-		{692BB2F9-CD24-482F-B13A-4335F27F6EC2}.Debug|Any CPU.ActiveCfg = Debug|x86
-		{692BB2F9-CD24-482F-B13A-4335F27F6EC2}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
-		{692BB2F9-CD24-482F-B13A-4335F27F6EC2}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{692BB2F9-CD24-482F-B13A-4335F27F6EC2}.Debug|x64.ActiveCfg = Debug|x64
 		{692BB2F9-CD24-482F-B13A-4335F27F6EC2}.Debug|x64.Build.0 = Debug|x64
 		{692BB2F9-CD24-482F-B13A-4335F27F6EC2}.Debug|x86.ActiveCfg = Debug|x86
 		{692BB2F9-CD24-482F-B13A-4335F27F6EC2}.Debug|x86.Build.0 = Debug|x86
-		{692BB2F9-CD24-482F-B13A-4335F27F6EC2}.Release|Any CPU.ActiveCfg = Release|x86
-		{692BB2F9-CD24-482F-B13A-4335F27F6EC2}.Release|Mixed Platforms.ActiveCfg = Release|x86
-		{692BB2F9-CD24-482F-B13A-4335F27F6EC2}.Release|Mixed Platforms.Build.0 = Release|x86
 		{692BB2F9-CD24-482F-B13A-4335F27F6EC2}.Release|x64.ActiveCfg = Release|x64
 		{692BB2F9-CD24-482F-B13A-4335F27F6EC2}.Release|x64.Build.0 = Release|x64
 		{692BB2F9-CD24-482F-B13A-4335F27F6EC2}.Release|x86.ActiveCfg = Release|x86
 		{692BB2F9-CD24-482F-B13A-4335F27F6EC2}.Release|x86.Build.0 = Release|x86
-		{CFD4B44A-328E-4294-89A0-6A2962DEFB33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{CFD4B44A-328E-4294-89A0-6A2962DEFB33}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{CFD4B44A-328E-4294-89A0-6A2962DEFB33}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
-		{CFD4B44A-328E-4294-89A0-6A2962DEFB33}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
-		{CFD4B44A-328E-4294-89A0-6A2962DEFB33}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{CFD4B44A-328E-4294-89A0-6A2962DEFB33}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{CFD4B44A-328E-4294-89A0-6A2962DEFB33}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{CFD4B44A-328E-4294-89A0-6A2962DEFB33}.Release|Any CPU.Build.0 = Release|Any CPU
-		{CFD4B44A-328E-4294-89A0-6A2962DEFB33}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
-		{CFD4B44A-328E-4294-89A0-6A2962DEFB33}.Release|Mixed Platforms.Build.0 = Release|Any CPU
-		{CFD4B44A-328E-4294-89A0-6A2962DEFB33}.Release|x64.ActiveCfg = Release|Any CPU
-		{CFD4B44A-328E-4294-89A0-6A2962DEFB33}.Release|x86.ActiveCfg = Release|Any CPU
+		{58EFC3AF-A52F-4492-A26A-D006890BE508}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{58EFC3AF-A52F-4492-A26A-D006890BE508}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{58EFC3AF-A52F-4492-A26A-D006890BE508}.Debug|x86.Build.0 = Debug|Any CPU
+		{58EFC3AF-A52F-4492-A26A-D006890BE508}.Release|x64.ActiveCfg = Release|Any CPU
+		{58EFC3AF-A52F-4492-A26A-D006890BE508}.Release|x86.ActiveCfg = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 6 - 0
src/SyncTrayzor/SyncTrayzor.csproj

@@ -78,6 +78,10 @@
     <Prefer32Bit>true</Prefer32Bit>
   </PropertyGroup>
   <ItemGroup>
+    <Reference Include="BouncyCastle.Crypto, Version=1.7.4137.9688, Culture=neutral, PublicKeyToken=a4292a325f69b123, processorArchitecture=MSIL">
+      <HintPath>..\packages\BouncyCastle.1.7.0\lib\Net40-Client\BouncyCastle.Crypto.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
     <Reference Include="FluentValidation">
       <HintPath>..\packages\FluentValidation.5.5.0.0\lib\Net40\FluentValidation.dll</HintPath>
     </Reference>
@@ -312,6 +316,7 @@
     <Compile Include="SyncThing\TransientWrapper.cs" />
     <Compile Include="Utils\AuthenticodeTools.cs" />
     <Compile Include="Utils\Buffer.cs" />
+    <Compile Include="Utils\ChecksumFileUtilities.cs" />
     <Compile Include="Utils\ChromiumWebBrowserExtensions.cs" />
     <Compile Include="Utils\EnumerableExtensions.cs" />
     <Compile Include="Utils\EnvVarTransformer.cs" />
@@ -320,6 +325,7 @@
     <Compile Include="Utils\LimitedConcurrencyTaskScheduler.cs" />
     <Compile Include="Utils\ObservableQueue.cs" />
     <Compile Include="Utils\PathEx.cs" />
+    <Compile Include="Utils\PgpClearsignUtilities.cs" />
     <Compile Include="Utils\SafeSyncthingExtensions.cs" />
     <Compile Include="Utils\SemaphoreSlimExtensions.cs" />
     <Compile Include="Utils\ShellTools.cs" />

+ 65 - 0
src/SyncTrayzor/Utils/ChecksumFileUtilities.cs

@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SyncTrayzor.Utils
+{
+    public static class ChecksumFileUtilities
+    {
+        private static string FormatHash(byte[] hash)
+        {
+            var hashFormatter = new StringBuilder(hash.Length * 2);
+            foreach (var b in hash)
+            {
+                hashFormatter.AppendFormat("{0:x2}", b);
+            }
+
+            return hashFormatter.ToString();
+        }
+
+        public static void WriteChecksumToFile(HashAlgorithm hashAlgorithm, Stream checksumFile, string filenameToChecksum, Stream fileToChecksum)
+        {
+            byte[] hash = hashAlgorithm.ComputeHash(fileToChecksum);
+            var formattedHash = FormatHash(hash);
+
+            using (var streamWriter = new StreamWriter(checksumFile, Encoding.ASCII, 256, true))
+            {
+                streamWriter.Write(formattedHash);
+                streamWriter.Write("  ");
+                streamWriter.Write(filenameToChecksum.Trim());
+                streamWriter.WriteLine();
+            }
+        }
+
+        public static bool ValidateChecksum(HashAlgorithm hashAlgorithm, Stream checksumFile, string filenameToCheck, Stream fileToCheck)
+        {
+            // Find the checksum...
+            string checksum = null;
+
+            using (var checksumFileReader = new StreamReader(checksumFile, Encoding.ASCII, false, 256, true))
+            {
+                while (checksum == null)
+                {
+                    var line = checksumFileReader.ReadLine().Trim();
+                    var parts = line.Split(new[] { ' ', '\t' }, 2, StringSplitOptions.RemoveEmptyEntries);
+                    if (parts.Length != 2)
+                        throw new ArgumentException("Invalid format of input file");
+                    if (parts[1] == filenameToCheck)
+                        checksum = parts[0];
+                }
+            }
+
+            if (checksum == null)
+                throw new ArgumentException(String.Format("Could not find checksum for file {0} in checksumFile", filenameToCheck));
+
+            byte[] hash = hashAlgorithm.ComputeHash(fileToCheck);
+            var formattedHash = FormatHash(hash);
+
+            return formattedHash == checksum;
+        }
+    }
+}

+ 121 - 0
src/SyncTrayzor/Utils/PgpClearsignUtilities.cs

@@ -0,0 +1,121 @@
+using Org.BouncyCastle.Bcpg;
+using Org.BouncyCastle.Bcpg.OpenPgp;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SyncTrayzor.Utils
+{
+    public static class PgpClearsignUtilities
+    {
+        private static PgpSecretKey ReadSecretKey(Stream inputStream)
+        {
+            var decodedInputStream = PgpUtilities.GetDecoderStream(inputStream);
+            var secretKeyRingBundle = new PgpSecretKeyRingBundle(decodedInputStream);
+
+            // we just loop through the collection till we find a key suitable for encryption, in the real
+            // world you would probably want to be a bit smarter about this.
+            foreach (PgpSecretKeyRing keyRing in secretKeyRingBundle.GetKeyRings())
+            {
+                foreach (PgpSecretKey key in keyRing.GetSecretKeys())
+                {
+                    if (key.IsSigningKey)
+                        return key;
+                }
+            }
+
+            throw new ArgumentException("Can't find signing key in key ring.");
+        }
+
+        public static void SignFile(Stream input, Stream outputStream, Stream keyIn, char[] pass)
+        {
+            var secretKey = ReadSecretKey(keyIn);
+            var privateKey = secretKey.ExtractPrivateKey(pass);
+
+            var signatureGenerator = new PgpSignatureGenerator(secretKey.PublicKey.Algorithm, HashAlgorithmTag.Sha1);
+            var subpacketGenerator = new PgpSignatureSubpacketGenerator();
+
+            signatureGenerator.InitSign(PgpSignature.CanonicalTextDocument, privateKey);
+            foreach (string userId in secretKey.PublicKey.GetUserIds())
+            {
+                var signatureSubpacketGenerator = new PgpSignatureSubpacketGenerator();
+                signatureSubpacketGenerator.SetSignerUserId(isCritical: false, userId: userId);
+                signatureGenerator.SetHashedSubpackets(signatureSubpacketGenerator.Generate());
+                // Just the first one!
+                break;
+            }
+
+            // Closing armouredOutputStream does not close the underlying stream
+            var armouredOutputStream = new ArmoredOutputStream(outputStream);
+            using (var bcpgOutputStream = new BcpgOutputStream(armouredOutputStream))
+            {
+                armouredOutputStream.BeginClearText(HashAlgorithmTag.Sha1);
+
+                int chr;
+                while ((chr = input.ReadByte()) > 0)
+                {
+                    signatureGenerator.Update((byte)chr);
+                    bcpgOutputStream.Write((byte)chr);
+                }
+
+                // For some reason we need to add a trailing newline
+                bcpgOutputStream.Write((byte)'\n'); 
+
+                armouredOutputStream.EndClearText();
+
+                signatureGenerator.Generate().Encode(bcpgOutputStream);
+            }
+        }
+
+        public static Stream ReadAndVerifyFile(Stream inputStream, Stream keyIn)
+        {
+            // Disposing this will close the underlying stream, which we don't want to do
+            var armouredInputStream = new ArmoredInputStream(inputStream);
+
+            // This stream is returned, so is not disposed
+            var cleartextStream = new MemoryStream();
+
+            int chr;
+
+            while ((chr = armouredInputStream.ReadByte()) >= 0 && armouredInputStream.IsClearText())
+            {
+                cleartextStream.WriteByte((byte)chr);
+            }
+
+            // Strip the trailing newline if set...
+            cleartextStream.Seek(-1, SeekOrigin.End);
+            if (cleartextStream.ReadByte() == '\n')
+                cleartextStream.SetLength(cleartextStream.Length - 1);
+
+            cleartextStream.Position = 0;
+
+            // This will either return inputStream, or a new ArmouredStream(inputStream)
+            // Either way, disposing it will close the underlying stream, which we don't want to do
+            var decoderStream = PgpUtilities.GetDecoderStream(inputStream);
+
+            var pgpObjectFactory = new PgpObjectFactory(decoderStream);
+
+            var signatureList = (PgpSignatureList)pgpObjectFactory.NextPgpObject();
+            var signature = signatureList[0];
+
+            var publicKeyRing = new PgpPublicKeyRingBundle(PgpUtilities.GetDecoderStream(keyIn));
+            var publicKey = publicKeyRing.GetPublicKey(signature.KeyId);
+
+            signature.InitVerify(publicKey);
+
+            while ((chr = cleartextStream.ReadByte()) > 0)
+            {
+                signature.Update((byte)chr);
+            }
+            cleartextStream.Position = 0;
+
+            if (signature.Verify())
+                return cleartextStream;
+            else
+                return null;
+        }
+    }
+}

+ 1 - 0
src/SyncTrayzor/packages.config

@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
+  <package id="BouncyCastle" version="1.7.0" targetFramework="net45" />
   <package id="cef.redist.x64" version="3.2062.1898" targetFramework="net45" />
   <package id="cef.redist.x86" version="3.2062.1898" targetFramework="net45" />
   <package id="CefSharp.Common" version="37.0.0" targetFramework="net45" />