Decrypt data using AES.GCM.SealedBox in Swift - swift

I am trying to decrypt data using AES.GCM.The encrypted data works fine but when I try to decrypt the data using the same key it gives authentication error.
Below is the code to decrypt
func decryptData(decryptToData: Data, key: SymmetricKey) -> String {
var decryptedString: String!
let combinedData = decryptToData // Previous sealed bo
let sealedBoxToOpen = try! AES.GCM.SealedBox(combined: decryptToData)
if let decryptedData = try? AES.GCM.open(sealedBoxToOpen, using: key) {
decryptedString = String(data: decryptedData, encoding: .utf8)!
print(decryptedString)
} else {
print("error", CryptoKitError.self)
// Ouch, doSomething() threw an error.
}
return decryptedString
}
The following is my encryption code
let iv = AES.GCM.Nonce()
var encryptedData: Data!
let key = SymmetricKey(size: .bits128)
func encryptData(encryptString: String, key: SymmetricKey) -> Data {
var encryptedData: Data?
do {
let datatoEncrypt1 = encryptString.data(using: .utf8)!
let mySealedBox = try AES.GCM.seal(datatoEncrypt1, using: key, nonce: iv)
encryptedData = mySealedBox.combined
} catch {
print("Error")
}
return encryptedData
}

import XCTest
import CryptoKit
import Foundation
class CryptoKitUnitTest: XCTestCase {
func testEncryptandDecrypt(){
let secret = "my-256-bit-secret-my-secret-my-s"
let key = SymmetricKey(data: secret.data(using: .utf8)!)
let plain = "Say hello to my little friend!"
let nonce = try! AES.GCM.Nonce(data: Data(base64Encoded: "fv1nixTVoYpSvpdA")!)
// Encrypt
let sealedBox = try! AES.GCM.seal(plain.data(using: .utf8)!, using: key, nonce: nonce)
let ciphertext = sealedBox.ciphertext.base64EncodedString()
print("ciphertext: \(ciphertext)") // bWtTZkPAu7oXpQ3QpHvoTvc4NQgDTIycXHFJWvjk
let sealedBoxToDecrypt = try! AES.GCM.SealedBox(nonce: nonce,
ciphertext: Data(base64Encoded: ciphertext)!,
tag: sealedBox.tag)
let decrypted = try! AES.GCM.open(sealedBoxToDecrypt, using: key)
print(String(decoding: decrypted, as: UTF8.self))
}
func testEncryptandDecryptFirstWay() {
let keyStr = "d5a423f64b607ea7c65b311d855dc48f36114b227bd0c7a3d403f6158a9e4412"
let key = SymmetricKey(data: Data(hex:keyStr))
let nonceData = Data(hex: "131348c0987c7eece60fc0bc")
let nonce: AES.GCM.Nonce = try! AES.GCM.Nonce(data: nonceData)
let plain = "This is first cypto graphy method"
var decyptedStr = ""
if let encyptedData = plain.asData.encrypt(nonce: nonce, key: key) {
decyptedStr = encyptedData.decrypt(nonce: nonce, key: key)
}
XCTAssertEqual(plain, decyptedStr)
}
}
extension Data {
func encrypt(nonce: AES.GCM.Nonce, key: SymmetricKey) ->Data?{
// Encrypt
do {
let sealedBox = try AES.GCM.seal(self, using: key, nonce: nonce)
let cipherText = sealedBox.ciphertext.base64EncodedString()
let tag = sealedBox.tag
let tagPlusCipherText = tag + cipherText.asData
return tagPlusCipherText
}
catch let exceptionInfo {
debugPrint("Encrypt exception Info: \(exceptionInfo)")
}
return nil
}
func decrypt(nonce: AES.GCM.Nonce, key: SymmetricKey) -> String{
let tag = self.subtract(0, 16)
let cipherTextData = self.subtract(tag.count, self.count - tag.count)
let cipherText = cipherTextData.asString
// Decrypt
var decodeStr: String = ""
do {
let sealedBoxToDecrypt = try AES.GCM.SealedBox(nonce: nonce,
ciphertext: Data(base64Encoded: cipherText)!,
tag: tag)
let decrypted = try AES.GCM.open(sealedBoxToDecrypt, using: key)
decodeStr = String(decoding: decrypted, as: UTF8.self)
} catch let exceptionInfo {
debugPrint("Decrypt exception info: \(exceptionInfo)")
}
return decodeStr
}
public func subtract(_ start: Int, _ length: Int) ->Data {
precondition(self.count >= start + length,
"Invalid data range range. trying to find out of bound data")
let allBytes = Array(Data(bytes: self.bytes, count: self.count))
let partBytes = Array(allBytes[start..<start + length])
let dataPart = Data(bytes: partBytes, count: partBytes.count)
return dataPart
}
var asString: String {
let str = String(decoding: self, as: UTF8.self)
return str
}
}
extension String {
var asData: Data {
return self.data(using: .utf8) ?? Data()
}
}

Related

Signature Version 4 Issue on Swift

