Generate certificate in Java -- Certificate chain

本文介绍如何使用Java生成证书链,包括创建自签名证书、生成中间证书及叶证书,并通过示例代码展示了整个过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

In previous post, we have introduced the use of Certificate and how to generate self signed certificate using Java. In this post, we will show you how to generate a certificate chain. Sometimes we may only have a certificate request or we don't have a trusted certificate which can be used for business. Now we need to have a trusted CA to sign our certificate so that it can be used on SSL communications.

To generate a certificate chain, we may first have our own certificate(A), then we may use other certificate(B) and its associated private key to sign the certificate we have, then we will use another certificate(C) and its associated private key to sign certificate B...this process will continue until you have a trusted root certificate to sign the previous certificate. Then you have a certificate chain. This trusted root CA may be from VeriSign or other CAs.

In Java, we can use below codes to create certificate chain.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
 
import javax.security.cert.Certificate;
 
import sun.security.x509.BasicConstraintsExtension;
import sun.security.x509.CertAndKeyGen;
import sun.security.x509.CertificateExtensions;
import sun.security.x509.CertificateIssuerName;
import sun.security.x509.X500Name;
import sun.security.x509.X509CertImpl;
import sun.security.x509.X509CertInfo;
 
public class CertificateChainGeneration {
     public static void main(String[] args){
         try {
             //Generate ROOT certificate
             CertAndKeyGen keyGen= new CertAndKeyGen( "RSA" , "SHA1WithRSA" , null );
             keyGen.generate( 1024 );
             PrivateKey rootPrivateKey=keyGen.getPrivateKey();
             
             X509Certificate rootCertificate = keyGen.getSelfCertificate( new X500Name( "CN=ROOT" ), ( long ) 365 * 24 * 60 * 60 );
             
             //Generate intermediate certificate
             CertAndKeyGen keyGen1= new CertAndKeyGen( "RSA" , "SHA1WithRSA" , null );
             keyGen1.generate( 1024 );
             PrivateKey middlePrivateKey=keyGen1.getPrivateKey();
             
             X509Certificate middleCertificate = keyGen1.getSelfCertificate( new X500Name( "CN=MIDDLE" ), ( long ) 365 * 24 * 60 * 60 );
             
             //Generate leaf certificate
             CertAndKeyGen keyGen2= new CertAndKeyGen( "RSA" , "SHA1WithRSA" , null );
             keyGen2.generate( 1024 );
             PrivateKey topPrivateKey=keyGen2.getPrivateKey();
             
             X509Certificate topCertificate = keyGen2.getSelfCertificate( new X500Name( "CN=TOP" ), ( long ) 365 * 24 * 60 * 60 );
             
             rootCertificate   = createSignedCertificate(rootCertificate,rootCertificate,rootPrivateKey);
             middleCertificate = createSignedCertificate(middleCertificate,rootCertificate,rootPrivateKey);
             topCertificate    = createSignedCertificate(topCertificate,middleCertificate,middlePrivateKey);
             
             X509Certificate[] chain = new X509Certificate[ 3 ];
             chain[ 0 ]=topCertificate;
             chain[ 1 ]=middleCertificate;
             chain[ 2 ]=rootCertificate;  
         } catch (Exception ex){
             ex.printStackTrace();
         }
     }
     
     private static X509Certificate createSignedCertificate(X509Certificate cetrificate,X509Certificate issuerCertificate,PrivateKey issuerPrivateKey){
         try {
             Principal issuer = issuerCertificate.getSubjectDN();
             String issuerSigAlg = issuerCertificate.getSigAlgName();
             
             byte [] inCertBytes = cetrificate.getTBSCertificate();
             X509CertInfo info = new X509CertInfo(inCertBytes);
             info.set(X509CertInfo.ISSUER, new CertificateIssuerName((X500Name) issuer));
             
             //No need to add the BasicContraint for leaf cert
             if (!cetrificate.getSubjectDN().getName().equals( "CN=TOP" )){
                 CertificateExtensions exts= new CertificateExtensions();
                 BasicConstraintsExtension bce = new BasicConstraintsExtension( true , - 1 );
                 exts.set(BasicConstraintsExtension.NAME, new BasicConstraintsExtension( false , bce.getExtensionValue()));
                 info.set(X509CertInfo.EXTENSIONS, exts);
             }
             
             X509CertImpl outCert = new X509CertImpl(info);
             outCert.sign(issuerPrivateKey, issuerSigAlg);
             
             return outCert;
         } catch (Exception ex){
             ex.printStackTrace();
         }
         return null ;
     }
}

