Skip to content

Commit 9bef875

Browse files
authored
Merge pull request #14 from tmael/pem-passwd-cb
Add support for wolfSSL Context password callback
2 parents 401e12a + 436b236 commit 9bef875

2 files changed

Lines changed: 64 additions & 14 deletions

File tree

src/wolfssl/__init__.py

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
# pylint: disable=too-many-public-methods, too-many-statements
2626

2727
import sys
28+
from functools import wraps
2829
import errno
2930
from socket import (
3031
socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_TYPE, error as socket_error
@@ -130,7 +131,6 @@ def get_der(self):
130131

131132
return derBytes
132133

133-
134134
class SSLContext(object):
135135
"""
136136
An SSLContext holds various SSL-related configuration options and
@@ -144,6 +144,8 @@ def __init__(self, protocol, server_side=None):
144144
self._server_side = server_side
145145
self._verify_mode = None
146146
self._check_hostname = False
147+
self._passwd_cb = None
148+
self._passwd_userdata = None
147149
self.native_object = _lib.wolfSSL_CTX_new(method.native_object)
148150

149151
# wolfSSL_CTX_new() takes ownership of the method.
@@ -269,7 +271,6 @@ def use_sni(self, server_hostname):
269271
if ret != _SSL_SUCCESS:
270272
raise SSLError("Unable to set wolfSSL CTX SNI")
271273

272-
273274
def load_cert_chain(self, certfile, keyfile=None, password=None):
274275
"""
275276
Load a private key and the corresponding certificate. The certfile
@@ -280,32 +281,30 @@ def load_cert_chain(self, certfile, keyfile=None, password=None):
280281
The keyfile string, if present, must point to a file containing the
281282
private key in.
282283
283-
The password parameter is not supported yet.
284+
If you are using a key protected cert or key file, you must call
285+
set_passwd_cb before calling load_cert_chain because wolfSSL
286+
validates the provided file the first time it is loaded.
287+
284288
285289
wolfSSL does not support loading a certificate file that contains
286290
both the certificate AND private key. In this case, users should
287291
split them into two separate files and load using the certfile
288292
and keyfile parameters, respectively.
289293
"""
290-
291-
if password is not None:
292-
raise NotImplementedError("password callback support not "
293-
"implemented yet")
294-
295294
if certfile is not None:
296295
ret = _lib.wolfSSL_CTX_use_certificate_chain_file(
297296
self.native_object, t2b(certfile))
298297
if ret != _SSL_SUCCESS:
299298
raise SSLError(
300-
"Unnable to load certificate chain. E(%d)" % ret)
299+
"Unable to load certificate chain. E(%d)" % ret)
301300
else:
302301
raise TypeError("certfile should be a valid filesystem path")
303302

304303
if keyfile is not None:
305304
ret = _lib.wolfSSL_CTX_use_PrivateKey_file(
306305
self.native_object, t2b(keyfile), _SSL_FILETYPE_PEM)
307306
if ret != _SSL_SUCCESS:
308-
raise SSLError("Unnable to load private key. E(%d)" % ret)
307+
raise SSLError("Unable to load private key. E(%d)" % ret)
309308

310309
def load_verify_locations(self, cafile=None, capath=None, cadata=None):
311310
"""
@@ -330,16 +329,39 @@ def load_verify_locations(self, cafile=None, capath=None, cadata=None):
330329
t2b(capath) if capath else _ffi.NULL)
331330

332331
if ret != _SSL_SUCCESS:
333-
raise SSLError("Unnable to load verify locations. E(%d)" % ret)
332+
raise SSLError("Unable to load verify locations. E(%d)" % ret)
334333

335334
if cadata is not None:
336335
ret = _lib.wolfSSL_CTX_load_verify_buffer(
337336
self.native_object, t2b(cadata),
338337
len(cadata), _SSL_FILETYPE_PEM)
339338

