我试图在JNA中映射CredWrite / CredRead,以便在Windows Credential Manager(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://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 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);
尝试写的输出:
CredWrite() - ok: false, errno: 87, errmsg: The parameter is incorrect.
然后我尝试从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
尝试阅读的输出:
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的网页,我在写测试中得到的错误87是以下错误:
ERROR_INVALID_PARAMETER
现有凭据中的某些字段无法更改。如果字段与受保护的值不匹配,则会返回此错误 现有凭证的字段。
但是,我在CREDENTIAL实例中放置的值是新凭据,而不是Windows凭据管理器中的现有凭据。
对于如何修复/改进的任何建议或想法表示赞赏。
===================================
应用FIX后更新:
新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中使用ByReference的JNA示例,并且他们通过新建ByReference并传递给函数以相同的方式进行。
答案 0 :(得分:1)
CredRead.PCREDENTIAL
应为CREDENTIAL.ByReference
。使用PointerByReference
最终会传入指向NULL值的指针,而不是指向CREDENTIAL
struct的预期指针。
CREDENTAL.CredentialBlob
必须是Pointer
或PointerType
(如果您自己初始化该块,则可能为Memory
)。使用内联字节数组将整个结构按数组大小移动,其中被调用者期望指向内存块的指针。
<强>更新强>
我想我误读了CredRead()
的声明。
CredRead
应继续使用PointerByReference
。使用PointerByReference.getValue()
提取&#34;返回&#34;来自CredRead()
的指针值,以便根据指针创建新的CREDENTIALS
实例。 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())
答案 1 :(得分:0)
如果查看CredRead()
的WIN32定义,第四个参数是PCREDENTIAL *类型,即它是指向指针的指针。所以......
CredRead()
的那个)时,会得到另一个指针(4字节块),它本身需要被解引用才能到达CREDENTIAL。 欢迎来到C: - )
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" } ) ;
}
} ;
要致电CredRead()
,请按以下方式声明:
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" ) ;
凭据blob是Windows分配的另一个内存块,因此您不需要自己分配,Windows会这样做,并通过将其地址放在CredentialBlob字段中来告诉您它在哪里。
由于Windows已经为您分配了这些内存块,并且由于它无法知道您何时完成这些内存,因此您有责任释放它们。因此,CREDENTIAL构造函数保留原始指针CredRead()
的副本,并在终结器中调用CredFree()
来释放该内存。 CredFree()
声明如下:
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
作为附录,如果它是在服务帐户或任何其他没有永久配置文件的帐户下运行,所有这些都会遇到问题。我需要为通过任务计划程序运行的作业执行此操作,使用没有交互式登录权限的服务帐户,以及会发生什么:
解决方案是创建永久性配置文件,理想情况是通过交互式登录,只需要执行一次。如果您不能这样做,可以programmatically执行此操作,尽管您需要管理员权限。
答案 2 :(得分:0)
Microsoft提供了一个MIT许可的Java库,用于访问VSTS令牌。 https://github.com/microsoft/vsts-authentication-library-for-java
它们在此处提供到凭证管理器功能和用法的JNA映射: https://github.com/microsoft/vsts-authentication-library-for-java/tree/master/storage/src/main/java/com/microsoft/alm/storage/windows/internal
如果您从头开始,非常有帮助。
答案 3 :(得分:0)
基于塔卡(taka)的回答,但出于以下考虑,我实施了一个完整的示例。
已考虑以下其他更正和方面:
完整样本: (请注意,它需要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);
}
}
}