10个版本 (4个稳定版)

3.0.0 2024年4月3日
2.0.1 2024年2月1日
1.0.0 2023年3月2日
0.9.0 2022年12月1日
0.2.0 2021年3月11日

#246编码

Download history 2/week @ 2024-05-19 4/week @ 2024-06-02 1/week @ 2024-06-09

每月下载量 448次
用于 bsn1_serde

GPL-3.0-only

240KB
4.5K SLoC

bsn1

bsn1 是一个Rust库,用于在 'ASN.1' 格式中进行序列化和反序列化。

什么是ASN.1?

ASN.1代表 '抽象语法表示法一',X.690是 'ITU-T' 标准中规定以下ASN.1编码格式的标准。

  • 基本编码规则(BER)
  • 规范编码规则(CER)
  • 区分编码规则(DER)

该软件包目前支持BER和DER。

ASN.1在某些方面与 'JSON' 类似,因为它们都是标准的结构化数据序列化格式,然而,它们在以下方面有所不同。

  • JSON易于人类阅读,而ASN.1对计算机来说是可读的。也就是说,ASN.1比JSON消耗更少的计算机资源(例如CPU时间)。
  • ASN.1格式中有4个类别,'通用'、'应用'、'上下文特定'和'私有'。'通用'类别定义了类似于 '整数'、'布尔'、'字符串'、'序列'等的类型,就像JSON一样。更重要的是,ASN.1允许用户使用另一个类别定义新的数据类型。

ASN.1已经在全球范围内使用了很长时间,它非常稳定。例如,'传输层安全性(TLS、SSL)'、'轻量级目录访问协议(LDAP)'、'第四代移动通信系统(4G)'等等。

参见 X.690 (07/2002)维基百科

bsn1_serde

bsn1_serde 提供了与广泛使用的crate serde 中类似的 derive 宏 SerializeDeserialize。它设计用于与 bsn1 一起使用,并为 ASN.1 格式提供专门的序列化支持。

serde 因其序列化和反序列化能力而闻名,但其本质上是作为通用框架设计的。这意味着它可能无法处理所有格式的特殊功能,如 ASN.1 中的那些。ASN.1有许多独特的功能,在其他的序列化格式中不太常见,这使得它难以适应 serde 的操作方式。

bsn1_serde 通过提供针对 ASN.1 格式的宏来解决这一空白,其设计灵感来源于 serde

另请参阅 bsn1_serde

示例

'轻量级目录访问协议(LDAP)' 采用了 BER。让我们来创建/解析 LDAP 绑定操作(即登录查询)。

有关详细信息,请参阅 RFC4511

use bsn1::{Ber, BerRef, ContentsRef, Id, IdRef, ClassTag, PCTag};

/// Creates a BindRequest from `name` and `password`.
///
/// BindRequest ::= [APPLICATION 0] SEQUENCE {
///          version INTEGER (1 .. 127),
///          name LDAPDN,
///          authentication AuthenticationChoice }
fn new_bind_request(name: &str, password: &[u8]) -> Ber {
    // BindRequest is constituted of version, name, and authentication.
    // '[APPLICATION 0] SEQUENCE' means "a sequence, but the class is APPLICATION, and the
    // number is 0."
    // The RFC does not refer to the Primitive/Constructed flag,
    // but SEQUENCE is usually Constructed.
    const ID_NUMBER: u32 = 0;
    let id = Id::new(ClassTag::Application, PCTag::Constructed, ID_NUMBER.into());

    let contents = [new_bind_version(), new_bind_name(name),
                    new_bind_authentication(password)];

    Ber::from_id_iterator(&id, contents.iter())
}

/// Creates a `version` for bind request.
/// This function always returns 3 (the current latest version.)
fn new_bind_version() -> Ber {
    Ber::from(3_i32)
}

/// Creates a `name` for bind request from `name`.
///
/// LDAPDN ::= LDAPString
///            -- Constrained to <distinguishedName> [RFC4514]
///
/// LDAPString ::= OCTET STRING -- UTF-8 encoded,
///                             -- [ISO10646] characters
fn new_bind_name(name: &str) -> Ber {
    Ber::from(name.as_bytes())
}

/// Creates a `simple authentication` for bind request from `password`.
///
/// AuthenticationChoice ::= CHOICE {
///      simple                  [0] OCTET STRING,
///      -- 1 and 2 reserved
///      sasl                    [3] SaslCredentials,
///      ... }
fn new_bind_authentication(password: &[u8]) -> Ber {
   // 'AuthenticationChoice' is either 'simple [0] OCTET STRING' or 'sasl [3] SaslCredentials'.
   // This function selects 'simple'.
   //
   // '[0] OCTET STRING' means "OCTET STRING, but the number is 0."
   // RFC does not refer to the class and Primitive/Constructed flag.
   // This function returns ContextSpecific and Primitive BER.
   const ID_NUMBER: u32 = 0;
   let id = Id::new(ClassTag::ContextSpecific, PCTag::Primitive, ID_NUMBER.into());

   let contents: &ContentsRef = password.into();

   Ber::new(&id, contents)
}

/// Tries to parse bind request and returns `(name, password)`.
fn parse_bind_request(req: &[u8]) -> Result<(&str, &[u8]), String> {
    // `req` should be a 'BER' and must not include any extra octets.
    let mut bytes = req;
    let req = BerRef::parse(&mut bytes)
                .map_err(|e| format!("Failed to parse the request as a BER: {}", e))?;
    if bytes.is_empty() == false {
        return Err("There are some bad octets at the end of the request.".to_string());
    }

    // Check the identifier of the request.
    let id = req.id();
    if id.class() != ClassTag::Application || id.number() != Ok(0_u8.into()) {
        return Err("The id of the request is bad.".to_string());
    }

    // Parse the contents of the request.
    // The contents should be composed of version, name, and authentication in this order.
    let mut bytes: &[u8] = req.contents().as_ref();

    let version = parse_bind_version(&mut bytes)?;
    if version != 3 {
        return Err("This function supports only version 3.".to_string());
    }

    let name = parse_bind_name(&mut bytes)?;
    let password = parse_bind_authentication(&mut bytes)?;

    Ok((name, password))
}

/// Tries to parse the version of bind request.
fn parse_bind_version(bytes: &mut &[u8]) -> Result<i32, String> {
    let ber = BerRef::parse(bytes).map_err(|e| format!("Failed to parse the version: {}", e))?;

    if ber.id() != IdRef::integer() {
        Err("The id of the version is bad.".to_string())
    } else {
        ber.contents()
           .to_integer()
           .map_err(|e| format!("Failed to parse the version: {}", e))
    }
}

/// Tries to parse the name of bind request.
fn parse_bind_name<'a>(bytes: &mut &'a [u8]) -> Result<&'a str, String> {
    let ber = BerRef::parse(bytes).map_err(|e| format!("Failed to parse the name: {}", e))?;

    if ber.id() != IdRef::octet_string() && ber.id() != IdRef::octet_string_constructed() {
        Err("The id of the name is bad.".to_string())
    } else {
        let contents = ber.contents().as_ref();
        std::str::from_utf8(contents).map_err(|e| format!("Failed to parse the name: {}", e))
    }
}

/// Tries to parse the password of bind request.
fn parse_bind_authentication<'a>(bytes: &mut &'a [u8]) -> Result<&'a [u8], String> {
    let ber = BerRef::parse(bytes)
                     .map_err(|e| format!("Failed to parse the authentication: {}", e))?;

    if ber.id().number() == Ok(3_u8.into()) {
        Err("This function supports only simple authentication".to_string())
    } else if ber.id().number() == Ok(0_u8.into()) {
        Ok(ber.contents().as_ref())
    } else {
        Err("The id of the authentication is bad".to_string())
    }
}

// Create a bind request
let name = "uid=user,dc=my_company,dc=co,dc=jp";
let password = "open_sesami".as_bytes();
let request = new_bind_request(name, password);

// The client will send the byte to the server actually.
let bytes = request.as_ref();

// The LDAP server will parse the request.
let (name_, password_) = parse_bind_request(bytes).unwrap();

assert_eq!(name, name_);
assert_eq!(password, password_);

依赖关系

~595KB
~12K SLoC