From 3f901e9e18933f9477ac6fc625cbbfc898296d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20K=C3=B6rber?= Date: Wed, 21 Mar 2018 23:14:08 +0100 Subject: [PATCH] Strip empty lines from downloaded cert chains This fixes the "insufficient data" issue on IBMs crypto implementation. --- README.md | 1 + .../acme4j/connector/DefaultConnection.java | 2 +- .../acme4j/connector/TrimmingInputStream.java | 74 +++++++++++++++++++ .../connector/TrimmingInputStreamTest.java | 64 ++++++++++++++++ 4 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 acme4j-client/src/main/java/org/shredzone/acme4j/connector/TrimmingInputStream.java create mode 100644 acme4j-client/src/test/java/org/shredzone/acme4j/connector/TrimmingInputStreamTest.java diff --git a/README.md b/README.md index 4c9fcee3..f1a8b3b6 100644 --- a/README.md +++ b/README.md @@ -41,4 +41,5 @@ _acme4j_ is open source software. The source code is distributed under the terms * I would like to thank Brian Campbell and all the other [jose4j](https://bitbucket.org/b_c/jose4j/wiki/Home) developers. _acme4j_ would not exist without your excellent work. * Thanks to [Daniel McCarney](https://github.com/cpu) for his help with the ACME protocol, Pebble, and Boulder. +* [Ulrich Krause](https://github.com/eknori) for his help to make _acme4j_ run on IBM Java VMs. * I also like to thank [everyone who contributed to _acme4j_](https://github.com/shred/acme4j/graphs/contributors). diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/DefaultConnection.java b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/DefaultConnection.java index 1568383d..7a99f366 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/DefaultConnection.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/DefaultConnection.java @@ -233,7 +233,7 @@ public class DefaultConnection implements Connection { throw new AcmeProtocolException("Unexpected content type: " + contentType); } - try (InputStream in = conn.getInputStream()) { + try (InputStream in = new TrimmingInputStream(conn.getInputStream())) { CertificateFactory cf = CertificateFactory.getInstance("X.509"); return cf.generateCertificates(in).stream() .map(c -> (X509Certificate) c) diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/TrimmingInputStream.java b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/TrimmingInputStream.java new file mode 100644 index 00000000..de6f6ec6 --- /dev/null +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/TrimmingInputStream.java @@ -0,0 +1,74 @@ +/* + * acme4j - Java ACME client + * + * Copyright (C) 2018 Richard "Shred" Körber + * http://acme4j.shredzone.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * 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. + */ +package org.shredzone.acme4j.connector; + +import java.io.IOException; +import java.io.InputStream; + +import javax.annotation.ParametersAreNonnullByDefault; + +/** + * Normalizes line separators in an InputStream. Converts all line separators to '\n'. + * Multiple line separators are compressed to a single line separator. + */ +@ParametersAreNonnullByDefault +public class TrimmingInputStream extends InputStream { + + private final InputStream in; + private boolean wasLineSeparator = true; + + /** + * Creates a new {@link TrimmingInputStream}. + * + * @param in + * {@link InputStream} to read from. Will be closed when this stream is + * closed. + */ + public TrimmingInputStream(InputStream in) { + this.in = in; + } + + @Override + public int read() throws IOException { + int ch = in.read(); + + if (wasLineSeparator) { + while (isLineSeparator(ch)) { + ch = in.read(); + } + } + + wasLineSeparator = isLineSeparator(ch); + + if (ch == '\r') { + ch = '\n'; + } + + return ch; + } + + @Override + public void close() throws IOException { + in.close(); + super.close(); + } + + /** + * Checks if the character is a line separator. + */ + private static boolean isLineSeparator(int ch) { + return ch == '\n' || ch == '\r'; + } + +} diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/TrimmingInputStreamTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/TrimmingInputStreamTest.java new file mode 100644 index 00000000..a38228d4 --- /dev/null +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/TrimmingInputStreamTest.java @@ -0,0 +1,64 @@ +/* + * acme4j - Java ACME client + * + * Copyright (C) 2018 Richard "Shred" Körber + * http://acme4j.shredzone.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * 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. + */ +package org.shredzone.acme4j.connector; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.junit.Test; + +/** + * Unit tests for {@link TrimmingInputStream}. + */ +public class TrimmingInputStreamTest { + + @Test + public void testEmpty() throws IOException { + String out = trimByStream(""); + assertThat(out, is("")); + } + + @Test + public void testTrim() throws IOException { + String out = trimByStream("\n\n" + + "Gallia est omnis divisa in partes tres,\r\n\r\n\r\n" + + "quarum unam incolunt Belgae, aliam Aquitani,\r\r\r\n\n" + + "tertiam, qui ipsorum lingua Celtae, nostra Galli appellantur."); + assertThat(out, is("Gallia est omnis divisa in partes tres,\n" + + "quarum unam incolunt Belgae, aliam Aquitani,\n" + + "tertiam, qui ipsorum lingua Celtae, nostra Galli appellantur.")); + } + + /** + * Trims a string by running it through the {@link TrimmingInputStream}. + */ + private String trimByStream(String str) throws IOException { + StringBuilder out = new StringBuilder(); + + try (TrimmingInputStream in = new TrimmingInputStream( + new ByteArrayInputStream(str.getBytes(StandardCharsets.US_ASCII)))) { + int ch; + while ((ch = in.read()) >= 0) { + out.append((char) ch); + } + } + + return out.toString(); + } + +}