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 Openvpn-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/openvpn-devel