How can 'SecKeyCreateSignature' be instructed to prompt for X.509 credentials once?

Greetings,


I'm trying, in a macOS, to perform digital signatures in a list of documents and, currently, facing a problem: for each single document, the method 'SecKeyCreateSignature' is asking for the keychain credentials in order to perform the operation, which goes against the current requirements for the piece of software I'm currently coding.


Ideally, I would like to be able to perform a series of signatures for a given Identity, almost like as if it were a "signature session".


I've used, primarily, the methods 'SecIdentityCopyPrivateKey' and 'SecKeyCreateSignature' in order to perform such task but, now I'm not sure if this is the correct approach anymore, since I've got weird (and different) results when trying this using a p12 certificate imported in the KeyChain and when using a USB Smartcard for the signature:


P12 in the KeyChain: Many credential prompts (relative to the number of items to sign).

USB Smartcard with proprietary driver: Only 1 credential prompt (the number of items to sign does not affect the number of prompts);


So, regarding the above situation, I'd like some help regarding the following points:


Is the pair of functions 'SecIdentityCopyPrivateKey' and 'SecKeyCreateSignature' the most adequate for the current task?

Is there any concept/API for "signature sessions" which I've missed?


Thanks in advance.

Accepted Reply

What we're looking for is some way to use the signing operations in a fashion that resembles the following pseudo-code

This model isn’t really supported on macOS.

btw The reason you’re seeing this with your smartcard is that a smartcard looks like a new keychain, so the first use of the key prompts the user to unlock that keychain (point A in my 27 Aug post).

The mentioned code snippet is part of a

.dylib
which is loaded by a app written in Java, hence the allowed executable in the ACL would be
java

Hmmm. But this is a double-clickable app, right? Most folks who build Java-based double-clickable Mac apps embed the Java runtime within their app. In that case the Java side of this is irrelevant: When you sign your app you can give it a sensible designated requirement (DR) [1], and thus accurately represent the app in a keychain ACL.

One thing you might consider is putting this digital identity in a separate keychain, which would give you something more like the smart card case.

Share and Enjoy

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

let myEmail = "eskimo" + "1" + "@apple.com"

[1] In modern code signing the DR is formed by a combination of your Team ID and the code signing identifier. For bundled code, like a standalone Mac app, this defaults to the bundle ID. In other cases you can force a specific code signing identifier via the

-i
option to
codesign
.

Replies

Below is a snippet (C++) with the core parts of it:


void NovaTentativa::myTestCase()
{
    //the test certificate has a subject name containing this common name
    CFStringRef searchStr = CFStringCreateWithCString(kCFAllocatorDefault, "My Test Cert", kCFStringEncodingUTF8);

    //we are just interested in Identities
    const void* keys[]   = { kSecClass, kSecMatchLimit, kSecMatchSubjectContains };
    const void* values[] = { kSecClassIdentity, kSecMatchLimitAll, searchStr };
    
    CFDictionaryRef dict = CFDictionaryCreate(kCFAllocatorDefault,
        keys, values, 3,
        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks
    );
    
    CFTypeRef queryResults;
    OSStatus err = SecItemCopyMatching(dict, &queryResults);
    
    //cleanup and dumb error check
    CFRelease(dict);
    CFRelease(searchStr);
    if (err != errSecSuccess) {
        std::cerr << "SecItemCopyMatching failed somehow" << std::endl;
        return;
    }
    
    //casts to array and retrieves the (expected) single identity
    CFArrayRef resList = reinterpret_cast<CFArrayRef>(queryResults);
    CFIndex arrayLength = CFArrayGetCount(resList);
    if (arrayLength != 1) {
        std::cerr << "SecItemCopyMatching was supposed to return a single item" << std::endl;
        return;
    }
    
    SecIdentityRef identity = (SecIdentityRef)CFArrayGetValueAtIndex(resList, 0);
    
    //gets the private key
    SecKeyRef privateKey = NULL;
    SecIdentityCopyPrivateKey(identity, &privateKey);
    
    //a list of items to be signed
    for (int i = 0; i < 3; ++i) {
        //makes a different input data
        std::stringstream inputDataBuilder;
        inputDataBuilder << "input." << i;
        
        std::string inputDataString = inputDataBuilder.str();
        CFDataRef inputData = CFDataCreate(kCFAllocatorDefault, (const UInt8*)inputDataString.c_str(), inputDataString.size());
        
        //signs the input data
        CFErrorRef theError;
        CFDataRef signedData = SecKeyCreateSignature(privateKey, kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA512, inputData, &theError);
        
        //clean-up and error check
        CFRelease(inputData);
        if (!signedData) {
            std::cerr << "signature for input " << i << " went wrong somehow" << std::endl;
        }
        
        std::cout << "done: " << inputDataString << std::endl;
    }
}

