summaryrefslogtreecommitdiffstats
path: root/Modules/_ssl/debughelpers.c
blob: 07e9ce7a6fce2de750fa5f1aac0d4fb370b2fc21 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/* Debug helpers */

#ifndef SSL3_MT_CHANGE_CIPHER_SPEC
/* Dummy message type for handling CCS like a normal handshake message
 * not defined in OpenSSL 1.0.2
 */
#define SSL3_MT_CHANGE_CIPHER_SPEC              0x0101
#endif

static void
_PySSL_msg_callback(int write_p, int version, int content_type,
                    const void *buf, size_t len, SSL *ssl, void *arg)
{
    const char *cbuf = (const char *)buf;
    PyGILState_STATE threadstate;
    PyObject *res = NULL;
    PySSLSocket *ssl_obj = NULL;  /* ssl._SSLSocket, borrowed ref */
    int msg_type;

    threadstate = PyGILState_Ensure();

    ssl_obj = (PySSLSocket *)SSL_get_app_data(ssl);
    assert(Py_IS_TYPE(ssl_obj, get_state_sock(ssl_obj)->PySSLSocket_Type));
    if (ssl_obj->ctx->msg_cb == NULL) {
        PyGILState_Release(threadstate);
        return;
    }

    PyObject *ssl_socket;  /* ssl.SSLSocket or ssl.SSLObject */
    if (ssl_obj->owner)
        ssl_socket = _PyWeakref_GET_REF(ssl_obj->owner);
    else if (ssl_obj->Socket)
        ssl_socket = _PyWeakref_GET_REF(ssl_obj->Socket);
    else
        ssl_socket = (PyObject *)Py_NewRef(ssl_obj);
    assert(ssl_socket != NULL);  // _PyWeakref_GET_REF() can return NULL

    /* assume that OpenSSL verifies all payload and buf len is of sufficient
       length */
    switch(content_type) {
      case SSL3_RT_CHANGE_CIPHER_SPEC:
        msg_type = SSL3_MT_CHANGE_CIPHER_SPEC;
        break;
      case SSL3_RT_ALERT:
        /* byte 0: level */
        /* byte 1: alert type */
        msg_type = (int)cbuf[1];
        break;
      case SSL3_RT_HANDSHAKE:
        msg_type = (int)cbuf[0];
        break;
#ifdef SSL3_RT_HEADER
      case SSL3_RT_HEADER:
        /* frame header encodes version in bytes 1..2 */
        version = cbuf[1] << 8 | cbuf[2];
        msg_type = (int)cbuf[0];
        break;
#endif
#ifdef SSL3_RT_INNER_CONTENT_TYPE
      case SSL3_RT_INNER_CONTENT_TYPE:
        msg_type = (int)cbuf[0];
        break;
#endif
      default:
        /* never SSL3_RT_APPLICATION_DATA */
        msg_type = -1;
        break;
    }

    res = PyObject_CallFunction(
        ssl_obj->ctx->msg_cb, "Osiiiy#",
        ssl_socket, write_p ? "write" : "read",
        version, content_type, msg_type,
        buf, len
    );
    if (res == NULL) {
        ssl_obj->exc = PyErr_GetRaisedException();
    } else {
        Py_DECREF(res);
    }
    Py_XDECREF(ssl_socket);

    PyGILState_Release(threadstate);
}


static PyObject *
_PySSLContext_get_msg_callback(PySSLContext *self, void *c) {
    if (self->msg_cb != NULL) {
        return Py_NewRef(self->msg_cb);
    } else {
        Py_RETURN_NONE;
    }
}

