Skip to content

Commit

Permalink
Adding functionality to generate self-signed X509 certificates for fi…
Browse files Browse the repository at this point in the history
…le encryption.

Other fixes related to file encryption UI.
  • Loading branch information
HighEncryption committed Jan 3, 2018
1 parent 9ef68f9 commit f6ed76b
Show file tree
Hide file tree
Showing 15 changed files with 334 additions and 29 deletions.
83 changes: 83 additions & 0 deletions SyncPro.Certificates/CertificateHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
namespace SyncPro.Certificates
{
using System;
using System.Security.Cryptography.X509Certificates;

using CERTENROLLLib;

public static class CertificateHelper
{
public static X509Certificate2 CreateSelfSignedCertificate(string subjectName)
{
var distinguishedName = new CX500DistinguishedName();
distinguishedName.Encode(
"CN=" + subjectName,
X500NameFlags.XCN_CERT_NAME_STR_NONE);

CCspInformations objCSPs = new CCspInformations();
CCspInformation objCSP = new CCspInformation();

objCSP.InitializeFromName(
"Microsoft Enhanced RSA and AES Cryptographic Provider");

objCSPs.Add(objCSP);

// Build the private key
CX509PrivateKey privateKey = new CX509PrivateKey();

privateKey.MachineContext = false;
privateKey.Length = 2048;
privateKey.CspInformations = objCSPs;
privateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE;
privateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_ALL_USAGES;
privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;

// Create the private key in the CSP's protected storage
privateKey.Create();

// Build the algorithm identifier
var hashobj = new CObjectId();
hashobj.InitializeFromAlgorithmName(
ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
AlgorithmFlags.AlgorithmFlagsNone,
"SHA256");

// Create the self-signing request from the private key
var certificateRequest = new CX509CertificateRequestCertificate();
certificateRequest.InitializeFromPrivateKey(
X509CertificateEnrollmentContext.ContextUser,
privateKey,
string.Empty);

certificateRequest.Subject = distinguishedName;
certificateRequest.Issuer = distinguishedName;
certificateRequest.NotBefore = DateTime.Now.AddDays(-1);
certificateRequest.NotAfter = DateTime.Now.AddYears(100);
certificateRequest.HashAlgorithm = hashobj;

certificateRequest.Encode();

var enrollment = new CX509Enrollment();

// Load the certificate request
enrollment.InitializeFromRequest(certificateRequest);
enrollment.CertificateFriendlyName = subjectName;

// Output the request in base64 and install it back as the response
string csr = enrollment.CreateRequest();

// Install the response
enrollment.InstallResponse(
InstallResponseRestrictionFlags.AllowUntrustedCertificate,
csr,
EncodingType.XCN_CRYPT_STRING_BASE64,
string.Empty);

// Get the new certificate without the private key
byte[] certificateData = Convert.FromBase64String(enrollment.Certificate);

return new X509Certificate2(certificateData);
}
}
}
36 changes: 36 additions & 0 deletions SyncPro.Certificates/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
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("SyncPro.Certificates")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SyncPro.Certificates")]
[assembly: AssemblyCopyright("Copyright © 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("11febc64-ee05-4095-bd49-9fc7fabcc1df")]

// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// 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")]
65 changes: 65 additions & 0 deletions SyncPro.Certificates/SyncPro.Certificates.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{11FEBC64-EE05-4095-BD49-9FC7FABCC1DF}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SyncPro.Certificates</RootNamespace>
<AssemblyName>SyncPro.Certificates</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="CertificateHelper.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<COMReference Include="CERTENROLLLib">
<Guid>{728AB348-217D-11DA-B2A4-000E7BBB2B09}</Guid>
<VersionMajor>1</VersionMajor>
<VersionMinor>0</VersionMinor>
<Lcid>0</Lcid>
<WrapperTool>tlbimp</WrapperTool>
<Isolated>False</Isolated>
<EmbedInteropTypes>True</EmbedInteropTypes>
</COMReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
9 changes: 8 additions & 1 deletion SyncPro.Core/Configuration/TriggerConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,16 @@ public class TriggerConfiguration
public int HourlyMinutesPastSyncTime { get; set; }
}

public enum EncryptionMode
{
None = 0,
Encrypt = 1,
Decrypt = 2
}

public class EncryptionConfiguration
{
public bool IsEnabled { get; set; }
public EncryptionMode Mode { get; set; }

public string CertificateThumbprint { get; set; }
}
Expand Down
9 changes: 3 additions & 6 deletions SyncPro.Core/Runtime/EncryptionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@ namespace SyncPro.Runtime
using System.Security.Cryptography.X509Certificates;

using SyncPro.Adapters;

public enum EncryptionMode
{
Encrypt,
Decrypt
}
using SyncPro.Configuration;

public class EncryptionManager : IDisposable
{
Expand Down Expand Up @@ -54,6 +49,8 @@ public EncryptionManager(
Pre.ThrowIfArgumentNull(encryptionCertificate, nameof(encryptionCertificate));
Pre.ThrowIfArgumentNull(outputStream, nameof(outputStream));

Pre.ThrowIfTrue(mode == EncryptionMode.None, "Encryption mode cannot be None");

this.encryptionCertificate = encryptionCertificate;
this.Mode = mode;
this.sourceFileSize = sourceFileSize;
Expand Down
35 changes: 27 additions & 8 deletions SyncPro.Core/Runtime/SyncRelationship.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;

using SyncPro.Adapters;
using SyncPro.Certificates;
using SyncPro.Configuration;
using SyncPro.Data;
using SyncPro.Tracing;
Expand Down Expand Up @@ -100,7 +102,7 @@ private SyncRelationship(RelationshipConfiguration configuration)
this.ThrottlingValue = configuration.ThrottlingConfiguration.Value;
this.ThrottlingScaleFactor = configuration.ThrottlingConfiguration.ScaleFactor;

this.EncryptionIsEnabled = configuration.EncryptionConfiguration.IsEnabled;
this.EncryptionMode = configuration.EncryptionConfiguration.Mode;
this.EncryptionCertificateThumbprint = configuration.EncryptionConfiguration.CertificateThumbprint;

this.State = SyncRelationshipState.NotInitialized;
Expand Down Expand Up @@ -137,8 +139,8 @@ public async Task SaveAsync()
this.Configuration.TriggerConfiguration.HourlyMinutesPastSyncTime = this.TriggerHourlyMinutesPastSyncTime;
this.Configuration.TriggerConfiguration.ScheduleInterval = this.TriggerScheduleInterval;

this.Configuration.EncryptionConfiguration.IsEnabled = this.EncryptionIsEnabled;
this.Configuration.EncryptionConfiguration.CertificateThumbprint = this.EncryptionCertificateThumbprint;
// Set the encryption mode for adapters that may need it. Other encryption configuration is set below.
this.Configuration.EncryptionConfiguration.Mode = this.EncryptionMode;

// If the relaionship contains adapters that arent in the configuration, add them
foreach (AdapterConfiguration adapterConfig in this.Adapters.Select(a => a.Configuration))
Expand Down Expand Up @@ -208,9 +210,20 @@ public async Task SaveAsync()
adapterBase.SaveConfiguration();
}

// Set the creation time of the adapter
// Check if we are creating this relationship for the first time
if (this.Configuration.InitiallyCreatedUtc == DateTime.MinValue)
{
// Create the encryption certificate if needed
if (this.EncryptionMode == EncryptionMode.Encrypt && this.EncryptionCreateCertificate)
{
string subjectName = "SyncProEncryption " + this.Configuration.RelationshipId.ToString("D").ToLowerInvariant();

X509Certificate2 encryptionCert = CertificateHelper.CreateSelfSignedCertificate(subjectName);

this.EncryptionCertificateThumbprint = encryptionCert.Thumbprint;
this.Configuration.EncryptionConfiguration.CertificateThumbprint = encryptionCert.Thumbprint;
}

this.Configuration.InitiallyCreatedUtc = DateTime.UtcNow;
}

Expand Down Expand Up @@ -409,14 +422,20 @@ public TriggerScheduleInterval TriggerScheduleInterval
#region Encryption Properties

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private bool encryptionIsEnabled;
private EncryptionMode encryptionMode;

public bool EncryptionIsEnabled
public EncryptionMode EncryptionMode
{
get { return this.encryptionIsEnabled; }
set { this.SetProperty(ref this.encryptionIsEnabled, value); }
get { return this.encryptionMode; }
set { this.SetProperty(ref this.encryptionMode, value); }
}

/// <summary>
/// Indicates whether the certificate should be created when the relationship is saved
/// for the first time.
/// </summary>
public bool EncryptionCreateCertificate { get; set; }

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string encryptionCertificateThumbprint;

Expand Down
14 changes: 11 additions & 3 deletions SyncPro.Core/Runtime/SyncRun.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.Threading.Tasks;

using SyncPro.Adapters;
using SyncPro.Configuration;
using SyncPro.Data;
using SyncPro.Tracing;

Expand Down Expand Up @@ -335,7 +336,7 @@ private async Task SyncInternalAsync()
{ "AnalyzeResultId", this.AnalyzeResult.Id },
});

