]> nmode's Git Repositories - signal-cli/commitdiff
Add --scrub-log flag to remove possibly sensitive information from the log
authorAsamK <asamk@gmx.de>
Sun, 4 Sep 2022 07:48:53 +0000 (09:48 +0200)
committerAsamK <asamk@gmx.de>
Sun, 4 Sep 2022 09:15:23 +0000 (11:15 +0200)
man/signal-cli.1.adoc
src/main/java/org/asamk/signal/App.java
src/main/java/org/asamk/signal/Main.java
src/main/java/org/asamk/signal/logging/LogConfigurator.java [moved from src/main/java/org/asamk/signal/LogConfigurator.java with 80% similarity]
src/main/java/org/asamk/signal/logging/Scrubber.java [new file with mode: 0644]
src/main/java/org/asamk/signal/logging/ScrubberPatternLayout.java [new file with mode: 0644]
src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator

index 025ee7ffc3d71dd9313a5b819c1756eef63e365f..f1e9e79ad5b203e266341e2845493f1da16084c3 100644 (file)
@@ -41,6 +41,9 @@ Raise log level and include lib signal logs.
 Write log output to the given file.
 If `--verbose` is also given, the detailed logs will only be written to the log file.
 
+*--scrub-log*::
+Scrub possibly sensitive information from the log, like phone numbers and UUIDs.
+
 *--config* CONFIG::
 Set the path, where to store the config.
 Make sure you have full read/write access to the given directory.
@@ -230,7 +233,8 @@ Read the message from standard input.
 
 *-a* [ATTACHMENT [ATTACHMENT ...]], *--attachment* [ATTACHMENT [ATTACHMENT ...]]::
 Add one or more files as attachment.
-Can be either a file path or a data URI. Data URI encoded attachments must follow the RFC 2397.
+Can be either a file path or a data URI.
+Data URI encoded attachments must follow the RFC 2397.
 Additionally a file name can be added:
 e.g.: `data:<MIME-TYPE>;filename=<FILENAME>;base64,<BASE64 ENCODED DATA>`
 
@@ -257,8 +261,7 @@ Specify the mentions of the original message (same format as `--mention`).
 
 *--preview-url*::
 Specify the url for the link preview.
-The same url must also appear in the message body, otherwise the preview won't be
-displayed by the apps.
+The same url must also appear in the message body, otherwise the preview won't be displayed by the apps.
 
 *--preview-title*::
 Specify the title for the link preview (mandatory).
