Skip to content

Commit

Permalink
Add --scrub-log flag to remove possibly sensitive information from th…
Browse files Browse the repository at this point in the history
…e log
  • Loading branch information
AsamK committed Sep 4, 2022
1 parent 2e8e81a commit 1d77153
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 17 deletions.
9 changes: 6 additions & 3 deletions man/signal-cli.1.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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>`

Expand All @@ -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).
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/asamk/signal/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ static ArgumentParser buildArgumentParser() {
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).");

Expand Down
8 changes: 6 additions & 2 deletions src/main/java/org/asamk/signal/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
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;
Expand All @@ -47,7 +48,8 @@ public static void main(String[] args) {
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();

Expand Down Expand Up @@ -82,6 +84,7 @@ private static Namespace parseArgs(String[] args) {
.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);
Expand All @@ -90,9 +93,10 @@ private static Namespace parseArgs(String[] args) {
}
}

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("")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.asamk.signal;
package org.asamk.signal.logging;

import java.io.File;

Expand All @@ -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;
Expand All @@ -29,6 +30,10 @@ public static void setLogFile(File logFile) {
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);

Expand Down Expand Up @@ -98,18 +103,26 @@ private LayoutWrappingEncoder<ILoggingEvent> createLayoutWrappingEncoder(final L
}

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();
}
}
}
256 changes: 256 additions & 0 deletions src/main/java/org/asamk/signal/logging/Scrubber.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading

0 comments on commit 1d77153

Please sign in to comment.