I really try hard to solve this problem on my project. But I can't.
I try to sign URLRequest headers and body by Amazon AWS Signature Version 4. And send it to server with Alamofire SDK.
But only headers without body work correctly.
I dunno why I get the response "403 forbidden" from server when I put httpBody into URLRequest.
Here is my source.
...
var request = URLRequest(url: convUrl)
if let jsonString = String(data: jsonData, encoding: .utf8) {
AWS4Signer().aws4Sign(request: &request, method: httpMethodType, payload: jsonString, query: [:], path: url.replacingOccurrences(of: self.url.host, with: ""))
}
AF.request(request).responseData { response in
}
func aws4Sign(request: inout URLRequest, method: HttpMethodType, payload: String?, query: [String:String]?, path: String) {
var headers = [String:String]()
let requested_date_time = self.getAmzDate(date: Date())
headers["x-amz-date"] = requested_date_time
// x-amz-content-sha256
var payload_hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" // empty
if let payload {
let utf8Data = payload.data(using: .utf8) ?? Data()
payload_hash = SHA256.hash(data: utf8Data).hexDigest()
if !payload.isEmpty {
headers["x-amz-content-sha256"] = payload_hash
request.httpBody = utf8Data
}
}
let canonical_querystring = ""
headers["host"] = "hosthost.com"
let sortedHeaders = headers.filter { ["host", "x-amz-date", "x-amz-content-sha256"].contains($0.key) }
.sorted { $0.key < $1.key }
let canonical_headers = sortedHeaders.map { "\($0.key.lowercased()):\($0.value)" }.joined(separator: "\n") + "\n"
let signed_headers = sortedHeaders.map { $0.key.lowercased() }.joined(separator: ";")
let canonical_uri = path
let canonical_request = "\(method.rawValue)\n\(canonical_uri)\n\(canonical_querystring)\n\(canonical_headers)\n\(signed_headers)\n\(payload_hash)"
let hashed_canonical_request = SHA256.hash(data: canonical_request.data(using: .utf8)!).hexDigest()
let algorithm = "AWS4-HMAC-SHA256"
let nowDate = Date().getString(format: "YYYYMMdd", timeZone: TimeZone(identifier: "UTC")!)
let credential_scope = "\(nowDate)/\(Const.key.awsRegion)/\(Const.key.awsServiceType)/aws4_request"
let string_to_sign = "\(algorithm)\n\(requested_date_time)\n\(credential_scope)\n\(hashed_canonical_request)"
let signing_key = self.getSignatureKey(key: Const.key.awsSecretAccessKey, dateStamp: nowDate, regionName: Const.key.awsRegion, serviceName: Const.key.awsServiceType)
let signature = self.sign(key: signing_key, msg: string_to_sign).hexDigest()
let authorization_header = "\(algorithm) Credential=\(Const.key.awsAccessKeyID)/\(credential_scope), SignedHeaders=\(signed_headers), Signature=\(signature)"
request.addValue(requested_date_time, forHTTPHeaderField: "X-amz-date")
request.addValue(authorization_header, forHTTPHeaderField: "Authorization")
}
func getSignatureKey(key: String, dateStamp: String, regionName: String, serviceName: String) -> Data {
let keyData = Data("AWS4\(key)".utf8)
let dateStampData = Data(dateStamp.utf8)
let regionNameData = Data(regionName.utf8)
let serviceNameData = Data(serviceName.utf8)
let signingData = Data("aws4_request".utf8)
var symmetricKey = SymmetricKey(data: keyData)
let dateSHA256 = HMAC<SHA256>.authenticationCode(for: dateStampData, using: symmetricKey)
symmetricKey = SymmetricKey(data: Data(dateSHA256))
let regionSHA256 = HMAC<SHA256>.authenticationCode(for: regionNameData, using: symmetricKey)
symmetricKey = SymmetricKey(data: Data(regionSHA256))
let serviceNameSHA256 = HMAC<SHA256>.authenticationCode(for: serviceNameData, using: symmetricKey)
symmetricKey = SymmetricKey(data: Data(serviceNameSHA256))
let signingSHA256 = HMAC<SHA256>.authenticationCode(for: signingData, using: symmetricKey)
let skeyString = keyData.map { String(format: "%02hhx", $0) }.joined()
let kDateString = Data(dateSHA256).map { String(format: "%02hhx", $0) }.joined()
let kRegionString = Data(regionSHA256).map { String(format: "%02hhx", $0) }.joined()
let kServiceString = Data(serviceNameSHA256).map { String(format: "%02hhx", $0) }.joined()
let kSigningString = Data(signingSHA256).map { String(format: "%02hhx", $0) }.joined()
return Data(signingSHA256)
}
func sign(key: Data, msg: String) -> Data {
let hmac = HMAC<SHA256>.authenticationCode(for: msg.data(using: .utf8)!, using: SymmetricKey(data: key))
return Data(hmac)
}
func getAmzDate(date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'"
formatter.timeZone = TimeZone(identifier: "UTC")
return formatter.string(from: date)
}
func getCanonicalQuerystring(query: [String: String]) -> String {
let sortedParameters = query.sorted { $0.0 < $1.0 }
let encodedQueryParameters = sortedParameters.map { (key, value) in
return "\(key.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")=\(value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")"
}
let canonicalQuerystring = encodedQueryParameters.joined(separator: "&")
return canonicalQuerystring
}
The "Authorization" data has it.
"AWS4-HMAC-SHA256 Credential=ACCESS_KEY_ID/20230213/ap-northeast-2/execute-api/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=cfa667aa18472d3d5a419f67aa2c321977428abac391a33f9f3e31839bcb4665"
ACCESS_KEY_ID is my access_key_id of AWS.
So far, only the POST method is tested.

CommonCrypto Encryption Decryption missmatch

