Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding wrappers to Cryptui/Crypt32 and structures to WinCrypt. #915

Closed
wants to merge 3 commits into from

Conversation

nahzor
Copy link

@nahzor nahzor commented Jan 31, 2018

No description provided.

@nahzor
Copy link
Author

nahzor commented Jan 31, 2018

Working on fixing these. I had run 'ant dist test' but not 'test-platform'.

Copy link
Member

@matthiasblaesing matthiasblaesing left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your work Roshan! I added comments to the bindings, as some of these need work to be correct.

Please have a look at the comments and see if they are helpful for you. If you find something unclear, feel free to ask.

public int cMsgCert;
public PCERT_CONTEXT rgpMsgCert;
public int cMsgCrl;
public CRL_CONTEXT rgpMsgCrl;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks strange - accoring to the documentation:
https://msdn.microsoft.com/en-us/library/windows/desktop/aa381468(v=vs.85).aspx
this is a Pointer, not the structure itself.

public static class CTL_ENTRY extends Structure {
public DATA_BLOB SubjectIdentifier;
public int cAttribute;
public CRYPT_ATTRIBUTE rgAttribute;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the documentation this is an array of CRYPT_ATTRIBUTE. This binding would not even work with a single CRYPT_ATTRIBUTE, as the attribute is not embedded in the structure. This might help:

https://www.eshayne.com/jnaex/index.html?example=16

public int cbSize;
public CRL_CONTEXT pBaseCRLContext;
public CRL_CONTEXT pDeltaCRLContext;
public CRL_ENTRY pCrlEntry;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the above elements are defined as pointers to structures and not embedded. These should be defined as CRL_CONTEXT.ByReference and CRL_ENTRY.ByReference

public CRL_CONTEXT pBaseCRLContext;
public CRL_CONTEXT pDeltaCRLContext;
public CRL_ENTRY pCrlEntry;
public boolean fDeltaCrlEntry;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a BOOL on the native side. To make this work you'll need to specify a type mapper to get this correctly mapped. See SSPI.SecPkgInfo. There the W32APITypeMapper.DEFAULT is used, that mapper not only maps java boolean to native BOOL, it also maps string correctly to a WString if the unicode binding is used.

This also applies to other structures below.

public int cbSize;
public int dwRevocationResult;
public LPSTR pszRevocationOid;
public LPVOID pvOidSpecificInfo;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the info should be supplied from the java side I would think about using a plain Pointer, that way you can prepare a Memory object and set it here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not used anymore but i've updated the type.

*/
boolean CertGetCertificateChain(Pointer hChainEngine, CERT_CONTEXT pCertContext, Pointer pTime,
Pointer hAdditionalStore, CERT_CHAIN_PARA pChainPara, int dwFlags, Pointer pvReserved,
CERT_CHAIN_CONTEXT ppChainContext);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good if a subclass of HANDLE would be introduces for hChainEngine and that the two predefined engines HCCE_CURRENT_USER and HCCE_LOCAL_MACHINE are bound.

pTime should be a FILETIME.

I would consider introducing HCERTSTORE as a subclass for HANDLE for the hAdditionalStore parameter.

It would be good if a unittests would be added, that verifies, that the output parameter ppChainContext is correctly filled.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated these.

* @return If the function succeeds, the return value is TRUE. If the function
* fails, the return value is FALSE.
*/
boolean CertCloseStore(HANDLE hCertStore, int dwFlags);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment for CertGetCertificateChain the hCertStore parameter is used inconsistently. It would be good if this would be unified.

* null character. If psz is NULL or csz is zero, returns the required
* size of the destination string.
*/
int CertNameToStr(int dwCertEncodingType, Pointer pName, int dwStrType, char[] psz, int csz);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pName is a PCERT_NAME_BLOB which is typedeffed from DATA_BLOB. Can the correct type be used here?

The char[] mapping for psz is problematic. This is correct if the system property w32.ascii is not set. This property determines if the Ascii or the unicode variant of the type- and functionmapper is used by default.

In this case I'd advise this: Bind CertNameToStr with Pointer as psz parameter and add utility method to Crypt32Util. That function would determine the needed size of the memory block to hold the name (depending on the value of the mentioned system property the factor 2 (unicode in windows is UCS2), allocate that memory, call the function and read the string from the memory.

See the comment about the return value:

Returns the number of characters converted, including the terminating null character.
If psz is NULL or csz is zero, returns the required size of the destination string.

* the policy.
*/
boolean CertVerifyCertificateChainPolicy(int pszPolicyOID, CERT_CHAIN_CONTEXT pChainContext,
CERT_CHAIN_POLICY_PARA pPolicyPara, CERT_CHAIN_POLICY_STATUS pPolicyStatus);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pszPolicyOID is a string, not an int. Beware - this is a LPCSTR so must always be called with ascii type mapping!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the methods in Crypt32 have params for both UNICODE and ASCII, would it be okay to keep the type mapper as the DEFAULT one and use 'com.sun.jna.platform.win32.WTypes.LPSTR' for pszPolicyOID?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can do this, but then you need to fix it first. LPSTR is defined as:

typedef char CHAR;
typedef /* [string] */  __RPC_string CHAR *LPSTR;

So it is an array of chars. The current definition in WTypes.java maps it to w_char. This should to it:

