การพิสูจน์ตัวตนใบรับรองไคลเอ็นต์ Java HTTPS


222

ฉันค่อนข้างใหม่HTTPS/SSL/TLSและฉันค่อนข้างสับสนกับสิ่งที่ลูกค้าควรจะนำเสนอเมื่อรับรองความถูกต้องกับใบรับรอง

ฉันเขียนลูกค้า Java ที่ความต้องการที่จะทำที่เรียบง่ายของข้อมูลที่จะโดยเฉพาะอย่างยิ่งPOST ว่าส่วนที่ทำงานได้ดีปัญหาเดียวก็คือมันควรจะทำได้มากกว่าURL ส่วนหนึ่งค่อนข้างง่ายที่จะจับ (ทั้งที่มีหรือใช้ Java ในตัวสนับสนุน) แต่ฉันติดอยู่ในการตรวจสอบที่มีใบรับรองไคลเอ็นต์ ฉันสังเกตเห็นว่ามีคำถามที่คล้ายกันอยู่แล้วในที่นี้ซึ่งฉันยังไม่ได้ลองด้วยรหัสของฉัน (จะทำได้เร็วพอ) ปัญหาปัจจุบันของฉันคือ - ไม่ว่าฉันจะทำอะไร - ไคลเอนต์ Java ไม่ส่งตามใบรับรอง (ฉันสามารถตรวจสอบได้ด้วยการทิ้ง)HTTPSHTTPSHTTPclientHTTPSPCAP

ฉันต้องการที่จะรู้ว่าสิ่งที่ลูกค้าควรจะนำเสนอไปยังเซิร์ฟเวอร์เมื่อรับรองความถูกต้องกับใบรับรอง (โดยเฉพาะสำหรับ Java - ถ้าสิ่งที่สำคัญเลย) นี้เป็นJKSแฟ้มหรือPKCS#12? สิ่งที่ควรจะเป็นในพวกเขา; เพียงแค่ใบรับรองไคลเอ็นต์หรือคีย์? ถ้าเป็นเช่นนั้นคีย์ใด? มีความสับสนเล็กน้อยเกี่ยวกับไฟล์ประเภทต่างๆประเภทใบรับรองและอื่น ๆ

อย่างที่ฉันได้พูดไปก่อนหน้านี้ฉันยังใหม่กับHTTPS/SSL/TLSฉันดังนั้นฉันจึงขอขอบคุณข้อมูลพื้นฐานบางอย่างเช่นกัน (ไม่จำเป็นต้องเป็นเรียงความ


ฉันได้รับสองใบรับรองจากลูกค้าวิธีการที่จะระบุว่าหนึ่งในความต้องการที่จะเพิ่มใน keystore และ truststore คุณช่วยระบุปัญหานี้เป็นคุณได้ผ่านไปแล้วชนิดของปัญหาที่คล้ายกันปัญหานี้ผมได้ยกขึ้นจริงไม่ได้รับเบาะแสว่าจะทำอย่างไร StackOverflow .com / คำถาม / 61374276 / …
henrycharles

คำตอบ:


233

ในที่สุดก็จัดการเพื่อแก้ไขปัญหาทั้งหมดดังนั้นฉันจะตอบคำถามของฉันเอง นี่คือการตั้งค่า / ไฟล์ที่ฉันเคยจัดการเพื่อแก้ไขปัญหาเฉพาะของฉัน

ที่เก็บคีย์ของไคลเอ็นต์คือไฟล์รูปแบบ PKCS # 12ที่มี

  1. ใบรับรองสาธารณะของลูกค้า(ในอินสแตนซ์นี้ลงนามโดย CA ที่ลงนามด้วยตนเอง)
  2. คีย์ส่วนตัวของลูกค้า

เพื่อสร้างมันฉันใช้pkcs12คำสั่งของ OpenSSL เช่น;

openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -name "Whatever"

เคล็ดลับ:ตรวจสอบให้แน่ใจว่าคุณได้รับ OpenSSL ล่าสุดไม่ใช่เวอร์ชัน 0.9.8h เนื่องจากดูเหมือนว่าจะมีปัญหาจากบั๊กซึ่งไม่อนุญาตให้คุณสร้างไฟล์ PKCS # 12 อย่างถูกต้อง

ไฟล์ PKCS # 12 นี้จะถูกใช้โดยไคลเอนต์ Java เพื่อแสดงใบรับรองไคลเอ็นต์ไปยังเซิร์ฟเวอร์เมื่อเซิร์ฟเวอร์ได้ร้องขอให้ไคลเอนต์รับรองความถูกต้องอย่างชัดเจน ดูบทความ Wikipedia บน TLSสำหรับภาพรวมของวิธีการใช้งานโปรโตคอลสำหรับการตรวจสอบใบรับรองลูกค้าจริง (อธิบายด้วยว่าทำไมเราต้องใช้รหัสส่วนตัวของลูกค้าที่นี่)

truststore ของลูกค้าเป็นตรงไปตรงมาในรูปแบบจางกึนซอกไฟล์ที่มีรากหรือใบรับรอง CA กลาง ใบรับรอง CA เหล่านี้จะกำหนดปลายทางที่คุณจะได้รับอนุญาตให้สื่อสารด้วยในกรณีนี้จะช่วยให้ลูกค้าของคุณเชื่อมต่อกับเซิร์ฟเวอร์ใดก็ตามที่แสดงใบรับรองที่ลงชื่อโดย CA แห่งใดแห่งหนึ่งของ truststore

เพื่อสร้างมันคุณสามารถใช้ Java keytool มาตรฐานตัวอย่างเช่น

keytool -genkey -dname "cn=CLIENT" -alias truststorekey -keyalg RSA -keystore ./client-truststore.jks -keypass whatever -storepass whatever
keytool -import -keystore ./client-truststore.jks -file myca.crt -alias myca

ใช้ truststore นี้ลูกค้าของคุณจะพยายามที่จะทำจับมือ SSL ที่สมบูรณ์แบบด้วยเซิร์ฟเวอร์ทั้งหมดที่นำเสนอใบรับรองที่ลงนามโดย CA myca.crtที่ระบุโดย

ไฟล์ด้านบนใช้สำหรับลูกค้าเท่านั้น เมื่อคุณต้องการตั้งค่าเซิร์ฟเวอร์เช่นกันเซิร์ฟเวอร์ต้องใช้ไฟล์คีย์และ truststore ของตัวเอง การเดินผ่านที่ยอดเยี่ยมสำหรับการตั้งค่าตัวอย่างการทำงานอย่างสมบูรณ์สำหรับทั้งไคลเอนต์ Java และเซิร์ฟเวอร์ (ใช้ Tomcat) สามารถพบได้บนเว็บไซต์นี้

ประเด็นปัญหา / ข้อสังเกต / เคล็ดลับ

  1. การพิสูจน์ตัวตนใบรับรองไคลเอ็นต์สามารถบังคับใช้โดยเซิร์ฟเวอร์เท่านั้น
  2. ( สำคัญ! ) เมื่อเซิร์ฟเวอร์ร้องขอใบรับรองไคลเอ็นต์ (ซึ่งเป็นส่วนหนึ่งของการจับมือ TLS) ก็จะแสดงรายการ CA ที่เชื่อถือได้ซึ่งเป็นส่วนหนึ่งของคำขอใบรับรอง เมื่อใบรับรองลูกค้าที่คุณต้องการนำเสนอสำหรับการตรวจสอบสิทธิ์ไม่ได้ลงนามโดยหนึ่งใน CA เหล่านี้มันจะไม่ถูกนำเสนอเลย (ในความคิดของฉันนี่เป็นพฤติกรรมแปลก ๆ แต่ฉันแน่ใจว่ามีเหตุผล) นี่เป็นสาเหตุหลักของปัญหาของฉันเนื่องจากบุคคลอื่นไม่ได้กำหนดค่าเซิร์ฟเวอร์ของตนให้ยอมรับใบรับรองไคลเอ็นต์ที่ลงชื่อด้วยตนเองของฉันและเราคิดว่าปัญหานี้สิ้นสุดแล้วที่ฉันไม่ได้ให้ใบรับรองไคลเอ็นต์ในคำขออย่างถูกต้อง
  3. รับ Wireshark มีการวิเคราะห์แพ็คเก็ต SSL / HTTPS ที่ยอดเยี่ยมและจะช่วยในการแก้ไขข้อบกพร่องและค้นหาปัญหาได้อย่างมาก มันคล้ายกับ-Djavax.net.debug=sslแต่มีโครงสร้างมากขึ้นและ (ตีความ) ง่ายต่อการตีความหากคุณรู้สึกไม่สะดวกใจกับ Java debug output
  4. เป็นไปได้อย่างสมบูรณ์ที่จะใช้ Apache httpclient library หากคุณต้องการใช้ httpclient เพียงแค่แทนที่ URL ปลายทางด้วย HTTPS ที่เทียบเท่าและเพิ่มอาร์กิวเมนต์ JVM ต่อไปนี้ (ซึ่งเหมือนกันสำหรับไคลเอนต์อื่น ๆ โดยไม่คำนึงถึงไลบรารีที่คุณต้องการใช้ในการส่ง / รับข้อมูลผ่าน HTTP / HTTPS) :

    -Djavax.net.debug=ssl
    -Djavax.net.ssl.keyStoreType=pkcs12
    -Djavax.net.ssl.keyStore=client.p12
    -Djavax.net.ssl.keyStorePassword=whatever
    -Djavax.net.ssl.trustStoreType=jks
    -Djavax.net.ssl.trustStore=client-truststore.jks
    -Djavax.net.ssl.trustStorePassword=whatever

6
"เมื่อใบรับรองไคลเอ็นต์ที่คุณต้องการแสดงเพื่อรับรองความถูกต้องไม่ได้ลงนามโดยหนึ่งใน CA เหล่านี้จะไม่มีการนำเสนอใบรับรองเลย" ไม่มีการนำเสนอใบรับรองเนื่องจากไคลเอนต์รู้ว่าจะไม่ได้รับการยอมรับจากเซิร์ฟเวอร์ นอกจากนี้ใบรับรองของคุณสามารถลงนามโดย CA ระดับกลาง "ICA" และเซิร์ฟเวอร์สามารถแสดงไคลเอนต์ของคุณด้วยรูท CA "RCA" และเว็บเบราว์เซอร์ของคุณจะยังคงให้คุณเลือกใบรับรองของคุณแม้ว่าจะลงนามโดย ICA ไม่ใช่ RCA
KyleM

2
เป็นตัวอย่างของความคิดเห็นข้างต้นพิจารณาสถานการณ์ที่คุณมีหนึ่งรูท CA (RCA1) และ CA ระดับกลางสองอัน (ICA1 และ ICA2) บน Apache Tomcat ถ้าคุณนำเข้า RCA1 ไปยังร้านค้าที่เชื่อถือได้เว็บเบราว์เซอร์ของคุณจะแสดงใบรับรองทั้งหมดที่ลงนามโดย ICA1 และ ICA2 แม้ว่าพวกเขาจะไม่ได้อยู่ในร้านค้าที่เชื่อถือได้ของคุณ นี่เป็นเพราะมันเป็นโซ่ที่ไม่เกี่ยวกับการร้องของแต่ละคน
KyleM

2
"ในความคิดของฉันนี่เป็นพฤติกรรมแปลก ๆ แต่ฉันแน่ใจว่ามีเหตุผล" เหตุผลก็คือว่าเป็นสิ่งที่กล่าวใน RFC 2246 ไม่มีอะไรแปลกเกี่ยวกับมัน การอนุญาตให้ไคลเอนต์แสดงใบรับรองที่ไม่ได้รับการยอมรับจากเซิร์ฟเวอร์คือสิ่งที่จะแปลกและเสียเวลาและพื้นที่อย่างสมบูรณ์
มาร์ควิสแห่ง Lorne

1
ฉันขอแนะนำอย่างยิ่งให้ใช้ที่เก็บคีย์เดียวแบบกว้าง ๆ JVM (และ truststore!) ตามที่คุณมีในตัวอย่างด้านบน การปรับแต่งการเชื่อมต่อเดียวนั้นปลอดภัยและยืดหยุ่นมากกว่า แต่ก็ต้องการให้คุณเขียนรหัสเพิ่มอีกเล็กน้อย คุณต้องปรับแต่งSSLContextคำตอบใน @ Magnus
Christopher Schultz

63

คำตอบอื่น ๆ แสดงวิธีกำหนดค่าใบรับรองไคลเอ็นต์ทั่วโลก อย่างไรก็ตามหากคุณต้องการกำหนดคีย์ไคลเอ็นต์โดยทางโปรแกรมสำหรับการเชื่อมต่อหนึ่งโดยเฉพาะแทนที่จะกำหนดไว้ทั่วโลกในทุก ๆ แอปพลิเคชันที่ทำงานบน JVM ของคุณคุณสามารถกำหนดค่า SSLContext ของคุณเองดังนี้:

String keyPassphrase = "";

KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("cert-key-pair.pfx"), keyPassphrase.toCharArray());

