@it-enterprise/digital-signature 1.1.6 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,12 @@
1
- import { EndUser, EndUserConstants, EndUserProxySettings, EndUserError, KSPSettings, EndUserCertificate, EndUserKeyMedia } from "../euscp/euscp";
1
+ import { EndUser, EndUserConstants, EndUserProxySettings, EndUserError, KSPSettings, EndUserCertificate, EndUserKeyMedia, EndUserSignContainerInfo } from "../euscp/euscp";
2
2
  import { DigitalSignatureKeyType, PrivateKeyInfo, FilePrivateKeyInfo, HardwarePrivateKeyInfo, KspPrivateKeyInfo } from "./Models";
3
- import { downloadData, readFile } from "./Utils";
3
+ import { downloadAndSignFiles, downloadData, format, readFile, signAlgoToHashAlgo } from "./Utils";
4
4
  import Resourses from "./Resourses.json";
5
5
  import GlSign from "./GlSign";
6
6
 
7
+ const PRIVATE_KEY_TYPE = "PKType";
8
+ const PRIVATE_KEY_INFO = "PKInfo";
9
+
7
10
  export default class DigitalSignature {
8
11
  /**
9
12
  * @param {DigitalSignatureSettings} settingsProvider
@@ -33,30 +36,85 @@ export default class DigitalSignature {
33
36
  depositsign.ksp = EndUserConstants.EU_KSP_IIT;
34
37
  depositsign.address = "https://depositsign.com/api/v1/it-enterprise/sign-server";
35
38
  depositsign.directAccess = true;
39
+ depositsign.needQRCode = false;
36
40
 
37
41
  const diia = new KSPSettings();
38
- diia.name = "ДіЯ";
42
+ diia.name = "Дія.Підпис";
39
43
  diia.ksp = EndUserConstants.EU_KSP_DIIA;
40
44
  diia.directAccess = true;
41
- diia.mobileAppName = "ДіЯ";
45
+ diia.mobileAppName = "Дія";
42
46
  diia.address = "https://diia-sign.it.ua/KSPSign";
43
47
  diia.systemId = "diia-sign-it-ent";
44
-
45
- /** @type {KSPSettings} */
46
- this._KSPs = [depositsign, diia];
48
+ diia.needQRCode = true;
49
+
50
+ const smartId = new KSPSettings();
51
+ smartId.name = "Приватбанк - хмарний підпис \"SmartID\"";
52
+ smartId.ksp = EndUserConstants.EU_KSP_PB;
53
+ smartId.directAccess = true;
54
+ smartId.mobileAppName = "Приват24";
55
+ smartId.address = "https://acsk.privatbank.ua/cloud/api/back/";
56
+ smartId.clientIdPrefix = "IEIS_";
57
+ smartId.confirmationURL = "https://www.privat24.ua/rd/kep";
58
+ smartId.needQRCode = true;
59
+
60
+ const vchasno = new KSPSettings();
61
+ vchasno.name = "Вчасно - хмарний підпис";
62
+ vchasno.ksp = EndUserConstants.EU_KSP_IIT;
63
+ vchasno.address = "https://cs.vchasno.ua/ss/";
64
+ vchasno.directAccess = true;
65
+ vchasno.needQRCode = false;
66
+
67
+ const cloudKey = new KSPSettings();
68
+ cloudKey.name = "ТОВ «ЦСК «Україна» - хмарний підпис CloudKey";
69
+ cloudKey.ksp = EndUserConstants.EU_KSP_PB;
70
+ cloudKey.directAccess = true;
71
+ cloudKey.mobileAppName = "CloudKey";
72
+ cloudKey.address = "https://sid.uakey.com.ua/smartid/iit/";
73
+ cloudKey.clientIdPrefix = "DIIA_2";
74
+ cloudKey.confirmationURL = "https://sid.uakey.com.ua/kep?hash=rd/kep";
75
+ cloudKey.needQRCode = true;
76
+
77
+ const esign = new KSPSettings();
78
+ esign.name = "ESign - хмарний підпис";
79
+ esign.ksp = EndUserConstants.EU_KSP_IIT;
80
+ esign.address = "https://cabinet.e-life.com.ua/api/EDG/Sign";
81
+ esign.directAccess = true;
82
+ esign.needQRCode = false;
83
+
84
+ const idd = new KSPSettings();
85
+ idd.name = "ІДД ДПС - хмарний підпис";
86
+ idd.ksp = EndUserConstants.EU_KSP_IIT;
87
+ idd.address = "https://smart-sign.tax.gov.ua/";
88
+ idd.port = "443";
89
+ idd.directAccess = true;
90
+ idd.clientIdType = 1;
91
+ idd.needQRCode = false;
92
+
93
+ this._KSPs = {
94
+ depositsign,
95
+ diia,
96
+ smartId,
97
+ vchasno,
98
+ cloudKey,
99
+ esign,
100
+ idd,
101
+ asArray: function() {
102
+ return [
103
+ depositsign,
104
+ diia,
105
+ smartId,
106
+ vchasno,
107
+ cloudKey,
108
+ esign,
109
+ idd
110
+ ];
111
+ }
112
+ };
47
113
 
48
114
  /** @type {PrivateKeyInfo} */
49
115
  this._readedKey = null;
50
116
  }
51
117
 
52
- get PRIVATE_KEY_TYPE() {
53
- return "_PrivateKeyType";
54
- }
55
-
56
- get PRIVATE_KEY_INFO() {
57
- return "_PrivateKeyInfo";
58
- }
59
-
60
118
  /**
61
119
  * Считанный приватный ключ
62
120
  * @type {PrivateKeyInfo}
@@ -78,21 +136,24 @@ export default class DigitalSignature {
78
136
  * @param {number} type
79
137
  */
