0

我正在尝试使用此 RFC中定义的“更改用户密码”扩展操作,该操作声明它采用三个可选参数的序列。但是,似乎 ldapjs 的 client.exop() 函数只允许我为它提供字符串或缓冲区。

这是我的尝试:

const dn = `uid=${username},ou=People,dc=${orginization},dc=com`
client.exop('1.3.6.1.4.1.4203.1.11.1', [dn, null, newPassword], (err, value, res) => {
  // ...
})

这是由此产生的错误:

TypeError: options.requestValue must be a buffer or a string

我应该如何将这些值编码为字符串?ldapjs 文档几乎没有提供有关将参数传递给扩展操作的信息。

4

1 回答 1

1

TL:博士;扩展操作参数需要是使用 BER 标准编码的 ASN.1 值。这不是一项简单的任务,因此您可能需要一个额外的 npm 库,例如asn1来帮助完成此过程。

在梳理了 ldapjs 的代码,阅读了一堆关于 ASN.1 以及 LDAP 如何使用 ASN.1 标准,以及一些试验和错误之后,我终于能够解决这个问题。由于明显缺乏这方面的文档,我想我会分享我在 stackoverflow 上学到的东西,这样其他人就不需要像我一样经历那么多麻烦了。

一个工作示例

这使用asn1 npm 库对发送的数据进行编码。

const { Ber } = require('asn1')

// ...

const CTX_SPECIFIC_CLASS = 0b10 << 6
const writer = new Ber.Writer()
writer.startSequence()
writer.writeString(dn, CTX_SPECIFIC_CLASS | 0) // sequence item number 0
// I'm choosing to omit the optional sequence item number 1
writer.writeString(newPassword, CTX_SPECIFIC_CLASS | 2) // sequence item number 2
writer.endSequence()
client.exop('1.3.6.1.4.1.4203.1.11.1', writer.buffer, (err, value, res) => {
  // ...
})

什么是 ASN.1?

ASN.1 是一种用于描述对象接口的语言。这些接口的特殊之处在于它们与语言无关——即javascript可以创建一个符合这些接口之一的对象,对其进行编码,然后将其发送到python服务器,然后根据相同的接口解码和验证对象。ASN.1 的大部分内容与我们要完成的工作无关,但重要的是要注意,我们要做的是创建一个符合这些 ASN.1 接口之一的对象(LDAP 是围绕他们)。

什么是BER?

BER 描述了一种表示符合 ASN.1 接口的对象的标准方法。使用 BER 标准,我们可以将 javascript 数据编码到 LDAP 服务器可以理解的缓冲区中。

BER 基础知识

BER 被设计成一个非常紧凑的编码标准。我将在这里介绍基础知识,但如果您想了解有关 BER 二进制表示的更多详细信息(它是为 LDAP 用户量身定制的) ,我强烈推荐这篇文章。ASN.1、BER 和 DER 子集的外行指南是另一个很好的资源。

ASN.1 描述了一些基本的对象类型,例如字符串和数字,它描述了结构化的对象类型,例如序列或集合。它还为用户提供了使用他们自己的自定义类型的能力。

在 BER 中,每条数据都以两个字节为前缀(通常):一个标识符字节和一个数据长度字节。标识符字节用关于它包含的数据类型的信息标记数据(字符串?序列?自定义类型?)。有四个“类”标签:通用(例如字符串)、应用程序(LDAP 定义了一些您可能会遇到的应用程序标签)、特定于上下文(请参阅下面的“BER 序列”部分)和私有(不太可能适用)这里)。字符串标记的位序列将始终被解释为字符串标记,但自定义标记的位序列的含义可能因环境而异,甚至在请求中也可能不同。

在 asn1 npm 库中,您可以写出一个字符串元素,如下所示:

writer.writeString('text')

要查找所有可用函数,该库的作者要求您查看源代码

BER序列

序列用于描述具有特定形状的对象(一组键值对)。有些元素可能是可选的,而其他元素是必需的。我遵循的RFC对其参数进行了以下描述。我们需要符合这个序列的接口,以便将我们的密码重置参数发送到 LDAP。

PasswdModifyRequestValue ::= SEQUENCE {
  userIdentity    [0]  OCTET STRING OPTIONAL
  oldPasswd       [1]  OCTET STRING OPTIONAL
  newPasswd       [2]  OCTET STRING OPTIONAL }

[0][1][2]指上下文特定的标签号。使用上下文特定标记 1 标记的值将被解释为 oldPasswd 参数的值。我们不需要使用全局字符串标记来指示我们的值是字符串类型 - LDAP 已经可以使用我们遵循的接口推断该信息。这意味着当以这个序列写一个字符串时,而不是writer.writeString('text')像以前那样做(它自动使用全局字符串标签),必须提供一个标签号,如下所示:

const CTX_SPECIFIC_CLASS = 0b10 << 6
writer.writeString(newPassword, CTX_SPECIFIC_CLASS | 2) // The second optional parameter allows you to set a custom tag on the data being set (instead of the default string tag).

标记字节的前两位保留用于指定标记类(在这种情况下,它是特定于上下文的类,或位“10”)。所以,CTX_SPECIFIC_CLASS | 2指的是 RFC 描述的 newPasswd 序列项。请注意,如果我想省略一个可选的序列条目,我只是不写出带有该序列 ID 标记的值。

结束语

希望这应该为读者提供足够的信息,以便能够为扩展的 LDAP 操作格式化和发送 BER 编码的参数。我确实想指出,我不是 ASN.1/BER 专家 - 以上所有这些信息都是我过去几天从自己的研究中理解这些概念的方式。因此,这篇文章中可能存在一些错误解释。如果您碰巧比我更了解此主题,请随时对其进行编辑。

于 2021-01-16T01:36:45.227 回答