Does anyone see what is going wrong in the below code, as the code below works well when encrypting and sending to the server (which means my encryption function is fine), however there seems some problem with my decryption logic which is not decrypting the information correctly.
Note: The server side enc/dec logic works well with other languages e.g. Java
Server-side implementation sample: Nodejs Crypto to Swift commonCrypto
MyEncDec.swift
import Foundation
import CommonCrypto
struct AES256 {
private var key: Data
private var iv: Data
public init(key: Data, iv: Data) throws {
guard key.count == kCCKeySizeAES256 else {
throw Error.badKeyLength
}
guard iv.count == kCCBlockSizeAES128 else {
throw Error.badInputVectorLength
}
self.key = key
self.iv = iv
}
enum Error: Swift.Error {
case keyGeneration(status: Int)
case cryptoFailed(status: CCCryptorStatus)
case badKeyLength
case badInputVectorLength
}
func encrypt(_ digest: Data) throws -> Data {
return try crypt(input: digest, operation: CCOperation(kCCEncrypt))
}
func decrypt(_ encrypted: Data) throws -> Data {
return try crypt(input: encrypted, operation: CCOperation(kCCDecrypt))
}
private func crypt(input: Data, operation: CCOperation) throws -> Data {
var outLength = Int(0)
var outBytes = [UInt8](repeating: 0, count: input.count + kCCBlockSizeAES128)
var status: CCCryptorStatus = CCCryptorStatus(kCCSuccess)
input.withUnsafeBytes { rawBufferPointer in
let encryptedBytes = rawBufferPointer.baseAddress!
iv.withUnsafeBytes { rawBufferPointer in
let ivBytes = rawBufferPointer.baseAddress!
key.withUnsafeBytes { rawBufferPointer in
let keyBytes = rawBufferPointer.baseAddress!
status = CCCrypt(operation,
CCAlgorithm(kCCAlgorithmAES128), // algorithm
CCOptions(kCCOptionPKCS7Padding), // options
keyBytes, // key
key.count, // keylength
ivBytes, // iv
encryptedBytes, // dataIn
input.count, // dataInLength
&outBytes, // dataOut
outBytes.count, // dataOutAvailable
&outLength) // dataOutMoved
}
}
}
guard status == kCCSuccess else {
throw Error.cryptoFailed(status: status)
}
return Data(bytes: &outBytes, count: outLength)
}
static func createKey(password: Data, salt: Data) throws -> Data {
let length = kCCKeySizeAES256
var status = Int32(0)
var derivedBytes = [UInt8](repeating: 0, count: length)
password.withUnsafeBytes { rawBufferPointer in
let passwordRawBytes = rawBufferPointer.baseAddress!
let passwordBytes = passwordRawBytes.assumingMemoryBound(to: Int8.self)
salt.withUnsafeBytes { rawBufferPointer in
let saltRawBytes = rawBufferPointer.baseAddress!
let saltBytes = saltRawBytes.assumingMemoryBound(to: UInt8.self)
status = CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2), // algorithm
passwordBytes, // password
password.count, // passwordLen
saltBytes, // salt
salt.count, // saltLen
CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1), // prf
10000, // rounds
&derivedBytes, // derivedKey
length) // derivedKeyLen
}
}
guard status == 0 else {
throw Error.keyGeneration(status: Int(status))
}
return Data(bytes: &derivedBytes, count: length)
}
static func randomIv() -> Data {
return randomData(length: kCCBlockSizeAES128)
}
static func iV() -> Data {
let arr: [UInt8] = [0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1]
return Data(arr)
}
static func randomSalt() -> Data {
return randomData(length: 8)
}
static func randomData(length: Int) -> Data {
var data = Data(count: length)
var mutableBytes: UnsafeMutableRawPointer!
data.withUnsafeMutableBytes { rawBufferPointer in
mutableBytes = rawBufferPointer.baseAddress!
}
let status = SecRandomCopyBytes(kSecRandomDefault, length, mutableBytes)
assert(status == Int32(0))
return data
}
}
I need to take a guess since you have not provided the code that runs encrypt and decrypt. But I think that you provide iv using randomIV() on both encryption and decryption side and this will be the problem.
You need to provide same iv for decryption and encryption side. It means that you need to send your random iv from encryption side to decryption side and use that iv in decryption process.
I'm not experienced in Swift, But I think you need to use your code like this in order to works correctly:
// Encryption side
let keyData = Data("KEY01234567890123456789012345678".utf8)
let data = Data("TEST123".utf8)
let iv = AES256.randomIv()
if let aes = try? AES256.init(key: keyData, iv: iv) {
if let encryptedData = try? aes.encrypt(data) {
var resultData = iv
resultData.append(encryptedData)
// use resultData here
}
}
// Decryption side
let keyData = Data("KEY01234567890123456789012345678".utf8)
let data = resultData
let iv = data.subdata(in: ..<kCCBlockSizeAES128)
let encryptedPart = data.subdata(in: kCCBlockSizeAES128...)
if let aes = try? AES256.init(key: keyData, iv: iv) {
if let decryptedData = try? aes.decrypt(encryptedPart) {
// use decryptedData here
}
}
I used cristallo code as base for this code.
I've just try to run your client side code encrypting and decrypting a sample data and it seems to work properly. Is the issue present only when you are trying to decrypt a data buffer coming from the server?
I've tested your code in the following way:
let keyData = Data("KEY01234567890123456789012345678".utf8)
let data = Data("TEST123".utf8)
let iv = AES256.randomIv()
if let aes = try? AES256.init(key: keyData, iv: iv), let aes2 = try? AES256.init(key: keyData, iv: iv) {
if let encriptedData = try? aes.encrypt(data) {
if let decryptedData = try? aes2.decrypt(encriptedData) {
let decryptedString = String(decoding: decryptedData, as: UTF8.self)
print(decryptedString)
}
}
}
I am not sure about how you are using the AES256 class but taking a look to the server side code:
The encryption function generates a string composition of the iv and the data using ":" as separator.
let final_encrypted = iv.toString('hex') + ':' + encrypted.toString('hex');
So the string coming from the server has to be parsed before decrypting it in order to retrieve iv and data.
func parseAndDecrypt(encryptedString: String) {
let keyData = Data("KEY01234567890123456789012345678".utf8)
let substrings = encryptedString.split(separator: ":")
if let ivString = substrings.first, let dataString = substrings.last {
let iv = Data(ivString.utf8)
let encryptedData = Data(dataString.utf8)
if let aes = try? AES256.init(key: keyData, iv: iv) {
if let decryptedData = try? aes.decrypt(encryptedData) {
let decryptedString = String(decoding: decryptedData, as: UTF8.self)
print(decryptedString)
}
}
}
}

