Change spec deterministic
This commit is contained in:
parent
a812f6fdd2
commit
339d0852d5
32
README.md
32
README.md
@ -71,7 +71,7 @@ using var ctx = await PCSCContext.WaitForCard();
|
||||
var ntag = ctx.CreateNTag424();
|
||||
|
||||
var uri = await ntag.TryReadNDefURI();
|
||||
var piccData = PICCData.TryBoltcardDecrypt(encryptionKey, authenticationKey, uri);
|
||||
var piccData = PICCData.TryBoltcardDecryptCheck(encryptionKey, authenticationKey, uri);
|
||||
if (piccData == null)
|
||||
throw new SecurityException("Impossible to decrypt or validate");
|
||||
|
||||
@ -112,28 +112,36 @@ await ntag.SetupBoltcard(lnurlwService, BoltcardKeys.Default, keys);
|
||||
|
||||
Here is an example of how to setup a card with deterministic keys, and decrypt the PICCData.
|
||||
```csharp
|
||||
using BTCPayServer.NTag424;
|
||||
using BTCPayServer.NTag424.PCSC;
|
||||
using System;
|
||||
using System.Security;
|
||||
using System.Collections;
|
||||
|
||||
using var ctx = await PCSCContext.WaitForCard();
|
||||
var ntag = ctx.CreateNTag424();
|
||||
var issuerKey = new IssuerKey("00000000000000000000000000000001".HexToBytes());
|
||||
|
||||
// First time authenticate is with the default 00.000 key
|
||||
await ntag.AuthenticateEV2First(0, AESKey.Default);
|
||||
var uid = await ntag.GetCardUID();
|
||||
|
||||
var issuerKey = new AESKey("00000000000000000000000000000001".HexToBytes());
|
||||
var batchKeys = new DeterministicBatchKeys(issuerKey, BatchId: 0);
|
||||
//var nonce = IssuerKey.RandomNonce();
|
||||
var nonce = new byte[16]; // Please use IssuerKey.RandomNonce() in production
|
||||
|
||||
// SaveNonce should be implemented by the server
|
||||
await SaveNonce(issuerKey.GetId(uid), nonce);
|
||||
|
||||
var keys = issuerKey.DeriveBoltcardKeys(uid, nonce);
|
||||
await ntag.SetupBoltcard("lnurlw://blahblah.com", BoltcardKeys.Default, keys);
|
||||
|
||||
var uri = await ntag.TryReadNDefURI();
|
||||
var piccData = PICCData.TryDeterministicBoltcardDecrypt(batchKeys, uri, uid);
|
||||
var piccData = issuerKey.TryDecrypt(uri);
|
||||
if (piccData == null)
|
||||
throw new SecurityException("Impossible to decrypt with batchKeys");
|
||||
// If this method didn't throw an exception, it has been successfully decrypted and authenticated.
|
||||
|
||||
// You can reset the card with `await ntag.ResetCard(batchKeys);`.
|
||||
// In real life, you would fetch the nonce from database
|
||||
// var nonce = await FetchNonce(issuerKey.GetId(piccData.Uid));
|
||||
|
||||
if (!issuerKey.CheckSunMac(uri, piccData, nonce))
|
||||
throw new SecurityException("Impossible to check the SUN MAC");
|
||||
|
||||
// If this method didn't throw an exception, it has been successfully decrypted and authenticated.
|
||||
// You can reset the card with `await ntag.ResetCard(issuerKey, nonce);`.
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Version>1.0.11</Version>
|
||||
<Version>1.0.12</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@ -3,6 +3,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.RegularExpressions;
|
||||
using static BTCPayServer.NTag424.Helpers;
|
||||
|
||||
namespace BTCPayServer.NTag424;
|
||||
@ -66,6 +67,8 @@ public class AESKey
|
||||
}
|
||||
public bool CheckSunMac(string mac, PICCData piccData, byte[]? payload = null)
|
||||
{
|
||||
if (mac is null || !Regex.IsMatch(mac, "[a-f0-9A-F]{16}"))
|
||||
return false;
|
||||
return CheckSunMac(mac.HexToBytes(), piccData, payload);
|
||||
}
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>10.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<Version>1.0.11</Version>
|
||||
<Version>1.0.12</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@ -1,53 +0,0 @@
|
||||
using System.Linq;
|
||||
using static BTCPayServer.NTag424.Helpers;
|
||||
|
||||
namespace BTCPayServer.NTag424;
|
||||
public record DeterministicBatchKeys(AESKey IssuerKey, uint BatchId = 0)
|
||||
{
|
||||
public AESKey DeriveEncryptionKey()
|
||||
{
|
||||
return IssuerKey.Derive(Helpers.Concat(
|
||||
new byte[] { 0x2d, 0x00, 0x3f, 0x77 },
|
||||
UIntToBytesLE(BatchId)));
|
||||
}
|
||||
public AESKey DeriveAuthenticationKey(byte[] uid)
|
||||
{
|
||||
return IssuerKey.Derive(Helpers.Concat(
|
||||
new byte[] { 0x2d, 0x00, 0x3f, 0x78 },
|
||||
UIntToBytesLE(BatchId),
|
||||
uid));
|
||||
}
|
||||
|
||||
public BoltcardKeys DeriveBoltcardKeys(byte[] uid)
|
||||
{
|
||||
var appMasterKey = IssuerKey.Derive(Helpers.Concat(
|
||||
new byte[] { 0x2d, 0x00, 0x3f, 0x76 },
|
||||
UIntToBytesLE(BatchId),
|
||||
uid));
|
||||
var encryptionKey = DeriveEncryptionKey();
|
||||
var authKey = DeriveAuthenticationKey(uid);
|
||||
var k1 = IssuerKey.Derive(Helpers.Concat(
|
||||
new byte[] { 0x2d, 0x00, 0x3f, 0x79 },
|
||||
UIntToBytesLE(BatchId),
|
||||
uid));
|
||||
var k2 = IssuerKey.Derive(Helpers.Concat(
|
||||
new byte[] { 0x2d, 0x00, 0x3f, 0x7a },
|
||||
UIntToBytesLE(BatchId),
|
||||
uid));
|
||||
|
||||
return new BoltcardKeys(appMasterKey, encryptionKey, authKey, k1, k2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the ID from the UID and the encryption key (K1)
|
||||
/// </summary>
|
||||
/// <param name="uid">The UID</param>
|
||||
/// <returns>The ID</returns>
|
||||
public byte[] GetId(byte[] uid)
|
||||
{
|
||||
return IssuerKey.Derive(Helpers.Concat(
|
||||
new byte[] { 0x2d, 0x00, 0x3f, 0x7b },
|
||||
UIntToBytesLE(BatchId),
|
||||
uid)).ToBytes().Take(7).ToArray();
|
||||
}
|
||||
}
|
||||
121
src/BTCPayServer.NTag424/IssuerKey.cs
Normal file
121
src/BTCPayServer.NTag424/IssuerKey.cs
Normal file
@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.RegularExpressions;
|
||||
using static BTCPayServer.NTag424.Helpers;
|
||||
|
||||
namespace BTCPayServer.NTag424;
|
||||
|
||||
/// <summary>
|
||||
/// Implement deterministic key derivation. <see cref="https://github.com/boltcard/boltcard/blob/main/docs/DETERMINISTIC.md"/>
|
||||
/// </summary>
|
||||
/// <param name="AESKey"></param>
|
||||
public record IssuerKey(AESKey AESKey)
|
||||
{
|
||||
public IssuerKey(ReadOnlySpan<byte> bytes) : this(new AESKey(bytes))
|
||||
{
|
||||
|
||||
}
|
||||
public static byte[] RandomNonce()
|
||||
{
|
||||
var nonce = new byte[16];
|
||||
RandomNumberGenerator.Fill(nonce);
|
||||
return nonce;
|
||||
}
|
||||
public AESKey DeriveEncryptionKey()
|
||||
{
|
||||
return AESKey.Derive(Helpers.Concat(
|
||||
new byte[] { 0x2d, 0x00, 0x3f, 0x77 }));
|
||||
}
|
||||
private void Validate(byte[] uid)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(uid);
|
||||
if (uid.Length != 7)
|
||||
throw new ArgumentException("uid should be 7 bytes", nameof(uid));
|
||||
}
|
||||
private void Validate(byte[] uid, byte[] nonce)
|
||||
{
|
||||
Validate(uid);
|
||||
ArgumentNullException.ThrowIfNull(nonce);
|
||||
if (nonce.Length != 16)
|
||||
throw new ArgumentException("nonce should be 16 bytes", nameof(nonce));
|
||||
}
|
||||
public AESKey DeriveAuthenticationKey(byte[] uid, byte[] nonce)
|
||||
{
|
||||
Validate(uid, nonce);
|
||||
return AESKey.Derive(Helpers.Concat(
|
||||
new byte[] { 0x2d, 0x00, 0x3f, 0x78 },
|
||||
uid,
|
||||
nonce));
|
||||
}
|
||||
|
||||
public BoltcardKeys DeriveBoltcardKeys(byte[] uid, byte[] nonce)
|
||||
{
|
||||
Validate(uid, nonce);
|
||||
var appMasterKey = AESKey.Derive(Helpers.Concat(
|
||||
new byte[] { 0x2d, 0x00, 0x3f, 0x76 },
|
||||
uid,
|
||||
nonce));
|
||||
var encryptionKey = DeriveEncryptionKey();
|
||||
var authKey = DeriveAuthenticationKey(uid, nonce);
|
||||
var k1 = AESKey.Derive(Helpers.Concat(
|
||||
new byte[] { 0x2d, 0x00, 0x3f, 0x79 },
|
||||
uid,
|
||||
nonce));
|
||||
var k2 = AESKey.Derive(Helpers.Concat(
|
||||
new byte[] { 0x2d, 0x00, 0x3f, 0x7a },
|
||||
uid,
|
||||
nonce));
|
||||
|
||||
return new BoltcardKeys(appMasterKey, encryptionKey, authKey, k1, k2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the ID from the UID
|
||||
/// </summary>
|
||||
/// <param name="uid">The UID</param>
|
||||
/// <returns>The ID</returns>
|
||||
public byte[] GetId(byte[] uid)
|
||||
{
|
||||
Validate(uid);
|
||||
return AESKey.Derive(Helpers.Concat(
|
||||
new byte[] { 0x2d, 0x00, 0x3f, 0x7b },
|
||||
uid)).ToBytes().ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the PICCData from the BoltCard and check the checksum.
|
||||
/// </summary>
|
||||
/// <param name="uri">The url with p= and c= parameters</param
|
||||
/// <param name="payload">Optional payload committed by c</param>
|
||||
/// <returns>The PICCData if the checksum passed verification or null.</returns>
|
||||
public BoltcardPICCData? TryDecrypt(Uri? uri, byte[]? payload = null)
|
||||
{
|
||||
return BoltcardPICCData.TryDecrypt(DeriveEncryptionKey(), uri, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the PICCData from the Boltcard and check the checksum.
|
||||
/// </summary>
|
||||
/// <param name="encryptionKey">The encryption key (K1)</param>
|
||||
/// <param name="p">p= encrypted PICCData parameter</param>
|
||||
/// <param name="c">c= checksum parameter</param>
|
||||
/// <param name="payload">Optional payload committed by c</param>
|
||||
/// <returns>The PICCData if the checksum passed verification or null.</returns>
|
||||
public BoltcardPICCData? TryDecrypt(string p, string c, byte[]? payload = null)
|
||||
{
|
||||
return BoltcardPICCData.TryDecrypt(DeriveEncryptionKey(), p, c, payload);
|
||||
}
|
||||
|
||||
public bool CheckSunMac([NotNullWhen(true)] Uri? uri, BoltcardPICCData piccData, byte[] nonce)
|
||||
{
|
||||
if (!PICCData.ExtractPC(uri, out _, out var c))
|
||||
return false;
|
||||
return this.DeriveAuthenticationKey(piccData.Uid, nonce).CheckSunMac(c, piccData);
|
||||
}
|
||||
public bool CheckSunMac([NotNullWhen(true)] string? c, BoltcardPICCData piccData, byte[] nonce)
|
||||
{
|
||||
return this.DeriveAuthenticationKey(piccData.Uid, nonce).CheckSunMac(c, piccData);
|
||||
}
|
||||
}
|
||||
@ -395,16 +395,17 @@ public class Ntag424
|
||||
/// Reset the card to factory settings using current application keys using deterministic keys
|
||||
/// </summary>
|
||||
/// <param name="issuerKey">The issuer key</param>
|
||||
/// <param name="nonce"></param>
|
||||
/// <returns></returns>
|
||||
public async Task ResetCard(DeterministicBatchKeys batchKeys)
|
||||
public async Task ResetCard(IssuerKey issuerKey, byte[] nonce)
|
||||
{
|
||||
var encryptionKey = batchKeys.DeriveEncryptionKey();
|
||||
var encryptionKey = issuerKey.DeriveEncryptionKey();
|
||||
if (CurrentSession is null)
|
||||
await AuthenticateEV2First(1, encryptionKey);
|
||||
if (encryptionKey != CurrentSession!.Key)
|
||||
await AuthenticateEV2NonFirst(1, encryptionKey);
|
||||
var uid = await GetCardUID();
|
||||
var keys = batchKeys.DeriveBoltcardKeys(uid);
|
||||
var keys = issuerKey.DeriveBoltcardKeys(uid, nonce);
|
||||
await ResetCard(keys);
|
||||
}
|
||||
|
||||
|
||||
@ -20,6 +20,49 @@ public record BoltcardPICCData : PICCData
|
||||
public BoltcardPICCData(PICCData piccData) : this(piccData.Uid!, piccData.Counter!.Value)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the PICCData from the BoltCard and check the checksum.
|
||||
/// </summary>
|
||||
/// <param name="encryptionKey">The encryption key (K1)</param>
|
||||
/// <param name="uri">The url with p= and c= parameters</param
|
||||
/// <param name="payload">Optional payload committed by c</param>
|
||||
/// <returns>The PICCData if the checksum passed verification or null.</returns>
|
||||
public static BoltcardPICCData? TryDecrypt(AESKey encryptionKey, Uri? uri, byte[]? payload = null)
|
||||
{
|
||||
if (!ExtractPC(uri, out var p, out var c))
|
||||
return null;
|
||||
|
||||
return TryDecrypt(encryptionKey, p, c, payload);
|
||||
}
|
||||
|
||||
// PICCData for boltcard starts with 0xc7, and end with 5 bytes of 0
|
||||
static bool ValidateBoltcardPICCData(byte[] piccData)
|
||||
{
|
||||
if (piccData is null || piccData.Length != 16)
|
||||
return false;
|
||||
return piccData[0] == 0xc7;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the PICCData from the Boltcard and check the checksum.
|
||||
/// </summary>
|
||||
/// <param name="encryptionKey">The encryption key (K1)</param>
|
||||
/// <param name="p">p= encrypted PICCData parameter</param>
|
||||
/// <param name="c">c= checksum parameter</param>
|
||||
/// <param name="payload">Optional payload committed by c</param>
|
||||
/// <returns>The PICCData if the checksum passed verification or null.</returns>
|
||||
public static BoltcardPICCData? TryDecrypt(AESKey encryptionKey, string p, string c, byte[]? payload = null)
|
||||
{
|
||||
if (!Validate(p, c))
|
||||
return null;
|
||||
var bytes = encryptionKey.Decrypt(p.HexToBytes());
|
||||
if (!ValidateBoltcardPICCData(bytes))
|
||||
return null;
|
||||
return new BoltcardPICCData(PICCData.Create(bytes));
|
||||
}
|
||||
}
|
||||
public record PICCData(byte[]? Uid, int? Counter)
|
||||
{
|
||||
@ -44,56 +87,7 @@ public record PICCData(byte[]? Uid, int? Counter)
|
||||
return new PICCData(uid, counter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the PICCData from the BoltCard and check the checksum.
|
||||
/// </summary>
|
||||
/// <param name="batchKeys">The deterministic batch keys</param>
|
||||
/// <param name="uri">The url with p= and c= parameters</param
|
||||
/// <param name="payload">Optional payload committed by c</param>
|
||||
/// <returns>The PICCData if the checksum passed verification or null.</returns>
|
||||
public static BoltcardPICCData? TryDeterministicBoltcardDecrypt(DeterministicBatchKeys batchKeys, Uri? uri, byte[]? payload = null)
|
||||
{
|
||||
if (!ExtractPC(uri, out var p, out var c))
|
||||
return null;
|
||||
return TryDeterministicBoltcardDecrypt(batchKeys, p, c, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the PICCData from the BoltCard and check the checksum.
|
||||
/// </summary>
|
||||
/// <param name="batchKeys">The deterministic batch keys</param>
|
||||
/// <param name="p">The p= parameter from the lnurlw (encrypted PICCData)</param>
|
||||
/// <param name="c">The c= parameter from the lnurlw (checksum)</param>
|
||||
/// <param name="payload">Optional payload committed by c</param>
|
||||
/// <returns>The PICCData if the checksum passed verification or null.</returns>
|
||||
public static BoltcardPICCData? TryDeterministicBoltcardDecrypt(DeterministicBatchKeys batchKeys, string p, string c, byte[]? payload = null)
|
||||
{
|
||||
if (!Validate(p, c))
|
||||
return null;
|
||||
var encryptionKey = batchKeys.DeriveEncryptionKey();
|
||||
var bytes = encryptionKey.Decrypt(p.HexToBytes());
|
||||
if (bytes[0] != 0xc7)
|
||||
return null;
|
||||
var piccData = new BoltcardPICCData(Create(bytes));
|
||||
var authenticationKey = batchKeys.DeriveAuthenticationKey(piccData.Uid);
|
||||
if (!authenticationKey.CheckSunMac(c, piccData, payload))
|
||||
return null;
|
||||
return new BoltcardPICCData(piccData);
|
||||
}
|
||||
|
||||
private static bool Validate(string p, string c)
|
||||
{
|
||||
if (p.Length != 32 || c.Length != 16)
|
||||
return false;
|
||||
foreach(var ch in p.Concat(c))
|
||||
{
|
||||
if (Extensions.IsDigitCore(ch) == 0xff)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ExtractPC(Uri? uri, [MaybeNullWhen(false)] out string p, [MaybeNullWhen(false)] out string c)
|
||||
internal static bool ExtractPC(Uri? uri, [MaybeNullWhen(false)] out string p, [MaybeNullWhen(false)] out string c)
|
||||
{
|
||||
p = null;
|
||||
c = null;
|
||||
@ -103,8 +97,8 @@ public record PICCData(byte[]? Uid, int? Counter)
|
||||
if (queryStringIdx == -1)
|
||||
return false;
|
||||
var queryString = uri.AbsoluteUri.Substring(queryStringIdx);
|
||||
var pm = Regex.Match(queryString, "p=(.*?)&");
|
||||
var cm = Regex.Match(queryString, "c=(.*)");
|
||||
var pm = Regex.Match(queryString, "p=([a-f0-9A-F]{32})");
|
||||
var cm = Regex.Match(queryString, "c=([a-f0-9A-F]{16})");
|
||||
if (pm is null || cm is null)
|
||||
return false;
|
||||
p = pm.Groups[1].Value;
|
||||
@ -120,11 +114,23 @@ public record PICCData(byte[]? Uid, int? Counter)
|
||||
/// <param name="uri">The url with p= and c= parameters</param
|
||||
/// <param name="payload">Optional payload committed by c</param>
|
||||
/// <returns>The PICCData if the checksum passed verification or null.</returns>
|
||||
public static PICCData? TryBoltcardDecrypt(AESKey encryptionKey, AESKey authenticationKey, Uri? uri, byte[]? payload = null)
|
||||
public static PICCData? TryBoltcardDecryptCheck(AESKey encryptionKey, AESKey authenticationKey, Uri? uri, byte[]? payload = null)
|
||||
{
|
||||
if (!ExtractPC(uri, out var p, out var c))
|
||||
return null;
|
||||
return TryBoltcardDecrypt(encryptionKey, authenticationKey, p, c, payload);
|
||||
return TryBoltcardDecryptCheck(encryptionKey, authenticationKey, p, c, payload);
|
||||
}
|
||||
|
||||
protected static bool Validate(string p, string c)
|
||||
{
|
||||
if (p.Length != 32 || c.Length != 16)
|
||||
return false;
|
||||
foreach (var ch in p.Concat(c))
|
||||
{
|
||||
if (Extensions.IsDigitCore(ch) == 0xff)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -136,7 +142,7 @@ public record PICCData(byte[]? Uid, int? Counter)
|
||||
/// <param name="c">THe c= parameter from the lnurlw (checksum)</param>
|
||||
/// <param name="payload">Optional payload committed by c</param>
|
||||
/// <returns>The PICCData if the checksum passed verification or null.</returns>
|
||||
public static PICCData? TryBoltcardDecrypt(AESKey encryptionKey, AESKey authenticationKey, string p, string c, byte[]? payload = null)
|
||||
public static PICCData? TryBoltcardDecryptCheck(AESKey encryptionKey, AESKey authenticationKey, string p, string c, byte[]? payload = null)
|
||||
{
|
||||
if (!Validate(p, c))
|
||||
return null;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System.Collections;
|
||||
using System.Security;
|
||||
using System.Text.RegularExpressions;
|
||||
using BTCPayServer.NTag424.PCSC;
|
||||
using NdefLibrary.Ndef;
|
||||
@ -76,17 +77,16 @@ public class UnitTest1
|
||||
[Fact]
|
||||
public void CanDeriveDeterministicBoltcard()
|
||||
{
|
||||
var issuerKey = new AESKey("00000000000000000000000000000001".HexToBytes());
|
||||
var batchId = 1U;
|
||||
var uid = "04a39493cc8680".HexToBytes();
|
||||
var batchKeys = new DeterministicBatchKeys(issuerKey, batchId);
|
||||
var keys = batchKeys.DeriveBoltcardKeys(uid);
|
||||
var nonce = "b45775776cb224c75bcde7ca3704e933".HexToBytes();
|
||||
var issuerKey = new IssuerKey("00000000000000000000000000000001".HexToBytes());
|
||||
var keys = issuerKey.DeriveBoltcardKeys(uid, nonce);
|
||||
Logs.WriteLine("K0: " + keys.AppMasterKey.ToBytes().ToHex());
|
||||
Logs.WriteLine("K1: " + keys.EncryptionKey.ToBytes().ToHex());
|
||||
Logs.WriteLine("K2: " + keys.AuthenticationKey.ToBytes().ToHex());
|
||||
Logs.WriteLine("K3: " + keys.K3.ToBytes().ToHex());
|
||||
Logs.WriteLine("K4: " + keys.K4.ToBytes().ToHex());
|
||||
Logs.WriteLine("ID: " + batchKeys.GetId(uid).ToHex());
|
||||
Logs.WriteLine("ID: " + issuerKey.GetId(uid).ToHex());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -172,18 +172,52 @@ public class UnitTest1
|
||||
Assert.Equal(uid1.ToHex(), uid2.ToHex());
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task CanDecryptAndCheckSUNMacOnDeterministicKey()
|
||||
{
|
||||
using var ctx = await PCSCContext.WaitForCard();
|
||||
var ntag = ctx.CreateNTag424();
|
||||
var issuerKey = new IssuerKey("00000000000000000000000000000001".HexToBytes());
|
||||
|
||||
// First time authenticate is with the default 00.000 key
|
||||
await ntag.AuthenticateEV2First(0, AESKey.Default);
|
||||
var uid = await ntag.GetCardUID();
|
||||
|
||||
var nonce = "00010000000000000000000000000000".HexToBytes();
|
||||
|
||||
var keys = issuerKey.DeriveBoltcardKeys(uid, nonce);
|
||||
await ntag.SetupBoltcard("lnurlw://blahblah.com", BoltcardKeys.Default, keys);
|
||||
|
||||
var uri = await ntag.TryReadNDefURI();
|
||||
var piccData = issuerKey.TryDecrypt(uri);
|
||||
Assert.NotNull(piccData);
|
||||
|
||||
// In real life, you would fetch the nonce from database
|
||||
// var nonce = await FetchNonce(issuerKey.GetId(piccData.Uid));
|
||||
|
||||
Assert.True(issuerKey.CheckSunMac(uri, piccData, nonce));
|
||||
|
||||
await ntag.ResetCard(issuerKey, nonce);
|
||||
// If this method didn't throw an exception, it has been successfully decrypted and authenticated.
|
||||
// You can reset the card with `await ntag.ResetCard(issuerKey, nonce);`.
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Reset()
|
||||
{
|
||||
var issuerKey = new AESKey("01000000000000000000000000000000".HexToBytes());
|
||||
var batchKeys = new DeterministicBatchKeys(issuerKey);
|
||||
var issuerKey = new IssuerKey("01000000000000000000000000000000".HexToBytes());
|
||||
var nonce = "00010000000000000000000000000000".HexToBytes();
|
||||
using var ctx = PCSCContext.Create();
|
||||
var enc = batchKeys.DeriveEncryptionKey();
|
||||
|
||||
var ntag = ctx.CreateNTag424();
|
||||
await ntag.AuthenticateEV2First(1, enc);
|
||||
await ntag.AuthenticateEV2First(0, AESKey.Default);
|
||||
var uid = await ntag.GetCardUID();
|
||||
var keys = batchKeys.DeriveBoltcardKeys(uid);
|
||||
await ntag.ResetCard(keys);
|
||||
var keys = issuerKey.DeriveBoltcardKeys(uid, nonce);
|
||||
await ntag.SetupBoltcard("lnurlw://test.com", BoltcardKeys.Default, keys);
|
||||
|
||||
await ntag.ResetCard(issuerKey, nonce);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -226,7 +260,7 @@ public class UnitTest1
|
||||
await ntag.SetupBoltcard("http://test.com", BoltcardKeys.Default, keys);
|
||||
var uri = await ntag.TryReadNDefURI();
|
||||
Assert.StartsWith("lnurlw://test.com/?p=", uri?.AbsoluteUri);
|
||||
var piccData = PICCData.TryBoltcardDecrypt(keys.EncryptionKey, keys.AuthenticationKey, uri);
|
||||
var piccData = PICCData.TryBoltcardDecryptCheck(keys.EncryptionKey, keys.AuthenticationKey, uri);
|
||||
Assert.NotNull(piccData);
|
||||
await ntag.ResetCard(keys);
|
||||
}
|
||||
@ -236,18 +270,19 @@ public class UnitTest1
|
||||
{
|
||||
using var ctx = PCSCContext.Create();
|
||||
var ntag = ctx.CreateNTag424();
|
||||
var issuerKey = new AESKey("00000000000000000000000000000001".HexToBytes());
|
||||
var batchKeys = new DeterministicBatchKeys(issuerKey);
|
||||
//await ntag.ResetCard(batchKeys);
|
||||
var issuerKey = new IssuerKey("00000000000000000000000000000001".HexToBytes());
|
||||
var nonce = "00010000000000000000000000000000".HexToBytes();
|
||||
//await ntag.ResetCard(issuerKey, nonce);
|
||||
await ntag.AuthenticateEV2First(0, AESKey.Default);
|
||||
var uid = await ntag.GetCardUID();
|
||||
|
||||
var keys = batchKeys.DeriveBoltcardKeys(uid);
|
||||
|
||||
var keys = issuerKey.DeriveBoltcardKeys(uid, nonce);
|
||||
await ntag.SetupBoltcard("http://test.com", BoltcardKeys.Default, keys);
|
||||
var uri = await ntag.TryReadNDefURI();
|
||||
var piccData = PICCData.TryDeterministicBoltcardDecrypt(batchKeys, uri);
|
||||
issuerKey.TryDecrypt(uri);
|
||||
var piccData = issuerKey.TryDecrypt(uri);
|
||||
Assert.NotNull(piccData);
|
||||
await ntag.ResetCard(batchKeys);
|
||||
await ntag.ResetCard(issuerKey, nonce);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user