The p12 certificate which I'm using in my tests (the base64 of it, actually. The password is "123"):

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

The original file-based keychain architecture on the Mac has two mechanisms that can result in UI:

  • The keychain lock/unlocked state (A)

  • The access control list (ACL) associated with the item (B)

I suspect that A is not the issue here; you can check using the Keychain Access app, which will show you the keychain’s lock/unlocked state on the left.

With regards B, the most common cause of problems like this is that you’ve imported the key such that the ACL triggers a prompt every time you use it. How are you importing this key?

Share and Enjoy

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

let myEmail = "eskimo" + "1" + "@apple.com"

Hi eskimo. Thanks for your fast reply!


I think the p12 file was imported just by clicking on it (the standard way?). It went on the "login" keychain, and it seems to be unlocked (the lock icon displays so).


Regarding the ACL, this is the current configuration, which seems adequate:

https: //imgur.com/Y3lVSI0

Ah, you imported the digital identity using Keychain Access. In that case you will not be able to access it from your app without user authorisation. You have a number of options here:

  • For a quick hack, use Keychain Access to open up the ACL.

    IMPORTANT Make sure your app has a consistent code signing identity before doing that, otherwise each new build of the app looks like a new app to the system, which makes the ACL entry meaningless.

  • Your app could try to change the ACL programmatically. This is a lot of un-fun code.

  • You can import the digital identity programmatically, at which point the ACL defaults to allowing access by your app.

Share and Enjoy

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

let myEmail = "eskimo" + "1" + "@apple.com"

I understand the given options, but we've previously dicarded this approach due the following situation (which I forgot to mention in the post, unfortunately):


The mentioned code snippet is part of a "dylib" which is loaded by a app written in Java, hence the allowed executable in the ACL would be "java", instead of only our application (that would be really insecure, I think);


What we're looking for is some way to use the signing operations in a fashion that resembles the following pseudo-code:


identity = find_identity_by_some_criteria();
key      = grab_private_key_ref(identity); //KeyChain asks for credentials at this point

foreach item in compute_items_to_be_signed() {
    perform_signature(item, key); //no credentials prompt
}


We are currently in doubt whether the APIs mentioned ('SecIdentityCopyPrivateKey' and 'SecKeyCreateSignature') are meant/able to be used in such a fashion.

What we're looking for is some way to use the signing operations in a fashion that resembles the following pseudo-code

This model isn’t really supported on macOS.

btw The reason you’re seeing this with your smartcard is that a smartcard looks like a new keychain, so the first use of the key prompts the user to unlock that keychain (point A in my 27 Aug post).

The mentioned code snippet is part of a

.dylib
which is loaded by a app written in Java, hence the allowed executable in the ACL would be
java

Hmmm. But this is a double-clickable app, right? Most folks who build Java-based double-clickable Mac apps embed the Java runtime within their app. In that case the Java side of this is irrelevant: When you sign your app you can give it a sensible designated requirement (DR) [1], and thus accurately represent the app in a keychain ACL.

One thing you might consider is putting this digital identity in a separate keychain, which would give you something more like the smart card case.

Share and Enjoy

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

let myEmail = "eskimo" + "1" + "@apple.com"

[1] In modern code signing the DR is formed by a combination of your Team ID and the code signing identifier. For bundled code, like a standalone Mac app, this defaults to the bundle ID. In other cases you can force a specific code signing identifier via the

-i
option to
codesign
.

I've tried using the "always allow" response in the Keychain credentials prompt for our bundled and signed distribution and, as you've pointed out, the created ACL matches the app bundle! 🙂


(I feel stupid for trying it only on the unbundled version 😐).


Anyway, I've brought this up to my superiors and, despite being a little bit more pleased regarding the ACL matching exactly the app bundle, it was decided to keep on researching an alternative method, given that the Java keystore provider "Apple" can surprisingly emulate the pseudo-code workflow.


Thanks for the help, eskimo!