How can one store and retrieve from Windows Credential Manager

I would like to know if it’s possible to store and retrieve credentials to/from Windows Credential Manager for my own application - I don’t need access to others, only to my own.

Or do you advise a different way to store credentials securely?

Hi,

Not sure if this helps, but I think it might?

The Chilkat library includes a a few base classes dedicated to Certificates, Certificate Chains and Certificate Stores - including the Windows Certificate store.

We provide a Clarion Wrapper class for the Chilkat library too. More info can be found here:-

Chilkat CertStore help -
https://www.chilkatsoft.com/refdoc/xChilkatCertStoreRef.html

noyantis Wrapper template -

Hope it helps,

Thanks,

Andy
noyantis.com

Not looking for the Certificate Store - but the Credentials Store/Manager

Ah sorry, my mistake - reading what I wanted rather than what’s there :slight_smile: lol

1 Like

I think Secwin uses encrypted tables. CapeSoft Secwin

Not exactly a cheap way to go, especially with the additional pre-requisite addons, but it’s a feature-rich investment.

For securely storing info in our apps, we encrypt all sensitive data (using the Chilkat crypt2 base class) and then store them either in our tables and / or in external json files.

For the external json files, we use the Chilkat Json Object base class to create, read, write etc…

The reason for using external Json files was at the request of a customer, they wanted to be able to look at “config” type files easily, but of course not see any sensitive data (hence parts of it being encrypted).

Anyway, that’s one way you could store + retrieve the info…

Thanks,

Andy
noyantis.com

So simply said, there’s no easy way to interact with Windows Credential Manager as everyone keeps recommending other stuff?

Off the top of my head - not an easy way.

I think you’re probably looking at either a 3rd Party tool, or Windows API calls and creating the functionality yourself.

Sorry :frowning:

There is a Clarion Live open webinar starting in around 25 mins… I’ll ask the question for you during the webinar - just in case anybody has done it without using 3rd party tools.

Cheers,

Andy
noyantis.com

Thank you for asking it in the Webinar!

Hi,

It really is as we thought, without external tools then you would have to code it yourself via Windows API calls.

Thanks,

Andy
noyantis.com

There is a Credentials Management API (I think it is a set of com interfaces).
Also there is a Data Protection API (DPAPI) which is implemented in EasyDotNet in TProtectedData class.

Might this help…

1 Like

Thanks for that - currently in the process of finding the correct matching types for everything in Clarion - is there any known mapping out there between Clarion en C++ which is more up to date than the one on clarion.help?
https://clarion.help/doku.php?id=resolving_data_types.htm

Still trying to match the structure - anyone know how to resemble the LPSTR (CHAR *) in a Group?
I tried *CSTRING,RAW as noted here: resolving_data_types.htm [Clarion Community Help] but it seems that Clarion isn’t accepting that

typedef struct _CREDENTIALA {
  DWORD                  Flags;
  DWORD                  Type;
  LPSTR                  TargetName;
  LPSTR                  Comment;
  FILETIME               LastWritten;
  DWORD                  CredentialBlobSize;
  LPBYTE                 CredentialBlob;
  DWORD                  Persist;
  DWORD                  AttributeCount;
  PCREDENTIAL_ATTRIBUTEA Attributes;
  LPSTR                  TargetAlias;
  LPSTR                  UserName;
} CREDENTIALA, *PCREDENTIALA;

My currently tried group:

WindowsCredentialAttributes GROUP,TYPE
Keyword               *CSTRING,RAW
Flags                 LONG
ValueSize             LONG
Value                 BYTE
END

WindowsCredential       GROUP,TYPE
Flags                 LONG
Type                  LONG
TargetName            *CSTRING(WindowsCredential_MAX_STRING_LENGTH),RAW
Comment               *CSTRING(WindowsCredential_MAX_STRING_LENGTH),RAW
LastWritten           LONG
CredentialBlobSize    LONG
CredentialBlob        BYTE
Persist               LONG
AttributeCount        LONG
Attributes            LIKE(WindowsCredentialAttributes)
TargetAlias           *CSTRING(WindowsCredential_MAX_STRING_LENGTH),RAW
UserName              *CSTRING(WindowsCredential_MAX_STRING_LENGTH),RAW
END

I also tried without * and RAW and then it compiles, but when using the group this way with the API - I get an INVALID PARAMETER ERROR - so I guess, something isn’t decoded/coded correctly

I remember it this way … “LPSTR = long pointer to a string”, API strings are often null terminated, so I declare like this:

szTargetName CSTRING(128)
lpszTargetName LONG

and code like this:

szTargetName = 'myName'
lpszTargetName = ADDRESS(szTargetName)

If you were to adopt this, you would declare a szTargetName outside the group, then your group declaration would be thus:

WindowsCredential       GROUP,TYPE
Flags                 LONG
Type                  LONG
lpszTargetName LONG
lpszComment      LONG
.

Sadly I am still getting the “Parameter invalid error (87)” even if I change that

What is giving that error, calling one of the API functions?
Perhaps share the prototyping of the function being called.

Yeah it’s the function being called, I’ll share some code below:

Before Global INCLUDEs

WindowsCredential_MAX_STRING_LENGTH EQUATE(256);

LPSTR EQUATE(LONG);