    public static class LPSTR extends PointerType {
        public static class ByReference extends LPSTR implements
                Structure.ByReference {
        }

        public LPSTR() {
            super(Pointer.NULL);
        }

        public LPSTR(Pointer pointer) {
            super(pointer);
        }

        public LPSTR(String value) {
            this(new Memory(value.length() + 1L));
            this.setValue(value);
        }

        public void setValue(String value) {
            this.getPointer().setString(0, value);
        }

        public String getValue() {
            Pointer pointer = this.getPointer();
            String str = null;
            if (pointer != null)
                str = pointer.getString(0);

            return str;
        }

        @Override
        public String toString() {
            return this.getValue();
        }
    }

Please test this!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I start working on this and push out the changes i made for the other comments.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated and tested this.

* CertFreeCertificateContext function.
*/
CERT_CONTEXT CryptUIDlgSelectCertificateFromStore(HANDLE hCertStore, HWND hwnd, String pwszTitle,
String pwszDisplayString, int dwDontUseColumn, int dwFlags, PointerType pvReserved);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter hCertStore should be checked - see above comments. For the string mappings beware! these are defined as LPWSTR and the binding to String is only correct if w32.ascii is not set/set to false or the whole binding is mapped with the W32APITypeMapper.UNICODE.

@matthiasblaesing
Copy link
Member

I missed another thing: This is a big enhancement, you should add an entry to CHANGES.md. Please have a look at the other entries for the general structure.

@nahzor
Copy link
Author

nahzor commented Feb 6, 2018

Thank you for the comments, Matthias. I will start working on these.

@nahzor
Copy link
Author

nahzor commented Feb 8, 2018

Hi Matthias, Could you take a look at the changes again and let me know if i need to makes any more changes?

@@ -179,7 +179,7 @@ public String getString() {
}

public static class LPSTR extends PointerType {
public static class ByReference extends BSTR implements
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure you don't extend BSTR?

Copy link
Member

@matthiasblaesing matthiasblaesing Feb 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct. The inner class LPSTR.ByReference is a LPSTR class marked as ByReference (the latter is a marker interface, that forces JNA to pass the value as a reference).

Copy link
Member

@matthiasblaesing matthiasblaesing left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went through the code again. The main thing I noticed was the mapping of array elements. If the structures are read-only (info only flows from native to java), you can just add a getter to get the whole array via

Structure#toArray

If the info is also set from the Java side, then it is more a question when synchronization happens. Structures are automaticly synchronizing the java mirrors, when a transtion Java<->Native or Native<->Java happens. For arrays this is difficult....

* function fails, the return value is zero (FALSE).
*/
boolean CryptSignMessage(CRYPT_SIGN_MESSAGE_PARA pSignPara, boolean fDetachedSignature, int cToBeSigned,
Pointer rgpbToBeSigned, Pointer rgcbToBeSigned, Pointer pbSignedBlob, IntByReference pcbSignedBlob);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rgcbToBeSigned is an array of DWORD/int
rgpbToBeSigned is an array of Pointers

I would expect, that the user setups one or multiple Memory objects that hold the data to be signed and that binding would match this expected usage better.

@@ -179,7 +179,7 @@ public String getString() {
}

public static class LPSTR extends PointerType {
public static class ByReference extends BSTR implements
Copy link
Member

@matthiasblaesing matthiasblaesing Feb 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct. The inner class LPSTR.ByReference is a LPSTR class marked as ByReference (the latter is a marker interface, that forces JNA to pass the value as a reference).

// You can use a command similar to the following to create a
// certificate that can be used with this example:
//
// makecert -n "cn=cryptsigntest" -sk cryptsigntest -ss my
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about creating the certificates on the fly and add them to the user stored. At test shutdown these could be removed again.

That way you could really test the functions.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am having some issues with implementing the CryptAcquireContext wrapper in advapi32, which is part of the workflow for creating a self signed certificate. I will update this after i figure out the issue.

}
IntByReference pcbSignedBlob2 = new IntByReference(0);
Crypt32.INSTANCE.CryptSignMessage(sigParams, false, messages2.length, rgpbToBeSigned2, rgcbToBeSigned2, Pointer.NULL, pcbSignedBlob2);
int signedSize2 = pcbSignedBlob2.getValue();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not use StringArray here - its byte arreay, that are signed, so I would use a Memory (see my comment in the CryptSignMessage method).

See also the comment in testCertGetCertificateChain for usage of a generated certificate.

*/
public static Pointer allocateTCharMemory(int size) {
size = Boolean.getBoolean("w32.ascii") ? size : size * 2;
return new Memory(size);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used somewhere? The size looks off by one (if not otherwise stated I expect strings to be terminate by a NULL byte. So size needs to be corrected, as size denotes number of characters, not bytes.

I would not use this function and add wrappers for the functions to Crypt32Util that handle the allocation inline.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had misunderstood your last comment about CertNameToStr. I've updated it to have a utility wrapper method for it instead of this.

public CRYPT_ALGORITHM_IDENTIFIER HashAlgorithm;
public Pointer pvHashAuxInfo;
public int cMsgCert;
public PCERT_CONTEXT.ByReference rgpMsgCert;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an array.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been having some issue getting the tests to pass after changing these to pointers and adding setters that handle conversion from arrays. I'll update this in the next commit once i figure out the right way to have it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had used this example to create the structure and test: https://www.eshayne.com/jnaex/index.html?example=6

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am having difficulty(Invalid memory access) setting it to a pointer and getting it to work. But this seems to work:
'''

           public int cMsgCert;	
            public PCERT_CONTEXT.ByReference rgpMsgCert;
	public void setRgpMsgCert(CERT_CONTEXT.ByReference[] contexts) {
		PCERT_CONTEXT.ByReference certContextArray = new PCERT_CONTEXT.ByReference();
		PCERT_CONTEXT[] certContextArrayValues = (PCERT_CONTEXT[]) certContextArray.toArray(contexts.length);
		
		for(int i = 0; i<contexts.length; i++) {
		certContextArrayValues[i].certContext = contexts[i];
		}
		
		this.cMsgCert = contexts.length;
		this.rgpMsgCert = certContextArray;
	}

'''
If this implementation sounds good to you, i'll implement the same for the rest of the array members in this structure and test them.
(I think this structure is the last change i have to make. I've pushed out changes for the rest.)

public int cMsgCert;
public PCERT_CONTEXT.ByReference rgpMsgCert;
public int cMsgCrl;
public CRL_CONTEXT.ByReference rgpMsgCrl;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an array.

public int cMsgCrl;
public CRL_CONTEXT.ByReference rgpMsgCrl;
public int cAuthAttr;
public CRYPT_ATTRIBUTE.ByReference rgAuthAttr;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an array.

public int cAuthAttr;
public CRYPT_ATTRIBUTE.ByReference rgAuthAttr;
public int cUnauthAttr;
public CRYPT_ATTRIBUTE.ByReference rgUnauthAttr;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an array.

public int cUnauthAttr;
public CRYPT_ATTRIBUTE.ByReference rgUnauthAttr;
public int dwFlags;
public int dwInnerContentType;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MSDN sees two further members here:
https://msdn.microsoft.com/en-us/library/windows/desktop/aa381468(v=vs.85).aspx
HashEncryptionAlgorithm and pvHashEncryptionAuxInfo

@nahzor
Copy link
Author

nahzor commented Feb 13, 2018

Thank you. I'm working on these and will send an update soon.

@matthiasblaesing
Copy link
Member

matthiasblaesing commented Feb 23, 2018

Thank you for the updates. I will not have the time for a full review this weekend, but I rechecked the array structures and I think the approach will work for the plain structure arrays, but there are arrays, that are arrays of pointers to structures. These are the ones I found:

  • CERT_SIMPLE_CHAIN.rgpElement
  • CERT_CHAIN_CONTEXT.rgpChain
  • CERT_CHAIN_CONTEXT.rgpLowerQualityChainContext
  • CERT_INFO.rgExtension
  • CRL_ENTRY.rgExtension
  • CRL_INFO.rgCRLEntry
  • CRL_INFO.rgExtension
  • CRYPT_KEY_PROV_INFO.rgProvParam
  • CRYPT_SIGN_MESSAGE_PARA.rgpMsgCert
  • CRYPT_SIGN_MESSAGE_PARA.rgpMsgCrl
  • CRYPT_SIGN_MESSAGE_PARA.rgAuthAttr
  • CRYPT_SIGN_MESSAGE_PARA.rgUnauthAttr

For CTL_USAGE you could see if you manage to bind it as a plain String[] (with an ASCII type mapper). In principle this should work.

@nahzor
Copy link
Author

nahzor commented Feb 23, 2018

I tried this out for one of the array of pointer to structures and tested it with CryptSignMessage. The following approach works:

        public int cMsgCert;
	public PointerByReference rgpMsgCert;
	
	public void setRgpMsgCert(CERT_CONTEXT.ByReference[] contexts) {
		this.cMsgCert = contexts.length;
		this.rgpMsgCert = new PointerByReference(contexts[0].getPointer());
	}

Could you confirm if this looks okay? Thanks!

Edit: I added a test for retrieving from an array of pointer types (CERT_INFO.rgExtension) after a certificate is retrieved from the store and this seems to retrieve the array properly. Was that the issue you were concerned about?

@matthiasblaesing
Copy link
Member

I had another look at the bindings and tried to implement the samples from MSDN using these. To get to a testable point I updated structures and bindings. From my perspective the most important point: The modified tests actually test the functionality. The sample programms from the MSDN look like a good place to start:

https://msdn.microsoft.com/en-us/library/windows/desktop/aa382372(v=vs.85).aspx

The primary problem of the API is the massive use of Pointer types and arrays. Arrays are problematic if the are pointers to structures, as the java side needs to keep a strong reference to prevent the structure from premature GC. This can be worked around, if the structure is used only one way (see CRYPT_SIGN_MESSAGE_PARA), but is problematic if the structure is written/modified from native and java side.

I squashed your commits and added my modifications on top:

https://github.com/matthiasblaesing/jna/tree/rosh89-CryptoLib

This is not a complete solution, but it shows what I mean above. When I updated the SSPI bindings, I started with separate extensions of the bindings in a separate project. I implemented the basic functions to check if the bindings are viable.

The result can be seen here:

https://github.com/matthiasblaesing/jna-sspi-sample

I repeat my question: What is the intention of the bindings? Most functions could be easier handled by using the bouncy castle libraries or even the JDK core classes. I would focus on the core of what you want and that can't be archived with pure java.

matthiasblaesing and others added 2 commits March 11, 2018 19:04
- CryptSignMessage + CryptVerifyMessageSignature functions and structures
  rebound and updated testcase to test full cycle
- In the structures holding arrays, the basic binding hold now only a
  pointer and adds a helper, that converts the pointer to the array of
  structures. Allowing improved bindings to use the pointer to add
  custom bindings. WinCryptUtil.MANAGED_CRYPT_SIGN_MESSAGE_PARA is a
  sample for this.
- Updated a working test for Crypt32#CertNameToStr
- Adding working version of Crypt32Util#CertNameToStr
- Use custom PKCS12 Store to test CertGetCertificateChain and update
  bindings of associated structures
- Use Structure#createFieldsOrder and hold fieldOrder in static field
  instead of recreating it on all calls
- Move Indent to 4 spaces (as in surrounding code)
@matthiasblaesing
Copy link
Member

Merged updated changeset via e939e66

Thank you for working through this.

@nahzor
Copy link
Author

nahzor commented Mar 13, 2018

Thanks Matthias.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants