Search code examples
javacmacospointersjna

Get result of function by void** parameter as char[] with JNA


TL;DR: What JNA type do I use for a void** pointer (that is really a char**), and how do I access the result from it?

I'm trying to get a password from macOS Keychain in Java using this C function:

OSStatus SecKeychainFindGenericPassword(CFTypeRef keychainOrArray,
        UInt32 serviceNameLength,
        const char *serviceName,
        UInt32 accountNameLength,
        const char *accountName,
        UInt32 *passwordLength,
        void * _Nullable *passwordData,
        SecKeychainItemRef  _Nullable *itemRef);

(Usage example.)

The problem is, I'm not sure how to handle the passwordData parameter. I managed to call it from C (see bottom of post). But, in Java, I'm getting nondeterministic and incorrect results for the passwordData parameter. Here's my attempt to call it from Java:

import com.sun.jna.*;
import com.sun.jna.ptr.*;

public class JnaTest {

    public interface Security extends Library {
        int SecKeychainFindGenericPassword(
                Object keychainOrArray,
                int serviceNameLength,
                String serviceName,
                int accountNameLength,
                String accountName,
                IntByReference passwordLength,
                // I've also tried char[][] and Pointer, which both give errors
                PointerByReference passwordData,
                Object itemRef);
    }

    public static void main(String[] args) {
        Security sec = Native.loadLibrary("Security", Security.class);

        PointerByReference pass = new PointerByReference();
        IntByReference len = new IntByReference();
        int rc = sec.SecKeychainFindGenericPassword(
                null,
                10, "SurfWriter",
                10, "MyUserAcct",
                len, pass,
                null);

        System.out.println(rc); // 0, good
        System.out.println(len.getValue()); // 11, same as in C
        // This prints Unicode characters nondeterministically; buffer overrun?
        System.out.println(new String(pass.getValue().getCharArray(0, len.getValue())));
    }

}

I was able to write this C program to do the same thing, and it works fine, so the problem is almost definitely with reading passwordData in JNA:

#include <Security/Security.h>

int main() {
    unsigned int len;
    void* pass;
    int rc = SecKeychainFindGenericPassword(
            NULL,
            10, "SurfWriter",
            10, "MyUserAcct",
            &len, &pass,
            NULL);
    printf("%d %s\n", len, pass);
}

Solution

  • It turns out that it works if I do this:

    pass.getValue().getString(0);
    

    It works. Perfectly. Unfortunately, this is not a complete solution, as I would like to avoid storing passwords in Strings for security reasons. Playing around some more, I found that this works:

    new String(pass.getValue().getByteArray(0, len.getValue()-4));
    

    (The -4 is because len is too large by 4 for some reason; I can't find anything on this in the docs for Security.h.) I realized that the "random" passwords it was printing before were only random in the second half, which leads me to a conclusion: Java uses 16-bit chars internally, so each of those characters I was getting was really two characters, and I was reading twice as far as necessary. Java was treating 2 ASCII characters as one UTF-16 character. (Shouldn't JNA handle this?)