340339
if ret != _SSL_SUCCESS:
341-
raise SSLError("Unnable to load verify locations. E(%d)" % ret)
340+
raise SSLError("Unable to load verify locations. E(%d)" % ret)
341+
342+
def set_passwd_cb(self, callback, userdata=None):
343+
"""
344+
This function can be called before loading a private key with a password.
345+
For example,
346+
password = "funPassphrase"
347+
self._ctx.set_passwd_cb(lambda *_: password)
348+
...
349+
self._ctx.load_cert_chain()
350+
"""
351+
if not callable(callback):
352+
raise TypeError("The specified callback must be callable")
342353

354+
_passwd_helper = self._wrap_cb(callback)
355+
self._passwd_cb = _passwd_helper.callback
356+
_lib.wolfSSL_CTX_set_default_passwd_cb(self.native_object,
357+
self._passwd_cb)
358+
self._passwd_userdata = userdata # keep it alive
359+
360+
def _wrap_cb(self, callback):
361+
@wraps(callback)
362+
def wrapper(sz, rw, userdata):
363+
return callback(sz, rw, self._passwd_userdata)
364+
return WolfsslPwd_cb(wrapper)
343365

344366
class SSLSocket(object):
345367
"""
@@ -826,12 +848,12 @@ def wrap_socket(sock, keyfile=None, certfile=None, server_side=False,
826848
ciphers=None):
827849
"""
828850
Takes an instance sock of socket.socket, and returns an instance of
829-
wolfssl.SSLSocket, wraping the underlying socket in an SSL context.
851+
wolfssl.SSLSocket, wrapping the underlying socket in an SSL context.
830852
831853
The sock parameter must be a SOCK_STREAM socket; other socket types are
832854
unsupported.
833855
834-
The keyfile and certfile parameters specify optional files whith proper
856+
The keyfile and certfile parameters specify optional files with proper
835857
key and the certificates used to identify the local side of the connection.
836858
837859
The parameter server_side is a boolean which identifies whether server-side
@@ -902,3 +924,29 @@ def wrap_socket(sock, keyfile=None, certfile=None, server_side=False,
902924
do_handshake_on_connect=do_handshake_on_connect,
903925
suppress_ragged_eofs=suppress_ragged_eofs,
904926
ciphers=ciphers)
927+
928+
class WolfsslPwd_cb(object):
929+
def __init__(self, password):
930+
self._passwd_wrapper = password
931+
932+
@property
933+
def callback(self):
934+
if self._passwd_wrapper is None or isinstance(self._passwd_wrapper, bytes):
935+
return _ffi.NULL
936+
elif callable(self._passwd_wrapper):
937+
return _ffi.callback("pem_password_cb", self._get_passwd)
938+
else:
939+
raise TypeError("Not callable or missing arguments")
940+
941+
def _get_passwd(self, passwd, sz, rw, userdata):
942+
try:
943+
result = self._passwd_wrapper(sz, rw, userdata)
944+
if not isinstance(result, bytes):
945+
raise ValueError("Problem, expected String, not bytes")
946+
if len(result) > sz:
947+
raise ValueError("Problem with password returned being long")
948+
for i in range(len(result)):
949+
passwd[i] = result[i:i + 1]
950+
return len(result)
951+
except Exception as e:
952+
raise ValueError("Problem getting password from callback")

src/wolfssl/_build_ffi.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
typedef unsigned char byte;
6262
typedef unsigned int word32;
6363
64+
typedef int pem_password_cb(char* passwd, int sz, int rw, void* userdata);
6465
/**
6566
* Memory free function
6667
*/
@@ -99,6 +100,7 @@
99100
int wolfSSL_CTX_UseSNI(void*, unsigned char, const void*, unsigned short);
100101
long wolfSSL_CTX_get_options(void*);
101102
long wolfSSL_CTX_set_options(void*, long);
103+
void wolfSSL_CTX_set_default_passwd_cb(void*, pem_password_cb*);
102104
103105
/**
104106
* SSL/TLS Session functions

0 commit comments

Comments
 (0)