以下根据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, ¤t_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, ¶ms)) {
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