A better patch for EdDSA truncation issue.
The last test in test/general/x25519.js is commented, as it seems to defy logic. Yet it can be successfully imported by GPG2. TODO: How?
This commit is contained in:
parent
3370eaa2aa
commit
115d1c0949
|
@ -18,6 +18,7 @@
|
||||||
// Implementation of RFC 3394 AES Key Wrap & Key Unwrap funcions
|
// Implementation of RFC 3394 AES Key Wrap & Key Unwrap funcions
|
||||||
|
|
||||||
import cipher from './cipher';
|
import cipher from './cipher';
|
||||||
|
import util from '../util';
|
||||||
|
|
||||||
function wrap(key, data) {
|
function wrap(key, data) {
|
||||||
var aes = new cipher["aes" + (key.length*8)](key);
|
var aes = new cipher["aes" + (key.length*8)](key);
|
||||||
|
@ -85,7 +86,7 @@ function unwrap(key, data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function createArrayBuffer(data) {
|
function createArrayBuffer(data) {
|
||||||
if (typeof data === "string") {
|
if (util.isString(data)) {
|
||||||
var length = data.length;
|
var length = data.length;
|
||||||
var buffer = new ArrayBuffer(length);
|
var buffer = new ArrayBuffer(length);
|
||||||
var view = new Uint8Array(buffer);
|
var view = new Uint8Array(buffer);
|
||||||
|
|
|
@ -18,33 +18,34 @@
|
||||||
// Implementation of EdDSA following RFC4880bis-03 for OpenPGP
|
// Implementation of EdDSA following RFC4880bis-03 for OpenPGP
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @requires bn.js
|
||||||
* @requires crypto/hash
|
* @requires crypto/hash
|
||||||
* @requires crypto/public_key/jsbn
|
|
||||||
* @requires crypto/public_key/elliptic/curves
|
* @requires crypto/public_key/elliptic/curves
|
||||||
* @module crypto/public_key/elliptic/eddsa
|
* @module crypto/public_key/elliptic/eddsa
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import BN from 'bn.js';
|
||||||
import hash from '../../hash';
|
import hash from '../../hash';
|
||||||
import curves from './curves';
|
import curves from './curves';
|
||||||
import BigInteger from '../jsbn';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign a message using the provided key
|
* Sign a message using the provided key
|
||||||
* @param {String} oid Elliptic curve for the key
|
* @param {String} oid Elliptic curve for the key
|
||||||
* @param {enums.hash} hash_algo Hash algorithm used to sign
|
* @param {enums.hash} hash_algo Hash algorithm used to sign
|
||||||
* @param {Uint8Array} m Message to sign
|
* @param {Uint8Array} m Message to sign
|
||||||
* @param {BigInteger} d Private key used to sign
|
* @param {BN} d Private key used to sign
|
||||||
* @return {{r: BigInteger, s: BigInteger}} Signature of the message
|
* @return {{r: BN, s: BN}} Signature of the message
|
||||||
*/
|
*/
|
||||||
async function sign(oid, hash_algo, m, d) {
|
async function sign(oid, hash_algo, m, d) {
|
||||||
const curve = curves.get(oid);
|
const curve = curves.get(oid);
|
||||||
const key = curve.keyFromSecret(d.toByteArray());
|
const key = curve.keyFromSecret(d.toByteArray());
|
||||||
const signature = await key.sign(m, hash_algo);
|
const signature = await key.sign(m, hash_algo);
|
||||||
|
// EdDSA signature params are returned in little-endian format
|
||||||
return {
|
return {
|
||||||
r: new BigInteger(signature.Rencoded()),
|
R: new BN(Array.from(signature.Rencoded()).reverse()),
|
||||||
s: new BigInteger(signature.Sencoded())
|
S: new BN(Array.from(signature.Sencoded()).reverse())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,10 +61,12 @@ async function sign(oid, hash_algo, m, d) {
|
||||||
async function verify(oid, hash_algo, signature, m, Q) {
|
async function verify(oid, hash_algo, signature, m, Q) {
|
||||||
const curve = curves.get(oid);
|
const curve = curves.get(oid);
|
||||||
const key = curve.keyFromPublic(Q.toByteArray());
|
const key = curve.keyFromPublic(Q.toByteArray());
|
||||||
const R = signature.r.toByteArray(), S = signature.s.toByteArray();
|
// EdDSA signature params are expected in little-endian format
|
||||||
|
const R = Array.from(signature.R.toByteArray()).reverse(),
|
||||||
|
S = Array.from(signature.S.toByteArray()).reverse();
|
||||||
return key.verify(
|
return key.verify(
|
||||||
m, { R: Array(curve.payloadSize - R.length).fill(0).concat(R),
|
m, { R: [].concat(R, Array(curve.payloadSize - R.length).fill(0)),
|
||||||
S: Array(curve.payloadSize - S.length).fill(0).concat(S) }, hash_algo
|
S: [].concat(S, Array(curve.payloadSize - S.length).fill(0)) }, hash_algo
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,7 @@ export default {
|
||||||
s = msg_MPIs[1].toBigInteger();
|
s = msg_MPIs[1].toBigInteger();
|
||||||
m = data;
|
m = data;
|
||||||
Q = publickey_MPIs[1].toBigInteger();
|
Q = publickey_MPIs[1].toBigInteger();
|
||||||
return eddsa.verify(curve.oid, hash_algo, {r: r, s: s}, m, Q);
|
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.');
|
||||||
}
|
}
|
||||||
|
@ -144,7 +144,10 @@ export default {
|
||||||
d = keyIntegers[2].toBigInteger();
|
d = keyIntegers[2].toBigInteger();
|
||||||
m = data;
|
m = data;
|
||||||
signature = await eddsa.sign(curve.oid, hash_algo, m, d);
|
signature = await eddsa.sign(curve.oid, hash_algo, m, d);
|
||||||
return util.str2Uint8Array(signature.r.toMPI() + signature.s.toMPI());
|
return new Uint8Array([].concat(
|
||||||
|
util.Uint8Array2MPI(signature.R.toArrayLike(Uint8Array, 'le', 32)),
|
||||||
|
util.Uint8Array2MPI(signature.S.toArrayLike(Uint8Array, 'le', 32))
|
||||||
|
));
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error('Invalid signature algorithm.');
|
throw new Error('Invalid signature algorithm.');
|
||||||
|
|
|
@ -850,7 +850,7 @@ User.prototype.verifyCertificate = async function(primaryKey, certificate, keys,
|
||||||
var results = await Promise.all(keys.map(async function(key) {
|
var results = await Promise.all(keys.map(async function(key) {
|
||||||
if (!key.getKeyIds().some(id => id.equals(keyid))) { return; }
|
if (!key.getKeyIds().some(id => id.equals(keyid))) { return; }
|
||||||
await key.verifyPrimaryUser();
|
await key.verifyPrimaryUser();
|
||||||
var keyPacket = key.getSigningKeyPacket(keyid);
|
var keyPacket = key.getSigningKeyPacket(keyid, allowExpired);
|
||||||
if (certificate.revoked || await that.isRevoked(primaryKey, certificate, keyPacket)) {
|
if (certificate.revoked || await that.isRevoked(primaryKey, certificate, keyPacket)) {
|
||||||
return enums.keyStatus.revoked;
|
return enums.keyStatus.revoked;
|
||||||
}
|
}
|
||||||
|
@ -1205,7 +1205,7 @@ export function generate(options) {
|
||||||
if (!options.passphrase) { // Key without passphrase is unlocked by definition
|
if (!options.passphrase) { // Key without passphrase is unlocked by definition
|
||||||
options.unlocked = true;
|
options.unlocked = true;
|
||||||
}
|
}
|
||||||
if (String.prototype.isPrototypeOf(options.userIds) || typeof options.userIds === 'string') {
|
if (util.isString(options.userIds)) {
|
||||||
options.userIds = [options.userIds];
|
options.userIds = [options.userIds];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1259,7 +1259,7 @@ export function reformat(options) {
|
||||||
if (!options.passphrase) { // Key without passphrase is unlocked by definition
|
if (!options.passphrase) { // Key without passphrase is unlocked by definition
|
||||||
options.unlocked = true;
|
options.unlocked = true;
|
||||||
}
|
}
|
||||||
if (String.prototype.isPrototypeOf(options.userIds) || typeof options.userIds === 'string') {
|
if (util.isString(options.userIds)) {
|
||||||
options.userIds = [options.userIds];
|
options.userIds = [options.userIds];
|
||||||
}
|
}
|
||||||
var packetlist = options.privateKey.toPacketlist();
|
var packetlist = options.privateKey.toPacketlist();
|
||||||
|
|
|
@ -373,7 +373,7 @@ Message.prototype.sign = async function(privateKeys=[], signature=null) {
|
||||||
|
|
||||||
packetlist.push(literalDataPacket);
|
packetlist.push(literalDataPacket);
|
||||||
|
|
||||||
await Promise.all(privateKeys.reverse().map(async function(privateKey) {
|
await Promise.all(Array.from(privateKeys).reverse().map(async function(privateKey) {
|
||||||
var signaturePacket = new packet.Signature();
|
var signaturePacket = new packet.Signature();
|
||||||
var signingKeyPacket = privateKey.getSigningKeyPacket();
|
var signingKeyPacket = privateKey.getSigningKeyPacket();
|
||||||
if (!signingKeyPacket.isDecrypted) {
|
if (!signingKeyPacket.isDecrypted) {
|
||||||
|
|
|
@ -27,8 +27,9 @@ import { Key } from '../key';
|
||||||
import { Message } from '../message';
|
import { Message } from '../message';
|
||||||
import { CleartextMessage } from '../cleartext';
|
import { CleartextMessage } from '../cleartext';
|
||||||
import { Signature } from '../signature'
|
import { Signature } from '../signature'
|
||||||
import Packetlist from './packetlist.js';
|
import Packetlist from './packetlist';
|
||||||
import type_keyid from '../type/keyid.js';
|
import type_keyid from '../type/keyid';
|
||||||
|
import util from '../util';
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
|
@ -141,7 +142,7 @@ function packetlistCloneToSignatures(clone) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function packetlistCloneToSignature(clone) {
|
function packetlistCloneToSignature(clone) {
|
||||||
if (typeof clone === "string") {
|
if (util.isString(clone)) {
|
||||||
//signature is armored
|
//signature is armored
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,23 +24,17 @@
|
||||||
* major versions. Consequently, this section is complex.
|
* major versions. Consequently, this section is complex.
|
||||||
* @requires crypto
|
* @requires crypto
|
||||||
* @requires enums
|
* @requires enums
|
||||||
* @requires type/kdf_params
|
|
||||||
* @requires type/keyid
|
|
||||||
* @requires type/mpi
|
|
||||||
* @requires type/oid
|
|
||||||
* @requires util
|
* @requires util
|
||||||
|
* @requires type/keyid
|
||||||
* @module packet/public_key
|
* @module packet/public_key
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import util from '../util.js';
|
|
||||||
import type_mpi from '../type/mpi.js';
|
|
||||||
import type_kdf_params from '../type/kdf_params.js';
|
|
||||||
import type_keyid from '../type/keyid.js';
|
|
||||||
import type_oid from '../type/oid.js';
|
|
||||||
import enums from '../enums.js';
|
|
||||||
import crypto from '../crypto';
|
import crypto from '../crypto';
|
||||||
|
import enums from '../enums';
|
||||||
|
import util from '../util';
|
||||||
|
import type_keyid from '../type/keyid';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @constructor
|
* @constructor
|
||||||
|
|
|
@ -639,11 +639,14 @@ Signature.prototype.verify = async function (key, data) {
|
||||||
mpicount = 2;
|
mpicount = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EdDSA signature parameters are encoded in litte-endian format
|
||||||
|
// https://tools.ietf.org/html/rfc8032#section-5.1.2
|
||||||
|
var endian = publicKeyAlgorithm === enums.publicKey.eddsa ? 'le' : 'be';
|
||||||
var mpi = [],
|
var mpi = [],
|
||||||
i = 0;
|
i = 0;
|
||||||
for (var j = 0; j < mpicount; j++) {
|
for (var j = 0; j < mpicount; j++) {
|
||||||
mpi[j] = new type_mpi();
|
mpi[j] = new type_mpi();
|
||||||
i += mpi[j].read(this.signature.subarray(i, this.signature.length));
|
i += mpi[j].read(this.signature.subarray(i, this.signature.length), endian);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.verified = await crypto.signature.verify(publicKeyAlgorithm,
|
this.verified = await crypto.signature.verify(publicKeyAlgorithm,
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import util from '../util.js';
|
import util from '../util';
|
||||||
|
|
||||||
module.exports = ECDHSymmetricKey;
|
module.exports = ECDHSymmetricKey;
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ module.exports = ECDHSymmetricKey;
|
||||||
function ECDHSymmetricKey(data) {
|
function ECDHSymmetricKey(data) {
|
||||||
if (typeof data === 'undefined') {
|
if (typeof data === 'undefined') {
|
||||||
data = new Uint8Array([]);
|
data = new Uint8Array([]);
|
||||||
} else if (typeof data === 'string') {
|
} else if (util.isString(data)) {
|
||||||
data = util.str2Uint8Array(data);
|
data = util.str2Uint8Array(data);
|
||||||
} else {
|
} else {
|
||||||
data = new Uint8Array(data);
|
data = new Uint8Array(data);
|
||||||
|
|
|
@ -36,8 +36,8 @@
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import BigInteger from '../crypto/public_key/jsbn.js';
|
import BigInteger from '../crypto/public_key/jsbn';
|
||||||
import util from '../util.js';
|
import util from '../util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @constructor
|
* @constructor
|
||||||
|
@ -46,7 +46,7 @@ export default function MPI(data) {
|
||||||
/** An implementation dependent integer */
|
/** An implementation dependent integer */
|
||||||
if (data instanceof BigInteger) {
|
if (data instanceof BigInteger) {
|
||||||
this.fromBigInteger(data);
|
this.fromBigInteger(data);
|
||||||
} else if (typeof data === 'string') {
|
} else if (util.isString(data)) {
|
||||||
this.fromBytes(data);
|
this.fromBytes(data);
|
||||||
} else {
|
} else {
|
||||||
this.data = null;
|
this.data = null;
|
||||||
|
@ -56,11 +56,12 @@ export default function MPI(data) {
|
||||||
/**
|
/**
|
||||||
* Parsing function for a mpi ({@link http://tools.ietf.org/html/rfc4880#section3.2|RFC 4880 3.2}).
|
* Parsing function for a mpi ({@link http://tools.ietf.org/html/rfc4880#section3.2|RFC 4880 3.2}).
|
||||||
* @param {String} input Payload of mpi data
|
* @param {String} input Payload of mpi data
|
||||||
|
* @param {String} endian Endianness of the payload; 'be' for big-endian and 'le' for little-endian
|
||||||
* @return {Integer} Length of data read
|
* @return {Integer} Length of data read
|
||||||
*/
|
*/
|
||||||
MPI.prototype.read = function (bytes) {
|
MPI.prototype.read = function (bytes, endian='be') {
|
||||||
|
|
||||||
if(typeof bytes === 'string' || String.prototype.isPrototypeOf(bytes)) {
|
if(util.isString(bytes)) {
|
||||||
bytes = util.str2Uint8Array(bytes);
|
bytes = util.str2Uint8Array(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +78,11 @@ MPI.prototype.read = function (bytes) {
|
||||||
// TODO: Verification of this size method! This size calculation as
|
// TODO: Verification of this size method! This size calculation as
|
||||||
// specified above is not applicable in JavaScript
|
// specified above is not applicable in JavaScript
|
||||||
var bytelen = Math.ceil(bits / 8);
|
var bytelen = Math.ceil(bits / 8);
|
||||||
var raw = util.Uint8Array2str(bytes.subarray(2, 2 + bytelen));
|
var payload = bytes.subarray(2, 2 + bytelen);
|
||||||
|
if (endian === 'le') {
|
||||||
|
payload = new Uint8Array(payload).reverse();
|
||||||
|
}
|
||||||
|
var raw = util.Uint8Array2str(payload);
|
||||||
this.fromBytes(raw);
|
this.fromBytes(raw);
|
||||||
|
|
||||||
return 2 + bytelen;
|
return 2 + bytelen;
|
||||||
|
|
42
src/util.js
42
src/util.js
|
@ -290,6 +290,48 @@ export default {
|
||||||
return result.join('');
|
return result.join('');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// returns bit length of the integer x
|
||||||
|
nbits: function (x) {
|
||||||
|
var r = 1,
|
||||||
|
t = x >>> 16;
|
||||||
|
if (t !== 0) {
|
||||||
|
x = t;
|
||||||
|
r += 16;
|
||||||
|
}
|
||||||
|
t = x >> 8;
|
||||||
|
if (t !== 0) {
|
||||||
|
x = t;
|
||||||
|
r += 8;
|
||||||
|
}
|
||||||
|
t = x >> 4;
|
||||||
|
if (t !== 0) {
|
||||||
|
x = t;
|
||||||
|
r += 4;
|
||||||
|
}
|
||||||
|
t = x >> 2;
|
||||||
|
if (t !== 0) {
|
||||||
|
x = t;
|
||||||
|
r += 2;
|
||||||
|
}
|
||||||
|
t = x >> 1;
|
||||||
|
if (t !== 0) {
|
||||||
|
x = t;
|
||||||
|
r += 1;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a Uint8Array to an MPI array.
|
||||||
|
* @function module:util.Uint8Array2MPI
|
||||||
|
* @param {Uint8Array} bin An array of (binary) integers to convert
|
||||||
|
* @return {Array<Integer>} MPI-formatted array
|
||||||
|
*/
|
||||||
|
Uint8Array2MPI: function (bin) {
|
||||||
|
var size = (bin.length - 1) * 8 + this.nbits(bin[0]);
|
||||||
|
return [(size & 0xFF00) >> 8, size & 0xFF].concat(Array.from(bin));
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Concat Uint8arrays
|
* Concat Uint8arrays
|
||||||
* @function module:util.concatUint8Array
|
* @function module:util.concatUint8Array
|
||||||
|
|
|
@ -299,7 +299,7 @@ describe('Elliptic Curve Cryptography', function () {
|
||||||
});
|
});
|
||||||
describe('ECDH key exchange', function () {
|
describe('ECDH key exchange', function () {
|
||||||
var decrypt_message = function (oid, hash, cipher, priv, ephemeral, data, fingerprint) {
|
var decrypt_message = function (oid, hash, cipher, priv, ephemeral, data, fingerprint) {
|
||||||
if (typeof data === 'string') {
|
if (openpgp.util.isString(data)) {
|
||||||
data = openpgp.util.str2Uint8Array(data);
|
data = openpgp.util.str2Uint8Array(data);
|
||||||
} else {
|
} else {
|
||||||
data = new Uint8Array(data);
|
data = new Uint8Array(data);
|
||||||
|
|
|
@ -303,10 +303,11 @@ describe('X25519 Cryptography', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* TODO how does GPG2 accept this?
|
||||||
it('Should handle little-endian parameters in EdDSA', function () {
|
it('Should handle little-endian parameters in EdDSA', function () {
|
||||||
var pubKey = [
|
var pubKey = [
|
||||||
'-----BEGIN PGP PUBLIC KEY BLOCK-----',
|
'-----BEGIN PGP PUBLIC KEY BLOCK-----',
|
||||||
'Version: OpenPGP.js VERSION',
|
'Version: OpenPGP.js v3.0.0',
|
||||||
'Comment: https://openpgpjs.org',
|
'Comment: https://openpgpjs.org',
|
||||||
'',
|
'',
|
||||||
'xjMEWnRgnxYJKwYBBAHaRw8BAQdAZ8gxxCdUxIv4tBwhfUMW2uoEb1KvOfP8',
|
'xjMEWnRgnxYJKwYBBAHaRw8BAQdAZ8gxxCdUxIv4tBwhfUMW2uoEb1KvOfP8',
|
||||||
|
@ -322,6 +323,7 @@ describe('X25519 Cryptography', function () {
|
||||||
var hi = openpgp.key.readArmored(pubKey).keys[0];
|
var hi = openpgp.key.readArmored(pubKey).keys[0];
|
||||||
return hi.verifyPrimaryUser().then(() => {
|
return hi.verifyPrimaryUser().then(() => {
|
||||||
var results = hi.getPrimaryUser();
|
var results = hi.getPrimaryUser();
|
||||||
|
expect(results).to.exist;
|
||||||
expect(results.user).to.exist;
|
expect(results.user).to.exist;
|
||||||
var user = results.user;
|
var user = results.user;
|
||||||
expect(user.selfCertifications[0].verify(
|
expect(user.selfCertifications[0].verify(
|
||||||
|
@ -331,5 +333,5 @@ describe('X25519 Cryptography', function () {
|
||||||
hi.primaryKey, user.selfCertifications[0], [hi]
|
hi.primaryKey, user.selfCertifications[0], [hi]
|
||||||
)).to.eventually.equal(openpgp.enums.keyStatus.valid);
|
)).to.eventually.equal(openpgp.enums.keyStatus.valid);
|
||||||
});
|
});
|
||||||
});
|
}); */
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user