accessing private key without username/password from daemon

I am trying to read the private key from certificate in the system keychain on the client to sign random data send by the server.

Note that the certificates aren't distributed by me. Users will install the certificate(s) either by downloading them from the different servers or importing pkcs file.


I am using below code.


std::string osxPrivateKey::signData(const uint8_t* pData, uint32_t nDataSize, vector
<uint8_t>& aSignature) {


    OSStatus nStatus;
    osxObject<SecTransformRef> signer;

    CFDataRef rawData = CFDataCreate(NULL, (const uint8_t*)pHash, nHashSize);
    CFErrorRef error;

    SecTransformRef signerRef = SecSignTransformCreate(m_privKey.get(), &error);
    signer.set(signerRef);
    if (error) {
        return false;
    }

    SecTransformSetAttribute(signer.get(), kSecTransformInputAttributeName, rawData, &error);
    SecTransformSetAttribute(signer.get(), kSecInputIsAttributeName , kSecInputIsPlainText, &error);
    //SecTransformSetAttribute(signer.get(), kSecPaddingKey, kSecPaddingPKCS1Key, &error);

    //SecTransformSetAttribute(signer.get(), kSecDigestTypeAttribute, kSecDigestSHA1, NULL);
    SecTransformSetAttribute(signer.get(), kSecDigestTypeAttribute, kSecDigestSHA2, NULL);
    

    int digestLength = 160;
    //if (type ==2)
    digestLength = 256;

    CFNumberRef dLen = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &digestLength);
    Boolean set = SecTransformSetAttribute(signer.get(), kSecDigestLengthAttribute, dLen, &error);
    CFRelease(dLen);
    
    if (error) {
        return false;
    }
    DSVERBOSE(Sardeep, "SecTransformExecute begin");
    Boolean allowed;
    SecKeychainGetUserInteractionAllowed(&allowed);
    DSVERBOSE(Sardeep, "SecKeychainGetUserInteractionAllowed '%d'", allowed);
    SecKeychainSetUserInteractionAllowed(true);
    CFDataRef signature = (CFDataRef)SecTransformExecute(signer.get(), &error);
    if (error) {
        CFStringRef errorDesc = CFErrorCopyDescription(error);
        CFIndex length = CFStringGetLength(errorDesc);
        CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
        char *buffer = (char *)malloc(maxSize);
        CFStringGetCString(errorDesc, buffer, maxSize,
                           kCFStringEncodingUTF8);
        DSERROR(facility, "SecTransformExecute error : '%s'", buffer);
        delete buffer;
        return false;
    }
    DSVERBOSE(Sardeep, "SecTransformExecute end");
    m_signHashAlgo = HCCertUtils::SIGN_HASH_ALGO_SHA256;
    char* base64Signature = new char[1024];
    
    unsigned char* rawSignature = new unsigned char[1024];
    int size = CFDataGetLength(signature);
    CFDataGetBytes(signature, CFRangeMake(0,CFDataGetLength(signature)), (UInt8*)(rawSignature));

    DSUtilEncodeBase64((const char*)rawSignature, size, base64Signature, 1023);
    base64Signature[1023] = '\0';
   
    strBase64Signature.assign(base64Signature);
    DSERROR(facility, "challenge data is successfully signed.");
    
    delete []base64Signature;
    delete []rawSignature;
    return true;


}


This code runs as a part of daemon on the client. I have written a test application (not a daemon) using same code and when I execute test application it prompts me for username/password in order to access the keychain. Once I provide username/password everything works fine.


But when I execute same code through daemon (client-server communication), it doesn't prompt for username/password. So is there any way or API to skip the password required since daemon runs as system user?


Coming from windows background, service (daemon) on windows can access the private key.

I have tried following options so far:


  1. impersonate to current user from daemon so that user gets the authorisation prompt. But no prompt for username/password. I am expecting prompt when SecTransformExecute is executed ( as in my test application). But it fails with error "Error Domain=Internal CSSM error Code=-2147415839 "Internal error #800108e1 at SignTransform_block_invoke".
  2. try to read Access Control List of the certificate and modify access for this certificate so that it doesn't prompt for password everytime my app tries for access.


SecAccessRef secaccess;
OSStatus ret = SecKeychainItemCopyAccess(pKeychain, &secaccess);


SecKeychainItemCopyAccess fails with error -25243 (The specified item has no access control ).


3. manually add my app in the access control from the keychain access.


Only 3rd option is working. But I can't expect clients to add it manually as there could be multiple certificates setup for client/server communiation.


Any suggestions? Is what I am trying to do possible on MacOS? If yes, how can I achieve it?

Replies

I’m helping Sardeep via some other channel but there’s one point I want to cover in this public context.

1. impersonate to current user from daemon so that user gets the authorisation prompt.

There is no supported way to ‘impersonate’ a user from a daemon. To understand why, you have to look at Technote 2083 Daemons and Agents and specifically the Execution Contexts section. macOS maintains lots of execution context information above and beyond the standard execution contexts maintained by a standard UNIX system (like the EUID and EGID).

This case is a perfect example of this. Access to the Security framework is mediated by the security context. Switching your EUID and EGID has no impact on that, which is why these APIs are failing mysteriously.

Note TN2083 says that the security context is tied to the bootstrap namespace. That was true at the time the technote was written, but has since changed. Changing your bootstrap namespace does not change your security context. Which brings us to…

There is no supported way to change the execution context of a process. If you’re creating a daemon and need to impersonate a user, the only supported approach is to create an agent that runs as that user and then have the daemon farm that work out to the agent via IPC.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
  • Hi,

    I was trying to use XPC connection in order to pass the generation of NSURLCredential to a user application (which is the XPC server in this scenario).

    while i'm able to obtain the NSURLCredential object from a user application, i fail to pass it in the XPC reply callback with the following reason:

    Exception: decodeObjectForKey: Object of class "NSURLCredential" returned nil from -initWithCoder: while being decoded for key <no key>

    it looks like this object require implementation of method -initWithCoder since it's an opaque object.

    Perhaps you can give me some guidelines about how to do that, I'm not sure I understand the entire contents of NSURLCredentials.

    BTW, This is how I create the object on the client application :

      NSURLCredential *certificateBasedCredential = [NSURLCredential credentialWithIdentity:certificates:persistence:];

    Thanks !

Add a Comment

I was trying to use XPC connection in order to pass the generation of NSURLCredential to a user application

For those reading along at home, I’m helping chapo213 on this thread.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"