Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: crystal-lang/crystal
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 77dc00a77608
Choose a base ref
...
head repository: crystal-lang/crystal
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 78e5206f4067
Choose a head ref
  • 3 commits
  • 10 files changed
  • 2 contributors

Commits on Jul 6, 2016

  1. Use getrandom and /dev/urandom to generate random bytes

    The implementation follows libsodium's sysrandom implementation, but
    limited to `getrandom(2)` (linux) and `/dev/urandom` since we don't
    support the other platforms yet (Windows, OpenBSD) and the `arc4random`
    functions don't seem safe on FreeBSD (?).
    
    closes #2792
    ysbaddaden committed Jul 6, 2016
    Copy the full SHA
    e776bce View commit details

Commits on Jul 7, 2016

  1. 3
    Copy the full SHA
    d99f402 View commit details

Commits on Sep 1, 2016

  1. Merge pull request #2959 from ysbaddaden/std-securerandom-getrandom-u…

    …random
    
    SecureRandom: use getrandom (linux) and /dev/urandom
    Ary Borenszweig authored Sep 1, 2016
    Copy the full SHA
    78e5206 View commit details
5 changes: 5 additions & 0 deletions spec/std/secure_random_spec.cr
Original file line number Diff line number Diff line change
@@ -63,6 +63,11 @@ describe SecureRandom do
bytes = SecureRandom.random_bytes(50)
bytes.size.should eq(50)
end

it "fully fills a large buffer" do
bytes = SecureRandom.random_bytes(10000)
bytes[9990, 10].should_not eq(Slice(UInt8).new(10))
end
end

describe "uuid" do
3 changes: 3 additions & 0 deletions src/lib_c/i686-linux-gnu/c/sys/syscall.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
lib LibC
SYS_getrandom = 318
end
1 change: 1 addition & 0 deletions src/lib_c/i686-linux-gnu/c/unistd.cr
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@ lib LibC
fun read(fd : Int, buf : Void*, nbytes : SizeT) : SSizeT
fun rmdir(path : Char*) : Int
fun symlink(from : Char*, to : Char*) : Int
fun syscall(sysno : Long, ...) : Long
fun sysconf(name : Int) : Long
fun unlink(name : Char*) : Int
fun write(fd : Int, buf : Void*, n : SizeT) : SSizeT
3 changes: 3 additions & 0 deletions src/lib_c/i686-linux-musl/c/sys/syscall.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
lib LibC
SYS_getrandom = 318
end
1 change: 1 addition & 0 deletions src/lib_c/i686-linux-musl/c/unistd.cr
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@ lib LibC
fun read(x0 : Int, x1 : Void*, x2 : SizeT) : SSizeT
fun rmdir(x0 : Char*) : Int
fun symlink(x0 : Char*, x1 : Char*) : Int
fun syscall(x0 : Long, ...) : Long
fun sysconf(x0 : Int) : Long
fun unlink(x0 : Char*) : Int
fun write(x0 : Int, x1 : Void*, x2 : SizeT) : SSizeT
3 changes: 3 additions & 0 deletions src/lib_c/x86_64-linux-gnu/c/sys/syscall.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
lib LibC
SYS_getrandom = 318
end
1 change: 1 addition & 0 deletions src/lib_c/x86_64-linux-gnu/c/unistd.cr
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@ lib LibC
fun read(fd : Int, buf : Void*, nbytes : SizeT) : SSizeT
fun rmdir(path : Char*) : Int
fun symlink(from : Char*, to : Char*) : Int
fun syscall(sysno : Long, ...) : Long
fun sysconf(name : Int) : Long
fun unlink(name : Char*) : Int
fun write(fd : Int, buf : Void*, n : SizeT) : SSizeT
3 changes: 3 additions & 0 deletions src/lib_c/x86_64-linux-musl/c/sys/syscall.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
lib LibC
SYS_getrandom = 318
end
1 change: 1 addition & 0 deletions src/lib_c/x86_64-linux-musl/c/unistd.cr
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@ lib LibC
fun read(x0 : Int, x1 : Void*, x2 : SizeT) : SSizeT
fun rmdir(x0 : Char*) : Int
fun symlink(x0 : Char*, x1 : Char*) : Int
fun syscall(x0 : Long, ...) : Long
fun sysconf(x0 : Int) : Long
fun unlink(x0 : Char*) : Int
fun write(x0 : Int, x1 : Void*, x2 : SizeT) : SSizeT
101 changes: 89 additions & 12 deletions src/secure_random.cr
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
require "base64"
{% if !flag?(:without_openssl) %}
require "openssl/lib_crypto"

