Skip to content

Commit

Permalink
WebAuthn
Browse files Browse the repository at this point in the history
  • Loading branch information
sk-keeper committed Jun 25, 2022
1 parent 11c5de0 commit f16db24
Show file tree
Hide file tree
Showing 13 changed files with 298 additions and 539 deletions.
2 changes: 1 addition & 1 deletion Commander/Commands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ public NotConnectedCliContext(bool autologin)

_auth = new Auth(ui, storage)
{
Endpoint = {DeviceName = "Commander C#", ClientVersion = "c16.1.0"}
Endpoint = {DeviceName = "Commander C#", ClientVersion = "c16.5.0"}
};

Commands.Add("login", new ParsableCommand<LoginOptions>
Expand Down
43 changes: 18 additions & 25 deletions Commander/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using KeeperSecurity.Vault;
using KeeperSecurity.Authentication;
using KeeperSecurity.Authentication.Async;
using System.Runtime.Serialization.Json;

namespace Commander
{
Expand Down Expand Up @@ -403,37 +404,29 @@ public override Task<bool> WaitForHttpProxyCredentials(IHttpProxyInfo proxyInfo)
return base.WaitForHttpProxyCredentials(proxyInfo);
}

public async Task<string> AuthenticateRequests(SecurityKeyAuthenticateRequest[] requests)
public async Task<string> AuthenticatePublicKeyRequest(PublicKeyCredentialRequestOptions request)
{
if (requests == null || requests.Length == 0) throw new Exception("Security key challenge is empty. Try another 2FA method.");
var cancellationSource = new CancellationTokenSource();
var clientData = new SecurityKeyClientData
{
dataType = SecurityKeyClientData.U2F_SIGN,
challenge = requests[0].challenge,
origin = requests[0].appId,
};
var keyHandles = new List<byte[]>
{
requests[0].keyHandle.Base64UrlDecode()
};

foreach (var rq in requests.Skip(1))
if (request == null || string.IsNullOrEmpty(request.challenge))
{
if (rq.challenge == clientData.challenge && rq.appId == clientData.origin)
{
keyHandles.Add(rq.keyHandle.Base64UrlDecode());
}
throw new Exception("Security key challenge is empty. Try another 2FA method.");
}
var cancellationSource = new CancellationTokenSource();

var u2fSignature = await WinWebAuthn.Authenticate.GetAssertion(WinWebAuthn.Authenticate.GetConsoleWindow(), clientData, keyHandles, cancellationSource.Token);
var signature = new SecurityKeySignature
var webAuthnSignature = await WinWebAuthn.Authenticate.GetAssertion(WinWebAuthn.Authenticate.GetConsoleWindow(), request, cancellationSource.Token);
var signature = new KeeperWebAuthnSignature
{
clientData = u2fSignature.clientData.Base64UrlEncode(),
signatureData = u2fSignature.signatureData.Base64UrlEncode(),
keyHandle = u2fSignature.keyHandle.Base64UrlEncode()
id = webAuthnSignature.credentialId.Base64UrlEncode(),
rawId = webAuthnSignature.credentialId.Base64UrlEncode(),
response = new SignatureResponse
{
authenticatorData = webAuthnSignature.authenticatorData.Base64UrlEncode(),
clientDataJSON = webAuthnSignature.clientData.Base64UrlEncode(),
signature = webAuthnSignature.signatureData.Base64UrlEncode(),
},
type = "public-key",
clientExtensionResults = new ClientExtensionResults(),
};
return Encoding.UTF8.GetString(JsonUtils.DumpJson(signature));
return Encoding.UTF8.GetString(JsonUtils.DumpJson(signature, false));
}
}

Expand Down
6 changes: 0 additions & 6 deletions KeeperSdk.sln
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
README.md = README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SecurityKey", "SecurityKey\SecurityKey.csproj", "{92DC07FC-F2D1-4325-9556-3C3CD09624F4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "Sample\Sample.csproj", "{01037E06-F6F2-4A20-BAFE-3E56D856DEE6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinWebAuthn", "WinWebAuthn\WinWebAuthn.csproj", "{912A6A55-C062-42FF-AAFB-13B56F74B7E5}"
Expand Down Expand Up @@ -51,10 +49,6 @@ Global
{6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.Build.0 = Release|Any CPU
{92DC07FC-F2D1-4325-9556-3C3CD09624F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{92DC07FC-F2D1-4325-9556-3C3CD09624F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{92DC07FC-F2D1-4325-9556-3C3CD09624F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{92DC07FC-F2D1-4325-9556-3C3CD09624F4}.Release|Any CPU.Build.0 = Release|Any CPU
{01037E06-F6F2-4A20-BAFE-3E56D856DEE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{01037E06-F6F2-4A20-BAFE-3E56D856DEE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{01037E06-F6F2-4A20-BAFE-3E56D856DEE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down
2 changes: 1 addition & 1 deletion KeeperSdk/auth/Endpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ public class KeeperEndpoint : IKeeperEndpoint
{
private const string DefaultDeviceName = ".NET Keeper API";
public static string DefaultKeeperServer = "keepersecurity.com";
private const string DefaultClientVersion = "c16.0.0";
private const string DefaultClientVersion = "c16.5.0";

static KeeperEndpoint()
{
Expand Down
58 changes: 30 additions & 28 deletions KeeperSdk/auth/LoginV3Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public LoginContext()

internal string V2TwoFactorToken { get; set; }

public ECPrivateKeyParameters DeviceKey { get; set; }
public ECPrivateKeyParameters DeviceKey { get; set; }

public byte[] MessageSessionUid { get; }
internal Queue<string> PasswordQueue { get; } = new Queue<string>();
Expand Down Expand Up @@ -199,7 +199,7 @@ private static async Task RegisterDeviceInRegion(this IAuth auth, IDeviceConfigu
#endif
try
{
await auth.Endpoint.ExecuteRest("authentication/register_device_in_region", new ApiRequestPayload {Payload = request.ToByteString()});
await auth.Endpoint.ExecuteRest("authentication/register_device_in_region", new ApiRequestPayload { Payload = request.ToByteString() });
}
catch (KeeperApiException kae)
{
Expand Down Expand Up @@ -234,7 +234,7 @@ private static async Task<IDeviceConfiguration> RegisterDevice(this IAuth auth)
#if DEBUG
Debug.WriteLine($"REST Request: endpoint \"register_device\": {request}");
#endif
var rs = await auth.Endpoint.ExecuteRest("authentication/register_device", new ApiRequestPayload {Payload = request.ToByteString()});
var rs = await auth.Endpoint.ExecuteRest("authentication/register_device", new ApiRequestPayload { Payload = request.ToByteString() });
var response = Device.Parser.ParseFrom(rs);
#if DEBUG
Debug.WriteLine($"REST Response: endpoint \"register_device\": {response}");
Expand Down Expand Up @@ -264,7 +264,7 @@ private static async Task RequestDeviceVerification(this IAuth auth, LoginContex
Debug.WriteLine($"REST Request: endpoint \"request_device_verification\": {request}");
#endif
await auth.Endpoint.ExecuteRest("authentication/request_device_verification",
new ApiRequestPayload {Payload = request.ToByteString()});
new ApiRequestPayload { Payload = request.ToByteString() });
}

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

internal static Task<T> ResumeLogin<T>(
Expand Down Expand Up @@ -460,16 +460,16 @@ private static async Task<AuthContext> ExecuteValidatePassword(
}

private static async Task<AuthContext> ExecuteValidateAuthHash(
this IAuth auth,
LoginContext v3,
this IAuth auth,
LoginContext v3,
ValidateAuthHashRequest request,
Func<EncryptedDataKeyType, byte[], byte[]> dataKeyDecryptor)
{
#if DEBUG
Debug.WriteLine($"REST Request: endpoint \"validate_auth_hash\": {request}");
#endif
var rs = await auth.Endpoint.ExecuteRest("authentication/validate_auth_hash",
new ApiRequestPayload {Payload = request.ToByteString()});
new ApiRequestPayload { Payload = request.ToByteString() });
var response = LoginResponse.Parser.ParseFrom(rs);
#if DEBUG
Debug.WriteLine($"REST response: endpoint \"validate_auth_hash\": {response}");
Expand Down Expand Up @@ -519,7 +519,7 @@ internal static MasterPasswordInfo ValidateAuthHashPrepare(
if (saltInfo == null)
{
throw new KeeperStartLoginException(
LoginState.RequiresAuthHash,
LoginState.RequiresAuthHash,
"Master Password has not been created.");
}

Expand Down Expand Up @@ -653,7 +653,7 @@ bool NotificationCallback(NotificationEvent message)
auth.PushNotifications?.RegisterCallback(NotificationCallback);

return Tuple.Create<IDeviceApprovalChannelInfo[], Action>(
new IDeviceApprovalChannelInfo[] {email, push, otp},
new IDeviceApprovalChannelInfo[] { email, push, otp },
() => { auth.PushNotifications?.RemoveCallback(NotificationCallback); });
}

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

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

case TwoFactorChannelType.TwoFaCtRsa:
{
var rsa = new RsaSecurIdTwoFactorChannel();
rsa.InvokeTwoFactorCodeAction = GetCodeDelegate(rsa, ch);
availableChannels.Add(rsa);
}
break;
break;

case TwoFactorChannelType.TwoFaCtSms:
{
Expand All @@ -812,7 +812,7 @@ TwoFactorCodeActionDelegate GetCodeDelegate(ITwoFactorAppCodeInfo channel, TwoFa
sms.InvokeTwoFactorCodeAction = GetCodeDelegate(sms, ch);
availableChannels.Add(sms);
}
break;
break;

case TwoFactorChannelType.TwoFaCtDuo:
{
Expand Down Expand Up @@ -842,7 +842,7 @@ TwoFactorCodeActionDelegate GetCodeDelegate(ITwoFactorAppCodeInfo channel, TwoFa
duoTfa.InvokeTwoFactorCodeAction = GetCodeDelegate(duoTfa, ch);
availableChannels.Add(duoTfa);
}
break;
break;

case TwoFactorChannelType.TwoFaCtDna:
{
Expand All @@ -854,30 +854,31 @@ TwoFactorCodeActionDelegate GetCodeDelegate(ITwoFactorAppCodeInfo channel, TwoFa
dna2Fa.InvokeTwoFactorCodeAction = GetCodeDelegate(dna2Fa, ch);
availableChannels.Add(dna2Fa);
}
break;
break;

case TwoFactorChannelType.TwoFaCtU2F:
case TwoFactorChannelType.TwoFaCtWebauthn:
if (auth.AuthCallback is IAuthSecurityKeyUI keyUi)
{
try
{
var rqs = JsonUtils.ParseJson<SecurityKeyRequest>(Encoding.UTF8.GetBytes(ch.Challenge));
var key2Fa = new TwoFactorSecurityKeyChannel();
key2Fa.InvokeTwoFactorPushAction = (action) =>
var rqs = JsonUtils.ParseJson<KeeperWebAuthnRequest>(Encoding.UTF8.GetBytes(ch.Challenge));
var key2Fa = new TwoFactorSecurityKeyChannel
{
return Task.Run(async () =>
InvokeTwoFactorPushAction = async (action) =>
{
var signature = await keyUi.AuthenticateRequests(rqs.authenticateRequests);
var signature = keyUi.AuthenticatePublicKeyRequest(rqs.publicKeyCredentialRequestOptions).GetAwaiter().GetResult();

var request = new TwoFactorValidateRequest
{
ChannelUid = ch.ChannelUid,
EncryptedLoginToken = loginToken,
ExpireIn = TwoFactorExpiration.TwoFaExpImmediately,
ValueType = ch.ChannelType == TwoFactorChannelType.TwoFaCtWebauthn ? TwoFactorValueType.TwoFaRespWebauthn : TwoFactorValueType.TwoFaRespU2F,
ValueType = TwoFactorValueType.TwoFaRespWebauthn,
Value = signature,
};
var validateRs = await auth.ExecuteTwoFactorValidateCode(request);
onLoginToken(validateRs.EncryptedLoginToken);
});
}
};
availableChannels.Add(key2Fa);
}
Expand All @@ -888,7 +889,8 @@ TwoFactorCodeActionDelegate GetCodeDelegate(ITwoFactorAppCodeInfo channel, TwoFa
}

break;
case TwoFactorChannelType.TwoFaCtWebauthn:

case TwoFactorChannelType.TwoFaCtU2F:
case TwoFactorChannelType.TwoFaCtKeeper:
break;
}
Expand Down Expand Up @@ -1109,7 +1111,7 @@ public static async Task RequestCreateUser(this IAuth auth, LoginContext v3, str
{
Payload = request.ToByteString()
};

Debug.WriteLine($"REST Request: endpoint \"request_create_user\": {request}");
await auth.Endpoint.ExecuteRest("authentication/request_create_user", apiRequest);
}
Expand Down Expand Up @@ -1156,7 +1158,7 @@ private static async Task<DeviceVerificationResponse> RequestDeviceAdminApproval
#if DEBUG
Debug.WriteLine($"REST Request: endpoint \"request_device_admin_approval\": {request}");
#endif
var payload = new ApiRequestPayload {Payload = request.ToByteString()};
var payload = new ApiRequestPayload { Payload = request.ToByteString() };
var rs = await auth.Endpoint.ExecuteRest("authentication/request_device_admin_approval", payload);
DeviceVerificationResponse response = null;
if (rs?.Length > 0)
Expand Down Expand Up @@ -1218,7 +1220,7 @@ bool ProcessDataKeyRequest(NotificationEvent message)
auth.PushNotifications?.RegisterCallback(ProcessDataKeyRequest);

return Tuple.Create<IDataKeyChannelInfo[], Action>(
new IDataKeyChannelInfo[] {pushChannel, adminChannel},
new IDataKeyChannelInfo[] { pushChannel, adminChannel },
() => { auth.PushNotifications?.RemoveCallback(ProcessDataKeyRequest); }
);
}
Expand Down
Loading

0 comments on commit f16db24

Please sign in to comment.