Skip to content

Commit

Permalink
ProtectedPrivateKey.WriteJson() method
Browse files Browse the repository at this point in the history
  • Loading branch information
dahlia committed Oct 30, 2019
1 parent 6d8a13d commit eed97be
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 40 deletions.
117 changes: 78 additions & 39 deletions Libplanet.Tests/KeyStore/ProtectedPrivateKeyTest.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Collections.Immutable;
using System.IO;
using System.Text;
using Libplanet.Crypto;
using Libplanet.KeyStore;
using Libplanet.KeyStore.Ciphers;
Expand All @@ -12,58 +14,79 @@ namespace Libplanet.Tests.KeyStore
{
public class ProtectedPrivateKeyTest
{
[Fact]
public void Unprotect()
public const string PassphraseFixture = "asdf";

public static readonly byte[] CiphertextFixture =
{
byte[] ciphertext =
{
0x53, 0x5b, 0xab, 0x75, 0xc1, 0x12, 0xfe, 0x32, 0x7d, 0xc2, 0xe2,
0x93, 0xf8, 0x02, 0x97, 0xff, 0x33, 0x9e, 0x1e, 0x3c, 0xb7, 0x67,
0x49, 0x61, 0x53, 0x13, 0xcd, 0xc2, 0xaa, 0xa3, 0xb3, 0x7a,
};
0x53, 0x5b, 0xab, 0x75, 0xc1, 0x12, 0xfe, 0x32, 0x7d, 0xc2, 0xe2,
0x93, 0xf8, 0x02, 0x97, 0xff, 0x33, 0x9e, 0x1e, 0x3c, 0xb7, 0x67,
0x49, 0x61, 0x53, 0x13, 0xcd, 0xc2, 0xaa, 0xa3, 0xb3, 0x7a,
};

ImmutableArray<byte> iv = new byte[]
{
0xbc, 0x7f, 0x2c, 0xa2, 0x3b, 0xfe, 0xe0, 0xdd,
0x97, 0x25, 0x22, 0x8a, 0xb2, 0xb0, 0xd9, 0x8a,
}.ToImmutableArray();
public static readonly ImmutableArray<byte> IvFixture = new byte[]
{
0xbc, 0x7f, 0x2c, 0xa2, 0x3b, 0xfe, 0xe0, 0xdd,
0x97, 0x25, 0x22, 0x8a, 0xb2, 0xb0, 0xd9, 0x8a,
}.ToImmutableArray();

byte[] salt =
{
0x3e, 0xea, 0xaf, 0x35, 0xda, 0x70, 0x92, 0x83, 0x87, 0xca, 0xe1,
0xea, 0xd3, 0x1e, 0xd7, 0x82, 0xb1, 0x13, 0x5d, 0x75, 0x78, 0xa8,
0x9d, 0x95, 0xe3, 0x0c, 0xc9, 0x14, 0x01, 0x0b, 0xa2, 0xed,
};
public static readonly byte[] SaltFixture =
{
0x3e, 0xea, 0xaf, 0x35, 0xda, 0x70, 0x92, 0x83, 0x87, 0xca, 0xe1,
0xea, 0xd3, 0x1e, 0xd7, 0x82, 0xb1, 0x13, 0x5d, 0x75, 0x78, 0xa8,
0x9d, 0x95, 0xe3, 0x0c, 0xc9, 0x14, 0x01, 0x0b, 0xa2, 0xed,
};

byte[] mac =
{
0xd8, 0x6a, 0xb9, 0xef, 0xf2, 0x3f, 0x28, 0x21, 0x0d, 0xc0, 0x10,
0x2a, 0x23, 0x64, 0x98, 0xe5, 0xc9, 0x68, 0x88, 0xbe, 0x6b, 0x5c,
0xd6, 0xf3, 0x09, 0x81, 0xaa, 0x89, 0x6b, 0xe8, 0x42, 0xd1,
};

var kdf = new Pbkdf2<Sha256Digest>(10240, salt, 32);
var cipher = new Aes128Ctr(iv);
var address = new Address("d80d933db45cc0cf69e9632090f8aaff635dc8e5");
var ppk = new ProtectedPrivateKey(address, kdf, mac, cipher, ciphertext);

Assert.Equal(address, ppk.Address);
Assert.Equal(address, ppk.Unprotect("asdf").PublicKey.ToAddress());
public static readonly byte[] MacFixture =
{
0xd8, 0x6a, 0xb9, 0xef, 0xf2, 0x3f, 0x28, 0x21, 0x0d, 0xc0, 0x10,
0x2a, 0x23, 0x64, 0x98, 0xe5, 0xc9, 0x68, 0x88, 0xbe, 0x6b, 0x5c,
0xd6, 0xf3, 0x09, 0x81, 0xaa, 0x89, 0x6b, 0xe8, 0x42, 0xd1,
};

public static readonly Address AddressFixture =
new Address("d80d933db45cc0cf69e9632090f8aaff635dc8e5");

public static readonly IKdf KdfFixture = new Pbkdf2<Sha256Digest>(10240, SaltFixture, 32);

public static readonly ICipher CipherFixture = new Aes128Ctr(IvFixture);

public static readonly ProtectedPrivateKey Fixture = new ProtectedPrivateKey(
AddressFixture,
KdfFixture,
MacFixture,
CipherFixture,
CiphertextFixture
);

[Fact]
public void Unprotect()
{
Assert.Equal(AddressFixture, Fixture.Address);
Assert.Equal(
AddressFixture,
Fixture.Unprotect(PassphraseFixture).PublicKey.ToAddress()
);
var incorrectPassphraseException = Assert.Throws<IncorrectPassphraseException>(
() => ppk.Unprotect("wrong passphrase")
() => Fixture.Unprotect("wrong passphrase")
);
TestUtils.AssertBytesEqual(
mac.ToImmutableArray(),
MacFixture.ToImmutableArray(),
incorrectPassphraseException.ExpectedMac
);
Assert.NotEqual(mac, incorrectPassphraseException.InputMac);
Assert.NotEqual(MacFixture, incorrectPassphraseException.InputMac);

var invalidPpk = new ProtectedPrivateKey(default, kdf, mac, cipher, ciphertext);
var invalidPpk = new ProtectedPrivateKey(
default,
KdfFixture,
MacFixture,
CipherFixture,
CiphertextFixture
);
var mismatchedAddressException = Assert.Throws<MismatchedAddressException>(
() => invalidPpk.Unprotect("asdf")
() => invalidPpk.Unprotect(PassphraseFixture)
);
Assert.Equal(default(Address), mismatchedAddressException.ExpectedAddress);
Assert.Equal(address, mismatchedAddressException.ActualAddress);
Assert.Equal(AddressFixture, mismatchedAddressException.ActualAddress);
}

[Fact]
Expand Down Expand Up @@ -656,5 +679,21 @@ public void FromJsonInvalidCases()
);
}
#pragma warning restore MEN003