SSLContext sslContext = SSLContexts.custom()
        .loadKeyMaterial(keyStore, null)
        .build();

HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build();
HttpResponse response = httpClient.execute(new HttpGet("https://example.com"));

sslContext = SSLContexts.custom().loadTrustMaterial(keyFile, PASSWORD).build();ผมต้องใช้ loadKeyMaterial(...)ฉันไม่สามารถรับมันทำงานร่วมกับ
Conor Svensson

4
วัสดุ @ConorSvensson Trust มีไว้สำหรับลูกค้าที่เชื่อถือใบรับรองเซิร์ฟเวอร์ระยะไกลวัสดุสำคัญสำหรับเซิร์ฟเวอร์ที่ไว้วางใจลูกค้า
แมกนัส

1
ฉันชอบคำตอบที่กระชับและตรงประเด็นนี้จริงๆ ในกรณีที่ผู้คนสนใจฉันจะให้ตัวอย่างการทำงานที่นี่พร้อมคำแนะนำการสร้าง stackoverflow.com/a/46821977/154527
Alain O'Dea

3
คุณบันทึกวันของฉัน! เพียงหนึ่งการเปลี่ยนแปลงพิเศษที่ฉันต้องทำส่งรหัสผ่านในloadKeyMaterial(keystore, keyPassphrase.toCharArray())รหัส 'เช่นกัน!
AnandShanbhag

