Khắc phục sự cố OpenSSL: Giải quyết lỗi unable to get local issuer certificate
Khi sử dụng OpenSSL để kết nối bảo mật (chẳng hạn thực hiện các truy vấn mạng trong môi trường PHP, chạy lệnh openssl
hoặc curl
, hoặc kết nối SSL/TLS trong ứng dụng Node.js/Python), các lập trình viên có thể gặp lỗi 20:unable to get local issuer certificate
. Đây là một vấn đề khá phổ biến, có liên quan đến quá trình OpenSSL xác thực chứng chỉ đối tác.
Vì lý do bảo mật, theo mặc định, OpenSSL yêu cầu biết chính xác các chứng chỉ gốc của tổ chức phát hành chứng chỉ (CA) được tin cậy để xác thực chuỗi chứng chỉ. Nếu không tìm thấy hoặc không chỉ định rõ các điểm neo tin cậy này, OpenSSL sẽ không thể xác thực hợp lệ chứng chỉ của máy chủ và trả về lỗi trên.
Tài liệu này sẽ giải thích chi tiết nguyên nhân của lỗi này và hướng dẫn cách khắc phục trong môi trường ServBay, bao gồm cấu hình nơi lưu trữ CA cho các thành phần như PHP, Python, Node.js và lệnh curl
.
Thông báo lỗi 20:unable to get local issuer certificate
Mô tả vấn đề
Khi OpenSSL cố gắng xác thực chứng chỉ SSL/TLS của máy chủ từ xa, nó sẽ xây dựng một chuỗi chứng chỉ từ chứng chỉ máy chủ đến CA gốc tin cậy. Nếu thiếu bất kỳ chứng chỉ trung gian hoặc chứng chỉ gốc CA nào trên máy địa phương, hoặc không có thư mục lưu trữ chứng chỉ đáng tin nào (CAFile hoặc CAPath) được cấu hình, quá trình xác thực sẽ thất bại và thông báo lỗi 20:unable to get local issuer certificate
xuất hiện.
Hiểu đơn giản, OpenSSL không biết nên tin tưởng tổ chức phát hành chứng chỉ nào, do đó không thể xác nhận danh tính máy chủ kết nối.
Phiên bản OpenSSL và đường dẫn chứng chỉ CA trong ServBay
ServBay là môi trường phát triển web bản địa tích hợp sẵn gói phần mềm OpenSSL và tập hợp đầy đủ các chứng chỉ CA gốc thường dùng, tiện lợi cho nhà phát triển. Phiên bản OpenSSL trong ServBay sẽ tùy thuộc vào kiến trúc thiết bị bạn sử dụng:
- ServBay cho Apple Silicon (chip M-series): dùng OpenSSL phiên bản 3.2.1.
- ServBay cho Intel: dùng OpenSSL phiên bản 1.1.1u.
Các phiên bản này sẽ sử dụng file chứng chỉ CA (cacert.pem
) và thư mục chứng chỉ (certs
) đặt tại đường dẫn cài đặt của ServBay. Bạn cần xác định chính xác đường dẫn tương ứng với nền tảng sử dụng:
ini
# File tổng hợp các chứng chỉ gốc tin cậy
cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
# Thư mục các file chứng chỉ riêng lẻ (thông thường chỉ cần cacert.pem, nhưng một số ứng dụng có thể cần capath)
capath=/Applications/ServBay/package/common/openssl/3.2/certs
1
2
3
4
2
3
4
ini
# File tổng hợp các chứng chỉ gốc tin cậy
cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
# Thư mục các file chứng chỉ riêng lẻ
capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
1
2
3
4
2
3
4
ini
# File tổng hợp các chứng chỉ gốc tin cậy
cafile=C:\ServBay\package\common\openssl\3.3\cacert.pem
# Thư mục các file chứng chỉ riêng lẻ
capath=C:\ServBay\package\common\openssl\3.3\certs
1
2
3
4
2
3
4
Giải pháp cho lỗi unable to get local issuer certificate
là chỉ rõ đường dẫn của cafile
hoặc capath
ở những nơi cần dùng OpenSSL, hướng OpenSSL đến đúng nơi chứa chứng chỉ CA đáng tin.
Ví dụ giải pháp
Dưới đây là hướng dẫn cấu hình OpenSSL CA lưu trữ trên các công cụ và ngôn ngữ khác nhau.
Ví dụ 1: Kiểm tra kết nối bằng lệnh openssl
Nếu bạn sử dụng lệnh openssl s_client
để test kết nối và nhận được lỗi sau:
bash
openssl s_client -quiet -connect gmail.com:443
1
Kết quả có thể chứa lỗi tương tự dưới đây với dòng verify error:num=20:unable to get local issuer certificate
:
bash
depth=2 C=US, O=Google Trust Services LLC, CN=GTS Root R1
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=1 C=US, O=Google Trust Services, CN=WR2
verify return:1
depth=0 CN=gmail.com
verify return:1
# ... thông tin kết nối khác ...
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Cách khắc phục:
Dùng tham số -CAfile
để chỉ định rõ đường dẫn file CA mà ServBay cung cấp:
bash
openssl s_client -quiet -connect gmail.com:443 -CAfile /Applications/ServBay/package/common/openssl/3.2/cacert.pem
1
bash
openssl s_client -quiet -connect gmail.com:443 -CAfile /Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
1
Nếu kết nối và xác thực chứng chỉ thành công, đầu ra sẽ không còn dòng verify error:num=20
mà chỉ có verify return:1
:
bash
depth=2 C=US, O=Google Trust Services LLC, CN=GTS Root R1
verify return:1
depth=1 C=US, O=Google Trust Services, CN=WR2
verify return:1
depth=0 CN=gmail.com
verify return:1
# ... thông tin kết nối khác ...
1
2
3
4
5
6
7
2
3
4
5
6
7
Ví dụ 2: Dùng OpenSSL trong PHP
Nhiều tính năng mạng của PHP (như dùng file_get_contents
với URL HTTPS, dùng stream_socket_client
cho kết nối SSL, hoặc cURL extension) đều dựa vào OpenSSL. Bạn có thể cấu hình CA lưu trữ thông qua chỉnh sửa php.ini
hoặc thiết lập trong mã nguồn.
Cách A: Chỉnh sửa php.ini
(khuyên dùng)
Đây là cách toàn diện, áp dụng cho toàn bộ môi trường. Hãy mở file php.ini
của phiên bản PHP đang dùng (bạn có thể tìm điểm chỉnh sửa này qua bảng điều khiển ServBay) và thiết lập tại nhóm [openssl]
phần sau, nhớ chọn đường dẫn đúng kiến trúc thiết bị:
ini
[openssl]
; Chỉ định file CA gốc tin cậy
openssl.cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
; Chỉ định thư mục chứa chứng chỉ CA (tùy chọn, khuyên nên cấu hình)
openssl.capath=/Applications/ServBay/package/common/openssl/3.2/certs
1
2
3
4
5
2
3
4
5
ini
[openssl]
; Chỉ định file CA gốc tin cậy
openssl.cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
; Chỉ định thư mục chứa chứng chỉ CA (tùy chọn, khuyên nên cấu hình)
openssl.capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
1
2
3
4
5
2
3
4
5
Sau khi sửa, hãy khởi động lại dịch vụ PHP trong ServBay (hoặc toàn bộ ServBay) để cập nhật cấu hình.
Cách B: Thiết lập trong mã nguồn (chỉ cho kết nối hiện tại)
Nếu không muốn chỉnh sửa toàn cục, bạn có thể cài đặt trực tiếp khi tạo context SSL/TLS bằng stream_context_create
:
php
<?php
// Ví dụ: kết nối đến máy chủ SMTP dùng SSL/TLS
$server = 'ssl0.ovh.net';
$port = 465;
// Chọn đường dẫn file CA phù hợp với thiết bị ServBay
// Đối với Apple Silicon:
$caCertFile = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem';
// Đối với Intel:
// $caCertFile = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem';
$contextOptions = [
'ssl' => [
'verify_peer' => true, // Bật xác thực chứng chỉ đối tác
'verify_peer_name' => true, // Xác thực tên máy chủ trong chứng chỉ có khớp với host kết nối
'allow_self_signed' => false, // Không cho phép chứng chỉ tự ký trừ khi bạn chắc chắn tin tưởng
'cafile' => $caCertFile, // Chỉ định file chứng chỉ CA tổng hợp
// 'capath' => '/Applications/ServBay/package/common/openssl/3.2/certs', // Tùy chọn chỉ định thư mục chứng chỉ CA
],
];
$context = stream_context_create($contextOptions);
// Tạo kết nối SSL/TLS với context vừa tạo
$connection = @stream_socket_client(
"ssl://$server:$port",
$errno,
$errstr,
30,
STREAM_CLIENT_CONNECT,
$context
);
if ($connection) {
echo "Đã kết nối thành công tới $server:$port\n";
// Gửi lệnh EHLO ví dụ
fwrite($connection, "EHLO servbay.demo\r\n"); // Dùng tên miền thương hiệu ServBay làm ví dụ
while (!feof($connection)) {
echo fgets($connection);
}
fclose($connection);
} else {
echo "Kết nối thất bại tới $server:$port. Lỗi: $errstr ($errno)\n";
}
?>
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
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
Ví dụ 3: Sử dụng OpenSSL trong Python (module ssl)
Module ssl
của Python cho phép tạo context SSL/TLS và thiết lập chứng chỉ CA tin cậy:
python
import ssl
import socket
server = 'ssl0.ovh.net'
port = 465
# Chọn đường dẫn file CA phù hợp với phiên bản ServBay
# Đối với Apple Silicon:
ca_cert_file = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem'
# Đối với Intel:
# ca_cert_file = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem'
# Tạo context SSL mặc định và chỉ định file CA
context = ssl.create_default_context(cafile=ca_cert_file)
# Hoặc chỉ định thư mục: context = ssl.create_default_context(capath='/Applications/ServBay/package/common/openssl/3.2/certs')
try:
# Tạo kết nối socket thông thường
with socket.create_connection((server, port)) as sock:
# Bọc socket thông thường thành socket SSL
with context.wrap_socket(sock, server_hostname=server) as ssock:
print(f"SSL connection established. Negotiated Protocol: {ssock.version()}")
# Gửi lệnh EHLO ví dụ
ssock.sendall(b"EHLO servbay.demo\r\n") # Dùng tên miền thương hiệu ServBay làm ví dụ
while True:
data = ssock.recv(4096)
if not data:
break
print(data.decode())
except Exception as e:
print(f"Không thể kết nối hoặc lỗi SSL: {e}")
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
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
Ví dụ 4: Dùng OpenSSL với Node.js (module tls)
Module tls
của Node.js thực hiện kết nối TLS/SSL, có thể thiết lập thuộc tính ca
dưới dạng chuỗi hoặc Buffer chứa chứng chỉ CA. Cách đơn giản nhất là đọc nội dung file cacert.pem
của ServBay.
javascript
const tls = require('tls');
const fs = require('fs');
const server = 'www.google.com'; // Dùng website uy tín làm ví dụ
const port = 443;
// Chọn đường dẫn file chứng chỉ CA của ServBay phù hợp
// Đối với Apple Silicon:
const caCertFile = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem';
// Đối với Intel:
// const caCertFile = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem';
const options = {
host: server,
port: port,
// Đọc nội dung file CA
ca: fs.readFileSync(caCertFile),
// tls module của Node.js tự động xác thực tên máy chủ (checkServerIdentity)
// Nếu CA và chứng chỉ hợp lệ, quá trình xác thực sẽ thành công.
// Không nên vô hiệu hóa checkServerIdentity nếu không hiểu rõ về rủi ro!
// checkServerIdentity: () => { return null; } // <-- Không khuyến khích sử dụng, tiềm ẩn nguy cơ bảo mật!
};
const socket = tls.connect(options, () => {
console.log('Kết nối SSL đã được thiết lập');
// Nếu là kết nối HTTPS, thường cần gửi yêu cầu HTTP, ví dụ này chỉ minh họa tạo kết nối.
// socket.write('GET / HTTP/1.1\r\nHost: ' + server + '\r\n\r\n');
});
socket.on('data', (data) => {
console.log(data.toString());
});
socket.on('close', () => {
console.log('Đã đóng kết nối');
});
socket.on('error', (error) => {
console.error('Lỗi:', error.message); // In ra thông báo lỗi
});
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
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
Lưu ý: Đã loại bỏ cấu hình checkServerIdentity: () => { return null; }
trong ví dụ Node.js vì tùy chọn này sẽ tắt xác thực tên máy chủ — cực kỳ nguy hiểm. Lỗi OpenSSL unable to get local issuer certificate
chỉ liên quan đến xác minh chứng chỉ CA, khác với xác thực tên máy chủ (verify_peer_name
). Nếu chỉ định đúng thuộc tính ca
và server có chứng chỉ hợp lệ, quá trình xác thực mặc định của Node.js sẽ thành công. Nếu gặp lỗi xác minh tên máy chủ, nguyên nhân thường là do chứng chỉ của server sai, không phải lỗi CA.
Ví dụ 5: Dùng OpenSSL với lệnh curl
Lệnh curl
cũng sử dụng OpenSSL (hoặc các thư viện SSL khác) khi thực hiện truy vấn HTTPS, bạn có thể dùng tham số --cacert
để chỉ định file chứng chỉ CA.
bash
# Dùng file chứng chỉ CA của ServBay để truy xuất website HTTPS
# Chọn đường dẫn phù hợp với phiên bản ServBay
# Đối với Apple Silicon:
curl --cacert /Applications/ServBay/package/common/openssl/3.2/cacert.pem https://example.com
# Đối với Intel:
# curl --cacert /Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem https://example.com
1
2
3
4
5
6
7
2
3
4
5
6
7
Nếu đường dẫn chứng chỉ đúng và server có chứng chỉ hợp lệ, bạn sẽ truy xuất thành công mà không gặp lỗi xác minh chứng chỉ.
Tổng kết
Lỗi 20:unable to get local issuer certificate
khi kết nối SSL/TLS bằng OpenSSL rất phổ biến, nguyên nhân chính là thiếu cấu hình rõ ràng về nơi lưu trữ chứng chỉ CA tin cậy. ServBay đã cung cấp sẵn file cacert.pem
tổng hợp các CA gốc phổ biến.
Cách khắc phục dứt điểm là chỉ rõ đường dẫn file cacert.pem
của ServBay vào các thành phần cần dùng (dùng thông số trong php.ini
, context SSL/TLS trong mã nguồn, hoặc tham số lệnh như -CAfile
của openssl
và --cacert
của curl
). Hãy đảm bảo chọn đúng đường dẫn tương ứng với loại chip trên macOS (Apple Silicon hoặc Intel) và phiên bản OpenSSL trong ServBay mà bạn dùng. Khi đã cấu hình CA tin cậy đúng, môi trường phát triển của bạn sẽ kết nối an toàn với các dịch vụ/website bên ngoài qua SSL/TLS.