Coverage Report

Created: 2025-12-31 10:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/svl/source/misc/PasswordHelper.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
21
#include <svl/PasswordHelper.hxx>
22
#include <comphelper/hash.hxx>
23
#include <rtl/digest.h>
24
#include <memory>
25
#include <unicode/regex.h>
26
#include <unicode/unistr.h>
27
#include <unicode/errorcode.h>
28
#include <zxcvbn.h>
29
#include <sal/log.hxx>
30
31
using namespace com::sun::star;
32
33
void SvPasswordHelper::GetHashPasswordSHA256(uno::Sequence<sal_Int8>& rPassHash, std::u16string_view rPassword)
34
0
{
35
0
    OString const tmp(OUStringToOString(rPassword, RTL_TEXTENCODING_UTF8));
36
0
    ::std::vector<unsigned char> const hash(::comphelper::Hash::calculateHash(
37
0
        tmp.getStr(), tmp.getLength(),
38
0
        ::comphelper::HashType::SHA256));
39
0
    rPassHash.realloc(hash.size());
40
0
    ::std::copy(hash.begin(), hash.end(), rPassHash.getArray());
41
0
    rtl_secureZeroMemory(const_cast<char *>(tmp.getStr()), tmp.getLength());
42
0
}
43
44
void SvPasswordHelper::GetHashPasswordSHA1UTF8(uno::Sequence<sal_Int8>& rPassHash, std::u16string_view rPassword)
45
0
{
46
0
    OString const tmp(OUStringToOString(rPassword, RTL_TEXTENCODING_UTF8));
47
0
    ::std::vector<unsigned char> const hash(::comphelper::Hash::calculateHash(
48
0
        tmp.getStr(), tmp.getLength(),
49
0
        ::comphelper::HashType::SHA1));
50
0
    rPassHash.realloc(hash.size());
51
0
    ::std::copy(hash.begin(), hash.end(), rPassHash.getArray());
52
0
    rtl_secureZeroMemory(const_cast<char *>(tmp.getStr()), tmp.getLength());
53
0
}
54
55
void SvPasswordHelper::GetHashPassword(uno::Sequence<sal_Int8>& rPassHash, const char* pPass, sal_uInt32 nLen)
56
0
{
57
0
    rPassHash.realloc(RTL_DIGEST_LENGTH_SHA1);
58
59
0
    rtlDigestError aError = rtl_digest_SHA1 (pPass, nLen, reinterpret_cast<sal_uInt8*>(rPassHash.getArray()), rPassHash.getLength());
60
0
    if (aError != rtl_Digest_E_None)
61
0
    {
62
0
        rPassHash.realloc(0);
63
0
    }
64
0
}
65
66
void SvPasswordHelper::GetHashPasswordLittleEndian(uno::Sequence<sal_Int8>& rPassHash, std::u16string_view sPass)
67
0
{
68
0
    sal_Int32 nSize(sPass.size());
69
0
    std::unique_ptr<char[]> pCharBuffer(new char[nSize * sizeof(sal_Unicode)]);
70
71
0
    for (sal_Int32 i = 0; i < nSize; ++i)
72
0
    {
73
0
        sal_Unicode ch(sPass[ i ]);
74
0
        pCharBuffer[2 * i] = static_cast< char >(ch & 0xFF);
75
0
        pCharBuffer[2 * i + 1] = static_cast< char >(ch >> 8);
76
0
    }
77
78
0
    GetHashPassword(rPassHash, pCharBuffer.get(), nSize * sizeof(sal_Unicode));
79
0
    rtl_secureZeroMemory(pCharBuffer.get(), nSize * sizeof(sal_Unicode));
80
0
}
81
82
void SvPasswordHelper::GetHashPasswordBigEndian(uno::Sequence<sal_Int8>& rPassHash, std::u16string_view sPass)
83
0
{
84
0
    sal_Int32 nSize(sPass.size());
85
0
    std::unique_ptr<char[]> pCharBuffer(new char[nSize * sizeof(sal_Unicode)]);
86
87
0
    for (sal_Int32 i = 0; i < nSize; ++i)
88
0
    {
89
0
        sal_Unicode ch(sPass[ i ]);
90
0
        pCharBuffer[2 * i] = static_cast< char >(ch >> 8);
91
0
        pCharBuffer[2 * i + 1] = static_cast< char >(ch & 0xFF);
92
0
    }
93
94
0
    GetHashPassword(rPassHash, pCharBuffer.get(), nSize * sizeof(sal_Unicode));
95
0
    rtl_secureZeroMemory(pCharBuffer.get(), nSize * sizeof(sal_Unicode));
96
0
}
97
98
void SvPasswordHelper::GetHashPassword(uno::Sequence<sal_Int8>& rPassHash, std::u16string_view sPass)
99
0
{
100
0
    GetHashPasswordLittleEndian(rPassHash, sPass);
101
0
}
102
103
bool SvPasswordHelper::CompareHashPassword(const uno::Sequence<sal_Int8>& rOldPassHash, std::u16string_view sNewPass)
104
0
{
105
0
    bool bResult = false;
106
107
0
    if (rOldPassHash.getLength() == RTL_DIGEST_LENGTH_SHA1)
108
0
    {
109
0
        uno::Sequence<sal_Int8> aNewPass(RTL_DIGEST_LENGTH_SHA1);
110
0
        GetHashPasswordSHA1UTF8(aNewPass, sNewPass);
111
0
        if (aNewPass == rOldPassHash)
112
0
        {
113
0
            bResult = true;
114
0
        }
115
0
        else
116
0
        {
117
0
            GetHashPasswordLittleEndian(aNewPass, sNewPass);
118
0
            if (aNewPass == rOldPassHash)
119
0
                bResult = true;
120
0
            else
121
0
            {
122
0
                GetHashPasswordBigEndian(aNewPass, sNewPass);
123
0
                bResult = (aNewPass == rOldPassHash);
124
0
            }
125
0
        }
126
0
    }
127
0
    else if (rOldPassHash.getLength() == 32)
128
0
    {
129
0
        uno::Sequence<sal_Int8> aNewPass;
130
0
        GetHashPasswordSHA256(aNewPass, sNewPass);
131
0
        bResult = aNewPass == rOldPassHash;
132
0
    }
133
134
0
    return bResult;
135
0
}
136
137
double SvPasswordHelper::GetPasswordStrengthPercentage(const char* pPassword)
138
0
{
139
    // Entropy bits ≥ 112 are mapped to 100% password strength.
140
    // 112 was picked since according to the linked below KeePass help page, it
141
    // corresponds to a strong password:
142
    // <http://web.archive.org/web/20231128131604/https://keepass.info/help/kb/pw_quality_est.html>
143
0
    static constexpr double fMaxPassStrengthEntropyBits = 112.0;
144
0
    return std::min(100.0,
145
0
                    ZxcvbnMatch(pPassword, nullptr, nullptr) * 100.0 / fMaxPassStrengthEntropyBits);
146
0
}
147
148
double SvPasswordHelper::GetPasswordStrengthPercentage(const OUString& aPassword)
149
0
{
150
0
    OString aPasswordUtf8 = aPassword.toUtf8();
151
0
    return GetPasswordStrengthPercentage(aPasswordUtf8.getStr());
152
0
}
153
154
bool SvPasswordHelper::PasswordMeetsPolicy(const char* pPassword,
155
                                           const std::optional<OUString>& oPasswordPolicy)
156
0
{
157
0
    if (oPasswordPolicy)
158
0
    {
159
0
        icu::ErrorCode aStatus;
160
0
        icu::UnicodeString sPassword(pPassword);
161
0
        icu::UnicodeString sRegex(oPasswordPolicy->getStr());
162
0
        icu::RegexMatcher aRegexMatcher(sRegex, sPassword, 0, aStatus);
163
164
0
        if (aRegexMatcher.matches(aStatus))
165
0
            return true;
166
167
0
        SAL_WARN_IF(
168
0
            aStatus.isFailure(), "svl.misc",
169
0
            "Password policy regular expression failed with error: " << aStatus.errorName());
170
171
0
        return false;
172
0
    }
173
0
    return true;
174
0
}
175
176
bool SvPasswordHelper::PasswordMeetsPolicy(const OUString& aPassword,
177
                                           const std::optional<OUString>& oPasswordPolicy)
178
0
{
179
0
    OString aPasswordUtf8 = aPassword.toUtf8();
180
0
    return PasswordMeetsPolicy(aPasswordUtf8.getStr(), oPasswordPolicy);
181
0
}
182
183
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */