mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-03-30 15:08:32 +00:00
Add symmetric encryption and MAC support
To enable stored messages to be protected using symmetric key encryption and validated using message authentication codes, this set of changes adds support for storing symmetric key material as Secret Key Packets, symmetric key encrypted session keys as Public Key Encrypted Session Key Packets, and MAC tags as Signature Packets. Co-authored-by: Konstantinos Andrikopoulos <kandrikopoulos@proton.ch> Co-authored-by: Daniel Huigens <d.huigens@protonmail.com>
This commit is contained in:
parent
dd01ee00cb
commit
c7ae1d88d8
@ -28,7 +28,11 @@ import mode from './mode';
|
||||
import { getRandomBytes } from './random';
|
||||
import { getCipherParams } from './cipher';
|
||||
import ECDHSymkey from '../type/ecdh_symkey';
|
||||
import ShortByteString from '../type/short_byte_string';
|
||||
import hash from './hash';
|
||||
import config from '../config';
|
||||
import KDFParams from '../type/kdf_params';
|
||||
import { SymAlgoEnum, AEADEnum, HashEnum } from '../type/enum';
|
||||
import enums from '../enums';
|
||||
import util from '../util';
|
||||
import OID from '../type/oid';
|
||||
@ -41,12 +45,13 @@ import ECDHXSymmetricKey from '../type/ecdh_x_symkey';
|
||||
* @param {module:enums.publicKey} keyAlgo - Public key algorithm
|
||||
* @param {module:enums.symmetric|null} symmetricAlgo - Cipher algorithm (v3 only)
|
||||
* @param {Object} publicParams - Algorithm-specific public key parameters
|
||||
* @param {Uint8Array} data - Session key data to be encrypted
|
||||
* @param {Object} privateParams - Algorithm-specific private key parameters
|
||||
* @param {Uint8Array} data - Data to be encrypted
|
||||
* @param {Uint8Array} fingerprint - Recipient fingerprint
|
||||
* @returns {Promise<Object>} Encrypted session key parameters.
|
||||
* @async
|
||||
*/
|
||||
export async function publicKeyEncrypt(keyAlgo, symmetricAlgo, publicParams, data, fingerprint) {
|
||||
export async function publicKeyEncrypt(keyAlgo, symmetricAlgo, publicParams, privateParams, data, fingerprint) {
|
||||
switch (keyAlgo) {
|
||||
case enums.publicKey.rsaEncrypt:
|
||||
case enums.publicKey.rsaEncryptSign: {
|
||||
@ -76,6 +81,21 @@ export async function publicKeyEncrypt(keyAlgo, symmetricAlgo, publicParams, dat
|
||||
const C = ECDHXSymmetricKey.fromObject({ algorithm: symmetricAlgo, wrappedKey });
|
||||
return { ephemeralPublicKey, C };
|
||||
}
|
||||
case enums.publicKey.aead: {
|
||||
if (!privateParams) {
|
||||
throw new Error('Cannot encrypt with symmetric key missing private parameters');
|
||||
}
|
||||
const { cipher: algo } = publicParams;
|
||||
const algoValue = algo.getValue();
|
||||
const { keyMaterial } = privateParams;
|
||||
const aeadMode = config.preferredAEADAlgorithm;
|
||||
const mode = getAEADMode(config.preferredAEADAlgorithm);
|
||||
const { ivLength } = mode;
|
||||
const iv = getRandomBytes(ivLength);
|
||||
const modeInstance = await mode(algoValue, keyMaterial);
|
||||
const c = await modeInstance.encrypt(data, iv, new Uint8Array());
|
||||
return { aeadMode: new AEADEnum(aeadMode), iv, c: new ShortByteString(c) };
|
||||
}
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
@ -128,6 +148,17 @@ export async function publicKeyDecrypt(algo, publicKeyParams, privateKeyParams,
|
||||
return publicKey.elliptic.ecdhX.decrypt(
|
||||
algo, ephemeralPublicKey, C.wrappedKey, A, k);
|
||||
}
|
||||
case enums.publicKey.aead: {
|
||||
const { cipher: algo } = publicKeyParams;
|
||||
const algoValue = algo.getValue();
|
||||
const { keyMaterial } = privateKeyParams;
|
||||
|
||||
const { aeadMode, iv, c } = sessionKeyParams;
|
||||
|
||||
const mode = getAEADMode(aeadMode.getValue());
|
||||
const modeInstance = await mode(algoValue, keyMaterial);
|
||||
return modeInstance.decrypt(c.data, iv, new Uint8Array());
|
||||
}
|
||||
default:
|
||||
throw new Error('Unknown public key encryption algorithm.');
|
||||
}
|
||||
@ -192,6 +223,13 @@ export function parsePublicKeyParams(algo, bytes) {
|
||||
const A = util.readExactSubarray(bytes, read, read + getCurvePayloadSize(algo)); read += A.length;
|
||||
return { read, publicParams: { A } };
|
||||
}
|
||||
case enums.publicKey.hmac:
|
||||
case enums.publicKey.aead: {
|
||||
const algo = new SymAlgoEnum(); read += algo.read(bytes);
|
||||
const digestLength = hash.getHashByteLength(enums.hash.sha256);
|
||||
const digest = bytes.subarray(read, read + digestLength); read += digestLength;
|
||||
return { read: read, publicParams: { cipher: algo, digest } };
|
||||
}
|
||||
default:
|
||||
throw new UnsupportedError('Unknown public key encryption algorithm.');
|
||||
}
|
||||
@ -201,7 +239,7 @@ export function parsePublicKeyParams(algo, bytes) {
|
||||
* Parse private key material in binary form to get the key parameters
|
||||
* @param {module:enums.publicKey} algo - The key algorithm
|
||||
* @param {Uint8Array} bytes - The key material to parse
|
||||
* @param {Object} publicParams - (ECC only) public params, needed to format some private params
|
||||
* @param {Object} publicParams - (ECC and symmetric only) public params, needed to format some private params
|
||||
* @returns {{ read: Number, privateParams: Object }} Number of read bytes plus the key parameters referenced by name.
|
||||
*/
|
||||
export function parsePrivateKeyParams(algo, bytes, publicParams) {
|
||||
@ -249,6 +287,20 @@ export function parsePrivateKeyParams(algo, bytes, publicParams) {
|
||||
const k = util.readExactSubarray(bytes, read, read + payloadSize); read += k.length;
|
||||
return { read, privateParams: { k } };
|
||||
}
|
||||
case enums.publicKey.hmac: {
|
||||
const { cipher: algo } = publicParams;
|
||||
const keySize = hash.getHashByteLength(algo.getValue());
|
||||
const hashSeed = bytes.subarray(read, read + 32); read += 32;
|
||||
const keyMaterial = bytes.subarray(read, read + keySize); read += keySize;
|
||||
return { read, privateParams: { hashSeed, keyMaterial } };
|
||||
}
|
||||
case enums.publicKey.aead: {
|
||||
const { cipher: algo } = publicParams;
|
||||
const hashSeed = bytes.subarray(read, read + 32); read += 32;
|
||||
const { keySize } = getCipherParams(algo.getValue());
|
||||
const keyMaterial = bytes.subarray(read, read + keySize); read += keySize;
|
||||
return { read, privateParams: { hashSeed, keyMaterial } };
|
||||
}
|
||||
default:
|
||||
throw new UnsupportedError('Unknown public key encryption algorithm.');
|
||||
}
|
||||
@ -298,6 +350,20 @@ export function parseEncSessionKeyParams(algo, bytes) {
|
||||
const C = new ECDHXSymmetricKey(); C.read(bytes.subarray(read));
|
||||
return { ephemeralPublicKey, C };
|
||||
}
|
||||
// Algorithm-Specific Fields for symmetric AEAD encryption:
|
||||
// - AEAD algorithm
|
||||
// - Starting initialization vector
|
||||
// - Symmetric key encryption of "m" dependent on cipher and AEAD mode prefixed with a one-octet length
|
||||
// - An authentication tag generated by the AEAD mode.
|
||||
case enums.publicKey.aead: {
|
||||
const aeadMode = new AEADEnum(); read += aeadMode.read(bytes.subarray(read));
|
||||
const { ivLength } = getAEADMode(aeadMode.getValue());
|
||||
|
||||
const iv = bytes.subarray(read, read + ivLength); read += ivLength;
|
||||
const c = new ShortByteString(); read += c.read(bytes.subarray(read));
|
||||
|
||||
return { aeadMode, iv, c };
|
||||
}
|
||||
default:
|
||||
throw new UnsupportedError('Unknown public key encryption algorithm.');
|
||||
}
|
||||
@ -315,7 +381,9 @@ export function serializeParams(algo, params) {
|
||||
enums.publicKey.ed25519,
|
||||
enums.publicKey.x25519,
|
||||
enums.publicKey.ed448,
|
||||
enums.publicKey.x448
|
||||
enums.publicKey.x448,
|
||||
enums.publicKey.aead,
|
||||
enums.publicKey.hmac
|
||||
]);
|
||||
const orderedParams = Object.keys(params).map(name => {
|
||||
const param = params[name];
|
||||
@ -330,10 +398,11 @@ export function serializeParams(algo, params) {
|
||||
* @param {module:enums.publicKey} algo - The public key algorithm
|
||||
* @param {Integer} bits - Bit length for RSA keys
|
||||
* @param {module:type/oid} oid - Object identifier for ECC keys
|
||||
* @param {module:enums.symmetric|enums.hash} symmetric - Hash or cipher algorithm for symmetric keys
|
||||
* @returns {Promise<{ publicParams: {Object}, privateParams: {Object} }>} The parameters referenced by name.
|
||||
* @async
|
||||
*/
|
||||
export function generateParams(algo, bits, oid) {
|
||||
export async function generateParams(algo, bits, oid, symmetric) {
|
||||
switch (algo) {
|
||||
case enums.publicKey.rsaEncrypt:
|
||||
case enums.publicKey.rsaEncryptSign:
|
||||
@ -373,6 +442,14 @@ export function generateParams(algo, bits, oid) {
|
||||
privateParams: { k },
|
||||
publicParams: { A }
|
||||
}));
|
||||
case enums.publicKey.hmac: {
|
||||
const keyMaterial = await publicKey.hmac.generate(symmetric);
|
||||
return createSymmetricParams(keyMaterial, new HashEnum(symmetric));
|
||||
}
|
||||
case enums.publicKey.aead: {
|
||||
const keyMaterial = generateSessionKey(symmetric);
|
||||
return createSymmetricParams(keyMaterial, new SymAlgoEnum(symmetric));
|
||||
}
|
||||
case enums.publicKey.dsa:
|
||||
case enums.publicKey.elgamal:
|
||||
throw new Error('Unsupported algorithm for key generation.');
|
||||
@ -381,6 +458,21 @@ export function generateParams(algo, bits, oid) {
|
||||
}
|
||||
}
|
||||
|
||||
async function createSymmetricParams(key, algo) {
|
||||
const seed = getRandomBytes(32);
|
||||
const bindingHash = await hash.sha256(seed);
|
||||
return {
|
||||
privateParams: {
|
||||
hashSeed: seed,
|
||||
keyMaterial: key
|
||||
},
|
||||
publicParams: {
|
||||
cipher: algo,
|
||||
digest: bindingHash
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate algorithm-specific key parameters
|
||||
* @param {module:enums.publicKey} algo - The public key algorithm
|
||||
@ -435,6 +527,20 @@ export async function validateParams(algo, publicParams, privateParams) {
|
||||
const { k } = privateParams;
|
||||
return publicKey.elliptic.ecdhX.validateParams(algo, A, k);
|
||||
}
|
||||
case enums.publicKey.hmac: {
|
||||
const { cipher: algo, digest } = publicParams;
|
||||
const { hashSeed, keyMaterial } = privateParams;
|
||||
const keySize = hash.getHashByteLength(algo.getValue());
|
||||
return keySize === keyMaterial.length &&
|
||||
util.equalsUint8Array(digest, await hash.sha256(hashSeed));
|
||||
}
|
||||
case enums.publicKey.aead: {
|
||||
const { cipher: algo, digest } = publicParams;
|
||||
const { hashSeed, keyMaterial } = privateParams;
|
||||
const { keySize } = getCipherParams(algo.getValue());
|
||||
return keySize === keyMaterial.length &&
|
||||
util.equalsUint8Array(digest, await hash.sha256(hashSeed));
|
||||
}
|
||||
default:
|
||||
throw new Error('Unknown public key algorithm.');
|
||||
}
|
||||
|
@ -126,5 +126,26 @@ export default {
|
||||
default:
|
||||
throw new Error('Invalid hash algorithm.');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the internal block size in bytes of the specified hash algorithm type
|
||||
* @param {module:enums.hash} algo - Hash algorithm type (See {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC 4880 9.4})
|
||||
* @returns {Integer} Underlying block size in bytes.
|
||||
*/
|
||||
getBlockSize: function(algo) {
|
||||
switch (algo) {
|
||||
case enums.hash.md5:
|
||||
case enums.hash.sha1:
|
||||
case enums.hash.ripemd:
|
||||
case enums.hash.sha224:
|
||||
case enums.hash.sha256:
|
||||
return 64;
|
||||
case enums.hash.sha384:
|
||||
case enums.hash.sha512:
|
||||
return 128;
|
||||
default:
|
||||
throw new Error('Invalid hash algorithm.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
67
src/crypto/public_key/hmac.js
Normal file
67
src/crypto/public_key/hmac.js
Normal file
@ -0,0 +1,67 @@
|
||||
import enums from '../../enums';
|
||||
import util from '../../util';
|
||||
|
||||
const supportedHashAlgos = new Set([enums.hash.sha1, enums.hash.sha256, enums.hash.sha512]);
|
||||
|
||||
const webCrypto = util.getWebCrypto();
|
||||
const nodeCrypto = util.getNodeCrypto();
|
||||
|
||||
export async function generate(hashAlgo) {
|
||||
if (!supportedHashAlgos.has(hashAlgo)) {
|
||||
throw new Error('Unsupported hash algorithm.');
|
||||
}
|
||||
const hashName = enums.read(enums.webHash, hashAlgo);
|
||||
|
||||
const crypto = webCrypto || nodeCrypto.webcrypto.subtle;
|
||||
const key = await crypto.generateKey(
|
||||
{
|
||||
name: 'HMAC',
|
||||
hash: { name: hashName }
|
||||
},
|
||||
true,
|
||||
['sign', 'verify']
|
||||
);
|
||||
const exportedKey = await crypto.exportKey('raw', key);
|
||||
return new Uint8Array(exportedKey);
|
||||
}
|
||||
|
||||
export async function sign(hashAlgo, key, data) {
|
||||
if (!supportedHashAlgos.has(hashAlgo)) {
|
||||
throw new Error('Unsupported hash algorithm.');
|
||||
}
|
||||
const hashName = enums.read(enums.webHash, hashAlgo);
|
||||
|
||||
const crypto = webCrypto || nodeCrypto.webcrypto.subtle;
|
||||
const importedKey = await crypto.importKey(
|
||||
'raw',
|
||||
key,
|
||||
{
|
||||
name: 'HMAC',
|
||||
hash: { name: hashName }
|
||||
},
|
||||
false,
|
||||
['sign']
|
||||
);
|
||||
const mac = await crypto.sign('HMAC', importedKey, data);
|
||||
return new Uint8Array(mac);
|
||||
}
|
||||
|
||||
export async function verify(hashAlgo, key, mac, data) {
|
||||
if (!supportedHashAlgos.has(hashAlgo)) {
|
||||
throw new Error('Unsupported hash algorithm.');
|
||||
}
|
||||
const hashName = enums.read(enums.webHash, hashAlgo);
|
||||
|
||||
const crypto = webCrypto || nodeCrypto.webcrypto.subtle;
|
||||
const importedKey = await crypto.importKey(
|
||||
'raw',
|
||||
key,
|
||||
{
|
||||
name: 'HMAC',
|
||||
hash: { name: hashName }
|
||||
},
|
||||
false,
|
||||
['verify']
|
||||
);
|
||||
return crypto.verify('HMAC', importedKey, mac, data);
|
||||
}
|
@ -7,6 +7,7 @@ import * as rsa from './rsa';
|
||||
import * as elgamal from './elgamal';
|
||||
import * as elliptic from './elliptic';
|
||||
import * as dsa from './dsa';
|
||||
import * as hmac from './hmac';
|
||||
|
||||
export default {
|
||||
/** @see module:crypto/public_key/rsa */
|
||||
@ -16,5 +17,7 @@ export default {
|
||||
/** @see module:crypto/public_key/elliptic */
|
||||
elliptic: elliptic,
|
||||
/** @see module:crypto/public_key/dsa */
|
||||
dsa: dsa
|
||||
dsa: dsa,
|
||||
/** @see module:crypto/public_key/hmac */
|
||||
hmac: hmac
|
||||
};
|
||||
|
@ -6,6 +6,7 @@
|
||||
import publicKey from './public_key';
|
||||
import enums from '../enums';
|
||||
import util from '../util';
|
||||
import ShortByteString from '../type/short_byte_string';
|
||||
import { UnsupportedError } from '../packet/packet';
|
||||
|
||||
/**
|
||||
@ -65,7 +66,10 @@ export function parseSignatureParams(algo, signature) {
|
||||
const RS = util.readExactSubarray(signature, read, read + rsSize); read += RS.length;
|
||||
return { read, signatureParams: { RS } };
|
||||
}
|
||||
|
||||
case enums.publicKey.hmac: {
|
||||
const mac = new ShortByteString(); read += mac.read(signature.subarray(read));
|
||||
return { read, signatureParams: { mac } };
|
||||
}
|
||||
default:
|
||||
throw new UnsupportedError('Unknown signature algorithm.');
|
||||
}
|
||||
@ -80,12 +84,13 @@ export function parseSignatureParams(algo, signature) {
|
||||
* @param {module:enums.hash} hashAlgo - Hash algorithm
|
||||
* @param {Object} signature - Named algorithm-specific signature parameters
|
||||
* @param {Object} publicParams - Algorithm-specific public key parameters
|
||||
* @param {Object} privateParams - Algorithm-specific private key parameters
|
||||
* @param {Uint8Array} data - Data for which the signature was created
|
||||
* @param {Uint8Array} hashed - The hashed data
|
||||
* @returns {Promise<Boolean>} True if signature is valid.
|
||||
* @async
|
||||
*/
|
||||
export async function verify(algo, hashAlgo, signature, publicParams, data, hashed) {
|
||||
export async function verify(algo, hashAlgo, signature, publicParams, privateParams, data, hashed) {
|
||||
switch (algo) {
|
||||
case enums.publicKey.rsaEncryptSign:
|
||||
case enums.publicKey.rsaEncrypt:
|
||||
@ -121,6 +126,14 @@ export async function verify(algo, hashAlgo, signature, publicParams, data, hash
|
||||
const { A } = publicParams;
|
||||
return publicKey.elliptic.eddsa.verify(algo, hashAlgo, signature, data, A, hashed);
|
||||
}
|
||||
case enums.publicKey.hmac: {
|
||||
if (!privateParams) {
|
||||
throw new Error('Cannot verify HMAC signature with symmetric key missing private parameters');
|
||||
}
|
||||
const { cipher: algo } = publicParams;
|
||||
const { keyMaterial } = privateParams;
|
||||
return publicKey.hmac.verify(algo.getValue(), keyMaterial, signature.mac.data, hashed);
|
||||
}
|
||||
default:
|
||||
throw new Error('Unknown signature algorithm.');
|
||||
}
|
||||
@ -176,6 +189,12 @@ export async function sign(algo, hashAlgo, publicKeyParams, privateKeyParams, da
|
||||
const { seed } = privateKeyParams;
|
||||
return publicKey.elliptic.eddsa.sign(algo, hashAlgo, data, A, seed, hashed);
|
||||
}
|
||||
case enums.publicKey.hmac: {
|
||||
const { cipher: algo } = publicKeyParams;
|
||||
const { keyMaterial } = privateKeyParams;
|
||||
const mac = await publicKey.hmac.sign(algo.getValue(), keyMaterial, hashed);
|
||||
return { mac: new ShortByteString(mac) };
|
||||
}
|
||||
default:
|
||||
throw new Error('Unknown signature algorithm.');
|
||||
}
|
||||
|
@ -95,7 +95,11 @@ export default {
|
||||
/** Ed25519 (Sign only) */
|
||||
ed25519: 27,
|
||||
/** Ed448 (Sign only) */
|
||||
ed448: 28
|
||||
ed448: 28,
|
||||
/** Persistent symmetric keys: encryption algorithm */
|
||||
aead: 100,
|
||||
/** Persistent symmetric keys: authentication algorithm */
|
||||
hmac: 101
|
||||
},
|
||||
|
||||
/** {@link https://tools.ietf.org/html/rfc4880#section-9.2|RFC4880, section 9.2}
|
||||
|
@ -75,6 +75,8 @@ function createKey(packetlist) {
|
||||
* @param {Date} options.date Creation date of the key and the key signatures
|
||||
* @param {Object} config - Full configuration
|
||||
* @param {Array<Object>} options.subkeys (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}]
|
||||
* @param {String} options.symmetricHash (optional) hash algorithm for symmetric keys
|
||||
* @param {String} options.symmetricCipher (optional) cipher algorithm for symmetric keys
|
||||
* sign parameter defaults to false, and indicates whether the subkey should sign rather than encrypt
|
||||
* @returns {Promise<{{ key: PrivateKey, revocationCertificate: String }}>}
|
||||
* @async
|
||||
|
@ -17,7 +17,7 @@ export async function generateSecretSubkey(options, config) {
|
||||
const secretSubkeyPacket = new SecretSubkeyPacket(options.date, config);
|
||||
secretSubkeyPacket.packets = null;
|
||||
secretSubkeyPacket.algorithm = enums.write(enums.publicKey, options.algorithm);
|
||||
await secretSubkeyPacket.generate(options.rsaBits, options.curve);
|
||||
await secretSubkeyPacket.generate(options.rsaBits, options.curve, options.symmetric);
|
||||
await secretSubkeyPacket.computeFingerprintAndKeyID();
|
||||
return secretSubkeyPacket;
|
||||
}
|
||||
@ -26,7 +26,7 @@ export async function generateSecretKey(options, config) {
|
||||
const secretKeyPacket = new SecretKeyPacket(options.date, config);
|
||||
secretKeyPacket.packets = null;
|
||||
secretKeyPacket.algorithm = enums.write(enums.publicKey, options.algorithm);
|
||||
await secretKeyPacket.generate(options.rsaBits, options.curve, options.config);
|
||||
await secretKeyPacket.generate(options.rsaBits, options.curve, options.symmetric);
|
||||
await secretKeyPacket.computeFingerprintAndKeyID();
|
||||
return secretKeyPacket;
|
||||
}
|
||||
@ -388,6 +388,8 @@ export function sanitizeKeyOptions(options, subkeyDefaults = {}) {
|
||||
options.type = options.type || subkeyDefaults.type;
|
||||
options.curve = options.curve || subkeyDefaults.curve;
|
||||
options.rsaBits = options.rsaBits || subkeyDefaults.rsaBits;
|
||||
options.symmetricHash = options.symmetricHash || subkeyDefaults.symmetricHash;
|
||||
options.symmetricCipher = options.symmetricCipher || subkeyDefaults.symmetricCipher;
|
||||
options.keyExpirationTime = options.keyExpirationTime !== undefined ? options.keyExpirationTime : subkeyDefaults.keyExpirationTime;
|
||||
options.passphrase = util.isString(options.passphrase) ? options.passphrase : subkeyDefaults.passphrase;
|
||||
options.date = options.date || subkeyDefaults.date;
|
||||
@ -420,6 +422,23 @@ export function sanitizeKeyOptions(options, subkeyDefaults = {}) {
|
||||
case 'rsa':
|
||||
options.algorithm = enums.publicKey.rsaEncryptSign;
|
||||
break;
|
||||
case 'symmetric':
|
||||
if (options.sign) {
|
||||
options.algorithm = enums.publicKey.hmac;
|
||||
try {
|
||||
options.symmetric = enums.write(enums.hash, options.symmetricHash);
|
||||
} catch (e) {
|
||||
throw new Error('Unknown hash algorithm');
|
||||
}
|
||||
} else {
|
||||
options.algorithm = enums.publicKey.aead;
|
||||
try {
|
||||
options.symmetric = enums.write(enums.symmetric, options.symmetricCipher);
|
||||
} catch (e) {
|
||||
throw new Error('Unknown symmetric algorithm');
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported key type ${options.type}`);
|
||||
}
|
||||
@ -435,6 +454,7 @@ export function validateSigningKeyPacket(keyPacket, signature, config) {
|
||||
case enums.publicKey.eddsaLegacy:
|
||||
case enums.publicKey.ed25519:
|
||||
case enums.publicKey.ed448:
|
||||
case enums.publicKey.hmac:
|
||||
if (!signature.keyFlags && !config.allowMissingKeyFlags) {
|
||||
throw new Error('None of the key flags is set: consider passing `config.allowMissingKeyFlags`');
|
||||
}
|
||||
@ -453,6 +473,7 @@ export function validateEncryptionKeyPacket(keyPacket, signature, config) {
|
||||
case enums.publicKey.ecdh:
|
||||
case enums.publicKey.x25519:
|
||||
case enums.publicKey.x448:
|
||||
case enums.publicKey.aead:
|
||||
if (!signature.keyFlags && !config.allowMissingKeyFlags) {
|
||||
throw new Error('None of the key flags is set: consider passing `config.allowMissingKeyFlags`');
|
||||
}
|
||||
|
@ -39,14 +39,28 @@ class PrivateKey extends PublicKey {
|
||||
toPublic() {
|
||||
const packetlist = new PacketList();
|
||||
const keyPackets = this.toPacketList();
|
||||
let symmetricFound = false;
|
||||
for (const keyPacket of keyPackets) {
|
||||
if (symmetricFound &&
|
||||
keyPacket.constructor.tag === enums.packet.Signature) {
|
||||
continue;
|
||||
} else if (symmetricFound) {
|
||||
symmetricFound = false;
|
||||
}
|
||||
switch (keyPacket.constructor.tag) {
|
||||
case enums.packet.secretKey: {
|
||||
if (keyPacket.algorithm === enums.publicKey.aead || keyPacket.algorithm === enums.publicKey.hmac) {
|
||||
throw new Error('Cannot create public key from symmetric private');
|
||||
}
|
||||
const pubKeyPacket = PublicKeyPacket.fromSecretKeyPacket(keyPacket);
|
||||
packetlist.push(pubKeyPacket);
|
||||
break;
|
||||
}
|
||||
case enums.packet.secretSubkey: {
|
||||
if (keyPacket.algorithm === enums.publicKey.aead || keyPacket.algorithm === enums.publicKey.hmac) {
|
||||
symmetricFound = true;
|
||||
break;
|
||||
}
|
||||
const pubSubkeyPacket = PublicSubkeyPacket.fromSecretSubkeyPacket(keyPacket);
|
||||
packetlist.push(pubSubkeyPacket);
|
||||
break;
|
||||
@ -219,10 +233,12 @@ class PrivateKey extends PublicKey {
|
||||
* Generates a new OpenPGP subkey, and returns a clone of the Key object with the new subkey added.
|
||||
* Supports RSA and ECC keys, as well as the newer Curve448 and Curve25519.
|
||||
* Defaults to the algorithm and bit size/curve of the primary key. DSA primary keys default to RSA subkeys.
|
||||
* @param {ecc|rsa|curve25519|curve448} options.type The subkey algorithm: ECC, RSA, Curve448 or Curve25519 (new format).
|
||||
* @param {ecc|rsa|curve25519|curve448|symmetric} options.type The subkey algorithm: ECC, RSA, Curve448 or Curve25519 (new format).
|
||||
* Note: Curve448 and Curve25519 are not widely supported yet.
|
||||
* @param {String} options.curve (optional) Elliptic curve for ECC keys
|
||||
* @param {Integer} options.rsaBits (optional) Number of bits for RSA subkeys
|
||||
* @param {String} options.symmetricCipher (optional) Symmetric algorithm for persistent symmetric aead keys
|
||||
* @param {String} options.symmetricHash (optional)Hash lgorithm for persistent symmetric hmac keys
|
||||
* @param {Number} options.keyExpirationTime (optional) Number of seconds from the key creation time after which the key expires
|
||||
* @param {Date} options.date (optional) Override the creation date of the key and the key signatures
|
||||
* @param {Boolean} options.sign (optional) Indicates whether the subkey should sign rather than encrypt. Defaults to false
|
||||
|
@ -37,13 +37,15 @@ import { checkKeyRequirements } from './key/helper';
|
||||
* The generated primary key will have signing capabilities. By default, one subkey with encryption capabilities is also generated.
|
||||
* @param {Object} options
|
||||
* @param {Object|Array<Object>} options.userIDs - User IDs as objects: `{ name: 'Jo Doe', email: 'info@jo.com' }`
|
||||
* @param {'ecc'|'rsa'|'curve448'|'curve25519'} [options.type='ecc'] - The primary key algorithm type: ECC (default for v4 keys), RSA, Curve448 or Curve25519 (new format, default for v6 keys).
|
||||
* @param {'ecc'|'rsa'|'curve448'|'curve25519'|'symmetric'} [options.type='ecc'] - The primary key algorithm type: ECC (default for v4 keys), RSA, Curve448, Curve25519 (new format, default for v6 keys) or symmetric.
|
||||
* Note: Curve448 and Curve25519 (new format) are not widely supported yet.
|
||||
* @param {String} [options.passphrase=(not protected)] - The passphrase used to encrypt the generated private key. If omitted or empty, the key won't be encrypted.
|
||||
* @param {Number} [options.rsaBits=4096] - Number of bits for RSA keys
|
||||
* @param {String} [options.curve='curve25519Legacy'] - Elliptic curve for ECC keys:
|
||||
* curve25519Legacy (default), nistP256, nistP384, nistP521, secp256k1,
|
||||
* brainpoolP256r1, brainpoolP384r1, or brainpoolP512r1
|
||||
* @param {String} options.symmetricCipher (optional) Symmetric algorithm for persistent symmetric aead keys
|
||||
* @param {String} options.symmetricHash (optional)Hash lgorithm for persistent symmetric hmac keys
|
||||
* @param {Date} [options.date=current date] - Override the creation date of the key and the key signatures
|
||||
* @param {Number} [options.keyExpirationTime=0 (never expires)] - Number of seconds from the key creation time after which the key expires
|
||||
* @param {Array<Object>} [options.subkeys=a single encryption subkey] - Options for each subkey e.g. `[{sign: true, passphrase: '123'}]`
|
||||
@ -55,7 +57,7 @@ import { checkKeyRequirements } from './key/helper';
|
||||
* @async
|
||||
* @static
|
||||
*/
|
||||
export async function generateKey({ userIDs = [], passphrase, type, curve, rsaBits = 4096, keyExpirationTime = 0, date = new Date(), subkeys = [{}], format = 'armored', config, ...rest }) {
|
||||
export async function generateKey({ userIDs = [], passphrase, type, curve, rsaBits = 4096, symmetricHash = 'sha256', symmetricCipher = 'aes256', keyExpirationTime = 0, date = new Date(), subkeys = [{}], format = 'armored', config, ...rest }) {
|
||||
config = { ...defaultConfig, ...config }; checkConfig(config);
|
||||
if (!type && !curve) {
|
||||
type = config.v6Keys ? 'curve25519' : 'ecc'; // default to new curve25519 for v6 keys (legacy curve25519 cannot be used with them)
|
||||
@ -74,7 +76,7 @@ export async function generateKey({ userIDs = [], passphrase, type, curve, rsaBi
|
||||
throw new Error(`rsaBits should be at least ${config.minRSABits}, got: ${rsaBits}`);
|
||||
}
|
||||
|
||||
const options = { userIDs, passphrase, type, rsaBits, curve, keyExpirationTime, date, subkeys };
|
||||
const options = { userIDs, passphrase, type, rsaBits, curve, keyExpirationTime, date, subkeys, symmetricHash, symmetricCipher };
|
||||
|
||||
try {
|
||||
const { key, revocationCertificate } = await generate(options, config);
|
||||
@ -82,7 +84,7 @@ export async function generateKey({ userIDs = [], passphrase, type, curve, rsaBi
|
||||
|
||||
return {
|
||||
privateKey: formatObject(key, format, config),
|
||||
publicKey: formatObject(key.toPublic(), format, config),
|
||||
publicKey: type !== 'symmetric' ? formatObject(key.toPublic(), format, config) : null,
|
||||
revocationCertificate
|
||||
};
|
||||
} catch (err) {
|
||||
|
@ -264,7 +264,7 @@ class PublicKeyPacket {
|
||||
|
||||
/**
|
||||
* Returns algorithm information
|
||||
* @returns {Object} An object of the form {algorithm: String, bits:int, curve:String}.
|
||||
* @returns {Object} An object of the form {algorithm: String, bits:int, curve:String, symmetric:String}.
|
||||
*/
|
||||
getAlgorithmInfo() {
|
||||
const result = {};
|
||||
@ -275,6 +275,8 @@ class PublicKeyPacket {
|
||||
result.bits = util.uint8ArrayBitLength(modulo);
|
||||
} else if (this.publicParams.oid) {
|
||||
result.curve = this.publicParams.oid.getName();
|
||||
} else if (this.publicParams.cipher) {
|
||||
result.symmetric = this.publicParams.cipher.getName();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -182,8 +182,9 @@ class PublicKeyEncryptedSessionKeyPacket {
|
||||
const sessionKeyAlgorithm = this.version === 3 ? this.sessionKeyAlgorithm : null;
|
||||
const fingerprint = key.version === 5 ? key.getFingerprintBytes().subarray(0, 20) : key.getFingerprintBytes();
|
||||
const encoded = encodeSessionKey(this.version, algo, sessionKeyAlgorithm, this.sessionKey);
|
||||
const privateParams = algo === enums.publicKey.aead ? key.privateParams : null;
|
||||
this.encrypted = await crypto.publicKeyEncrypt(
|
||||
algo, sessionKeyAlgorithm, key.publicParams, encoded, fingerprint);
|
||||
algo, sessionKeyAlgorithm, key.publicParams, privateParams, encoded, fingerprint);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -230,6 +231,7 @@ function encodeSessionKey(version, keyAlgo, cipherAlgo, sessionKeyData) {
|
||||
case enums.publicKey.rsaEncryptSign:
|
||||
case enums.publicKey.elgamal:
|
||||
case enums.publicKey.ecdh:
|
||||
case enums.publicKey.aead:
|
||||
// add checksum
|
||||
return util.concatUint8Array([
|
||||
new Uint8Array(version === 6 ? [] : [cipherAlgo]),
|
||||
@ -250,7 +252,8 @@ function decodeSessionKey(version, keyAlgo, decryptedData, randomSessionKey) {
|
||||
case enums.publicKey.rsaEncrypt:
|
||||
case enums.publicKey.rsaEncryptSign:
|
||||
case enums.publicKey.elgamal:
|
||||
case enums.publicKey.ecdh: {
|
||||
case enums.publicKey.ecdh:
|
||||
case enums.publicKey.aead: {
|
||||
// verify checksum in constant time
|
||||
const result = decryptedData.subarray(0, decryptedData.length - 2);
|
||||
const checksum = decryptedData.subarray(decryptedData.length - 2);
|
||||
|
@ -523,7 +523,7 @@ class SecretKeyPacket extends PublicKeyPacket {
|
||||
}
|
||||
}
|
||||
|
||||
async generate(bits, curve) {
|
||||
async generate(bits, curve, symmetric) {
|
||||
// The deprecated OIDs for Ed25519Legacy and Curve25519Legacy are used in legacy version 4 keys and signatures.
|
||||
// Implementations MUST NOT accept or generate v6 key material using the deprecated OIDs.
|
||||
if (this.version === 6 && (
|
||||
@ -532,7 +532,7 @@ class SecretKeyPacket extends PublicKeyPacket {
|
||||
)) {
|
||||
throw new Error(`Cannot generate v6 keys of type 'ecc' with curve ${curve}. Generate a key of type 'curve25519' instead`);
|
||||
}
|
||||
const { privateParams, publicParams } = await crypto.generateParams(this.algorithm, bits, curve);
|
||||
const { privateParams, publicParams } = await crypto.generateParams(this.algorithm, bits, curve, symmetric);
|
||||
this.privateParams = privateParams;
|
||||
this.publicParams = publicParams;
|
||||
this.isEncrypted = false;
|
||||
|
@ -781,9 +781,10 @@ class SignaturePacket {
|
||||
}
|
||||
|
||||
this.params = await this.params;
|
||||
const privateParams = this.publicKeyAlgorithm === enums.publicKey.hmac ? key.privateParams : null;
|
||||
|
||||
this[verified] = await crypto.signature.verify(
|
||||
this.publicKeyAlgorithm, this.hashAlgorithm, this.params, key.publicParams,
|
||||
this.publicKeyAlgorithm, this.hashAlgorithm, this.params, key.publicParams, privateParams,
|
||||
toHash, hash
|
||||
);
|
||||
|
||||
|
78
src/type/enum.js
Normal file
78
src/type/enum.js
Normal file
@ -0,0 +1,78 @@
|
||||
// OpenPGP.js - An OpenPGP implementation in javascript
|
||||
// Copyright (C) 2015-2016 Decentral
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 3.0 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper for enums
|
||||
*
|
||||
* @requires enums
|
||||
* @module type/enum.js
|
||||
*/
|
||||
|
||||
|
||||
import enums from '../enums';
|
||||
|
||||
const type_enum = type => class EnumType {
|
||||
constructor(data) {
|
||||
if (typeof data === 'undefined') {
|
||||
this.data = null;
|
||||
} else {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an enum entry
|
||||
* @param {Uint8Array} input Where to read the symmetric algo from
|
||||
*/
|
||||
read(input) {
|
||||
const data = input[0];
|
||||
this.data = enums.write(type, data);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an enum as an integer
|
||||
* @returns {Uint8Array} An integer representing the algorithm
|
||||
*/
|
||||
write() {
|
||||
return new Uint8Array([this.data]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the enum entry
|
||||
* @returns {string} The name string
|
||||
*/
|
||||
getName() {
|
||||
return enums.read(type, this.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the integer value of the enum entry
|
||||
* @returns {Number} The integer value
|
||||
*/
|
||||
getValue() {
|
||||
return this.data;
|
||||
}
|
||||
};
|
||||
const AEADEnum = type_enum(enums.aead);
|
||||
const SymAlgoEnum = type_enum(enums.symmetric);
|
||||
const HashEnum = type_enum(enums.hash);
|
||||
|
||||
export { AEADEnum, SymAlgoEnum, HashEnum };
|
||||
|
||||
export default type_enum;
|
32
src/type/short_byte_string.js
Normal file
32
src/type/short_byte_string.js
Normal file
@ -0,0 +1,32 @@
|
||||
import util from '../util';
|
||||
|
||||
class ShortByteString {
|
||||
constructor(data) {
|
||||
if (typeof data === 'undefined') {
|
||||
data = new Uint8Array([]);
|
||||
}
|
||||
if (!util.isUint8Array(data)) {
|
||||
throw new Error('data must be in the form of a Uint8Array');
|
||||
}
|
||||
this.data = data;
|
||||
this.length = this.data.byteLength;
|
||||
}
|
||||
|
||||
write() {
|
||||
return util.concatUint8Array([new Uint8Array([this.length]), this.data]);
|
||||
}
|
||||
|
||||
read(input) {
|
||||
if (input.length >= 1) {
|
||||
const length = input[0];
|
||||
if (input.length >= length + 1) {
|
||||
this.data = input.subarray(1, 1 + length);
|
||||
this.length = length;
|
||||
return 1 + length;
|
||||
}
|
||||
}
|
||||
throw new Error('Invalid octet string');
|
||||
}
|
||||
}
|
||||
|
||||
export default ShortByteString;
|
@ -214,7 +214,7 @@ export default () => describe('API functional testing', function() {
|
||||
openpgp.enums.publicKey.rsaEncryptSign, openpgp.enums.hash.sha1, RSAPublicParams, RSAPrivateParams, data, await crypto.hash.digest(2, data)
|
||||
);
|
||||
const success = await crypto.signature.verify(
|
||||
openpgp.enums.publicKey.rsaEncryptSign, openpgp.enums.hash.sha1, RSAsignedData, RSAPublicParams, data, await crypto.hash.digest(2, data)
|
||||
openpgp.enums.publicKey.rsaEncryptSign, openpgp.enums.hash.sha1, RSAsignedData, RSAPublicParams, null, data, await crypto.hash.digest(2, data)
|
||||
);
|
||||
return expect(success).to.be.true;
|
||||
});
|
||||
@ -224,7 +224,7 @@ export default () => describe('API functional testing', function() {
|
||||
openpgp.enums.publicKey.dsa, openpgp.enums.hash.sha1, DSAPublicParams, DSAPrivateParams, data, await crypto.hash.digest(2, data)
|
||||
);
|
||||
const success = await crypto.signature.verify(
|
||||
openpgp.enums.publicKey.dsa, openpgp.enums.hash.sha1, DSAsignedData, DSAPublicParams, data, await crypto.hash.digest(2, data)
|
||||
openpgp.enums.publicKey.dsa, openpgp.enums.hash.sha1, DSAsignedData, DSAPublicParams, null, data, await crypto.hash.digest(2, data)
|
||||
);
|
||||
|
||||
return expect(success).to.be.true;
|
||||
@ -239,7 +239,7 @@ export default () => describe('API functional testing', function() {
|
||||
openpgp.enums.publicKey.ed448, openpgp.enums.hash.sha512, { A }, { seed }, data, toSign
|
||||
);
|
||||
const success = await crypto.signature.verify(
|
||||
openpgp.enums.publicKey.ed448, openpgp.enums.hash.sha512, signedData, { A }, data, toSign
|
||||
openpgp.enums.publicKey.ed448, openpgp.enums.hash.sha512, signedData, { A }, null, data, toSign
|
||||
);
|
||||
|
||||
return expect(success).to.be.true;
|
||||
@ -271,7 +271,7 @@ export default () => describe('API functional testing', function() {
|
||||
|
||||
it('Asymmetric using RSA with eme_pkcs1 padding', function () {
|
||||
const symmKey = crypto.generateSessionKey(openpgp.enums.symmetric.aes256);
|
||||
return crypto.publicKeyEncrypt(algoRSA, openpgp.enums.symmetric.aes256, RSAPublicParams, symmKey).then(RSAEncryptedData => {
|
||||
return crypto.publicKeyEncrypt(algoRSA, openpgp.enums.symmetric.aes256, RSAPublicParams, null, symmKey).then(RSAEncryptedData => {
|
||||
return crypto.publicKeyDecrypt(
|
||||
algoRSA, RSAPublicParams, RSAPrivateParams, RSAEncryptedData
|
||||
).then(data => {
|
||||
@ -282,7 +282,7 @@ export default () => describe('API functional testing', function() {
|
||||
|
||||
it('Asymmetric using Elgamal with eme_pkcs1 padding', function () {
|
||||
const symmKey = crypto.generateSessionKey(openpgp.enums.symmetric.aes256);
|
||||
return crypto.publicKeyEncrypt(algoElGamal, openpgp.enums.symmetric.aes256, elGamalPublicParams, symmKey).then(ElgamalEncryptedData => {
|
||||
return crypto.publicKeyEncrypt(algoElGamal, openpgp.enums.symmetric.aes256, elGamalPublicParams, null, symmKey).then(ElgamalEncryptedData => {
|
||||
return crypto.publicKeyDecrypt(
|
||||
algoElGamal, elGamalPublicParams, elGamalPrivateParams, ElgamalEncryptedData
|
||||
).then(data => {
|
||||
|
34
test/crypto/hmac.js
Normal file
34
test/crypto/hmac.js
Normal file
@ -0,0 +1,34 @@
|
||||
import { use as chaiUse, expect } from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised'; // eslint-disable-line import/newline-after-import
|
||||
chaiUse(chaiAsPromised);
|
||||
|
||||
import { sign, verify } from '../../src/crypto/public_key/hmac';
|
||||
import enums from '../../src/enums';
|
||||
import util from '../../src/util';
|
||||
|
||||
export default () => describe('HMAC', function () {
|
||||
it('Test vectors', async function() {
|
||||
const vectors = [
|
||||
{
|
||||
algo: enums.hash.sha256,
|
||||
key: util.hexToUint8Array('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'),
|
||||
data: util.hexToUint8Array('4869205468657265'),
|
||||
expected: util.hexToUint8Array('b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7')
|
||||
},
|
||||
{
|
||||
algo: enums.hash.sha512,
|
||||
key: util.hexToUint8Array('4a656665'),
|
||||
data: util.hexToUint8Array('7768617420646f2079612077616e7420666f72206e6f7468696e673f'),
|
||||
expected: util.hexToUint8Array('164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737')
|
||||
}
|
||||
];
|
||||
|
||||
await Promise.all(vectors.map(async vec => {
|
||||
const res = await sign(vec.algo, vec.key, vec.data);
|
||||
const verified = await verify(vec.algo, vec.key, vec.expected, vec.data);
|
||||
expect(util.equalsUint8Array(res, vec.expected)).to.be.true;
|
||||
expect(verified).to.be.true;
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@ -8,6 +8,7 @@ import testECDH from './ecdh';
|
||||
import testPKCS5 from './pkcs5';
|
||||
import testAESKW from './aes_kw';
|
||||
import testHKDF from './hkdf';
|
||||
import testHMAC from './hmac';
|
||||
import testGCM from './gcm';
|
||||
import testEAX from './eax';
|
||||
import testOCB from './ocb';
|
||||
@ -25,6 +26,7 @@ export default () => describe('Crypto', function () {
|
||||
testPKCS5();
|
||||
testAESKW();
|
||||
testHKDF();
|
||||
testHMAC();
|
||||
testGCM();
|
||||
testEAX();
|
||||
testOCB();
|
||||
|
@ -4728,6 +4728,71 @@ I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUrk0mXubZvyl4GBg==
|
||||
await subkey.verify();
|
||||
});
|
||||
|
||||
it('create and add a new symmetric subkey to a rsa key', async function() {
|
||||
const privateKey = await openpgp.decryptKey({
|
||||
privateKey: await openpgp.readKey({ armoredKey: priv_key_rsa }),
|
||||
passphrase: 'hello world'
|
||||
});
|
||||
const total = privateKey.getSubkeys().length;
|
||||
const opt2 = { type: 'symmetric', symmetricCipher: 'aes256' };
|
||||
let newPrivateKey = await privateKey.addSubkey(opt2);
|
||||
const armoredKey = await newPrivateKey.armor();
|
||||
newPrivateKey = await openpgp.readKey({ armoredKey: armoredKey });
|
||||
const subKey = newPrivateKey.getSubkeys()[total];
|
||||
expect(subKey).to.exist;
|
||||
expect(newPrivateKey.getSubkeys().length).to.be.equal(total + 1);
|
||||
expect(subKey.getAlgorithmInfo().symmetric).to.be.equal('aes256');
|
||||
expect(subKey.keyPacket.publicParams.digest).to.exist.and.to.have.length(32);
|
||||
expect(subKey.keyPacket.privateParams.keyMaterial).to.exist.and.to.have.length(32);
|
||||
await subKey.verify(privateKey.primaryKey);
|
||||
});
|
||||
|
||||
it('create and add a new rsa key with a symmetric encryption subkey', async function() {
|
||||
const userId = { name: 'test', email: 'a@b.com' };
|
||||
const opt = { rsaBits: 512, userIDs: [userId], subkeys:[{ type: 'symmetric', symmetricCipher: 'aes256' }], format: 'object' };
|
||||
|
||||
const { privateKey } = await openpgp.generateKey(opt);
|
||||
const armoredKey = await privateKey.armor();
|
||||
const newKey = await openpgp.readKey({ armoredKey: armoredKey });
|
||||
const subKey = newKey.getSubkeys()[0];
|
||||
expect(newKey.getSubkeys().length).to.be.equal(1);
|
||||
expect(subKey).to.exist;
|
||||
expect(subKey.getAlgorithmInfo().symmetric).to.be.equal('aes256');
|
||||
expect(subKey.keyPacket.publicParams.cipher).to.exist;
|
||||
expect(subKey.keyPacket.publicParams.cipher.getValue()).to.be.equal(openpgp.enums.symmetric.aes256);
|
||||
expect(subKey.keyPacket.publicParams.digest).to.exist.and.to.have.length(32);
|
||||
expect(subKey.keyPacket.privateParams.keyMaterial).to.exist.and.to.have.length(32);
|
||||
await subKey.verify(newKey.primaryKey);
|
||||
});
|
||||
|
||||
it('create and add a new encrypted rsa key with a symmetric encryption subkey', async function() {
|
||||
const userId = { name: 'test', email: 'a@b.com' };
|
||||
const opt = { rsaBits: 512, userIDs: [userId], subkeys:[{ type: 'symmetric', symmetricCipher: 'aes256' }], format: 'object' };
|
||||
|
||||
const { privateKey } = await openpgp.generateKey(opt);
|
||||
const subKey = privateKey.getSubkeys()[0];
|
||||
expect(privateKey.getSubkeys().length).to.be.equal(1);
|
||||
expect(subKey).to.exist;
|
||||
expect(subKey.getAlgorithmInfo().symmetric).to.be.equal('aes256');
|
||||
expect(subKey.keyPacket.publicParams.cipher).to.exist;
|
||||
expect(subKey.keyPacket.publicParams.cipher.getValue()).to.be.equal(openpgp.enums.symmetric.aes256);
|
||||
expect(subKey.keyPacket.publicParams.digest).to.exist.and.to.have.length(32);
|
||||
expect(subKey.keyPacket.privateParams.keyMaterial).to.exist.and.to.have.length(32);
|
||||
await subKey.verify(privateKey.primaryKey);
|
||||
});
|
||||
|
||||
it('create and add a symmetric signing key', async function() {
|
||||
const opt = { userIDs: [{ name: 'test', email: 'a@b.com' }], type: 'symmetric', symmetricHash: 'sha256', format: 'object' };
|
||||
const { privateKey } = await openpgp.generateKey(opt);
|
||||
const signed = await openpgp.sign({ message: await openpgp.createMessage({ text: 'the data to be signed' }), signingKeys: privateKey, format: 'binary' });
|
||||
const message = await openpgp.readMessage({ binaryMessage: signed });
|
||||
const { signatures } = await openpgp.verify({ message, verificationKeys: [privateKey], expectSigned: true });
|
||||
expect(signatures).to.exist;
|
||||
expect(signatures.length).to.be.equal(1);
|
||||
expect(signatures[0].keyID.toHex()).to.be.equal(privateKey.getKeyID().toHex());
|
||||
expect(await signatures[0].verified).to.be.true;
|
||||
});
|
||||
|
||||
it('sign/verify data with the new subkey correctly using curve25519 (legacy format)', async function() {
|
||||
const userID = { name: 'test', email: 'a@b.com' };
|
||||
const opt = { curve: 'curve25519', userIDs: [userID], format: 'object', subkeys:[] };
|
||||
|
@ -1225,6 +1225,119 @@ V+HOQJQxXJkVRYa3QrFUehiMzTeqqMdgC6ZqJy7+
|
||||
});
|
||||
});
|
||||
|
||||
describe('Persistent symmetric keys', function() {
|
||||
it('Parse persistent key encrypted session key packet (PKESK encrypted with persistent symmetric key)', () => {
|
||||
const serializedPacket = new Uint8Array([
|
||||
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x64, 0x01, 0x4c, 0x80, 0x3d, 0x9b, 0xb6,
|
||||
0x44, 0x13, 0x25, 0x90, 0x24, 0x6e, 0x59, 0x0d,
|
||||
0x68, 0xee, 0x1e, 0x23, 0x75, 0x8a, 0x90, 0x85,
|
||||
0x78, 0x1b, 0xf7, 0xc4, 0x4d, 0x58, 0xc7, 0x64,
|
||||
0xd2, 0xe5, 0xb3, 0x4f, 0xf6, 0x6e, 0xef, 0x53,
|
||||
0xc4, 0xc3, 0x76, 0x7b, 0xba, 0xf9, 0x03, 0x86,
|
||||
0xee, 0xc0, 0x9d, 0x60, 0x23, 0xa6, 0x8a
|
||||
]);
|
||||
const expectedIV = new Uint8Array([
|
||||
0x4c, 0x80, 0x3d, 0x9b, 0xb6, 0x44, 0x13, 0x25,
|
||||
0x90, 0x24, 0x6e, 0x59, 0x0d, 0x68, 0xee, 0x1e
|
||||
]);
|
||||
const expectedCiphertext = new Uint8Array([
|
||||
0x75, 0x8a, 0x90, 0x85, 0x78, 0x1b, 0xf7, 0xc4,
|
||||
0x4d, 0x58, 0xc7, 0x64, 0xd2, 0xe5, 0xb3, 0x4f,
|
||||
0xf6, 0x6e, 0xef, 0x53, 0xc4, 0xc3, 0x76, 0x7b,
|
||||
0xba, 0xf9, 0x03, 0x86, 0xee, 0xc0, 0x9d, 0x60,
|
||||
0x23, 0xa6, 0x8a
|
||||
]);
|
||||
const packet = new openpgp.PublicKeyEncryptedSessionKeyPacket();
|
||||
packet.read(serializedPacket);
|
||||
|
||||
expect(packet.publicKeyAlgorithm).to.equal(openpgp.enums.publicKey.aead);
|
||||
expect(packet.encrypted.aeadMode.data).to.equal(openpgp.enums.aead.eax);
|
||||
expect(util.equalsUint8Array(packet.encrypted.iv, expectedIV)).to.be.true;
|
||||
expect(util.equalsUint8Array(packet.encrypted.c.data, expectedCiphertext)).to.be.true;
|
||||
});
|
||||
|
||||
it('Parse signature packet from persistent symmetric key', () => {
|
||||
const serializedPacket = new Uint8Array([
|
||||
0x04, 0x00, 0x65, 0x08, 0x00, 0x10, 0x05, 0x02,
|
||||
0x60, 0x64, 0x52, 0x28, 0x09, 0x10, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x6b, 0x1d, 0x40, 0x76, 0xf9, 0x35, 0xf8,
|
||||
0xe4, 0xc5, 0x2f, 0x49, 0xce, 0xf7, 0x91, 0x23,
|
||||
0xb4, 0x00, 0x3b, 0x77, 0x92, 0x60, 0x2a, 0xfe,
|
||||
0x2e, 0x4c, 0xf5, 0x5f, 0x6c, 0x75, 0x80, 0x5a,
|
||||
0x3a, 0xf4, 0x38, 0x09, 0x22, 0x97, 0x1a, 0x01,
|
||||
0x2e, 0x8a, 0x59, 0x86, 0x26, 0x77, 0x79, 0xe1,
|
||||
0xb7, 0xd7, 0x4d, 0x7c, 0xa6, 0x61, 0xe7, 0x00,
|
||||
0xf1, 0x4b, 0xa4, 0x5b, 0xc8, 0x3a, 0xe2, 0x35,
|
||||
0x9e, 0x67, 0x4a, 0x44
|
||||
]);
|
||||
|
||||
const expectedMAC = new Uint8Array([
|
||||
0x76, 0xf9, 0x35, 0xf8, 0xe4, 0xc5, 0x2f, 0x49,
|
||||
0xce, 0xf7, 0x91, 0x23, 0xb4, 0x00, 0x3b, 0x77,
|
||||
0x92, 0x60, 0x2a, 0xfe, 0x2e, 0x4c, 0xf5, 0x5f,
|
||||
0x6c, 0x75, 0x80, 0x5a, 0x3a, 0xf4, 0x38, 0x09,
|
||||
0x22, 0x97, 0x1a, 0x01, 0x2e, 0x8a, 0x59, 0x86,
|
||||
0x26, 0x77, 0x79, 0xe1, 0xb7, 0xd7, 0x4d, 0x7c,
|
||||
0xa6, 0x61, 0xe7, 0x00, 0xf1, 0x4b, 0xa4, 0x5b,
|
||||
0xc8, 0x3a, 0xe2, 0x35, 0x9e, 0x67, 0x4a, 0x44
|
||||
]);
|
||||
|
||||
const packet = new openpgp.SignaturePacket();
|
||||
packet.read(serializedPacket);
|
||||
|
||||
const readMAC = packet.params.mac.data;
|
||||
|
||||
expect(packet.publicKeyAlgorithm).to.equal(openpgp.enums.publicKey.hmac);
|
||||
expect(util.equalsUint8Array(readMAC, expectedMAC)).to.be.true;
|
||||
});
|
||||
|
||||
it('encrypt/decrypt using persistent symmetric key', async () => {
|
||||
const persistentSymmetricKeyPacket = new openpgp.SecretSubkeyPacket();
|
||||
persistentSymmetricKeyPacket.version = 4;
|
||||
persistentSymmetricKeyPacket.algorithm = openpgp.enums.publicKey.aead;
|
||||
await persistentSymmetricKeyPacket.generate(null, null, openpgp.enums.symmetric.aes256);
|
||||
await persistentSymmetricKeyPacket.computeFingerprintAndKeyID();
|
||||
|
||||
const sessionKey = new Uint8Array([1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2]);
|
||||
|
||||
const pkesk = openpgp.PublicKeyEncryptedSessionKeyPacket.fromObject({
|
||||
version: 3,
|
||||
encryptionKeyPacket: persistentSymmetricKeyPacket,
|
||||
sessionKey,
|
||||
sessionKeyAlgorithm: openpgp.enums.symmetric.aes256
|
||||
});
|
||||
await pkesk.encrypt(persistentSymmetricKeyPacket);
|
||||
|
||||
const parsedPKESK = new openpgp.PublicKeyEncryptedSessionKeyPacket();
|
||||
parsedPKESK.read(pkesk.write());
|
||||
await parsedPKESK.decrypt(persistentSymmetricKeyPacket);
|
||||
expect(util.equalsUint8Array(sessionKey, parsedPKESK.sessionKey)).to.be.true;
|
||||
});
|
||||
|
||||
it('throws when encrypting with encrypted symmetric key', async () => {
|
||||
const persistentSymmetricKeyPacket = new openpgp.SecretSubkeyPacket();
|
||||
persistentSymmetricKeyPacket.version = 4;
|
||||
persistentSymmetricKeyPacket.algorithm = openpgp.enums.publicKey.aead;
|
||||
await persistentSymmetricKeyPacket.generate(null, null, openpgp.enums.symmetric.aes256);
|
||||
await persistentSymmetricKeyPacket.computeFingerprintAndKeyID();
|
||||
await persistentSymmetricKeyPacket.encrypt('passphrase');
|
||||
await persistentSymmetricKeyPacket.clearPrivateParams();
|
||||
|
||||
const sessionKey = new Uint8Array([1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2]);
|
||||
|
||||
const pkesk = openpgp.PublicKeyEncryptedSessionKeyPacket.fromObject({
|
||||
version: 3,
|
||||
encryptionKeyPacket: persistentSymmetricKeyPacket,
|
||||
sessionKey,
|
||||
sessionKeyAlgorithm: openpgp.enums.symmetric.aes256
|
||||
});
|
||||
|
||||
await expect(pkesk.encrypt(persistentSymmetricKeyPacket)).to.be.rejectedWith('Cannot encrypt with symmetric key missing private parameters');
|
||||
});
|
||||
});
|
||||
|
||||
describe('PacketList parsing', function () {
|
||||
it('Ignores unknown packet version with `config.ignoreUnsupportedPackets` enabled', async function() {
|
||||
const armoredSignature = `-----BEGIN PGP SIGNATURE-----
|
||||
|
@ -241,7 +241,7 @@ export default () => describe('X25519 Cryptography (legacy format)', function ()
|
||||
expect(R).to.deep.eq(r);
|
||||
expect(S).to.deep.eq(s);
|
||||
}),
|
||||
signature.verify(22, openpgp.enums.hash.sha256, { r: R, s: S }, publicParams, undefined, data).then(result => {
|
||||
signature.verify(22, openpgp.enums.hash.sha256, { r: R, s: S }, publicParams, null, undefined, data).then(result => {
|
||||
expect(result).to.be.true;
|
||||
})
|
||||
]);
|
||||
|
Loading…
x
Reference in New Issue
Block a user