node-opcua-crypto 2.1.2 → 3.0.0-beta.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.
Files changed (117) hide show
  1. package/.fossa.yml +18 -18
  2. package/.github/FUNDING.yml +12 -12
  3. package/.github/workflows/main.yml +109 -106
  4. package/.prettierrc.js +6 -6
  5. package/LICENSE +23 -23
  6. package/README.md +14 -14
  7. package/_tmp_certificate.pem +20 -0
  8. package/_tmp_csr.pem +17 -0
  9. package/_tmp_privatekey.der +0 -0
  10. package/_tmp_privatekey.pem +28 -0
  11. package/dist/source/asn1.d.ts +73 -73
  12. package/dist/source/asn1.js +359 -359
  13. package/dist/source/asn1.js.map +1 -1
  14. package/dist/source/buffer_utils.d.ts +5 -6
  15. package/dist/source/buffer_utils.js +21 -21
  16. package/dist/source/common.d.ts +20 -14
  17. package/dist/source/common.js +10 -2
  18. package/dist/source/common.js.map +1 -1
  19. package/dist/source/create_key_pair.d.ts +42 -0
  20. package/dist/source/create_key_pair.js +136 -0
  21. package/dist/source/create_key_pair.js.map +1 -0
  22. package/dist/source/crypto_explore_certificate.d.ts +107 -107
  23. package/dist/source/crypto_explore_certificate.js +601 -601
  24. package/dist/source/crypto_utils.d.ts +76 -76
  25. package/dist/source/crypto_utils.js +329 -329
  26. package/dist/source/derived_keys.d.ts +72 -72
  27. package/dist/source/derived_keys.js +248 -248
  28. package/dist/source/explore_certificate.d.ts +30 -30
  29. package/dist/source/explore_certificate.js +43 -43
  30. package/dist/source/explore_certificate_revocation_list.d.ts +28 -28
  31. package/dist/source/explore_certificate_revocation_list.js +69 -69
  32. package/dist/source/explore_certificate_signing_request.d.ts +13 -13
  33. package/dist/source/explore_certificate_signing_request.js +44 -44
  34. package/dist/source/explore_private_key.d.ts +29 -29
  35. package/dist/source/explore_private_key.js +95 -97
  36. package/dist/source/explore_private_key.js.map +1 -1
  37. package/dist/source/index.d.ts +17 -13
  38. package/dist/source/index.js +33 -29
  39. package/dist/source/index.js.map +1 -1
  40. package/dist/source/oid_map.d.ts +7 -7
  41. package/dist/source/oid_map.js +303 -303
  42. package/dist/source/public_private_match.d.ts +3 -3
  43. package/dist/source/public_private_match.js +36 -36
  44. package/dist/source/subject.d.ts +27 -0
  45. package/dist/source/subject.js +125 -0
  46. package/dist/source/subject.js.map +1 -0
  47. package/dist/source/verify_certificate_signature.d.ts +10 -10
  48. package/dist/source/verify_certificate_signature.js +101 -101
  49. package/dist/source/x509/_build_public_key.d.ts +1 -0
  50. package/dist/source/x509/_build_public_key.js +36 -0
  51. package/dist/source/x509/_build_public_key.js.map +1 -0
  52. package/dist/source/x509/_crypto.d.ts +3 -0
  53. package/dist/source/x509/_crypto.js +9 -0
  54. package/dist/source/x509/_crypto.js.map +1 -0
  55. package/dist/source/x509/_fix.d.ts +2 -0
  56. package/dist/source/x509/_fix.js +74 -0
  57. package/dist/source/x509/_fix.js.map +1 -0
  58. package/dist/source/x509/_get_attributes.d.ts +8 -0
  59. package/dist/source/x509/_get_attributes.js +56 -0
  60. package/dist/source/x509/_get_attributes.js.map +1 -0
  61. package/dist/source/x509/_internals.d.ts +0 -0
  62. package/dist/source/x509/_internals.js +2 -0
  63. package/dist/source/x509/_internals.js.map +1 -0
  64. package/dist/source/x509/create_certificate_signing_request.d.ts +18 -0
  65. package/dist/source/x509/create_certificate_signing_request.js +53 -0
  66. package/dist/source/x509/create_certificate_signing_request.js.map +1 -0
  67. package/dist/source/x509/create_key_pair.d.ts +28 -0
  68. package/dist/source/x509/create_key_pair.js +62 -0
  69. package/dist/source/x509/create_key_pair.js.map +1 -0
  70. package/dist/source/x509/create_self_signed_certificate.d.ts +17 -0
  71. package/dist/source/x509/create_self_signed_certificate.js +71 -0
  72. package/dist/source/x509/create_self_signed_certificate.js.map +1 -0
  73. package/dist/source_nodejs/generate_private_key_filename.d.ts +1 -0
  74. package/dist/source_nodejs/generate_private_key_filename.js +25 -0
  75. package/dist/source_nodejs/generate_private_key_filename.js.map +1 -0
  76. package/dist/source_nodejs/index.d.ts +4 -3
  77. package/dist/source_nodejs/index.js +20 -19
  78. package/dist/source_nodejs/index.js.map +1 -1
  79. package/dist/source_nodejs/read.d.ts +23 -23
  80. package/dist/source_nodejs/read.js +106 -106
  81. package/dist/source_nodejs/read_certificate_revocation_list.d.ts +2 -2
  82. package/dist/source_nodejs/read_certificate_revocation_list.js +27 -27
  83. package/dist/source_nodejs/read_certificate_signing_request.d.ts +3 -3
  84. package/dist/source_nodejs/read_certificate_signing_request.js +27 -27
  85. package/index.d.ts +2 -2
  86. package/index.js +4 -4
  87. package/index_web.js +3 -3
  88. package/package.json +15 -9
  89. package/source/asn1.ts +404 -404
  90. package/source/buffer_utils.ts +18 -18
  91. package/source/common.ts +7 -0
  92. package/source/crypto_explore_certificate.ts +764 -764
  93. package/source/derived_keys.ts +287 -287
  94. package/source/explore_certificate.ts +66 -66
  95. package/source/explore_certificate_revocation_list.ts +122 -122
  96. package/source/explore_certificate_signing_request.ts +58 -58
  97. package/source/explore_private_key.ts +1 -2
  98. package/source/index.ts +17 -13
  99. package/source/oid_map.ts +310 -310
  100. package/source/subject.ts +144 -0
  101. package/source/verify_certificate_signature.ts +105 -105
  102. package/source/x509/_build_public_key.ts +25 -0
  103. package/source/x509/_crypto.ts +5 -0
  104. package/source/x509/_get_attributes.ts +60 -0
  105. package/source/x509/create_certificate_signing_request.ts +64 -0
  106. package/source/x509/create_key_pair.ts +70 -0
  107. package/source/x509/create_self_signed_certificate.ts +91 -0
  108. package/source_nodejs/generate_private_key_filename.ts +10 -0
  109. package/source_nodejs/index.ts +4 -3
  110. package/source_nodejs/read_certificate_revocation_list.ts +14 -14
  111. package/source_nodejs/read_certificate_signing_request.ts +17 -17
  112. package/test_certificate.ts +34 -34
  113. package/tsconfig.json +19 -18
  114. package/tslint.json +34 -34
  115. package/dist/source/certificate_matches_private_key.d.ts +0 -2
  116. package/dist/source/certificate_matches_private_key.js +0 -22
  117. package/dist/source/certificate_matches_private_key.js.map +0 -1