WindowsCredentialAttributes GROUP,TYPE
Keyword               LPSTR
Flags                 LONG
ValueSize             LONG
Value                 BYTE
END

WindowsCredential       GROUP,TYPE
Flags                 LONG
Type                  LONG
TargetName            LPSTR
Comment               LPSTR
LastWritten           LONG
CredentialBlobSize    LONG
CredentialBlob        BYTE
Persist               LONG
AttributeCount        LONG
Attributes            LIKE(WindowsCredentialAttributes)
TargetAlias           LPSTR
UserName              LPSTR
END

WindowsCredential_Flags_Prompt_Now      EQUATE(0x1);
WindowsCredential_Flags_Username_Target EQUATE(0x2);

WindowsCredential_TYPE_GENERIC          EQUATE(0x1);
WindowsCredential_TYPE_DOMAIN_PASSWORD  EQUATE(0x2);
WindowsCredential_TYPE_DOMAIN_CERTIFICATE EQUATE(0x3);
WindowsCredential_TYPE_DOMAIN_VISIBLE_PASSWORD EQUATE(0x4);
WindowsCredential_TYPE_GENERIC_CERTIFICATE EQUATE(0x5);
WindowsCredential_TYPE_DOMAIN_EXTENDED  EQUATE(0x6);
WindowsCredential_TYPE_MAXIMUM          EQUATE(0x7);
WindowsCredential_TYPE_MAXIMUM_EX       EQUATE(WindowsCredential_TYPE_MAXIMUM+1000);

WindowsCredential_PERSIST_SESSION       EQUATE(0x1);
WindowsCredential_PERSIST_LOCAL_MACHINE EQUATE(0x2);
WindowsCredential_PERSIST_ENTERPRISE    EQUATE(0x3);

Global Data

SFTPCredential              LIKE(WindowsCredential)

Inside the Global Map

MODULE('Advapi32')
   CredWriteW(*WindowsCredential Credential, LONG Flags),BOOL,RAW,PASCAL,NAME('CredWriteW')
   CredReadW(LONG TargetName, LONG Type, LONG Flags, *WindowsCredential Credential),BOOL,RAW,PASCAL,NAME('CredReadW')
   CredFree(LONG Buffer),RAW,PASCAL,NAME('CredFree')
END 

My main procedure - Data Section

SFTPCredentialTargetName    CSTRING(WindowsCredential_MAX_STRING_LENGTH)
SFTPCredentialUserName    CSTRING(WindowsCredential_MAX_STRING_LENGTH)
SFTPCredentialCredential    STRING(WindowsCredential_MAX_STRING_LENGTH)

My main procedure - Processed Code

 SFTPCredential.Type = WindowsCredential_TYPE_GENERIC;

 SFTPCredentialTargetName = FOO/SFTP';

 SFTPCredential.TargetName = ADDRESS(SFTPCredentialTargetName);
 
 IF (ReadCredentials(SFTPCredentialTargetName, SFTPCredential.Type) = FALSE)
        MESSAGE('Reading of credentials failed[' & GetLastError() & ']: ' & GetLastErrorMessage(GetLastError()));
        POST(EVENT:CloseDown);
        RETURN;
 END

 SFTPCredentialCredential = 'Welkom123';
 SFTPCredential.CredentialBlobSize = LEN(SFTPCredentialCredential);
 SFTPCredential.CredentialBlob = ADDRESS(SFTPCredentialCredential);
 SFTPCredential.Persist = WindowsCredential_PERSIST_LOCAL_MACHINE;
 SFTPCredentialUserName = 'JohnDoe';
 SFTPCredential.UserName = ADDRESS(SFTPCredentialUserName);

 IF (WriteCredentials(0) = FALSE)
        MESSAGE('Writing of credentials failed[' & GetLastError() & ']: ' & GetLastErrorMessage(GetLastError()));
        POST(EVENT:CloseDown);
        RETURN;
 END
1 Like

err … what happens inside WriteCredentials() ?

Also, you’ve prototyped CredWriteW and CredReadW, I think these will expect “Wide Chars”, aren’t there CredWriteA and CredReadA versions that expect ASCII?

Oops, forgot those, here you go:

!!! <summary>
!!! Generated from procedure template - Source
!!! </summary>
ReadCredentials      PROCEDURE  (*CSTRING targetName,LONG type) ! Declare Procedure

  CODE
! SFTPCredential.TargetName = CLIP(targetName);

 CLEAR(SFTPCredential);

 SFTPCredential.TargetName = ADDRESS(targetName);

! UNLOCKTHREAD();
 b# = CredReadW(SFTPCredential.TargetName, type, 0, SFTPCredential);
! LOCKTHREAD();
 
 RETURN b#;
!!! <summary>
!!! Generated from procedure template - Source
!!! </summary>
WriteCredentials     PROCEDURE  (LONG flags)               ! Declare Procedure

  CODE
 UNLOCKTHREAD();
 b# = CredWriteW(SFTPCredential, flags);
 LOCKTHREAD();
 
 RETURN b#;
!!! <summary>
!!! Generated from procedure template - Source
!!! </summary>
FreeCredentials      PROCEDURE                             ! Declare Procedure

  CODE
 UNLOCKTHREAD();
 CredFree(ADDRESS(SFTPCredential));
 LOCKTHREAD();

Oh, I’ll try the other Methods