Java 如何在JNA中映射Windows API CredWrite/CredRead?
我试图在JNA中映射CredWrite/CredArder,以便在Windows凭据管理器(OS Windows 10)中存储Java应用程序中使用的第三方凭据 以下是C语言的原始签名:Java 如何在JNA中映射Windows API CredWrite/CredRead?,java,winapi,jna,credentials,Java,Winapi,Jna,Credentials,我试图在JNA中映射CredWrite/CredArder,以便在Windows凭据管理器(OS Windows 10)中存储Java应用程序中使用的第三方凭据 以下是C语言的原始签名: // https://msdn.microsoft.com/en-us/library/aa375187(v=vs.85).aspx BOOL CredWrite( _In_ PCREDENTIAL Credential, _In_ DWORD Flags ); // https://ms
// 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"
});
}
}
“尝试写入”的输出:
CredWrite() - ok: false, errno: 87, errmsg: The parameter is incorrect.
然后我尝试从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);
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
CREWARD测试:
public boolean CredRead(
String TargetName,
int Type,
int Flags,
CREDENTIAL.ByReference Credential
);
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并传递给函数。
CredRead.PCREDENTIAL
应该是凭证。ByReference
。使用PointerByReference
最终会传入一个指向空值的指针,而不是指向CREDENTIAL
struct的预期指针
CREDENTAL.CredentialBlob
需要是指针
或指针类型
(如果您自己初始化块,可能是内存
)。使用内联字节数组按数组大小移动整个结构,其中被调用方希望有一个指向内存块的指针
更新
我想我误读了credear()
的声明
creaward
应继续使用PointerByReference
。使用PointerByReference.getValue()
从CredRead()
中提取“返回的”指针值,以便基于指针创建新的凭据
实例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())
如果查看
CredRead()
的WIN32定义,第四个参数的类型为PCREDENTIAL*,即它是指向指针的指针。所以
- 您需要传入指针地址,即4字节内存块李>
- Windows分配一个内存块来保存凭证结构,然后通过将新内存块的地址放在传入的4字节块中来告诉您凭证结构的位置李>
- 当您取消引用原始指针(您传递到
)的指针)时,您会得到另一个指针(4字节块),它本身需要取消引用才能访问凭据李>CredRead()
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" } ) ;
}
} ;
public boolean CredRead( String target , int type , int flags , PointerByReference cred ) ;
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" ) ;
public void CredFree( Pointer cred ) ;
然后像这样调用它:
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" } ) ;
}
} ;
public boolean CredRead( String target , int type , int flags , PointerByReference cred ) ;
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" ) ;
public void CredFree( Pointer cred ) ;
凭据blob是Windows分配的另一个内存块,因此您不需要自己分配它,Windows会这样做,并通过将其地址放入CredentialBlob字段来告诉您它在哪里
由于Windows已经为您分配了这些内存块,并且由于它无法知道您何时将完成这些内存块,因此您有责任释放它们。因此,凭证构造函数保留原始指针CredRead()
的副本,并在终结器中调用CredFree()
,以释放该内存CredFree()
声明如下:
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" } ) ;
}
} ;
public boolean CredRead( String target , int type , int flags , PointerByReference cred ) ;
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" ) ;
public void CredFree( Pointer cred ) ;
要保存凭据,您需要按照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
作为补充,如果在服务帐户或任何其他没有永久配置文件的帐户下运行,所有这些都会遇到问题。我需要使用一个没有交互登录权限的服务帐户,通过任务调度器运行一个作业,这样做的结果是:
- 我创建了一个设置密码的批处理文件,并通过TaskScheduler运行它(这样它就可以在服务帐户下运行,密码就会进入正确的存储)
- Windows创建一个临时配置文件(检查事件日志)并将密码输入其中
- 另一个转储密码的批处理文件显示密码已成功设置
- 运行主作业有效,因为临时配置文件仍然存在,但在5或10分钟后,Windows会删除它,包括您设置的密码:-/,因此下次运行主作业时,它会失败,因为密码不再存在
解决方案是创建一个永久的配置文件,最好是通过交互方式登录,只需执行一次。如果您不能做到这一点,尽管您需要管理员权限,但也可以做到这一点。Microsoft提供了麻省理工学院许可的Java库,用于访问VSTS令牌。 它们提供了到凭证管理器功能和用法的JNA映射:
如果您是从零开始的,这将非常有用。基于taka的回答,但考虑到以下其他因素,我实现了一个完整的示例 已考虑了以下其他更正和方面:
- 在CredArdw中,targetName的类型必须为WString,而不是String。使用CredWriteW将数据写入windows vauld时,也已使用WString
- 简化:我没有使用getFieldOrder()方法,而是使用了注释样式
- 不建议直接读取Kernel32 GetLastError,因为JNA可能会调用删除前一个LastError的其他调用。如中所述,我改为捕获最后一个错误作为异常
- 如taka所示,不需要在credentialBlobSize中添加“1+”,但更重要的是使用实际内存或字节[]大小,因为UTF-8编码导致的字符串长度包含的字符数少于Windows API函数UTF-16LE编码产生的内存数
包装在.christoph-bimminger.sample;
导入java.io.UnsupportedEncodingException;
导入java.util.array;
导入java.util.List;
导入com.sun.jna.LastErrorException;
导入com.sun.jna.Library;
导入com.sun.jna.Memory;
导入com.sun.jna.Native;
接口信息处理器