OS Protected Storage classes?

18 views
Skip to first unread message

Jeffrey Walton

unread,
Mar 16, 2017, 6:39:02 PM3/16/17
to Crypto++ Users
Hi Everyone,

Android, Apple and Microsoft provide protected storage. Protected Storage is OS help with saving secrets. Android and Apple call it KeyChain, while Microsoft has called it a few names over the years, including Protected Storage, PStore, and DPAPI. There's also a separate MS credential store.

I have some classes that provide sources, filters and sinks for the OS's protected storage. They were built over the years and they are copy/paste drop-ins when a project has platform security integration requirements. An example of DPAPI is shown below.

My question is, should these be added to the library? Would folks use them if they were available?

Another option is to place them on the Patch Page so they can be downloaded on demand. The PEM Pack is there, and I see it in the wild on occasion.

What do you guys think?

Jeff

====================

Usage:

std::string s1 = "This is a test", s2, s3;
byte pword[] = "super secret";
size_t psize = 12;
DpapiParameters params(pword, psize);

StringSource(s1, true, new DpapiEncryptor(params, new HexEncoder(new StringSink(s2))));
StringSource(s2, true, new HexDecoder(new DpapiDecryptor(params, new StringSink(s3))));
std::cout << "Message: " << s1 << std::endl;
std::cout << "Entropy: " << pword << std::endl;
std::cout << "Encrypted: " << s2 << std::endl;
std::cout << "Decrypted: " << s3 << std::endl;

s2.clear();
DpapiEncryptor dpapi(params, new HexEncoder(new StringSink(s2)));
dpapi.Put((const byte*)s1.data(), s1.size());
dpapi.MessageEnd();

std::cout << "Test: " << s2;

====================

Classes:

enum DpapiFlags
{
    LOCAL_MACHINE = 0x04,
    UI_FORBIDDEN = 0x01,
    AUDIT = 0x10,
    DEFAULT_FLAGS = LOCAL_MACHINE | UI_FORBIDDEN
};

struct DpapiParameters
{
    DpapiParameters()
        : m_secret(NULLPTR), m_length(0), m_description(NULLPTR), m_flags(DEFAULT_FLAGS) {}

    DpapiParameters(const char *password, const char *description = NULLPTR, DpapiFlags flags=DpapiFlags::DEFAULT_FLAGS)
        : m_secret(reinterpret_cast<const byte*>(password)), m_length(std::strlen(password)), m_description(description), m_flags(flags) {}

    DpapiParameters(const byte *secret, size_t secretLength, const char *description = NULLPTR, DpapiFlags flags=DpapiFlags::DEFAULT_FLAGS)
        : m_secret(secret), m_length(secretLength), m_description(description), m_flags(flags) {}

    const byte * m_secret;
    size_t m_length;
    const char * m_description;
    DpapiFlags m_flags;
};

template <bool T_Encryption>
class DpapiFilter : public FilterWithBufferedInput
{
public:
    virtual ~DpapiFilter() {}

    DpapiFilter(const DpapiParameters &params, BufferedTransformation *attachment)
        : m_params(params), FilterWithBufferedInput(256, SIZE_MAX, 0, attachment) {}

    size_t Put2(const byte *inString, size_t length, int messageEnd, bool blocking);
    void FirstPut(const byte *inString);
    void LastPut(const byte *inString, size_t length);

private:
    const DpapiParameters &m_params;
    ByteQueue m_data;
};

typedef DpapiFilter<true>  DpapiEncryptor;
typedef DpapiFilter<false> DpapiDecryptor;

====================

#include <Windows.h>
#include <Wincrypt.h>
#pragma comment(lib, "crypt32")

template <bool T_Encryption>
void DpapiFilter<T_Encryption>::FirstPut(const byte *inString)
{
    // Buffer incoming message
    if (inString)
        m_data.Put(inString, m_firstSize);
}

template <bool T_Encryption>
void DpapiFilter<T_Encryption>::LastPut(const byte *inString, size_t length)
{
    if (inString && length)
        m_data.Put(inString, length);
}