[Fact]
public void WriteJson()
{
string json;
using (var stream = new MemoryStream())
{
Fixture.WriteJson(stream);
json = Encoding.UTF8.GetString(stream.ToArray());
}

// TODO: More decent tests should be written.
ProtectedPrivateKey key = ProtectedPrivateKey.FromJson(json);
Assert.Equal(AddressFixture, key.Address);
Assert.Equal(AddressFixture, key.Unprotect(PassphraseFixture).PublicKey.ToAddress());
}
}
}
10 changes: 10 additions & 0 deletions Libplanet/KeyStore/Ciphers/Aes128Ctr.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace Libplanet.KeyStore.Ciphers
/// <summary>
/// AES-128-CTR (AES 128-bit in counter moder).
/// </summary>
[Pure]
public sealed class Aes128Ctr : ICipher
{
/// <summary>
Expand Down Expand Up @@ -66,6 +67,15 @@ in ImmutableArray<byte> ciphertext
) =>
Cipher(false, key, ciphertext);

/// <inheritdoc />
public string WriteJson(Utf8JsonWriter writer)
{
writer.WriteStartObject();
writer.WriteString("iv", ByteUtil.Hex(Iv));
writer.WriteEndObject();
return "aes-128-ctr";
}

internal static ICipher FromJson(in JsonElement paramsElement)
{
if (!paramsElement.TryGetProperty("iv", out JsonElement ivElement))
Expand Down
11 changes: 11 additions & 0 deletions Libplanet/KeyStore/Ciphers/ICipher.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Immutable;
using System.Diagnostics.Contracts;
using System.Text.Json;

namespace Libplanet.KeyStore.Ciphers
{
Expand Down Expand Up @@ -33,5 +34,15 @@ ImmutableArray<byte> Decrypt(
in ImmutableArray<byte> key,
in ImmutableArray<byte> ciphertext
);

/// <summary>
/// Dumps the cipher parameters as a JSON representation according to Ethereum's
/// <a href="https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition">Web3
/// Secret Storage Definition</a>.
/// </summary>
/// <param name="writer">A JSON writer which has not begun object nor array.</param>
/// <returns>A unique identifier of the cipher algorithm. This is going to be the
/// <c>crypto.cipher</c> field in the key JSON file.</returns>
string WriteJson(Utf8JsonWriter writer);
}
}
9 changes: 9 additions & 0 deletions Libplanet/KeyStore/Kdfs/IKdf.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Immutable;
using System.Diagnostics.Contracts;
using System.Text.Json;

