Skip to content

Commit

Permalink
Add unit test to verify that decryption is possible
Browse files Browse the repository at this point in the history
  • Loading branch information
ermshiperete committed Jun 10, 2024
1 parent 016c276 commit 33fbe1a
Show file tree
Hide file tree
Showing 4 changed files with 306 additions and 68 deletions.
4 changes: 2 additions & 2 deletions CREATE_RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ This document describes how to create a new release.

### DigiTally

- Compress all files from `output/Release/DigiTally/net472` into a `DigitaleBriefwahl.
DigiTally-1.4.0.zip` file.
- Compress all files from `output/Release/DigiTally/net472` into a
`DigitaleBriefwahl.DigiTally-1.4.0.zip` file.

### MacOS

Expand Down
66 changes: 0 additions & 66 deletions DigitaleBriefwahlTests/EncryptVoteTests.cs

This file was deleted.

93 changes: 93 additions & 0 deletions DigitaleBriefwahlTests/Encryption/DecryptHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System.IO;
using System.Linq;
using Org.BouncyCastle.Bcpg.OpenPgp;
using Org.BouncyCastle.Utilities.IO;

namespace DigitaleBriefwahlTests.Encryption
{
public static class DecryptHelper
{
private static PgpPrivateKey GetPrivateKey(string privateKeyPath, char[] passPhrase)
{
using Stream keyIn = File.OpenRead(privateKeyPath);
using Stream inputStream = PgpUtilities.GetDecoderStream(keyIn);
PgpSecretKeyRingBundle secretKeyRingBundle = new PgpSecretKeyRingBundle(inputStream);

PgpSecretKey key = null;
foreach (PgpSecretKeyRing kRing in secretKeyRingBundle.GetKeyRings())
{
foreach (PgpSecretKey secretKey in kRing.GetSecretKeys())
{
return secretKey.ExtractPrivateKey(passPhrase);
}
}

return null;
}

public static string DecryptFile(string inputFile, string privateKeyFile)
{
using var stream = File.OpenRead(inputFile);
var outputFile = Path.GetTempFileName();
if (DecryptFile(stream, outputFile, privateKeyFile))
{
var content = File.ReadAllText(outputFile);
File.Delete(outputFile);
return content;
}

File.Delete(outputFile);
return null;
}

private static bool DecryptFile(Stream inputStream, string outputFile,
string privateKeyLoc)
{
PgpEncryptedDataList enc;

using var newStream = PgpUtilities.GetDecoderStream(inputStream);
var pgpObjF = new PgpObjectFactory(newStream);
if (pgpObjF.NextPgpObject() is PgpEncryptedDataList list)
{
enc = list;
}
else
{
enc = (PgpEncryptedDataList)pgpObjF.NextPgpObject();
}

var privKey = GetPrivateKey(privateKeyLoc, null);

var pbe = enc.GetEncryptedDataObjects().Cast<PgpPublicKeyEncryptedData>().First();

using var clear = pbe.GetDataStream(privKey);
using var memoryStream = new MemoryStream();
clear.CopyTo(memoryStream);
var plainBytes = memoryStream.ToArray();
var plainFact = new PgpObjectFactory(plainBytes);
var message = plainFact.NextPgpObject();
if (message is PgpCompressedData cData)
{
using var compDataIn = cData.GetDataStream();
var o = new PgpObjectFactory(compDataIn);
message = o.NextPgpObject();
if (message is PgpOnePassSignatureList)
{
message = o.NextPgpObject();
}

PgpLiteralData literalData = null;
literalData = (PgpLiteralData)message;
if (literalData != null)
{
using var output = File.Create(outputFile);
using var unc = literalData.GetInputStream();
Streams.PipeAll(unc, output);
return true;
}
}

return false;
}
}
}
211 changes: 211 additions & 0 deletions DigitaleBriefwahlTests/Encryption/EncryptVoteTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
// Copyright (c) 2021 Eberhard Beilharz
// This software is licensed under the GNU General Public License version 3
// (https://opensource.org/licenses/GPL-3.0)

using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using DigitaleBriefwahl;
using DigitaleBriefwahl.Encryption;
using DigitaleBriefwahl.Model;
using NUnit.Framework;
using Org.BouncyCastle.Bcpg;
using Org.BouncyCastle.Utilities.Encoders;
using Org.BouncyCastle.Utilities.IO;

namespace DigitaleBriefwahlTests.Encryption
{
[TestFixture]
public class EncryptVoteTests
{
private readonly List<string> _filesToDelete = new List<string>();

[TearDown]
public void TearDown()
{
foreach (var file in _filesToDelete)
{
try
{
if (Directory.Exists(file))
{
foreach (var f in Directory.GetFiles(file))
{
File.Delete(f);
}

Directory.Delete(file);
}
else
{
File.Delete(file);
}
}
// ReSharper disable once EmptyGeneralCatchClause
catch
{
}
}
_filesToDelete.Clear();
}

[Test]
[Repeat(10)]
public void BallotFilePath_HasConstantLength()
{
var sut = new EncryptVote("foo");
var ballotFilePath = sut.BallotFilePath;
var tempPath = Path.GetTempPath();
tempPath = tempPath.Replace(@"\", @"\\");
Assert.That(ballotFilePath, Does.Match($"{tempPath}foo_[0-9A-F]{{32}}.txt"));
Assert.That(ballotFilePath.Length, Is.EqualTo(Path.GetTempPath().Length + 4 /* foo_ */ + 32 + 4 /* .txt */));
}

[Test]
public void BallotFilePath_NumberMatchesBallotId()
{
var ballotId = BallotHelper.BallotId;
var expectedNumber = ballotId.Replace("-", "");

var sut = new EncryptVote("foo");
Assert.That(sut.BallotFilePath, Is.EqualTo($"{Path.GetTempPath()}foo_{expectedNumber}.txt"));
}

[Test]
public void GetEncryptedFilePath()
{
var sut = new EncryptVote("foo");
Assert.That(sut.GetEncryptedFilePath(sut.BallotFilePath),
Is.EqualTo(sut.BallotFilePath.Substring(0, sut.BallotFilePath.Length - 4) + ".gpg"));
}

[Test]
public void GetEncryptedFilePath_Umlaut_AllASCII()
{
var sut= new EncryptVote("ÄÖÜäöüß");
var filePath = Path.GetFileName(sut.GetEncryptedFilePath(sut.BallotFilePath));
Assert.That(filePath.StartsWith("AEOEUEaeoeuess"), Is.True,
$"Unexpected FilePath.\n Expected: AEOEUEaeoeuess...\n But was: {filePath}");
}

[Test]
public void GetEncryptedFilePath_Other_AllASCII()
{
var sut= new EncryptVote("ខ្មែរ");
var filePath = sut.GetEncryptedFilePath(sut.BallotFilePath);
foreach (var c in filePath.ToCharArray())
{
Assert.That(c, Is.LessThanOrEqualTo(128),
$"FilePath contains non-ASCII characters: {filePath}");
}
}

#region Test Keys

private const string KeyHandle = "484AE369B6634B18";
private const string PublicKey = @"-----BEGIN PGP PUBLIC KEY BLOCK-----
mI0EZmc63wEEANYnSkt/s1S7Jxn9WdN/oZlDRw+NRcFb6DrTg9qil+IBoQ56Sj/s
HJmhOunZcNSQv3l1KzZ+Uy+JIOXCed0FGgoipc0TJUjCZa8I7v/Q30LPtlotSr3g
DjoFz9edDQ6vSEUiKaNrBMyqyBu76o66s8abfa36QxWK7CFnum8ztR6JABEBAAG0
DURpZ2l3YWhsIFRlc3SIzgQTAQoAOBYhBKGXuzRDmCQ0KgxA4EhK42m2Y0sYBQJm
ZzrfAhsDBQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAAAoJEEhK42m2Y0sYbJID/1JW
5khjkcszWej1BnGfubdKL/5quIfzZ0V6b/UrTmWwSFNnlvXhFvGEyfptm5GuwXTF
TQqyoWlVTI0StWxTbhYECLDUdGtreKsNIDCNSxomDb8fB7wZx6V+n34Mmcls1DRy
Y7v3UJG7pvdOhOYiP5VrsTS/5sJ3/f1ZGQOwWHaduI0EZmc63wEEAMyV9mBNWfDc
L5xCZAIIHCr/aSkFswn2/hwfI5DXR2GpBJsaF5GD9mFZwmfHHRmHKy32mRIfJdH3
oEhZKSI74+wRTR5GpAlWzQRNV1vHJJYPF9tliDFl1DTHxLeWMkiW1J8GbZth94zP
7JRkk7YTswDMdeT/eAThj3Y+FK6VgMsBABEBAAGItgQYAQoAIBYhBKGXuzRDmCQ0
KgxA4EhK42m2Y0sYBQJmZzrfAhsMAAoJEEhK42m2Y0sYvtgD/23SSlQ0C835jCBf
JRhm6fwSnCk4V9KZUg5uIk8b+8P7FsiJpdl4GwaPK8bwYS3yNE/ilBTBR7s8eHWI
8TOZFChiOJZXU0UByL9MlSX6kos/kAEu8tVzcw2nwr9PAC7ILT6EcvLuPCqQ7j01
PZ+GJ7xGnHB/W53Im+DIMxdon6+l
=Iqyo
-----END PGP PUBLIC KEY BLOCK-----
";

private const string PrivateKeyData = @"lQHYBGZnOt8BBADWJ0pLf7NUuycZ/VnTf6GZQ0cPjUXBW+g604PaopfiAaEOeko/7ByZoTrp2XDU
kL95dSs2flMviSDlwnndBRoKIqXNEyVIwmWvCO7/0N9Cz7ZaLUq94A46Bc/XnQ0Or0hFIimjawTM
qsgbu+qOurPGm32t+kMViuwhZ7pvM7UeiQARAQABAAP+IAvLG1clr8U9ya7W1UZhGT0vEg2y5Ydq
Tfs+3OW90q0pu6/jEoOzl+2/kiTetIBglr1I1SO8MP3VNWyPrdpIlftdNvo3b4F/P+To43pgk2+9
Dk7RP885AG+9auDGZNiqAxJgm40RlnleV2EDOYBlb/yNHWecqkAGr/F+p/oWnC0CANdfdtbMT4bm
PCdHA7xa+wmfP+ZbtzLQFuy16SvCgJMH9aybZijO2Opuq3bfQ8RE4pTNWN2USlp8gnn5TjuO+h8C
AP6M8FCynwun7PBOXDaoueQzgJt4KLrZBRXeRKHdAeET3M0XDRsGezSEH/cVGCytr72RxBzPvYTU
zMnR5rZaIlcB/2yDh4SNjvUYpecuLtEvcMMsCU40QEDBmrpZ+2xwsm6ohuSrI87o6YV13vbZoAZq
qFxWFglgG9w+cFFK+setVZamsrQNRGlnaXdhaGwgVGVzdIjOBBMBCgA4FiEEoZe7NEOYJDQqDEDg
SErjabZjSxgFAmZnOt8CGwMFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AACgkQSErjabZjSxhskgP/
UlbmSGORyzNZ6PUGcZ+5t0ov/mq4h/NnRXpv9StOZbBIU2eW9eEW8YTJ+m2bka7BdMVNCrKhaVVM
jRK1bFNuFgQIsNR0a2t4qw0gMI1LGiYNvx8HvBnHpX6ffgyZyWzUNHJju/dQkbum906E5iI/lWux
NL/mwnf9/VkZA7BYdp2dAdgEZmc63wEEAMyV9mBNWfDcL5xCZAIIHCr/aSkFswn2/hwfI5DXR2Gp
BJsaF5GD9mFZwmfHHRmHKy32mRIfJdH3oEhZKSI74+wRTR5GpAlWzQRNV1vHJJYPF9tliDFl1DTH
xLeWMkiW1J8GbZth94zP7JRkk7YTswDMdeT/eAThj3Y+FK6VgMsBABEBAAEAA/0YNwPDYERuAtHL
z/vgVa9n03VR+q+3VTm8gF8ITMLRJ105Mnxpp9HoseQVvnaOR/ZvaUmy4GbslD+vcIYy3B3c4+1Z
iYsGgJMr9m02aFCXzpHLFVvb0t+hZ7RFpEav0J5Zmp23br/SUoby+QhKp5cW8xTte7jtawTKWj2j
DBz0wQIA2cM4nJ1ia/tJp+jNuFEni64OWm3J0OmL4zWWUB9o0fUPT5tRzyjB4Z+thMaRLp2IPGiz
yhkisQbDnmNyNHyoaQIA8IJsWcdJK6WWSnA15BZ0pXwpaYZqZU/m7SaANCR+kRZFqGOHkdeijq7v
hX93eIwY0SLsFHQh60m3gdNRYHR62QIA49xtsTi3bj1MXe00fhN6dm2pbXaVJdZrzKSVuZkMgBqP
u7r9CBhSzYE6jtp27v4nfBBq6uECgq7qIe0RnTugNqXtiLYEGAEKACAWIQShl7s0Q5gkNCoMQOBI
SuNptmNLGAUCZmc63wIbDAAKCRBISuNptmNLGL7YA/9t0kpUNAvN+YwgXyUYZun8EpwpOFfSmVIO
biJPG/vD+xbIiaXZeBsGjyvG8GEt8jRP4pQUwUe7PHh1iPEzmRQoYjiWV1NFAci/TJUl+pKLP5AB
LvLVc3MNp8K/TwAuyC0+hHLy7jwqkO49NT2fhie8Rpxwf1udyJvgyDMXaJ+vpQ==
";
#endregion

[Test]
public void EncryptVote_CanDecrypt()
{
// Setup
var privateKeyFile = Path.GetTempFileName();
_filesToDelete.Add(privateKeyFile);
var privateKey = Base64.Decode(PrivateKeyData);
File.WriteAllBytes(privateKeyFile, privateKey);

var tempFolder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(tempFolder);
_filesToDelete.Add(tempFolder);

var publicKey = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
$"{KeyHandle}.asc");
File.WriteAllText(publicKey, PublicKey);

var wahlini = Path.Combine(tempFolder, "wahl.ini");
File.WriteAllText(wahlini, @$"[Wahlen]
Titel=Test
PublicKey={KeyHandle}.asc
Email=nobody@example.com");

var configuration = Configuration.Configure(wahlini);

var vote = "Vereinswahlen 2024\r\n" +
"==================\r\n" +
"\r\n" +
"Geschäftsführer\r\n" +
"---------------\r\n" +
"(J=Ja, E=Enthaltung, N=Nein)\r\n" +
"1. [J] Siegfried Siegreich\r\n" +
"\r\n" +
"Vorstand\r\n" +
"--------\r\n" +
"(1 Stimme; Wahl mit 1. kennzeichnen)\r\n" +
" Max Mayer\r\n" +
"1. Anton Anders\r\n" +
" Gustav Graf\r\n" +
"\r\n" +
"\r\n" +
$"{BallotHelper.BallotId}\r\n";

var voteFile = Path.Combine(tempFolder,
$"Test_{BallotHelper.BallotId.Replace("-", "")}.txt");

// Execute
var filename = new EncryptVote("Test").WriteVote(vote, voteFile);
_filesToDelete.Add(filename);

// Verify
Assert.That(filename, Is.EqualTo(Path.Combine(tempFolder, Path.GetFileNameWithoutExtension
(voteFile) + ".gpg")));
Assert.That(DecryptHelper.DecryptFile(filename, privateKeyFile), Is.EqualTo(vote));
}
}
}

0 comments on commit 33fbe1a

Please sign in to comment.