diff --git a/modules/bitgo/src/v2/internal/opengpgUtils.ts b/modules/bitgo/src/v2/internal/opengpgUtils.ts index 2f14fb44a8..0c9e3f54af 100644 --- a/modules/bitgo/src/v2/internal/opengpgUtils.ts +++ b/modules/bitgo/src/v2/internal/opengpgUtils.ts @@ -1,4 +1,12 @@ -import { createMessage, encrypt, Key, readKey } from 'openpgp'; +import { + createMessage, + decrypt, + encrypt, + Key, + readKey, + readMessage, + readPrivateKey, +} from 'openpgp'; import { BitGo } from '../../bitgo'; /** @@ -18,9 +26,13 @@ export async function getBitgoGpgPubKey(bitgo: BitGo): Promise { /** * Encrypts string using gpg key + * @DEPRECATED - should use encryptAndSignText instead for added security + * * @param text string to encrypt * @param key encryption key * @return {string} encrypted string + * + * TODO(BG-47170): Delete once gpg signatures are fully supported */ export async function encryptText(text: string, key: Key): Promise { const messageToEncrypt = await createMessage({ @@ -37,3 +49,53 @@ export async function encryptText(text: string, key: Key): Promise { }, }); } + + +/** + * Encrypts and signs a string + * @param text string to encrypt and sign + * @param publicArmor public key to encrypt with + * @param privateArmor private key to sign with + */ +export async function encryptAndSignText(text: string, publicArmor: string, privateArmor: string): Promise { + const publicKey = await readKey({ armoredKey: publicArmor }); + const privateKey = await readPrivateKey({ armoredKey: privateArmor }); + + const message = await createMessage({ text }); + + const signedMessage = await encrypt({ + message, + encryptionKeys: publicKey, + signingKeys: privateKey, + format: 'armored', + config: { + rejectCurves: new Set(), + showVersion: false, + showComment: false, + }, + }); + + return signedMessage; +} + +/** + * Reads a signed and encrypted message + * + * @param signed signed and encrypted message + * @param publicArmor public key to verify signature + * @param privateArmor private key to decrypt message + */ +export async function readSignedMessage(signed: string, publicArmor: string, privateArmor: string): Promise { + const publicKey = await readKey({ armoredKey: publicArmor }); + const privateKey = await readPrivateKey({ armoredKey: privateArmor }); + + const message = await readMessage({ armoredMessage: signed }); + const decrypted = await decrypt({ + message, + verificationKeys: publicKey, + decryptionKeys: privateKey, + expectSigned: true, + }); + + return decrypted.data; +} diff --git a/modules/bitgo/test/v2/unit/internal/opengpgUtils.ts b/modules/bitgo/test/v2/unit/internal/opengpgUtils.ts new file mode 100644 index 0000000000..30c9cd5265 --- /dev/null +++ b/modules/bitgo/test/v2/unit/internal/opengpgUtils.ts @@ -0,0 +1,64 @@ +import * as openpgp from 'openpgp'; +import 'should'; + +import * as openpgpUtils from '../../../../src/v2/internal/opengpgUtils'; + +describe('OpenGPG Utils Tests', function () { + let senderKey: { publicKey: string, privateKey: string }; + let recipientKey: { publicKey: string, privateKey: string }; + let otherKey: { publicKey: string, privateKey: string }; + + before(async function () { + senderKey = await openpgp.generateKey({ + userIDs: [ + { + name: 'sender', + email: 'sender@username.com', + }, + ], + }); + recipientKey = await openpgp.generateKey({ + userIDs: [ + { + name: 'recipient', + email: 'recipient@username.com', + }, + ], + }); + otherKey = await openpgp.generateKey({ + userIDs: [ + { + name: 'other', + email: 'other@username.com', + }, + ], + }); + }); + + describe('encrypt and decrypt with signing', function() { + it('should successfully encrypt, sign, and decrypt', async function () { + const text = 'original message'; + + const signedMessage = await openpgpUtils.encryptAndSignText(text, recipientKey.publicKey, senderKey.privateKey); + const decryptedMessage = await openpgpUtils.readSignedMessage(signedMessage, senderKey.publicKey, recipientKey.privateKey); + + decryptedMessage.should.equal(text); + }); + + it('should fail on verification with wrong public key', async function () { + const text = 'original message'; + + const signedMessage = await openpgpUtils.encryptAndSignText(text, recipientKey.publicKey, senderKey.privateKey); + await openpgpUtils.readSignedMessage(signedMessage, otherKey.publicKey, recipientKey.privateKey) + .should.be.rejected(); + }); + + it('should fail on decryption with wrong private key', async function () { + const text = 'original message'; + + const signedMessage = await openpgpUtils.encryptAndSignText(text, recipientKey.publicKey, senderKey.privateKey); + await openpgpUtils.readSignedMessage(signedMessage, senderKey.publicKey, otherKey.privateKey) + .should.be.rejectedWith('Error decrypting message: Session key decryption failed.'); + }); + }); +});