Related
I'm trying to get AudioKit to pipe the microphone to Google's Speech-to-Text API as seen here but I'm not entirely sure how to go about it.
To prepare the audio for the Speech-to-Text engine, you need to set up the encoding and pass it through as chunks. In the example Google uses, they use Apple's AVFoundation, but I'd like to use AudioKit so I can preform some pre-processing such as cutting of low amplitudes etc.
I believe the right way to do this is to use a Tap:
First, I should match the format by:
var asbd = AudioStreamBasicDescription()
asbd.mSampleRate = 16000.0
asbd.mFormatID = kAudioFormatLinearPCM
asbd.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked
asbd.mBytesPerPacket = 2
asbd.mFramesPerPacket = 1
asbd.mBytesPerFrame = 2
asbd.mChannelsPerFrame = 1
asbd.mBitsPerChannel = 16
AudioKit.format = AVAudioFormat(streamDescription: &asbd)!
Then create a tap such as:
open class TestTap {
internal let bufferSize: UInt32 = 1_024
#objc public init(_ input: AKNode?) {
input?.avAudioNode.installTap(onBus: 0, bufferSize: bufferSize, format: AudioKit.format) { buffer, _ in
// do work here
}
}
}
But I wasn't able to identify the right way of handling this data to be sent to the Google Speech-to-Text API via the method streamAudioData in real-time with AudioKit but perhaps I am going about this the wrong way?
UPDATE:
I've created a Tap as such:
open class TestTap {
internal var audioData = NSMutableData()
internal let bufferSize: UInt32 = 1_024
func toData(buffer: AVAudioPCMBuffer) -> NSData {
let channelCount = 2 // given PCMBuffer channel count is
let channels = UnsafeBufferPointer(start: buffer.floatChannelData, count: channelCount)
return NSData(bytes: channels[0], length:Int(buffer.frameCapacity * buffer.format.streamDescription.pointee.mBytesPerFrame))
}
#objc public init(_ input: AKNode?) {
input?.avAudioNode.installTap(onBus: 0, bufferSize: bufferSize, format: AudioKit.format) { buffer, _ in
self.audioData.append(self.toData(buffer: buffer) as Data)
// We recommend sending samples in 100ms chunks (from Google)
let chunkSize: Int /* bytes/chunk */ = Int(0.1 /* seconds/chunk */
* AudioKit.format.sampleRate /* samples/second */
* 2 /* bytes/sample */ )
if self.audioData.length > chunkSize {
SpeechRecognitionService
.sharedInstance
.streamAudioData(self.audioData,
completion: { response, error in
if let error = error {
print("ERROR: \(error.localizedDescription)")
SpeechRecognitionService.sharedInstance.stopStreaming()
} else if let response = response {
print(response)
}
})
self.audioData = NSMutableData()
}
}
}
}
and in viewDidLoad:, I'm setting AudioKit up with:
AKSettings.sampleRate = 16_000
AKSettings.bufferLength = .shortest
However, Google complains with:
ERROR: Audio data is being streamed too fast. Please stream audio data approximately at real time.
I've tried changing multiple parameters such as the chunk size to no avail.
I found the solution here.
Final code for my Tap is:
open class GoogleSpeechToTextStreamingTap {
internal var converter: AVAudioConverter!
#objc public init(_ input: AKNode?, sampleRate: Double = 16000.0) {
let format = AVAudioFormat(commonFormat: AVAudioCommonFormat.pcmFormatInt16, sampleRate: sampleRate, channels: 1, interleaved: false)!
self.converter = AVAudioConverter(from: AudioKit.format, to: format)
self.converter?.sampleRateConverterAlgorithm = AVSampleRateConverterAlgorithm_Normal
self.converter?.sampleRateConverterQuality = .max
let sampleRateRatio = AKSettings.sampleRate / sampleRate
let inputBufferSize = 4410 // 100ms of 44.1K = 4410 samples.
input?.avAudioNode.installTap(onBus: 0, bufferSize: AVAudioFrameCount(inputBufferSize), format: nil) { buffer, time in
let capacity = Int(Double(buffer.frameCapacity) / sampleRateRatio)
let bufferPCM16 = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(capacity))!
var error: NSError? = nil
self.converter?.convert(to: bufferPCM16, error: &error) { inNumPackets, outStatus in
outStatus.pointee = AVAudioConverterInputStatus.haveData
return buffer
}
let channel = UnsafeBufferPointer(start: bufferPCM16.int16ChannelData!, count: 1)
let data = Data(bytes: channel[0], count: capacity * 2)
SpeechRecognitionService
.sharedInstance
.streamAudioData(data,
completion: { response, error in
if let error = error {
print("ERROR: \(error.localizedDescription)")
SpeechRecognitionService.sharedInstance.stopStreaming()
} else if let response = response {
print(response)
}
})
}
}
You can likely record using AKNodeRecorder, and pass along the buffer from the resulting AKAudioFile to the API. If you wanted more real-time, you could try installing a tap on the avAudioNode property of the AKNode you want to record and pass the buffers to the API continuously.
However, I'm curious why you see the need for pre-processing - I'm sure the Google API is plenty optimized for recordings produced by the sample code you noted.
I've had a lot of success / fun with the iOS Speech API. Not sure if there's a reason you want to go with the Google API, but I'd consider checking it out and seeing if it might better serve your needs if you haven't already.
Hope this helps!
I'm trying to sign data and verify the signature using Elliptic Curve algorithm on iOS. Creating the keys works well enough, but attempting to sign the data returns error -1 - which is very generic.
The keys are created as follows:
publicKeyRef = NULL;
privateKeyRef = NULL;
NSDictionary * privateKeyAttr = #{(id)kSecAttrIsPermanent : #1,
(id)kSecAttrApplicationTag : privateTag};
NSDictionary * publicKeyAttr = #{(id)kSecAttrIsPermanent : #1,
(id)kSecAttrApplicationTag : privateTag};
NSDictionary * keyPairAttr = #{(id)kSecAttrKeySizeInBits : #(keySize),
(id)kSecAttrKeyType : (id)kSecAttrKeyTypeEC,
(id)kSecPrivateKeyAttrs : privateKeyAttr,
(id)kSecPublicKeyAttrs : publicKeyAttr};
OSStatus status = SecKeyGeneratePair((CFDictionaryRef)keyPairAttr, &publicKeyRef, &privateKeyRef);
This returns status 0, so far so good. The actual signing happens like this:
- (NSData *) signData:(NSData *)dataToSign withPrivateKey:(SecKeyRef)privateKey {
NSData * digestToSign = [self sha1DigestForData:dataToSign];
size_t signedHashBytesSize = SecKeyGetBlockSize(privateKey);
uint8_t * signedHashBytes = malloc( signedHashBytesSize * sizeof(uint8_t) );
memset((void *)signedHashBytes, 0x0, signedHashBytesSize);
OSStatus signErr = SecKeyRawSign(privateKey,
kSecPaddingPKCS1,
digestToSign.bytes,
digestToSign.length,
(uint8_t *)signedHashBytes,
&signedHashBytesSize);
NSLog(#"Status: %d", signErr);
NSData * signedHash = [NSData dataWithBytes:(const void *)signedHashBytes length:(NSUInteger)signedHashBytesSize];
if (signedHashBytes) free(signedHashBytes);
return (signErr == noErr) ? signedHash : nil;
}
- (NSData *)sha1DigestForData:(NSData *)data {
NSMutableData *result = [[NSMutableData alloc] initWithLength:CC_SHA1_DIGEST_LENGTH];
CC_SHA1(data.bytes, (CC_LONG) data.length, result.mutableBytes);
return result;
}
The call to SecKeyRawSign() returns -1.
This is adapted from https://forums.developer.apple.com/message/95740#95740
What is the correct way to use an EC key for signing data? There is a working solution for RSA keys here: Signing and Verifying on iOS using RSA but I was unable to adapt it to EC keys.
Seems like part of the trouble is with the correct syntax when creating the pointers and calculating the size of data for calls to SecKeyRawSign. A working example in Swift 3 looks like this:
Generate keys, stored in the Secure Enclave (and temporarily in instance variables):
func generateKeyPair() -> Bool {
if let access = SecAccessControlCreateWithFlags(nil,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
[.userPresence, .privateKeyUsage],
nil) {
let privateKeyAttr = [kSecAttrIsPermanent : 1,
kSecAttrApplicationTag : privateTag,
kSecAttrAccessControl as String: access
] as NSDictionary
let publicKeyAttr = [kSecAttrIsPermanent : 0,
kSecAttrApplicationTag : publicTag
] as NSDictionary
let keyPairAttr = [kSecAttrKeySizeInBits : 256,
kSecAttrKeyType : kSecAttrKeyTypeEC,
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecPrivateKeyAttrs : privateKeyAttr,
kSecPublicKeyAttrs : publicKeyAttr] as NSDictionary
let err = SecKeyGeneratePair(keyPairAttr, &publicKey, &privateKey)
return err == noErr
}
Sign data:
func signData(plainText: Data) -> NSData? {
guard privateKey != nil else {
print("Private key unavailable")
return nil
}
let digestToSign = self.sha1DigestForData(data: plainText as NSData) as Data
let signature = UnsafeMutablePointer<UInt8>.allocate(capacity: 128)
var signatureLength = 128
let err = SecKeyRawSign(privateKey!,
.PKCS1SHA1,
[UInt8](digestToSign),
Int(CC_SHA1_DIGEST_LENGTH),
signature,
&signatureLength)
print("Signature status: \(err)")
let sigData = NSData(bytes: signature, length: Int(signatureLength))
return sigData
}
func sha1DigestForData(data: NSData) -> NSData {
let len = Int(CC_SHA1_DIGEST_LENGTH)
let digest = UnsafeMutablePointer<UInt8>.allocate(capacity: len)
CC_SHA1(data.bytes, CC_LONG(data.length), digest)
return NSData(bytesNoCopy: UnsafeMutableRawPointer(digest), length: len)
}
Verify signature:
func verifySignature(plainText: Data, signature: NSData) -> Bool {
guard publicKey != nil else {
print("Public key unavailable")
return false
}
let digestToSign = self.sha1DigestForData(data: plainText as NSData) as Data
let signedHashBytesSize = signature.length
let err = SecKeyRawVerify(publicKey!,
.PKCS1SHA1,
[UInt8](digestToSign),
Int(CC_SHA1_DIGEST_LENGTH),
[UInt8](signature as Data),
signedHashBytesSize)
print("Verification status: \(err)")
return err == noErr
}
If you need to export the public key so that it can be used by another application or device, this can be done like this:
let parameters = [
kSecClass as String: kSecClassKey,
kSecAttrKeyType as String: kSecAttrKeyTypeEC,
kSecAttrLabel as String: "Public Key",
kSecAttrIsPermanent as String: false,
kSecValueRef as String: publicKey,
kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
kSecReturnData as String: true
] as CFDictionary
var data:AnyObject?
let status = SecItemAdd(parameters, &data)
print("Public key added \(status)")
if let keyData = data as? NSData {
print("This is the key, send it where it needs to go:\n\(keyData)")
}
ECDSA, unlike RSA, does not need hashed data prior to signing.
Apple released improved API in iOS 10 to address the problems of working with and calculating the size of raw data and returned generic error codes like -1. The newer ones, SecKeyCreateSignature in place of SecKeyRawSign, return data and error objects and replaced EC legacy constants for clarity. Here's an updated example:
- (NSData *) signData:(NSData *)dataToSign withPrivateKey:(SecKeyRef)privateKey {
NSData *signedData = nil;
if (dataToSign && privateKey && SecKeyCreateSignature != NULL) //Also check for iOS 10 +
{
CFErrorRef error = NULL;
CFDataRef signatureData = SecKeyCreateSignature(privateKey, kSecKeyAlgorithmECDSASignatureMessageX962SHA512, (__bridge CFDataRef)dataToSign, &error);
if (signatureData)
{
if (error)
{
CFShow(error); // <-- here you get way more info than "-1"
CFRelease(signatureData);
}
else
{
signedData = (__bridge NSData *)CFAutorelease(signatureData);
}
}
if (error)
{
CFRelease(error);
}
}
return signedData;
}
iOS is quite fussy about the parameters for EC in regards to the older functions. Passing in the "wrong key" can give you either -1 or -50, especially as the engineers only focused EC support on newer APIs that use the secure enclave. Here's an updated example for key generation that generates compatible keys:
if (SecKeyCreateRandomKey != NULL && !(TARGET_IPHONE_SIMULATOR)) //iOS 10 + check, real device
{
CFErrorRef error = NULL;
SecAccessControlRef accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, kSecAccessControlPrivateKeyUsage, &error);
if (error)
{
CFShow(error); // <- error instead of OSStatus
CFRelease(error);
error = NULL;
}
if (accessControl)
{
static const uint8_t identifier[] = "com.company.yourKey";
CFDataRef privateTag = CFDataCreate(kCFAllocatorDefault, identifier, sizeof(identifier));
if (privateTag)
{
const void* accessKeys[] = { kSecAttrIsPermanent, kSecAttrApplicationTag, kSecAttrAccessControl };
const void* accessValues[] = { kCFBooleanTrue, privateTag, accessControl };
CFDictionaryRef accessDictionary = CFDictionaryCreate(kCFAllocatorDefault, accessKeys, accessValues, 3, NULL, NULL);
if (accessDictionary)
{
CFMutableDictionaryRef parameters = CFDictionaryCreateMutable(kCFAllocatorDefault, 7, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (parameters)
{
SInt32 keySize = 256;
CFNumberRef keySizeNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &keySize);
if (keySizeNumber)
{
CFDictionaryAddValue(parameters, kSecAttrKeySizeInBits, keySizeNumber);
CFRelease(keySizeNumber);
}
CFDictionaryAddValue(parameters, kSecAttrKeyType, kSecAttrKeyTypeECSECPrimeRandom);
CFDictionaryAddValue(parameters, kSecAttrTokenID, kSecAttrTokenIDSecureEnclave);
CFDictionaryAddValue(parameters, kSecPrivateKeyAttrs, accessDictionary);
SecKeyRef privateKey = SecKeyCreateRandomKey(parameters, &error); // <- pass in an error object
if (privateKey)
{
SecKeyRef publicKey = SecKeyCopyPublicKey(privateKey);
if (publicKey)
{
//...
CFRelease(publicKey);
}
//...
CFRelease(privateKey);
}
if (error)
{
CFRelease(error);
}
CFRelease(parameters);
}
CFRelease(accessDictionary);
}
CFRelease(privateTag);
}
CFRelease(accessControl);
}
}
i need to encrypt and decrypt my password by using PBEWithMD5AndDES JAVA encryption method PBEWithMD5AndDES , JAVA PBEWithMD5AndDES
, i need to do the encryption and decryption in swift similar to below code
#implementation CryptoHelper
#pragma mark -
#pragma mark Init Methods
- (id)init
{
if(self = [super init])
{
}
return self;
}
#pragma mark -
#pragma mark String Specific Methods
/**
* Encrypts a string for social blast service.
*
* #param plainString The string to encrypt;
*
* #return NSString The encrypted string.
*/
- (NSString *)encryptString: (NSString *) plainString{
// Convert string to data and encrypt
NSData *data = [self encryptPBEWithMD5AndDESData:[plainString dataUsingEncoding:NSUTF8StringEncoding] password:#"1111"];
// Get encrypted string from data
return [data base64EncodingWithLineLength:1024];
}
/**
* Descrypts a string from social blast service.
*
* #param plainString The string to decrypt;
*
* #return NSString The decrypted string.
*/
- (NSString *)decryptString: (NSString *) encryptedString{
// decrypt the data
NSData * data = [self decryptPBEWithMD5AndDESData:[NSData dataWithBase64EncodedString:encryptedString] password:#"1111"];
// extract and return string
return [NSString stringWithUTF8String:[data bytes]];
}
#pragma mark -
#pragma mark Crypto Methods
- (NSData *)encryptPBEWithMD5AndDESData:(NSData *)inData password:(NSString *)password {
return [self encodePBEWithMD5AndDESData:inData password:password direction:1];
}
- (NSData *)decryptPBEWithMD5AndDESData:(NSData *)inData password:(NSString *)password {
return [self encodePBEWithMD5AndDESData:inData password:password direction:0];
}
- (NSData *)encodePBEWithMD5AndDESData:(NSData *)inData password:(NSString *)password direction:(int)direction
{
NSLog(#"helper data = %#", inData);
static const char gSalt[] =
{
(unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA,
(unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA,
(unsigned char)0x00
};
unsigned char *salt = (unsigned char *)gSalt;
int saltLen = strlen(gSalt);
int iterations = 15;
EVP_CIPHER_CTX cipherCtx;
unsigned char *mResults; // allocated storage of results
int mResultsLen = 0;
const char *cPassword = [password UTF8String];
unsigned char *mData = (unsigned char *)[inData bytes];
int mDataLen = [inData length];
SSLeay_add_all_algorithms();
X509_ALGOR *algorithm = PKCS5_pbe_set(NID_pbeWithMD5AndDES_CBC,
iterations, salt, saltLen);
memset(&cipherCtx, 0, sizeof(cipherCtx));
if (algorithm != NULL)
{
EVP_CIPHER_CTX_init(&(cipherCtx));
if (EVP_PBE_CipherInit(algorithm->algorithm, cPassword, strlen(cPassword),
algorithm->parameter, &(cipherCtx), direction))
{
EVP_CIPHER_CTX_set_padding(&cipherCtx, 1);
int blockSize = EVP_CIPHER_CTX_block_size(&cipherCtx);
int allocLen = mDataLen + blockSize + 1; // plus 1 for null terminator on decrypt
mResults = (unsigned char *)OPENSSL_malloc(allocLen);
unsigned char *in_bytes = mData;
int inLen = mDataLen;
unsigned char *out_bytes = mResults;
int outLen = 0;
int outLenPart1 = 0;
if (EVP_CipherUpdate(&(cipherCtx), out_bytes, &outLenPart1, in_bytes, inLen))
{
out_bytes += outLenPart1;
int outLenPart2 = 0;
if (EVP_CipherFinal(&(cipherCtx), out_bytes, &outLenPart2))
{
outLen += outLenPart1 + outLenPart2;
mResults[outLen] = 0;
mResultsLen = outLen;
}
} else {
unsigned long err = ERR_get_error();
ERR_load_crypto_strings();
ERR_load_ERR_strings();
char errbuff[256];
errbuff[0] = 0;
ERR_error_string_n(err, errbuff, sizeof(errbuff));
NSLog(#"OpenSLL ERROR:\n\tlib:%s\n\tfunction:%s\n\treason:%s\n",
ERR_lib_error_string(err),
ERR_func_error_string(err),
ERR_reason_error_string(err));
ERR_free_strings();
}
NSData *encryptedData = [NSData dataWithBytes:mResults length:mResultsLen]; //(NSData *)encr_buf;
//NSLog(#"encryption result: %#\n", [encryptedData base64EncodingWithLineLength:1024]);
EVP_cleanup();
return encryptedData;
}
}
EVP_cleanup();
return nil;
}
#end
PBEWithMD5AndDES means encrypting some data using a key derivation function such as PBKDF2 (Password Based Key Derivation Function) using the MD5 (Message Digest) hash function and an encryption method of DES (Data Encryption Standard).
This can easily be done in iOS with Swift using Common Crypto.
But it all that is rather old and today's Best Practice would use PBBKDF2 with SHA in place of MD5 and AES in place of DES. MD5 and DES are extremely week and should not be used in new work.
If you do not need to interoperate with an existing encrypted files you can use RNCryptor. Also see Using RNCryptor Swift
If you need to interoperate you can piece together a matching scheme using Common Crypto. If you need to go this way add more details to the question including sample existing code and hex dumps of all inputs and outputs along with al input parameters and your Swift attempt code.
Example from deprecated documentation section:
Password Based Key Derivation 2 (Swift 3+)
Password Based Key Derivation can be used both for deriving an encryption key from password text and saving a password for authentication purposes.
There are several hash algorithms that can be used including SHA1, SHA256, SHA512 which are provided by this example code.
The rounds parameter is used to make the calculation slow so that an attacker will have to spend substantial time on each attempt. Typical delay values fall in the 100ms to 500ms, shorter values can be used if there is unacceptable performance.
This example requires Common Crypto
It is necessary to have a bridging header to the project:
#import <CommonCrypto/CommonCrypto.h>
Add the Security.framework to the project.
Parameters:
password password String
salt salt Data
keyByteCount number of key bytes to generate
rounds Iteration rounds
returns Derived key
func pbkdf2SHA1(password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
return pbkdf2(hash:CCPBKDFAlgorithm(kCCPRFHmacAlgSHA1), password:password, salt:salt, keyByteCount:keyByteCount, rounds:rounds)
}
func pbkdf2SHA256(password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
return pbkdf2(hash:CCPBKDFAlgorithm(kCCPRFHmacAlgSHA256), password:password, salt:salt, keyByteCount:keyByteCount, rounds:rounds)
}
func pbkdf2SHA512(password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
return pbkdf2(hash:CCPBKDFAlgorithm(kCCPRFHmacAlgSHA512), password:password, salt:salt, keyByteCount:keyByteCount, rounds:rounds)
}
func pbkdf2(hash :CCPBKDFAlgorithm, password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
let passwordData = password.data(using:String.Encoding.utf8)!
var derivedKeyData = Data(repeating:0, count:keyByteCount)
let derivationStatus = derivedKeyData.withUnsafeMutableBytes {derivedKeyBytes in
salt.withUnsafeBytes { saltBytes in
CCKeyDerivationPBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
password, passwordData.count,
saltBytes, salt.count,
hash,
UInt32(rounds),
derivedKeyBytes, derivedKeyData.count)
}
}
if (derivationStatus != 0) {
print("Error: \(derivationStatus)")
return nil;
}
return derivedKeyData
}
Example usage:
let password = "password"
//let salt = "saltData".data(using: String.Encoding.utf8)!
let salt = Data(bytes: [0x73, 0x61, 0x6c, 0x74, 0x44, 0x61, 0x74, 0x61])
let keyByteCount = 16
let rounds = 100000
let derivedKey = pbkdf2SHA1(password:password, salt:salt, keyByteCount:keyByteCount, rounds:rounds)
print("derivedKey (SHA1): \(derivedKey! as NSData)")
Example Output:
derivedKey (SHA1): <6b9d4fa3 0385d128 f6d196ee 3f1d6dbf>
AES encryption in CBC mode with a random IV (Swift 3+)
The iv is prefixed to the encrypted data
aesCBC128Encrypt will create a random IV and prefixed to the encrypted code.
aesCBC128Decrypt will use the prefixed IV during decryption.
Inputs are the data and key are Data objects. If an encoded form such as Base64 if required convert to and/or from in the calling method.
The key should be exactly 128-bits (16-bytes), 192-bits (24-bytes) or 256-bits (32-bytes) in length. If another key size is used an error will be thrown.
PKCS#7 padding is set by default.
This example requires Common Crypto
It is necessary to have a bridging header to the project:
#import <CommonCrypto/CommonCrypto.h>
Add the Security.framework to the project.
This is example, not production code.
enum AESError: Error {
case KeyError((String, Int))
case IVError((String, Int))
case CryptorError((String, Int))
}
// The iv is prefixed to the encrypted data
func aesCBCEncrypt(data:Data, keyData:Data) throws -> Data {
let keyLength = keyData.count
let validKeyLengths = [kCCKeySizeAES128, kCCKeySizeAES192, kCCKeySizeAES256]
if (validKeyLengths.contains(keyLength) == false) {
throw AESError.KeyError(("Invalid key length", keyLength))
}
let ivSize = kCCBlockSizeAES128;
let cryptLength = size_t(ivSize + data.count + kCCBlockSizeAES128)
var cryptData = Data(count:cryptLength)
let status = cryptData.withUnsafeMutableBytes {ivBytes in
SecRandomCopyBytes(kSecRandomDefault, kCCBlockSizeAES128, ivBytes)
}
if (status != 0) {
throw AESError.IVError(("IV generation failed", Int(status)))
}
var numBytesEncrypted :size_t = 0
let options = CCOptions(kCCOptionPKCS7Padding)
let cryptStatus = cryptData.withUnsafeMutableBytes {cryptBytes in
data.withUnsafeBytes {dataBytes in
keyData.withUnsafeBytes {keyBytes in
CCCrypt(CCOperation(kCCEncrypt),
CCAlgorithm(kCCAlgorithmAES),
options,
keyBytes, keyLength,
cryptBytes,
dataBytes, data.count,
cryptBytes+kCCBlockSizeAES128, cryptLength,
&numBytesEncrypted)
}
}
}
if UInt32(cryptStatus) == UInt32(kCCSuccess) {
cryptData.count = numBytesEncrypted + ivSize
}
else {
throw AESError.CryptorError(("Encryption failed", Int(cryptStatus)))
}
return cryptData;
}
// The iv is prefixed to the encrypted data
func aesCBCDecrypt(data:Data, keyData:Data) throws -> Data? {
let keyLength = keyData.count
let validKeyLengths = [kCCKeySizeAES128, kCCKeySizeAES192, kCCKeySizeAES256]
if (validKeyLengths.contains(keyLength) == false) {
throw AESError.KeyError(("Invalid key length", keyLength))
}
let ivSize = kCCBlockSizeAES128;
let clearLength = size_t(data.count - ivSize)
var clearData = Data(count:clearLength)
var numBytesDecrypted :size_t = 0
let options = CCOptions(kCCOptionPKCS7Padding)
let cryptStatus = clearData.withUnsafeMutableBytes {cryptBytes in
data.withUnsafeBytes {dataBytes in
keyData.withUnsafeBytes {keyBytes in
CCCrypt(CCOperation(kCCDecrypt),
CCAlgorithm(kCCAlgorithmAES128),
options,
keyBytes, keyLength,
dataBytes,
dataBytes+kCCBlockSizeAES128, clearLength,
cryptBytes, clearLength,
&numBytesDecrypted)
}
}
}
if UInt32(cryptStatus) == UInt32(kCCSuccess) {
clearData.count = numBytesDecrypted
}
else {
throw AESError.CryptorError(("Decryption failed", Int(cryptStatus)))
}
return clearData;
}
Example usage:
let clearData = "clearData0123456".data(using:String.Encoding.utf8)!
let keyData = "keyData890123456".data(using:String.Encoding.utf8)!
print("clearData: \(clearData as NSData)")
print("keyData: \(keyData as NSData)")
var cryptData :Data?
do {
cryptData = try aesCBCEncrypt(data:clearData, keyData:keyData)
print("cryptData: \(cryptData! as NSData)")
}
catch (let status) {
print("Error aesCBCEncrypt: \(status)")
}
let decryptData :Data?
do {
let decryptData = try aesCBCDecrypt(data:cryptData!, keyData:keyData)
print("decryptData: \(decryptData! as NSData)")
}
catch (let status) {
print("Error aesCBCDecrypt: \(status)")
}
Example Output:
clearData: <636c6561 72446174 61303132 33343536>
keyData: <6b657944 61746138 39303132 33343536>
cryptData: <92c57393 f454d959 5a4d158f 6e1cd3e7 77986ee9 b2970f49 2bafcf1a 8ee9d51a bde49c31 d7780256 71837a61 60fa4be0>
decryptData: <636c6561 72446174 61303132 33343536>
Notes:
One typical problem with CBC mode example code is that it leaves the creation and sharing of the random IV to the user. This example includes generation of the IV, prefixed the encrypted data and uses the prefixed IV during decryption. This frees the casual user from the details that are necessary for CBC mode.
For security the encrypted data also should have authentication, this example code does not provide that in order to be small and allow better interoperability for other platforms.
Also missing is key derivation of the key from a password, it is suggested that PBKDF2 be used is text passwords are used as keying material.
For robust production ready multi-platform encryption code see RNCryptor.
IOS code is
#import "DESCodec.h"
#import <CommonCrypto/CommonCryptor.h>
#implementation DESCodec
{
NSString *key;
}
-(id) init{
self=[super init];
if(self){
key=#"12345678";
}
return self;
}
-(NSString *) decode:(NSString *)encoded{
NSData *inputData = [[NSData alloc] initWithBase64EncodedString:encoded options:0];
NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
size_t outLength;
NSMutableData *outputData = [NSMutableData dataWithLength:(inputData.length +
kCCBlockSizeDES)];
CCCryptorStatus
result = CCCrypt(kCCDecrypt, // operation
kCCAlgorithmDES, // Algorithm
kCCOptionPKCS7Padding , // options
keyData.bytes, // key
keyData.length, // keylength
nil,// iv
inputData.bytes, // dataIn
inputData.length, // dataInLength,
outputData.mutableBytes, // dataOut
outputData.length, // dataOutAvailable
&outLength); // dataOutMoved
if (result != kCCSuccess) {
return nil;
}
[outputData setLength:outLength];
return [[NSString alloc] initWithData:outputData `encoding:NSUTF8StringEncoding];`
}
-(NSString *) encode:(NSString *)decoded{
NSData *inputData = [decoded dataUsingEncoding:NSUTF8StringEncoding];
NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
size_t outLength;
NSMutableData *outputData = [NSMutableData dataWithLength:(inputData.length + kCCBlockSizeDES)];
CCCryptorStatus result = CCCrypt(kCCEncrypt, // operation
kCCAlgorithmDES, // Algorithm
kCCOptionPKCS7Padding, // options
keyData.bytes, // key
keyData.length, // keylength
nil,// iv
inputData.bytes, // dataIn
inputData.length, // dataInLength,
outputData.mutableBytes, // dataOut
outputData.length, // dataOutAvailable
&outLength); // dataOutMoved
if (result != kCCSuccess) {
return nil;
}
[outputData setLength:outLength];
NSString *r = [outputData base64EncodedStringWithOptions:0];
return r;
}
#end
DESCodec *codec=[[DESCodec alloc] init];
NSString *encoded=[codec encode:#"12345678"];
NSString decoded=[codec decode:encoded];
NSLog(#" %# %#",encoded,decoded);
value is ltACiHjVjImOJQ1fTHZkSw== and 12345678
but in java encypted text is "ltACiHjVjIn+uVm31GQvyw=="
I not good in Objective C and I can't able to trigger out the problem.
can anybody please help me. Thanks and regards
Java code is
public class DESCodec {
/**
* Secret key that shall be used for encryption and decryption.
*/
private String strSecretKey = "12345678";
private static final String UNICODE_FORMAT = "UTF-8";
private static final String DES_ENCRYPTION_SCHEME = "DES";
private static final String TAG = "DESCodec";
private Cipher cipher;
private SecretKey key;
public DESCodec() {
try {
this.strSecretKey = strSecretKey;
String myEncryptionScheme = DES_ENCRYPTION_SCHEME;
byte[] keyAsBytes = strSecretKey.getBytes(UNICODE_FORMAT);
DESKeySpec myKeySpec = new DESKeySpec(keyAsBytes);
SecretKeyFactory mySecretKeyFactory = SecretKeyFactory.getInstance(myEncryptionScheme);
cipher = Cipher.getInstance(myEncryptionScheme);
key = mySecretKeyFactory.generateSecret(myKeySpec);
} catch (Exception e) {
e.printStackTrace();
}
}
public String desEncrypt(String message) {
String encryptedString = null;
try {
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] plainText = message.getBytes(UNICODE_FORMAT);
byte[] encryptedText = cipher.doFinal(plainText);
encryptedString = Base64.encodeToString(encryptedText, Base64.DEFAULT);
} catch (Exception e) {
e.printStackTrace();
}
return encryptedString;
}
public String desDecrypt(String message) {
String decryptedText = null;
try {
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] encryptedText = Base64.decode(message, Base64.DEFAULT);
byte[] plainText = cipher.doFinal(encryptedText);
decryptedText = bytes2String(plainText);
} catch (Exception e) {
e.printStackTrace();
}
return decryptedText;
}
private String bytes2String(byte[] bytes) {
try {
return new String(bytes, UNICODE_FORMAT);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
}
It's obviously only a problem with the mode of operation, because the first block matches. In Java you're using ECB mode, because "DES" defaults to "DES/ECB/PKCS5Padding". I think that CCCryptor defaults to CBC.
Don't ever use ECB mode. It's not semantically secure. You need to use at least CBC mode with a random IV. The IV doesn't have to be secret, so you can prepend it to the ciphertext. Please look at RNCryptor that has additional security features like authentication of ciphertext. It also has a Java implementation.
Don't use DES anymore. It's not secure anymore. You should use AES. Triple DES is also not that bad.
I hava the same problem when i develop an iOS app.And the android client is used by many people, so i couldn't change the algorithm to AES or others.As Artjom B. said in Java 'DES' defaults to 'DES/ECB/PKCS5Padding', in the source you can find that
Cipher c1 = Cipher.getInstance("DES/ECB/PKCS5Padding");
but unfortunately in iOS you just find that
enum {
kCCOptionPKCS7Padding = 0x0001,
kCCOptionECBMode = 0x0002
}
But finally i find a solution like this
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
kCCAlgorithmDES,
kCCOptionPKCS7Padding | kCCOptionECBMode,
[key UTF8String],
kCCKeySizeDES,
nil,
[cipherData bytes],
[cipherData length],
buffer,
1024,
&numBytesDecrypted);
The importance is kCCOptionPKCS7Padding | kCCOptionECBMode, you can try this method.
This question was apparently similar but had no answers of any kind: Programmatically create a x509 certificate for iPhone without using OpenSSL
In our application (server, client), we are implementing client authentication (SSL based on X509Certificate). We already have a way to generate a keypair, create a PKCS10 Certificate Signing Request, have this signed by the self-signed CA and create a X509Certificate, send this back. However, to use this certificate in SSL requests, the private key and the X509Certificate have to be exported to a PKCS12 (P12) keystore.
Does anyone know anything about how to do this, or even if it's possible? The client has to generate the P12 file (we don't want to give out the private key), and the client is running iOS, and is a mobile device. The solution worked for Android using BouncyCastle (SpongyCastle), but we found nothing for iOS.
EDIT: In Java, this export is done by the following:
ByteArrayOutputStream bos = new ByteArrayOutputStream();
KeyStore ks = KeyStore.getInstance("PKCS12", BouncyCastleProvider.PROVIDER_NAME);
ks.load(null);
ks.setKeyEntry("key-alias", (Key) key, password.toCharArray(),
new java.security.cert.Certificate[] { x509Certificate });
ks.store(bos, password.toCharArray());
bos.close();
return bos.toByteArray();
If you use openssl, you don't have to copy the full source code into your project, it is enough to add the libs and headers, so the openssl library can be used without any size problem.
You can generate a key and a cert like that with openssl:
EVP_PKEY * pkey;
pkey = EVP_PKEY_new();
RSA * rsa;
rsa = RSA_generate_key(
2048, /* number of bits for the key - 2048 is a sensible value */
RSA_F4, /* exponent - RSA_F4 is defined as 0x10001L */
NULL, /* callback - can be NULL if we aren't displaying progress */
NULL /* callback argument - not needed in this case */
);
EVP_PKEY_assign_RSA(pkey, rsa);
X509 * x509;
x509 = X509_new();
ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
X509_gmtime_adj(X509_get_notBefore(x509), 0);
X509_gmtime_adj(X509_get_notAfter(x509), 31536000L);
X509_set_pubkey(x509, pkey);
X509_NAME * name;
name = X509_get_subject_name(x509);
X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC,
(unsigned char *)"CA", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC,
(unsigned char *)"MyCompany Inc.", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
(unsigned char *)"localhost", -1, -1, 0);
X509_set_issuer_name(x509, name);
//X509_sign(x509, pkey, EVP_sha1());
const EVP_CIPHER *aConst = EVP_des_ede3_cbc();
And you can write this into pem format with these functions:
PEM_write_PrivateKey(f, pkey, NULL, NULL, 0, NULL, NULL);
PEM_write_X509(
f, /* write the certificate to the file we've opened */
x509 /* our certificate */
);
After that it is possible to write these files into a p12 file, source from here:
https://github.com/luvit/openssl/blob/master/openssl/demos/pkcs12/pkwrite.c
/* pkwrite.c */
#include <stdio.h>
#include <stdlib.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/pkcs12.h>
/* Simple PKCS#12 file creator */
int main(int argc, char **argv)
{
FILE *fp;
EVP_PKEY *pkey;
X509 *cert;
PKCS12 *p12;
if (argc != 5) {
fprintf(stderr, "Usage: pkwrite infile password name p12file\n");
exit(1);
}
SSLeay_add_all_algorithms();
ERR_load_crypto_strings();
if (!(fp = fopen(argv[1], "r"))) {
fprintf(stderr, "Error opening file %s\n", argv[1]);
exit(1);
}
cert = PEM_read_X509(fp, NULL, NULL, NULL);
rewind(fp);
pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL);
fclose(fp);
p12 = PKCS12_create(argv[2], argv[3], pkey, cert, NULL, 0,0,0,0,0);
if(!p12) {
fprintf(stderr, "Error creating PKCS#12 structure\n");
ERR_print_errors_fp(stderr);
exit(1);
}
if (!(fp = fopen(argv[4], "wb"))) {
fprintf(stderr, "Error opening file %s\n", argv[1]);
ERR_print_errors_fp(stderr);
exit(1);
}
i2d_PKCS12_fp(fp, p12);
PKCS12_free(p12);
fclose(fp);
return 0;
}
Thank you all very much for this nice solution!
I translated your code to Swift 3 and built the following function to create a P12 keystore using a signed X509 certificate and a RSA private key, both in PEM format:
func createP12(pemCertificate: String, pemPrivateKey: String) {
// Read certificate
let buffer = BIO_new(BIO_s_mem())
pemCertificate.data(using: .utf8)!.withUnsafeBytes({ (bytes: UnsafePointer<Int8>) -> Void in
BIO_puts(buffer, bytes)
})
let certificate = PEM_read_bio_X509(buffer, nil, nil, nil)
X509_print_fp(stdout, certificate)
// Read private key
let privateKeyBuffer = BIO_new(BIO_s_mem())
pemPrivateKey.data(using: .utf8)!.withUnsafeBytes({ (bytes: UnsafePointer<Int8>) -> Void in
BIO_puts(privateKeyBuffer, bytes)
})
let privateKey = PEM_read_bio_PrivateKey(privateKeyBuffer, nil, nil, nil)
PEM_write_PrivateKey(stdout, privateKey, nil, nil, 0, nil, nil)
// Check if private key matches certificate
guard X509_check_private_key(certificate, privateKey) == 1 else {
NSLog("Private key does not match certificate")
return
}
// Set OpenSSL parameters
OPENSSL_add_all_algorithms_noconf()
ERR_load_crypto_strings()
// Create P12 keystore
let passPhrase = UnsafeMutablePointer(mutating: ("" as NSString).utf8String)
let name = UnsafeMutablePointer(mutating: ("SSL Certificate" as NSString).utf8String)
guard let p12 = PKCS12_create(passPhrase, name, privateKey, certificate, nil, 0, 0, 0, 0, 0) else {
NSLog("Cannot create P12 keystore:")
ERR_print_errors_fp(stderr)
return
}
// Save P12 keystore
let fileManager = FileManager.default
let tempDirectory = NSTemporaryDirectory() as NSString
let path = tempDirectory.appendingPathComponent("ssl.p12")
fileManager.createFile(atPath: path, contents: nil, attributes: nil)
guard let fileHandle = FileHandle(forWritingAtPath: path) else {
NSLog("Cannot open file handle: \(path)")
return
}
let p12File = fdopen(fileHandle.fileDescriptor, "w")
i2d_PKCS12_fp(p12File, p12)
fclose(p12File)
fileHandle.closeFile()
}
EDIT:
OpenSSL can be used in iOS with the OpenSSL-for-iPhone project:
Check out the repository
Build the static libraries with ./build-libssl.sh
Add $(YOUR_PATH)/OpenSSL-for-iPhone/include to header search paths
Add $(YOUR_PATH)/OpenSSL-for-iPhone/lib to library search paths
Add libcrypto.a and libssl.a to linked frameworks and libraries
Add the following headers to the bridging header:
Project-Bridging-Header.h:
#import <openssl/err.h>
#import <openssl/pem.h>
#import <openssl/pkcs12.h>
#import <openssl/x509.h>
My solution is similar to sundance's, I reworked it to get around address sanitizer / heap overflow issues encountered in XCode 9.
func createP12(secCertificate: SecCertificate, secPrivateKeyBase64: String, p12FileName: String, _ p12Password: String = "") throws -> String {
// Read certificate
// Convert sec certificate to DER certificate
let derCertificate = SecCertificateCopyData(secCertificate)
// Create strange pointer to read DER certificate with OpenSSL
// data must be a two-dimensional array containing the pointer to the DER certificate as single element at position [0][0]
let certificatePointer = CFDataGetBytePtr(derCertificate)
let certificateLength = CFDataGetLength(derCertificate)
let certificateData = UnsafeMutablePointer<UnsafePointer<UInt8>?>.allocate(capacity: 1)
certificateData.pointee = certificatePointer
// Read DER certificate
let certificate = d2i_X509(nil, certificateData, certificateLength)
// Print certificate
#if DEBUG
X509_print_fp(stdout, certificate)
#endif
let pemPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\n\(secPrivateKeyBase64)\n-----END RSA PRIVATE KEY-----\n"
let p12Path = try pemPrivateKey.data(using: .utf8)!.withUnsafeBytes({ (bytes: UnsafePointer<Int8>) -> String in
let privateKeyBuffer = BIO_new_mem_buf(bytes, Int32(pemPrivateKey.characters.count))
let privateKey = PEM_read_bio_PrivateKey(privateKeyBuffer, nil, nil, nil)
defer {
BIO_free(privateKeyBuffer)
}
// Print private key
#if DEBUG
PEM_write_PrivateKey(stdout, privateKey, nil, nil, 0, nil, nil)
#endif
// Check if private key matches certificate
guard X509_check_private_key(certificate, privateKey) == 1 else {
throw X509Error.privateKeyDoesNotMatchCertificate
}
// Set OpenSSL parameters
OPENSSL_add_all_algorithms_noconf()
ERR_load_crypto_strings()
// Create P12 keystore
let passPhrase = UnsafeMutablePointer(mutating: (p12Password as NSString).utf8String)
let name = UnsafeMutablePointer(mutating: ("SSL Certificate" as NSString).utf8String)
guard let p12 = PKCS12_create(passPhrase, name, privateKey, certificate, nil, 0, 0, 0, 0, 0) else {
ERR_print_errors_fp(stderr)
throw X509Error.cannotCreateP12Keystore
}
// Save P12 keystore
let fileManager = FileManager.default
let documentsPathURL = URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
let path = documentsPathURL.appendingPathComponent(p12FileName).path
fileManager.createFile(atPath: path, contents: nil, attributes: nil)
guard let fileHandle = FileHandle(forWritingAtPath: path) else {
NSLog("Cannot open file handle: \(path)")
throw X509Error.cannotOpenFileHandles
}
let p12File = fdopen(fileHandle.fileDescriptor, "w")
i2d_PKCS12_fp(p12File, p12)
PKCS12_free(p12)
fclose(p12File)
fileHandle.closeFile()
NSLog("Wrote P12 keystore to: \(path)")
return path
})
return p12Path
}
enum X509Error: Error {
case privateKeyDoesNotMatchCertificate
case cannotCreateP12Keystore
case cannotOpenFileHandles
}
Problem solved! Thank you, guys.
The p12 file now is created correctly.
The code now is:
NSString *certPem = [certificate pemCertificate];
[certPem writeToFile:[self certFilePath] atomically:YES encoding:NSUTF8StringEncoding error:nil];
const char *cert_chars = [certPem cStringUsingEncoding:NSUTF8StringEncoding];
BIO *buffer = BIO_new(BIO_s_mem());
BIO_puts(buffer, cert_chars);
X509 *cert;
cert = PEM_read_bio_X509(buffer, NULL, 0, NULL);
if (cert == NULL) {
NSLog(#"error");
}
X509_print_fp(stdout, cert);
if (!X509_check_private_key(cert, [certificate privateKey])) {
NSLog(#"PK error");
}
PKCS12 *p12;
SSLeay_add_all_algorithms();
ERR_load_crypto_strings();
p12 = PKCS12_create("passPhrase", "iOSMobileCertificate", [certificate privateKey], cert, NULL, 0,0,0,0,0);
if(!p12) {
fprintf(stderr, "Error creating PKCS#12 structure\n");
ERR_print_errors_fp(stderr);
exit(1);
}
[self saveP12File:p12];
saveP12File is:
//create empty file
NSString *p12FilePath = [self p12FilePath];
if (![[NSFileManager defaultManager] createFileAtPath:p12FilePath contents:nil attributes:nil])
{
NSLog(#"Error creating file for P12");
#throw [[NSException alloc] initWithName:#"Fail getP12File" reason:#"Fail Error creating file for P12" userInfo:nil];
}
//get a FILE struct for the P12 file
NSFileHandle *outputFileHandle = [NSFileHandle fileHandleForWritingAtPath:p12FilePath];
FILE *p12File = fdopen([outputFileHandle fileDescriptor], "w");
i2d_PKCS12_fp(p12File, p12);
PKCS12_free(p12);
fclose(p12File);
And p12FilePath is:
NSString *documentsFolder = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
return [documentsFolder stringByAppendingPathComponent:#"CERT.p12"];
Thank you!
My solution is similar to mbonness', but reworked to suppress deprecation warning with Swift 5, and taking an optional rootCA certificate.
static func pkcs12(fromPem pemCertificate: String,
withPrivateKey pemPrivateKey: String,
p12Password: String = "",
certificateAuthorityFileURL: URL? = nil) throws -> NSData {
// Create sec certificates from PEM string
let modifiedCert = pemCertificate
.replacingOccurrences(of: "-----BEGIN CERTIFICATE-----", with: "")
.replacingOccurrences(of: "-----END CERTIFICATE-----", with: "")
.replacingOccurrences(of: "\n", with: "")
.trimmingCharacters(in: .whitespacesAndNewlines)
guard let derCertificate = NSData(base64Encoded: modifiedCert, options: [])
else {
throw X509Error.cannotReadPEMCertificate
}
// Create strange pointer to read DER certificate with OpenSSL
// Data must be a two-dimensional array containing the pointer to the DER certificate
// as single element at position [0][0]
let certificatePointer = CFDataGetBytePtr(derCertificate)
let certificateLength = CFDataGetLength(derCertificate)
let certificateData = UnsafeMutablePointer<UnsafePointer<UInt8>?>.allocate(capacity: 1)
certificateData.pointee = certificatePointer
// Read DER certificate
let certificate = d2i_X509(nil, certificateData, certificateLength)
let p12Path = try pemPrivateKey.data(using: .utf8)!
.withUnsafeBytes { bytes throws -> String in
let privateKeyBuffer = BIO_new_mem_buf(bytes.baseAddress, Int32(pemPrivateKey.count))
let privateKey = PEM_read_bio_PrivateKey(privateKeyBuffer, nil, nil, nil)
defer {
BIO_free(privateKeyBuffer)
}
// Check if private key matches certificate
guard X509_check_private_key(certificate, privateKey) == 1 else {
throw X509Error.privateKeyDoesNotMatchCertificate
}
// Set OpenSSL parameters
OpenSSL_add_all_algorithms()
ERR_load_CRYPTO_strings()
// The CA cert needs to be in a stack of certs
let certsStack = sk_X509_new_null()
if let certificateAuthorityFileURL = certificateAuthorityFileURL {
// Read root certiticate
let rootCAFileHandle = try FileHandle(forReadingFrom: certificateAuthorityFileURL)
let rootCAFile = fdopen(rootCAFileHandle.fileDescriptor, "r")
let rootCA = PEM_read_X509(rootCAFile, nil, nil, nil)
fclose(rootCAFile)
rootCAFileHandle.closeFile()
// Add certificate to the stack
sk_X509_push(certsStack, rootCA)
}
// Create P12 keystore
let passPhrase = UnsafeMutablePointer(mutating: (p12Password as NSString).utf8String)
let name = UnsafeMutablePointer(mutating: ("SSL Certificate" as NSString).utf8String)
guard let p12 = PKCS12_create(passPhrase,
name,
privateKey,
certificate,
certsStack,
0,
0,
0,
PKCS12_DEFAULT_ITER,
0) else {
ERR_print_errors_fp(stderr)
throw X509Error.cannotCreateP12Keystore
}
// Save P12 keystore
let fileManager = FileManager.default
let path = fileManager
.temporaryDirectory
.appendingPathComponent(UUID().uuidString)
.path
fileManager.createFile(atPath: path, contents: nil, attributes: nil)
guard let fileHandle = FileHandle(forWritingAtPath: path) else {
NSLog("Cannot open file handle: \(path)")
throw X509Error.cannotOpenFileHandles
}
let p12File = fdopen(fileHandle.fileDescriptor, "w")
i2d_PKCS12_fp(p12File, p12)
PKCS12_free(p12)
fclose(p12File)
fileHandle.closeFile()
return path
}
// Read P12 Data
guard let p12Data = NSData(contentsOfFile: p12Path) else {
throw X509Error.cannotReadP12Certificate
}
// Remove temporary file
try? FileManager.default.removeItem(atPath: p12Path)
return p12Data
}