Система аутентификации, встроенная в JC-WebClient необходима для последующего создания защищенного соединения и собственно аутентификации. Она может быть заменена на любую аналогичную систему или не использоваться вовсе. В предложенном варианте она основана на протоколе Handshake и поддерживает два вида аутентификации: взаимную и одностороннюю.
Примечание
Для успешного взаимодействия клиента с сервером при аутентификации и защищённой передаче данных важно, чтобы и на стороне клиента, и в криптографическом компоненте сервера использовались одинаковые наборы параметров эллиптических кривых согласно 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()
для
взаимной аутентификации, и unilateralAuthenticationBegin()
и
unilateralAuthenticationContinue()
для односторонней соответственно.В подразделах ниже будут рассмотрены основные особенности каждого из видов аутентификации.
Взаимная аутентификация предполагает обмен сертификатами между клиентом и
сервером и создание на их основе ключа согласования (Протокол Диффи—Хеллмана). Взаимодействие
между клиентом и сервером происходит по следующей схеме с использованием
функций establishSChannelBegin()
и establishSChannelContinue()
:
client_random
и server_random
).pre_master secret
, шифрует ее и
передает серверу.pre_master secret
, client_random
и
server_random
формируют master secret
сессии.Пример реализации взаимной аутентификации на стороне клиента
// Аутентификация пользователя
$("#loginButton").click(function () {
// Если не выбран сертификат, выдать ошибку
if ($("#certSelect").attr("selectedIndex") == -1) {
jAlert("Выберите сертификат");
return;
}
// Получить объект, ассоциированный с элементом списка сертификатов
var certHandle = $.data($("#certSelect option:selected")[0], "jcWebClientData");
try {
// Прочитать открытый ключ (пример вызова функции)
var publicKey = JCWebClient().readPublicKey(certHandle.tokenID,
certHandle.certID);
// Если было выполнено предъявление PIN-кода, отменить авторизацию
if (JCWebClient().getLoggedInState()[0] != STATE_NOT_BINDED) {
JCWebClient().unbindToken();
}
// Предъявить PIN-код
JCWebClient().bindToken(certHandle.tokenID, $('#pinInput').val());
// Начать установку защищенного соединения
var clientData = JCWebClient().establishSChannelBegin(certHandle.certID);
// Данные для отправки на сервер
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 (JCWebClient().getLoggedInState()[0] != STATE_SECURE_CHANNEL_ESTABLISHED)
{
// Продолжить установку канала
clientData = JCWebClient().establishSChannelContinue(dataFromServer,
connectionID);
// Если защищенный канал установлен выйти из цикла
if (JCWebClient().getLoggedInState()[0] == STATE_SECURE_CHANNEL_ESTABLISHED)
{
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) {
jAlert(JCWebClient().getErrorMessage(JCWebClient().getLastError()));
//jAlert("Произошла ошибка" + error);
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()
:
Пример реализации односторонней аутентификации на стороне клиента
// Односторонняя аутентификация пользователя
$("#uniauthButton").click(function () {
// Если не выбран сертификат, выдать ошибку
if ($("#uniauthCertSelect").attr("selectedIndex") == -1) {
jAlert("Выберите сертификат");
return;
}
// Получить объект, ассоциированный с элементом списка сертификатов
var certHandle = $.data($("#uniauthCertSelect option:selected")[0],
"jcWebClientData");
try {
// Если было выполнено предъявление PIN-кода, отменить авторизацию
if (JCWebClient().getLoggedInState()[0] != STATE_NOT_BINDED) {
JCWebClient().unbindToken();
}
// Предъявить PIN-код
JCWebClient().bindToken(certHandle.tokenID, $('#uniauthPinInput').val());
// Начать одностороннюю аутентификацию
var clientData = JCWebClient().unilateralAuthenticationBegin(certHandle.certID);
// Данные для отправки на сервер
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 (JCWebClient().getLoggedInState()[0] !=
UNILATERAL_AUTHENTICATION_COMPLETE) {
// Продолжить аутентификацию
clientData = JCWebClient().unilateralAuthenticationContinue(dataFromServer,
connectionID);
// Если аутентификацию прошла успешно выйти из цикла
if (JCWebClient().getLoggedInState()[0] ==
UNILATERAL_AUTHENTICATION_COMPLETE) {
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) {
jAlert(JCWebClient().getErrorMessage(JCWebClient().getLastError()));
//jAlert("Произошла ошибка" + error);
return;
}
jAlert("Аутентификация прошла успешно");
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;
}