草庐IT

ios - Swift iOS 客户端证书认证

coder 2023-07-16 原文

我要使用的 Web 服务需要客户端证书。如何将我的证书发送给它?

为了进一步说明,我不明白如何创建 SecIdentityRef

在我的 NSURLConnection didReceiveAuthenticationChallenge 中,我在 ServerTrust 之后得到了这个条件:

else if challenge?.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate
    {
        var secIdent : SecIdentityRef = ?????????
        var certCred = NSURLCredential(identity: secIdent, certificates: [getClientCertificate()], persistence: NSURLCredentialPersistence.Permanent)
        challenge?.sender.useCredential(certCred, forAuthenticationChallenge: challenge!)
    }

getClientCertificate 方法:

func getClientCertificate() -> SecCertificateRef
{
    let mainBundle : NSBundle = NSBundle.mainBundle()
    var mainBund = mainBundle.pathForResource("iosClientCert", ofType: "cer") //exported the cert in der format.
    var key : NSData = NSData(contentsOfFile: mainBund!)!
    var turnToCert : SecCertificateRef = SecCertificateCreateWithData(kCFAllocatorDefault, key).takeRetainedValue()

    return turnToCert;
}

最佳答案

从技术上讲,当我认识的某个人需要在 Swift 中实现时,他使用以下 Objective-C 实现来获取连接的 NSURLCredential 对象; based on the private key and X509 Certificate pair contained in a PKCS12 keystore .

抱歉,我无法使用 Swift 解决方案访问源代码。我所知道的是 NSURLCredential 已返回给 Swift,并直接用于那里的 http url 连接。不过,它与此类似。

我不是 iOS 开发人员,所以我无法帮助您解决“桥接到 Swift”的问题。

- (void)getMessageWithURL:(NSString *)url {

    NSURL *URL = [NSURL URLWithString:url];

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
    [request setURL:URL];
    [request setHTTPMethod:@"GET"];
    NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
    [connection self];
}

- (void)postMessageWithURL:(NSString *)url withContent:(NSString *)content {

    NSData *postData = [content dataUsingEncoding:NSUTF8StringEncoding];
    NSString *postLength = [NSString stringWithFormat:@"%d", [postData length]];

    NSURL *myURL = [NSURL URLWithString:url];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:myURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60];

    [request setHTTPMethod:@"POST"];
    [request setValue:postLength forHTTPHeaderField:@"Content-Length"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setHTTPBody:postData];

    NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
    [connection self];

}

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
    return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
    NSLog(@"didReceiveAuthenticationChallenge");
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    responseData = [[NSMutableData alloc] init];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [responseData appendData:data];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    NSLog(@"Unable to fetch data");
    NSLog(@"%@", error);
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    NSLog(@"Succeeded! Received %lu bytes of data", (unsigned long)[responseData
            length]);

    NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
    NSLog(@"%@", responseString);

    [bridge callHandler:handlerName data:responseString];

}

- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {

    /*
    Reading the certificate and creating the identity
    */
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = paths[0]; // Get documents directory

    NSData *p12data = [CertificateManager getP12Data]; //returns essentially a byte array containing a valid PKCS12 certificate

    if (!p12data) {
      return;
      NSAssert(p12data, @"Couldn't load p12 file...");
    }

    CFStringRef password = CFSTR("password");

    const void *keys[] = {kSecImportExportPassphrase};
    const void *values[] = {password};
    CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
    CFArrayRef p12Items;

    OSStatus result = SecPKCS12Import((__bridge CFDataRef) p12data, optionsDictionary, &p12Items);

    if (result == noErr) {
        CFDictionaryRef identityDict = CFArrayGetValueAtIndex(p12Items, 0);
        SecIdentityRef identityApp = (SecIdentityRef) CFDictionaryGetValue(identityDict, kSecImportItemIdentity);

        SecCertificateRef certRef;
        SecIdentityCopyCertificate(identityApp, &certRef);

        SecCertificateRef certArray[1] = {certRef};
        CFArrayRef myCerts = CFArrayCreate(NULL, (void *) certArray, 1, NULL);
        CFRelease(certRef);

        NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identityApp certificates:nil persistence:NSURLCredentialPersistenceNone];
        CFRelease(myCerts);

        [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
    }
    else {
        // Certificate is invalid or password is invalid given the certificate
        NSLog(@"Invalid certificate or password");
        NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:result userInfo:nil];
        return;
    }
}

