Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add javadoc link to README.md and update some english translations #204

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

[![maven](https://img.shields.io/maven-central/v/com.github.monkeywie/proxyee.svg)](https://search.maven.org/search?q=com.github.monkeywie)
[![license](https://img.shields.io/github/license/monkeywie/proxyee.svg)](https://opensource.org/licenses/MIT)
[![javadoc](https://javadoc.io/badge2/com.github.monkeywie/proxyee/javadoc.svg)](https://javadoc.io/doc/com.github.monkeywie/proxyee)

</p>
<p>
Expand Down
1 change: 1 addition & 0 deletions README_zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

[![maven](https://img.shields.io/maven-central/v/com.github.monkeywie/proxyee.svg)](https://search.maven.org/search?q=com.github.monkeywie)
[![license](https://img.shields.io/github/license/monkeywie/proxyee.svg)](https://opensource.org/licenses/MIT)
[![javadoc](https://javadoc.io/badge2/com.github.monkeywie/proxyee/javadoc.svg)](https://javadoc.io/doc/com.github.monkeywie/proxyee)

</p>
<p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,17 @@
import java.util.stream.Stream;

/**
* 使用了 BC 套件的证书生成器.
* <div class="zh">使用了 BC 套件的证书生成器.</div>
* <div class="en">Use the BouncyCastle suite's certificate generator.</div>
*
* @author LamGC
*/
@CertGeneratorInfo(name = "BouncyCastle")
public class BouncyCastleCertGenerator implements CertGenerator {

static {
//注册BouncyCastleProvider加密库
// 注册BouncyCastleProvider加密库
// Register the BouncyCastleProvider cryptographic library
Security.addProvider(new BouncyCastleProvider());
}

Expand All @@ -44,7 +46,8 @@ public X509Certificate generateServerCert(String issuer, PrivateKey caPriKey, Da
String... hosts) throws Exception {
/* String issuer = "C=CN, ST=GD, L=SZ, O=lee, OU=study, CN=ProxyeeRoot";
String subject = "C=CN, ST=GD, L=SZ, O=lee, OU=study, CN=" + host;*/
//根据CA证书subject来动态生成目标服务器证书的issuer和subject
// 根据CA证书subject来动态生成目标服务器证书的issuer和subject
// Dynamically generate the issuer and subject of the target server certificate based on the CA certificate subject
String subject = Stream.of(issuer.split(", ")).map(item -> {
String[] arr = item.split("=");
if ("CN".equals(arr[0])) {
Expand All @@ -54,22 +57,27 @@ public X509Certificate generateServerCert(String issuer, PrivateKey caPriKey, Da
}
}).collect(Collectors.joining(", "));

//doc from https://www.cryptoworkshop.com/guide/
// doc from https://www.cryptoworkshop.com/guide/
JcaX509v3CertificateBuilder jv3Builder = new JcaX509v3CertificateBuilder(new X500Name(issuer),
//issue#3 修复ElementaryOS上证书不安全问题(serialNumber为1时证书会提示不安全),避免serialNumber冲突,采用时间戳+4位随机数生成
// Fix the insecure certificate issue on ElementaryOS (when serialNumber is 1, the certificate will be
// prompted to be insecure), avoid serialNumber conflict, and use timestamp + 4-digit random number to
// generate
BigInteger.valueOf(System.currentTimeMillis() + (long) (Math.random() * 10000) + 1000),
caNotBefore,
caNotAfter,
new X500Name(subject),
serverPubKey);
//SAN扩展证书支持的域名,否则浏览器提示证书不安全
// SAN扩展证书支持的域名,否则浏览器提示证书不安全
// The domain name supported by the SAN extended certificate, otherwise the browser prompts that the certificate is not secure
GeneralName[] generalNames = new GeneralName[hosts.length];
for (int i = 0; i < hosts.length; i++) {
generalNames[i] = new GeneralName(GeneralName.dNSName, hosts[i]);
}
GeneralNames subjectAltName = new GeneralNames(generalNames);
jv3Builder.addExtension(Extension.subjectAlternativeName, false, subjectAltName);
//SHA256 用SHA1浏览器可能会提示证书不安全
// SHA256 用SHA1浏览器可能会提示证书不安全
// SHA256 and SHA1 browsers may prompt that the certificate is not secure
ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSAEncryption").build(caPriKey);
return new JcaX509CertificateConverter().getCertificate(jv3Builder.build(signer));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,45 @@
import java.util.Date;

/**
* 证书生成器接口.
*
* <p> 该接口用于在无法使用本库内置的 BC 加密套件时, 可自行实现具体加密细节以绕过 BC 加密套件.
* <p> 实现后, 请注意对实现添加 {@link CertGeneratorInfo} 注解, 并按照 SPI 机制规范注册实现.
*
* <div class="zh">
* 证书生成器接口.
* <p> 该接口用于在无法使用本库内置的 BC 加密套件时, 可自行实现具体加密细节以绕过 BC 加密套件.</p>
* <p> 实现后, 请注意对实现添加 {@link CertGeneratorInfo} 注解, 并按照 SPI 机制规范注册实现.</p>
* </div>
* <div class="en">
* Certificate generator interface.
* <p>This interface is used to implement specific encryption details to bypass the BC cipher suite when the built-in BC cipher suite of this library cannot be used.</p>
* <p>After implementation, please note to add {@link CertGeneratorInfo} annotation to the implementation, and register the implementation according to the SPI mechanism specification.</p>
* </div>
* @author LamGC
*/
public interface CertGenerator {

/**
* 生成服务端自签名证书.
* @param issuer 元数据(X509 Names)
* @param caPriKey 用于进行签名的 CA 私钥.
* @param caNotBefore 证书生效时间, 在这个时间之前证书也是失效的.
* @param caNotAfter 证书失效时间, 过了这个时间后证书即失效.
* @param serverPubKey 服务端证书公钥.
* @param hosts 证书所属域名.
* @return 返回指定域名所属的服务端 X509 证书.
* @throws Exception 当发生任意异常时, 异常将直接抛出至调用方.
* <div class="zh">生成服务端自签名证书.</div>
* <div class="en">Generate a server-side self-signed certificate.</div>
* @param issuer <div class="zh">元数据(X509 Names)</div><div class="en">Metadata (X509 Names)</div>
* @param caPriKey <div class="zh">用于进行签名的 CA 私钥.</div><div class="en">CA private key used for signing.</div>
* @param caNotBefore <div class="zh">证书生效时间, 在这个时间之前证书也是失效的.</div><div class="en">The validity time of the certificate, before this time the certificate is also invalid.</div>
* @param caNotAfter <div class="zh">证书失效时间, 过了这个时间后证书即失效.</div><div class="en">The certificate expiration time, after which the certificate will become invalid.</div>
* @param serverPubKey <div class="zh">服务端证书公钥.</div><div class="en">Server certificate public key.</div>
* @param hosts <div class="zh">证书所属域名.</div><div class="en">The domain name to which the certificate belongs.</div>
* @return <div class="zh">返回指定域名所属的服务端 X509 证书.</div><div class="en">Returns the server X509 certificate to which the specified domain name belongs.</div>
* @throws Exception <div class="zh">当发生任意异常时, 异常将直接抛出至调用方.</div><div class="en">When any exception occurs, the exception will be thrown directly to the caller.</div>
*/
X509Certificate generateServerCert(String issuer, PrivateKey caPriKey, Date caNotBefore,
Date caNotAfter, PublicKey serverPubKey,
String... hosts) throws Exception;

/**
* 生成 CA 证书(自签名).
* @param subject 元数据(X509 Names)
* @param caNotBefore 证书生效时间, 在这个时间之前证书也是失效的.
* @param caNotAfter 证书失效时间, 过了这个时间后证书即失效.
* @param keyPair RSA 密钥对.
* @return 返回自签名 CA 证书.
* @throws Exception 当发生任意异常时, 异常将直接抛出至调用方.
* <div class="zh">生成 CA 证书(自签名).</div>
* <div class="en">Generate a CA certificate (self-signed).</div>
* @param subject <div class="zh">元数据(X509 Names)</div><div class="en">Metadata (X509 Names)</div>
* @param caNotBefore <div class="zh">证书生效时间, 在这个时间之前证书也是失效的.</div><div class="en>The validity time of the certificate, before this time the certificate is also invalid.</div>
* @param caNotAfter <div class="zh">证书失效时间, 过了这个时间后证书即失效.</div><div class="en">The certificate expiration time, after which the certificate will become invalid.</div>
* @param keyPair <div class="zh">RSA 密钥对.</div><div class="en">RSA key pair.</div>
* @return <div class="zh">返回自签名 CA 证书.</div><div class="en">Returns a self-signed CA certificate.</div>
* @throws Exception <div class="zh">当发生任意异常时, 异常将直接抛出至调用方.</div><div class="en">When any exception occurs, the exception will be thrown directly to the caller.</div>
*/
X509Certificate generateCaCert(String subject, Date caNotBefore, Date caNotAfter, KeyPair keyPair) throws Exception;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,30 @@
import java.lang.annotation.*;

/**
* 证书生成器注解.
* <p>用于标注声明生成器的具体不可变信息.
*
* <div class="zh">
* 证书生成器注解.
* <p>用于标注声明生成器的具体不可变信息.</p>
* </div>
* <div class="en">
* Certificate generator annotations.
* <p>Concrete immutable information used to annotate the declaration generator.</p>
* </div>
* @author LamGC
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CertGeneratorInfo {

/**
* 生成器名称, 该名称要求唯一, 不可重复.
* <p>当遇到名称相同的不同生成器实例时, 将选择第一个加载的实现.
* @return 返回生成器唯一名称.
* <div class="zh">
* 生成器名称, 该名称要求唯一, 不可重复.
* <p>当遇到名称相同的不同生成器实例时, 将选择第一个加载的实现.</p>
* </div>
* <div class="en">
* Generator name, the name must be unique and cannot be repeated.
* <p>When a different generator instance with the same name is encountered, the first loaded implementation will be chosen.</p>
* </div>
* @return <div class="zh">返回生成器唯一名称.</div><div class="en">Returns the generator unique name.</div>
*/
String name();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
import io.netty.handler.proxy.ProxyHandler;

/**
* HTTP代理,转发解码后的HTTP报文
* <div class="en">HTTP proxy, forwarding decoded HTTP packets</div>
* <div class="zh">HTTP代理,转发解码后的HTTP报文</div>
*/
public class HttpProxyInitializer extends ChannelInitializer {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,20 +120,23 @@ public void channelRead(final ChannelHandlerContext ctx, final Object msg) throw
if (msg instanceof HttpRequest) {
HttpRequest request = (HttpRequest) msg;
// 第一次建立连接取host和端口号和处理代理握手
// The first connection is established to take the host and port number and handle the proxy handshake
if (getStatus() == 0) {
setRequestProto(ProtoUtil.getRequestProto(request));
if (getRequestProto() == null) { // bad request
ctx.channel().close();
return;
}
// 首次连接处理
// first connection handling
if (getServerConfig().getHttpProxyAcceptHandler() != null
&& !getServerConfig().getHttpProxyAcceptHandler().onAccept(request, ctx.channel())) {
setStatus(2);
ctx.channel().close();
return;
}
// 代理身份验证
// proxy authentication
if (!authenticate(ctx, request)) {
setStatus(2);
ctx.channel().close();
Expand Down Expand Up @@ -165,9 +168,9 @@ public void channelRead(final ChannelHandlerContext ctx, final Object msg) throw
ReferenceCountUtil.release(msg);
setStatus(1);
}
} else { // ssl和websocket的握手处理
} else { // ssl和websocket的握手处理 | Handshake processing of ssl and websocket
ByteBuf byteBuf = (ByteBuf) msg;
if (getServerConfig().isHandleSsl() && byteBuf.getByte(0) == 22) {// ssl握手
if (getServerConfig().isHandleSsl() && byteBuf.getByte(0) == 22) {// ssl握手 | ssl handshake
getRequestProto().setSsl(true);
int port = ((InetSocketAddress) ctx.channel().localAddress()).getPort();
SslContext sslCtx = SslContextBuilder
Expand All @@ -182,6 +185,7 @@ public void channelRead(final ChannelHandlerContext ctx, final Object msg) throw
return;
}
// 如果connect后面跑的是HTTP报文,也可以抓包处理
// If the HTTP packet is running behind the connect, you can also capture the packet and process it.
if (isHttp(byteBuf)) {
ctx.pipeline().addFirst("httpCodec", new HttpServerCodec());
ctx.pipeline().fireChannelRead(msg);
Expand Down Expand Up @@ -244,6 +248,7 @@ private boolean authenticate(ChannelHandlerContext ctx, HttpRequest request) {
private void handleProxyData(Channel channel, Object msg, boolean isHttp) throws Exception {
if (getChannelFuture() == null) {
// connection异常 还有HttpContent进来,不转发
// connection exception and HttpContent comes in, not forwarded
if (isHttp && !(msg instanceof HttpRequest)) {
return;
}
Expand All @@ -261,9 +266,11 @@ private void handleProxyData(Channel channel, Object msg, boolean isHttp) throws
if (isHttp) {
HttpRequest httpRequest = (HttpRequest) msg;
// 检查requestProto是否有修改
// Check if requestProto is modified
RequestProto newRP = ProtoUtil.getRequestProto(httpRequest);
if (!newRP.equals(requestProto)) {
// 更新Host请求头
// Update the Host request header
if ((getRequestProto().getSsl() && getRequestProto().getPort() == 443)
|| (!getRequestProto().getSsl() && getRequestProto().getPort() == 80)) {
httpRequest.headers().set(HttpHeaderNames.HOST, getRequestProto().getHost());
Expand All @@ -277,15 +284,19 @@ private void handleProxyData(Channel channel, Object msg, boolean isHttp) throws
* 添加SSL client hello的Server Name Indication extension(SNI扩展) 有些服务器对于client
* hello不带SNI扩展时会直接返回Received fatal alert: handshake_failure(握手错误)
* 例如:https://cdn.mdn.mozilla.net/static/img/favicon32.7f3da72dcea1.png
*
* Add the Server Name Indication extension (SNI extension) of the SSL client hello to some servers for the client
* Received fatal alert: handshake_failure (handshake error) will be returned directly without the SNI extension.
* For example: https://cdn.mdn.mozilla.net/static/img/favicon32.7f3da72dcea1.png
*/
ChannelInitializer channelInitializer = isHttp ? new HttpProxyInitializer(channel, requestProto, proxyHandler)
: new TunnelProxyInitializer(channel, proxyHandler);
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(getServerConfig().getProxyLoopGroup()) // 注册线程池
.channel(NioSocketChannel.class) // 使用NioSocketChannel来作为连接用的channel类
bootstrap.group(getServerConfig().getProxyLoopGroup()) // 注册线程池 | register thread pool
.channel(NioSocketChannel.class) // 使用NioSocketChannel来作为连接用的channel类 | Use NioSocketChannel as the connection channel class
.handler(channelInitializer);
if (proxyHandler != null) {
// 代理服务器解析DNS和连接
// 代理服务器解析DNS和连接 | Proxy server resolves DNS and connects
bootstrap.resolver(NoopAddressResolverGroup.INSTANCE);
} else {
bootstrap.resolver(getServerConfig().resolver());
Expand Down Expand Up @@ -338,7 +349,7 @@ public void afterResponse(Channel clientChannel, Channel proxyChannel, HttpRespo
HttpProxyInterceptPipeline pipeline) throws Exception {
clientChannel.writeAndFlush(httpResponse);
if (HttpHeaderValues.WEBSOCKET.toString().equals(httpResponse.headers().get(HttpHeaderNames.UPGRADE))) {
// websocket转发原始报文
// websocket转发原始报文 | Websocket forwards original packets
proxyChannel.pipeline().remove("httpCodec");
clientChannel.pipeline().remove("httpCodec");
}
Expand All @@ -355,6 +366,7 @@ public void afterResponse(Channel clientChannel, Channel proxyChannel, HttpConte
}

// fix issue #186: 不拦截https报文时,暴露一个扩展点用于代理设置,并且保持一致的编程接口
// When not intercepting https messages, expose an extension point for proxy settings and maintain a consistent programming interface
private HttpProxyInterceptPipeline buildOnlyConnectPipeline() {
HttpProxyInterceptPipeline interceptPipeline = new HttpProxyInterceptPipeline(new HttpProxyIntercept());
getInterceptInitializer().init(interceptPipeline);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
import io.netty.handler.proxy.ProxyHandler;

/**
* http代理隧道,转发原始报文
* <div class="zh">http代理隧道,转发原始报文</div>
* <div class="en">http proxy tunnel, forwarding original packets</div>
*/
public class TunnelProxyInitializer extends ChannelInitializer {

Expand Down
Loading