Here we first create three self signed certificates and their associated private keys. Then we use one certificate and its private key to sign another certificate.

Note that the certificate except the leaf cert in a workable certificate chain should contain an CA extension to indicate the certificate is a CA certificate. So you will see that we will add CA extension to the certificate when we sign a certificate in createSignedCertificate().

In next post, we will show you how to store the certificate and keys into different types of keystores such as JKS, PKCS12

<think>我们正在使用Java通过HTTPS调用本地部署的大模型(例如Qwen2-7B-Instruct)。由于是本地部署,通常我们使用HTTP,但若需要HTTPS,则需要配置SSL/TLS。不过,本地部署的大模型服务(如vLLM、Ollama)默认可能使用HTTP。为了使用HTTPS,我们可以通过反向代理(如Nginx)添加SSL层,或者如果大模型服务本身支持HTTPS则直接配置。 根据引用[1]和[3],我们知道可以通过HTTP与本地大模型服务交互。现在用户要求使用HTTPS,因此我们需要将HTTP服务升级为HTTPS。 步骤: 1. 确保大模型服务在本地运行(例如Ollama默认在11434端口提供HTTP服务)。 2. 生成自签名证书(用于测试)或使用有效证书。 3. 配置一个支持HTTPS的反向代理(如Nginx),将HTTPS请求转发到本地的HTTP服务。 4. 在Java代码中,使用HTTPS协议调用反向代理的HTTPS端点。 或者,如果大模型服务本身支持HTTPS(例如通过配置),则直接配置即可。但通常Ollama等工具默认不支持HTTPS,所以这里我们采用反向代理的方式。 下面详细说明: ### 1. 生成自签名证书(用于测试) 可以使用OpenSSL生成自签名证书: ```bash openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes ``` 这将生成`cert.pem`(证书)和`key.pem`(私钥)。 ### 2. 配置Nginx反向代理 假设Ollama运行在`http://localhost:11434`,我们配置Nginx监听443端口(HTTPS),并将请求转发到Ollama。 在Nginx配置文件中(例如`/etc/nginx/conf.d/ollama.conf`)添加: ```nginx server { listen 443 ssl; server_name localhost; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; location / { proxy_pass http://localhost:11434; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } ``` 重启Nginx服务。 ### 3. Java代码调用HTTPS接口 由于使用了自签名证书,Java默认不信任,因此需要配置信任该证书或忽略SSL证书验证(仅测试环境使用)。 方法一:导入自签名证书到Java信任库(推荐在测试环境中使用) 步骤: - 将`cert.pem`导入到Java的信任库中: ```bash keytool -import -alias ollama -keystore $JAVA_HOME/lib/security/cacerts -file cert.pem ``` 默认密码是`changeit`。 方法二:在代码中忽略证书验证(不推荐,仅用于测试) 我们可以创建一个忽略SSL验证的HttpClient。 以下是使用HttpClient调用HTTPS接口的示例代码(忽略SSL验证): ```java import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.TrustSelfSignedStrategy; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.util.EntityUtils; import javax.net.ssl.SSLContext; import java.util.HashMap; import java.util.Map; public class HttpsModelCaller { public static void main(String[] args) throws Exception { // 创建SSLContext,信任自签名证书 SSLContext sslContext = SSLContextBuilder.create() .loadTrustMaterial(new TrustSelfSignedStrategy()) .build(); // 创建SSL连接工厂,忽略主机名验证 SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory( sslContext, NoopHostnameVerifier.INSTANCE); // 创建HttpClient try (CloseableHttpClient httpClient = HttpClients.custom() .setSSLSocketFactory(socketFactory) .build()) { // 请求URL(使用HTTPS) String url = "https://localhost/api/generate"; // 创建POST请求 HttpPost httpPost = new HttpPost(url); httpPost.addHeader("Content-Type", "application/json"); // 构建请求体 Map<String, Object> requestBody = new HashMap<>(); requestBody.put("model", "deepseek-r1:1.5b"); // 或者使用其他模型,如"qwen2-7b-instruct" requestBody.put("prompt", "3*4 结果是什么,不要分析和解读。"); requestBody.put("stream", false); // 将请求体设置为JSON字符串 StringEntity entity = new StringEntity(JSONUtil.toJsonStr(requestBody)); // 假设使用hutool的JSONUtil,或者用其他JSON库 httpPost.setEntity(entity); // 发送请求 try (CloseableHttpResponse response = httpClient.execute(httpPost)) { HttpEntity responseEntity = response.getEntity(); if (responseEntity != null) { String result = EntityUtils.toString(responseEntity); System.out.println(result); } } } } } ``` 注意:上述代码使用了Apache HttpClient 4.x,并且使用了hutool的JSONUtil(也可以使用其他JSON库如Jackson、Gson等)。另外,忽略SSL验证仅用于测试环境,生产环境应使用有效的证书并正确配置信任。 如果使用Ollama,则API路径为`/api/generate`,如引用[3]和[4]所示。如果使用其他模型服务(如vLLM),则需调整API路径和参数。 ### 4. 使用有效证书(生产环境) 在生产环境中,应使用由受信任的证书颁发机构(CA)签发的证书,这样就不需要忽略SSL验证,Java默认会信任。 总结:通过配置反向代理(如Nginx)为本地大模型服务添加HTTPS支持,然后在Java中调用HTTPS接口。注意处理自签名证书的信任问题(测试环境可忽略,生产环境应使用有效证书)。 §§相关问题§§ 1. 如何为本地大模型服务(如Ollama)配置HTTPS? 2. 在Java中如何处理自签名证书的HTTPS请求? 3. 除了Nginx,还有哪些方法可以为本地服务添加HTTPS支持? 4. 如何生成由受信任的CA签发的证书用于生产环境?</think>### Java通过HTTPS调用本地大模型指南 #### 核心步骤 1. **配置本地模型服务HTTPS支持** - 生成自签名证书(测试环境): ```bash openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes ``` - 修改模型服务配置(如Ollama): ```yaml # ollama配置文件 ssl_cert: "/path/to/cert.pem" ssl_key: "/path/to/key.pem" ``` 重启服务后,模型API将支持`https://localhost:11434`访问[^3]。 2. **Java客户端实现HTTPS调用** ```java import javax.net.ssl.SSLContext; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; public class HttpsModelCaller { public static void main(String[] args) throws Exception { // 1. 创建信任自签名证书的SSL上下文 SSLContext sslContext = SSLContextBuilder.create() .loadTrustMaterial((chain, authType) -> true) // 信任所有证书 .build(); // 2. 创建支持HTTPS的HttpClient try (CloseableHttpClient httpClient = HttpClients.custom() .setSSLContext(sslContext) .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE) // 忽略主机名验证 .build()) { // 3. 构建HTTPS请求 HttpPost post = new HttpPost("https://localhost:11434/api/generate"); post.setHeader("Content-Type", "application/json"); // 4. 设置请求体(JSON格式) String jsonBody = "{\"model\":\"deepseek-r1:1.5b\"," + "\"prompt\":\"解释量子纠缠现象\"," + "\"stream\":false}"; post.setEntity(new StringEntity(jsonBody)); // 5. 执行请求并获取响应 try (CloseableHttpResponse response = httpClient.execute(post)) { String result = EntityUtils.toString(response.getEntity()); System.out.println("模型响应: " + result); } } } } ``` #### 关键配置说明 1. **证书验证处理**: - 测试环境:使用`loadTrustMaterial((chain, authType) -> true)`信任所有证书 - 生产环境:需将CA证书导入Java信任库: ```bash keytool -import -alias model_cert -keystore $JAVA_HOME/lib/security/cacerts -file ca.pem ``` 2. **请求参数说明**: ```json { "model": "模型名称", // 如qwen2-7b-instruct "prompt": "你的问题", "stream": false, // 是否流式响应 "max_tokens": 500 // 最大输出长度 } ``` 3. **流式响应处理**: 当`stream=true`时,需使用分块传输解析: ```java // 在HttpClient配置中添加 .setConnectionReuseStrategy((response, context) -> false) // 响应解析 InputStream content = response.getEntity().getContent(); BufferedReader reader = new BufferedReader(new InputStreamReader(content)); String line; while ((line = reader.readLine()) != null) { System.out.println("流式数据块: " + line); } ``` #### 常见问题解决 1. **证书验证错误**: - 错误信息:`PKIX path building failed` - 解决方案:添加`SSLContextBuilder.create().loadTrustMaterial(new TrustSelfSignedStrategy())` 2. **连接超时**: - 在请求配置中增加超时设置: ```java RequestConfig config = RequestConfig.custom() .setConnectTimeout(30000) .setSocketTimeout(60000).build(); httpClient.setDefaultRequestConfig(config); ``` 3. **中文乱码问题**: ```java StringEntity entity = new StringEntity(jsonBody, "UTF-8"); entity.setContentType("application/json; charset=UTF-8"); ``` > **安全建议**:生产环境应使用正式CA证书,通过Nginx配置HTTPS反向代理,避免在Java代码中绕过证书验证[^1][^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值