Skip to content

Commit f16db24

Browse files
committed
WebAuthn
1 parent 11c5de0 commit f16db24

File tree

13 files changed

+298
-539
lines changed

13 files changed

+298
-539
lines changed

Commander/Commands.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ public NotConnectedCliContext(bool autologin)
284284

285285
_auth = new Auth(ui, storage)
286286
{
287-
Endpoint = {DeviceName = "Commander C#", ClientVersion = "c16.1.0"}
287+
Endpoint = {DeviceName = "Commander C#", ClientVersion = "c16.5.0"}
288288
};
289289

290290
Commands.Add("login", new ParsableCommand<LoginOptions>

Commander/Program.cs

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
using KeeperSecurity.Vault;
2222
using KeeperSecurity.Authentication;
2323
using KeeperSecurity.Authentication.Async;
24+
using System.Runtime.Serialization.Json;
2425

2526
namespace Commander
2627
{
@@ -403,37 +404,29 @@ public override Task<bool> WaitForHttpProxyCredentials(IHttpProxyInfo proxyInfo)
403404
return base.WaitForHttpProxyCredentials(proxyInfo);
404405
}
405406

406-
public async Task<string> AuthenticateRequests(SecurityKeyAuthenticateRequest[] requests)
407+
public async Task<string> AuthenticatePublicKeyRequest(PublicKeyCredentialRequestOptions request)
407408
{
408-
if (requests == null || requests.Length == 0) throw new Exception("Security key challenge is empty. Try another 2FA method.");
409-
var cancellationSource = new CancellationTokenSource();
410-
var clientData = new SecurityKeyClientData
411-
{
412-
dataType = SecurityKeyClientData.U2F_SIGN,
413-
challenge = requests[0].challenge,
414-
origin = requests[0].appId,
415-
};
416-
var keyHandles = new List<byte[]>
417-
{
418-
requests[0].keyHandle.Base64UrlDecode()
419-
};
420-
421-
foreach (var rq in requests.Skip(1))
409+
if (request == null || string.IsNullOrEmpty(request.challenge))
422410
{
423-
if (rq.challenge == clientData.challenge && rq.appId == clientData.origin)
424-
{
425-
keyHandles.Add(rq.keyHandle.Base64UrlDecode());
426-
}
411+
throw new Exception("Security key challenge is empty. Try another 2FA method.");
427412
}
413+
var cancellationSource = new CancellationTokenSource();
428414

429-
var u2fSignature = await WinWebAuthn.Authenticate.GetAssertion(WinWebAuthn.Authenticate.GetConsoleWindow(), clientData, keyHandles, cancellationSource.Token);
430-
var signature = new SecurityKeySignature
415+
var webAuthnSignature = await WinWebAuthn.Authenticate.GetAssertion(WinWebAuthn.Authenticate.GetConsoleWindow(), request, cancellationSource.Token);
416+
var signature = new KeeperWebAuthnSignature
431417
{
432-
clientData = u2fSignature.clientData.Base64UrlEncode(),
433-
signatureData = u2fSignature.signatureData.Base64UrlEncode(),
434-
keyHandle = u2fSignature.keyHandle.Base64UrlEncode()
418+
id = webAuthnSignature.credentialId.Base64UrlEncode(),
419+
rawId = webAuthnSignature.credentialId.Base64UrlEncode(),
420+
response = new SignatureResponse
421+
{
422+
authenticatorData = webAuthnSignature.authenticatorData.Base64UrlEncode(),
423+
clientDataJSON = webAuthnSignature.clientData.Base64UrlEncode(),
424+
signature = webAuthnSignature.signatureData.Base64UrlEncode(),
425+
},
426+
type = "public-key",
427+
clientExtensionResults = new ClientExtensionResults(),
435428
};
436-
return Encoding.UTF8.GetString(JsonUtils.DumpJson(signature));
429+
return Encoding.UTF8.GetString(JsonUtils.DumpJson(signature, false));
437430
}
438431
}
439432

