1

I'm trying to sign data and verify the signature using Elliptic Curve algorithm on iOS. Creating the keys works well enough, but attempting to sign the data returns error -1 - which is very generic.

The keys are created as follows:

publicKeyRef = NULL;
privateKeyRef = NULL;

NSDictionary * privateKeyAttr = @{(id)kSecAttrIsPermanent : @1,
                                  (id)kSecAttrApplicationTag : privateTag};

NSDictionary * publicKeyAttr = @{(id)kSecAttrIsPermanent : @1,
                                 (id)kSecAttrApplicationTag : privateTag};

NSDictionary * keyPairAttr = @{(id)kSecAttrKeySizeInBits : @(keySize),
                               (id)kSecAttrKeyType : (id)kSecAttrKeyTypeEC,
                               (id)kSecPrivateKeyAttrs : privateKeyAttr,
                               (id)kSecPublicKeyAttrs : publicKeyAttr};

OSStatus status = SecKeyGeneratePair((CFDictionaryRef)keyPairAttr, &publicKeyRef, &privateKeyRef);

This returns status 0, so far so good. The actual signing happens like this:

- (NSData *) signData:(NSData *)dataToSign withPrivateKey:(SecKeyRef)privateKey {
    NSData * digestToSign = [self sha1DigestForData:dataToSign];

    size_t signedHashBytesSize = SecKeyGetBlockSize(privateKey);

    uint8_t * signedHashBytes = malloc( signedHashBytesSize * sizeof(uint8_t) );
    memset((void *)signedHashBytes, 0x0, signedHashBytesSize);
    OSStatus signErr = SecKeyRawSign(privateKey,
                                kSecPaddingPKCS1,
                                digestToSign.bytes,
                                digestToSign.length,
                                (uint8_t *)signedHashBytes,
                                &signedHashBytesSize);
    NSLog(@"Status: %d", signErr);

    NSData * signedHash = [NSData dataWithBytes:(const void *)signedHashBytes length:(NSUInteger)signedHashBytesSize];
    if (signedHashBytes) free(signedHashBytes);

    return (signErr == noErr) ? signedHash : nil;
}

- (NSData *)sha1DigestForData:(NSData *)data {
    NSMutableData *result = [[NSMutableData alloc] initWithLength:CC_SHA1_DIGEST_LENGTH];
    CC_SHA1(data.bytes, (CC_LONG) data.length, result.mutableBytes);

    return result;
}  

The call to SecKeyRawSign() returns -1.

This is adapted from https://forums.developer.apple.com/message/95740#95740

What is the correct way to use an EC key for signing data? There is a working solution for RSA keys here: Signing and Verifying on iOS using RSA but I was unable to adapt it to EC keys.

Community
  • 1
  • 1
SaltyNuts
  • 5,068
  • 8
  • 48
  • 80

2 Answers2

4

Seems like part of the trouble is with the correct syntax when creating the pointers and calculating the size of data for calls to SecKeyRawSign. A working example in Swift 3 looks like this:

Generate keys, stored in the Secure Enclave (and temporarily in instance variables):

