From c5c71eaf99639a00ab2d0fba5b6e1c6f17659f47 Mon Sep 17 00:00:00 2001 From: danthe1st Date: Sun, 10 Mar 2024 11:47:28 +0100 Subject: [PATCH] checkerframework --- .gitignore | 1 + pom.xml | 38 +++++++++++++++- .../httpsintercept/config/Config.java | 7 ++- .../config/HostMatcherConfig.java | 7 ++- .../handler/ServerHandlersInit.java | 3 +- .../http/HttpResponseContentAccessor.java | 11 +++-- .../raw/RawForwardIncomingRequestHandler.java | 3 +- .../handler/sni/SNIHandlerMapping.java | 18 +++++--- .../handler/sni/SslContextCacheEntry.java | 3 +- .../matcher/FilterIterator.java | 15 ++++--- .../httpsintercept/matcher/HostMatcher.java | 43 ++++++++++--------- .../httpsintercept/rules/ProcessingRule.java | 2 + 12 files changed, 107 insertions(+), 44 deletions(-) diff --git a/.gitignore b/.gitignore index c925067..fdb68f0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.pem .secret intercept.yaml +.classes.jsa # Created by https://www.toptal.com/developers/gitignore/api/eclipse,maven,java,intellij+all # Edit at https://www.toptal.com/developers/gitignore?templates=eclipse,maven,java,intellij+all diff --git a/pom.xml b/pom.xml index 58e516f..e68cc01 100644 --- a/pom.xml +++ b/pom.xml @@ -10,6 +10,7 @@ 2.0.12 UTF-8 1.5.1 + 3.42.0 @@ -47,6 +48,12 @@ jsoup 1.17.2 + + org.checkerframework + checker-qual + ${checkerframework.version} + provided + org.junit.jupiter junit-jupiter-api @@ -63,6 +70,34 @@ 3.12.1 21 + true + + + org.checkerframework + checker + ${checkerframework.version} + + + + org.checkerframework.checker.nullness.NullnessChecker + + + -J-XX:+AutoCreateSharedArchive + -J-XX:SharedArchiveFile=.classes.jsa + -Xmaxerrs + 10000 + -Xmaxwarns + 10000 + -J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED + @@ -116,8 +151,7 @@ ${native-plugin-version} true - + -O3 --gc=G1 --strict-image-heap diff --git a/src/main/java/io/github/danthe1st/httpsintercept/config/Config.java b/src/main/java/io/github/danthe1st/httpsintercept/config/Config.java index e5c9ca5..4ffd564 100644 --- a/src/main/java/io/github/danthe1st/httpsintercept/config/Config.java +++ b/src/main/java/io/github/danthe1st/httpsintercept/config/Config.java @@ -11,6 +11,9 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import io.github.danthe1st.httpsintercept.rules.PostForwardRule; import io.github.danthe1st.httpsintercept.rules.PreForwardRule; +import org.checkerframework.checker.initialization.qual.UnderInitialization; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; public record Config( HostMatcherConfig ignoredHosts, @@ -18,7 +21,7 @@ public record Config( List postForwardRules ) { - public Config(HostMatcherConfig ignoredHosts, List preForwardRules, List postForwardRules) { + public Config(@Nullable HostMatcherConfig ignoredHosts, @Nullable List preForwardRules, @Nullable List postForwardRules) { if(ignoredHosts == null){ ignoredHosts = new HostMatcherConfig(null, null, null); } @@ -27,7 +30,7 @@ public Config(HostMatcherConfig ignoredHosts, List preForwardRul this.postForwardRules = emptyIfNull(postForwardRules); } - private List emptyIfNull(List preForwardRules) { + private <@NonNull T> List emptyIfNull(@UnderInitialization Config this, @Nullable List preForwardRules) { if(preForwardRules == null){ return Collections.emptyList(); } diff --git a/src/main/java/io/github/danthe1st/httpsintercept/config/HostMatcherConfig.java b/src/main/java/io/github/danthe1st/httpsintercept/config/HostMatcherConfig.java index cf877cd..853f99d 100644 --- a/src/main/java/io/github/danthe1st/httpsintercept/config/HostMatcherConfig.java +++ b/src/main/java/io/github/danthe1st/httpsintercept/config/HostMatcherConfig.java @@ -3,17 +3,20 @@ import java.util.Collections; import java.util.Set; +import org.checkerframework.checker.initialization.qual.UnderInitialization; +import org.checkerframework.checker.nullness.qual.Nullable; + public record HostMatcherConfig(Set exact, Set partial, Set regex) { - public HostMatcherConfig(Set exact, Set partial, Set regex) { + public HostMatcherConfig(@Nullable Set exact, @Nullable Set partial, @Nullable Set regex) { this.exact = emptyIfNull(exact); this.partial = emptyIfNull(partial); this.regex = emptyIfNull(regex); } - private Set emptyIfNull(Set data) { + private Set emptyIfNull(@UnderInitialization HostMatcherConfig this, @Nullable Set data) { if(data == null){ return Collections.emptySet(); } diff --git a/src/main/java/io/github/danthe1st/httpsintercept/handler/ServerHandlersInit.java b/src/main/java/io/github/danthe1st/httpsintercept/handler/ServerHandlersInit.java index a52a520..14ba3c2 100644 --- a/src/main/java/io/github/danthe1st/httpsintercept/handler/ServerHandlersInit.java +++ b/src/main/java/io/github/danthe1st/httpsintercept/handler/ServerHandlersInit.java @@ -24,6 +24,7 @@ import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.ssl.SniHandler; +import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,7 +48,7 @@ public ServerHandlersInit(Bootstrap clientBootstrap, Config config) throws KeySt ignoredHostMatcher = new HostMatcher<>(List.of(Map.entry(config.ignoredHosts(), new Object())), false); } - private HostMatcher createMatcherFromRules(List ruleList) { + private HostMatcher createMatcherFromRules(@UnderInitialization ServerHandlersInit this, List ruleList) { List> rules = new ArrayList<>(); for(T rule : ruleList){ HostMatcherConfig hostMatcher = rule.hostMatcher(); diff --git a/src/main/java/io/github/danthe1st/httpsintercept/handler/http/HttpResponseContentAccessor.java b/src/main/java/io/github/danthe1st/httpsintercept/handler/http/HttpResponseContentAccessor.java index c9664b4..6a65b13 100644 --- a/src/main/java/io/github/danthe1st/httpsintercept/handler/http/HttpResponseContentAccessor.java +++ b/src/main/java/io/github/danthe1st/httpsintercept/handler/http/HttpResponseContentAccessor.java @@ -5,10 +5,12 @@ import io.netty.buffer.ByteBuf; import io.netty.handler.codec.http.FullHttpResponse; import org.bouncycastle.util.Arrays; +import org.checkerframework.checker.nullness.qual.EnsuresNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; public class HttpResponseContentAccessor { private ByteBuf contentBuf; - private byte[] bytes; + private byte @Nullable [] bytes; public HttpResponseContentAccessor(FullHttpResponse res) { contentBuf = res.content(); @@ -24,15 +26,18 @@ public String getAsString() { return new String(bytes, StandardCharsets.UTF_8); } + @EnsuresNonNull("bytes") private void ensureBytesPresent() { if(bytes == null){ + byte[] b; ByteBuf buf = contentBuf.copy(); try{ - bytes = new byte[buf.readableBytes()]; - buf.readBytes(bytes); + b = new byte[buf.readableBytes()]; + buf.readBytes(b); }finally{ buf.release(); } + bytes = b; } } } \ No newline at end of file diff --git a/src/main/java/io/github/danthe1st/httpsintercept/handler/raw/RawForwardIncomingRequestHandler.java b/src/main/java/io/github/danthe1st/httpsintercept/handler/raw/RawForwardIncomingRequestHandler.java index 316ad74..4e68534 100644 --- a/src/main/java/io/github/danthe1st/httpsintercept/handler/raw/RawForwardIncomingRequestHandler.java +++ b/src/main/java/io/github/danthe1st/httpsintercept/handler/raw/RawForwardIncomingRequestHandler.java @@ -4,6 +4,7 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; +import org.checkerframework.checker.nullness.qual.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,7 +19,7 @@ public final class RawForwardIncomingRequestHandler extends ChannelInboundHandle private final String hostname; private final Bootstrap clientBootstrapTemplate; - private Channel outChannel = null; + private @Nullable Channel outChannel = null; public RawForwardIncomingRequestHandler(String hostname, Bootstrap clientBootstrapTemplate) { this.hostname = hostname; diff --git a/src/main/java/io/github/danthe1st/httpsintercept/handler/sni/SNIHandlerMapping.java b/src/main/java/io/github/danthe1st/httpsintercept/handler/sni/SNIHandlerMapping.java index 711ff3c..13177a2 100644 --- a/src/main/java/io/github/danthe1st/httpsintercept/handler/sni/SNIHandlerMapping.java +++ b/src/main/java/io/github/danthe1st/httpsintercept/handler/sni/SNIHandlerMapping.java @@ -64,11 +64,19 @@ private SNIHandlerMapping() throws KeyStoreException, IOException, NoSuchAlgorit ks.load(is, passphrase); } - rootCert = (X509Certificate) ks.getCertificate("root"); + if(!(ks.getCertificate("root") instanceof X509Certificate loadedRoot)){ + throw new IllegalStateException("No root certificate found"); + } + + if(!(ks.getKey("root", privateKeyPassword) instanceof PrivateKey rootKey)){ + throw new IllegalStateException("No root certificate private key found"); + } + + rootCert = loadedRoot; rootKeyPair = new KeyPair( rootCert.getPublicKey(), - (PrivateKey) ks.getKey("root", privateKeyPassword) + rootKey ); serverKeyPair = CertificateGenerator.generateKeyPair(); @@ -94,10 +102,6 @@ private void runCleanupDaemon() { } } - private KeyPair extractKeyPair(KeyStore ks, String keyName, char[] passphrase) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { - return new KeyPair(ks.getCertificate(keyName).getPublicKey(), (PrivateKey)ks.getKey(keyName, passphrase)); - } - @Override public SslContext map(String hostname) { LOG.debug("loadding certificate for hostname {}", hostname); @@ -114,7 +118,7 @@ public SslContext map(String hostname) { private SslContext createSslContext(String hostname) { try{ X509Certificate newCert = CertificateGenerator.createCertificate(serverKeyPair, hostname, rootKeyPair, rootCert, false); - return SslContextBuilder.forServer(serverKeyPair.getPrivate(), (String)null, newCert, rootCert).build(); + return SslContextBuilder.forServer(serverKeyPair.getPrivate(), newCert, rootCert).build(); }catch(SSLException | CertIOException | OperatorCreationException | CertificateException e){ throw new CertificateGenerationException("ailed to initialize the server-side SSLContext", e); } diff --git a/src/main/java/io/github/danthe1st/httpsintercept/handler/sni/SslContextCacheEntry.java b/src/main/java/io/github/danthe1st/httpsintercept/handler/sni/SslContextCacheEntry.java index a68e720..8250b78 100644 --- a/src/main/java/io/github/danthe1st/httpsintercept/handler/sni/SslContextCacheEntry.java +++ b/src/main/java/io/github/danthe1st/httpsintercept/handler/sni/SslContextCacheEntry.java @@ -3,6 +3,7 @@ import java.util.Objects; import io.netty.handler.ssl.SslContext; +import org.checkerframework.checker.initialization.qual.UnknownInitialization; class SslContextCacheEntry { private final SslContext sslContext; @@ -18,7 +19,7 @@ public SslContext getSslContext() { return sslContext; } - private void refresh() { + private void refresh(@UnknownInitialization SslContextCacheEntry this) { lastAccessTime = System.currentTimeMillis(); } diff --git a/src/main/java/io/github/danthe1st/httpsintercept/matcher/FilterIterator.java b/src/main/java/io/github/danthe1st/httpsintercept/matcher/FilterIterator.java index 1882dbc..879cfda 100644 --- a/src/main/java/io/github/danthe1st/httpsintercept/matcher/FilterIterator.java +++ b/src/main/java/io/github/danthe1st/httpsintercept/matcher/FilterIterator.java @@ -4,16 +4,21 @@ import java.util.NoSuchElementException; import java.util.function.Predicate; -final class FilterIterator implements Iterator { - private final Iterator iterator; - private final Predicate filter; - private T current; +import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +final class FilterIterator<@NonNull T> implements Iterator { + private final Iterator<@NonNull T> iterator; + private final Predicate<@NonNull T> filter; + private @Nullable T current; - public FilterIterator(Iterator iterator, Predicate filter) { + public FilterIterator(Iterator<@NonNull T> iterator, Predicate<@NonNull T> filter) { this.iterator = iterator; this.filter = filter; } @Override + @EnsuresNonNullIf(expression = "current", result = true) public boolean hasNext() { if(current != null){ return true; diff --git a/src/main/java/io/github/danthe1st/httpsintercept/matcher/HostMatcher.java b/src/main/java/io/github/danthe1st/httpsintercept/matcher/HostMatcher.java index c806c07..048c5d5 100644 --- a/src/main/java/io/github/danthe1st/httpsintercept/matcher/HostMatcher.java +++ b/src/main/java/io/github/danthe1st/httpsintercept/matcher/HostMatcher.java @@ -14,19 +14,22 @@ import java.util.regex.Pattern; import io.github.danthe1st.httpsintercept.config.HostMatcherConfig; +import org.checkerframework.checker.initialization.qual.UnderInitialization; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; -public final class HostMatcher { - private final Map> exactHosts; - private final Map> hostParts; - private final Map> hostRegexes; - private final List wildcards; +public final class HostMatcher<@NonNull T> { + private final Map> exactHosts; + private final Map> hostParts; + private final Map> hostRegexes; + private final List<@NonNull T> wildcards; - public HostMatcher(List> configs, boolean allowWildcard) { - Map> hosts = new HashMap<>(); - Map> parts = new HashMap<>(); - Map> regexes = new HashMap<>(); - List wildcardElements = new ArrayList<>(); - for(Map.Entry entry : configs){ + public HostMatcher(List> configs, boolean allowWildcard) { + Map> hosts = new HashMap<>(); + Map> parts = new HashMap<>(); + Map> regexes = new HashMap<>(); + List<@NonNull T> wildcardElements = new ArrayList<>(); + for(Map.Entry entry : configs){ HostMatcherConfig config = entry.getKey(); T value = entry.getValue(); if(allowWildcard && config.exact().isEmpty() && config.partial().isEmpty() && config.regex().isEmpty()){ @@ -43,21 +46,21 @@ public HostMatcher(List> configs, boolean allowW this.wildcards = List.copyOf(wildcardElements); } - private void addToMap(Map> multimap, T value, Set configValue, Function keyTransformer) { + private void addToMap(@UnderInitialization HostMatcher this, Map> multimap, @NonNull T value, Set configValue, Function keyTransformer) { for(String host : configValue){ multimap - .computeIfAbsent(keyTransformer.apply(host), h -> new ArrayList<>()) + .computeIfAbsent(keyTransformer.apply(host), h -> new ArrayList<@NonNull T>()) .add(value); } } - private Map> toImmutable(Map> multimap) { + private <@NonNull K> Map<@NonNull K, List<@NonNull T>> toImmutable(@UnderInitialization HostMatcher this, Map<@NonNull K, List<@NonNull T>> multimap) { multimap.replaceAll((k, list) -> List.copyOf(list)); return Map.copyOf(multimap); } - public Iterator allMatches(String hostname) { - Queue> iterators = new ArrayDeque<>(); + public Iterator<@NonNull T> allMatches(String hostname) { + Queue> iterators = new ArrayDeque<>(); if(exactHosts.containsKey(hostname)){ iterators.add(exactHosts.get(hostname).iterator()); @@ -68,11 +71,11 @@ public Iterator allMatches(String hostname) { iterators.add(new RegexIterator<>(hostRegexes, hostname)); iterators.add(wildcards.iterator()); - Iterator it = new IteratingIterator<>() { - private Iterator current = iterators.poll(); + Iterator<@NonNull T> it = new IteratingIterator<@NonNull T>() { + private @Nullable Iterator<@NonNull T> current = iterators.poll(); @Override - protected Iterator findNextIterator() { + protected Iterator<@NonNull T> findNextIterator() { while(current != null && !current.hasNext()){ current = iterators.poll(); } @@ -86,7 +89,7 @@ protected Iterator findNextIterator() { return distinctIterator(it); } - private Iterator distinctIterator(Iterator it) { + private Iterator<@NonNull T> distinctIterator(Iterator<@NonNull T> it) { Set matchers = Collections.newSetFromMap(new IdentityHashMap<>()); return new FilterIterator<>(it, element -> { if(!matchers.contains(element)){ diff --git a/src/main/java/io/github/danthe1st/httpsintercept/rules/ProcessingRule.java b/src/main/java/io/github/danthe1st/httpsintercept/rules/ProcessingRule.java index 6a5d754..662c1d4 100644 --- a/src/main/java/io/github/danthe1st/httpsintercept/rules/ProcessingRule.java +++ b/src/main/java/io/github/danthe1st/httpsintercept/rules/ProcessingRule.java @@ -1,7 +1,9 @@ package io.github.danthe1st.httpsintercept.rules; import io.github.danthe1st.httpsintercept.config.HostMatcherConfig; +import org.checkerframework.checker.nullness.qual.Nullable; public interface ProcessingRule { + @Nullable HostMatcherConfig hostMatcher(); }