1
@peterh ใช่มันเป็นเฉพาะกับ Apache http ห้องสมุด HTTP ทุกคนจะมีวิธีการของตัวเองจากการถูกกำหนดค่า SSLContextแต่ส่วนใหญ่ของพวกเขาอย่างใดควรใช้
แมกนัส

30

ไฟล์ JKS เป็นเพียงคอนเทนเนอร์สำหรับใบรับรองและคู่คีย์ ในสถานการณ์การรับรองความถูกต้องฝั่งไคลเอ็นต์ส่วนต่าง ๆ ของคีย์จะอยู่ที่นี่:

  • ลูกค้าร้าน 's จะมีของลูกค้าภาครัฐและเอกชนคู่คีย์ มันจะเรียกว่าเป็นที่เก็บคีย์
  • เซิร์ฟเวอร์ร้าน 's จะมีของลูกค้าประชาชนที่สำคัญ มันถูกเรียกว่าtruststore

การแยก truststore และ keystore ไม่จำเป็นต้องทำ แต่แนะนำ พวกเขาสามารถเป็นไฟล์ทางกายภาพเดียวกัน

ในการตั้งค่าตำแหน่งระบบไฟล์ของสองร้านค้าให้ใช้คุณสมบัติของระบบต่อไปนี้:

-Djavax.net.ssl.keyStore=clientsidestore.jks

และบนเซิร์ฟเวอร์:

-Djavax.net.ssl.trustStore=serversidestore.jks

ในการส่งออกใบรับรองของลูกค้า (กุญแจสาธารณะ) ไปยังไฟล์ดังนั้นคุณสามารถคัดลอกไปยังเซิร์ฟเวอร์ใช้

keytool -export -alias MYKEY -file publicclientkey.cer -store clientsidestore.jks

เมื่อต้องการนำเข้ากุญแจสาธารณะของลูกค้าไปยังที่เก็บคีย์ของเซิร์ฟเวอร์ให้ใช้ (ดังที่ผู้โพสต์กล่าวถึงโดยผู้ดูแลระบบของเซิร์ฟเวอร์ได้ทำสิ่งนี้แล้ว)

keytool -import -file publicclientkey.cer -store serversidestore.jks

ฉันควรจะพูดถึงว่าฉันไม่สามารถควบคุมเซิร์ฟเวอร์ เซิร์ฟเวอร์ได้นำเข้าใบรับรองสาธารณะของเราแล้ว ฉันได้รับการบอกเล่าจากผู้ดูแลระบบว่าฉันต้องให้ใบรับรองอย่างชัดเจนเพื่อให้สามารถส่งได้ในระหว่างการจับมือกัน (เซิร์ฟเวอร์ของพวกเขาร้องขออย่างชัดเจน)
tmbrggmn

คุณจะต้องใช้กุญแจสาธารณะและกุญแจส่วนตัวสำหรับใบรับรองสาธารณะของคุณ (อันที่รู้จักกันในเซิร์ฟเวอร์) เป็นไฟล์ JKS
sfussenegger

ขอบคุณสำหรับตัวอย่างรหัส ในรหัสด้านบน "mykey-public.cer" คืออะไรกันแน่ นี่เป็นใบรับรองสาธารณะของลูกค้าหรือไม่ (เราใช้ใบรับรองที่ลงชื่อด้วยตนเอง)
tmbrggmn

ใช่แล้วฉันได้เปลี่ยนชื่อไฟล์ในตัวอย่างโค้ดแล้ว ฉันหวังว่านี่จะทำให้ชัดเจน
mhaller

ใช่แล้วขอบคุณ ฉันสับสนอยู่เสมอเพราะเห็นได้ชัดว่า "คีย์" และ "ใบรับรอง" ใช้แทนกันได้
tmbrggmn

10

Maven pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>some.examples</groupId>
    <artifactId>sslcliauth</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>sslcliauth</name>
    <dependencies>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.4</version>
        </dependency>
    </dependencies>
</project>

รหัส Java:

package some.examples;

import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLContext;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.http.entity.InputStreamEntity;

public class SSLCliAuthExample {

private static final Logger LOG = Logger.getLogger(SSLCliAuthExample.class.getName());

private static final String CA_KEYSTORE_TYPE = KeyStore.getDefaultType(); //"JKS";
private static final String CA_KEYSTORE_PATH = "./cacert.jks";
private static final String CA_KEYSTORE_PASS = "changeit";

private static final String CLIENT_KEYSTORE_TYPE = "PKCS12";
private static final String CLIENT_KEYSTORE_PATH = "./client.p12";
private static final String CLIENT_KEYSTORE_PASS = "changeit";

public static void main(String[] args) throws Exception {
    requestTimestamp();
}

public final static void requestTimestamp() throws Exception {
    SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(
            createSslCustomContext(),
            new String[]{"TLSv1"}, // Allow TLSv1 protocol only
            null,
            SSLConnectionSocketFactory.getDefaultHostnameVerifier());
    try (CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(csf).build()) {
        HttpPost req = new HttpPost("https://changeit.com/changeit");
        req.setConfig(configureRequest());
        HttpEntity ent = new InputStreamEntity(new FileInputStream("./bytes.bin"));
        req.setEntity(ent);
        try (CloseableHttpResponse response = httpclient.execute(req)) {
            HttpEntity entity = response.getEntity();
            LOG.log(Level.INFO, "*** Reponse status: {0}", response.getStatusLine());
            EntityUtils.consume(entity);
            LOG.log(Level.INFO, "*** Response entity: {0}", entity.toString());
        }
    }
}

public static RequestConfig configureRequest() {
    HttpHost proxy = new HttpHost("changeit.local", 8080, "http");
    RequestConfig config = RequestConfig.custom()
            .setProxy(proxy)
            .build();
    return config;
}

public static SSLContext createSslCustomContext() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, KeyManagementException, UnrecoverableKeyException {
    // Trusted CA keystore
    KeyStore tks = KeyStore.getInstance(CA_KEYSTORE_TYPE);
    tks.load(new FileInputStream(CA_KEYSTORE_PATH), CA_KEYSTORE_PASS.toCharArray());

    // Client keystore
    KeyStore cks = KeyStore.getInstance(CLIENT_KEYSTORE_TYPE);
    cks.load(new FileInputStream(CLIENT_KEYSTORE_PATH), CLIENT_KEYSTORE_PASS.toCharArray());

    SSLContext sslcontext = SSLContexts.custom()
            //.loadTrustMaterial(tks, new TrustSelfSignedStrategy()) // use it to customize
            .loadKeyMaterial(cks, CLIENT_KEYSTORE_PASS.toCharArray()) // load client certificate
            .build();
    return sslcontext;
}

}

หากคุณต้องการให้ใบรับรองพร้อมใช้งานสำหรับแอปพลิเคชันทั้งหมดที่ใช้การติดตั้ง JVM เฉพาะให้ทำตามคำตอบนี้
ADTC

วิธีconfigureRequest()ในการตั้งค่าพร็อกซีของโครงการลูกค้าถูกต้องหรือไม่
shareef

