diff --git a/javascript/package.json b/javascript/package.json
index 831e44ed0cb09a3a05b79ff9daad071e936675d8..1cc608b267fb1be5d7df4e7551dca5cb29716d6b 100644
--- a/javascript/package.json
+++ b/javascript/package.json
@@ -25,7 +25,9 @@
   "dependencies": {
     "asn1js": "^2.0.21",
     "axios": "0.18.0",
+    "data-uri-to-blob": "^0.0.4",
     "libmime": "^4.0.1",
+    "libqp": "^1.1.0",
     "penpal": "^3.0.3",
     "pkijs": "^2.1.69",
     "pvutils": "^1.0.16",
diff --git a/javascript/src/constants.js b/javascript/src/constants/authentication.js
similarity index 100%
rename from javascript/src/constants.js
rename to javascript/src/constants/authentication.js
diff --git a/javascript/src/constants/certificates.js b/javascript/src/constants/certificates.js
new file mode 100644
index 0000000000000000000000000000000000000000..d151bfb0a79442711aaee29b7af0f21e2cfa2f97
--- /dev/null
+++ b/javascript/src/constants/certificates.js
@@ -0,0 +1,38 @@
+/**
+ * Certificate attribute types
+ * For reference http://oidref.com/2.5.4.3
+ */
+
+export const rdnmap = {
+  "2.5.4.3": "commonName",
+  "2.5.4.4": "surname",
+  "2.5.4.6": "country",
+  "2.5.4.7": "locality",
+  "2.5.4.8": "state",
+  "2.5.4.10": "organization",
+  "2.5.4.11": "organizationUnit",
+  "2.5.4.12": "title",
+  "2.5.4.42": "givenName",
+  "2.5.4.43": "initials",
+  "1.2.840.113549.1.9.1": "email"
+};
+
+/**
+ * Common algorithm OIDs and corresponding types
+ */
+
+export const algomap = {
+  "1.2.840.113549.1.1.2": "MD2 with RSA",
+  "1.2.840.113549.1.1.4": "MD5 with RSA",
+  "1.2.840.10040.4.3": "SHA1 with DSA",
+  "1.2.840.10045.4.1": "SHA1 with ECDSA",
+  "1.2.840.10045.4.3.2": "SHA256 with ECDSA",
+  "1.2.840.10045.4.3.3": "SHA384 with ECDSA",
+  "1.2.840.10045.4.3.4": "SHA512 with ECDSA",
+  "1.2.840.113549.1.1.10": "RSA-PSS",
+  "1.2.840.113549.1.1.5": "SHA1 with RSA",
+  "1.2.840.113549.1.1.14": "SHA224 with RSA",
+  "1.2.840.113549.1.1.11": "SHA256 with RSA",
+  "1.2.840.113549.1.1.12": "SHA384 with RSA",
+  "1.2.840.113549.1.1.13": "SHA512 with RSA"
+};
diff --git a/javascript/src/helpers/mailparser.js b/javascript/src/helpers/mailparser.js
new file mode 100644
index 0000000000000000000000000000000000000000..8fd6f11fe5bcbd4f5259b0f4d7e306ab891543b1
--- /dev/null
+++ b/javascript/src/helpers/mailparser.js
@@ -0,0 +1,357 @@
+import libmime from "libmime";
+import libqp from "libqp";
+
+const newline = /\r\n|\r|\n/g;
+const SMIMEStart =
+  "This is a cryptographically signed message in MIME format.\r\n\r\n";
+
+function findAllOccurences(text, subject, from, to) {
+  const result = [];
+  let index = text.indexOf(subject, from);
+  if (index < 0 || index > to) {
+    return result;
+  }
+
+  result.push(index + 2);
+
+  while (true) {
+    index = text.indexOf(subject, index + 1);
+    if (index < 0 || index > to) {
+      break;
+    } else {
+      result.push(index + 2);
+    }
+  }
+
+  return result;
+}
+
+function findFirstBoundary(body, from, to) {
+  if (from >= to) {
+    return null;
+  }
+  const search = "\r\n--";
+
+  let start = body.indexOf(search, from);
+  if (start < 0 || start > to) {
+    return null;
+  }
+
+  start += 2;
+
+  const end = body.indexOf("\r\n", start);
+  if (end < 0 || end > to) {
+    return null;
+  }
+
+  const boundary = body.substring(start, end);
+
+  const boundaryEnd = boundary + "--\r\n";
+
+  const startBoundaryEnd = body.indexOf(boundaryEnd, end);
+
+  if (startBoundaryEnd < 0 || startBoundaryEnd > to) {
+    return findFirstBoundary(body, end + 2, to);
+  }
+
+  return boundary;
+}
+
+function calculateParts(body, from, to, previousBondary) {
+  let boundary = findFirstBoundary(body, from, to);
+
+  if (boundary == null) {
+    return [{ indices: { from: from, to: to }, boundary: previousBondary }];
+  }
+
+  const realBoundary = boundary;
+  boundary = "\r\n" + boundary;
+
+  const boundaryPairs = [];
+
+  const boundaryIndices = findAllOccurences(body, boundary, from, to);
+  const boundaryIndicesLength = boundaryIndices.length;
+  for (let i = 0; i < boundaryIndicesLength; i++) {
+    const startBoundary = boundaryIndices[i];
+    const endBoundary = body.indexOf("\r\n", startBoundary);
+    boundaryPairs.push({ start: startBoundary, end: endBoundary });
+  }
+
+  let bodies = [];
+
+  for (let i = 0; i < boundaryIndicesLength - 1; i++) {
+    const firstPair = boundaryPairs[i];
+    const secondPair = boundaryPairs[i + 1];
+    const newFrom = firstPair.end + 2;
+    const newTo = secondPair.start - 2;
+    const bodyForBoundary = calculateParts(body, newFrom, newTo, realBoundary);
+    bodies = bodies.concat(bodyForBoundary);
+  }
+
+  return bodies;
+}
+
+function parsePartsHeaders(mimeBody, parts) {
+  const result = [];
+  for (let i = 0; i < parts.length; i++) {
+    const part = parts[i];
+    const indices = part["indices"];
+    let headersEnd = mimeBody
+      .substring(indices.from, indices.to)
+      .indexOf("\r\n\r\n");
+    if (headersEnd < 0) {
+      headersEnd = indices.from;
+    } else {
+      headersEnd = headersEnd + indices.from + 4;
+    }
+    part["indices"].headersEnd = headersEnd;
+
+    part["headers"] = libmime.decodeHeaders(
+      mimeBody.substring(indices.from, headersEnd)
+    );
+    result.push(part);
+  }
+
+  return result;
+}
+
+export function fixNewLines(mime) {
+  return mime.replace(newline, "\r\n");
+}
+
+export function parseMIME(mime) {
+  let mimeStart = 0;
+  let headersEnd = mime.indexOf("\r\n\r\n"); //the first empty line
+  let mimeBody = "";
+  if (headersEnd < 0 && mime.startsWith("\r\n")) {
+    mime = mime.substring(2); //should not happen
+    mimeBody = mime;
+    mimeStart = 0;
+  } else {
+    mimeBody = mime.substring(headersEnd + 4);
+    mimeStart = headersEnd + 4;
+  }
+
+  let headers = libmime.decodeHeaders(mime.substring(0, headersEnd));
+
+  let indexOfSMIME = mimeBody.indexOf(SMIMEStart);
+
+  if (indexOfSMIME >= 0) {
+    mimeBody = mimeBody.substring(indexOfSMIME + SMIMEStart.length);
+    mimeStart += indexOfSMIME + SMIMEStart.length;
+  }
+
+  mimeBody = "\r\n" + mimeBody + "\r\n";
+  const mimeBodyLen = mimeBody.length - 1;
+  let parts = calculateParts(mimeBody, 0, mimeBodyLen, null);
+  parts = parsePartsHeaders(mimeBody, parts);
+
+  for (let i = 0; i < parts.length; i++) {
+    parts[i].indices.from = parts[i].indices.from + (mimeStart - 2);
+    parts[i].indices.headersEnd = parts[i].indices.headersEnd + (mimeStart - 2);
+    parts[i].indices.to = parts[i].indices.to + (mimeStart - 2);
+  }
+
+  parts.push({
+    indices: { from: 0, to: mime.length, headersEnd: headersEnd },
+    headers: headers,
+    boundary: "mimemessage"
+  });
+
+  return parts;
+}
+
+function getHeaderValue(header, part) {
+  if (part.headers !== null && part.headers !== undefined) {
+    if (part.headers[header] !== null && part.headers[header] !== undefined) {
+      if (part.headers[header].length > 0) {
+        return part.headers[header];
+      } else {
+        return null;
+      }
+    } else {
+      return null;
+    }
+  } else {
+    return null;
+  }
+}
+
+export function getGlobalHeaderValue(header, parts) {
+  for (let i = 0; i < parts.length; i++) {
+    if (parts[i].boundary === "mimemessage") {
+      return getHeaderValue(header, parts[i]);
+    }
+  }
+
+  return null;
+}
+
+function getBody(mime, part) {
+  if (part.indices === null || part.indices === undefined) {
+    return null;
+  }
+
+  const indices = part.indices;
+
+  return mime.substring(indices.headersEnd, indices.to);
+}
+
+export function decodeMimeBody(descriptor, mimeString) {
+  let mimeBody = mimeString.slice(
+    descriptor.indices.headersEnd,
+    descriptor.indices.to
+  );
+
+  let contentTransferEncoding = getHeaderValue(
+    "content-transfer-encoding",
+    descriptor
+  );
+  if (contentTransferEncoding) {
+    contentTransferEncoding = contentTransferEncoding[0];
+  } else {
+    return mimeBody;
+  }
+
+  let charset = "utf8";
+  let contentType = getHeaderValue("content-type", descriptor);
+  if (contentType) {
+    contentType = contentType[0];
+    let parsedContentType = libmime.parseHeaderValue(contentType);
+    if (
+      parsedContentType &&
+      parsedContentType.params &&
+      parsedContentType.params.charset
+    ) {
+      if (parsedContentType.params.charset.toLowerCase() === "us-ascii") {
+        charset = "ascii";
+      } else if (Buffer.isEncoding(parsedContentType.params.charset)) {
+        charset = parsedContentType.params.charset;
+      } else {
+        //TODO log the charset and make sure we can support it
+      }
+    }
+  }
+
+  if (contentTransferEncoding.toLowerCase() === "quoted-printable") {
+    let buff = libqp.decode(mimeBody);
+    return buff.toString(charset);
+  } else if (contentTransferEncoding.toLowerCase() === "base64") {
+    let buff = Buffer.from(mimeBody, "base64");
+    return buff.toString(charset);
+  }
+
+  return mimeBody;
+}
+
+export function getHTML(mime, parts) {
+  let html;
+  let htmlPart = null;
+  for (let i = 0; i < parts.length; i++) {
+    if (parts[i].boundary === "mimemessage") {
+      continue;
+    }
+    let contentType = getHeaderValue("content-type", parts[i]);
+    if (contentType === null || contentType === undefined) {
+      continue;
+    }
+
+    contentType = contentType[0];
+    if (contentType.indexOf("text/html") >= 0) {
+      htmlPart = parts[i];
+      break;
+    }
+  }
+
+  html = decodeMimeBody(htmlPart, mime);
+
+  for (let i = 0; i < parts.length; i++) {
+    let contentDisposition = getHeaderValue("content-disposition", parts[i]);
+    if (contentDisposition === null || contentDisposition === undefined) {
+      continue;
+    }
+    contentDisposition = contentDisposition[0];
+    if (contentDisposition.indexOf("inline") >= 0) {
+      let contentId = getHeaderValue("content-id", parts[i]);
+      if (contentId === null || contentId === undefined) {
+        continue;
+      }
+
+      contentId = contentId[0];
+      const contentIdLen = contentId.length;
+      contentId = contentId.substring(1, contentIdLen - 1);
+      contentId = "cid:" + contentId;
+      let contentType = getHeaderValue("content-type", parts[i]);
+      if (contentType === null || contentType === undefined) {
+        continue;
+      }
+      contentType = contentType[0];
+      const normalizedBody = getBody(mime, parts[i]).replace(newline, "");
+      const src = "data:" + contentType + ";base64, " + normalizedBody;
+      html = html.split(contentId).join(src);
+    }
+  }
+  return html;
+}
+
+export function getPlain(mime, parts) {
+  let plain;
+  let plainPart = null;
+  for (let i = 0; i < parts.length; i++) {
+    let contentType = getHeaderValue("content-type", parts[i]);
+    if (contentType === null || contentType === undefined) {
+      continue;
+    }
+    contentType = contentType[0];
+    if (contentType.indexOf("text/plain") >= 0) {
+      plainPart = parts[i];
+      break;
+    }
+  }
+
+  plain = decodeMimeBody(plainPart, mime);
+  return plain;
+}
+
+export function getAttachments(mime, parts) {
+  const attachments = [];
+  for (let i = 0; i < parts.length; i++) {
+    let contentDisposition = getHeaderValue("content-disposition", parts[i]);
+    if (contentDisposition === null || contentDisposition === undefined) {
+      continue;
+    }
+    contentDisposition = contentDisposition[0];
+    if (contentDisposition.indexOf("attachment") >= 0) {
+      attachments.push(parts[i]);
+    }
+  }
+
+  return attachments;
+}
+
+export function getAttachment(mime, part) {
+  let contentType = getHeaderValue("content-type", part);
+  if (contentType === null || contentType === undefined) {
+    return null;
+  }
+  contentType = contentType[0];
+  let contentTransferEncoding = getHeaderValue(
+    "content-transfer-encoding",
+    part
+  );
+  if (contentTransferEncoding) {
+    contentTransferEncoding = contentTransferEncoding[0];
+  } else {
+    return null;
+  }
+
+  let base64;
+  if (contentTransferEncoding.toLowerCase() === "base64") {
+    base64 = getBody(mime, part).replace(newline, "");
+  } else {
+    const body = decodeMimeBody(part, mime);
+    base64 = window.btoa(body);
+  }
+
+  return { contentType, base64 }
+}
diff --git a/javascript/src/iframe/viamapi-iframe.js b/javascript/src/iframe/viamapi-iframe.js
index ff4a071dc6b1c28a3aa696efa98cc9696f1690c0..cf62bf4b9dcc0be361911a1c03f7aad5c417c0c1 100644
--- a/javascript/src/iframe/viamapi-iframe.js
+++ b/javascript/src/iframe/viamapi-iframe.js
@@ -1,3 +1,5 @@
+import { parseSMIME } from '../utilities/emailUtilities';
+
 const QRCode = require('qrcode');
 const Penpal = require('penpal').default;
 
@@ -7,7 +9,7 @@ import {
   encodeResponse,
   listIdentitiesFromLocalStorage, makeid
 } from '../utilities/appUtility';
-import {LOGIN_MODES} from '../constants';
+import {LOGIN_MODES} from '../constants/authentication';
 import {
   createOneTimePassportCertificate,
   createPassportCertificate,
@@ -718,6 +720,7 @@ const connection = Penpal.connectToParent({
         });
       });
     },
+    parseSMIME,
     getCurrentlyLoggedInUUID() {
       return new Penpal.Promise(result => {
         const authenticationPublicKey = localStorage.getItem("authenticatedIdentity");
diff --git a/javascript/src/utilities/dateTimeUtilities.js b/javascript/src/utilities/dateTimeUtilities.js
new file mode 100644
index 0000000000000000000000000000000000000000..3b7c54e146b6b2d5d5401087f2875f54abc0e437
--- /dev/null
+++ b/javascript/src/utilities/dateTimeUtilities.js
@@ -0,0 +1,7 @@
+export const formatDateNumeric = date =>
+  date.toLocaleDateString("en-US", {
+    year: "numeric",
+    month: "numeric",
+    day: "numeric"
+  });
+
diff --git a/javascript/src/utilities/emailUtilities.js b/javascript/src/utilities/emailUtilities.js
new file mode 100644
index 0000000000000000000000000000000000000000..7bc1971b2afc9248124784529618b4fafd2ef936
--- /dev/null
+++ b/javascript/src/utilities/emailUtilities.js
@@ -0,0 +1,104 @@
+import dataUriToBlob from "data-uri-to-blob";
+import libmime from 'libmime';
+
+import {
+  fixNewLines,
+  parseMIME,
+  getHTML,
+  getPlain,
+  getAttachments,
+  getAttachment,
+  getGlobalHeaderValue
+} from "../helpers/mailparser";
+import { getCertificateChain } from "./signingUtilities";
+
+const SIGNATURE_CONTENT_TYPE = "application/pkcs7-signature";
+export const DEFAULT_ATTACHMENT_NAME = 'attachment';
+
+export const parseSMIME = smimeString => {
+  return new Promise(resolve => {
+    setTimeout(async () => {
+      const emailString = fixNewLines(smimeString);
+      const parts = parseMIME(emailString);
+      const html = getHTML(emailString, parts);
+      const plain = getPlain(emailString, parts);
+      const rawAttachments = getAttachments(emailString, parts);
+
+      const attachments = [];
+      let signatureBase64;
+      for (const rawAttachment of rawAttachments) {
+        const { contentType, base64 } = getAttachment(
+          emailString,
+          rawAttachment
+        );
+
+        if (contentType.indexOf(SIGNATURE_CONTENT_TYPE) !== -1) {
+          signatureBase64 = base64;
+        }
+
+        const dataURI = "data:" + contentType + ";base64, " + base64;
+        const blob = dataUriToBlob(dataURI);
+        const filename = getFilenameFromHeaders(rawAttachment.headers);
+
+        attachments.push({
+          blob,
+          filename
+        });
+      }
+
+      const certificateChain = getCertificateChain(signatureBase64);
+
+      const message = {
+        from: getGlobalHeaderValue("from", parts),
+        to: getGlobalHeaderValue("to", parts),
+        cc: getGlobalHeaderValue("cc", parts),
+        subject: getGlobalHeaderValue("subject", parts).join(" "),
+        html: extractHtmlBodyFromString(html),
+        plain,
+        attachments,
+        certificateChain
+      };
+
+      resolve(message);
+    }, 50);
+  });
+};
+
+/**
+ * Function extracts file name from content type header
+ * @param headers
+ * @returns {string} ('file.txt')
+ */
+export const getFilenameFromHeaders = headers => {
+  const headersToSearch = ['content-type', 'content-disposition'];
+
+  const filename = headers && Object.keys(headers)
+    .filter(key => headersToSearch.includes(key))
+    .reduce((result, key) => {
+      const headerValue = libmime.parseHeaderValue(headers[key][0]);
+      return result || headerValue.params.name || headerValue.params.filename;
+    }, '');
+
+  return filename || DEFAULT_ATTACHMENT_NAME;
+};
+
+/**
+ * Function extracts all tags within <body></body> from provided string
+ * and removes whitespaces between tags and HTML comments.
+ * @param string
+ * @returns {*}
+ */
+export const extractHtmlBodyFromString = string => {
+  const extractBodyRegex = /<body.*?>([\s\S]+)<\/body>/gm;
+  const bodyMatch = extractBodyRegex.exec(string);
+
+  let body;
+
+  if (bodyMatch && bodyMatch[1]) {
+    body = bodyMatch[1]
+      .replace(/>\s+</gm, '><')
+      .replace(/<!--[\s\S]*?-->/gm, '').trim()
+  }
+
+  return body;
+};
\ No newline at end of file
diff --git a/javascript/src/utilities/signingUtilities.js b/javascript/src/utilities/signingUtilities.js
index dad16e412bdaa725838b6560b4faa7b71fb79a0f..3dd93bf140e5de3f127d455f0ef37b0e02ce760f 100644
--- a/javascript/src/utilities/signingUtilities.js
+++ b/javascript/src/utilities/signingUtilities.js
@@ -1,4 +1,9 @@
 import {canTryPincode, failPincodeAttempt, getTimeLeftInLocalStorage, makeid} from './appUtility';
+import {bufferToHexCodes, stringToArrayBuffer} from 'pvutils';
+import {fromBER} from 'asn1js';
+import {ContentInfo, SignedData} from 'pkijs';
+import {formatDateNumeric} from './dateTimeUtilities';
+import {algomap, rdnmap} from '../constants/certificates';
 
 const libmime = require('libmime');
 const pkijs = require('pkijs');
@@ -952,4 +957,84 @@ function capitalizeHeader(string) {
 function makeBoundary() {
   let len = 20 + Math.random() * 20;
   return 'W0RyLiBEYW15YW4gTWl0ZXZd--' + makeid(len)
-}
\ No newline at end of file
+}
+
+export const parseCertificates = signatureBase64 => {
+  try {
+    const certificateDecoded = atob(signatureBase64);
+    const buffer = stringToArrayBuffer(certificateDecoded);
+    const asn1 = fromBER(buffer);
+
+    const contentInfo = new ContentInfo({ schema: asn1.result });
+    const signedData = new SignedData({ schema: contentInfo.content });
+
+    return signedData.certificates.map((certificate, index) => {
+      const certificateData = { issuer: {}, subject: {}, validity: {} };
+      const serialNumber = bufferToHexCodes(
+        certificate.serialNumber.valueBlock.valueHex
+      );
+      const issuer = certificate.issuer.typesAndValues;
+      const subject = certificate.subject.typesAndValues;
+      const notAfter = formatDateNumeric(certificate.notAfter.value);
+      const notBefore = formatDateNumeric(certificate.notBefore.value);
+      let signatureAlgorithm =
+        algomap[certificate.signatureAlgorithm.algorithmId];
+      if (typeof signatureAlgorithm === "undefined") {
+        signatureAlgorithm = certificate.signatureAlgorithm.algorithmId;
+      } else {
+        signatureAlgorithm = `${signatureAlgorithm}`;
+      }
+
+      for (const typeAndValue of issuer) {
+        let typeVal = rdnmap[typeAndValue.type];
+        if (typeof typeVal === "undefined") {
+          typeVal = typeAndValue.type;
+        }
+        const subjVal = typeAndValue.value.valueBlock.value;
+        certificateData.issuer[typeVal] = subjVal;
+      }
+
+      for (const typeAndValue of subject) {
+        let typeVal = rdnmap[typeAndValue.type];
+        if (typeof typeVal === "undefined") {
+          typeVal = typeAndValue.type;
+        }
+        const subjVal = typeAndValue.value.valueBlock.value;
+        certificateData.subject[typeVal] = subjVal;
+      }
+
+      certificateData.signatureAlgorithm = signatureAlgorithm;
+      certificateData.serialNumber = serialNumber;
+      certificateData.validity = {
+        notAfter,
+        notBefore
+      };
+
+      return certificateData;
+    });
+  } catch (e) {
+    console.error("Error parsing certificate", e);
+  }
+};
+
+export const getCertificateChain = signatureBase64 => {
+  const certificateChain = [];
+
+  try {
+    const certificates = parseCertificates(signatureBase64);
+
+    // Add first certificate in the chain
+    certificateChain.push(certificates[0]);
+
+    // Go through all certificates to build a chain from first certificate to the root
+    certificates.forEach(certificate => {
+      if (certificateChain[0].issuer.commonName === certificate.subject.commonName) {
+        certificateChain.unshift(certificate);
+      }
+    });
+  } catch (e) {
+    console.warn("Error getting certificate data", e);
+  }
+
+  return certificateChain;
+};
diff --git a/javascript/yarn.lock b/javascript/yarn.lock
index 78cbf18503b123887b2b4ff2da0c717160a9a320..e1ea2f56622300f72230db5db0cfa7c949639441 100644
--- a/javascript/yarn.lock
+++ b/javascript/yarn.lock
@@ -1478,6 +1478,10 @@ damerau-levenshtein@^1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz#03191c432cb6eea168bb77f3a55ffdccb8978514"
 
+data-uri-to-blob@^0.0.4:
+  version "0.0.4"
+  resolved "https://registry.yarnpkg.com/data-uri-to-blob/-/data-uri-to-blob-0.0.4.tgz#087a7bff42f41a6cc0b2e2fb7312a7c29904fbaa"
+
 date-now@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
@@ -2900,7 +2904,7 @@ libmime@^4.0.1:
     libbase64 "1.0.3"
     libqp "1.1.0"
 
-libqp@1.1.0:
+libqp@1.1.0, libqp@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/libqp/-/libqp-1.1.0.tgz#f5e6e06ad74b794fb5b5b66988bf728ef1dedbe8"