Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(WIP) Integrate encryption and decryption #6

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 50 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import { NativeModules, Platform } from 'react-native'
import { Buffer } from 'buffer'
import hasher from 'hash.js'
const { RNECC } = NativeModules
const preHash = RNECC.preHash !== false
const { RNECC } = NativeModules;
const preHash = RNECC.preHash && RNECC.preHash !== false
const isAndroid = Platform.OS === 'android'
const encoding = 'base64'
const curves = {
Expand Down Expand Up @@ -34,7 +34,9 @@ module.exports = {
verify,
lookupKey,
hasKey,
keyFromPublic
keyFromPublic,
encrypt,
decrypt
}

function setServiceID (id) {
Expand Down Expand Up @@ -140,6 +142,51 @@ function verify ({ pubKey, data, algorithm, sig }, cb) {
RNECC.verify(normalizeOpts(opts), normalizeCallback(cb))
}

/**
* Decrypt a piece of cipher text
* @param pubKey {Buffer} options.pubKey - pubKey corresponding to private key to decrypt the cipher text
* @param data {Buffer} options.data - cipher text to decrypt
* @param cb {Function} callback function
*/
function decrypt({pubKey, data}, cb) {
checkServiceID()
assert(Buffer.isBuffer(pubKey) || typeof pubKey === 'string')
assert(Buffer.isBuffer(data) || typeof data === 'string')

checkNotCompact(pubKey)

const opts = {
service: serviceID,
accessGroup: accessGroup,
pub: pubKey,
data,
}

assert(typeof cb === 'function')

RNECC.decrypt(normalizeOpts(opts), normalizeCallback(cb))
}

/**
* Encrypt a piece of cipher text
* @param pubKey {Buffer} options.pubKey - pubKey key to encrypt the clear text
* @param data {Buffer} options.data - clear text to encrypt
* @param cb {Function} callback function
*/
function encrypt({pubKey, data}, cb) {
checkNotCompact(pubKey)

assert(Buffer.isBuffer(data) || typeof data === 'string')
assert(typeof pubKey === 'string' || Buffer.isBuffer(pubKey))
assert(typeof cb === 'function')

const opts = {
pub: pubKey,
data,
};
RNECC.encrypt(normalizeOpts(opts), normalizeCallback(cb));
}

function normalizeOpts (opts) {
;['data', 'hash', 'pub', 'sig'].forEach(prop => {
if (opts[prop]) opts[prop] = toString(opts[prop])
Expand Down
2 changes: 2 additions & 0 deletions ios/RNECC.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@
- (NSString *) uuidString;
- (NSData *)sign:(nonnull NSDictionary*)options errMsg:(NSString **) errMsg;
- (BOOL) verify:(NSString *)base64pub hash:(NSData *)hash sig:(NSData *)sig errMsg:(NSString **)errMsg;
- (NSData *)encrypt: (nonnull NSDictionary*)options errMsg:(NSString **) errMsg;
- (NSData *)decrypt: (nonnull NSDictionary*)options errMsg:(NSString **) errMsg;
@end
129 changes: 124 additions & 5 deletions ios/RNECC.m
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ - (NSString *) generateECPair:(nonnull NSDictionary*) options

// Should be the secret invalidated when passcode is removed? If not then use `kSecAttrAccessibleWhenUnlocked`.
sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
// kSecAccessControlTouchIDAny | kSecAccessControlPrivateKeyUsage,
// kSecAccessControlUserPresence,
kNilOptions,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
kSecAccessControlUserPresence | kSecAccessControlPrivateKeyUsage,
// kSecAccessControlTouchIDAny | kSecAccessControlPrivateKeyUsage,
// kSecAccessControlUserPresence,
&sacErr);

if (sacErr) {
Expand Down Expand Up @@ -124,7 +124,7 @@ - (NSString *) generateECPair:(nonnull NSDictionary*) options
[parameters setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup];
}

if (sizeInBits == @256 && !isSimulator && floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_8_0) {
if ([sizeInBits isEqualToNumber: @256] && !isSimulator && floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_8_0) {
NSOperatingSystemVersion os = [[NSProcessInfo processInfo] operatingSystemVersion];
if (os.majorVersion >= 9) {
[parameters setObject:(__bridge id)kSecAttrTokenIDSecureEnclave forKey:(__bridge id)kSecAttrTokenID];
Expand Down Expand Up @@ -206,6 +206,125 @@ - (NSString *) generateECPair:(nonnull NSDictionary*) options
});
}

RCT_EXPORT_METHOD(encrypt:(nonnull NSDictionary *)options
callback:(RCTResponseSenderBlock)callback) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString* errMsg;
NSData* cipherText = [self encrypt:options errMsg:&errMsg];
if (!cipherText) {
callback(@[rneccMakeError(errMsg)]);
return;
}

NSString* base64cipherText = [cipherText base64EncodedStringWithOptions:0];
callback(@[[NSNull null], base64cipherText]);
});
}

-(NSData *)encrypt:(nonnull NSDictionary *)options
errMsg:(NSString **) errMsg {
NSString* base64pub = [options valueForKey:@"pub"];
NSString* base64data = [options valueForKey:@"data"];

SecKeyRef publicKey = [self getPublicKeyRef:base64pub];
if (!publicKey) {
*errMsg = @"can't find public key";
return nil;
}

NSData *data = [[NSData alloc] initWithBase64EncodedString:base64data options:0];

SecKeyAlgorithm algorithm = kSecKeyAlgorithmECIESEncryptionStandardX963SHA512AESGCM;
BOOL canEncrypt = SecKeyIsAlgorithmSupported(publicKey,
kSecKeyOperationTypeEncrypt,
algorithm);
if(!canEncrypt) {
*errMsg = @"can't encrypt";
return nil;
}
BOOL isSizeCorrect = ([data length] < (SecKeyGetBlockSize(publicKey)-130));
if(! isSizeCorrect) {
*errMsg = @"size is not correct";
return nil;
}

NSData* cipherText = nil;
CFErrorRef error = NULL;

cipherText = (NSData*)CFBridgingRelease( // ARC takes ownership
SecKeyCreateEncryptedData(publicKey,
algorithm,
(__bridge CFDataRef)data,
&error));

CFRelease(publicKey);
if (!cipherText) {
NSError *err = CFBridgingRelease(error); // ARC takes ownership
*errMsg = [err description];
return nil;
}
return cipherText;
}

RCT_EXPORT_METHOD(decrypt:(nonnull NSDictionary *)options
callback:(RCTResponseSenderBlock)callback) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString* errMsg;
NSData* clearText = [self decrypt:options errMsg:&errMsg];
if (!clearText) {
callback(@[rneccMakeError(errMsg)]);
return;
}

NSString* base64clearText = [clearText base64EncodedStringWithOptions:0];
callback(@[[NSNull null], base64clearText]);
});
}

-(NSData *)decrypt: (nonnull NSDictionary*)options
errMsg:(NSString **) errMsg {

NSString* base64pub = [options valueForKey:@"pub"];
NSString* base64cipherText = [options valueForKey:@"data"];
SecKeyRef publicKey = [self getPublicKeyRef:base64pub];

OSStatus status;
NSData *cipherText = [[NSData alloc] initWithBase64EncodedString:base64cipherText options:0];

SecKeyRef privateKey = [self getPrivateKeyRef:options status:&status];
if (!privateKey) {
*errMsg = keychainStatusToString(status);
return nil;
}

BOOL canDecrypt = SecKeyIsAlgorithmSupported(privateKey,
kSecKeyOperationTypeDecrypt,
kSecKeyAlgorithmECIESEncryptionStandardX963SHA512AESGCM);
if(!canDecrypt) {
*errMsg = @"can't encrypt";
return nil;
}

NSData* clearText = nil;

CFErrorRef error = NULL;
clearText = (NSData*)CFBridgingRelease( // ARC takes ownership
SecKeyCreateDecryptedData(privateKey,
kSecKeyAlgorithmECIESEncryptionStandardX963SHA512AESGCM,
(__bridge CFDataRef)cipherText,
&error));
CFRelease(privateKey);
CFRelease(publicKey);
if (!clearText) {
NSError *err = CFBridgingRelease(error); // ARC takes ownership
*errMsg = [err description];
return nil;
}
return clearText;
}



RCT_EXPORT_METHOD(sign:(nonnull NSDictionary *)options
// withAuthenticationPrompt:(NSString *)prompt
callback:(RCTResponseSenderBlock)callback) {
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,8 @@
"dependencies": {
"buffer": "^4.9.1",
"hash.js": "^1.0.3"
},
"devDependencies": {
"react-native": "^0.60.5"
}
}
Loading