编辑:哈哈,非常有趣,当你自己在赏金上升时没有打扰时两次对我投反对票。 *提示*

无论如何,要使用上面的内容,您只需要从 Swift 访问它即可。

func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge) {
    if let p12Data = UserManager.currentP12,
       let credential = CertificateManager.getCredentialsForP12(p12Data) as? NSURLCredential {
            challenge.sender.useCredential(credential, forAuthenticationChallenge: challenge)
    } else {
        UIApplication.sharedApplication().networkActivityIndicatorVisible = false
    }   
}

就是用这个。

+ (id)getCredentialsForP12:(NSData *)p12 {
    NSData* p12data = p12;
    const void *keys[] = {kSecImportExportPassphrase};
    const void *values[] = {CFSTR("thePassword")};
    CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
    CFArrayRef p12Items;
    OSStatus result = SecPKCS12Import((__bridge CFDataRef) p12data, optionsDictionary, &p12Items);
    if (result == noErr) {
        CFDictionaryRef identityDict = CFArrayGetValueAtIndex(p12Items, 0);
        SecIdentityRef identityApp = (SecIdentityRef) CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
        SecCertificateRef certRef;
        SecIdentityCopyCertificate(identityApp, &certRef);
        SecCertificateRef certArray[1] = {certRef};
        CFArrayRef myCerts = CFArrayCreate(NULL, (void *) certArray, 1, NULL);
        CFRelease(certRef);

        NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identityApp certificates:nil persistence:NSURLCredentialPersistenceNone];
        CFRelease(myCerts);
        return credential;

    }
    else {
        // Certificate is invalid or password is invalid given the certificate
        NSLog(@"Invalid certificate or password");

        UIAlertView* av = [[UIAlertView alloc] initWithTitle:@"Error" message:@"Invalid cert or pass" delegate:nil cancelButtonTitle:@"ok" otherButtonTitles: nil];
        [av show];
        NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:result userInfo:nil];
        return nil;
    }

