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     Function("getRandomValues", getRandomValues)
19 
20     Function("randomUUID") {
21       UUID().uuidString
22     }
23   }
24 }
25 
26 private func getRandomBase64String(length: Int) throws -> String {
27   var bytes = [UInt8](repeating: 0, count: length)
28   let status = SecRandomCopyBytes(kSecRandomDefault, length, &bytes)
29 
30   guard status == errSecSuccess else {
31     throw FailedGeneratingRandomBytesException(status)
32   }
33   return Data(bytes).base64EncodedString()
34 }
35 
36 private func digestString(algorithm: DigestAlgorithm, str: String, options: DigestOptions) throws -> String {
37   guard let data = str.data(using: .utf8) else {
38     throw LossyConversionException()
39   }
40 
41   let length = Int(algorithm.digestLength)
42   var digest = [UInt8](repeating: 0, count: length)
43 
44   data.withUnsafeBytes { bytes in
45     let _ = algorithm.digest(bytes.baseAddress, UInt32(data.count), &digest)
46   }
47 
48   switch options.encoding {
49   case .hex:
50     return digest.reduce("") { $0 + String(format: "%02x", $1) }
51   case .base64:
52     return Data(digest).base64EncodedString()
53   }
54 }
55 
56 private func getRandomValues(array: TypedArray) throws -> TypedArray {
57   let status = SecRandomCopyBytes(
58     kSecRandomDefault,
59     array.byteLength,
60     array.rawPointer
61   )
62 
63   guard status == errSecSuccess else {
64     throw FailedGeneratingRandomBytesException(status)
65   }
66   return array
67 }
68 
69 private class LossyConversionException: Exception {
70   override var reason: String {
71     "Unable to convert given string without losing some information"
72   }
73 }
74 
75 private class FailedGeneratingRandomBytesException: GenericException<OSStatus> {
76   override var reason: String {
77     "Generating random bytes has failed with OSStatus code: \(param)"
78   }
79 }
80