290 lines
9.4 KiB
Nim
290 lines
9.4 KiB
Nim
#
|
|
#
|
|
# NimCrypto
|
|
# (c) Copyright 2018 Eugene Kabanov
|
|
#
|
|
# See the file "LICENSE", included in this
|
|
# distribution, for details about the copyright.
|
|
#
|
|
|
|
## This module implements interface to operation system's random number
|
|
## generator.
|
|
##
|
|
## ``Windows`` using BCryptGenRandom (if available),
|
|
## CryptGenRandom(PROV_INTEL_SEC) (if available), RtlGenRandom.
|
|
##
|
|
## RtlGenRandom (available from Windows XP)
|
|
## BCryptGenRandom (available from Windows Vista SP1)
|
|
## CryptGenRandom(PROV_INTEL_SEC) (only when Intel SandyBridge
|
|
## CPU is available).
|
|
##
|
|
## ``Linux`` using genrandom (if available), `/dev/urandom`.
|
|
##
|
|
## ``OpenBSD`` using getentropy.
|
|
##
|
|
## ``NetBSD``, ``FreeBSD``, ``MacOS``, ``Solaris`` using `/dev/urandom`.
|
|
|
|
{.deadCodeElim:on.}
|
|
|
|
when defined(posix):
|
|
import os, posix
|
|
|
|
proc urandomRead(pbytes: pointer, nbytes: int): int =
|
|
result = -1
|
|
var st: Stat
|
|
let fd = posix.open("/dev/urandom", posix.O_RDONLY)
|
|
if fd != -1:
|
|
if posix.fstat(fd, st) != -1 and S_ISCHR(st.st_mode):
|
|
result = 0
|
|
while result < nbytes:
|
|
var p = cast[pointer](cast[uint]((pbytes)) + uint(result))
|
|
var res = posix.read(fd, p, nbytes - result)
|
|
if res > 0:
|
|
result += res
|
|
elif res == 0:
|
|
break
|
|
else:
|
|
if osLastError().int32 != EINTR:
|
|
result = -1
|
|
break
|
|
discard posix.close(fd)
|
|
|
|
when defined(openbsd):
|
|
import posix, os
|
|
|
|
proc getentropy(pBytes: pointer, nBytes: int): cint
|
|
{.importc: "getentropy", header: "<unistd.h>".}
|
|
|
|
proc randomBytes*(pbytes: pointer, nbytes: int): int =
|
|
var p: pointer
|
|
while result < nbytes:
|
|
p = cast[pointer](cast[uint](pbytes) + uint(result))
|
|
let res = getentropy(p, nbytes - result)
|
|
if res > 0:
|
|
result += res
|
|
elif res == 0:
|
|
break
|
|
else:
|
|
if osLastError().int32 != EINTR:
|
|
result = -1
|
|
break
|
|
|
|
if result == -1:
|
|
result = urandomRead(pbytes, nbytes)
|
|
elif result < nbytes:
|
|
p = cast[pointer](cast[uint](pbytes) + uint(result))
|
|
let res = urandomRead(p, nbytes - result)
|
|
if res != -1:
|
|
result += res
|
|
|
|
elif defined(linux):
|
|
import posix, os
|
|
when defined(i386):
|
|
const SYS_getrandom = 355
|
|
elif defined(powerpc64) or defined(powerpc64el) or defined(powerpc):
|
|
const SYS_getrandom = 359
|
|
elif defined(arm64):
|
|
const SYS_getrandom = 278
|
|
elif defined(arm):
|
|
const SYS_getrandom = 384
|
|
elif defined(amd64):
|
|
const SYS_getrandom = 318
|
|
elif defined(mips):
|
|
when sizeof(int) == 8:
|
|
const SYS_getrandom = 4000 + 313
|
|
else:
|
|
const SYS_getrandom = 4000 + 353
|
|
else:
|
|
const SYS_getrandom = 0
|
|
const
|
|
GRND_NONBLOCK = 1
|
|
|
|
type
|
|
SystemRng = ref object of RootRef
|
|
getRandomPresent: bool
|
|
|
|
proc syscall(number: clong): clong {.importc: "syscall",
|
|
header: """#include <unistd.h>
|
|
#include <sys/syscall.h>""", varargs, discardable.}
|
|
|
|
var gSystemRng {.threadvar.}: SystemRng ## System thread global RNG
|
|
|
|
proc newSystemRNG(): SystemRng =
|
|
result = SystemRng()
|
|
|
|
if SYS_getrandom != 0:
|
|
var data: int
|
|
result.getRandomPresent = true
|
|
let res = syscall(SYS_getrandom, addr data, 1, GRND_NONBLOCK)
|
|
if res == -1:
|
|
let err = osLastError().int32
|
|
if err == ENOSYS or err == EPERM:
|
|
result.getRandomPresent = false
|
|
|
|
proc getSystemRNG(): SystemRng =
|
|
if gSystemRng.isNil: gSystemRng = newSystemRng()
|
|
result = gSystemRng
|
|
|
|
proc randomBytes*(pbytes: pointer, nbytes: int): int =
|
|
var p: pointer
|
|
let srng = getSystemRNG()
|
|
if srng.getRandomPresent:
|
|
while result < nbytes:
|
|
p = cast[pointer](cast[uint](pbytes) + uint(result))
|
|
let res = syscall(SYS_getrandom, pBytes, nBytes - result, 0)
|
|
if res > 0:
|
|
result += res
|
|
elif res == 0:
|
|
break
|
|
else:
|
|
if osLastError().int32 != EINTR:
|
|
result = -1
|
|
break
|
|
|
|
if result == -1:
|
|
result = urandomRead(pbytes, nbytes)
|
|
elif result < nbytes:
|
|
p = cast[pointer](cast[uint](pbytes) + uint(result))
|
|
let res = urandomRead(p, nbytes - result)
|
|
if res != -1:
|
|
result += res
|
|
else:
|
|
result = urandomRead(pbytes, nbytes)
|
|
|
|
elif defined(windows):
|
|
import os, winlean, dynlib
|
|
|
|
const
|
|
VER_GREATER_EQUAL = 3'u8
|
|
VER_MINORVERSION = 0x0000001
|
|
VER_MAJORVERSION = 0x0000002
|
|
VER_SERVICEPACKMINOR = 0x0000010
|
|
VER_SERVICEPACKMAJOR = 0x0000020
|
|
PROV_INTEL_SEC = 22
|
|
INTEL_DEF_PROV = "Intel Hardware Cryptographic Service Provider"
|
|
CRYPT_VERIFYCONTEXT = 0xF0000000'i32
|
|
CRYPT_SILENT = 0x00000040'i32
|
|
BCRYPT_USE_SYSTEM_PREFERRED_RNG = 0x00000002
|
|
type
|
|
OSVERSIONINFOEXW {.final, pure.} = object
|
|
dwOSVersionInfoSize: DWORD
|
|
dwMajorVersion: DWORD
|
|
dwMinorVersion: DWORD
|
|
dwBuildNumber: DWORD
|
|
dwPlatformId: DWORD
|
|
szCSDVersion: array[128, Utf16Char]
|
|
wServicePackMajor: uint16
|
|
wServicePackMinor: uint16
|
|
wSuiteMask: uint16
|
|
wProductType: byte
|
|
wReserved: byte
|
|
|
|
HCRYPTPROV = uint
|
|
|
|
BCGRMPROC = proc(hAlgorithm: pointer, pBuffer: pointer, cBuffer: ULONG,
|
|
dwFlags: ULONG): LONG {.stdcall, gcsafe.}
|
|
QPCPROC = proc(hProcess: Handle, cycleTime: var uint64): WINBOOL {.
|
|
stdcall, gcsafe.}
|
|
QUITPROC = proc(itime: var uint64) {.stdcall, gcsafe.}
|
|
QIPCPROC = proc(bufferLength: var uint32, idleTime: ptr uint64): WINBOOL {.
|
|
stdcall, gcsafe.}
|
|
|
|
SystemRng = ref object of RootRef
|
|
bCryptGenRandom: BCGRMPROC
|
|
queryProcessCycleTime: QPCPROC
|
|
queryUnbiasedInterruptTime: QUITPROC
|
|
queryIdleProcessorCycleTime: QIPCPROC
|
|
coresCount: uint32
|
|
hIntel: HCRYPTPROV
|
|
|
|
var gSystemRng {.threadvar.}: SystemRng ## System thread global RNG
|
|
|
|
proc verifyVersionInfo(lpVerInfo: ptr OSVERSIONINFOEXW, dwTypeMask: DWORD,
|
|
dwlConditionMask: uint64): WINBOOL
|
|
{.importc: "VerifyVersionInfoW", stdcall, dynlib: "kernel32.dll".}
|
|
proc verSetConditionMask(conditionMask: uint64, dwTypeMask: DWORD,
|
|
condition: byte): uint64
|
|
{.importc: "VerSetConditionMask", stdcall, dynlib: "kernel32.dll".}
|
|
proc cryptAcquireContext(phProv: ptr HCRYPTPROV, pszContainer: WideCString,
|
|
pszProvider: WideCString, dwProvType: DWORD,
|
|
dwFlags: DWORD): WINBOOL
|
|
{.importc: "CryptAcquireContextW", stdcall, dynlib: "advapi32.dll".}
|
|
proc cryptReleaseContext(phProv: HCRYPTPROV, dwFlags: DWORD): WINBOOL
|
|
{.importc: "CryptReleaseContext", stdcall, dynlib: "advapi32.dll".}
|
|
proc cryptGenRandom(phProv: HCRYPTPROV, dwLen: DWORD,
|
|
pBuffer: pointer): WINBOOL
|
|
{.importc: "CryptGenRandom", stdcall, dynlib: "advapi32.dll".}
|
|
proc rtlGenRandom(bufptr: pointer, buflen: ULONG): WINBOOL
|
|
{.importc: "SystemFunction036", stdcall, dynlib: "advapi32.dll".}
|
|
|
|
proc isEqualOrHigher(major: int, minor: int, servicePack: int): bool =
|
|
var mask = 0'u64
|
|
var ov = OSVERSIONINFOEXW()
|
|
ov.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW).DWORD
|
|
ov.dwMajorVersion = major.DWORD
|
|
ov.dwMinorVersion = minor.DWORD
|
|
ov.wServicePackMajor = servicePack.uint16
|
|
ov.wServicePackMinor = 0
|
|
var typeMask = (VER_MAJORVERSION or VER_MINORVERSION or
|
|
VER_SERVICEPACKMAJOR or VER_SERVICEPACKMINOR).DWORD
|
|
mask = verSetConditionMask(mask, VER_MAJORVERSION, VER_GREATER_EQUAL)
|
|
mask = verSetConditionMask(mask, VER_MINORVERSION, VER_GREATER_EQUAL)
|
|
mask = verSetConditionMask(mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL)
|
|
mask = verSetConditionMask(mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL)
|
|
return (verifyVersionInfo(addr ov, typeMask, mask) == 1)
|
|
|
|
proc newSystemRNG(): SystemRng =
|
|
result = SystemRng()
|
|
if isEqualOrHigher(6, 0, 0):
|
|
if isEqualOrHigher(6, 0, 1):
|
|
let lib = loadLib("bcrypt.dll")
|
|
if lib != nil:
|
|
var lProc = cast[BCGRMPROC](symAddr(lib, "BCryptGenRandom"))
|
|
if not isNil(lProc):
|
|
result.bCryptGenRandom = lProc
|
|
|
|
var hp: HCRYPTPROV = 0
|
|
let intelDef = newWideCString(INTEL_DEF_PROV)
|
|
let res1 = cryptAcquireContext(addr hp, nil, intelDef, PROV_INTEL_SEC,
|
|
CRYPT_VERIFYCONTEXT or CRYPT_SILENT).bool
|
|
if res1:
|
|
result.hIntel = hp
|
|
|
|
proc getSystemRNG(): SystemRng =
|
|
if gSystemRng.isNil: gSystemRng = newSystemRng()
|
|
result = gSystemRng
|
|
|
|
proc randomBytes*(pbytes: pointer, nbytes: int): int =
|
|
let srng = getSystemRNG()
|
|
result = -1
|
|
if not isNil(srng.bCryptGenRandom):
|
|
if srng.bCryptGenRandom(nil, pbytes, nbytes.ULONG,
|
|
BCRYPT_USE_SYSTEM_PREFERRED_RNG) == 0:
|
|
result = nbytes
|
|
|
|
if srng.hIntel != 0 and result == -1:
|
|
if cryptGenRandom(srng.hIntel, nbytes.DWORD, pbytes) != 0:
|
|
result = nbytes
|
|
|
|
if result == -1:
|
|
if rtlGenRandom(pBytes, nbytes.ULONG) != 0:
|
|
result = nbytes
|
|
|
|
proc randomClose*() =
|
|
let srng = getSystemRNG()
|
|
if srng.hIntel != 0:
|
|
if cryptReleaseContext(srng.hIntel, 0) == 0:
|
|
raiseOsError(osLastError())
|
|
else:
|
|
import posix, os
|
|
|
|
proc randomBytes*(pbytes: pointer, nbytes: int): int =
|
|
result = urandomRead(pbytes, nbytes)
|
|
|
|
proc randomBytes*[T](bytes: var openarray[T]): int =
|
|
assert(len(bytes) > 0)
|
|
let length = len(bytes) * sizeof(T)
|
|
result = randomBytes(addr bytes[0], length)
|
|
if result != -1:
|
|
result = result div sizeof(T)
|