编辑:上面的快速版本在这里,虽然它已经够乱了,我们宁愿不使用它。

            var p12items : Unmanaged<CFArrayRef>?

            let index: CFIndex = 1
            let password: CFString = "password"
            let key = kSecImportExportPassphrase.takeRetainedValue() as String
            var values = [unsafeAddressOf(password)]
            var keys = [unsafeAddressOf(key)]

            var keyCallbacks = kCFTypeDictionaryKeyCallBacks
            var valueCallbacks = kCFTypeDictionaryValueCallBacks

            let length: CFIndex = p12Data.length
            let p12CfData: CFData = CFDataCreate(kCFAllocatorDefault, UnsafePointer<UInt8>(p12Data.bytes), length)

            let options = CFDictionaryCreate(kCFAllocatorDefault, &keys, &values, index, &keyCallbacks, &valueCallbacks)
            let result = SecPKCS12Import(p12CfData, options, &p12items)

            if result == noErr {

                let idIndex: CFIndex = 0
                var items = p12items?.takeRetainedValue()
                var identityDict = CFArrayGetValueAtIndex(items!, idIndex) 

                var key = kSecImportItemIdentity.takeRetainedValue() as String
                var keyAddress = unsafeAddressOf(key)
                var identityApp: SecIdentityRef = CFDictionaryGetValue(identityDict, keyAddress) 
                var certRef : Unmanaged<SecCertificateRef>?
                SecIdentityCopyCertificate(identityApp, &certRef)

                var cert: SecCertificateRef = certRef!.takeRetainedValue()
                var certArray = [unsafeAddressOf(cert)]
                var arrayCallback = kCFTypeArrayCallBacks
                var myCerts: CFArrayRef = CFArrayCreate(kCFAllocatorDefault, &certArray, index, &arrayCallback);

                let credential: NSURLCredential = NSURLCredential(identity: identityApp, certificates: [AnyObject](), persistence: NSURLCredentialPersistence.None)

关于ios - Swift iOS 客户端证书认证,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30174052/

有关ios - Swift iOS 客户端证书认证的更多相关文章

  1. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  2. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  3. 区块链之加解密算法&数字证书 - 2

    目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非

  4. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  5. ruby - 为什么不能使用类IO的实例方法noecho? - 2

    print"Enteryourpassword:"pass=STDIN.noecho(&:gets)puts"Yourpasswordis#{pass}!"输出:Enteryourpassword:input.rb:2:in`':undefinedmethod`noecho'for#>(NoMethodError) 最佳答案 一开始require'io/console'后来的Ruby1.9.3 关于ruby-为什么不能使用类IO的实例方法noecho?,我们在StackOverflow上

  6. ruby - token 认证 - 2

    简单代码require'net/http'url=URI.parse('getjson/otherdatahere[link]')req=Net::HTTP::Get.new(url.to_s)res=Net::HTTP.start(url.host,url.port){|http|http.request(req)}putsres.body只是想知道如何在phpcURL中放置身份验证token,我是这样做的    curl_setopt($ch,CURLOPT_HTTPHEADER,array('Authorization:Bearerxxx'));//Bearertokenfora

  7. ruby - 在 TCPServer (Ruby) 中,我如何从客户端获取 IP/MAC? - 2

    我想在Ruby的TCPServer中获取客户端的IP地址。以及(如果可能的话)MAC地址。例如,Ruby中的时间服务器,请参阅评论。tcpserver=TCPServer.new("",80)iftcpserverputs"Listening"loopdosocket=tcpserver.acceptifsocketThread.newdoputs"Connectedfrom"+#HERE!HowcanigettheIPAddressfromtheclient?socket.write(Time.now.to_s)socket.closeendendendend非常感谢!

  8. ruby - HTTParty 摘要认证 - 2

    谁能提供一个使用HTTParty和digestauth的例子?我在网上找不到例子,希望有人能提供一些帮助。谢谢。 最佳答案 您可以在定义类时使用digest_auth方法设置用户名和密码classFooincludeHTTPartydigest_auth'username','password'end 关于ruby-HTTParty摘要认证,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questi

  9. ruby-on-rails - 为什么我必须在使用客户验证器后重新加载 rspec 中的记录? - 2

    我有一个模型User,它在创建后的回调中创建了选项#Userhas_one:user_optionsafter_create:create_optionsprivatedefcreate_optionsUserOptions.create(user:self)end我对此有一些简单的Rspec覆盖:describe"newuser"doit"createsuser_optionsaftertheuseriscreated"douser=create(:user)user.user_options.shouldbe_kind_of(UserOptions)endend一切正常,直到我将自

  10. ruby - 为 IO::popen 拯救 "command not found" - 2

    当我将IO::popen与不存在的命令一起使用时,我在屏幕上打印了一条错误消息:irb>IO.popen"fakefake"#=>#irb>(irb):1:commandnotfound:fakefake有什么方法可以捕获此错误,以便我可以在脚本中进行检查? 最佳答案 是:升级到ruby​​1.9。如果您在1.9中运行它,则会引发Errno::ENOENT,您将能够拯救它。(编辑)这是在1.8中的一种hackish方式:error=IO.pipe$stderr.reopenerror[1]pipe=IO.popen'qwe'#

随机推荐