func generateKeyPair() -> Bool {
    if let access = SecAccessControlCreateWithFlags(nil,
                                                    kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
                                                    [.userPresence, .privateKeyUsage],
                                                    nil) {

        let privateKeyAttr = [kSecAttrIsPermanent : 1,
                              kSecAttrApplicationTag : privateTag,
                              kSecAttrAccessControl as String: access
            ] as NSDictionary

        let publicKeyAttr = [kSecAttrIsPermanent : 0,
                             kSecAttrApplicationTag : publicTag
            ] as NSDictionary

        let keyPairAttr = [kSecAttrKeySizeInBits : 256,
                           kSecAttrKeyType : kSecAttrKeyTypeEC,
                           kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
                           kSecPrivateKeyAttrs : privateKeyAttr,
                           kSecPublicKeyAttrs : publicKeyAttr] as NSDictionary

        let err = SecKeyGeneratePair(keyPairAttr, &publicKey, &privateKey)
        return err == noErr
}

Sign data:

func signData(plainText: Data) -> NSData? {
    guard privateKey != nil else {
        print("Private key unavailable")
        return nil
    }

    let digestToSign = self.sha1DigestForData(data: plainText as NSData) as Data

    let signature = UnsafeMutablePointer<UInt8>.allocate(capacity: 128)
    var signatureLength = 128
    let err = SecKeyRawSign(privateKey!,
                            .PKCS1SHA1,
                            [UInt8](digestToSign),
                            Int(CC_SHA1_DIGEST_LENGTH),
                            signature,
                            &signatureLength)

    print("Signature status: \(err)")

    let sigData = NSData(bytes: signature, length: Int(signatureLength))

    return sigData
}

func sha1DigestForData(data: NSData) -> NSData {
    let len = Int(CC_SHA1_DIGEST_LENGTH)
    let digest = UnsafeMutablePointer<UInt8>.allocate(capacity: len)
    CC_SHA1(data.bytes, CC_LONG(data.length), digest)
    return NSData(bytesNoCopy: UnsafeMutableRawPointer(digest), length: len)
}

Verify signature:

func verifySignature(plainText: Data, signature: NSData) -> Bool {
    guard publicKey != nil else {
        print("Public key unavailable")
        return false
    }

    let digestToSign = self.sha1DigestForData(data: plainText as NSData) as Data
    let signedHashBytesSize = signature.length

    let err = SecKeyRawVerify(publicKey!,
                              .PKCS1SHA1,
                              [UInt8](digestToSign),
                              Int(CC_SHA1_DIGEST_LENGTH),
                              [UInt8](signature as Data),
                              signedHashBytesSize)

    print("Verification status: \(err)")

    return err == noErr
}

If you need to export the public key so that it can be used by another application or device, this can be done like this:

let parameters = [
    kSecClass as String: kSecClassKey,
    kSecAttrKeyType as String: kSecAttrKeyTypeEC,
    kSecAttrLabel as String: "Public Key",
    kSecAttrIsPermanent as String: false,
    kSecValueRef as String: publicKey,
    kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
    kSecReturnData as String: true
    ] as CFDictionary
var data:AnyObject?
let status = SecItemAdd(parameters, &data)
print("Public key added \(status)")
if let keyData = data as? NSData {
    print("This is the key, send it where it needs to go:\n\(keyData)")
}
SaltyNuts
  • 5,068
  • 8
  • 48
  • 80
0

ECDSA, unlike RSA, does not need hashed data prior to signing.

Apple released improved API in iOS 10 to address the problems of working with and calculating the size of raw data and returned generic error codes like -1. The newer ones, SecKeyCreateSignature in place of SecKeyRawSign, return data and error objects and replaced EC legacy constants for clarity. Here's an updated example:

- (NSData *) signData:(NSData *)dataToSign withPrivateKey:(SecKeyRef)privateKey {
    NSData *signedData = nil;
    if (dataToSign && privateKey && SecKeyCreateSignature != NULL) //Also check for iOS 10 +
    {
        CFErrorRef error = NULL;
        CFDataRef signatureData = SecKeyCreateSignature(privateKey, kSecKeyAlgorithmECDSASignatureMessageX962SHA512, (__bridge CFDataRef)dataToSign, &error);
        if (signatureData)
        {
            if (error)
            {
                CFShow(error); // <-- here you get way more info than "-1"
                CFRelease(signatureData);
            }
            else
            {
                signedData = (__bridge NSData *)CFAutorelease(signatureData);
            }
        }

        if (error)
        {
            CFRelease(error);
        }
    }
    return signedData;
}

iOS is quite fussy about the parameters for EC in regards to the older functions. Passing in the "wrong key" can give you either -1 or -50, especially as the engineers only focused EC support on newer APIs that use the secure enclave. Here's an updated example for key generation that generates compatible keys:

if (SecKeyCreateRandomKey != NULL && !(TARGET_IPHONE_SIMULATOR)) //iOS 10 + check, real device
{
    CFErrorRef error = NULL;
    SecAccessControlRef accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, kSecAccessControlPrivateKeyUsage, &error);
    if (error)
    {
        CFShow(error); // <- error instead of OSStatus
        CFRelease(error);
        error = NULL;
    }

    if (accessControl)
    {
        static const uint8_t identifier[] = "com.company.yourKey";
        CFDataRef privateTag = CFDataCreate(kCFAllocatorDefault, identifier, sizeof(identifier));
        if (privateTag)
        {
            const void* accessKeys[] = { kSecAttrIsPermanent, kSecAttrApplicationTag, kSecAttrAccessControl };
            const void* accessValues[] = { kCFBooleanTrue, privateTag, accessControl };
            CFDictionaryRef accessDictionary = CFDictionaryCreate(kCFAllocatorDefault, accessKeys, accessValues, 3, NULL, NULL);
            if (accessDictionary)
            {
                CFMutableDictionaryRef parameters = CFDictionaryCreateMutable(kCFAllocatorDefault, 7, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
                if (parameters)
                {
                    SInt32 keySize = 256;
                    CFNumberRef keySizeNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &keySize);
                    if (keySizeNumber)
                    {
                        CFDictionaryAddValue(parameters, kSecAttrKeySizeInBits, keySizeNumber);
                        CFRelease(keySizeNumber);
                    }

                    CFDictionaryAddValue(parameters, kSecAttrKeyType, kSecAttrKeyTypeECSECPrimeRandom);
                    CFDictionaryAddValue(parameters, kSecAttrTokenID, kSecAttrTokenIDSecureEnclave);
                    CFDictionaryAddValue(parameters, kSecPrivateKeyAttrs, accessDictionary);

                    SecKeyRef privateKey = SecKeyCreateRandomKey(parameters, &error); // <- pass in an error object
                    if (privateKey)
                    {
                        SecKeyRef publicKey = SecKeyCopyPublicKey(privateKey);
                        if (publicKey)
                        {
                            //...
                            CFRelease(publicKey);
                        }

                        //...
                        CFRelease(privateKey);
                    }

                    if (error)
                    {
                        CFRelease(error);
                    }

                    CFRelease(parameters);
                }
                CFRelease(accessDictionary);
            }
            CFRelease(privateTag);
        }
        CFRelease(accessControl);
    }
}
NSDestr0yer
  • 1,419
  • 16
  • 20