我正在尝试为我的服务器创建一个iOS客户端。理想情况下,我希望在建立与服务器的tls 1.2连接时,iOS会向我提供它所获得的证书,以便我可以将其与预期的证书相匹配。经过大量的谷歌搜索,它看起来不可能。这在android中很容易做到。之后,我愿意选择第二名并让iOS接受我自己的私人CA签署的任何证书。这样我可以保证它所连接的服务器至少是我的。
看起来iOS看起来没有像C中那样的标准套接字,在创建它之后,你可以在其fd或Java上读写,你可以在其中获得套接字的输入和输出流来做像C一样读写。看起来我需要像类一样创建自己的套接字以使C / Java像读写一样。
import Foundation
class SSLSocket: NSObject, StreamDelegate
{
//(allow the getter to be public, only the setter is private)
private(set) var inputStream: InputStream?
private(set) var outputStream: OutputStream?
private var inputDelegate: StreamDelegate?
private var outputDelegate: StreamDelegate?
private var host: String
private var port: Int
public init(host: String, port: Int)
{
self.host = host
self.port = port
}
func connect()
{
Stream.getStreamsToHost(withName: host, port: port, inputStream: &inputStream, outputStream: &outputStream)
//iOS specific oddities (nothing similar in the android version)
inputDelegate = self
outputDelegate = self
inputStream!.delegate = inputDelegate
outputStream!.delegate = outputDelegate
inputStream!.schedule(in: RunLoop.main, forMode: RunLoopMode.defaultRunLoopMode)
outputStream!.schedule(in: RunLoop.main, forMode: RunLoopMode.defaultRunLoopMode)
//tlsv1.0+ enforcement??? (looks like no 1.2 only)
//don't do any "legitimacy checks" on the certificate or the host.
let sslSettings = //must present ssl properties in an array, not 1 by 1 in .setPropery(...
[
String(kCFStreamPropertySocketSecurityLevel): kCFStreamSocketSecurityLevelTLSv1,
String(kCFStreamSSLValidatesCertificateChain): kCFBooleanFalse
] as [String : Any]
inputStream!.setProperty(sslSettings, forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey)
outputStream!.setProperty(sslSettings, forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey)
//open the streams
inputStream!.open()
outputStream!.open()
}
func close() //better not cause timing problems because it is a bit less than instantaneous
{
inputStream!.delegate = nil
outputStream!.delegate = nil
inputStream!.close()
outputStream!.close()
inputStream!.remove(from: .main, forMode: .defaultRunLoopMode)
outputStream!.remove(from: .main, forMode: .defaultRunLoopMode)
//let the automatic reference count get rid of these
inputStream = nil
outputStream = nil
}
func stream(_ aStream: Stream, handle eventCode: Stream.Event)
{
switch eventCode
{
case Stream.Event.endEncountered:
print("socked died")
aStream.close()
aStream.remove(from: RunLoop.main, forMode: .defaultRunLoopMode)
break
case Stream.Event.hasSpaceAvailable:
print("matching presented certificate with expected")
//get the presented certificate
let sslTrustInput: SecTrust? = aStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as! SecTrust
if(sslTrustInput == nil)
{
print("something went horribly wrong in fetching the presented certificate")
broadcastSocketResult(result: false)
return
}
if(Vars.expectedCert == nil)
{
print("probably a bug, there is no expected certificate in Vars. fail/crash in 3, 2, 1...")
broadcastSocketResult(result: false)
return
}
//set the expected certificate as the only "trusted" one
let acceptedCerts: NSMutableArray = NSMutableArray()
acceptedCerts.add(Vars.expectedCert!)
SecTrustSetAnchorCertificates(sslTrustInput!, acceptedCerts)
//check the certificate match test results
var result: SecTrustResultType = SecTrustResultType.fatalTrustFailure //must initialize with something
let err: OSStatus = SecTrustEvaluate(sslTrustInput!, &result)
if(err != errSecSuccess)
{
print("problem evaluating certificate match")
broadcastSocketResult(result: false)
return
}
if (result != SecTrustResultType.proceed)
{
print("certificate was not signed by private CA")
broadcastSocketResult(result: false)
return
}
print("socket ssl turned out ok")
broadcastSocketResult(result: true)
break
case Stream.Event.openCompleted:
print("socket is useable")
break
case Stream.Event.errorOccurred:
print("something bad happened")
broadcastSocketResult(result: false)
break;
default:
print("some other code" + String(describing: eventCode))
broadcastSocketResult(result: false)
break
}
}
private func broadcastSocketResult(result: Bool)
{
let extras = [Const.BORADCAST_SOCKET_RESULT: result]
NotificationCenter.default.post(name: NSNotification.Name(rawValue: Const.BROADCAST_SOCKET), object: extras)
}
}
私有CA的公钥是从base64编码转储中获取到文本框中的。
var certDumpValue: String? = certDump.text
if(certDumpValue == nil || certDumpValue! == "" || certDumpValue!.characters.count < 28)
{
//in android the certificate is either there or not. In iOS it could be there, or not, or incomplete.
//also no longer a file in iOS but a base64 dump
Utils.showOk(screen: self, message: "Certificate corrupted. Can't use.")
return;
}
else
{
//check it is a real certificate and not just random text or a poem
//https://stackoverflow.com/questions/28957940/remove-all-line-breaks-at-the-beginning-of-a-string-in-swift
//https://digitalleaves.com/blog/2015/10/sharing-public-keys-between-ios-and-the-rest-of-the-world/
//prepare the base64 string dump for usage
//trim off the ---start ceritificate--- and end certificate tags, get rid of the newlines
certDumpValue = certDumpValue!.replacingOccurrences(of: "\n", with: "")
let startChop = certDumpValue!.index(certDumpValue!.startIndex, offsetBy: 27)
let endChop = certDumpValue!.index(certDumpValue!.startIndex, offsetBy: certDumpValue!.characters.count-26)
certDumpValue = certDumpValue![startChop...endChop] //most complicated method of substring imaginable
//check if the string is a valid base64 encoded string
let certRaw: NSData? = NSData(base64Encoded: certDumpValue!)
if(certRaw == nil)
{
Utils.showOk(screen: self, message: "Certificate corrupted. Can't use.")
return;
}
//if it is valid base64, check if it's a real certificate
let cert = SecCertificateCreateWithData(nil, certRaw!)
if(cert == nil)
{
Utils.showOk(screen: self, message: "Certificate corrupted. Can't use.")
return;
}
Vars.expectedCert = cert
}
当我尝试连接到服务器时,我总是会收到可恢复的信任错误。我将CA公钥设置为信任锚,因此不应该发生这种情况。该策略主要基于this
答案 0 :(得分:0)
这是我最近创建的一个软件包,用于处理Apple对TCP / TLS施加的最新限制-它是Obj-C,但也许仍然会有用吗?