if (this.relationship.EncryptionIsEnabled)
if (this.relationship.EncryptionMode != Configuration.EncryptionMode.None)
{
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
Expand Down Expand Up @@ -887,18 +888,25 @@ private async Task CopyFileAsync(
{
long writeStreamLength = updateInfo.Entry.SourceSize;

if (this.relationship.EncryptionIsEnabled)
if (this.relationship.EncryptionMode == EncryptionMode.Encrypt)
{
short padding;
writeStreamLength = EncryptionManager.CalculateEncryptedFileSize(
updateInfo.Entry.SourceSize,
out padding);
}
else if (this.relationship.EncryptionMode == EncryptionMode.Decrypt)
{
short padding;
writeStreamLength = EncryptionManager.CalculateDecryptedFileSize(
updateInfo.Entry.SourceSize,
out padding);
}

fromStream = fromAdapter.GetReadStreamForEntry(updateInfo.Entry);
toStream = toAdapter.GetWriteStreamForEntry(updateInfo.Entry, writeStreamLength);

if (this.relationship.EncryptionIsEnabled)
if (this.relationship.EncryptionMode != EncryptionMode.None)
{
// Create a copy of the certificate from the original cert's handle. A unique copy is required
// because the encryption manager will dispose of the RSA CSP derived from the cert, and will
Expand Down
4 changes: 4 additions & 0 deletions SyncPro.Core/SyncPro.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@
<None Include="SyncPro.ruleset" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SyncPro.Certificates\SyncPro.Certificates.csproj">
<Project>{11febc64-ee05-4095-bd49-9fc7fabcc1df}</Project>
<Name>SyncPro.Certificates</Name>
</ProjectReference>
<ProjectReference Include="..\SyncPro.Tracing\SyncPro.Tracing.csproj">
<Project>{CE6A7780-BC06-4818-B15F-8DAD91032A71}</Project>
<Name>SyncPro.Tracing</Name>
Expand Down
3 changes: 2 additions & 1 deletion SyncPro.UI/Controls/EncryptionSettingsDialog.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
Text="Files can be encrypted/decrypted when synchronized. Select the way that encryption should be performed below. Encryption cannot be enabled or disabled once a relationship is created." />
</StackPanel>

<Grid Grid.Row="1" VerticalAlignment="Stretch" Margin="24,0">
<Grid Grid.Row="1" VerticalAlignment="Stretch" Margin="24,0"
IsEnabled="{Binding Path=IsCreateMode}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="8" />
Expand Down
13 changes: 12 additions & 1 deletion SyncPro.UI/RelationshipEditor/Sections/SyncOptionsSection.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,18 @@
</Button>

<TextBlock FontStyle="Oblique"
Text="{Binding Path=EncryptedSettingsStatus}" />
Text="{Binding Path=EncryptedSettingsStatus}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=EncryptedSettingsStatusImportant}" Value="true">
<Setter Property="Foreground" Value="{StaticResource Common.Focus.Border}" />
<Setter Property="FontWeight" Value="Bold" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</Grid>
</StackPanel>
Expand Down
Loading

0 comments on commit f6ed76b

Please sign in to comment.