namespace Libplanet.KeyStore.Kdfs
{
Expand All @@ -17,5 +18,13 @@ public interface IKdf
/// <returns>A derived cryptographic key.</returns>
[Pure]
ImmutableArray<byte> Derive(string passphrase);

/// <summary>
/// Dumps the KDF parameters as a JSON representation.
/// </summary>
/// <param name="writer">A JSON writer which has not begun object nor array.</param>
/// <returns>A unique identifier of the KDF. This is going to be the
/// <c>crypto.kdf</c> field in the key JSON file.</returns>
string WriteJson(Utf8JsonWriter writer);
}
}
16 changes: 16 additions & 0 deletions Libplanet/KeyStore/Kdfs/Pbkdf2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace Libplanet.KeyStore.Kdfs
"Microsoft.StyleCop.CSharp.ReadabilityRules",
"SA1402",
Justification = "There are just generic and non-generic verions of the same name classes.")]
[Pure]
public sealed class Pbkdf2<T> : IKdf
where T : GeneralDigest, new()
{
Expand Down Expand Up @@ -79,6 +80,21 @@ public ImmutableArray<byte> Derive(string passphrase)
var key = (KeyParameter)pdb.GenerateDerivedMacParameters(KeyLength * 8);
return ImmutableArray.Create(key.GetKey(), 0, KeyLength);
}

/// <inheritdoc/>
public string WriteJson(Utf8JsonWriter writer)
{
writer.WriteStartObject();
writer.WriteNumber("c", Iterations);
writer.WriteNumber("dklen", KeyLength);
writer.WriteString(
"prf",
"hmac-" + new T().AlgorithmName.ToLower().Replace("-", string.Empty)
);
writer.WriteString("salt", ByteUtil.Hex(Salt));
writer.WriteEndObject();
return "pbkdf2";
}
}

internal static class Pbkdf2
Expand Down
48 changes: 47 additions & 1 deletion Libplanet/KeyStore/ProtectedPrivateKey.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Immutable;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text.Json;
Expand Down Expand Up @@ -122,7 +123,9 @@ public static ProtectedPrivateKey Protect(PrivateKey privateKey, string passphra
}

/// <summary>
/// Loads a <see cref="ProtectedPrivateKey"/> from a JSON.
/// Loads a <see cref="ProtectedPrivateKey"/> from a JSON, according to Ethereum's
/// <a href="https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition">Web3
/// Secret Storage Definition</a>.
/// </summary>
/// <param name="json">A JSON string that encodes a <see cref="ProtectedPrivateKey"/>.
/// </param>
Expand Down Expand Up @@ -331,6 +334,49 @@ public PrivateKey Unprotect(string passphrase)
return key;
}

/// <summary>
/// Dumps the cipher parameters as a JSON representation according to Ethereum's
/// <a href="https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition">Web3
/// Secret Storage Definition</a>.
/// </summary>
/// <param name="writer">A JSON writer which has not begun object nor array.</param>
/// <param name="id">A unique identifier, which goes to the <c>id</c> field in the key JSON
/// file. If <c>null</c> (which is default) it is random-generated.</param>
public void WriteJson(Utf8JsonWriter writer, [Pure] in Guid? id = null)
{
writer.WriteStartObject();
writer.WriteNumber("version", 3);
writer.WriteString("id", (id ?? Guid.NewGuid()).ToString().ToLower());
writer.WriteString("address", Address.ToHex().ToLower());
writer.WriteStartObject("crypto");
writer.WriteString("ciphertext", ByteUtil.Hex(Ciphertext));
writer.WritePropertyName("cipherparams");
string cipherName = Cipher.WriteJson(writer);
writer.WriteString("cipher", cipherName);
writer.WritePropertyName("kdfparams");
string kdfName = Kdf.WriteJson(writer);
writer.WriteString("kdf", kdfName);
writer.WriteString("mac", ByteUtil.Hex(Mac));
writer.WriteEndObject();
writer.WriteEndObject();
}

/// <summary>
/// Dumps the cipher parameters as a JSON representation according to Ethereum's
/// <a href="https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition">Web3
/// Secret Storage Definition</a>.
/// </summary>
/// <param name="stream">The destination for writing JSON text.</param>
/// <param name="id">A unique identifier, which goes to the <c>id</c> field in the key JSON
/// file. If <c>null</c> (which is default) it is random-generated.</param>
public void WriteJson(Stream stream, [Pure] in Guid? id = null)
{
using (var writer = new Utf8JsonWriter(stream))
{
WriteJson(writer, id);
}
}

private static ImmutableArray<byte> MakeEncryptionKey(ImmutableArray<byte> derivedKey)
{
const int keySubBytes = 16;
Expand Down

0 comments on commit eed97be

Please sign in to comment.