Skip to content

Pythonista 3.5: legacy SDK for objc_util #789

@o-murphy

Description

@o-murphy

Can't find symbols to generate ed25519 keypair

import ctypes
from objc_util import *
import base64
import struct
import uuid

# --- Constants for Security.framework ---
c = ctypes.CDLL(None)
kSecAttrKeyType = c_void_p.in_dll(c, 'kSecAttrKeyType')
kSecAttrKeyTypeRSA = c_void_p.in_dll(c, 'kSecAttrKeyTypeRSA')
kSecAttrKeyTypeEC = c_void_p.in_dll(c, 'kSecAttrKeyTypeEC')

# NEW: The correct constant for Ed25519 is kSecAttrKeyTypeCurve25519
kSecAttrKeyTypeCurve25519 = c_void_p.in_dll(c, 'kSecAttrKeyTypeCurve25519')

kSecAttrKeySizeInBits = c_void_p.in_dll(c, 'kSecAttrKeySizeInBits')
kSecPrivateKeyAttrs = c_void_p.in_dll(c, 'kSecPrivateKeyAttrs')
kSecPublicKeyAttrs = c_void_p.in_dll(c, 'kSecPublicKeyAttrs')
kSecAttrIsPermanent = c_void_p.in_dll(c, 'kSecAttrIsPermanent')
kCFBooleanTrue = c_void_p.in_dll(c, 'kCFBooleanTrue')
kCFBooleanFalse = c_void_p.in_dll(c, 'kCFBooleanFalse')
kSecAttrIsExtractable = c_void_p.in_dll(c, 'kSecAttrIsExtractable')
kSecAttrApplicationTag = c_void_p.in_dll(c, 'kSecAttrApplicationTag')

# --- Native C function declarations (unchanged) ---
SecKeyGeneratePair = c.SecKeyGeneratePair
SecKeyGeneratePair.restype = c_void_p
SecKeyGeneratePair.argtypes = [c_void_p, c_void_p, c_void_p]

SecKeyCopyExternalRepresentation = c.SecKeyCopyExternalRepresentation
SecKeyCopyExternalRepresentation.restype = c_void_p
SecKeyCopyExternalRepresentation.argtypes = [c_void_p, c_void_p]

CFRelease = c.CFRelease
CFRelease.restype = None
CFRelease.argtypes = [c_void_p]

CFDataGetLength = c.CFDataGetLength
CFDataGetLength.restype = ctypes.c_long
CFDataGetLength.argtypes = [c_void_p]

CFDataGetBytePtr = c.CFDataGetBytePtr
CFDataGetBytePtr.restype = ctypes.POINTER(ctypes.c_byte)
CFDataGetBytePtr.argtypes = [c_void_p]

SecItemCopyMatching = c.SecItemCopyMatching
SecItemCopyMatching.restype = ctypes.c_int32
SecItemCopyMatching.argtypes = [c_void_p, c_void_p]

kSecClass = c_void_p.in_dll(c, 'kSecClass')
kSecClassKey = c_void_p.in_dll(c, 'kSecClassKey')
kSecReturnRef = c_void_p.in_dll(c, 'kSecReturnRef')

# --- Helper functions (unchanged) ---
def encode_string(s):
    if not isinstance(s, bytes):
        s = s.encode('utf-8')
    return struct.pack('>I', len(s)) + s

# This parser is specific to RSA and not used for EC or Ed25519
def parse_der_asn1_rsa(der_data):
    if not isinstance(der_data, bytes):
        raise TypeError(f"Expected bytes, but received {type(der_data)}.")
    if not der_data.startswith(b'\x30'):
        raise ValueError("Invalid ASN.1 data: Does not start with SEQUENCE.")
    modulus_start_index = der_data.find(b'\x02')
    if modulus_start_index == -1:
        raise ValueError("Modulus INTEGER not found.")
    exponent_start_index = der_data.find(b'\x02', modulus_start_index + 1)
    if exponent_start_index == -1:
        raise ValueError("Exponent INTEGER not found.")
    modulus_length_bytes = der_data[modulus_start_index+1]
    if modulus_length_bytes & 0x80:
        length_bytes_count = modulus_length_bytes & 0x7F
        modulus_length = int.from_bytes(der_data[modulus_start_index+2:modulus_start_index+2+length_bytes_count], 'big')
        modulus_data_start = modulus_start_index + 2 + length_bytes_count
    else:
        modulus_length = modulus_length_bytes
        modulus_data_start = modulus_start_index + 2
    modulus = der_data[modulus_data_start:modulus_data_start + modulus_length]
    exponent_length_bytes = der_data[exponent_start_index+1]
    if exponent_length_bytes & 0x80:
        length_bytes_count = exponent_length_bytes & 0x7F
        exponent_length = int.from_bytes(der_data[exponent_start_index+2:exponent_start_index+2+length_bytes_count], 'big')
        exponent_data_start = exponent_start_index + 2 + length_bytes_count
    else:
        exponent_length = exponent_length_bytes
        exponent_data_start = exponent_start_index + 2
    exponent = der_data[exponent_data_start:exponent_data_start + exponent_length]
    return exponent, modulus


def generate_key_pair(key_type, key_size_bits, application_tag=None, exportable=False):
    """
    Generates a new key pair of a specified type.
    
    Args:
        key_type: A C-void_p constant for the key type (e.g., kSecAttrKeyTypeEC, kSecAttrKeyTypeRSA).
        key_size_bits: The desired key size in bits.
        application_tag: A unique tag to store the private key in the Keychain.
        exportable: If True, the private key can be extracted and exported.
                         
    Returns:
        A tuple of (public_key, private_key) ObjCInstances.
    """
    public_attrs = NSMutableDictionary.alloc().init()
    public_attrs.setObject_forKey_(kCFBooleanFalse, kSecAttrIsPermanent)

    private_attrs = NSMutableDictionary.alloc().init()
    if application_tag:
        private_attrs.setObject_forKey_(kCFBooleanTrue, kSecAttrIsPermanent)
        private_attrs.setObject_forKey_(ns(application_tag), kSecAttrApplicationTag)
    else:
        private_attrs.setObject_forKey_(kCFBooleanFalse, kSecAttrIsPermanent)
    
    if exportable:
        private_attrs.setObject_forKey_(kCFBooleanTrue, kSecAttrIsExtractable)
    else:
        private_attrs.setObject_forKey_(kCFBooleanFalse, kSecAttrIsExtractable)

    key_gen_attrs = NSMutableDictionary.alloc().init()
    key_gen_attrs.setObject_forKey_(key_type, kSecAttrKeyType)
    key_gen_attrs.setObject_forKey_(NSNumber.numberWithInt_(key_size_bits), kSecAttrKeySizeInBits)
    key_gen_attrs.setObject_forKey_(public_attrs, kSecPublicKeyAttrs)
    key_gen_attrs.setObject_forKey_(private_attrs, kSecPrivateKeyAttrs)

    public_key_ptr = c_void_p(0)
    private_key_ptr = c_void_p(0)

    error_ptr = SecKeyGeneratePair(key_gen_attrs, byref(public_key_ptr), byref(private_key_ptr))

    if error_ptr:
        CFRelease(error_ptr)
        print(f"Error generating key pair: {error_ptr}")
        return None, None
    else:
        public_key = ObjCInstance(public_key_ptr)
        private_key = ObjCInstance(private_key_ptr)
        return public_key, private_key


