Go 怎么读取 PEM 证书的域名和到期时间
来源:17golang原创
时间:2026-09-06 03:29:54 254浏览 收藏
在 Go 里读取 .pem 证书,关键不是把文本按换行切开,而是先用 encoding/pem 找到 CERTIFICATE 块,再把其中的 DER 字节交给 crypto/x509。解析成功后,域名通常看 DNSNames,IP 地址看 IPAddresses,到期时间看 NotAfter;Subject.CommonName 更适合做展示或兼容旧数据,不能代替现代主机名校验。
pem.Decode得到的是 PEM 块,x509.ParseCertificate才会得到证书对象。- 域名优先读取
DNSNames,IP 地址单独读取IPAddresses,不要只盯着 Common Name。 NotBefore和NotAfter是时间边界;展示倒计时前还要判断证书是否已经生效或过期。
PEM 到 x509.Certificate 的关系
PEM 是带有头尾标记的文本封装,证书主体通常是 Base64 表示的 DER 数据。Go 官方的处理路径可以压缩成两步:pem.Decode 找到块并取出 block.Bytes,x509.ParseCertificate 再解析这些 DER 字节。任何一步失败,都不应该继续读取证书字段。

先解析 CERTIFICATE 块,再读取证书字段
下面的函数接收 PEM 文件路径,返回第一张证书的关键信息。类型检查很重要:同一个文件里可能同时放着私钥、证书链或其他 PEM 块,不能看到 block != nil 就直接解析。
package main
import (
"crypto/x509"
"encoding/pem"
"fmt"
"os"
"time"
)
func readCertificate(path string) (*x509.Certificate, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("读取证书文件失败: %w", err) // 保留文件路径阶段的原始错误
}
block, _ := pem.Decode(data)
if block == nil {
return nil, fmt.Errorf("没有找到 PEM 数据块") // 空块通常意味着格式或文件内容不对
}
if block.Type != "CERTIFICATE" {
return nil, fmt.Errorf("第一个 PEM 块不是证书: %s", block.Type) // 防止把私钥交给证书解析器
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("解析 DER 证书失败: %w", err) // 解析失败时不要读取半成品字段
}
return cert, nil
}
func main() {
cert, err := readCertificate("server-cert.pem")
if err != nil {
fmt.Println(err)
return
}
fmt.Println("域名:", cert.DNSNames) // SAN 中的 DNS 名称
fmt.Println("IP 地址:", cert.IPAddresses) // SAN 中的 IP 地址
fmt.Println("生效时间:", cert.NotBefore)
fmt.Println("到期时间:", cert.NotAfter)
fmt.Println("距离到期:", time.Until(cert.NotAfter)) // 负数表示已经过期
}
这里没有调用 Verify,所以代码回答的是“证书里写了什么”,不是“证书是否被当前系统信任”。如果要判断某个主机名能否使用这张证书,应使用 cert.VerifyHostname("api.example.com"),并把信任链验证和字段展示分成两个动作。
域名和到期时间应该从哪些字段读取
一个证书可能同时包含多个 DNS 名称和多个 IP 地址,返回值都是切片。常见字段可以这样区分:
| 字段 | 表示什么 | 读取建议 |
|---|---|---|
DNSNames | Subject Alternative Name 中的 DNS 名称 | 域名列表优先读取它 |
IPAddresses | Subject Alternative Name 中的 IP 地址 | 连接目标是 IP 时单独展示或比较 |
Subject.CommonName | 证书主题中的旧式通用名称 | 可用于兼容显示,不作为主机名验证依据 |
NotBefore / NotAfter | 证书有效期的起止边界 | 和 time.Now() 或 time.Until 一起判断状态 |

证书没有域名或已经过期时怎么处理
DNSNames 为空不一定代表解析失败,可能是这张证书只包含 IP 地址,也可能是输入本身不适合作为服务器证书。展示层可以把空切片显示为“未声明 DNS 名称”,但业务校验应该明确要求 DNS 名称还是 IP 地址。
now := time.Now()
switch {
case now.Before(cert.NotBefore):
fmt.Println("证书尚未生效") // 当前时间早于 NotBefore
case now.After(cert.NotAfter):
fmt.Println("证书已经过期") // 当前时间晚于 NotAfter
default:
fmt.Printf("证书有效,剩余 %s\n", time.Until(cert.NotAfter).Round(time.Minute)) // 便于日志阅读
}
if err := cert.VerifyHostname("api.example.com"); err != nil {
fmt.Println("主机名不匹配:", err) // VerifyHostname 会按 DNSNames 检查域名
}
如果证书来自自动续期系统,可以把“剩余时间”作为提醒条件,但不要把提醒阈值写死成证书有效期。真正上线前还要确认系统加载的是新文件、服务进程是否重新读取,以及证书链是否完整。
PEM 文件包含证书链时要保留 rest
pem.Decode 会同时返回当前块和剩余字节。只取第一块适合读取叶子证书;如果文件里还有中间证书,就应沿着 rest 继续解码,并按 block.Type == "CERTIFICATE" 过滤。解析链时不要把多个证书的域名简单合并成一张证书的域名清单,每张证书的字段归属仍然独立。
另外,PEM 文件可能带有空白、注释或私钥。遇到 block == nil 时先检查文件是否真的是 PEM;遇到类型不是 CERTIFICATE 时记录块类型并跳过或终止,避免将私钥内容写进日志。
常见问题
为什么 CommonName 有域名,但 DNSNames 是空的?
这是旧式证书字段与现代 SAN 字段的区别。CommonName 可以用于展示旧数据,但 VerifyHostname 不把它作为现代主机名匹配依据;需要做主机校验时,应让证书包含正确的 DNSNames。
读取 NotAfter 就能证明证书可用吗?
不能。NotAfter 只说明时间上的结束边界,还需要结合 NotBefore、主机名匹配、用途和信任链判断是否真的可用。
一个 PEM 文件有多个证书,应该选哪一张?
通常第一张是叶子证书,后续块是中间证书,但不要只靠位置猜测业务语义。遍历每个 CERTIFICATE 块并根据 Subject、Issuer 和验证需求建立链,展示域名时明确标出证书来源。
结论:域名看 SAN,到期看 NotAfter
Go 读取 PEM 证书的稳定路径是 pem.Decode → x509.ParseCertificate。域名优先使用 DNSNames,IP 地址看 IPAddresses,到期时间看 NotAfter,再用 NotBefore 和 VerifyHostname 补足状态判断。这样既能准确展示证书信息,也不会把“字段存在”误判成“证书已被信任”。
-
860 收藏
-
843 收藏
-
826 收藏
-
809 收藏
-
792 收藏
-
283 收藏
-
Golang · Go教程 | 31分钟前 | 网络编程 · Go教程 · 进程通信 · Unix Socket · Go Unix Socket net.UnixConn ListenUnix DialUnix418 收藏
-
188 收藏
-
298 收藏
-
401 收藏
-
472 收藏
-
470 收藏
-
459 收藏
-
456 收藏
-
318 收藏
-
Golang · Go教程 | 2小时前 | 文件操作 · Go教程 · 文本处理 · io.MultiReader · Go io.Copy io.MultiReader 文本文件合并 os.Open120 收藏
-
104 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习