OpenSSL

密码学与SSL/TLS工具包

代理证书

名称

proxy-certificates - OpenSSL中的代理证书

描述

代理证书在RFC 3820中定义。它们用于将权限扩展到其他实体(通常是计算机进程,有时也可能是用户本身)。这允许实体代表EE(终端实体)证书所有者执行操作。

有效代理证书的要求是

  • 它们由终端实体颁发,可以是普通EE证书,也可以是另一个代理证书。

  • 它们不得具有subjectAltNameissuerAltName扩展。

  • 它们必须具有proxyCertInfo扩展。

  • 它们必须具有其发行者的主题,并添加一个commonName

启用代理证书验证

OpenSSL期望想要使用代理证书的应用程序能够专门识别它们,并明确表示。这是通过设置X509验证标志来完成的

X509_STORE_CTX_set_flags(ctx, X509_V_FLAG_ALLOW_PROXY_CERTS);

X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_ALLOW_PROXY_CERTS);

有关此要求的讨论,请参阅"注释"

创建代理证书

可以使用openssl-x509(1)命令和一些额外的扩展来创建代理证书

[ 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

也可以在单独的部分中指定代理扩展

proxyCertInfo = critical,@proxy_ext

[ proxy_ext ]
language = id-ppl-anyLanguage
pathlen = 0
policy = text:BC

策略值具有特定的语法,syntag:string,其中syntag确定将对字符串执行的操作。识别以下syntag

文本

指示字符串是字节序列,没有任何编码

policy=text:räksmörgås
十六进制

指示字符串是十六进制编码的二进制数据,字节之间用冒号分隔(每两位十六进制数字)

policy=hex:72:E4:6B:73:6D:F6:72:67:E5:73
文件

指示应从文件中获取策略的文本。然后字符串是文件名。这对于超过几行的策略(例如XML或其他标记)很有用。

请注意,代理策略值决定了在代理证书期间授予进程的权限,应用程序负责解释和组合这些策略。>

使用代理扩展,创建代理证书只需两个命令

openssl req -new -config proxy.cnf \
    -out proxy.req -keyout proxy.key \
    -subj "/DC=org/DC=openssl/DC=users/CN=proxy"

openssl x509 -req -CAcreateserial -in proxy.req -out proxy.crt \
    -CA user.crt -CAkey user.key -days 7 \
    -extfile proxy.cnf -extensions proxy

您还可以使用另一个代理证书作为发行者来创建代理证书。请注意,此示例对代理扩展使用不同的配置部分

openssl req -new -config proxy.cnf \
    -out proxy2.req -keyout proxy2.key \
    -subj "/DC=org/DC=openssl/DC=users/CN=proxy/CN=proxy 2"

openssl x509 -req -CAcreateserial -in proxy2.req -out proxy2.crt \
    -CA proxy.crt -CAkey proxy.key -days 7 \
    -extfile proxy.cnf -extensions proxy_2

在应用程序中使用代理证书

为了解释代理策略,应用程序通常会从一些默认权限(可能根本没有权限)开始,然后通过根据代理证书链、用户证书和CA证书检查权限来计算结果权限。

复杂的部分是如何在应用程序和证书验证过程之间传递数据。

此类处理需要以下要素

  • 一个回调函数,将在验证每个证书时调用。回调函数为每个证书调用多次,因此必须小心地在正确的时间进行代理策略解释。还需要在检查EE证书时填写默认值。

  • 一个在应用程序代码和回调函数之间共享的数据结构。

  • 一个设置所有内容的包装函数。

  • 一个ex_data索引函数,用于创建指向附加到X509验证上下文的通用ex_data存储的索引。

以下骨架代码可以用作起点

#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 its
                     * 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;
}

如果使用SSL或TLS,则可以使用上述代码轻松设置回调以正确检查证书

SSL_CTX_set_cert_verify_callback(s_ctx, my_X509_verify_cert,
                                 &needed_rights);

注释

迄今为止,似乎代理证书仅在了解它们的系统环境中使用,并且似乎没有人调查如何在这样的环境之外使用或滥用它们。

因此,OpenSSL要求了解代理证书的应用程序也必须明确表示这一点。

subjectAltNameissuerAltName在代理证书中是被禁止的,并且在OpenSSL中强制执行。主题必须与发行者相同,并添加一个commonName。

另请参阅

X509_STORE_CTX_set_flags(3)X509_STORE_CTX_set_verify_cb(3)X509_VERIFY_PARAM_set_flags(3)SSL_CTX_set_cert_verify_callback(3)openssl-req(1)openssl-x509(1)RFC 3820

版权所有 2019-2020 OpenSSL项目作者。保留所有权利。

根据Apache许可证2.0(“许可证”)获得许可。除非符合许可证,否则您不得使用此文件。您可以在源代码分发中的LICENSE文件中或https://www.openssl.org/source/license.html中获取副本。