From 3e6e004ea0f9ab5c1f2f052e88dd4111ee5c5bd7 Mon Sep 17 00:00:00 2001 From: Oleg Yukhnevich Date: Mon, 13 Mar 2023 19:53:02 +0300 Subject: [PATCH] reimplement linux random: * support getrandom sys call * use getrandom by default with fallback to urandom * cache file descriptor for urandom in ThreadLocal * test both urandom and getrandom implementations --- cryptography-random/build.gradle.kts | 8 ++++ cryptography-random/linux.def | 2 + .../sources/linux/CryptographyRandom.linux.kt | 21 +------- .../main/sources/linux/GetRandom.kt | 28 +++++++++++ .../main/sources/linux/LinuxRandom.kt | 18 +++++++ .../main/sources/linux/URandom.kt | 48 +++++++++++++++++++ .../main/sources/linux/errno.kt | 13 +++++ .../test/sources/linux/LinuxRandomTest.kt | 5 ++ 8 files changed, 123 insertions(+), 20 deletions(-) create mode 100644 cryptography-random/linux.def create mode 100644 cryptography-random/main/sources/linux/GetRandom.kt create mode 100644 cryptography-random/main/sources/linux/LinuxRandom.kt create mode 100644 cryptography-random/main/sources/linux/URandom.kt create mode 100644 cryptography-random/main/sources/linux/errno.kt create mode 100644 cryptography-random/test/sources/linux/LinuxRandomTest.kt diff --git a/cryptography-random/build.gradle.kts b/cryptography-random/build.gradle.kts index 01a76559..e97ca1af 100644 --- a/cryptography-random/build.gradle.kts +++ b/cryptography-random/build.gradle.kts @@ -9,4 +9,12 @@ description = "cryptography-kotlin random API" kotlin { allTargets() + + linuxX64 { + val main by compilations.getting { + val random by cinterops.creating { + defFile("linux.def") + } + } + } } diff --git a/cryptography-random/linux.def b/cryptography-random/linux.def new file mode 100644 index 00000000..bd7e1623 --- /dev/null +++ b/cryptography-random/linux.def @@ -0,0 +1,2 @@ +package = dev.whyoleg.cryptography.random.internal.cinterop +headers = linux/random.h diff --git a/cryptography-random/main/sources/linux/CryptographyRandom.linux.kt b/cryptography-random/main/sources/linux/CryptographyRandom.linux.kt index b4c29ab2..a9ccd2a4 100644 --- a/cryptography-random/main/sources/linux/CryptographyRandom.linux.kt +++ b/cryptography-random/main/sources/linux/CryptographyRandom.linux.kt @@ -1,22 +1,3 @@ package dev.whyoleg.cryptography.random -import kotlinx.cinterop.* -import platform.posix.* - -internal actual fun defaultCryptographyRandom(): CryptographyRandom = URandomCryptographyRandom - -private object URandomCryptographyRandom : PlatformRandom() { - override fun fillBytes(array: ByteArray) { - val file = checkNotNull(fopen("/dev/urandom", "rb")) { "Failed to open /dev/urandom" } - val result = array.usePinned { pinned -> - fread( - __ptr = pinned.addressOf(0), - __size = 1.convert(), - __n = pinned.get().size.convert(), - __stream = file - ) - } - fclose(file) - if (result <= 0UL) error("Failed to read from /dev/urandom") - } -} +internal actual fun defaultCryptographyRandom(): CryptographyRandom = createGetRandom() ?: createURandom() diff --git a/cryptography-random/main/sources/linux/GetRandom.kt b/cryptography-random/main/sources/linux/GetRandom.kt new file mode 100644 index 00000000..6c99cf36 --- /dev/null +++ b/cryptography-random/main/sources/linux/GetRandom.kt @@ -0,0 +1,28 @@ +package dev.whyoleg.cryptography.random + +import dev.whyoleg.cryptography.random.internal.cinterop.* +import kotlinx.cinterop.* +import platform.linux.* +import platform.posix.* + +internal fun createGetRandom(): CryptographyRandom? = if (getRandomAvailable()) GetRandom else null + +private object GetRandom : LinuxRandom() { + override fun fillBytes(pointer: CPointer, size: Int): Int = getrandom(pointer, size.convert(), 0.convert()) +} + +private fun getRandomAvailable(): Boolean { + val stubArray = ByteArray(1) + val stubSize = stubArray.size + stubArray.usePinned { + if (getrandom(it.addressOf(0), stubSize.convert(), GRND_NONBLOCK.convert()) >= 0) return true + } + + return when (errno) { + ENOSYS, EPERM -> false + else -> true + } +} + +private fun getrandom(out: CPointer?, outSize: size_t, flags: UInt): Int = + syscall(SYS_getrandom.convert(), out, outSize, flags).convert() diff --git a/cryptography-random/main/sources/linux/LinuxRandom.kt b/cryptography-random/main/sources/linux/LinuxRandom.kt new file mode 100644 index 00000000..910fc4d8 --- /dev/null +++ b/cryptography-random/main/sources/linux/LinuxRandom.kt @@ -0,0 +1,18 @@ +package dev.whyoleg.cryptography.random + +import kotlinx.cinterop.* + +internal abstract class LinuxRandom : PlatformRandom() { + protected abstract fun fillBytes(pointer: CPointer, size: Int): Int + final override fun fillBytes(array: ByteArray) { + val size = array.size + array.usePinned { + var filled = 0 + while (filled < size) { + val chunkSize = fillBytes(it.addressOf(filled), size - filled) + if (chunkSize < 0) errnoCheck() + filled += chunkSize + } + } + } +} diff --git a/cryptography-random/main/sources/linux/URandom.kt b/cryptography-random/main/sources/linux/URandom.kt new file mode 100644 index 00000000..86bfb718 --- /dev/null +++ b/cryptography-random/main/sources/linux/URandom.kt @@ -0,0 +1,48 @@ +package dev.whyoleg.cryptography.random + +import kotlinx.cinterop.* +import platform.posix.* + +internal fun createURandom(): CryptographyRandom { + awaitURandomReady() + return URandom +} + +private object URandom : LinuxRandom() { + override fun fillBytes(pointer: CPointer, size: Int): Int = read(FD.value, pointer, size.convert()).convert() +} + +@ThreadLocal +private object FD { + val value = open("/dev/urandom") +} + +private fun awaitURandomReady() { + val randomFd = open("/dev/random") + try { + memScoped { + val pollFd = alloc { + fd = randomFd + events = POLLIN.convert() + revents = 0 + } + + while (true) { + if (poll(pollFd.ptr, 1, -1) >= 0) break + + when (errno) { + EINTR, EAGAIN -> continue + else -> errnoCheck() + } + } + } + } finally { + close(randomFd) + } +} + +private fun open(path: String): Int { + val fd = open(path, O_RDONLY, null) + if (fd <= 0) errnoCheck() + return fd +} diff --git a/cryptography-random/main/sources/linux/errno.kt b/cryptography-random/main/sources/linux/errno.kt new file mode 100644 index 00000000..9a611ac7 --- /dev/null +++ b/cryptography-random/main/sources/linux/errno.kt @@ -0,0 +1,13 @@ +package dev.whyoleg.cryptography.random + +import platform.posix.* + +internal fun errnoCheck(): Nothing { + val message = when (val value = errno) { + EFAULT -> "The address referred to by buf is outside the accessible address space." + EINTR -> "The call was interrupted by a signal handler; see the description of how interrupted read(2) calls on 'slow' devices are handled with and without the SA_RESTART flag in the signal(7) man page." + EINVAL -> "An invalid flag was specified in flags." + else -> "POSIX error: $value" + } + error(message) +} diff --git a/cryptography-random/test/sources/linux/LinuxRandomTest.kt b/cryptography-random/test/sources/linux/LinuxRandomTest.kt new file mode 100644 index 00000000..7095dfea --- /dev/null +++ b/cryptography-random/test/sources/linux/LinuxRandomTest.kt @@ -0,0 +1,5 @@ +package dev.whyoleg.cryptography.random + +class URandomTest : CryptographyRandomTest(createURandom()) + +class GetRandomTest : CryptographyRandomTest(createGetRandom()!!)