Changeset 832182b in mod_gnutls for src/gnutls_hooks.c


Ignore:
Timestamp:
Nov 16, 2013, 2:46:50 AM (6 years ago)
Author:
Daniel Kahn Gillmor <dkg@…>
Branches:
debian/master, debian/stretch-backports, jessie-backports, master, upstream
Children:
a01f8ab
Parents:
140d237
git-author:
Daniel Kahn Gillmor <dkg@…> (02/01/13 05:15:50)
git-committer:
Daniel Kahn Gillmor <dkg@…> (11/16/13 02:46:50)
Message:

extracting the user ID from a certificate cleanly.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/gnutls_hooks.c

    r140d237 r832182b  
    4040static void mgs_add_common_cert_vars(request_rec * r, gnutls_x509_crt_t cert, int side, int export_full_cert);
    4141static void mgs_add_common_pgpcert_vars(request_rec * r, gnutls_openpgp_crt_t cert, int side, int export_full_cert);
     42static const char* mgs_x509_construct_uid(request_rec * pool, gnutls_x509_crt_t cert);
    4243
    4344/* Pool Cleanup Function */
     
    12041205                char* ptr = cert_pem_buf;
    12051206                char* outptr = cert_pem_buf2;
     1207                const char* candidate = mgs_x509_construct_uid(r, cert.x509[0]);
    12061208                /* convert PEM to JSON-friendly string by escaping all newlines
    12071209                   (this should really be done within libmsv) */
     
    12141216               
    12151217                /* FIXME : put together a name from the cert we received, instead of hard-coding this value: */
    1216                 rv = msv_query_agent(NULL, "https", "client", "Test User <test0@modgnutls.test>", "x509pem", cert_pem_buf2, &resp);
     1218                rv = msv_query_agent(NULL, "https", "client", candidate, "x509pem", cert_pem_buf2, &resp);
    12171219                if (rv == LIBMSV_ERROR_SUCCESS) {
    12181220                    status = 0;
     
    13551357
    13561358}
     1359
     1360static const char* mgs_x509_leaf_oid_from_dn(apr_pool_t *pool, const char* oid, gnutls_x509_crt_t cert) {
     1361    int rv=GNUTLS_E_SUCCESS, i;
     1362    size_t sz=0, lastsz=0;
     1363    char* data=NULL;
     1364
     1365    i = -1;
     1366    while(rv != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
     1367        i++;
     1368        lastsz=sz;
     1369        sz=0;
     1370        rv = gnutls_x509_crt_get_dn_by_oid (cert, oid, i, 0, NULL, &sz);
     1371    }
     1372    if (i > 0) {
     1373        data = apr_palloc(pool, lastsz);
     1374        sz=lastsz;
     1375        rv = gnutls_x509_crt_get_dn_by_oid (cert, oid, i-1, 0, data, &sz);
     1376        if (rv == GNUTLS_E_SUCCESS)
     1377            return data;
     1378    }
     1379    return NULL;
     1380}
     1381
     1382static const char* mgs_x509_first_type_from_san(apr_pool_t *pool, gnutls_x509_subject_alt_name_t target, gnutls_x509_crt_t cert) {
     1383    int rv=GNUTLS_E_SUCCESS;
     1384    size_t sz;
     1385    char* data=NULL;
     1386    unsigned int i;
     1387    gnutls_x509_subject_alt_name_t thistype;
     1388
     1389    i = 0;
     1390    while(rv != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
     1391        sz = 0;
     1392        rv = gnutls_x509_crt_get_subject_alt_name2(cert, i, NULL, &sz, &thistype, NULL);
     1393        if (rv == GNUTLS_E_SHORT_MEMORY_BUFFER && thistype == target) {
     1394            data = apr_palloc(pool, sz);
     1395            rv = gnutls_x509_crt_get_subject_alt_name2(cert, i, data, &sz, &thistype, NULL);
     1396            if (rv == target)
     1397                return data;
     1398        }
     1399        i++;
     1400    }
     1401    return NULL;
     1402}
     1403
     1404/* Create a string representing a candidate User ID from an X.509
     1405 * certificate
     1406
     1407 * We need this for client certification because a client gives us a
     1408 * certificate, but doesn't tell us (in any other way) who they are
     1409 * trying to authenticate as.
     1410
     1411 * TODO: we might need another parallel for OpenPGP, but for that it's
     1412 * much simpler: we can just assume that the first User ID marked as
     1413 * "primary" (or the first User ID, period) is the identity the user
     1414 * is trying to present as.
     1415
     1416 * one complaint might be "but the user wanted to be another identity,
     1417 * which is also in the certificate (e.g. in a SubjectAltName)"
     1418 * However, given that any user can regenerate their own X.509
     1419 * certificate with their own public key content, they should just do
     1420 * so, and not expect us to guess at their identity :)
     1421
     1422 * This function allocates it's response from the pool given it.  When
     1423 * that pool is reclaimed, the response will also be deallocated.
     1424
     1425 * FIXME: what about extracting a server-style cert
     1426 *        (e.g. https://imposter.example) from the DN or any sAN?
     1427
     1428 * FIXME: what if we want to call this outside the context of a
     1429 *        request?  That complicates the logging.
     1430 */
     1431static const char* mgs_x509_construct_uid(request_rec *r, gnutls_x509_crt_t cert) {
     1432    /* basic strategy, assuming humans are the users: we are going to
     1433     * try to reconstruct a "conventional" User ID by pulling in a
     1434     * name, comment, and e-mail address.
     1435     */
     1436    apr_pool_t *pool = r->pool;
     1437    const char *name=NULL, *comment=NULL, *email=NULL;
     1438    const char *ret=NULL;
     1439    /* subpool for temporary allocation: */
     1440    apr_pool_t *sp=NULL;
     1441
     1442    if (APR_SUCCESS != apr_pool_create(&sp, pool))       
     1443        return NULL; /* i'm assuming that libapr would log this kind
     1444                      * of error on its own */
     1445
     1446     /* Name
     1447     
     1448     the name comes from the leaf commonName of the cert's Subject.
     1449     
     1450     (MAYBE: should we look at trying to assemble a candidate from
     1451             givenName, surName, suffix, etc?  the "name" field
     1452             appears to be case-insensitive, which seems problematic
     1453             from what we expect; see:
     1454             http://www.itu.int/rec/T-REC-X.520-200102-s/e )
     1455
     1456     (MAYBE: should we try pulling a commonName or otherName or
     1457             something from subjectAltName? see:
     1458             https://tools.ietf.org/html/rfc5280#section-4.2.1.6
     1459             GnuTLS does not support looking for Common Names in the
     1460             SAN yet)
     1461     */
     1462    name = mgs_x509_leaf_oid_from_dn(sp, GNUTLS_OID_X520_COMMON_NAME, cert);
     1463
     1464    /* Comment
     1465
     1466       I am inclined to punt on this for now, as Comment has been so
     1467       atrociously misused in OpenPGP.  Perhaps if there is a
     1468       pseudonym (OID 2.5.4.65, aka GNUTLS_OID_X520_PSEUDONYM) field
     1469       in the subject or sAN?
     1470    */
     1471    comment = mgs_x509_leaf_oid_from_dn(sp, GNUTLS_OID_X520_PSEUDONYM, cert);
     1472
     1473    /* E-mail
     1474
     1475       This should be the the first rfc822Name from the sAN.
     1476
     1477   (MAYBE: leaf rfc822Name in the certificate's subject, but this is
     1478           deprecated, and i don't see the OID in x509.h; can we
     1479           support it?)
     1480
     1481     */
     1482    email = mgs_x509_first_type_from_san(sp, GNUTLS_SAN_RFC822NAME, cert);
     1483
     1484
     1485    /* assemble all the parts: */
     1486
     1487    /* must have at least a name or an e-mail. */
     1488    if (name == NULL && email == NULL) {
     1489        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
     1490                "GnuTLS: Need either a name or an e-mail address to get a User ID from an X.509 certificate.");
     1491        goto end;
     1492    }
     1493    if (name) {
     1494        if (comment) {
     1495            if (email) {
     1496                ret = apr_psprintf(pool, "%s (%s) <%s>", name, comment, email);
     1497            } else {
     1498                ret = apr_psprintf(pool, "%s (%s)", name, comment);
     1499            }
     1500        } else {
     1501            if (email) {
     1502                ret = apr_psprintf(pool, "%s <%s>", name, email);
     1503            } else {
     1504                ret = apr_pstrdup(pool, name);
     1505            }
     1506        }
     1507    } else {
     1508        if (comment) {
     1509            ret = apr_psprintf(pool, "(%s) <%s>", comment, email);
     1510        } else {
     1511            ret = apr_psprintf(pool, "<%s>", email);
     1512        }
     1513    }
     1514
     1515end:
     1516    apr_pool_destroy(sp);
     1517    return ret;
     1518}
Note: See TracChangeset for help on using the changeset viewer.