开发者

Using SecKeyRawSign on the iPhone

I'm trying to sign some data using SecKeyRawSign but I keep getting a -4 errSecUnimplemented. That seems strange since the documentation states that it is available in iPhon开发者_运维百科e OS2.0 and later.

Has anyone been able to use this function? If so, are there any tricks involved?

~Nate


If you're having this problem, most likely it is because the private key you generated isn't actually being saved into the keychain. I figured this out when stopping and restarting the application and signing the message wasn't working.

So here are my methods to make this work.

This one generates the key pair

- (void)generateKeyPair:(NSUInteger)keySize {
    OSStatus sanityCheck = noErr;
    publicKeyRef = NULL;
    privateKeyRef = NULL;

    LOGGING_FACILITY1( keySize == 512 || keySize == 1024 || keySize == 2048, @"%d is an invalid and unsupported key size.", keySize );

    // First delete current keys.
    [self deleteAsymmetricKeys];

    // Container dictionaries.

    // See SecKey.h for other values
    NSDictionary *privateKeyDict = @{
                    (__bridge id) kSecAttrIsPermanent : [NSNumber numberWithBool:YES],
                    (__bridge id) kSecAttrApplicationTag : privateTag
    };

    // See SecKey.h for other values
    NSDictionary *publicKeyDict = @{
                    (__bridge id) kSecAttrIsPermanent : [NSNumber numberWithBool:YES],
                    (__bridge id) kSecAttrApplicationTag : publicTag
    };

    NSDictionary *keyPairDict = @{
                    (__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeRSA,
                    (__bridge id) kSecAttrKeySizeInBits : [NSNumber numberWithUnsignedInteger:keySize],
                    (__bridge id) kSecPrivateKeyAttrs : privateKeyDict,
                    (__bridge id) kSecPublicKeyAttrs : publicKeyDict
    };

    // SecKeyGeneratePair returns the SecKeyRefs
    sanityCheck = SecKeyGeneratePair((__bridge CFDictionaryRef) keyPairDict, &publicKeyRef, &privateKeyRef);
    LOGGING_FACILITY( sanityCheck == noErr && publicKeyRef != NULL && privateKeyRef != NULL, @"Something really bad went wrong with generating the key pair." );

    // retrieve the actual bits for the keys, not just the references
    NSData *publicKeyBits = [self getKeyBitsFromKey:publicKeyRef];
    NSData *privateKeyBits = [self getKeyBitsFromKey:privateKeyRef];

    // save the keys to the keychain
    [self saveKeyToKeychain:publicKeyBits keySize:keySize private:NO];
    [self saveKeyToKeychain:privateKeyBits keySize:keySize private:YES];
}

** EDIT **

iOS 9 introduced a new feature called the Secure Enclave. If you want to generate a key that will be stored there and only there, you will be required to use a 256-bit EC key, as that is the only type supported by the enclave. The keyPairDict will look like this instead:

NSDictionary *keyPairDict = @{
                (__bridge id)kSecAttrTokenID: (__bridge id)kSecAttrTokenIDSecureEnclave,
                (__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeEC,
                // we can use keySize here if we want
                // but since 256 is the only available size
                // we can just hardcode it for now
                (__bridge id) kSecAttrKeySizeInBits : @256],
                (__bridge id) kSecPrivateKeyAttrs : privateKeyDict,
                (__bridge id) kSecPublicKeyAttrs : publicKeyDict
};

I know the parameters are correct, but I haven't myself tested the Secure Enclave yet, so let me know if this doesn't work for some reason.

Also, for reference: a 256-bit EC key is equivalent to a 3072-bit RSA key.

The query used to retrieve the key below would also be different:

NSDictionary *queryKey = @{
                (__bridge id) kSecClass : (__bridge id) kSecClassKey,
                (__bridge id) kSecAttrApplicationTag : tempTag,
                (__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeEC
};

Because the Secure Enclave is, well, secure, you most likely won't be able to retrieve the private key bits. Most likely, you'll only be able to generate a reference. But you shouldn't need to handle the private key data anyway.

** END EDIT **

This method retrieves the actual bits from the keychain and not just the reference

- (NSData *)getKeyBitsFromKey:(SecKeyRef)givenKey {
    static const uint8_t publicKeyIdentifier[] = "com.sample.temp";
    NSData *tempTag = [[NSData alloc] initWithBytes:publicKeyIdentifier length:sizeof(publicKeyIdentifier)];

    NSDictionary *queryKey = @{
                    (__bridge id) kSecClass : (__bridge id) kSecClassKey,
                    (__bridge id) kSecAttrApplicationTag : tempTag,
                    (__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeRSA
    };

    // Temporarily add key to the Keychain, return as data:
    NSMutableDictionary *attributes = [[NSMutableDictionary alloc] initWithDictionary:queryKey copyItems:YES];
    [attributes setObject:(__bridge id) givenKey forKey:(__bridge id) kSecValueRef];
    [attributes setObject:@YES forKey:(__bridge id) kSecReturnData];

    // result codes: https://developer.apple.com/library/ios/documentation/Security/Reference/certifkeytrustservices/Reference/reference.html#//apple_ref/doc/uid/TP30000157-CH4g-339030
    OSStatus sanityCheck = noErr;
    NSData *keyBits = nil;

    CFTypeRef result;
    sanityCheck = SecItemAdd((__bridge CFDictionaryRef) attributes, &result);
    if (sanityCheck == errSecSuccess) {
            keyBits = CFBridgingRelease(result);

            // Remove from Keychain again:
            (void) SecItemDelete((__bridge CFDictionaryRef) queryKey);
            return keyBits;
    }
    else if (sanityCheck == errSecDuplicateItem) {
            // Remove from Keychain again:
            (void) SecItemDelete((__bridge CFDictionaryRef) queryKey);
            return [self getKeyBitsFromKey:givenKey];
    }

    return nil;
}

This method saves the bits to the keychain

- (void)saveKeyToKeychain:(NSData *)key keySize:(NSUInteger)keySize private:(BOOL)isPrivate {
    OSStatus sanityCheck = noErr;
    NSData *tag;
    id keyClass;
    if (isPrivate) {
            tag = privateTag;
            keyClass = (__bridge id) kSecAttrKeyClassPrivate;
    }
    else {
            tag = publicTag;
            keyClass = (__bridge id) kSecAttrKeyClassPublic;
    }

    NSDictionary *saveDict = @{
                    (__bridge id) kSecClass : (__bridge id) kSecClassKey,
                    (__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeRSA,
                    (__bridge id) kSecAttrApplicationTag : tag,
                    (__bridge id) kSecAttrKeyClass : keyClass,
                    (__bridge id) kSecValueData : key,
                    (__bridge id) kSecAttrKeySizeInBits : [NSNumber numberWithUnsignedInteger:keySize],
                    (__bridge id) kSecAttrEffectiveKeySize : [NSNumber numberWithUnsignedInteger:keySize],
                    (__bridge id) kSecAttrCanDerive : (__bridge id) kCFBooleanFalse,
                    (__bridge id) kSecAttrCanEncrypt : (__bridge id) kCFBooleanTrue,
                    (__bridge id) kSecAttrCanDecrypt : (__bridge id) kCFBooleanFalse,
                    (__bridge id) kSecAttrCanVerify : (__bridge id) kCFBooleanTrue,
                    (__bridge id) kSecAttrCanSign : (__bridge id) kCFBooleanFalse,
                    (__bridge id) kSecAttrCanWrap : (__bridge id) kCFBooleanTrue,
                    (__bridge id) kSecAttrCanUnwrap : (__bridge id) kCFBooleanFalse
    };

    SecKeyRef savedKey = NULL;
    sanityCheck = SecItemAdd((__bridge CFDictionaryRef) saveDict, (CFTypeRef *)&savedKey);
    if (sanityCheck != errSecSuccess) {
            LOGGING_FACILITY1(sanityCheck != noErr, @"Problem saving the key to keychain, OSStatus == %d.", sanityCheck);
    }
}

And then you sign like so:

- (NSData *)getSignatureBytes:(NSData *)plainText {
    OSStatus sanityCheck = noErr;
    NSData *signedHash = nil;

    uint8_t *signedHashBytes = NULL;
    size_t signedHashBytesSize = 0;

    SecKeyRef privateKey = NULL;

    privateKey = [self getKeyRef:YES];
    signedHashBytesSize = SecKeyGetBlockSize(privateKey);

    // Malloc a buffer to hold signature.
    signedHashBytes = malloc(signedHashBytesSize * sizeof(uint8_t));
    memset((void *) signedHashBytes, 0x0, signedHashBytesSize);

    // Sign the SHA1 hash.
    sanityCheck = SecKeyRawSign(privateKey,
            kTypeOfSigPadding,
            (const uint8_t *) [[self getHashBytes:plainText] bytes],
            kChosenDigestLength,
            signedHashBytes,
            &signedHashBytesSize
    );

    LOGGING_FACILITY1( sanityCheck == noErr, @"Problem signing the SHA1 hash, OSStatus == %d.", sanityCheck );

    // Build up signed SHA1 blob.
    signedHash = [NSData dataWithBytes:(const void *) signedHashBytes length:(NSUInteger) signedHashBytesSize];

    if (signedHashBytes) {
        free(signedHashBytes);
    }

    return signedHash;
}


The -4 errSecUnimplemented error was being caused by a bad reference to the private key used to sign the data. Confusing error for that situation. A errSecParam would have been nicer.

~NAte

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