-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cryptopro-cades): Добавлена функция генерации контейнера и запро…
…са на сертификат (#43) * feat(cryptopro-cades): Добавлено разворачивание полей доступных считывателей * feat(cryptopro-cades): Добавлена функция генерации контейнера и запроса на сертификат * feat(cryptopro-cades): Вынес и описал тип для генерации контейнера и CSR * doc(cryptopro-cades): Задокументировал функцию создания запроса на сертификат * refactor(cryptopro-cades): Переименовал и экспортировал функцию запроса на сертификат * doc(cryptopro-cades): Убрал неверный комментарий к полю * test(cryptopro-cades): Написал тест на внешнюю функцию * doc(cryptopro-cades): Задокументировал некоторые константы
- Loading branch information
Showing
15 changed files
with
508 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
import { | ||
ALLOW_EXPORT_FLAG, | ||
AT_KEYEXCHANGE, | ||
CERT_POLICY_QUALIFIER_TYPE, | ||
CRYPTO_OBJECTS, | ||
CSP_NAME_MAX_LENGTH, | ||
SUBJECT_SIGN_TOOL_OID, | ||
XCN_CRYPT_STRING_BASE64, | ||
XCN_CRYPT_STRING_BASE64REQUESTHEADER, | ||
} from '../constants'; | ||
import { CreateCSRInputDTO } from '../types/СreateCSRInputDTO'; | ||
import { outputDebug } from '../utils'; | ||
|
||
import { createObject } from './createObject'; | ||
import { afterPluginLoaded } from './internal/afterPluginLoaded'; | ||
import { convertStringToUTF8ByteArray } from './internal/convertStringToUTF8ByteArray'; | ||
import { setCryptoProperty } from './internal/setCryptoProperty'; | ||
|
||
/** | ||
* Функция, формирующая контейнер и запрос на сертификат за один криптосеанс | ||
* @param {CreateCSRInputDTO} data данные для формирования контейнера и запроса на сертификат | ||
* @returns {Promise<string>} Строка, содержащая CSR в DER формате | ||
*/ | ||
export const createCSR = (data: CreateCSRInputDTO): Promise<string> => { | ||
return afterPluginLoaded(async () => { | ||
const logData = []; | ||
|
||
logData.push({ data }); | ||
|
||
try { | ||
// Формируем приватный ключ | ||
const pKey = await createObject(CRYPTO_OBJECTS.privateKey); | ||
|
||
await setCryptoProperty(pKey, 'ProviderName', data.providerName); | ||
await setCryptoProperty(pKey, 'ProviderType', data.providerCode); | ||
await setCryptoProperty(pKey, 'ContainerName', data.containerName); | ||
|
||
await setCryptoProperty( | ||
pKey, | ||
'ExportPolicy', | ||
data.isExportable ? ALLOW_EXPORT_FLAG : 0, | ||
); | ||
|
||
// помечаем, что ключ и для шифрования, и для подписывания | ||
// @see https://learn.microsoft.com/ru-ru/windows/win32/api/certenroll/ne-certenroll-x509keyspec | ||
await setCryptoProperty(pKey, 'KeySpec', AT_KEYEXCHANGE); | ||
|
||
// инициализируем запрос на сертификат | ||
const certRequestPkcs10 = await createObject( | ||
CRYPTO_OBJECTS.certificateRequest, | ||
); | ||
|
||
await certRequestPkcs10.InitializeFromPrivateKey(0x1, pKey, ''); | ||
|
||
// описываем аттрибуты Subject сертификата | ||
const distinguishedName = await createObject( | ||
CRYPTO_OBJECTS.distinguishedName, | ||
); | ||
const dnValue = data.attributes | ||
.map((attr) => `${attr.oid}="${attr.value}"`) | ||
.join(', '); | ||
|
||
await distinguishedName.Encode(dnValue); | ||
await setCryptoProperty(certRequestPkcs10, 'Subject', distinguishedName); | ||
|
||
// объект с расширениями, который будем наполнять | ||
const extensions = await certRequestPkcs10.X509Extensions; | ||
|
||
// key usages сертификата | ||
const keyUsageExt = await createObject(CRYPTO_OBJECTS.extensionKeyUsage); | ||
|
||
await keyUsageExt.InitializeEncode(data.keyUsage); | ||
await extensions.Add(keyUsageExt); | ||
|
||
// описываем enhanced key usages сертификата | ||
const enhKeyUsageCollection = await createObject( | ||
CRYPTO_OBJECTS.objectIds, | ||
); | ||
|
||
for (const enhKeyOid of data.enhKeyUsage) { | ||
const enhKeyUsageOid = await createObject(CRYPTO_OBJECTS.objectId); | ||
|
||
await enhKeyUsageOid.InitializeFromValue(enhKeyOid); | ||
await enhKeyUsageCollection.Add(enhKeyUsageOid); | ||
} | ||
|
||
const enhancedKeyUsageExt = await createObject( | ||
CRYPTO_OBJECTS.extensionEnhancedKeyUsage, | ||
); | ||
|
||
await enhancedKeyUsageExt.InitializeEncode(enhKeyUsageCollection); | ||
// добавляем enhanced key usages в расширения сертификата | ||
await extensions.Add(enhancedKeyUsageExt); | ||
|
||
// описываем политики сертификата | ||
const certPolicyCollection = await createObject( | ||
CRYPTO_OBJECTS.certificatePolicies, | ||
); | ||
|
||
for (const policy of data.certPolicies) { | ||
const policyOid = await createObject(CRYPTO_OBJECTS.objectId); | ||
|
||
await policyOid.InitializeFromValue(policy.oid); | ||
|
||
const certPolicy = await createObject(CRYPTO_OBJECTS.certificatePolicy); | ||
|
||
await certPolicy.Initialize(policyOid); | ||
|
||
// если oid не полностью определяет политику, то необходимо определить и добавить квалификатор | ||
if (Boolean(policy.value.length)) { | ||
const policyQualifier = await createObject( | ||
CRYPTO_OBJECTS.policyQualifier, | ||
); | ||
|
||
await policyQualifier.InitializeEncode( | ||
policy.value, | ||
CERT_POLICY_QUALIFIER_TYPE.UNKNOWN, | ||
); | ||
|
||
const policyQualifierCollection = await certPolicy.PolicyQualifiers; | ||
|
||
await policyQualifierCollection.Add(policyQualifier); | ||
} | ||
|
||
await certPolicyCollection.Add(certPolicy); | ||
} | ||
|
||
const certPoliciesExt = await createObject( | ||
CRYPTO_OBJECTS.extensionCertificatePolicies, | ||
); | ||
|
||
await certPoliciesExt.InitializeEncode(certPolicyCollection); | ||
// добавляем политики в расширения сертификата | ||
await extensions.Add(certPoliciesExt); | ||
|
||
// subject sign tool (custom) | ||
// Необходимо добавлять руками, т.к. плагин может вписать только CryptoPRO CSP 5.0 | ||
// @see https://aleksandr.ru/blog/dobavlenie_subjectsigntool_v_kriptopro_ecp_browser_plug_in | ||
// TODO: 09.2024 это поле станет необязательным, можно удалить код после доработок на УЦ | ||
const subjectSignToolOid = await createObject(CRYPTO_OBJECTS.objectId); | ||
|
||
await subjectSignToolOid.InitializeFromValue(SUBJECT_SIGN_TOOL_OID); | ||
|
||
// необходимо обрезать название (на деле оно всегда < 128 символов) | ||
const shortName = data.signTool.slice(0, CSP_NAME_MAX_LENGTH); | ||
const utf8arr = convertStringToUTF8ByteArray(shortName); | ||
|
||
// @see https://learn.microsoft.com/ru-ru/windows/win32/seccertenroll/about-utf8string | ||
const utf8stringTag = 0x0c; | ||
|
||
utf8arr.unshift(utf8stringTag, utf8arr.length); | ||
|
||
const base64String = btoa( | ||
// @ts-ignore TODO: обновить версию TS, 4.8.3 -> 4.9.5 | ||
String.fromCharCode.apply(null, new Uint8Array(utf8arr)), | ||
); | ||
|
||
const subjectSignToolExt = await createObject(CRYPTO_OBJECTS.extension); | ||
|
||
await subjectSignToolExt.Initialize( | ||
subjectSignToolOid, | ||
XCN_CRYPT_STRING_BASE64, | ||
base64String, | ||
); | ||
|
||
// добавляем способ подписания в расширения сертификата | ||
await extensions.Add(subjectSignToolExt); | ||
|
||
// identification kind | ||
const identificationKindExt = await createObject( | ||
CRYPTO_OBJECTS.extensionIdentificationKind, | ||
); | ||
|
||
await identificationKindExt.InitializeEncode(data.identificationKind); | ||
await extensions.Add(identificationKindExt); | ||
|
||
// запрос | ||
const enroll = await createObject(CRYPTO_OBJECTS.enrollment); | ||
|
||
await enroll.InitializeFromRequest(certRequestPkcs10); | ||
|
||
const csr = await enroll.CreateRequest( | ||
XCN_CRYPT_STRING_BASE64REQUESTHEADER, | ||
); | ||
|
||
logData.push({ csr }); | ||
|
||
return csr; | ||
} catch (error) { | ||
logData.push({ error }); | ||
throw error.message; | ||
} finally { | ||
outputDebug('createCSR >>', logData); | ||
} | ||
})(); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
...-cades/src/api/internal/convertStringToUTF8ByteArray/convertStringToUTF8ByteArray.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { convertStringToUTF8ByteArray } from './convertStringToUTF8ByteArray'; | ||
|
||
describe('convertStringToUTF8ByteArray', () => { | ||
it('Конвертирует название СКЗИ Крипто Про как Тулбокс', () => { | ||
const stringToConvert = '"КриптоПро CSP" (версия 4.0)'; | ||
const resultFromToolbox = [ | ||
34, 208, 154, 209, 128, 208, 184, 208, 191, 209, 130, 208, 190, 208, 159, | ||
209, 128, 208, 190, 32, 67, 83, 80, 34, 32, 40, 208, 178, 208, 181, 209, | ||
128, 209, 129, 208, 184, 209, 143, 32, 52, 46, 48, 41, | ||
]; | ||
|
||
const result = convertStringToUTF8ByteArray(stringToConvert); | ||
|
||
expect(result).toEqual(resultFromToolbox); | ||
}); | ||
}); |
40 changes: 40 additions & 0 deletions
40
...topro-cades/src/api/internal/convertStringToUTF8ByteArray/convertStringToUTF8ByteArray.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/** | ||
* Forked from github.com/google/closure-library | ||
* Converts a JS string to a UTF-8 "byte" array. | ||
* @param {string} str 16-bit unicode string. | ||
* @return {!Array<number>} UTF-8 byte array. | ||
* @see https://github.com/google/closure-library/blob/e877b1eac410c0d842bcda118689759512e0e26f/closure/goog/crypt/crypt.js | ||
*/ | ||
export function convertStringToUTF8ByteArray(str: string): number[] { | ||
// TODO(user): Use native implementations if/when available | ||
var out = [], | ||
p = 0; | ||
|
||
for (var i = 0; i < str.length; i++) { | ||
var c = str.charCodeAt(i); | ||
|
||
if (c < 128) { | ||
out[p++] = c; | ||
} else if (c < 2048) { | ||
out[p++] = (c >> 6) | 192; | ||
out[p++] = (c & 63) | 128; | ||
} else if ( | ||
(c & 0xfc00) == 0xd800 && | ||
i + 1 < str.length && | ||
(str.charCodeAt(i + 1) & 0xfc00) == 0xdc00 | ||
) { | ||
// Surrogate Pair | ||
c = 0x10000 + ((c & 0x03ff) << 10) + (str.charCodeAt(++i) & 0x03ff); | ||
out[p++] = (c >> 18) | 240; | ||
out[p++] = ((c >> 12) & 63) | 128; | ||
out[p++] = ((c >> 6) & 63) | 128; | ||
out[p++] = (c & 63) | 128; | ||
} else { | ||
out[p++] = (c >> 12) | 224; | ||
out[p++] = ((c >> 6) & 63) | 128; | ||
out[p++] = (c & 63) | 128; | ||
} | ||
} | ||
|
||
return out; | ||
} |
1 change: 1 addition & 0 deletions
1
packages/cryptopro-cades/src/api/internal/convertStringToUTF8ByteArray/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './convertStringToUTF8ByteArray'; |
Oops, something went wrong.