joelhockey / jacknji11

Java Native Interface for PKCS#11
MIT License
30 stars 21 forks source link

Can't derive ECDH secret #51

Open pentiak opened 2 years ago

pentiak commented 2 years ago

I am trying to derive a shared secret between two EC parties, but I have difficulties providing correct CKM. As there is no CK_ECDH1_DERIVE_PARAMS struct defined, I am trying to allocate native memory myself, but without luck so far.

Can you advise what I am doing wrong?

    public long deriveECDH(long session, byte[] otherPublicKey, long privateKeyHandle) {
    CKA[] secretTemplate = new CKA[]{
            new CKA(CKA.TOKEN, true),
            new CKA(CKA.CLASS, CKO.SECRET_KEY),
            new CKA(CKA.KEY_TYPE, CKK.GENERIC_SECRET),
            new CKA(CKA.SENSITIVE, false),
            new CKA(CKA.EXTRACTABLE, true)
    };

    Memory deriveParam = new Memory((long) NativeLong.SIZE + NativeLong.SIZE + Native.POINTER_SIZE + NativeLong.SIZE + Native.POINTER_SIZE);
    int offset = 0;
    deriveParam.setLong(offset, CKD.NULL);
    offset += NativeLong.SIZE;
    deriveParam.setLong(offset, 0L);
    offset += NativeLong.SIZE;
    deriveParam.setPointer(offset, Pointer.NULL);
    offset += Native.POINTER_SIZE;
    deriveParam.setLong(offset, otherPublicKey.length);
    offset += NativeLong.SIZE;
    Memory pubKeyPointer = new Memory(otherPublicKey.length);
    pubKeyPointer.write(0, otherPublicKey, 0, otherPublicKey.length);
    deriveParam.setPointer(offset, pubKeyPointer);

    return CE.DeriveKey(session, new CKM(CKM.ECDH1_DERIVE, deriveParam, (int) deriveParam.size()), privateKeyHandle, secretTemplate);
}
pentiak commented 1 year ago

I solved the issue using this struct I created:

package org.pkcs11.jacknji11.jna;

import com.sun.jna.Memory;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import org.pkcs11.jacknji11.CKD;

import java.util.Arrays;
import java.util.List;

/**
 * typedef struct CK_ECDH1_DERIVE_PARAMS {
 * unsigned long  kdf;
 * unsigned long  ulSharedDataLen;
 * unsigned char *  pSharedData;
 * unsigned long  ulPublicDataLen;
 * unsigned char *  pPublicData;
 * } CK_ECDH1_DERIVE_PARAMS;
 */
public class JNA_CK_ECDH1_DERIVE_PARAMS extends Structure {
    public NativeLong kdf;
    public NativeLong ulSharedDataLen;
    public Pointer pSharedData;
    public NativeLong ulPublicDataLen;
    public Pointer pPublicData;

    public JNA_CK_ECDH1_DERIVE_PARAMS(byte[] otherPublicKey) {
        super();
        this.kdf = new NativeLong(CKD.NULL);
        this.ulSharedDataLen = new NativeLong(0);
        this.pSharedData = null;
        Memory pubKeyPointer = new Memory(otherPublicKey.length);
        pubKeyPointer.write(0, otherPublicKey, 0, otherPublicKey.length);
        this.ulPublicDataLen = new NativeLong(pubKeyPointer.size());
        this.pPublicData = pubKeyPointer;
        write();
    }

    @Override
    protected List<String> getFieldOrder() {
        return Arrays.asList("kdf", "ulSharedDataLen", "pSharedData", "ulPublicDataLen", "pPublicData");
    }
}

It is working on linux an macos machines, but is failing under windows with MECHANISM_PARAM_INVALID error. Any advice what might be at fault?

Bragolgirith commented 1 year ago

This is most likely due to the structure packing alignment issue described here. In short, although the PKCS11 v2.40 spec explicitly says:

Cryptoki structures are packed to occupy as little space as is possible. Cryptoki structures SHALL be packed with 1-byte alignment.

in practice (most) Linux libraries don't do this, while (most) Windows libraries do.

Depending on the library used, calling super(ALIGN_NONE); instead of super(); might make it work on Windows.

Bragolgirith commented 1 year ago

The C# Pkcs11Interop library solves both this (structure packing alignment) and another common Cryptoki interop issue (the C ulong type size) on the library level by doing runtime feature recognition:

public Pkcs11LibraryFactory()
        {
            if (Platform.NativeULongSize == 4)
            {
                if (Platform.StructPackingSize == 0)
                    _factory = new HighLevelAPI40.Factories.Pkcs11LibraryFactory();
                else
                    _factory = new HighLevelAPI41.Factories.Pkcs11LibraryFactory();
            }
            else
            {
                if (Platform.StructPackingSize == 0)
                    _factory = new HighLevelAPI80.Factories.Pkcs11LibraryFactory();
                else
                    _factory = new HighLevelAPI81.Factories.Pkcs11LibraryFactory();
            }
        }

We could maybe consider doing something similar in the future?

pentiak commented 1 year ago

This is most likely due to the structure packing alignment issue described here. In short, although the PKCS11 v2.40 spec explicitly says:

Cryptoki structures are packed to occupy as little space as is possible. Cryptoki structures SHALL be packed with 1-byte alignment.

in practice (most) Linux libraries don't do this, while (most) Windows libraries do.

Depending on the library used, calling super(ALIGN_NONE); instead of super(); might make it work on Windows.

I've tried it and it still fails with same error. Maybe the fact that the struct for JNA_CKM has still the default alignment is at fault (pParameter - JNA_CK_ECDH1_DERIVE_PARAMS has alignment changed)?