Skip to content

Commit

Permalink
Merge pull request #75 from richardschneider/secure-string
Browse files Browse the repository at this point in the history
Use a Secure String
  • Loading branch information
richardschneider authored Apr 2, 2019
2 parents 4e72f2b + acdfeca commit fedcbfb
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 15 deletions.
21 changes: 13 additions & 8 deletions src/Cryptography/KeyChain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -86,17 +88,20 @@ FileStore<string, EncryptedKey> Store
/// Neither the <paramref name="passphrase"/> nor the DEK are stored.
/// </para>
/// </remarks>
public async Task SetPassphraseAsync (char[] passphrase, CancellationToken cancel = default(CancellationToken))
public async Task SetPassphraseAsync (SecureString passphrase, CancellationToken cancel = default(CancellationToken))
{
// TODO: Verify DEK options.
// TODO: get digest based on Options.Hash
var pdb = new Pkcs5S2ParametersGenerator(new Sha256Digest());
pdb.Init(
Encoding.UTF8.GetBytes(passphrase),
Encoding.UTF8.GetBytes(Options.Dek.Salt),
Options.Dek.IterationCount);
var key = (KeyParameter)pdb.GenerateDerivedMacParameters(Options.Dek.KeyLength * 8);
dek = key.GetKey().ToBase64NoPad().ToCharArray();
passphrase.UseSecretBytes(plain =>
{
var pdb = new Pkcs5S2ParametersGenerator(new Sha256Digest());
pdb.Init(
plain,
Encoding.UTF8.GetBytes(Options.Dek.Salt),
Options.Dek.IterationCount);
var key = (KeyParameter)pdb.GenerateDerivedMacParameters(Options.Dek.KeyLength * 8);
dek = key.GetKey().ToBase64NoPad().ToCharArray();
});

// Verify that that pass phrase is okay, by reading a key.
var akey = await Store.TryGetAsync("self", cancel);
Expand Down
37 changes: 37 additions & 0 deletions src/Cryptography/SecureStringExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Runtime.InteropServices;
using System.Security;
namespace Ipfs.Engine.Cryptography
{
/// <summary>
/// Extensions for a <see cref="SecureString"/>.
/// </summary>
public static class SecureStringExtensions
{
/// <summary>
/// Use the plain bytes of a <see cref="SecureString"/>.
/// </summary>
/// <param name="s">The secure string to access.</param>
/// <param name="action">
/// A function to call with the plain bytes.
/// </param>
public static void UseSecretBytes(this SecureString s, Action<byte[]> action)
{
var length = s.Length;
var p = SecureStringMarshal.SecureStringToGlobalAllocAnsi(s);
var plain = new byte[length];
try
{
Marshal.Copy(p, plain, 0, length);
action(plain);
}
finally
{
Marshal.ZeroFreeGlobalAllocAnsi(p);
p = IntPtr.Zero;
Array.Clear(plain, 0, length);
}
}

}
}
42 changes: 36 additions & 6 deletions src/IpfsEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@
using Nito.AsyncEx;
using Makaretu.Dns;
using System.Collections.Concurrent;
using System.Security;

namespace Ipfs.Engine
{
/// <summary>
/// Implements the <see cref="ICoreApi">Core API</see> which makes it possible to create a decentralised and distributed
/// application without relying on an "IPFS daemon".
/// Implements the <see cref="ICoreApi">Core API</see> which makes it possible to create a decentralised and distributed
/// application without relying on an "IPFS daemon".
/// </summary>
/// <remarks>
/// The engine should be used as a shared object in your program. It is thread safe (re-entrant) and conserves
Expand All @@ -32,16 +33,45 @@ public partial class IpfsEngine : ICoreApi, IService, IDisposable
static ILog log = LogManager.GetLogger(typeof(IpfsEngine));

KeyChain keyChain;
char[] passphrase;
SecureString passphrase;
ConcurrentBag<Func<Task>> stopTasks = new ConcurrentBag<Func<Task>>();

/// <summary>
/// Creates a new instance of the <see cref="IpfsEngine"/> class.
/// Creates a new instance of the <see cref="IpfsEngine"/> class
/// with the specified passphrase.
/// </summary>
/// <param name="passphrase">
/// The password used to access the keychain.
/// </param>
/// <remarks>
/// A <b>SecureString</b> copy of the passphrase is made so that the array can be
/// zeroed out after the call.
/// </remarks>
public IpfsEngine(char[] passphrase)
{
this.passphrase = new SecureString();
foreach (var c in passphrase)
{
this.passphrase.AppendChar(c);
}
Init();
}

/// <summary>
/// Creates a new instance of the <see cref="IpfsEngine"/> class
/// with the specified passphrase.
/// </summary>
/// <param name="passphrase">
/// The password used to access the keychain.
/// </param>
public IpfsEngine(SecureString passphrase)
{
this.passphrase = passphrase;
Init();
}

void Init()
{
// Init the core api inteface.
Bitswap = new BitswapApi(this);
Block = new BlockApi(this);
Expand Down Expand Up @@ -263,9 +293,9 @@ public async Task StartAsync()
await swarm.StartAsync();

var multicast = new MulticastService();
#pragma warning disable CS1998
#pragma warning disable CS1998
stopTasks.Add(async () => multicast.Dispose());
#pragma warning restore CS1998
#pragma warning restore CS1998

var tasks = new List<Func<Task>>
{
Expand Down
8 changes: 7 additions & 1 deletion src/IpfsEngine.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,13 @@
<PackageReference Include="Portable.BouncyCastle" Version="1.8.4" />
<PackageReference Include="protobuf-net" Version="2.4.0" />
</ItemGroup>


<ItemGroup Condition="'$(TargetFramework)' == 'netstandard14'">
<PackageReference Include="System.Security.SecureString">
<Version>4.3.0</Version>
</PackageReference>
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' != 'netstandard14'">
<PackageReference Include="SharpZipLib">
<Version>1.0.0</Version>
Expand Down
31 changes: 31 additions & 0 deletions test/Cryptography/SecureStringExtensionsTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security;
using System.Text;
using System.Threading.Tasks;

namespace Ipfs.Engine.Cryptography
{
[TestClass]
public class SecureStringExtensionsTest
{
[TestMethod]
public void UseBytes()
{
var secret = new SecureString();
var expected = new char[] { 'a', 'b', 'c' };
foreach (var c in expected) secret.AppendChar(c);
secret.UseSecretBytes(bytes =>
{
Assert.AreEqual(expected.Length, bytes.Length);
for (var i = 0; i < expected.Length; ++i)
Assert.AreEqual((int)expected[i], (int)bytes[i]);
});
}


}
}
16 changes: 16 additions & 0 deletions test/IpfsEngineTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Newtonsoft.Json.Linq;
using System;
using System.Linq;
using System.Security;
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -19,6 +20,21 @@ public void Can_Create()
Assert.IsNotNull(ipfs);
}

[TestMethod]
public async Task SecureString_Passphrase()
{
var secret = "this is not a secure pass phrase";
var ipfs = new IpfsEngine(secret.ToCharArray());
ipfs.Options = TestFixture.Ipfs.Options;
await ipfs.KeyChain();

var passphrase = new SecureString();
foreach (var c in secret) passphrase.AppendChar(c);
ipfs = new IpfsEngine(passphrase);
ipfs.Options = TestFixture.Ipfs.Options;
await ipfs.KeyChain();
}

[TestMethod]
public async Task Wrong_Passphrase()
{
Expand Down

0 comments on commit fedcbfb

Please sign in to comment.