Для аутентификации будем использовать старую добрую майкрософтовскую библиотеку capicom.
Описание:
https://msdn.microsoft.com/en-us/library/windows/desktop/aa376489(v=vs.85).aspx
https://msdn.microsoft.com/ru-ru/library/windows/desktop/aa376542(v=vs.85).aspx
https://msdn.microsoft.com/ru-ru/library/windows/desktop/aa376525(v=vs.85).aspx
Так как вся работа с ЭЦП происходит на стороне клиента, изобретать велосипед не будем, воспользуемся готовой js оболочкой для работы с capicom:
https://github.com/Infotecs/Capicom
немного доработав ее для работы не только с файлами, но и для работы со строковым хешем:
/**
* Методы для работы с CAPICOM
*
* http://www.microsoft.com/ru-ru/download/details.aspx?id=3207
*/
(function($) {
$.capicom = $.capicom || {};
// Cryptography enumerations
// http://msdn.microsoft.com/en-us/library/aa380250(v=vs.85).aspx
$.capicom.CAPICOM_CURRENT_USER_STORE = 2; // http://msdn.microsoft.com/en-us/library/aa375743(v=vs.85).aspx
$.capicom.CAPICOM_STORE_OPEN_READ_ONLY = 0; // http://msdn.microsoft.com/en-us/library/aa375747(v=vs.85).aspx
$.capicom.CAPICOM_CERTIFICATE_FIND_TIME_VALID = 9; // http://msdn.microsoft.com/en-us/library/aa375642(v=vs.85).aspx
$.capicom.CAPICOM_CERTIFICATE_FIND_SHA1_HASH = 0; // http://msdn.microsoft.com/en-us/library/aa375642(v=vs.85).aspx
$.capicom.CAPICOM_CERT_INFO_SUBJECT_SIMPLE_NAME = 0; // http://msdn.microsoft.com/en-us/library/aa375652(v=vs.85).aspx
$.capicom.CAPICOM_AUTHENTICATED_ATTRIBUTE_SIGNING_TIME = 0; // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375631(v=vs.85).aspx
$.capicom.CAPICOM_ENCODE_BASE64 = 0; // http://msdn.microsoft.com/en-us/library/aa375673(v=vs.85)
$.capicom.CAPICOM_ENCODE_BINARY = 1; // http://msdn.microsoft.com/en-us/library/aa375673(v=vs.85)
$.capicom.CAPICOM_VERIFY_SIGNATURE_AND_CERTIFICATE = 1; // http://msdn.microsoft.com/en-us/library/aa375740(v=vs.85).aspx
$.capicom.CAPICOM_E_CANCELLED = -2138568446;
$.capicom.CAPICOM_E_NOT_INSTALLED = -2146827859;
/**
* Получение списка действительных сертификатов
*/
$.capicom.getCertificatesList = function() {
try {
// инициализация объекта CAPICOM.Store: предоставляет методы для работы с хранилищем сертификатов
var myStore = new ActiveXObject("CAPICOM.Store");
// открывает хранилище персональных сертификатов
myStore.Open($.capicom.CAPICOM_CURRENT_USER_STORE, "My", $.capicom.CAPICOM_STORE_OPEN_READ_ONLY);
// поиск всех действующих сертификатов (фильтр по дате)
// доступные фильтры: http://msdn.microsoft.com/en-us/library/aa375642(v=vs.85).aspx
var filteredCertificates = myStore.Certificates.Find($.capicom.CAPICOM_CERTIFICATE_FIND_TIME_VALID);
var result = [];
for ( var i = 1; i <= filteredCertificates.Count; i++) {
var cert = filteredCertificates.Item(i);
var certInfo = {
thumbprint : cert.Thumbprint, // строка, содержащая SHA-1 хеш от сертификата
displayName : cert.GetInfo($.capicom.CAPICOM_CERT_INFO_SUBJECT_SIMPLE_NAME)
};
result.push(certInfo);
}
return result;
} catch (e) {
return [];
}
};
/**
* Вывод диалогового окна для выбора сертификата
*/
$.capicom.getDialogBoxCertificates = function() {
// инициализация объекта CAPICOM.Store: предоставляет методы для работы с хранилищем сертификатов
var myStore = new ActiveXObject("CAPICOM.Store");
// открывает хранилище персональных сертификатов
myStore.Open($.capicom.CAPICOM_CURRENT_USER_STORE, "My", $.capicom.CAPICOM_STORE_OPEN_READ_ONLY);
var filteredCertificates = myStore.Certificates.Find($.capicom.CAPICOM_CERTIFICATE_FIND_TIME_VALID);
if (filteredCertificates.Count > 1) {
var certificates = myStore.Certificates.Select();
return certificates.Item(1);
} else {
return filteredCertificates.Item(1);
}
}
/**
* Поиск сертификата в хранилище сертификатов
*
* @param hash отпечаток сертиката
*/
$.capicom.findCertificateByHash = function(hash) {
try {
// инициализация объекта CAPICOM.Store: предоставляет методы для работы с хранилищем сертификатов
var store = new ActiveXObject("CAPICOM.Store");
// открывает хранилище персональных сертификатов
store.Open($.capicom.CAPICOM_CURRENT_USER_STORE, "My", $.capicom.CAPICOM_STORE_OPEN_READ_ONLY);
// поиск сертификатов, хеш которых соответствует заданному отпечатку (thumbprint), в хранилище сертификатов
var filteredCertificates = store.Certificates.Find($.capicom.CAPICOM_CERTIFICATE_FIND_SHA1_HASH, hash);
// инициализация объекта CAPICOM.Signer: для указания ключа подписи
var signer = new ActiveXObject("CAPICOM.Signer");
signer.Certificate = filteredCertificates.Item(1);
return signer;
} catch (e) {
if (e.number != CAPICOM_E_CANCELLED) {
return new ActiveXObject("CAPICOM.Signer");
}
}
};
/**
* Подпись бинарных данных
*
* @param rawData бинарные данные для подписи
* @param detached флаг открепленной подписи
* @param cert_hash отпечаток сертификата для подписи
* @return byteArray
*/
$.capicom.signBin = function(rawData, detached, cert_hash) {
try {
// инициализация объекта CAPICOM.SignedData: предоставляет методы для создания и верификации подписи
var signedData = new ActiveXObject("CAPICOM.SignedData");
// инициализация объекта CAPICOM.Utilities
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa388176(v=vs.85).aspx
var utils = new ActiveXObject("CAPICOM.Utilities");
// подписываемые данные
signedData.Content = rawData;
// поиск сертификата ключа подписи в хранилище сертификатов
var signer = $.capicom.findCertificateByHash(cert_hash);
// время подписи
var timeAttribute = new ActiveXObject("CAPICOM.Attribute");
var today = new Date();
timeAttribute.Name = $.capicom.CAPICOM_AUTHENTICATED_ATTRIBUTE_SIGNING_TIME;
timeAttribute.Value = today.getVarDate();
today = null;
signer.AuthenticatedAttributes.Add(timeAttribute);
// возов метода SignedData.Sign
// signer - сертификат ключа подписи
// detached - флаг открепленной подписи (исходное сообщение не включается в итоговый CMS-контейнер)
// detached = false - прикрепленная подпись (исходное сообщение вкючено в CMS-контейнер)
// CAPICOM_ENCODE_BINARY - подпись будет сформирована в виде бинарной последовательности
// http://msdn.microsoft.com/en-us/library/aa387726(v=vs.85)
var signature = signedData.Sign(signer, detached, $.capicom.CAPICOM_ENCODE_BINARY);
// конвертируем полученную подпись в ByteArray для дальнейшего сохранения в файловой системе
return utils.BinaryStringToByteArray(signature);
} catch (e) {
return false;
}
};
/**
* Подпись данных
*
* @param rawData бинарные данные для подписи
* @param detached флаг открепленной подписи
* @param cert_hash отпечаток сертификата для подписи
* @return byteArray
*/
$.capicom.signBase64 = function(rawData, detached, cert_hash) {
try {
// инициализация объекта CAPICOM.SignedData: предоставляет методы для создания и верификации подписи
var signedData = new ActiveXObject("CAPICOM.SignedData");
// подписываемые данные
signedData.Content = rawData;
// поиск сертификата ключа подписи в хранилище сертификатов
var signer = $.capicom.findCertificateByHash(cert_hash);
//console.log(signer.Certificate.SubjectName);
// время подписи
var timeAttribute = new ActiveXObject("CAPICOM.Attribute");
var today = new Date();
timeAttribute.Name = $.capicom.CAPICOM_AUTHENTICATED_ATTRIBUTE_SIGNING_TIME;
timeAttribute.Value = today.getVarDate();
today = null;
signer.AuthenticatedAttributes.Add(timeAttribute);
// вызов метода SignedData.Sign
// signer - сертификат ключа подписи
// detached - флаг открепленной подписи (исходное сообщение не включается в итоговый CMS-контейнер)
// detached = false - прикрепленная подпись (исходное сообщение вкючено в CMS-контейнер)
// CAPICOM_ENCODE_BASE64 - подпись будет сформирована в виде кодированной Base64 строки
// http://msdn.microsoft.com/en-us/library/aa387726(v=vs.85)
var signature = signedData.Sign(signer, detached, $.capicom.CAPICOM_ENCODE_BASE64);
return signature;
} catch (e) {
return false;
}
};
/**
* Проверка подписи
*
* @param signature CMS-контейнер
*/
$.capicom.verifyBin = function(signature) {
// инициализация объекта CAPICOM.SignedData: предоставляет методы для создания и верификации подписи
var signedData = new ActiveXObject("CAPICOM.SignedData");
var utils = new ActiveXObject("CAPICOM.Utilities");
try {
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa387728(v=vs.85).aspx
// signature - cms-конейнер, содержащий подпись
// CAPICOM_VERIFY_SIGNATURE_AND_CERTIFICATE - проверка хеша и сертифката
var res = signedData.Verify(signature, false, $.capicom.CAPICOM_VERIFY_SIGNATURE_AND_CERTIFICATE);
var result = {
rawData: utils.BinaryStringToByteArray(signedData.Content), // извлекаем исходные данные
success: true,
};
} catch(e) {
// сообщение об ошибке в случае невалидной подписи
var result = {
error: e,
success: false
};
}
return result;
};
/**
* Проверка подписи
*
* @param signature CMS-контейнер
*/
$.capicom.verifyBase64 = function(signature) {
// инициализация объекта CAPICOM.SignedData: предоставляет методы для создания и верификации подписи
var signedData = new ActiveXObject("CAPICOM.SignedData");
try {
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa387728(v=vs.85).aspx
// signature - cms-конейнер, содержащий подпись
// CAPICOM_VERIFY_SIGNATURE_AND_CERTIFICATE - проверка хеша и сертифката
var res = signedData.Verify(signature, false, $.capicom.CAPICOM_VERIFY_SIGNATURE_AND_CERTIFICATE);
console.log(signedData.Content);
var result = {
rawData: signedData.Content, // извлекаем исходные данные
success: true,
};
} catch(e) {
// сообщение об ошибке в случае невалидной подписи
var result = {
error: e,
success: false
};
}
return result;
};
})(jQuery);
Использование данного модуля происходит следующим способом:
– подпись строки
jQuery(document).ready(function() {
if (!("ActiveXObject" in window)){
alert ("Ваш браузер не поддерживает возможность работы с ЭЦП.");
}
var certificate = $.capicom.getDialogBoxCertificates();
var source_string = "Какая-то строка, которую нужно подписать;
var signer_hash = certificate.thumbprint;
var signature = $.capicom.signBase64(source_string, false, signer_hash);
$.ajax({
method: "POST",
url: "/save-signature",
data: {
signature: signature,
}
})
.done(function( data ) {
alert ("Подпись сохранена.");
});
});
– проверка подписи
var result = $.capicom.verifyBase64(signature);
if (result.success) {
// подпись верна
alert('Подпись верна.');
} else {
// подпись неверна
alert('Подпись неверна. ' + result.error);
}