static int
_PySSLContext_set_msg_callback(PySSLContext *self, PyObject *arg, void *c) {
    Py_CLEAR(self->msg_cb);
    if (arg == Py_None) {
        SSL_CTX_set_msg_callback(self->ctx, NULL);
    }
    else {
        if (!PyCallable_Check(arg)) {
            SSL_CTX_set_msg_callback(self->ctx, NULL);
            PyErr_SetString(PyExc_TypeError,
                            "not a callable object");
            return -1;
        }
        self->msg_cb = Py_NewRef(arg);
        SSL_CTX_set_msg_callback(self->ctx, _PySSL_msg_callback);
    }
    return 0;
}

static void
_PySSL_keylog_callback(const SSL *ssl, const char *line)
{
    PyGILState_STATE threadstate;
    PySSLSocket *ssl_obj = NULL;  /* ssl._SSLSocket, borrowed ref */
    int res, e;

    threadstate = PyGILState_Ensure();

    ssl_obj = (PySSLSocket *)SSL_get_app_data(ssl);
    assert(Py_IS_TYPE(ssl_obj, get_state_sock(ssl_obj)->PySSLSocket_Type));
    PyThread_type_lock lock = get_state_sock(ssl_obj)->keylog_lock;
    assert(lock != NULL);
    if (ssl_obj->ctx->keylog_bio == NULL) {
        return;
    }
    /*
     * The lock is neither released on exit nor on fork(). The lock is
     * also shared between all SSLContexts although contexts may write to
     * their own files. IMHO that's good enough for a non-performance
     * critical debug helper.
     */

    PySSL_BEGIN_ALLOW_THREADS
    PyThread_acquire_lock(lock, 1);
    res = BIO_printf(ssl_obj->ctx->keylog_bio, "%s\n", line);
    e = errno;
    (void)BIO_flush(ssl_obj->ctx->keylog_bio);
    PyThread_release_lock(lock);
    PySSL_END_ALLOW_THREADS

    if (res == -1) {
        errno = e;
        PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError,
                                             ssl_obj->ctx->keylog_filename);
        ssl_obj->exc = PyErr_GetRaisedException();
    }
    PyGILState_Release(threadstate);
}

static PyObject *
_PySSLContext_get_keylog_filename(PySSLContext *self, void *c) {
    if (self->keylog_filename != NULL) {
        return Py_NewRef(self->keylog_filename);
    } else {
        Py_RETURN_NONE;
    }
}

static int
_PySSLContext_set_keylog_filename(PySSLContext *self, PyObject *arg, void *c) {
    FILE *fp;
    /* Reset variables and callback first */
    SSL_CTX_set_keylog_callback(self->ctx, NULL);
    Py_CLEAR(self->keylog_filename);
    if (self->keylog_bio != NULL) {
        BIO *bio = self->keylog_bio;
        self->keylog_bio = NULL;
        PySSL_BEGIN_ALLOW_THREADS
        BIO_free_all(bio);
        PySSL_END_ALLOW_THREADS
    }

    if (arg == Py_None) {
        /* None disables the callback */
        return 0;
    }

    /* _Py_fopen_obj() also checks that arg is of proper type. */
    fp = _Py_fopen_obj(arg, "a" PY_STDIOTEXTMODE);
    if (fp == NULL)
        return -1;

    self->keylog_bio = BIO_new_fp(fp, BIO_CLOSE | BIO_FP_TEXT);
    if (self->keylog_bio == NULL) {
        PyErr_SetString(get_state_ctx(self)->PySSLErrorObject,
                        "Can't malloc memory for keylog file");
        return -1;
    }
    self->keylog_filename = Py_NewRef(arg);

    /* Write a header for seekable, empty files (this excludes pipes). */
    PySSL_BEGIN_ALLOW_THREADS
    if (BIO_tell(self->keylog_bio) == 0) {
        BIO_puts(self->keylog_bio,
                 "# TLS secrets log file, generated by OpenSSL / Python\n");
        (void)BIO_flush(self->keylog_bio);
    }
    PySSL_END_ALLOW_THREADS
    SSL_CTX_set_keylog_callback(self->ctx, _PySSL_keylog_callback);
    return 0;
}