index 4daaa815cb706679dd3b9c6de139bf00df900d50..4f045345b62e0d22294aad19e573b749641cd273 100644 (file)
@@ -71,6 +71,9 @@ public class App {
         parser.addArgument("--log-file")
                 .type(File.class)
                 .help("Write log output to the given file. If --verbose is also given, the detailed logs will only be written to the log file.");
+        parser.addArgument("--scrub-log")
+                .action(Arguments.storeTrue())
+                .help("Scrub possibly sensitive information from the log, like phone numbers and UUIDs.");
         parser.addArgument("-c", "--config")
                 .help("Set the path, where to store the config (Default: $XDG_DATA_HOME/signal-cli , $HOME/.local/share/signal-cli).");
 
index 3da4fbcba6459e2628d687d8e6d240c8bf4c33e9..429cdc939199ae59403e12bd1da65fb9b2ef44bb 100644 (file)
@@ -27,6 +27,7 @@ import org.asamk.signal.commands.exceptions.IOErrorException;
 import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
 import org.asamk.signal.commands.exceptions.UntrustedKeyErrorException;
 import org.asamk.signal.commands.exceptions.UserErrorException;
+import org.asamk.signal.logging.LogConfigurator;
 import org.asamk.signal.manager.ManagerLogger;
 import org.asamk.signal.util.SecurityProvider;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
@@ -47,7 +48,8 @@ public class Main {
         final var nsLog = parseArgs(args);
         final var verboseLevel = nsLog == null ? 0 : nsLog.getInt("verbose");
         final var logFile = nsLog == null ? null : nsLog.<File>get("log-file");
-        configureLogging(verboseLevel, logFile);
+        final var scrubLog = nsLog != null && nsLog.getBoolean("scrub-log");
+        configureLogging(verboseLevel, logFile, scrubLog);
 
         var parser = App.buildArgumentParser();
 
@@ -82,6 +84,7 @@ public class Main {
                 .defaultHelp(false);
         parser.addArgument("-v", "--verbose").action(Arguments.count());
         parser.addArgument("--log-file").type(File.class);
+        parser.addArgument("--scrub-log").action(Arguments.storeTrue());
 
         try {
             return parser.parseKnownArgs(args, null);
@@ -90,9 +93,10 @@ public class Main {
         }
     }
 
-    private static void configureLogging(final int verboseLevel, final File logFile) {
+    private static void configureLogging(final int verboseLevel, final File logFile, final boolean scrubLog) {
         LogConfigurator.setVerboseLevel(verboseLevel);
         LogConfigurator.setLogFile(logFile);
+        LogConfigurator.setScrubSensitiveInformation(scrubLog);
 
         if (verboseLevel > 0) {
             java.util.logging.Logger.getLogger("")
similarity index 80%
rename from src/main/java/org/asamk/signal/LogConfigurator.java
rename to src/main/java/org/asamk/signal/logging/LogConfigurator.java
index 714ec4a133ec67b9e99007581332bfeba016399e..39b2315b23cbf812f4bba30a0729c78b49dde020 100644 (file)
@@ -1,4 +1,4 @@
-package org.asamk.signal;
+package org.asamk.signal.logging;
 
 import java.io.File;
 
@@ -20,6 +20,7 @@ public class LogConfigurator extends ContextAwareBase implements Configurator {
 
     private static int verboseLevel = 0;
     private static File logFile = null;
+    private static boolean scrubSensitiveInformation = false;
 
     public static void setVerboseLevel(int verboseLevel) {
         LogConfigurator.verboseLevel = verboseLevel;
@@ -29,6 +30,10 @@ public class LogConfigurator extends ContextAwareBase implements Configurator {
         LogConfigurator.logFile = logFile;
     }
 
+    public static void setScrubSensitiveInformation(final boolean scrubSensitiveInformation) {
+        LogConfigurator.scrubSensitiveInformation = scrubSensitiveInformation;
+    }
+
     public ExecutionStatus configure(LoggerContext lc) {
         final var rootLogger = lc.getLogger(Logger.ROOT_LOGGER_NAME);
 
@@ -98,18 +103,26 @@ public class LogConfigurator extends ContextAwareBase implements Configurator {
     }
 
     private PatternLayout createSimpleLoggingLayout(final LoggerContext lc) {
-        return new PatternLayout() {{
-            setPattern("%-5level %logger{0} - %msg%n");
-            setContext(lc);
-            start();
-        }};
+        final var patternLayout = getPatternLayout();
+        patternLayout.setPattern("%-5level %logger{0} - %msg%n");
+        patternLayout.setContext(lc);
+        patternLayout.start();
+        return patternLayout;
     }
 
     private PatternLayout createDetailedLoggingLayout(final LoggerContext lc) {
-        return new PatternLayout() {{
-            setPattern("%d{yyyy-MM-dd'T'HH:mm:ss.SSSXX} [%thread] %-5level %logger{36} - %msg%n");
-            setContext(lc);
-            start();
-        }};
+        final var patternLayout = getPatternLayout();
+        patternLayout.setPattern("%d{yyyy-MM-dd'T'HH:mm:ss.SSSXX} [%thread] %-5level %logger{36} - %msg%n");
+        patternLayout.setContext(lc);
+        patternLayout.start();
+        return patternLayout;
+    }
+
+    private PatternLayout getPatternLayout() {
+        if (scrubSensitiveInformation) {
+            return new ScrubberPatternLayout();
+        } else {
+            return new PatternLayout();
+        }
     }
 }
diff --git a/src/main/java/org/asamk/signal/logging/Scrubber.java b/src/main/java/org/asamk/signal/logging/Scrubber.java
new file mode 100644 (file)
index 0000000..efa196c
--- /dev/null
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2014 Open Whisper Systems
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.asamk.signal.logging;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Scrub data for possibly sensitive information.
+ */
+public final class Scrubber {
+
+    private Scrubber() {
+    }
+
+    /**
+     * The middle group will be censored.
+     * Supposedly, the shortest international phone numbers in use contain seven digits.
+     * Handles URL encoded +, %2B
+     */
+    private static final Pattern E164_PATTERN = Pattern.compile("(\\+|%2B)(\\d{5,13})(\\d{2})");
+    private static final String E164_CENSOR = "*************";
+
+    /**
+     * The second group will be censored.
+     */
+    private static final Pattern CRUDE_EMAIL_PATTERN = Pattern.compile("\\b([^\\s/])([^\\s/]*@[^\\s]+)");
+    private static final String EMAIL_CENSOR = "...@...";
+
+    /**
+     * The middle group will be censored.
+     */
+    private static final Pattern UUID_PATTERN = Pattern.compile(
+            "(JOB::)?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{10})([0-9a-f]{2})",
+            Pattern.CASE_INSENSITIVE);
+    private static final String UUID_CENSOR = "********-****-****-****-**********";
+
+    /**
+     * The entire string is censored.
+     */
+    private static final Pattern IPV4_PATTERN = Pattern.compile("\\b"
+            + "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\."
+            + "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\."
+            + "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\."
+            + "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"
+            + "\\b");
+    private static final String IPV4_CENSOR = "...ipv4...";
+
+    /**
+     * The domain name except for TLD will be censored.
+     */
+    private static final Pattern DOMAIN_PATTERN = Pattern.compile("([a-z0-9]+\\.)+([a-z0-9\\-]*[a-z\\-][a-z0-9\\-]*)",
+            Pattern.CASE_INSENSITIVE);
+    private static final String DOMAIN_CENSOR = "***.";
+    private static final Set<String> TOP_100_TLDS = new HashSet<>(Arrays.asList("com",
+            "net",
+            "org",
+            "jp",
+            "de",
+            "uk",
+            "fr",
+            "br",
+            "it",
+            "ru",
+            "es",
+            "me",
+            "gov",
+            "pl",
+            "ca",
+            "au",
+            "cn",
+            "co",
+            "in",
+            "nl",
+            "edu",
+            "info",
+            "eu",
+            "ch",
+            "id",
+            "at",
+            "kr",
+            "cz",
+            "mx",
+            "be",
+            "tv",
+            "se",
+            "tr",
+            "tw",
+            "al",
+            "ua",
+            "ir",
+            "vn",
+            "cl",
+            "sk",
+            "ly",
+            "cc",
+            "to",
+            "no",
+            "fi",
+            "us",
+            "pt",
+            "dk",
+            "ar",
+            "hu",
+            "tk",
+            "gr",
+            "il",
+            "news",
+            "ro",
+            "my",
+            "biz",
+            "ie",
+            "za",
+            "nz",
+            "sg",
+            "ee",
+            "th",
+            "io",
+            "xyz",
+            "pe",
+            "bg",
+            "hk",
+            "lt",
+            "link",
+            "ph",
+            "club",
+            "si",
+            "site",
+            "mobi",
+            "by",
+            "cat",
+            "wiki",
+            "la",
+            "ga",
+            "xxx",
+            "cf",
+            "hr",
+            "ng",
+            "jobs",
+            "online",
+            "kz",
+            "ug",
+            "gq",
+            "ae",
+            "is",
+            "lv",
+            "pro",
+            "fm",
+            "tips",
+            "ms",
+            "sa",
+            "app"));
+
+    public static CharSequence scrub(CharSequence in) {
+
+        in = scrubE164(in);
+        in = scrubEmail(in);
+        in = scrubUuids(in);
+        in = scrubDomains(in);
+        in = scrubIpv4(in);
+
+        return in;
+    }
+
+    private static CharSequence scrubE164(CharSequence in) {
+        return scrub(in,
+                E164_PATTERN,
+                (matcher, output) -> output.append(matcher.group(1))
+                        .append(E164_CENSOR, 0, matcher.group(2).length())
+                        .append(matcher.group(3)));
+    }
+
+    private static CharSequence scrubEmail(CharSequence in) {
+        return scrub(in,
+                CRUDE_EMAIL_PATTERN,
+                (matcher, output) -> output.append(matcher.group(1)).append(EMAIL_CENSOR));
+    }
+
+    private static CharSequence scrubUuids(CharSequence in) {
+        return scrub(in, UUID_PATTERN, (matcher, output) -> {
+            if (matcher.group(1) != null && !matcher.group(1).isEmpty()) {
+                output.append(matcher.group(1)).append(matcher.group(2)).append(matcher.group(3));
+            } else {
+                output.append(UUID_CENSOR).append(matcher.group(3));
+            }
+        });
+    }
+
+    private static CharSequence scrubDomains(CharSequence in) {
+        return scrub(in, DOMAIN_PATTERN, (matcher, output) -> {
+            String match = matcher.group(0);
+            if (matcher.groupCount() == 2
+                    && TOP_100_TLDS.contains(matcher.group(2).toLowerCase(Locale.US))
+                    && !match.endsWith("whispersystems.org")
+                    && !match.endsWith("signal.org")) {
+                output.append(DOMAIN_CENSOR).append(matcher.group(2));
+            } else {
+                output.append(match);
+            }
+        });
+    }
+
+    private static CharSequence scrubIpv4(CharSequence in) {
+        return scrub(in, IPV4_PATTERN, (matcher, output) -> output.append(IPV4_CENSOR));
+    }
+
+    private static CharSequence scrub(
+            CharSequence in, Pattern pattern, ProcessMatch processMatch
+    ) {
+        final StringBuilder output = new StringBuilder(in.length());
+        final Matcher matcher = pattern.matcher(in);
+
+        int lastEndingPos = 0;
+
+        while (matcher.find()) {
+            output.append(in, lastEndingPos, matcher.start());
+
+            processMatch.scrubMatch(matcher, output);
+
+            lastEndingPos = matcher.end();
+        }
+
+        if (lastEndingPos == 0) {
+            // there were no matches, save copying all the data
+            return in;
+        } else {
+            output.append(in, lastEndingPos, in.length());
+
+            return output;
+        }
+    }
+
+    private interface ProcessMatch {
+
+        void scrubMatch(Matcher matcher, StringBuilder output);
+    }
+}
diff --git a/src/main/java/org/asamk/signal/logging/ScrubberPatternLayout.java b/src/main/java/org/asamk/signal/logging/ScrubberPatternLayout.java
new file mode 100644 (file)
index 0000000..b15af5a
--- /dev/null
@@ -0,0 +1,12 @@
+package org.asamk.signal.logging;
+
+import ch.qos.logback.classic.PatternLayout;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+
+public class ScrubberPatternLayout extends PatternLayout {
+
+    @Override
+    public String doLayout(ILoggingEvent event) {
+        return Scrubber.scrub(super.doLayout(event)).toString();
+    }
+}
index 354cf5bd29f3a2257aa1e10c5084541b2ecaa171..6e92cd393fab9087e6df761da54babffb9a51c17 100644 (file)
@@ -1 +1 @@
-org.asamk.signal.LogConfigurator
+org.asamk.signal.logging.LogConfigurator