【发布时间】:2016-07-15 20:23:13
【问题描述】:
我正在尝试在 JNA 中映射 CredWrite/CredRead,以便将在我的 Java 应用程序中使用的第三方凭据存储在 Windows 凭据管理器 (OS Windows 10) 中。
这是 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;
这是我的 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"
});
}
}
首先我尝试向 Windows 凭据管理器写入凭据:
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);
尝试写入的输出:
CredWrite() - ok: false, errno: 87, errmsg: The parameter is incorrect.
然后我尝试从 Windows 凭据管理器中读取现有凭据:
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
尝试读取的输出:
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
所以尝试写入失败,尝试读取成功但未能根据输出创建 CREDENTIAL 对象。
根据CredWrite API的网页,我在写测试中得到的errno 87是以下错误:
ERROR_INVALID_PARAMETER
无法更改现有凭据中的某些字段。如果字段与受保护的值不匹配,则返回此错误 现有凭据的字段。
但是,我在 CREDENTIAL 实例中输入的值是新的凭据,而不是 Windows 凭据管理器中的现有凭据。
感谢任何有关如何修复/改进的建议或想法。
====================================
应用修复后更新:
新的CredRead:
public boolean CredRead(
String TargetName,
int Type,
int Flags,
CREDENTIAL.ByReference Credential
);
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));
结果:
CredRead() - ok: true, errno: 0, errmsg: The operation completed successfully.
Read username = 'null', password='NULL' (0 bytes)
我检查了 contrib 中的 JNA 样本如何在 out arg 上使用 ByReference,它们通过新的 ByReference 并传递给函数以相同的方式进行操作。
【问题讨论】:
标签: java winapi jna credentials