Skip to content

Commit

Permalink
post forward rules
Browse files Browse the repository at this point in the history
  • Loading branch information
danthe1st committed Mar 8, 2024
1 parent a23c751 commit 6a8907e
Show file tree
Hide file tree
Showing 15 changed files with 263 additions and 37 deletions.
12 changes: 11 additions & 1 deletion graal/reflect-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,22 @@
"allPublicConstructors" : true,
"allRecordComponents": true
},{
"name" : "io.github.danthe1st.httpsintercept.rules.SetHeaderRule",
"name" : "io.github.danthe1st.httpsintercept.rules.pre.SetRequestHeaderRule",
"allPublicConstructors" : true,
"allRecordComponents": true
},{
"name" : "io.github.danthe1st.httpsintercept.rules.post.HtmlBasedBlocker",
"allPublicConstructors" : true,
"allRecordComponents": true
},{
"name" : "java.util.HashSet",
"allPublicConstructors" : true
},{
"name" : "java.nio.file.Path",
"allPublicMethods" : true
},{
"name" : "java.util.regex.Pattern",
"allPublicMethods" : true
},{
"name" : "java.util.ArrayList",
"allPublicConstructors" : true
Expand Down
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.17.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@
import io.github.danthe1st.httpsintercept.handler.ServerHandlersInit;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HttpsIntercept {
private static final int LOCAL_PORT = Integer.getInteger("localPort", 1337);
private static final Logger LOG = LoggerFactory.getLogger(HttpsIntercept.class);

public static void main(String[] args) throws InterruptedException, IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException {

Expand All @@ -33,11 +37,15 @@ public static void main(String[] args) throws InterruptedException, IOException,
.channel(NioSocketChannel.class);

ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
ChannelFuture serverFuture = serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ServerHandlersInit(clientBootstrap, config))
.childOption(ChannelOption.AUTO_READ, true)
.bind(LOCAL_PORT).sync().channel().closeFuture().sync();
.bind(LOCAL_PORT).sync();

LOG.info("Https intercept is ready");

serverFuture.channel().closeFuture().sync();
}finally{
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,34 @@
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import io.github.danthe1st.httpsintercept.rules.PostForwardRule;
import io.github.danthe1st.httpsintercept.rules.PreForwardRule;

public record Config(
HostMatcherConfig ignoredHosts,
List<PreForwardRule> preForwardRules
List<PreForwardRule> preForwardRules,
List<PostForwardRule> postForwardRules
) {

public Config(HostMatcherConfig ignoredHosts, List<PreForwardRule> preForwardRules) {
Objects.requireNonNull(ignoredHosts);
public Config(HostMatcherConfig ignoredHosts, List<PreForwardRule> preForwardRules, List<PostForwardRule> postForwardRules) {
if(ignoredHosts == null){
ignoredHosts = new HostMatcherConfig(null, null, null);
}
this.ignoredHosts = ignoredHosts;
this.preForwardRules = List.copyOf(preForwardRules);
this.preForwardRules = emptyIfNull(preForwardRules);
this.postForwardRules = emptyIfNull(postForwardRules);
}

private <T> List<T> emptyIfNull(List<T> preForwardRules) {
if(preForwardRules == null){
return Collections.emptyList();
}
return List.copyOf(preForwardRules);
}

public static Config load(Path path) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
import io.github.danthe1st.httpsintercept.handler.sni.CustomSniHandler;
import io.github.danthe1st.httpsintercept.handler.sni.SNIHandlerMapping;
import io.github.danthe1st.httpsintercept.matcher.IterativeHostMatcher;
import io.github.danthe1st.httpsintercept.rules.PostForwardRule;
import io.github.danthe1st.httpsintercept.rules.PreForwardRule;
import io.github.danthe1st.httpsintercept.rules.ProcessingRule;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
Expand All @@ -31,27 +33,30 @@ public class ServerHandlersInit extends ChannelInitializer<SocketChannel> {

private final Bootstrap clientBootstrapTemplate;
private final SNIHandlerMapping sniMapping;
private final Config config;

private final IterativeHostMatcher<PreForwardRule> preForwardMatcher;
private final IterativeHostMatcher<Object> ignoredHostMatcher;
private final IterativeHostMatcher<PreForwardRule> preForwardMatcher;
private final IterativeHostMatcher<PostForwardRule> postForwardMatcher;

public ServerHandlersInit(Bootstrap clientBootstrap, Config config) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException {
this.clientBootstrapTemplate = clientBootstrap;
sniMapping = SNIHandlerMapping.createMapping();
this.config = config;

List<PreForwardRule> preForwardRules = config.preForwardRules();
List<Map.Entry<HostMatcherConfig, PreForwardRule>> rules = new ArrayList<>();
for(PreForwardRule preForwardRule : preForwardRules){
HostMatcherConfig hostMatcher = preForwardRule.hostMatcher();
preForwardMatcher = createMatcherFromRules(config.preForwardRules());
postForwardMatcher = createMatcherFromRules(config.postForwardRules());
ignoredHostMatcher = new IterativeHostMatcher<>(List.of(Map.entry(config.ignoredHosts(), new Object())));
}

private <T extends ProcessingRule> IterativeHostMatcher<T> createMatcherFromRules(List<T> ruleList) {
List<Map.Entry<HostMatcherConfig, T>> rules = new ArrayList<>();
for(T rule : ruleList){
HostMatcherConfig hostMatcher = rule.hostMatcher();
if(hostMatcher == null){
hostMatcher = new HostMatcherConfig(null, null, null);
}
rules.add(Map.entry(hostMatcher, preForwardRule));
rules.add(Map.entry(hostMatcher, rule));
}
preForwardMatcher = new IterativeHostMatcher<>(rules);
ignoredHostMatcher = new IterativeHostMatcher<>(List.of(Map.entry(config.ignoredHosts(), new Object())));
return new IterativeHostMatcher<>(rules);
}

@Override
Expand All @@ -60,8 +65,8 @@ protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(
sniHandler,
new HttpServerCodec(),
new HttpObjectAggregator(1048576),
new IncomingHttpRequestHandler(sniHandler, clientBootstrapTemplate, preForwardMatcher)
new HttpObjectAggregator(Integer.MAX_VALUE),
new IncomingHttpRequestHandler(sniHandler, clientBootstrapTemplate, preForwardMatcher, postForwardMatcher)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.github.danthe1st.httpsintercept.handler.http;

import java.nio.charset.StandardCharsets;

import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.FullHttpResponse;
import org.bouncycastle.util.Arrays;

public class HttpResponseContentAccessor {
private ByteBuf contentBuf;
private byte[] bytes;

public HttpResponseContentAccessor(FullHttpResponse res) {
contentBuf = res.content();
}

public byte[] getBytes() {
ensureBytesPresent();
return Arrays.copyOf(bytes, bytes.length);
}

public String getAsString() {
ensureBytesPresent();
return new String(bytes, StandardCharsets.UTF_8);
}

private void ensureBytesPresent() {
if(bytes == null){
ByteBuf buf = contentBuf.copy();
bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.io.PrintStream;

import io.github.danthe1st.httpsintercept.matcher.IterativeHostMatcher;
import io.github.danthe1st.httpsintercept.rules.PostForwardRule;
import io.github.danthe1st.httpsintercept.rules.PreForwardRule;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
Expand All @@ -31,16 +32,18 @@ public final class IncomingHttpRequestHandler extends SimpleChannelInboundHandle
private final SniHandler sniHandler;
private final Bootstrap clientBootstrap;
private final IterativeHostMatcher<PreForwardRule> hostMatcher;
private final IterativeHostMatcher<PostForwardRule> postForwardMatcher;

/**
* @param sniHandler Netty handler for Server Name Identification (contains the actual target host name)
* @param clientSslContext {@link SslContext} used for the outgoing request
* @param clientBootstrap template for sending the outgoing request
*/
public IncomingHttpRequestHandler(SniHandler sniHandler, Bootstrap clientBootstrap, IterativeHostMatcher<PreForwardRule> hostMatcher) {
public IncomingHttpRequestHandler(SniHandler sniHandler, Bootstrap clientBootstrap, IterativeHostMatcher<PreForwardRule> hostMatcher, IterativeHostMatcher<PostForwardRule> postForwardMatcher) {
this.sniHandler = sniHandler;
this.clientBootstrap = clientBootstrap;
this.hostMatcher = hostMatcher;
this.postForwardMatcher = postForwardMatcher;
}

@Override
Expand Down Expand Up @@ -68,7 +71,7 @@ private void forwardRequest(ChannelHandlerContext channelHandlerContext, FullHtt
}

Bootstrap actualClientBootstrap = clientBootstrap.clone()
.handler(new OutgoingHttpRequestHandler(channelHandlerContext, hostname));
.handler(new OutgoingHttpRequestHandler(channelHandlerContext, fullHttpRequest, hostname, postForwardMatcher));
try{
Channel outChannel = actualClientBootstrap.connect(hostname, 443)
.sync()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;

import io.github.danthe1st.httpsintercept.matcher.IterativeHostMatcher;
import io.github.danthe1st.httpsintercept.rules.PostForwardRule;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.ssl.SslHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -21,10 +25,15 @@ final class OutgoingHttpRequestHandler extends ChannelInitializer<SocketChannel>

private final ChannelHandlerContext originalClientContext;
private final String hostname;
private final FullHttpRequest fullHttpRequest;

OutgoingHttpRequestHandler(ChannelHandlerContext originalClientContext, String hostname) {
private final IterativeHostMatcher<PostForwardRule> postForwardMatcher;

OutgoingHttpRequestHandler(ChannelHandlerContext originalClientContext, FullHttpRequest fullHttpRequest, String hostname, IterativeHostMatcher<PostForwardRule> postForwardMatcher) {
this.originalClientContext = originalClientContext;
this.hostname = hostname;
this.fullHttpRequest = fullHttpRequest;
this.postForwardMatcher = postForwardMatcher;
}

@Override
Expand All @@ -38,7 +47,8 @@ protected void initChannel(SocketChannel ch) throws Exception {
p.addLast(
new SslHandler(engine),
new HttpClientCodec(), // encode/decode HTTP
new ResponseHandler(originalClientContext)
new HttpObjectAggregator(Integer.MAX_VALUE),
new ResponseHandler(originalClientContext, fullHttpRequest, postForwardMatcher.matchesAsIterable(hostname))
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package io.github.danthe1st.httpsintercept.handler.http;

import io.github.danthe1st.httpsintercept.rules.PostForwardRule;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -14,21 +16,39 @@ final class ResponseHandler extends ChannelInboundHandlerAdapter {
private static final Logger LOG = LoggerFactory.getLogger(ResponseHandler.class);

private final ChannelHandlerContext originalClientContext;
private final Iterable<PostForwardRule> postForwardRules;

public ResponseHandler(ChannelHandlerContext originalClientContext) {
private final FullHttpRequest fullHttpRequest;

public ResponseHandler(ChannelHandlerContext originalClientContext, FullHttpRequest fullHttpRequest, Iterable<PostForwardRule> postForwardRules) {
this.originalClientContext = originalClientContext;
this.fullHttpRequest = fullHttpRequest;
this.postForwardRules = postForwardRules;
}

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
LOG.debug("read: {}", msg);
originalClientContext.writeAndFlush(msg);

if(msg instanceof LastHttpContent){
LOG.debug("last HTTP content");
originalClientContext.channel().close();
ctx.channel().close();
if(msg instanceof FullHttpResponse res){
processRules(res);
}else{
LOG.error("respose message not assignable to FullHttpResponse: {}", msg);
originalClientContext.writeAndFlush(msg);
}

originalClientContext.channel().close();
ctx.channel().close();
}

private void processRules(FullHttpResponse res) {
HttpResponseContentAccessor contentAccessor = new HttpResponseContentAccessor(res);
for(PostForwardRule rule : postForwardRules){
if(!rule.processRequest(fullHttpRequest, res, contentAccessor, originalClientContext.channel())){
return;
}
}
originalClientContext.writeAndFlush(res);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ private SNIHandlerMapping() throws KeyStoreException, IOException, NoSuchAlgorit
char[] passphrase = secretLines.get(0).toCharArray();
char[] privateKeyPassword = secretLines.get(1).toCharArray();

LOG.info("Initiating SSL context");
LOG.debug("Initiating SSL context");

ks = KeyStore.getInstance("JKS");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,15 @@ protected Iterator<T> findNextIterator() {
if(hostParts.containsKey(hostPart)){
current = hostParts.get(hostPart).iterator();
if(current.hasNext()){
nextIndex();
return current;
}
}
}while((index = hostname.indexOf('.', index) + 1) != 0 && index < hostname.length());
}while(nextIndex() != 0 && index < hostname.length());
return Collections.emptyIterator();
}

private int nextIndex() {
return index = hostname.indexOf('.', index) + 1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.github.danthe1st.httpsintercept.rules;

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.github.danthe1st.httpsintercept.handler.http.HttpResponseContentAccessor;
import io.github.danthe1st.httpsintercept.rules.post.HtmlBasedBlocker;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({ @Type(name = "htmlBasedBlock", value = HtmlBasedBlocker.class) })
public interface PostForwardRule extends ProcessingRule {
boolean processRequest(FullHttpRequest fullHttpRequest, FullHttpResponse response, HttpResponseContentAccessor responseContentAccessor, Channel channel);
}
Loading

0 comments on commit 6a8907e

Please sign in to comment.