WEBRTC: Local streams are visible on OPENVIDU WEB(other Participant) end But Remote Streams are coming NIL on my end locally swift iOS

I am using OpenVidu for Video Chat, Using this Repo https://github.com/OpenVidu/openvidu-ios-app I know this has bugs, but I have to use this as This is working fine at Android and WEB. I am able to make it working where my Local video can be seen on OpenVidu Web but Remote or other person who has joined session from Web whose video(Video Stream and Audio Stream) is not coming at my end. however I can see Remote Participant ID and Name at my end when a user joined the session.
Attached Image is the screen shot showing Remote streams are nil.
Below is the WebSocketListener Class I am using, I have updated pods so have to update delegates as well.
//
// WebSocketListener.swift
// WebRTCapp
//
// Created by Sergio Paniego Blanco on 01/05/2018.
// Copyright © 2018 Sergio Paniego Blanco. All rights reserved.
//
import Foundation
import Starscream
import WebRTC
class WebSocketListener: WebSocketDelegate {
let JSON_RPCVERSION = "2.0"
let useSSL = true
var socket: WebSocket
var helloWorldTimer: Timer?
var id = 0
var url: String
var sessionName: String
var participantName: String
var localOfferParams: [String: String]?
var iceCandidatesParams: [[String:String]]?
var userId: String?
var remoteParticipantId: String?
var participants: [String: RemoteParticipant]
var localPeer: RTCPeerConnection?
var peersManager: PeersManager
var token: String
var views: [UIView]!
var names: [UILabel]!
var key: String?
init(url: String, sessionName: String, participantName: String, peersManager: PeersManager, token: String, views: [UIView], names: [UILabel]) {
self.url = url
self.sessionName = sessionName
self.participantName = participantName
self.participants = [String: RemoteParticipant]()
self.peersManager = peersManager
self.token = token
var request = URLRequest(url: URL(string: url)!)
request.timeoutInterval = 20
socket = WebSocket(request: request)
socket.delegate = self
socket.connect()
self.localPeer = self.peersManager.localPeer
self.iceCandidatesParams = []
self.views = views
self.names = names
}
// func websocketDidConnect(socket: WebSocketClient) {
// print("Connected")
// pingMessageHandler()
// var joinRoomParams: [String: String] = [:]
// joinRoomParams["recorder"] = "false"
// joinRoomParams["platform"] = "iOS"
// joinRoomParams[JSONConstants.Metadata] = "{\"clientData\": \"" + "iOSUser" + "\"}"
// joinRoomParams["secret"] = "MY_SECRET"
// joinRoomParams["session"] = sessionName
// joinRoomParams["token"] = token
// sendJson(method: "joinRoom", params: joinRoomParams)
// if localOfferParams != nil {
// sendJson(method: "publishVideo",params: localOfferParams!)
// }
// }
func pingMessageHandler() {
helloWorldTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(WebSocketListener.doPing), userInfo: nil, repeats: true)
doPing()
}
#objc func doPing() {
var pingParams: [String: String] = [:]
pingParams["interval"] = "5000"
sendJson(method: "ping", params: pingParams)
socket.write(ping: Data())
}
var isConnected = false
func websocketDidDisconnect(socket: WebSocketClient, error: Error?) {
print("Disconnect: " + error.debugDescription)
}
func didReceive(event: WebSocketEvent, client: WebSocket) {
switch event {
case .connected(let headers):
isConnected = true
print("websocket is connected: \(headers)")
pingMessageHandler()
var joinRoomParams: [String: String] = [:]
joinRoomParams["recorder"] = "false"
joinRoomParams["platform"] = "iOS"
joinRoomParams[JSONConstants.Metadata] = "{\"clientData\": \"\(self.participantName)\"}"
joinRoomParams["secret"] = ""
joinRoomParams["session"] = sessionName
joinRoomParams["token"] = token
sendJson(method: "joinRoom", params: joinRoomParams)
if localOfferParams != nil {
sendJson(method: "publishVideo",params: localOfferParams!)
}
case .disconnected(let reason, let code):
isConnected = false
print("websocket is disconnected: \(reason) with code: \(code)")
case .text(let string):
print("Received text: \(string)")
let data = string.data(using: .utf8)!
do {
let json: [String: Any] = try JSONSerialization.jsonObject(with: data, options : .allowFragments) as! [String : Any]
if json[JSONConstants.Result] != nil {
handleResult(json: json)
} else {
handleMethod(json: json)
}
} catch let error as NSError {
print("ERROR parsing JSON: ", error)
}
case .binary(let data):
print("Received data: \(data.count)")
case .ping(_):
break
case .pong(_):
break
case .viabilityChanged(_):
break
case .reconnectSuggested(_):
break
case .cancelled:
isConnected = false
case .error(let error):
isConnected = false
print(error.debugDescription)
}
}
// func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {
// print("Recieved message: " + text)
// let data = text.data(using: .utf8)!
// do {
// let json: [String: Any] = try JSONSerialization.jsonObject(with: data, options : .allowFragments) as! [String : Any]
//
// if json[JSONConstants.Result] != nil {
// handleResult(json: json)
// } else {
// handleMethod(json: json)
// }
//
// } catch let error as NSError {
// print("ERROR parsing JSON: ", error)
// }
// }
func handleResult(json: [String: Any]) {
let result: [String: Any] = json[JSONConstants.Result] as! [String: Any]
if result[JSONConstants.SdpAnswer] != nil {
saveAnswer(json: result)
} else if result[JSONConstants.SessionId] != nil {
if result[JSONConstants.Value] != nil {
let value = result[JSONConstants.Value] as! [[String:Any]]
if !value.isEmpty {
addParticipantsAlreadyInRoom(result: result)
}
self.userId = result[JSONConstants.Id] as? String
for var iceCandidate in iceCandidatesParams! {
iceCandidate["endpointName"] = self.userId
sendJson(method: "onIceCandidate", params: iceCandidate)
}
}
} else if result[JSONConstants.Value] != nil {
print("pong")
} else {
print("Unrecognized")
}
}
func addParticipantsAlreadyInRoom(result: [String: Any]) {
let values = result[JSONConstants.Value] as! [[String: Any]]
for participant in values {
print(participant[JSONConstants.Id]!)
self.remoteParticipantId = participant[JSONConstants.Id]! as? String
let remoteParticipant = RemoteParticipant()
remoteParticipant.id = participant[JSONConstants.Id] as? String
let metadataString = participant[JSONConstants.Metadata] as! String
let data = metadataString.data(using: .utf8)!
do {
if let metadata = try JSONSerialization.jsonObject(with: data, options : .allowFragments) as? Dictionary<String,Any>
{
remoteParticipant.participantName = metadata["clientData"] as? String
}
} catch let error as NSError {
print(error)
}
self.participants[remoteParticipant.id!] = remoteParticipant
self.peersManager.createRemotePeerConnection(remoteParticipant: remoteParticipant)
let mandatoryConstraints = ["OfferToReceiveAudio": "true", "OfferToReceiveVideo": "true"]
let sdpConstraints = RTCMediaConstraints(mandatoryConstraints: mandatoryConstraints, optionalConstraints: nil)
remoteParticipant.peerConnection!.offer(for: sdpConstraints, completionHandler: {(sessionDescription, error) in
print("Remote Offer: " + error.debugDescription)
self.participants[remoteParticipant.id!]!.peerConnection!.setLocalDescription(sessionDescription!, completionHandler: {(error) in
print("Remote Peer Local Description set " + error.debugDescription)
})
var remoteOfferParams: [String:String] = [:]
remoteOfferParams["sdpOffer"] = sessionDescription!.sdp
remoteOfferParams["sender"] = self.remoteParticipantId! + "_CAMERA"
self.sendJson(method: "receiveVideoFrom", params: remoteOfferParams)
})
self.peersManager.remotePeer!.delegate = self.peersManager
}
}
func saveAnswer(json: [String:Any]) {
let sessionDescription = RTCSessionDescription(type: RTCSdpType.answer, sdp: json["sdpAnswer"] as! String)
if localPeer == nil {
self.localPeer = self.peersManager.localPeer
}
if (localPeer!.remoteDescription != nil) {
participants[remoteParticipantId!]!.peerConnection!.setRemoteDescription(sessionDescription, completionHandler: {(error) in
print("Remote Peer Remote Description set: " + error.debugDescription)
if self.peersManager.remoteStreams.count >= self.participants.count {
DispatchQueue.main.async {
print("Count: " + self.participants.count.description)
if UIDevice().userInterfaceIdiom == .phone && UIScreen.main.nativeBounds.height == 2436 {
let renderer = RTCEAGLVideoView(frame: self.views[self.participants.count-1].frame)
let videoTrack = self.peersManager.remoteStreams[self.participants.count-1].videoTracks[0]
videoTrack.add(renderer)
// Add the view and name to the first free space available
var index = 0
while (index < 2 && !(self.names[index].text?.isEmpty)!) {
index += 1
}
if index < 2 {
self.names[index].text = self.participants[self.remoteParticipantId!]?.participantName
self.names[index].backgroundColor = UIColor.black
self.names[index].textColor = UIColor.white
self.embedView(renderer, into: self.views[index])
self.participants[self.remoteParticipantId!]?.index = index
self.views[index].bringSubview(toFront: self.names[index])
}
}else
{
#if arch(arm64)
let renderer = RTCMTLVideoView(frame: self.views[self.participants.count-1].frame)
#else
let renderer = RTCEAGLVideoView(frame: self.views[self.participants.count-1].frame)
#endif
let videoTrack = self.peersManager.remoteStreams[self.participants.count-1].videoTracks[0]
videoTrack.add(renderer)
// Add the view and name to the first free space available
var index = 0
while (index < 2 && !(self.names[index].text?.isEmpty)!) {
index += 1
}
if index < 2 {
self.names[index].text = self.participants[self.remoteParticipantId!]?.participantName
self.names[index].backgroundColor = UIColor.black
self.names[index].textColor = UIColor.white
self.embedView(renderer, into: self.views[index])
self.participants[self.remoteParticipantId!]?.index = index
self.views[index].bringSubview(toFront: self.names[index])
}
}
}
}
})
} else {
localPeer!.setRemoteDescription(sessionDescription, completionHandler: {(error) in
print("Local Peer Remote Description set: " + error.debugDescription)
})
}
}
func handleMethod(json: Dictionary<String,Any>) {
if json[JSONConstants.Params] != nil {
let method = json[JSONConstants.Method] as! String
let params = json[JSONConstants.Params] as! Dictionary<String, Any>
switch method {
case JSONConstants.IceCandidate:
iceCandidateMethod(params: params)
case JSONConstants.ParticipantJoined:
participantJoinedMethod(params: params)
case JSONConstants.ParticipantPublished:
participantPublished(params: params)
case JSONConstants.ParticipantLeft:
participantLeft(params: params)
default:
print("Error handleMethod, " + "method '" + method + "' is not implemented")
}
}
}
func iceCandidateMethod(params: Dictionary<String, Any>) {
// if (params["endpointName"] as? String == userId) {
// saveIceCandidate(json: params, endPointName: nil)
// } else {
// saveIceCandidate(json: params, endPointName: params["endpointName"] as? String)
// }
DispatchQueue.main.async {
if params["senderConnectionId"] != nil {
self.key = "senderConnectionId"
} else {
self.key = "endpointName"
}
if (params[self.key ?? ""] as? String == self.userId) {
self.saveIceCandidate(json: params, endPointName: params["endpointName"] as? String)
} else {
self.saveIceCandidate(json: params, endPointName: params[self.key ?? ""] as? String)
}
}
}
// func websocketDidReceiveData(socket: WebSocketClient, data: Data) {
// print("Received data: " + data.description)
// }
func participantJoinedMethod(params: Dictionary<String, Any>) {
let remoteParticipant = RemoteParticipant()
remoteParticipant.id = params[JSONConstants.Id] as? String
self.participants[params[JSONConstants.Id] as! String] = remoteParticipant
let metadataString = params[JSONConstants.Metadata] as! String
let data = metadataString.data(using: .utf8)!
do {
if let metadata = try JSONSerialization.jsonObject(with: data, options : .allowFragments) as? Dictionary<String,Any>
{
remoteParticipant.participantName = metadata["clientData"] as? String
self.peersManager.createRemotePeerConnection(remoteParticipant: remoteParticipant)
} else {
print("bad json")
}
} catch let error as NSError {
print(error)
}
participantPublished(params: params)
}
func participantPublished(params: Dictionary<String, Any>) {
self.remoteParticipantId = params[JSONConstants.Id] as? String
print("ID: " + remoteParticipantId!)
let remoteParticipantPublished = participants[remoteParticipantId!]!
let mandatoryConstraints = ["OfferToReceiveAudio": "true", "OfferToReceiveVideo": "true"]
remoteParticipantPublished.peerConnection!.offer(for: RTCMediaConstraints.init(mandatoryConstraints: mandatoryConstraints, optionalConstraints: nil), completionHandler: { (sessionDescription, error) in
remoteParticipantPublished.peerConnection!.setLocalDescription(sessionDescription!, completionHandler: {(error) in
print("Remote Peer Local Description set")
})
var remoteOfferParams: [String: String] = [:]
remoteOfferParams["sdpOffer"] = sessionDescription!.description
remoteOfferParams["sender"] = remoteParticipantPublished.id! + "_webcam"
self.sendJson(method: "receiveVideoFrom", params: remoteOfferParams)
})
self.peersManager.remotePeer!.delegate = self.peersManager
}
func participantLeft(params: Dictionary<String, Any>) {
print("participants", participants)
print("params", params)
let participantId = params["connectionId"] as! String
participants[participantId]!.peerConnection!.close()
if UIDevice().userInterfaceIdiom == .phone && UIScreen.main.nativeBounds.height == 2436 {
let renderer = RTCEAGLVideoView(frame: self.views[0].frame)
//REMOVE VIEW
if(self.peersManager.remoteStreams.count > 0){
let videoTrack = self.peersManager.remoteStreams[0].videoTracks[0]
videoTrack.remove(renderer)
if let index = self.participants.keys.index(of: participantId) {
let i = participants.distance(from: participants.startIndex, to: index)
self.views[i].willRemoveSubview(renderer)
self.names[i].text = ""
self.names[i].backgroundColor = UIColor.clear
}
}
participants.removeValue(forKey: participantId)
}else
{
#if arch(arm64)
let renderer = RTCMTLVideoView(frame: self.views[0].frame)
#else
let renderer = RTCEAGLVideoView(frame: self.views[self.participants.count-1].frame)
#endif
//REMOVE VIEW
if(self.peersManager.remoteStreams.count > 0){
let videoTrack = self.peersManager.remoteStreams[0].videoTracks[0]
videoTrack.remove(renderer)
if let index = self.participants.keys.index(of: participantId) {
let i = participants.distance(from: participants.startIndex, to: index)
self.views[i].willRemoveSubview(renderer)
self.names[i].text = ""
self.names[i].backgroundColor = UIColor.clear
}
participants.removeValue(forKey: participantId)
}
}
}
func saveIceCandidate(json: Dictionary<String, Any>, endPointName: String?) {
let iceCandidate = RTCIceCandidate(sdp: json["candidate"] as! String, sdpMLineIndex: json["sdpMLineIndex"] as! Int32, sdpMid: json["sdpMid"] as? String)
if (endPointName == nil || participants[endPointName!] == nil) {
self.localPeer = self.peersManager.localPeer
self.localPeer!.add(iceCandidate)
} else {
participants[endPointName!]!.peerConnection!.add(iceCandidate)
}
}
func sendJson(method: String, params: [String: String]) {
let json: NSMutableDictionary = NSMutableDictionary()
json.setValue(method, forKey: JSONConstants.Method)
json.setValue(id, forKey: JSONConstants.Id)
id += 1
json.setValue(params, forKey: JSONConstants.Params)
json.setValue(JSON_RPCVERSION, forKey: JSONConstants.JsonRPC)
let jsonData: NSData
do {
jsonData = try JSONSerialization.data(withJSONObject: json, options: JSONSerialization.WritingOptions()) as NSData
let jsonString = NSString(data: jsonData as Data, encoding: String.Encoding.utf8.rawValue)! as String
print("Sending = \(jsonString)")
socket.write(string: jsonString)
} catch _ {
print ("JSON Failure")
}
}
func addIceCandidate(iceCandidateParams: [String: String]) {
iceCandidatesParams!.append(iceCandidateParams)
}
func embedView(_ view: UIView, into containerView: UIView) {
containerView.addSubview(view)
containerView.backgroundColor = UIColor.white.withAlphaComponent(0.8)
view.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
view.centerYAnchor.constraint(equalTo: containerView.centerYAnchor).isActive = true
}
}
Any One who has used this code, needed help here. I have Participant ID what is the way to get Audio Video streams, Do I need to make some connection(Peer connection)? or I will get these streams in the socket connection response only.