ใช่มันคือการกำหนดค่าไคลเอนต์ http และมันคือการกำหนดค่าพร็อกซีในกรณีนี้
kinjelom

บางทีฉันอาจมีคำถามโง่ แต่ฉันจะสร้างไฟล์ cacert.jks ได้อย่างไร ฉันมีข้อยกเว้นข้อยกเว้นในเธรด "main" java.io.FileNotFoundException:. \ cacert.jks (ระบบไม่สามารถหาไฟล์ที่ระบุได้)
Patlatus

6

สำหรับผู้ที่ต้องการตั้งค่าการรับรองความถูกต้องสองทาง (ใบรับรองเซิร์ฟเวอร์และไคลเอนต์) การรวมกันของลิงก์ทั้งสองนี้จะพาคุณไปที่นั่น:

การตั้งค่าการตรวจสอบสองทาง:

https://linuxconfig.org/apache-web-server-ssl-authentication

คุณไม่จำเป็นต้องใช้ไฟล์ configsl ที่พวกเขาพูดถึง เพียงแค่ใช้

  • $ openssl genrsa -des3 -out ca.key 4096

  • $ openssl req - ใหม่ -x509 - วันที่ 365 -key ca.key -out ca.crt

เพื่อสร้างใบรับรอง CA ของคุณเองจากนั้นสร้างและเซ็นชื่อเซิร์ฟเวอร์และไคลเอนต์คีย์ผ่าน:

  • $ openssl genrsa -des3 -out server.key 4096

  • $ openssl req -new -key server.key -out server.csr

  • $ openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 100 -out server.crt

และ

  • $ openssl genrsa -des3 -out client.key 4096

  • $ openssl req -new -key client.key -out client.csr

  • $ openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 101 -out client.crt

สำหรับส่วนที่เหลือทำตามขั้นตอนในลิงค์ การจัดการใบรับรองสำหรับ Chrome ทำงานเหมือนกับในตัวอย่างสำหรับ firefox ที่กล่าวถึง

ถัดไปตั้งค่าเซิร์ฟเวอร์ผ่าน:

https://www.digitalocean.com/community/tutorials/how-to-create-a-ssl-certificate-on-apache-for-ubuntu-14-04

โปรดทราบว่าคุณได้สร้างเซิร์ฟเวอร์. crt และ. key ไว้แล้วคุณจึงไม่ต้องทำขั้นตอนนั้นอีกต่อไป


1
คิดว่าคุณมีการพิมพ์ผิดในเซิร์ฟเวอร์ขั้นตอนการสร้างความรับผิดชอบต่อสังคม: ควรใช้server.keyไม่ได้client.key
the.Legend

0

ฉันเชื่อมต่อกับธนาคารด้วย SSL แบบสองทาง (ไคลเอนต์และใบรับรองเซิร์ฟเวอร์) ด้วย Spring Boot เพื่ออธิบายทุกขั้นตอนของฉันที่นี่หวังว่ามันจะช่วยคน (วิธีแก้ปัญหาการทำงานที่ง่ายที่สุดที่ฉันเคยพบ):

  1. สร้างการร้องขอใบรับรอง:

    • สร้างรหัสส่วนตัว:

      openssl genrsa -des3 -passout pass:MY_PASSWORD -out user.key 2048
    • สร้างคำขอใบรับรอง:

      openssl req -new -key user.key -out user.csr -passin pass:MY_PASSWORD

    เก็บuser.key(และรหัสผ่าน) และส่งการร้องขอใบรับรองuser.csrไปยังธนาคารสำหรับใบรับรองของฉัน

  2. รับ 2 ใบรับรอง: ใบรับรองลูกค้าของฉันclientId.crtและใบรับรองธนาคาร:bank.crt

  3. สร้าง Java keystore (ป้อนรหัสผ่านคีย์และตั้งรหัสผ่าน keystore):

    openssl pkcs12 -export -in clientId.crt -inkey user.key -out keystore.p12 -name clientId -CAfile ca.crt -caname root

    unable to write 'random state'ไม่ให้ความสนใจในการส่งออก: keystore.p12สร้างJava PKCS12 แล้ว

  4. เพิ่มลงในที่เก็บคีย์bank.crt(เพื่อความเรียบง่ายฉันได้ใช้ที่เก็บคีย์หนึ่งอัน):

    keytool -import -alias banktestca -file banktestca.crt -keystore keystore.p12 -storepass javaops

    ตรวจสอบใบรับรองที่เก็บคีย์โดย:

    keytool -list -keystore keystore.p12
  5. พร้อมแล้วสำหรับโค้ด Java :) ฉันใช้ Spring Boot RestTemplateพร้อมการเพิ่มการorg.apache.httpcomponents.httpcoreอ้างอิง:

    @Bean("sslRestTemplate")
    public RestTemplate sslRestTemplate() throws Exception {
      char[] storePassword = appProperties.getSslStorePassword().toCharArray();
      URL keyStore = new URL(appProperties.getSslStore());
    
      SSLContext sslContext = new SSLContextBuilder()
            .loadTrustMaterial(keyStore, storePassword)
      // use storePassword twice (with key password do not work)!!
            .loadKeyMaterial(keyStore, storePassword, storePassword) 
            .build();
    
      // Solve "Certificate doesn't match any of the subject alternative names"
      SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
    
      CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
      HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(client);
      RestTemplate restTemplate = new RestTemplate(factory);
      // restTemplate.setMessageConverters(List.of(new Jaxb2RootElementHttpMessageConverter()));
      return restTemplate;
    }

