import Foundation enum HMACAlgorithm { case MD5, SHA1, SHA224, SHA256, SHA384, SHA512 func toCCHmacAlgorithm() -> CCHmacAlgorithm { var result: Int = 0 switch self { case .MD5: result = kCCHmacAlgMD5 case .SHA1: result = kCCHmacAlgSHA1 case .SHA224: result = kCCHmacAlgSHA224 case .SHA256: result = kCCHmacAlgSHA256 case .SHA384: result = kCCHmacAlgSHA384 case .SHA512: result = kCCHmacAlgSHA512 } return CCHmacAlgorithm(result) } func digestLength() -> Int { var result: CInt = 0 switch self { case .MD5: result = CC_MD5_DIGEST_LENGTH case .SHA1: result = CC_SHA1_DIGEST_LENGTH case .SHA224: result = CC_SHA224_DIGEST_LENGTH case .SHA256: result = CC_SHA256_DIGEST_LENGTH case .SHA384: result = CC_SHA384_DIGEST_LENGTH case .SHA512: result = CC_SHA512_DIGEST_LENGTH } return Int(result) } } extension String { func hmac(algorithm: HMACAlgorithm, key: String) -> String { let cKey = key.cString(using: String.Encoding.utf8) let cData = self.cString(using: String.Encoding.utf8) var result = [CUnsignedChar](repeating: 0, count: Int(algorithm.digestLength())) CCHmac(algorithm.toCCHmacAlgorithm(), cKey!, Int(strlen(cKey!)), cData!, Int(strlen(cData!)), &result) let hmacData:NSData = NSData(bytes: result, length: (Int(algorithm.digestLength()))) var bytes = [UInt8](repeating:0, count: hmacData.length) hmacData.getBytes(&bytes, length: hmacData.length) var hexString = "" for byte in bytes { hexString += String(format:"%02hhx", UInt8(byte)) } return hexString } } extension Data { struct HexEncodingOptions: OptionSet { let rawValue: Int static let upperCase = HexEncodingOptions(rawValue: 1 << 0) } func hexEncodedString(options: HexEncodingOptions = []) -> String { let hexDigits = Array((options.contains(.upperCase) ? "0123456789ABCDEF" : "0123456789abcdef").utf16) var chars: [unichar] = [] chars.reserveCapacity(2 * count) for byte in self { chars.append(hexDigits[Int(byte / 16)]) chars.append(hexDigits[Int(byte % 16)]) } return String(utf16CodeUnits: chars, count: chars.count) } } func pbkdf2(hash: CCPBKDFAlgorithm, password: String, salt: String, keyByteCount: Int, rounds: Int) -> String? { guard let passwordData = password.data(using: .utf8), let saltData = salt.data(using: .utf8) else { return nil } var derivedKeyData = Data(repeating: 0, count: keyByteCount) let derivedCount = derivedKeyData.count let derivationStatus: Int32 = derivedKeyData.withUnsafeMutableBytes { derivedKeyBytes in let keyBuffer: UnsafeMutablePointer = derivedKeyBytes.baseAddress!.assumingMemoryBound(to: UInt8.self) return saltData.withUnsafeBytes { saltBytes -> Int32 in let saltBuffer: UnsafePointer = saltBytes.baseAddress!.assumingMemoryBound(to: UInt8.self) return CCKeyDerivationPBKDF( CCPBKDFAlgorithm(kCCPBKDF2), password, passwordData.count, saltBuffer, saltData.count, hash, UInt32(rounds), keyBuffer, derivedCount) } } return derivationStatus == kCCSuccess ? derivedKeyData.hexEncodedString() : nil } @objc(LessPassModule) class LessPassModule: NSObject { @objc(createFingerprint:resolver:rejecter:) func createFingerprint(_ masterPassword: String, resolver resolve: RCTPromiseResolveBlock, rejecter reject:RCTPromiseRejectBlock) -> Void { resolve("".hmac(algorithm: HMACAlgorithm.SHA256, key: masterPassword)) } @objc(calcEntropy:withLogin:withMasterPassword:withCounter:resolver:rejecter:) func calcEntropy(_ site: String, withLogin login: String, withMasterPassword masterPassword: String, withCounter counter: String, resolver resolve: RCTPromiseResolveBlock, rejecter reject:RCTPromiseRejectBlock) -> Void { let salt = site + login + counter let r = pbkdf2(hash: CCPBKDFAlgorithm(kCCPRFHmacAlgSHA256), password: masterPassword, salt: salt, keyByteCount: 32, rounds: 100000) resolve(r) } @objc static func requiresMainQueueSetup() -> Bool { return false } }