Swift / Apple Sign In - Type HASH256 has no member hash

Issue: "Type HASH256 has no member hash"
Background: Trying to implement Apple sign in with Firebase on Swift
Tried to resolve the issue with the following:
-all pods update
-import CommonCrypto + import CryptoKit
-clean build folder / build
The error is still present
// Adapted from https://auth0.com/docs/api-auth/tutorials/nonce#generate-a-cryptographically-random-nonce
private func randomNonceString(length: Int = 32) -> String {
precondition(length > 0)
let charset: Array<Character> =
Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
var result = ""
var remainingLength = length
while remainingLength > 0 {
let randoms: [UInt8] = (0 ..< 16).map { _ in
var random: UInt8 = 0
let errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random)
if errorCode != errSecSuccess {
fatalError("Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)")
}
return random
}
randoms.forEach { random in
if length == 0 {
return
}
if random < charset.count {
result.append(charset[Int(random)])
remainingLength -= 1
}
}
}
return result
}
//Start Apple's sign-in flow
// Unhashed nonce.
fileprivate var currentNonce: String?
#available(iOS 13, *)
func startSignInWithAppleFlow() {
let nonce = randomNonceString()
currentNonce = nonce
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
request.nonce = sha256(nonce)
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self as! ASAuthorizationControllerDelegate
authorizationController.presentationContextProvider = self as! ASAuthorizationControllerPresentationContextProviding
authorizationController.performRequests()
}
#available(iOS 13, *)
private func sha256(_ input: String) -> String {
let inputData = Data(input.utf8)
let hashedData = SHA256.hash(data: inputData)
let hashString = hashedData.compactMap {
return String(format: "%02x", $0)
}.joined()
return hashString
}
// func SHA256() -> String {
//
// let data = self.data(using: String.Encoding.utf8)
// let res = NSMutableData(length: Int(CC_SHA256_DIGEST_LENGTH))
// CC_SHA256(((data! as NSData)).bytes, CC_LONG(data!.count), res?.mutableBytes.assumingMemoryBound(to: UInt8.self))
// let hashedString = "\(res!)".replacingOccurrences(of: "", with: "").replacingOccurrences(of: " ", with: "")
// let badchar: CharacterSet = CharacterSet(charactersIn: "\"<\",\">\"")
// let cleanedstring: String = (hashedString.components(separatedBy: badchar) as NSArray).componentsJoined(by: "")
// return cleanedstring
//
// }
}
//Apple extension
#available(iOS 13.0, *)
extension AuthViewController: ASAuthorizationControllerDelegate {
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
guard let nonce = currentNonce else {
fatalError("Invalid state: A login callback was received, but no login request was sent.")
}
guard let appleIDToken = appleIDCredential.identityToken else {
print("Unable to fetch identity token")
return
}
guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
print("Unable to serialize token string from data: \(appleIDToken.debugDescription)")
return
}
// Initialize a Firebase credential.
let credential = OAuthProvider.credential(withProviderID: "apple.com",
idToken: idTokenString,
accessToken: nonce)
// Sign in with Firebase.
Auth.auth().signIn(with: credential) { (authResult, error) in
if (error != nil) {
// Error. If error.code == .MissingOrInvalidNonce, make sure
// you're sending the SHA256-hashed nonce as a hex string with
// your request to Apple.
print(error?.localizedDescription)
return
}
// User is signed in to Firebase with Apple.
// ...
}
}
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
// Handle error.
print("Sign in with Apple errored: \(error)")
}
}
Image of error
I encountered the same problem, I spend two days figured it out!
The reason is we mistaken installed 'CryptoKit' in our Podfile. which apple also has a build-in 'CryptoKit' for iOS version 13+.
Solution :
1.deleted pod ''CryptoKit' in our pod file.
2. pod install
after that, we will use apple build in 'CryptoKit' which has the build-in method hash.
This should work: add this outside of your class and then instead of request.nonce = sha256(nonce), type request.nonce = nonce.sha256()
extension String {
func sha256() -> String{
if let stringData = self.data(using: String.Encoding.utf8) {
return hexStringFromData(input: digest(input: stringData as NSData))
}
return ""
}
private func digest(input : NSData) -> NSData {
let digestLength = Int(CC_SHA256_DIGEST_LENGTH)
var hash = [UInt8](repeating: 0, count: digestLength)
CC_SHA256(input.bytes, UInt32(input.length), &hash)
return NSData(bytes: hash, length: digestLength)
}
private func hexStringFromData(input: NSData) -> String {
var bytes = [UInt8](repeating: 0, count: input.length)
input.getBytes(&bytes, length: input.length)
var hexString = ""
for byte in bytes {
hexString += String(format:"%02x", UInt8(byte))
}
return hexString
}
}
credit

