From f807872cb1119cc926a2a56f4d5fb7a189201893 Mon Sep 17 00:00:00 2001 From: Nikolay Polukhin Date: Wed, 16 May 2018 11:06:45 +0400 Subject: [PATCH] Added iOS support --- index.js | 2 +- ios/RNCloudPayments.h | 4 + ios/RNCloudPayments.m | 46 +++ ios/RNCloudPayments.xcodeproj/project.pbxproj | 297 ++++++++++++++ ios/SDK/Card.h | 30 ++ ios/SDK/Card.m | 387 ++++++++++++++++++ ios/SDK/NSDataENBase64.h | 45 ++ ios/SDK/NSDataENBase64.m | 328 +++++++++++++++ 8 files changed, 1138 insertions(+), 1 deletion(-) create mode 100644 ios/RNCloudPayments.h create mode 100644 ios/RNCloudPayments.m create mode 100644 ios/RNCloudPayments.xcodeproj/project.pbxproj create mode 100755 ios/SDK/Card.h create mode 100755 ios/SDK/Card.m create mode 100755 ios/SDK/NSDataENBase64.h create mode 100755 ios/SDK/NSDataENBase64.m diff --git a/index.js b/index.js index fb0c000..4311031 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,7 @@ import { NativeModules } from 'react-native'; const RNCloudPaymentsModule = NativeModules.RNCloudPayments; export default class RNCloudPayments { - static async isValidNumber(cardNumber, cardExp, cardCvv) { + static async isValidCard(cardNumber, cardExp, cardCvv) { try { return await RNCloudPaymentsModule.isValidNumber(cardNumber, cardExp, cardCvv); } catch(error) { diff --git a/ios/RNCloudPayments.h b/ios/RNCloudPayments.h new file mode 100644 index 0000000..3c88d5d --- /dev/null +++ b/ios/RNCloudPayments.h @@ -0,0 +1,4 @@ +#import + +@interface RNCloudPayments : NSObject +@end diff --git a/ios/RNCloudPayments.m b/ios/RNCloudPayments.m new file mode 100644 index 0000000..20e27be --- /dev/null +++ b/ios/RNCloudPayments.m @@ -0,0 +1,46 @@ +#import "RNCloudPayments.h" +#import "SDK/Card.m" + +@implementation RNCloudPayments + +RCT_EXPORT_MODULE(); + +RCT_EXPORT_METHOD(isValidNumber: (NSString *)cardNumber + cardExp: (NSString *)cardExp + cardCvv: (NSString *)cardCvv + resolve: (RCTPromiseResolveBlock)resolve + reject: (RCTPromiseRejectBlock)reject) +{ + if([Card isCardNumberValid: cardNumber]) { + resolve(@YES); + } else { + resolve(@NO); + } +}; + +RCT_EXPORT_METHOD(getType: (NSString *)cardNumber + cardExp: (NSString *)cardExp + cardCvv: (NSString *)cardCvv + resolve: (RCTPromiseResolveBlock)resolve + reject: (RCTPromiseRejectBlock)reject) +{ + CardType cardType = [Card cardTypeFromCardNumber: cardNumber]; + NSString *cardTypeString = [Card cardTypeToString: cardType]; + + resolve(cardTypeString); +} + +RCT_EXPORT_METHOD(createCryptogram: (NSString *)cardNumber + cardExp: (NSString *)cardExp + cardCvv: (NSString *)cardCvv + publicId: (NSString *)publicId + resolve: (RCTPromiseResolveBlock)resolve + reject: (RCTPromiseRejectBlock)reject) +{ + Card *_card = [[Card alloc] init]; + + NSString *cryptogram = [_card makeCardCryptogramPacket: cardNumber andExpDate:cardExp andCVV:cardCvv andMerchantPublicID:publicId]; + + resolve(cryptogram); +} +@end diff --git a/ios/RNCloudPayments.xcodeproj/project.pbxproj b/ios/RNCloudPayments.xcodeproj/project.pbxproj new file mode 100644 index 0000000..6993c14 --- /dev/null +++ b/ios/RNCloudPayments.xcodeproj/project.pbxproj @@ -0,0 +1,297 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 954FC90C20AADA700017B273 /* RNCloudPayments.m in Sources */ = {isa = PBXBuildFile; fileRef = 954FC90B20AADA700017B273 /* RNCloudPayments.m */; }; + 954FC9F920AC08E60017B273 /* Card.m in Sources */ = {isa = PBXBuildFile; fileRef = 954FC9F620AC08E60017B273 /* Card.m */; }; + 954FC9FA20AC08E60017B273 /* NSDataENBase64.m in Sources */ = {isa = PBXBuildFile; fileRef = 954FC9F720AC08E60017B273 /* NSDataENBase64.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 95E3C63020ADAE9D0098BA8E /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 954FC90720AADA700017B273 /* libRNCloudPayments.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNCloudPayments.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 954FC90A20AADA700017B273 /* RNCloudPayments.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNCloudPayments.h; sourceTree = ""; }; + 954FC90B20AADA700017B273 /* RNCloudPayments.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNCloudPayments.m; sourceTree = ""; }; + 954FC9F520AC08E60017B273 /* NSDataENBase64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSDataENBase64.h; sourceTree = ""; }; + 954FC9F620AC08E60017B273 /* Card.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Card.m; sourceTree = ""; }; + 954FC9F720AC08E60017B273 /* NSDataENBase64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSDataENBase64.m; sourceTree = ""; }; + 954FC9F820AC08E60017B273 /* Card.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Card.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 954FC90420AADA700017B273 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 954FC8FE20AADA700017B273 = { + isa = PBXGroup; + children = ( + 954FC9F420AC08D70017B273 /* SDK */, + 954FC90B20AADA700017B273 /* RNCloudPayments.m */, + 954FC90A20AADA700017B273 /* RNCloudPayments.h */, + 954FC90820AADA700017B273 /* Products */, + ); + sourceTree = ""; + }; + 954FC90820AADA700017B273 /* Products */ = { + isa = PBXGroup; + children = ( + 954FC90720AADA700017B273 /* libRNCloudPayments.a */, + ); + name = Products; + sourceTree = ""; + }; + 954FC9F420AC08D70017B273 /* SDK */ = { + isa = PBXGroup; + children = ( + 954FC9F820AC08E60017B273 /* Card.h */, + 954FC9F620AC08E60017B273 /* Card.m */, + 954FC9F520AC08E60017B273 /* NSDataENBase64.h */, + 954FC9F720AC08E60017B273 /* NSDataENBase64.m */, + ); + path = SDK; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 954FC90620AADA700017B273 /* RNCloudPayments */ = { + isa = PBXNativeTarget; + buildConfigurationList = 954FC91020AADA700017B273 /* Build configuration list for PBXNativeTarget "RNCloudPayments" */; + buildPhases = ( + 954FC90320AADA700017B273 /* Sources */, + 954FC90420AADA700017B273 /* Frameworks */, + 95E3C63020ADAE9D0098BA8E /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RNCloudPayments; + productName = RNCloudPayments; + productReference = 954FC90720AADA700017B273 /* libRNCloudPayments.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 954FC8FF20AADA700017B273 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0930; + TargetAttributes = { + 954FC90620AADA700017B273 = { + CreatedOnToolsVersion = 9.3; + }; + }; + }; + buildConfigurationList = 954FC90220AADA700017B273 /* Build configuration list for PBXProject "RNCloudPayments" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 954FC8FE20AADA700017B273; + productRefGroup = 954FC90820AADA700017B273 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 954FC90620AADA700017B273 /* RNCloudPayments */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 954FC90320AADA700017B273 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 954FC90C20AADA700017B273 /* RNCloudPayments.m in Sources */, + 954FC9F920AC08E60017B273 /* Card.m in Sources */, + 954FC9FA20AC08E60017B273 /* NSDataENBase64.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 954FC90E20AADA700017B273 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.3; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 954FC90F20AADA700017B273 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.3; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 954FC91120AADA700017B273 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 954FC91220AADA700017B273 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 954FC90220AADA700017B273 /* Build configuration list for PBXProject "RNCloudPayments" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 954FC90E20AADA700017B273 /* Debug */, + 954FC90F20AADA700017B273 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 954FC91020AADA700017B273 /* Build configuration list for PBXNativeTarget "RNCloudPayments" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 954FC91120AADA700017B273 /* Debug */, + 954FC91220AADA700017B273 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 954FC8FF20AADA700017B273 /* Project object */; +} diff --git a/ios/SDK/Card.h b/ios/SDK/Card.h new file mode 100755 index 0000000..659a3b7 --- /dev/null +++ b/ios/SDK/Card.h @@ -0,0 +1,30 @@ +#import + +typedef enum { + Unknown, + Visa, + MasterCard, + Maestro, + Mir, + JCB +} CardType; + +@interface Card : NSObject { + NSMutableArray *keyRefs; +} + ++(BOOL) isCardNumberValid: (NSString *) cardNumberString; + +/** + * Create cryptogram + * cardNumberString valid card number stirng + * expDateString string in format YYMM + * CVVString 3-digit number + * storePublicID public_id of store + */ +-(NSString *) makeCardCryptogramPacket: (NSString *) cardNumberString andExpDate: (NSString *) expDateString andCVV: (NSString *) CVVString andMerchantPublicID: (NSString *) merchantPublicIDString; + ++(CardType) cardTypeFromCardNumber:(NSString *)cardNumberString; ++(NSString *) cardTypeToString:(CardType)cardType; +@end + diff --git a/ios/SDK/Card.m b/ios/SDK/Card.m new file mode 100755 index 0000000..b465c13 --- /dev/null +++ b/ios/SDK/Card.m @@ -0,0 +1,387 @@ +#import "Card.h" +#import "NSDataENBase64.h" + +#define kPublicKey @"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArBZ1NNjvszen6BNWsgyD\nUJvDUZDtvR4jKNQtEwW1iW7hqJr0TdD8hgTxw3DfH+Hi/7ZjSNdH5EfChvgVW9wt\nTxrvUXCOyJndReq7qNMo94lHpoSIVW82dp4rcDB4kU+q+ekh5rj9Oj6EReCTuXr3\nfoLLBVpH0/z1vtgcCfQzsLlGkSTwgLqASTUsuzfI8viVUbxE1a+600hN0uBh/CYK\noMnCp/EhxV8g7eUmNsWjZyiUrV8AA/5DgZUCB+jqGQT/Dhc8e21tAkQ3qan/jQ5i\n/QYocA/4jW3WQAldMLj0PA36kINEbuDKq8qRh25v+k4qyjb7Xp4W2DywmNtG3Q20\nMQIDAQAB\n-----END PUBLIC KEY-----" +#define kPublicKeyVersion @"04" + +@interface Card (Private) ++ (NSString *)cleanCreditCardNo:(NSString *)aCreditCardNo; +@end + +@implementation Card + +-(id)init +{ + self = [super init]; + if(self) { + keyRefs = [NSMutableArray array]; + [self addPublicKey:kPublicKey withTag:@"public_key"]; + } + return self; +} + +#pragma mark - Private messages ++ (NSString *)cleanCreditCardNo:(NSString *)aCreditCardNo { + return [[aCreditCardNo componentsSeparatedByCharactersInSet: + [[NSCharacterSet decimalDigitCharacterSet] invertedSet]] + componentsJoinedByString:@""]; +} + + +// gets public key from const in .pch file ++ (NSData *) getPublicKey { + NSString *s_key = [NSString string]; + NSArray *a_key = [kPublicKey componentsSeparatedByString:@"\n"]; + BOOL f_key = FALSE; + + for (NSString *a_line in a_key) { + if ([a_line isEqualToString:@"-----BEGIN PUBLIC KEY-----"]) { + f_key = TRUE; + } + else if ([a_line isEqualToString:@"-----END PUBLIC KEY-----"]) { + f_key = FALSE; + } + else if (f_key) { + s_key = [s_key stringByAppendingString:a_line]; + } + } + if (s_key.length == 0) { + return(FALSE); + } else { + + // s_key = [s_key stringByAppendingString:@"\n"]; + NSData *returnData = [[NSData alloc] initWithBase64EncodedString:s_key options:NSDataBase64DecodingIgnoreUnknownCharacters]; + + returnData = [Card stripPublicKeyHeader:returnData]; + + + return returnData; + } +} + ++ (NSData *)stripPublicKeyHeader:(NSData *)d_key +{ + // Skip ASN.1 public key header + if (d_key == nil) return(nil); + + unsigned long len = [d_key length]; + if (!len) return(nil); + + unsigned char *c_key = (unsigned char *)[d_key bytes]; + unsigned int idx = 0; + + if (c_key[idx++] != 0x30) return(nil); + + if (c_key[idx] > 0x80) idx += c_key[idx] - 0x80 + 1; + else idx++; + + // PKCS #1 rsaEncryption szOID_RSA_RSA + static unsigned char seqiod[] = + { 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x01, 0x05, 0x00 }; + if (memcmp(&c_key[idx], seqiod, 15)) return(nil); + + idx += 15; + + if (c_key[idx++] != 0x03) return(nil); + + if (c_key[idx] > 0x80) idx += c_key[idx] - 0x80 + 1; + else idx++; + + if (c_key[idx++] != '\0') return(nil); + + // Now make a new NSData from this buffer + return([NSData dataWithBytes:&c_key[idx] length:len - idx]); +} + +#pragma mark - Public messages ++(BOOL) isCardNumberValid: (NSString *) cardNumberString { + NSString *cleanCardNumber = [Card cleanCreditCardNo:cardNumberString]; + + if (cleanCardNumber.length == 0) { + return NO; + } + + NSMutableArray *cardNumberCharactersArray = [[NSMutableArray alloc] initWithCapacity:[cleanCardNumber length]]; + for (int i=0; i < [cleanCardNumber length]; i++) { + NSString *ichar = [NSString stringWithFormat:@"%c", [cleanCardNumber characterAtIndex:i]]; + [cardNumberCharactersArray addObject:ichar]; + } + + // INFO: one of Luhn algorithm implementation + BOOL isOdd = YES; + int oddSum = 0; + int evenSum = 0; + + for (int i = (int)[cleanCardNumber length] - 1; i >= 0; i--) { + int digit = [(NSString *)[cardNumberCharactersArray objectAtIndex:i] intValue]; + if (isOdd) { + oddSum += digit; + } else { + evenSum += digit/5 + (2*digit) % 10; + } + + isOdd = !isOdd; + } + + return ((oddSum + evenSum) % 10 == 0); +} + +-(NSString *) makeCardCryptogramPacket: (NSString *) cardNumberString andExpDate: (NSString *) expDateString andCVV: (NSString *) CVVString andMerchantPublicID: (NSString *) merchantPublicIDString { + + // ExpDate must be in YYMM format + NSArray *cardDateComponents = [expDateString componentsSeparatedByString:@"/"]; + NSString *cardExpirationDateString = [NSString stringWithFormat:@"%@%@",cardDateComponents[1],cardDateComponents[0]]; + + NSMutableString *packetString = [NSMutableString string]; + NSString *cryptogramString = nil; + NSString *decryptedCryptogram = nil; + + // create cryptogram + NSString *cleanCardNumber = [Card cleanCreditCardNo:cardNumberString]; + decryptedCryptogram = [NSString stringWithFormat:@"%@@%@@%@@%@", + cleanCardNumber, + cardExpirationDateString, + CVVString, + merchantPublicIDString]; + + SecKeyRef key = [self publicKey]; + cryptogramString = [Card encryptRSA:decryptedCryptogram key:key]; + cryptogramString = [cryptogramString stringByReplacingOccurrencesOfString:@"\n" withString:@""]; + cryptogramString = [cryptogramString stringByReplacingOccurrencesOfString:@"\r" withString:@""]; + + [packetString appendString:@"02"]; + [packetString appendString:[cleanCardNumber substringWithRange:NSMakeRange(0, 6)]]; + [packetString appendString:[cleanCardNumber substringWithRange:NSMakeRange(cleanCardNumber.length-4, 4)]]; + [packetString appendString:cardExpirationDateString]; + [packetString appendString:kPublicKeyVersion]; + [packetString appendString:cryptogramString]; + + return (NSString *) packetString; +} + + ++(CardType) cardTypeFromCardNumber:(NSString *)cardNumberString { + NSString *cleanCardNumber = [Card cleanCreditCardNo:cardNumberString]; + + if (cleanCardNumber.length < 1) { + return Unknown; + } + + int first = [[cleanCardNumber substringWithRange:NSMakeRange(0, 1)] intValue]; + + if (first == 4) { + return Visa; + } + + if (first == 6) { + return Maestro; + } + + if (cleanCardNumber.length < 2) { + return Unknown; + } + + int firstTwo = [[cleanCardNumber substringWithRange:NSMakeRange(0, 2)] intValue]; + + if (firstTwo == 35) { + return JCB; + } + + if (firstTwo == 50 || (firstTwo >= 56 && firstTwo <= 58)) { + return Maestro; + } + + if (firstTwo >= 51 && firstTwo <= 55) { + return MasterCard; + } + + if (cleanCardNumber.length < 4) { + return Unknown; + } + + int firstFour = [[cleanCardNumber substringWithRange:NSMakeRange(0, 4)] intValue]; + + if (firstFour >= 2200 && firstFour <= 2204) { + return Mir; + } + + if (firstFour >= 2221 && firstFour <= 2720) { + return MasterCard; + } + + return Unknown; +} + + ++(NSString *) cardTypeToString:(CardType)cardType { + + switch(cardType) { + case Visa: + return @"Visa"; + break; + case MasterCard: + return @"MasterCard"; + break; + case Maestro: + return @"Maestro"; + break; + case Mir: + return @"MIR"; + break; + case JCB: + return @"JCB"; + break; + default: + return @"Unknown"; + } +} + +#pragma mark - Security ++(NSString *)encryptRSA:(NSString *)plainTextString key:(SecKeyRef)publicKey +{ + size_t cipherBufferSize = SecKeyGetBlockSize(publicKey); + uint8_t *cipherBuffer = malloc(cipherBufferSize); + uint8_t *nonce = (uint8_t *)[plainTextString cStringUsingEncoding:NSASCIIStringEncoding]; + SecKeyEncrypt(publicKey, + kSecPaddingOAEP, + nonce, + strlen( (char*)nonce ), + &cipherBuffer[0], + &cipherBufferSize); + NSData *encryptedData = [NSData dataWithBytes:cipherBuffer length:cipherBufferSize]; + return [NSDataENBase64 base64StringFromData:encryptedData]; +} + ++(NSString *)decryptRSA:(NSString *)cipherString key:(SecKeyRef) privateKey { + size_t plainBufferSize = SecKeyGetBlockSize(privateKey); + uint8_t *plainBuffer = malloc(plainBufferSize); + NSData *incomingData = [NSDataENBase64 dataFromBase64String:cipherString]; + uint8_t *cipherBuffer = (uint8_t*)[incomingData bytes]; + size_t cipherBufferSize = SecKeyGetBlockSize(privateKey); + SecKeyDecrypt(privateKey, + kSecPaddingOAEP, + cipherBuffer, + cipherBufferSize, + plainBuffer, + &plainBufferSize); + NSData *decryptedData = [NSData dataWithBytes:plainBuffer length:plainBufferSize]; + NSString *decryptedString = [[NSString alloc] initWithData:decryptedData encoding:NSASCIIStringEncoding]; + return decryptedString; +} + +-(SecKeyRef)publicKey +{ + SecKeyRef p_key = NULL; + for (NSValue *refVal in keyRefs) { + [refVal getValue:&p_key]; + if (p_key == NULL) continue; + } + return p_key; +} + +- (NSData *)stripPublicKeyHeader:(NSData *)d_key +{ + // Skip ASN.1 public key header + if (d_key == nil) return(nil); + + unsigned long len = [d_key length]; + if (!len) return(nil); + + unsigned char *c_key = (unsigned char *)[d_key bytes]; + unsigned int idx = 0; + + if (c_key[idx++] != 0x30) return(nil); + + if (c_key[idx] > 0x80) idx += c_key[idx] - 0x80 + 1; + else idx++; + + // PKCS #1 rsaEncryption szOID_RSA_RSA + static unsigned char seqiod[] = + { 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x01, 0x05, 0x00 }; + if (memcmp(&c_key[idx], seqiod, 15)) return(nil); + + idx += 15; + + if (c_key[idx++] != 0x03) return(nil); + + if (c_key[idx] > 0x80) idx += c_key[idx] - 0x80 + 1; + else idx++; + + if (c_key[idx++] != '\0') return(nil); + + // Now make a new NSData from this buffer + return([NSData dataWithBytes:&c_key[idx] length:len - idx]); +} + +- (BOOL)addPublicKey:(NSString *)key withTag:(NSString *)tag +{ + NSString *s_key = [NSString string]; + NSArray *a_key = [key componentsSeparatedByString:@"\n"]; + BOOL f_key = FALSE; + + for (NSString *a_line in a_key) { + if ([a_line isEqualToString:@"-----BEGIN PUBLIC KEY-----"]) { + f_key = TRUE; + } + else if ([a_line isEqualToString:@"-----END PUBLIC KEY-----"]) { + f_key = FALSE; + } + else if (f_key) { + s_key = [s_key stringByAppendingString:a_line]; + } + } + if (s_key.length == 0) return(FALSE); + + // This will be base64 encoded, decode it. + NSData *d_key = [NSDataENBase64 dataFromBase64String:s_key]; + d_key = [self stripPublicKeyHeader:d_key]; + if (d_key == nil) return(FALSE); + + NSData *d_tag = [NSData dataWithBytes:[tag UTF8String] length:[tag length]]; + + // Delete any old lingering key with the same tag + NSMutableDictionary *publicKey = [[NSMutableDictionary alloc] init]; + [publicKey setObject:(__bridge id) kSecClassKey forKey:(__bridge id)kSecClass]; + [publicKey setObject:(__bridge id) kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType]; + [publicKey setObject:d_tag forKey:(__bridge id)kSecAttrApplicationTag]; + SecItemDelete((__bridge CFDictionaryRef)publicKey); + + CFTypeRef persistKey = nil; + + // Add persistent version of the key to system keychain + [publicKey setObject:d_key forKey:(__bridge id)kSecValueData]; + [publicKey setObject:(__bridge id) kSecAttrKeyClassPublic forKey:(__bridge id) + kSecAttrKeyClass]; + [publicKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id) + kSecReturnPersistentRef]; + + OSStatus secStatus = SecItemAdd((__bridge CFDictionaryRef)publicKey, &persistKey); + if (persistKey != nil) CFRelease(persistKey); + + if ((secStatus != noErr) && (secStatus != errSecDuplicateItem)) { + return(FALSE); + } + + // Now fetch the SecKeyRef version of the key + SecKeyRef keyRef = nil; + + [publicKey removeObjectForKey:(__bridge id)kSecValueData]; + [publicKey removeObjectForKey:(__bridge id)kSecReturnPersistentRef]; + [publicKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnRef + ]; + [publicKey setObject:(__bridge id) kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType]; + secStatus = SecItemCopyMatching((__bridge CFDictionaryRef)publicKey, + (CFTypeRef *)&keyRef); + + if (keyRef == nil) return(FALSE); + + // Add to our pseudo keychain + [keyRefs addObject:[NSValue valueWithBytes:&keyRef objCType:@encode( + SecKeyRef)]]; + + return(TRUE); +} + +@end diff --git a/ios/SDK/NSDataENBase64.h b/ios/SDK/NSDataENBase64.h new file mode 100755 index 0000000..0571fbe --- /dev/null +++ b/ios/SDK/NSDataENBase64.h @@ -0,0 +1,45 @@ +// +// NSData+ENBase64.h +// base64 +// +// Created by Matt Gallagher on 2009/06/03. +// Copyright 2009 Matt Gallagher. All rights reserved. +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. Permission is granted to anyone to +// use this software for any purpose, including commercial applications, and to +// alter it and redistribute it freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source +// distribution. +// + +#import + +void *NewBase64Decode( + const char *inputBuffer, + size_t length, + size_t *outputLength); + +char *NewBase64Encode( + const void *inputBuffer, + size_t length, + bool separateLines, + size_t *outputLength); + +@interface NSDataENBase64: NSData + ++ (NSData *)dataFromBase64String:(NSString *)aString; ++ (NSString *)base64StringFromData: (NSData *)aData; + +- (NSString *)base64EncodedString; + +@end + diff --git a/ios/SDK/NSDataENBase64.m b/ios/SDK/NSDataENBase64.m new file mode 100755 index 0000000..4d2ef0c --- /dev/null +++ b/ios/SDK/NSDataENBase64.m @@ -0,0 +1,328 @@ +// +// NSData+ENBase64.m +// base64 +// +// Created by Matt Gallagher on 2009/06/03. +// Copyright 2009 Matt Gallagher. All rights reserved. +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. Permission is granted to anyone to +// use this software for any purpose, including commercial applications, and to +// alter it and redistribute it freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source +// distribution. +// + +#import "NSDataENBase64.h" + +// +// Mapping from 6 bit pattern to ASCII character. +// +static unsigned char base64EncodeLookup[65] = +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +// +// Definition for "masked-out" areas of the base64DecodeLookup mapping +// +#define xx 65 + +// +// Mapping from ASCII character to 6 bit pattern. +// +static unsigned char base64DecodeLookup[256] = +{ + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 62, xx, xx, xx, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, xx, xx, xx, xx, xx, xx, + xx, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, xx, xx, xx, xx, xx, + xx, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, xx, xx, xx, xx, xx, + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, +}; + +// +// Fundamental sizes of the binary and base64 encode/decode units in bytes +// +#define BINARY_UNIT_SIZE 3 +#define BASE64_UNIT_SIZE 4 + +// +// NewBase64Decode +// +// Decodes the base64 ASCII string in the inputBuffer to a newly malloced +// output buffer. +// +// inputBuffer - the source ASCII string for the decode +// length - the length of the string or -1 (to specify strlen should be used) +// outputLength - if not-NULL, on output will contain the decoded length +// +// returns the decoded buffer. Must be free'd by caller. Length is given by +// outputLength. +// +void *NewBase64Decode( + const char *inputBuffer, + size_t length, + size_t *outputLength) +{ + size_t invalLength = -1; + if (length == invalLength) + { + length = strlen(inputBuffer); + } + + size_t outputBufferSize = + ((length+BASE64_UNIT_SIZE-1) / BASE64_UNIT_SIZE) * BINARY_UNIT_SIZE; + unsigned char *outputBuffer = (unsigned char *)malloc(outputBufferSize); + + size_t i = 0; + size_t j = 0; + while (i < length) + { + // + // Accumulate 4 valid characters (ignore everything else) + // + unsigned char accumulated[BASE64_UNIT_SIZE]; + size_t accumulateIndex = 0; + while (i < length) + { + unsigned char decode = base64DecodeLookup[inputBuffer[i++]]; + if (decode != xx) + { + accumulated[accumulateIndex] = decode; + accumulateIndex++; + + if (accumulateIndex == BASE64_UNIT_SIZE) + { + break; + } + } + } + + // + // Store the 6 bits from each of the 4 characters as 3 bytes + // + // (Uses improved bounds checking suggested by Alexandre Colucci) + // + if(accumulateIndex >= 2) + outputBuffer[j] = (accumulated[0] << 2) | (accumulated[1] >> 4); + if(accumulateIndex >= 3) + outputBuffer[j + 1] = (accumulated[1] << 4) | (accumulated[2] >> 2); + if(accumulateIndex >= 4) + outputBuffer[j + 2] = (accumulated[2] << 6) | accumulated[3]; + j += accumulateIndex - 1; + } + + if (outputLength) + { + *outputLength = j; + } + return outputBuffer; +} + +// +// NewBase64Encode +// +// Encodes the arbitrary data in the inputBuffer as base64 into a newly malloced +// output buffer. +// +// inputBuffer - the source data for the encode +// length - the length of the input in bytes +// separateLines - if zero, no CR/LF characters will be added. Otherwise +// a CR/LF pair will be added every 64 encoded chars. +// outputLength - if not-NULL, on output will contain the encoded length +// (not including terminating 0 char) +// +// returns the encoded buffer. Must be free'd by caller. Length is given by +// outputLength. +// +char *NewBase64Encode( + const void *buffer, + size_t length, + bool separateLines, + size_t *outputLength) +{ + const unsigned char *inputBuffer = (const unsigned char *)buffer; + +#define MAX_NUM_PADDING_CHARS 2 +#define OUTPUT_LINE_LENGTH 64 +#define INPUT_LINE_LENGTH ((OUTPUT_LINE_LENGTH / BASE64_UNIT_SIZE) * BINARY_UNIT_SIZE) +#define CR_LF_SIZE 2 + + // + // Byte accurate calculation of final buffer size + // + size_t outputBufferSize = + ((length / BINARY_UNIT_SIZE) + + ((length % BINARY_UNIT_SIZE) ? 1 : 0)) + * BASE64_UNIT_SIZE; + if (separateLines) + { + outputBufferSize += + (outputBufferSize / OUTPUT_LINE_LENGTH) * CR_LF_SIZE; + } + + // + // Include space for a terminating zero + // + outputBufferSize += 1; + + // + // Allocate the output buffer + // + char *outputBuffer = (char *)malloc(outputBufferSize); + if (!outputBuffer) + { + return NULL; + } + + size_t i = 0; + size_t j = 0; + const size_t lineLength = separateLines ? INPUT_LINE_LENGTH : length; + size_t lineEnd = lineLength; + + while (true) + { + if (lineEnd > length) + { + lineEnd = length; + } + + for (; i + BINARY_UNIT_SIZE - 1 < lineEnd; i += BINARY_UNIT_SIZE) + { + // + // Inner loop: turn 48 bytes into 64 base64 characters + // + outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0xFC) >> 2]; + outputBuffer[j++] = base64EncodeLookup[((inputBuffer[i] & 0x03) << 4) + | ((inputBuffer[i + 1] & 0xF0) >> 4)]; + outputBuffer[j++] = base64EncodeLookup[((inputBuffer[i + 1] & 0x0F) << 2) + | ((inputBuffer[i + 2] & 0xC0) >> 6)]; + outputBuffer[j++] = base64EncodeLookup[inputBuffer[i + 2] & 0x3F]; + } + + if (lineEnd == length) + { + break; + } + + // + // Add the newline + // + outputBuffer[j++] = '\r'; + outputBuffer[j++] = '\n'; + lineEnd += lineLength; + } + + if (i + 1 < length) + { + // + // Handle the single '=' case + // + outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0xFC) >> 2]; + outputBuffer[j++] = base64EncodeLookup[((inputBuffer[i] & 0x03) << 4) + | ((inputBuffer[i + 1] & 0xF0) >> 4)]; + outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i + 1] & 0x0F) << 2]; + outputBuffer[j++] = '='; + } + else if (i < length) + { + // + // Handle the double '=' case + // + outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0xFC) >> 2]; + outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0x03) << 4]; + outputBuffer[j++] = '='; + outputBuffer[j++] = '='; + } + outputBuffer[j] = 0; + + // + // Set the output length and return the buffer + // + if (outputLength) + { + *outputLength = j; + } + return outputBuffer; +} + +@implementation NSDataENBase64 : NSData + +// +// dataFromBase64String: +// +// Creates an NSData object containing the base64 decoded representation of +// the base64 string 'aString' +// +// Parameters: +// aString - the base64 string to decode +// +// returns the autoreleased NSData representation of the base64 string +// ++ (NSData *)dataFromBase64String:(NSString *)aString +{ + NSData *data = [aString dataUsingEncoding:NSASCIIStringEncoding]; + size_t outputLength; + void *outputBuffer = NewBase64Decode([data bytes], [data length], &outputLength); + NSData *result = [NSData dataWithBytes:outputBuffer length:outputLength]; + free(outputBuffer); + return result; +} + ++ (NSString *)base64StringFromData: (NSData *)aData { + size_t outputLength = 0; + char *outputBuffer = + NewBase64Encode([aData bytes], [aData length], true, &outputLength); + + NSString *result = + [[NSString alloc] + initWithBytes:outputBuffer + length:outputLength + encoding:NSASCIIStringEncoding]; + free(outputBuffer); + return result; +} + +// +// base64EncodedString +// +// Creates an NSString object that contains the base 64 encoding of the +// receiver's data. Lines are broken at 64 characters long. +// +// returns an autoreleased NSString being the base 64 representation of the +// receiver. +// +- (NSString *)base64EncodedString +{ + size_t outputLength = 0; + char *outputBuffer = + NewBase64Encode([self bytes], [self length], true, &outputLength); + + NSString *result = + [[NSString alloc] + initWithBytes:outputBuffer + length:outputLength + encoding:NSASCIIStringEncoding]; + free(outputBuffer); + return result; +} + +@end +