1 // Copyright 2022-present 650 Industries. All rights reserved.
2 
3 import CommonCrypto
4 import ExpoModulesCore
5 
6 public class CryptoModule: Module {
7   public func definition() -> ModuleDefinition {
8     Name("ExpoCrypto")
9 
10     AsyncFunction("digestStringAsync", digestString)
11 
12     Function("digestString", digestString)
13 
14     AsyncFunction("getRandomBase64StringAsync", getRandomBase64String)
15 
16     Function("getRandomBase64String", getRandomBase64String)
17   }
18 }
19 
20 private func getRandomBase64String(length: Int) throws -> String {
21   var bytes = [UInt8](repeating: 0, count: length)
22   let status = SecRandomCopyBytes(kSecRandomDefault, length, &bytes)
23 
24   guard status == errSecSuccess else {
25     throw FailedGeneratingRandomBytesException(status)
26   }
27   return Data(bytes).base64EncodedString()
28 }
29 
30 private func digestString(algorithm: DigestAlgorithm, str: String, options: DigestOptions) throws -> String {
31   guard let data = str.data(using: .utf8) else {
32     throw LossyConversionException()
33   }
34 
35   let length = Int(algorithm.digestLength)
36   var digest = [UInt8](repeating: 0, count: length)
37 
38   data.withUnsafeBytes { bytes in
39     let _ = algorithm.digest(bytes.baseAddress, UInt32(data.count), &digest)
40   }
41 
42   switch options.encoding {
43   case .hex:
44     return digest.reduce("") { $0 + String(format: "%02x", $1) }
45   case .base64:
46     return Data(digest).base64EncodedString()
47   }
48 }
49 
50 private class LossyConversionException: Exception {
51   override var reason: String {
52     "Unable to convert given string without losing some information"
53   }
54 }
55 
56 private class FailedGeneratingRandomBytesException: GenericException<OSStatus> {
57   override var reason: String {
58     "Generating random bytes has failed with OSStatus code: \(param)"
59   }
60 }
61