Hello Selva,
I'll send an updated patch wrt. some of your notes, for now just a quick reply
to some of them:
> I'm not convinced of the utility of this. It could be marginally useful in
> some
> setups where all users are tied to a domain and choosing any certificate that
> matches a template is appropriate. I don't have a setup to test this.
That's not the only use case. I'm currently deploying this patch to implement a
machine always-on VPN (machines automatically register a specific certificate
type which contains the machine name as a SAN, which can then be selected using
the TMPL: logic and on the server side the client certificate looked up in a
tls-verify script to check whether the machine account is active). This patch
allows moving the client certificate selection out of the configuration file,
as the selector based on template ID is always equal on any client (SUBJ of
course isn't) and as such can be embedded as a plain .ovpn file in the MSI
which deploys the software, without any additional configuration required on
the part of the client, and especially resilient to later renaming of the
client if that should happen (new certificate deployed with certutil /pulse,
new VPN connection found). Same of course goes for your scenario of user
auto-registered certificates.
> > +static const CRYPT_OID_INFO *
> > +find_oid(DWORD keytype, const void *key, DWORD groupid, bool
> > +fallback) {
> > + const CRYPT_OID_INFO *info = NULL;
> > +
> > + /* force resolve from local as first step */
> > + if (groupid != CRYPT_HASH_ALG_OID_GROUP_ID &&
> > + groupid != CRYPT_ENCRYPT_ALG_OID_GROUP_ID &&
> > + groupid != CRYPT_PUBKEY_ALG_OID_GROUP_ID &&
> > + groupid != CRYPT_SIGN_ALG_OID_GROUP_ID &&
> > + groupid != CRYPT_RDN_ATTR_OID_GROUP_ID &&
> > + groupid != CRYPT_EXT_OR_ATTR_OID_GROUP_ID &&
> > + groupid != CRYPT_KDF_OID_GROUP_ID)
> > + {
>
> Why not do the local lookup for all grupids? Anyway you are falling back to
> domain-wide look up if this fails.
Simply because Microsoft does it this way in their generic wrapping of
CryptFindOIDInfo for certificate selection helpers:
https://github.com/dotnet/runtime/blob/01b7e73cd378145264a7cb7a09365b41ed42b240/src/libraries/Common/src/Interop/Windows/Crypt32/Interop.FindOidInfo.cs#L51
ff - the groups excluded here will always be looked up locally, independent of
the CRYPT_OID_DISABLE_SEARCH_FLAG, so that we can save one call for them.
> > + info = CryptFindOIDInfo(keytype, (void*)key, groupid |
> CRYPT_OID_DISABLE_SEARCH_DS_FLAG);
> > + }
> > +
> > + /* try proper resolve if not found yet, also including AD */
> > + if (!info)
> > + {
> > + info = CryptFindOIDInfo(keytype, (void*)key, groupid);
>
> How long would this take to time out -- we try to avoid calling functions that
> need network access to the DC as that's often not available before the tunnel
> comes up.
Good question which I hadn't taken into account so far; from what I've seen on
machine clients I've deployed that select their certificate using this logic,
the timeout is almost instantaneous, i.e. CryptFindOIDInfo does not find an
object for the TMPL: parameter if it can't reach the domain. Of course, your
note generally limits the functionality of actually using CryptFindOIDInfo to
resolve a printable template name from a specific domain to an OID (which is
then found in the actual certificate), but I wouldn't take this out if it
doesn't introduce a long delay (possibly, there are two tunnels in some cases,
where an independent tunnel sets up reachability of the domain).
> > + }
> > +
> > + /* fall back to all groups if not found yet and fallback requested */
> > + if (!info && fallback && groupid)
> > + {
> > + info = CryptFindOIDInfo(keytype, (void*)key, 0);
>
> That could be a second network call. Why not combine the two into one call.
The difference being that the first call only resolves the name to OID for the
specific object type (e.g., certificate template) being passed, whereas this
call resolves it for any object type. Of course, you always prefer the specific
match if that's available, and only fall back to the generic match if the
caller actually wants to try that. I've implemented this method more or less
equal to the implementation in the .NET framework, simply to facilitate using
it for additional certificate matches which might want to be implemented; there
are other use cases for CryptFindOIDInfo in other match helpers where this
logic actually makes sense. It's debatable whether the fallback to a generic
match actually makes sense in the case of certificate template OID matching
(see the use below), but at least the .NET framework implementation does this:
https://github.com/dotnet/runtime/blob/01b7e73cd378145264a7cb7a09365b41ed42b240/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/FindPal.cs#L190
> > + /* check for V1 extension (Windows 2K and below) */
> > + ext = CertFindExtension(szOID_ENROLL_CERTTYPE_EXTENSION, info-
> >cExtension, info->rgExtension);
> > + if (ext)
> > + {
> > + pvext = decode_object(gc, X509_UNICODE_ANY_STRING, &ext->Value,
> 0, &cbext);
> > + if (pvext && cbext >= sizeof(CERT_NAME_VALUE))
> > + {
> > + const CERT_NAME_VALUE *nv = (const CERT_NAME_VALUE*)pvext;
> > + if (!_wcsicmp((const WCHAR*)nv->Value.pbData, tmpl_name))
>
> Is it guaranteed that pbData is NULL terminated? (MS docs are unclear on
> this).
> We may have to add a safety check that the data length length is >=
> wcslen(tmpl_name).
Yes, that's guaranteed, at least according to the corresponding use in the .NET
framework:
https://github.com/dotnet/runtime/blob/01b7e73cd378145264a7cb7a09365b41ed42b240/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/FindPal.cs#L165
- of course it doesn't hurt to have an additional check anyway.
> > + const CRYPT_OID_INFO *tmpl_oid =
> find_oid(CRYPT_OID_INFO_NAME_KEY, tmpl_name,
> CRYPT_TEMPLATE_OID_GROUP_ID, true);
> > + if (tmpl_oid && !stricmp(tmpl_oid->pszOID, cte->pszObjId))
> > + {
> > + /* found OID match in extension against resolved key */
> > + return true;
> > + }
> > + else if (!tmpl_oid && !stricmp(cert_prop, cte->pszObjId))
> > + {
>
> Why not test this first so that the call to find_oid could be avoided if this
> matches?
For one, because the match in
https://github.com/dotnet/runtime/blob/01b7e73cd378145264a7cb7a09365b41ed42b240/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/FindPal.cs#L187
ff also does it just this way, and secondly, because this way the preference
is on matching a template name (i.e., if a string passed can be interpreted as
a template name in the current domain, it will be, even if it looks like an
OID), whereas only resolving when the OID doesn't match directly would prefer
the interpretation as an OID. It's debatable whether the former or the latter
is preferential, I'd also go to the former, just like the .NET framework does,
especially considering that the "old" X509 extension containing the certificate
template reference actually contains the name, not an OID (see the first
condition), so that possibly some people actually expect the parameter to be a
name.
For all the other notes, those are clear; I'll create a new patch with the
changes applied. Thanks for your feedback!
--- Heiko.
_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel