From 44a005e0e68e882a50d9bc061ca8daef4d84efa0 Mon Sep 17 00:00:00 2001 From: Thomas <> Date: Thu, 25 Mar 2021 15:24:48 +0100 Subject: add masking pattern to clear personal information from certificate logging --- moaSig/moa-sig-lib/build.gradle | 6 +- .../moa/spss/server/logging/IaikLog.java | 56 ++++++- .../moa/spss/logger/IaikLoggerMaskingTest.java | 182 +++++++++++++++++++++ .../moa/spss/logger/MemoryLoggingAppender.java | 56 +++++++ 4 files changed, 295 insertions(+), 5 deletions(-) create mode 100644 moaSig/moa-sig-lib/src/test/java/test/at/gv/egovernment/moa/spss/logger/IaikLoggerMaskingTest.java create mode 100644 moaSig/moa-sig-lib/src/test/java/test/at/gv/egovernment/moa/spss/logger/MemoryLoggingAppender.java diff --git a/moaSig/moa-sig-lib/build.gradle b/moaSig/moa-sig-lib/build.gradle index 9602ee1..4f13db2 100644 --- a/moaSig/moa-sig-lib/build.gradle +++ b/moaSig/moa-sig-lib/build.gradle @@ -13,7 +13,6 @@ dependencies { api fileTree(dir: '../libs', include: '*.jar') api group: 'at.gv.egovernment.moa.sig', name: 'tsl-lib', version: '2.0.4.1' - api 'log4j:log4j:1.2.17' api 'commons-logging:commons-logging:1.2' api 'commons-io:commons-io:2.8.0' api 'commons-codec:commons-codec:1.15' @@ -30,6 +29,11 @@ dependencies { api group: 'org.apache.pdfbox', name: 'preflight-app', version: '2.0.23' api group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0' api group: 'org.apache.httpcomponents', name: 'httpclient-cache', version: '4.5.13' + + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-migrationsupport', version: '5.7.1' + testImplementation group: 'org.junit.platform', name: 'junit-platform-engine', version: '1.7.1' + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.1' + testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' } task releases(type: Copy) { diff --git a/moaSig/moa-sig-lib/src/main/java/at/gv/egovernment/moa/spss/server/logging/IaikLog.java b/moaSig/moa-sig-lib/src/main/java/at/gv/egovernment/moa/spss/server/logging/IaikLog.java index f477588..e4a3921 100644 --- a/moaSig/moa-sig-lib/src/main/java/at/gv/egovernment/moa/spss/server/logging/IaikLog.java +++ b/moaSig/moa-sig-lib/src/main/java/at/gv/egovernment/moa/spss/server/logging/IaikLog.java @@ -23,6 +23,13 @@ package at.gv.egovernment.moa.spss.server.logging; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,6 +50,24 @@ public class IaikLog implements iaik.logging.Log { /** The node ID to use. */ private String nodeId; + public static final String X509_INFO_CLEARING_PATTERN = "(?!serialNumber)(=)(.*?)(,|\"|$)"; + + private static Pattern multilinePattern; + private static List maskPatterns = new ArrayList<>(); + + /** + * Add masking pattern into logger. + * + * @param maskPattern + */ + public static void addMaskPattern(String maskPattern) { + maskPatterns.add(maskPattern); + multilinePattern = Pattern.compile( + maskPatterns.stream() + .collect(Collectors.joining("|")), Pattern.MULTILINE + ); +} + /** * Create a new IaikLog. * @@ -83,7 +108,7 @@ public class IaikLog implements iaik.logging.Log { */ @Override public void info(TransactionId transactionId, Object message, Throwable t) { - final IaikLogMsg msg = new IaikLogMsg(transactionId, nodeId, message); + final IaikLogMsg msg = new IaikLogMsg(transactionId, nodeId, maskMessage(message)); log.info(msg.toString(), t); } @@ -101,7 +126,7 @@ public class IaikLog implements iaik.logging.Log { */ @Override public void warn(TransactionId transactionId, Object message, Throwable t) { - final IaikLogMsg msg = new IaikLogMsg(transactionId, nodeId, message); + final IaikLogMsg msg = new IaikLogMsg(transactionId, nodeId, maskMessage(message)); log.warn(msg.toString(), t); } @@ -119,7 +144,7 @@ public class IaikLog implements iaik.logging.Log { */ @Override public void error(TransactionId transactionId, Object message, Throwable t) { - final IaikLogMsg msg = new IaikLogMsg(transactionId, nodeId, message); + final IaikLogMsg msg = new IaikLogMsg(transactionId, nodeId, maskMessage(message)); log.error(msg.toString(), t); } @@ -137,7 +162,7 @@ public class IaikLog implements iaik.logging.Log { */ @Override public void fatal(TransactionId transactionId, Object message, Throwable t) { - final IaikLogMsg msg = new IaikLogMsg(transactionId, nodeId, message); + final IaikLogMsg msg = new IaikLogMsg(transactionId, nodeId, maskMessage(message)); log.error(msg.toString(), t); } @@ -158,4 +183,27 @@ public class IaikLog implements iaik.logging.Log { return nodeId; } + + private String maskMessage(Object message) { + String msg = message != null ? message.toString() : ""; + + if (multilinePattern == null) { + return msg; + + } + + StringBuilder sb = new StringBuilder(msg); + Matcher matcher = multilinePattern.matcher(sb); + while (matcher.find()) { + IntStream.rangeClosed(1, matcher.groupCount()).forEach( + group -> { + if (matcher.group(group) != null) { + IntStream.range(matcher.start(group), + matcher.end(group)).forEach(i -> sb.setCharAt(i, '*')); // replace each character with asterisk + } + }); + } + return sb.toString(); +} + } diff --git a/moaSig/moa-sig-lib/src/test/java/test/at/gv/egovernment/moa/spss/logger/IaikLoggerMaskingTest.java b/moaSig/moa-sig-lib/src/test/java/test/at/gv/egovernment/moa/spss/logger/IaikLoggerMaskingTest.java new file mode 100644 index 0000000..b3bf0e8 --- /dev/null +++ b/moaSig/moa-sig-lib/src/test/java/test/at/gv/egovernment/moa/spss/logger/IaikLoggerMaskingTest.java @@ -0,0 +1,182 @@ +package test.at.gv.egovernment.moa.spss.logger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.slf4j.LoggerFactory; + +import at.gv.egovernment.moa.spss.server.logging.IaikLog; +import at.gv.egovernment.moa.spss.server.logging.TransactionId; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; + +@RunWith(BlockJUnit4ClassRunner.class) +public class IaikLoggerMaskingTest { + + private IaikLog log; + private TransactionId transId; + + private MemoryLoggingAppender memoryAppender = null; + + private static final String LOGMSG_1 = + "Signature OK from signer: serialNumber=882486130371,givenName=XXXĤáčęk,SN=XXXMûstérfřău,CN=XXXĤáčęk XXXMûstérfřău,C=AT"; + + private static final String LOGMSG_2 = + "storing cert \"serialNumber=882486130371,givenName=XXXĤáčęk,SN=XXXMûstérfřău,CN=XXXĤáčęk XXXMûstérfřău,C=AT\" to: /data/eID/springboot-authhandler/config/moa-spss/certstore/subjectdn/518D25DA7380CF1967B5014DDB74E862E5E52827/9E1D7A221A7D1A522A9E169FA6F9A2E81EEAB643"; + + @Before + public void initialize() { + log = new IaikLog(RandomStringUtils.randomAlphabetic(5)); + transId = new TransactionId(RandomStringUtils.randomAlphanumeric(5)); + + // setup log appender + if (memoryAppender == null) { + final Logger logger = (Logger) LoggerFactory.getLogger("iaik.server"); + memoryAppender = new MemoryLoggingAppender(); + memoryAppender.setContext((LoggerContext) LoggerFactory.getILoggerFactory()); + logger.setLevel(Level.DEBUG); + logger.addAppender(memoryAppender); + memoryAppender.start(); + + } else { + memoryAppender.reset(); + + } + + } + + @Test + public void certificateMaskingInfoLevelMorePatterns() { + // patterns + IaikLog.addMaskPattern("(C=)(.*?)(,|$)"); + IaikLog.addMaskPattern("(CN=)(.*?)(,|$)"); + IaikLog.addMaskPattern("(SN=)(.*?)(,|$)"); + IaikLog.addMaskPattern("(serialNumber=)(.*?)(,|$)"); + IaikLog.addMaskPattern("(givenName=)(.*?)(,|$)"); + + //test + log.info(transId, LOGMSG_1, null); + + //verify log + verifyLogMessge(Arrays.asList("882486130371", "ûsté", "XĤáčę", "AT")); + + } + + @Test + public void certificateMaskingInfoLevelOnePattern() { + // Patterns + IaikLog.addMaskPattern(IaikLog.X509_INFO_CLEARING_PATTERN); + + //test + log.info(transId, LOGMSG_1, null); + + //verify log + verifyLogMessge(Arrays.asList("882486130371", "ûsté", "XĤáčę", "AT")); + + } + + @Test + public void certificateMaskingSecondMessage() { + // Patterns + IaikLog.addMaskPattern(IaikLog.X509_INFO_CLEARING_PATTERN); + + //test + log.info(transId, LOGMSG_2, null); + + //verify log + verifyLogMessge(Arrays.asList("882486130371", "ûsté", "XĤáčę", "AT")); + + } + + @Test + public void certificateMaskingWarnLevelMorePatterns() { + // patterns + IaikLog.addMaskPattern("(C=)(.*?)(,|$)"); + IaikLog.addMaskPattern("(CN=)(.*?)(,|$)"); + IaikLog.addMaskPattern("(SN=)(.*?)(,|$)"); + IaikLog.addMaskPattern("(serialNumber=)(.*?)(,|$)"); + IaikLog.addMaskPattern("(givenName=)(.*?)(,|$)"); + + //test + log.warn(transId, LOGMSG_1, null); + + //verify log + verifyLogMessge(Arrays.asList("882486130371", "ûsté", "XĤáčę", "AT")); + + } + + @Test + public void certificateMaskingWarnLevelOnePattern() { + // Patterns + IaikLog.addMaskPattern(IaikLog.X509_INFO_CLEARING_PATTERN); + + //test + log.warn(transId, LOGMSG_1, null); + + //verify log + verifyLogMessge(Arrays.asList("882486130371", "ûsté", "XĤáčę", "AT")); + + } + + + @Test + public void certificateMaskingErrorLevelOnePattern() { + // Patterns + IaikLog.addMaskPattern(IaikLog.X509_INFO_CLEARING_PATTERN); + + //test + log.error(transId, LOGMSG_1, null); + + //verify log + verifyLogMessge(Arrays.asList("882486130371", "ûsté", "XĤáčę", "AT")); + + } + + @Test + public void certificateMaskingFatalLevelOnePattern() { + // Patterns + IaikLog.addMaskPattern(IaikLog.X509_INFO_CLEARING_PATTERN); + + //test + log.fatal(transId, LOGMSG_1, null); + + //verify log + verifyLogMessge(Arrays.asList("882486130371", "ûsté", "XĤáčę", "AT")); + + } + + @Test + public void randomMessage() { + // Patterns + IaikLog.addMaskPattern(IaikLog.X509_INFO_CLEARING_PATTERN); + String msg = RandomStringUtils.randomAlphanumeric(25); + + //test + log.info(transId, msg, null); + + //verify log + Arrays.asList(msg) + .stream().forEach( + el -> assertTrue("find wrong element", memoryAppender.getLoggedEvents().get(0).getMessage().contains(el))); + + } + + + private void verifyLogMessge(List checks) { + assertEquals("no log", 1, memoryAppender.getSize()); + checks.stream().forEach( + el -> assertFalse("find wrong element", memoryAppender.getLoggedEvents().get(0).getMessage().contains(el))); + + } + +} diff --git a/moaSig/moa-sig-lib/src/test/java/test/at/gv/egovernment/moa/spss/logger/MemoryLoggingAppender.java b/moaSig/moa-sig-lib/src/test/java/test/at/gv/egovernment/moa/spss/logger/MemoryLoggingAppender.java new file mode 100644 index 0000000..e1c6fce --- /dev/null +++ b/moaSig/moa-sig-lib/src/test/java/test/at/gv/egovernment/moa/spss/logger/MemoryLoggingAppender.java @@ -0,0 +1,56 @@ +package test.at.gv.egovernment.moa.spss.logger; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; + +/** + * In-Memory Logging-Appender to check log messages. + * + * @author tlenz + * + */ +public class MemoryLoggingAppender extends ListAppender { + + public void reset() { + this.list.clear(); + } + + public boolean contains(String string, Level level) { + return this.list.stream() + .anyMatch(event -> event.getMessage().toString().contains(string) + && event.getLevel().equals(level)); + } + + public int countEventsForLogger(String loggerName) { + return (int) this.list.stream() + .filter(event -> event.getLoggerName().contains(loggerName)) + .count(); + } + + public List search(String string) { + return this.list.stream() + .filter(event -> event.getMessage().toString().contains(string)) + .collect(Collectors.toList()); + } + + public List search(String string, Level level) { + return this.list.stream() + .filter(event -> event.getMessage().toString().contains(string) + && event.getLevel().equals(level)) + .collect(Collectors.toList()); + } + + public int getSize() { + return this.list.size(); + } + + public List getLoggedEvents() { + return Collections.unmodifiableList(this.list); + } + +} -- cgit v1.2.3