mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			110 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Swift
		
	
		
		
			
		
	
	
			110 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Swift
		
	
| 
											3 years ago
										 | // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. | ||
|  | 
 | ||
|  | import Foundation | ||
|  | import CryptoKit | ||
|  | import Curve25519Kit | ||
|  | 
 | ||
|  | public extension Digest { | ||
|  |     var bytes: [UInt8] { Array(makeIterator()) } | ||
|  |     var data: Data { Data(bytes) } | ||
|  |      | ||
|  |     var hexString: String { | ||
|  |         bytes.map { String(format: "%02X", $0) }.joined() | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | // MARK: - AES.GCM | ||
|  | 
 | ||
|  | public extension AES.GCM { | ||
|  |     static let ivSize: Int = 12 | ||
|  |      | ||
|  |     struct EncryptionResult { | ||
|  |         public let ciphertext: Data | ||
|  |         public let symmetricKey: Data | ||
|  |         public let ephemeralPublicKey: Data | ||
|  |     } | ||
|  | 
 | ||
|  |     enum Error: LocalizedError { | ||
|  |         case keyPairGenerationFailed | ||
|  |         case sharedSecretGenerationFailed | ||
|  | 
 | ||
|  |         public var errorDescription: String? { | ||
|  |             switch self { | ||
|  |                 case .keyPairGenerationFailed: return "Couldn't generate a key pair." | ||
|  |                 case .sharedSecretGenerationFailed: return "Couldn't generate a shared secret." | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     /// - Note: Sync. Don't call from the main thread. | ||
|  |     static func generateSymmetricKey(x25519PublicKey: Data, x25519PrivateKey: Data) throws -> Data { | ||
|  |         if Thread.isMainThread { | ||
|  |             #if DEBUG | ||
|  |             preconditionFailure("It's illegal to call encrypt(_:forSnode:) from the main thread.") | ||
|  |             #endif | ||
|  |         } | ||
|  |         guard let sharedSecret: Data = try? Curve25519.generateSharedSecret(fromPublicKey: x25519PublicKey, privateKey: x25519PrivateKey) else { | ||
|  |             throw Error.sharedSecretGenerationFailed | ||
|  |         } | ||
|  |         let salt = "LOKI" | ||
|  |          | ||
|  |         return Data( | ||
|  |             HMAC<SHA256>.authenticationCode( | ||
|  |                 for: sharedSecret, | ||
|  |                 using: SymmetricKey(data: salt.bytes) | ||
|  |             ) | ||
|  |         ) | ||
|  |     } | ||
|  | 
 | ||
|  |     /// - Note: Sync. Don't call from the main thread. | ||
|  |     static func decrypt(_ nonceAndCiphertext: Data, with symmetricKey: Data) throws -> Data { | ||
|  |         if Thread.isMainThread { | ||
|  |             #if DEBUG | ||
|  |             preconditionFailure("It's illegal to call decrypt(_:usingAESGCMWithSymmetricKey:) from the main thread.") | ||
|  |             #endif | ||
|  |         } | ||
|  |          | ||
|  |         return try AES.GCM.open( | ||
|  |             try AES.GCM.SealedBox(combined: nonceAndCiphertext), | ||
|  |             using: SymmetricKey(data: symmetricKey) | ||
|  |         ) | ||
|  |     } | ||
|  | 
 | ||
|  |     /// - Note: Sync. Don't call from the main thread. | ||
|  |     static func encrypt(_ plaintext: Data, with symmetricKey: Data) throws -> Data { | ||
|  |         if Thread.isMainThread { | ||
|  |             #if DEBUG | ||
|  |             preconditionFailure("It's illegal to call encrypt(_:usingAESGCMWithSymmetricKey:) from the main thread.") | ||
|  |             #endif | ||
|  |         } | ||
|  |          | ||
|  |         let nonceData: Data = try Randomness.generateRandomBytes(numberBytes: ivSize) | ||
|  |         let sealedData: AES.GCM.SealedBox = try AES.GCM.seal( | ||
|  |             plaintext, | ||
|  |             using: SymmetricKey(data: symmetricKey), | ||
|  |             nonce: try AES.GCM.Nonce(data: nonceData) | ||
|  |         ) | ||
|  |          | ||
|  |         guard let cipherText: Data = sealedData.combined else { | ||
|  |             throw GeneralError.keyGenerationFailed | ||
|  |         } | ||
|  |          | ||
|  |         return cipherText | ||
|  |     } | ||
|  | 
 | ||
|  |     /// - Note: Sync. Don't call from the main thread. | ||
|  |     static func encrypt(_ plaintext: Data, for hexEncodedX25519PublicKey: String) throws -> EncryptionResult { | ||
|  |         if Thread.isMainThread { | ||
|  |             #if DEBUG | ||
|  |             preconditionFailure("It's illegal to call encrypt(_:forSnode:) from the main thread.") | ||
|  |             #endif | ||
|  |         } | ||
|  |         let x25519PublicKey = Data(hex: hexEncodedX25519PublicKey) | ||
|  |         let ephemeralKeyPair = Curve25519.generateKeyPair() | ||
|  |         let symmetricKey = try generateSymmetricKey(x25519PublicKey: x25519PublicKey, x25519PrivateKey: ephemeralKeyPair.privateKey) | ||
|  |         let ciphertext = try encrypt(plaintext, with: Data(symmetricKey)) | ||
|  |          | ||
|  |         return EncryptionResult(ciphertext: ciphertext, symmetricKey: Data(symmetricKey), ephemeralPublicKey: ephemeralKeyPair.publicKey) | ||
|  |     } | ||
|  | } |