preface

Sometimes you need to store resources locally and download them from the server, and because runtime security is involved, it is necessary to add validation logic, hence some thoughts in this article.

The IPA packet is tampered with

The first consideration is the security of IPA packets. Through iTunes, we can download ipA and extract, modify the files in the package, and then compress into ipA package.

  • 1. If developer A gets the IPA package of app P and modifies any files in it, the signature will be invalid and the IPA package cannot be installed. (The signature is stored in the _CodeSignature folder of the.app file)
  • 2. If developer B gets the IPA package of app P, installs it on his phone, and directly modifies the configuration file under the Bundle/Application, app P can still run.
  • 3. If developer C gets the IPA package of app P, modifs some configuration files, re-signs with his own certificate and releases the IPA package through other channels, the IPA package can be installed normally.

During normal application, the file of app package cannot be modified, and only the jailbroken machine will have the situation 2; The ipA package re-signed in case 3 cannot be uploaded to the AppStore. The real app installation directory isvar/mobile/Containers/Bundle/ApplicationThe sandbox directory isvar/mobile/Containers/Data/ApplicationSimilarly, the emulated installation directory is also in/BundleNext, the sandbox is in/DataUnder;

Validation of downloaded resources

Downloaded resources are stored in a sandbox directory that developers cannot modify without jailbreaking. However, resources are downloaded using HTTP or HTTPS. You can modify the downloaded resources by proxy (unless HTTPS certificate verification is enabled). In order to ensure the reliability of download resources, a set of authentication scheme based on RSA algorithm is adopted. The specific key points are as follows: 1. The developer generates a pair of keys: public key and private key. 2. After the file is uploaded to the configuration platform, the configuration platform hash the file to obtain MD5STR, and sign the md5str with the private key to obtain signStr, and then send the file and signStr to the client. 3. The client downloads the file and signStr, calculates the digest of the file (MD5) to obtain MD5STR, and verifies the validity of signStr with MD5STR and the public key.

Explanation:The calculation of asymmetric encryption algorithm is complicated, so only digest (MD5 value) is encrypted.

The specific flow chart is as follows:

The iOS RSA algorithm

The RSA algorithm has two encryption modes:

  • The public key is encrypted and the private key is decrypted. (Generally used to send messages from the public key holder (client) to the private key holder (background))
  • Encrypt the private key and decrypt the public key. (Generally used for signature and authentication, private key encryption is equivalent to signature, public key decryption is equivalent to authentication)

The Security. Framework provided by Apple has the following four methods:

  • SecKeyEncrypt — encrypts a block of data using the specified key.
  • SecKeyDecrypt — Decrypts a block of data using the specified key.
  • SecKeyRawSign — Signs a block of data using the specified key.
  • SecKeyRawVerify – verifies a signature against a block of data and a specified key.

Compared to OpenSSL, it provides the following four interfaces:

int     RSA_public_encrypt(int flen, const unsigned char *from,
                unsigned char *to, RSA *rsa,int padding);
int     RSA_private_encrypt(int flen, const unsigned char *from,
                unsigned char *to, RSA *rsa,int padding);
int     RSA_public_decrypt(int flen, const unsigned char *from,
                unsigned char *to, RSA *rsa,int padding);
int     RSA_private_decrypt(int flen, const unsigned char *from,
                unsigned char *to, RSA *rsa,int padding);
Copy the code

Because the RSA algorithm requires a large amount of computation, RSA is not used to encrypt data directly. Instead, AES is used to encrypt data. RSA algorithm principle, here is a detailed introduction to the PRINCIPLE of RSA algorithm article.

Saving digital signatures

After receiving the signature sent from the background, you need to save the signature. You can choose to save the signature in a file, to NSUserDefault, or to the database. Besides, can I save in file properties? Write a piece of code to test it:

    NSMutableDictionary *changedAttrDict = [[NSMutableDictionary alloc] init];
    [changedAttrDict setObject:@"loying" forKey:NSFileOwnerAccountName];
    [changedAttrDict setObject:@"NSFileGroupOwnerAccountName" forKey:NSFileGroupOwnerAccountName];
    [changedAttrDict setObject:[NSDate dateWithTimeIntervalSinceNow:3600] forKey:NSFileCreationDate];
    
    NSError *error;
    
    BOOL ret = [[NSFileManager defaultManager] setAttributes:changedAttrDict ofItemAtPath:encodedDataPath error:&error];
Copy the code

