From 49b3026574ab692cfabcabe90751b163a76df31b Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Thu, 2 Mar 2017 11:58:07 -0800 Subject: [PATCH] crypto: add secure random reader using getrandom This commit adds in RandReader, a cryptographically secure io.Reader that will fail when the os has insufficient randomness. This is done using the getrandom() syscall in non-blocking mode. see: http://man7.org/linux/man-pages/man2/getrandom.2.html Any kernel new enough to have filesystem encryption will also have this syscall. This RandReader is preferable to the one provided by the standard library in crypto/rand. See the bugs: https://github.com/golang/go/issues/11833 https://github.com/golang/go/issues/19274 This will be removed when go updates the crypto/rand implementation. Change-Id: Icccaf07bc6011b95cd31a5c268e7486807dcffe2 --- crypto/crypto_test.go | 31 ++++++++++++++++++++++ crypto/rand.go | 62 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 crypto/rand.go diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index 025b5b9..7447e40 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -21,6 +21,7 @@ package crypto import ( "bytes" + "compress/zlib" "os" "testing" ) @@ -139,3 +140,33 @@ func TestBadAddKeys(t *testing.T) { t.Error("InsertPolicyKey should fail with bad service") } } + +// Check that we can create random keys. All this test does to test the +// "randomness" is generate a page of random bytes and attempts compression. +// If the data can be compressed it is probably not very random. This isn't +// indented to be a sufficient test for randomness (which is impossible), but a +// way to catch simple regressions (key is all zeros or contains a repeating +// pattern). +func TestRandomKeyGen(t *testing.T) { + key, err := NewRandomKey(os.Getpagesize()) + if err != nil { + t.Fatal(err) + } + defer key.Wipe() + + if didCompress(key.data) { + t.Errorf("Random key (%d bytes) should not be compressible", key.Len()) + } +} + +// didCompress checks if the given data can be compressed. Specifically, it +// returns true if running zlib on the provided input produces a shorter output. +func didCompress(input []byte) bool { + var output bytes.Buffer + + w := zlib.NewWriter(&output) + _, err := w.Write(input) + w.Close() + + return err == nil && len(input) > output.Len() +} diff --git a/crypto/rand.go b/crypto/rand.go new file mode 100644 index 0000000..d9d4cff --- /dev/null +++ b/crypto/rand.go @@ -0,0 +1,62 @@ +/* + * rand.go - Reader used to generate secure random data for fscrypt. + * + * Copyright 2017 Google Inc. + * Author: Joe Richey (joerichey@google.com) + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package crypto + +import ( + "io" + + "golang.org/x/sys/unix" + + "fscrypt/util" +) + +/* +RandReader uses the Linux Getrandom() syscall to read random bytes. If the +operating system has insufficient randomness, the read will fail. This is an +improvement over Go's built-in crypto/rand which will still return bytes if the +system has insufficiency entropy (https://github.com/golang/go/issues/19274). + +While this syscall was only introduced in Kernel v3.17, it predates the +introduction of filesystem encryption, so it introduces no additional +compatibility issues. +*/ +var RandReader io.Reader = randReader{} + +// As we just call into Getrandom, no internal data is needed. +type randReader struct{} + +func (r randReader) Read(buffer []byte) (int, error) { + n, err := unix.Getrandom(buffer, unix.GRND_NONBLOCK) + switch err { + case nil: + return n, nil + case unix.EAGAIN: + return 0, util.SystemErrorF("entropy pool not yet initialized") + case unix.ENOSYS: + return 0, util.SystemErrorF("getrandom not implemented; kernel must be v3.17 or later") + default: + return 0, util.SystemErrorF("cannot get randomness: %v", err) + } +} + +// NewRandomKey creates a random key (from RandReader) of the specified length. +func NewRandomKey(length int) (*Key, error) { + return NewFixedLengthKeyFromReader(RandReader, length) +} -- 2.39.5