KeeperSdk.sln

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
2222
README.md = README.md
2323
EndProjectSection
2424
EndProject
25-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SecurityKey", "SecurityKey\SecurityKey.csproj", "{92DC07FC-F2D1-4325-9556-3C3CD09624F4}"
26-
EndProject
2725
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "Sample\Sample.csproj", "{01037E06-F6F2-4A20-BAFE-3E56D856DEE6}"
2826
EndProject
2927
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinWebAuthn", "WinWebAuthn\WinWebAuthn.csproj", "{912A6A55-C062-42FF-AAFB-13B56F74B7E5}"
@@ -51,10 +49,6 @@ Global
5149
{6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.Build.0 = Debug|Any CPU
5250
{6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.ActiveCfg = Release|Any CPU
5351
{6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.Build.0 = Release|Any CPU
54-
{92DC07FC-F2D1-4325-9556-3C3CD09624F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
55-
{92DC07FC-F2D1-4325-9556-3C3CD09624F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
56-
{92DC07FC-F2D1-4325-9556-3C3CD09624F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
57-
{92DC07FC-F2D1-4325-9556-3C3CD09624F4}.Release|Any CPU.Build.0 = Release|Any CPU
5852
{01037E06-F6F2-4A20-BAFE-3E56D856DEE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
5953
{01037E06-F6F2-4A20-BAFE-3E56D856DEE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
6054
{01037E06-F6F2-4A20-BAFE-3E56D856DEE6}.Release|Any CPU.ActiveCfg = Release|Any CPU

KeeperSdk/auth/Endpoint.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ public class KeeperEndpoint : IKeeperEndpoint
237237
{
238238
private const string DefaultDeviceName = ".NET Keeper API";
239239
public static string DefaultKeeperServer = "keepersecurity.com";
240-
private const string DefaultClientVersion = "c16.0.0";
240+
private const string DefaultClientVersion = "c16.5.0";
241241

242242
static KeeperEndpoint()
243243
{

KeeperSdk/auth/LoginV3Extensions.cs

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public LoginContext()
3131

3232
internal string V2TwoFactorToken { get; set; }
3333

34-
public ECPrivateKeyParameters DeviceKey { get; set; }
34+
public ECPrivateKeyParameters DeviceKey { get; set; }
3535

3636
public byte[] MessageSessionUid { get; }
3737
internal Queue<string> PasswordQueue { get; } = new Queue<string>();
@@ -199,7 +199,7 @@ private static async Task RegisterDeviceInRegion(this IAuth auth, IDeviceConfigu
199199
#endif
200200
try
201201
{
202-
await auth.Endpoint.ExecuteRest("authentication/register_device_in_region", new ApiRequestPayload {Payload = request.ToByteString()});
202+
await auth.Endpoint.ExecuteRest("authentication/register_device_in_region", new ApiRequestPayload { Payload = request.ToByteString() });
203203
}
204204
catch (KeeperApiException kae)
205205
{
@@ -234,7 +234,7 @@ private static async Task<IDeviceConfiguration> RegisterDevice(this IAuth auth)
234234
#if DEBUG
235235
Debug.WriteLine($"REST Request: endpoint \"register_device\": {request}");
236236
#endif
237-
var rs = await auth.Endpoint.ExecuteRest("authentication/register_device", new ApiRequestPayload {Payload = request.ToByteString()});
237+
var rs = await auth.Endpoint.ExecuteRest("authentication/register_device", new ApiRequestPayload { Payload = request.ToByteString() });
238238
var response = Device.Parser.ParseFrom(rs);
239239
#if DEBUG
240240
Debug.WriteLine($"REST Response: endpoint \"register_device\": {response}");
@@ -264,7 +264,7 @@ private static async Task RequestDeviceVerification(this IAuth auth, LoginContex
264264
Debug.WriteLine($"REST Request: endpoint \"request_device_verification\": {request}");
265265
#endif
266266
await auth.Endpoint.ExecuteRest("authentication/request_device_verification",
267-
new ApiRequestPayload {Payload = request.ToByteString()});
267+
new ApiRequestPayload { Payload = request.ToByteString() });
268268
}
269269

270270
internal static async Task ValidateDeviceVerificationCode(this IAuth auth, LoginContext v3, string code)
@@ -281,7 +281,7 @@ internal static async Task ValidateDeviceVerificationCode(this IAuth auth, Login
281281
Debug.WriteLine($"REST Request: endpoint \"validate_device_verification_code\": {request}");
282282
#endif
283283
await auth.Endpoint.ExecuteRest("authentication/validate_device_verification_code",
284-
new ApiRequestPayload {Payload = request.ToByteString()});
284+
new ApiRequestPayload { Payload = request.ToByteString() });
285285
}
286286

287287
internal static Task<T> ResumeLogin<T>(
@@ -460,16 +460,16 @@ private static async Task<AuthContext> ExecuteValidatePassword(
460460
}
461461

462462
private static async Task<AuthContext> ExecuteValidateAuthHash(
463-
this IAuth auth,
464-
LoginContext v3,
463+
this IAuth auth,
464+
LoginContext v3,
465465
ValidateAuthHashRequest request,
466466
Func<EncryptedDataKeyType, byte[], byte[]> dataKeyDecryptor)
467467
{
468468
#if DEBUG
469469
Debug.WriteLine($"REST Request: endpoint \"validate_auth_hash\": {request}");
470470
#endif
471471
var rs = await auth.Endpoint.ExecuteRest("authentication/validate_auth_hash",
472-
new ApiRequestPayload {Payload = request.ToByteString()});
472+
new ApiRequestPayload { Payload = request.ToByteString() });
473473
var response = LoginResponse.Parser.ParseFrom(rs);
474474
#if DEBUG
475475
Debug.WriteLine($"REST response: endpoint \"validate_auth_hash\": {response}");
@@ -519,7 +519,7 @@ internal static MasterPasswordInfo ValidateAuthHashPrepare(
519519
if (saltInfo == null)
520520
{
521521
throw new KeeperStartLoginException(
522-
LoginState.RequiresAuthHash,
522+
LoginState.RequiresAuthHash,
523523
"Master Password has not been created.");
524524
}
525525

@@ -653,7 +653,7 @@ bool NotificationCallback(NotificationEvent message)
653653
auth.PushNotifications?.RegisterCallback(NotificationCallback);
654654

655655
return Tuple.Create<IDeviceApprovalChannelInfo[], Action>(
656-
new IDeviceApprovalChannelInfo[] {email, push, otp},
656+
new IDeviceApprovalChannelInfo[] { email, push, otp },
657657
() => { auth.PushNotifications?.RemoveCallback(NotificationCallback); });
658658
}
659659

@@ -715,7 +715,7 @@ private static async Task ExecutePushAction(this IAuth auth, TwoFactorSendPushRe
715715
#if DEBUG
716716
Debug.WriteLine($"REST Request: endpoint \"2fa_send_push\": {request}");
717717
#endif
718-
await auth.Endpoint.ExecuteRest("authentication/2fa_send_push", new ApiRequestPayload {Payload = request.ToByteString()});
718+
await auth.Endpoint.ExecuteRest("authentication/2fa_send_push", new ApiRequestPayload { Payload = request.ToByteString() });
719719
}
720720

721721
private static async Task<TwoFactorValidateResponse> ExecuteTwoFactorValidateCode(this IAuth auth, TwoFactorValidateRequest request)
@@ -792,15 +792,15 @@ TwoFactorCodeActionDelegate GetCodeDelegate(ITwoFactorAppCodeInfo channel, TwoFa
792792
totp.InvokeTwoFactorCodeAction = GetCodeDelegate(totp, ch);
793793
availableChannels.Add(totp);
794794
}
795-
break;
795+
break;
796796

797797
case TwoFactorChannelType.TwoFaCtRsa:
798798
{
799799
var rsa = new RsaSecurIdTwoFactorChannel();
800800
rsa.InvokeTwoFactorCodeAction = GetCodeDelegate(rsa, ch);
801801
availableChannels.Add(rsa);
802802
}
803-
break;
803+
break;
804804

805805
case TwoFactorChannelType.TwoFaCtSms:
806806
{
@@ -812,7 +812,7 @@ TwoFactorCodeActionDelegate GetCodeDelegate(ITwoFactorAppCodeInfo channel, TwoFa
812812
sms.InvokeTwoFactorCodeAction = GetCodeDelegate(sms, ch);
813813
availableChannels.Add(sms);
814814
}
815-
break;
815+
break;
816816

817817
case TwoFactorChannelType.TwoFaCtDuo:
818818
{
@@ -842,7 +842,7 @@ TwoFactorCodeActionDelegate GetCodeDelegate(ITwoFactorAppCodeInfo channel, TwoFa
842842
duoTfa.InvokeTwoFactorCodeAction = GetCodeDelegate(duoTfa, ch);
843843
availableChannels.Add(duoTfa);
844844
}
845-
break;
845+
break;
846846

847847
case TwoFactorChannelType.TwoFaCtDna:
848848
{
@@ -854,30 +854,31 @@ TwoFactorCodeActionDelegate GetCodeDelegate(ITwoFactorAppCodeInfo channel, TwoFa
854854
dna2Fa.InvokeTwoFactorCodeAction = GetCodeDelegate(dna2Fa, ch);
855855
availableChannels.Add(dna2Fa);
856856
}
857-
break;
857+
break;
858858

859-
case TwoFactorChannelType.TwoFaCtU2F:
859+
case TwoFactorChannelType.TwoFaCtWebauthn:
860860
if (auth.AuthCallback is IAuthSecurityKeyUI keyUi)
861861
{
862862
try
863863
{
864-
var rqs = JsonUtils.ParseJson<SecurityKeyRequest>(Encoding.UTF8.GetBytes(ch.Challenge));
865-
var key2Fa = new TwoFactorSecurityKeyChannel();
866-
key2Fa.InvokeTwoFactorPushAction = (action) =>
864+
var rqs = JsonUtils.ParseJson<KeeperWebAuthnRequest>(Encoding.UTF8.GetBytes(ch.Challenge));
865+
var key2Fa = new TwoFactorSecurityKeyChannel
867866
{
868-
return Task.Run(async () =>
867+
InvokeTwoFactorPushAction = async (action) =>
869868
{
870-
var signature = await keyUi.AuthenticateRequests(rqs.authenticateRequests);
869+
var signature = keyUi.AuthenticatePublicKeyRequest(rqs.publicKeyCredentialRequestOptions).GetAwaiter().GetResult();
870+
871871
var request = new TwoFactorValidateRequest
872872
{
873+
ChannelUid = ch.ChannelUid,
873874
EncryptedLoginToken = loginToken,
874875
ExpireIn = TwoFactorExpiration.TwoFaExpImmediately,
875-
ValueType = ch.ChannelType == TwoFactorChannelType.TwoFaCtWebauthn ? TwoFactorValueType.TwoFaRespWebauthn : TwoFactorValueType.TwoFaRespU2F,
876+
ValueType = TwoFactorValueType.TwoFaRespWebauthn,
876877
Value = signature,
877878
};
878879
var validateRs = await auth.ExecuteTwoFactorValidateCode(request);
879880
onLoginToken(validateRs.EncryptedLoginToken);
880-
});
881+
}
881882
};
882883
availableChannels.Add(key2Fa);
883884
}
@@ -888,7 +889,8 @@ TwoFactorCodeActionDelegate GetCodeDelegate(ITwoFactorAppCodeInfo channel, TwoFa
888889
}
889890

890891
break;
891-
case TwoFactorChannelType.TwoFaCtWebauthn:
892+
893+
case TwoFactorChannelType.TwoFaCtU2F:
892894
case TwoFactorChannelType.TwoFaCtKeeper:
893895
break;
894896
}
@@ -1109,7 +1111,7 @@ public static async Task RequestCreateUser(this IAuth auth, LoginContext v3, str
11091111
{
11101112
Payload = request.ToByteString()
11111113
};
1112-
1114+
11131115
Debug.WriteLine($"REST Request: endpoint \"request_create_user\": {request}");
11141116
await auth.Endpoint.ExecuteRest("authentication/request_create_user", apiRequest);
11151117
}
@@ -1156,7 +1158,7 @@ private static async Task<DeviceVerificationResponse> RequestDeviceAdminApproval
11561158
#if DEBUG
11571159
Debug.WriteLine($"REST Request: endpoint \"request_device_admin_approval\": {request}");
11581160
#endif
1159-
var payload = new ApiRequestPayload {Payload = request.ToByteString()};
1161+
var payload = new ApiRequestPayload { Payload = request.ToByteString() };
11601162
var rs = await auth.Endpoint.ExecuteRest("authentication/request_device_admin_approval", payload);
11611163
DeviceVerificationResponse response = null;
11621164
if (rs?.Length > 0)
@@ -1218,7 +1220,7 @@ bool ProcessDataKeyRequest(NotificationEvent message)
12181220
auth.PushNotifications?.RegisterCallback(ProcessDataKeyRequest);
12191221

12201222
return Tuple.Create<IDataKeyChannelInfo[], Action>(
1221-
new IDataKeyChannelInfo[] {pushChannel, adminChannel},
1223+
new IDataKeyChannelInfo[] { pushChannel, adminChannel },
12221224
() => { auth.PushNotifications?.RemoveCallback(ProcessDataKeyRequest); }
12231225
);
12241226
}

0 commit comments

Comments
 (0)