Skip to content

Commit d25a42e

Browse files
authored
Add CTAP support (#281)
* Add Ctap project * Rename Ctap -> Ctap2 * Make various Cbor models public to support Ctap * Add PublicKeyCredentialUserEntity model * Make PublicKeyCredentialRpEntity.Icon optional * Add CTAP models * Add basic CTAP test coverage * Rename AuthenticatorBase -> FidoAuthenticator * Use C# 10 * [Ctap2] Include AuthenticationMakeCredentialCommand extensions * [Cbor2] Implement AuthenticatorClientPinResponse.FromCborObject * Add CredentialPublicKey(ECDsa ecdsaPublicKey, COSE.Algorithm alg) constructor * Make PubKeyCredParam options public * [Ctap2] Implement higher level APIs * Update System.Formats.Cbor
1 parent bec164c commit d25a42e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1952
-68
lines changed

Src/Fido2.Ctap2/Cbor/CborHelper.cs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using Fido2NetLib.Cbor;
2+
using Fido2NetLib.Objects;
3+
4+
namespace Fido2NetLib.Ctap2;
5+
6+
internal sealed class CborHelper
7+
{
8+
public static PublicKeyCredentialDescriptor DecodePublicKeyCredentialDescriptor(CborMap map)
9+
{
10+
var result = new PublicKeyCredentialDescriptor();
11+
12+
foreach (var (key, value) in map)
13+
{
14+
switch ((string)key)
15+
{
16+
case "id":
17+
result.Id = (byte[])value;
18+
break;
19+
case "type" when (value is CborTextString { Value: "public-key" }):
20+
result.Type = PublicKeyCredentialType.PublicKey;
21+
break;
22+
}
23+
}
24+
25+
return result;
26+
}
27+
28+
public static PublicKeyCredentialUserEntity DecodePublicKeyCredentialUserEntity(CborMap map)
29+
{
30+
var result = new PublicKeyCredentialUserEntity();
31+
32+
foreach (var (key, value) in map)
33+
{
34+
switch ((string)key)
35+
{
36+
case "id":
37+
result.Id = (byte[])value;
38+
break;
39+
case "name":
40+
result.Name = (string)value;
41+
break;
42+
case "displayName":
43+
result.DisplayName = (string)value;
44+
break;
45+
case "icon":
46+
result.Icon = (string)value;
47+
break;
48+
}
49+
}
50+
51+
return result;
52+
}
53+
54+
public static string[] ToStringArray(CborObject cborObject)
55+
{
56+
var cborArray = (CborArray)cborObject;
57+
58+
var result = new string[cborArray.Length];
59+
60+
for (int i = 0; i < cborArray.Length; i++)
61+
{
62+
result[i] = (string)cborArray[i];
63+
}
64+
65+
return result;
66+
}
67+
68+
public static int[] ToInt32Array(CborObject cborObject)
69+
{
70+
var cborArray = (CborArray)cborObject;
71+
72+
var result = new int[cborArray.Length];
73+
74+
for (int i = 0; i < cborArray.Length; i++)
75+
{
76+
result[i] = (int)cborArray[i];
77+
}
78+
79+
return result;
80+
}
81+
}

Src/Fido2.Ctap2/Cbor/CborMember.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace Fido2NetLib.Ctap2;
2+
3+
public sealed class CborMember : Attribute
4+
{
5+
public object _key;
6+
7+
public CborMember(byte key)
8+
{
9+
_key = key;
10+
}
11+
12+
public CborMember(string key)
13+
{
14+
_key = key;
15+
}
16+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
using Fido2NetLib.Cbor;
2+
using Fido2NetLib.Objects;
3+
4+
namespace Fido2NetLib.Ctap2;
5+
6+
public sealed class AuthenticatorClientPinCommand : CtapCommand
7+
{
8+
public AuthenticatorClientPinCommand(
9+
uint pinProtocol,
10+
AuthenticatorClientPinSubCommand subCommand,
11+
CredentialPublicKey? keyAgreement = null,
12+
byte[]? pinAuth = null,
13+
byte[]? newPinEnc = null,
14+
byte[]? pinHashEnc = null)
15+
{
16+
17+
PinProtocol = pinProtocol;
18+
SubCommand = subCommand;
19+
KeyAgreement = keyAgreement;
20+
PinAuth = pinAuth;
21+
NewPinEnc = newPinEnc;
22+
PinHashEnc = pinHashEnc;
23+
}
24+
25+
/// <summary>
26+
/// Required PIN protocol version chosen by the client
27+
/// </summary>
28+
[CborMember(0x01)]
29+
public uint PinProtocol { get; }
30+
31+
/// <summary>
32+
/// The authenticator Client PIN sub command currently being requested.
33+
/// </summary>
34+
[CborMember(0x02)]
35+
public AuthenticatorClientPinSubCommand SubCommand { get; }
36+
37+
/// <summary>
38+
/// Public key of platformKeyAgreementKey.
39+
/// The COSE_Key-encoded public key MUST contain the optional "alg" parameter and MUST NOT contain any other optional parameters.
40+
/// The "alg" parameter MUST contain a COSEAlgorithmIdentifier value.
41+
/// </summary>
42+
[CborMember(0x03)]
43+
public CredentialPublicKey? KeyAgreement { get; }
44+
45+
/// <summary>
46+
/// First 16 bytes of HMAC-SHA-256 of encrypted contents using sharedSecret.
47+
/// </summary>
48+
[CborMember(0x04)]
49+
public byte[]? PinAuth { get; }
50+
51+
/// <summary>
52+
/// Encrypted new PIN using sharedSecret.
53+
/// </summary>
54+
[CborMember(0x05)]
55+
public byte[]? NewPinEnc { get; }
56+
57+
/// <summary>
58+
/// Encrypted first 16 bytes of SHA-256 of PIN using sharedSecret.
59+
/// </summary>
60+
[CborMember(0x06)]
61+
public byte[]? PinHashEnc { get; }
62+
63+
public override CtapCommandType Type => CtapCommandType.AuthenticatorClientPin;
64+
65+
protected override CborObject? GetParameters()
66+
{
67+
var cbor = new CborMap
68+
{
69+
{ 0x01, PinProtocol },
70+
{ 0x02, (int)SubCommand }
71+
};
72+
73+
if (KeyAgreement != null)
74+
{
75+
cbor.Add(0x03, KeyAgreement.GetCborObject());
76+
}
77+
78+
if (PinAuth != null)
79+
{
80+
cbor.Add(0x04, PinAuth);
81+
}
82+
83+
if (NewPinEnc != null)
84+
{
85+
cbor.Add(0x05, NewPinEnc);
86+
}
87+
88+
if (PinHashEnc != null)
89+
{
90+
cbor.Add(0x06, PinHashEnc);
91+
}
92+
93+
return cbor;
94+
}
95+
}
96+
97+
public enum AuthenticatorClientPinSubCommand
98+
{
99+
GetRetries = 0x01,
100+
GetKeyAgreement = 0x02,
101+
SetPin = 0x03,
102+
ChangePin = 0x04,
103+
GetPinToken = 0x05,
104+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
using Fido2NetLib.Cbor;
2+
using Fido2NetLib.Objects;
3+
4+
namespace Fido2NetLib.Ctap2;
5+
6+
public sealed class AuthenticatorGetAssertionCommand : CtapCommand
7+
{
8+
public AuthenticatorGetAssertionCommand(
9+
string rpId,
10+
byte[] clientDataHash,
11+
PublicKeyCredentialDescriptor[] allowList,
12+
CborMap? extensions = null,
13+
AuthenticatorGetAssertionOptions? options = null,
14+
byte[]? pinAuth = null,
15+
uint? pinProtocol = null)
16+
{
17+
ArgumentNullException.ThrowIfNull(rpId);
18+
ArgumentNullException.ThrowIfNull(clientDataHash);
19+
20+
RpId = rpId;
21+
ClientDataHash = clientDataHash;
22+
AllowList = allowList;
23+
Extensions = extensions;
24+
Options = options;
25+
PinAuth = pinAuth;
26+
PinProtocol = pinProtocol;
27+
}
28+
29+
/// <summary>
30+
/// Relying party identifier
31+
/// </summary>
32+
[CborMember(0x01)]
33+
public string RpId { get; }
34+
35+
/// <summary>
36+
/// Hash of the serialized client data collected by the host
37+
/// </summary>
38+
[CborMember(0x02)]
39+
public byte[] ClientDataHash { get; }
40+
41+
/// <summary>
42+
/// A sequence of PublicKeyCredentialDescriptor structures, each denoting a credential, as specified in [WebAuthn].
43+
/// If this parameter is present and has 1 or more entries, the authenticator MUST only generate an assertion using one of the denoted credentials.
44+
/// </summary>
45+
[CborMember(0x03)]
46+
public PublicKeyCredentialDescriptor[] AllowList { get; }
47+
48+
/// <summary>
49+
/// CBOR map of extension identifier → authenticator extension input values
50+
/// </summary>
51+
[CborMember(0x04)]
52+
public CborMap? Extensions { get; }
53+
54+
/// <summary>
55+
/// Map of authenticator options
56+
/// </summary>
57+
[CborMember(0x05)]
58+
public AuthenticatorGetAssertionOptions? Options { get; }
59+
60+
/// <summary>
61+
/// First 16 bytes of HMAC-SHA-256 of clientDataHash using pinToken which platform got from the authenticator:
62+
/// HMAC-SHA-256(pinToken, clientDataHash).
63+
/// </summary>
64+
[CborMember(0x06)]
65+
public byte[]? PinAuth { get; }
66+
67+
/// <summary>
68+
/// PIN protocol version selected by client.
69+
/// </summary>
70+
[CborMember(0x07)]
71+
public uint? PinProtocol { get; }
72+
73+
public override CtapCommandType Type => CtapCommandType.AuthenticatorGetAssertion;
74+
75+
protected override CborObject? GetParameters()
76+
{
77+
var cbor = new CborMap
78+
{
79+
{ 0x01, RpId },
80+
{ 0x02, ClientDataHash }
81+
};
82+
83+
cbor.Add(0x03, AllowList.ToCborArray()); // allowList
84+
85+
if (Extensions != null)
86+
{
87+
cbor.Add(0x04, Extensions);
88+
}
89+
90+
if (Options != null)
91+
{
92+
cbor.Add(0x05, Options.ToCborObject());
93+
}
94+
95+
if (PinAuth is not null)
96+
{
97+
cbor.Add(0x06, PinAuth); // pinAuth(0x08)
98+
cbor.Add(0x07, PinProtocol ?? 1); // pinProtocol(0x09)
99+
}
100+
101+
return cbor;
102+
}
103+
}
104+
105+
106+
public sealed class AuthenticatorGetAssertionOptions
107+
{
108+
/// <summary>
109+
/// Instructs the authenticator to require user consent to complete the operation.
110+
/// </summary>
111+
[CborMember("up")]
112+
public bool? UserPresence { get; init; }
113+
114+
/// <summary>
115+
/// Instructs the authenticator to require a gesture that verifies the user to complete the request. Examples of such gestures are fingerprint scan or a PIN.
116+
/// </summary>
117+
[CborMember("uv")]
118+
public bool? UserVerification { get; init; }
119+
120+
public CborMap ToCborObject()
121+
{
122+
var result = new CborMap();
123+
124+
if (UserPresence is bool up)
125+
{
126+
result.Add("up", up);
127+
}
128+
129+
if (UserVerification is bool uv)
130+
{
131+
result.Add("uv", uv);
132+
}
133+
134+
return result;
135+
}
136+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Fido2NetLib.Ctap2;
2+
3+
public sealed class AuthenticatorGetInfoCommand : CtapCommand
4+
{
5+
public override CtapCommandType Type => CtapCommandType.AuthenticatorGetInfo;
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Fido2NetLib.Ctap2;
2+
3+
public sealed class AuthenticatorGetNextAssertionCommand : CtapCommand
4+
{
5+
public override CtapCommandType Type => CtapCommandType.AuthenticatorGetNextAssertion;
6+
}

0 commit comments

Comments
 (0)