Khắc phục sự cố OpenSSL: Giải quyết lỗi unable to get local issuer certificate
Khi thực hiện kết nối bảo mật với OpenSSL (ví dụ như gửi yêu cầu 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à vấn đề quen thuộc liên quan đến việc OpenSSL xác minh chứng chỉ của bên đối tác.
Vì lý do bảo mật, theo mặc định OpenSSL luôn cần xác định rõ các tổ chức phát hành chứng chỉ gốc (CA) được tin tưởng trong quá trình xác thực chuỗi chứng chỉ. Nếu không tìm thấy hoặc không khai báo các điểm neo tin cậy này, OpenSSL sẽ không thể xác minh hợp pháp chứng chỉ máy chủ và thông báo lỗi này.
Bài viết này sẽ phân tích nguyên nhân dẫn đến lỗi và hướng dẫn chi tiết khắc phục trên môi trường ServBay, bao gồm việc cấu hình kho tin cậy OpenSSL cho 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 xác thực chứng chỉ SSL/TLS từ máy chủ từ xa, nó sẽ xây dựng một chuỗi chứng chỉ từ chứng chỉ của máy chủ lên CA gốc được tin cậy. Nếu không tìm được chứng chỉ trung gian hoặc CA gốc hợp lệ trên máy tính, hoặc không được cấu hình kho tin cậy (CAFile hoặc CAPath) phù hợp, quá trình xác thực sẽ thất bại và báo về lỗi 20:unable to get local issuer certificate
.
Nói ngắn gọn, OpenSSL không biết nên tin tưởng tổ chức nào phát hành chứng chỉ, nên không thể xác nhận danh tính của máy chủ mà bạn đang kết nối.
Phiên bản OpenSSL & Đường dẫn chứng chỉ CA trong ServBay
ServBay là môi trường phát triển web tích hợp cục bộ, đã đóng gói sẵn OpenSSL cùng bộ chứng chỉ gốc CA công khai phổ biến, giúp lập trình viên sử dụng thuận tiện. Phiên bản OpenSSL mà ServBay sử dụng phụ thuộc vào loại chip máy của bạn:
- ServBay cho Apple Silicon (dòng chip M): Dùng OpenSSL phiên bản 3.2.1.
- ServBay cho Intel: Dùng OpenSSL 1.1.1u.
Các tập tin chứng chỉ CA (cacert.pem
) và thư mục chứng chỉ (certs
) sẽ được đặt trong đường dẫn cài đặt ServBay tương ứng với từng phiên bản:
# Tập tin chứa tất cả chứng chỉ gốc CA được tin cậy
cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
# Thư mục chứa các tệp chứng chỉ rời (thường chỉ cần cacert.pem, nhưng một số ứng dụng cần capath)
capath=/Applications/ServBay/package/common/openssl/3.2/certs
2
3
4
# Tập tin chứa tất cả chứng chỉ gốc CA được tin cậy
cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
# Thư mục chứa các tệp chứng chỉ rời
capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
2
3
4
Cách khắc phục trọng tâm là tại mọi chỗ cần sử dụng OpenSSL, bạn cần chỉ định rõ đường dẫn cafile
hoặc capath
này, báo cho OpenSSL biết nơi tìm chứng chỉ CA tin cậy.
Ví dụ giải pháp
Dưới đây là các ví dụ cấu hình kho tin cậy CA của OpenSSL trên nhiều 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 khi bạn dùng lệnh openssl s_client
mà gặp lỗi:
openssl s_client -quiet -connect gmail.com:443
Bạn có thể nhận được kết quả xuất hiện dòng lỗi tương tự sau, trong đó có verify error:num=20:unable to get local issuer certificate
:
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 ...
2
3
4
5
6
7
8
Cách khắc phục:
Thêm tham số -CAfile
để cụ thể hóa đường dẫn đến tập tin chứng chỉ CA do ServBay cung cấp:
openssl s_client -quiet -connect gmail.com:443 -CAfile /Applications/ServBay/package/common/openssl/3.2/cacert.pem
openssl s_client -quiet -connect gmail.com:443 -CAfile /Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
Kết nối và xác thực thành công sẽ có dòng verify return
là 1
, đồng thời không còn hiện dòng lỗi verify error:num=20
:
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 ...
2
3
4
5
6
7
Ví dụ 2: Sử dụng OpenSSL trong PHP
Nhiều chức năng mạng của PHP (như file_get_contents
để truy cập HTTPS, stream_socket_client
kết nối SSL, hoặc cURL extension) đều sử dụng OpenSSL phía dưới. Bạn có thể cấu hình kho tin cậy CA bằng cách chỉnh sửa php.ini
hoặc cấu hình trực tiếp trong code.
Cách A: Chỉnh sửa php.ini
(khuyến nghị)
Đây là phương pháp tốt nhất để cấu hình cho toàn bộ hệ thống. Mở tập tin php.ini
tương ứng với PHP version đang dùng (bạn có thể tìm nơi chỉnh sửa này trong giao diện ServBay), đến mục [openssl]
, rồi thêm hoặc chỉnh lại như sau. Hãy dùng đúng đường dẫn theo loại chip máy:
[openssl]
; Chỉ định tập tin chứng chỉ CA đáng tin cậy
openssl.cafile=/Applications/ServBay/package/common/openssl/3.2/cacert.pem
; (Tùy chọn nên đặt) Chỉ định thư mục chứa chứng chỉ CA
openssl.capath=/Applications/ServBay/package/common/openssl/3.2/certs
2
3
4
5
[openssl]
; Chỉ định tập tin chứng chỉ CA đáng tin cậy
openssl.cafile=/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem
; (Tùy chọn nên đặt) Chỉ định thư mục chứa chứng chỉ CA
openssl.capath=/Applications/ServBay/package/common/openssl/1.1.1u/certs
2
3
4
5
Sau khi chỉnh sửa, hãy khởi động lại dịch vụ PHP (hoặc toàn bộ ServBay) để cấu hình có hiệu lực.
Cách B: Thêm cấu hình trực tiếp trong mã (chỉ cho kết nối hiện tại)
Nếu không muốn thay đổi php.ini
, bạn có thể chỉ định cafile
trong tùy chọn context khi sử dụng stream_context_create
cho kết nối SSL/TLS:
<?php
// Ví dụ: Kết nối đến server SMTP có SSL/TLS
$server = 'ssl0.ovh.net';
$port = 465;
// Chỉ định đúng đường dẫn file CA theo phiên bản ServBay bạn sử dụng
// Dành cho Apple Silicon:
$caCertFile = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem';
// Dành cho 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ỉ máy chủ
'verify_peer_name' => true, // Xác thực tên miền trong chứng chỉ
'allow_self_signed' => false, // Không chấp nhận chứng chỉ tự ký, trừ khi bạn tự tin tin tưởng nó
'cafile' => $caCertFile, // Chỉ định tập tin CA
// 'capath' => '/Applications/ServBay/package/common/openssl/3.2/certs', // Tùy chọn: Chỉ định thư mục 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, // Thời gian timeout kết nối
STREAM_CLIENT_CONNECT,
$context // Truyền context tùy chọn
);
if ($connection) {
echo "Connection established to $server:$port\n";
// Ví dụ: Gửi lệnh EHLO
fwrite($connection, "EHLO servbay.demo\r\n"); // Sử dụng tên miền demo theo thương hiệu ServBay
while (!feof($connection)) {
echo fgets($connection);
}
fclose($connection);
} else {
echo "Failed to connect to $server:$port. Error: $errstr ($errno)\n";
}
?>
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
Ví dụ 3: Dùng OpenSSL trong Python (mô-đun ssl)
Mô-đun ssl
của Python cho phép bạn tạo context SSL/TLS và chỉ định chứng chỉ CA tin cậy:
import ssl
import socket
server = 'ssl0.ovh.net'
port = 465
# Chỉ định lại đường dẫn file CA theo bản ServBay mà bạn cài
# Với Apple Silicon:
ca_cert_file = '/Applications/ServBay/package/common/openssl/3.2/cacert.pem'
# Với Intel:
# ca_cert_file = '/Applications/ServBay/package/common/openssl/1.1.1u/cacert.pem'
# Tạo context SSL mặc định và gán thêm CA file
context = ssl.create_default_context(cafile=ca_cert_file)
# Cũng có thể sử dụng capath: 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:
# Bao bọc socket thành socket SSL
with context.wrap_socket(sock, server_hostname=server) as ssock:
print(f"SSL connection established. Negotiated Protocol: {ssock.version()}")
# Ví dụ: Gửi lệnh EHLO
ssock.sendall(b"EHLO servbay.demo\r\n") # Sử dụng tên miền demo theo thương hiệu ServBay
while True:
data = ssock.recv(4096)
if not data:
break
print(data.decode())
except Exception as e:
print(f"Failed to connect or SSL error: {e}")
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
Ví dụ 4: Dùng OpenSSL trong Node.js (mô-đun tls)
Trong Node.js, mô-đun tls
được dùng để thực hiện kết nối TLS/SSL. Bạn nên chỉ định CA bằng thuộc tính ca
trong options, có thể là chuỗi hoặc Buffer đọc từ tập tin cacert.pem
mà ServBay cung cấp.
const tls = require('tls');
const fs = require('fs');
const server = 'www.google.com'; // Sử dụng một trang web tin cậy phổ biến làm ví dụ
const port = 443;
// Chỉ định đúng đường dẫn CA tùy phiên bản ServBay
// Đố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 chứng chỉ CA
ca: fs.readFileSync(caCertFile),
// Mặc định Node.js sẽ kiểm tra tên miền certificate (checkServerIdentity).
// Nếu file CA và certificate hợp lệ, xác thực này sẽ thành công.
// Đừng tắt checkServerIdentity nếu không cần thiết vì lý do bảo mật!
// checkServerIdentity: () => { return null; } // <-- Không nên dùng, làm mất bảo vệ an toàn quan trọng!
};
const socket = tls.connect(options, () => {
console.log('SSL connection established');
// Đối với HTTPS, cần gửi HTTP request tiếp; đây chỉ là ví dụ kiểm tra connect
// 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('Connection closed');
});
socket.on('error', (error) => {
console.error('Error:', error.message); // In ra thông báo lỗi
});
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 ý: Đoạn ví dụ Node.js đã bỏ thuộc tính checkServerIdentity: () => { return null; }
vì nó sẽ tắt kiểm tra tên máy chủ, rất nguy hiểm. Lỗi OpenSSL unable to get local issuer certificate
chỉ liên quan đến kho CA tin cậy, không phải kiểm tra DNS. Khi chỉ định đúng file CA, nếu chứng chỉ hợp lệ, Node.js sẽ xác thực thành công. Nếu bạn gặp lỗi hostname, đó là do lỗi chứng chỉ, không phải do CA.
Ví dụ 5: Dùng OpenSSL trong lệnh curl
Lệnh curl
cũng chạy qua OpenSSL (hoặc thư viện SSL khác) để truy vấn HTTPS. Bạn chỉ cần thêm tham số --cacert
chỉ đến tập tin chứng chỉ CA:
# Truy cập một trang HTTPS với chứng chỉ CA của ServBay
# Chỉ định đúng đường dẫn chạy với phiên bản ServBay tương ứng
# Đố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
2
3
4
5
6
7
Nếu định vị đúng tệp CA và máy chủ cung cấp chứng chỉ hợp lệ, curl
sẽ lấy được nội dung mà không báo lỗi xác thực chứng chỉ.
Tổng kết
Lỗi 20:unable to get local issuer certificate
thường gặp khi thiết lập kết nối SSL/TLS bằng OpenSSL, nguyên nhân gốc là OpenSSL cần một kho tin cậy (trust store) được chỉ định rõ ràng để xác thực máy chủ. ServBay đã cung cấp sẵn file chứng chỉ gốc CA (cacert.pem
) phổ biến phục vụ các môi trường phát triển.
Cách xử lý là hãy chỉ định đường dẫn file cacert.pem
do ServBay cung cấp ở nơi phù hợp (trong php.ini
, qua các options context SSL của code, hoặc qua tham số dòng lệnh như -CAfile
cho OpenSSL, --cacert
cho curl). Hãy xác định chính xác loại chip trên macOS (Apple Silicon hoặc Intel) và phiên bản OpenSSL của ServBay rồi chọn đúng đường dẫn. Khi cấu hình trust store đúng chuẩn, môi trường phát triển của bạn sẽ đảm bảo các kết nối SSL/TLS tới dịch vụ bên ngoài luôn an toàn và ổn định.