Использование SecKeyRawSign на iPhone

Я пытаюсь подписать некоторые данные с помощью SecKeyRawSign, но получаю ошибку -4 errSecUnimplemented. Это кажется странным, поскольку в документации указано, что он доступен в iPhone OS 2.0 и более поздних версиях.

Кому-нибудь удалось воспользоваться этой функцией? Если да, то есть ли какие-то хитрости?

~ Нейт


person Nate    schedule 19.05.2010    source источник


Ответы (2)


Если у вас возникла эта проблема, скорее всего, это связано с тем, что сгенерированный вами закрытый ключ на самом деле не сохраняется в цепочке ключей. Я понял это при остановке и перезапуске приложения, и подпись сообщения не работала.

Итак, вот мои методы, чтобы сделать эту работу.

Этот генерирует пару ключей

- (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];
}

** ИЗМЕНИТЬ **

В iOS 9 появилась новая функция под названием Secure Enclave. Если вы хотите сгенерировать ключ, который будет храниться там и только там, вам потребуется использовать ключ 256-bit EC, так как это единственный тип, поддерживаемый анклавом. Вместо этого keyPairDict будет выглядеть так:

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
};

Я знаю, что параметры правильные, но я еще не тестировал Secure Enclave, поэтому дайте мне знать, если это по какой-то причине не работает.

Также для справки: ключ 256-bit EC эквивалентен ключу 3072-bit RSA.

Запрос, используемый для получения приведенного ниже ключа, также будет другим:

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

Поскольку Secure Enclave является безопасным, вы, скорее всего, не сможете получить биты закрытого ключа. Скорее всего, вы сможете создать только ссылку. Но вам все равно не нужно обрабатывать данные закрытого ключа.

** КОНЕЦ РЕДАКТИРОВАНИЯ **

Этот метод извлекает фактические биты из цепочки для ключей, а не только ссылку

- (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;
}

Этот метод сохраняет биты в связке ключей

- (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);
    }
}

И тогда вы подписываете так:

- (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;
}
person mikeho    schedule 20.05.2014
comment
iOS 9 позволяет хранить закрытый ключ в защищенном анклаве. Какие изменения необходимы, чтобы воспользоваться этим преимуществом? (очевидно, что приватный ключ нельзя экспортировать, но было бы неплохо это проверить) - person halfbit; 07.01.2016
comment
@LamonteCristo, поскольку поле для комментариев более ограничено, я добавил правку выше, чтобы ответить на ваш вопрос. - person mikeho; 07.01.2016
comment
@LamonteCristo Я давно не прикасался к этому коду, поэтому я вернусь к нему, когда у меня будет шанс. - person mikeho; 07.01.2016

Ошибка -4 errSecUnimplemented была вызвана неверной ссылкой на закрытый ключ, используемый для подписи данных. Непонятная ошибка для этой ситуации. Параметр errSecParam был бы лучше.

~НАте

person Nate    schedule 19.05.2010
comment
Это была именно та проблема, которая у меня была. Оказывается, класс SecKeyWrapper в CryptoExercise не сохранял закрытый ключ в связке ключей! - person mikeho; 20.05.2014