Skip to content

encrypt + encode encrypted/plain emails #1058

Description

@tomholub

part of #1051

Right now we use node for initial structure and then we tag on files in Kotlin. Can do all in Kotlin.

Current implementation is at https://github.com/FlowCrypt/flowcrypt-mobile-core/blob/master/source/mobile-interface/endpoints.ts

Here:

  public composeEmail = async (uncheckedReq: any): Promise<Buffers> => {
    const req = ValidateInput.composeEmail(uncheckedReq);
    const mimeHeaders: RichHeaders = { to: req.to, from: req.from, subject: req.subject, cc: req.cc, bcc: req.bcc };
    if (req.replyToMimeMsg) {
      const previousMsg = await Mime.decode(Buf.fromUtfStr((req.replyToMimeMsg.substr(0, 10000).split('\n\n')[0] || '') + `\n\nno content`));
      const replyHeaders = Mime.replyHeaders(previousMsg);
      mimeHeaders['in-reply-to'] = replyHeaders['in-reply-to'];
      mimeHeaders['references'] = replyHeaders['references'];
    }
    if (req.format === 'plain') {
      const atts = (req.atts || []).map(({ name, type, base64 }) => new Att({ name, type, data: Buf.fromBase64Str(base64) }));
      return fmtRes({}, Buf.fromUtfStr(await Mime.encode({ 'text/plain': req.text }, mimeHeaders, atts)));
    } else if (req.format === 'encrypt-inline') {
      const encryptedAtts: Att[] = [];
      for (const att of req.atts || []) {
        const encryptedAtt = await PgpMsg.encrypt({ pubkeys: req.pubKeys, data: Buf.fromBase64Str(att.base64), filename: att.name, armor: false }) as OpenPGP.EncryptBinaryResult;
        encryptedAtts.push(new Att({ name: att.name, type: 'application/pgp-encrypted', data: encryptedAtt.message.packets.write() }))
      }
      const encrypted = await PgpMsg.encrypt({ pubkeys: req.pubKeys, data: Buf.fromUtfStr(req.text), armor: true }) as OpenPGP.EncryptArmorResult;
      return fmtRes({}, Buf.fromUtfStr(await Mime.encode({ 'text/plain': encrypted.data }, mimeHeaders, encryptedAtts)));
    } else {
      throw new Error(`Unknown format: ${req.format}`);
    }
  }

Tests at https://github.com/FlowCrypt/flowcrypt-mobile-core/blob/master/source/test.ts

ava.default('composeEmail format:plain -> parseDecryptMsg', async t => {
  const content = 'hello\nwrld';
  const { keys } = getKeypairs('rsa1');
  const req = { format: 'plain', text: content, to: ['some@to.com'], cc: ['some@cc.com'], bcc: [], from: 'some@from.com', subject: 'a subj' };
  const { data: plainMimeMsg, json: composeEmailJson } = await request('composeEmail', req, []);
  expectEmptyJson(composeEmailJson);
  const plainMimeStr = plainMimeMsg.toString();
  expect(plainMimeStr).contains('To: some@to.com');
  expect(plainMimeStr).contains('From: some@from.com');
  expect(plainMimeStr).contains('Subject: a subj');
  expect(plainMimeStr).contains('Cc: some@cc.com');
  expect(plainMimeStr).contains('Date: ');
  expect(plainMimeStr).contains('MIME-Version: 1.0');
  const { data: blocks, json: parseJson } = await request('parseDecryptMsg', { keys, isEmail: true }, plainMimeMsg);
  expect(parseJson).to.deep.equal({ text: content, replyType: 'plain', subject: 'a subj' });
  expectData(blocks, 'msgBlocks', [{ rendered: true, frameColor: 'plain', htmlContent: content.replace(/\n/g, '<br />') }]);
  t.pass();
});

ava.default('composeEmail format:plain (reply)', async t => {
  const replyToMimeMsg = `Content-Type: multipart/mixed;
 boundary="----sinikael-?=_1-15535259519270.930031460416217"
To: some@to.com
From: some@from.com
Subject: Re: original
Date: Mon, 25 Mar 2019 14:59:11 +0000
Message-Id: <originalmsg@from.com>
MIME-Version: 1.0
------sinikael-?=_1-15535259519270.930031460416217
Content-Type: text/plain
Content-Transfer-Encoding: quoted-printable
orig message
------sinikael-?=_1-15535259519270.930031460416217--`
  const req = { format: 'plain', text: 'replying', to: ['some@to.com'], cc: [], bcc: [], from: 'some@from.com', subject: 'Re: original', replyToMimeMsg };
  const { data: mimeMsgReply, json } = await request('composeEmail', req, []);
  expectEmptyJson(json);
  const mimeMsgReplyStr = mimeMsgReply.toString();
  expect(mimeMsgReplyStr).contains('In-Reply-To: <originalmsg@from.com>');
  expect(mimeMsgReplyStr).contains('References: <originalmsg@from.com>');
  t.pass();
});

ava.default('composeEmail format:encrypt-inline -> parseDecryptMsg', async t => {
  const content = 'hello\nwrld';
  const { pubKeys, keys } = getKeypairs('rsa1');
  const req = { pubKeys, format: 'encrypt-inline', text: content, to: ['encrypted@to.com'], cc: [], bcc: [], from: 'encr@from.com', subject: 'encr subj' };
  const { data: encryptedMimeMsg, json: encryptJson } = await request('composeEmail', req, []);
  expectEmptyJson(encryptJson);
  const encryptedMimeStr = encryptedMimeMsg.toString();
  expect(encryptedMimeStr).contains('To: encrypted@to.com');
  expect(encryptedMimeStr).contains('MIME-Version: 1.0');
  expectData(encryptedMimeMsg, 'armoredMsg'); // armored msg block should be contained in the mime message
  const { data: blocks, json: decryptJson } = await request('parseDecryptMsg', { keys, isEmail: true }, encryptedMimeMsg);
  expect(decryptJson).deep.equal({ text: content, replyType: 'encrypted', subject: 'encr subj' });
  expectData(blocks, 'msgBlocks', [{ rendered: true, frameColor: 'green', htmlContent: content.replace(/\n/g, '<br />') }]);
  t.pass();
});

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions