diff --git a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultHtmlExporter.java b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultHtmlExporter.java new file mode 100644 index 00000000..7339a7e3 --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultHtmlExporter.java @@ -0,0 +1,109 @@ +package com.beemdevelopment.aegis.vault; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.util.Base64; + +import com.beemdevelopment.aegis.R; +import com.beemdevelopment.aegis.encoding.Base32; +import com.beemdevelopment.aegis.encoding.Hex; +import com.beemdevelopment.aegis.helpers.QrCodeHelper; +import com.beemdevelopment.aegis.otp.GoogleAuthInfo; +import com.beemdevelopment.aegis.otp.HotpInfo; +import com.beemdevelopment.aegis.otp.MotpInfo; +import com.beemdevelopment.aegis.otp.OtpInfo; +import com.beemdevelopment.aegis.otp.YandexInfo; +import com.google.common.html.HtmlEscapers; +import com.google.zxing.WriterException; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Collection; + +public class VaultHtmlExporter { + private VaultHtmlExporter() { + + } + + public static void export(Context context, PrintStream ps, Collection entries) throws WriterException, IOException { + ps.print(""); + ps.print(context.getString(R.string.export_html_title)); + ps.print(""); + ps.print("

"); + ps.print(context.getString(R.string.export_html_title)); + ps.print("

"); + ps.print(""); + ps.print(""); + ps.print(""); + ps.print(""); + ps.print(""); + ps.print(""); + ps.print(""); + ps.print(""); + ps.print(""); + ps.print(""); + ps.print(""); + ps.print(""); + ps.print(""); + ps.print(""); + ps.print(""); + for (VaultEntry entry : entries) { + ps.print(""); + OtpInfo info = entry.getInfo(); + GoogleAuthInfo gaInfo = new GoogleAuthInfo(info, entry.getName(), entry.getIssuer()); + appendRow(ps, entry.getIssuer()); + appendRow(ps, entry.getName()); + appendRow(ps, info.getType()); + appendQrRow(ps, gaInfo.getUri().toString()); + appendRow(ps, entry.getUUID().toString()); + appendRow(ps, entry.getNote()); + appendRow(ps, Boolean.toString(entry.isFavorite())); + appendRow(ps, info.getAlgorithm(false)); + appendRow(ps, Integer.toString(info.getDigits())); + if (info instanceof MotpInfo) { + appendRow(ps, Hex.encode(info.getSecret())); + } else { + appendRow(ps, Base32.encode(info.getSecret())); + } + if (info instanceof HotpInfo) { + appendRow(ps, Long.toString(((HotpInfo) info).getCounter())); + } else { + appendRow(ps, "-"); + } + if (info instanceof YandexInfo) { + appendRow(ps, ((YandexInfo) info).getPin()); + } else if (info instanceof MotpInfo) { + appendRow(ps, ((MotpInfo) info).getPin()); + } else { + appendRow(ps, "-"); + } + ps.print(""); + } + ps.print("
IssuerNameTypeQR CodeUUIDNoteFavoriteAlgoDigitsSecretCounterPIN
"); + ps.print(""); + ps.print(""); + } + + private static void appendRow(PrintStream ps, String s) { + ps.print(""); + ps.print(escape(s)); + ps.print(""); + } + + private static void appendQrRow(PrintStream ps, String s) throws IOException, WriterException { + ps.print(""); + } + + private static String escape(String s) { + return HtmlEscapers.htmlEscaper().escape(s); + } +} diff --git a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultRepository.java b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultRepository.java index 3aa5af4f..6b96fe79 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultRepository.java +++ b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultRepository.java @@ -1,28 +1,18 @@ package com.beemdevelopment.aegis.vault; import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.util.Base64; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.util.AtomicFile; -import com.beemdevelopment.aegis.R; -import com.beemdevelopment.aegis.encoding.Base32; -import com.beemdevelopment.aegis.helpers.QrCodeHelper; import com.beemdevelopment.aegis.otp.GoogleAuthInfo; -import com.beemdevelopment.aegis.otp.HotpInfo; -import com.beemdevelopment.aegis.otp.OtpInfo; import com.beemdevelopment.aegis.util.IOUtils; -import com.google.common.html.HtmlEscapers; import com.google.zxing.WriterException; import org.json.JSONObject; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -32,9 +22,9 @@ import java.io.PrintStream; import java.nio.charset.StandardCharsets; import java.text.Collator; import java.util.Collection; -import java.util.Objects; import java.util.TreeSet; import java.util.UUID; +import java.util.stream.Collectors; public class VaultRepository { public static final String FILENAME = "aegis.json"; @@ -215,82 +205,15 @@ public class VaultRepository { * Username and QR Code and writing it to the given OutputStream. */ public void exportHtml(OutputStream outStream, @Nullable Vault.EntryFilter filter) throws VaultRepositoryException { - try { - PrintStream printStream = new PrintStream(outStream, false, StandardCharsets.UTF_8.name()); - printStream.print(""); - printStream.print(_context.getString(R.string.export_html_title)); - printStream.print(""); - printStream.print("

"); - printStream.print(_context.getString(R.string.export_html_title)); - printStream.print("

"); - printStream.print(""); - printStream.print(""); - printStream.print(""); - printStream.print(""); - printStream.print(""); - printStream.print(""); - printStream.print(""); - printStream.print(""); - printStream.print(""); - printStream.print(""); - printStream.print(""); - printStream.print(""); - printStream.print(""); - printStream.print(""); - for (VaultEntry entry : getEntries()) { - if (filter == null || filter.includeEntry(entry)) { - printStream.print(""); - GoogleAuthInfo info = new GoogleAuthInfo(entry.getInfo(), entry.getName(), entry.getIssuer()); - OtpInfo otpInfo = info.getOtpInfo(); - printStream.print(""); - printStream.print(""); - printStream.print(""); - Bitmap bm = QrCodeHelper.encodeToBitmap(info.getUri().toString(),256, 256, Color.WHITE); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - bm.compress(Bitmap.CompressFormat.PNG, 100, baos); - byte[] b = baos.toByteArray(); - String encodedImage = Base64.encodeToString(b, Base64.DEFAULT); - printStream.print(""); - printStream.print(""); - printStream.print(""); - printStream.print(""); - printStream.print(""); - printStream.print(""); - printStream.print(""); - printStream.print(""); - printStream.print(""); - } - }; - printStream.print("
IssuerUsernameTypeQR CodeUUIDNoteFavoriteAlgoDigitsSecretCounter
"); - printStream.print(HtmlEscapers.htmlEscaper().escape(info.getIssuer())); - printStream.print(""); - printStream.print(HtmlEscapers.htmlEscaper().escape(entry.getName())); - printStream.print(""); - printStream.print(HtmlEscapers.htmlEscaper().escape(otpInfo.getType())); - printStream.print(""); - printStream.print(HtmlEscapers.htmlEscaper().escape(entry.getUUID().toString())); - printStream.print(""); - printStream.print(HtmlEscapers.htmlEscaper().escape(entry.getNote())); - printStream.print(""); - printStream.print(HtmlEscapers.htmlEscaper().escape(entry.isFavorite() ? "true" : "false")); - printStream.print(""); - printStream.print(HtmlEscapers.htmlEscaper().escape(otpInfo.getAlgorithm(false))); - printStream.print(""); - printStream.print(HtmlEscapers.htmlEscaper().escape(Integer.toString(otpInfo.getDigits()))); - printStream.print(""); - printStream.print(HtmlEscapers.htmlEscaper().escape(Base32.encode(otpInfo.getSecret()))); - printStream.print(""); - if (Objects.equals(otpInfo.getTypeId(), HotpInfo.ID)) { - printStream.print(HtmlEscapers.htmlEscaper().escape(Long.toString(((HotpInfo) otpInfo).getCounter()))); - } else { - printStream.print("-"); - } - printStream.print("
"); - printStream.print(""); - printStream.print(""); - printStream.flush(); + Collection entries = getEntries(); + if (filter != null) { + entries = entries.stream() + .filter(filter::includeEntry) + .collect(Collectors.toList()); + } + + try (PrintStream ps = new PrintStream(outStream, false, StandardCharsets.UTF_8.name())) { + VaultHtmlExporter.export(_context, ps, entries); } catch (WriterException | IOException e) { throw new VaultRepositoryException(e); } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dfd08c9f..41c029ac 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -110,7 +110,7 @@ Export all groups Select which groups to export: No groups selected to export - Aegis Authenticator Export + Aegis Authenticator Export Security Aegis is a security-focused 2FA app. Tokens are stored in a vault, that can optionally be encrypted with a password of your choosing. If an attacker obtains your encrypted vault file, they will not be able to access the contents without knowing the password.\n\nWe\'ve preselected the option that we think would fit best for your device.