OpenSSL

密碼學與 SSL/TLS 工具包

proxy-certificates

名稱

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

text

表示字串為位元組序列,沒有任何編碼

policy=text:räksmörgås
hex

表示字串為編碼十六進位編碼的二進位資料,每個位元組之間(每兩個十六進位數字)都有冒號

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

表示應從檔案取得政策文字。字串即為檔案名稱。這對於超過數行的政策(例如 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 The OpenSSL Project Authors。保留所有權利。

根據 Apache 授權 2.0(「授權」)授權。您不得在不遵守授權的情況下使用此檔案。您可以在原始程式碼散佈中的 LICENSE 檔案中取得一份副本,或前往 https://www.openssl.org/source/license.html