keygen and sign/verify with ed25519 works
This commit is contained in:
parent
e6820d7b2a
commit
35f18444b0
|
@ -183,6 +183,7 @@ export default {
|
||||||
return ['mpi'];
|
return ['mpi'];
|
||||||
case 'ecdh':
|
case 'ecdh':
|
||||||
case 'ecdsa':
|
case 'ecdsa':
|
||||||
|
case 'eddsa':
|
||||||
// Algorithm-Specific Fields for ECDSA or ECDH secret keys:
|
// Algorithm-Specific Fields for ECDSA or ECDH secret keys:
|
||||||
// - MPI of an integer representing the secret key.
|
// - MPI of an integer representing the secret key.
|
||||||
return ['mpi'];
|
return ['mpi'];
|
||||||
|
@ -217,10 +218,11 @@ export default {
|
||||||
// - MPI of DSA public-key value y (= g**x mod p where x is secret).
|
// - MPI of DSA public-key value y (= g**x mod p where x is secret).
|
||||||
case 'dsa':
|
case 'dsa':
|
||||||
return ['mpi', 'mpi', 'mpi', 'mpi'];
|
return ['mpi', 'mpi', 'mpi', 'mpi'];
|
||||||
// Algorithm-Specific Fields for ECDSA public keys:
|
// Algorithm-Specific Fields for ECDSA/EdDSA public keys:
|
||||||
// - OID of curve;
|
// - OID of curve;
|
||||||
// - MPI of EC point representing public key.
|
// - MPI of EC point representing public key.
|
||||||
case 'ecdsa':
|
case 'ecdsa':
|
||||||
|
case 'eddsa':
|
||||||
return ['oid', 'mpi'];
|
return ['oid', 'mpi'];
|
||||||
// Algorithm-Specific Fields for ECDH public keys:
|
// Algorithm-Specific Fields for ECDH public keys:
|
||||||
// - OID of curve;
|
// - OID of curve;
|
||||||
|
@ -279,6 +281,7 @@ export default {
|
||||||
});
|
});
|
||||||
|
|
||||||
case 'ecdsa':
|
case 'ecdsa':
|
||||||
|
case 'eddsa':
|
||||||
return publicKey.elliptic.generate(curve).then(function (keyObject) {
|
return publicKey.elliptic.generate(curve).then(function (keyObject) {
|
||||||
return constructParams([keyObject.oid, keyObject.Q, keyObject.d], types);
|
return constructParams([keyObject.oid, keyObject.Q, keyObject.d], types);
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,118 +27,144 @@
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import {ec as EC} from 'elliptic';
|
import { ec as EC, eddsa as EdDSA } from 'elliptic';
|
||||||
import {KeyPair} from './key.js';
|
import { KeyPair } from './key';
|
||||||
import BigInteger from '../jsbn.js';
|
import BigInteger from '../jsbn';
|
||||||
|
import random from '../../random';
|
||||||
import config from '../../../config';
|
import config from '../../../config';
|
||||||
import enums from '../../../enums.js';
|
import enums from '../../../enums';
|
||||||
import util from '../../../util.js';
|
import util from '../../../util';
|
||||||
import base64 from '../../../encoding/base64.js';
|
import base64 from '../../../encoding/base64';
|
||||||
|
|
||||||
const webCrypto = util.getWebCrypto();
|
const webCrypto = util.getWebCrypto();
|
||||||
const nodeCrypto = util.getNodeCrypto();
|
const nodeCrypto = util.getNodeCrypto();
|
||||||
|
|
||||||
var webCurves = [], nodeCurves = [];
|
var webCurves = {}, nodeCurves = {};
|
||||||
if (webCrypto && config.use_native) {
|
if (webCrypto && config.use_native) {
|
||||||
// see https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API/Supported_algorithms
|
webCurves = {
|
||||||
webCurves = ['P-256', 'P-384', 'P-521'];
|
'p256': 'P-256',
|
||||||
|
'p384': 'P-384',
|
||||||
|
'p521': 'P-521'
|
||||||
|
};
|
||||||
} else if (nodeCrypto && config.use_native) {
|
} else if (nodeCrypto && config.use_native) {
|
||||||
// FIXME make sure the name translations are correct
|
var knownCurves = nodeCrypto.getCurves();
|
||||||
nodeCurves = nodeCrypto.getCurves();
|
nodeCurves = {
|
||||||
|
'secp256k1': knownCurves.includes('secp256k1') ? 'secp256k1' : undefined,
|
||||||
|
'p256': knownCurves.includes('prime256v1') ? 'prime256v1' : undefined,
|
||||||
|
'p384': knownCurves.includes('secp384r1') ? 'secp384r1' : undefined,
|
||||||
|
'p521': knownCurves.includes('secp521r1') ? 'secp521r1' : undefined
|
||||||
|
// TODO add more here
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const curves = {
|
const curves = {
|
||||||
p256: {
|
p256: {
|
||||||
oid: util.bin2str([0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]),
|
oid: util.bin2str([0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]),
|
||||||
pointSize: 66, // FIXME
|
|
||||||
namedCurve: 'P-256',
|
|
||||||
opensslCurve: 'prime256v1',
|
|
||||||
hashName: 'SHA-256',
|
|
||||||
hash: enums.hash.sha256,
|
hash: enums.hash.sha256,
|
||||||
cipher: enums.symmetric.aes128,
|
cipher: enums.symmetric.aes128,
|
||||||
node: nodeCurves.includes('prime256v1'),
|
node: nodeCurves.secp256r1,
|
||||||
web: webCurves.includes('P-256')
|
web: webCurves.secp256r1,
|
||||||
|
payloadSize: 32
|
||||||
},
|
},
|
||||||
p384: {
|
p384: {
|
||||||
oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x22]),
|
oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x22]),
|
||||||
pointSize: 48,
|
|
||||||
namedCurve: 'P-384',
|
|
||||||
opensslCurve: 'secp384r1', // FIXME
|
|
||||||
hashName: 'SHA-384',
|
|
||||||
hash: enums.hash.sha384,
|
hash: enums.hash.sha384,
|
||||||
cipher: enums.symmetric.aes192,
|
cipher: enums.symmetric.aes192,
|
||||||
node: nodeCurves.includes('secp384r1'), // FIXME
|
node: nodeCurves.secp384r1,
|
||||||
web: webCurves.includes('P-384')
|
web: webCurves.secp384r1,
|
||||||
|
payloadSize: 48
|
||||||
},
|
},
|
||||||
p521: {
|
p521: {
|
||||||
oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x23]),
|
oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x23]),
|
||||||
pointSize: 66,
|
|
||||||
namedCurve: 'P-521',
|
|
||||||
opensslCurve: 'secp521r1', // FIXME
|
|
||||||
hashName: 'SHA-512',
|
|
||||||
hash: enums.hash.sha512,
|
hash: enums.hash.sha512,
|
||||||
cipher: enums.symmetric.aes256,
|
cipher: enums.symmetric.aes256,
|
||||||
node: nodeCurves.includes('secp521r1'), // FIXME
|
node: nodeCurves.secp521r1,
|
||||||
web: webCurves.includes('P-521')
|
web: webCurves.secp521r1,
|
||||||
|
payloadSize: 66
|
||||||
},
|
},
|
||||||
secp256k1: {
|
secp256k1: {
|
||||||
oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x0A]),
|
oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x0A]),
|
||||||
pointSize: 66, // FIXME
|
|
||||||
namedCurve: 'SECP-256K1',
|
|
||||||
opensslCurve: 'secp256k1',
|
|
||||||
hashName: 'SHA-256',
|
|
||||||
hash: enums.hash.sha256,
|
hash: enums.hash.sha256,
|
||||||
cipher: enums.symmetric.aes128,
|
cipher: enums.symmetric.aes128,
|
||||||
node: false, // FIXME nodeCurves.includes('secp256k1'),
|
node: false // FIXME when we replace jwk-to-pem or it supports this curve
|
||||||
// this is because jwk-to-pem does not support this curve.
|
|
||||||
web: false
|
|
||||||
},
|
},
|
||||||
curve25519 : {},
|
ed25519: {
|
||||||
ed25519 : {}
|
oid: util.bin2str([0x2B, 0x06, 0x01, 0x04, 0x01, 0xDA, 0x47, 0x0F, 0x01]),
|
||||||
|
hash: enums.hash.sha512,
|
||||||
|
keyType: enums.publicKey.eddsa
|
||||||
|
},
|
||||||
|
curve25519: {
|
||||||
|
oid: util.bin2str([0x2B, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01]),
|
||||||
|
hash: enums.hash.sha256,
|
||||||
|
cipher: enums.symmetric.aes128
|
||||||
|
},
|
||||||
|
brainpoolP256r1: { // TODO 1.3.36.3.3.2.8.1.1.7
|
||||||
|
oid: util.bin2str([0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07])
|
||||||
|
},
|
||||||
|
brainpoolP512r1: { // TODO 1.3.36.3.3.2.8.1.1.13
|
||||||
|
oid: util.bin2str([0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D])
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function Curve(name, {oid, pointSize, hash, cipher, namedCurve, opensslCurve, hashName, node, web}) {
|
function Curve(name, params) {
|
||||||
|
if (params.keyType === enums.publicKey.eddsa) {
|
||||||
|
this.curve = new EdDSA(name);
|
||||||
|
this.keyType = enums.publicKey.eddsa;
|
||||||
|
} else {
|
||||||
this.curve = new EC(name);
|
this.curve = new EC(name);
|
||||||
this.name = name;
|
this.keyType = enums.publicKey.ecdsa;
|
||||||
this.oid = oid;
|
}
|
||||||
this.pointSize = pointSize;
|
this.oid = curves[name].oid;
|
||||||
this.hash = hash;
|
this.hash = params.hash;
|
||||||
this.cipher = cipher;
|
this.cipher = params.cipher;
|
||||||
this.namedCurve= namedCurve;
|
this.node = params.node && curves[name].node;
|
||||||
this.opensslCurve = opensslCurve;
|
this.web = params.web && curves[name].web;
|
||||||
this.hashName = hashName;
|
this.payloadSize = curves[name].payloadSize;
|
||||||
this.node = node;
|
|
||||||
this.web = web;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Curve.prototype.keyFromPrivate = function (priv) {
|
Curve.prototype.keyFromPrivate = function (priv) { // Not for ed25519
|
||||||
return new KeyPair(this.curve, {priv: priv});
|
return new KeyPair(this.curve, { priv: priv });
|
||||||
|
};
|
||||||
|
|
||||||
|
Curve.prototype.keyFromSecret = function (secret) { // Only for ed25519
|
||||||
|
return new KeyPair(this.curve, { secret: secret });
|
||||||
};
|
};
|
||||||
|
|
||||||
Curve.prototype.keyFromPublic = function (pub) {
|
Curve.prototype.keyFromPublic = function (pub) {
|
||||||
return new KeyPair(this.curve, {pub: pub});
|
return new KeyPair(this.curve, { pub: pub });
|
||||||
};
|
};
|
||||||
|
|
||||||
Curve.prototype.genKeyPair = async function () {
|
Curve.prototype.genKeyPair = async function () {
|
||||||
var keyPair;
|
var r, keyPair;
|
||||||
if (webCrypto && config.use_native && this.web) {
|
if (webCrypto && config.use_native && this.web) {
|
||||||
keyPair = await webGenKeyPair(this.namedCurve, "ECDSA"); // FIXME
|
keyPair = await webGenKeyPair(this.name, "ECDSA"); // FIXME is ECDH different?
|
||||||
} else if (nodeCrypto && config.use_native && this.node) {
|
} else if (nodeCrypto && config.use_native && this.node) {
|
||||||
keyPair = await nodeGenKeyPair(this.opensslCurve);
|
keyPair = await nodeGenKeyPair(this.name);
|
||||||
} else {
|
} else {
|
||||||
var r = this.curve.genKeyPair();
|
if (this.keyType === enums.publicKey.eddsa) {
|
||||||
|
keyPair = {
|
||||||
|
secret: util.hexidump(random.getRandomBytes(32))
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
r = this.curve.genKeyPair();
|
||||||
keyPair = {
|
keyPair = {
|
||||||
pub: r.getPublic().encode(),
|
pub: r.getPublic().encode(),
|
||||||
priv: r.getPrivate().toArray()
|
priv: r.getPrivate().toArray()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return new KeyPair(this.curve, keyPair);
|
return new KeyPair(this.curve, keyPair);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
function get(oid_or_name) {
|
function get(oid_or_name) {
|
||||||
for (var name in curves) {
|
var name;
|
||||||
if (curves[name].oid === oid_or_name || name === oid_or_name) {
|
if (enums.curve[oid_or_name]) {
|
||||||
|
name = enums.write(enums.curve, oid_or_name);
|
||||||
|
return new Curve(name, curves[name]);
|
||||||
|
}
|
||||||
|
for (name in curves) {
|
||||||
|
if (curves[name].oid === oid_or_name) {
|
||||||
return new Curve(name, curves[name]);
|
return new Curve(name, curves[name]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,11 +197,11 @@ module.exports = {
|
||||||
//////////////////////////
|
//////////////////////////
|
||||||
|
|
||||||
|
|
||||||
async function webGenKeyPair(namedCurve, algorithm) {
|
async function webGenKeyPair(name, algorithm) {
|
||||||
var webCryptoKey = await webCrypto.generateKey(
|
var webCryptoKey = await webCrypto.generateKey(
|
||||||
{
|
{
|
||||||
name: algorithm === "ECDH" ? "ECDH" : "ECDSA",
|
name: algorithm === "ECDH" ? "ECDH" : "ECDSA",
|
||||||
namedCurve: namedCurve
|
namedCurve: webCurves[name]
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
algorithm === "ECDH" ? ["deriveKey", "deriveBits"] : ["sign", "verify"]
|
algorithm === "ECDH" ? ["deriveKey", "deriveBits"] : ["sign", "verify"]
|
||||||
|
@ -193,8 +219,8 @@ async function webGenKeyPair(namedCurve, algorithm) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function nodeGenKeyPair(opensslCurve) {
|
async function nodeGenKeyPair(name) {
|
||||||
var ecdh = nodeCrypto.createECDH(opensslCurve);
|
var ecdh = nodeCrypto.createECDH(name === "secp256r1" ? "prime256v1" : name);
|
||||||
await ecdh.generateKeys();
|
await ecdh.generateKeys();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -37,7 +37,9 @@ import util from '../../../util.js';
|
||||||
import base64 from '../../../encoding/base64.js';
|
import base64 from '../../../encoding/base64.js';
|
||||||
|
|
||||||
const webCrypto = util.getWebCrypto();
|
const webCrypto = util.getWebCrypto();
|
||||||
|
const webCurves = curves.webCurves;
|
||||||
const nodeCrypto = util.getNodeCrypto();
|
const nodeCrypto = util.getNodeCrypto();
|
||||||
|
const nodeCurves = curves.nodeCurves;
|
||||||
|
|
||||||
var ECDSASignature = ASN1.define('ECDSASignature', function() {
|
var ECDSASignature = ASN1.define('ECDSASignature', function() {
|
||||||
this.seq().obj(
|
this.seq().obj(
|
||||||
|
@ -67,8 +69,8 @@ async function sign(oid, hash_algo, m, d) {
|
||||||
signature = await key.sign(m, hash_algo);
|
signature = await key.sign(m, hash_algo);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
r: new BigInteger(signature.r),
|
r: new BigInteger(signature.r.toArray()),
|
||||||
s: new BigInteger(signature.s)
|
s: new BigInteger(signature.s.toArray())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +114,7 @@ module.exports = {
|
||||||
|
|
||||||
|
|
||||||
async function webSign(curve, hash_algo, message, keyPair) {
|
async function webSign(curve, hash_algo, message, keyPair) {
|
||||||
var l = curve.pointSize;
|
var l = curve.payloadSize;
|
||||||
if (typeof message === 'string') {
|
if (typeof message === 'string') {
|
||||||
message = util.str2Uint8Array(message);
|
message = util.str2Uint8Array(message);
|
||||||
}
|
}
|
||||||
|
@ -120,7 +122,7 @@ async function webSign(curve, hash_algo, message, keyPair) {
|
||||||
"jwk",
|
"jwk",
|
||||||
{
|
{
|
||||||
"kty": "EC",
|
"kty": "EC",
|
||||||
"crv": curve.namedCurve,
|
"crv": webCurves[curve.name],
|
||||||
"x": base64.encode(new Uint8Array(keyPair.getPublic().getX().toArray('be', l)), null, 'base64url'),
|
"x": base64.encode(new Uint8Array(keyPair.getPublic().getX().toArray('be', l)), null, 'base64url'),
|
||||||
"y": base64.encode(new Uint8Array(keyPair.getPublic().getY().toArray('be', l)), null, 'base64url'),
|
"y": base64.encode(new Uint8Array(keyPair.getPublic().getY().toArray('be', l)), null, 'base64url'),
|
||||||
"d": base64.encode(new Uint8Array(keyPair.getPrivate().toArray('be', l)), null, 'base64url'),
|
"d": base64.encode(new Uint8Array(keyPair.getPrivate().toArray('be', l)), null, 'base64url'),
|
||||||
|
@ -129,8 +131,8 @@ async function webSign(curve, hash_algo, message, keyPair) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ECDSA",
|
"name": "ECDSA",
|
||||||
"namedCurve": curve.namedCurve,
|
"namedCurve": webCurves[curve.name],
|
||||||
"hash": { name: curve.hashName }
|
"hash": { name: enums.read(enums.webHash, curve.hash) }
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
["sign"]
|
["sign"]
|
||||||
|
@ -139,7 +141,7 @@ async function webSign(curve, hash_algo, message, keyPair) {
|
||||||
const signature = new Uint8Array(await webCrypto.sign(
|
const signature = new Uint8Array(await webCrypto.sign(
|
||||||
{
|
{
|
||||||
"name": 'ECDSA',
|
"name": 'ECDSA',
|
||||||
"namedCurve": curve.namedCurve,
|
"namedCurve": webCurves[curve.name],
|
||||||
"hash": { name: enums.read(enums.webHash, hash_algo) }
|
"hash": { name: enums.read(enums.webHash, hash_algo) }
|
||||||
},
|
},
|
||||||
key,
|
key,
|
||||||
|
@ -152,7 +154,7 @@ async function webSign(curve, hash_algo, message, keyPair) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function webVerify(curve, hash_algo, signature, message, publicKey) {
|
async function webVerify(curve, hash_algo, signature, message, publicKey) {
|
||||||
var r = signature.r.toByteArray(), s = signature.s.toByteArray(), l = curve.pointSize;
|
var r = signature.r.toByteArray(), s = signature.s.toByteArray(), l = curve.payloadSize;
|
||||||
r = (r.length === l) ? r : [0].concat(r);
|
r = (r.length === l) ? r : [0].concat(r);
|
||||||
s = (s.length === l) ? s : [0].concat(s);
|
s = (s.length === l) ? s : [0].concat(s);
|
||||||
signature = new Uint8Array(r.concat(s)).buffer;
|
signature = new Uint8Array(r.concat(s)).buffer;
|
||||||
|
@ -163,7 +165,7 @@ async function webVerify(curve, hash_algo, signature, message, publicKey) {
|
||||||
"jwk",
|
"jwk",
|
||||||
{
|
{
|
||||||
"kty": "EC",
|
"kty": "EC",
|
||||||
"crv": curve.namedCurve,
|
"crv": webCurves[curve.name],
|
||||||
"x": base64.encode(new Uint8Array(publicKey.getX().toArray('be', l)), null, 'base64url'),
|
"x": base64.encode(new Uint8Array(publicKey.getX().toArray('be', l)), null, 'base64url'),
|
||||||
"y": base64.encode(new Uint8Array(publicKey.getY().toArray('be', l)), null, 'base64url'),
|
"y": base64.encode(new Uint8Array(publicKey.getY().toArray('be', l)), null, 'base64url'),
|
||||||
"use": "sig",
|
"use": "sig",
|
||||||
|
@ -171,8 +173,8 @@ async function webVerify(curve, hash_algo, signature, message, publicKey) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ECDSA",
|
"name": "ECDSA",
|
||||||
"namedCurve": curve.namedCurve,
|
"namedCurve": webCurves[curve.name],
|
||||||
"hash": { name: curve.hashName }
|
"hash": { name: enums.read(enums.webHash, curve.hash) }
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
["verify"]
|
["verify"]
|
||||||
|
@ -181,7 +183,7 @@ async function webVerify(curve, hash_algo, signature, message, publicKey) {
|
||||||
return webCrypto.verify(
|
return webCrypto.verify(
|
||||||
{
|
{
|
||||||
"name": 'ECDSA',
|
"name": 'ECDSA',
|
||||||
"namedCurve": curve.namedCurve,
|
"namedCurve": webCurves[curve.name],
|
||||||
"hash": { name: enums.read(enums.webHash, hash_algo) }
|
"hash": { name: enums.read(enums.webHash, hash_algo) }
|
||||||
},
|
},
|
||||||
key,
|
key,
|
||||||
|
@ -198,7 +200,7 @@ async function nodeSign(curve, hash_algo, message, keyPair) {
|
||||||
const key = jwkToPem(
|
const key = jwkToPem(
|
||||||
{
|
{
|
||||||
"kty": "EC",
|
"kty": "EC",
|
||||||
"crv": curve.namedCurve,
|
"crv": nodeCurves[curve.name],
|
||||||
"x": base64.encode(new Uint8Array(keyPair.getPublic().getX().toArray())),
|
"x": base64.encode(new Uint8Array(keyPair.getPublic().getX().toArray())),
|
||||||
"y": base64.encode(new Uint8Array(keyPair.getPublic().getY().toArray())),
|
"y": base64.encode(new Uint8Array(keyPair.getPublic().getY().toArray())),
|
||||||
"d": base64.encode(new Uint8Array(keyPair.getPrivate().toArray())),
|
"d": base64.encode(new Uint8Array(keyPair.getPrivate().toArray())),
|
||||||
|
@ -231,7 +233,7 @@ async function nodeVerify(curve, hash_algo, signature, message, publicKey) {
|
||||||
const key = jwkToPem(
|
const key = jwkToPem(
|
||||||
{
|
{
|
||||||
"kty": "EC",
|
"kty": "EC",
|
||||||
"crv": curve.namedCurve,
|
"crv": nodeCurves[curve.name],
|
||||||
"x": base64.encode(new Uint8Array(publicKey.getX().toArray())),
|
"x": base64.encode(new Uint8Array(publicKey.getX().toArray())),
|
||||||
"y": base64.encode(new Uint8Array(publicKey.getY().toArray())),
|
"y": base64.encode(new Uint8Array(publicKey.getY().toArray())),
|
||||||
"use": "sig",
|
"use": "sig",
|
||||||
|
|
50
src/crypto/public_key/elliptic/eddsa.js
Normal file
50
src/crypto/public_key/elliptic/eddsa.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
// Implementation of EdDSA for OpenPGP
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import curves from './curves.js';
|
||||||
|
import BigInteger from '../jsbn.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign a message using the provided key
|
||||||
|
* @param {String} oid Elliptic curve for the key
|
||||||
|
* @param {enums.hash} hash_algo Hash algorithm used to sign
|
||||||
|
* @param {Uint8Array} m Message to sign
|
||||||
|
* @param {BigInteger} d Private key used to sign
|
||||||
|
* @return {{r: BigInteger, s: BigInteger}} Signature of the message
|
||||||
|
*/
|
||||||
|
async function sign(oid, hash_algo, m, d) {
|
||||||
|
var signature;
|
||||||
|
const curve = curves.get(oid);
|
||||||
|
hash_algo = hash_algo ? hash_algo : curve.hash;
|
||||||
|
const key = curve.keyFromSecret(d.toByteArray());
|
||||||
|
signature = await key.sign(m, hash_algo);
|
||||||
|
return {
|
||||||
|
r: new BigInteger(signature.Rencoded()),
|
||||||
|
s: new BigInteger(signature.Sencoded())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies if a signature is valid for a message
|
||||||
|
* @param {String} oid Elliptic curve for the key
|
||||||
|
* @param {enums.hash} hash_algo Hash algorithm used in the signature
|
||||||
|
* @param {{r: BigInteger, s: BigInteger}} signature Signature to verify
|
||||||
|
* @param {Uint8Array} m Message to verify
|
||||||
|
* @param {BigInteger} Q Public key used to verify the message
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
async function verify(oid, hash_algo, signature, m, Q) {
|
||||||
|
var result;
|
||||||
|
const curve = curves.get(oid);
|
||||||
|
hash_algo = hash_algo ? hash_algo : curve.hash; // FIXME is this according to the RFC?
|
||||||
|
const key = curve.keyFromPublic(Q.toByteArray());
|
||||||
|
return key.verify(
|
||||||
|
m, {R: signature.r.toByteArray(), S: signature.s.toByteArray()}, hash_algo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
sign: sign,
|
||||||
|
verify: verify
|
||||||
|
};
|
|
@ -21,18 +21,21 @@
|
||||||
* @requires crypto/public_key/elliptic/curve
|
* @requires crypto/public_key/elliptic/curve
|
||||||
* @requires crypto/public_key/elliptic/ecdh
|
* @requires crypto/public_key/elliptic/ecdh
|
||||||
* @requires crypto/public_key/elliptic/ecdsa
|
* @requires crypto/public_key/elliptic/ecdsa
|
||||||
|
* @requires crypto/public_key/elliptic/eddsa
|
||||||
* @module crypto/public_key/elliptic
|
* @module crypto/public_key/elliptic
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import {get, generate} from './curves.js';
|
import {get, generate} from './curves';
|
||||||
import ecdh from './ecdh.js';
|
import ecdsa from './ecdsa';
|
||||||
import ecdsa from './ecdsa.js';
|
import eddsa from './eddsa';
|
||||||
|
import ecdh from './ecdh';
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ecdh: ecdh,
|
|
||||||
ecdsa: ecdsa,
|
ecdsa: ecdsa,
|
||||||
|
eddsa: eddsa,
|
||||||
|
ecdh: ecdh,
|
||||||
get: get,
|
get: get,
|
||||||
generate: generate
|
generate: generate
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,10 +26,12 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import hash from '../../hash';
|
import hash from '../../hash';
|
||||||
import util from '../../../util.js';
|
import util from '../../../util';
|
||||||
|
import enums from '../../../enums';
|
||||||
|
|
||||||
function KeyPair(curve, options) {
|
function KeyPair(curve, options) {
|
||||||
this.curve = curve;
|
this.curve = curve;
|
||||||
|
this.keyType = curve.curve.type === 'edwards' ? enums.publicKey.eddsa : enums.publicKey.ecdsa;
|
||||||
this.keyPair = this.curve.keyPair(options);
|
this.keyPair = this.curve.keyPair(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,11 +40,7 @@ KeyPair.prototype.sign = function (message, hash_algo) {
|
||||||
message = util.str2Uint8Array(message);
|
message = util.str2Uint8Array(message);
|
||||||
}
|
}
|
||||||
const digest = (typeof hash_algo === 'undefined') ? message : hash.digest(hash_algo, message);
|
const digest = (typeof hash_algo === 'undefined') ? message : hash.digest(hash_algo, message);
|
||||||
const signature = this.keyPair.sign(digest);
|
return this.keyPair.sign(digest);
|
||||||
return {
|
|
||||||
r: signature.r.toArray(),
|
|
||||||
s: signature.s.toArray()
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
KeyPair.prototype.verify = function (message, signature, hash_algo) {
|
KeyPair.prototype.verify = function (message, signature, hash_algo) {
|
||||||
|
@ -54,18 +52,25 @@ KeyPair.prototype.verify = function (message, signature, hash_algo) {
|
||||||
};
|
};
|
||||||
|
|
||||||
KeyPair.prototype.derive = function (pub) {
|
KeyPair.prototype.derive = function (pub) {
|
||||||
|
if (this.keyType === enums.publicKey.eddsa) {
|
||||||
|
throw new Error('Key can only be used for EdDSA');
|
||||||
|
}
|
||||||
return this.keyPair.derive(pub.keyPair.getPublic()).toArray();
|
return this.keyPair.derive(pub.keyPair.getPublic()).toArray();
|
||||||
};
|
};
|
||||||
|
|
||||||
KeyPair.prototype.getPublic = function () {
|
KeyPair.prototype.getPublic = function () {
|
||||||
return this.keyPair.getPublic().encode();
|
return this.keyPair.getPublic('array');
|
||||||
};
|
};
|
||||||
|
|
||||||
KeyPair.prototype.getPrivate = function () {
|
KeyPair.prototype.getPrivate = function () {
|
||||||
|
if (this.keyType === enums.publicKey.eddsa) {
|
||||||
|
return this.keyPair.getSecret();
|
||||||
|
} else {
|
||||||
return this.keyPair.getPrivate().toArray();
|
return this.keyPair.getPrivate().toArray();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
KeyPair.prototype.isValid = function () {
|
KeyPair.prototype.isValid = function () { // FIXME
|
||||||
return this.keyPair.validate().result;
|
return this.keyPair.validate().result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,10 @@ export default {
|
||||||
*/
|
*/
|
||||||
verify: async function(algo, hash_algo, msg_MPIs, publickey_MPIs, data) {
|
verify: async function(algo, hash_algo, msg_MPIs, publickey_MPIs, data) {
|
||||||
var m;
|
var m;
|
||||||
|
var r;
|
||||||
|
var s;
|
||||||
|
var Q;
|
||||||
|
var curve;
|
||||||
|
|
||||||
data = util.Uint8Array2str(data);
|
data = util.Uint8Array2str(data);
|
||||||
|
|
||||||
|
@ -59,12 +63,21 @@ export default {
|
||||||
case 19:
|
case 19:
|
||||||
// ECDSA
|
// ECDSA
|
||||||
const ecdsa = publicKey.elliptic.ecdsa;
|
const ecdsa = publicKey.elliptic.ecdsa;
|
||||||
const curve = publickey_MPIs[0];
|
curve = publickey_MPIs[0];
|
||||||
const r = msg_MPIs[0].toBigInteger();
|
r = msg_MPIs[0].toBigInteger();
|
||||||
const s = msg_MPIs[1].toBigInteger();
|
s = msg_MPIs[1].toBigInteger();
|
||||||
m = data;
|
m = data;
|
||||||
const Q = publickey_MPIs[1].toBigInteger();
|
Q = publickey_MPIs[1].toBigInteger();
|
||||||
return ecdsa.verify(curve.oid, hash_algo, {r: r, s: s}, m, Q);
|
return ecdsa.verify(curve.oid, hash_algo, {r: r, s: s}, m, Q);
|
||||||
|
case 22:
|
||||||
|
// EdDSA
|
||||||
|
const eddsa = publicKey.elliptic.eddsa;
|
||||||
|
curve = publickey_MPIs[0];
|
||||||
|
r = msg_MPIs[0].toBigInteger();
|
||||||
|
s = msg_MPIs[1].toBigInteger();
|
||||||
|
m = data;
|
||||||
|
Q = publickey_MPIs[1].toBigInteger();
|
||||||
|
return eddsa.verify(curve.oid, hash_algo, {r: r, s: s}, m, Q);
|
||||||
default:
|
default:
|
||||||
throw new Error('Invalid signature algorithm.');
|
throw new Error('Invalid signature algorithm.');
|
||||||
}
|
}
|
||||||
|
@ -84,6 +97,8 @@ export default {
|
||||||
|
|
||||||
var m;
|
var m;
|
||||||
var d;
|
var d;
|
||||||
|
var curve;
|
||||||
|
var signature;
|
||||||
|
|
||||||
switch (algo) {
|
switch (algo) {
|
||||||
case 1:
|
case 1:
|
||||||
|
@ -117,10 +132,18 @@ export default {
|
||||||
case 19:
|
case 19:
|
||||||
// ECDSA
|
// ECDSA
|
||||||
var ecdsa = publicKey.elliptic.ecdsa;
|
var ecdsa = publicKey.elliptic.ecdsa;
|
||||||
var curve = keyIntegers[0];
|
curve = keyIntegers[0];
|
||||||
d = keyIntegers[2].toBigInteger();
|
d = keyIntegers[2].toBigInteger();
|
||||||
m = data;
|
m = data;
|
||||||
const signature = await ecdsa.sign(curve.oid, hash_algo, m, d);
|
signature = await ecdsa.sign(curve.oid, hash_algo, m, d);
|
||||||
|
return util.str2Uint8Array(signature.r.toMPI() + signature.s.toMPI());
|
||||||
|
case 22:
|
||||||
|
// EdDSA
|
||||||
|
var eddsa = publicKey.elliptic.eddsa;
|
||||||
|
curve = keyIntegers[0];
|
||||||
|
d = keyIntegers[2].toBigInteger();
|
||||||
|
m = data;
|
||||||
|
signature = await eddsa.sign(curve.oid, hash_algo, m, d);
|
||||||
return util.str2Uint8Array(signature.r.toMPI() + signature.s.toMPI());
|
return util.str2Uint8Array(signature.r.toMPI() + signature.s.toMPI());
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
34
src/enums.js
34
src/enums.js
|
@ -6,6 +6,37 @@
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
||||||
|
/** Maps curve names under various standards to one
|
||||||
|
* @enum {String}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
curve: {
|
||||||
|
"p256": "p256",
|
||||||
|
"P-256": "p256",
|
||||||
|
"secp256r1": "p256",
|
||||||
|
"prime256v1": "p256",
|
||||||
|
"1.2.840.10045.3.1.7": "p256",
|
||||||
|
|
||||||
|
"p384": "p384",
|
||||||
|
"P-384": "p384",
|
||||||
|
"secp384r1": "p384",
|
||||||
|
"1.3.132.0.34": "p384",
|
||||||
|
|
||||||
|
"p521": "p521",
|
||||||
|
"P-521": "p521",
|
||||||
|
"secp521r1": "p521",
|
||||||
|
"1.3.132.0.35": "p521",
|
||||||
|
|
||||||
|
"secp256k1": "secp256k1",
|
||||||
|
"1.3.132.0.10": "secp256k1",
|
||||||
|
|
||||||
|
"ed25519": "ed25519",
|
||||||
|
"1.3.6.1.4.1.11591.15.1": "ed25519",
|
||||||
|
|
||||||
|
"curve25519": "curve25519",
|
||||||
|
"1.3.6.1.4.1.3029.1.5.1": "curve25519"
|
||||||
|
},
|
||||||
|
|
||||||
/** A string to key specifier type
|
/** A string to key specifier type
|
||||||
* @enum {Integer}
|
* @enum {Integer}
|
||||||
* @readonly
|
* @readonly
|
||||||
|
@ -28,7 +59,8 @@ export default {
|
||||||
elgamal: 16,
|
elgamal: 16,
|
||||||
dsa: 17,
|
dsa: 17,
|
||||||
ecdh: 18,
|
ecdh: 18,
|
||||||
ecdsa: 19
|
ecdsa: 19,
|
||||||
|
eddsa: 22
|
||||||
},
|
},
|
||||||
|
|
||||||
/** {@link http://tools.ietf.org/html/rfc4880#section-9.2|RFC4880, section 9.2}
|
/** {@link http://tools.ietf.org/html/rfc4880#section-9.2|RFC4880, section 9.2}
|
||||||
|
|
|
@ -89,7 +89,7 @@ export function destroyWorker() {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a new OpenPGP key pair. Currently only supports RSA keys. Primary and subkey will be of same type.
|
* Generates a new OpenPGP key pair. Supports RSA and ECC keys. Primary and subkey will be of same type.
|
||||||
* @param {Array<Object>} userIds array of user IDs e.g. [{ name:'Phil Zimmermann', email:'phil@openpgp.org' }]
|
* @param {Array<Object>} userIds array of user IDs e.g. [{ name:'Phil Zimmermann', email:'phil@openpgp.org' }]
|
||||||
* @param {String} passphrase (optional) The passphrase used to encrypt the resulting private key
|
* @param {String} passphrase (optional) The passphrase used to encrypt the resulting private key
|
||||||
* @param {Number} numBits (optional) number of bits for the key creation. (should be 2048 or 4096)
|
* @param {Number} numBits (optional) number of bits for the key creation. (should be 2048 or 4096)
|
||||||
|
@ -100,7 +100,7 @@ export function destroyWorker() {
|
||||||
* @static
|
* @static
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function generateKey({ userIds=[], passphrase, numBits=2048, unlocked=false, keyExpirationTime=0, curve=""} = {}) {
|
export function generateKey({ userIds=[], passphrase, numBits=2048, unlocked=false, keyExpirationTime=0, curve="" } = {}) {
|
||||||
userIds = formatUserIds(userIds);
|
userIds = formatUserIds(userIds);
|
||||||
const options = {userIds, passphrase, numBits, unlocked, keyExpirationTime, curve};
|
const options = {userIds, passphrase, numBits, unlocked, keyExpirationTime, curve};
|
||||||
|
|
||||||
|
|
|
@ -633,7 +633,9 @@ Signature.prototype.verify = async function (key, data) {
|
||||||
// Algorithm-Specific Fields for DSA and ECDSA signatures:
|
// Algorithm-Specific Fields for DSA and ECDSA signatures:
|
||||||
// - MPI of DSA value r.
|
// - MPI of DSA value r.
|
||||||
// - MPI of DSA value s.
|
// - MPI of DSA value s.
|
||||||
else if (publicKeyAlgorithm === 17 || publicKeyAlgorithm === 19) {
|
else if (publicKeyAlgorithm === enums.publicKey.dsa ||
|
||||||
|
publicKeyAlgorithm === enums.publicKey.ecdsa ||
|
||||||
|
publicKeyAlgorithm === enums.publicKey.eddsa) {
|
||||||
mpicount = 2;
|
mpicount = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user