Skip to content

Commit a41a2bc

Browse files
committed
Sign/Verify Operations for ECDSA
also an Operation for ECDSA signature conversion, as there could be multiple formats of the signature
1 parent c2b6fdc commit a41a2bc

File tree

6 files changed

+644
-0
lines changed

6 files changed

+644
-0
lines changed

src/core/config/Categories.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@
142142
"RSA Encrypt",
143143
"RSA Decrypt",
144144
"Generate ECDSA Key Pair",
145+
"ECDSA Signature Conversion",
146+
"ECDSA Sign",
147+
"ECDSA Verify",
145148
"Parse SSH Host Key"
146149
]
147150
},

src/core/operations/ECDSASign.mjs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/**
2+
* @author cplussharp
3+
* @copyright Crown Copyright 2021
4+
* @license Apache-2.0
5+
*/
6+
7+
import Operation from "../Operation.mjs";
8+
import OperationError from "../errors/OperationError.mjs";
9+
import r from "jsrsasign";
10+
11+
/**
12+
* ECDSA Sign operation
13+
*/
14+
class ECDSASign extends Operation {
15+
16+
/**
17+
* ECDSASign constructor
18+
*/
19+
constructor() {
20+
super();
21+
22+
this.name = "ECDSA Sign";
23+
this.module = "Ciphers";
24+
this.description = "Sign a plaintext message with a PEM encoded EC key.";
25+
this.infoURL = "https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm";
26+
this.inputType = "string";
27+
this.outputType = "string";
28+
this.args = [
29+
{
30+
name: "ECDSA Private Key (PEM)",
31+
type: "text",
32+
value: "-----BEGIN EC PRIVATE KEY-----"
33+
},
34+
{
35+
name: "Message Digest Algorithm",
36+
type: "option",
37+
value: [
38+
"SHA-256",
39+
"SHA-384",
40+
"SHA-512",
41+
"SHA-1",
42+
"MD5"
43+
]
44+
},
45+
{
46+
name: "Output Format",
47+
type: "option",
48+
value: [
49+
"ASN.1 HEX",
50+
"Concat HEX",
51+
"JSON"
52+
]
53+
}
54+
];
55+
}
56+
57+
/**
58+
* @param {string} input
59+
* @param {Object[]} args
60+
* @returns {string}
61+
*/
62+
run(input, args) {
63+
const [keyPem, mdAlgo, outputFormat] = args;
64+
65+
if (keyPem.replace("-----BEGIN EC PRIVATE KEY-----", "").length === 0) {
66+
throw new OperationError("Please enter a private key.");
67+
}
68+
69+
const internalAlgorithmName = mdAlgo.replace("-", "") + "withECDSA";
70+
const sig = new r.KJUR.crypto.Signature({ alg: internalAlgorithmName });
71+
const key = r.KEYUTIL.getKey(keyPem);
72+
if (key.type !== "EC") {
73+
throw new OperationError("Provided key is not an EC key.");
74+
}
75+
if (!key.isPrivate) {
76+
throw new OperationError("Provided key is not a private key.");
77+
}
78+
sig.init(key);
79+
const signatureASN1Hex = sig.signString(input);
80+
81+
let result;
82+
switch (outputFormat) {
83+
case "ASN.1 HEX":
84+
result = signatureASN1Hex;
85+
break;
86+
case "Concat HEX":
87+
result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex);
88+
break;
89+
case "JSON": {
90+
const signatureRS = r.KJUR.crypto.ECDSA.parseSigHexInHexRS(signatureASN1Hex);
91+
result = JSON.stringify(signatureRS);
92+
break;
93+
}
94+
}
95+
96+
return result;
97+
}
98+
}
99+
100+
export default ECDSASign;
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/**
2+
* @author cplussharp
3+
* @copyright Crown Copyright 2021
4+
* @license Apache-2.0
5+
*/
6+
7+
import Operation from "../Operation.mjs";
8+
import r from "jsrsasign";
9+
10+
/**
11+
* ECDSA Sign operation
12+
*/
13+
class ECDSASignatureConversion extends Operation {
14+
15+
/**
16+
* ECDSASignatureConversion constructor
17+
*/
18+
constructor() {
19+
super();
20+
21+
this.name = "ECDSA Signature Conversion";
22+
this.module = "Ciphers";
23+
this.description = "Convert an ECDSA signature between hex, asn1 and json.";
24+
this.infoURL = "https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm";
25+
this.inputType = "string";
26+
this.outputType = "string";
27+
this.args = [
28+
{
29+
name: "Input Format",
30+
type: "option",
31+
value: [
32+
"Auto",
33+
"ASN.1 HEX",
34+
"Concat HEX",
35+
"JSON"
36+
]
37+
},
38+
{
39+
name: "Output Format",
40+
type: "option",
41+
value: [
42+
"ASN.1 HEX",
43+
"Concat HEX",
44+
"JSON"
45+
]
46+
}
47+
];
48+
}
49+
50+
/**
51+
* @param {string} input
52+
* @param {Object[]} args
53+
* @returns {string}
54+
*/
55+
run(input, args) {
56+
let inputFormat = args[0];
57+
const outputFormat = args[1];
58+
59+
// detect input format
60+
if (inputFormat === "Auto") {
61+
if (input.substr(0, 2) === "30" && r.ASN1HEX.isASN1HEX(input)) {
62+
inputFormat = "ASN.1 HEX";
63+
} else if (input.indexOf("{") !== -1) {
64+
inputFormat = "JSON";
65+
} else {
66+
inputFormat = "Concat HEX";
67+
}
68+
}
69+
70+
// convert input to ASN.1 hex
71+
let signatureASN1Hex;
72+
switch (inputFormat) {
73+
case "ASN.1 HEX":
74+
signatureASN1Hex = input;
75+
break;
76+
case "Concat HEX":
77+
signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(input);
78+
break;
79+
case "JSON": {
80+
const inputJson = JSON.parse(input);
81+
signatureASN1Hex = r.KJUR.crypto.ECDSA.hexRSSigToASN1Sig(inputJson.r, inputJson.s);
82+
break;
83+
}
84+
}
85+
86+
// convert ASN.1 hex to output format
87+
let result;
88+
switch (outputFormat) {
89+
case "ASN.1 HEX":
90+
result = signatureASN1Hex;
91+
break;
92+
case "Concat HEX":
93+
result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex);
94+
break;
95+
case "JSON": {
96+
const signatureRS = r.KJUR.crypto.ECDSA.parseSigHexInHexRS(signatureASN1Hex);
97+
result = JSON.stringify(signatureRS);
98+
break;
99+
}
100+
}
101+
102+
return result;
103+
}
104+
}
105+
106+
export default ECDSASignatureConversion;

