2014-10-11 01:54:02 -04:00

473 lines
14 KiB
C

/* -*- C -*- */
/*
* Uses Windows CryptoAPI CryptGenRandom to get random bytes.
* The "new" method returns an object, whose "get_bytes" method
* can be called repeatedly to get random bytes, seeded by the
* OS. See the description in the comment at the end.
*
* If you have the Intel Security Driver header files (icsp4ms.h)
* for their hardware random number generator in the 810 and 820 chipsets,
* then define HAVE_INTEL_RNG.
*
* =======================================================================
* The contents of this file are dedicated to the public domain. To the
* extent that dedication to the public domain is not available, everyone
* is granted a worldwide, perpetual, royalty-free, non-exclusive license
* to exercise all rights associated with the contents of this file for
* any purpose whatsoever. No rights are reserved.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
* =======================================================================
*
*/
/* Author: Mark Moraes */
#include "Python.h"
#include "pycrypto_compat.h"
#ifdef MS_WIN32
#define _WIN32_WINNT 0x400
#define WINSOCK
#include <windows.h>
#include <wincrypt.h>
#ifdef HAVE_INTEL_RNG
# include "icsp4ms.h"
#else
# define PROV_INTEL_SEC 22
# define INTEL_DEF_PROV "Intel Hardware Cryptographic Service Provider"
#endif
/* To-Do: store provider name and type for print/repr? */
typedef struct
{
PyObject_HEAD
HCRYPTPROV hcp;
} WRobject;
/* Please see PEP3123 for a discussion of PyObject_HEAD and changes made in 3.x to make it conform to Standard C.
* These changes also dictate using Py_TYPE to check type, and PyVarObject_HEAD_INIT(NULL, 0) to initialize
*/
#ifdef IS_PY3K
static PyTypeObject WRtype;
#define is_WRobject(v) (Py_TYPE(v) == &WRtype)
#else
staticforward PyTypeObject WRtype;
#define is_WRobject(v) ((v)->ob_type == &WRtype)
#define PyLong_FromLong PyInt_FromLong /* for Python 2.x */
#endif
static void
WRdealloc(PyObject *ptr)
{
WRobject *o = (WRobject *)ptr;
if (! is_WRobject(ptr)) {
PyErr_Format(PyExc_TypeError,
"WinRandom trying to dealloc non-WinRandom object");
return;
}
if (! CryptReleaseContext(o->hcp, 0)) {
PyErr_Format(PyExc_SystemError,
"CryptReleaseContext failed, error 0x%x",
(unsigned int) GetLastError());
return;
}
/* Overwrite the contents of the object */
o->hcp = 0;
PyObject_Del(ptr);
}
static char winrandom__doc__[] =
"new([provider], [provtype]): Returns an object handle to Windows\n\
CryptoAPI that can be used to access a cryptographically strong\n\
pseudo-random generator that uses OS-gathered entropy.\n\
Provider is a string that specifies the Cryptographic Service Provider\n\
to use, default is the default OS CSP.\n\
provtype is an integer specifying the provider type to use, default\n\
is 1 (PROV_RSA_FULL)";
static char WR_get_bytes__doc__[] =
"get_bytes(nbytes, [userdata]]): Returns nbytes of random data\n\
from Windows CryptGenRandom.\n\
userdata is a string with any additional entropic data that the\n\
user wishes to provide.";
static WRobject *
winrandom_new(PyObject *self, PyObject *args, PyObject *kwdict)
{
HCRYPTPROV hcp = 0;
WRobject *res;
char *provname = NULL;
int provtype = PROV_RSA_FULL;
static char *kwlist[] = { "provider", "provtype", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwdict, "|si", kwlist,
&provname, &provtype)) {
return NULL;
}
if (! CryptAcquireContext(&hcp, NULL, (LPCTSTR) provname,
(DWORD) provtype,
CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) {
PyErr_Format(PyExc_SystemError,
"CryptAcquireContext for provider \"%s\" type %i failed, error 0x%x",
provname? provname : "(null)", provtype,
(unsigned int) GetLastError());
return NULL;
}
res = PyObject_New(WRobject, &WRtype);
res->hcp = hcp;
return res;
}
static PyObject *
WR_get_bytes(WRobject *self, PyObject *args)
{
int n, nbytes, len = 0;
PyObject *res;
char *buf, *str = NULL;
if (! is_WRobject(self)) {
PyErr_Format(PyExc_TypeError,
"WinRandom trying to get_bytes with non-WinRandom object");
return NULL;
}
if (!PyArg_ParseTuple(args, "i|s#", &n, &str, &len)) {
return NULL;
}
if (n <= 0) {
PyErr_SetString(PyExc_ValueError, "nbytes must be positive number");
return NULL;
}
/* Just in case char != BYTE, or userdata > desired result */
nbytes = (((n > len) ? n : len) * sizeof(char)) / sizeof(BYTE) + 1;
if ((buf = (char *) PyMem_Malloc(nbytes)) == NULL)
return PyErr_NoMemory();
if (len > 0)
memcpy(buf, str, len);
/*
* if userdata > desired result, we end up getting
* more bytes than we really needed to return. No
* easy way to avoid that: we prefer that
* CryptGenRandom does the distillation of userdata
* down to entropy, rather than trying to do it
* ourselves. Since the extra bytes presumably come
* from an RC4 stream, they should be relatively
* cheap.
*/
if (! CryptGenRandom(self->hcp, (DWORD) nbytes, (BYTE *) buf)) {
PyErr_Format(PyExc_SystemError,
"CryptGenRandom failed, error 0x%x",
(unsigned int) GetLastError());
PyMem_Free(buf);
return NULL;
}
res = PyBytes_FromStringAndSize(buf, n);
PyMem_Free(buf);
return res;
}
/* WinRandom object methods */
static PyMethodDef WRmethods[] =
{
{"get_bytes", (PyCFunction) WR_get_bytes, METH_VARARGS,
WR_get_bytes__doc__},
{NULL, NULL} /* sentinel */
};
/* winrandom module methods */
static PyMethodDef WR_mod_methods[] = {
{"new", (PyCFunction) winrandom_new, METH_VARARGS|METH_KEYWORDS,
winrandom__doc__},
{NULL, NULL} /* Sentinel */
};
static PyObject *
#ifdef IS_PY3K
WRgetattro(PyObject *s, PyObject *attr)
#else
WRgetattr(PyObject *s, char *name)
#endif
{
WRobject *self = (WRobject*)s;
if (! is_WRobject(self)) {
PyErr_Format(PyExc_TypeError,
"WinRandom trying to getattr with non-WinRandom object");
return NULL;
}
#ifdef IS_PY3K
if (!PyUnicode_Check(attr))
goto generic;
if (PyUnicode_CompareWithASCIIString(attr, "hcp") == 0)
#else
if (strcmp(name, "hcp") == 0)
#endif
return PyLong_FromLong((long) self->hcp);
#ifdef IS_PY3K
generic:
return PyObject_GenericGetAttr(s, attr);
#else
return Py_FindMethod(WRmethods, (PyObject *) self, name);
#endif
}
static PyTypeObject WRtype =
{
#ifdef IS_PY3K
PyVarObject_HEAD_INIT(NULL, 0) /* deferred type init for compilation on Windows, type will be filled in at runtime */
#else
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
#endif
"winrandom.WinRandom", /*tp_name*/
sizeof(WRobject), /*tp_size*/
0, /*tp_itemsize*/
/* methods */
(destructor) WRdealloc, /*tp_dealloc*/
0, /*tp_print*/
#ifndef IS_PY3K
WRgetattr, /*tp_getattr*/
#else
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number */
0, /*tp_as_sequence */
0, /*tp_as_mapping */
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
WRgetattro, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
0, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
WRmethods, /*tp_methods*/
#endif
};
#ifdef IS_PY3K
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"winrandom",
NULL,
-1,
WR_mod_methods,
NULL,
NULL,
NULL,
NULL
};
#endif
#ifdef IS_PY3K
PyMODINIT_FUNC
PyInit_winrandom()
#else
void
initwinrandom()
#endif
{
PyObject *m;
#ifdef IS_PY3K
/* PyType_Ready automatically fills in ob_type with &PyType_Type if it's not already set */
if (PyType_Ready(&WRtype) < 0)
return NULL;
/* Initialize the module */
m = PyModule_Create(&moduledef);
if (m == NULL)
return NULL;
#else
WRtype.ob_type = &PyType_Type;
m = Py_InitModule("winrandom", WR_mod_methods);
#endif
/* define Windows CSP Provider Types */
#ifdef PROV_RSA_FULL
PyModule_AddIntConstant(m, "PROV_RSA_FULL", PROV_RSA_FULL);
#endif
#ifdef PROV_RSA_SIG
PyModule_AddIntConstant(m, "PROV_RSA_SIG", PROV_RSA_SIG);
#endif
#ifdef PROV_DSS
PyModule_AddIntConstant(m, "PROV_DSS", PROV_DSS);
#endif
#ifdef PROV_FORTEZZA
PyModule_AddIntConstant(m, "PROV_FORTEZZA", PROV_FORTEZZA);
#endif
#ifdef PROV_MS_EXCHANGE
PyModule_AddIntConstant(m, "PROV_MS_EXCHANGE", PROV_MS_EXCHANGE);
#endif
#ifdef PROV_SSL
PyModule_AddIntConstant(m, "PROV_SSL", PROV_SSL);
#endif
#ifdef PROV_RSA_SCHANNEL
PyModule_AddIntConstant(m, "PROV_RSA_SCHANNEL", PROV_RSA_SCHANNEL);
#endif
#ifdef PROV_DSS_DH
PyModule_AddIntConstant(m, "PROV_DSS_DH", PROV_DSS_DH);
#endif
#ifdef PROV_EC_ECDSA_SIG
PyModule_AddIntConstant(m, "PROV_EC_ECDSA_SIG", PROV_EC_ECDSA_SIG);
#endif
#ifdef PROV_EC_ECNRA_SIG
PyModule_AddIntConstant(m, "PROV_EC_ECNRA_SIG", PROV_EC_ECNRA_SIG);
#endif
#ifdef PROV_EC_ECDSA_FULL
PyModule_AddIntConstant(m, "PROV_EC_ECDSA_FULL", PROV_EC_ECDSA_FULL);
#endif
#ifdef PROV_EC_ECNRA_FULL
PyModule_AddIntConstant(m, "PROV_EC_ECNRA_FULL", PROV_EC_ECNRA_FULL);
#endif
#ifdef PROV_SPYRUS_LYNKS
PyModule_AddIntConstant(m, "PROV_SPYRUS_LYNKS", PROV_SPYRUS_LYNKS);
#endif
#ifdef PROV_INTEL_SEC
PyModule_AddIntConstant(m, "PROV_INTEL_SEC", PROV_INTEL_SEC);
#endif
/* Define Windows CSP Provider Names */
#ifdef MS_DEF_PROV
PyModule_AddStringConstant(m, "MS_DEF_PROV", MS_DEF_PROV);
#endif
#ifdef MS_ENHANCED_PROV
PyModule_AddStringConstant(m, "MS_ENHANCED_PROV", MS_ENHANCED_PROV);
#endif
#ifdef MS_DEF_RSA_SIG_PROV
PyModule_AddStringConstant(m, "MS_DEF_RSA_SIG_PROV",
MS_DEF_RSA_SIG_PROV);
#endif
#ifdef MS_DEF_RSA_SCHANNEL_PROV
PyModule_AddStringConstant(m, "MS_DEF_RSA_SCHANNEL_PROV",
MS_DEF_RSA_SCHANNEL_PROV);
#endif
#ifdef MS_ENHANCED_RSA_SCHANNEL_PROV
PyModule_AddStringConstant(m, "MS_ENHANCED_RSA_SCHANNEL_PROV",
MS_ENHANCED_RSA_SCHANNEL_PROV);
#endif
#ifdef MS_DEF_DSS_PROV
PyModule_AddStringConstant(m, "MS_DEF_DSS_PROV", MS_DEF_DSS_PROV);
#endif
#ifdef MS_DEF_DSS_DH_PROV
PyModule_AddStringConstant(m, "MS_DEF_DSS_DH_PROV",
MS_DEF_DSS_DH_PROV);
#endif
#ifdef INTEL_DEF_PROV
PyModule_AddStringConstant(m, "INTEL_DEF_PROV", INTEL_DEF_PROV);
#endif
if (PyErr_Occurred())
Py_FatalError("can't initialize module winrandom");
#ifdef IS_PY3K
return m;
#endif
}
/*
CryptGenRandom usage is described in
http://msdn.microsoft.com/library/en-us/security/security/cryptgenrandom.asp
and many associated pages on Windows Cryptographic Service
Providers, which say:
With Microsoft CSPs, CryptGenRandom uses the same
random number generator used by other security
components. This allows numerous processes to
contribute to a system-wide seed. CryptoAPI stores
an intermediate random seed with every user. To form
the seed for the random number generator, a calling
application supplies bits it might havefor instance,
mouse or keyboard timing inputthat are then added to
both the stored seed and various system data and
user data such as the process ID and thread ID, the
system clock, the system time, the system counter,
memory status, free disk clusters, the hashed user
environment block. This result is SHA-1 hashed, and
the output is used to seed an RC4 stream, which is
then used as the random stream and used to update
the stored seed.
The only other detailed description I've found of the
sources of randomness for CryptGenRandom is this excerpt
from a posting
http://www.der-keiler.de/Newsgroups/comp.security.ssh/2002-06/0169.html
From: Jon McClelland (dowot69@hotmail.com)
Date: 06/12/02
...
Windows, call a function such as CryptGenRandom, which has two of
the properties of a good random number generator, unpredictability and
even value distribution. This function, declared in Wincrypt.h, is
available on just about every Windows platform, including Windows 95
with Internet Explorer 3.02 or later, Windows 98, Windows Me, Windows
CE v3, Windows NT 4, Windows 2000, and Windows XP.
CryptGenRandom gets its randomness, also known as entropy, from many
sources in Windows 2000, including the following:
The current process ID (GetCurrentProcessID).
The current thread ID (GetCurrentThreadID).
The ticks since boot (GetTickCount).
The current time (GetLocalTime).
Various high-precision performance counters (QueryPerformanceCounter).
A Message Digest 4 (MD4) hash of the user's environment block, which
includes username, computer name, and search path.
High-precision internal CPU counters, such as RDTSC, RDMSR, RDPMC (x86
only-more information about these counters is at
developer.intel.com/software/idap/resources/technical_collateral/pentiumii/RDTSCPM1.HTM
<http://developer.intel.com>).
Low-level system information, such as idle time, kernel time,
interrupt times, commit limit, page read count, cache read count,
nonpaged pool allocations, alignment fixup count, operating system
lookaside information.
Such information is added to a buffer, which is hashed using MD4 and
used as the key to modify a buffer, using RC4, provided by the user.
(Refer to the CryptGenRandom documentation in the Platform SDK for
more information about the user-provided buffer.) Hence, if the user
provides additional data in the buffer, this is used as an element in
the witches brew to generate the random data. The result is a
cryptographically random number generator.
Also, note that if you plan to sell your software to the United States
federal government, you'll need to use FIPS 140-1-approved algorithms.
The default versions of CryptGenRandom in Microsoft Windows CE v3,
Windows 95, Windows 98, Windows Me, Windows 2000, and Windows XP are
FIPS-approved. Obviously FIPS-140 compliance is necessary but not
sufficient to provide a properly secure source of random data.
*/
/*
[Update: 2007-11-13]
CryptGenRandom does not necessarily provide forward secrecy or reverse
secrecy. See the paper by Leo Dorrendorf and Zvi Gutterman and Benny
Pinkas, _Cryptanalysis of the Random Number Generator of the Windows
Operating System_, Cryptology ePrint Archive, Report 2007/419,
http://eprint.iacr.org/2007/419
*/
#endif /* MS_WIN32 */