def find_private_key(application_tag):
    """
    Finds a private key in the Keychain using its application tag.
    """
    query = NSMutableDictionary.alloc().init()
    query.setObject_forKey_(kSecClassKey, kSecClass)
    query.setObject_forKey_(ns(application_tag), kSecAttrApplicationTag)
    query.setObject_forKey_(kCFBooleanTrue, kSecReturnRef)
    
    key_ref_ptr = c_void_p(0)
    status = SecItemCopyMatching(query, byref(key_ref_ptr))
    
    if status == 0 and key_ref_ptr.value:
        return ObjCInstance(key_ref_ptr)
    else:
        print(f"Key not found or error occurred. Status code: {status}")
        if key_ref_ptr.value:
            CFRelease(key_ref_ptr)
        return None
       
def to_pem_format(key_data, key_type="PUBLIC KEY"):
    """
    Converts raw key data to a PEM-formatted string.
    """
    header = f"-----BEGIN {key_type}-----\n"
    footer = f"\n-----END {key_type}-----"
    
    encoded_data = base64.b64encode(key_data).decode('utf-8')
    pem_string = header + '\n'.join(encoded_data[i:i+64] for i in range(0, len(encoded_data), 64)) + footer
    
    return pem_string

# --- Main execution block for Ed25519 ---
if __name__ == "__main__":
    key_size = 256 # The key size parameter is a formality for Curve25519
    key_tag = f"com.example.curve25519key.{uuid.uuid4()}" 
    private_key_file_path = "curve25519_private_key.pem"
    public_key_file_path = "curve25519_public_key.pem"
    
    print(f"Attempting to generate an exportable {key_size}-bit Ed25519 key pair with tag: {key_tag}")

    pub_key, priv_key = generate_key_pair(kSecAttrKeyTypeCurve25519, key_size, application_tag=key_tag, exportable=True)

    if pub_key and priv_key:
        print("Ed25519 key pair generated successfully!")
        
        # --- Export and save the private key ---
        print("\nExporting the private key data...")
        priv_key_data_ref = SecKeyCopyExternalRepresentation(priv_key.ptr, None)
        if priv_key_data_ref:
            try:
                priv_data_ptr = CFDataGetBytePtr(priv_key_data_ref)
                priv_data_len = CFDataGetLength(priv_key_data_ref)
                priv_bytes = ctypes.string_at(priv_data_ptr, priv_data_len)
                CFRelease(priv_key_data_ref)
                # The PEM type for Ed25519 keys is commonly "PRIVATE KEY"
                pem_key = to_pem_format(priv_bytes, "PRIVATE KEY")
                print("\nPrivate Key (PEM format):")
                print(pem_key)
                with open(private_key_file_path, "w") as f:
                    f.write(pem_key)
                print(f"\nPrivate key successfully saved to '{private_key_file_path}'.")
            except (ValueError, TypeError) as e:
                print(f"Error exporting private key data: {e}")
        else:
            print("Error: Could not get the external representation of the private key.")
        
        # --- Export and save the public key ---
        print("\nExporting the public key data...")
        pub_key_data_ref = SecKeyCopyExternalRepresentation(pub_key.ptr, None)
        if pub_key_data_ref:
            try:
                pub_data_ptr = CFDataGetBytePtr(pub_key_data_ref)
                pub_data_len = CFDataGetLength(pub_key_data_ref)
                pub_bytes = ctypes.string_at(pub_data_ptr, pub_data_len)
                CFRelease(pub_key_data_ref)
                
                # The public key PEM type is "PUBLIC KEY"
                pem_pub_key = to_pem_format(pub_bytes, "PUBLIC KEY")
                print("\nPublic Key (PEM format):")
                print(pem_pub_key)
                
                with open(public_key_file_path, "w") as f:
                    f.write(pem_pub_key)
                print(f"\nPublic key successfully saved to '{public_key_file_path}'.")
                
            except (ValueError, TypeError) as e:
                print(f"Error exporting public key data: {e}")
        else:
            print("Error: Could not get the external representation of the public key.")

    else:
        print("Error: Failed to generate the Ed25519 key pair.")

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions