This project has 2 main parts: A SQRL client and a SQRL library. Below is information on both.
SQRL Dot Net Core Client
SQRL Dot Net Core Library
An implementation of a fully-featured SQRL client, along with a cross-platform user interface (using the Avalonia UI framework).
Installation instructions for all supported platforms can be found in the project's Github Wiki at https://github.com/sqrldev/SQRLDotNetClient/wiki.
Enjoy your new SQRL Client!
An implementation of the full client protocol for SQRL written in .Net Core, fully cross-platform too (Win, Nix, Mac).
Install-Package SQRLClientLib
This is a .Net Core 3.1 library so you will need a compatible project.
Almost all of the library's functionality can be accessed by simply calling the static methods of the SQRL
class:
/* Import the library's namespace */
using SQRLUtilsLib;
/*
There is no need to instanciate the library
if no CPS server is needed. Just call the
static functions of the SQRL class.
*/
var rc = SQRL.CreateRescueCode();
/*
Create an instance of the SQRL library only if
you need the CPS server functionality. The SQRL
class follows the singleton pattern, so instead
of calling the constructor, you call the library's
GetInstance method, passing in true if you want
to immediately start the CPS server, or false
otherwise.
*/
SQRL sqrlLib = SQRL.GetInstance(true);
//Creates a new Identity object
SQRLIdentity newIdentity = new SQRLUtilsLib.SQRLIdentity();
//Generates a Identity Unlock Key
var iuk = SQRL.CreateIUK();
// Generaties a Rescue Code
var rescueCode = SQRL.CreateRescueCode();
// Used to report progress when encrypting / decrypting (progress bar maybe)
var progress = new Progress<KeyValuePair<int, string>>(percent =>
{
Console.WriteLine($"{percent.Value}: {percent.Key}%");
});
newIdentity = await SQRL.GenerateIdentityBlock1(iuk, "My-Awesome-Password", newIdentity, progress);
newIdentity = await SQRL.GenerateIdentityBlock2(iuk, rescueCode, newIdentity, progress);
SQRLIdentity newIdentity = SQRLIdentity.FromFile(@"C:\Temp\identiy.sqrl");
//Creates a new Identity object
string identityTxt = "KKcC 3BaX akxc Xwbf xki7 k7mF GHhg jQes gzWd 6TrK vMsZ dBtB pZbC zsz8 cUWj DtS2 ZK2s ZdAQ 8Yx3 iDyt QuXt CkTC y6gc qG8n Xfj9 bHDA 422";
string rescueCode = "119887487132283883187570";
string password = "Zingo-Bingo-Slingo-Dingo";
//Reports progress while decrypting / encrypting the identity
var progress = new Progress<KeyValuePair<int, string>>(percent =>
{
Console.WriteLine($"{percent.Value}: {percent.Key}%");
});
// Decodes the identity from text import
SQRLIdentity newIdentity = await SQRL.DecodeSqrlIdentityFromText(identityTxt, rescueCode, password, progress);
newIdentity.WriteToFile(@"C:\Temp\My-SQRL-Identity.sqrl");
//Have an existing Identity object (somehow)
SQRLIdentity existingIdentity = ...
//Reports progress while decrypting / encrypting the identity it is optional
var progress = new Progress<KeyValuePair<int, string>>(percent =>
{
Console.WriteLine($"{percent.Value}: {percent.Key}%");
});
//Re-Keys the existing identity object and returns a tuple of your new rescue code and the new identity (which now contains a new entry in block3 )
var reKeyResponse = await SQRL.RekeyIdentity(existingIdentity, rescueCode, "My-New-Even-Better-Password", progress);
Console.WriteLine($"New Rescue Code: {reKeyResponse.Key}");
var NewlyReKeyedIdentity = reKeyResponse.Value;
//Reports progress while decrypting / encrypting the identity
var progress = new Progress<KeyValuePair<int, string>>(percent =>
{
Console.WriteLine($"{percent.Value}: {percent.Key}%");
});
//Have an existing identity (somehow)
SQRLIdentity existingIdentity = ...
//Returns an object containing a boolean sucess indicator, the IMK and the ILK
var block1DecryptedData = await SQRL.DecryptBlock1(existingIdentity, "My-Awesome-Password", progress);
if (block1DecryptedData.DecryptionSucceeded)
{
//This is the site's Key-Pair for signing requests
Sodium.KeyPair siteKP = SQRL.CreateSiteKey(
new Uri("sqrl://sqrl.grc.com/cli.sqrl?nut=fXkb4MBToCm7"), "Alt-ID-If-You-Want-One", block1DecryptedData.Imk);
}
else
throw new Exception("Invalid password, failed to decrypt");
Assumes you have a valid SiteKeyPair
//SQRL url
Uri sqrlUrl = new Uri("sqrl://sqrl.grc.com/cli.sqrl?nut=fXkb4MBToCm7");
//SQRL client options include CPS, SUK, HARDLOCK, NOIPTEST,SQRLONLY
SQRLOptions opts = new SQRLOptions(SQRLOptions.SQRLOpts.CPS | SQRLOptions.SQRLOpts.SUK | SQRLOptions.SQRLOpts.);
/*
Generates a query command and sends it to the server, requires that you have a valid site keypair.
Returns a "SQRLServerResponse" object which contains all pertinent data of the response from the server.
*/
var serverRespose = SQRL.GenerateQueryCommand(requestURI, siteKeyPair, opts, null, 0, priorSiteKeyPairs);
if (serverRespose.HasAsk) //Returns true if server sent "Ask"
{
Console.WriteLine(serverRespose.AskMessage);
Console.WriteLine($"Enter 1 for {serverRespose.GetAskButtons[0]} or 2 for {serverRespose.GetAskButtons[1]}");
int resp;
do
{
string response = Console.ReadLine();
int.TryParse(response, out resp);
if (resp == 0)
{
Console.WriteLine("Invalid Entry, please enter 1 or 2 as shown above");
}
} while (resp == 0);
askResponse = resp;
}
StringBuilder additionalClientData = null;
if (askResponse > 0)
{
additionalClientData = new StringBuilder();
additionalClientData.AppendLineWindows($"btn={askResponse}");
}
// additionalClientData now needs to be passed in to the next command (Ident)
Assumes you have a generated SiteKeyPair. Assumes you have a decrypted ILK (Identity Lock Key) by decrypting block1.
if (!serverRespose.CurrentIDMatch) //New Account
{
//Generates a new identity with a new SUK/VUK generated from the decrypted block1
serverRespose = SQRL.GenerateNewIdentCommand(serverRespose.NewNutURL, siteKeyPair, serverRespose.FullServerRequest, decryptedData.Ilk, opts);
}
if (serverRespose.SQRLDisabled)
{
Console.WriteLine("SQRL is disabled, to continue you must enable it. Do you want to? (Y/N)");
if (Console.ReadLine().StartsWith("Y", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("Enter your Rescue Code (no sapces or dashes)");
string rescueCode = Console.ReadLine().Trim();
progress = new Progress<KeyValuePair<int, string>>(percent =>
{
Console.WriteLine($"Decrypting with Rescue Code: {percent.Key}%");
});
var decryptionResult = await SQRL.DecryptBlock2(newId, rescueCode, progress);
if (decryptionResult.DecryptionSucceeded)
{
byte[] ursKey = null;
ursKey = SQRL.GetURSKey(decryptionResult.Iuk, Sodium.Utilities.Base64ToBinary(serverRespose.SUK, string.Empty, Sodium.Utilities.Base64Variant.UrlSafeNoPadding));
decryptionResult.Iuk.ZeroFill(); // Overwrite IUK so that we leave no traces of it in RAM
serverRespose = SQRL.GenerateEnableCommand(serverRespose.NewNutURL, siteKeyPair, serverRespose.FullServerRequest, ursKey, additionalClientData, opts);
}
else
{
throw new Exception("Failed to decrypt block 2, invalid rescue code");
}
}
}
// Instantiate the sqrl library to get the
// CPS server functionality
SQRL sqrlInstance = SQRL.GetInstance(true);
Console.WriteLine("This will disable all use of this SQRL identity on the server, are you sure you want to proceed?: (Y/N)");
if (Console.ReadLine().StartsWith("Y", StringComparison.OrdinalIgnoreCase))
{
serverRespose = SQRL.GenerateSQRLCommand(SQRLCommands.disable, serverRespose.NewNutURL, siteKeyPair, serverRespose.FullServerRequest, additionalClientData, opts);
if (sqrlInstance.cps != null && sqrlInstance.cps.PendingResponse)
{
sqrlInstance.cps.cpsBC.Add(sqrlInstance.cps.Can);
}
}
// Instantiate the sqrl library to get the
// CPS server functionality
SQRL sqrlInstance = SQRL.GetInstance(true);
Console.WriteLine("Enter your rescue code (no sapces or dashes)");
string rescueCode = Console.ReadLine().Trim();
progress = new Progress<KeyValuePair<int, string>>(percent =>
{
Console.WriteLine($"Decrypting with rescue code: {percent.Key}%");
});
var decryptionResult = await SQRL.DecryptBlock2(newId, rescueCode);
if (decryptionResult.DecryptionSucceeded)
{
byte[] ursKey = SQRL.GetURSKey(decryptionResult.Iuk, Sodium.Utilities.Base64ToBinary(serverRespose.SUK, string.Empty, Sodium.Utilities.Base64Variant.UrlSafeNoPadding));
decryptionResult.Iuk.ZeroFill(); // Overwrite IUK so that we leave no traces of it in RAM
serverRespose = SQRL.GenerateSQRLCommand(SQRLCommands.remove, serverRespose.NewNutURL, siteKeyPair, serverRespose.FullServerRequest, additionalClientData, opts, null, ursKey);
if (sqrlInstance.cps != null && sqrlInstance.cps.PendingResponse)
{
sqrlInstance.cps.cpsBC.Add(sqrlInstance.cps.Can);
}
}
else
throw new Exception("Failed to decrypt block 2, invalid rescue code");
Sends an "Ident" command along with new SUK/VUK and prior URS to replace identity.
if(serverRespose.PreviousIDMatch)
{
byte[] ursKey = null;
ursKey = SQRL.GetURSKey(serverRespose.PriorMatchedKey.Key, Sodium.Utilities.Base64ToBinary(serverRespose.SUK, string.Empty, Sodium.Utilities.Base64Variant.UrlSafeNoPadding));
serverRespose = SQRL.GenerateIdentCommandWithReplace(serverRespose.NewNutURL, siteKeyPair, serverRespose.FullServerRequest, decryptedData.Ilk, ursKey, serverRespose.PriorMatchedKey.KeyPair, opts);
}
Any serverResponse can be dealt with via CPS, if CPS is enabled and has a "pendingRequest".
// Instantiate the sqrl library to get the
// CPS server functionality
SQRL sqrlInstance = SQRL.GetInstance(true);
var serverRespose = SQRL.GenerateSQRLCommand(SQRLCommands.ident, serverRespose.NewNutURL, siteKeyPair, serverRespose.FullServerRequest, additionalClientData, opts);
if (sqrlInstance.cps != null && sqrlInstance.cps.PendingResponse)
{
sqrlInstance.cps.cpsBC.Add(new Uri(serverRespose.SuccessUrl));
}