80
138
  async setLibraryType(type) {
81
-
82
- if (this._euSign) {
83
- await this._euSign.ResetPrivateKey();
84
- }
85
-
86
139
  switch (type) {
87
140
  case DigitalSignatureKeyType.Token:
141
+ if (this._euSign === this._euSignKeyMedia) {
142
+ return;
143
+ }
88
144
  this._euSign = this._euSignKeyMedia;
89
145
  break;
90
146
  default:
147
+ if (this._euSign === this._euSignFile) {
148
+ return;
149
+ }
91
150
  this._euSign = this._euSignFile;
92
151
  break;
93
152
  }
94
153
 
95
- this._preferredKeyType = type;
154
+ if (type != null && type != undefined) {
155
+ this._preferredKeyType = type;
156
+ }
96
157
 
97
158
  await this.initialise();
98
159
  this.keyType = type;
@@ -103,7 +164,6 @@ export default class DigitalSignature {
103
164
  * @returns {Promise<number>} Текущий тип библиотеки
104
165
  */
105
166
  async initialise() {
106
-
107
167
  if (!this._glSign) {
108
168
  this._glSign = await Promise.resolve(this._settingsProvider.getGlSign());
109
169
  }
@@ -185,38 +245,55 @@ export default class DigitalSignature {
185
245
  return this.getLibraryType();
186
246
  }
187
247
 
188
- const certificates = await this._settings.certificatesProvider.loadCertificates();
189
-
190
- const euSettings = {
191
- language: this._settings.language,
192
- encoding: "UTF-16LE",
193
- httpProxyServiceURL: this._settings.httpProxyServiceURL,
194
- directAccess: true,
195
- CAs: certificates.CAs,
196
- CACertificates: certificates.CACertificates,
197
- mssServiceURL: this._settings.mssServiceURL,
198
- KSPs: this._KSPs,
199
- allowedKeyMediaTypes: [
200
- "е.ключ BIFIT iToken",
201
- "криптомод. ІІТ Гряда-61",
202
- "криптомод. ІІТ Гряда-301",
203
- "е.ключ ІІТ Алмаз-1К",
204
- "е.ключ ІІТ Кристал-1",
205
- "ID-карта громадянина (БЕН)",
206
- "е.ключ ІІТ Алмаз-1К (PKCS#11)",
207
- "е.ключ ІІТ Кристал-1 (PKCS#11)",
208
- "е.ключ чи смарт-карта Avest (PKCS#11)",
209
- "е.ключ Ефіт Key (PKCS#11)",
210
- "е.ключ чи смарт-карта Автор (PKCS#11)",
211
- "е.ключ чи смарт-карта Автор 338 (PKCS#11)",
212
- "смарт-карта Техноконс. TEllipse3 (PKCS#11)",
213
- "смарт-карта Техноконс. TEllipse",
214
- "смарт-карта Техноконс. TEllipse2/3",
215
- "е.ключ SafeNet iKey (PKCS#11, RSA)"
216
- ]
217
- };
248
+ if (!this._euSettings) {
249
+ let certificates;
250
+ try {
251
+ certificates = await this._settings.certificatesProvider.loadCertificates();
252
+ } catch (error) {
253
+ if (error && error.code === EndUserError.EU_ERROR_DOWNLOAD_FILE) {
254
+ throw {
255
+ code: EndUserError.EU_ERROR_DOWNLOAD_FILE,
256
+ message: this._resourses.DownloadingRootCertificatesError
257
+ };
258
+ } else {
259
+ throw error;
260
+ }
261
+ }
218
262
 
219
- await euSign.Initialize(euSettings);
263
+ this._euSettings = {
264
+ language: this._language,
265
+ encoding: "UTF-16LE",
266
+ httpProxyServiceURL: this._settings.httpProxyServiceURL,
267
+ directAccess: true,
268
+ CAs: certificates.CAs,
269
+ CACertificates: certificates.CACertificates,
270
+ KSPs: this.KSPs,
271
+ allowedKeyMediaTypes: [
272
+ "е.ключ BIFIT iToken",
273
+ "криптомод. ІІТ Гряда-61",
274
+ "е.ключ ІІТ Алмаз-1К",
275
+ "е.ключ ІІТ Алмаз-1К (Bluetooth)",
276
+ "е.ключ ІІТ Кристал-1",
277
+ "криптомод. ІІТ Гряда-301",
278
+ "ID-карта громадянина (БЕН)",
279
+ "е.ключ ІІТ Алмаз-1К (PKCS#11)",
280
+ "е.ключ ІІТ Кристал-1 (PKCS#11)",
281
+ "е.ключ чи смарт-карта Avest (PKCS#11)",
282
+ "е.ключ Ефіт Key (PKCS#11)",
283
+ "е.ключ чи смарт-карта Автор (PKCS#11)",
284
+ "е.ключ чи смарт-карта Автор 338 (PKCS#11)",
285
+ "смарт-карта Техноконс. TEllipse3 (PKCS#11)",
286
+ "смарт-карта Техноконс. TEllipse",
287
+ "смарт-карта Техноконс. TEllipse2/3",
288
+ "е.ключ SafeNet iKey (PKCS#11, RSA)"
289
+ ]
290
+ };
291
+ }
292
+
293
+ await euSign.Initialize(this._euSettings);
294
+ if (euSign === this._euSignKeyMedia && !await this._euSignFile.IsInitialized()) {
295
+ this._euSignFile.Initialize(this._euSettings);
296
+ }
220
297
  if (this._glSign.ApplyProxySettings) {
221
298
  const { UseProxy, ProxyAddress, ProxyPort, ProxyUser, ProxyPassword } = this._glSign;
222
299
  const proxy = new EndUserProxySettings();
@@ -228,9 +305,15 @@ export default class DigitalSignature {
228
305
  proxy.password = ProxyPassword;
229
306
  proxy.savePassword = true;
230
307
  await euSign.SetProxySettings(proxy);
308
+ if (euSign === this._euSignKeyMedia && !await this._euSignFile.IsInitialized()) {
309
+ this._euSignFile.SetProxySettings(proxy);
310
+ }
231
311
  }
232
312
 
233
313
  await euSign.SetRuntimeParameter(EndUserConstants.EU_SIGN_TYPE_PARAMETER, EndUserConstants.EndUserSignType.CAdES_X_Long);
314
+ if (euSign === this._euSignKeyMedia && !await this._euSignFile.IsInitialized()) {
315
+ this._euSignFile.SetRuntimeParameter(EndUserConstants.EU_SIGN_TYPE_PARAMETER, EndUserConstants.EndUserSignType.CAdES_X_Long);
316
+ }
234
317
 
235
318
  return this.getLibraryType();
236
319
  }
@@ -245,13 +328,11 @@ export default class DigitalSignature {
245
328
 
246
329
  /**
247
330
  * Получить список подключённых устройств
248
- * @returns {Promise<Array<EndUserKeyMedia>>} Информация о владельце ключа
331
+ * @returns {Promise<Array<EndUserKeyMedia>>} Список подключённых устройств
249
332
  */
250
333
  async getKeyMedias() {
251
- if (this._euSign != this._euSignKeyMedia) {
252
- await this.setLibraryType(DigitalSignatureKeyType.Token);
253
- }
254
- return this._euSign.GetKeyMedias();
334
+ await this.setLibraryType(DigitalSignatureKeyType.Token);
335
+ return this._euSignKeyMedia.GetKeyMedias();
255
336
  }
256
337
 
257
338
  /**
@@ -260,20 +341,57 @@ export default class DigitalSignature {
260
341
  * @param {Array<Uint8Array>?} certs - Сертификаты ключа (при необходимости)
261
342
  */
262
343
  async readHardwareKey(keyMedia, certs) {
263
- if (this._euSign != this._euSignKeyMedia) {
264
- await this.setLibraryType(DigitalSignatureKeyType.Token);
344
+ await this.setLibraryType(DigitalSignatureKeyType.Token);
345
+
346
+ if (!keyMedia) {
347
+ throw {
348
+ code: EndUserError.EU_ERROR_BAD_PARAMETER,
349
+ message: this._resourses.BadParameter + " keyMedia"
350
+ };
265
351
  }
352
+ if (!keyMedia.password) {
353
+ throw {
354
+ code: EndUserError.EU_ERROR_BAD_PARAMETER,
355
+ message: this._resourses.PasswordNotSet
356
+ };
357
+ }
358
+
266
359
  if (Array.isArray(certs)) {
267
360
  certs = await Promise.all(certs.map(cert => cert instanceof File || cert instanceof Blob ? readFile(cert) : cert));
268
361
  }
269
- const ownerInfo = await this._euSign.ReadPrivateKey(keyMedia, certs, this._selectedIssuerCN);
270
- this._readedKey = new HardwarePrivateKeyInfo(
271
- DigitalSignatureKeyType.Token,
272
- ownerInfo,
273
- await this._euSign.GetOwnCertificates(),
274
- keyMedia
275
- );
276
- return this._readedKey;
362
+
363
+ try {
364
+ const ownerInfo = await this._euSign.ReadPrivateKey(keyMedia, certs, this._selectedIssuerCN);
365
+ this._readedKey = new HardwarePrivateKeyInfo(
366
+ DigitalSignatureKeyType.Token,
367
+ ownerInfo,
368
+ await this._euSign.GetOwnCertificates(),
369
+ keyMedia
370
+ );
371
+ return this._readedKey;
372
+ } catch (error) {
373
+ if (error && error.code === EndUserError.EU_ERROR_CERT_NOT_FOUND) {
374
+ if (!this._selectedIssuerCN) {
375
+ throw {
376
+ code: EndUserError.EU_ERROR_CERT_NOT_FOUND,
377
+ message: this._resourses.ReadPrivateKeyCAAutoDetectError
378
+ };
379
+ } else if (this._selectedIssuerCN) {
380
+ if (!this._selectedCA.cmpAddress) {
381
+ throw {
382
+ code: EndUserError.EU_ERROR_CERT_NOT_FOUND,
383
+ message: format(this._resourses.ReadPrivateKeyNeedCertificateError, this._selectedIssuerCN)
384
+ };
385
+ } else {
386
+ throw {
387
+ code: EndUserError.EU_ERROR_CERT_NOT_FOUND,
388
+ message: format(this._resourses.ReadPrivateKeyInvalidCAError, this._selectedIssuerCN)
389
+ };
390
+ }
391
+ }
392
+ }
393
+ throw error;
394
+ }
277
395
  }
278
396
 
279
397
  /**
@@ -283,26 +401,60 @@ export default class DigitalSignature {
283
401
  * @param {Array<Uint8Array|File>?} certs - Сертификаты ключа (при необходимости)
284
402
  */
285
403
  async readFileKey(privateKey, password, certs) {
286
- let keyName = "Key";
287
- if (this._euSign != this._euSignFile) {
288
- await this.setLibraryType(DigitalSignatureKeyType.File);
404
+ await this.setLibraryType(DigitalSignatureKeyType.File);
405
+
406
+ if (!privateKey) {
407
+ throw {
408
+ code: EndUserError.EU_ERROR_BAD_PARAMETER,
409
+ message: this._resourses.BadParameter + " keyMedia"
410
+ };
289
411
  }
412
+ if (!password) {
413
+ throw {
414
+ code: EndUserError.EU_ERROR_BAD_PARAMETER,
415
+ message: this._resourses.PasswordNotSet
416
+ };
417
+ }
418
+
290
419
  if (privateKey instanceof File || privateKey instanceof Blob) {
291
- keyName = privateKey.name;
292
420
  privateKey = await readFile(privateKey);
293
421
  }
294
422
  if (Array.isArray(certs)) {
295
423
  certs = await Promise.all(certs.map(cert => cert instanceof File || cert instanceof Blob ? readFile(cert) : cert));
296
424
  }
297
- const ownerInfo = await this._euSign.ReadPrivateKeyBinary(privateKey, password, certs, this._selectedIssuerCN);
298
- this._readedKey = new FilePrivateKeyInfo(
299
- DigitalSignatureKeyType.File,
300
- ownerInfo,
301
- await this._euSign.GetOwnCertificates(),
302
- privateKey,
303
- this.glSign.AllowSavePassword ? password : undefined
304
- );
305
- return this._readedKey;
425
+ try {
426
+ const ownerInfo = await this._euSign.ReadPrivateKeyBinary(privateKey, password, certs, this._selectedIssuerCN);
427
+ this._readedKey = new FilePrivateKeyInfo(
428
+ DigitalSignatureKeyType.File,
429
+ ownerInfo,
430
+ await this._euSign.GetOwnCertificates(),
431
+ privateKey,
432
+ password
433
+ );
434
+ return this._readedKey;
435
+ } catch (error) {
436
+ if (error && error.code === EndUserError.EU_ERROR_CERT_NOT_FOUND) {
437
+ if (!this._selectedIssuerCN) {
438
+ throw {
439
+ code: EndUserError.EU_ERROR_CERT_NOT_FOUND,
440
+ message: this._resourses.ReadPrivateKeyCAAutoDetectError
441
+ };
442
+ } else if (this._selectedIssuerCN) {
443
+ if (!this._selectedCA.cmpAddress) {
444
+ throw {
445
+ code: EndUserError.EU_ERROR_CERT_NOT_FOUND,
446
+ message: format(this._resourses.ReadPrivateKeyNeedCertificateError, this._selectedIssuerCN)
447
+ };
448
+ } else {
449
+ throw {
450
+ code: EndUserError.EU_ERROR_CERT_NOT_FOUND,
451
+ message: format(this._resourses.ReadPrivateKeyInvalidCAError, this._selectedIssuerCN)
452
+ };
453
+ }
454
+ }
455
+ }
456
+ throw error;
457
+ }
306
458
  }
307
459
 
308
460
  /**
@@ -311,9 +463,8 @@ export default class DigitalSignature {
311
463
  * @returns {Promise<EndUserJKSPrivateKeyInfo[]>}
312
464
  */
313
465
  async getJKSPrivateKeys(jks) {
314
- if (this._euSign != this._euSignFile) {
315
- await this.setLibraryType(DigitalSignatureKeyType.File);
316
- }
466
+ await this.setLibraryType(DigitalSignatureKeyType.File);
467
+
317
468
  if (jks instanceof File || jks instanceof Blob) {
318
469
  jks = await readFile(jks);
319
470
  }
@@ -329,49 +480,39 @@ export default class DigitalSignature {
329
480
  }
330
481
 
331
482
  /**
332
- * Считать ключ с Дiя
333
- * @param {string} userId - Идентификатор пользователя
334
- * @param {boolean?} getCerts - Получать информацию о ключе пользователя. Это приведёт к дополнительному запросу на подписание
335
- */
483
+ * Считать ключ с DepositSign
484
+ * @param {string} userId - Идентификатор пользователя
485
+ * @param {boolean?} getCerts - Получать информацию о ключе пользователя. Это приведёт к дополнительному запросу на подписание
486
+ */
336
487
  async readPrivateKeyDepositsign(userId, getCerts = false) {
337
- return await this.readPrivateKeyKSP(userId, this.KSPs[0], getCerts);
488
+ return await this.readPrivateKeyKSP(userId, this._KSPs.depositsign, getCerts);
338
489
  }
339
490
 
340
491
  /**
341
492
  * Считать ключ с Дiя
342
493
  * @param {boolean?} getCerts - Получать информацию о ключе пользователя. Это приведёт к дополнительному запросу на подписание
343
494
  */
344
- async readPrivateKeyDiia(getCerts = false) {
345
- const diia = "DIIA";
346
- if (sessionStorage[diia] != "+") {
347
- try {
348
- sessionStorage[diia] = await downloadData("https://diia-sign.it.ua/diia/");
349
- } catch {
350
- throw {
351
- message: this._resourses.DiiaError + `<a target="_blank" href="${window.origin}">${window.origin}</a></li><ul>`
352
- };
353
- }
354
- }
355
-
356
- return await this.readPrivateKeyKSP(null, this.KSPs[1], getCerts);
495
+ async readPrivateKeyDiia(getCerts = false) {
496
+ return await this.readPrivateKeyKSP(null, this.KSPs.diia, getCerts);
357
497
  }
358
498
 
359
499
  /**
360
500
  * Считать ключ с облачного провайдера
361
- * @param {string} userId - Идентификатор пользователя
362
- * @param {string|KSPSettings} ksp - Идентификатор облачного провайдера
501
+ * @param {KSPSettings} ksp - Идентификатор облачного провайдера
502
+ * @param {string?} userId - Идентификатор пользователя
363
503
  * @param {boolean?} getCerts - Получать информацию о ключе пользователя. Это приведёт к дополнительному запросу на подписание
364
504
  */
365
- async readPrivateKeyKSP(userId, ksp, getCerts = false) {
366
- if (this._euSign != this._euSignFile) {
367
- await this.setLibraryType(DigitalSignatureKeyType.KSP);
368
- }
369
- if (ksp != EndUserConstants.EndUserKSP.DIIA && typeof ksp !== "string") {
370
- ksp = ksp.name;
505
+ async readPrivateKeyKSP(ksp, userId, getCerts = false) {
506
+ await this.setLibraryType(DigitalSignatureKeyType.KSP);
507
+
508
+ if (ksp.ksp === EndUserConstants.EU_KSP_DIIA) {
509
+ await this._diiaCheckAccess();
371
510
  }
372
- const ownerInfo = await this._euSign.ReadPrivateKeyKSP(userId, ksp, getCerts);
511
+
512
+ const ownerInfo = await this._euSign.ReadPrivateKeyKSP(userId, ksp.name, getCerts);
373
513
  if (getCerts && !ownerInfo) {
374
514
  throw {
515
+ code: EndUserError.EU_ERROR_KEY_MEDIAS_READ_FAILED,
375
516
  message: this._resourses.PrivateKeyNotReaded
376
517
  };
377
518
  }
@@ -395,11 +536,7 @@ export default class DigitalSignature {
395
536
  * @param {authenticationCallback} event - Коллбэк. Вызывается при запросе на подписание.
396
537
  */
397
538
  async addConfirmKSPOperationEventListener(event) {
398
- if (this._euSign != this._euSignFile) {
399
- await this.setLibraryType(DigitalSignatureKeyType.KSP);
400
- }
401
-
402
- await this._euSign.AddEventListener(EndUserConstants.EndUserEventType.ConfirmKSPOperation, function(data) {
539
+ await this._euSignFile.AddEventListener(EndUserConstants.EndUserEventType.ConfirmKSPOperation, function(data) {
403
540
  data.qrCode = "data:image/bmp;base64," + data.qrCode;
404
541
  event(data);
405
542
  });
@@ -410,7 +547,7 @@ export default class DigitalSignature {
410
547
  * @returns {KSPSettings[]} Список поддержирживаемых облачных провайдеров
411
548
  */
412
549
  get KSPs() {
413
- return this._KSPs;
550
+ return this._KSPs.asArray();
414
551
  }
415
552
 
416
553
  /**
@@ -418,16 +555,18 @@ export default class DigitalSignature {
418
555
  * @returns {Promise<boolean>} считан ли ключ
419
556
  */
420
557
  async isPrivateKeyReaded() {
421
- return !!this._readedKey && await this._euSign.IsPrivateKeyReaded();
558
+ try {
559
+ return !!this._readedKey && await this._euSign.IsPrivateKeyReaded();
560
+ } catch {
561
+ return false;
562
+ }
422
563
  }
423
564
 
424
565
  /**
425
566
  * Очистить считанный ключ
426
567
  */
427
568
  async resetPrivateKey() {
428
- await Promise.all([
429
- this._euSign.ResetPrivateKey()
430
- ]);
569
+ await this._euSign.ResetPrivateKey();
431
570
  this._readedKey = null;
432
571
  }
433
572
 
@@ -436,7 +575,7 @@ export default class DigitalSignature {
436
575
  * @returns {Promise<Array<CASettings>>}
437
576
  */
438
577
  async getCAs() {
439
- return await this._euSign.GetCAs();
578
+ return await this._euSignFile.GetCAs();
440
579
  }
441
580
 
442
581
  /**
@@ -460,49 +599,78 @@ export default class DigitalSignature {
460
599
 
461
600
  /**
462
601
  * Подписать данные
463
- * @param {Uint8Array | string | NamedeData | Array<Uint8Array | string | NamedeData>} data - Данные для подписи. Можно передавать данные в массиве для наложения нескольких подписей за раз
464
- * @param {boolean?} internal - Внутренняя или внешняя подпись
602
+ * @param {Uint8Array | string | NamedData | Array<Uint8Array | string | NamedData>} data - Данные для подписи. Можно передавать данные в массиве для наложения нескольких подписей за раз
603
+ * @param {EndUserSignContainerInfo?} signType - Тип подписи
465
604
  * @param {boolean?} asByteArray - Возвращать подпись в виде массива байтов. По умолчанию подпись возвращается в виде строки в base64
466
605
  */
467
- async signData(data, internal, asByteArray) {
468
- if (typeof internal !== "boolean") {
469
- internal = false;
606
+ async signData(data, signType, asByteArray) {
607
+ if (signType === true) {
608
+ // Обратная совместимость. Если передать в параметре значение true, дожна создаться подпись CAdES Enveloped (внутренняя)
609
+ signType = new EndUserSignContainerInfo();
610
+ signType.type = EndUserConstants.EndUserSignContainerType.CAdES;
611
+ signType.subType = EndUserConstants.EndUserCAdESType.Enveloped;
612
+ } else if (typeof signType != "object") {
613
+ // По умолчанию создаётся подпись CAdES Detached
614
+ signType = new EndUserSignContainerInfo();
615
+ signType.type = EndUserConstants.EndUserSignContainerType.CAdES;
616
+ signType.subType = EndUserConstants.EndUserCAdESType.Detached;
470
617
  }
471
618
  if (typeof asByteArray !== "boolean") {
472
619
  asByteArray = false;
473
620
  }
474
- return await this._euSign.SignDataEx(this._readedKey.getSignAlgo(), data, !internal, true, !asByteArray);
621
+
622
+ if (this.readedKey.keyType === DigitalSignatureKeyType.KSP && signType.type !== EndUserConstants.EndUserSignContainerType.CAdES) {
623
+ throw {
624
+ code: EndUserError.EU_ERROR_NOT_SUPPORTED,
625
+ message: this._resourses.KSPSignFormatError
626
+ };
627
+ }
628
+
629
+ if (signType.type === EndUserConstants.EndUserSignContainerType.ASiC) {
630
+ const signLevel = signType.asicSignType === EndUserConstants.EndUserASiCSignType.CAdES ? EndUserConstants.EndUserSignType.CAdES_X_Long : EndUserConstants.EndUserXAdESSignLevel.B_LT;
631
+ return await this._euSign.ASiCSignData(this._readedKey.getSignAlgo(), signType.subType, signType.asicSignType, signLevel, data, !asByteArray);
632
+ } else if (signType.type === EndUserConstants.EndUserSignContainerType.XAdES) {
633
+ let returnArray = true;
634
+ if (!Array.isArray(data)) {
635
+ data = [data];
636
+ returnArray = false;
637
+ }
638
+ data = data.map((e, i) => !e.name && !e.val ? { name: "data" + i, val: e } : e);
639
+ const signs = [];
640
+ for (let i = 0; i < data.length; i++) {
641
+ const result = await this._euSign.XAdESSignData(this._readedKey.getSignAlgo(), signType.subType, EndUserConstants.EndUserXAdESSignLevel.B_LT, data[i], !asByteArray);
642
+ signs[i] = result;
643
+ }
644
+ return returnArray ? signs : signs[0];
645
+ } else if (signType.type === EndUserConstants.EndUserSignContainerType.PAdES) {
646
+ return await this._euSign.PDFSignData(this._readedKey.getSignAlgo(), data, EndUserConstants.EndUserPAdESSignLevel.B_T, !asByteArray);
647
+ } else if (signType.type === EndUserConstants.EndUserSignContainerType.CAdES) {
648
+ if (signType.subType === EndUserConstants.EndUserCAdESType.Detached) {
649
+ const hash = await this._euSign.HashData(this._readedKey.getHashAlgo(), data, !asByteArray);
650
+ return await this._euSign.SignHash(this._readedKey.getSignAlgo(), hash, true, !asByteArray);
651
+ } else {
652
+ return await this._euSign.SignDataEx(this._readedKey.getSignAlgo(), data, false, true, !asByteArray);
653
+ }
654
+ } else {
655
+ throw {
656
+ code: EndUserError.EU_ERROR_BAD_PARAMETER,
657
+ message: this._resourses.BadSignatureType
658
+ };
659
+ }
475
660
  }
476
661
 
477
662
  /**
478
663
  * Подписать файл
479
- * @param {string | NamedeData | Array<string | NamedeData>} fileUrl - Ссылка на загрузку файла. Можно передавать несколько ссылок в массиве для наложения нескольких подписей за раз
480
- * @param {boolean?} internal - Внутренняя или внешняя подпись
664
+ * @param {string | NamedData | Array<string | NamedData>} fileUrl - Ссылка на загрузку файла. Можно передавать несколько ссылок в массиве для наложения нескольких подписей за раз
665
+ * @param {EndUserSignContainerInfo?} signType - Тип подписи
481
666
  * @param {boolean?} asByteArray - Возвращать подпись в виде массива байт. По умолчанию подпись возвращается в виде строки в base64
482
667
  */
483
- async signFile(fileUrl, internal, asByteArray) {
484
- if (typeof internal !== "boolean") {
485
- internal = false;
486
- }
487
- if (typeof asByteArray !== "boolean") {
488
- asByteArray = false;
489
- }
490
- const isNamedData = Array.isArray(fileUrl) && fileUrl.every(url => typeof url === "object") || typeof fileUrl === "object";
491
- let data;
492
- if (Array.isArray(fileUrl)) {
493
- data = await Promise.all(fileUrl.map(function(url) {
494
- const downloadedData = downloadData(isNamedData ? url.val : url, "binary");
495
- return isNamedData ? {name: url.name, val: downloadedData} : downloadedData;
496
- }));
497
- } else {
498
- const downloadedData = await downloadData(isNamedData ? fileUrl.val : fileUrl, "binary");
499
- data = isNamedData ? {name: fileUrl.name, val: downloadedData} : downloadedData;
500
- }
501
- return await this.signData(data, internal, asByteArray);
668
+ async signFile(fileUrl, signType, asByteArray) {
669
+ return await downloadAndSignFiles(fileUrl, async (data) => await this.signData(data, signType, asByteArray));
502
670
  }
503
671
 
504
672
  /**
505
- * Подписать хеш
673
+ * Подписать хеш подписью CAdES Detached
506
674
  * @param {Uint8Array | string | NamedData | Array<Uint8Array | string> | NamedData} hash - Хеш файла. Можно передавать несколько хешей в массиве для наложения нескольких подписей за раз
507
675
  * @param {boolean?} asByteArray - Возвращать подпись в виде массива байт. По умолчанию подпись возвращается в виде строки в base64
508
676
  */
@@ -514,38 +682,57 @@ export default class DigitalSignature {
514
682
  }
515
683
 
516
684
  /**
517
- * Подписать хеш из файла
685
+ * Подписать хеш из файла подписью CAdES Detached
518
686
  * @param {string | NamedData | Array<string | NamedData>} hashUrl - Ссылка на скачивание хеша. Можно передавать несколько ссылок в массиве для наложения нескольких подписей за раз
519
687
  * @param {boolean?} asByteArray - Возвращать подпись в виде массива байт. По умолчанию подпись возвращается в виде строки в base64
520
688
  */
521
689
  async signFileHash(hashUrl, asByteArray) {
522
- if (typeof asByteArray !== "boolean") {
523
- asByteArray = false;
524
- }
525
- const isNamedData = Array.isArray(hashUrl) && hashUrl.every(url => typeof url === "object") || typeof hashUrl === "object";
526
- let hash;
527
- if (Array.isArray(hashUrl)) {
528
- hash = await Promise.all(hashUrl.map(function(url) {
529
- const downloadedData = downloadData(isNamedData ? url.val : url, "binary");
530
- return isNamedData ? {name: url.name, val: downloadedData} : downloadedData;
531
- }));
532
- } else {
533
- const downloadedData = await downloadData(hashUrl, "binary");
534
- hash = isNamedData ? {name: hashUrl.name, val: downloadedData} : downloadedData;
535
- }
536
- return await this.signHash(hash, asByteArray);
690
+ return await downloadAndSignFiles(hashUrl, async (hash) => await this.signHash(hash, asByteArray));
537
691
  }
538
692
 
539
693
  /**
540
- * Проверить внешнюю подпись
694
+ * Получить данные о типе подписи
695
+ * @param {string | Uint8Array} sign - Подпись
696
+ */
697
+ async getSignContainerInfo(sign) {
698
+ return await this._euSign.GetSignContainerInfo(sign);
699
+ }
700
+
701
+ /**
702
+ * Проверить подпись (Соответствие подписанных данных не проверяется)
541
703
  * @param {Uint8Array | string} data - Подписанные данные
542
704
  * @param {Uint8Array | string} sign - Подпись
543
705
  * @param {number?} signIndex - Номер подписи. -1 что бы проверить все подписи
544
706
  */
545
707
  async verifyData(data, sign, signIndex) {
546
708
  if (!Number.isInteger(signIndex)) {
547
- signIndex = 0;
709
+ signIndex = -1;
548
710
  }
711
+ const signContainerInfo = await this._euSign.GetSignContainerInfo(sign.val || sign);
712
+
713
+ if (signContainerInfo.type === EndUserConstants.EndUserSignContainerType.ASiC) {
714
+ return await this._euSign.ASiCVerifyData(sign, signIndex);
715
+ } else if (signContainerInfo.type === EndUserConstants.EndUserSignContainerType.PAdES) {
716
+ return await this._euSign.PDFVerifyData(sign, signIndex);
717
+ } else if (signContainerInfo.type === EndUserConstants.EndUserSignContainerType.XAdES) {
718
+ return await this._euSign.XAdESVerifyData(data, sign.val || sign, signIndex);
719
+ } else if (signContainerInfo.type === EndUserConstants.EndUserSignContainerType.CAdES) {
720
+ if (signContainerInfo.subType === EndUserConstants.EndUserCAdESType.Detached) {
721
+ if (!data) {
722
+ throw {
723
+ code: EndUserError.EU_ERROR_BAD_PARAMETER,
724
+ message: this._resourses.BadParameter + " data"
725
+ };
726
+ }
727
+ const signerInfo = await this._euSign.GetSigner(sign, signIndex, false);
728
+ const hashAlgo = signAlgoToHashAlgo((Array.isArray(signerInfo) ? signerInfo[0] : signerInfo).infoEx.publicKeyType);
729
+ const hash = await this._euSign.HashData(hashAlgo, data);
730
+ return await this._euSign.VerifyHash(hash.val || hash, sign.val || sign, signIndex);
731
+ } else {
732
+ return await this._euSign.VerifyDataInternal(sign.val || sign, signIndex);
733
+ }
734
+ }
735
+
549
736
  return await this._euSign.VerifyData(data, sign, signIndex);
550
737
  }
551
738
 
@@ -569,10 +756,7 @@ export default class DigitalSignature {
569
756
  * @param {number?} signIndex - Номер подписи. -1 что бы проверить все подписи
570
757
  */
571
758
  async verifyDataInternal(sign, signIndex) {
572
- if (!Number.isInteger(signIndex)) {
573
- signIndex = 0;
574
- }
575
- return await this._euSign.VerifyDataInternal(sign, signIndex);
759
+ return await this.verifyData(null, sign, signIndex);
576
760
  }
577
761
 
578
762
  /**
@@ -586,7 +770,7 @@ export default class DigitalSignature {
586
770
  }
587
771
 
588
772
  /**
589
- * Проверить подпись хеша
773
+ * Проверить подпись хеша (Только CAdES Detached)
590
774
  * @param {Uint8Array | string} hash - Хеш подписанного файла
591
775
  * @param {Uint8Array | string} sign - Подпись
592
776
  * @param {number?} signIndex - Номер подписи. -1 что бы проверить все подписи
@@ -599,7 +783,7 @@ export default class DigitalSignature {
599
783
  }
600
784
 
601
785
  /**
602
- * Проверить подпись хеша из файла
786
+ * Проверить подпись хеша из файла (Только CAdES Detached)
603
787
  * @param {string} hashUrl - Ссылка на загрузку файла
604
788
  * @param {string} signUrl - Ссылка на загрузку подписи
605
789
  * @param {number?} signIndex - Номер подписи. -1 что бы проверить все подписи
@@ -614,34 +798,48 @@ export default class DigitalSignature {
614
798
 
615
799
  /**
616
800
  * Выполнить подписание данных с проверкой подписи
617
- * @param {Uint8Array | string | NamedData} data - данные для подписи
618
- * @param {boolean?} internal - Накладывать внутреннюю или внешнюю подпись. По умолчанию накладывается внешняя.
619
- */
620
- async signDataEx(data, internal) {
621
- if (typeof internal !== "boolean") {
622
- internal = false;
623
- }
624
- const isNamedData = typeof data === "object" && !(data instanceof Uint8Array);
625
- if (!internal) {
626
- const hashedData = await this._euSign.HashData(this._readedKey.getHashAlgo(), isNamedData ? data.val : data, false);
627
- if(isNamedData) {
628
- data.val = hashedData;
629
- } else {
630
- data = hashedData;
801
+ * @param {Uint8Array | string | NamedData | Array<Uint8Array | string | NamedData>} data - Данные для подписи. Можно передавать данные в массиве для наложения нескольких подписей за раз
802
+ * @param {EndUserSignContainerInfo?} signType - Тип подписи
803
+ */
804
+ async signDataEx(data, signType) {
805
+ const signs = await this.signData(data, signType);
806
+
807
+ if (Array.isArray(data)) {
808
+ const result = [];
809
+ for (let i = 0; i < signs.length; i++) {
810
+ const sign = signs[i];
811
+ let signInfo = await this.verifyData(data[i], sign);
812
+ if (Array.isArray(signInfo)) {
813
+ signInfo = signInfo[signInfo.length - 1];
814
+ }
815
+ result[i] = {
816
+ Success: true,
817
+ Sign: sign.val || sign,
818
+ SignatureInfo: {
819
+ Success: true,
820
+ DateTimeStr: signInfo.timeInfo.time,
821
+ Signer: signInfo.ownerInfo.subjCN,
822
+ OwnerInfo: signInfo.ownerInfo
823
+ }
824
+ };
631
825
  }
632
- }
633
- const signature = internal ? await this.signData(data, internal) : await this.signHash(data);
634
- const signatureInfo = internal ? await this.verifyDataInternal(isNamedData ? signature.val : signature, 0) : await this.verifyHash(isNamedData ? data.val : data, isNamedData ? signature.val : signature);
635
- return {
636
- Success: true,
637
- Sign: isNamedData ? signature.val : signature,
638
- SignatureInfo: {
639
- Success: true,
640
- DateTimeStr: signatureInfo.timeInfo.time,
641
- Signer: signatureInfo.ownerInfo.subjCN,
642
- OwnerInfo: signatureInfo.ownerInfo
826
+ return result;
827
+ } else {
828
+ let signInfo = await this.verifyData(data, signs);
829
+ if (Array.isArray(signInfo)) {
830
+ signInfo = signInfo[signInfo.length - 1];
643
831
  }
644
- };
832
+ return {
833
+ Success: true,
834
+ Sign: signs.val || signs,
835
+ SignatureInfo: {
836
+ Success: true,
837
+ DateTimeStr: signInfo.timeInfo.time,
838
+ Signer: signInfo.ownerInfo.subjCN,
839
+ OwnerInfo: signInfo.ownerInfo
840
+ }
841
+ };
842
+ }
645
843
  }
646
844
 
647
845
  /**
@@ -652,24 +850,14 @@ export default class DigitalSignature {
652
850
  async signFileEx(fileUrl, hash) {
653
851
  const isNamedData = typeof fileUrl === "object";
654
852
  let data = await downloadData(isNamedData ? fileUrl.val : fileUrl, "binary");
655
- if(!hash) {
656
- data = await this._euSign.HashData(this._readedKey.getHashAlgo(), data, false);
657
- if(isNamedData) {
658
- data = {name: fileUrl.name, val: data};
659
- }
853
+ if (isNamedData) {
854
+ data = {name: fileUrl.name, val: data};
855
+ }
856
+ if (hash) {
857
+ return await this.signHashEx(data);
858
+ } else {
859
+ return await this.signDataEx(data, false);
660
860
  }
661
- const signature = await this.signHash(data);
662
- const signatureInfo = await this.verifyHash(isNamedData ? data.val : data, isNamedData ? signature.val : signature);
663
- return {
664
- Success: true,
665
- Sign: isNamedData ? signature.val : signature,
666
- SignatureInfo: {
667
- Success: true,
668
- DateTimeStr: signatureInfo.timeInfo.time,
669
- Signer: signatureInfo.ownerInfo.subjCN,
670
- OwnerInfo: signatureInfo.ownerInfo
671
- }
672
- };
673
861
  }
674
862
 
675
863
  /**
@@ -750,18 +938,18 @@ export default class DigitalSignature {
750
938
  * @param {boolean} toLocalStorage - Будет ли ключ сохранён после закрытия вкладки
751
939
  */
752
940
  async storePrivateKeyInfo(privateKeyInfo, toLocalStorage) {
753
- if(privateKeyInfo.ksp && (privateKeyInfo.ksp == EndUserConstants.EndUserKSP.DIIA || privateKeyInfo.ksp == this.KSPs[1].name)) {
941
+ if (privateKeyInfo.ksp && privateKeyInfo.ksp.needQrcode) {
754
942
  return;
755
943
  }
756
944
 
757
945
  const storage = toLocalStorage ? localStorage : sessionStorage;
758
946
  const keys = await this.getStoredPrivateKeyInfo();
759
947
 
760
- if(keys.length == 0 || keys.filter(key => key.id == privateKeyInfo.id).length < 1) {
948
+ if (keys.length == 0 || keys.filter(key => key.id == privateKeyInfo.id).length < 1) {
761
949
  const keys = await this.getPrivateKeyInfoFromStorage(toLocalStorage);
762
950
  keys.push(privateKeyInfo);
763
951
  const data = await this._euSignFile.ProtectDataByPassword(JSON.stringify(keys), "", true);
764
- storage[this._userId + this.PRIVATE_KEY_INFO] = data;
952
+ storage[this._userId + PRIVATE_KEY_INFO] = data;
765
953
  }
766
954
  }
767
955
 
@@ -778,27 +966,27 @@ export default class DigitalSignature {
778
966
 
779
967
  async getPrivateKeyInfoFromStorage(fromLocalStorage, keyType) {
780
968
  const storage = fromLocalStorage ? localStorage : sessionStorage;
781
- const storedKeys = storage[this._userId + this.PRIVATE_KEY_INFO];
969
+ const storedKeys = storage[this._userId + PRIVATE_KEY_INFO];
782
970
 
783
971
  let ls;
784
972
  const result = [];
785
- if(!storedKeys) {
973
+ if (!storedKeys) {
786
974
  return result;
787
975
  }
788
976
  const data = await this._euSignFile.UnprotectDataByPassword(storedKeys, "", true);
789
- try{
977
+ try {
790
978
  ls = JSON.parse(data);
791
- if(keyType >= 0) {
979
+ if (keyType >= 0) {
792
980
  ls = ls.filter(key => key.keyType == keyType);
793
981
  }
794
982
  }
795
- catch{
983
+ catch {
796
984
  ls = [];
797
985
  }
798
986
 
799
987
  ls.forEach(key => {
800
988
  const password = key.password;
801
- if(key.keyType == DigitalSignatureKeyType.File) {
989
+ if (key.keyType == DigitalSignatureKeyType.File) {
802
990
  key.privateKey = new Uint8Array(Object.assign(new Array(), key.privateKey));
803
991
  key = new FilePrivateKeyInfo(key.keyType, key.ownerInfo, key.certificates, key.privateKey, key.password);
804
992
  }
@@ -825,19 +1013,19 @@ export default class DigitalSignature {
825
1013
  * Удалить сохранённые ключи
826
1014
  * @param {string} keyId - Идентификатор ключа
827
1015
  */
828
- async removeStoredPrivateKeyInfo(keyId) {
829
- if (keyId === undefined) {
1016
+ async removeStoredPrivateKeyInfo(keyId) {
1017
+ if (keyId === undefined) {
830
1018
  localStorage.removeItem(this._userId + this.PRIVATE_KEY_INFO);
831
1019
  sessionStorage.removeItem(this._userId + this.PRIVATE_KEY_INFO);
832
1020
  }
833
- else{
1021
+ else {
834
1022
  const localStoredKeys = await this.getPrivateKeyInfoFromStorage(true);
835
1023
  const sessionStoredKeys = await this.getPrivateKeyInfoFromStorage(false);
836
1024
  const storage = localStoredKeys.filter(item => item.id == keyId).length > 0 ? localStorage : sessionStorage;
837
1025
  const keys = storage == localStorage ? localStoredKeys : sessionStoredKeys;
838
1026
  const keyIndex = keys.findIndex((element) => element.id == keyId);
839
1027
 
840
- if(keyIndex > -1) {
1028
+ if (keyIndex > -1) {
841
1029
  keys.splice(keyIndex, 1);
842
1030
  const data = await this._euSignFile.ProtectDataByPassword(JSON.stringify(keys), "", true);
843
1031
  storage[this._userId + this.PRIVATE_KEY_INFO] = data;
@@ -849,11 +1037,11 @@ export default class DigitalSignature {
849
1037
  * Получить предпочитаемый тип ключа
850
1038
  */
851
1039
  get _preferredKeyType () {
852
- const keyType = parseInt(localStorage[this._userId + this.PRIVATE_KEY_TYPE]);
1040
+ const keyType = parseInt(localStorage[this._userId + PRIVATE_KEY_TYPE]);
853
1041
  if (typeof keyType === "number" && keyType > -1) {
854
1042
  return keyType;
855
1043
  } else {
856
- localStorage[this._userId + this.PRIVATE_KEY_TYPE] = DigitalSignatureKeyType.File;
1044
+ localStorage[this._userId + PRIVATE_KEY_TYPE] = DigitalSignatureKeyType.File;
857
1045
  return DigitalSignatureKeyType.File;
858
1046
  }
859
1047
  }
@@ -864,15 +1052,36 @@ export default class DigitalSignature {
864
1052
  */
865
1053
  set _preferredKeyType (keyType) {
866
1054
  if (typeof keyType === "number" && keyType > -1) {
867
- localStorage[this._userId + this.PRIVATE_KEY_TYPE] = keyType;
1055
+ localStorage[this._userId + PRIVATE_KEY_TYPE] = keyType;
868
1056
  }
869
1057
  }
870
1058
 
871
1059
  get _resourses() {
872
- return Resourses[this._settings.language];
1060
+ return Resourses[this._language];
873
1061
  }
874
1062
 
875
1063
  get _userId () {
876
- return typeof this._settings.userId === "function" ? this._settings.userId() : this._settings.userId;
1064
+ return typeof this._settingsProvider.userId === "function" ? this._settingsProvider.userId() : this._settingsProvider.userId;
1065
+ }
1066
+
1067
+ get _language () {
1068
+ return typeof this._settingsProvider.language === "function" ? this._settingsProvider.language() : this._settingsProvider.language;
1069
+ }
1070
+
1071
+ /**
1072
+ * Проверка доступности Дiя
1073
+ */
1074
+ async _diiaCheckAccess() {
1075
+ const diia = "DIIA";
1076
+ if (sessionStorage[diia] != "+") {
1077
+ try {
1078
+ sessionStorage[diia] = await downloadData(new URL(this._KSPs.diia.address).origin + "/diia");
1079
+ } catch {
1080
+ throw {
1081
+ code: EndUserError.EU_ERROR_TRANSMIT_REQUEST,
1082
+ message: this._resourses.DiiaError + `<a target="_blank" href="${window.origin}">${window.origin}</a></li><ul>`
1083
+ };
1084
+ }
1085
+ }
877
1086
  }
878
1087
  }