当前位置:首页 > 文章列表 > Golang > Go教程 > Go 怎么读取 PEM 证书的域名和到期时间

Go 怎么读取 PEM 证书的域名和到期时间

来源:17golang原创 2026-09-06 03:29:54 0浏览 收藏

在 Go 里读取 .pem 证书,关键不是把文本按换行切开,而是先用 encoding/pem 找到 CERTIFICATE 块,再把其中的 DER 字节交给 crypto/x509。解析成功后,域名通常看 DNSNames,IP 地址看 IPAddresses,到期时间看 NotAfterSubject.CommonName 更适合做展示或兼容旧数据,不能代替现代主机名校验。

要点速览
  • pem.Decode 得到的是 PEM 块,x509.ParseCertificate 才会得到证书对象。
  • 域名优先读取 DNSNames,IP 地址单独读取 IPAddresses,不要只盯着 Common Name。
  • NotBeforeNotAfter 是时间边界;展示倒计时前还要判断证书是否已经生效或过期。

PEM 到 x509.Certificate 的关系

PEM 是带有头尾标记的文本封装,证书主体通常是 Base64 表示的 DER 数据。Go 官方的处理路径可以压缩成两步:pem.Decode 找到块并取出 block.Bytesx509.ParseCertificate 再解析这些 DER 字节。任何一步失败,都不应该继续读取证书字段。

Go PEM 证书从 PEM 文本到 x509.Certificate 的编码层和证书对象层关系图
图1:PEM 文本、DER 字节与 x509.Certificate 对象的静态关系。

先解析 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 地址,返回值都是切片。常见字段可以这样区分:

字段表示什么读取建议
DNSNamesSubject Alternative Name 中的 DNS 名称域名列表优先读取它
IPAddressesSubject Alternative Name 中的 IP 地址连接目标是 IP 时单独展示或比较
Subject.CommonName证书主题中的旧式通用名称可用于兼容显示,不作为主机名验证依据
NotBefore / NotAfter证书有效期的起止边界time.Now()time.Until 一起判断状态
Go x509 Certificate 的 DNSNames、IPAddresses、CommonName 与 NotAfter 字段关系图
图2:域名、IP 地址与证书有效期分别落在 Certificate 的不同字段中。

证书没有域名或已经过期时怎么处理

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,再用 NotBeforeVerifyHostname 补足状态判断。这样既能准确展示证书信息,也不会把“字段存在”误判成“证书已被信任”。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
喵次元打不开怎么办?DNS、下载地址失效与联系入口说明喵次元打不开怎么办?DNS、下载地址失效与联系入口说明
上一篇
喵次元打不开怎么办?DNS、下载地址失效与联系入口说明
Go 拼接 URL 为什么不能直接用 filepath.Join
下一篇
Go 拼接 URL 为什么不能直接用 filepath.Join
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • SuperCLUE中文大模型评测基准:功能、能力维度与应用指南
    SuperCLUE
    SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
    158次使用
  • C-Eval中文评测基准:大语言模型多学科能力评估指南
    C-Eval
    深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
    87次使用
  • ClickPrompt:AI提示词生成与优化工具,支持Stable Diffusion、ChatGPT及代码辅助
    ClickPrompt
    ClickPrompt是一款专为AI提示词编写者设计的开源在线工具,支持Stable Diffusion绘图、ChatGPT对话及GitHub Copilot代码辅助。提供Prompt自动生成、一键运行、社区分享及可视化优化功能,帮助用户高效获取精准AI输出。
    47次使用
  • PromptHero官网:AI提示词搜索、优化与学习平台,支持Midjourney/Stable Diffusion
    PromptHero
    PromptHero是专业的AI提示词搜索引擎与优化平台,支持Stable Diffusion、Midjourney等主流模型。提供海量提示词库、分类搜索、在线课程及社区互动,助力用户高效生成高质量AI图像与文本。
    30次使用
  • OpenArt免费开源指南:Stable Diffusion Prompt Book提示词手册详解
    Stable Diffusion Prompt Book
    深入解析OpenArt推出的Stable Diffusion Prompt Book,这本免费的开源提示词指南涵盖从基础语法到高级技巧,提供风格化词库与参数建议,助您优化AI绘画生成效果。
    30次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码