You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用rust-openssl获取X509证书SAN的othername值?

解决rust-openssl获取X509证书SAN字段中othername值的问题

问题场景

你需要解析X509证书SAN字段里的othername: UPN::myuser@somedomain.com内容,但rust-openssl的GeneralName类型未提供直接获取othername的方法,调用ipaddress()/email()/dnsname()均返回None。

解决方案

rust-openssl的GeneralName封装了OpenSSL底层结构体,我们可以通过底层指针或ASN.1解析来提取othername的UPN值,以下是两种可行实现:

方式一:调用OpenSSL底层API(推荐)

直接通过as_ptr()获取底层GENERAL_NAME指针,利用OpenSSL原生逻辑提取内容:

use std::ptr;
use openssl::x509::{GeneralName, X509};
use openssl::asn1::Asn1OctetString;
use openssl::nid::Nid;

// 从GeneralName中提取UPN格式的othername值
fn get_upn_from_general_name(name: &GeneralName) -> Option<String> {
    let gn_ptr = name.as_ptr();
    
    unsafe {
        // 检查是否为OTHERNAME类型
        if (*gn_ptr).type_ != openssl::sys::GENERAL_NAME_GEN_OTHERNAME {
            return None;
        }
        
        let othername = (*gn_ptr).d.otherName;
        if othername.is_null() {
            return None;
        }
        
        // 验证类型ID是否为UPN的OID
        let nid = openssl::sys::OBJ_obj2nid((*othername).type_id);
        if nid != Nid::USER_PRINCIPAL_NAME.as_raw() {
            return None;
        }
        
        // 提取并转换为UTF-8字符串
        let octet_str = Asn1OctetString::from_ptr((*othername).value);
        String::from_utf8(octet_str.as_slice().to_vec()).ok()
    }
}

fn main() {
    static SOME_PEM: &str = "spikes/x509_parser/cert.pem";
    let data = std::fs::read(SOME_PEM).expect("Could not read file");
    let cert = X509::from_pem(data.as_slice()).expect("Could not load cert");

    let sans = cert.subject_alt_names().unwrap();

    println!("SAN count: {}", sans.len());

    for entry in &sans {
        if let Some(upn) = get_upn_from_general_name(entry) {
            println!("Found UPN: {}", upn);
        }
    }
}

方式二:手动解析ASN.1结构

通过将GeneralName转为DER字节流,手动解析ASN.1格式提取内容(需依赖asn1-rs库):

# Cargo.toml中添加依赖
[dependencies]
openssl = { version = "0.10", features = ["v110"] }
asn1-rs = "0.5"
use openssl::x509::{GeneralName, X509};

fn get_upn_from_general_name(name: &GeneralName) -> Option<String> {
    // 将GeneralName转为ASN.1 DER字节
    let der = name.to_der().ok()?;
    let mut cursor = std::io::Cursor::new(der);

    // 验证是否为otherName类型的ASN.1标签
    let (tag, _) = asn1_rs::read_tag(&mut cursor).ok()?;
    if tag != asn1_rs::Tag::ContextSpecific(0) {
        return None;
    }

    // 解析OtherName的SEQUENCE结构
    let (_, seq_content) = asn1_rs::read_sequence(&mut cursor).ok()?;
    let mut seq_cursor = std::io::Cursor::new(seq_content);

    // 验证OID是否为UPN的标识(1.3.6.1.4.1.311.20.2.3)
    let oid = asn1_rs::read_oid(&mut seq_cursor).ok()?;
    if oid != "1.3.6.1.4.1.311.20.2.3" {
        return None;
    }

    // 提取并转换为UTF-8字符串
    let (_, upn_bytes) = asn1_rs::read_explicit(&mut seq_cursor, asn1_rs::Tag::ContextSpecific(0)).ok()?;
    String::from_utf8(upn_bytes.to_vec()).ok()
}

// main函数同方式一

关键说明

  • GeneralName的底层GENERAL_NAME结构体中,type_字段为GENERAL_NAME_GEN_OTHERNAME时对应othername类型。
  • UPN的标准OID为1.3.6.1.4.1.311.20.2.3,需验证该OID确保提取的是目标值。
  • 两种方式均可提取出myuser@somedomain.com这类UPN内容。

内容的提问来源于stack exchange,提问作者sethcall

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 04:06:11