superfalcon
Version:
Falcon and Ed25519 paired together as a single signing scheme
855 lines (733 loc) • 20.8 kB
JavaScript
var isNode = false;
try {
isNode = eval('global').process.release.name === 'node';
}
catch (_) {}
var falcon = require('falcon-crypto');
var fastSHA512 = require('fast-sha512');
var sodium = require('libsodium-wrappers-sumo');
var sodiumUtil = require('sodiumutil');
var nodeCrypto, Buffer;
if (isNode) {
nodeCrypto = eval('require')('crypto');
Buffer = eval('global.Buffer');
}
function hashWithAdditionalData (message, additionalData, preHashed) {
var shouldClearAdditionalData = typeof additionalData === 'string';
var shouldClearMessage = typeof message === 'string';
return Promise.resolve().then(function () {
message = sodiumUtil.from_string(message);
if (preHashed && message.length !== fastSHA512.bytes) {
throw new Error('Invalid pre-hashed message.');
}
return Promise.all([
fastSHA512.baseHash(
additionalData ? sodiumUtil.from_string(additionalData) : new Uint8Array(0),
shouldClearAdditionalData
),
preHashed ? message : fastSHA512.baseHash(message, shouldClearMessage)
]);
}).then(function (results) {
var additionalDataHash = results[0];
var messageToHash = results[1];
var fullMessage = new Uint8Array(additionalDataHash.length + fastSHA512.bytes);
fullMessage.set(additionalDataHash);
fullMessage.set(messageToHash, additionalDataHash.length);
sodiumUtil.memzero(additionalDataHash);
if (shouldClearMessage || !preHashed) {
sodiumUtil.memzero(messageToHash);
}
return fastSHA512.baseHash(fullMessage, true);
});
}
function deriveEncryptionKey (password, salt) {
if (isNode) {
return new Promise(function (resolve, reject) {
nodeCrypto.pbkdf2(
Buffer.from(password),
Buffer.from(salt),
aes.keyDerivation.iterations,
aes.keyBytes,
aes.keyDerivation.hashFunction,
function (err, key) {
if (err) {
reject(err);
}
else {
resolve(key);
}
}
);
});
}
else {
return Promise.resolve().then(function () {
return crypto.subtle.importKey(
'raw',
sodiumUtil.from_string(password),
{
name: aes.keyDerivation.algorithm,
},
false,
['deriveKey']
);
}).then(function (keyOrigin) {
return crypto.subtle.deriveKey(
{
name: aes.keyDerivation.algorithm,
salt: salt,
iterations: aes.keyDerivation.iterations,
hash: {
name: aes.keyDerivation.hashFunction
},
},
keyOrigin,
{
name: aes.algorithm,
length: aes.keyBits
},
false,
['encrypt', 'decrypt']
);
});
}
}
function encrypt (plaintext, password) {
var setup = Promise.resolve().then(function () {
var iv = isNode ?
nodeCrypto.randomBytes(aes.ivBytes) :
crypto.getRandomValues(new Uint8Array(aes.ivBytes))
;
var salt = isNode ?
nodeCrypto.randomBytes(aes.keyDerivation.saltBytes) :
crypto.getRandomValues(new Uint8Array(aes.keyDerivation.saltBytes))
;
return Promise.all([iv, salt, deriveEncryptionKey(password, salt)]);
}).then(function (results) {
return {
iv: results[0],
salt: results[1],
key: results[2]
};
});
if (isNode) {
return setup.then(function (o) {
var cipher = nodeCrypto.createCipheriv(aes.algorithm, o.key, o.iv);
var buf1 = cipher.update(Buffer.from(plaintext));
var buf2 = cipher.final();
var buf3 = cipher.getAuthTag();
var cyphertext = new Uint8Array(Buffer.concat([o.iv, o.salt, buf1, buf2, buf3]));
sodiumUtil.memzero(o.iv);
sodiumUtil.memzero(o.salt);
sodiumUtil.memzero(o.key);
sodiumUtil.memzero(buf1);
sodiumUtil.memzero(buf2);
sodiumUtil.memzero(buf3);
return cyphertext;
});
}
else {
return setup.then(function (o) {
return Promise.all([o, crypto.subtle.encrypt(
{
name: aes.algorithm,
iv: o.iv,
tagLength: aes.tagBits
},
o.key,
plaintext
)]);
}).then(function (results) {
var o = results[0];
var encrypted = new Uint8Array(results[1]);
var cyphertext = new Uint8Array(
aes.ivBytes + aes.keyDerivation.saltBytes + encrypted.length
);
cyphertext.set(o.iv);
cyphertext.set(o.salt, aes.ivBytes);
cyphertext.set(encrypted, aes.ivBytes + aes.keyDerivation.saltBytes);
sodiumUtil.memzero(o.iv);
sodiumUtil.memzero(o.salt);
sodiumUtil.memzero(o.key);
sodiumUtil.memzero(encrypted);
return cyphertext;
});
}
}
function decrypt (cyphertext, password) {
return Promise.resolve().then(function () {
var iv = new Uint8Array(cyphertext.buffer, cyphertext.byteOffset, aes.ivBytes);
var salt = new Uint8Array(
cyphertext.buffer,
cyphertext.byteOffset + aes.ivBytes,
aes.keyDerivation.saltBytes
);
return Promise.all([iv, deriveEncryptionKey(password, salt)]);
}).then(function (results) {
var iv = results[0];
var key = results[1];
var decrypted;
if (isNode) {
var encrypted = new Uint8Array(
cyphertext.buffer,
cyphertext.byteOffset + aes.ivBytes + aes.keyDerivation.saltBytes,
cyphertext.length -
aes.ivBytes -
aes.keyDerivation.saltBytes -
aes.tagBytes
);
var authTag = new Uint8Array(
cyphertext.buffer,
cyphertext.byteOffset + cyphertext.length - aes.tagBytes,
aes.tagBytes
);
var decipher = nodeCrypto.createDecipheriv(
aes.algorithm,
Buffer.from(key),
Buffer.from(iv)
);
decipher.setAuthTag(Buffer.from(authTag));
var buf1 = decipher.update(Buffer.from(encrypted));
var buf2 = decipher.final();
decrypted = Buffer.concat([buf1, buf2]);
sodiumUtil.memzero(buf1);
sodiumUtil.memzero(buf2);
}
else {
var encrypted = new Uint8Array(
cyphertext.buffer,
cyphertext.byteOffset + aes.ivBytes + aes.keyDerivation.saltBytes,
cyphertext.length - aes.ivBytes - aes.keyDerivation.saltBytes
);
decrypted = crypto.subtle.decrypt(
{
name: aes.algorithm,
iv: iv,
tagLength: aes.tagBits
},
key,
encrypted
);
}
return Promise.all([key, decrypted]);
}).then(function (results) {
var key = results[0];
var decrypted = results[1];
sodiumUtil.memzero(key);
return new Uint8Array(decrypted);
});
}
var aes = {
algorithm: isNode ? 'aes-256-gcm' : 'AES-GCM',
ivBytes: 12,
keyBytes: 32,
keyBits: 256,
tagBytes: 16,
tagBits: 128,
keyDerivation: {
algorithm: 'PBKDF2',
hashFunction: isNode ? 'sha512' : 'SHA-512',
iterations: 1000000,
saltBytes: 32
}
};
var publicKeyBytes, privateKeyBytes, bytes, falconBytes;
var initiated = Promise.all([
falcon.publicKeyBytes,
falcon.privateKeyBytes,
falcon.bytes,
sodium.ready
]).then(function (results) {
falconBytes = {
publicKeyBytes: results[0],
privateKeyBytes: results[1],
bytes: results[2]
};
publicKeyBytes = sodium.crypto_sign_PUBLICKEYBYTES + falconBytes.publicKeyBytes;
privateKeyBytes = sodium.crypto_sign_SECRETKEYBYTES + falconBytes.privateKeyBytes;
bytes = sodium.crypto_sign_BYTES + falconBytes.bytes;
});
var superFalcon = {
_sodiumUtil: sodiumUtil,
publicKeyBytes: initiated.then(function () { return publicKeyBytes; }),
privateKeyBytes: initiated.then(function () { return privateKeyBytes; }),
bytes: initiated.then(function () { return bytes; }),
hashBytes: Promise.resolve(fastSHA512.bytes),
hash: function (message, onlyBinary) {
return fastSHA512.hash(message, onlyBinary);
},
keyPair: function () { return initiated.then(function () {
return Promise.all([
sodium.crypto_sign_keypair(),
falcon.keyPair()
]).then(function (results) {
var eccKeyPair = results[0];
var falconKeyPair = results[1];
var keyPair = {
keyType: 'superfalcon',
publicKey: new Uint8Array(publicKeyBytes),
privateKey: new Uint8Array(privateKeyBytes)
};
keyPair.publicKey.set(eccKeyPair.publicKey);
keyPair.privateKey.set(eccKeyPair.privateKey);
keyPair.publicKey.set(falconKeyPair.publicKey, sodium.crypto_sign_PUBLICKEYBYTES);
keyPair.privateKey.set(falconKeyPair.privateKey, sodium.crypto_sign_SECRETKEYBYTES);
sodiumUtil.memzero(falconKeyPair.privateKey);
sodiumUtil.memzero(eccKeyPair.privateKey);
sodiumUtil.memzero(falconKeyPair.publicKey);
sodiumUtil.memzero(eccKeyPair.publicKey);
return keyPair;
});
}); },
sign: function (message, privateKey, additionalData) { return initiated.then(function () {
var shouldClearMessage = typeof message === 'string';
return superFalcon.signDetached(
message,
privateKey,
additionalData
).then(function (signature) {
message = sodiumUtil.from_string(message);
var signed = new Uint8Array(
bytes + message.length
);
signed.set(signature);
signed.set(message, bytes);
if (shouldClearMessage) {
sodiumUtil.memzero(message);
}
sodiumUtil.memzero(signature);
return signed;
}).catch(function (err) {
if (shouldClearMessage) {
sodiumUtil.memzero(message);
}
throw err;
});
}); },
signBase64: function (message, privateKey, additionalData) { return initiated.then(function () {
return superFalcon.sign(message, privateKey, additionalData).then(function (signed) {
var s = sodiumUtil.to_base64(signed);
sodiumUtil.memzero(signed);
return s;
});
}); },
signDetached: function (
message,
privateKey,
additionalData,
preHashed
) { return initiated.then(function () {
return hashWithAdditionalData(message, additionalData, preHashed).then(function (hash) {
return Promise.all([
hash,
sodium.crypto_sign_detached(
hash,
new Uint8Array(
privateKey.buffer,
privateKey.byteOffset,
sodium.crypto_sign_SECRETKEYBYTES
)
),
falcon.signDetached(
hash,
new Uint8Array(
privateKey.buffer,
privateKey.byteOffset + sodium.crypto_sign_SECRETKEYBYTES,
falconBytes.privateKeyBytes
)
)
]);
}).then(function (results) {
var hash = results[0];
var eccSignature = results[1];
var falconSignature = results[2];
var signature = new Uint8Array(bytes);
signature.set(eccSignature);
signature.set(falconSignature, sodium.crypto_sign_BYTES);
sodiumUtil.memzero(hash);
sodiumUtil.memzero(falconSignature);
sodiumUtil.memzero(eccSignature);
return signature;
});
}); },
signDetachedBase64: function (
message,
privateKey,
additionalData,
preHashed
) { return initiated.then(function () {
return superFalcon.signDetached(
message,
privateKey,
additionalData,
preHashed
).then(function (signature) {
var s = sodiumUtil.to_base64(signature);
sodiumUtil.memzero(signature);
return s;
});
});
},
open: function (
signed,
publicKey,
additionalData,
knownGoodHash,
includeHash
) { return initiated.then(function () {
var shouldClearSigned = typeof signed === 'string';
return Promise.resolve().then(function () {
signed = sodiumUtil.from_base64(signed);
var signature = new Uint8Array(
signed.buffer,
signed.byteOffset,
bytes
);
var message = new Uint8Array(
signed.buffer,
signed.byteOffset + bytes,
signed.length - bytes
);
return Promise.all([message, superFalcon.verifyDetached(
signature,
message,
publicKey,
additionalData,
knownGoodHash,
includeHash
)]);
}).then(function (results) {
var message = new Uint8Array(results[0]);
var hash = includeHash ? results[1].hash : undefined;
var isValid = includeHash ? results[1].valid : results[1];
if (shouldClearSigned) {
sodiumUtil.memzero(signed);
}
if (isValid) {
return includeHash ? {hash: hash, message: message} : message;
}
else {
throw new Error('Failed to open SuperFalcon signed message.');
}
}).catch(function (err) {
if (shouldClearSigned) {
sodiumUtil.memzero(signed);
}
throw err;
});
}); },
openString: function (
signed,
publicKey,
additionalData,
knownGoodHash,
includeHash
) { return initiated.then(function () {
return superFalcon.open(
signed,
publicKey,
additionalData,
knownGoodHash,
includeHash
).then(function (message) {
var hash = undefined;
if (includeHash) {
hash = sodiumUtil.to_hex(message.hash);
sodiumUtil.memzero(message.hash);
message = message.message;
}
var s = sodiumUtil.to_string(message);
sodiumUtil.memzero(message);
return includeHash ? {hash: hash, message: s} : s;
});
}); },
verifyDetached: function (
signature,
message,
publicKey,
additionalData,
knownGoodHash,
includeHash
) { return initiated.then(function () {
var shouldClearSignature = typeof signature === 'string';
return hashWithAdditionalData(message, additionalData).then(function (hash) {
signature = sodiumUtil.from_base64(signature);
var shouldClearKnownGoodHash = false;
if (typeof knownGoodHash === 'string' && knownGoodHash.length > 0) {
knownGoodHash = sodiumUtil.from_hex(knownGoodHash);
shouldClearKnownGoodHash = true;
}
var hashAlreadyVerified =
knownGoodHash instanceof Uint8Array &&
knownGoodHash.length > 0 &&
sodiumUtil.memcmp(hash, knownGoodHash)
;
if (shouldClearKnownGoodHash) {
sodiumUtil.memzero(knownGoodHash);
}
var publicKeyPromise =
hashAlreadyVerified ?
undefined :
publicKey instanceof Uint8Array ?
Promise.resolve(publicKey) :
superFalcon.importKeys(publicKey).then(function (kp) {
return kp.publicKey;
})
;
return Promise.all([
hash,
hashAlreadyVerified || publicKeyPromise.then(function (pk) {
return sodium.crypto_sign_verify_detached(
new Uint8Array(signature.buffer, signature.byteOffset, sodium.crypto_sign_BYTES),
hash,
new Uint8Array(pk.buffer, pk.byteOffset, sodium.crypto_sign_PUBLICKEYBYTES)
);
}),
hashAlreadyVerified || publicKeyPromise.then(function (pk) {
return falcon.verifyDetached(
new Uint8Array(
signature.buffer,
signature.byteOffset + sodium.crypto_sign_BYTES,
falconBytes.bytes
),
hash,
new Uint8Array(
pk.buffer,
pk.byteOffset + sodium.crypto_sign_PUBLICKEYBYTES,
falconBytes.publicKeyBytes
)
);
})
]);
}).then(function (results) {
var hash = results[0];
var eccIsValid = results[1];
var falconIsValid = results[2];
var valid = eccIsValid && falconIsValid;
if (shouldClearSignature) {
sodiumUtil.memzero(signature);
}
if (includeHash) {
return {hash: hash, valid: valid};
}
sodiumUtil.memzero(hash);
return valid;
}).catch(function (err) {
if (shouldClearSignature) {
sodiumUtil.memzero(signature);
}
throw err;
});;
}); },
exportKeys: function (keyPair, password) {
return initiated.then(function () {
if (!keyPair.privateKey) {
return null;
}
var eccPrivateKey = new Uint8Array(
sodium.crypto_sign_PUBLICKEYBYTES +
sodium.crypto_sign_SECRETKEYBYTES
);
var falconPrivateKey = new Uint8Array(
falconBytes.publicKeyBytes +
falconBytes.privateKeyBytes
);
var superFalconPrivateKey = new Uint8Array(
publicKeyBytes +
privateKeyBytes
);
eccPrivateKey.set(new Uint8Array(
keyPair.publicKey.buffer,
keyPair.publicKey.byteOffset,
sodium.crypto_sign_PUBLICKEYBYTES
));
eccPrivateKey.set(
new Uint8Array(
keyPair.privateKey.buffer,
keyPair.privateKey.byteOffset,
sodium.crypto_sign_SECRETKEYBYTES
),
sodium.crypto_sign_PUBLICKEYBYTES
);
falconPrivateKey.set(new Uint8Array(
keyPair.publicKey.buffer,
keyPair.publicKey.byteOffset + sodium.crypto_sign_PUBLICKEYBYTES,
falconBytes.publicKeyBytes
));
falconPrivateKey.set(
new Uint8Array(
keyPair.privateKey.buffer,
keyPair.privateKey.byteOffset + sodium.crypto_sign_SECRETKEYBYTES,
falconBytes.privateKeyBytes
),
falconBytes.publicKeyBytes
);
superFalconPrivateKey.set(keyPair.publicKey);
superFalconPrivateKey.set(keyPair.privateKey, publicKeyBytes);
if (password != null && password.length > 0) {
return Promise.all([
encrypt(eccPrivateKey, password),
encrypt(falconPrivateKey, password),
encrypt(superFalconPrivateKey, password)
]).then(function (results) {
sodiumUtil.memzero(superFalconPrivateKey);
sodiumUtil.memzero(falconPrivateKey);
sodiumUtil.memzero(eccPrivateKey);
return results;
});
}
else {
return [
eccPrivateKey,
falconPrivateKey,
superFalconPrivateKey
];
}
}).then(function (results) {
if (!results) {
return {
classical: null,
combined: null,
postQuantum: null
};
}
var eccPrivateKey = results[0];
var falconPrivateKey = results[1];
var superFalconPrivateKey = results[2];
var privateKeyData = {
classical: sodiumUtil.to_base64(eccPrivateKey),
combined: sodiumUtil.to_base64(superFalconPrivateKey),
postQuantum: sodiumUtil.to_base64(falconPrivateKey)
};
sodiumUtil.memzero(superFalconPrivateKey);
sodiumUtil.memzero(falconPrivateKey);
sodiumUtil.memzero(eccPrivateKey);
return privateKeyData;
}).then(function (privateKeyData) {
return {
private: privateKeyData,
public: {
classical: sodiumUtil.to_base64(new Uint8Array(
keyPair.publicKey.buffer,
keyPair.publicKey.byteOffset,
sodium.crypto_sign_PUBLICKEYBYTES
)),
combined: sodiumUtil.to_base64(keyPair.publicKey),
postQuantum: sodiumUtil.to_base64(new Uint8Array(
keyPair.publicKey.buffer,
keyPair.publicKey.byteOffset + sodium.crypto_sign_PUBLICKEYBYTES,
falconBytes.publicKeyBytes
))
}
};
});
},
importKeys: function (keyData, password) {
return initiated.then(function () {
if (keyData.private && typeof keyData.private.combined === 'string') {
var superFalconPrivateKey = sodiumUtil.from_base64(keyData.private.combined);
if (password != null && password.length > 0) {
return Promise.all([decrypt(superFalconPrivateKey, password)]);
}
else {
return [superFalconPrivateKey];
}
}
else if (
keyData.private &&
typeof keyData.private.classical === 'string' &&
typeof keyData.private.postQuantum === 'string'
) {
var eccPrivateKey = sodiumUtil.from_base64(keyData.private.classical);
var falconPrivateKey = sodiumUtil.from_base64(keyData.private.postQuantum);
if (password == null || password.length < 1) {
return [eccPrivateKey, falconPrivateKey];
}
return Promise.all([
decrypt(
eccPrivateKey,
typeof password === 'string' ? password : password.classical
),
decrypt(
falconPrivateKey,
typeof password === 'string' ? password : password.postQuantum
)
]);
}
return null;
}).then(function (results) {
var keyPair = {
publicKey: new Uint8Array(publicKeyBytes),
privateKey: null
};
if (!results) {
return keyPair;
}
keyPair.privateKey = new Uint8Array(privateKeyBytes);
if (results.length === 1) {
var superFalconPrivateKey = results[0];
keyPair.publicKey.set(new Uint8Array(
superFalconPrivateKey.buffer,
superFalconPrivateKey.byteOffset,
publicKeyBytes
));
keyPair.privateKey.set(new Uint8Array(
superFalconPrivateKey.buffer,
superFalconPrivateKey.byteOffset + publicKeyBytes,
privateKeyBytes
));
}
else {
var eccPrivateKey = results[0];
var falconPrivateKey = results[1];
keyPair.publicKey.set(
new Uint8Array(
eccPrivateKey.buffer,
eccPrivateKey.byteOffset,
sodium.crypto_sign_PUBLICKEYBYTES
)
);
keyPair.publicKey.set(
new Uint8Array(
falconPrivateKey.buffer,
falconPrivateKey.byteOffset,
falconBytes.publicKeyBytes
),
sodium.crypto_sign_PUBLICKEYBYTES
);
keyPair.privateKey.set(
new Uint8Array(
eccPrivateKey.buffer,
eccPrivateKey.byteOffset + sodium.crypto_sign_PUBLICKEYBYTES,
sodium.crypto_sign_SECRETKEYBYTES
)
);
keyPair.privateKey.set(
new Uint8Array(
falconPrivateKey.buffer,
falconPrivateKey.byteOffset + falconBytes.publicKeyBytes,
falconBytes.privateKeyBytes
),
sodium.crypto_sign_SECRETKEYBYTES
);
}
return keyPair;
}).then(function (keyPair) {
if (!keyPair.privateKey) {
if (keyData.public.combined) {
keyPair.publicKey.set(sodiumUtil.from_base64(keyData.public.combined));
}
else if (keyData.public.classical && keyData.public.postQuantum) {
keyPair.publicKey.set(sodiumUtil.from_base64(keyData.public.classical));
keyPair.publicKey.set(
sodiumUtil.from_base64(keyData.public.postQuantum),
sodium.crypto_sign_PUBLICKEYBYTES
);
}
}
return keyPair;
});
}
};
superFalcon.superFalcon = superFalcon;
module.exports = superFalcon;