คุณสามารถทำได้ทุกอย่างด้วย keytool ไม่จำเป็นต้องเปิด OpenSSL เลย
มาร์ควิสแห่ง Lorne

0

รับไฟล์ p12 ที่มีทั้งใบรับรองและไพรเวตคีย์ (สร้างโดย openssl เป็นต้น) รหัสต่อไปนี้จะใช้สำหรับ HttpsURLConnection เฉพาะสำหรับการเชื่อมต่อ:

    KeyStore keyStore = KeyStore.getInstance("pkcs12");
    keyStore.load(new FileInputStream(keyStorePath), keystorePassword.toCharArray());
    KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    kmf.init(keyStore, keystorePassword.toCharArray());
    SSLContext ctx = SSLContext.getInstance("TLS");
    ctx.init(kmf.getKeyManagers(), null, null);
    SSLSocketFactory sslSocketFactory = ctx.getSocketFactory();

    HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
    connection.setSSLSocketFactory(sslSocketFactory);

การSSLContextเริ่มต้นใช้เวลาสักครู่ดังนั้นคุณอาจต้องการแคช


-1

ฉันคิดว่าการแก้ไขที่นี่คือประเภท keystore, pkcs12 (pfx) มักจะมีคีย์ส่วนตัวและประเภท JKS สามารถอยู่ได้โดยไม่ต้องมีคีย์ส่วนตัว ถ้าคุณไม่ระบุในรหัสของคุณหรือเลือกใบรับรองผ่านเบราว์เซอร์เซิร์ฟเวอร์จะไม่มีทางรู้ว่ามันเป็นตัวแทนของลูกค้าในส่วนอื่น ๆ


1
รูปแบบดั้งเดิมของ PKCS12 ใช้สำหรับ privatekey-AND-cert แต่ Java ตั้งแต่ 8 ในปี 2014 (มากกว่าหนึ่งปีก่อนคำตอบนี้) ได้รับการสนับสนุน PKCS12 ที่มีใบรับรองโดยไม่ต้องมีคีย์ส่วนตัว คำนึงถึงรูปแบบที่เก็บคีย์การตรวจสอบสิทธิ์ไคลเอ็นต์จะต้องใช้ privatekey-AND-cert ฉันไม่เข้าใจประโยคที่สองของคุณ แต่ไคลเอนต์ Java สามารถเลือกใบรับรองลูกค้าและคีย์โดยอัตโนมัติหากมีรายการที่เหมาะสมอย่างน้อยหนึ่งรายการพร้อมใช้งานหรือสามารถกำหนดค่าkeymanager ให้ใช้รายการที่ระบุได้
dave_thompson_085

ประโยคที่สองของคุณไม่ถูกต้องทั้งหมด เซิร์ฟเวอร์จัดให้มีผู้ลงนามที่เชื่อถือได้และลูกค้าอาจตายหรือไม่มีใบรับรองที่ตอบสนองข้อ จำกัด ดังกล่าว อัตโนมัติไม่ผ่าน 'ในรหัสของคุณ' นั่นคือวิธีการที่เซิร์ฟเวอร์รู้ว่ามันเป็นตัวแทนของลูกค้า
มาร์ควิสแห่ง Lorne
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.