IKEv2使用DNSSEC的IPSECKEY资源记录获取公钥的认证流程

以下根据strongswan代码中的testing/tests/ikev2/net2net-dnssec/中的测试环境,来看一下IKEv2协议在协商时不交换证书,而是通过DNSSEC获取对端公钥的认证流程。拓扑结构如下:

在这里插入图片描述

拓扑图中使用到的设备包括:虚拟网关moon和sun网关。

网关配置

moon的配置文件:/etc/ipsec.conf,内容如下。注意此处的leftsigkey字段,在strongswan中此字段等同于leftrsasigkey字段,用于配置本地的证书公钥。

conn %default
        rekeymargin=3m
        keyexchange=ikev2
        mobike=no

conn net-net
        left=PH_IP_MOON
        leftid=moon.strongswan.org
        leftsubnet=10.1.0.0/16
        leftsigkey=moonPub.pem
        leftauth=pubkey
        leftfirewall=yes
        right=sun.strongswan.org
        rightid=sun.strongswan.org
        rightsubnet=10.2.0.0/16
        rightauth=pubkey
        auto=add

配置文件/etc/strongswan.conf中加载相关插件dnskey、ipseckey和unbound。此外charon.plugins.ipseckey.enable等于yes,表明使能由DNS中获取IPSECKEY RRs资源记录,默认请求下禁止此操作。charon.plugin.unbound.trust_anchors的默认值为:/etc/ipsec.d/dnssec.keys,其指定了DNS可信锚所在文件,以下将看到其内容。

charon {
  load = random nonce aes sha1 sha2 hmac curve25519 gmp dnskey pem pkcs1 pubkey unbound ipseckey curl kernel-netlink socket-default stroke updown

  plugins {
    ipseckey {
      enable = yes
    }
    unbound {
      # trust_anchors = /etc/ipsec.d/dnssec.keys
      # resolv_conf = /etc/resolv.conf
    }
  }
}

此外,moon网关的公钥位于:/etc/ipsec.d/certs/moonPub.pem文件中。moon网关的文件/etc/ipsec.d/dnssec.keys如下,按照RFC4034中对DNSSEC RR的格式定义解析以下数据:

  • 第一个字段指定了秘钥拥有者为Root Zone(.)
  • IN指定DNS的类Class为INTERNET(数值1,定义在rfc1035中)
  • DNSKEY指明RR的类型
  • 标志字段Flags值为257(0x0101),第7位(右到左)置位表明此为DNS Zone秘钥(通常情况下都为1);第15bit为SEP(Secure Entry Point)位,表明秘钥将作为一个安全入口点使用。此为一个KSK(key-signing key),用来签名Zone key。
  • 接下来是DNSSEC协议字段,固定为3。
  • 随后的数字8为签名算法,表示RSASHA256,此定义在RFC8624中
  • 最后为公钥字段,采用Base64编码。
; This is a key-signing key, keyid 32329, for .
.               IN      DNSKEY  257 3 8 (
                                AwEAAbcskaratFgvgvXl0bNq4I43ZBzd9jYnoPqsIcA0ahqXlUTUa+c2
                                XzN2mS7DGcI4Z5Gn+8v/Ih4lQJQrlf9I/c2HjooCAsK1bA5cRS2DiU+b
                                L6Ge0nLtvNOf4C0MHGLrWcDONg5QoL0OcFvMXuUtOvDkoIMdtfDYDScx
                                E9vSokc98Sx553/MTxpssXeM9i+OauGqohIZU+MVRdWwvJPieCL7Ma4b
                                AttgG+KSbQy7x/qXPISoqzwGQvCxsL93fvD/cpp+KziqA0oH+Dfryvc5
                                nWdCdra4gYz7WCFFwcY1PW6PbL5ie4jnjl3WWxopuzT46HKROxDhE+FO
                                O9fOgGnjzAk=
                                )

sun网关的配置与moon网关基本相同,并且具有和sun网关相同dnssec.key文件。

DNSSEC相关插件加载

文件strongswan-5.8.1/src/charon/charon.c中主函数main,调用daemon_t结构类型的charon的成员函数initialize,根据配置文件strongswan.conf中的"charon.load"部分的值,初始化要加载的插件。

/* Main function, starts the daemon.
 */
