From 78a79955b68acf13928df15e4af0bba9d597232d Mon Sep 17 00:00:00 2001 From: Max Kanat-Alexander Date: Thu, 4 Aug 2011 16:05:11 -0700 Subject: Bug 658407: Make Bugzilla not use Math::Random::Secure anymore, due to the difficulty of installing its dependencies. Instead move the code directly into Bugzilla itself. r=LpSolit, r=glob, a=mkanat --- Bugzilla/RNG.pm | 233 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 Bugzilla/RNG.pm (limited to 'Bugzilla/RNG.pm') diff --git a/Bugzilla/RNG.pm b/Bugzilla/RNG.pm new file mode 100644 index 000000000..caa63bae2 --- /dev/null +++ b/Bugzilla/RNG.pm @@ -0,0 +1,233 @@ +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Bug Tracking System. +# +# The Initial Developer of the Original Code is Google Inc. +# Portions created by the Initial Developer are Copyright (C) 2011 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Max Kanat-Alexander + +package Bugzilla::RNG; +use strict; +use base qw(Exporter); +use Bugzilla::Constants qw(ON_WINDOWS); + +use IO::File; +use Math::Random::ISAAC; +use if ON_WINDOWS, 'Win32::API'; + +our $RNG; +our @EXPORT_OK = qw(rand srand irand); + +# ISAAC, a 32-bit generator, should only be capable of generating numbers +# between 0 and 2^32 - 1. We want _to_float to generate numbers possibly +# including 0, but always less than 1.0. Dividing the integer produced +# by irand() by this number should do that exactly. +use constant DIVIDE_BY => 2**32; + +# How many bytes of seed to read. +use constant SEED_SIZE => 16; # 128 bits. + +################# +# Windows Stuff # +################# + +# The type of cryptographic service provider we want to use. +# This doesn't really matter for our purposes, so we just pick +# PROV_RSA_FULL, which seems reasonable. For more info, see +# http://msdn.microsoft.com/en-us/library/aa380244(v=VS.85).aspx +use constant PROV_RSA_FULL => 1; + +# Flags for CryptGenRandom: +# Don't ever display a UI to the user, just fail if one would be needed. +use constant CRYPT_SILENT => 64; +# Don't require existing public/private keypairs. +use constant CRYPT_VERIFYCONTEXT => 0xF0000000; + +# For some reason, BOOLEAN doesn't work properly as a return type with +# Win32::API. +use constant RTLGENRANDOM_PROTO => <irand(); + if (defined $limit) { + # We can't just use the mod operator because it will bias + # our output. Search for "modulo bias" on the Internet for + # details. This is slower than mod(), but does not have a bias, + # as demonstrated by Math::Random::Secure's uniform.t test. + return int(_to_float($int, $limit)); + } + return $int; +} + +sub srand (;$) { + my ($value) = @_; + # Remove any RNG that might already have been made. + $RNG = undef; + my %args; + if (defined $value) { + $args{seed} = $value; + } + $RNG = _create_rng(\%args); +} + +sub _to_float { + my ($integer, $limit) = @_; + $limit ||= 1; + return ($integer / DIVIDE_BY) * $limit; +} + +########################## +# Seed and PRNG Creation # +########################## + +sub _create_rng { + my ($params) = @_; + + if (!defined $params->{seed}) { + $params->{seed} = _get_seed(); + } + + _check_seed($params->{seed}); + + my @seed_ints = unpack('L*', $params->{seed}); + + my $rng = Math::Random::ISAAC->new(@seed_ints); + + # It's faster to skip the frontend interface of Math::Random::ISAAC + # and just use the backend directly. However, in case the internal + # code of Math::Random::ISAAC changes at some point, we do make sure + # that the {backend} element actually exists first. + return $rng->{backend} ? $rng->{backend} : $rng; +} + +sub _check_seed { + my ($seed) = @_; + if (length($seed) < 8) { + warn "Your seed is less than 8 bytes (64 bits). It could be" + . " easy to crack"; + } + # If it looks like we were seeded with a 32-bit integer, warn the + # user that they are making a dangerous, easily-crackable mistake. + elsif (length($seed) <= 10 and $seed =~ /^\d+$/) { + warn "RNG seeded with a 32-bit integer, this is easy to crack"; + } +} + +sub _get_seed { + return _windows_seed() if ON_WINDOWS; + + if (-r '/dev/urandom') { + return _read_seed_from('/dev/urandom'); + } + + return _read_seed_from('/dev/random'); +} + +sub _read_seed_from { + my ($from) = @_; + + my $fh = IO::File->new($from, "r") or die "$from: $!"; + my $buffer; + $fh->read($buffer, SEED_SIZE); + if (length($buffer) < SEED_SIZE) { + die "Could not read enough seed bytes from $from, got only " + . length($buffer); + } + $fh->close; + return $buffer; +} + +sub _windows_seed { + my ($major, $minor) = (Win32::GetOSVersion())[1,2]; + if ($major < 5) { + die "Bugzilla does not support versions of Windows before" + . " Windows 2000"; + } + # This means Windows 2000. + if ($major == 5 and $minor == 0) { + return _win2k_seed(); + } + + my $rtlgenrand = Win32::API->new('advapi32', RTLGENRANDOM_PROTO); + if (!defined $rtlgenrand) { + die "Could not import RtlGenRand: $^E"; + } + my $buffer = chr(0) x SEED_SIZE; + my $result = $rtlgenrand->Call($buffer, SEED_SIZE); + if (!$result) { + die "RtlGenRand failed: $^E"; + } + return $buffer; +} + +sub _win2k_seed { + my $crypt_acquire = Win32::API->new( + "advapi32", 'CryptAcquireContext', 'PPPNN', 'I'); + if (!defined $crypt_acquire) { + die "Could not import CryptAcquireContext: $^E"; + } + + my $crypt_release = Win32::API->new( + "advapi32", 'CryptReleaseContext', 'NN', 'I'); + if (!defined $crypt_release) { + die "Could not import CryptReleaseContext: $^E"; + } + + my $crypt_gen_random = Win32::API->new( + "advapi32", 'CryptGenRandom', 'NNP', 'I'); + if (!defined $crypt_gen_random) { + die "Could not import CryptGenRandom: $^E"; + } + + my $context = chr(0) x Win32::API::Type->sizeof('PULONG'); + my $acquire_result = $crypt_acquire->Call( + $context, 0, 0, PROV_RSA_FULL, CRYPT_SILENT | CRYPT_VERIFYCONTEXT); + if (!defined $acquire_result) { + die "CryptAcquireContext failed: $^E"; + } + + my $pack_type = Win32::API::Type::packing('PULONG'); + $context = unpack($pack_type, $context); + + my $buffer = chr(0) x SEED_SIZE; + my $rand_result = $crypt_gen_random->Call($context, SEED_SIZE, $buffer); + my $rand_error = $^E; + # We don't check this if it fails, we don't care. + $crypt_release->Call($context, 0); + if (!defined $rand_result) { + die "CryptGenRandom failed: $rand_error"; + } + return $buffer; +} + +1; -- cgit v1.2.3-24-g4f1b