Система аутентификации, встроенная в JC-WebClient необходима для последующего создания защищенного соединения и собственно аутентификации. Она может быть заменена на любую аналогичную систему или не использоваться вовсе. В предложенном варианте она основана на протоколе Handshake и поддерживает два вида аутентификации: взаимную и одностороннюю.
Важно
Система аутентификации поддерживается только для токенов GOST и PRO.
Примечание
Для успешного взаимодействия клиента с сервером при аутентификации и защищённой передаче данных важно, чтобы и на стороне клиента, и в криптографическом компоненте сервера использовались одинаковые наборы параметров эллиптических кривых согласно RFC 4357. В частности (только для токена GOST):
id-GostR3410-2001-CryptoPro-A-ParamSet
id-GostR3410-2001-CryptoPro-B-ParamSet
id-GostR3410-2001-CryptoPro-C-ParamSet
id-GostR3410-2001-CryptoPro-XchA-ParamSet
id-GostR3410-2001-CryptoPro-XchB-ParamSet
В рамках пакета SDK поставляется готовая реализация аутентификации на стороне
сервера в виде библиотеки sslServer.dll
, но ее рекомендуется использовать
не в качестве готового решения, а в качестве примера исходного кода. Данный
раздел описывает основные принципы устройства подобной системы, которыми можно
руководствоваться при создании системы аутентификации для каждого конкретного
Web-приложения.
Общие принципы реализации системы аутентификации
В подразделах ниже будут рассмотрены основные особенности каждого из видов аутентификации.
Взаимная аутентификация предполагает обмен сертификатами между клиентом и сервером и создание на их основе ключа согласования (Протокол Диффи—Хеллмана). Взаимодействие между клиентом и сервером происходит по следующей схеме с использованием команд establishSChannelBegin и establishSChannelContinue:
client_random
и server_random
).pre_master secret
, шифрует ее и
передает серверу.pre_master secret
, client_random
и
server_random
формируют master secret
сессии.Пример реализации взаимной аутентификации на стороне клиента
// Идентификатор токена
var tokenID = 0;
// Идентификатор контейнера
var contID = 0;
try {
// Прочитать открытый ключ
var publicKey = JCWebClient2.readPublicKey({
args: {
tokenID: tokenID,
contID: contID
}
});
// Если было выполнено предъявление PIN-кода, отменить авторизацию
if (JCWebClient2.getLoggedInState().state != JCWebClient2.Vars.AuthState.notBinded) {
JCWebClient2.unbindToken();
}
// Предъявить PIN-код
JCWebClient2.bindToken({
args: {
tokenID: tokenID,
pin: "my pin"
}
});
// Начать установку защищенного соединения
var clientData = JCWebClient2.establishSChannelBegin({
args: {
contID: contID
}
});
// Данные для отправки на сервер
clientData = { clientData: clientData };
// Сериализовать в JSON
clientData = $.JSON.encode(clientData);
// Оправить данные на сервер
$.ajax({
async: false,
type: "POST",
url: "WebService.asmx/loginBegin",
data: clientData,
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (msg) {
dataFromServer = msg.d.serverData;
connectionID = msg.d.id;
ajaxError = false;
},
err: function (a, b, c) {
alert(a + ';' + b + ';' + c);
ajaxError = true;
}
});
if (ajaxError == true) {
throw "";
}
// Если защищенный канал еще не установлен
while (JCWebClient2.getLoggedInState().state != JCWebClient2.Vars.AuthState.secureChannelEstablished) {
// Продолжить установку канала
clientData = JCWebClient2.establishSChannelContinue({
args: {
connectionID: connectionID,
serverData: dataFromServer
}
});
// Если защищенный канал установлен выйти из цикла
if (JCWebClient2.getLoggedInState().state == JCWebClient2.Vars.AuthState.secureChannelEstablished) {
break;
}
// Данные для отправки на сервер
clientData = { clientData: clientData, connectionID: connectionID };
// Сериализовать в JSON
clientData = $.JSON.encode(clientData);
// Оправить данные на сервер
$.ajax({
async: false,
type: "POST",
url: "WebService.asmx/loginContinue",
data: clientData,
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (msg) {
dataFromServer = msg.d.serverData;
ajaxError = false;
},
err: function (a, b, c) {
alert(a + ';' + b + ';' + c);
ajaxError = true;
}
});
if (ajaxError == true) {
throw "";
}
}
}
catch (error) {
console.log("Произошла ошибка: " + error.message);
return;
}
// Перейти на страницу пользователя
window.location = "./userHome.aspx"
Пример реализации взаимной аутентификации на стороне сервера (C#)
// Начать аутентификацию пользователя.
// Обработка данных, полученных от establishSChannelBegin
[WebMethod()]
public LoginBeginReturnVal loginBegin(byte[] clientData)
{
// Получить случайный идентификатор соединения
Random rng = new Random();
int id = rng.Next();
string serverType = WebConfigurationManager.AppSettings["serverType"];
// Создать объект SSL-соединения
SSLServer server;
if (serverType == "OSSL")
{
serverCertFileName = String.Format(@"{0}App_Data\server.pem",
Server.MapPath("~"));
server = new SSLServer(serverCertFileName,
serverCertPassword,
SSLServer.osslServer);
}
else if (serverType == "SSPI")
{
server = new SSLServer(serverCertSubjectName, null, SSLServer.sspiServer);
}
else
{
throw new System.Exception("Invalid server configuration");
}
// Данные для клиента для установки защищенного соединения
byte[] serverData;
// Начать установку защищенного соединения
server.accept(clientData, out serverData);
// Создать объект соединения с клиентом
ConnectionState state = new ConnectionState(server);
// Сохранить созданный объект
connectionStates.Add(id, state);
// Сформировать выходной объект
LoginBeginReturnVal retVal = new LoginBeginReturnVal();
retVal.id = id;
retVal.serverData = serverData;
return retVal;
}
// Продолжить аутентификацию пользователя
// Обработка данных, полученных от establishSChannelContinue
[WebMethod()]
public LoginContinueReturnVal loginContinue(int connectionID, byte[] clientData)
{
// Получить объект соединения
ConnectionState state = getConnection(connectionID);
// Получить объект SSL-сервера
SSLServer server = state.server;
// Проверить не произведена ли уже аутентификация
if (state.userLoggedIn == true)
{
throw new System.Exception("Already logged in");
}
// Данные для клиента для установки защищенного соединения
byte[] serverData;
// Продолжить установку защищенного соединения
int res = server.accept(clientData, out serverData);
// Если защищенный канал установлен
if (res == SSLServer.tlsErrorSuccess)
{
// Получить открытый ключ клиента
byte[] pubKey = server.getPeerPublicKey();
// Преобразовать в base64
string encodedPubKey = Convert.ToBase64String(pubKey,
Base64FormattingOptions.None);
// SQL соединение
SqliteConnection conn;
// Запрос SQL
SqliteCommand cmd;
SqliteDataReader rdr;
// Соединение с базой Database из web.config
conn = new SqliteConnection(
DemoBank2.Global.GetConnectionString(Server.MapPath("~")));
// Получить имя пользователя из таблицы UserData, соответствующее открытому
// ключу
cmd = new SqliteCommand(" SELECT UserName FROM UserData WHERE PublicKey='" +
encodedPubKey +
"'", conn);
cmd.CommandType = CommandType.Text;
using (conn)
{
conn.Open();
rdr = cmd.ExecuteReader();
// Удалось ли прочитать имя пользователя
if (rdr.Read())
{
// Записать имя пользователя в объект соединения
state.userName = (String)rdr["UserName"];
// Аутентификация выполнена
state.userLoggedIn = true;
}
else
{
// Пользователь с таким открытым ключем не зарегистрирован
throw new System.Exception("Connection failed");
}
}
}
// Сформировать выходной объект
LoginContinueReturnVal retVal = new LoginContinueReturnVal();
retVal.serverData = serverData;
return retVal;
}
В данном случае односторонняя аутентификация – это аутентификация клиента сервером. Взаимодействие между клиентом и сервером происходит по следующей схеме с использованием команд unilateralAuthenticationBegin и unilateralAuthenticationContinue:
Пример реализации односторонней аутентификации на стороне клиента
// Идентификатор токена
var tokenID = 0;
// Идентификатор контейнера
var contID = 0;
try {
// Если было выполнено предъявление PIN-кода, отменить авторизацию
if (JCWebClient2.getLoggedInState().state != JCWebClient2.Vars.AuthState.notBinded) {
JCWebClient2.unbindToken();
}
// Предъявить PIN-код
JCWebClient2.bindToken({
args: {
tokenID: tokenID,
pin: "my pin"
}
});
// Начать одностороннюю аутентификацию
var clientData = JCWebClient2.unilateralAuthenticationBegin({
args: {
contID: contID
}
});
// Данные для отправки на сервер
clientData = { clientData: clientData };
// Сериализовать в JSON
clientData = $.JSON.encode(clientData);
// Оправить данные на сервер
$.ajax({
async: false,
type: "POST",
url: "WebService.asmx/unilateralAuthenticationBegin",
data: clientData,
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (msg) {
dataFromServer = msg.d.serverData;
connectionID = msg.d.id;
ajaxError = false;
},
err: function (a, b, c) {
alert(a + ';' + b + ';' + c);
ajaxError = true;
}
});
if (ajaxError == true) {
throw "";
}
// Если аутентификация не пройдена
while (JCWebClient2.getLoggedInState().state != JCWebClient2.Vars.AuthState.unilateralAuthenticationComplete) {
// Продолжить аутентификацию
clientData = JCWebClient2.unilateralAuthenticationContinue({
args: {
connectionID: connectionID,
serverData: dataFromServer
}
});
// Если аутентификацию прошла успешно выйти из цикла
if (JCWebClient2.getLoggedInState().state == JCWebClient2.Vars.AuthState.unilateralAuthenticationComplete) {
break;
}
// Данные для отправки на сервер
clientData = { clientData: clientData, connectionID: connectionID };
// Сериализовать в JSON
clientData = $.JSON.encode(clientData);
// Оправить данные на сервер
$.ajax({
async: false,
type: "POST",
url: "WebService.asmx/unilateralAuthenticationContinue",
data: clientData,
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (msg) {
dataFromServer = msg.d.serverData;
ajaxError = false;
},
err: function (a, b, c) {
alert(a + ';' + b + ';' + c);
ajaxError = true;
}
});
if (ajaxError == true) {
throw "";
}
}
}
catch (error) {
console.log("Произошла ошибка: " + error.message);
return;
}
// Перейти на страницу пользователя
window.location = "./userHome.aspx";
Пример реализации односторонней аутентификации на стороне сервера (C#)
// Начать одностороннюю аутентификацию пользователя.
// Обработка данных, полученных от unilateralAuthenticationBegin
[WebMethod()]
public LoginBeginReturnVal unilateralAuthenticationBegin(byte[] clientData)
{
// Получить случайный идентификатор соединения
Random rng = new Random();
int id = rng.Next();
string serverType = WebConfigurationManager.AppSettings["serverType"];
// Создать объект SSL-соединения
SSLServer server;
if (serverType == "OSSL")
{
serverCertFileName = String.Format(@"{0}App_Data\server.pem",
Server.MapPath("~"));
server = new SSLServer(serverCertFileName,
serverCertPassword,
SSLServer.osslServer);
}
else if (serverType == "SSPI")
{
server = new SSLServer(serverCertSubjectName, null, SSLServer.sspiServer);
}
else
{
throw new System.Exception("Invalid server configuration");
}
// Данные для клиента для установки защищенного соединения
byte[] serverData;
// Начать установку защищенного соединения
server.accept(clientData, out serverData);
// Создать объект соединения с клиентом
ConnectionState state = new ConnectionState(server);
// Сохранить созданный объект
connectionStates.Add(id, state);
// Сформировать выходной объект
LoginBeginReturnVal retVal = new LoginBeginReturnVal();
retVal.id = id;
retVal.serverData = serverData;
return retVal;
}
// Продолжить одностороннюю аутентификацию пользователя.
// Обработка данных, полученных от unilateralAuthenticationContinue
[WebMethod()]
public LoginContinueReturnVal unilateralAuthenticationContinue(int connectionID,
byte[] clientData)
{
// Получить объект соединения
ConnectionState state = getConnection(connectionID);
// Получить объект SSL-сервера
SSLServer server = state.server;
// Данные для клиента для установки защищенного соединения
byte[] serverData;
// Продолжить установку защищенного соединения
int res = server.accept(clientData, out serverData);
// Если аутентификация пройдена
if (res == SSLServer.tlsErrorSuccess)
{
// Получить открытый ключ клиента
byte[] pubKey = server.getPeerPublicKey();
// Преобразовать в base64
string encodedPubKey = Convert.ToBase64String(pubKey,
Base64FormattingOptions.None);
// SQL соединение
SqliteConnection conn;
// Запрос SQL
SqliteCommand cmd;
SqliteDataReader rdr;
// Соединение с базой Database из web.config
conn = new SqliteConnection(
DemoBank2.Global.GetConnectionString(Server.MapPath("~")));
// Получить имя пользователя из таблицы UserData, соответствующее открытому
// ключу
cmd = new SqliteCommand(" SELECT UserName FROM UserData WHERE PublicKey='" +
encodedPubKey +
"'", conn);
cmd.CommandType = CommandType.Text;
using (conn)
{
conn.Open();
rdr = cmd.ExecuteReader();
// Удалось ли прочитать имя пользователя
if (rdr.Read())
{
// Записать имя пользователя в объект соединения
state.userName = (String)rdr["UserName"];
// Аутентификация выполнена
state.userLoggedIn = true;
}
else
{
// Пользователь с таким открытым ключем не зарегистрирован
throw new System.Exception("Connection failed");
}
}
}
// Сформировать выходной объект
LoginContinueReturnVal retVal = new LoginContinueReturnVal();
retVal.serverData = serverData;
return retVal;
}