LCOV - code coverage report
Current view: top level - tests - catch_password.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 89 89 100.0 %
Date: 2023-06-11 18:21:25 Functions: 2 2 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Copyright (c) 2019-2023  Made to Order Software Corp.  All Rights Reserved
       2             : //
       3             : // https://snapwebsites.org/project/safepasswords
       4             : // contact@m2osw.com
       5             : //
       6             : // This program is free software: you can redistribute it and/or modify
       7             : // it under the terms of the GNU General Public License as published by
       8             : // the Free Software Foundation, either version 3 of the License, or
       9             : // (at your option) any later version.
      10             : //
      11             : // This program is distributed in the hope that it will be useful,
      12             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      13             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14             : // GNU General Public License for more details.
      15             : //
      16             : // You should have received a copy of the GNU General Public License
      17             : // along with this program.  If not, see <https://www.gnu.org/licenses/>.
      18             : 
      19             : // self
      20             : //
      21             : #include    "catch_main.h"
      22             : 
      23             : 
      24             : // safepasswords
      25             : //
      26             : #include    <safepasswords/password.h>
      27             : 
      28             : #include    <safepasswords/exception.h>
      29             : 
      30             : 
      31             : // snapdev
      32             : //
      33             : #include    <snapdev/not_used.h>
      34             : 
      35             : 
      36             : // libutf8
      37             : //
      38             : #include    <libutf8/libutf8.h>
      39             : 
      40             : 
      41             : // C++
      42             : //
      43             : #include    <set>
      44             : 
      45             : 
      46             : // C
      47             : //
      48             : #include    <malloc.h>
      49             : 
      50             : 
      51             : // last include
      52             : //
      53             : #include    <snapdev/poison.h>
      54             : 
      55             : 
      56             : 
      57           4 : CATCH_TEST_CASE("password", "[password]")
      58             : {
      59           4 :     CATCH_START_SECTION("password: verify constructor (empty)")
      60             :     {
      61           1 :         safepasswords::password p;
      62           1 :         CATCH_REQUIRE(p.get_digest() == "sha512");  // digest has a default
      63           1 :         CATCH_REQUIRE(p.get_plain().empty());
      64           1 :         CATCH_REQUIRE(p.get_salt().empty());
      65             : 
      66             :         // the get_encrypted() actually generates a password if none is
      67             :         // defined, so it is not going to be empty ever and then the
      68             :         // plain & salt strings are also not empty after this call
      69             :         //
      70           1 :         CATCH_REQUIRE_FALSE(p.get_encrypted().empty());
      71             : 
      72           1 :         CATCH_REQUIRE_FALSE(p.get_plain().empty());
      73           1 :         CATCH_REQUIRE_FALSE(p.get_salt().empty());
      74             : 
      75             :         // try setting it back, this clears the plain password
      76             :         //
      77           1 :         safepasswords::string encrypted(p.get_encrypted());
      78           1 :         safepasswords::string salt(p.get_salt());
      79           1 :         p.set_encrypted(encrypted, salt);
      80             : 
      81           1 :         CATCH_REQUIRE(p.get_encrypted() == encrypted);
      82           1 :         CATCH_REQUIRE(p.get_plain().empty());
      83           1 :         CATCH_REQUIRE(p.get_salt() == salt);
      84             : 
      85             :         // test the clear() function
      86             :         //
      87           1 :         p.clear();
      88           1 :         CATCH_REQUIRE(p.get_plain().empty());
      89           1 :         CATCH_REQUIRE(p.get_salt().empty());
      90           1 :     }
      91           4 :     CATCH_END_SECTION()
      92             : 
      93           4 :     CATCH_START_SECTION("password: compare passwords with varying digests")
      94             :     {
      95             :         // try the following command to get a list of existing digests:
      96             :         //
      97             :         //   generate-password --list-digests
      98             :         //
      99           1 :         char const * const digests[] = {
     100             :             "sha256",
     101             :             "sha512",
     102             :             "md5",
     103             :             "2.16.840.1.101.3.4.2.11", // SHAKE-128
     104             :             "sha1",
     105             :             "ssl3-md5",
     106             :             "sha384",
     107             :             "sha224",
     108             :             "sm3",
     109             :             "blake2s256",
     110             :         };
     111          11 :         for(std::size_t idx(0); idx < std::size(digests); ++idx)
     112             :         {
     113          10 :             std::string const p1(SNAP_CATCH2_NAMESPACE::random_string(1, 100));
     114          10 :             std::string const p2(SNAP_CATCH2_NAMESPACE::random_string(1, 100));
     115             : 
     116          10 :             safepasswords::password password1;
     117          10 :             password1.set_digest(digests[idx]);
     118          10 :             password1.set_plain(safepasswords::string(p1.c_str(), p1.length()));
     119          10 :             safepasswords::password password2;
     120          10 :             password2.set_digest(digests[(idx + rand() % 20) % std::size(digests)]);
     121          10 :             password2.set_plain(safepasswords::string(p2.c_str(), p2.length()));
     122             : 
     123          10 :             auto r(password1 <=> password2);
     124          10 :             auto q(password1.get_encrypted().to_std_string() <=> password2.get_encrypted().to_std_string());
     125          10 :             CATCH_REQUIRE(r == q);
     126          10 :         }
     127             :     }
     128           4 :     CATCH_END_SECTION()
     129             : 
     130           4 :     CATCH_START_SECTION("password: generate short passwords")
     131             :     {
     132          11 :         for(int idx(0); idx < 10; ++idx)
     133             :         {
     134          10 :             int const min_len(rand() % 16 + 16);
     135          10 :             int const max_len(min_len + rand() % 10);
     136             : 
     137          10 :             safepasswords::password p;
     138          10 :             p.generate(min_len, max_len);
     139             : 
     140          10 :             CATCH_REQUIRE(p.get_plain().length() >= static_cast<std::size_t>(min_len));
     141          10 :             CATCH_REQUIRE(p.get_plain().length() <= static_cast<std::size_t>(max_len));
     142             : 
     143             :             // try again with equal min/max
     144             :             //
     145          10 :             p.generate(min_len, min_len);
     146          10 :             CATCH_REQUIRE(p.get_plain().length() == static_cast<std::size_t>(min_len));
     147          10 :         }
     148             :     }
     149           4 :     CATCH_END_SECTION()
     150             : 
     151           4 :     CATCH_START_SECTION("password: generate passwords with negative max. length")
     152             :     {
     153          11 :         for(int idx(0); idx < 10; ++idx)
     154             :         {
     155          10 :             int const min_len(rand() % 16 + 16);
     156          10 :             int const max_len(-min_len - rand() % 10);
     157             : 
     158          10 :             safepasswords::password p;
     159          10 :             p.generate(min_len, max_len);
     160             : 
     161          10 :             CATCH_REQUIRE(p.get_plain().length() >= static_cast<std::size_t>(min_len));
     162          10 :             CATCH_REQUIRE(p.get_plain().length() <= safepasswords::PASSWORD_DEFAULT_MAX_LENGTH);
     163          10 :         }
     164             :     }
     165           4 :     CATCH_END_SECTION()
     166           4 : }
     167             : 
     168             : 
     169           4 : CATCH_TEST_CASE("password_invalid", "[password][error]")
     170             : {
     171           4 :     CATCH_START_SECTION("password_invalid: unknown digest")
     172             :     {
     173           1 :         safepasswords::password bad_digest;
     174             : 
     175           5 :         CATCH_REQUIRE_THROWS_MATCHES(
     176             :                   bad_digest.set_digest("bad-digest")
     177             :                 , safepasswords::digest_not_available
     178             :                 , Catch::Matchers::ExceptionMessage(
     179             :                           "safepasswords_exception: the specified digest (bad-digest) was not found."));
     180             : 
     181           1 :         CATCH_REQUIRE(bad_digest.get_digest() == "sha512");
     182             : 
     183             :         // the get_encrypted() still works, it just uses the default digest
     184             :         //
     185           1 :         bad_digest.get_encrypted();
     186           1 :     }
     187           4 :     CATCH_END_SECTION()
     188             : 
     189           4 :     CATCH_START_SECTION("password_invalid: invalid salt length")
     190             :     {
     191           1 :         safepasswords::password bad_salt;
     192           1 :         safepasswords::string p("test", 4);
     193           1 :         safepasswords::string s("bad", 3);
     194             : 
     195           1 :         CATCH_REQUIRE_THROWS_MATCHES(
     196             :                   bad_salt.set_plain(p, s)
     197             :                 , safepasswords::invalid_parameter
     198             :                 , Catch::Matchers::ExceptionMessage(
     199             :                           "safepasswords_exception: if defined, the salt must be exactly 32 bytes."));
     200           1 :     }
     201           4 :     CATCH_END_SECTION()
     202             : 
     203           4 :     CATCH_START_SECTION("password_invalid: generate passwords where min > max")
     204             :     {
     205          11 :         for(int idx(0); idx < 10; ++idx)
     206             :         {
     207          10 :             int const min_len(rand() % 16 + 16);
     208          10 :             int const max_len(min_len + rand() % 10 + 1);
     209             : 
     210          10 :             safepasswords::password p;
     211             : 
     212          10 :             CATCH_REQUIRE_THROWS_MATCHES(
     213             :                       p.generate(max_len, min_len)
     214             :                     , safepasswords::invalid_parameter
     215             :                     , Catch::Matchers::ExceptionMessage(
     216             :                               "safepasswords_exception: adjusted minimum and"
     217             :                               " maximum lengths are improperly sorted; minimum "
     218             :                             + std::to_string(max_len)  // it is swapped above, so swapped here too
     219             :                             + " is larger than maximum "
     220             :                             + std::to_string(min_len)
     221             :                             + '.'));
     222          10 :         }
     223             :     }
     224           4 :     CATCH_END_SECTION()
     225             : 
     226           4 :     CATCH_START_SECTION("password_invalid: generate passwords where max < 8")
     227             :     {
     228           9 :         for(int max_len(0); max_len < safepasswords::PASSWORD_MIN_LENGTH; ++max_len)
     229             :         {
     230           8 :             safepasswords::password p;
     231             : 
     232             :             // minimum gets adjusted to safepasswords::PASSWORD_MIN_LENGTH
     233             :             // so any maximum that is smaller than that value will generate
     234             :             // an error
     235             :             //
     236           8 :             CATCH_REQUIRE_THROWS_MATCHES(
     237             :                       p.generate(0, max_len)
     238             :                     , safepasswords::invalid_parameter
     239             :                     , Catch::Matchers::ExceptionMessage(
     240             :                               "safepasswords_exception: adjusted minimum and "
     241             :                               "maximum lengths are improperly sorted; minimum "
     242             :                             + std::to_string(safepasswords::PASSWORD_MIN_LENGTH)  // the adjusted minimum
     243             :                             + " is larger than maximum "
     244             :                             + std::to_string(max_len)
     245             :                             + '.'));
     246           8 :         }
     247             :     }
     248           4 :     CATCH_END_SECTION()
     249           4 : }
     250             : 
     251             : 
     252             : 
     253             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.14