diff --git a/src/libcalamares/CMakeLists.txt b/src/libcalamares/CMakeLists.txt index 8b4233659..0e7f7c6e7 100644 --- a/src/libcalamares/CMakeLists.txt +++ b/src/libcalamares/CMakeLists.txt @@ -39,7 +39,7 @@ set( libSources # Modules modulesystem/InstanceKey.cpp - + # Network service network/Manager.cpp @@ -50,6 +50,7 @@ set( libSources utils/CalamaresUtilsSystem.cpp utils/CommandList.cpp utils/Dirs.cpp + utils/Entropy.cpp utils/Logger.cpp utils/PluginFactory.cpp utils/Retranslator.cpp diff --git a/src/libcalamares/utils/Entropy.cpp b/src/libcalamares/utils/Entropy.cpp new file mode 100644 index 000000000..643346855 --- /dev/null +++ b/src/libcalamares/utils/Entropy.cpp @@ -0,0 +1,124 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019-2020, Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include "Entropy.h" + +#include + +#include + +CalamaresUtils::EntropySource +CalamaresUtils::getEntropy( int size, QByteArray& b ) +{ + b.clear(); + if ( size < 1 ) + { + return EntropySource::None; + } + + b.resize( size ); + char* buffer = b.data(); + std::fill( buffer, buffer + size, 0xcb ); + + int readSize = 0; + QFile urandom( "/dev/urandom" ); + if ( urandom.exists() && urandom.open( QIODevice::ReadOnly ) ) + { + readSize = urandom.read( buffer, size ); + urandom.close(); + } + + if ( readSize >= size ) + { + return EntropySource::URandom; + } + + // If it wasn't available, or did not return enough bytes, + // complete it with twister (and tell the client). + std::random_device r; + std::seed_seq seed { r(), r(), r(), r(), r(), r(), r(), r() }; + std::mt19937_64 twister( seed ); + + std::uint64_t next = 0; + do + { + next = twister(); + // Eight times, for a 64-bit next +#define GET_ONE_BYTE \ + if ( readSize < size ) \ + { \ + buffer[ readSize++ ] = next & 0xff; \ + next = next >> 8; \ + } + GET_ONE_BYTE + GET_ONE_BYTE + GET_ONE_BYTE + GET_ONE_BYTE + GET_ONE_BYTE + GET_ONE_BYTE + GET_ONE_BYTE + GET_ONE_BYTE + } while ( readSize < size ); + + return EntropySource::Twister; +} + +CalamaresUtils::EntropySource +CalamaresUtils::getPrintableEntropy( int size, QString& s ) +{ + s.clear(); + if ( size < 1 ) + { + return EntropySource::None; + } + + static const char salt_chars[] = { '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; + static_assert( sizeof( salt_chars ) == 64, "Missing salt_chars" ); + + // Number of bytes we're going to need + int byteSize = ( ( size * 6 ) / 8 ) + 1; + QByteArray b; + EntropySource r = getEntropy( byteSize, b ); + + int bitsLeft = 0; + int byteOffset = 0; + qint64 next = 0; + do + { + if ( bitsLeft < 6 ) + { + next = ( next << 8 ) | b.at( byteOffset++ ); + bitsLeft += 8; + } + char c = salt_chars[ next & 0b0111111 ]; + next >>= 6; + bitsLeft -= 6; + s.append( c ); + } while ( ( s.length() < size ) && ( byteOffset < b.size() ) ); + + if ( s.length() < size ) + { + // It's incomplete, not really no-entropy + return EntropySource::None; + } + + return r; +} diff --git a/src/libcalamares/utils/Entropy.h b/src/libcalamares/utils/Entropy.h new file mode 100644 index 000000000..2ab7a609d --- /dev/null +++ b/src/libcalamares/utils/Entropy.h @@ -0,0 +1,53 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019-2020, Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef UTILS_ENTROPY_H +#define UTILS_ENTROPY_H + +#include "DllMacro.h" + +#include + +namespace CalamaresUtils +{ +/// @brief Which entropy source was actually used for the entropy. +enum class EntropySource +{ + None, ///< Buffer is empty, no random data + URandom, ///< Read from /dev/urandom + Twister ///< Generated by pseudo-random +}; + +/** @brief Fill buffer @p b with exactly @p size random bytes + * + * The array is cleared and resized, then filled with 0xcb + * "just in case", after which it is filled with random + * bytes from a suitable source. Returns which source was used. + */ +DLLEXPORT EntropySource getEntropy( int size, QByteArray& b ); + +/** @brief Fill string @p s with exactly @p size random printable ASCII characters + * + * The characters are picked from a set of 64 (2^6). The string + * contains 6 * size bits of entropy. * Returns which source was used. + * @see getEntropy + */ +DLLEXPORT EntropySource getPrintableEntropy( int size, QString& s ); +} // namespace CalamaresUtils + +#endif diff --git a/src/libcalamares/utils/Tests.cpp b/src/libcalamares/utils/Tests.cpp index 97edd0bc4..16faec9a1 100644 --- a/src/libcalamares/utils/Tests.cpp +++ b/src/libcalamares/utils/Tests.cpp @@ -19,6 +19,7 @@ #include "Tests.h" #include "CalamaresUtilsSystem.h" +#include "Entropy.h" #include "Logger.h" #include "UMask.h" #include "Yaml.h" @@ -176,3 +177,47 @@ LibCalamaresTests::testUmask() QCOMPARE( CalamaresUtils::setUMask( 022 ), m ); QCOMPARE( CalamaresUtils::setUMask( m ), 022 ); } + +void +LibCalamaresTests::testEntropy() +{ + QByteArray data; + + auto r0 = CalamaresUtils::getEntropy( 0, data ); + QCOMPARE( CalamaresUtils::EntropySource::None, r0 ); + QCOMPARE( data.size(), 0 ); + + auto r1 = CalamaresUtils::getEntropy( 16, data ); + QVERIFY( r1 != CalamaresUtils::EntropySource::None ); + QCOMPARE( data.size(), 16 ); + // This can randomly fail (but not often) + QVERIFY( data.at( data.size() - 1 ) != char( 0xcb ) ); + + auto r2 = CalamaresUtils::getEntropy( 8, data ); + QVERIFY( r2 != CalamaresUtils::EntropySource::None ); + QCOMPARE( data.size(), 8 ); + QCOMPARE( r1, r2 ); + // This can randomly fail (but not often) + QVERIFY( data.at( data.size() - 1 ) != char( 0xcb ) ); +} + +void +LibCalamaresTests::testPrintableEntropy() +{ + QString s; + + auto r0 = CalamaresUtils::getPrintableEntropy( 0, s ); + QCOMPARE( CalamaresUtils::EntropySource::None, r0 ); + QCOMPARE( s.length(), 0 ); + + auto r1 = CalamaresUtils::getPrintableEntropy( 16, s ); + QVERIFY( r1 != CalamaresUtils::EntropySource::None ); + QCOMPARE( s.length(), 16 ); + for ( QChar c : s ) + { + QVERIFY( c.isPrint() ); + QCOMPARE( c.row(), 0 ); + QVERIFY( c.cell() > 32 ); // ASCII SPACE + QVERIFY( c.cell() < 127 ); + } +} diff --git a/src/libcalamares/utils/Tests.h b/src/libcalamares/utils/Tests.h index d9140e0d0..d369ed4cb 100644 --- a/src/libcalamares/utils/Tests.h +++ b/src/libcalamares/utils/Tests.h @@ -39,6 +39,10 @@ private Q_SLOTS: /** @brief Test that all the UMask objects work correctly. */ void testUmask(); + + /** @brief Tests the entropy functions. */ + void testEntropy(); + void testPrintableEntropy(); }; #endif diff --git a/src/modules/machineid/Workers.cpp b/src/modules/machineid/Workers.cpp index 178f03de5..fefaf24b9 100644 --- a/src/modules/machineid/Workers.cpp +++ b/src/modules/machineid/Workers.cpp @@ -3,7 +3,7 @@ * Copyright 2014, Kevin Kofler * Copyright 2016, Philip Müller * Copyright 2017, Alf Gaida - * Copyright 2019, Adriaan de Groot + * Copyright 2019-2020, Adriaan de Groot * * Calamares is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,6 +22,7 @@ #include "Workers.h" #include "utils/CalamaresUtilsSystem.h" +#include "utils/Entropy.h" #include "utils/Logger.h" #include @@ -83,7 +84,7 @@ getUrandomPoolSize() { if ( v.endsWith( '\n' ) ) { - v.chop(1); + v.chop( 1 ); } bool ok = false; poolSize = v.toInt( &ok ); @@ -93,44 +94,42 @@ getUrandomPoolSize() } } } - return (poolSize >= minimumPoolSize) ? poolSize : minimumPoolSize; + return ( poolSize >= minimumPoolSize ) ? poolSize : minimumPoolSize; } Calamares::JobResult createNewEntropy( int poolSize, const QString& rootMountPoint, const QString& fileName ) { - QFile urandom( "/dev/urandom" ); - if ( urandom.exists() && urandom.open( QIODevice::ReadOnly ) ) + QFile entropyFile( rootMountPoint + fileName ); + if ( entropyFile.exists() ) { - QByteArray data = urandom.read( poolSize ); - urandom.close(); - - QFile entropyFile( rootMountPoint + fileName ); - if ( entropyFile.exists() ) - { - cWarning() << "Entropy file" << ( rootMountPoint + fileName ) << "already exists."; - return Calamares::JobResult::ok(); // .. anyway - } - if ( !entropyFile.open( QIODevice::WriteOnly ) ) - { - return Calamares::JobResult::error( - QObject::tr( "File not found" ), - QObject::tr( "Could not create new random file
%1
." ).arg( fileName ) ); - } - entropyFile.write( data ); - entropyFile.close(); - if ( entropyFile.size() < data.length() ) - { - cWarning() << "Entropy file is" << entropyFile.size() << "bytes, random data was" << data.length(); - } - if ( data.length() < poolSize ) - { - cWarning() << "Entropy data is" << data.length() << "bytes, rather than poolSize" << poolSize; - } + cWarning() << "Entropy file" << ( rootMountPoint + fileName ) << "already exists."; + return Calamares::JobResult::ok(); // .. anyway } - return Calamares::JobResult::error( - QObject::tr( "File not found" ), - QObject::tr( "Could not read random file
%1
." ).arg( QStringLiteral( "/dev/urandom" ) ) ); + if ( !entropyFile.open( QIODevice::WriteOnly ) ) + { + return Calamares::JobResult::error( + QObject::tr( "File not found" ), + QObject::tr( "Could not create new random file
%1
." ).arg( fileName ) ); + } + + QByteArray data; + CalamaresUtils::EntropySource source = CalamaresUtils::getEntropy( poolSize, data ); + entropyFile.write( data ); + entropyFile.close(); + if ( entropyFile.size() < data.length() ) + { + cWarning() << "Entropy file is" << entropyFile.size() << "bytes, random data was" << data.length(); + } + if ( data.length() < poolSize ) + { + cWarning() << "Entropy data is" << data.length() << "bytes, rather than poolSize" << poolSize; + } + if ( source != CalamaresUtils::EntropySource::URandom ) + { + cWarning() << "Entropy data for pool is low-quality."; + } + return Calamares::JobResult::ok(); } diff --git a/src/modules/users/SetPasswordJob.cpp b/src/modules/users/SetPasswordJob.cpp index 325d24fe3..363a04ccc 100644 --- a/src/modules/users/SetPasswordJob.cpp +++ b/src/modules/users/SetPasswordJob.cpp @@ -22,6 +22,7 @@ #include "GlobalStorage.h" #include "JobQueue.h" #include "utils/CalamaresUtilsSystem.h" +#include "utils/Entropy.h" #include "utils/Logger.h" #include @@ -63,41 +64,18 @@ SetPasswordJob::make_salt( int length ) Q_ASSERT( length >= 8 ); Q_ASSERT( length <= 128 ); - static const char salt_chars[] = { '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', - 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', - 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', - 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; - - static_assert( sizeof( salt_chars ) == 64, "Missing salt_chars" ); - - std::random_device r; - std::seed_seq seed { r(), r(), r(), r(), r(), r(), r(), r() }; - std::mt19937_64 twister( seed ); - - std::uint64_t next; - int current_length = 0; - QString salt_string; - salt_string.reserve( length + 10 ); - - while ( current_length < length ) + CalamaresUtils::EntropySource source = CalamaresUtils::getPrintableEntropy( length, salt_string ); + if ( salt_string.length() != length ) { - next = twister(); - // In 64 bits, we have 10 blocks of 6 bits; map each block of 6 bits - // to a single salt character. - for ( unsigned int char_count = 0; char_count < 10; ++char_count ) - { - char c = salt_chars[ next & 0b0111111 ]; - next >>= 6; - salt_string.append( c ); - if ( ++current_length >= length ) - { - break; - } - } + cWarning() << "getPrintableEntropy returned string of length" << salt_string.length() << "expected" << length; + salt_string.truncate( length ); + } + if ( source != CalamaresUtils::EntropySource::URandom ) + { + cWarning() << "Entropy data for salt is low-quality."; } - salt_string.truncate( length ); salt_string.insert( 0, "$6$" ); salt_string.append( '$' ); return salt_string;