Operations/QCCRSASHASignatureCompat.m
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
Implements RSA SHA signature signing and verification in a maximally compatible way. |
*/ |
#import "QCCRSASHASignatureCompat.h" |
#include <CommonCrypto/CommonCrypto.h> |
static SecKeyAlgorithm _Nonnull secAlgorithmForAlgorithm(QCCRSASHASignatureCompatAlgorithm algorithm) { |
switch (algorithm) { |
case QCCRSASHASignatureCompatAlgorithmSHA1: { return kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA1; } break; |
case QCCRSASHASignatureCompatAlgorithmSHA2_224: { return kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA224; } break; |
case QCCRSASHASignatureCompatAlgorithmSHA2_256: { return kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA256; } break; |
case QCCRSASHASignatureCompatAlgorithmSHA2_384: { return kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA384; } break; |
case QCCRSASHASignatureCompatAlgorithmSHA2_512: { return kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA512; } break; |
default: { abort(); } break; |
} |
} |
static NSData * _Nonnull digestForAlgorithmOverInputData(QCCRSASHASignatureCompatAlgorithm algorithm, NSData * _Nonnull inputData) { |
NSMutableData * digest; |
switch (algorithm) { |
case QCCRSASHASignatureCompatAlgorithmSHA1: { |
digest = [[NSMutableData alloc] initWithLength:CC_SHA1_DIGEST_LENGTH]; |
(void) CC_SHA1(inputData.bytes, (CC_LONG) inputData.length, digest.mutableBytes); |
} break; |
case QCCRSASHASignatureCompatAlgorithmSHA2_224: { |
digest = [[NSMutableData alloc] initWithLength:CC_SHA224_DIGEST_LENGTH]; |
(void) CC_SHA224(inputData.bytes, (CC_LONG) inputData.length, digest.mutableBytes); |
} break; |
case QCCRSASHASignatureCompatAlgorithmSHA2_256: { |
digest = [[NSMutableData alloc] initWithLength:CC_SHA256_DIGEST_LENGTH]; |
(void) CC_SHA256(inputData.bytes, (CC_LONG) inputData.length, digest.mutableBytes); |
} break; |
case QCCRSASHASignatureCompatAlgorithmSHA2_384: { |
digest = [[NSMutableData alloc] initWithLength:CC_SHA384_DIGEST_LENGTH]; |
(void) CC_SHA384(inputData.bytes, (CC_LONG) inputData.length, digest.mutableBytes); |
} break; |
case QCCRSASHASignatureCompatAlgorithmSHA2_512: { |
digest = [[NSMutableData alloc] initWithLength:CC_SHA512_DIGEST_LENGTH]; |
(void) CC_SHA512(inputData.bytes, (CC_LONG) inputData.length, digest.mutableBytes); |
} break; |
default: { abort(); } break; |
} |
return digest; |
} |
#pragma mark - Verify |
NS_ASSUME_NONNULL_BEGIN |
@interface QCCRSASHAVerifyCompat () |
// read/write versions of public properties |
@property (atomic, copy, readwrite, nullable) NSError * error; |
@property (atomic, assign, readwrite) BOOL verified; |
@end |
NS_ASSUME_NONNULL_END |
@implementation QCCRSASHAVerifyCompat |
- (instancetype)init { |
abort(); |
} |
- (instancetype)initWithAlgorithm:(QCCRSASHASignatureCompatAlgorithm)algorithm inputData:(NSData *)inputData publicKey:(SecKeyRef)publicKey signatureData:(NSData *)signatureData { |
NSParameterAssert(inputData != nil); |
NSParameterAssert(publicKey != NULL); |
NSParameterAssert(signatureData != nil); |
self = [super init]; |
if (self != nil) { |
self->_algorithm = algorithm; |
self->_inputData = [inputData copy]; |
self->_publicKey = publicKey; |
self->_signatureData = [signatureData copy]; |
} |
return self; |
} |
- (void)runUsingUnified { |
NSData * digest; |
CFErrorRef errorCF = NULL; |
// First create a SHA digest of the data (this works out the right SecKeyAlgorithm at the same time). |
// |
// You can simplify this process by passing in a kSecKeyAlgorithmRSASignatureMessageXxx algorithm, |
// whereupon the system will automatically calculate the digest for you. For an example of this, |
// see QCCRSASHAVerify. However, in some cases it's necessary to verify a digest directly, and this |
// code shows how to do that. |
digest = digestForAlgorithmOverInputData(self.algorithm, self.inputData); |
// Then verify it. |
self.verified = SecKeyVerifySignature( |
self.publicKey, |
secAlgorithmForAlgorithm(self.algorithm), |
(__bridge CFDataRef) digest, |
(__bridge CFDataRef) self.signatureData, |
&errorCF |
) != false; |
// Deal with the results. |
if ( ! self.verified ) { |
NSError * error; |
error = CFBridgingRelease( errorCF ); |
if ([error.domain isEqual:NSOSStatusErrorDomain] && (error.code == errSecVerifyFailed)) { |
// An explicit verify failure is not considered an error. |
assert(self.error == nil); |
} else { |
self.error = error; |
} |
} |
} |
#if TARGET_OS_OSX |
static BOOL setupTransformForAlgorithm(SecTransformRef transform, QCCRSASHASignatureCompatAlgorithm algorithm, CFErrorRef _Nullable * _Nullable errorCFPtr) { |
BOOL success; |
if (algorithm == QCCRSASHASignatureCompatAlgorithmSHA1) { |
success = SecTransformSetAttribute(transform, kSecDigestTypeAttribute, kSecDigestSHA1, errorCFPtr) != false; |
} else { |
success = SecTransformSetAttribute(transform, kSecDigestTypeAttribute, kSecDigestSHA2, errorCFPtr) != false; |
if (success) { |
NSInteger digestSize; |
switch (algorithm) { |
case QCCRSASHASignatureCompatAlgorithmSHA1: { abort(); } break; |
case QCCRSASHASignatureCompatAlgorithmSHA2_224: { digestSize = 224; } break; |
case QCCRSASHASignatureCompatAlgorithmSHA2_256: { digestSize = 256; } break; |
case QCCRSASHASignatureCompatAlgorithmSHA2_384: { digestSize = 384; } break; |
case QCCRSASHASignatureCompatAlgorithmSHA2_512: { digestSize = 512; } break; |
default: { abort(); } break; |
} |
success = SecTransformSetAttribute(transform, kSecDigestLengthAttribute, (__bridge CFNumberRef) @(digestSize), errorCFPtr) != false; |
} |
} |
return success; |
} |
- (void)runUsingTransforms { |
BOOL success; |
SecTransformRef transform; |
CFBooleanRef result; |
CFErrorRef errorCF; |
result = NULL; |
errorCF = NULL; |
// Set up the transform. |
transform = SecVerifyTransformCreate(self.publicKey, (__bridge CFDataRef) self.signatureData, &errorCF); |
success = (transform != NULL); |
// Note: kSecInputIsAttributeName defaults to kSecInputIsPlainText, which is what we want. |
if (success) { |
success = setupTransformForAlgorithm(transform, self.algorithm, &errorCF); |
} |
if (success) { |
success = SecTransformSetAttribute(transform, kSecTransformInputAttributeName, (__bridge CFDataRef) self.inputData, &errorCF) != false; |
} |
// Run it. |
if (success) { |
result = SecTransformExecute(transform, &errorCF); |
success = (result != NULL); |
} |
// Process the results. |
if (success) { |
assert(CFGetTypeID(result) == CFBooleanGetTypeID()); |
self.verified = (CFBooleanGetValue(result) != false); |
} else { |
assert(errorCF != NULL); |
self.error = (__bridge NSError *) errorCF; |
} |
// Clean up. |
if (result != NULL) { |
CFRelease(result); |
} |
if (errorCF != NULL) { |
CFRelease(errorCF); |
} |
if (transform != NULL) { |
CFRelease(transform); |
} |
} |
#endif |
#if TARGET_OS_IPHONE |
static SecPadding secPaddingForAlgorithm(QCCRSASHASignatureCompatAlgorithm algorithm) { |
switch (algorithm) { |
case QCCRSASHASignatureCompatAlgorithmSHA1: { return kSecPaddingPKCS1SHA1; } break; |
case QCCRSASHASignatureCompatAlgorithmSHA2_224: { return kSecPaddingPKCS1SHA224; } break; |
case QCCRSASHASignatureCompatAlgorithmSHA2_256: { return kSecPaddingPKCS1SHA256; } break; |
case QCCRSASHASignatureCompatAlgorithmSHA2_384: { return kSecPaddingPKCS1SHA384; } break; |
case QCCRSASHASignatureCompatAlgorithmSHA2_512: { return kSecPaddingPKCS1SHA512; } break; |
default: { abort(); } break; |
} |
} |
- (void)runUsingRaw { |
OSStatus err; |
NSData * digest; |
// First create a SHA digest of the data. |
digest = digestForAlgorithmOverInputData(self.algorithm, self.inputData); |
// Then verify it. |
err = SecKeyRawVerify( |
self.publicKey, |
secPaddingForAlgorithm(self.algorithm), |
digest.bytes, |
digest.length, |
self.signatureData.bytes, |
self.signatureData.length |
); |
// Deal with the results. |
if (err == errSecSuccess) { |
self.verified = YES; |
} else if (err == errSSLCrypto) { |
assert( ! self.verified ); |
} else { |
self.error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; |
} |
} |
#endif |
- (void)main { |
if ( (SecKeyVerifySignature != NULL) && ! self.debugUseCompatibilityCode) { |
[self runUsingUnified]; |
} else { |
#if TARGET_OS_OSX |
[self runUsingTransforms]; |
#elif TARGET_OS_IPHONE |
[self runUsingRaw]; |
#else |
#error What platform? |
#endif |
} |
} |
@end |
#pragma mark - Sign |
NS_ASSUME_NONNULL_BEGIN |
@interface QCCRSASHASignCompat () |
// read/write versions of public properties |
@property (atomic, copy, readwrite, nullable) NSError * error; |
@property (atomic, copy, readwrite, nullable) NSData * signatureData; |
@end |
NS_ASSUME_NONNULL_END |
@implementation QCCRSASHASignCompat |
- (instancetype)init { |
abort(); |
} |
- (instancetype)initWithAlgorithm:(QCCRSASHASignatureCompatAlgorithm)algorithm inputData:(NSData *)inputData privateKey:(SecKeyRef)privateKey { |
NSParameterAssert(inputData != nil); |
NSParameterAssert(privateKey != NULL); |
self = [super init]; |
if (self != nil) { |
self->_algorithm = algorithm; |
self->_inputData = [inputData copy]; |
self->_privateKey = privateKey; |
} |
return self; |
} |
- (void)runUsingUnified { |
CFErrorRef errorCF = NULL; |
NSData * digest; |
NSData * resultData; |
// First create a SHA digest of the data. This isn't strictly speaking necessary. If you |
// use a kSecKeyAlgorithmRSASignatureMessageXxx algorithm, the system will automatically |
// calculate the digest for you. For an example of this, see QCCRSASHASign. However, in |
// some cases it's necessary to sign a digest directly, and this code shows how to do that. |
digest = digestForAlgorithmOverInputData(self.algorithm, self.inputData); |
// Then sign it. |
resultData = CFBridgingRelease( SecKeyCreateSignature( |
self.privateKey, |
secAlgorithmForAlgorithm(self.algorithm), |
(__bridge CFDataRef) digest, |
&errorCF |
) ); |
// Deal with the results. |
if (resultData == nil) { |
self.error = CFBridgingRelease( errorCF ); |
} else { |
self.signatureData = resultData; |
} |
} |
#if TARGET_OS_OSX |
- (void)runUsingTransforms { |
BOOL success; |
SecTransformRef transform; |
CFDataRef resultData; |
CFErrorRef errorCF; |
resultData = NULL; |
errorCF = NULL; |
// Set up the transform. |
transform = SecSignTransformCreate(self.privateKey, &errorCF); |
success = (transform != NULL); |
if (success) { |
success = setupTransformForAlgorithm(transform, self.algorithm, &errorCF); |
} |
if (success) { |
success = SecTransformSetAttribute(transform, kSecTransformInputAttributeName, (__bridge CFDataRef) self.inputData, &errorCF) != false; |
} |
// Run it. |
if (success) { |
resultData = SecTransformExecute(transform, &errorCF); |
success = (resultData != NULL); |
} |
// Process the results. |
if (success) { |
assert(CFGetTypeID(resultData) == CFDataGetTypeID()); |
self.signatureData = (__bridge NSData *) resultData; |
} else { |
assert(errorCF != NULL); |
self.error = (__bridge NSError *) errorCF; |
} |
// Clean up. |
if (resultData != NULL) { |
CFRelease(resultData); |
} |
if (errorCF != NULL) { |
CFRelease(errorCF); |
} |
if (transform != NULL) { |
CFRelease(transform); |
} |
} |
#endif |
#if TARGET_OS_IPHONE |
- (void)runUsingRaw { |
OSStatus err; |
NSData * digest; |
NSMutableData * resultData; |
size_t resultDataLength; |
// First create a SHA digest of the data. |
digest = digestForAlgorithmOverInputData(self.algorithm, self.inputData); |
// Then sign it. |
resultData = [[NSMutableData alloc] initWithLength:SecKeyGetBlockSize(self.privateKey)]; |
resultDataLength = resultData.length; |
err = SecKeyRawSign( |
self.privateKey, |
secPaddingForAlgorithm(self.algorithm), |
digest.bytes, |
digest.length, |
resultData.mutableBytes, |
&resultDataLength |
); |
// Deal with the results. |
if (err == errSecSuccess) { |
assert(resultDataLength == [resultData length]); |
self.signatureData = resultData; |
} else { |
self.error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; |
} |
} |
#endif |
- (void)main { |
if ( (SecKeyCreateSignature != NULL) && ! self.debugUseCompatibilityCode) { |
[self runUsingUnified]; |
} else { |
#if TARGET_OS_OSX |
[self runUsingTransforms]; |
#elif TARGET_OS_IPHONE |
[self runUsingRaw]; |
#else |
#error What platform? |
#endif |
} |
} |
@end |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-11-17