After testing, the NSFileCreationDate property can be modified; NSFileGroupOwnerAccountName and cannot be modified NSFileOwnerAccountName (real machine for @ “mobile”); The simulator cannot modify the two properties. The most likely reason is that the files generated by the simulator are not authorized enough to modify the file properties. CreateDirectoryAtPath: withIntermediateDirectories: attributes, this method also has the limit. There are other limitations to writing file attributes. When a file is copied on a different hard disk format (HFS+ and FAT32), the attributes attached to the file may disappear.

NSFileProtectionKey is used to read and write files in background mode

For the convenience of development, you can choose to save to NSUserDefault. Create a new NSMutableDictionary with the file as the key and FileConfig as the value. FileConfig is the encapsulation of validation-related attributes for subsequent development.

All attributes of NSUserDefault will eventually be written to the plist file under Libary/Preference/, so NSUserDefault cannot store sensitive information. 2, if there are any errors, Terminating app due to uncaught exception ‘NSInvalidArgumentException, reason: ‘Attempt to insert non-property list object

That’s because NSUserDefault can only store basic types. If the dict contains custom types, you need to convert them to NSData before storing them. (There is no structure information in plIST, only key-value)

IOS Access Procedure

The preceding section describes the RSA-based download resource authentication scheme. The iOS process is as follows:

  • Upload resource files in the background, configure the platform to hash the files and sign them with private keys to obtain signature string signature.
  • Package the file and signature into a ZIP package and deliver it to the client.
  • The client decompresses the ZIP file, obtains the file and signature string, hashes the file, loads the local public key, and sends the hash value, signature, and public key to security.framework.
  • Provided by Security. FrameworkSecKeyRawVerifyMethod to verify the hash value, signature, and public key. If it passes, the file is not modified.

1. Decompress the ZIP

MiniZipArchive is available for iOS.

- (BOOL)unzipFile:(NSString *)file toFilePath:(NSString *)unZipFilePath overWrite:(BOOL)overWrite { MiniZipArchive *za =  [[MiniZipArchive alloc] init]; BOOL success = NO; if ([za UnzipOpenFile:file]) { success = [za UnzipFileTo:unZipFilePath overWrite:overWrite]; [za UnzipCloseFile]; } return success; }Copy the code

2. Load public and private keys

Der and. Pem formats: the. Der format is binary encoding, and the. Pem format is Base64 encoding. The iOS public key must be in. Der format and the private key must be in. P12 format, which can be converted using openSSL commands. (see instructions at the end) when loaded with NSData first loading the keys, again with the following: getPrivateKeyRefWithContentsOfFile: password: method to load the key; GetPublicKeyRefrenceFromeData: method to load the public key;

/ / get the private key - (SecKeyRef) getPrivateKeyRefWithContentsOfFile: (NSData *) p12Data password: (password nsstrings *) {if (! p12Data) { return nil; } SecKeyRef privateKeyRef = NULL; NSMutableDictionary * options = [[NSMutableDictionary alloc] init]; [options setObject: password forKey:(__bridge id)kSecImportExportPassphrase]; CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL); OSStatus securityError = SecPKCS12Import((__bridge CFDataRef) p12Data, (__bridge CFDictionaryRef)options, &items); if (securityError == noErr && CFArrayGetCount(items) > 0) { CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0); SecIdentityRef identityApp = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity); securityError = SecIdentityCopyPrivateKey(identityApp, &privateKeyRef); if (securityError ! = noErr) { privateKeyRef = NULL; } } CFRelease(items); return privateKeyRef; } - (SecKeyRef)getPublicKeyRefrenceFromeData:(NSData *)certData { SecKeyRef publicKeyRef = NULL; CFDataRef myCertData = (__bridge CFDataRef)certData; SecCertificateRef cert = SecCertificateCreateWithData(NULL, (CFDataRef)myCertData); if (cert == nil) { NSLog(@"Can not read certificate "); return nil; } SecPolicyRef policy = SecPolicyCreateBasicX509(); SecCertificateRef certArray[1] = {cert}; CFArrayRef myCerts = CFArrayCreate(NULL, (void *)(void *)certArray, 1, NULL); SecTrustRef trust; OSStatus status = SecTrustCreateWithCertificates(myCerts, policy, &trust); if (status ! = noErr) { NSLog(@"SecTrustCreateWithCertificates fail. Error Code: %d", (int)status); CFRelease(cert); CFRelease(policy); CFRelease(myCerts); return nil; } SecTrustResultType trustResult; status = SecTrustEvaluate(trust, &trustResult); if (status ! = noErr) { NSLog(@"SecTrustEvaluate fail. Error Code: %d", (int)status); CFRelease(cert); CFRelease(policy); CFRelease(trust); CFRelease(myCerts); return nil; } publicKeyRef = SecTrustCopyPublicKey(trust); CFRelease(cert); CFRelease(policy); CFRelease(trust); CFRelease(myCerts); return publicKeyRef; }Copy the code

