New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PKINIT certificate authorization pluggable interface #610
Conversation
|
I will have more later, but from a quick look:
|
To provide a little more guidance here: certauth_modules should not be a global variable; it should be a linked from the PKINIT kdcpreauth moddata, and should be cleaned up when that moddata is finalized. The PKINIT kdcpreauth moddata is currently an array of pkinit_kdc_context objects, one per realm. So there are two options:
Option 1 may seem inefficient, but the overwhelmingly common case is for there only to be one realm served by a KDC, and even when there are multiple realms, they are relatively heavyweight objects. Option 2 means that any per-realm data (currently represented in "opts") needs to be passed to the authorize method rather than a moddata initialization method. That might be more convenient for modules anyway. |
Would it be preferable to add a "free_indicator" method for this, or provide a buffer allocated by the PKINIT module?
That's fair. I don't think that we need to go too far into proper decoding and picking attributes out of the cert in the test module anyways, so checking the DER directly for the subject seems sufficient.
This sounds like the better option to me as well. |
Probably the former. Another option is for the module to just call the add_auth_indicator callback in the kdcpreauth callbacks structure. There is some potential for bad behavior there if one module adds an authentication indicator and another module says no. At that point it's unlikely that the KDC would issue a ticket because PKINIT will fail, so I'm not sure it's worth worrying about. On an unrelated note, I was looking at the PR's server_authorize_cert() and it doesn't seem like it provides a way for a module to say "pass" as described in the wiki page. |
If failure happens with any of the modules returning a FALSE status, is there a reason to make a distinction between a TRUE status and "pass"? I understood "pass" as meaning the module did not perform any authorization checks, for which it can just return TRUE. |
The desired accumulator behavior (which we use for several existing authorization pluggable interfaces) is that at least one module must say yes, and zero modules must say no. If a module can't give distinct "yes" and "pass" results, then there is no way to know whether any of the modules had a positive authorization result, or if they all just had nothing to say about the cert. |
69a98d2
to
58ed85f
Compare
|
Updated with a round of changes:
|
58ed85f
to
0cc3136
Compare
|
Just fixed a typo and made some alignment adjustments in plugins/certauth/test/main.c. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I expect to have more review comments, but here are some for now.
src/plugins/certauth/test/main.c
Outdated
| c[sizeof(cntag)] = strlen(name); | ||
| memcpy(c + sizeof(cntag) + 1, name, strlen(name)); | ||
|
|
||
| cc = memmem(cert.data, cert.length, c, sizeof(cntag) + strlen(name) + 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
memmem() is a Gnu libc extension. I'm not sure what you can use instead; I will think about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you'll want to use memchr() and memcmp() instead. There will be a fiddly length check involved, but it shouldn't be a lot of code.
src/plugins/certauth/test/main.c
Outdated
| } | ||
|
|
||
| static krb5_boolean | ||
| has_cn(krb5_context context, krb5_data cert, char *name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const char *name here, probably.
src/plugins/certauth/test/main.c
Outdated
|
|
||
| /* Construct a DER search string of CN tag + length tag + name to | ||
| * check for princ as CN. */ | ||
| c = malloc(sizeof(cntag) + strlen(name) + 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using k5buf here might not be any less code, but it is probably less error-prone. asprintf() is also an option but tends to be awkward for composing binary strings.
| return ret; | ||
| } | ||
|
|
||
| krb5_error_code |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function can be static (and its declaration removed, if the new code is ordered to allow that).
| return ret; | ||
| } | ||
|
|
||
| krb5_error_code |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function can be static (and its declaration removed).
| } | ||
|
|
||
| static krb5_error_code | ||
| certauth_authorize(krb5_context context, krb5_certauth_moddata moddata, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function name seems off; it's for the pkinitsrv certauth module but its name suggests it's associated with the certauth interface itself.
| * Do certificate auth based on match expressions in the pkinit_cert_match | ||
| * attribute string. An expression should be in the same form as those used for | ||
| * the pkinit_cert_match configuration option. Multiple expressions are | ||
| * separated by ':'. The certificate is authorized if all expressions are |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need support for multiple expressions? An expression can already contain multiple rules whose results can either be and-ed or or-ed together. If we have a use case for an and-of-ors, that functionality is probably best moved into pkinit_matching.c.
| ret = KRB5_PLUGIN_NO_HANDLE; | ||
| goto out; | ||
| } | ||
| matchcpy = k5memdup0(match, strlen(match), &ret); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly moot, but this is more clearly written using strdup(), even though that doesn't set ret. (I've considered adding a k5strdup helper for that reason, but haven't done so yet.)
src/include/krb5/certauth_plugin.h
Outdated
| * - KRB5_PLUGIN_NO_HANDLE - The module answers "passed" and had nothing to say | ||
| * about the cert. | ||
| * | ||
| * opts is used by the builtin kdc modules. db_entry receives the client |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need a little more verbiage here. "opts is used by built-in modules and should be ignored by other modules."
src/include/krb5/certauth_plugin.h
Outdated
| /* Abstract module data type. */ | ||
| typedef struct krb5_certauth_moddata_st *krb5_certauth_moddata; | ||
|
|
||
| /* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Every pluggable interface method description should say whether it is optional or mandatory to implement. See other interface headers for examples.
0cc3136
to
ddadd91
Compare
Since the pkinit_cert_match option allowed multiple expressions I just tried to keep the same behavior for the string attribute in some way, so there is no explicit requirement. Does the client certificate matching require multiple statements in a way that would not fit for the server's checking purposes? I'm OK with changing it to just use one expression if multiple are unnecessary. |
src/plugins/certauth/test/main.c
Outdated
| c_len = cert.length; | ||
|
|
||
| /* Check for the CN needle in the certificate haystack. */ | ||
| c = memchr(cert.data, b_start, c_len); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens in the loop below if memchr() finds b_start at the last character of the cert?
|
I think pkinit_cert_matching() only supports multiple rule sets because the profile library naturally produces multiple results. Since you can have several components in a rule set, I would say that we should start with support for just a single rule set in the string attribute. |
ddadd91
to
4a3cf4c
Compare
|
|
More partial feedback (sorry): we will need documentation in pkinit.rst describing how to use pkinit_cert_match (it can reference pkinit_cert_match in krb5_conf.rst but should give a simple example using a subject match). The pkinit_cert_match string attribute should also be briefly described in kadmin_local.rst under set_string. |
|
While working on the doc text I thought it might make sense for the dbmatch module to have an option for a default matching expression that it could apply to all principals in a realm. This way when using the dbmatch module solo as enable_only, you would not need to apply a match expression attribute to each principal. Thoughts on this? |
|
Wouldn't you need some way to substitute the principal name into a matching pattern, for that to be useful? That starts to get complicated. On a related note, I think the dbmatch module, as well as its documentation and tests, should be moved into a second commit. That can happen after we've finished review on the code changes to simplify development. |
Without that, it could be useful for some broader cases, like say if you wanted the only cert restriction to be that the subject mentions a certain realm. I figure that using dbmatch alone would be used as a common way around the standard policy imposed by the pkinitsrv module, and those doing so may see this as a problem.
That's fine with me. |
4a3cf4c
to
d88cca3
Compare
|
Updated with added doc text. |
If you were to set a default match expression of, say, ".*@myrealm", then the dbmatch module would say "yes" for any cert with a subject containing MYREALM, even if the user in the cert bears no relationship to the user in the request, wouldn't it? That doesn't seem like a useful policy. I could see it being useful to be able to supply a default expression which is necessary to match but not sufficient, but then you would still need some criteria to judge a certificate as sufficient. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More partial feedback since I ran out of time.
doc/admin/pkinit.rst
Outdated
| **pkinit_cert_match** can be specified by the **pkinit_cert_match** | ||
| attribute:: | ||
|
|
||
| kadmin -q 'set_string user@REALM pkinit_cert_match "<SUBJECT>.*user@REALM.*"' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure why one would want to put wildcards in this regexp. And actually, I think the regexp match isn't anchored (can you confirm whether or not that's the case?) so I think you would want <SUBJECT>^user@REALM$ .
| @@ -661,6 +661,13 @@ KDC: | |||
| *principal*. The *value* is a JSON string representing an array | |||
| of objects, each having optional ``type`` and ``username`` fields. | |||
|
|
|||
| **pkint_cert_match** | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo here (should be pkinit, not pkint).
doc/admin/pkinit.rst
Outdated
| contain "user@REALM" in the subject in order to authenticate with | ||
| PKINIT. By default, if **pkinit_cert_match** is set on a principal | ||
| the KDC performs the match in addition to checking for the attributes | ||
| outlined in the above PKINIT client certificate configuration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It isn't clear from this documentation what certificate criteria are necessary and sufficient when a pkinit_cert_match attribute is present.
src/include/krb5/certauth_plugin.h
Outdated
| * after the certificate signature has been verified. A module can be | ||
| * implemented in the same shared object as a kdb module and define an | ||
| * authorization policy on top of the builtin module, or override the builtin | ||
| * module with enable_only. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would strike the last sentence in this comment.
src/include/krb5/certauth_plugin.h
Outdated
| /* | ||
| * Mandatory: | ||
| * Return 0 if the DER encoded cert is authorized for PKINIT | ||
| * authentication by princ, otherwise return one of the following error codes: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use a semicolon here; it's a run-on setence with a comma.
| pkinit_plg_crypto_context plgctx, | ||
| pkinit_req_crypto_context reqctx, | ||
| const char *match_rule, | ||
| krb5_boolean *matched); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of blank lines in these prototypes is inconsistent.
| * defined by modules. | ||
| */ | ||
| static krb5_error_code | ||
| server_authorize_cert(krb5_context context, certauth_handle *certauth_modules, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need the server_ prefix on this name? We're in pkinit_srv.c and this is a static function.
| static krb5_error_code | ||
| pkinitsrv_authorize(krb5_context context, krb5_certauth_moddata moddata, | ||
| krb5_data cert, krb5_principal princ, void *opts, | ||
| void *db_entry, char ***authinds_out) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we're feeding the right things into the accumulator here, with the result that administrators will have to use module configuration to disable the pkinitsrv module when they shouldn't have to.
If the EKU is not found, I think we should say no (KRB5KDC_ERR_INCONSISTENT_KEY_PURPOSE). An admin can disable EKU checking in configuration if desired.
If the SAN is not found, I think we should pass. The certificate may still match the client principal according to another module's criteria, and there is no way to disable SAN checking in configuration.
| } | ||
|
|
||
| if (accepted) | ||
| ret = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And otherwise we return KRB5_PLUGIN_NO_HANDLE? I don't think that's meaningful to the caller. I think we should return KRB5KDC_ERR_CLIENT_NAME_MISMATCH, in that no module has asserted a match between the certificate and the client principal name.
| goto out; | ||
|
|
||
| if (!matched) | ||
| ret = KRB5KDC_ERR_CERTIFICATE_MISMATCH; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if this should be a "no", but leave it alone for now; there's a reasonable argument that if the string attribute is present, it should be necessary as well as sufficient.
d88cca3
to
ff0e1db
Compare
You might be confusing it with <SAN> which would make sense with an anchored match. For that example I was thinking of a cert subject that also contains OU, CN=, etc., the tests also run this same <SUBJECT> regexp.
I revised this part a bit. If it’s still not clear some suggestions would be welcome.
Actually no; the offset-by-one was wrong between the c_left assignment and the in-loop memchr(), so my update corrects this (although I don't think we would see this with our test certs).
In order for the SAN check to be able to return pass independently from the EKU check, I split them into separate pkinitsrv modules, and made verify_client_san() return ENOENT when no SAN exists. |
If I am right that the code does an unanchored regexp match, then the leading and trailing More importantly, simply looking for the substring |
434cbb9
to
0b686fe
Compare
I see, so I've updated the doc text and the test regexps to what should be more sensible examples. |
I suggest the following (this is not necessarily wrapped correctly for pkinit.rst): |
src/include/krb5/certauth_plugin.h
Outdated
| */ | ||
| typedef krb5_error_code | ||
| (*krb5_certauth_authorize_fn)(krb5_context context, | ||
| krb5_certauth_moddata moddata, krb5_data cert, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't like passing structure types as function parameters or return values (as stated in http://k5wiki.kerberos.org/wiki/Coding_style/Practices). I recommend using separate "const uint8_t *cert" and "size_t len" parameters; I think that will be easier for most consumers to deal with than a krb5_data object anyway.
(static inline functions such as those in k5-int.h are an exception to this rule.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A little more partial feedback; I will have more later.
src/plugins/certauth/test/main.c
Outdated
| k5_buf_init_dynamic(&buf); | ||
| k5_buf_add_len(&buf, cntag, sizeof(cntag)); | ||
| name_len = strlen(name); | ||
| k5_buf_add_len(&buf, &name_len, 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line seems to assume a little-endian platform. name_len should be of type uint8_t. You should also add "assert(strlen(name) < 128);".
src/plugins/certauth/test/main.c
Outdated
| { | ||
| krb5_error_code ret; | ||
| krb5_boolean match = FALSE; | ||
| uint8_t cntag[6] = "\x06\x03\x55\x04\x03\x0c"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be a little clearer to a reader versed in DER to trim the last byte here and add it separately (with k5_buf_add(&buf, "\x0C");) before adding the length byte. That way the five bytes of cntag are the encoding of the type, while 0C <len> <len bytes> is the encoding of the value.
(As an aside, we usually use uppercase hex constants, or at least I do, because it's easier to visually distinguish the lowercase x from the uppercase hex digits.)
src/include/krb5/certauth_plugin.h
Outdated
| (*krb5_certauth_authorize_fn)(krb5_context context, | ||
| krb5_certauth_moddata moddata, krb5_data cert, | ||
| krb5_principal princ, void *opts, | ||
| krb5_db_entry *db_entry, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
db_entry should probably be of type const krb5_db_entry *. princ should also be of type krb5_const_principal, and opts should be of type const void * (which means that in the built-in consumers, it should be cast to a const pointer to the options structure).
src/plugins/certauth/test/main.c
Outdated
|
|
||
| if (authinds == NULL) | ||
| return; | ||
| for (i = 0; authinds[i] != NULL; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No braces here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still wrapping my head around the pkinit_srv.c changes, so this is more partial feedback. I do have one question: I get why it's not trivial to extract the DER-encoded cert from the crypto context because it was contained within a CMS ContentInfo object that we decoded in one step. That accounts for crypto_encode_der_cert(). But if the cert we're authorizing is in received_cert, why decode it again in each built-in callback? Why not just use received_cert as is?
| attributes required for the client certificate used by the | ||
| principal during PKINIT authentication. The matching expression | ||
| is in the same format as those used by the **pkinit_cert_match** | ||
| option in :ref:`kdc.conf(5)`. (New in release 1.16.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
krb5.conf, not kdc.conf
doc/plugindev/certauth.rst
Outdated
| A certauth module implements the **authorize** method to determine if | ||
| a client's certificate is authorized to authenticate a client | ||
| principal. **authorize** receives the DER encoded certificate, client | ||
| request principal, and an opaque pointer to the client's krb5_db_entry |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The DB entry pointer isn't opaque.
doc/plugindev/certauth.rst
Outdated
| from **authorize**, it should also implement the **free_ind** method | ||
| to free the list. | ||
|
|
||
| Two builtin modules are provided with the certauth interface. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Three now.
src/plugins/certauth/test/main.c
Outdated
| /* Create an indicator list with the module name and CN. */ | ||
| assert(ais = calloc(3, sizeof(*ais))); | ||
| assert(ai1 = strdup("test2")); | ||
| assert(ai2 = strdup(name)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't put expressions with side-effects inside assert(), as asserts can be compiled out. Separately assert(ais != NULL); etc. after each allocation.
You can also simplify this code by getting rid of ai1 and ai2 and just assigning to ais[0] and ais[1]. We don't really need the logic for freeing ais on failure because we're just going to abort on allocation failure anyway.
0b686fe
to
5b78523
Compare
|
Updated:
Good point, thinking about it now this was only due to my initial idea of how the interface was to be designed (and that initially I used the pkinit objects during testing). Now that it has changed, I see the decoding part is unnecessary. I've removed the decoding step from the builtins and assuming we do not care to export any OpenSSL decoding for use by custom modules, I removed the decoding functions. |
What I see now is a breakdown into five commits, but no separation of dbmatch from the certauth interface. The second commit contains support functions used only by dbmatch as well as one used by the other certauth modules; the third commit contains the dbmatch module along with the certauth interface consuming code; the fourth commit contains dbmatch tests along with certauth tests; the fifth commit contains dbmatch documentation mixed in with certauth documentation. What I would like to see is a first commit containing the certauth interface, with its tests and its documentation, as if we had never conceived of dbmatch as a clever piece of added functionality, and then a second commit containing the dbmatch feature, with its tests and its documentation. |
5b78523
to
e1b0843
Compare
|
Updated with revised commits. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feedback should be approaching comprehensive. I still need to delve into the details of the new matching functions.
-
The style checker finds a number of problems with both commits. Some of them are slightly overlong lines; others arise from pkinit_crypto.h still using tabs (I'm not sure why that file was never converted to use spaces). Please fix all of the issues it finds.
-
I'd like the module names pkinitsrv_san and pkinitsrv_eku renamed to pkinit_san and pkinit_eku. "pkinitsrv_san" seems like it refers to part of our implementation, while "pkinit_san" naturally suggests checking for the standard PKINIT SAN.
-
crypto_encode_der_cert() should output a uint8_t * and a size_t, since that is what we now desire in its caller.
| if (hd->vt.authorize == NULL) | ||
| continue; | ||
|
|
||
| ret = hd->vt.authorize(context, hd->moddata, (const uint8_t *)cert.data, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likely moot due to other changes, but it's only necessary to cast to (uint8_t *) here.
| goto cleanup; | ||
| } | ||
|
|
||
| if (hd->vt.free_ind != NULL) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is documented as mandatory if indicators are returned; providing a fallback doesn't seem like a good idea.
| size_t i; | ||
| if (ai_total == NULL) | ||
| return; | ||
| for (i = 0; ai_total[i] != NULL; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No braces here.
| static void | ||
| free_ai_list(char **ai_total) | ||
| { | ||
| size_t i; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a blank line after declarations.
| @@ -51,6 +79,47 @@ pkinit_find_realm_context(krb5_context context, | |||
| krb5_kdcpreauth_moddata moddata, | |||
| krb5_principal princ); | |||
|
|
|||
| static void | |||
| free_ai_list(char **ai_total) | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"ai_total" seems like a relic from when this was part of another piece of code. "list" would be more natural.
| goto out; | ||
|
|
||
| if (!valid_san) { | ||
| pkiDebug("%s: did not find an acceptable SAN in user " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This string constant now fits on one line. (Also applies to EKU module.)
| ret = KRB5KDC_ERR_CLIENT_NAME_MISMATCH; | ||
| } | ||
|
|
||
| out: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there's nothing to put in the cleanup handler, don't use a cleanup label; just have earlier return points. (Also applies to EKU module.)
| vt->authorize = pkinitsrv_san_authorize; | ||
| vt->init = NULL; | ||
| vt->fini = NULL; | ||
| vt->free_ind = NULL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not our practice to explicitly set methods to NULL in initvt functions; not having to do so is a nice benefit of using a zero-filled vtable provided by the caller. (Also applies to the EKU module, the test1 module, and the dbmatch module.)
| * Do certificate auth based on match expressions in the pkinit_cert_match | ||
| * attribute string. An expression should be in the same form as those used for | ||
| * the pkinit_cert_match configuration option. Multiple expressions are | ||
| * separated by ':'. The certificate is authorized if all expressions are |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment still needs to be updated now that the code supports only one expression.
| h->moddata = NULL; | ||
| if (h->vt.init != NULL) { | ||
| ret = h->vt.init(context, &h->moddata); | ||
| if (ret) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to see a trace log here; see the localauth, hostrealm, or ccselect consumers for examples. (Note that PKINIT trace log definitions are in pkinit_trace.h, though.)
e1b0843
to
b972580
Compare
|
|
I'm going to push some adjusted commits to the PR branch to, among various miscellaneous edits:
I will also be removing the third reformatting commit. It changes the license statement, which is not necessary okay, and this PR is too involved to include out-of-scope commits. |
b972580
to
3f3408a
Compare
|
I still haven't delved fully into the new matching code, but I am concerned about the memory management in crypto_req_cert_matching_data(). It allocates cd, fills it in, passes it to crypto_cert_get_matching_data() which makes an alias within the resulting md, and then frees it while md is still live. Am I missing something or does this create a dangling pointer? |
Yes, so technically there is a dangling pointer in md (and this would also apply for the ci pointer) however those two aliases of md->ch and md->cred are not used during the component_match() calls in pkinit_client_cert_match(), and md lasts until crypto_cert_free_matching_data(). Note that crypto_cert_free_matching_data() does not clean up md->ch or md->cred. There may be a cleaner way to handle the md here, it might need changing crypto_cert_free_matching_data() to be able to (optionally) release md->ch/md->cred. |
|
I see how that pointer is only used by crypto_cert_select() which we aren't calling. I think the way this code uses the existing matching interfaces creates too much technical debt. To make the matching interface properly usable for the server code, I think we need to:
For step 1, there are a couple of approaches. We could create an array of cert_handle/matching_data pairs in obtain_all_cert_matching_data(). Another approach might be to replace crypto_cert_iteration_begin()/crypto_cert_iteration_next() with a function that returns an array of cert handles (this would be substantially less code) and maintain two parallel arrays with the same indexes. These internal changes should be in a separate commit that doesn't introduce any new functionality or change any existing behavior. |
Actually, I think we can go simpler still: get rid of cert handles entirely, and just ask the crypto code for an array of matching data objects for an id_cryptoctx (with a plgctx and reqctx as additional parameters for for the SAN/EKU functions). Then select a cert by id_cryptoctx and index. |
|
I've updated with a new commit implementing those changes. |
fbb87b5
to
bc3c3c5
Compare
|
I flipped the second and third commits, and made some minor edits. I will make a final review pass probably tomorrow, and hopefully merge this. |
Add the header include/krb5/certauth_plugin.h, defining a pluggable interface to control authorization of PKINIT client certificates. Add the "pkinit_san" and "pkinit_eku" builtin certauth modules and related PKINIT crypto X.509 helper functions. Add authorize_cert() as the entry function for certauth plugin module checks called in pkinit_server_verify_padata(). Modify kdcpreauth_moddata to hold the list of certauth module handles, and load the modules when the PKINIT kdcpreauth server plugin is initialized. Change crypto_retrieve_X509_sans() to return ENOENT when no SAN is found. Add test modules in plugins/certauth/test. Create t_certauth.py with basic certauth tests. Add plugin interface documentation in doc/plugindev/certauth.rst and doc/admin/krb5_conf.rst. [ghudson@mit.edu: simplified code, edited docs] ticket: 8561 (new)
Remove the pkinit_cert_handle structures and iteration functions used during certificate matching. Instead, make pkinit_matching.c obtain a list of matching data objects from the crypto code, and then select a cert based on the index into that list. Also fix a typo in the name of crypto_retrieve_X509_key_usage(). [ghudson@mit.edu: simplified code]
Add and enable the "dbmatch" builtin module. Add the pkinit_client_cert_match() and crypto_req_cert_matching_data() helper functions. Add dbmatch tests to t_pkinit.py. Add documentation to krb5_conf.rst, pkinit.rst, and kadmin_local.rst. [ghudson@mit.edu: simplified code, edited docs] ticket: 8562 (new)
bc3c3c5
to
89634ca
Compare
This PR implements the certauth plugin interface and builtin modules as outlined in https://k5wiki.kerberos.org/wiki/Projects/Certificate_authorization_pluggable_interface