1 // Copyright 2022-present 650 Industries. All rights reserved.
2 
3 import CommonCrypto
4 import ABI49_0_0ExpoModulesCore
5 
6 public class CryptoModule: Module {
definitionnull7   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("digest", digest)
21 
22     Function("randomUUID") {
23       UUID().uuidString.lowercased()
24     }
25   }
26 }
27 
getRandomBase64Stringnull28 private func getRandomBase64String(length: Int) throws -> String {
29   var bytes = [UInt8](repeating: 0, count: length)
30   let status = SecRandomCopyBytes(kSecRandomDefault, length, &bytes)
31 
32   guard status == errSecSuccess else {
33     throw FailedGeneratingRandomBytesException(status)
34   }
35   return Data(bytes).base64EncodedString()
36 }
37 
digestStringnull38 private func digestString(algorithm: DigestAlgorithm, str: String, options: DigestOptions) throws -> String {
39   guard let data = str.data(using: .utf8) else {
40     throw LossyConversionException()
41   }
42 
43   let length = Int(algorithm.digestLength)
44   var digest = [UInt8](repeating: 0, count: length)
45 
46   data.withUnsafeBytes { bytes in
47     let _ = algorithm.digest(bytes.baseAddress, UInt32(data.count), &digest)
48   }
49 
50   switch options.encoding {
51   case .hex:
52     return digest.reduce("") { $0 + String(format: "%02x", $1) }
53   case .base64:
54     return Data(digest).base64EncodedString()
55   }
56 }
57 
getRandomValuesnull58 private func getRandomValues(array: TypedArray) throws -> TypedArray {
59   let status = SecRandomCopyBytes(
60     kSecRandomDefault,
61     array.byteLength,
62     array.rawPointer
63   )
64 
65   guard status == errSecSuccess else {
66     throw FailedGeneratingRandomBytesException(status)
67   }
68   return array
69 }
70 
digestnull71 private func digest(algorithm: DigestAlgorithm, output: TypedArray, data: TypedArray) {
72   let length = Int(algorithm.digestLength)
73   let outputPtr = output.rawPointer.assumingMemoryBound(to: UInt8.self)
74   algorithm.digest(data.rawPointer, UInt32(data.byteLength), outputPtr)
75 }
76 
77 private class LossyConversionException: Exception {
78   override var reason: String {
79     "Unable to convert given string without losing some information"
80   }
81 }
82 
83 private class FailedGeneratingRandomBytesException: GenericException<OSStatus> {
84   override var reason: String {
85     "Generating random bytes has failed with OSStatus code: \(param)"
86   }
87 }
88