@@ -0,0 +1,144 @@
1
+ // ---------------------------------------------------------------------------------------------------------------------
2
+ // node-opcua-pki
3
+ // ---------------------------------------------------------------------------------------------------------------------
4
+ // Copyright (c) 2014-2022 - Etienne Rossignon - etienne.rossignon (at) gadz.org
5
+ // Copyright (c) 2022 - Sterfive.com
6
+ // ---------------------------------------------------------------------------------------------------------------------
7
+ //
8
+ // This project is licensed under the terms of the MIT license.
9
+ //
10
+ // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
11
+ // documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
12
+ // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
13
+ // permit persons to whom the Software is furnished to do so, subject to the following conditions:
14
+ //
15
+ // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
16
+ // Software.
17
+ //
18
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
19
+ // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
20
+ // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
21
+ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ // ---------------------------------------------------------------------------------------------------------------------
23
+
24
+ export interface SubjectOptions {
25
+ commonName?: string;
26
+ organization?: string;
27
+ organizationalUnit?: string;
28
+ locality?: string;
29
+ state?: string;
30
+ country?: string;
31
+ domainComponent?: string;
32
+ }
33
+
34
+ const _keys = {
35
+ C: "country",
36
+ CN: "commonName",
37
+ DC: "domainComponent",
38
+ L: "locality",
39
+ O: "organization",
40
+ OU: "organizationalUnit",
41
+ ST: "state",
42
+ };
43
+
44
+ const enquoteIfNecessary = (str: string) => {
45
+ str = str.replace(/"/g, "”");
46
+ return str.match(/\/|=/) ? `"${str}"` : str;
47
+ };
48
+ const unquote = (str: string) => str.replace(/"/gm, "");
49
+ const unquote2 = (str?: string | undefined) => {
50
+ if (!str) return str;
51
+ const m = str.match(/^"(.*)"$/);
52
+ return m ? m[1] : str;
53
+ };
54
+ /**
55
+ * subjectName The subject name to use for the Certificate.
56
+ * If not specified the ApplicationName and/or domainNames are used to create a suitable default value.
57
+ */
58
+ export class Subject implements SubjectOptions {
59
+ public readonly commonName?: string;
60
+ public readonly organization?: string;
61
+ public readonly organizationalUnit?: string;
62
+ public readonly locality?: string;
63
+ public readonly state?: string;
64
+ public readonly country?: string;
65
+ public readonly domainComponent?: string;
66
+
67
+ constructor(options: SubjectOptions | string) {
68
+ if (typeof options === "string") {
69
+ options = Subject.parse(options);
70
+ }
71
+ this.commonName = unquote2(options.commonName);
72
+ this.organization = unquote2(options.organization);
73
+ this.organizationalUnit = unquote2(options.organizationalUnit);
74
+ this.locality = unquote2(options.locality);
75
+ this.state = unquote2(options.state);
76
+ this.country = unquote2(options.country);
77
+ this.domainComponent = unquote2(options.domainComponent);
78
+ }
79
+
80
+ public static parse(str: string): SubjectOptions {
81
+ const elements = str.split(/\/(?=[^/]*?=)/);
82
+ const options: Record<string, unknown> = {};
83
+
84
+ elements.forEach((element: string) => {
85
+ if (element.length === 0) {
86
+ return;
87
+ }
88
+ const s: string[] = element.split("=");
89
+
90
+ if (s.length !== 2) {
91
+ throw new Error("invalid format for " + element);
92
+ }
93
+ const longName = (_keys as Record<string, string>)[s[0]];
94
+ if (!longName) {
95
+ throw new Error("Invalid field found in subject name " + s[0]);
96
+ }
97
+ const value = s[1];
98
+ options[longName] = unquote(Buffer.from(value, "ascii").toString("utf8"));
99
+ });
100
+ return options as SubjectOptions;
101
+ }
102
+
103
+ public toStringInternal(sep: string): string {
104
+ // https://reference.opcfoundation.org/v104/GDS/docs/7.6.4/
105
+ // The format of the subject name is a sequence of name value pairs separated by a ‘/’.
106
+ // The name shall be one of ‘CN’, ‘O’, ‘OU’, ‘DC’, ‘L’, ‘S’ or ‘C’ and
107
+ // shall be followed by a ‘=’ and then followed by the value.
108
+ // The value may be any printable character except for ‘”’.
109
+ // If the value contains a ‘/’ or a ‘=’ then it shall be enclosed in double quotes (‘”’).
110
+
111
+ const tmp: string[] = [];
112
+ if (this.country) {
113
+ tmp.push("C=" + enquoteIfNecessary(this.country));
114
+ }
115
+ if (this.state) {
116
+ tmp.push("ST=" + enquoteIfNecessary(this.state));
117
+ }
118
+ if (this.locality) {
119
+ tmp.push("L=" + enquoteIfNecessary(this.locality));
120
+ }
121
+ if (this.organization) {
122
+ tmp.push("O=" + enquoteIfNecessary(this.organization));
123
+ }
124
+ if (this.organizationalUnit) {
125
+ tmp.push("OU=" + enquoteIfNecessary(this.organizationalUnit));
126
+ }
127
+ if (this.commonName) {
128
+ tmp.push("CN=" + enquoteIfNecessary(this.commonName));
129
+ }
130
+ if (this.domainComponent) {
131
+ tmp.push("DC=" + enquoteIfNecessary(this.domainComponent));
132
+ }
133
+ return tmp.join(sep);
134
+ }
135
+ public toStringForOPCUA(): string {
136
+ return this.toStringInternal("/");
137
+ }
138
+ public toString(): string {
139
+ // standard for SSL is to have a / in front of each Field
140
+ // see https://www.digicert.com/kb/ssl-support/openssl-quick-reference-guide.htm
141
+ const t = this.toStringForOPCUA();
142
+ return t ? "/" + t : t;
143
+ }
144
+ }
@@ -1,105 +1,105 @@
1
- // tslint:disable: no-console
2
-
3
- // Now that we got a hash of the original certificate,
4
- // we need to verify if we can obtain the same hash by using the same hashing function
5
- // (in this case SHA-384). In order to do that, we need to extract just the body of
6
- // the signed certificate. Which, in our case, is everything but the signature.
7
- // The start of the body is always the first digit of the second line of the following command:
8
- import * as crypto from "crypto";
9
-
10
- import { Certificate, PrivateKey } from "./common";
11
- import { split_der, exploreCertificate } from "./crypto_explore_certificate";
12
- import { toPem } from "./crypto_utils";
13
- import { _readAlgorithmIdentifier, _readSignatureValueBin, TagType, readTag, _readStruct, _getBlock } from "./asn1";
14
-
15
- export function verifyCertificateOrClrSignature(certificateOrCrl: Buffer, parentCertificate: Certificate): boolean {
16
- const block_info = readTag(certificateOrCrl, 0);
17
- const blocks = _readStruct(certificateOrCrl, block_info);
18
- const bufferToBeSigned = certificateOrCrl.slice(block_info.position, blocks[1].position - 2);
19
-
20
- //xx console.log("bufferToBeSigned = ", bufferToBeSigned.length, bufferToBeSigned.toString("hex").substr(0, 50), bufferToBeSigned.toString("hex").substr(-10));
21
- const signatureAlgorithm = _readAlgorithmIdentifier(certificateOrCrl, blocks[1]);
22
- const signatureValue = _readSignatureValueBin(certificateOrCrl, blocks[2]);
23
-
24
- const p = split_der(parentCertificate)[0];
25
- //xx const publicKey = extractPublicKeyFromCertificateSync(p);
26
- const certPem = toPem(p, "CERTIFICATE");
27
- const verify = crypto.createVerify(signatureAlgorithm.identifier);
28
- verify.update(bufferToBeSigned);
29
- verify.end();
30
- return verify.verify(certPem, signatureValue);
31
- }
32
-
33
- export function verifyCertificateSignature(certificate: Certificate, parentCertificate: Certificate): boolean {
34
- return verifyCertificateOrClrSignature(certificate, parentCertificate);
35
- }
36
- export function verifyCertificateRevocationListSignature(
37
- certificateRevocationList: Certificate,
38
- parentCertificate: Certificate
39
- ): boolean {
40
- return verifyCertificateOrClrSignature(certificateRevocationList, parentCertificate);
41
- }
42
-
43
- export type _VerifyStatus = "BadCertificateIssuerUseNotAllowed" | "BadCertificateInvalid" | "Good";
44
- export async function verifyCertificateChain(certificateChain: Certificate[]): Promise<{ status: _VerifyStatus; reason: string }> {
45
- // verify that all the certificate
46
- // second certificate must be used for CertificateSign
47
-
48
- for (let index = 1; index < certificateChain.length; index++) {
49
- const cert = certificateChain[index - 1];
50
- const certParent = certificateChain[index];
51
-
52
- // parent child must have keyCertSign
53
- const certParentInfo = exploreCertificate(certParent);
54
- const keyUsage = certParentInfo.tbsCertificate.extensions!.keyUsage!;
55
-
56
- // istanbul ignore next
57
- if (!keyUsage.keyCertSign) {
58
- return {
59
- status: "BadCertificateIssuerUseNotAllowed",
60
- reason: "One of the certificate in the chain has not keyUsage set for Certificate Signing",
61
- };
62
- }
63
-
64
- const parentSignChild = verifyCertificateSignature(cert, certParent);
65
- if (!parentSignChild) {
66
- return {
67
- status: "BadCertificateInvalid",
68
- reason: "One of the certificate in the chain is not signing the previous certificate",
69
- };
70
- }
71
- const certInfo = exploreCertificate(cert);
72
-
73
- // istanbul ignore next
74
- if (!certInfo.tbsCertificate.extensions) {
75
- return {
76
- status: "BadCertificateInvalid",
77
- reason: "Cannot find X409 Extension 3 in certificate",
78
- };
79
- }
80
-
81
- // istanbul ignore next
82
- if (!certParentInfo.tbsCertificate.extensions || !certInfo.tbsCertificate.extensions.authorityKeyIdentifier) {
83
- return {
84
- status: "BadCertificateInvalid",
85
- reason: "Cannot find X409 Extension 3 in certificate (parent)",
86
- };
87
- }
88
-
89
- // istanbul ignore next
90
- if (
91
- certParentInfo.tbsCertificate.extensions.subjectKeyIdentifier !==
92
- certInfo.tbsCertificate.extensions.authorityKeyIdentifier.keyIdentifier
93
- ) {
94
- return {
95
- status: "BadCertificateInvalid",
96
- reason:
97
- "subjectKeyIdentifier authorityKeyIdentifier in child certificate do not match subjectKeyIdentifier of parent certificate",
98
- };
99
- }
100
- }
101
- return {
102
- status: "Good",
103
- reason: `certificate chain is valid(length = ${certificateChain.length})`,
104
- };
105
- }
1
+ // tslint:disable: no-console
2
+
3
+ // Now that we got a hash of the original certificate,
4
+ // we need to verify if we can obtain the same hash by using the same hashing function
5
+ // (in this case SHA-384). In order to do that, we need to extract just the body of
6
+ // the signed certificate. Which, in our case, is everything but the signature.
7
+ // The start of the body is always the first digit of the second line of the following command:
8
+ import * as crypto from "crypto";
9
+
10
+ import { Certificate, PrivateKey } from "./common";
11
+ import { split_der, exploreCertificate } from "./crypto_explore_certificate";
12
+ import { toPem } from "./crypto_utils";
13
+ import { _readAlgorithmIdentifier, _readSignatureValueBin, TagType, readTag, _readStruct, _getBlock } from "./asn1";
14
+
15
+ export function verifyCertificateOrClrSignature(certificateOrCrl: Buffer, parentCertificate: Certificate): boolean {
16
+ const block_info = readTag(certificateOrCrl, 0);
17
+ const blocks = _readStruct(certificateOrCrl, block_info);
18
+ const bufferToBeSigned = certificateOrCrl.slice(block_info.position, blocks[1].position - 2);
19
+
20
+ //xx console.log("bufferToBeSigned = ", bufferToBeSigned.length, bufferToBeSigned.toString("hex").substr(0, 50), bufferToBeSigned.toString("hex").substr(-10));
21
+ const signatureAlgorithm = _readAlgorithmIdentifier(certificateOrCrl, blocks[1]);
22
+ const signatureValue = _readSignatureValueBin(certificateOrCrl, blocks[2]);
23
+
24
+ const p = split_der(parentCertificate)[0];
25
+ //xx const publicKey = extractPublicKeyFromCertificateSync(p);
26
+ const certPem = toPem(p, "CERTIFICATE");
27
+ const verify = crypto.createVerify(signatureAlgorithm.identifier);
28
+ verify.update(bufferToBeSigned);
29
+ verify.end();
30
+ return verify.verify(certPem, signatureValue);
31
+ }
32
+
33
+ export function verifyCertificateSignature(certificate: Certificate, parentCertificate: Certificate): boolean {
34
+ return verifyCertificateOrClrSignature(certificate, parentCertificate);
35
+ }
36
+ export function verifyCertificateRevocationListSignature(
37
+ certificateRevocationList: Certificate,
38
+ parentCertificate: Certificate
39
+ ): boolean {
40
+ return verifyCertificateOrClrSignature(certificateRevocationList, parentCertificate);
41
+ }
42
+
43
+ export type _VerifyStatus = "BadCertificateIssuerUseNotAllowed" | "BadCertificateInvalid" | "Good";
44
+ export async function verifyCertificateChain(certificateChain: Certificate[]): Promise<{ status: _VerifyStatus; reason: string }> {
45
+ // verify that all the certificate
46
+ // second certificate must be used for CertificateSign
47
+
48
+ for (let index = 1; index < certificateChain.length; index++) {
49
+ const cert = certificateChain[index - 1];
50
+ const certParent = certificateChain[index];
51
+
52
+ // parent child must have keyCertSign
53
+ const certParentInfo = exploreCertificate(certParent);
54
+ const keyUsage = certParentInfo.tbsCertificate.extensions!.keyUsage!;
55
+
56
+ // istanbul ignore next
57
+ if (!keyUsage.keyCertSign) {
58
+ return {
59
+ status: "BadCertificateIssuerUseNotAllowed",
60
+ reason: "One of the certificate in the chain has not keyUsage set for Certificate Signing",
61
+ };
62
+ }
63
+
64
+ const parentSignChild = verifyCertificateSignature(cert, certParent);
65
+ if (!parentSignChild) {
66
+ return {
67
+ status: "BadCertificateInvalid",
68
+ reason: "One of the certificate in the chain is not signing the previous certificate",
69
+ };
70
+ }
71
+ const certInfo = exploreCertificate(cert);
72
+
73
+ // istanbul ignore next
74
+ if (!certInfo.tbsCertificate.extensions) {
75
+ return {
76
+ status: "BadCertificateInvalid",
77
+ reason: "Cannot find X409 Extension 3 in certificate",
78
+ };
79
+ }
80
+
81
+ // istanbul ignore next
82
+ if (!certParentInfo.tbsCertificate.extensions || !certInfo.tbsCertificate.extensions.authorityKeyIdentifier) {
83
+ return {
84
+ status: "BadCertificateInvalid",
85
+ reason: "Cannot find X409 Extension 3 in certificate (parent)",
86
+ };
87
+ }
88
+
89
+ // istanbul ignore next
90
+ if (
91
+ certParentInfo.tbsCertificate.extensions.subjectKeyIdentifier !==
92
+ certInfo.tbsCertificate.extensions.authorityKeyIdentifier.keyIdentifier
93
+ ) {
94
+ return {
95
+ status: "BadCertificateInvalid",
96
+ reason:
97
+ "subjectKeyIdentifier authorityKeyIdentifier in child certificate do not match subjectKeyIdentifier of parent certificate",
98
+ };
99
+ }
100
+ }
101
+ return {
102
+ status: "Good",
103
+ reason: `certificate chain is valid(length = ${certificateChain.length})`,
104
+ };
105
+ }
@@ -0,0 +1,25 @@
1
+ import { cryptoProvider } from "@peculiar/x509";
2
+
3
+ // https://stackoverflow.com/questions/56807959/generate-public-key-from-private-key-using-webcrypto-api
4
+ export async function buildPublicKey(privateKey: CryptoKey): Promise<CryptoKey> {
5
+ const crypto = cryptoProvider.get();
6
+
7
+ // export private key to JWK
8
+ const jwk = await crypto.subtle.exportKey("jwk", privateKey);
9
+
10
+ // remove private data from JWK
11
+ delete jwk.d;
12
+ delete jwk.dp;
13
+ delete jwk.dq;
14
+ delete jwk.q;
15
+ delete jwk.qi;
16
+ jwk.key_ops = ["encrypt", "wrapKey"];
17
+
18
+ // import public key
19
+ const publicKey = await crypto.subtle.importKey("jwk", jwk, { name: "RSA-OAEP", hash: "SHA-512" }, true, [
20
+ "encrypt",
21
+ "wrapKey",
22
+ ]);
23
+
24
+ return publicKey;
25
+ }
@@ -0,0 +1,5 @@
1
+ import * as x509 from "@peculiar/x509";
2
+ import { Crypto } from "@peculiar/webcrypto";
3
+ export const crypto = new Crypto();
4
+ x509.cryptoProvider.set(crypto);
5
+ export * as x509 from "@peculiar/x509";
@@ -0,0 +1,60 @@
1
+ import { CertificatePurpose } from "../common";
2
+ import {x509} from "./_crypto";
3
+
4
+ // key usage of OPCUA Server or OPCUA Client
5
+ const keyUsageApplication =
6
+ x509.KeyUsageFlags.keyEncipherment | x509.KeyUsageFlags.dataEncipherment | x509.KeyUsageFlags.digitalSignature;
7
+
8
+ // key usage for CA certificate
9
+ const keyUsageCA = x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.cRLSign;
10
+
11
+ export function getAttributes(purpose: CertificatePurpose): {
12
+ nsComment: string;
13
+ basicConstraints: x509.BasicConstraintsExtension;
14
+ keyUsageExtension: x509.ExtendedKeyUsage[];
15
+ usages: x509.KeyUsageFlags;
16
+ } {
17
+ let basicConstraints: x509.BasicConstraintsExtension;
18
+ let keyUsageExtension: x509.ExtendedKeyUsage[] = [];
19
+ let usages: x509.KeyUsageFlags;
20
+ let nsComment: string;
21
+ let extension: string;
22
+ switch (purpose) {
23
+ case CertificatePurpose.ForCertificateAuthority:
24
+ extension = "v3_ca";
25
+ /**
26
+ [ v3_ca ]
27
+ subjectKeyIdentifier = hash
28
+ authorityKeyIdentifier = keyid:always,issuer:always
29
+ * basicConstraints = CA:TRUE
30
+ * keyUsage = critical, cRLSign, keyCertSign
31
+ * nsComment = "Self-signed Certificate for CA generated by Node-OPCUA Certificate utility"
32
+ subjectAltName = $ENV::ALTNAME
33
+ */
34
+ basicConstraints = new x509.BasicConstraintsExtension(true, undefined, false);
35
+ usages = keyUsageCA;
36
+ keyUsageExtension = [];
37
+ nsComment = "Self-signed certificate for CA generated by Node-OPCUA Certificate utility V2";
38
+ break;
39
+ case CertificatePurpose.ForApplication:
40
+ case CertificatePurpose.ForUserAuthentication:
41
+ default:
42
+ /**
43
+ [ v3_selfsigned]
44
+ subjectKeyIdentifier = hash
45
+ authorityKeyIdentifier = keyid,issuer
46
+ * basicConstraints = critical, CA:FALSE
47
+ * keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyCertSign
48
+ * extendedKeyUsage = clientAuth,serverAuth
49
+ * nsComment = "Self-signed certificate generated by Node-OPCUA Certificate utility"
50
+ subjectAltName = $ENV::ALTNAME
51
+ */
52
+ extension = "v3_selfsigned";
53
+ basicConstraints = new x509.BasicConstraintsExtension(false, undefined, true);
54
+ usages = keyUsageApplication;
55
+ keyUsageExtension = [x509.ExtendedKeyUsage.serverAuth, x509.ExtendedKeyUsage.clientAuth];
56
+ nsComment = "Self-signed certificate generated by Node-OPCUA Certificate utility V2";
57
+ break;
58
+ }
59
+ return { nsComment, basicConstraints, keyUsageExtension, usages };
60
+ }
@@ -0,0 +1,64 @@
1
+ import { Subject } from "../subject";
2
+ import { CertificatePurpose } from "../common";
3
+ import { getAttributes } from "./_get_attributes";
4
+ import { x509 } from "./_crypto";
5
+ import { buildPublicKey } from "./_build_public_key";
6
+
7
+ interface CreateCertificateSigningRequestOptions {
8
+ privateKey: CryptoKey;
9
+ notBefore?: Date;
10
+ notAfter?: Date;
11
+ validity?: number;
12
+ subject?: string;
13
+ dns?: string[];
14
+ ip?: string[];
15
+ applicationUri?: string;
16
+ purpose: CertificatePurpose;
17
+ }
18
+ export async function createCertificateSigningRequest({
19
+ privateKey,
20
+ subject,
21
+ dns,
22
+ ip,
23
+ applicationUri,
24
+ purpose,
25
+ }: CreateCertificateSigningRequestOptions) {
26
+ const modulusLength = 2048;
27
+
28
+ const alg = {
29
+ name: "RSASSA-PKCS1-v1_5",
30
+ hash: { name: "SHA-256" },
31
+ publicExponent: new Uint8Array([1, 0, 1]),
32
+ modulusLength,
33
+ };
34
+
35
+ const publicKey = await buildPublicKey(privateKey);
36
+
37
+ const keys = {
38
+ privateKey,
39
+ publicKey,
40
+ };
41
+
42
+ const alternativeNameExtensions: x509.JsonGeneralName[] = [];
43
+ dns && dns.forEach((d) => alternativeNameExtensions.push({ type: "dns", value: d }));
44
+ ip && ip.forEach((d) => alternativeNameExtensions.push({ type: "ip", value: d }));
45
+ applicationUri && alternativeNameExtensions.push({ type: "url", value: applicationUri });
46
+
47
+ const { basicConstraints, usages } = getAttributes(purpose);
48
+
49
+ const s = new Subject(subject || "");
50
+ const s1 = s.toStringInternal(", ");
51
+ const name = s1;
52
+
53
+ const csr = await x509.Pkcs10CertificateRequestGenerator.create({
54
+ name,
55
+ keys,
56
+ signingAlgorithm: alg,
57
+ extensions: [
58
+ basicConstraints,
59
+ new x509.KeyUsagesExtension(usages, true),
60
+ new x509.SubjectAlternativeNameExtension(alternativeNameExtensions),
61
+ ],
62
+ });
63
+ return { csr: csr.toString("pem"), der: csr };
64
+ }
@@ -0,0 +1,70 @@
1
+ import * as x509 from "@peculiar/x509";
2
+ import { Crypto } from "@peculiar/webcrypto";
3
+ const crypto = new Crypto();
4
+ x509.cryptoProvider.set(crypto);
5
+
6
+ // ---------------------------------------------------------
7
+
8
+ interface KeyAlgorithm {
9
+ name: string;
10
+ }
11
+ type KeyType = "private" | "public" | "secret";
12
+ type KeyUsage = "decrypt" | "deriveBits" | "deriveKey" | "encrypt" | "sign" | "unwrapKey" | "verify" | "wrapKey";
13
+
14
+ /**
15
+ * The CryptoKey dictionary of the Web Crypto API represents a cryptographic key.
16
+ * Available only in secure contexts.
17
+ */
18
+ interface CryptoKey {
19
+ readonly algorithm: KeyAlgorithm;
20
+ readonly extractable: boolean;
21
+ readonly type: KeyType;
22
+ readonly usages: KeyUsage[];
23
+ }
24
+ interface CryptoKeyPair {
25
+ privateKey: CryptoKey;
26
+ publicKey: CryptoKey;
27
+ }
28
+
29
+ // ---------------------------------------------------------
30
+
31
+ export async function generateKeyPair(modulusLength: 1024 | 2048 | 3072 | 4096 = 2048): Promise<CryptoKeyPair> {
32
+ const alg: RsaHashedKeyGenParams = {
33
+ name: "RSASSA-PKCS1-v1_5",
34
+ hash: { name: "SHA-256" },
35
+ publicExponent: new Uint8Array([1, 0, 1]),
36
+ modulusLength,
37
+ };
38
+ const keys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
39
+
40
+ return keys;
41
+ }
42
+
43
+ export async function generatePrivateKey(modulusLength: 1024 | 2048 | 3072 | 4096 = 2048): Promise<CryptoKey> {
44
+ return (await generateKeyPair(modulusLength)).privateKey;
45
+ }
46
+
47
+ export async function privateKeyToPEM(privateKey: CryptoKey) {
48
+ const privDer = await crypto.subtle.exportKey("pkcs8", privateKey);
49
+ const privPem = x509.PemConverter.encode(privDer, "PRIVATE KEY");
50
+ return { privPem, privDer };
51
+ }
52
+
53
+ export async function derToPrivateKey(privDer: ArrayBuffer): Promise<CryptoKey> {
54
+ return await crypto.subtle.importKey(
55
+ "pkcs8",
56
+ privDer,
57
+ {
58
+ name: "RSASSA-PKCS1-v1_5",
59
+ hash: { name: "SHA-256" },
60
+ },
61
+ true,
62
+ ["sign", "encrypt", "decrypt", "verify", "wrapKey", "unwrapKey", "deriveKey", "deriveBits"]
63
+ );
64
+ }
65
+
66
+ export async function pemToPrivateKey(pem: string): Promise<CryptoKey> {
67
+ // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey
68
+ const privDer = x509.PemConverter.decode(pem);
69
+ return derToPrivateKey(privDer[0]);
70
+ }
@@ -0,0 +1,91 @@
1
+ import { Subject } from "../subject";
2
+ import { CertificatePurpose } from "../common";
3
+ import { x509 } from "./_crypto";
4
+ import { getAttributes } from "./_get_attributes";
5
+ import { buildPublicKey } from "./_build_public_key";
6
+
7
+ export interface CreateSelfSignCertificateOptions {
8
+ privateKey: CryptoKey;
9
+ notBefore?: Date;
10
+ notAfter?: Date;
11
+ validity?: number;
12
+ // CN=common/O=Org/C=US/ST=State/L=City
13
+ subject?: string;
14
+ dns?: string[];
15
+ ip?: string[];
16
+ applicationUri?: string;
17
+ purpose: CertificatePurpose;
18
+ }
19
+ export async function createSelfSignedCertificate({
20
+ privateKey,
21
+ notAfter,
22
+ notBefore,
23
+ validity,
24
+ subject,
25
+ dns,
26
+ ip,
27
+ applicationUri,
28
+ purpose,
29
+ }: CreateSelfSignCertificateOptions) {
30
+
31
+ const publicKey = await buildPublicKey(privateKey);
32
+
33
+ const keys = {
34
+ privateKey,
35
+ publicKey,
36
+ };
37
+
38
+ const { nsComment, basicConstraints, keyUsageExtension, usages } = getAttributes(purpose);
39
+
40
+ notBefore = notBefore || new Date();
41
+ validity = validity || 0;
42
+ if (!notAfter) {
43
+ validity = validity || 365;
44
+ }
45
+ notAfter = notAfter || new Date(notBefore.getTime() + validity * 24 * 60 * 60 * 1000);
46
+
47
+ const alternativeNameExtensions: x509.JsonGeneralName[] = [];
48
+ dns && dns.forEach((d) => alternativeNameExtensions.push({ type: "dns", value: d }));
49
+ ip && ip.forEach((d) => alternativeNameExtensions.push({ type: "ip", value: d }));
50
+ applicationUri && alternativeNameExtensions.push({ type: "url", value: applicationUri });
51
+
52
+ // https://opensource.apple.com/source/OpenSSH/OpenSSH-186/osslshim/heimdal-asn1/rfc2459.asn1.auto.html
53
+ const ID_NETSCAPE_COMMENT = "2.16.840.1.113730.1.13";
54
+
55
+ const s = new Subject(subject || "");
56
+ const s1 = s.toStringInternal(", ");
57
+ const name = s1;
58
+ // const issuer = s1;
59
+ /**
60
+ * name: "CN=Test, O=Дом",
61
+ * subject: "CN=Test, O=Дом",
62
+ * issuer: "CN=Test, O=Дом",
63
+
64
+ */
65
+
66
+ // const gg = new x509.GeneralNames(gga);
67
+ const cert = await x509.X509CertificateGenerator.createSelfSigned({
68
+ serialNumber: "01",
69
+ name,
70
+ // subject: s1,
71
+ // issuer,
72
+ notBefore,
73
+ notAfter,
74
+
75
+ signingAlgorithm: { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } },
76
+
77
+ keys,
78
+
79
+ extensions: [
80
+ new x509.Extension(ID_NETSCAPE_COMMENT, false, Buffer.from(nsComment, "ascii")),
81
+ // new x509.BasicConstraintsExtension(true, 2, true),
82
+ basicConstraints,
83
+ new x509.ExtendedKeyUsageExtension(keyUsageExtension, true),
84
+ new x509.KeyUsagesExtension(usages, true),
85
+ await x509.SubjectKeyIdentifierExtension.create(keys.publicKey),
86
+ new x509.SubjectAlternativeNameExtension(alternativeNameExtensions),
87
+ ],
88
+ });
89
+
90
+ return { cert: cert.toString("pem"), der: cert };
91
+ }