3. Private key signature and public key authentication

After loading the public and private keys, with the private key to sign the original data, see the PKCSSignBytesSHA256withRSA method, returns the signature string; In the extract with zip out to verify the signature of the string, need to use the local public attestation, raw data and signature string, see PKCSVerifyBytesSHA256withRSA method; Note that since the algorithm chosen is kSecPaddingPKCS1SHA256, the raw data needs to be hashed once for SHA256. (kSecPaddingPKCS1SHA256 can only be used for SecKeyRawSign/SecKeyRawVerify)

BOOL PKCSVerifyBytesSHA256withRSA(NSData* plainData, NSData* signature, SecKeyRef publicKey) { if (! plainData || ! Signature) {// Protect return NO; } size_t signedHashBytesSize = SecKeyGetBlockSize(publicKey); const void* signedHashBytes = [signature bytes]; size_t hashBytesSize = CC_SHA256_DIGEST_LENGTH; uint8_t* hashBytes = malloc(hashBytesSize); if (! CC_SHA256([plainData bytes], (CC_LONG)[plainData length], hashBytes)) { return NO; } OSStatus status = SecKeyRawVerify(publicKey, kSecPaddingPKCS1SHA256, hashBytes, hashBytesSize, signedHashBytes, signedHashBytesSize); return status == errSecSuccess; } NSData* PKCSSignBytesSHA256withRSA(NSData* plainData, SecKeyRef privateKey) { size_t signedHashBytesSize = SecKeyGetBlockSize(privateKey); uint8_t* signedHashBytes = malloc(signedHashBytesSize); memset(signedHashBytes, 0x0, signedHashBytesSize); size_t hashBytesSize = CC_SHA256_DIGEST_LENGTH; uint8_t* hashBytes = malloc(hashBytesSize); if (! CC_SHA256([plainData bytes], (CC_LONG)[plainData length], hashBytes)) { return nil; } SecKeyRawSign(privateKey, kSecPaddingPKCS1SHA256, hashBytes, hashBytesSize, signedHashBytes, &signedHashBytesSize); NSData* signedHash = [NSData dataWithBytes:signedHashBytes length:(NSUInteger)signedHashBytesSize]; if (hashBytes) free(hashBytes); if (signedHashBytes) free(signedHashBytes); return signedHash; }Copy the code

4. Save the signature string

The signature string can use setxattrf to write the extended attributes of the file, ensuring a one-to-one correspondence between the signature string and the resource.

-(BOOL)setExtendValueWithPath:(NSString *)path key:(NSString *)key value:(NSData *)value {
    ssize_t writelen = setxattr([path fileSystemRepresentation],
                                [key UTF8String],
                                [value bytes],
                                [value length],
                                0,
                                0);
    return writelen == 0;
}
Copy the code

Surprisingly, the size of the file after writing the extended attributes does not change much. After a deliberate search of the document, I found the following sentence: Space consumed for extended attributes is counted towards the disk quotasof the file owner and file group It turns out that extended attributes are not written to files, but are saved by the file system.

Problems encountered

SecKeyRawVerify returns -9809

The common problem is that the signature of the configuration platform fails to be verified on the iOS client. You can perform the following operations to check the signature:

  • First, ensure that the public and private keys at both ends are a pair.
  • After platform signatures are configured, the public key of the iOS client is used for local authentication.
  • Ensure that the signature algorithm parameters on both sides are consistent.
  • The iOS client uses the private key of the configuration platform for signature and the public key for authentication.
  • Compare the platform signature string with the iOS signature string.

Pem -sha256 -out sign source openssl DGST -verify rsa_public_key.pem -sha256 – Signature sign source If the verification is successful, the following message is displayed: Verified OK

2. Openssl X509: appears when the certificate fails to be generatedExpecting: TRUSTED CERTIFICATEThe error

Pem 1024 openssl req -new -key private_key.pem -out rsacertreq.csr openssl x509 -req -days 3650 -in rsaCertReq.csr -signkey private_key.pem -out rsaCert.crt openssl x509 -outform der -in rsaCert.crt -out public_key.der openssl pkcs12 -export -out private_key.p12 -inkey private_key.pem -in rsaCert.crt

Reference since GithubGist

conclusion

No means can completely prevent malicious attacks, can only raise the threshold. RSA not only ensures that the resource is not tampered with, but also acts as a kind of verification to check whether the resource is missing files for various reasons.

The appendix

IOS uses security. framework to encrypt, decrypt, and verify RSA signatures blog.methodname.com/da-zao-yin-… Signing and Verifying on iOS using RSA xattr manpages demo address