|
| ||||||||||||
| ||||||||||||
|
2004 г. Windows и Delphi на защите секретов (часть 4)Константин Виноградов, Комиздат Что такое сертификат? Какова его роль в защите информации? И, самое главное, - как можно использовать на практике механизм сертификации? Об этом и пойдет речь в нашей статье Предположим, две стороны (назовем их по традиции Алисой и Бобом) решили обменяться открытыми ключами по незащищенному каналу связи, например по телефонной линии или интернету. Некто третий (по имени Ева) во время пересылки может подменить ключи и получить таким образом доступ к секретной переписке. Чтобы исключить эту угрозу безопасности, нужно иметь возможность подтвердить подлинность полученного открытого ключа. Для этого и предусмотрены сертификаты. Сертификаты Представим себе, что существует организация, которой Боб полностью доверяет. Назовем ее Центром сертификации (Certification Authority). Алиса отсылает свой открытый ключ в Центр сертификации (ЦС). Там у нее запрашивают документы и устанавливают, действительно ли она та особа, за которую себя выдает. Затем ЦС генерирует электронный документ, содержащий информацию об Алисе и ее открытый ключ, и подписывает его своим закрытым ключом. Этот цифровой документ и является сертификатом. Сертификат может не только служить для аутентификации личности (как в случае с Алисой), но и подтверждать подлинность оборудования, веб-сайта, организации и т.п. Поэтому набор сведений, входящих в его состав, может изменяться и удовлетворять одному из стандартов. Как правило, сертификат содержит следующие данные:
Итак, ЦС удостоверяет личность Алисы и своей подписью заверяет связь между ней и ее ключом. Поскольку сведения об Алисе подписаны, Боб может убедиться в их целостности. Для этого ему нужно обратиться в ЦС и проверить цифровую подпись, входящую в состав сертификата. Даже если Ева, о который мы упомянули вначале, перехватит сертификат и изменит сведения об Алисе, то ей придется подделывать подпись ЦС. Кроме того, Боб тоже может получить в ЦС "электронное удостоверение личности" - и тогда уверенностью в подлинности открытых ключей будут обладать оба корреспондента. Остался один невыясненный вопрос: на чем держится доверие к Центру сертификации? Если Алиса и Боб - сотрудники одной и той же организации, а переписку ведут в деловых целях, то в качестве ЦС может выступить сама организация. Для этого ей потребуется обзавестись штатным специалистом по защите информации и поручить ему выполнение всех процедур, связанных с выдачей и проверкой сертификатов. Ну а на чем должно основываться доверие фирмы к своему сотруднику, думаем, объяснять не нужно. В случае же, если Алиса и Боб - представители различных организаций, им придется обращаться в независимый центр сертификации. Деятельность таких центров, как правило, лицензируется службами безопасности страны или другими государственными органами. Например, украинские организации, работающие в сфере защиты информации, должны получить лицензию СБУ. Сертификату, выданному таким ЦС, можно доверять в той же степени, в какой вы доверяете паспорту гражданина некого государства. Центры сертификации могут находиться в различных странах, работать с различным программным обеспечением, поддерживать разные стандарты сертификатов и отличающиеся процедуры их выдачи. Поэтому возникает необходимость наладить отношения доверия между самими ЦС. Эта система доверительных отношений может строиться по иерархическому принципу. Существует небольшое число ЦС, называющихся корневыми (Root Certification Authority). Они удостоверяют сертификаты дочерних центров, которые, в свою очередь, подтверждают подлинность других ЦС и т.д. В глобальном масштабе структура ЦС представляет собой несколько иерархий (см. рис. 1). Поддержка сертификатов в CryptoAPI Рассмотрим решение основных задач, возникающих при выпуске и обслуживании сертификатов на уровне библиотеки ОС Windows - CryptoAPI. Кстати говоря, для "внутреннего" употребления сертификатов (в рамках одной организации) процедуру лицензирования проходить не нужно. Никто ведь не может запретить вам обмениваться файлами, которые по секрету от СБУ вы будете считать цифровыми документами. Чтобы обзавестись собственным сертификатом, прежде всего нужно создать запрос специального формата, который должен содержать ваше имя, открытые ключи обмена ключами и подписи. Этот запрос подписывается отправителем при помощи личного закрытого ключа и отсылается в центр сертификации. Запрос на получение сертификата Рассмотрим процесс создания запроса более подробно. Для хранения данных запроса в CryptoAPI предназначена специальная структура - CERT_REQUEST_INFO. Она содержит ссылки на другие структуры, хранящие имя владельца сертификата, информацию об открытых ключах и, возможно, некоторые дополнительные атрибуты (рис. 2). В результате для создания запроса сертификата следует выполнить следующие шаги:
Полученный в результате подписанный и закодированный запрос сертификата нужно сохранить в файле и отправить в центр сертификации. Далее приведен текст процедуры (без обработки ошибок), реализующей описанный выше алгоритм:
{SubjectEdit - поле формы, в которое
пользователь вводит текст
поля Subject сертификата; поле формы
ContainerEdit может содержать
имя контейнера ключей (если остается пустым,
используется контейнер
по умолчанию)}
procedure TCreateReqForm.OKBtnClick (Sender: TObject);
var nameAttr: CERT_RDN_ATTR;
nameString: PChar;
rdn: CERT_RDN;
nameInfo: CERT_NAME_INFO;
certReqInfo: CERT_REQUEST_INFO;
subjNameBlob: CERT_NAME_BLOB;
encNameLen: DWORD;
encName: PBYTE;
prov: HCRYPTPROV;
pubKeyInfoLen: DWORD;
pubKeyInfo: PCERT_PUBLIC_KEY_INFO;
encCertReqLen: DWORD;
params: CRYPT_OBJID_BLOB;
sigAlg: CRYPT_ALGORITHM_IDENTIFIER;
signedEncCertReq: PBYTE;
cont: PChar;
err: string;
encType: DWORD;
f: file;
begin
encType:= PKCS_7_ASN_ENCODING or X509_ASN_ENCODING;
nameString:= StrAlloc (length (SubjectEdit.text)+1);
StrPCopy (nameString, SubjectEdit.Text);
nameAttr.pszObjId:= '2.5.4.3';
nameAttr.dwValueType:= CERT_RDN_PRINTABLE_STRING;
nameAttr.Value.cbData:= length (SubjectEdit.text);
nameAttr.Value.pbData:= PBYTE (nameString);
rdn.cRDNAttr:= 1;
rdn.rgRDNAttr:= @nameAttr;
nameInfo.cRDN:= 1;
nameInfo.rgRDN:= @rdn;
{выясняем размер закодированного имени пользователя}
CryptEncodeObject (encType, X509_NAME,
@nameInfo, nil, @encNameLen) or (encNameLen < 1);
GetMem (encName, encNameLen);
{кодируем имя пользователя}
CryptEncodeObject (PKCS_7_ASN_ENCODING or
X509_ASN_ENCODING, X509_NAME, @nameInfo, encName,
@encNameLen) or (encNameLen < 1);
subjNameBlob.cbData:= encNameLen;
subjNameBlob.pbData:= encName;
certReqInfo.Subject:= subjNameBlob;
certReqInfo.cAttribute:= 0;
certReqInfo.rgAttribute:= nil;
certReqInfo.dwVersion:= CERT_REQUEST_V1;
if length (ContainerEdit.Text) = 0
then cont:= nil
else
begin
err:= ContainerEdit.Text;
cont:= StrAlloc (length (err) + 1);
StrPCopy (cont, err);
end;
CryptAcquireContext (@prov, cont, nil,
PROV_RSA_FULL, 0);
CryptExportPublicKeyInfo (prov, AT_SIGNATURE,
encType, nil, @pubKeyInfoLen);
GetMem (pubKeyInfo, pubKeyInfoLen);
CryptExportPublicKeyInfo (prov, AT_SIGNATURE,
encType, pubKeyInfo, @pubKeyInfoLen);
certReqInfo.SubjectPublicKeyInfo:= pubKeyInfo^;
FillChar (params, sizeof (params), 0);
sigAlg.pszObjId:= szOID_OIWSEC_sha1RSASign;
sigAlg.Parameters:= params;
CryptSignAndEncodeCertificate (prov, AT_SIGNATURE,
encType,
X509_CERT_REQUEST_TO_BE_SIGNED, @certReqInfo,
@sigAlg, nil, nil,
@encCertReqLen);
GetMem (signedEncCertReq, encCertReqLen);
CryptSignAndEncodeCertificate (prov, AT_SIGNATURE,
encType, X509_CERT_REQUEST_TO_BE_SIGNED,
@certReqInfo, @sigAlg, nil, signedEncCertReq,
@encCertReqLen);
if SaveDlg.Execute then
begin
AssignFile (f, SaveDlg.FileName);
rewrite (f, 1);
BlockWrite (f, signedEncCertReq^, encCertReqLen);
CloseFile (f);
end;
StrDispose (nameString);
FreeMem (encName, encNameLen);
if cont <> nil then StrDispose (cont);
FreeMem (pubKeyInfo, pubKeyInfoLen);
FreeMem (signedEncCertReq, encCertReqLen);
CryptReleaseContext (prov, 0);
end;
Итак, запрос сертификата создан, но что с ним делать? Если вам нужен сертификат, который можно использовать для организации безопасной связи с зарубежными партнерами, нужно отправить созданный запрос в центр сертификации, предоставив необходимые подтверждающие личность документы и уплатив соответствующую сумму. Если вы собираетесь обмениваться только в рамках сети предприятия, располагающей сервером Windows 2000 и выше, можно воспользоваться Microsoft Certificate Server. Если же вашей целью, например, является только отладка программ, или если группа, внутри которой вы собираетесь организовать обмен информацией, располагает лишь компьютерами под Windows 98 (и ниже), то можно подписать сертификат самостоятельно. Подписание сертификата При этом важно не забыть цель создания сертификата - он создавался, чтобы связь между именем пользователя и его открытым ключом удостоверялась подписью некоторой доверенной стороны. Такой "доверенный" пользователь внутри группы (будем называть его администратором) должен создать ключевую пару администратора и создать для нее сертификат, который он подписывает тем же закрытым ключом администратора. Этот сертификат называется корневым. Ключ администратора используется и для подписания сертификатов пользователей. Когда один пользователь хочет проверить подлинность сертификата другого пользователя, он проверяет подпись под ним администратора, используя для проверки тот самый корневой сертификат. Итак, сделаем из только что созданного запроса сертификата подписанный корневой сертификат. Это можно проделать при помощи функций CryptoAPI. Сама процедура в документации даже не упоминается, однако ее можно "вывести" из описания соответствующих функций. Проследим ее на примере программы, позволяющей открыть файл с запросом сертификата, проверить подпись под ним и выпустить подписанный сертификат с заданным сроком действия. Программа управляется формой, показанной на рис. 3.
{encType, size, rsize: DWORD;
buf: PBYTE;}
encType:= PKCS_7_ASN_ENCODING or X509_ASN_ENCODING;
GetMem (buf, 2048);
rsize:= 2048;
CryptDecodeObject (encType, X509_CERT,
reqEncoded, size, 0, buf, @rsize);
{p: pointer;
signedContent: PCERT_SIGNED_CONTENT_INFO;
DERBLOB: CRYPT_DER_BLOB;
buf2: PBYTE;
pCertReqInfo: PCERT_REQUEST_INFO;}
p:= buf;
signedContent:= p;
DERBLOB:= signedContent^.toBeSigned;
rsize:= 512;
GetMem (buf2, rsize);
CryptDecodeObject (encType,
X509_CERT_REQUEST_TO_BE_SIGNED,
DERBLOB.pbData, DERBLOB.cbData, 0, buf2, @rsize);
p:= buf2;
pCertReqInfo:= p;
{subjNameBLOB: CERT_NAME_BLOB;
subjNameString: PChar;}
subjNameBLOB:= pCertReqInfo^.Subject;
rsize:= CertNameToStr (encType, @subjNameBLOB,
CERT_SIMPLE_NAME_STR, subjNameString, 512);
subjectEdit.Text:= subjNameString;
signCheckBox.Checked:=
CryptVerifyCertificateSignature(prov, encType,
reqEncoded, size,
@(pCertReqInfo^.SubjectPublicKeyInfo));
NotBeforeEdit.Text:= DateTimeToStr (Now); NotAfterEdit.Text:= DateTimeToStr (Now + 365);
{certInfo: CERT_INFO;
serNum: int64;
params: CRYPT_OBJID_BLOB;
nameStr: PChar;
nameAttr: CERT_RDN_ATTR;
rdn: CERT_RDN;
nameInfo: CERT_NAME_INFO;}
certInfo.dwVersion:= CERT_V1; {версия 1}
serNum:= strtoint64(serNumEdit.Text);
certInfo.SerialNumber.cbData:= sizeof (serNum);
certInfo.SerialNumber.pbData:= @serNum;
FillChar (params, sizeof (params), 0);
certInfo.SignatureAlgorithm.pszObjId:=
szOID_OIWSEC_sha1RSASign;
certInfo.SignatureAlgorithm.Parameters:= params;
nameStr:= StrAlloc (length (IssuerEdit.text)+1);
StrPCopy (nameStr, IssuerEdit.Text);
nameAttr.pszObjId:= '2.5.4.3';
nameAttr.dwValueType:= CERT_RDN_PRINTABLE_STRING;
nameAttr.Value.cbData:= length (IssuerEdit.text);
nameAttr.Value.pbData:= PBYTE (nameStr);
rdn.cRDNAttr:= 1;
rdn.rgRDNAttr:= @nameAttr;
nameInfo.cRDN:= 1;
nameInfo.rgRDN:= @rdn;
{encNameLen: DWORD;
encName: PBYTE;
issNameBLOB: CERT_NAME_BLOB;}
CryptEncodeObject (encType, X509_NAME,
@nameInfo, nil, @encNameLen);
GetMem (encName, encNameLen);
CryptEncodeObject (encType, X509_NAME,
@nameInfo, encName, @encNameLen);
issNameBlob.cbData:= encNameLen;
issNameBlob.pbData:= encName;
certInfo.Issuer:= issNameBLOB;
{sysTime: TSystemTime;}
DateTimeToSystemTime (StrToDateTime
(NotBeforeEdit.Text), sysTime);
SystemTimeToFileTime (sysTime, certInfo.notBefore);
DateTimeToSystemTime (StrToDateTime
(NotAfterEdit.Text), sysTime);
SystemTimeToFileTime (sysTime,
certInfo.notAfter);
certInfo.Subject:= pCertReqInfo.Subject; certInfo.SubjectPublicKeyInfo:= pCertReqInfo.SubjectPublicKeyInfo; certInfo.IssuerUniqueId.cbData:= 0; certInfo.IssuerUniqueId.pbData:= nil; certInfo.IssuerUniqueId.cUnusedBits:= 0; certInfo.SubjectUniqueId.cbData:= 0; certInfo.SubjectUniqueId.pbData:= nil; certInfo.SubjectUniqueId.cUnusedBits:= 0; certInfo.cExtension:= 0; certInfo.rgExtension:= nil;
{encCertLen: DWORD;
encCert: PByte;}
CryptSignAndEncodeCertificate (prov, AT_SIGNATURE,
encType,
X509_CERT_TO_BE_SIGNED, @certInfo,
@(certInfo.SignatureAlgorithm),
nil, nil, @encCertLen);
GetMem (encCert, encCertLen);
CryptSignAndEncodeCertificate (prov, AT_SIGNATURE,
encType,
X509_CERT_TO_BE_SIGNED, @certInfo,
@(certInfo.SignatureAlgorithm), nil,
encCert, @encCertLen);
Хранение сертификатов Созданный сертификат владелец может отправлять своим корреспондентам для организации безопасной связи. Чтобы сделать полученный сертификат доступным системе, его нужно поместить в одно из хранилищ сертификатов. Windows предусматривает существование нескольких системных хранилищ сертификатов: MY (для хранения сертификатов отдельного пользователя), СА (от Certification Authority - для хранения сертификатов центров сертификации) и ROOT (для хранения корневых сертификатов). Открытое (загруженное в память) хранилище сертификатов представляет собой связанный список блоков данных, каждый из которых содержит ссылку на следующий блок и на данные сертификата. Каждое хранилище сертификатов физически размещается либо в отдельном файле, либо в реестре Windows. При этом для работы с системными хранилищами не нужно знать их местоположения - достаточно указать приведенные выше имена. Для помещения сохраненного в файле подписанного сертификата в системное хранилище нужно выполнить следующие шаги.
{encCertLen: DWORD; - размер закодированного сертификата
encCert: PByte; - данные сертификата
context: PCCERT_CONTEXT;
encType: DWORD;}
encType:= PKCS_7_ASN_ENCODING or X509_ASN_ENCODING;
context:= CertCreateCertificateContext
(encType, encCert, encCertLen);
{store: HCERTSTORE;}
store:= CertOpenSystemStore (0, 'MY');
n:= nil; CertAddCertificateContextToStore (store, context, CERT_STORE_ADD_REPLACE_EXISTING, n) Функции CertAddCertificateContextToStore, кроме дескрипторов хранилища и добавляемого контекста сертификата, передается параметр, определяющий действия системы в том случае, если в данном хранилище уже имеется идентичный сертификат. Использованная константа CERT_STORE_ADD_REPLACE_EXISTING предписывает в таком случае удалить старый сертификат и заменить его новым. Последний параметр функции позволяет получить указатель на указатель на копию сертификата, созданную при добавлении контекста в хранилище (если параметр равен пустому указателю, то ссылка не возвращается). CertCloseStore (store, 0); CertFreeCertificateContext (context); FreeMem (encCert, encCertLen); Просмотреть имеющиеся в хранилище сертификаты можно с помощью функции CertEnumCertificatesInStore. Ей нужно передать дескриптор нужного хранилища и, при первом вызове, пустой указатель, а при последующих - указатель на предыдущий сертификат. Например, для просмотра содержимого одного из системных хранилищ сертификатов может быть использован следующий фрагмент программы (форма, из которой он вызывается, с результатами работы показана на рис. 5):
{store: HCERTSTORE;
cont, stor: PChar;
err: string;
cert: PCCERT_CONTEXT;
nameString: PChar;
size: DWORD;
nameBLOB: CERT_NAME_BLOB;
подключение к криптопровайдеру считается выполненным!}
err:= CertStoreBox.Text;
RepMemo.Lines.Add ('');
RepMemo.Lines.Add ('====================');
RepMemo.Lines.Add ('Contents of store ' + err);
RepMemo.Lines.Add ('====================');
stor:= StrAlloc (length (err) + 1);
StrPCopy (stor, err);
store:= CertOpenSystemStore (prov, stor);
cert:= CertEnumCertificatesInStore (store, nil);
nameString:= StrAlloc (512);
while cert <> nil do
begin
RepMemo.Lines.Add ('---------------');
RepMemo.Lines.Add ('Subject:');
nameBLOB:= cert^.pCertInfo^.Subject;
size:= CertNameToStr (encType, @nameBlob,
CERT_SIMPLE_NAME_STR, nameString, 512);
if size > 1 then
RepMemo.Lines.Add (nameString)
else
RepMemo.Lines.Add ('Error');
RepMemo.Lines.Add ('Issuer:');
nameBLOB:= cert^.pCertInfo^.Issuer;
size:= CertNameToStr (encType, @nameBlob,
CERT_SIMPLE_NAME_STR,
nameString, 512);
if size > 1 then
RepMemo.Lines.Add (nameString)
else
RepMemo.Lines.Add ('Error');
cert:= CertEnumCertificatesInStore (store, cert);
end;
StrDispose (nameString);
Проверка сертификата Получив новый сертификат, конечно, следует убедиться в корректности подписи под ним. При этом сам сертификат, скорее всего, будет помещен в личное хранилище пользователя (MY), а сертификат его издателя может находиться в хранилищах CA или ROOT. Кстати, при установке Windows в эти хранилища помещается множество сертификатов признанных центров сертификации, список которых можно просмотреть при помощи приведенной выше программы. Для выполнения проверки следует считать из проверяемого сертификата строку с именем его издателя и найти в одном из системных хранилищ его сертификат. Для облегчения операции поиска в нескольких хранилищах CryptoAPI 2.0 поддерживает механизм коллекций (или логических хранилищ) сертификатов. Коллекция может объединять данные из нескольких физических хранилищ и выполнять операции над всеми их сертификатами одновременно. Кстати говоря, в Windows 2000 и выше системные хранилища сертификатов сами представляют собой коллекции. Поиск сертификата издателя и проверка заданного сертификата выполняются одновременно функцией CertGetIssuerCertificateFromStore. Ей следует передать дескриптор хранилища сертификатов, ссылку на контекст проверяемого сертификата, возможно - ссылку на предыдущий найденный сертификат издателя (если вызов функции производится повторно) и ссылку на флаговую переменную. Эта переменная при вызове функции задает режим проверки, а при возврате - служит индикатором успеха проверки. Функция поддерживает несколько режимов проверки, которые могут комбинироваться; мы воспользуемся сочетанием двух из них - CERT_STORE_SIGNATURE_FLAG (проверить подпись издателя под заданным сертификатом) и CERT_STORE_TIME_VALIDITY_FLAG (проверить срок действия заданного сертификата). Рассмотрим процедуру проверки сертификата с заданным полем Subject на примере программы.
{subj: PWideChar;
err: string;
cert: PCCERT_CONTEXT;
тип и значение encType - см. выше}
err:= SubjectEdit.Text;
GetMem (subj, 2 * length (err) + 1);
StringToWideChar (err, subj, 2 * length (err) + 1);
cert:= CertFindCertificateInStore (store, encType, 0,
CERT_FIND_SUBJECT_STR, subj, nil);
FreeMem (subj, 2 * length (err) + 1);
if cert = nil then
begin
MessageDlg ('Certificate not found',
mtError, [mbOK], 0);
CertCloseStore (store, 0);
exit;
end;
{collect: HCERTSTORE;}
collect:= CertOpenStore (CERT_STORE_PROV_COLLECTION,
0, 0, 0, nil);
{mystore, castore, rootstore: HCERTSTORE;}
mystore:= CertOpenSystemStore (prov, 'MY');
CertAddStoreToCollection (collect, mystore, 0, 0);
castore:= CertOpenSystemStore (prov, 'CA');
CertAddStoreToCollection (collect, castore, 0, 2);
rootstore:= CertOpenSystemStore (prov, 'ROOT');
CertAddStoreToCollection (collect, rootstore, 0, 1)
Последним параметром функции CertAddStoreToCollection задается приоритет добавляемого хранилища в коллекции. Мы ожидаем, что сертификат издателя окажется в хранилище СА, поэтому для этого хранилища задан самый высокий приоритет, для ROOT - более низкий, а для MY - низший.
{flags: DWORD;}
flags:= CERT_STORE_SIGNATURE_FLAG or
CERT_STORE_TIME_VALIDITY_FLAG;
{icert: PCCERT_CONTEXT;}
icert:= CertGetIssuerCertificateFromStore
(collect, cert, nil, @flags);
if icert = nil then
case int64(GetLastError) of
CRYPT_E_NOT_FOUND: MessageDlg ('Issuer certificate not
found', mtError, [mbOK], 0);
CRYPT_E_SELF_SIGNED: MessageDlg ('This is root
certificate', mtWarning, [mbOK], 0);
else MessageDlg ('Invalid arguments',
mtError, [mbOK], 0);
end
else if flags = 0
then MessageDlg ('Certificate is valid',
mtInformation, [mbOK], 0)
else MessageDlg ('Certificate is invalid',
mtError, [mbOK], 0);
В результате при правильном обращении к функции может возникнуть одна из четырех ситуаций:
Итак, мы рассмотрели механизмы создания и управления сертификатами безопасности на основе CryptoAPI. Используя сертификаты, вы сможете выполнять действия с сообщениями при помощи многочисленных функций CryptoAPI, автоматизирующих процессы шифрования и расшифровки, подписания и проверки подписи, и представляющие результаты своих действий в соответствии с существующими стандартами. Приложение. Архив (rar, 84,3 KB)Авторы ищут издателя для публикации учебного пособия по данной теме.
|
|
CITForum © 1997–2025