import Foundation import OpenSSL import CryptoTokenKit @available(iOS 13, macOS 10.15, *) public class OpenSSLUtils { private static var loaded = false /// Returns any OpenSSL Error as a String public static func getOpenSSLError() -> String { guard let out = BIO_new(BIO_s_mem()) else { return "Unknown" } defer { BIO_free(out) } ERR_print_errors( out ) let str = OpenSSLUtils.bioToString( bio:out ) return str } /// Extracts the contents of a BIO object and returns it as a String /// - Parameter bio: a Pointer to a BIO buffer /// - Returns: A string containing the contents of the BIO buffer static func bioToString( bio : OpaquePointer ) -> String { let len = BIO_ctrl(bio, BIO_CTRL_PENDING, 0, nil) var buffer = [CChar](repeating: 0, count: len+1) BIO_read(bio, &buffer, Int32(len)) // Ensure last value is 0 (null terminated) otherwise we get buffer overflow! buffer[len] = 0 let ret = String(cString:buffer) return ret } static func X509ToPEM( x509: OpaquePointer ) -> String { let out = BIO_new(BIO_s_mem())! defer { BIO_free( out) } PEM_write_bio_X509(out, x509); let str = OpenSSLUtils.bioToString( bio:out ) return str } static func pubKeyToPEM( pubKey: OpaquePointer ) -> String { let out = BIO_new(BIO_s_mem())! defer { BIO_free( out) } PEM_write_bio_PUBKEY(out, pubKey); let str = OpenSSLUtils.bioToString( bio:out ) return str } static func privKeyToPEM( privKey: OpaquePointer ) -> String { let out = BIO_new(BIO_s_mem())! defer { BIO_free( out) } PEM_write_bio_PrivateKey(out, privKey, nil, nil, 0, nil, nil) let str = OpenSSLUtils.bioToString( bio:out ) return str } static func pkcs7DataToPEM( pkcs7: Data ) -> String { let inf = BIO_new(BIO_s_mem())! defer { BIO_free( inf) } let out = BIO_new(BIO_s_mem())! defer { BIO_free( out) } let _ = pkcs7.withUnsafeBytes { (ptr) in BIO_write(inf, ptr.baseAddress?.assumingMemoryBound(to: Int8.self), Int32(pkcs7.count)) } guard let p7 = d2i_PKCS7_bio(inf, nil) else { return "" } defer { PKCS7_free(p7) } PEM_write_bio_PKCS7(out, p7) let str = OpenSSLUtils.bioToString( bio:out ) return str } /// Extracts a X509 certificate in PEM format from a PKCS7 container /// - Parameter pkcs7Der: The PKCS7 container in DER format /// - Returns: The PEM formatted X509 certificate /// - Throws: A OpenSSLError.UnableToGetX509CertificateFromPKCS7 are thrown for any error static func getX509CertificatesFromPKCS7( pkcs7Der : Data ) throws -> [X509Wrapper] { guard let inf = BIO_new(BIO_s_mem()) else { throw OpenSSLError.UnableToGetX509CertificateFromPKCS7("Unable to allocate input buffer") } defer { BIO_free(inf) } let _ = pkcs7Der.withUnsafeBytes { (ptr) in BIO_write(inf, ptr.baseAddress?.assumingMemoryBound(to: Int8.self), Int32(pkcs7Der.count)) } guard let p7 = d2i_PKCS7_bio(inf, nil) else { throw OpenSSLError.UnableToGetX509CertificateFromPKCS7("Unable to read PKCS7 DER data") } defer { PKCS7_free(p7) } var certs : OpaquePointer? = nil let i = OBJ_obj2nid(p7.pointee.type); switch (i) { case NID_pkcs7_signed: if let sign = p7.pointee.d.sign { certs = sign.pointee.cert } break; case NID_pkcs7_signedAndEnveloped: if let signed_and_enveloped = p7.pointee.d.signed_and_enveloped { certs = signed_and_enveloped.pointee.cert } break; default: break; } var ret = [X509Wrapper]() if let certs = certs { let certCount = sk_X509_num(certs) for i in 0 ..< certCount { let x = sk_X509_value(certs, i); if let x509 = X509Wrapper(with:x) { ret.append( x509 ) } } } return ret } /// Checks whether a trust chain can be built up to verify a X509 certificate. A CAFile containing a list of trusted certificates (each in PEM format) /// is used to build the trust chain. /// The trusted certificates in this use case are typically from a Countries master list (see the scripts for more informaton on how to prepare this) /// - Parameter x509Cert: The X509 certificate (in PEM format) to verify /// - Parameter CAFile: The URL path of a file containing the list of certificates used to try to discover and build a trust chain /// - Returns: either the X509 issue signing certificate that was used to sign the passed in X509 certificate or an error static func verifyTrustAndGetIssuerCertificate( x509 : X509Wrapper, CAFile : URL ) -> Result { guard let cert_ctx = X509_STORE_new() else { return .failure(OpenSSLError.UnableToVerifyX509CertificateForSOD("Unable to create certificate store")) } defer { X509_STORE_free(cert_ctx) } X509_STORE_set_verify_cb(cert_ctx) { (ok, ctx) -> Int32 in let cert_error = X509_STORE_CTX_get_error(ctx) if ok == 0 { let errVal = X509_verify_cert_error_string(Int(cert_error)) let val = errVal!.withMemoryRebound(to: CChar.self, capacity: 1000) { (ptr) in return String(cString: ptr) } Log.error("error \(cert_error) at \(X509_STORE_CTX_get_error_depth(ctx)) depth lookup:\(val)" ) } return ok; } guard let lookup = X509_STORE_add_lookup(cert_ctx, X509_LOOKUP_file()) else { return .failure(OpenSSLError.UnableToVerifyX509CertificateForSOD("Unable to add lookup to store")) } // Load masterList.pem file var rc = X509_LOOKUP_ctrl(lookup, X509_L_FILE_LOAD, CAFile.path, Int(X509_FILETYPE_PEM), nil) guard let store = X509_STORE_CTX_new() else { return .failure(OpenSSLError.UnableToVerifyX509CertificateForSOD("Unable to create new X509_STORE_CTX")) } defer { X509_STORE_CTX_free(store) } X509_STORE_set_flags(cert_ctx, 0) rc = X509_STORE_CTX_init(store, cert_ctx, x509.cert, nil) if rc == 0 { return .failure(OpenSSLError.UnableToVerifyX509CertificateForSOD("Unable to initialise X509_STORE_CTX")) } // discover and verify X509 certificte chain let i = X509_verify_cert(store); if i != 1 { let err = X509_STORE_CTX_get_error(store) return .failure(OpenSSLError.UnableToVerifyX509CertificateForSOD("Verification of certificate failed - errorCode \(err)")) } // Get chain and issue certificate is the last cert in the chain let chain = X509_STORE_CTX_get1_chain(store); let nrCertsInChain = sk_X509_num(chain) if nrCertsInChain > 1 { let cert = sk_X509_value(chain, nrCertsInChain-1) if let certWrapper = X509Wrapper(with: cert) { return .success( certWrapper ) } } return .failure(OpenSSLError.UnableToVerifyX509CertificateForSOD("Unable to get issuer certificate - not found")) } /// Verifies the signed data section against the stored certificate and extracts the signed data section from a PKCS7 container (if present and valid) /// - Parameter pkcs7Der: The PKCS7 container in DER format /// - Returns: The signed data from a PKCS7 container if we could read it /// /// - Note: To test from the command line using openssl (NOTE NOT THE default mac version as it doesn't currently support CMS): /// extract the SOD Base64 from an exported passport (you will need to unescape slashes!) - save this to ppt.b64 /// convert to binary (cat ppt.b64 | base64 -D > ppt.bin /// extract the der file from the SOD (which includes header) - tail -c+5 ppt.bin > aq.der (blindy discards header) /// convert der to PEM - openssl pkcs7 -in ppt.der --inform der -out ppt.pem -outform pem /// verify signature data against included document signing cert - openssl cms -verify -in ppt.pem -inform pem -noverify /// the -noverify is don't verify against the signers certifcate (as we don' thave that!) /// /// This should return Verification Successful and the signed data static func verifyAndReturnSODEncapsulatedDataUsingCMS( sod : SOD ) throws -> Data { guard let inf = BIO_new(BIO_s_mem()) else { throw OpenSSLError.VerifyAndReturnSODEncapsulatedData("CMS - Unable to allocate input buffer") } defer { BIO_free(inf) } guard let out = BIO_new(BIO_s_mem()) else { throw OpenSSLError.VerifyAndReturnSODEncapsulatedData("CMS - Unable to allocate output buffer") } defer { BIO_free(out) } let _ = sod.body.withUnsafeBytes { (ptr) in BIO_write(inf, ptr.baseAddress?.assumingMemoryBound(to: UInt8.self), Int32(sod.body.count)) } guard let cms = d2i_CMS_bio(inf, nil) else { throw OpenSSLError.VerifyAndReturnSODEncapsulatedData("CMS - Verification of P7 failed - unable to create CMS") } defer { CMS_ContentInfo_free(cms) } let flags : UInt32 = UInt32(CMS_NO_SIGNER_CERT_VERIFY) if CMS_verify(cms, nil, nil, nil, out, flags) == 0 { throw OpenSSLError.VerifyAndReturnSODEncapsulatedData("CMS - Verification of P7 failed - unable to verify signature") } Log.debug("Verification successful\n"); let len = BIO_ctrl(out, BIO_CTRL_PENDING, 0, nil) var buffer = [UInt8](repeating: 0, count: len) BIO_read(out, &buffer, Int32(len)) let sigData = Data(buffer) return sigData } static func verifyAndReturnSODEncapsulatedData( sod : SOD ) throws -> Data { let encapsulatedContent = try sod.getEncapsulatedContent() let signedAttribsHashAlgo = try sod.getEncapsulatedContentDigestAlgorithm() let signedAttributes = try sod.getSignedAttributes() let messageDigest = try sod.getMessageDigestFromSignedAttributes() let signature = try sod.getSignature() let sigType = try sod.getSignatureAlgorithm() let pubKey = try sod.getPublicKey() let mdHash : Data = try Data(calcHash(data: [UInt8](encapsulatedContent), hashAlgorithm: signedAttribsHashAlgo)) // Make sure that hash equals the messageDigest if messageDigest != mdHash { // Invalid - signed data hash doesn't match message digest hash throw OpenSSLError.VerifyAndReturnSODEncapsulatedData("messageDigest Hash doesn't hatch that of the signed attributes") } // Verify signed attributes if !verifySignature( data : [UInt8](signedAttributes), signature : [UInt8](signature), pubKey : pubKey, digestType: sigType ) { throw OpenSSLError.VerifyAndReturnSODEncapsulatedData("Unable to verify signature for signed attributes") } return encapsulatedContent } /// Parses a signed data structures encoded in ASN1 format and returns the structure in text format /// - Parameter data: The data to be parsed in ASN1 format /// - Returns: The parsed data as A String static func ASN1Parse( data: Data ) throws -> String { guard let out = BIO_new(BIO_s_mem()) else { throw OpenSSLError.UnableToParseASN1("Unable to allocate output buffer") } defer { BIO_free(out) } var parsed : String = "" let _ = try data.withUnsafeBytes { (ptr) in let rc = ASN1_parse_dump(out, ptr.baseAddress?.assumingMemoryBound(to: UInt8.self), data.count, 0, 0) if rc == 0 { let str = OpenSSLUtils.getOpenSSLError() Log.debug( "Failed to parse ASN1 Data - \(str)" ) throw OpenSSLError.UnableToParseASN1("Failed to parse ASN1 Data - \(str)") } parsed = bioToString(bio: out) } return parsed } /// Reads an RSA Public Key in DER format and converts it to an OpenSSL EVP_PKEY value for use whilst decrypting or verifying an RSA signature /// - Parameter data: The RSA key in DER format /// - Returns: The EVP_PKEY value /// NOTE THE CALLER IS RESPONSIBLE FOR FREEING THE RETURNED KEY USING /// EVP_PKEY_free(pemKey); static func readRSAPublicKey( data : [UInt8] ) throws -> OpaquePointer? { guard let inf = BIO_new(BIO_s_mem()) else { throw OpenSSLError.UnableToReadECPublicKey("Unable to allocate output buffer") } defer { BIO_free(inf) } let _ = data.withUnsafeBytes { (ptr) in BIO_write(inf, ptr.baseAddress?.assumingMemoryBound(to: UInt8.self), Int32(data.count)) } guard let rsakey = d2i_RSA_PUBKEY_bio(inf, nil) else { throw OpenSSLError.UnableToReadECPublicKey("Failed to load") } defer{ RSA_free(rsakey) } let key = EVP_PKEY_new() if EVP_PKEY_set1_RSA(key, rsakey) != 1 { EVP_PKEY_free(key) throw OpenSSLError.UnableToReadECPublicKey("Failed to load") } return key } /// This code is taken pretty much from rsautl.c - to decrypt a signature with a public key /// NOTE: Current no padding is used! - This seems to be the default for Active Authentication RSA signatures (guess) /// - Parameter signature: The RSA encrypted signature to decrypt /// - Parameter pubKey: The RSA Public Key /// - Returns: The decrypted signature data static func decryptRSASignature( signature : Data, pubKey : OpaquePointer ) throws -> [UInt8] { let pad = RSA_NO_PADDING let rsa = EVP_PKEY_get1_RSA( pubKey ) let keysize = RSA_size(rsa); var outputBuf = [UInt8](repeating: 0, count: Int(keysize)) // Decrypt signature var outlen : Int32 = 0 let _ = signature.withUnsafeBytes { (sigPtr) in let _ = outputBuf.withUnsafeMutableBytes { (outPtr) in outlen = RSA_public_decrypt(Int32(signature.count), sigPtr.baseAddress?.assumingMemoryBound(to: UInt8.self), outPtr.baseAddress?.assumingMemoryBound(to: UInt8.self), rsa, pad) } } if outlen == 0 { let error = OpenSSLUtils.getOpenSSLError() throw OpenSSLError.UnableToDecryptRSASignature( "RSA_public_decrypt failed - \(error)" ) } return outputBuf } /// Reads an ECDSA Public Key in DER format and converts it to an OpenSSL EVP_PKEY value for use whilst verifying a ECDSA signature /// - Parameter data: The ECDSA key in DER forma /// - Returns: The EVP_PKEY value /// NOTE THE CALLER IS RESPONSIBLE FOR FREEING THE RETURNED KEY USING /// EVP_PKEY_free(pemKey); static func readECPublicKey( data : [UInt8] ) throws -> OpaquePointer? { guard let inf = BIO_new(BIO_s_mem()) else { throw OpenSSLError.UnableToReadECPublicKey("Unable to allocate output buffer") } defer { BIO_free(inf) } let _ = data.withUnsafeBytes { (ptr) in BIO_write(inf, ptr.baseAddress?.assumingMemoryBound(to: UInt8.self), Int32(data.count)) } guard let eckey = d2i_EC_PUBKEY_bio(inf, nil) else { throw OpenSSLError.UnableToReadECPublicKey("Failed to load") } defer{ EC_KEY_free(eckey) } guard let outf = BIO_new(BIO_s_mem()) else { throw OpenSSLError.UnableToReadECPublicKey("Unable to allocate output buffer") } defer { BIO_free(outf) } let _ = PEM_write_bio_EC_PUBKEY(outf, eckey); let pemKey = PEM_read_bio_PUBKEY(outf, nil, nil, nil) return pemKey } /// Verifies Active Authentication data valid against an ECDSA signature and ECDSA Public Key - used in Active Authentication /// - Parameter publicKey: The OpenSSL EVP_PKEY ECDSA key /// - Parameter signature: The ECDSA signature to verify /// - Parameter data: The data used to generate the signature /// - Returns: True if the signature was verified static func verifyECDSASignature( publicKey:OpaquePointer, signature: [UInt8], data: [UInt8], digestType: String = "" ) -> Bool { // We first need to convert the signature from PLAIN ECDSA to ASN1 DER encoded let ecsig = ECDSA_SIG_new() defer { ECDSA_SIG_free(ecsig) } let sigData = signature let l = sigData.count / 2 sigData.withUnsafeBufferPointer { (unsafeBufPtr) in let unsafePointer = unsafeBufPtr.baseAddress! let r = BN_bin2bn(unsafePointer, Int32(l), nil) let s = BN_bin2bn((unsafePointer + l), Int32(l), nil) ECDSA_SIG_set0(ecsig, r, s) } let sigSize = i2d_ECDSA_SIG(ecsig, nil) var derBytes = [UInt8](repeating: 0, count: Int(sigSize)) derBytes.withUnsafeMutableBufferPointer { (unsafeBufPtr) in var unsafePointer = unsafeBufPtr.baseAddress let _ = i2d_ECDSA_SIG(ecsig, &unsafePointer) } let rc = verifySignature(data: data, signature: derBytes, pubKey: publicKey, digestType: digestType) return rc } /// Verifies that a signature is valid for some data and a Public Key /// - Parameter data: The data used to generate the signature /// - Parameter signature: The signature to verify /// - Parameter publicKey: The OpenSSL EVP_PKEY key /// - Parameter digestType: the type of hash to use (empty string to use no digest type) /// - Returns: True if the signature was verified static func verifySignature( data : [UInt8], signature : [UInt8], pubKey : OpaquePointer, digestType: String ) -> Bool { var digest = "sha256" let digestType = digestType.lowercased() if digestType.contains( "sha1" ) { digest = "sha1" } else if digestType.contains( "sha224" ) { digest = "sha224" } else if digestType.contains( "sha256" ) || digestType.contains( "rsassapss" ) { digest = "sha256" } else if digestType.contains( "sha384" ) { digest = "sha384" } else if digestType.contains( "sha512" ) { digest = "sha512" } // Fix for some invalid ECDSA based signatures // An ECDSA signature comprises of a Sequence of 2 big integers (R & S) and the verification // is a linear equation of these two integers, the data hash and the public key // However, in some passports the encoding of the integers is incorrect and has a leading 00 // causing the verification to fail. // So in this case, we check to see if it is actually a valid BigInteger, and if not, we remove the // leading prefix, check again and if a valid big integer this time then we use this otherwise // we keep the original value // If we change any values then we re-generate the signature and use this var fixedSignature = signature if digestType.contains( "ecdsa" ) { // Decode signature if let sequence = TKBERTLVRecord(from:Data(signature)), sequence.tag == 0x30, var intRecords = TKBERTLVRecord.sequenceOfRecords(from: sequence.value), intRecords.count == 2 { var didFix = false for (idx, rec) in intRecords.enumerated() { // Only process if the first byte is a 0 if rec.value[0] != 0 { continue } // There is a feature in TKBERTLVRecord.sequenceOfRecords where the 2nd record.data call // contains the data for the whole data not the actual record // (reported as FB9077037) // So for the moment, work aroud this and create a new record let fixedRec = TKBERTLVRecord( tag: rec.tag, value: rec.value) let data = [UInt8](fixedRec.data) // Check to see if a valid Big Integer (we need the whole record including tag and length for the d2i_ASN1_INTEGER call) data.withUnsafeBufferPointer { (ptr) in var address = ptr.baseAddress let v = d2i_ASN1_INTEGER(nil, &address, data.count) defer { ASN1_INTEGER_free(v) } if v == nil { // Not a valid BigInteger, so remove the first value and try again let newRec = TKBERTLVRecord( tag: rec.tag, value: rec.value[1...]) let data2 = [UInt8](newRec.data) data2.withUnsafeBufferPointer { (ptr) in var address = ptr.baseAddress let v2 = d2i_ASN1_INTEGER(nil, &address, data2.count) defer { ASN1_INTEGER_free(v2) } if v2 != nil { // OK, we have a valid BigInteger this time so replace the original // record with the new one intRecords[idx] = newRec didFix = true } } } } } // We only reencode if we changed any of the integers, otherwise assume they were actually // correctly encoded if didFix { // re-encode let newSequence = TKBERTLVRecord( tag: sequence.tag, records: intRecords) fixedSignature = [UInt8](newSequence.data) } } } let md = EVP_get_digestbyname(digest) let ctx = EVP_MD_CTX_new() var pkey_ctx : OpaquePointer? defer{ EVP_MD_CTX_free( ctx) } var nRes = EVP_DigestVerifyInit(ctx, &pkey_ctx, md, nil, pubKey) if ( nRes != 1 ) { return false; } if digestType.contains( "rsassapss" ) { EVP_PKEY_CTX_ctrl_str(pkey_ctx, "rsa_padding_mode", "pss" ) EVP_PKEY_CTX_ctrl_str(pkey_ctx, "rsa_pss_saltlen", "auto" ) } nRes = EVP_DigestUpdate(ctx, data, data.count); if ( nRes != 1 ) { return false; } nRes = EVP_DigestVerifyFinal(ctx, fixedSignature, fixedSignature.count); if (nRes != 1) { return false; } return true } @available(iOS 13, macOS 10.15, *) static func generateAESCMAC( key: [UInt8], message : [UInt8] ) -> [UInt8] { let ctx = CMAC_CTX_new(); defer { CMAC_CTX_free(ctx) } var key = key var mac = [UInt8](repeating: 0, count: 32) var maclen : Int = 0 if key.count == 16 { CMAC_Init(ctx, &key, key.count, EVP_aes_128_cbc(), nil) } else if key.count == 24 { CMAC_Init(ctx, &key, key.count, EVP_aes_192_cbc(), nil) } else if key.count == 32 { CMAC_Init(ctx, &key, key.count, EVP_aes_256_cbc(), nil) } CMAC_Update(ctx, message, message.count); CMAC_Final(ctx, &mac, &maclen); Log.verbose( "aesMac - mac - \(binToHexRep(mac))" ) return [UInt8](mac[0.. [UInt8] { let obj = OBJ_txt2obj( oid.cString(using: .utf8), 1) let payloadLen = i2d_ASN1_OBJECT(obj, nil) var data = [UInt8](repeating: 0, count: Int(payloadLen)) let _ = data.withUnsafeMutableBytes { (ptr) in var newPtr = ptr.baseAddress?.assumingMemoryBound(to: UInt8.self) _ = i2d_ASN1_OBJECT(obj, &newPtr) } return data } @available(iOS 13, macOS 10.15, *) public static func getPublicKeyData(from key:OpaquePointer) -> [UInt8]? { var data : [UInt8] = [] // Get Key type let v = EVP_PKEY_base_id( key ) if v == EVP_PKEY_DH || v == EVP_PKEY_DHX { guard let dh = EVP_PKEY_get0_DH(key) else { return nil } var dhPubKey : OpaquePointer? DH_get0_key(dh, &dhPubKey, nil) let nrBytes = (BN_num_bits(dhPubKey)+7)/8 data = [UInt8](repeating: 0, count: Int(nrBytes)) _ = BN_bn2bin(dhPubKey, &data) } else if v == EVP_PKEY_EC { guard let ec = EVP_PKEY_get0_EC_KEY(key), let ec_pub = EC_KEY_get0_public_key(ec), let ec_group = EC_KEY_get0_group(ec) else { return nil } let form = EC_KEY_get_conv_form(ec) let len = EC_POINT_point2oct(ec_group, ec_pub, form, nil, 0, nil) data = [UInt8](repeating: 0, count: Int(len)) if len == 0 { return nil } _ = EC_POINT_point2oct(ec_group, ec_pub, form, &data, len, nil) } return data } // Caller is responsible for freeing the key @available(iOS 13, macOS 10.15, *) public static func decodePublicKeyFromBytes(pubKeyData: [UInt8], params: OpaquePointer) -> OpaquePointer? { var pubKey : OpaquePointer? let keyType = EVP_PKEY_base_id( params ) if keyType == EVP_PKEY_DH || keyType == EVP_PKEY_DHX { let dhKey = DH_new() defer{ DH_free(dhKey) } // We don't free this as its part of the key! let bn = BN_bin2bn(pubKeyData, Int32(pubKeyData.count), nil) DH_set0_key(dhKey, bn, nil) pubKey = EVP_PKEY_new() guard EVP_PKEY_set1_DH(pubKey, dhKey) == 1 else { return nil } } else { let ec = EVP_PKEY_get1_EC_KEY(params) let group = EC_KEY_get0_group(ec); let ecp = EC_POINT_new(group); let key = EC_KEY_new(); defer { EC_KEY_free(ec) EC_POINT_free(ecp) EC_KEY_free(key) } // Read EC_Point from public key data guard EC_POINT_oct2point(group, ecp, pubKeyData, pubKeyData.count, nil) == 1, EC_KEY_set_group(key, group) == 1, EC_KEY_set_public_key(key, ecp) == 1 else { return nil } pubKey = EVP_PKEY_new() guard EVP_PKEY_set1_EC_KEY(pubKey, key) == 1 else { return nil } } return pubKey } public static func computeSharedSecret( privateKeyPair: OpaquePointer, publicKey: OpaquePointer ) -> [UInt8] { // Oddly it seems that we cant use EVP_PKEY stuff for DH as it uses DTX keys which OpenSSL doesn't quite handle right // OR I'm misunderstanding something (which is more possible) // Works fine though for ECDH keys var secret : [UInt8] let keyType = EVP_PKEY_base_id( privateKeyPair ) if keyType == EVP_PKEY_DH || keyType == EVP_PKEY_DHX { // Get bn for public key let dh = EVP_PKEY_get1_DH(privateKeyPair); let dh_pub = EVP_PKEY_get1_DH(publicKey) var bn = BN_new() DH_get0_key( dh_pub, &bn, nil ) secret = [UInt8](repeating: 0, count: Int(DH_size(dh))) let len = DH_compute_key(&secret, bn, dh); Log.verbose( "OpenSSLUtils.computeSharedSecret - DH secret len - \(len)" ) } else { let ctx = EVP_PKEY_CTX_new(privateKeyPair, nil) defer{ EVP_PKEY_CTX_free(ctx) } if EVP_PKEY_derive_init(ctx) != 1 { // error Log.error( "ERROR - \(OpenSSLUtils.getOpenSSLError())" ) } // Set the public key if EVP_PKEY_derive_set_peer( ctx, publicKey ) != 1 { // error Log.error( "ERROR - \(OpenSSLUtils.getOpenSSLError())" ) } // get buffer length needed for shared secret var keyLen = 0 if EVP_PKEY_derive(ctx, nil, &keyLen) != 1 { // Error Log.error( "ERROR - \(OpenSSLUtils.getOpenSSLError())" ) } // Derive the shared secret secret = [UInt8](repeating: 0, count: keyLen) if EVP_PKEY_derive(ctx, &secret, &keyLen) != 1 { // Error Log.error( "ERROR - \(OpenSSLUtils.getOpenSSLError())" ) } } return secret } }