int main(int argc, char *argv[])
{
        ...
        /* initialize daemon */
        if (!charon->initialize(charon, lib->settings->get_str(lib->settings, "charon.load", PLUGINS))) {
                DBG1(DBG_DMN, "initialization failed - aborting charon");
                goto deinit;
        }
        lib->plugins->status(lib->plugins, LEVEL_CTRL);

如下为文件strongswan-5.8.1/src/libcharon/daemon.c中的initialize函数实现,其主体调用了plugin_loader_t结构的load函数指针,其指向src/libstrongswan/plugins/plugin_loader.c文件中的函数load_plugins。默认情况下所有的plugin都按照在目录/usr/local/lib/ipsec/plugins/下。

METHOD(daemon_t, initialize, bool, private_daemon_t *this, char *plugins)
{
        ...
        /* load plugins, further infrastructure may need it */
        if (!lib->plugins->load(lib->plugins, plugins))
        {
                return FALSE;
        }

在加载插件对应的so库文件之后,将调用其中的函数%name_plugin_create,对于DNSSEC相关的插件:dnskey/unbound/ipseckey,将分别调用函数dnskey_plugin_create/unbound_plugin_create/ipseckey_plugin_create,具体调用过程可参见文件libstrongswan/plugins/plugin_loader.c中的函数create_plugin。

接下来,函数register_features将通过调用插件中提供的get_features函数,获取插件可提供的特性。最后,通过函数load_features,加载插件特性,在此之前,将首先加载此插件所依赖的其它插件,参见函数load_feature。以下为最终的插件特性加载函数,对于类型FEATURE_CALLBACK,此处将调用其回调函数。

bool plugin_feature_load(plugin_t *plugin, plugin_feature_t *feature, plugin_feature_t *reg)
{
        char *name;

        if (reg->kind == FEATURE_CALLBACK) {
                if (!reg->arg.cb.f || reg->arg.cb.f(plugin, feature, TRUE, reg->arg.cb.data)) {
                        return TRUE;
                }
                return FALSE;
        }
        name = plugin->get_name(plugin);
        switch (feature->type) {
                ...
                case FEATURE_PUBKEY:
                        lib->creds->add_builder(lib->creds, CRED_PUBLIC_KEY, feature->arg.pubkey, reg->arg.reg.final, reg->arg.reg.f);
                        break;
                case FEATURE_CERT_DECODE:
                case FEATURE_CERT_ENCODE:
                        lib->creds->add_builder(lib->creds, CRED_CERTIFICATE, feature->arg.cert, reg->arg.reg.final, reg->arg.reg.f);
                case FEATURE_RESOLVER:
                        lib->resolver->add_resolver(lib->resolver, reg->arg.reg.f);
                        break;

ipseckey插件

ipseckey插件注册了FEATURE_CALLBACK类型特性,在以上加载函数plugin_feature_load中,将调用其注册的回调函数plugin_cb。

METHOD(plugin_t, get_features, int, private_ipseckey_plugin_t *this, plugin_feature_t *features[])
{               
        static plugin_feature_t f[] = {
                PLUGIN_CALLBACK((plugin_feature_callback_t)plugin_cb, NULL),
                        PLUGIN_PROVIDE(CUSTOM, "ipseckey"),
                                PLUGIN_DEPENDS(RESOLVER),
                                PLUGIN_DEPENDS(PUBKEY, KEY_RSA),
                                PLUGIN_DEPENDS(CERT_ENCODE, CERT_TRUSTED_PUBKEY),
        };
        *features = f;
        return countof(f);

dnskey插件

与以上的ipseckey插件不同,dnskey插件不是FEATURE_CALLBACK类型。其提供了FEATURE_PUBKEY,在加载此插件时,函数plugin_feature_load将调用lib->creds->add_builder函数指针,向系统的信任管理模块(credential_factory_t结构)添加一个builder。

METHOD(plugin_t, get_features, int, private_dnskey_plugin_t *this, plugin_feature_t *features[])
{
        static plugin_feature_t f[] = {
                PLUGIN_REGISTER(PUBKEY, dnskey_public_key_load, FALSE),
                        PLUGIN_PROVIDE(PUBKEY, KEY_ANY),
                PLUGIN_REGISTER(PUBKEY, dnskey_public_key_load, FALSE),
                        PLUGIN_PROVIDE(PUBKEY, KEY_RSA),
        };
        *features = f;
        return countof(f);

在此插件加载时,如下插件创建函数dnskey_plugin_create,将调用lib->encoding->add_encoder函数指针,向系统的信任管理模块添加一个编码器。

plugin_t *dnskey_plugin_create()
{
        private_dnskey_plugin_t *this;

        INIT(this,
                .public = {
                        .plugin = {
                                .get_name = _get_name,
                                .get_features = _get_features,
                                .destroy = _destroy,
                        },
                },
        );

        lib->encoding->add_encoder(lib->encoding, dnskey_encoder_encode);

        return &this->public.plugin;

unbound插件

与以上的dnskey插件类似,unbound插件也不是FEATURE_CALLBACK类型,get_feature没有指定回调函数。unbound插件的大部分函数使用系统的unbound和ldns库实现,在编译Makefile中,需要链接这两个库(-lunbound -lldns)。

METHOD(plugin_t, get_features, int,  private_unbound_plugin_t *this, plugin_feature_t *features[])
{
        static plugin_feature_t f[] = {
                PLUGIN_REGISTER(RESOLVER, unbound_resolver_create),
                        PLUGIN_PROVIDE(RESOLVER),
        };
        *features = f;
        return countof(f);

由以上函数plugin_feature_load可知,对于FEATURE_RESOLVER类型,将调用函数指针lib->resolver->add_resolver,向系统的resolver管理器(resolver_manager_t结构)添加一个resolver。

以下为unbound插件的加载函数,在本例的配置中,没有未其指定加载参数,所以unbound.resolv_conf字段使用默认值:’/etc/resolv.conf’。字段unbound.trust_anchors也使用默认值:’/etc/ipsec.d/dnssec.keys’。字段dlv_anchors的默认值为NULL。此插件仅实现了一个公共函数query。

resolver_t *unbound_resolver_create(void)
{
        private_resolver_t *this;
        char *resolv_conf, *trust_anchors, *dlv_anchors;

        resolv_conf = lib->settings->get_str(lib->settings, "%s.plugins.unbound.resolv_conf", RESOLV_CONF_FILE, lib->ns);
        trust_anchors = lib->settings->get_str(lib->settings, "%s.plugins.unbound.trust_anchors", TRUST_ANCHOR_FILE, lib->ns);
        dlv_anchors = lib->settings->get_str(lib->settings, "%s.plugins.unbound.dlv_anchors", NULL, lib->ns);

        INIT(this,
                .public = {
                        .query = _query,
                        .destroy = _destroy,
                },
        );

以下为unbound库的初始化函数,分别将DNS服务器配置文件resolv_conf,和DNS可信任锚文件trust_anchors添加到unbound库中。

        this->ctx = ub_ctx_create();
        if (!this->ctx) {  ...  return NULL;   }

        DBG2(DBG_CFG, "loading unbound resolver config from '%s'", resolv_conf);
        ub_retval = ub_ctx_resolvconf(this->ctx, resolv_conf);
        if (ub_retval) {   ...    return NULL;   }

        DBG2(DBG_CFG, "loading unbound trust anchors from '%s'", trust_anchors);
        ub_retval = ub_ctx_add_ta_file(this->ctx, trust_anchors);
        if (ub_retval) {
                DBG1(DBG_CFG, "failed to load trust anchors: %s (%s)", ub_strerror(ub_retval), strerror(errno));
        }
        ...
        return &this->public;

sigkey字段初始化

文件src/libcharon/plugins/stroke/stroke_config.c中,函数build_auth_cfg解析处理sigkey字段配置。

static auth_cfg_t *build_auth_cfg(private_stroke_config_t *this, stroke_msg_t *msg, bool local, bool primary)
{
        /* add raw RSA public key */
        pubkey = end->rsakey;
        if (pubkey && !streq(pubkey, "") && !streq(pubkey, "%cert")) {
                certificate = this->cred->load_pubkey(this->cred, pubkey, identity);
                if (certificate) {
                        cfg->add(cfg, AUTH_RULE_SUBJECT_CERT, certificate);
                }
        }

文件src/libcharon/plugins/stroke/stroke_cred.c中函数load_pubkey如下,由于在配置文件中leftsigkey字段值等于moonPub.pem,使用默认的路径CERTIFICATE_DIR(目录:/etc/ipsec.d/certs/)获取秘钥文件。之后,据此文件调用函数lib->creds->create生成证书结构(certificate_t),其中的类型参数type为CRED_CERTIFICATE,子类型为CERT_TRUSTED_PUBKEY,依据这两个参数找到合适的处理插件。

METHOD(stroke_cred_t, load_pubkey, certificate_t*, private_stroke_cred_t *this, char *filename, identification_t *identity)
{
        certificate_t *cert;
        public_key_t *key;
        key_type_t type = KEY_ANY;

        if (strncaseeq(filename, "0x", 2) || strncaseeq(filename, "0s", 2)) { ...
        } else {
                if (*filename == '/') {
                        snprintf(path, sizeof(path), "%s", filename);
                } else {
                        snprintf(path, sizeof(path), "%s/%s", CERTIFICATE_DIR, filename);
                }

                cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_TRUSTED_PUBKEY,
                                          BUILD_FROM_FILE, path, BUILD_SUBJECT, identity, BUILD_END);
                if (cert) {
                        cert = this->creds->add_cert_ref(this->creds, TRUE, cert);
                        key = cert->get_public_key(cert);
                        type = key->get_type(key);
                        key->destroy(key);
                        DBG1(DBG_CFG, "  loaded %N public key for \"%Y\" from '%s'", key_type_names, type, identity, filename);
                        return cert;

位于文件src/libstrongswan/plugins/pem/pem_plugin.c中的PEM插件,注册的函数pem_certificate_load,提供子类型为CERT_TRUSTED_PUBKEY的特性。由以上函数plugin_feature_load可知,所有支持CERT_DECODE/CERT_ENCODE的插件,都将向系统注册类型为CRED_CERTIFICATE的credential builder,与以上的create函数对应,这里调用注册函数pem_certificate_load。

METHOD(plugin_t, get_features, int, private_pem_plugin_t *this, plugin_feature_t *features[])
{
        static plugin_feature_t f[] = {

                /* certificate PEM decoding */
                ...
                PLUGIN_REGISTER(CERT_DECODE, pem_certificate_load, FALSE),
                        PLUGIN_PROVIDE(CERT_DECODE, CERT_TRUSTED_PUBKEY),

函数pem_certificate_load,封装了文件src/libstrongswan/plugins/pem/pem_builder.c文件中的函数pem_load,如下所示。解析出参数,文件名file和subject,调用函数load_from_file继续处理,此函数读取文件内容到缓存块chunk_t结构中,调用函数load_from_blob处理。

static void *pem_load(credential_type_t type, int subtype, va_list args)
{
        char *file = NULL;
        identification_t *subject = NULL;

        while (TRUE) {
                switch (va_arg(args, builder_part_t)) {
                        case BUILD_FROM_FILE:
                                file = va_arg(args, char*);
                                continue;
                        case BUILD_SUBJECT:
                                subject = va_arg(args, identification_t*);
                                continue;
                        case BUILD_END:
                                break;
                        default:
                                return NULL;
                }
                break;
        }
        if (file) { return load_from_file(file, type, subtype, subject, flags); }

如下所示,第一个参数blob保存了秘钥文件的内容,这里再次调用credential_factory_t结构的create函数,与上次不同,此次使用的两个参数为BUILD_BLOB_ASN1_DER和BUILD_SUBJECT。

static void *load_from_blob(chunk_t blob, credential_type_t type, int subtype, identification_t *subject, x509_flag_t flags)
{
        void *cred = NULL;

        if (type == CRED_CERTIFICATE && subtype == CERT_TRUSTED_PUBKEY && subject) {
                cred = lib->creds->create(lib->creds, type, subtype,
                                          BUILD_BLOB_ASN1_DER, blob, BUILD_SUBJECT, subject, BUILD_END);
        } 

位于文件src/libstrongswan/plugins/pubkey/pubkey_plugin.c的插件pubkey,注册了类型为RED_CERTIFICATE,子类型为CERT_TRUSTED_PUBKEY的特性,这里调用其函数pubkey_cert_wrap进行处理。

METHOD(plugin_t, get_features, int, private_pubkey_plugin_t *this, plugin_feature_t *features[])
{
        static plugin_feature_t f[] = {
                PLUGIN_REGISTER(CERT_ENCODE, pubkey_cert_wrap, FALSE),
                        PLUGIN_PROVIDE(CERT_ENCODE, CERT_TRUSTED_PUBKEY),
                PLUGIN_REGISTER(CERT_DECODE, pubkey_cert_wrap, TRUE),
                        PLUGIN_PROVIDE(CERT_DECODE, CERT_TRUSTED_PUBKEY),

如下src/libstrongswan/plugins/pubkey/pubkey_cert.c文件中函数pubkey_cert_wrap,根据传入的参数bloc和subject,再次调用credential_factory_t结构的create指针创建public_key_t结构,这次使用的类型值为CRED_PUBLIC_KEY,子类型为KEY_ANY。完成之后,由函数pubkey_cert_create据此publick_key_t结构创建一个公钥证书结构pubkey_cert_t。

pubkey_cert_t *pubkey_cert_wrap(certificate_type_t type, va_list args)
{                       
        public_key_t *key = NULL;
        chunk_t blob = chunk_empty;
        identification_t *subject = NULL;
        time_t notBefore = UNDEFINED_TIME, notAfter = UNDEFINED_TIME;
                                
        while (TRUE) {
                switch (va_arg(args, builder_part_t)) {
                        case BUILD_BLOB_ASN1_DER:
                                blob = va_arg(args, chunk_t);
                                continue;
                        case BUILD_SUBJECT:
                                subject = va_arg(args, identification_t*);
                                continue;
                        case BUILD_END:
                                break;
                        default:
                                return NULL;
                }
                break;
        }
        if (key) { ... }
        else if (blob.ptr) {
                key = lib->creds->create(lib->creds, CRED_PUBLIC_KEY, KEY_ANY, BUILD_BLOB_ASN1_DER, blob, BUILD_END);
        }
        if (key) { return pubkey_cert_create(key, notBefore, notAfter, subject); }

文件src/libstrongswan/plugins/pkcs1/pkcs1_plugin.c中实现的pkcs1插件,提供了对类型为CRED_PUBLIC_KEY,子类型为KEY_ANY的支持,由函数pkcs1_public_key_load完成。

METHOD(plugin_t, get_features, int, private_pkcs1_plugin_t *this, plugin_feature_t *features[])
{       
        static plugin_feature_t f[] = {
                PLUGIN_REGISTER(PUBKEY, pkcs1_public_key_load, FALSE),
                        PLUGIN_PROVIDE(PUBKEY, KEY_ANY),

如下为pkcs1_public_key_load函数,其根据BUILD_BLOB_ASN1_DER类型,取得公钥文件内容blob,之后根据公钥类型KEY_ANY,调用函数parse_public_key进行处理,而且将会再次调用credential_factory_t结构的create函数指针,但是,将key_type_t参数修改为KEY_ESA。因此,最终将调用到以下的函数parse_rsa_public_key。

public_key_t *pkcs1_public_key_load(key_type_t type, va_list args)
{                       
        chunk_t blob = chunk_empty;
                                
        while (TRUE)             {                       
                switch (va_arg(args, builder_part_t)) {               
                        case BUILD_BLOB_ASN1_DER:
                                blob = va_arg(args, chunk_t);
                                continue;
                        case BUILD_END:
                                break;
                        default:
                                return NULL;
                }
                break;
        }
        switch (type) {
                case KEY_ANY:
                        return parse_public_key(blob);
                case KEY_RSA:
                        return parse_rsa_public_key(blob);

如下函数parse_rsa_public_key,其由blob中解析出PUB_KEY_MODULUS和PUB_KEY_EXPONENT两个字段,以这两个值为参数再次调用credential_factory_t结构的create函数指针。

static public_key_t *parse_rsa_public_key(chunk_t blob)
{
        chunk_t n, e;
        asn1_parser_t *parser;

        parser = asn1_parser_create(pubkeyObjects, blob);
        while (parser->iterate(parser, &objectID, &object)) {
                switch (objectID) {
                        case PUB_KEY_MODULUS:
                                n = object;
                                break;
                        case PUB_KEY_EXPONENT:
                                e = object;
                                break;
                }
        }
        success = parser->success(parser);
        parser->destroy(parser);

        if (!success) {  return NULL;  }
        return lib->creds->create(lib->creds, CRED_PUBLIC_KEY, KEY_RSA, BUILD_RSA_MODULUS, n, BUILD_RSA_PUB_EXP, e, BUILD_END);

如下文件src/libstrongswan/plugins/gmp/gmp_plugin.c中定义的gmp插件,其注册了FEATURE_PUBKEY,子类型为KEY_RSA的处理特性。处理函数为gmp_rsa_public_key_load。

METHOD(plugin_t, get_features, int, private_gmp_plugin_t *this, plugin_feature_t *features[])
{
        static plugin_feature_t f[] = {
                PLUGIN_REGISTER(PUBKEY, gmp_rsa_public_key_load, TRUE),
                        PLUGIN_PROVIDE(PUBKEY, KEY_RSA),

以下函数gmp_rsa_public_key_load,首先提取参数中传入的RSA的modulus和exponent数据;之后,使用GMP库函数mpz_import导入modules和exponent两个数据。

gmp_rsa_public_key_t *gmp_rsa_public_key_load(key_type_t type, va_list args)
{                       
        private_gmp_rsa_public_key_t *this; 
        chunk_t n, e;
                
        n = e = chunk_empty;
        while (TRUE) {               
                switch (va_arg(args, builder_part_t)) {       
                        case BUILD_RSA_MODULUS:
                                n = va_arg(args, chunk_t);
                                continue;
                        case BUILD_RSA_PUB_EXP:
                                e = va_arg(args, chunk_t);
                                continue;
                        ...
                }
                break;
        }
        if (!e.len || !n.len || (n.ptr[n.len-1] & 0x01) == 0) { return NULL; }

        INIT(this,
                .public = {
                        ...
                },
                .ref = 1,
        );

        mpz_init(this->n);
        mpz_init(this->e);

        mpz_import(this->n, n.len, 1, 1, 1, 0, n.ptr);
        mpz_import(this->e, e.len, 1, 1, 1, 0, e.ptr);

        this->k = (mpz_sizeinbase(this->n, 2) + 7) / BITS_PER_BYTE;

        if (!mpz_sgn(this->e)) {  destroy(this);  return NULL; }
        return &this->public;

至此,公钥文件解析完成,用到的插件有gmp、pem、pkcs1和pubkey等,这些插件都在strongswan.conf文件中进行了加载。

IKE_SA_INIT报文

文件src/libcharon/sa/ikev2/tasks/ike_cert_pre.c中,函数build_r在响应的IKE_SA_INIT报文中,添加类型为PLV2_CERTREQ(38)的证书请求载荷。

METHOD(task_t, build_r, status_t, private_ike_cert_pre_t *this, message_t *message)
{
        if (message->get_exchange_type(message) == IKE_SA_INIT) {
                build_certreqs(this, message);
        }
        if (this->final) {  return SUCCESS; }
        return NEED_MORE;

在上节函数load_pubkey(位于文件src/libcharon/plugins/stroke/stroke_cred.c)的介绍中可知,此处的证书类型为CERT_TRUSTED_PUBKEY。函数add_certreq仅对证书类型为CERT_X509有效,所以,在sun网关回复的IKE_SA_INIT报文中,并不带有PLV1_CERTREQ(7)类型的载荷。

static void add_certreq(certreq_payload_t **req, certificate_t *cert)
{
        switch (cert->get_type(cert)) {
                case CERT_X509: {
                        ...
                        break;
                }
                default:
                        break;

随后,在IKE_AUTH报文的交互中,sun和moon双方都不会发送PLV1_CERTREQ(7)类型的载荷,并且也不为发送PLV1_CERTIFICATE(6)类型的载荷,双方需要通过DNSSEC获取对端的公钥。

IKE_AUTH验证

sun网关在接收到moon网关的IKE_AUTH报文之后,调用文件src/libcharon/sa/ikev2/authenticators/pubkey_authenticator.c中的函数process,处理报文中的PLV2_AUTH载荷数据。主要的验证工作由public_key_t结构的verify函数实现。

METHOD(authenticator_t, process, status_t, private_pubkey_authenticator_t *this, message_t *message)
{
        auth_payload = (auth_payload_t*)message->get_payload(message, PLV2_AUTH);
        if (!auth_payload) {       
                return FAILED;
        }
        auth_method = auth_payload->get_auth_method(auth_payload);
        auth_data = auth_payload->get_data(auth_payload);

        switch (auth_method) {
                case AUTH_RSA:
                        key_type = KEY_RSA;
                        params->scheme = SIGN_RSA_EMSA_PKCS1_SHA1;
                        break;
                ...
        }

        enumerator = lib->credmgr->create_public_enumerator(lib->credmgr, key_type, id, auth, online);
        while (enumerator->enumerate(enumerator, &public, &current_auth)) {
                if (public->verify(public, params->scheme, params->params, octets, auth_data) &&
                        is_compliant_cert(current_auth))

以上函数中的lib->credmgr->create_public_enumerator将调用到文件src/libcharon/plugins/ipseckey/ipseckey_cred.c中ipseckey插件的函数create_cert_enumerator,如下所示。首先,调用resolver_t结构的query函数执行DNS查询操作,对于moon主机,其ID载荷字段中带有ID类型为ID_FQDN(2),值为:moon.strongswan.org。此处的query函数将尝试解析此域名,请求RR_CLASS_IN类,RR_TYPE_IPSECKEY类型的资源记录。返回结构保存在返回的resolver_response_t结构中。

METHOD(credential_set_t, create_cert_enumerator, enumerator_t*, private_ipseckey_cred_t *this, certificate_type_t cert, key_type_t key,
        identification_t *id, bool trusted)
{
        resolver_response_t *response;
		cert_enumerator_t *e;

        /* query the DNS for the required IPSECKEY RRs */
        if (asprintf(&fqdn, "%Y", id) <= 0) {
                DBG1(DBG_CFG, "failed to determine FQDN to retrieve IPSECKEY RRs");
                return enumerator_create_empty();
        }
        DBG1(DBG_CFG, "performing a DNS query for IPSECKEY RRs of '%s'", fqdn);
        response = this->res->query(this->res, fqdn, RR_CLASS_IN, RR_TYPE_IPSECKEY);
        if (!response) {
                DBG1(DBG_CFG, "  query for IPSECKEY RRs failed");
                free(fqdn);
                return enumerator_create_empty();
        }
        ...

参考注释说明,以下代码根据DNS回复报文中的第一个RRSIG RR记录中的证书起止时间,来验证IPSECKEY RR记录的有效期。之后的版本,可能考虑报文中的多个RRSIG记录。之后,由DNS报文中RRSIG记录中获取出起止时间,保存在变量:nAfter和nBefore中。函数最后,初始化一个cert_enumerator_t结构,返回其public成员(enumerator_t结构类型)。

        /* determine the validity period of the retrieved IPSECKEYs
         * we use the "Signature Inception" and "Signature Expiration" field of the first RRSIG RR to determine the validity period of the IPSECKEY RRs. TODO: Take multiple RRSIGs into account. */
        rrset = response->get_rr_set(response);
        rrsig_enum = rrset->create_rrsig_enumerator(rrset);
        if (!rrsig_enum || !rrsig_enum->enumerate(rrsig_enum, &rrsig)) { ... return enumerator_create_empty(); }

        reader = bio_reader_create(rrsig->get_rdata(rrsig));  /* parse the RRSIG for its validity period (RFC 4034) */
        if (!reader->read_data(reader, 8, &ignore) ||
                !reader->read_uint32(reader, &nAfter) || !reader->read_uint32(reader, &nBefore)) {
                ...
                return enumerator_create_empty();
        }

        INIT(e,
                .public = {
                        .enumerate = enumerator_enumerate_default,
                        .venumerate = _cert_enumerator_enumerate,
                },
                .inner = rrset->create_rr_enumerator(rrset),
                .response = response,
                .notBefore = nBefore,
                .notAfter = nAfter,
                .identity = id,
        );
        return &e->public;

接下来pubkey_authenticator.c文件中的函数process,将调用以上函数中返回的enumerator_t结构的成员函数enumerate获取DNS报文中公钥(public_key_t),以及本地认证配置信息(auth_cfg_t),之后,由公钥结构的成员函数verify(public->verify)来执行对moon网关发送的AUTH载荷中数据的验证。

其中enumerate函数,实际上调用的为ipseckey_cred.c文件中的函数cert_enumerator_enumerate。如下所示,其将遍历所有的IPSECKEY RR记录,据此记录创建IPSEC秘钥结构ipseckey_t,首先具体结构创建公钥结构public_key_t;再将此公钥结构封装在证书(certificate_t)结构之内,

METHOD(enumerator_t, cert_enumerator_enumerate, bool, cert_enumerator_t *this, va_list args)
{
        certificate_t **cert;
        ipseckey_t *cur_ipseckey;
        public_key_t *public;
        rr_t *cur_rr;
        chunk_t key;

        /* Get the next supported IPSECKEY using the inner enumerator. */
        while (this->inner->enumerate(this->inner, &cur_rr)) {
                cur_ipseckey = ipseckey_create_frm_rr(cur_rr);

                if (cur_ipseckey->get_algorithm(cur_ipseckey) != IPSECKEY_ALGORITHM_RSA) { ...  continue; }

                /* wrap the key of the IPSECKEY in a certificate and return this certificate */
                key = cur_ipseckey->get_public_key(cur_ipseckey);
                public = lib->creds->create(lib->creds, CRED_PUBLIC_KEY, KEY_RSA, BUILD_BLOB_DNSKEY, key, BUILD_END);

                this->cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_TRUSTED_PUBKEY,
                                      BUILD_PUBLIC_KEY,      public,           BUILD_SUBJECT, this->identity,
                                      BUILD_NOT_BEFORE_TIME, this->notBefore,  BUILD_NOT_AFTER_TIME, this->notAfter,  BUILD_END);
                public->destroy(public);

                *cert = this->cert;
                return TRUE;

其中,函数ipseckey_create_frm_rr负责根据RR记录创建ipseckey_t结构的IPSEC秘钥。函数gmp_rsa_public_key_load负责将原始秘钥,封装为公钥结构public_key_t。最后函数pubkey_cert_wrap负责将公钥结构封装在证书(pubkey_cert_t)结构内。

之后,将调用credential_manager.c文件中的trusted_enumerate函数,如下,获取相应的本地认证配置。

METHOD(enumerator_t, trusted_enumerate, bool, trusted_enumerator_t *this, va_list args)
{
        certificate_t *current, **cert;
        auth_cfg_t **auth;

        VA_ARGS_VGET(args, cert, auth);
        this->auth = auth_cfg_create();

        if (!this->candidates) {    
                this->candidates = create_cert_enumerator(this->this, CERT_ANY, this->type, this->id, FALSE);
                this->pretrusted = get_pretrusted_cert(this->this, this->type, this->id);
                if (this->pretrusted) {
                        /* if we find a trusted self signed certificate, we just accept it... */
                        if (issued_by(this->this, this->pretrusted, this->pretrusted, NULL) ||
                                verify_trust_chain(this->this, this->pretrusted, this->auth, TRUE, this->online))
                        {
                                DBG1(DBG_CFG, "  using trusted certificate \"%Y\"", this->pretrusted->get_subject(this->pretrusted));
                                *cert = this->pretrusted;
                                if (!this->auth->get(this->auth, AUTH_RULE_SUBJECT_CERT)) {
                                        this->auth->add(this->auth, AUTH_RULE_SUBJECT_CERT, this->pretrusted->get_ref(this->pretrusted));
                                }
                                if (auth) {
                                        *auth = this->auth;

如下为文件src/libstrongswan/plugins/gmp/gmp_rsa_public_key.c中的验证函数verify,其封装了函数verify_emsa_pkcs1_signature,本例中使用的scheme为SHA256。

METHOD(public_key_t, verify, bool, private_gmp_rsa_public_key_t *this, signature_scheme_t scheme, void *params, 
                   chunk_t data, chunk_t signature)
{
        switch (scheme) {
                case SIGN_RSA_EMSA_PKCS1_SHA2_256:
                        return verify_emsa_pkcs1_signature(this, HASH_SHA256, data, signature);

以下为函数verify_emsa_pkcs1_signature,其负责根据数据生成期望的编码消息,并将其与证书中获取到的编码消息进行对比,以验证签名的有效性。

static bool verify_emsa_pkcs1_signature(private_gmp_rsa_public_key_t *this, hash_algorithm_t algorithm, chunk_t data, chunk_t signature)
{
        chunk_t em_expected, em;
        bool success = FALSE;

        /* remove any preceding 0-bytes from signature */
        while (signature.len && *(signature.ptr) == 0x00) { signature = chunk_skip(signature, 1); }

        if (signature.len == 0 || signature.len > this->k) { return FALSE; }

        /* generate expected signature value */
        if (!gmp_emsa_pkcs1_signature_data(algorithm, data, this->k, &em_expected)) { return FALSE; }

        /* unpack signature */
        em = rsavp1(this, signature);
        success = chunk_equals_const(em_expected, em);

至此,sun网关对接收到的IKE_AUTH报文中的AUTH载荷验证完成,连接建立,并且组织IKE_AUTH回复报文,发送到moon网关。moon网关在接收到之后,对AUTH字段的验证与以上介绍的sun网关的处理流程一致。

DNSSEC交互

文件src/libstrongswan/plugins/unbound/unbound_resolver.c中函数query如下。首先调用unbound库中的函数ub_resolve执行域名解析操作;之后,由函数unbound_response_create_frm_libub_response解析返回的结果信息。

unbound库的代码可在github地址:https://github.com/NLnetLabs/unbound.git中获得。

METHOD(resolver_t, query, resolver_response_t*, private_resolver_t *this, char *domain, rr_class_t rr_class, rr_type_t rr_type)
{
        unbound_response_t *response = NULL;

        ub_retval = ub_resolve(this->ctx, domain, rr_type, rr_class, &result);
        if (ub_retval) {
                DBG1(DBG_LIB, "unbound resolver error: %s", ub_strerror(ub_retval));
                ub_resolve_free(result);
                return NULL;
        }

        response = unbound_response_create_frm_libub_response(result);
        if (!response) {
                DBG1(DBG_LIB, "unbound resolver failed to create response");
                ub_resolve_free(result);
                return NULL;
        }
        ub_resolve_free(result);
        return (resolver_response_t*)response;

以下为sun网关发送的第一个DNS query报文,这里查询的域名为:moon.strongswan.org,查询的类为INTERNET(0x0001),类型为IPSECKEY(45)。另外,在附加区域中,包含一个EDNS0定义的OPT伪资源记录,其名称为根root,EDNS的版本version为0,标志位DO(DNSSEC OK)为1,表明可接收DNSSEC定义的安全相关RR记录。EDNS的相关初始化可参见文件libunbound/libworker.c中函数setup_qinfo_edns。

另外在DNS报文头部,将标志为CD(Checking disabled)设置为0,表明不接受未认证的数据。

在这里插入图片描述

以下为相应的query回复报文。在libunbound库中,文件iterator/iterator.c中的函数process_response负责处理DNS回复消息。之后文件validator/validator.c中的函数val_handle进行验证处理,初始情况下,认证器处于VAL_INIT_STATE状态,子函数processInit负责处理。

以下Answer段中RRSIG资源记录中的签名者字段为strongswan.org,而之前使用函数ub_ctx_add_ta_file添加的trusted anchor文件/etc/ipsec.d/dnssec.keys,包含的是根root的KSK。签名者字段strongswan.org为根域的子域。

Key tag字段值为:9396,用于标识可验证此签名的公钥所对应的DNSKEY资源记录。

在这里插入图片描述

由于本端dnssec.keys文件中包含的为根root的KSK,无法对RRSIG中的签名数据进行验证。接下来请求root的DNSKEY资源记录。如下为sun网关发送的第二个DNS query报文,请求类型为DNSKEY,域名为根。与之前的请求不同,这里设置DNS头部的CD位为1,接受非认证的数据。

在这里插入图片描述

以下为相应的回复报文,可见其中包含根root的KSK(用于验证ZSK签名)和ZSK(用于验证区数据签名),以及对应的RRSIG签名资源记录。回顾以上在Trust Anchor配置文件/etc/ipsec.d/dnssec.keys中,指定了可信任的key id为32329的KSK,其可用来验证如下具有相同keytag(32329)值的RRSIG资源记录中的签名的正确性。如下所示,签名算法使用RSA,哈希算法使用SHA-256,除此之外,还需验证签名的有效期。此签名覆盖了之前的两个DNSKEY资源记录。

完整的验证过程可参考文件validator/val_sigcrypt.c中的dnskey_verify_rrset_sig函数。首先根据哈希算法SHA-256对DNSKEY资源记录数据进行哈希计算,之后,使用Openssl函数EVP_VerifyFinal根据签名数据和公钥(dnssec.keys),验证DNSKEY资源记录的完整性。

最后,注意在此报文中包含一个keyid等于43749的DNSKEY资源记录,将用于对随后子域中相应签名的验证。

在这里插入图片描述

以上我们获取并验证了根域root(.)中DNSKEY资源记录信息,下一步获取根域中org.子域的代理签名(Delegation Signer)信息。以下为sun网关发送的第3个DNS query报文,请求类型为DS(Delegation Signer):

在这里插入图片描述

以下为相应的回复报文,可见获取到了两个DS资源记录,keyid(计算而得,非报文内容)都为0xca93(51859),但是前一个RR使用的摘要算法为SHA-256;而后一个使用的为SHA-1。除此之外,还可见一个RRSIG资源记录,其可由keytag为43749的DNSKEY进行验证,此正是在之前根域中获取的一个DNSKEY记录。

文件validator/validator.c中的函数process_ds_response负责处理此报文。函数val_verify_rrset_entry负责根据根域(.)中的keyid为43749的DNSKEY(ZSK),以及RRSIG签名记录,验证DS资源的完整性,算法为RSA/SHA-256,除使用的DNSKEY不同外,其余流程与以上介绍的验证流程相同。

至此,根域的信息都已经获取并处理完成,得到了两个keyid为0xca93(51859)的可信DS资源记录。

关于DNSKEY资源记录的key id值的计算可参考函数sldns_calc_keytag_raw,在rfc4034中有原理说明,即将所属域名和RDATA字段按照16bit进行累加所得。

在这里插入图片描述

接下来,获取资源org.相关的DNSSEC信息。以下为sun网关发送的第4个DNS query报文,请求类型为DNSKEY,域名为org:

在这里插入图片描述

以下为相应的回复报文。可见两个DNSKEY资源记录,以及相应的RRSIG签名资源记录。其中第二个DNSKEY资源记录的keyid为51859,正对应于在根域中获取的DS资源记录的keyid值。对此DNSKEY资源记录的验证由函数val_verify_DNSKEY_with_DS完成。这里使用的算法为SHA-256,首先对DNSKEY资源记录数据进行摘要计算;之后将结果与DS资源记录中的摘要数据进行对比,相同的话验证通过。注意此DNSKEY为一个KSK。

最后,使用此DNSKEY验证其RRSIG资源记录的有效性,此RRSIG签名覆盖了之前的两个DS资源记录。另外一个keyid为24285的DNSKEY资源记录,其类型为ZSK,用于之后DS字段的签名验证。

在这里插入图片描述

由于我们最终要查找的为moon.strongswan.org.域名的DNSKEY资源记录。接下来sun网关发送的第5个DNS query报文,请求org.区中子域strongswan.org的代理签名资源记录DS(Delegation Signer):

在这里插入图片描述

以下为相应的回复报文。其中包含两个key id(此处keyid为报文数据,不同于DNSKEY中的计算所得值)为0x01e1(481)的DS资源记录,前者算法为SHA-1,后者使用算法SHA-256。最后的为RRSIG资源记录,可见此签名数据由keytag为24285的名称为org的DNSKEY所签,此签名覆盖了之前的两个DNSKEY资源记录。

至此,org区中的DNSSEC相关数据都已经得到。

在这里插入图片描述

以下为sun网关发送的第6个DNS query报文,请求类型为DNSKEY,域名为strongswan.org:

在这里插入图片描述

以下为相应的回复报文。可见其中的两个DNSKEY资源记录,前者经计算的keyid为481,后者keyid为9396。另外是两个RRSIG资源记录,分别对应keytag为481和9396。由keytag为481的RRSIG数据可知,其签名数据由strongswan.org生成,可使用keytag为481的DNSKEY进行验证。

如前所述,在验证签名之前,首先使用org中的keyid为481的DS资源记录,验证keyid为481的DNSKEY的有效性。之后,验证RRSIG中签名数据的有效性。此签名覆盖了之前的两个DNSKEY资源记录。其中keyid为9396的DNSKEY用于验证moon.strongswan.org域中RRSIG签名的公钥。

至此,可对moon.strongswan.org中的资源记录IPSECKEY的签名进行验证,验证成功之后,即得到了一个可信的moon.strongswan.org的公钥。

在这里插入图片描述

对于moon网关而言,其使用同样的方法可获得sun.strongswan.org的可信公钥。

DNS报文解析

以下为解析函数unbound_response_create_frm_libub_response,首先,调用ldns库函数ldns_wire2pkt将unbound库结构ub_result中的answer_packet转换为ldns库结构dns_pkt(结构为:ldns_pkt)。接下来,遍历其中的Answers段中的RR记录,这其中有两个记录:IPSECKEY和RRSIG记录,unbound库结构libub_response的成员qtype和qclass保存着发起query请求时,指定的类型和类。

对于IPSECKEY类型记录,使用函数unbound_rr_create_frm_ldns_rr创建一个private_unbound_rr_t结构,将记录中的内容赋值到此结构中,包括:type/class/ttl/rdata字段。并且将此新建的结构插入到rr_list链表。

unbound_response_t *unbound_response_create_frm_libub_response(struct ub_result *libub_response)
{
        private_unbound_response_t *this = NULL;

        /* Create RRset */
        if (this->query_name_exist && this->has_data) {
                status = ldns_wire2pkt(&dns_pkt, libub_response->answer_packet, libub_response->answer_len);
                if (status != LDNS_STATUS_OK) { ... return NULL; }

                rr_list = linked_list_create();
                orig_rr_list = ldns_pkt_answer(dns_pkt);
                orig_rr_count = ldns_rr_list_rr_count(orig_rr_list);

                for (i = 0; i < orig_rr_count; i++) {
                        orig_rr = ldns_rr_list_rr(orig_rr_list, i);

                        if (ldns_rr_get_type(orig_rr) == libub_response->qtype &&
                                ldns_rr_get_class(orig_rr) == libub_response->qclass) {

                                rr = unbound_rr_create_frm_ldns_rr(orig_rr);
                                if (rr) {
                                        rr_list->insert_last(rr_list, rr);
                                } else {
                                        DBG1(DBG_LIB, "failed to create RR");
                                }
                        }

对于RRSIG类型记录,检查其type covered字段的值,此处为IPSECKEY(45),表明其中的签名数据属于当前查询的RRset,此种情况下,同样使用unbound_rr_create_frm_ldns_rr函数创建一个private_unbound_rr_t结构,初始化之后,将其链接到另外的一个rrsig_list链表。

                        if (ldns_rr_get_type(orig_rr) == LDNS_RR_TYPE_RRSIG)
                        {
                                orig_rdf = ldns_rr_rrsig_typecovered(orig_rr);
                                if (!orig_rdf) {
                                        ...
                                }
                                else if (ldns_rdf2native_int16(orig_rdf) == libub_response->qtype) {
                                        /* The current RR represent a signature (RRSIG) which belongs to the queried RRset.
                                         * => add it to the list of signatures.
                                         */
                                        rr = unbound_rr_create_frm_ldns_rr(orig_rr);
                                        if (rr) {
                                                if (!rrsig_list) {
                                                        rrsig_list = linked_list_create();
                                                }
                                                rrsig_list->insert_last(rrsig_list, rr);
                                        } 
                                }
                        }
                }

函数最后,将IPSECKEY类型记录的链表rr_list和RRSIG类型记录的链接rrsig_list,共同初始化一个private_rr_set_t结构,并且赋值给private_unbound_response_t结构成员rr_set,以供之后使用。

                /* Create the RRset for which the query was performed.
                 */
                this->rr_set = rr_set_create(rr_list, rrsig_list);
                ldns_pkt_free(dns_pkt);
        }
        return &this->public;

IPSECKEY记录

上节的处理流程返回到ipseckey_cred.c文件中的create_cert_enumerator函数之后,将由返回结构response中取出rrsig结构,进而读取其中的证书有效期,最后初始化一个cert_enumerator_t结构,其中包含RRSIG记录数据,证书有效期等数据。并且,根据返回结构中的rrset(包含IPSECKEY和RRSIG链表)创建一个enumerator_t结构赋值于成员inner。

METHOD(credential_set_t, create_cert_enumerator, enumerator_t*, private_ipseckey_cred_t *this, certificate_type_t cert, key_type_t key,
        identification_t *id, bool trusted)
{
        rrset = response->get_rr_set(response);
        rrsig_enum = rrset->create_rrsig_enumerator(rrset);

        reader = bio_reader_create(rrsig->get_rdata(rrsig));
        if (!reader->read_data(reader, 8, &ignore) ||
                !reader->read_uint32(reader, &nAfter) || !reader->read_uint32(reader, &nBefore)) {
                ... return enumerator_create_empty();
        }
        INIT(e,
                .public = {
                        .enumerate = enumerator_enumerate_default,
                        .venumerate = _cert_enumerator_enumerate,
                        .destroy = _cert_enumerator_destroy,
                },
                .inner = rrset->create_rr_enumerator(rrset),
                .response = response,
                .notBefore = nBefore,
                .notAfter = nAfter,
                .identity = id,

以下的函数cert_enumerator_enumerate开始调用以上创建的inner的成员函数enumerate,虽然inner中包含有两个RR,IPSECKEY和RRSIG,但是此处仅处理IPSECKEY类型的RR。

METHOD(enumerator_t, cert_enumerator_enumerate, bool, cert_enumerator_t *this, va_list args)
{
        certificate_t **cert;
        ipseckey_t *cur_ipseckey;
        public_key_t *public;
        rr_t *cur_rr;
        chunk_t key;

        VA_ARGS_VGET(args, cert);

        /* Get the next supported IPSECKEY using the inner enumerator. */
        while (this->inner->enumerate(this->inner, &cur_rr)) {
                cur_ipseckey = ipseckey_create_frm_rr(cur_rr);
                if (!cur_ipseckey) {  ... continue; }
                if (cur_ipseckey->get_algorithm(cur_ipseckey) != IPSECKEY_ALGORITHM_RSA) {  ... continue; }
                key = cur_ipseckey->get_public_key(cur_ipseckey);
				
                public = lib->creds->create(lib->creds, CRED_PUBLIC_KEY, KEY_RSA, BUILD_BLOB_DNSKEY, key, BUILD_END);
                cur_ipseckey->destroy(cur_ipseckey);
                if (!public) { ...  continue; }
                DESTROY_IF(this->cert);
                this->cert = lib->creds->create(lib->creds, CRED_CERTIFICATE,
                                                                                CERT_TRUSTED_PUBKEY,
                                                                                BUILD_PUBLIC_KEY, public,
                                                                                BUILD_SUBJECT, this->identity,
                                                                                BUILD_NOT_BEFORE_TIME, this->notBefore,
                                                                                BUILD_NOT_AFTER_TIME, this->notAfter,
                                                                                BUILD_END);
                public->destroy(public);
                if (!this->cert) { ...  continue; }
                *cert = this->cert;
                return TRUE;

如下函数ipseckey_create_frm_rr,解析IPSECKEY类型RR记录,获取RDATA字段中的precedence优先级(10)、gateway类型(3),以及公钥算法(RSA)。最后,将IPSECKEY RR记录中公钥数据读取到private_ipseckey_t结构变量的成员public_key中。

ipseckey_t *ipseckey_create_frm_rr(rr_t *rr)
{
        private_ipseckey_t *this;
        bio_reader_t *reader = NULL;

        if (rr->get_type(rr) != RR_TYPE_IPSECKEY) { ...  return NULL; }

        /** Parse the content (RDATA field) of the RR */
        reader = bio_reader_create(rr->get_rdata(rr));
        if (!reader->read_uint8(reader, &this->precedence) ||
                !reader->read_uint8(reader, &this->gateway_type) ||
                !reader->read_uint8(reader, &this->algorithm)) {
                ... return NULL;
        }
        switch (this->gateway_type) {
                case IPSECKEY_GW_TP_WR_ENC_DNAME:
                        /* Uncompressed domain name as defined in RFC 1035 chapter 3. TODO: Currently we ignore wire encoded domain names.
                         */
                        while (reader->read_uint8(reader, &label) && label != 0 && label < 192) {
                                if (!reader->read_data(reader, label, &tmp)) { ... return NULL; }
                        }
                        break;
        }

        if (!reader->read_data(reader, reader->remaining(reader), &this->public_key)) { return NULL; }
        this->public_key = chunk_clone(this->public_key);

之后,函数指针lib->creds->create将依据以上由IPSECKEY记录中读取的公钥数据,创建公钥结构public_key_t。此函数指针的实现为文件src/libstrongswan/plugins/dnskey/dnskey_builder.c中的函数dnskey_public_key_load。其主要功能由函数parse_rsa_public_key完成。

dnskey_public_key_t *dnskey_public_key_load(key_type_t type, va_list args)
{
        chunk_t blob = chunk_empty;

        while (TRUE) {
                switch (va_arg(args, builder_part_t)) {
                        case BUILD_BLOB_DNSKEY:
                                blob = va_arg(args, chunk_t);
                                continue;
                        case BUILD_END:
                                break;
                        default:
                                return NULL;
                }
                break;
        }
        if (!blob.ptr) { return NULL; }
        switch (type) {
                case KEY_ANY:
                        return parse_public_key(blob);
                case KEY_RSA:
                        return parse_rsa_public_key(blob);

如下函数parse_rsa_public_key,其将IPSECKEY记录中的公钥数据拆分成两个部分:Modules和Exponent,拆分算法如下,如果数据中第一个字节不为空,其表示exponent数据的长度;否则,exponent数据的长度为一个16bit的值,高8位为数据中的第二个字节,低8位为数据中的第三个字节。确定exponent数据后,剩余的就为modules数据。最后,调用函数指针lib->creds->create完成创建工作,与上次调用此函数指针不同,此处传入不同的参数。

实际调用的为文件src/libstrongswan/plugins/gmp/gmp_rsa_public_key.c中的函数gmp_rsa_public_key_load。

static dnskey_public_key_t *parse_rsa_public_key(chunk_t blob)
{       
        chunk_t n, e;

        if (blob.ptr[0]) {       
                e.len = blob.ptr[0]; 
                blob = chunk_skip(blob, 1);
        } else {       
                e.len = blob.ptr[1] * 256 + blob.ptr[2];
                blob = chunk_skip(blob, 3);
        }
        e.ptr = blob.ptr;
        if (e.len >= blob.len) {       
                DBG1(DBG_LIB, "RFC 3110 public key blob too short for exponent");
                return NULL;
        } 
        n = chunk_skip(blob, e.len);
        return lib->creds->create(lib->creds, CRED_PUBLIC_KEY, KEY_RSA, BUILD_RSA_MODULUS, n, BUILD_RSA_PUB_EXP, e, BUILD_END);

如下为函数gmp_rsa_public_key_load,首先读取modulus和exponent参数数据到n,e缓存中。随后,使用GNU MP库函数mpz_import加载这两个数据。新创建结构private_gmp_rsa_public_key_t的成员k保存了modulus数据的字节表示的长度值。最后,确认exponent的值不为零。

gmp_rsa_public_key_t *gmp_rsa_public_key_load(key_type_t type, va_list args)
{
        private_gmp_rsa_public_key_t *this;
        chunk_t n, e;

        n = e = chunk_empty;
        while (TRUE) {       
                switch (va_arg(args, builder_part_t)) {       
                        case BUILD_RSA_MODULUS:
                                n = va_arg(args, chunk_t);
                                continue;
                        case BUILD_RSA_PUB_EXP:
                                e = va_arg(args, chunk_t);
                                continue;
                        ...
        }

        mpz_init(this->n);
        mpz_init(this->e);
        mpz_import(this->n, n.len, 1, 1, 1, 0, n.ptr);
        mpz_import(this->e, e.len, 1, 1, 1, 0, e.ptr);
        this->k = (mpz_sizeinbase(this->n, 2) + 7) / BITS_PER_BYTE;

        if (!mpz_sgn(this->e)) { return NULL; }
        return &this->public;

ipseckey_cred.c文件中的函数cert_enumerator_enumerate的最后,函数指针lib->creds->create根据以上创建的public_key_t结构数据,以及对端identity信息,和由RRSIG记录中取得的有效期信息,来创建一个证书结构certificate_t。

实际上调用的是文件libstrongswan/plugins/pubkey/pubkey_cert.c中的函数pubkey_cert_wrap,如下所示,其首先由参数中读取到公钥结构数据和有效期,subject信息;随后由函数pubkey_cert_wrap进行处理。

pubkey_cert_t *pubkey_cert_wrap(certificate_type_t type, va_list args)
{
        public_key_t *key = NULL;
        identification_t *subject = NULL;
        time_t notBefore = UNDEFINED_TIME, notAfter = UNDEFINED_TIME;

        while (TRUE) {
                switch (va_arg(args, builder_part_t)) {
                        case BUILD_BLOB_ASN1_DER:
                                blob = va_arg(args, chunk_t);
                                continue;
                        case BUILD_PUBLIC_KEY:
                                key = va_arg(args, public_key_t*);
                                continue;
                        case BUILD_NOT_BEFORE_TIME:
                                notBefore = va_arg(args, time_t);
                                continue;
                        case BUILD_NOT_AFTER_TIME:
                                notAfter = va_arg(args, time_t);
                                continue;
                        case BUILD_SUBJECT:
                                subject = va_arg(args, identification_t*);
                                continue;
                        ...
        }
        if (key) {
                return pubkey_cert_create(key, notBefore, notAfter, subject);

函数pubkey_cert_create内容如下,分配private_pubkey_cert_t结构,进行相应的赋值操作,最后,返回private_pubkey_cert_t结构的公共部分public,结构类型为pubkey_cert_t。自此,将IPSECKEY记录中的公钥,封装在了证书结构pubkey_cert_t中。

static pubkey_cert_t *pubkey_cert_create(public_key_t *key, time_t notBefore, time_t notAfter, identification_t *subject)
{
        private_pubkey_cert_t *this;
        chunk_t fingerprint;

        INIT(this,
                .public = {
                        .interface = {
                                ...
                        },
                        .set_subject = _set_subject,
                },
                .ref = 1,
                .key = key,
                .notBefore = notBefore,
                .notAfter = notAfter,
                .issuer = identification_create_from_encoding(ID_ANY, chunk_empty),
        );

        if (subject) {
                this->subject = subject->clone(subject);
        } 
        return &this->public;

AUTH载荷生成

在文件libcharon/sa/ikev2/tasks/ike_auth.c中,函数build_r调用authenticator_t结构的build函数创建AUTH载荷,对于此例子,结构authenticator_t由函数authenticator_create_builder的子函数pubkey_authenticator_create_builder创建。

authenticator_t *authenticator_create_builder(ike_sa_t *ike_sa, auth_cfg_t *cfg,
                            chunk_t received_nonce, chunk_t sent_nonce, chunk_t received_init, chunk_t sent_init, char reserved[3])
{
        switch ((uintptr_t)cfg->get(cfg, AUTH_RULE_AUTH_CLASS))
        {
                case AUTH_CLASS_PUBKEY:
                        return (authenticator_t*)pubkey_authenticator_create_builder(ike_sa,
                                                                                received_nonce, sent_init, reserved);

其build函数位于文件libcharon/sa/ikev2/authenticators/pubkey_authenticator.c中,如下所示,auth载荷数据的生成主要由sign_signature_auth函数完成。

METHOD(authenticator_t, build, status_t, private_pubkey_authenticator_t *this, message_t *message)
{
        private_key_t *private;
        auth_cfg_t *auth;

        id = this->ike_sa->get_my_id(this->ike_sa);
        auth = this->ike_sa->get_auth_cfg(this->ike_sa, TRUE);
        private = lib->credmgr->get_private(lib->credmgr, KEY_ANY, id, auth);

        if (this->ike_sa->supports_extension(this->ike_sa, EXT_SIGNATURE_AUTH))
        {
                status = sign_signature_auth(this, auth, private, id, message);
        } else {
                status = sign_classic(this, auth, private, id, message);
        }

由以下函数sign_signature_auth可见,原始数据主要由两部分组成:本端发送的的IKE_SA_INIT报文数据和接收到的对端的Nonce数据。之后,使用本端的私钥对其进行签名。auth载荷中除包含签名数据外,在开始字段,包含有使用的算法标识,此处为 1.2.840.113549.1.1.11,即sha256WithRSAEncryption。

static status_t sign_signature_auth(private_pubkey_authenticator_t *this,
                                    auth_cfg_t *auth, private_key_t *private, identification_t *id, message_t *message)
{
        keymat_v2_t *keymat;
        signature_params_t *params = NULL;
        chunk_t octets = chunk_empty, auth_data;

        keymat = (keymat_v2_t*)this->ike_sa->get_keymat(this->ike_sa);
        schemes = select_signature_schemes(keymat, auth, private);

        if (keymat->get_auth_octets(keymat, FALSE, this->ike_sa_init, this->nonce, this->ppk, id, this->reserved, &octets, schemes))
        {
                enumerator = array_create_enumerator(schemes);
                while (enumerator->enumerate(enumerator, &params)) {
                        if (!private->sign(private, params->scheme, params->params, octets, &auth_data) ||
                                !build_signature_auth_data(&auth_data, params))
                        {
                                DBG2(DBG_IKE, "unable to create %N signature for %N key",
                                         signature_scheme_names, params->scheme, key_type_names, private->get_type(private));
                                continue;
                        }
                        add_auth_to_message(message, AUTH_DS, auth_data, FALSE);
                        status = SUCCESS;

strongswan版本: 5.8.1

已标记关键词 清除标记
目前正在研究dnssec的有关内容,需要对得到的rrsig记录进行验签。目前打算使用的解决方法是通过dnspython的dnssec.validate_rrsig()函数进行验证。 但相关的文档和代码实在太少了,自己多次尝试构建rrsig和rrset记录去验签都验证不通过。验签所用的dnskey和rrsig记录都来自于公网,理论上来说应该是能够验签的。 请问是否是我的传参有问题?亦或者是否有其他的python解决方案? rrsig记录集: > paypal.com. 300 IN A 64.4.250.36 > paypal.com. 300 IN A 64.4.250.37 > paypal.com. 300 IN RRSIG A 5 2 300 20190208233736 20190109232130 11811 paypal.com. YsbDXEurHDP+LJOQ6i5PkspB1O7PjRE8bReV2QsP4r9Hhn41sjR9JkKM 4VqhmFFuN+mVefaoj5Els9rpGpYgSXXIP2bkseuITKCf9BXkITNuy1BY cgmRA67VVAnedkLGzGBBvUIchrwGF9NOt/jHMW9pbO9UqUiya/NDYDmY YIw= dnskey: > paypal.com. 600 IN DNSKEY 256 3 5 AwEAAc/7r7w6qEg59vzyfcKgIm7K3h43tglKOJjoFyK5lxhl2e7vh8Cw vj3cwKQHccsvWuvAK0ummTSysxZT0JJgw8gkhIGGiZ63MJTcbocDezgZ iA/q4ejpjdrj27gs5mCHAsyC9BAVZiysfIwqtRFsP0GjivNYzIc6qGWA XcAJKLLX 报错内容:验证失败 > raise ValidationFailure('verify failure') > dns.dnssec.ValidationFailure: verify failure 我尝试验签的代码如下: ``` import dns import base64 from dns.rdatatype import (DNSKEY, RRSIG) from dns.rdataclass import (IN) import dns.name import dns.rdtypes.ANY.DNSKEY import dns.rdtypes.ANY.RRSIG import dns.rrset import dns.zone signer = dns.name.from_text('paypal.com.') signature = 'YsbDXEurHDP+LJOQ6i5PkspB1O7PjRE8bReV2QsP4r9Hhn41sjR9JkKM 4VqhmFFuN+mVefaoj5Els9rpGpYgSXXIP2bkseuITKCf9BXkITNuy1BY cgmRA67VVAnedkLGzGBBvUIchrwGF9NOt/jHMW9pbO9UqUiya/NDYDmY YIw=' rsignature = base64.b64decode(bytes(signature.encode('utf-8'))) rrsig = dns.rdtypes.ANY.RRSIG.RRSIG(IN, RRSIG, dns.rdatatype.A, 5, 2, 300, 1549640256, 1547047290, 11811, signer, rsignature) rname = dns.name.from_text('paypal.com.') rrset = dns.rrset.from_rdata(rname, 600, rrsig) # rrset = name + ttl + rdata r=dns.zone.Zone(dns.name.from_text('.')) r['paypal.com.'] = dns.rdataset.from_text(IN, DNSKEY, 600, '256 3 5 AwEAAc/7r7w6qEg59vzyfcKgIm7K3h43tglKOJjoFyK5lxhl2e7vh8Cw vj3cwKQHccsvWuvAK0ummTSysxZT0JJgw8gkhIGGiZ63MJTcbocDezgZ iA/q4ejpjdrj27gs5mCHAsyC9BAVZiysfIwqtRFsP0GjivNYzIc6qGWA XcAJKLLX') dns.dnssec.validate_rrsig(rrset, rrsig, r) ```
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页