I'm trying to map CredWrite/CredRead in JNA in order to store a thrid party credential used in my Java application in Windows Credential Manager (OS Windows 10).
Here're the original signatures in C:
// https://msdn.microsoft.com/en-us/library/aa375187(v=vs.85).aspx
BOOL CredWrite(
_In_ PCREDENTIAL Credential,
_In_ DWORD Flags
);
// https://msdn.microsoft.com/en-us/library/aa374804(v=vs.85).aspx
BOOL CredRead(
_In_ LPCTSTR TargetName,
_In_ DWORD Type,
_In_ DWORD Flags,
_Out_ PCREDENTIAL *Credential
);
typedef struct _CREDENTIAL {
DWORD Flags;
DWORD Type;
LPTSTR TargetName;
LPTSTR Comment;
FILETIME LastWritten;
DWORD CredentialBlobSize;
LPBYTE CredentialBlob;
DWORD Persist;
DWORD AttributeCount;
PCREDENTIAL_ATTRIBUTE Attributes;
LPTSTR TargetAlias;
LPTSTR UserName;
} CREDENTIAL, *PCREDENTIAL;
typedef struct _CREDENTIAL_ATTRIBUTE {
LPTSTR Keyword;
DWORD Flags;
DWORD ValueSize;
LPBYTE Value;
} CREDENTIAL_ATTRIBUTE, *PCREDENTIAL_ATTRIBUTE;
Here're my maps in Java:
WinCrypt instance = (WinCrypt) Native.loadLibrary("Advapi32", WinCrypt.class, W32APIOptions.DEFAULT_OPTIONS);
public boolean CredWrite(
CREDENTIAL.ByReference Credential,
int Flags
);
public boolean CredRead(
String TargetName,
int Type,
int Flags,
PointerByReference Credential
);
public static class CREDENTIAL extends Structure {
public int Flags;
public int Type;
public String TargetName;
public String Comment;
public FILETIME LastWritten;
public int CredentialBlobSize;
public byte[] CredentialBlob = new byte[128];
public int Persist;
public int AttributeCount;
public CREDENTIAL_ATTRIBUTE.ByReference Attributes;
public String TargetAlias;
public String UserName;
public static class ByReference extends CREDENTIAL implements Structure.ByReference {
public ByReference() {
}
public ByReference(Pointer memory) {
super(memory); // LINE 55
}
}
public CREDENTIAL() {
super();
}
public CREDENTIAL(Pointer memory) {
super(memory);
read(); // LINE 65
}
@Override
protected List<String> getFieldOrder() {
return Arrays.asList(new String[] {
"Flags",
"Type",
"TargetName",
"Comment",
"LastWritten",
"CredentialBlobSize",
"CredentialBlob",
"Persist",
"AttributeCount",
"Attributes",
"TargetAlias",
"UserName"
});
}
}
public static class CREDENTIAL_ATTRIBUTE extends Structure {
public String Keyword;
public int Flags;
public int ValueSize;
public byte[] Value = new byte[128];
public static class ByReference extends CREDENTIAL_ATTRIBUTE implements Structure.ByReference {
}
@Override
protected List<String> getFieldOrder() {
return Arrays.asList(new String[] {
"Keyword",
"Flags",
"ValueSize",
"Value"
});
}
}
First I tried to write a credential to Windows Credential Manager:
String password = "passwordtest";
int cbCreds = 1 + password.length();
CREDENTIAL.ByReference credRef = new CREDENTIAL.ByReference();
credRef.Type = WinCrypt.CRED_TYPE_GENERIC;
credRef.TargetName = "TEST/account";
credRef.CredentialBlobSize = cbCreds;
credRef.CredentialBlob = password.getBytes();
credRef.Persist = WinCrypt.CRED_PERSIST_LOCAL_MACHINE;
credRef.UserName = "administrator";
boolean ok = WinCrypt.instance.CredWrite(credRef, 0);
int rc = Kernel32.INSTANCE.GetLastError();
String errMsg = Kernel32Util.formatMessage(rc);
System.out.println("CredWrite() - ok: " + ok + ", errno: " + rc + ", errmsg: " + errMsg);
Output of the try to write:
CredWrite() - ok: false, errno: 87, errmsg: The parameter is incorrect.
Then I tried to read an existing credential from Windows Credential Manager:
PointerByReference pref = new PointerByReference();
boolean ok = WinCrypt.instance.CredRead("build-apps", WinCrypt.CRED_TYPE_DOMAIN_PASSWORD, 0, pref);
int rc = Kernel32.INSTANCE.GetLastError();
String errMsg = Kernel32Util.formatMessage(rc);
System.out.println("CredRead() - ok: " + ok + ", errno: " + rc + ", errmsg: " + errMsg);
CREDENTIAL cred = new CREDENTIAL.ByReference(pref.getPointer()); // LINE 44
Output of the try to read:
CredRead() - ok: true, errno: 0, errmsg: The operation completed successfully.
Exception in thread "main" java.lang.IllegalArgumentException: Structure exceeds provided memory bounds
at com.sun.jna.Structure.ensureAllocated(Structure.java:366)
at com.sun.jna.Structure.ensureAllocated(Structure.java:346)
at com.sun.jna.Structure.read(Structure.java:552)
at com.abc.crypt.WinCrypt$CREDENTIAL.<init>(WinCrypt.java:65)
at com.abc.crypt.WinCrypt$CREDENTIAL$ByReference.<init>(WinCrypt.java:55)
at com.abc.crypt.CryptTest.main(CryptTest.java:44)
Caused by: java.lang.IndexOutOfBoundsException: Bounds exceeds available space : size=8, offset=200
at com.sun.jna.Memory.boundsCheck(Memory.java:203)
at com.sun.jna.Memory$SharedMemory.boundsCheck(Memory.java:87)
at com.sun.jna.Memory.share(Memory.java:131)
at com.sun.jna.Structure.ensureAllocated(Structure.java:363)
... 5 more
So the try to write failed, the try to read succeeded but failed to create a CREDENTIAL object based on the output.
According to the webpage of CredWrite API, the errno 87 I got in write test is the following error:
ERROR_INVALID_PARAMETER
Certain fields cannot be changed in an existing credential. This error is returned if a field does not match the value in a protected field of the existing credential.
However the value I put in CREDENTIAL instance is a new credential rather than an existing one in the Windows Credential Manager.
Any suggestion or idea on how to fix/improve is appreciated.
===================================
UPDATE AFTER APPLYING FIX:
New CredRead:
public boolean CredRead(
String TargetName,
int Type,
int Flags,
CREDENTIAL.ByReference Credential
);
Test for CredRead:
CREDENTIAL.ByReference pref = new CREDENTIAL.ByReference();
boolean ok = WinCrypt.instance.CredRead("TEST/account", WinCrypt.CRED_TYPE_GENERIC, 0, pref);
int rc = Kernel32.INSTANCE.GetLastError();
String errMsg = Kernel32Util.formatMessage(rc);
System.out.println("CredRead() - ok: " + ok + ", errno: " + rc + ", errmsg: " + errMsg);
System.out.println(String.format("Read username = '%s', password='%S' (%d bytes)\n",
pref.UserName, pref.CredentialBlob, pref.CredentialBlobSize));
Result:
CredRead() - ok: true, errno: 0, errmsg: The operation completed successfully.
Read username = 'null', password='NULL' (0 bytes)
I checked how JNA samples in contrib use ByReference on out arg and they are doing in the same way by newing a ByReference and pass to the function.