def _get_finished_message(self, function): """ Helper to implement :py:meth:`get_finished` and :py:meth:`get_peer_finished`. :param function: Either :py:data:`SSL_get_finished`: or :py:data:`SSL_get_peer_finished`. :return: :py:data:`None` if the desired message has not yet been received, otherwise the contents of the message. :rtype: :py:class:`bytes` or :py:class:`NoneType` """ # The OpenSSL documentation says nothing about what might happen if the # count argument given is zero. Specifically, it doesn't say whether # the output buffer may be NULL in that case or not. Inspection of the # implementation reveals that it calls memcpy() unconditionally. # Section 7.1.4, paragraph 1 of the C standard suggests that # memcpy(NULL, source, 0) is not guaranteed to produce defined (let # alone desirable) behavior (though it probably does on just about # every implementation...) # # Allocate a tiny buffer to pass in (instead of just passing NULL as # one might expect) for the initial call so as to be safe against this # potentially undefined behavior. empty = _ffi.new("char[]", 0) size = function(self._ssl, empty, 0) if size == 0: # No Finished message so far. return None buf = _ffi.new("char[]", size) function(self._ssl, buf, size) return _ffi.buffer(buf, size)[:]
def wrapper(ssl, out, outlen, arg): outstr = callback(Connection._reverse_mapping[ssl]) self._npn_advertise_callback_args = [ _ffi.new("unsigned int *", len(outstr)), _ffi.new("unsigned char[]", outstr), ] outlen[0] = self._npn_advertise_callback_args[0][0] out[0] = self._npn_advertise_callback_args[1] return 0
def wrapper(ssl, out, outlen, in_, inlen, arg): outstr = callback( Connection._reverse_mapping[ssl], _ffi.string(in_)) self._npn_select_callback_args = [ _ffi.new("unsigned char *", len(outstr)), _ffi.new("unsigned char[]", outstr), ] outlen[0] = self._npn_select_callback_args[0][0] out[0] = self._npn_select_callback_args[1] return 0
def get_next_proto_negotiated(self): """ Get the protocol that was negotiated by NPN. """ data = _ffi.new("unsigned char **") data_len = _ffi.new("unsigned int *") _lib.SSL_get0_next_proto_negotiated(self._ssl, data, data_len) if not data_len[0]: return "" else: return _ffi.string(data[0])
def get_csr(client, key_id, key_version_id, subject_name, domain, kms_algorithm, signature_algorithm): clear_text_public_key_pem = get_public_key(client, key_id, key_version_id) # kms get_public_key pkey = load_publickey(FILETYPE_PEM, clear_text_public_key_pem) req = X509Req() req.set_pubkey(pkey) req.get_subject().CN = subject_name.get('CN') req.get_subject().C = subject_name.get('C') req.get_subject().O = subject_name.get('O') req.set_version(0) # addExtensions req.add_extensions([ X509Extension(b'subjectAltName', False, ','.join(domain).encode('ascii')) ]) result_buffer = _ffi.new('unsigned char**') encode_result = _lib.i2d_re_X509_REQ_tbs(req._req, result_buffer) md_length = _ffi.new("unsigned int *") md = _ffi.new("unsigned char[]", 32) evp_md = _lib.EVP_get_digestbyname(b"sha256") md_ctx = _lib.Cryptography_EVP_MD_CTX_new() md_ctx = _ffi.gc(md_ctx, _lib.Cryptography_EVP_MD_CTX_free) _lib.EVP_DigestInit_ex(md_ctx, evp_md, _ffi.NULL) _lib.EVP_DigestUpdate(md_ctx, result_buffer[0], encode_result) _lib.EVP_DigestFinal_ex(md_ctx, md, md_length) psig = _ffi.new("ASN1_BIT_STRING **") palg = _ffi.new("X509_ALGOR **") _lib.X509_REQ_get0_signature(req._req, psig, palg) # kms_sign sign_data = kms_sign(client, key_id, key_version_id, kms_algorithm, bytes(md)) _lib.ASN1_STRING_set(psig[0], sign_data, len(sign_data)) psig[0].flags &= ~(0x08 | 0x07) psig[0].flags |= 0x08 _lib.OPENSSL_free(result_buffer[0]) palg[0].algorithm = _lib.OBJ_nid2obj(_lib.OBJ_sn2nid(signature_algorithm)) csr_pem_str = dump_certificate_request(FILETYPE_PEM, req) return csr_pem_str
def sendall(self, buf, flags=0): """ Send "all" data on the connection. This calls send() repeatedly until all data is sent. If an error occurs, it's impossible to tell how much data has been sent. :param buf: The string, buffer or memoryview to send :param flags: (optional) Included for compatibility with the socket API, the value is ignored :return: The number of bytes written """ if isinstance(buf, _memoryview): buf = buf.tobytes() if isinstance(buf, unicode): try: buf = str(buf) except: raise TypeError("couldn't convert unicode buf to byte string") if isinstance(buf, _buffer): buf = str(buf) if not isinstance(buf, bytes): raise TypeError("buf must be a memoryview, buffer or byte string") left_to_send = len(buf) total_sent = 0 data = _ffi.new("char[]", buf) while left_to_send: result = _lib.SSL_write(self._ssl, data + total_sent, left_to_send) self._raise_ssl_error(self._ssl, result) total_sent += result left_to_send -= result
def sendall(self, buf, flags=0): """ Send "all" data on the connection. This calls send() repeatedly until all data is sent. If an error occurs, it's impossible to tell how much data has been sent. :param buf: The string to send :param flags: (optional) Included for compatibility with the socket API, the value is ignored :return: The number of bytes written """ if isinstance(buf, _memoryview): buf = buf.tobytes() if not isinstance(buf, bytes): raise TypeError("buf must be a byte string") left_to_send = len(buf) total_sent = 0 data = _ffi.new("char[]", buf) while left_to_send: result = _lib.SSL_write(self._ssl, data + total_sent, left_to_send) self._raise_ssl_error(self._ssl, result) total_sent += result left_to_send -= result
def wrapper(b64, lib=lib): if isinstance(b64, str): b64 = b64.encode('ascii') b64_ptr = _sslffi.new('char[]', b64) spki_obj = lib.NETSCAPE_SPKI_b64_decode(b64_ptr, len(b64)) if spki_obj == cffi.NULL: raise ValueError("Invalid SPKI base64") def free(spki_obj, ref=b64_ptr): _ssllib.NETSCAPE_SPKI_free(spki_obj) return _sslffi.gc(spki_obj, free)
def b64_decode(cls, spkac_str): """ Construct a NetscapeSPKI from spkac base64 string :param spkac_str: base64 encoded string """ new = cls() arg = _ffi.new('char[]', spkac_str) spki = _lib.NETSCAPE_SPKI_b64_decode(arg, -1) if spki == _ffi.NULL: raise ValueError('Invalid SPKAC string') new._spki = _ffi.gc(spki, _lib.NETSCAPE_SPKI_free) return new
def recv(self, bufsiz, flags=None): """ Receive data on the connection. NOTE: If you get one of the WantRead, WantWrite or WantX509Lookup exceptions on this, you have to call the method again with the SAME buffer. :param bufsiz: The maximum number of bytes to read :param flags: (optional) Included for compatibility with the socket API, the value is ignored :return: The string read from the Connection """ buf = _ffi.new("char[]", bufsiz) result = _lib.SSL_read(self._ssl, buf, bufsiz) self._raise_ssl_error(self._ssl, result) return _ffi.buffer(buf, result)[:]
def bio_read(self, bufsiz): """ When using non-socket connections this function reads the "dirty" data that would have traveled away on the network. :param bufsiz: The maximum number of bytes to read :return: The string read. """ if self._from_ssl is None: raise TypeError("Connection sock was not None") if not isinstance(bufsiz, integer_types): raise TypeError("bufsiz must be an integer") buf = _ffi.new("char[]", bufsiz) result = _lib.BIO_read(self._from_ssl, buf, bufsiz) if result <= 0: self._handle_bio_errors(self._from_ssl, result) return _ffi.buffer(buf, result)[:]
def bytes(num_bytes): """ Get some random bytes as a string. :param num_bytes: The number of bytes to fetch :return: A string of random bytes """ if not isinstance(num_bytes, _integer_types): raise TypeError("num_bytes must be an integer") if num_bytes < 0: raise ValueError("num_bytes must not be negative") result_buffer = _ffi.new("char[]", num_bytes) result_code = _lib.RAND_bytes(result_buffer, num_bytes) if result_code == -1: # TODO: No tests for this code path. Triggering a RAND_bytes failure # might involve supplying a custom ENGINE? That's hard. _raise_current_error() return _ffi.buffer(result_buffer)[:]
def _new_mem_buf(buffer=None): """ Allocate a new OpenSSL memory BIO. Arrange for the garbage collector to clean it up automatically. :param buffer: None or some bytes to use to put into the BIO so that they can be read out. """ if buffer is None: bio = _lib.BIO_new(_lib.BIO_s_mem()) free = _lib.BIO_free else: data = _ffi.new("char[]", buffer) bio = _lib.BIO_new_mem_buf(data, len(buffer)) # Keep the memory alive as long as the bio is alive! def free(bio, ref=data): return _lib.BIO_free(bio) if bio == _ffi.NULL: # TODO: This is untested. _raise_current_error() bio = _ffi.gc(bio, free) return bio