1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

OpenSSL ソースリーディング その4

Last updated at Posted at 2018-07-08

その1
その2
その3

今回はssl3_read_bytes関数を見ます。
ただ、ここの処理の内容の正しさは保証できません。ご注意ください。
誤りが見つかった場合、都度訂正していく予定です。

ssl3_read_bytes

ssl3_read_bytes関数は、以下の引数を取ります。

ssl/record/rec_layer_s3.c
int ssl3_read_bytes(SSL *s, int type, int *recvd_type, unsigned char *buf,
                    int len, int peek)
{

typeは呼び出し側が期待するSSLレコードのタイプです。これは、SSL3_RT_APPLICATION_DATAまたはSSL3_RT_HANDSHAKEのどちらかしか指定されません。
SSL3_RT_CHANGE_CIPHER_SPECはネゴシエーションの流れで入るものであり、SSL3_RT_ALERTはどのタイミングで来るか、データを読み取るまでわからないからです。

bufは、復号したデータを格納するバッファです。このbufに最大lenバイト分のデータをコピーします。
peekは、復号したデータを見たいが、SSL側で管理しているバッファを消費したくない場合に有効にされます。

ssl/record/rec_layer_s3.c
    if ((type && (type != SSL3_RT_APPLICATION_DATA)
         && (type != SSL3_RT_HANDSHAKE)) || (peek
                                             && (type !=
                                                 SSL3_RT_APPLICATION_DATA))) {
        SSLerr(SSL_F_SSL3_READ_BYTES, ERR_R_INTERNAL_ERROR);
        return -1;
    }

処理の初めにtypeのチェックが行われています。
また、peekが有効な時は、SSL_peekからの流れになるため、typeはSSL3_RT_APPLICATION_DATAでしか呼び出されません。

ssl/record/rec_layer_s3.c
    if ((type == SSL3_RT_HANDSHAKE) && (s->rlayer.handshake_fragment_len > 0))
        /* (partially) satisfy request from storage */
    {
        unsigned char *src = s->rlayer.handshake_fragment;
        unsigned char *dst = buf;
        unsigned int k;

        /* peek == 0 */
        n = 0;
        while ((len > 0) && (s->rlayer.handshake_fragment_len > 0)) {
            *dst++ = *src++;
            len--;
            s->rlayer.handshake_fragment_len--;
            n++;
        }
        /* move any remaining fragment bytes: */
        for (k = 0; k < s->rlayer.handshake_fragment_len; k++)
            s->rlayer.handshake_fragment[k] = *src++;

        if (recvd_type != NULL)
            *recvd_type = SSL3_RT_HANDSHAKE;

        return n;
    }

ハンドシェイクの場合、断片化されたデータを持っていないか確認します。
handshake_fragmentはchar[4]の4バイト領域です。これは、以降の処理で一部のデータを先読みしたデータを格納する領域です。
この領域は4バイトであり、ハンドシェイクの先頭のtype(1バイト)とlength(3バイト)を格納します。この時、受信されたデータが4バイトに満たない場合、断片化されたものとして、次のデータの読み込み処理までhandshake_fragmentに保持します。
ここでの処理はすでに断片化された情報を受け取っていた場合、呼び出し元が指定した出力バッファへコピーします。
この時の呼び出し元は、(多分)tls_get_message_headerのハンドシェイクメッセージのヘッダ部分の読み込み処理になるため、s->init_buf->dataへ断片化されたハンドシェイクメッセージを入れることになります。

handshake_fragmentに保持して、呼び出し元にデータを出さないのは、フラグメントを検知した時の期待値がヘッダの読み込み(=tls_get_message_headerからの呼び出し)であるか、ボディの読み込み(=tls_get_message_body)であるかが不明であるためです。

ssl/record/rec_layer_s3.c
    if (!ossl_statem_get_in_handshake(s) && SSL_in_init(s)) {
        /* type == SSL3_RT_APPLICATION_DATA */
        i = s->handshake_func(s);
        if (i < 0)
            return (i);
        if (i == 0) {
            SSLerr(SSL_F_SSL3_READ_BYTES, SSL_R_SSL_HANDSHAKE_FAILURE);
            return (-1);
        }
    }

次のこの処理は、ハンドシェイク状態に設定されておらず、またネゴシエーションも完了していない状態の場合、handshake_func(ossl_statem_accept/ossl_statem_connect)を呼び出します。
つまり、SSL_accept関数などで初回のネゴシエーション完了前や再ネゴシエーション中にSSL_read関数を呼び出しても、ネゴシエーション処理が行われることになります。

初回のネゴシエーションが完了して、暗号通信が開始されている場合、これ以降の処理に入ることになります。

ssl/record/rec_layer_s3.c
 start:
    s->rwstate = SSL_NOTHING;

    /*-
     * For each record 'i' up to |num_recs]
     * rr[i].type     - is the type of record
     * rr[i].data,    - data
     * rr[i].off,     - offset into 'data' for next read
     * rr[i].length,  - number of bytes.
     */
    rr = s->rlayer.rrec;
    num_recs = RECORD_LAYER_get_numrpipes(&s->rlayer);

    do {
        /* get new records if necessary */
        if (num_recs == 0) {
            ret = ssl3_get_record(s);
            if (ret <= 0)
                return (ret);
            num_recs = RECORD_LAYER_get_numrpipes(&s->rlayer);
            if (num_recs == 0) {
                /* Shouldn't happen */
                al = SSL_AD_INTERNAL_ERROR;
                SSLerr(SSL_F_SSL3_READ_BYTES, ERR_R_INTERNAL_ERROR);
                goto f_err;
            }
        }
        /* Skip over any records we have already read */
        for (curr_rec = 0;
             curr_rec < num_recs && SSL3_RECORD_is_read(&rr[curr_rec]);
             curr_rec++) ;
        if (curr_rec == num_recs) {
            RECORD_LAYER_set_numrpipes(&s->rlayer, 0);
            num_recs = 0;
            curr_rec = 0;
        }
    } while (num_recs == 0);
    rr = &rr[curr_rec];

ここではまずRECORD_LAYER_get_numrpipes分のレコードを読み込みます。
暗号データはssl3_get_record関数で復号されている状態になっています。

ssl/record/rec_layer_s3.c
    /*
     * Reset the count of consecutive warning alerts if we've got a non-empty
     * record that isn't an alert.
     */
    if (SSL3_RECORD_get_type(rr) != SSL3_RT_ALERT
            && SSL3_RECORD_get_length(rr) != 0)
        s->rlayer.alert_count = 0;

ここは、SSL Death Alert(CVE-2016-8610)の脆弱性対応のコードです。
多量のWarningアラートが含まれたデータを受け付けるとリソースを消費し、DOS攻撃となるものです。
そのため、アラートをカウントするのですが、アラート以外の別のレコードを受けた場合のクリア処理です。

ssl/record/rec_layer_s3.c
    if (type == SSL3_RECORD_get_type(rr)
        || (SSL3_RECORD_get_type(rr) == SSL3_RT_CHANGE_CIPHER_SPEC
            && type == SSL3_RT_HANDSHAKE && recvd_type != NULL)) {
        /*
         * SSL3_RT_APPLICATION_DATA or
         * SSL3_RT_HANDSHAKE or
         * SSL3_RT_CHANGE_CIPHER_SPEC
         */

ここは、呼び出し側が期待したタイプのデータ(SSL3_RT_APPLICATION_DATAまたはSSL3_RT_HANDSHAKE)か、ハンドシェイクを期待した時のCHANGE_CIPHER_SPECの場合に入ります。

ssl/record/rec_layer_s3.c
        /*
         * make sure that we are not getting application data when we are
         * doing a handshake for the first time
         */
        if (SSL_in_init(s) && (type == SSL3_RT_APPLICATION_DATA) &&
            (s->enc_read_ctx == NULL)) {
            al = SSL_AD_UNEXPECTED_MESSAGE;
            SSLerr(SSL_F_SSL3_READ_BYTES, SSL_R_APP_DATA_IN_HANDSHAKE);
            goto f_err;
        }

ネゴシエーション中にSSL3_RT_APPLICATION_DATAが来た場合はSSL_AD_UNEXPECTED_MESSAGEで切断します。

ssl/record/rec_layer_s3.c
        read_bytes = 0;
        do {
            if ((unsigned int)len - read_bytes > SSL3_RECORD_get_length(rr))
                n = SSL3_RECORD_get_length(rr);
            else
                n = (unsigned int)len - read_bytes;

            memcpy(buf, &(rr->data[rr->off]), n);
            buf += n;
            if (peek) {
                /* Mark any zero length record as consumed CVE-2016-6305 */
                if (SSL3_RECORD_get_length(rr) == 0)
                    SSL3_RECORD_set_read(rr);
            } else {
                SSL3_RECORD_sub_length(rr, n);
                SSL3_RECORD_add_off(rr, n);
                if (SSL3_RECORD_get_length(rr) == 0) {
                    s->rlayer.rstate = SSL_ST_READ_HEADER;
                    SSL3_RECORD_set_off(rr, 0);
                    SSL3_RECORD_set_read(rr);
                }
            }
            if (SSL3_RECORD_get_length(rr) == 0
                || (peek && n == SSL3_RECORD_get_length(rr))) {
                curr_rec++;
                rr++;
            }
            read_bytes += n;
        } while (type == SSL3_RT_APPLICATION_DATA && curr_rec < num_recs
                 && read_bytes < (unsigned int)len);

このループで復号したデータをバッファに出力しています。
SSL3_RT_APPLICATION_DATAの場合は連続して複数のレコードを読み込みますが、SSL3_RT_HANDSHAKEの場合は、1レコード分のみ出力します。

ssl/record/rec_layer_s3.c
    /*
     * In case of record types for which we have 'fragment' storage, fill
     * that so that we can process the data at a fixed place.
     */
    {
        unsigned int dest_maxlen = 0;
        unsigned char *dest = NULL;
        unsigned int *dest_len = NULL;

        if (SSL3_RECORD_get_type(rr) == SSL3_RT_HANDSHAKE) {
            dest_maxlen = sizeof(s->rlayer.handshake_fragment);
            dest = s->rlayer.handshake_fragment;
            dest_len = &s->rlayer.handshake_fragment_len;
        } else if (SSL3_RECORD_get_type(rr) == SSL3_RT_ALERT) {
            dest_maxlen = sizeof(s->rlayer.alert_fragment);
            dest = s->rlayer.alert_fragment;
            dest_len = &s->rlayer.alert_fragment_len;
        }

        if (dest_maxlen > 0) {
            n = dest_maxlen - *dest_len; /* available space in 'dest' */
            if (SSL3_RECORD_get_length(rr) < n)
                n = SSL3_RECORD_get_length(rr); /* available bytes */

            /* now move 'n' bytes: */
            while (n-- > 0) {
                dest[(*dest_len)++] =
                    SSL3_RECORD_get_data(rr)[SSL3_RECORD_get_off(rr)];
                SSL3_RECORD_add_off(rr, 1);
                SSL3_RECORD_add_length(rr, -1);
            }

            if (*dest_len < dest_maxlen) {
                SSL3_RECORD_set_read(rr);
                goto start;     /* fragment was too small */
            }
        }
    }

次の処理で必要となるデータを読み込みます。
ハンドシェイクの場合4バイト(ハンドシェイクタイプ+Length)、アラートの場合は2バイト(アラートレベル+詳細コード)になります。
データが足りない場合はstartのラベルへ戻り、読み込み処理に入ります。

ssl/record/rec_layer_s3.c
    /* If we are a client, check for an incoming 'Hello Request': */
    if ((!s->server) &&
        (s->rlayer.handshake_fragment_len >= 4) &&
        (s->rlayer.handshake_fragment[0] == SSL3_MT_HELLO_REQUEST) &&
        (s->session != NULL) && (s->session->cipher != NULL)) {
        s->rlayer.handshake_fragment_len = 0;

ここはHelloRequestを受けた場合のクライアント側の処理です。
再ネゴシエーションに入れる場合は再ネゴシエーション処理に入ります。

ssl/record/rec_layer_s3.c
    if (s->server
            && SSL_is_init_finished(s)
            && s->version > SSL3_VERSION
            && s->rlayer.handshake_fragment_len >= SSL3_HM_HEADER_LENGTH
            && s->rlayer.handshake_fragment[0] == SSL3_MT_CLIENT_HELLO
            && s->s3->previous_client_finished_len != 0
            && ((!s->s3->send_connection_binding
                    && (s->options
                        & SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION) == 0)
                || (s->options & SSL_OP_NO_RENEGOTIATION) != 0)) {

サーバ側が再ネゴシエーションを行えるかのチェック処理です。
設定により再ネゴシエーションを行わない場合や古い形式の再ネゴシエーションなどをチェックします。

ssl/record/rec_layer_s3.c
    if (s->rlayer.alert_fragment_len >= 2) {
        int alert_level = s->rlayer.alert_fragment[0];
        int alert_descr = s->rlayer.alert_fragment[1];

alert_fragment_lenが0以外の場合は、アラートメッセージの処理になります。
Fatalレベルの場合はそのままシャットダウン処理に入ります。
Warningレベルの場合は処理を継続することができるため、startラベルへと戻ります。

ssl/record/rec_layer_s3.c
    /*
     * Unexpected handshake message (Client Hello, or protocol violation)
     */
    if ((s->rlayer.handshake_fragment_len >= 4)
        && !ossl_statem_get_in_handshake(s)) {
        if (SSL_is_init_finished(s) &&
            !(s->s3->flags & SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS)) {
            ossl_statem_set_in_init(s, 1);
            s->renegotiate = 1;
            s->new_session = 1;
        }
        i = s->handshake_func(s);

ハンドシェイクデータの場合、ここでネゴシエーション処理へと入る場合があります。ただ、初回のネゴシエーションはここより前でs->handshake_func(s)を行うため、この場合は再ネゴシエーションになると思われます。

ssl/record/rec_layer_s3.c
    switch (SSL3_RECORD_get_type(rr)) {
    default:
        /*
         * TLS 1.0 and 1.1 say you SHOULD ignore unrecognised record types, but
         * TLS 1.2 says you MUST send an unexpected message alert. We use the
         * TLS 1.2 behaviour for all protocol versions to prevent issues where
         * no progress is being made and the peer continually sends unrecognised
         * record types, using up resources processing them.
         */
        al = SSL_AD_UNEXPECTED_MESSAGE;
        SSLerr(SSL_F_SSL3_READ_BYTES, SSL_R_UNEXPECTED_RECORD);
        goto f_err;
    case SSL3_RT_CHANGE_CIPHER_SPEC:
    case SSL3_RT_ALERT:
    case SSL3_RT_HANDSHAKE:
        /*
         * we already handled all of these, with the possible exception of
         * SSL3_RT_HANDSHAKE when ossl_statem_get_in_handshake(s) is true, but
         * that should not happen when type != rr->type
         */
        al = SSL_AD_UNEXPECTED_MESSAGE;
        SSLerr(SSL_F_SSL3_READ_BYTES, ERR_R_INTERNAL_ERROR);
        goto f_err;
    case SSL3_RT_APPLICATION_DATA:
        /*
         * At this point, we were expecting handshake data, but have
         * application data.  If the library was running inside ssl3_read()
         * (i.e. in_read_app_data is set) and it makes sense to read
         * application data at this point (session renegotiation not yet
         * started), we will indulge it.
         */
        if (ossl_statem_app_data_allowed(s)) {
            s->s3->in_read_app_data = 2;
            return (-1);
        } else {
            al = SSL_AD_UNEXPECTED_MESSAGE;
            SSLerr(SSL_F_SSL3_READ_BYTES, SSL_R_UNEXPECTED_RECORD);
            goto f_err;
        }
    }

基本的にはここまで来ることはないように思われます。
ただ、HelloRequestを送信した後に来たアプリケーションデータの場合、
s->s3->in_read_app_data = 2;
されることにより、ssl3_read_internal関数でossl_statem_set_in_handshake関数により、ハンドシェイクを無効化し、アプリケーションデータの読み込みを行います。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?