一· 快速命令
1.生成ca證書
openssl req -new -x509 -keyout ca.key -out ca.crt -days 36500
在本目錄得到 ca.key 和 ca.crt 文件
2.生成服務端和客戶端私鑰
openssl genrsa -des3 -out server.key 1024
openssl genrsa -des3 -out client.key 1024
3.根據 key 生成 csr 文件
openssl req -new -key server.key -out server.csr
openssl req -new -key client.key -out client.csr
4.根據 ca 證書 server.csr 和 client.csr 生成 x509 證書
openssl x509 -req -days 36500 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt
openssl x509 -req -days 36500 -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt
5.將 key 文件進行 PKCS#8 編碼
openssl pkcs8 -topk8 -in server.key -out pkcs8_server.key -nocrypt
openssl pkcs8 -topk8 -in client.key -out pkcs8_client.key -nocrypt
最后得到有用的文件分別為
- 服務器端: ca.crt、server.crt、pkcs8_server.key
- 客戶端端: ca.crt、client.crt、pkcs8_client.key
6. 查看命令
openssl 查看證書細節
打印證書的過期時間
openssl x509 -in signed.crt -noout -dates
打印出證書的內容:
openssl x509 -in cert.pem -noout -text
打印出證書的系列號
openssl x509 -in cert.pem -noout -serial
打印出證書的擁有者名字
openssl x509 -in cert.pem -noout -subject
以RFC2253規定的格式打印出證書的擁有者名字
openssl x509 -in cert.pem -noout -subject -nameopt RFC2253
在支持UTF8的終端一行過打印出證書的擁有者名字
openssl x509 -in cert.pem -noout -subject -nameopt oneline -nameopt -escmsb
打印出證書的MD5特征參數
openssl x509 -in cert.pem -noout -fingerprint
打印出證書的SHA特征參數
openssl x509 -sha1 -in cert.pem -noout -fingerprint
把PEM格式的證書轉化成DER格式
openssl x509 -in cert.pem -inform PEM -out cert.der -outform DER
把一個證書轉化成CSR
openssl x509 -x509toreq -in cert.pem -out req.pem -signkey key.pem
給一個CSR進行處理,頒發字簽名證書,增加CA擴展項
openssl x509 -req -in careq.pem -extfile openssl.cnf -extensions v3_ca -signkey key.pem -out cacert.pem
給一個CSR簽名,增加用戶證書擴展項
openssl x509 -req -in req.pem -extfile openssl.cnf -extensions v3_usr -CA cacert.pem -CAkey key.pem -CAcreateserial
查看csr文件細節:
openssl req -in my.csr -noout -text
二· 部分參考代碼
服務端的代碼:
@Slf4j
public class HidsSslContextBuilder {
private final static String serverCrt = "/static/keys/server.crt";
private final static String serverKey = "/static/keys/pkcs8_server.key";
private final static String caCrt = "/static/keys/ca.crt";
private final static String keyPassword = "";
public static SslContext build(ClientAuth clientAuth) {
InputStream certInput = null;
InputStream priKeyInput = null;
InputStream caInput = null;
try {
certInput = HidsSslContextBuilder.class.getResourceAsStream(serverCrt);
priKeyInput = HidsSslContextBuilder.class.getResourceAsStream(serverKey);
caInput = HidsSslContextBuilder.class.getResourceAsStream(caCrt);
return SslContextBuilder.forServer(certInput, priKeyInput)
.clientAuth(clientAuth)
.trustManager(caInput).build();
} catch (Throwable e) {
log.error("HidsSslContextBuilder", e);
} finally {
IOUtils.closeQuietly(certInput);
IOUtils.closeQuietly(priKeyInput);
IOUtils.closeQuietly(caInput);
}
return null;
}
public static SslContext buildSelfSignedCer() {
try {
SelfSignedCertificate ssc = new SelfSignedCertificate();
return SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
.build();
} catch (Throwable e) {
log.error("buildSelfSignedCer", e);
}
return null;
}
}
...
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
// INBOUND: from head to tail
// OUTBOUND: from tail to head
// ssl
if (sslContext != null) {
pipeline.addLast(sslContext.newHandler(socketChannel.alloc()));
}
pipeline.addLast("TLVDecoder", new TLVDecoder());
pipeline.addLast("TLVEncoder", new TLVEncoder());
pipeline.addLast("Decompressor", new Decompressor());
pipeline.addLast("Compressor", new Compressor());
pipeline.addLast("tlvChannelHandler", tlvChannelHandler);
// 增加channel對應的數據
prepareChannelContext(socketChannel);
}
客戶端的測試代碼
public class ClientChannelTest {
@Test
public void testClient() throws Throwable {
String host = "0.0.0.0";
int port = 8888;
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
final SslContext sslCtx = SslContextBuilder.forClient()
// 雙向驗證
// .keyManager(this.getClass().getResourceAsStream("/keys/client.crt"),
// this.getClass().getResourceAsStream("/keys/pkcs8_client.key"))
// CA證書,驗證對方證書
.trustManager(this.getClass().getResourceAsStream("/keys/ca.crt"))
// 不驗證SERVER
// .trustManager(InsecureTrustManagerFactory.INSTANCE)
.build();
Bootstrap b = new Bootstrap(); // (1)
b.group(workerGroup); // (2)
b.channel(NioSocketChannel.class); // (3)
b.option(ChannelOption.SO_KEEPALIVE, true); // (4)
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()));
ch.pipeline().addLast(new TLVDecoder());
ch.pipeline().addLast(new TLVEncoder());
ch.pipeline().addLast("Decompressor", new Decompressor());
ch.pipeline().addLast("Compressor", new Compressor());
ch.pipeline().addLast(new ClientHandler());
}
});
// Start the client.
ChannelFuture f = b.connect(host, port).sync(); // (5)
// Wait until the connection is closed.
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
}
}
}
在Channel上綁定鏈接上下文信息,類似Session的功能,存儲在Channel中的Attr
中。
public class ChannelUtils {
public static ClientContext getOrCreate(Channel socketChannel) {
AttributeKey<ClientContext> key = AttributeKey.valueOf(ClientContext.class.toString());
Attribute<ClientContext> attr = socketChannel.attr(key);
if (attr.get() == null) {
attr.set(createClientContext(socketChannel));
}
return attr.get();
}
private static ClientContext createClientContext(Channel socketChannel) {
return new DefaultClientContext(socketChannel.pipeline());
}
}