template <bool T_Encryption>
size_t DpapiFilter<T_Encryption>::Put2(const byte *inString, size_t length, int messageEnd, bool blocking)
{
    CRYPTOPP_UNUSED(blocking); CRYPTOPP_UNUSED(messageEnd);
    CRYPTOPP_ASSERT(!messageEnd || (messageEnd && !length));
    CRYPTOPP_COMPILE_ASSERT(DpapiFlags::LOCAL_MACHINE == CRYPTPROTECT_LOCAL_MACHINE);
    CRYPTOPP_COMPILE_ASSERT(DpapiFlags::UI_FORBIDDEN == CRYPTPROTECT_UI_FORBIDDEN);
    CRYPTOPP_COMPILE_ASSERT(DpapiFlags::AUDIT == CRYPTPROTECT_AUDIT);

    // Buffer incoming message
    if (inString && length)
        m_data.Put(inString, length);

    if (messageEnd-- == 0)
        return 0;

    DATA_BLOB in, ent, out = {0, NULL};
    SecByteBlock temp(static_cast<size_t>(m_data.MaxRetrievable()));
    m_data.Get(temp, temp.size());

    in.pbData = reinterpret_cast<BYTE*>(temp.BytePtr());
    in.cbData = static_cast<DWORD>(temp.SizeInBytes());

    ent.pbData = const_cast<BYTE*>(reinterpret_cast<const BYTE*>(m_params.m_secret));
    ent.cbData = static_cast<DWORD>(m_params.m_length);

    if (T_Encryption == true)
    {
        std::wstring desc(m_params.m_description ? StringWiden(m_params.m_description) : L"");
        const wchar_t* dptr = desc.empty() ? NULLPTR : desc.c_str();

        BOOL bResult = CryptProtectData(&in, dptr, &ent, NULL, NULL, m_params.m_flags, &out);
        if (bResult == FALSE)
        {
            const DWORD dwError = GetLastError();
            throw InvalidArgument("CryptProtectData() call failed with error " + IntToString(dwError));
        }
    }
    else
    {
        BOOL bResult = CryptUnprotectData(&in, NULL, &ent, NULL, NULL, m_params.m_flags, &out);
        if (bResult == FALSE)
        {
            // It appears ERROR_INVALID_DATA is returned for all crypto related
            //  failures, including bad password, tamper data and tamper mac.
            const DWORD dwError = GetLastError();
            throw InvalidArgument("CryptUnprotectData() call failed with error " + IntToString(dwError));
        }
    }

    AttachedTransformation()->Put2(out.pbData, out.cbData, messageEnd, blocking);
    LocalFree(out.pbData);

    AttachedTransformation()->MessageEnd();

    return 0;
}

template class DpapiFilter<true>;
template class DpapiFilter<false>;

====================

And here is what a small message is encrypted and mac'd to:

String: This is a test
Entropy: super secret
Encrypted: 01000000D08C9DDF0115D1118C7A00C04FC297EB010000009B53121C755FD94384293
2EA67C1EF7F04000000020000000000106600000001000020000000F34C1BA7A9A7B2A0C823E52BF
F69716FDF9390948CE9BF4ED8744426E6F560CC000000000E80000000020000200000002DD36C0E6
6E9BA76B9C605CC371C69380A3EE164DD750372906736CD25290C4710000000E792981D0A56CD0EA
4F307CF40499AE840000000AB2675B720015C1AA414FFB46B707B55F3FA3D9BB3EA4BA14C7BD5E11
EF16A80C75FBFB68F6C79514B125AF3AB6F52B2113D5B0AF4A690A099E56A67770708D1

Jeffrey Walton

unread,
Mar 17, 2017, 7:09:38 AM3/17/17
to Crypto++ Users

Android, Apple and Microsoft provide protected storage. Protected Storage is OS help with saving secrets. Android and Apple call it KeyChain, while Microsoft has called it a few names over the years, including Protected Storage, PStore, and DPAPI. There's also a separate MS credential store.

I have some classes that provide sources, filters and sinks for the OS's protected storage. They were built over the years and they are copy/paste drop-ins when a project has platform security integration requirements. An example of DPAPI is shown below.

My question is, should these be added to the library? Would folks use them if they were available?

Another option is to place them on the Patch Page so they can be downloaded on demand. The PEM Pack is there, and I see it in the wild on occasion.

I've started to add it to the wiki's Patch Page. This will avoid library clutter while ensuring folks who need it can access it. The wiki page for the classes is located at:

* https://www.cryptopp.com/wiki/Protected_Storage_Pack

One check-in for the library was performed to help accommodate Protected_Storage_Pack. The check-in was StringWiden function to convert a narrow string to a wide string using C/C++'s mbstowcs functions. It promotes "thing just work" by ensuring users *don't* have to patch the Crypto++ library. The check-in of interest is:

* https://github.com/weidai11/cryptopp/commit/c90a63196a148478b624e7a1e6d7a5910d57c96f

Jeff
Reply all
Reply to author
Forward
0 new messages