Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 63 additions & 1 deletion modules/bitgo/src/v2/internal/opengpgUtils.ts
Original file line number Diff line number Diff line change
@@ -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';

/**
Expand All @@ -18,9 +26,13 @@ export async function getBitgoGpgPubKey(bitgo: BitGo): Promise<Key> {

/**
* 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<string> {
const messageToEncrypt = await createMessage({
Expand All @@ -37,3 +49,53 @@ export async function encryptText(text: string, key: Key): Promise<string> {
},
});
}


/**
* 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<string> {
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<string> {
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;
}
64 changes: 64 additions & 0 deletions modules/bitgo/test/v2/unit/internal/opengpgUtils.ts
Original file line number Diff line number Diff line change
@@ -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.');
});
});
});