dirmngr: Support the new Active Directory schema

* dirmngr/ks-engine-ldap.c (SERVERINFO_): New constants.
(my_ldap_connect): Relace args pgpkeyattrp and real_ldapp by a new
serverinfo arg.  Set the new info flags.
(ks_ldap_get): Adjust for change.
(ks_ldap_search): Ditto.
(ks_ldap_put): Ditto.  Replace xmalloc by xtrymalloc.  Change the DN
for use with NTDS (aka Active Directory).
* doc/ldap/gnupg-ldap-init.ldif (pgpSoftware): Update definition of
pgpVersion.
* doc/ldap/gnupg-ldap-ad-init.ldif: New.
* doc/ldap/gnupg-ldap-ad-schema.ldif: New.
--

This is a first take on better Active Directory support.  The main
change for NTDS in the code is that the an top-RDN of CN is used
instead of the old pgpCertID.  More changes to come; for example using
and storing the fingerprint.

Signed-off-by: Werner Koch <wk@gnupg.org>
This commit is contained in:
Werner Koch 2020-12-14 19:28:25 +01:00
parent cc056eb534
commit e9ddd61fe9
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
4 changed files with 523 additions and 110 deletions

View File

@ -1,7 +1,7 @@
/* ks-engine-ldap.c - talk to a LDAP keyserver /* ks-engine-ldap.c - talk to a LDAP keyserver
* Copyright (C) 2001, 2002, 2004, 2005, 2006 * Copyright (C) 2001, 2002, 2004, 2005, 2006
* 2007 Free Software Foundation, Inc. * 2007 Free Software Foundation, Inc.
* Copyright (C) 2015 g10 Code GmbH * Copyright (C) 2015, 2020 g10 Code GmbH
* *
* This file is part of GnuPG. * This file is part of GnuPG.
* *
@ -49,6 +49,15 @@
#include "ks-engine.h" #include "ks-engine.h"
#include "ldap-parse-uri.h" #include "ldap-parse-uri.h"
/* Flags with infos from the connected server. */
#define SERVERINFO_REALLDAP 1 /* This is not the PGP keyserver. */
#define SERVERINFO_PGPKEYV2 2 /* Needs "pgpeyV2" instead of "pgpKey" */
#define SERVERINFO_SCHEMAV2 4 /* Version 2 of the Schema. */
#define SERVERINFO_NTDS 8 /* Server is an Active Directory. */
#ifndef HAVE_TIMEGM #ifndef HAVE_TIMEGM
time_t timegm(struct tm *tm); time_t timegm(struct tm *tm);
#endif #endif
@ -427,40 +436,42 @@ keyspec_to_ldap_filter (const char *keyspec, char **filter, int only_exact)
The values are returned in the passed variables. If you pass NULL, The values are returned in the passed variables. If you pass NULL,
then the value won't be returned. It is the caller's then the value won't be returned. It is the caller's
responsibility to release *LDAP_CONNP with ldap_unbind and xfree responsibility to release *LDAP_CONNP with ldap_unbind and xfree
*BASEDNP and *PGPKEYATTRP. *BASEDNP.
If this function successfully interrogated the server, it returns If this function successfully interrogated the server, it returns
0. If there was an LDAP error, it returns the LDAP error code. If 0. If there was an LDAP error, it returns the LDAP error code. If
an error occurred, *basednp, etc., are undefined (and don't need to an error occurred, *basednp, etc., are undefined (and don't need to
be freed.) be freed.)
R_SERVERINFO receives information about the server.
If no LDAP error occurred, you still need to check that *basednp is If no LDAP error occurred, you still need to check that *basednp is
valid. If it is NULL, then the server does not appear to be an valid. If it is NULL, then the server does not appear to be an
OpenPGP Keyserver. In this case, you also do not need to xfree OpenPGP Keyserver. */
*pgpkeyattrp. */
static int static int
my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp, my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
char **basednp, char **pgpkeyattrp, int *real_ldapp) char **basednp, unsigned int *r_serverinfo)
{ {
int err = 0; int err = 0;
LDAP *ldap_conn = NULL; LDAP *ldap_conn = NULL;
char *user = uri->auth; char *user = uri->auth;
struct uri_tuple_s *password_param = uri_query_lookup (uri, "password"); struct uri_tuple_s *password_param;
char *password = password_param ? password_param->value : NULL; char *password;
char *basedn = NULL; char *basedn = NULL;
/* Whether to look for the pgpKey or pgpKeyv2 attribute. */
char *pgpkeyattr = "pgpKey";
int real_ldap = 0;
log_debug ("my_ldap_connect(%s:%d/%s????%s%s%s%s%s)\n", *r_serverinfo = 0;
uri->host, uri->port,
uri->path ?: "", password_param = uri_query_lookup (uri, "password");
uri->auth ? "bindname=" : "", uri->auth ?: "", password = password_param ? password_param->value : NULL;
uri->auth && password ? "," : "",
password ? "password=" : "", password ?: ""); if (opt.debug)
log_debug ("my_ldap_connect(%s:%d/%s????%s%s%s%s%s)\n",
uri->host, uri->port,
uri->path ?: "",
uri->auth ? "bindname=" : "", uri->auth ?: "",
uri->auth && password ? "," : "",
password ? "password=" : "",
password ? ">not shown<": "");
/* If the uri specifies a secure connection and we don't support /* If the uri specifies a secure connection and we don't support
TLS, then fail; don't silently revert to an insecure TLS, then fail; don't silently revert to an insecure
@ -490,7 +501,7 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
err = ldap_set_option (ldap_conn, LDAP_OPT_PROTOCOL_VERSION, &ver); err = ldap_set_option (ldap_conn, LDAP_OPT_PROTOCOL_VERSION, &ver);
if (err != LDAP_SUCCESS) if (err != LDAP_SUCCESS)
{ {
log_error ("gpgkeys: unable to go to LDAP 3: %s\n", log_error ("ks-ldap: unable to go to LDAP 3: %s\n",
ldap_err2string (err)); ldap_err2string (err));
goto out; goto out;
} }
@ -553,8 +564,9 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
/* By default we don't bind as there is usually no need to. */ /* By default we don't bind as there is usually no need to. */
if (uri->auth) if (uri->auth)
{ {
log_debug ("LDAP bind to %s, password %s\n", if (opt.debug)
user, password ? ">not shown<" : ">none<"); log_debug ("LDAP bind to %s, password %s\n",
user, password ? ">not shown<" : ">none<");
err = ldap_simple_bind_s (ldap_conn, user, password); err = ldap_simple_bind_s (ldap_conn, user, password);
if (err != LDAP_SUCCESS) if (err != LDAP_SUCCESS)
@ -566,18 +578,17 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
} }
if (uri->path && *uri->path) if (uri->path && *uri->path)
/* User specified base DN. */
{ {
/* User specified base DN. */
basedn = xstrdup (uri->path); basedn = xstrdup (uri->path);
/* If the user specifies a base DN, then we know the server is a /* If the user specifies a base DN, then we know the server is a
real LDAP server. */ * real LDAP server. */
real_ldap = 1; *r_serverinfo |= SERVERINFO_REALLDAP;
} }
else else
{ { /* Look for namingContexts. */
LDAPMessage *res = NULL; LDAPMessage *res = NULL;
/* Look for namingContexts. */
char *attr[] = { "namingContexts", NULL }; char *attr[] = { "namingContexts", NULL };
err = ldap_search_s (ldap_conn, "", LDAP_SCOPE_BASE, err = ldap_search_s (ldap_conn, "", LDAP_SCOPE_BASE,
@ -586,21 +597,22 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
{ {
char **context = ldap_get_values (ldap_conn, res, "namingContexts"); char **context = ldap_get_values (ldap_conn, res, "namingContexts");
if (context) if (context)
/* We found some, so try each namingContext as the search
base and look for pgpBaseKeySpaceDN. Because we found
this, we know we're talking to a regular-ish LDAP
server and not an LDAP keyserver. */
{ {
/* We found some, so try each namingContext as the
* search base and look for pgpBaseKeySpaceDN. Because
* we found this, we know we're talking to a regular-ish
* LDAP server and not an LDAP keyserver. */
int i; int i;
char *attr2[] = char *attr2[] =
{ "pgpBaseKeySpaceDN", "pgpVersion", "pgpSoftware", NULL }; { "pgpBaseKeySpaceDN", "pgpVersion", "pgpSoftware", NULL };
real_ldap = 1; *r_serverinfo |= SERVERINFO_REALLDAP;
for (i = 0; context[i] && ! basedn; i++) for (i = 0; context[i] && ! basedn; i++)
{ {
char **vals; char **vals;
LDAPMessage *si_res; LDAPMessage *si_res;
int is_gnupg = 0;
{ {
char *object = xasprintf ("cn=pgpServerInfo,%s", char *object = xasprintf ("cn=pgpServerInfo,%s",
@ -624,7 +636,10 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
"pgpSoftware"); "pgpSoftware");
if (vals) if (vals)
{ {
log_debug ("Server: \t%s\n", vals[0]); if (opt.debug)
log_debug ("Server: \t%s\n", vals[0]);
if (!ascii_strcasecmp (vals[0], "GnuPG"))
is_gnupg = 1;
ldap_value_free (vals); ldap_value_free (vals);
} }
@ -632,7 +647,20 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
"pgpVersion"); "pgpVersion");
if (vals) if (vals)
{ {
log_debug ("Version:\t%s\n", vals[0]); if (opt.debug)
log_debug ("Version:\t%s\n", vals[0]);
if (is_gnupg)
{
const char *fields[2];
int nfields;
nfields = split_fields (vals[0],
fields, DIM(fields));
if (nfields > 0 && atoi(fields[0]) > 1)
*r_serverinfo |= SERVERINFO_SCHEMAV2;
if (nfields > 1
&& !ascii_strcasecmp (fields[1], "ntds"))
*r_serverinfo |= SERVERINFO_NTDS;
}
ldap_value_free (vals); ldap_value_free (vals);
} }
} }
@ -650,7 +678,7 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
else else
{ {
/* We don't have an answer yet, which means the server might /* We don't have an answer yet, which means the server might
be an LDAP keyserver. */ be a PGP.com keyserver. */
char **vals; char **vals;
LDAPMessage *si_res = NULL; LDAPMessage *si_res = NULL;
@ -660,9 +688,9 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
"(objectClass=*)", attr2, 0, &si_res); "(objectClass=*)", attr2, 0, &si_res);
if (err == LDAP_SUCCESS) if (err == LDAP_SUCCESS)
{ {
/* For the LDAP keyserver, this is always /* For the PGP LDAP keyserver, this is always
"OU=ACTIVE,O=PGP KEYSPACE,C=US", but it might not be * "OU=ACTIVE,O=PGP KEYSPACE,C=US", but it might not be
in the future. */ * in the future. */
vals = ldap_get_values (ldap_conn, si_res, "baseKeySpaceDN"); vals = ldap_get_values (ldap_conn, si_res, "baseKeySpaceDN");
if (vals) if (vals)
@ -674,14 +702,16 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
vals = ldap_get_values (ldap_conn, si_res, "software"); vals = ldap_get_values (ldap_conn, si_res, "software");
if (vals) if (vals)
{ {
log_debug ("ldap: Server: \t%s\n", vals[0]); if (opt.debug)
log_debug ("ks-ldap: PGP Server: \t%s\n", vals[0]);
ldap_value_free (vals); ldap_value_free (vals);
} }
vals = ldap_get_values (ldap_conn, si_res, "version"); vals = ldap_get_values (ldap_conn, si_res, "version");
if (vals) if (vals)
{ {
log_debug ("ldap: Version:\t%s\n", vals[0]); if (opt.debug)
log_debug ("ks-ldap: PGP Server Version:\t%s\n", vals[0]);
/* If the version is high enough, use the new /* If the version is high enough, use the new
pgpKeyV2 attribute. This design is iffy at best, pgpKeyV2 attribute. This design is iffy at best,
@ -690,7 +720,7 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
keyserver vendor with a different numbering keyserver vendor with a different numbering
scheme. */ scheme. */
if (atoi (vals[0]) > 1) if (atoi (vals[0]) > 1)
pgpkeyattr = "pgpKeyV2"; *r_serverinfo |= SERVERINFO_PGPKEYV2;
ldap_value_free (vals); ldap_value_free (vals);
} }
@ -706,29 +736,20 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
} }
out: out:
if (! err) if (!err && opt.debug)
{ {
log_debug ("ldap_conn: %p\n", ldap_conn); log_debug ("ldap_conn: %p\n", ldap_conn);
log_debug ("real_ldap: %d\n", real_ldap); log_debug ("server_type: %s\n", ((*r_serverinfo & SERVERINFO_REALLDAP)
? "LDAP" : "PGP.com keyserver") );
log_debug ("basedn: %s\n", basedn); log_debug ("basedn: %s\n", basedn);
log_debug ("pgpkeyattr: %s\n", pgpkeyattr); log_debug ("pgpkeyattr: %s\n",
(*r_serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey");
} }
if (! err && real_ldapp)
*real_ldapp = real_ldap;
if (err) if (err)
xfree (basedn); xfree (basedn);
else else
{ {
if (pgpkeyattrp)
{
if (basedn)
*pgpkeyattrp = xstrdup (pgpkeyattr);
else
*pgpkeyattrp = NULL;
}
if (basednp) if (basednp)
*basednp = basedn; *basednp = basedn;
else else
@ -834,16 +855,11 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
{ {
gpg_error_t err = 0; gpg_error_t err = 0;
int ldap_err; int ldap_err;
unsigned int serverinfo;
char *filter = NULL; char *filter = NULL;
LDAP *ldap_conn = NULL; LDAP *ldap_conn = NULL;
char *basedn = NULL; char *basedn = NULL;
char *pgpkeyattr = NULL;
estream_t fp = NULL; estream_t fp = NULL;
LDAPMessage *message = NULL; LDAPMessage *message = NULL;
(void) ctrl; (void) ctrl;
@ -863,7 +879,7 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
return (err); return (err);
/* Make sure we are talking to an OpenPGP LDAP server. */ /* Make sure we are talking to an OpenPGP LDAP server. */
ldap_err = my_ldap_connect (uri, &ldap_conn, &basedn, &pgpkeyattr, NULL); ldap_err = my_ldap_connect (uri, &ldap_conn, &basedn, &serverinfo);
if (ldap_err || !basedn) if (ldap_err || !basedn)
{ {
if (ldap_err) if (ldap_err)
@ -879,24 +895,26 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
may be discarded we aren't in verbose mode. */ may be discarded we aren't in verbose mode. */
char *attrs[] = char *attrs[] =
{ {
pgpkeyattr, "dummy",
"pgpcertid", "pgpuserid", "pgpkeyid", "pgprevoked", "pgpdisabled", "pgpcertid", "pgpuserid", "pgpkeyid", "pgprevoked", "pgpdisabled",
"pgpkeycreatetime", "modifytimestamp", "pgpkeysize", "pgpkeytype", "pgpkeycreatetime", "modifytimestamp", "pgpkeysize", "pgpkeytype",
NULL NULL
}; };
/* 1 if we want just attribute types; 0 if we want both attribute /* 1 if we want just attribute types; 0 if we want both attribute
types and values. */ * types and values. */
int attrsonly = 0; int attrsonly = 0;
int count; int count;
/* Replace "dummy". */
attrs[0] = (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2" : "pgpKey";
ldap_err = ldap_search_s (ldap_conn, basedn, LDAP_SCOPE_SUBTREE, ldap_err = ldap_search_s (ldap_conn, basedn, LDAP_SCOPE_SUBTREE,
filter, attrs, attrsonly, &message); filter, attrs, attrsonly, &message);
if (ldap_err) if (ldap_err)
{ {
err = ldap_err_to_gpg_err (ldap_err); err = ldap_err_to_gpg_err (ldap_err);
log_error ("gpgkeys: LDAP search error: %s\n", log_error ("ks-ldap: LDAP search error: %s\n",
ldap_err2string (ldap_err)); ldap_err2string (ldap_err));
goto out; goto out;
} }
@ -904,7 +922,7 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
count = ldap_count_entries (ldap_conn, message); count = ldap_count_entries (ldap_conn, message);
if (count < 1) if (count < 1)
{ {
log_error ("gpgkeys: key %s not found on keyserver\n", keyspec); log_info ("ks-ldap: key %s not found on keyserver\n", keyspec);
if (count == -1) if (count == -1)
err = ldap_to_gpg_err (ldap_conn); err = ldap_to_gpg_err (ldap_conn);
@ -954,11 +972,11 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
extract_keys (fp, ldap_conn, certid[0], each); extract_keys (fp, ldap_conn, certid[0], each);
vals = ldap_get_values (ldap_conn, each, pgpkeyattr); vals = ldap_get_values (ldap_conn, each, attrs[0]);
if (! vals) if (! vals)
{ {
err = ldap_to_gpg_err (ldap_conn); err = ldap_to_gpg_err (ldap_conn);
log_error("gpgkeys: unable to retrieve key %s " log_error("ks-ldap: unable to retrieve key %s "
"from keyserver\n", certid[0]); "from keyserver\n", certid[0]);
goto out; goto out;
} }
@ -1001,7 +1019,6 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
*r_fp = fp; *r_fp = fp;
} }
xfree (pgpkeyattr);
xfree (basedn); xfree (basedn);
if (ldap_conn) if (ldap_conn)
@ -1012,6 +1029,7 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
return err; return err;
} }
/* Search the keyserver identified by URI for keys matching PATTERN. /* Search the keyserver identified by URI for keys matching PATTERN.
On success R_FP has an open stream to read the data. */ On success R_FP has an open stream to read the data. */
gpg_error_t gpg_error_t
@ -1020,13 +1038,10 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
{ {
gpg_error_t err; gpg_error_t err;
int ldap_err; int ldap_err;
unsigned int serverinfo;
char *filter = NULL; char *filter = NULL;
LDAP *ldap_conn = NULL; LDAP *ldap_conn = NULL;
char *basedn = NULL; char *basedn = NULL;
estream_t fp = NULL; estream_t fp = NULL;
(void) ctrl; (void) ctrl;
@ -1049,7 +1064,7 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
} }
/* Make sure we are talking to an OpenPGP LDAP server. */ /* Make sure we are talking to an OpenPGP LDAP server. */
ldap_err = my_ldap_connect (uri, &ldap_conn, &basedn, NULL, NULL); ldap_err = my_ldap_connect (uri, &ldap_conn, &basedn, &serverinfo);
if (ldap_err || !basedn) if (ldap_err || !basedn)
{ {
if (ldap_err) if (ldap_err)
@ -1082,7 +1097,8 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
"pgpkeysize", "pgpkeytype", NULL "pgpkeysize", "pgpkeytype", NULL
}; };
log_debug ("SEARCH '%s' => '%s' BEGIN\n", pattern, filter); if (opt.debug)
log_debug ("SEARCH '%s' => '%s' BEGIN\n", pattern, filter);
ldap_err = ldap_search_s (ldap_conn, basedn, ldap_err = ldap_search_s (ldap_conn, basedn,
LDAP_SCOPE_SUBTREE, filter, attrs, 0, &res); LDAP_SCOPE_SUBTREE, filter, attrs, 0, &res);
@ -1095,7 +1111,7 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
err = ldap_err_to_gpg_err (ldap_err); err = ldap_err_to_gpg_err (ldap_err);
log_error ("SEARCH %s FAILED %d\n", pattern, err); log_error ("SEARCH %s FAILED %d\n", pattern, err);
log_error ("gpgkeys: LDAP search error: %s\n", log_error ("ks-ldap: LDAP search error: %s\n",
ldap_err2string (err)); ldap_err2string (err));
goto out; goto out;
} }
@ -1117,10 +1133,10 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
if (ldap_err == LDAP_SIZELIMIT_EXCEEDED) if (ldap_err == LDAP_SIZELIMIT_EXCEEDED)
{ {
if (count == 1) if (count == 1)
log_error ("gpgkeys: search results exceeded server limit." log_error ("ks-ldap: search results exceeded server limit."
" First 1 result shown.\n"); " First 1 result shown.\n");
else else
log_error ("gpgkeys: search results exceeded server limit." log_error ("ks-ldap: search results exceeded server limit."
" First %d results shown.\n", count); " First %d results shown.\n", count);
} }
@ -1272,7 +1288,8 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
free_strlist (dupelist); free_strlist (dupelist);
} }
log_debug ("SEARCH %s END\n", pattern); if (opt.debug)
log_debug ("SEARCH %s END\n", pattern);
out: out:
if (err) if (err)
@ -1865,15 +1882,11 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
{ {
gpg_error_t err = 0; gpg_error_t err = 0;
int ldap_err; int ldap_err;
unsigned int serverinfo;
LDAP *ldap_conn = NULL; LDAP *ldap_conn = NULL;
char *basedn = NULL; char *basedn = NULL;
char *pgpkeyattr = NULL;
int real_ldap;
LDAPMod **modlist = NULL; LDAPMod **modlist = NULL;
LDAPMod **addlist = NULL; LDAPMod **addlist = NULL;
char *data_armored = NULL; char *data_armored = NULL;
/* The last byte of the info block. */ /* The last byte of the info block. */
@ -1898,8 +1911,7 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
return gpg_error (GPG_ERR_NOT_SUPPORTED); return gpg_error (GPG_ERR_NOT_SUPPORTED);
} }
ldap_err = my_ldap_connect (uri, ldap_err = my_ldap_connect (uri, &ldap_conn, &basedn, &serverinfo);
&ldap_conn, &basedn, &pgpkeyattr, &real_ldap);
if (ldap_err || !basedn) if (ldap_err || !basedn)
{ {
if (ldap_err) if (ldap_err)
@ -1909,22 +1921,31 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
goto out; goto out;
} }
if (! real_ldap) if (!(serverinfo & SERVERINFO_REALLDAP))
/* We appear to have an OpenPGP Keyserver, which can unpack the key
on its own (not just a dumb LDAP server). */
{ {
LDAPMod mod, *attrs[2]; /* We appear to have a PGP.com Keyserver, which can unpack the
char *key[] = { data, NULL }; * key on its own (not just a dump LDAP server). This will
* rarely be the case these days. */
LDAPMod mod;
LDAPMod *attrs[2];
char *key[2];
char *dn; char *dn;
key[0] = data;
key[1] = NULL;
memset (&mod, 0, sizeof (mod)); memset (&mod, 0, sizeof (mod));
mod.mod_op = LDAP_MOD_ADD; mod.mod_op = LDAP_MOD_ADD;
mod.mod_type = pgpkeyattr; mod.mod_type = (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey";
mod.mod_values = key; mod.mod_values = key;
attrs[0] = &mod; attrs[0] = &mod;
attrs[1] = NULL; attrs[1] = NULL;
dn = xasprintf ("pgpCertid=virtual,%s", basedn); dn = xtryasprintf ("pgpCertid=virtual,%s", basedn);
if (!dn)
{
err = gpg_error_from_syserror ();
goto out;
}
ldap_err = ldap_add_s (ldap_conn, dn, attrs); ldap_err = ldap_add_s (ldap_conn, dn, attrs);
xfree (dn); xfree (dn);
@ -1937,7 +1958,12 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
goto out; goto out;
} }
modlist = xmalloc (sizeof (LDAPMod *)); modlist = xtrymalloc (sizeof (LDAPMod *));
if (!modlist)
{
err = gpg_error_from_syserror ();
goto out;
}
*modlist = NULL; *modlist = NULL;
if (dump_modlist) if (dump_modlist)
@ -1996,10 +2022,10 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
/* Sanity check. */ /* Sanity check. */
if (! temp) if (! temp)
assert ((char *) info + infolen - 1 == infoend); log_assert ((char *) info + infolen - 1 == infoend);
else else
{ {
assert (infolen == -1); log_assert (infolen == -1);
xfree (temp); xfree (temp);
} }
} }
@ -2010,7 +2036,9 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
if (err) if (err)
goto out; goto out;
modlist_add (&addlist, pgpkeyattr, data_armored); modlist_add (&addlist,
(serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey",
data_armored);
/* Now append addlist onto modlist. */ /* Now append addlist onto modlist. */
modlists_join (&modlist, addlist); modlists_join (&modlist, addlist);
@ -2037,17 +2065,25 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
char *dn; char *dn;
certid = modlist_lookup (addlist, "pgpCertID"); certid = modlist_lookup (addlist, "pgpCertID");
if (/* We should have a value. */ /* We should have exactly one value. */
! certid if (!certid || !(certid[0] && !certid[1]))
/* Exactly one. */
|| !(certid[0] && !certid[1]))
{ {
log_error ("Bad certid.\n"); log_error ("ks-ldap: bad pgpCertID provided\n");
err = GPG_ERR_GENERAL; err = GPG_ERR_GENERAL;
goto out; goto out;
} }
dn = xasprintf ("pgpCertID=%s,%s", certid[0], basedn); if ((serverinfo & SERVERINFO_NTDS))
dn = xtryasprintf ("CN=%s,%s", certid[0], basedn);
else
dn = xtryasprintf ("pgpCertID=%s,%s", certid[0], basedn);
if (!dn)
{
err = gpg_error_from_syserror ();
goto out;
}
if (opt.debug)
log_debug ("ks-ldap: using DN: %s\n", dn);
err = ldap_modify_s (ldap_conn, dn, modlist); err = ldap_modify_s (ldap_conn, dn, modlist);
if (err == LDAP_NO_SUCH_OBJECT) if (err == LDAP_NO_SUCH_OBJECT)
@ -2057,7 +2093,7 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
if (err != LDAP_SUCCESS) if (err != LDAP_SUCCESS)
{ {
log_error ("gpgkeys: error adding key to keyserver: %s\n", log_error ("ks-ldap: error adding key to keyserver: %s\n",
ldap_err2string (err)); ldap_err2string (err));
err = ldap_err_to_gpg_err (err); err = ldap_err_to_gpg_err (err);
} }
@ -2071,7 +2107,6 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
ldap_unbind (ldap_conn); ldap_unbind (ldap_conn);
xfree (basedn); xfree (basedn);
xfree (pgpkeyattr);
modlist_free (modlist); modlist_free (modlist);
xfree (addlist); xfree (addlist);

View File

@ -0,0 +1,17 @@
# gnupg-ldap-ad-init.ldif -*- conf -*-
#
# Entries connecting the schema specified in gnupg-ldap-ad-schema.ldif.
# Revision: 2020-12-08
dn: cn=GnuPG Keys,dc=w32demo,dc=g10code,dc=de
changetype: add
objectClass: container
cn: GnuPG Keys
dn: cn=PGPServerInfo,dc=w32demo,dc=g10code,dc=de
changetype: add
objectClass: pgpServerInfo
cn: PGPServerInfo
pgpBaseKeySpaceDN: cn=GnuPG Keys,dc=w32demo,dc=g10code,dc=de
pgpSoftware: GnuPG
pgpVersion: 2 ntds

View File

@ -0,0 +1,353 @@
# gnupg-ldap-scheme.ldif -*- conf -*-
#
# Schema for an OpenPGP LDAP keyserver. This is a slighly enhanced
# version of the original LDAP schema used for PGP keyservers as
# installed at quite some sites.
# Revision: 2020-12-08
# Some notes:
# - Backup your AD! It is not possible to revert changes of the schema.
# - Try it first on a test system.
# - To import the new attributes and classes use:
# ldifde -i -vv -f gnupg-ldap-ad-schema.ldif
# -c "DC=EXAMPLEDC" "DC=example,DC=org"
# (the above command is given as one line)
# - The schema does not get its own distingished name as done with OpenLDAP.
# - The first GUID we use is f406e7a5-a5ea-411e-9ddd-2e4e66899800
# and incremented for each attribute.
#
# - Some OIDs, oMSyntax, and original OIDs:
# 2.5.5.1 (127) Object (DS-DN) (1.3.6.1.4.1.1466.115.121.1.12)
# 2.5.5.3 (27) Case-sensitive string
# 2.5.5.9 (2) 32 bit signed integer
# 2.5.5.10 (4) Octet string (1.3.6.1.4.1.1466.115.121.1.26)
# 2.5.5.11 (23) UTC-Time string
# 2.5.5.12 (64) Case-insensitive Unicode string
# 2.5.5.12 (64) Directory String in UTF-8 (1.3.6.1.4.1.1466.115.121.1.15)
# 2.5.5.16 (65) 64 bit signed integer
# The base DN for the PGP key space by querying the
# pgpBaseKeySpaceDN attribute (This is normally
# 'ou=PGP Keys,dc=example,dc=com').
dn: CN=pgpBaseKeySpaceDN,CN=Schema,CN=Configuration,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.8
lDAPDisplayName: pgpBaseKeySpaceDN
description: Points to DN of the object that will store the PGP keys.
attributeSyntax: 2.5.5.1
oMSyntax: 127
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYAA==
# See gnupg-ldap-init.ldif for a description of this attribute
dn: CN=pgpSoftware,CN=Schema,CN=Configuration,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.9
lDAPDisplayName: pgpSoftware
description: 'Origin of the GnuPG keyserver schema'
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYAQ==
# See gnupg-ldap-init.ldif for a description of this attribute
dn: CN=pgpVersion,CN=Schema,CN=Configuration,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.10
lDAPDisplayName: pgpVersion
description: Version of this schema
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYAg==
# The attribute holding the OpenPGP keyblock.
# The legacy PGP LDAP server used pgpKeyV2 instead.
dn: CN=pgpKey,CN=Schema,CN=Configuration,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.11
lDAPDisplayName: pgpKey
description: OpenPGP public key block
attributeSyntax: 2.5.5.10
oMSyntax: 4
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYAw==
# The long key-ID
dn: CN=pgpCertID,CN=Schema,CN=Configuration,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.12
lDAPDisplayName: pgpCertID
description: OpenPGP long key id
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYBA==
# A flag to temporary disable a keyblock
dn: CN=pgpDisabled,CN=Schema,CN=Configuration,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.13
lDAPDisplayName: pgpDisabled
description: pgpDisabled attribute for PGP
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYBQ==
# The short key id. This is actually not required and should thus not
# be used by client software.
dn: CN=pgpKeyID,CN=Schema,CN=Configuration,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.14
lDAPDisplayName: pgpKeyID
description: OpenPGP short key id
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYBg==
# The algorithm of the key. Used to be "RSA" or "DSS/DH".
dn: CN=pgpKeyType,CN=Schema,CN=Configuration,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.15
lDAPDisplayName: pgpKeyType
description: pgpKeyType attribute for PGP
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYBw==
# The User-ID. GnuPG maps its user-ID classes this way:
# exact: (pgpUserID=%s)
# substr: (pgpUserID=*%s*)
# mail: (pgpUserID=*<%s>*)
# mailsub: (pgpUserID=*<*%s*>*)
# mailend: (pgpUserID=*<*%s>*)
dn: CN=pgpUserID,CN=Schema,CN=Configuration,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.16
lDAPDisplayName: pgpUserID
description: User ID(s) associated with the key
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: FALSE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYCA==
# The creation time of the primary key.
# Stored in ISO format: "20201231 120000"
dn: CN=pgpKeyCreateTime,CN=Schema,CN=Configuration,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.17
lDAPDisplayName: pgpKeyCreateTime
description: Primary key creation time
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYCQ==
# SignerIDs are not used
dn: CN=pgpSignerID,CN=Schema,CN=Configuration,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.18
lDAPDisplayName: pgpSignerID
description: pgpSignerID attribute for PGP
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: FALSE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYCg==
# A value of 1 indicates that the keyblock has been revoked
dn: CN=pgpRevoked,CN=Schema,CN=Configuration,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.19
lDAPDisplayName: pgpRevoked
description: pgpRevoked attribute for PGP
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYCw==
# The Subkey key ids
dn: CN=pgpSubKeyID,CN=Schema,CN=Configuration,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.20
lDAPDisplayName: pgpSubKeyID
description: Sub-key ID(s) of the PGP key
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: FALSE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYDA==
# A hint on the keysize.
dn: CN=pgpKeySize,CN=Schema,CN=Configuration,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.21
lDAPDisplayName: pgpKeySize
description: pgpKeySize attribute for PGP
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: FALSE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYDQ==
# Expiration time of the primary key.
# Stored in ISO format: "20201231 120000"
dn: CN=pgpKeyExpireTime,CN=Schema,CN=Configuration,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.22
lDAPDisplayName: pgpKeyExpireTime
description: pgpKeyExpireTime attribute for PGP
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYDg==
# The hex encoded fingerprint of the primary key.
dn: CN=gpgFingerprint,CN=Schema,CN=Configuration,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.11591.2.4.1.1
lDAPDisplayName: gpgFingerprint
description: Fingerprint of the primary key
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYDw==
# A list of hex encoded fingerprints of the subkeys.
dn: CN=gpgSubFingerprint,CN=Schema,CN=Configuration,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.11591.2.4.1.2
lDAPDisplayName: gpgSubFingerprint
description: Fingerprints of the secondary keys
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: FALSE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYEA==
# A list of utf8 encoded addr-spec used instead of mail/rfc822Mailbox
dn: CN=gpgMailbox,CN=Schema,CN=Configuration,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.11591.2.4.1.3
lDAPDisplayName: gpgMailbox
description: The utf8 encoded addr-spec of a mailbox
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: FALSE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYEQ==
# A list of hex encoded long keyids of all subkeys.
dn: CN=gpgSubCertID,CN=Schema,CN=Configuration,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.11591.2.4.1.4
lDAPDisplayName: gpgSubCertID
description: OpenPGP long subkey id
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: FALSE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYEg==
# Unused GUIDs:
# 9AbnpaXqQR6d3S5OZomYEw==
# 9AbnpaXqQR6d3S5OZomYFA==
# 9AbnpaXqQR6d3S5OZomYFQ==
# 9AbnpaXqQR6d3S5OZomYFg==
# 9AbnpaXqQR6d3S5OZomYFw==
# 9AbnpaXqQR6d3S5OZomYGA==
# 9AbnpaXqQR6d3S5OZomYGQ==
# 9AbnpaXqQR6d3S5OZomYGg==
# 9AbnpaXqQR6d3S5OZomYGw==
# 9AbnpaXqQR6d3S5OZomYHA==
# 9AbnpaXqQR6d3S5OZomYHQ==
# 9AbnpaXqQR6d3S5OZomYHg==
# 9AbnpaXqQR6d3S5OZomYHw==
# Sync the schema cache
DN:
changetype: modify
add: schemaUpdateNow
schemaUpdateNow: 1
-
#
# Used by regular LDAP servers to indicate pgp support.
# (structural class)
#
dn: CN=pgpServerInfo,CN=Schema,CN=Configuration,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: classSchema
governsID: 1.3.6.1.4.1.3401.8.2.23
lDAPDisplayName: pgpServerInfo
description: An OpenPGP public keyblock store
subClassOf: top
objectClassCategory: 1
mustContain: cn
mustContain: pgpBaseKeySpaceDN
mayContain: pgpSoftware
mayContain: pgpVersion
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYIA==
# The original PGP key object extended with a few extra attributes.
# All new software should set them but this is not enforced for
# backward compatibility of client software.
# (structural class, writable)
dn: CN=pgpKeyInfo,CN=Schema,CN=Configuration,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: classSchema
governsID: 1.3.6.1.4.1.3401.8.2.24
lDAPDisplayName: pgpKeyInfo
description: An OpenPGP public keyblock
subClassOf: top
objectClassCategory: 1
instanceType: 4
mustContain: pgpCertID
mustContain: pgpKey
mayContain: pgpDisabled
mayContain: pgpKeyID
mayContain: pgpKeyType
mayContain: pgpUserID
mayContain: pgpKeyCreateTime
mayContain: pgpSignerID
mayContain: pgpRevoked
mayContain: pgpSubKeyID
mayContain: pgpKeySize
mayContain: pgpKeyExpireTime
mayContain: gpgFingerprint
mayContain: gpgSubFingerprint
mayContain: gpgSubCertID
mayContain: gpgMailbox
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYIQ==
# Sync the schema cache
DN:
changetype: modify
add: schemaUpdateNow
schemaUpdateNow: 1
-
#
# end-of-file
#

View File

@ -12,10 +12,18 @@ pgpBaseKeySpaceDN: ou=GnuPG Keys,dc=example,dc=com
# Using the value GnuPG here indicates that pgpVersion below has a # Using the value GnuPG here indicates that pgpVersion below has a
# well-defined meaning. # well-defined meaning.
pgpSoftware: GnuPG pgpSoftware: GnuPG
# Currently used values: # pgpVersion is a string with space delimited items:
# 1 :: Classic PGP schema #
# 2 :: The attributes gpgFingerprint, gpgSubFingerprint, # Item 1 - Implemented schema version. This is an integer with one
# gpgSubCertID, and gpgMailbox are part of the schema. # of these values:
# 1 = Classic PGP schema (default)
# 2 = The attributes gpgFingerprint, gpgSubFingerprint,
# gpgSubCertID, and gpgMailbox are part of the schema.
# Item 2 - A string with the used LDAP server
# "-" = Unknown (default)
# "ntds" = Windows Directory Service (AD DS)
# "openldap" = OpenLDAP
#
pgpVersion: 2 pgpVersion: 2
dn: ou=GnuPG Keys,dc=example,dc=com dn: ou=GnuPG Keys,dc=example,dc=com