| HOWTO proxy certificates |
| |
| 0. WARNING |
| |
| NONE OF THE CODE PRESENTED HERE HAS BEEN CHECKED! The code is just examples to |
| show you how things could be done. There might be typos or type conflicts, and |
| you will have to resolve them. |
| |
| 1. Introduction |
| |
| Proxy certificates are defined in RFC 3820. They are really usual certificates |
| with the mandatory extension proxyCertInfo. |
| |
| Proxy certificates are issued by an End Entity (typically a user), either |
| directly with the EE certificate as issuing certificate, or by extension through |
| an already issued proxy certificate. Proxy certificates are used to extend |
| rights to some other entity (a computer process, typically, or sometimes to the |
| user itself). This allows the entity to perform operations on behalf of the |
| owner of the EE certificate. |
| |
| See http://www.ietf.org/rfc/rfc3820.txt for more information. |
| |
| |
| 2. A warning about proxy certificates |
| |
| No one seems to have tested proxy certificates with security in mind. To this |
| date, it seems that proxy certificates have only been used in a context highly |
| aware of them. |
| |
| Existing applications might misbehave when trying to validate a chain of |
| certificates which use a proxy certificate. They might incorrectly consider the |
| leaf to be the certificate to check for authorisation data, which is controlled |
| by the EE certificate owner. |
| |
| subjectAltName and issuerAltName are forbidden in proxy certificates, and this |
| is enforced in OpenSSL. The subject must be the same as the issuer, with one |
| commonName added on. |
| |
| Possible threats we can think of at this time include: |
| |
| - impersonation through commonName (think server certificates). |
| - use of additional extensions, possibly non-standard ones used in certain |
| environments, that would grant extra or different authorisation rights. |
| |
| For these reasons, OpenSSL requires that the use of proxy certificates be |
| explicitly allowed. Currently, this can be done using the following methods: |
| |
| - if the application directly calls X509_verify_cert(), it can first call: |
| |
| X509_STORE_CTX_set_flags(ctx, X509_V_FLAG_ALLOW_PROXY_CERTS); |
| |
| Where ctx is the pointer which then gets passed to X509_verify_cert(). |
| |
| - proxy certificate validation can be enabled before starting the application |
| by setting the environment variable OPENSSL_ALLOW_PROXY_CERTS. |
| |
| In the future, it might be possible to enable proxy certificates by editing |
| openssl.cnf. |
| |
| |
| 3. How to create proxy certificates |
| |
| Creating proxy certificates is quite easy, by taking advantage of a lack of |
| checks in the 'openssl x509' application (*ahem*). You must first create a |
| configuration section that contains a definition of the proxyCertInfo extension, |
| for example: |
| |
| [ v3_proxy ] |
| # A proxy certificate MUST NEVER be a CA certificate. |
| basicConstraints=CA:FALSE |
| |
| # Usual authority key ID |
| authorityKeyIdentifier=keyid,issuer:always |
| |
| # The extension which marks this certificate as a proxy |
| proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:1,policy:text:AB |
| |
| It's also possible to specify the proxy extension in a separate section: |
| |
| proxyCertInfo=critical,@proxy_ext |
| |
| [ proxy_ext ] |
| language=id-ppl-anyLanguage |
| pathlen=0 |
| policy=text:BC |
| |
| The policy value has a specific syntax, {syntag}:{string}, where the syntag |
| determines what will be done with the string. The following syntags are |
| recognised: |
| |
| text indicates that the string is simply bytes, without any encoding: |
| |
| policy=text:räksmörgås |
| |
| Previous versions of this design had a specific tag for UTF-8 text. |
| However, since the bytes are copied as-is anyway, there is no need for |
| such a specific tag. |
| |
| hex indicates the string is encoded in hex, with colons between each byte |
| (every second hex digit): |
| |
| policy=hex:72:E4:6B:73:6D:F6:72:67:E5:73 |
| |
| Previous versions of this design had a tag to insert a complete DER |
| blob. However, the only legal use for this would be to surround the |
| bytes that would go with the hex: tag with whatever is needed to |
| construct a correct OCTET STRING. The DER tag therefore felt |
| superfluous, and was removed. |
| |
| file indicates that the text of the policy should really be taken from a |
| file. The string is then really a file name. This is useful for |
| policies that are large (more than a few lines, e.g. XML documents). |
| |
| The 'policy' setting can be split up in multiple lines like this: |
| |
| 0.policy=This is |
| 1.policy= a multi- |
| 2.policy=line policy. |
| |
| NOTE: the proxy policy value is the part which determines the rights granted to |
| the process using the proxy certificate. The value is completely dependent on |
| the application reading and interpreting it! |
| |
| Now that you have created an extension section for your proxy certificate, you |
| can easily create a proxy certificate by doing: |
| |
| openssl req -new -config openssl.cnf -out proxy.req -keyout proxy.key |
| openssl x509 -req -CAcreateserial -in proxy.req -days 7 -out proxy.crt \ |
| -CA user.crt -CAkey user.key -extfile openssl.cnf -extensions v3_proxy |
| |
| You can also create a proxy certificate using another proxy certificate as |
| issuer (note: I'm using a different configuration section for it): |
| |
| openssl req -new -config openssl.cnf -out proxy2.req -keyout proxy2.key |
| openssl x509 -req -CAcreateserial -in proxy2.req -days 7 -out proxy2.crt \ |
| -CA proxy.crt -CAkey proxy.key -extfile openssl.cnf -extensions v3_proxy2 |
| |
| |
| 4. How to have your application interpret the policy? |
| |
| The basic way to interpret proxy policies is to start with some default rights, |
| then compute the resulting rights by checking the proxy certificate against |
| the chain of proxy certificates, user certificate and CA certificates. You then |
| use the final computed rights. Sounds easy, huh? It almost is. |
| |
| The slightly complicated part is figuring out how to pass data between your |
| application and the certificate validation procedure. |
| |
| You need the following ingredients: |
| |
| - a callback function that will be called for every certificate being |
| validated. The callback be called several times for each certificate, |
| so you must be careful to do the proxy policy interpretation at the right |
| time. You also need to fill in the defaults when the EE certificate is |
| checked. |
| |
| - a data structure that is shared between your application code and the |
| callback. |
| |
| - a wrapper function that sets it all up. |
| |
| - an ex_data index function that creates an index into the generic ex_data |
| store that is attached to an X509 validation context. |
| |
| Here is some skeleton code you can fill in: |
| |
| #include <string.h> |
| #include <netdb.h> |
| #include <openssl/x509.h> |
| #include <openssl/x509v3.h> |
| |
| #define total_rights 25 |
| |
| /* |
| * In this example, I will use a view of granted rights as a bit |
| * array, one bit for each possible right. |
| */ |
| typedef struct your_rights { |
| unsigned char rights[(total_rights + 7) / 8]; |
| } YOUR_RIGHTS; |
| |
| /* |
| * The following procedure will create an index for the ex_data |
| * store in the X509 validation context the first time it's called. |
| * Subsequent calls will return the same index. */ |
| static int get_proxy_auth_ex_data_idx(X509_STORE_CTX *ctx) |
| { |
| static volatile int idx = -1; |
| if (idx < 0) { |
| X509_STORE_lock(X509_STORE_CTX_get0_store(ctx)); |
| if (idx < 0) { |
| idx = X509_STORE_CTX_get_ex_new_index(0, |
| "for verify callback", |
| NULL,NULL,NULL); |
| } |
| X509_STORE_unlock(X509_STORE_CTX_get0_store(ctx)); |
| } |
| return idx; |
| } |
| |
| /* Callback to be given to the X509 validation procedure. */ |
| static int verify_callback(int ok, X509_STORE_CTX *ctx) |
| { |
| if (ok == 1) { |
| /* |
| * It's REALLY important you keep the proxy policy |
| * check within this section. It's important to know |
| * that when ok is 1, the certificates are checked |
| * from top to bottom. You get the CA root first, |
| * followed by the possible chain of intermediate |
| * CAs, followed by the EE certificate, followed by |
| * the possible proxy certificates. |
| */ |
| X509 *xs = X509_STORE_CTX_get_current_cert(ctx); |
| |
| if (X509_get_extension_flags(xs) & EXFLAG_PROXY) { |
| YOUR_RIGHTS *rights = |
| (YOUR_RIGHTS *)X509_STORE_CTX_get_ex_data(ctx, |
| get_proxy_auth_ex_data_idx(ctx)); |
| PROXY_CERT_INFO_EXTENSION *pci = |
| X509_get_ext_d2i(xs, NID_proxyCertInfo, NULL, NULL); |
| |
| switch (OBJ_obj2nid(pci->proxyPolicy->policyLanguage)) { |
| case NID_Independent: |
| /* |
| * Do whatever you need to grant explicit rights to |
| * this particular proxy certificate, usually by |
| * pulling them from some database. If there are none |
| * to be found, clear all rights (making this and any |
| * subsequent proxy certificate void of any rights). |
| */ |
| memset(rights->rights, 0, sizeof(rights->rights)); |
| break; |
| case NID_id_ppl_inheritAll: |
| /* |
| * This is basically a NOP, we simply let the current |
| * rights stand as they are. |
| */ |
| break; |
| default: |
| /* This is usually the most complex section of code. |
| * You really do whatever you want as long as you |
| * follow RFC 3820. In the example we use here, the |
| * simplest thing to do is to build another, temporary |
| * bit array and fill it with the rights granted by |
| * the current proxy certificate, then use it as a |
| * mask on the accumulated rights bit array, and |
| * voilà, you now have a new accumulated rights bit |
| * array. |
| */ |
| { |
| int i; |
| YOUR_RIGHTS tmp_rights; |
| memset(tmp_rights.rights, 0, sizeof(tmp_rights.rights)); |
| |
| /* |
| * process_rights() is supposed to be a procedure |
| * that takes a string and it's length, interprets |
| * it and sets the bits in the YOUR_RIGHTS pointed |
| * at by the third argument. |
| */ |
| process_rights((char *) pci->proxyPolicy->policy->data, |
| pci->proxyPolicy->policy->length, |
| &tmp_rights); |
| |
| for(i = 0; i < total_rights / 8; i++) |
| rights->rights[i] &= tmp_rights.rights[i]; |
| } |
| break; |
| } |
| PROXY_CERT_INFO_EXTENSION_free(pci); |
| } else if (!(X509_get_extension_flags(xs) & EXFLAG_CA)) { |
| /* We have an EE certificate, let's use it to set default! */ |
| YOUR_RIGHTS *rights = |
| (YOUR_RIGHTS *)X509_STORE_CTX_get_ex_data(ctx, |
| get_proxy_auth_ex_data_idx(ctx)); |
| |
| /* The following procedure finds out what rights the owner |
| * of the current certificate has, and sets them in the |
| * YOUR_RIGHTS structure pointed at by the second |
| * argument. |
| */ |
| set_default_rights(xs, rights); |
| } |
| } |
| return ok; |
| } |
| |
| static int my_X509_verify_cert(X509_STORE_CTX *ctx, |
| YOUR_RIGHTS *needed_rights) |
| { |
| int ok; |
| int (*save_verify_cb)(int ok,X509_STORE_CTX *ctx) = |
| X509_STORE_CTX_get_verify_cb(ctx); |
| YOUR_RIGHTS rights; |
| |
| X509_STORE_CTX_set_verify_cb(ctx, verify_callback); |
| X509_STORE_CTX_set_ex_data(ctx, get_proxy_auth_ex_data_idx(ctx), &rights); |
| X509_STORE_CTX_set_flags(ctx, X509_V_FLAG_ALLOW_PROXY_CERTS); |
| ok = X509_verify_cert(ctx); |
| |
| if (ok == 1) { |
| ok = check_needed_rights(rights, needed_rights); |
| } |
| |
| X509_STORE_CTX_set_verify_cb(ctx, save_verify_cb); |
| |
| return ok; |
| } |
| |
| |
| If you use SSL or TLS, you can easily set up a callback to have the |
| certificates checked properly, using the code above: |
| |
| SSL_CTX_set_cert_verify_callback(s_ctx, my_X509_verify_cert, &needed_rights); |
| |
| |
| -- |
| Richard Levitte |