keyNotFound(CodingKeys(stringValue: "coord", intValue: nil)

I am building a small swift weather app using the openweatherAPI and I am running into some issues trying to parse the JSON. I have used the following function to parse the get and parse the json.
Below is my weather data struct:
struct WeatherData: Codable {
let coord: Coord
let weather: [Weather]
let base: String
let main: Main
let visibility: Int
let wind: Wind
let clouds: Clouds
let dt: Int
let sys: Sys
let id: Int
let name: String
let cod: Int
}
struct Clouds: Codable {
let all: Int
}
struct Coord: Codable {
let lon, lat: Double
}
struct Main: Codable {
let temp: Double
let pressure, humidity: Int
let tempMin, tempMax: Double
enum CodingKeys: String, CodingKey {
case temp, pressure, humidity
case tempMin = "temp_min"
case tempMax = "temp_max"
}
}
struct Sys: Codable {
let type, id: Int
let message: Double
let country: String
let sunrise, sunset: Int
}
struct Weather: Codable {
let id: Int
let main, description, icon: String
}
struct Wind: Codable {
let speed: Double
let deg: Int
}
private func getWeatherData(url: String, parameters: [String : String]) {
let JsonURLString:[String: Any] = ["url": WEATHER_URL, "parameters": parameters]
print(JsonURLString)
let urlString = JsonURLString["url"] as? String
guard let url = URL(string: urlString!) else { return }
URLSession.shared.dataTask(with: url) { ( data, response, err ) in
DispatchQueue.main.sync {
if let err = err {
print("Failed to get data from url:", err)
return
}
guard let data = data else { return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let city = try decoder.decode(WeatherData.self, from: data)
self.weatherData.description = city.weather[0].description
self.weatherData.temperature = Int(city.main.temp - 273)
self.weatherData.city = city.name
self.weatherData.condition = city.weather[0].id
self.updateUIWeatherData()
} catch {
print(error)
self.cityLabel.text = "Connection issues"
}
}
}.resume()
}
The exact error I am getting is the following:
longitude = -0.1337, latitude = 51.50998
["parameters": ["lat": "51.50998", "long": "-0.1337", "appid": "xxxxxxxxxxxxxxxxxx"], "url": "https://api.openweathermap.org/data/2.5/weather"]
keyNotFound(CodingKeys(stringValue: "coord", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"coord\", intValue: nil) (\"coord\").", underlyingError: nil))
I have looked at the following example and don't see how this would apply. Any help would be appreciated.
Icon is not appearing. Here is my model:
import UIKit
class WeatherDataModel {
//Declare your model variables here
var temperature: Int = 0
var condition: Int = 0
var city: String = ""
var weatherIconName = ""
var description: String = ""
//This method turns a condition code into the name of the weather condition image
func updateWeatherIcon(condition: Int) -> String {
switch (condition) {
case 0...300 :
return "tstorm1"
case 301...500 :
return "light_rain"
case 501...600 :
return "shower3"
case 601...700 :
return "snow4"
case 701...771 :
return "fog"
case 772...799 :
return "tstorm3"
case 800 :
return "sunny"
case 801...804 :
return "cloudy2"
case 900...903, 905...1000 :
return "tstorm3"
case 903 :
return "snow5"
case 904 :
return "sunny"
default :
return "dunno"
}
}
}
I have added my own icons. I have added this in the do catch block.
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let city = try decoder.decode(WeatherData.self, from: data)
print(city)
self.weatherData.description = city.weather[0].description
self.weatherData.temperature = Int(city.main.temp - 273)
self.weatherData.city = city.name
self.weatherData.condition = city.weather[0].id
self.weatherData.weatherIconName = WeatherDataModel.updateWeatherIcon(self.weatherData.condition)
self.updateUIWeatherData()
} catch {
print(error)
self.cityLabel.text = "Connection issues"
}
The error I am getting this error now:
Instance member 'updateWeatherIcon' cannot be used on type 'WeatherDataModel'; did you mean to use a value of this type instead?
You are creating only the openweathermap URL but you ignore the parameters.
Use something like this for example URLComponents and URLQueryItem to build the URL query properly
private func getWeatherData(parameters: [String : String]) {
guard let lat = parameters["lat"],
let long = parameters["long"],
let appID = parameters["appid"] else { print("Invalid parameters"); return }
var urlComponents = URLComponents(string: "https://api.openweathermap.org/data/2.5/weather")!
let queryItems = [URLQueryItem(name: "lat", value: lat),
URLQueryItem(name: "lon", value: long),
URLQueryItem(name: "appid", value: appID)]
urlComponents.queryItems = queryItems
guard let url = urlComponents.url else { return }
URLSession.shared.dataTask(with: url) { ( data, response, err ) in
DispatchQueue.main.async { // never, never, never sync !!
if let err = err {
print("Failed to get data from url:", err)
return
}
guard let data = data else { return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let city = try decoder.decode(WeatherData.self, from: data)
print(city)
self.weatherData.description = city.weather[0].description
self.weatherData.temperature = Int(city.main.temp - 273)
self.weatherData.city = city.name
self.weatherData.condition = city.weather[0].id
self.updateUIWeatherData()
} catch {
print(error)
self.cityLabel.text = "Connection issues"
}
}
}.resume()
}
and pass only
["lat": "51.50998", "long": "-0.1337", "appid": "xxxxxxxxxxxxxxxxxx"]
as parameters.