How can one store and retrieve from Windows Credential Manager

MSDN defines CredReadA thus:

 BOOL CredReadA(
  [in]  LPCSTR       TargetName,
  [in]  DWORD        Type,
  [in]  DWORD        Flags,
  [out] PCREDENTIALA *Credential
);

So my prototyping of this would be:

CredReadA(LONG lpszTargetName,LONG dwType,LONG dwFlags,LONG lpCredential),LONG,PASCAL,Name('CredReadA')

The last parameter returned to you is a pointer so I would be passing the address of a LONG that can be populated during the call:

lpCredential LONG

`CredReadA(Address(szTargetName),dwType,dwFlags,Address(lpCredential))

Some would prototype thus:

CredReadA(*CSTRING szTargetName,LONG dwType,LONG dwFlags,*LONG lpCredential),LONG,RAW,PASCAL,Name('CredReadA')

and when doing this, you can pass the variables rather than the address of the variables.

Reading your code, it isn’t clear what is a string variable and what is a pointer to a string; using a little prefix gives a clue whilst reading …

sz? = string zero terminated string (CSTRING)
lpsz? = long pointer to zero terminated string (LONG)

Hope that helps.

Hi, some comments

DWORD would be ULONG, though usually operating with LONGs works too
FILETIME structure is 64bits, you could use i64 or ULONG,DIM(2) etc
LPBYTE is pointer to binary data (CredentialBlob,Value in Attributes) you could solve the similar as LPSTR but to STRING (or byte array) instead CSTRING
Attributes is a pointer to the other structure, not a stucture inside a structure
For AttributesCount=0 this pointer would be NULL (0) for AttributesCount=1 this would pointer to the structure, for AttributesCount>1 it is not clear how it is implemented, guessing more contiguous pointers or contiguous Attributes stuctures (array), as it doesn’t shows linking pointers.

Attached credtest.zip with .clw .sln .cwproj implementing CredReadA and analyzing structures
credtest.zip (2,7 KB)

Thank you @FedericoNavarro - thanks to you I solved my “Invalid Parameter” error - it was because I used 0x1 as notation instead of just 1 in my constants (even though the docs write them that way) - now I got reading almost working - only getting the first character - but I’ll figure that one - next thing: Adding and Removing credentials

When trying the write - I am getting a ERROR_ARITHMETIC_OVERFLOW error - any idea?

Below is the update to date definitions:

[PROGRAM]
[COMMON]
FROM ABC ABC
MODIFIED '2023/06/23' '14:54:26'
[EMBED]
EMBED %GlobalMap
[DEFINITION]
[SOURCE]
PROPERTY:BEGIN
PRIORITY 2916
PROPERTY:END
MODULE('API')
    FormatMessage(LONG dwFlags, LONG lpSource, LONG dwMessageId, LONG dwLanguageId, *CSTRING lpBuffer, LONG nSize, LONG VA_List=0),LONG,PASCAL,RAW,DLL(1),NAME('FormatMessageA')
END
[SOURCE]
PROPERTY:BEGIN
PRIORITY 4000
PROPERTY:END
MODULE('win32')
  GetLastError(),LONG,PASCAL,DLL(1)
END 
[SOURCE]
PROPERTY:BEGIN
PRIORITY 4000
PROPERTY:END
MODULE('Advapi32')
   CredWrite(*LONG Credential, LONG Flags),BOOL,RAW,PASCAL,NAME('CredWriteA')
   CredRead(*CSTRING TargetName, LONG Type, LONG Flags, *LONG Credential),BOOL,RAW,PASCAL,NAME('CredReadA')
   CredFree(LONG Buffer),RAW,PASCAL,NAME('CredFree')
END 
[END]
EMBED %ProgramSetup
[DEFINITION]
[END]
EMBED %GlobalData
[DEFINITION]
[SOURCE]
PROPERTY:BEGIN
PRIORITY 3600
PROPERTY:END
SFTPCredential              GROUP,PRE(SFTP)
TargetName          &CSTRING
UserName            &CSTRING
Credential          STRING(WindowsCredential_MAX_STRING_LENGTH)
CredentialBlob      &STRING
END
[END]
EMBED %BeforeGlobalIncludes
[DEFINITION]
[SOURCE]
PROPERTY:BEGIN
PRIORITY 4000
PROPERTY:END
WindowsCredential_MAX_STRING_LENGTH EQUATE(256);

LPSTR EQUATE(LONG);

WindowsCredentialAttributes GROUP,TYPE
Keyword               UNSIGNED
Flags                 UNSIGNED
ValueSize             UNSIGNED
Value                 UNSIGNED
END

WindowsCredential       GROUP,TYPE
Flags                 UNSIGNED
Type                  UNSIGNED
TargetName            UNSIGNED
Comment               UNSIGNED
LastWritten           GROUP(INT64) END
CredentialBlobSize    UNSIGNED
CredentialBlob        UNSIGNED
Persist               UNSIGNED
AttributeCount        UNSIGNED
Attributes            UNSIGNED
TargetAlias           UNSIGNED
UserName              UNSIGNED
END

WindowsCredential_Flags_Prompt_Now              EQUATE(1);
WindowsCredential_Flags_Username_Target         EQUATE(2);

WindowsCredential_TYPE_GENERIC                  EQUATE(1);
WindowsCredential_TYPE_DOMAIN_PASSWORD          EQUATE(2);
WindowsCredential_TYPE_DOMAIN_CERTIFICATE       EQUATE(3);
WindowsCredential_TYPE_DOMAIN_VISIBLE_PASSWORD  EQUATE(4);
WindowsCredential_TYPE_GENERIC_CERTIFICATE      EQUATE(5);
WindowsCredential_TYPE_DOMAIN_EXTENDED          EQUATE(6);
WindowsCredential_TYPE_MAXIMUM                  EQUATE(7);
WindowsCredential_TYPE_MAXIMUM_EX               EQUATE(WindowsCredential_TYPE_MAXIMUM+1000);

WindowsCredential_PERSIST_SESSION               EQUATE(1);
WindowsCredential_PERSIST_LOCAL_MACHINE         EQUATE(2);
WindowsCredential_PERSIST_ENTERPRISE            EQUATE(3);
[END]
[END]
[END]
[PROCEDURE]
NAME GetLastErrorMessage
PROTOTYPE '(long),string'
PARAMETERS '(long GetLastErrorCode)'
GLOBAL
[COMMON]
FROM ABC Source
MODIFIED '2023/06/23' '11:43:44'
[PROMPTS]
%GenerateOpenClose LONG  (0)
%GenerateSaveRestore LONG  (0)
[EMBED]
EMBED %DataSection
[DEFINITION]
[SOURCE]
PROPERTY:BEGIN
PRIORITY 4000
PROPERTY:END
dwLength    LONG,AUTO
cMsgBuf     CSTRING(512)
[END]
EMBED %ProcessedCode
[DEFINITION]
[SOURCE]
PROPERTY:BEGIN
PRIORITY 4000
PROPERTY:END
 IF GetLastErrorCode THEN   !From_System, Ignore_Inserts, Max_Width_Mask
    dwLength = FormatMessage(12FFh,0,GetLastErrorCode,0,cMsgBuf,SIZE(cMsgBuf)-1);
 end
    RETURN CLIP(cMsgBuf);
[END]
[END]
[PROCEDURE]
NAME ReadCredentials
PROTOTYPE '(*CSTRING, LONG),BOOL,PROC'
PARAMETERS '(*CSTRING targetName,LONG type)'
GLOBAL
[COMMON]
FROM ABC Source
MODIFIED '2023/06/23' '15:38:38'
[PROMPTS]
%GenerateOpenClose LONG  (0)
%GenerateSaveRestore LONG  (0)
[EMBED]
EMBED %ProcessedCode
[DEFINITION]
[SOURCE]
PROPERTY:BEGIN
PRIORITY 4000
PROPERTY:END
 CLEAR(SFTPCredential);

 lpCredential = 0;

 UNLOCKTHREAD();
 b# = CredRead(targetName, type, 0, lpCredential);
 LOCKTHREAD();
 
 Credential &= lpCredential +0;

 CLEAR(SFTPCredential);

 IF (b# = TRUE)
     SFTP:TargetName &= Credential.TargetName +0
     SFTP:UserName &= Credential.UserName +0;
     SFTP:CredentialBlob &= Credential.CredentialBlob & ':' & Credential.CredentialBlobSize;
     SFTP:Credential = RemoveNull(SFTP:CredentialBlob);
 END

 UNLOCKTHREAD();
 CredFree(lpCredential);
 LOCKTHREAD();

 RETURN b#;
[END]
EMBED %DataSection
[DEFINITION]
[SOURCE]
PROPERTY:BEGIN
PRIORITY 4000
PROPERTY:END
lpCredential LONG
Credential &WindowsCredential
[END]
EMBED %LocalDataAfterClasses
[DEFINITION]
[SOURCE]
PROPERTY:BEGIN
PRIORITY 4000
PROPERTY:END
 MAP
RemoveNull PROCEDURE(String inString),String
END
[END]
EMBED %LocalProcedures
[DEFINITION]
[SOURCE]
PROPERTY:BEGIN
PRIORITY 4000
PROPERTY:END
RemoveNull PROCEDURE(String inString)!,String
idx       LONG
idy       LONG
s         STRING(WindowsCredential_MAX_STRING_LENGTH)
  CODE
  idy = 0;
  LOOP idx = 1 to SIZE(inString)
    IF inString[idx] = '<0>'
       idy = idy + 1;
       CYCLE;
    END
        
    s[idx - idy] = inString[idx];
  END
  RETURN CLIP(s);
[END]
[END]
[PROCEDURE]
NAME WriteCredentials
PROTOTYPE '(STRING,STRING,STRING,LONG,LONG),BOOL,PROC'
PARAMETERS '(STRING targetName,STRING userName,STRING credentials,UNSIGNED type,LONG flags)'
GLOBAL
[COMMON]
FROM ABC Source
MODIFIED '2023/06/23' '16:36:08'
[PROMPTS]
%GenerateOpenClose LONG  (0)
%GenerateSaveRestore LONG  (0)
[EMBED]
EMBED %ProcessedCode
[DEFINITION]
[SOURCE]
PROPERTY:BEGIN
PRIORITY 4000
PROPERTY:END
 Credential.Type = type; 
 Credential.TargetName = ADDRESS(targetName);
 Credential.UserName = ADDRESS(userName);
 
 CredentialBlob &= NEW CSTRING(SIZE(credentials) * 2);
 CredentialBlob = AddNulls(credentials);
 Credential.CredentialBlobSize = SIZE(CredentialBlob);
 Credential.CredentialBlob = CredentialBlob;
 Credential.Persist = WindowsCredential_PERSIST_ENTERPRISE;

 lpCredential = ADDRESS(Credential);

 UNLOCKTHREAD();
 b# = CredWrite(lpCredential, flags);
 LOCKTHREAD();
 
 RETURN b#;
[END]
EMBED %DataSection
[DEFINITION]
[SOURCE]
PROPERTY:BEGIN
PRIORITY 4000
PROPERTY:END
lpCredential        LONG
Credential          LIKE(WindowsCredential)
CredentialBlob      &CSTRING
[END]
EMBED %LocalProcedures
[DEFINITION]
[SOURCE]
PROPERTY:BEGIN
PRIORITY 4000
PROPERTY:END
AddNulls PROCEDURE(STRING text)!,STRING
tmp STRING(WindowsCredential_MAX_STRING_LENGTH)
idx LONG
idy LONG
CODE
  idy = (LEN(CLIP(text)) - 1);
  LOOP idx = 0 to idy BY 1
    tmp[(2 * idx) + 1] = text[idx + 1];
    tmp[(2 * idx) + 2] = '<0>';
  END
  RETURN CLIP(tmp);
    
[END]
EMBED %LocalDataAfterClasses
[DEFINITION]
[SOURCE]
PROPERTY:BEGIN
PRIORITY 4000
PROPERTY:END
 MAP
AddNulls PROCEDURE(STRING text),STRING
END
[END]
[END]
[PROCEDURE]
NAME FreeCredentials
GLOBAL
[COMMON]
FROM ABC Source
MODIFIED '2023/06/15' '15:20:23'
[PROMPTS]
%GenerateOpenClose LONG  (0)
%GenerateSaveRestore LONG  (0)
[EMBED]
EMBED %ProcessedCode
[DEFINITION]
[SOURCE]
PROPERTY:BEGIN
PRIORITY 4000
PROPERTY:END
 UNLOCKTHREAD();
 CredFree(ADDRESS(SFTPCredential));
 LOCKTHREAD();
[END]
[END]
[PROCEDURE]
NAME Main
[COMMON]
FROM ABC Source
MODIFIED '2023/06/23' '15:05:19'
[PROMPTS]
%GenerateOpenClose LONG  (0)
%GenerateSaveRestore LONG  (0)
[EMBED]
EMBED %ProcessedCode
[DEFINITION]
[SOURCE]
PROPERTY:BEGIN
PRIORITY 3600
PROPERTY:END
! SFTPCredential.Type = WindowsCredential_TYPE_GENERIC;
!
 SFTPCredentialTargetName = 'FOO:LOGIN';
!
! SFTPCredential.TargetName = ADDRESS(SFTPCredentialTargetName);
! 
 IF (ReadCredentials(SFTPCredentialTargetName, WindowsCredential_TYPE_GENERIC) = FALSE)
        MESSAGE('Reading of credentials failed[' & GetLastError() & ']: ' & GetLastErrorMessage(GetLastError()));
        POST(EVENT:CloseDown);
        RETURN;
 END

 MESSAGE('TargetName: ' & SFTP:TargetName & '|Username: ' & SFTP:UserName & '|Credential: ' & SFTP:Credential);

 IF (WriteCredentials('FOO:LOGIN:2', 'SomeUser', 'Welkom123', WindowsCredential_TYPE_GENERIC, 0) = FALSE)
        MESSAGE('Writing of credentials failed[' & GetLastError() & ']: ' & GetLastErrorMessage(GetLastError()));
        POST(EVENT:CloseDown);
        RETURN;
 END
[END]
EMBED %DataSection
[DEFINITION]
[SOURCE]
PROPERTY:BEGIN
PRIORITY 4000
PROPERTY:END
SFTPCredentialTargetName    CSTRING(WindowsCredential_MAX_STRING_LENGTH)
SFTPCredentialUserName    CSTRING(WindowsCredential_MAX_STRING_LENGTH)
SFTPCredentialCredential    STRING(WindowsCredential_MAX_STRING_LENGTH)
[END]
EMBED %ProcedureRoutines
[DEFINITION]
[END]
[END]

Your CredWrite Prototype is wrong, change *LONG to LONG, or, just pass the variable without enclosing in ADDRESS()

Hi
-As Julian said, use CredWrite(LONG Credential without the *: When reading it creates a new Credential and the pointer is returned through the parameter but writing it expects the credential address directly, and call it like CardWrite(Address(Credential) or prototype it as CredWrite(*WindowsCredential Credential and call it as CredWrite(Credential, allowing the compiler to verify the data type

-You need to copy Clarion String parameters to CSTRINGs to ensure they end with null

-You use CRED_MAX_STRING_LENGTH (256) as max length of everything but note according to the docs you also have:

CRED_MAX_DOMAIN_TARGET_NAME_LENGTH (337)
CRED_MAX_GENERIC_TARGET_NAME_LENGTH (32767)
CRED_MAX_CREDENTIAL_BLOB_SIZE (5*512)
CRED_MAX_USERNAME_LENGTH (513)

-You missed using ADDRESS in Credential.CredentialBlob = ADDRESS(CredentialBlob). Also for CredentialBlob as a generic buffer, use &STRING instead of &CSTRING. As a side note the method RemoveNulls I used on my CredRead example app was not intended for a Unicode to ANSI converter but only to allow the buffer be displayed by MESSAGE command.

I attached here the two main clw’s of your app modified
dasoft.clw (4,8 KB)
dasoft001.clw (4,6 KB)