@universis/signer-worker 1.1.0 → 1.5.0
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.
- package/README.md +89 -3
- package/dist/SignerService.d.ts +72 -0
- package/dist/SignerServiceImpl.d.ts +61 -0
- package/dist/SignerWorker.d.ts +5 -24
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +341 -11
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +343 -9
- package/dist/index.js.map +1 -1
- package/dist/signerWorkerRouter.d.ts +1 -1
- package/jest.setup.js +23 -0
- package/package.json +16 -7
- package/src/SignerService.ts +339 -0
- package/src/SignerServiceImpl.ts +58 -0
- package/src/SignerWorker.ts +32 -76
- package/src/index.ts +3 -1
- package/src/signerWorkerRouter.ts +3 -2
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Install `@universis/signer-worker`
|
|
|
8
8
|
|
|
9
9
|
npm i @universis/signer-worker
|
|
10
10
|
|
|
11
|
-
Important note: Verify that [Universis Signer](https://gitlab.com/universis/universis-signer) is already installed as
|
|
11
|
+
Important note: Verify that [Universis Signer](https://gitlab.com/universis/universis-signer) is already installed as a service. Follow [installation instructions](https://gitlab.com/universis/universis-signer#installation) to install and configure Universis Signer.
|
|
12
12
|
|
|
13
13
|
## Configuration
|
|
14
14
|
|
|
@@ -16,10 +16,96 @@ Configure [Universis api server](https://gitlab.com/universis/universis-api) to
|
|
|
16
16
|
|
|
17
17
|
{
|
|
18
18
|
"services": [
|
|
19
|
-
...,
|
|
20
19
|
{
|
|
21
20
|
"serviceType": "@universis/signer-worker#SignerWorker"
|
|
22
21
|
}
|
|
23
22
|
]
|
|
24
|
-
...
|
|
25
23
|
}
|
|
24
|
+
|
|
25
|
+
or configure [Universis api server](https://gitlab.com/universis/universis-api) to use `SignerService` service.
|
|
26
|
+
|
|
27
|
+
{
|
|
28
|
+
"services": [
|
|
29
|
+
{
|
|
30
|
+
"serviceType": "@universis/signer-worker#SignerService"
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Sign documents
|
|
36
|
+
|
|
37
|
+
> `SignerService.signFile(context: DataContext, file: string, contentType?: string, options?: { timestampServer?: string, position?: string, reason?: string })`
|
|
38
|
+
|
|
39
|
+
`SignerService.signFile` can be used for signing *.pdf files using a server instance of [Universis Signer](https://gitlab.com/universis/universis-signer#use-docker-image)
|
|
40
|
+
|
|
41
|
+
For completing sign process, the signer service instance should be configured by adding `settings/universis/signerService` section at application configuration
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
|
|
45
|
+
{
|
|
46
|
+
"settings": {
|
|
47
|
+
"universis": {
|
|
48
|
+
"signerService": {
|
|
49
|
+
"user": "user",
|
|
50
|
+
"password": "password",
|
|
51
|
+
"host": "http://localhost:2465"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
where `host` is the URL of the Universis Signer service, `user` and `password` are the credentials for the Universis Signer service.
|
|
60
|
+
|
|
61
|
+
Normally, the Universis Signer service which is running on `http://localhost:2465`, uses a PKCS12 store with secret key and certificate for signing.
|
|
62
|
+
In this case, the `user` and `password` are used to access the PKCS12 store. `SignerService` service will try to get valid certificates,
|
|
63
|
+
and select the first one that is valid for signing. Use the configuration setting `thumbnail` if you want to explicitly define
|
|
64
|
+
the certificate that should be used for signing.
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"settings": {
|
|
69
|
+
"universis": {
|
|
70
|
+
"signerService": {
|
|
71
|
+
"user": "user",
|
|
72
|
+
"password": "password",
|
|
73
|
+
"host": "http://localhost:2465",
|
|
74
|
+
"thumbnail": "2d49be3b03db5d176482640549d5aaede84c925d"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
# Sign data
|
|
82
|
+
|
|
83
|
+
> `SignerService.sign(context: DataContext, data: unknown, options?: { timestampServer?: string, reason?: string })`
|
|
84
|
+
|
|
85
|
+
`SignerService.sign` method can be used for signing data.
|
|
86
|
+
The data can be any JSON-serializable object, and the method will return a signed version of the data.
|
|
87
|
+
|
|
88
|
+
The `options` parameter can include a `timestampServer` URL for timestamping the signature, a `position` string to specify the position of the signature in the document,
|
|
89
|
+
and a `reason` string to specify the reason for signing.
|
|
90
|
+
|
|
91
|
+
# Example
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import {SignerService} from '@universis/signer-worker';
|
|
95
|
+
|
|
96
|
+
function signJson(context: DataContext, data: unknown) {
|
|
97
|
+
const signerService = context.getApplication().getService(SignerService);
|
|
98
|
+
return signerService.sign(context, data, {
|
|
99
|
+
timestampServer: "http://timestamp.server.com",
|
|
100
|
+
reason: "Signing example data"
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
signJson(context, { message: "Hello, World!" })
|
|
105
|
+
.then(signedData => {
|
|
106
|
+
console.log("Signed data:", signedData);
|
|
107
|
+
})
|
|
108
|
+
.catch(error => {
|
|
109
|
+
console.error("Error signing data:", error);
|
|
110
|
+
});
|
|
111
|
+
```
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { ApplicationBase, ApplicationService } from '@themost/common';
|
|
3
|
+
import { ExpressDataContext } from '@themost/express';
|
|
4
|
+
import { Certificate, JsonWebKey, VerifySignatureResult, SignJsonSignatureResult, SignerServiceImpl } from './SignerServiceImpl';
|
|
5
|
+
import { TOTPOptions } from 'xotp';
|
|
6
|
+
export declare class SignerService extends ApplicationService implements SignerServiceImpl {
|
|
7
|
+
protected options?: {
|
|
8
|
+
host?: string;
|
|
9
|
+
user?: string;
|
|
10
|
+
password?: string;
|
|
11
|
+
secret?: string;
|
|
12
|
+
thumbprint?: string;
|
|
13
|
+
inspector?: {
|
|
14
|
+
text?: string[];
|
|
15
|
+
textPosition: string;
|
|
16
|
+
width?: number;
|
|
17
|
+
height?: number;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
constructor(app: ApplicationBase);
|
|
21
|
+
/**
|
|
22
|
+
* @param {ExpressDataContext} context The underlying data context
|
|
23
|
+
* @param {*} data The data to be verified
|
|
24
|
+
* @param {{filename: string, contentType: string}=} options Extra attributes (optional)
|
|
25
|
+
* @returns {Promise<VerifySignatureResult[]>}
|
|
26
|
+
*/
|
|
27
|
+
verify(context: ExpressDataContext, data: unknown, options?: {
|
|
28
|
+
filename: string;
|
|
29
|
+
contentType: string;
|
|
30
|
+
}): Promise<VerifySignatureResult[]>;
|
|
31
|
+
getJwks(): Promise<JsonWebKey[]>;
|
|
32
|
+
getCerts(): Promise<Certificate[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Generates a one-time password using the configured secret.
|
|
35
|
+
*/
|
|
36
|
+
generateVerificationCode(options?: TOTPOptions): string;
|
|
37
|
+
/**
|
|
38
|
+
* @param {ExpressDataContext} context The underlying data context
|
|
39
|
+
* @param {*} data Any JSON object to be signed
|
|
40
|
+
* @param {{timestampServer?:string}} extraOptions
|
|
41
|
+
* @returns {Promise<SignJsonSignatureResult>} An array of signatures
|
|
42
|
+
*/
|
|
43
|
+
sign(context: ExpressDataContext, data: unknown, extraOptions?: {
|
|
44
|
+
timestampServer?: string;
|
|
45
|
+
reason?: string;
|
|
46
|
+
}): Promise<SignJsonSignatureResult>;
|
|
47
|
+
signFile(context: ExpressDataContext, file: string, contentType?: string, extraOptions?: {
|
|
48
|
+
page?: number;
|
|
49
|
+
position?: number[];
|
|
50
|
+
timestampServer?: string;
|
|
51
|
+
reason?: string;
|
|
52
|
+
}): Promise<Buffer>;
|
|
53
|
+
verifyFile(context: ExpressDataContext, file: string, options?: {
|
|
54
|
+
filename: string;
|
|
55
|
+
contentType: string;
|
|
56
|
+
}): Promise<VerifySignatureResult[]>;
|
|
57
|
+
inspect(context: ExpressDataContext, file: string, options?: {
|
|
58
|
+
filename: string;
|
|
59
|
+
contentType: string;
|
|
60
|
+
}): Promise<{
|
|
61
|
+
page: number;
|
|
62
|
+
position: number[];
|
|
63
|
+
text: string;
|
|
64
|
+
}>;
|
|
65
|
+
requiresVerificationCode(): boolean;
|
|
66
|
+
protected signFinal<T>(context: ExpressDataContext, file: string, contentType?: string, extraOptions?: {
|
|
67
|
+
position?: number[];
|
|
68
|
+
page?: number;
|
|
69
|
+
timestampServer?: string;
|
|
70
|
+
reason?: string;
|
|
71
|
+
}): Promise<T>;
|
|
72
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { ExpressDataContext } from '@themost/express';
|
|
3
|
+
export declare interface Certificate {
|
|
4
|
+
version: number;
|
|
5
|
+
subjectDN: string;
|
|
6
|
+
sigAlgName?: string;
|
|
7
|
+
sigAlgOID?: string;
|
|
8
|
+
issuerDN: string;
|
|
9
|
+
serialNumber: any;
|
|
10
|
+
notAfter: string;
|
|
11
|
+
notBefore: string;
|
|
12
|
+
expired: boolean;
|
|
13
|
+
thumbprint: string;
|
|
14
|
+
commonName: string;
|
|
15
|
+
}
|
|
16
|
+
export declare interface JsonWebKey {
|
|
17
|
+
kty: string;
|
|
18
|
+
use: string;
|
|
19
|
+
kid: string;
|
|
20
|
+
alg: string;
|
|
21
|
+
n: string;
|
|
22
|
+
e: string;
|
|
23
|
+
}
|
|
24
|
+
export declare interface VerifySignatureResult {
|
|
25
|
+
valid: boolean;
|
|
26
|
+
certificates: Certificate[];
|
|
27
|
+
signatureProperties?: {
|
|
28
|
+
signingDate: string;
|
|
29
|
+
reason: string;
|
|
30
|
+
signingCertificate: Certificate;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export declare interface SignJsonSignatureResult {
|
|
34
|
+
payload: string;
|
|
35
|
+
signatures: {
|
|
36
|
+
protected: string;
|
|
37
|
+
signature: string;
|
|
38
|
+
}[];
|
|
39
|
+
}
|
|
40
|
+
export declare interface SignerServiceImpl {
|
|
41
|
+
getCerts(): Promise<Certificate[]>;
|
|
42
|
+
getJwks(): Promise<JsonWebKey[]>;
|
|
43
|
+
verify(context: ExpressDataContext, document: string, options?: {
|
|
44
|
+
filename: string;
|
|
45
|
+
contentType: string;
|
|
46
|
+
}): Promise<VerifySignatureResult[]>;
|
|
47
|
+
verifyFile(context: ExpressDataContext, file: string, options?: {
|
|
48
|
+
filename: string;
|
|
49
|
+
contentType: string;
|
|
50
|
+
}): Promise<VerifySignatureResult[]>;
|
|
51
|
+
signFile(context: ExpressDataContext, file: string, contentType?: string, extraOptions?: {
|
|
52
|
+
page?: number;
|
|
53
|
+
position?: number[];
|
|
54
|
+
timestampServer?: string;
|
|
55
|
+
reason?: string;
|
|
56
|
+
}): Promise<Buffer>;
|
|
57
|
+
sign(context: ExpressDataContext, data: unknown, extraOptions?: {
|
|
58
|
+
timestampServer?: string;
|
|
59
|
+
reason?: string;
|
|
60
|
+
}): Promise<SignJsonSignatureResult>;
|
|
61
|
+
}
|
package/dist/SignerWorker.d.ts
CHANGED
|
@@ -1,39 +1,20 @@
|
|
|
1
1
|
import { ApplicationBase, ApplicationService } from '@themost/common';
|
|
2
2
|
import { ExpressDataContext } from '@themost/express';
|
|
3
|
-
|
|
4
|
-
version: number;
|
|
5
|
-
subjectDN: string;
|
|
6
|
-
sigAlgName?: string;
|
|
7
|
-
sigAlgOID?: string;
|
|
8
|
-
issuerDN: string;
|
|
9
|
-
serialNumber: any;
|
|
10
|
-
notAfter: string;
|
|
11
|
-
notBefore: string;
|
|
12
|
-
expired: boolean;
|
|
13
|
-
thumbprint: string;
|
|
14
|
-
commonName: string;
|
|
15
|
-
}
|
|
16
|
-
declare interface VerifySignatureResult {
|
|
17
|
-
valid: boolean;
|
|
18
|
-
certificates: Certificate[];
|
|
19
|
-
signatureProperties?: {
|
|
20
|
-
signingDate: string;
|
|
21
|
-
reason: string;
|
|
22
|
-
signingCertificate: Certificate;
|
|
23
|
-
};
|
|
24
|
-
}
|
|
3
|
+
import { VerifySignatureResult } from './SignerServiceImpl';
|
|
25
4
|
export declare class SignerWorker extends ApplicationService {
|
|
5
|
+
protected options?: {
|
|
6
|
+
host?: string;
|
|
7
|
+
};
|
|
26
8
|
constructor(app: ApplicationBase);
|
|
27
9
|
/**
|
|
28
10
|
*
|
|
29
11
|
* @param {ExpressDataContext} context The underlying data context
|
|
30
12
|
* @param {string} document The file path
|
|
31
13
|
* @param {{filename: string, contentType: string}=} options Extra attributes (optional)
|
|
32
|
-
* @returns
|
|
14
|
+
* @returns Promise<VerifySignatureResult[]>
|
|
33
15
|
*/
|
|
34
16
|
verify(context: ExpressDataContext, document: string, options?: {
|
|
35
17
|
filename: string;
|
|
36
18
|
contentType: string;
|
|
37
19
|
}): Promise<VerifySignatureResult[]>;
|
|
38
20
|
}
|
|
39
|
-
export {};
|
package/dist/index.d.ts
CHANGED
package/dist/index.esm.js
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import { HttpMethodNotAllowedError, ApplicationService, HttpError } from '@themost/common';
|
|
1
|
+
import { HttpMethodNotAllowedError, ApplicationService, HttpError, TraceUtils } from '@themost/common';
|
|
2
2
|
import express from 'express';
|
|
3
3
|
import { createProxyMiddleware } from 'http-proxy-middleware';
|
|
4
4
|
import { serviceRouter } from '@themost/express';
|
|
5
5
|
import request from 'superagent';
|
|
6
|
+
import { URL } from 'url';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { writeFile as writeFile$1, unlink as unlink$1 } from 'fs';
|
|
9
|
+
import { promisify } from 'util';
|
|
10
|
+
import crypto from 'crypto';
|
|
11
|
+
import os from 'os';
|
|
12
|
+
import { DataCacheStrategy } from '@themost/data';
|
|
13
|
+
import { Secret, TOTP } from 'xotp';
|
|
6
14
|
|
|
7
15
|
/*! *****************************************************************************
|
|
8
16
|
Copyright (c) Microsoft Corporation.
|
|
@@ -29,16 +37,17 @@ function __awaiter(thisArg, _arguments, P, generator) {
|
|
|
29
37
|
});
|
|
30
38
|
}
|
|
31
39
|
|
|
32
|
-
function signerWorkerRouter() {
|
|
40
|
+
function signerWorkerRouter(target = 'http://localhost:2465/') {
|
|
33
41
|
const router = express.Router();
|
|
34
42
|
router.use('/sign', (req, res, next) => {
|
|
35
43
|
return next(new HttpMethodNotAllowedError('This method is not supported when using remote services.'));
|
|
36
44
|
});
|
|
37
45
|
router.use(createProxyMiddleware({
|
|
38
|
-
target
|
|
46
|
+
target,
|
|
39
47
|
pathRewrite: {
|
|
40
48
|
'^/api/signer': ''
|
|
41
49
|
},
|
|
50
|
+
logLevel: 'warn',
|
|
42
51
|
changeOrigin: true
|
|
43
52
|
}));
|
|
44
53
|
return router;
|
|
@@ -75,6 +84,10 @@ class ScopeAccessConfiguration {
|
|
|
75
84
|
class SignerWorker extends ApplicationService {
|
|
76
85
|
constructor(app) {
|
|
77
86
|
super(app);
|
|
87
|
+
// get configuration
|
|
88
|
+
this.options = Object.assign({
|
|
89
|
+
host: 'http://localhost:2465/'
|
|
90
|
+
}, app.getConfiguration().getSourceAt('settings/universis/signerWorker'));
|
|
78
91
|
const thisApp = app;
|
|
79
92
|
if (thisApp && thisApp.container) {
|
|
80
93
|
thisApp.container.subscribe((container) => {
|
|
@@ -85,7 +98,7 @@ class SignerWorker extends ApplicationService {
|
|
|
85
98
|
scopeAccess.elements.push.apply(scopeAccess.elements, SignerScopeAccess);
|
|
86
99
|
}
|
|
87
100
|
// use signer worker router (for authenticated users)
|
|
88
|
-
serviceRouter.use('/signer/', signerWorkerRouter());
|
|
101
|
+
serviceRouter.use('/signer/', signerWorkerRouter(this.options.host));
|
|
89
102
|
}
|
|
90
103
|
});
|
|
91
104
|
}
|
|
@@ -95,19 +108,336 @@ class SignerWorker extends ApplicationService {
|
|
|
95
108
|
* @param {ExpressDataContext} context The underlying data context
|
|
96
109
|
* @param {string} document The file path
|
|
97
110
|
* @param {{filename: string, contentType: string}=} options Extra attributes (optional)
|
|
98
|
-
* @returns
|
|
111
|
+
* @returns Promise<VerifySignatureResult[]>
|
|
99
112
|
*/
|
|
100
113
|
verify(context, document, options) {
|
|
101
114
|
return __awaiter(this, void 0, void 0, function* () {
|
|
102
|
-
|
|
103
|
-
.
|
|
104
|
-
|
|
105
|
-
|
|
115
|
+
try {
|
|
116
|
+
const response = yield request.post(new URL('/verify', this.options.host).toString())
|
|
117
|
+
.attach('file', document, options);
|
|
118
|
+
if (response.ok) {
|
|
119
|
+
return response.body;
|
|
120
|
+
}
|
|
121
|
+
// noinspection ExceptionCaughtLocallyJS
|
|
122
|
+
throw new HttpError(response.statusCode);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
if (err.response) {
|
|
126
|
+
TraceUtils.error(err);
|
|
127
|
+
throw new HttpError(err.response.status);
|
|
128
|
+
}
|
|
129
|
+
throw err;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const writeFile = promisify(writeFile$1);
|
|
136
|
+
const unlink = promisify(unlink$1);
|
|
137
|
+
function tmpFile(extension = '.tmp') {
|
|
138
|
+
const tmpDir = os.tmpdir();
|
|
139
|
+
const filename = `file-${crypto.randomBytes(16).toString('hex')}${extension}`;
|
|
140
|
+
return path.join(tmpDir, filename);
|
|
141
|
+
}
|
|
142
|
+
class SignerService extends ApplicationService {
|
|
143
|
+
constructor(app) {
|
|
144
|
+
super(app);
|
|
145
|
+
// get signer worker configuration
|
|
146
|
+
const options = app.getConfiguration().getSourceAt('settings/universis/signerService') || {};
|
|
147
|
+
this.options = Object.assign({}, {
|
|
148
|
+
'host': 'http://localhost:2465/'
|
|
149
|
+
}, options);
|
|
150
|
+
const thisApp = app;
|
|
151
|
+
if (thisApp && thisApp.container) {
|
|
152
|
+
thisApp.container.subscribe((container) => {
|
|
153
|
+
if (container) {
|
|
154
|
+
container.get('/api/signer/.well-known/jwks', (_req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
155
|
+
try {
|
|
156
|
+
const results = yield this.getJwks();
|
|
157
|
+
return res.json(results);
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
return next(err);
|
|
161
|
+
}
|
|
162
|
+
}));
|
|
163
|
+
container.get('/api/signer/.well-known/certs', (_req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
164
|
+
try {
|
|
165
|
+
const results = yield this.getCerts();
|
|
166
|
+
return res.json(results);
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
return next(err);
|
|
170
|
+
}
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* @param {ExpressDataContext} context The underlying data context
|
|
178
|
+
* @param {*} data The data to be verified
|
|
179
|
+
* @param {{filename: string, contentType: string}=} options Extra attributes (optional)
|
|
180
|
+
* @returns {Promise<VerifySignatureResult[]>}
|
|
181
|
+
*/
|
|
182
|
+
verify(context, data, options) {
|
|
183
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
184
|
+
if (data == null) {
|
|
185
|
+
throw new Error('The given data cannot be empty during sign.');
|
|
186
|
+
}
|
|
187
|
+
const json = (typeof data === 'object') ? JSON.stringify(data) : JSON.stringify({
|
|
188
|
+
value: data,
|
|
189
|
+
});
|
|
190
|
+
const file = tmpFile('.json');
|
|
191
|
+
// write data to temporary file
|
|
192
|
+
yield writeFile(file, json, { encoding: 'utf8' });
|
|
193
|
+
try {
|
|
194
|
+
return yield this.verifyFile(context, file, options);
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
throw err;
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
try {
|
|
201
|
+
yield unlink(file); // remove temporary file
|
|
202
|
+
}
|
|
203
|
+
catch (e) {
|
|
204
|
+
// ignore error
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
getJwks() {
|
|
210
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
211
|
+
try {
|
|
212
|
+
const cache = this.getApplication().getConfiguration().getStrategy(DataCacheStrategy);
|
|
213
|
+
// check if cache is available
|
|
214
|
+
return cache.getOrDefault('/api/signer/keystore/jwks', () => __awaiter(this, void 0, void 0, function* () {
|
|
215
|
+
try {
|
|
216
|
+
const response = yield request
|
|
217
|
+
.get(new URL('/keystore/jwks', this.options.host).toString())
|
|
218
|
+
.auth(this.options.user, this.options.password);
|
|
219
|
+
if (response.ok) {
|
|
220
|
+
return response.body;
|
|
221
|
+
}
|
|
222
|
+
// noinspection ExceptionCaughtLocallyJS
|
|
223
|
+
throw new HttpError(response.status, 'Something went wrong while getting json web key set');
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
TraceUtils.error(err);
|
|
227
|
+
throw new HttpError(err.status, 'Failed to get json web key set', err.message);
|
|
228
|
+
}
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
throw new HttpError(err.status, 'Failed to get json web key set', err.message);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
getCerts() {
|
|
237
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
238
|
+
const cache = this.getApplication().getConfiguration().getStrategy(DataCacheStrategy);
|
|
239
|
+
return cache.getOrDefault('/api/signer/keystore/certs', () => __awaiter(this, void 0, void 0, function* () {
|
|
240
|
+
try {
|
|
241
|
+
const response = yield request.get(new URL('/keystore/certs', this.options.host).toString())
|
|
242
|
+
.auth(this.options.user, this.options.password);
|
|
243
|
+
if (response.ok) {
|
|
244
|
+
return response.body.filter((item) => {
|
|
245
|
+
return item.notAfter && new Date(item.notAfter) > new Date();
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
TraceUtils.error(err);
|
|
252
|
+
throw new HttpError(err.status, 'Failed to get certificates', err.message);
|
|
253
|
+
}
|
|
254
|
+
}));
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Generates a one-time password using the configured secret.
|
|
259
|
+
*/
|
|
260
|
+
generateVerificationCode(options) {
|
|
261
|
+
if (this.options.secret == null) {
|
|
262
|
+
throw new Error('One-time password is not supported');
|
|
263
|
+
}
|
|
264
|
+
const secret = Secret.from(this.options.secret, 'base32');
|
|
265
|
+
const opts = Object.assign({
|
|
266
|
+
algorithm: 'sha1',
|
|
267
|
+
digits: 6,
|
|
268
|
+
period: 30,
|
|
269
|
+
}, options);
|
|
270
|
+
const totp = new TOTP(opts);
|
|
271
|
+
return totp.generate({ secret });
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* @param {ExpressDataContext} context The underlying data context
|
|
275
|
+
* @param {*} data Any JSON object to be signed
|
|
276
|
+
* @param {{timestampServer?:string}} extraOptions
|
|
277
|
+
* @returns {Promise<SignJsonSignatureResult>} An array of signatures
|
|
278
|
+
*/
|
|
279
|
+
sign(context, data, extraOptions) {
|
|
280
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
281
|
+
if (data == null) {
|
|
282
|
+
throw new Error('The given data cannot be empty during sign.');
|
|
283
|
+
}
|
|
284
|
+
const json = (typeof data === 'object') ? JSON.stringify(data) : JSON.stringify({
|
|
285
|
+
value: data,
|
|
286
|
+
});
|
|
287
|
+
const file = tmpFile('.json');
|
|
288
|
+
// write data to temporary file
|
|
289
|
+
yield writeFile(file, json, { encoding: 'utf8' });
|
|
290
|
+
try {
|
|
291
|
+
return yield this.signFinal(context, file, 'application/json', extraOptions);
|
|
292
|
+
}
|
|
293
|
+
catch (err) {
|
|
294
|
+
if (err.response) {
|
|
295
|
+
throw new HttpError(err.status, 'Failed to sign data', err.message);
|
|
296
|
+
}
|
|
297
|
+
throw err;
|
|
298
|
+
}
|
|
299
|
+
finally {
|
|
300
|
+
try {
|
|
301
|
+
yield unlink(file); // remove temporary file
|
|
302
|
+
}
|
|
303
|
+
catch (e) {
|
|
304
|
+
// ignore error
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
signFile(context, file, contentType, extraOptions) {
|
|
310
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
311
|
+
return this.signFinal(context, file, contentType, extraOptions);
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
verifyFile(context, file, options) {
|
|
315
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
316
|
+
try {
|
|
317
|
+
const response = yield request.post(new URL('/verify', this.options.host).toString())
|
|
318
|
+
.attach('file', file, options);
|
|
319
|
+
if (response.ok) {
|
|
320
|
+
return response.body;
|
|
321
|
+
}
|
|
322
|
+
// noinspection ExceptionCaughtLocallyJS
|
|
323
|
+
throw new HttpError(response.statusCode);
|
|
324
|
+
}
|
|
325
|
+
catch (err) {
|
|
326
|
+
if (err.response) {
|
|
327
|
+
TraceUtils.error(err);
|
|
328
|
+
throw new HttpError(err.response.status, 'Failed to verify data', err.message);
|
|
329
|
+
}
|
|
330
|
+
throw err;
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
inspect(context, file, options) {
|
|
335
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
336
|
+
try {
|
|
337
|
+
const fields = {};
|
|
338
|
+
if (this.options.inspector) {
|
|
339
|
+
Object.assign(fields, this.options.inspector);
|
|
340
|
+
}
|
|
341
|
+
const response = yield request.post(new URL('/signature/inspect', this.options.host).toString())
|
|
342
|
+
.field(fields)
|
|
343
|
+
.attach('file', file, options);
|
|
344
|
+
if (response.ok) {
|
|
345
|
+
return response.body;
|
|
346
|
+
}
|
|
347
|
+
// noinspection ExceptionCaughtLocallyJS
|
|
348
|
+
throw new HttpError(response.statusCode);
|
|
349
|
+
}
|
|
350
|
+
catch (err) {
|
|
351
|
+
if (err.response) {
|
|
352
|
+
TraceUtils.error(err);
|
|
353
|
+
throw new HttpError(err.response.status, 'Failed to verify data', err.message);
|
|
354
|
+
}
|
|
355
|
+
throw err;
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
requiresVerificationCode() {
|
|
360
|
+
return typeof this.options.secret === 'string' && this.options.secret.length > 0;
|
|
361
|
+
}
|
|
362
|
+
signFinal(context, file, contentType, extraOptions) {
|
|
363
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
364
|
+
// try to find thumbprint if empty
|
|
365
|
+
if (typeof this.options.thumbprint === 'undefined') {
|
|
366
|
+
const [cert] = yield this.getCerts();
|
|
367
|
+
if (cert) {
|
|
368
|
+
this.options.thumbprint = cert.thumbprint;
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
this.options.thumbprint = '';
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
const fields = Object.assign({}, extraOptions);
|
|
375
|
+
// if the file is a PDF or the content type is application/pdf, try to inspect signature position
|
|
376
|
+
if (path.extname(file) === '.pdf' || contentType === 'application/pdf') {
|
|
377
|
+
if ((extraOptions === null || extraOptions === void 0 ? void 0 : extraOptions.page) && (extraOptions === null || extraOptions === void 0 ? void 0 : extraOptions.position)) {
|
|
378
|
+
// if page and position are defined, use them
|
|
379
|
+
Object.assign(fields, {
|
|
380
|
+
page: extraOptions.page,
|
|
381
|
+
position: extraOptions.position.map((element) => {
|
|
382
|
+
return Math.round(element);
|
|
383
|
+
}).join(',')
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
else if (this.options.inspector) {
|
|
387
|
+
// if inspector is defined, use it
|
|
388
|
+
const inspectorResult = yield this.inspect(context, file);
|
|
389
|
+
if ((inspectorResult === null || inspectorResult === void 0 ? void 0 : inspectorResult.page) && (inspectorResult === null || inspectorResult === void 0 ? void 0 : inspectorResult.position)) {
|
|
390
|
+
Object.assign(fields, {
|
|
391
|
+
page: inspectorResult.page,
|
|
392
|
+
position: inspectorResult.position.map((element) => {
|
|
393
|
+
return Math.round(element);
|
|
394
|
+
}).join(',')
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (this.options.thumbprint.length) {
|
|
400
|
+
Object.assign(fields, {
|
|
401
|
+
thumbprint: this.options.thumbprint,
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
const fileOptions = {
|
|
405
|
+
filename: path.basename(file)
|
|
406
|
+
};
|
|
407
|
+
if (contentType) {
|
|
408
|
+
Object.assign(fileOptions, {
|
|
409
|
+
contentType
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
try {
|
|
413
|
+
// if secret is defined, generate one-time password
|
|
414
|
+
if (this.requiresVerificationCode()) {
|
|
415
|
+
Object.assign(fields, {
|
|
416
|
+
otp: this.generateVerificationCode()
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
const response = yield request.post(new URL('/sign', this.options.host).toString())
|
|
420
|
+
.type('form')
|
|
421
|
+
.auth(this.options.user, this.options.password)
|
|
422
|
+
.field(fields)
|
|
423
|
+
.buffer(contentType !== 'application/json')
|
|
424
|
+
.attach('file', file, fileOptions);
|
|
425
|
+
if (response.ok) {
|
|
426
|
+
return response.body;
|
|
427
|
+
}
|
|
428
|
+
// noinspection ExceptionCaughtLocallyJS
|
|
429
|
+
throw new HttpError(response.status);
|
|
430
|
+
}
|
|
431
|
+
catch (err) {
|
|
432
|
+
if (err.response) {
|
|
433
|
+
TraceUtils.error(err);
|
|
434
|
+
throw new HttpError(err.response.status, 'Failed to sign data', err.message);
|
|
435
|
+
}
|
|
436
|
+
throw err;
|
|
106
437
|
}
|
|
107
|
-
throw new HttpError(response.statusCode);
|
|
108
438
|
});
|
|
109
439
|
}
|
|
110
440
|
}
|
|
111
441
|
|
|
112
|
-
export { SignerWorker, signerWorkerRouter };
|
|
442
|
+
export { SignerService, SignerWorker, signerWorkerRouter };
|
|
113
443
|
//# sourceMappingURL=index.esm.js.map
|