[英]How to map Windows API CredWrite/CredRead in JNA?
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).我正在尝试在 JNA 中映射 CredWrite/CredRead,以便将我的 Java 应用程序中使用的第三方凭据存储在 Windows 凭据管理器(操作系统 Windows 10)中。
Here're the original signatures in C:这是 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:这是我的 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:首先,我尝试向 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);
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:然后我尝试从 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
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.因此尝试写入失败,尝试读取成功但未能根据输出创建 CREDENTIAL 对象。
According to the webpage of CredWrite API, the errno 87 I got in write test is the following error:根据CredWrite API的网页,我在写测试中得到的errno 87是以下错误:
ERROR_INVALID_PARAMETER
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.但是,我放入 CREDENTIAL 实例的值是一个新的凭据,而不是 Windows 凭据管理器中的现有凭据。
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:测试 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.我检查了 contrib 中的 JNA 样本如何在 out arg 上使用 ByReference 并且它们以相同的方式通过新建 ByReference 并传递给函数来执行。
If you look at the WIN32 definition of CredRead()
, the fourth parameter is of type PCREDENTIAL* ie it's a pointer to a pointer.如果您查看
CredRead()
的 WIN32 定义,则第四个参数的类型为 PCREDENTIAL*,即它是指向指针的指针。 So...所以...
CredRead()
), you get another pointer (the 4-byte block), which itself needs to be dereferenced to get to the CREDENTIAL.CredRead()
指针)时,您会得到另一个指针(4 字节块),它本身需要取消引用才能获得 CREDENTIAL。 Welcome to C :-)欢迎来到 C :-)
TL;DR: The CREDENTIAL class needs to be defined like this: TL;DR:CREDENTIAL 类需要像这样定义:
public static class CREDENTIAL extends Structure {
public int Flags;
public int Type;
public WString TargetName;
public WString Comment;
public FILETIME LastWritten;
public int CredentialBlobSize;
public Pointer CredentialBlob; // <== discussed below
public int Persist;
public int AttributeCount;
public Pointer Attributes;
public WString TargetAlias;
public WString UserName;
private Pointer RawMemBlock; // <== discussed below
public CREDENTIAL() { }
public CREDENTIAL( Pointer ptr )
{
// initialize ourself from the raw memory block returned to us by ADVAPI32
super( ptr ) ;
RawMemBlock = ptr ;
read() ;
}
@Override
protected void finalize()
{
// clean up
WinCrypt.INSTANCE.CredFree( RawMemBlock ) ;
}
@Override
protected List<String> getFieldOrder()
{
return Arrays.asList( new String[] { "Flags" , "Type" , "TargetName" , "Comment" , "LastWritten" , "CredentialBlobSize" , "CredentialBlob" , "Persist" , "AttributeCount" , "Attributes" , "TargetAlias" , "UserName" } ) ;
}
} ;
To call CredRead()
, declare it like this:要调用
CredRead()
,请像这样声明:
public boolean CredRead( String target , int type , int flags , PointerByReference cred ) ;
and invoke it like this:并像这样调用它:
PointerByReference pptr = new PointerByReference() ;
boolean rc = WinCrypt.INSTANCE.CredRead( target , credType , 0 , pptr ) ;
if ( ! rc )
... ; // handle the error
CREDENTIAL cred = new CREDENTIAL( pptr.getValue() ) ;
String userName = cred.UserName.toString() ;
String password = new String( cred.CredentialBlob.getByteArray(0,cred.CredentialBlobSize) , "UTF-16LE" ) ;
The credential blob is another block of memory allocated by Windows, so you don't need to allocate it yourself, Windows will do it, and will tell you where it is by putting its address in the CredentialBlob field.凭据 blob 是 Windows 分配的另一块内存,因此您不需要自己分配它,Windows 会自己分配,并通过将其地址放在 CredentialBlob 字段中来告诉您它的位置。
Since Windows has allocated these blocks of memory for you, and since it has no way of knowing when you will be finished with them, it's your responsibility to free them.由于 Windows 已为您分配了这些内存块,并且无法知道您何时完成它们,因此您有责任释放它们。 So, the CREDENTIAL constructor keeps a copy of the raw pointer
CredRead()
gave it, and calls CredFree()
in the finalizer, to free that memory.因此,CREDENTIAL 构造函数保留了给它的原始指针
CredRead()
的副本,并在终结器中调用CredFree()
以释放该内存。 CredFree()
is declared like this: CredFree()
声明如下:
public void CredFree( Pointer cred ) ;
To save a credential, you need to prepare the credential blob in the way that CredWrite()
is expecting ie by storing a pointer to it in the CREDENTIAL.CredentialBlob field:要保存凭据,您需要按照
CredWrite()
期望的方式准备凭据 blob,即通过在 CREDENTIAL.CredentialBlob 字段中存储指向它的指针:
// prepare the credential blob
byte[] credBlob = password.getBytes( "UTF-16LE" ) ;
Memory credBlobMem = new Memory( credBlob.length ) ;
credBlobMem.write( 0 , credBlob , 0 , credBlob.length ) ;
// create the credential
CREDENTIAL cred = new CREDENTIAL() ;
cred.Type = CRED_TYPE_GENERIC ;
cred.TargetName = new WString( target ) ;
cred.CredentialBlobSize = (int) credBlobMem.size() ;
cred.CredentialBlob = credBlobMem ;
cred.Persist = CRED_PERSIST_LOCAL_MACHINE ;
cred.UserName = new WString( userName ) ;
// save the credential
boolean rc = WinCrypt.INSTANCE.CredWrite( cred , 0 ) ;
if ( ! rc )
... ; // handle the error
As an addendum, all this will run into problems if it's being run under a service account, or any other account that doesn't have a permanent profile.作为附录,如果它在服务帐户或任何其他没有永久配置文件的帐户下运行,所有这些都会遇到问题。 I needed to do this for a job being run via Task Scheduler, using a service account that didn't have interactive login rights, and what happens is:
我需要为通过 Task Scheduler 运行的作业执行此操作,使用没有交互式登录权限的服务帐户,结果是:
The solution is to create a permanent profile, ideally by logging in interactively, which only needs to be done once.解决方案是创建一个永久的配置文件,理想情况下通过交互登录,只需要完成一次。 If you can't do this, it's possible to do it programmatically although you will need admin rights for this.
如果您无法执行此操作,则可以通过编程方式执行此操作,尽管您需要为此具有管理员权限。
CredRead.PCREDENTIAL
should be a CREDENTIAL.ByReference
. CredRead.PCREDENTIAL
应该是CREDENTIAL.ByReference
。 Using PointerByReference
ends up passing in a pointer to a NULL value instead of the expected pointer to CREDENTIAL
struct.使用
PointerByReference
最终会传入一个指向 NULL 值的指针,而不是指向CREDENTIAL
结构的预期指针。
CREDENTAL.CredentialBlob
needs to be a Pointer
or PointerType
(probably Memory
if you're initializing the block yourself). CREDENTAL.CredentialBlob
需要是Pointer
或PointerType
(如果您自己初始化块,可能是Memory
)。 Using an inline byte array shifts the entire structure by the array size, where the callee is expecting a pointer to a block of memory.使用内联字节数组会按数组大小移动整个结构,其中被调用者需要一个指向内存块的指针。
UPDATE更新
I think I misread the declaration of CredRead()
.我想我误读了
CredRead()
的声明。
CredRead
should continue to use PointerByReference
. CredRead
应该继续使用PointerByReference
。 Use PointerByReference.getValue()
to extract the "returned" pointer value from CredRead()
in order to create a new CREDENTIALS
instance based on the pointer.使用
PointerByReference.getValue()
从CredRead()
中提取“返回的”指针值,以便基于指针创建新的CREDENTIALS
实例。 PointerByReference.getPointer()
gives you the address of the memory allocated to hold the pointer value. PointerByReference.getPointer()
为您提供分配用于保存指针值的内存地址。
public boolean CredWrite(
CREDENTIAL Credential,
int Flags
);
public boolean CredRead(
String TargetName,
int Type,
int Flags,
PointerByReference pref
);
PointerByReference pref = new PointerByReference()
CredRead(name, type, flags, pref);
creds = new Credentials(pref.getValue())
Microsoft provides an MIT-licensed Java library for accessing VSTS tokens. Microsoft 提供了一个 MIT 许可的 Java 库来访问 VSTS 令牌。 https://github.com/microsoft/vsts-authentication-library-for-java
https://github.com/microsoft/vsts-authentication-library-for-java
They provide a JNA mapping to Credential Manager functions and usage here: https://github.com/microsoft/vsts-authentication-library-for-java/tree/master/storage/src/main/java/com/microsoft/alm/storage/windows/internal他们在此处提供到凭证管理器功能和用法的 JNA 映射: https : //github.com/microsoft/vsts-authentication-library-for-java/tree/master/storage/src/main/java/com/microsoft/alm /存储/窗口/内部
Very helpful if you're starting from scratch.如果您从头开始,非常有帮助。
Based on taka's answer, but with following additional considerations, I implemented a full sample.基于 taka 的回答,但考虑到以下其他因素,我实现了一个完整的示例。
Following additional corrections and aspects have been considered:已考虑以下附加更正和方面:
Full sample: (note that it requires JNA library; I was using JNA Version 5.6.0 available at https://github.com/java-native-access/jna )完整示例:(请注意,它需要 JNA 库;我使用的是https://github.com/java-native-access/jna 上提供的 JNA 5.6.0 版)
package at.christoph-bimminger.sample;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.LastErrorException;
import com.sun.jna.Library;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Platform;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.Structure.FieldOrder;
import com.sun.jna.WString;
import com.sun.jna.ptr.PointerByReference;
public class Main {
public interface WinCrypt extends Library {
WinCrypt INSTANCE = (WinCrypt) Native.load("Advapi32", WinCrypt.class);
boolean CredWriteW(CREDENTIAL.ByReference credentialw, int flags) throws LastErrorException;
boolean CredReadW(WString TargetName, int Type, int Flags, PointerByReference pptr) throws LastErrorException;
public static final class Type {
/**
* The credential is a generic credential. The credential will not be used by
* any particular authentication package. The credential will be stored securely
* but has no other significant characteristics.
*/
final static int CRED_TYPE_GENERIC = 1;
/**
* The credential is a password credential and is specific to Microsoft's
* authentication packages. The NTLM, Kerberos, and Negotiate authentication
* packages will automatically use this credential when connecting to the named
* target.
*/
final static int CRED_TYPE_DOMAIN_PASSWORD = 2;
/**
* The credential is a certificate credential and is specific to Microsoft's
* authentication packages. The Kerberos, Negotiate, and Schannel authentication
* packages automatically use this credential when connecting to the named
* target.
*
*/
final static int CRED_TYPE_DOMAIN_CERTIFICATE = 3;
/**
* This value is no longer supported. Windows Server 2003 and Windows XP: The
* credential is a password credential and is specific to authentication
* packages from Microsoft. The Passport authentication package will
* automatically use this credential when connecting to the named target.
*
* Additional values will be defined in the future. Applications should be
* written to allow for credential types they do not understand.
*
*/
final static int CRED_TYPE_DOMAIN_VISIBLE_PASSWORD = 4;
/**
* The credential is a certificate credential that is a generic authentication
* package. Windows Server 2008, Windows Vista, Windows Server 2003 and Windows
* XP: This value is not supported.
*/
final static int CRED_TYPE_GENERIC_CERTIFICATE = 5;
/**
* The credential is supported by extended Negotiate packages. Windows Server
* 2008, Windows Vista, Windows Server 2003 and Windows XP: This value is not
* supported.
*
*/
final static int CRED_TYPE_DOMAIN_EXTENDED = 6;
/**
* The maximum number of supported credential types.Windows Server 2008, Windows
* Vista, Windows Server 2003 and Windows XP: This value is not supported.
*
*/
final static int CRED_TYPE_MAXIMUM = 7;
final static int CRED_TYPE_MAXIMUM_EX = CRED_TYPE_MAXIMUM + 1000;
}
public static final class Persist {
final static int CRED_PERSIST_SESSION = 1;
final static int CRED_PERSIST_LOCAL_MACHINE = 2;
final static int CRED_PERSIST_ENTERPRISE = 3;
}
}
/**
* Representation of native struct FILETIME. See
* https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime
*
* @author Christoph Bimminger
*
*/
@FieldOrder({ "dwLowDateTime", "dwHighDateTime" })
public static final class FILETIME extends Structure {
public int dwLowDateTime;
public int dwHighDateTime;
}
/**
* Representation of native struct CREDENTIALW. See
* https://docs.microsoft.com/en-us/windows/win32/api/wincred/ns-wincred-credentialw
*
* @author Christoph Bimminger
*
*/
@FieldOrder({ "flags", "type", "targetName", "comment", "lastWritten", "credentialBlobSize", "credentialBlob",
"persist", "attributeCount", "attributes", "targetAlias", "userName" })
public static class CREDENTIAL extends Structure {
public int flags;
public int type;
public WString targetName;
public WString comment;
public FILETIME lastWritten;
public int credentialBlobSize = 256;
public Pointer credentialBlob;
public int persist;
public int attributeCount;
public CREDENTIAL_ATTRIBUTE.ByReference attributes;
public WString targetAlias;
public WString 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
}
}
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" });
}
}
public static void main(String[] args) throws UnsupportedEncodingException {
if (!Platform.isWindows())
throw new UnsatisfiedLinkError("This sample requires a windows environment, it uses wincred.h");
{ // --- SAVE
String password = "brillant";
// prepare the credential blob
byte[] credBlob = password.getBytes("UTF-16LE");
Memory credBlobMem = new Memory(credBlob.length);
credBlobMem.write(0, credBlob, 0, credBlob.length);
int cbCreds = credBlob.length;
CREDENTIAL.ByReference cred = new CREDENTIAL.ByReference();
cred.type = WinCrypt.Type.CRED_TYPE_GENERIC;
cred.targetName = new WString("FOO/account");
cred.credentialBlobSize = cbCreds;
cred.credentialBlob = credBlobMem;
cred.persist = WinCrypt.Persist.CRED_PERSIST_LOCAL_MACHINE;
cred.userName = new WString("paula");
try {
boolean ok = WinCrypt.INSTANCE.CredWriteW(cred, 0);
} catch (LastErrorException error) {
int rc = error.getErrorCode();
String errMsg = error.getMessage();
System.out.println(rc + ": " + errMsg);
System.exit(1);
}
}
///////////////////// READ PASS
try {
PointerByReference pptr = new PointerByReference();
boolean ok = WinCrypt.INSTANCE.CredReadW(new WString("FOO/account"), WinCrypt.Type.CRED_TYPE_GENERIC, 0,
pptr);
CREDENTIAL cred = new CREDENTIAL(pptr.getValue());
String password = new String(cred.credentialBlob.getByteArray(0, cred.credentialBlobSize), "UTF-16LE");
System.out.println(password);
} catch (LastErrorException error) {
int rc = error.getErrorCode();
String errMsg = error.getMessage();
System.out.println(rc + ": " + errMsg);
System.exit(1);
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.