src/core/operations/ECDSAVerify.mjs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/**
2+
* @author cplussharp
3+
* @copyright Crown Copyright 2021
4+
* @license Apache-2.0
5+
*/
6+
7+
import Operation from "../Operation.mjs";
8+
import OperationError from "../errors/OperationError.mjs";
9+
import r from "jsrsasign";
10+
11+
/**
12+
* ECDSA Verify operation
13+
*/
14+
class ECDSAVerify extends Operation {
15+
16+
/**
17+
* ECDSAVerify constructor
18+
*/
19+
constructor() {
20+
super();
21+
22+
this.name = "ECDSA Verify";
23+
this.module = "Ciphers";
24+
this.description = "Verify a message against a signature and a public PEM encoded EC key.";
25+
this.infoURL = "https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm";
26+
this.inputType = "string";
27+
this.outputType = "string";
28+
this.args = [
29+
{
30+
name: "Input Format",
31+
type: "option",
32+
value: [
33+
"Auto",
34+
"ASN.1 HEX",
35+
"Concat HEX",
36+
"JSON"
37+
]
38+
},
39+
{
40+
name: "Message Digest Algorithm",
41+
type: "option",
42+
value: [
43+
"SHA-256",
44+
"SHA-384",
45+
"SHA-512",
46+
"SHA-1",
47+
"MD5"
48+
]
49+
},
50+
{
51+
name: "ECDSA Public Key (PEM)",
52+
type: "text",
53+
value: "-----BEGIN PUBLIC KEY-----"
54+
},
55+
{
56+
name: "Message",
57+
type: "text",
58+
value: ""
59+
}
60+
];
61+
}
62+
63+
/**
64+
* @param {string} input
65+
* @param {Object[]} args
66+
* @returns {string}
67+
*/
68+
run(input, args) {
69+
let inputFormat = args[0];
70+
const [, mdAlgo, keyPem, msg] = args;
71+
72+
if (keyPem.replace("-----BEGIN PUBLIC KEY-----", "").length === 0) {
73+
throw new OperationError("Please enter a public key.");
74+
}
75+
76+
// detect input format
77+
if (inputFormat === "Auto") {
78+
if (input.substr(0, 2) === "30" && r.ASN1HEX.isASN1HEX(input)) {
79+
inputFormat = "ASN.1 HEX";
80+
} else if (input.indexOf("{") !== -1) {
81+
inputFormat = "JSON";
82+
} else {
83+
inputFormat = "Concat HEX";
84+
}
85+
}
86+
87+
// convert to ASN.1 signature
88+
let signatureASN1Hex;
89+
switch (inputFormat) {
90+
case "ASN.1 HEX":
91+
signatureASN1Hex = input;
92+
break;
93+
case "Concat HEX":
94+
signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(input);
95+
break;
96+
case "JSON": {
97+
const inputJson = JSON.parse(input);
98+
signatureASN1Hex = r.KJUR.crypto.ECDSA.hexRSSigToASN1Sig(inputJson.r, inputJson.s);
99+
break;
100+
}
101+
}
102+
103+
// verify signature
104+
const internalAlgorithmName = mdAlgo.replace("-", "") + "withECDSA";
105+
const sig = new r.KJUR.crypto.Signature({ alg: internalAlgorithmName });
106+
const key = r.KEYUTIL.getKey(keyPem);
107+
if (key.type !== "EC") {
108+
throw new OperationError("Provided key is not an EC key.");
109+
}
110+
if (!key.isPublic) {
111+
throw new OperationError("Provided key is not a public key.");
112+
}
113+
sig.init(key);
114+
sig.updateString(msg);
115+
const result = sig.verify(signatureASN1Hex);
116+
return result ? "Verified OK" : "Verification Failure";
117+
}
118+
}
119+
120+
export default ECDSAVerify;

tests/operations/index.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import "./tests/ConditionalJump.mjs";
3838
import "./tests/Crypt.mjs";
3939
import "./tests/CSV.mjs";
4040
import "./tests/DateTime.mjs";
41+
import "./tests/ECDSA.mjs";
4142
import "./tests/ExtractEmailAddresses.mjs";
4243
import "./tests/Fork.mjs";
4344
import "./tests/FromDecimal.mjs";

0 commit comments

Comments
 (0)