{% if flag?(:linux) %}
require "c/unistd"
require "c/sys/syscall"
{% end %}

# The SecureRandom module is an interface for creating secure random values in different formats.
# It uses the RNG (random number generator) of libcrypto (OpenSSL).
# The SecureRandom module is an interface for creating cryptography secure
# random values in different formats.
#
# For example:
# Examples:
# ```crystal
# SecureRandom.base64 # => "LIa9s/zWzJx49m/9zDX+VQ=="
# SecureRandom.hex # => "c8353864ff9764a39ef74983ec0d4a38"
# SecureRandom.uuid # => "c7ee4add-207f-411a-97b7-0d22788566d6"
# ```
#
# The implementation follows the
# [libsodium sysrandom](https://github.com/jedisct1/libsodium/blob/6fad3644b53021fb377ca1207fa6e1ac96d0b131/src/libsodium/randombytes/sysrandom/randombytes_sysrandom.c)
# implementation and uses `getrandom` on Linux (when provided by the kernel),
# then tries to read from `/dev/urandom`.
module SecureRandom
@@initialized = false

# Generates *n* random bytes that are encoded into Base64.
#
# Check `Base64#strict_encode` for details.
@@ -60,16 +69,84 @@ module SecureRandom
raise ArgumentError.new "negative size: #{n}"
end

slice = Slice(UInt8).new(n)
result = LibCrypto.rand_bytes slice, n
if result != 1
error = LibCrypto.err_get_error
error_string = String.new LibCrypto.err_error_string(error, nil)
raise error_string
init unless @@initialized

{% if flag?(:linux) %}
if @@getrandom_available
return getrandom(n)
end
{% end %}

buf = Slice(UInt8).new(n)

if urandom = @@urandom
urandom.read_fully(buf)
return buf
end
slice

raise "Failed to access secure source to generate random bytes!"
end

private def self.init
@@initialized = true

{% if flag?(:linux) %}
if getrandom(Slice(UInt8).new(16)) >= 0
@@getrandom_available = true
return
end
{% end %}

@@urandom = urandom = File.open("/dev/urandom", "r")
urandom.sync = true # don't buffer bytes
end

# TODO: remove after 0.19.0 is released
@@getrandom_available : Bool?

{% if flag?(:linux) %}
@@getrandom_available = false

# Reads n random bytes using the Linux `getrandom(2)` syscall.
private def self.getrandom(n : Int)
Slice(UInt8).new(n).tap do |buf|
# getrandom(2) may only read up to 256 bytes at once without being
# interrupted or returning early
chunk_size = 256

while buf.size > 0
if buf.size < chunk_size
chunk_size = buf.size
end

read_bytes = getrandom(buf[0, chunk_size])
raise Errno.new("getrandom") if read_bytes == -1

buf += read_bytes
end
end
end

# Low-level wrapper for the `getrandom(2)` syscall, returns the number of
# bytes read or `-1` if an error occured (or the syscall isn't available)
# and sets `Errno.value`.
#
# We use the kernel syscall instead of the `getrandom` C function so any
# binary compiled for Linux will always use getrandom if the kernel is 3.17+
# and silently fallback to read from /dev/urandom if not (so it's more
# portable).
private def self.getrandom(buf : Slice(UInt8))
loop do
read_bytes = LibC.syscall(LibC::SYS_getrandom, buf, LibC::SizeT.new(buf.size), 0)
if read_bytes < 0 && (Errno.value == Errno::EINTR || Errno.value == Errno::EAGAIN)
Fiber.yield
else
return read_bytes
end
end
end
{% end %}

# Generates a UUID (Universally Unique Identifier)
#
# It generates a random v4 UUID. Check [RFC 4122 Section 4.4](https://tools.ietf.org/html/rfc4122#section-4.4)