Browse Source

Added more unit tests

Fixed a possible divide by zero error
Cleaned up some of the id blinding methods (ie. removing handling for impossible error states)
Added unit tests for the new Sodium methods (used for id blinding)
Added unit tests for some of the shared code
Added unit tests for the MessageSender+Encryption extension functions
Added unit tests for the MessageReceiver+Decryption extension functions
Updated the unit test key constants to be consistent with the SOGS auth-example keys for consistency
pull/592/head
Morgan Pretty 11 months ago
parent
commit
c44256b1d6
  1. 78
      Session.xcodeproj/project.pbxproj
  2. 2
      SessionMessagingKit/Common Networking/Request.swift
  3. 18
      SessionMessagingKit/Open Groups/OpenGroupManager.swift
  4. 47
      SessionMessagingKit/Open Groups/Types/SodiumProtocols.swift
  5. 35
      SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift
  6. 8
      SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift
  7. 12
      SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift
  8. 4
      SessionMessagingKit/Utilities/ContactUtilities.swift
  9. 32
      SessionMessagingKit/Utilities/Dependencies.swift
  10. 81
      SessionMessagingKit/Utilities/Sodium+Utilities.swift
  11. 20
      SessionMessagingKitTests/Common Networking/HeaderSpec.swift
  12. 32
      SessionMessagingKitTests/Common Networking/Models/FileUploadResponseSpec.swift
  13. 167
      SessionMessagingKitTests/Common Networking/RequestSpec.swift
  14. 48
      SessionMessagingKitTests/Contacts/BlindedIdMappingSpec.swift
  15. 385
      SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift
  16. 2
      SessionMessagingKitTests/Open Groups/Models/OpenGroupSpec.swift
  17. 2
      SessionMessagingKitTests/Open Groups/Models/ServerSpec.swift
  18. 14
      SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift
  19. 13
      SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift
  20. 500
      SessionMessagingKitTests/Sending & Receiving/MessageReceiverDecryptionSpec.swift
  21. 272
      SessionMessagingKitTests/Sending & Receiving/MessageSenderEncryptionSpec.swift
  22. 351
      SessionMessagingKitTests/Utilities/SodiumUtilitiesSpec.swift
  23. 10
      SessionMessagingKitTests/_TestUtilities/DependencyExtensions.swift
  24. 17
      SessionMessagingKitTests/_TestUtilities/MockBox.swift
  25. 1
      SessionMessagingKitTests/_TestUtilities/MockSign.swift
  26. 11
      SessionMessagingKitTests/_TestUtilities/MockSodium.swift
  27. 10
      SessionMessagingKitTests/_TestUtilities/OGMDependencyExtensions.swift
  28. 1
      SessionUtilitiesKit/General/String+Encoding.swift
  29. 6
      SessionUtilitiesKitTests/General/SessionIdSpec.swift
  30. 12
      SharedTest/TestConstants.swift

78
Session.xcodeproj/project.pbxproj

@ -788,6 +788,15 @@
FD078E6027E2BB36000769AF /* MockIdentityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E5F27E2BB36000769AF /* MockIdentityManager.swift */; };
FD0BA51B27CD88EC00CC6805 /* BlindedIdMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0BA51A27CD88EC00CC6805 /* BlindedIdMapping.swift */; };
FD0BA51D27CDC34600CC6805 /* SOGSV4Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0BA51C27CDC34600CC6805 /* SOGSV4Migration.swift */; };
FD3C905C27E3FBEF00CD579F /* BatchRequestInfoSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C905B27E3FBEF00CD579F /* BatchRequestInfoSpec.swift */; };
FD3C906027E410F700CD579F /* FileUploadResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C905F27E410F700CD579F /* FileUploadResponseSpec.swift */; };
FD3C906227E411AF00CD579F /* HeaderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906127E411AF00CD579F /* HeaderSpec.swift */; };
FD3C906427E4122F00CD579F /* RequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906327E4122F00CD579F /* RequestSpec.swift */; };
FD3C906727E416AF00CD579F /* BlindedIdMappingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906627E416AF00CD579F /* BlindedIdMappingSpec.swift */; };
FD3C906A27E417CE00CD579F /* SodiumUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906927E417CE00CD579F /* SodiumUtilitiesSpec.swift */; };
FD3C906D27E43C4B00CD579F /* MessageSenderEncryptionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906C27E43C4B00CD579F /* MessageSenderEncryptionSpec.swift */; };
FD3C906F27E43E8700CD579F /* MockBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906E27E43E8700CD579F /* MockBox.swift */; };
FD3C907127E445E500CD579F /* MessageReceiverDecryptionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C907027E445E500CD579F /* MessageReceiverDecryptionSpec.swift */; };
FD5D200F27AA2B6000FEA984 /* MessageRequestResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5D200E27AA2B6000FEA984 /* MessageRequestResponse.swift */; };
FD5D201127AA331F00FEA984 /* ConfigurationMessage+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5D201027AA331F00FEA984 /* ConfigurationMessage+Convenience.swift */; };
FD5D201E27B0D87C00FEA984 /* SessionId.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5D201D27B0D87C00FEA984 /* SessionId.swift */; };
@ -1938,6 +1947,15 @@
FD078E5F27E2BB36000769AF /* MockIdentityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockIdentityManager.swift; sourceTree = "<group>"; };
FD0BA51A27CD88EC00CC6805 /* BlindedIdMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlindedIdMapping.swift; sourceTree = "<group>"; };
FD0BA51C27CDC34600CC6805 /* SOGSV4Migration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSV4Migration.swift; sourceTree = "<group>"; };
FD3C905B27E3FBEF00CD579F /* BatchRequestInfoSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchRequestInfoSpec.swift; sourceTree = "<group>"; };
FD3C905F27E410F700CD579F /* FileUploadResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUploadResponseSpec.swift; sourceTree = "<group>"; };
FD3C906127E411AF00CD579F /* HeaderSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderSpec.swift; sourceTree = "<group>"; };
FD3C906327E4122F00CD579F /* RequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestSpec.swift; sourceTree = "<group>"; };
FD3C906627E416AF00CD579F /* BlindedIdMappingSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlindedIdMappingSpec.swift; sourceTree = "<group>"; };
FD3C906927E417CE00CD579F /* SodiumUtilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SodiumUtilitiesSpec.swift; sourceTree = "<group>"; };
FD3C906C27E43C4B00CD579F /* MessageSenderEncryptionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSenderEncryptionSpec.swift; sourceTree = "<group>"; };
FD3C906E27E43E8700CD579F /* MockBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBox.swift; sourceTree = "<group>"; };
FD3C907027E445E500CD579F /* MessageReceiverDecryptionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageReceiverDecryptionSpec.swift; sourceTree = "<group>"; };
FD5D200E27AA2B6000FEA984 /* MessageRequestResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestResponse.swift; sourceTree = "<group>"; };
FD5D201027AA331F00FEA984 /* ConfigurationMessage+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConfigurationMessage+Convenience.swift"; sourceTree = "<group>"; };
FD5D201D27B0D87C00FEA984 /* SessionId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionId.swift; sourceTree = "<group>"; };
@ -3884,6 +3902,49 @@
path = Session;
sourceTree = "<group>";
};
FD3C905D27E410DB00CD579F /* Common Networking */ = {
isa = PBXGroup;
children = (
FD3C905E27E410EE00CD579F /* Models */,
FD3C906127E411AF00CD579F /* HeaderSpec.swift */,
FD3C906327E4122F00CD579F /* RequestSpec.swift */,
);
path = "Common Networking";
sourceTree = "<group>";
};
FD3C905E27E410EE00CD579F /* Models */ = {
isa = PBXGroup;
children = (
FD3C905F27E410F700CD579F /* FileUploadResponseSpec.swift */,
);
path = Models;
sourceTree = "<group>";
};
FD3C906527E416A200CD579F /* Contacts */ = {
isa = PBXGroup;
children = (
FD3C906627E416AF00CD579F /* BlindedIdMappingSpec.swift */,
);
path = Contacts;
sourceTree = "<group>";
};
FD3C906827E417B100CD579F /* Utilities */ = {
isa = PBXGroup;
children = (
FD3C906927E417CE00CD579F /* SodiumUtilitiesSpec.swift */,
);
path = Utilities;
sourceTree = "<group>";
};
FD3C906B27E43C2400CD579F /* Sending & Receiving */ = {
isa = PBXGroup;
children = (
FD3C906C27E43C4B00CD579F /* MessageSenderEncryptionSpec.swift */,
FD3C907027E445E500CD579F /* MessageReceiverDecryptionSpec.swift */,
);
path = "Sending & Receiving";
sourceTree = "<group>";
};
FD659ABE27A7648200F12C02 /* Message Requests */ = {
isa = PBXGroup;
children = (
@ -3924,6 +3985,7 @@
children = (
FD83B9C227CF33F7005E1583 /* ServerSpec.swift */,
FD83B9C427CF3E2A005E1583 /* OpenGroupSpec.swift */,
FD3C905B27E3FBEF00CD579F /* BatchRequestInfoSpec.swift */,
FD83B9C627CF3F10005E1583 /* CapabilitiesSpec.swift */,
FDC2908627D7047F005DAE71 /* RoomSpec.swift */,
FDC2908827D70656005DAE71 /* RoomPollInfoSpec.swift */,
@ -4038,7 +4100,11 @@
isa = PBXGroup;
children = (
FDC4389B27BA01E300C60D73 /* _TestUtilities */,
FD3C905D27E410DB00CD579F /* Common Networking */,
FD3C906527E416A200CD579F /* Contacts */,
FD3C906B27E43C2400CD579F /* Sending & Receiving */,
FDC4389827BA001800C60D73 /* Open Groups */,
FD3C906827E417B100CD579F /* Utilities */,
);
path = SessionMessagingKitTests;
sourceTree = "<group>";
@ -4061,9 +4127,10 @@
FD078E5F27E2BB36000769AF /* MockIdentityManager.swift */,
FDC4389C27BA01F000C60D73 /* MockStorage.swift */,
FD859EF327C2F49200510D0C /* MockSodium.swift */,
FD3C906E27E43E8700CD579F /* MockBox.swift */,
FD859EF927C2F5C500510D0C /* MockGenericHash.swift */,
FD859EF527C2F52C00510D0C /* MockSign.swift */,
FD859EF727C2F58900510D0C /* MockAeadXChaCha20Poly1305Ietf.swift */,
FD859EF927C2F5C500510D0C /* MockGenericHash.swift */,
FD859EFB27C2F60700510D0C /* MockEd25519.swift */,
FD078E5927E29F09000769AF /* MockNonce16Generator.swift */,
FD078E5B27E29F78000769AF /* MockNonce24Generator.swift */,
@ -5673,20 +5740,25 @@
buildActionMask = 2147483647;
files = (
FDC290AC27DB0B1C005DAE71 /* MockedExtensions.swift in Sources */,
FD3C905C27E3FBEF00CD579F /* BatchRequestInfoSpec.swift in Sources */,
FD078E5827E1B831000769AF /* TestIncomingMessage.swift in Sources */,
FD859EFA27C2F5C500510D0C /* MockGenericHash.swift in Sources */,
FDC2909427D710B4005DAE71 /* SOGSEndpointSpec.swift in Sources */,
FDC290AF27DFEE97005DAE71 /* TestTransaction.swift in Sources */,
FDC290B327DFF9F5005DAE71 /* TestOnionRequestAPI.swift in Sources */,
FDC2909127D709CA005DAE71 /* SOGSMessageSpec.swift in Sources */,
FD3C906A27E417CE00CD579F /* SodiumUtilitiesSpec.swift in Sources */,
FD078E6027E2BB36000769AF /* MockIdentityManager.swift in Sources */,
FD3C907127E445E500CD579F /* MessageReceiverDecryptionSpec.swift in Sources */,
FDC2909627D71252005DAE71 /* SOGSErrorSpec.swift in Sources */,
FDC2908727D7047F005DAE71 /* RoomSpec.swift in Sources */,
FD078E4F27E175F1000769AF /* DependencyExtensions.swift in Sources */,
FDC2909C27D713D2005DAE71 /* SodiumProtocolsSpec.swift in Sources */,
FD3C906027E410F700CD579F /* FileUploadResponseSpec.swift in Sources */,
FD83B9C327CF33F7005E1583 /* ServerSpec.swift in Sources */,
FD83B9C727CF3F10005E1583 /* CapabilitiesSpec.swift in Sources */,
FDC2909A27D71376005DAE71 /* NonceGeneratorSpec.swift in Sources */,
FD3C906427E4122F00CD579F /* RequestSpec.swift in Sources */,
FD078E4827E02561000769AF /* CommonMockedExtensions.swift in Sources */,
FDC290A027D85826005DAE71 /* TestContactThread.swift in Sources */,
FD859EF827C2F58900510D0C /* MockAeadXChaCha20Poly1305Ietf.swift in Sources */,
@ -5697,20 +5769,24 @@
FD859EFC27C2F60700510D0C /* MockEd25519.swift in Sources */,
FDC290A627D860CE005DAE71 /* Mock.swift in Sources */,
FD83B9C027CF2294005E1583 /* TestConstants.swift in Sources */,
FD3C906F27E43E8700CD579F /* MockBox.swift in Sources */,
FDC4389A27BA002500C60D73 /* OpenGroupAPISpec.swift in Sources */,
FD83B9C527CF3E2A005E1583 /* OpenGroupSpec.swift in Sources */,
FDC2908B27D707F3005DAE71 /* SendMessageRequestSpec.swift in Sources */,
FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
FD3C906227E411AF00CD579F /* HeaderSpec.swift in Sources */,
FDC290B727E00FDB005DAE71 /* TestGroupThread.swift in Sources */,
FDC2908F27D70938005DAE71 /* SendDirectMessageRequestSpec.swift in Sources */,
FDC438BD27BB2AB400C60D73 /* Mockable.swift in Sources */,
FD859EF627C2F52C00510D0C /* MockSign.swift in Sources */,
FDC2908927D70656005DAE71 /* RoomPollInfoSpec.swift in Sources */,
FD078E5A27E29F09000769AF /* MockNonce16Generator.swift in Sources */,
FD3C906D27E43C4B00CD579F /* MessageSenderEncryptionSpec.swift in Sources */,
FDC2908D27D70905005DAE71 /* UpdateMessageRequestSpec.swift in Sources */,
FDC290A227D85890005DAE71 /* TestInteraction.swift in Sources */,
FD078E5427E197CA000769AF /* OpenGroupManagerSpec.swift in Sources */,
FDC4389D27BA01F000C60D73 /* MockStorage.swift in Sources */,
FD3C906727E416AF00CD579F /* BlindedIdMappingSpec.swift in Sources */,
FD83B9D227D59495005E1583 /* MockUserDefaults.swift in Sources */,
FD078E5C27E29F78000769AF /* MockNonce24Generator.swift in Sources */,
);

2
SessionMessagingKit/Common Networking/Request.swift

@ -96,3 +96,5 @@ struct Request<T: Encodable, Endpoint: EndpointType> {
return urlRequest
}
}
extension Request: Equatable where T: Equatable {}

18
SessionMessagingKit/Open Groups/OpenGroupManager.swift

@ -21,12 +21,6 @@ public protocol OGMCacheType {
func getTimeSinceLastOpen(using dependencies: Dependencies) -> TimeInterval
}
extension OGMCacheType {
func getTimeSinceLastOpen() -> TimeInterval {
return getTimeSinceLastOpen(using: Dependencies())
}
}
// MARK: - OpenGroupManager
@objc(SNOpenGroupManager)
@ -49,7 +43,7 @@ public final class OpenGroupManager: NSObject {
public var timeSinceLastPoll: [String: TimeInterval] = [:]
fileprivate var _timeSinceLastOpen: TimeInterval?
public func getTimeSinceLastOpen(using dependencies: Dependencies = Dependencies()) -> TimeInterval {
public func getTimeSinceLastOpen(using dependencies: Dependencies) -> TimeInterval {
if let storedTimeSinceLastOpen: TimeInterval = _timeSinceLastOpen {
return storedTimeSinceLastOpen
}
@ -702,9 +696,10 @@ extension OpenGroupManager {
identityManager: IdentityManagerProtocol? = nil,
storage: SessionMessagingKitStorageProtocol? = nil,
sodium: SodiumType? = nil,
aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil,
sign: SignType? = nil,
box: BoxType? = nil,
genericHash: GenericHashType? = nil,
sign: SignType? = nil,
aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil,
ed25519: Ed25519Type? = nil,
nonceGenerator16: NonceGenerator16ByteType? = nil,
nonceGenerator24: NonceGenerator24ByteType? = nil,
@ -718,9 +713,10 @@ extension OpenGroupManager {
identityManager: identityManager,
storage: storage,
sodium: sodium,
aeadXChaCha20Poly1305Ietf: aeadXChaCha20Poly1305Ietf,
sign: sign,
box: box,
genericHash: genericHash,
sign: sign,
aeadXChaCha20Poly1305Ietf: aeadXChaCha20Poly1305Ietf,
ed25519: ed25519,
nonceGenerator16: nonceGenerator16,
nonceGenerator24: nonceGenerator24,

47
SessionMessagingKit/Open Groups/Types/SodiumProtocols.swift

@ -5,18 +5,19 @@ import Sodium
import Curve25519Kit
public protocol SodiumType {
func getBox() -> BoxType
func getGenericHash() -> GenericHashType
func getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType
func getSign() -> SignType
func getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType
func generateBlindingFactor(serverPublicKey: String) -> Bytes?
func generateBlindingFactor(serverPublicKey: String, genericHash: GenericHashType) -> Bytes?
func blindedKeyPair(serverPublicKey: String, edKeyPair: Box.KeyPair, genericHash: GenericHashType) -> Box.KeyPair?
func sogsSignature(message: Bytes, secretKey: Bytes, blindedSecretKey ka: Bytes, blindedPublicKey kA: Bytes) -> Bytes?
func combineKeys(lhsKeyBytes: Bytes, rhsKeyBytes: Bytes) -> Bytes?
func sharedBlindedEncryptionKey(secretKey a: Bytes, otherBlindedPublicKey: Bytes, fromBlindedPublicKey kA: Bytes, toBlindedPublicKey kB: Bytes, genericHash: GenericHashType) -> Bytes?
func sessionId(_ sessionId: String, matchesBlindedId blindedSessionId: String, serverPublicKey: String) -> Bool
func sessionId(_ sessionId: String, matchesBlindedId blindedSessionId: String, serverPublicKey: String, genericHash: GenericHashType) -> Bool
}
public protocol AeadXChaCha20Poly1305IetfType {
@ -32,12 +33,9 @@ public protocol Ed25519Type {
func verifySignature(_ signature: Data, publicKey: Data, data: Data) throws -> Bool
}
public protocol SignType {
var PublicKeyBytes: Int { get }
func toX25519(ed25519PublicKey: Bytes) -> Bytes?
func signature(message: Bytes, secretKey: Bytes) -> Bytes?
func verify(message: Bytes, publicKey: Bytes, signature: Bytes) -> Bool
public protocol BoxType {
func seal(message: Bytes, recipientPublicKey: Bytes) -> Bytes?
func open(anonymousCipherText: Bytes, recipientPublicKey: Bytes, recipientSecretKey: Bytes) -> Bytes?
}
public protocol GenericHashType {
@ -46,8 +44,25 @@ public protocol GenericHashType {
func hashSaltPersonal(message: Bytes, outputLength: Int, key: Bytes?, salt: Bytes, personal: Bytes) -> Bytes?
}
public protocol SignType {
var Bytes: Int { get }
var PublicKeyBytes: Int { get }
func toX25519(ed25519PublicKey: Bytes) -> Bytes?
func signature(message: Bytes, secretKey: Bytes) -> Bytes?
func verify(message: Bytes, publicKey: Bytes, signature: Bytes) -> Bool
}
// MARK: - Default Values
extension GenericHashType {
func hash(message: Bytes) -> Bytes? { return hash(message: message, key: nil) }
func hashSaltPersonal(message: Bytes, outputLength: Int, salt: Bytes, personal: Bytes) -> Bytes? {
return hashSaltPersonal(message: message, outputLength: outputLength, key: nil, salt: salt, personal: personal)
}
}
extension AeadXChaCha20Poly1305IetfType {
func encrypt(message: Bytes, secretKey: Bytes, nonce: Bytes) -> Bytes? {
return encrypt(message: message, secretKey: secretKey, nonce: nonce, additionalData: nil)
@ -58,17 +73,10 @@ extension AeadXChaCha20Poly1305IetfType {
}
}
extension GenericHashType {
func hash(message: Bytes) -> Bytes? { return hash(message: message, key: nil) }
func hashSaltPersonal(message: Bytes, outputLength: Int, salt: Bytes, personal: Bytes) -> Bytes? {
return hashSaltPersonal(message: message, outputLength: outputLength, key: nil, salt: salt, personal: personal)
}
}
// MARK: - Conformance
extension Sodium: SodiumType {
public func getBox() -> BoxType { return box }
public func getGenericHash() -> GenericHashType { return genericHash }
public func getSign() -> SignType { return sign }
public func getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType { return aead.xchacha20poly1305ietf }
@ -78,9 +86,10 @@ extension Sodium: SodiumType {
}
}
extension Aead.XChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType {}
extension Sign: SignType {}
extension Box: BoxType {}
extension GenericHash: GenericHashType {}
extension Sign: SignType {}
extension Aead.XChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType {}
struct Ed25519Wrapper: Ed25519Type {
func sign(data: Bytes, keyPair: ECKeyPair) throws -> Bytes? {

35
SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift

@ -3,27 +3,40 @@ import SessionUtilitiesKit
import Sodium
extension MessageReceiver {
internal static func decryptWithSessionProtocol(ciphertext: Data, using x25519KeyPair: ECKeyPair) throws -> (plaintext: Data, senderX25519PublicKey: String) {
internal static func decryptWithSessionProtocol(ciphertext: Data, using x25519KeyPair: ECKeyPair, dependencies: Dependencies = Dependencies()) throws -> (plaintext: Data, senderX25519PublicKey: String) {
let recipientX25519PrivateKey = x25519KeyPair.privateKey
let recipientX25519PublicKey = Data(hex: x25519KeyPair.hexEncodedPublicKey.removingIdPrefixIfNeeded())
let sodium = Sodium()
let signatureSize = sodium.sign.Bytes
let ed25519PublicKeySize = sodium.sign.PublicKeyBytes
let signatureSize = dependencies.sign.Bytes
let ed25519PublicKeySize = dependencies.sign.PublicKeyBytes
// 1. ) Decrypt the message
guard let plaintextWithMetadata = sodium.box.open(anonymousCipherText: Bytes(ciphertext), recipientPublicKey: Box.PublicKey(Bytes(recipientX25519PublicKey)),
recipientSecretKey: Bytes(recipientX25519PrivateKey)), plaintextWithMetadata.count > (signatureSize + ed25519PublicKeySize) else { throw Error.decryptionFailed }
guard
let plaintextWithMetadata = dependencies.box.open(
anonymousCipherText: Bytes(ciphertext),
recipientPublicKey: Box.PublicKey(Bytes(recipientX25519PublicKey)),
recipientSecretKey: Bytes(recipientX25519PrivateKey)
),
plaintextWithMetadata.count > (signatureSize + ed25519PublicKeySize)
else {
throw Error.decryptionFailed
}
// 2. ) Get the message parts
let signature = Bytes(plaintextWithMetadata[plaintextWithMetadata.count - signatureSize ..< plaintextWithMetadata.count])
let senderED25519PublicKey = Bytes(plaintextWithMetadata[plaintextWithMetadata.count - (signatureSize + ed25519PublicKeySize) ..< plaintextWithMetadata.count - signatureSize])
let plaintext = Bytes(plaintextWithMetadata[0..<plaintextWithMetadata.count - (signatureSize + ed25519PublicKeySize)])
// 3. ) Verify the signature
let verificationData = plaintext + senderED25519PublicKey + recipientX25519PublicKey
let isValid = sodium.sign.verify(message: verificationData, publicKey: senderED25519PublicKey, signature: signature)
guard isValid else { throw Error.invalidSignature }
guard dependencies.sign.verify(message: verificationData, publicKey: senderED25519PublicKey, signature: signature) else {
throw Error.invalidSignature
}
// 4. ) Get the sender's X25519 public key
guard let senderX25519PublicKey = sodium.sign.toX25519(ed25519PublicKey: senderED25519PublicKey) else { throw Error.decryptionFailed }
guard let senderX25519PublicKey = dependencies.sign.toX25519(ed25519PublicKey: senderED25519PublicKey) else {
throw Error.decryptionFailed
}
return (Data(plaintext), SessionId(.standard, publicKey: senderX25519PublicKey).hexString)
}
@ -72,7 +85,7 @@ extension MessageReceiver {
])
/// Verify that the inner sender_edpk (A) yields the same outer kA we got with the message
guard let blindingFactor: Bytes = dependencies.sodium.generateBlindingFactor(serverPublicKey: openGroupPublicKey) else {
guard let blindingFactor: Bytes = dependencies.sodium.generateBlindingFactor(serverPublicKey: openGroupPublicKey, genericHash: dependencies.genericHash) else {
throw Error.invalidSignature
}
guard let sharedSecret: Bytes = dependencies.sodium.combineKeys(lhsKeyBytes: blindingFactor, rhsKeyBytes: sender_edpk) else {

8
SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift

@ -18,7 +18,7 @@ extension MessageReceiver {
case let message as ExpirationTimerUpdate: handleExpirationTimerUpdate(message, using: transaction)
case let message as ConfigurationMessage: handleConfigurationMessage(message, using: transaction)
case let message as UnsendRequest: handleUnsendRequest(message, using: transaction)
case let message as MessageRequestResponse: handleMessageRequestResponse(message, using: transaction)
case let message as MessageRequestResponse: handleMessageRequestResponse(message, using: transaction, dependencies: dependencies)
case let message as VisibleMessage: try handleVisibleMessage(message, associatedWithProto: proto, openGroupID: openGroupID, isBackgroundPoll: isBackgroundPoll, using: transaction, dependencies: dependencies)
default: fatalError()
}
@ -839,7 +839,7 @@ extension MessageReceiver {
}
}
public static func handleMessageRequestResponse(_ message: MessageRequestResponse, using transaction: Any) {
public static func handleMessageRequestResponse(_ message: MessageRequestResponse, using transaction: Any, dependencies: Dependencies) {
let userPublicKey = getUserHexEncodedPublicKey()
var hadBlindedContact: Bool = false
var blindedThreadIds: [String] = []
@ -870,7 +870,7 @@ extension MessageReceiver {
// If the sessionId matches the blindedId then this thread needs to be converted to an un-blinded thread
guard let serverPublicKey: String = blindedThread.originalOpenGroupPublicKey else { continue }
guard Sodium().sessionId(senderId, matchesBlindedId: blindedId, serverPublicKey: serverPublicKey) else { continue }
guard dependencies.sodium.sessionId(senderId, matchesBlindedId: blindedId, serverPublicKey: serverPublicKey, genericHash: dependencies.genericHash) else { continue }
guard let blindedThreadId: String = blindedThread.uniqueId else { continue }
guard let view: YapDatabaseAutoViewTransaction = transaction.ext(TSMessageDatabaseViewExtensionName) as? YapDatabaseAutoViewTransaction else {
continue
@ -878,7 +878,7 @@ extension MessageReceiver {
// Cache the mapping
let mapping: BlindedIdMapping = BlindedIdMapping(blindedId: blindedId, sessionId: senderId, serverPublicKey: serverPublicKey)
Storage.shared.cacheBlindedIdMapping(mapping, using: transaction)
dependencies.storage.cacheBlindedIdMapping(mapping, using: transaction)
// Flag that we had a blinded contact and add the `blindedThreadId` to an array so we can remove
// them at the end of processing

12
SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift

@ -2,22 +2,20 @@ import SessionUtilitiesKit
import Sodium
extension MessageSender {
internal static func encryptWithSessionProtocol(_ plaintext: Data, for recipientHexEncodedX25519PublicKey: String) throws -> Data {
guard let userED25519KeyPair = SNMessagingKitConfiguration.shared.storage.getUserED25519KeyPair() else {
internal static func encryptWithSessionProtocol(_ plaintext: Data, for recipientHexEncodedX25519PublicKey: String, using dependencies: Dependencies = Dependencies()) throws -> Data {
guard let userED25519KeyPair = dependencies.storage.getUserED25519KeyPair() else {
throw Error.noUserED25519KeyPair
}
let recipientX25519PublicKey = Data(hex: recipientHexEncodedX25519PublicKey.removingIdPrefixIfNeeded())
let sodium = Sodium()
let verificationData = plaintext + Data(userED25519KeyPair.publicKey) + recipientX25519PublicKey
guard let signature = sodium.sign.signature(message: Bytes(verificationData), secretKey: userED25519KeyPair.secretKey) else {
guard let signature = dependencies.sign.signature(message: Bytes(verificationData), secretKey: userED25519KeyPair.secretKey) else {
throw Error.signingFailed
}
let plaintextWithMetadata = plaintext + Data(userED25519KeyPair.publicKey) + Data(signature)
guard let ciphertext = sodium.box.seal(message: Bytes(plaintextWithMetadata), recipientPublicKey: Bytes(recipientX25519PublicKey)) else {
guard let ciphertext = dependencies.box.seal(message: Bytes(plaintextWithMetadata), recipientPublicKey: Bytes(recipientX25519PublicKey)) else {
throw Error.encryptionFailed
}
@ -26,7 +24,7 @@ extension MessageSender {
internal static func encryptWithSessionBlindingProtocol(_ plaintext: Data, for recipientBlindedId: String, openGroupPublicKey: String, using dependencies: Dependencies = Dependencies()) throws -> Data {
guard SessionId.Prefix(from: recipientBlindedId) == .blinded else { throw Error.signingFailed }
guard let userEd25519KeyPair = SNMessagingKitConfiguration.shared.storage.getUserED25519KeyPair() else {
guard let userEd25519KeyPair = dependencies.storage.getUserED25519KeyPair() else {
throw Error.noUserED25519KeyPair
}
guard let blindedKeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: openGroupPublicKey, edKeyPair: userEd25519KeyPair, genericHash: dependencies.genericHash) else {

4
SessionMessagingKit/Utilities/ContactUtilities.swift

@ -73,7 +73,7 @@ public enum ContactUtilities {
// Then we try loop through all approved contact threads to see if one of those contacts can be blinded to match
ContactUtilities.enumerateApprovedContactThreads(using: transaction) { contactThread, contact, stop in
guard dependencies.sodium.sessionId(contact.sessionID, matchesBlindedId: blindedId, serverPublicKey: serverPublicKey) else {
guard dependencies.sodium.sessionId(contact.sessionID, matchesBlindedId: blindedId, serverPublicKey: serverPublicKey, genericHash: dependencies.genericHash) else {
return
}
@ -91,7 +91,7 @@ public enum ContactUtilities {
// a thread with this contact in a different SOGS and had cached the mapping)
dependencies.storage.enumerateBlindedIdMapping(using: transaction) { mapping, stop in
guard mapping.serverPublicKey != serverPublicKey else { return }
guard dependencies.sodium.sessionId(mapping.sessionId, matchesBlindedId: blindedId, serverPublicKey: serverPublicKey) else {
guard dependencies.sodium.sessionId(mapping.sessionId, matchesBlindedId: blindedId, serverPublicKey: serverPublicKey, genericHash: dependencies.genericHash) else {
return
}

32
SessionMessagingKit/Utilities/Dependencies.swift

@ -30,10 +30,16 @@ public class Dependencies {
set { _sodium = newValue }
}
internal var _aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType?
public var aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType {
get { Dependencies.getValueSettingIfNull(&_aeadXChaCha20Poly1305Ietf) { sodium.getAeadXChaCha20Poly1305Ietf() } }
set { _aeadXChaCha20Poly1305Ietf = newValue }
internal var _box: BoxType?
public var box: BoxType {
get { Dependencies.getValueSettingIfNull(&_box) { sodium.getBox() } }
set { _box = newValue }
}
internal var _genericHash: GenericHashType?
public var genericHash: GenericHashType {
get { Dependencies.getValueSettingIfNull(&_genericHash) { sodium.getGenericHash() } }
set { _genericHash = newValue }
}
internal var _sign: SignType?
@ -42,10 +48,10 @@ public class Dependencies {
set { _sign = newValue }
}
internal var _genericHash: GenericHashType?
public var genericHash: GenericHashType {
get { Dependencies.getValueSettingIfNull(&_genericHash) { sodium.getGenericHash() } }
set { _genericHash = newValue }
internal var _aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType?
public var aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType {
get { Dependencies.getValueSettingIfNull(&_aeadXChaCha20Poly1305Ietf) { sodium.getAeadXChaCha20Poly1305Ietf() } }
set { _aeadXChaCha20Poly1305Ietf = newValue }
}
internal var _ed25519: Ed25519Type?
@ -85,9 +91,10 @@ public class Dependencies {
identityManager: IdentityManagerProtocol? = nil,
storage: SessionMessagingKitStorageProtocol? = nil,
sodium: SodiumType? = nil,
aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil,
sign: SignType? = nil,
box: BoxType? = nil,
genericHash: GenericHashType? = nil,
sign: SignType? = nil,
aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil,
ed25519: Ed25519Type? = nil,
nonceGenerator16: NonceGenerator16ByteType? = nil,
nonceGenerator24: NonceGenerator24ByteType? = nil,
@ -98,9 +105,10 @@ public class Dependencies {
_identityManager = identityManager
_storage = storage
_sodium = sodium
_aeadXChaCha20Poly1305Ietf = aeadXChaCha20Poly1305Ietf
_sign = sign
_box = box
_genericHash = genericHash
_sign = sign
_aeadXChaCha20Poly1305Ietf = aeadXChaCha20Poly1305Ietf
_ed25519 = ed25519
_nonceGenerator16 = nonceGenerator16
_nonceGenerator24 = nonceGenerator24

81
SessionMessagingKit/Utilities/Sodium+Utilities.swift

@ -41,6 +41,15 @@ extension Sign {
}
}
/// These extenion methods are used to generate a sign "blinded" messages
///
/// According to the Swift engineers the only situation when `UnsafeRawBufferPointer.baseAddress` is nil is when it's an
/// empty collection; as such our guard cases wihch return `-1` when unwrapping this value should never be hit and we can ignore
/// them as possible results.
///
/// For more information see:
/// https://forums.swift.org/t/when-is-unsafemutablebufferpointer-baseaddress-nil/32136/5
/// https://github.com/apple/swift-evolution/blob/master/proposals/0055-optional-unsafe-pointers.md#unsafebufferpointer
extension Sodium {
private static let scalarLength: Int = Int(crypto_core_ed25519_scalarbytes()) // 32
private static let noClampLength: Int = Int(crypto_scalarmult_ed25519_bytes()) // 32
@ -49,7 +58,7 @@ extension Sodium {
private static let secretKeyLength: Int = Int(crypto_sign_secretkeybytes()) // 64
/// 64-byte blake2b hash then reduce to get the blinding factor
public func generateBlindingFactor(serverPublicKey: String) -> Bytes? {
public func generateBlindingFactor(serverPublicKey: String, genericHash: GenericHashType) -> Bytes? {
/// k = salt.crypto_core_ed25519_scalar_reduce(blake2b(server_pk, digest_size=64).digest())
guard let serverPubKeyData: Data = serverPublicKey.dataFromHex() else { return nil }
guard let serverPublicKeyHashBytes: Bytes = genericHash.hash(message: [UInt8](serverPubKeyData), outputLength: 64) else {
@ -59,18 +68,15 @@ extension Sodium {
/// Reduce the server public key into an ed25519 scalar (`k`)
let kPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.scalarLength)
let kResult = serverPublicKeyHashBytes.withUnsafeBytes { (serverPublicKeyHashPtr: UnsafeRawBufferPointer) -> Int32 in
_ = serverPublicKeyHashBytes.withUnsafeBytes { (serverPublicKeyHashPtr: UnsafeRawBufferPointer) -> Int32 in
guard let serverPublicKeyHashBaseAddress: UnsafePointer<UInt8> = serverPublicKeyHashPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return -1
return -1 // Impossible case (refer to comments at top of extension)
}
crypto_core_ed25519_scalar_reduce(kPtr, serverPublicKeyHashBaseAddress)
return 0
}
/// Ensure the above worked
guard kResult == 0 else { return nil }
return Data(bytes: kPtr, count: Sodium.scalarLength).bytes
}
@ -78,21 +84,20 @@ extension Sodium {
/// convert to an *x* secret key, which seems wrong--but isn't because converted keys use the
/// same secret scalar secret (and so this is just the most convenient way to get 'a' out of
/// a sodium Ed25519 secret key)
private func generatePrivateKeyScalar(secretKey: Bytes) -> Bytes? {
func generatePrivateKeyScalar(secretKey: Bytes) -> Bytes {
/// a = s.to_curve25519_private_key().encode()
let aPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.scalarMultLength)
let aResult = secretKey.withUnsafeBytes { (secretKeyPtr: UnsafeRawBufferPointer) -> Int32 in
/// Looks like the `crypto_sign_ed25519_sk_to_curve25519` function can't actually fail so no need to verify the result
/// See: https://github.com/jedisct1/libsodium/blob/master/src/libsodium/crypto_sign/ed25519/ref10/keypair.c#L70
_ = secretKey.withUnsafeBytes { (secretKeyPtr: UnsafeRawBufferPointer) -> Int32 in
guard let secretKeyBaseAddress: UnsafePointer<UInt8> = secretKeyPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return -1
return -1 // Impossible case (refer to comments at top of extension)
}
return crypto_sign_ed25519_sk_to_curve25519(aPtr, secretKeyBaseAddress)
}
/// Ensure the above worked
guard aResult == 0 else { return nil }
return Data(bytes: aPtr, count: Sodium.scalarMultLength).bytes
}
@ -101,20 +106,22 @@ extension Sodium {
guard edKeyPair.publicKey.count == Sodium.publicKeyLength && edKeyPair.secretKey.count == Sodium.secretKeyLength else {
return nil
}
guard let kBytes: Bytes = generateBlindingFactor(serverPublicKey: serverPublicKey) else { return nil }
guard let aBytes: Bytes = generatePrivateKeyScalar(secretKey: edKeyPair.secretKey) else { return nil }
guard let kBytes: Bytes = generateBlindingFactor(serverPublicKey: serverPublicKey, genericHash: genericHash) else {
return nil
}
let aBytes: Bytes = generatePrivateKeyScalar(secretKey: edKeyPair.secretKey)
/// Generate the blinded key pair `ka`, `kA`
let kaPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.secretKeyLength)
let kAPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.publicKeyLength)
let kaResult = aBytes.withUnsafeBytes { (aPtr: UnsafeRawBufferPointer) -> Int32 in
_ = aBytes.withUnsafeBytes { (aPtr: UnsafeRawBufferPointer) -> Int32 in
return kBytes.withUnsafeBytes { (kPtr: UnsafeRawBufferPointer) -> Int32 in
guard let kBaseAddress: UnsafePointer<UInt8> = kPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return -1
return -1 // Impossible case (refer to comments at top of extension)
}
guard let aBaseAddress: UnsafePointer<UInt8> = aPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return -1
return -1 // Impossible case (refer to comments at top of extension)
}
crypto_core_ed25519_scalar_mul(kaPtr, kBaseAddress, aBaseAddress)
@ -122,9 +129,6 @@ extension Sodium {
}
}
/// Ensure the above worked
guard kaResult == 0 else { return nil }
guard crypto_scalarmult_ed25519_base_noclamp(kAPtr, kaPtr) == 0 else { return nil }
return Box.KeyPair(
@ -144,18 +148,15 @@ extension Sodium {
let combinedHashBytes: Bytes = (H_rh + kA + message).sha512()
let rPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.scalarLength)
let rResult = combinedHashBytes.withUnsafeBytes { (combinedHashPtr: UnsafeRawBufferPointer) -> Int32 in
_ = combinedHashBytes.withUnsafeBytes { (combinedHashPtr: UnsafeRawBufferPointer) -> Int32 in
guard let combinedHashBaseAddress: UnsafePointer<UInt8> = combinedHashPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return -1
return -1 // Impossible case (refer to comments at top of extension)
}
crypto_core_ed25519_scalar_reduce(rPtr, combinedHashBaseAddress)
return 0
}
/// Ensure the above worked
guard rResult == 0 else { return nil }
/// sig_R = salt.crypto_scalarmult_ed25519_base_noclamp(r)
let sig_RPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.noClampLength)
guard crypto_scalarmult_ed25519_base_noclamp(sig_RPtr, rPtr) == 0 else { return nil }
@ -165,25 +166,22 @@ extension Sodium {
let HRAMHashBytes: Bytes = (sig_RBytes + kA + message).sha512()
let HRAMPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.scalarLength)
let HRAMResult = HRAMHashBytes.withUnsafeBytes { (HRAMHashPtr: UnsafeRawBufferPointer) -> Int32 in
_ = HRAMHashBytes.withUnsafeBytes { (HRAMHashPtr: UnsafeRawBufferPointer) -> Int32 in
guard let HRAMHashBaseAddress: UnsafePointer<UInt8> = HRAMHashPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return -1
return -1 // Impossible case (refer to comments at top of extension)
}
crypto_core_ed25519_scalar_reduce(HRAMPtr, HRAMHashBaseAddress)
return 0
}
/// Ensure the above worked
guard HRAMResult == 0 else { return nil }
/// sig_s = salt.crypto_core_ed25519_scalar_add(r, salt.crypto_core_ed25519_scalar_mul(HRAM, ka))
let sig_sMulPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.scalarLength)
let sig_sPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.scalarLength)
let sig_sResult = ka.withUnsafeBytes { (kaPtr: UnsafeRawBufferPointer) -> Int32 in
_ = ka.withUnsafeBytes { (kaPtr: UnsafeRawBufferPointer) -> Int32 in
guard let kaBaseAddress: UnsafePointer<UInt8> = kaPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return -1
return -1 // Impossible case (refer to comments at top of extension)
}
crypto_core_ed25519_scalar_mul(sig_sMulPtr, HRAMPtr, kaBaseAddress)
@ -191,8 +189,6 @@ extension Sodium {
return 0
}
guard sig_sResult == 0 else { return nil }
/// full_sig = sig_R + sig_s
return (Data(bytes: sig_RPtr, count: Sodium.noClampLength).bytes + Data(bytes: sig_sPtr, count: Sodium.scalarLength).bytes)
}
@ -204,10 +200,10 @@ extension Sodium {
let result = rhsKeyBytes.withUnsafeBytes { (rhsKeyBytesPtr: UnsafeRawBufferPointer) -> Int32 in
return lhsKeyBytes.withUnsafeBytes { (lhsKeyBytesPtr: UnsafeRawBufferPointer) -> Int32 in
guard let lhsKeyBytesBaseAddress: UnsafePointer<UInt8> = lhsKeyBytesPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return -1
return -1 // Impossible case (refer to comments at top of extension)
}
guard let rhsKeyBytesBaseAddress: UnsafePointer<UInt8> = rhsKeyBytesPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return -1
return -1 // Impossible case (refer to comments at top of extension)
}
return crypto_scalarmult_ed25519_noclamp(combinedPtr, lhsKeyBytesBaseAddress, rhsKeyBytesBaseAddress)
@ -228,18 +224,23 @@ extension Sodium {
///
/// BLAKE2b(b kA || kA || kB)
public func sharedBlindedEncryptionKey(secretKey: Bytes, otherBlindedPublicKey: Bytes, fromBlindedPublicKey kA: Bytes, toBlindedPublicKey kB: Bytes, genericHash: GenericHashType) -> Bytes? {
guard let aBytes: Bytes = generatePrivateKeyScalar(secretKey: secretKey) else { return nil }
guard let combinedKeyBytes: Bytes = combineKeys(lhsKeyBytes: aBytes, rhsKeyBytes: otherBlindedPublicKey) else { return nil }
let aBytes: Bytes = generatePrivateKeyScalar(secretKey: secretKey)
guard let combinedKeyBytes: Bytes = combineKeys(lhsKeyBytes: aBytes, rhsKeyBytes: otherBlindedPublicKey) else {
return nil
}
return genericHash.hash(message: (combinedKeyBytes + kA + kB), outputLength: 32)
}
/// This method should be used to check if a users standard sessionId matches a blinded one
public func sessionId(_ standardSessionId: String, matchesBlindedId blindedSessionId: String, serverPublicKey: String) -> Bool {
public func sessionId(_ standardSessionId: String, matchesBlindedId blindedSessionId: String, serverPublicKey: String, genericHash: GenericHashType) -> Bool {
// Only support generating blinded keys for standard session ids
guard let sessionId: SessionId = SessionId(from: standardSessionId), sessionId.prefix == .standard else { return false }
guard let blindedId: SessionId = SessionId(from: blindedSessionId), blindedId.prefix == .blinded else { return false }
guard let kBytes: Bytes = generateBlindingFactor(serverPublicKey: serverPublicKey) else { return false }
guard let kBytes: Bytes = generateBlindingFactor(serverPublicKey: serverPublicKey, genericHash: genericHash) else {
return false
}
/// From the session id (ignoring 05 prefix) we have two possible ed25519 pubkeys; the first is the positive (which is what
/// Signal's XEd25519 conversion always uses)

20
SessionMessagingKitTests/Common Networking/HeaderSpec.swift

@ -0,0 +1,20 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import Quick
import Nimble
@testable import SessionMessagingKit
class HeaderSpec: QuickSpec {
// MARK: - Spec
override func spec() {
describe("a Dictionary of Header to String values") {
it("can be converted into a dictionary of String to String values") {
expect([Header.authorization: "test"].toHTTPHeaders()).to(equal(["Authorization": "test"]))
}
}
}
}

32
SessionMessagingKitTests/Common Networking/Models/FileUploadResponseSpec.swift

@ -0,0 +1,32 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import Quick
import Nimble
@testable import SessionMessagingKit
class FileUploadResponseSpec: QuickSpec {
// MARK: - Spec
override func spec() {
describe("a FileUploadResponse") {
context("when decoding") {
it("handles a string id value") {
let jsonData: Data = "{\"id\":\"123\"}".data(using: .utf8)!
let response: FileUploadResponse? = try? JSONDecoder().decode(FileUploadResponse.self, from: jsonData)
expect(response?.id).to(equal("123"))
}
it("handles an int id value") {
let jsonData: Data = "{\"id\":124}".data(using: .utf8)!
let response: FileUploadResponse? = try? JSONDecoder().decode(FileUploadResponse.self, from: jsonData)
expect(response?.id).to(equal("124"))
}
}
}
}
}

167
SessionMessagingKitTests/Common Networking/RequestSpec.swift

@ -0,0 +1,167 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import Quick
import Nimble
@testable import SessionMessagingKit
class RequestSpec: QuickSpec {
struct TestType: Codable, Equatable {
let stringValue: String
}
// MARK: - Spec
override func spec() {
describe("a Request") {
it("is initialized with the correct default values") {
let request: Request<NoBody, OpenGroupAPI.Endpoint> = Request(
server: "testServer",
endpoint: .batch
)
expect(request.method.rawValue).to(equal("GET"))
expect(request.queryParameters).to(equal([:]))
expect(request.headers).to(equal([:]))
expect(request.body).to(beNil())
}
context("when generating a URL") {
it("adds a leading forward slash to the endpoint path") {
let request: Request<NoBody, OpenGroupAPI.Endpoint> = Request(
server: "testServer",
endpoint: .batch
)
expect(request.urlPathAndParamsString).to(equal("/batch"))
}
it("creates a valid URL with no query parameters") {
let request: Request<NoBody, OpenGroupAPI.Endpoint> = Request(
server: "testServer",
endpoint: .batch
)
expect(request.urlPathAndParamsString).to(equal("/batch"))
}
it("creates a valid URL when query parameters are provided") {
let request: Request<NoBody, OpenGroupAPI.Endpoint> = Request(
server: "testServer",
endpoint: .batch,
queryParameters: [
.limit: "123"
]
)
expect(request.urlPathAndParamsString).to(equal("/batch?limit=123"))
}
}
context("when generating a URLRequest") {
it("sets all the values correctly") {
let request: Request<NoBody, OpenGroupAPI.Endpoint> = Request(
method: .delete,
server: "testServer",
endpoint: .batch,
headers: [
.authorization: "test"
]
)
let urlRequest: URLRequest? = try? request.generateUrlRequest()
expect(urlRequest?.httpMethod).to(equal("DELETE"))
expect(urlRequest?.allHTTPHeaderFields).to(equal(["Authorization": "test"]))
expect(urlRequest?.httpBody).to(beNil())
}
it("throws an error if the URL is invalid") {
let request: Request<NoBody, OpenGroupAPI.Endpoint> = Request(
server: "testServer",
endpoint: .roomPollInfo("!!%%", 123)
)
expect {
try request.generateUrlRequest()
}
.to(throwError(HTTP.Error.invalidURL))
}
context("with a base64 string body") {
it("successfully encodes the body") {
let request: Request<String, OpenGroupAPI.Endpoint> = Request(
server: "testServer",
endpoint: .batch,
body: "TestMessage".data(using: .utf8)!.base64EncodedString()
)
let urlRequest: URLRequest? = try? request.generateUrlRequest()
let requestBody: Data? = Data(base64Encoded: urlRequest?.httpBody?.base64EncodedString() ?? "")
let requestBodyString: String? = String(data: requestBody ?? Data(), encoding: .utf8)
expect(requestBodyString).to(equal("TestMessage"))
}
it("throws an error if the body is not base64 encoded") {
let request: Request<String, OpenGroupAPI.Endpoint> = Request(
server: "testServer",
endpoint: .batch,
body: "TestMessage"
)
expect {
try request.generateUrlRequest()
}
.to(throwError(HTTP.Error.parsingFailed))
}
}
context("with a byte body") {
it("successfully encodes the body") {
let request: Request<[UInt8], OpenGroupAPI.Endpoint> = Request(
server: "testServer",
endpoint: .batch,
body: [1, 2, 3]
)
let urlRequest: URLRequest? = try? request.generateUrlRequest()
expect(urlRequest?.httpBody?.bytes).to(equal([1, 2, 3]))
}
}
context("with a JSON body") {
it("successfully encodes the body") {
let request: Request<TestType, OpenGroupAPI.Endpoint> = Request(
server: "testServer",
endpoint: .batch,
body: TestType(stringValue: "test")
)
let urlRequest: URLRequest? = try? request.generateUrlRequest()
let requestBody: TestType? = try? JSONDecoder().decode(
TestType.self,
from: urlRequest?.httpBody ?? Data()
)
expect(requestBody).to(equal(TestType(stringValue: "test")))
}
it("successfully encodes no body") {
let request: Request<NoBody, OpenGroupAPI.Endpoint> = Request(
server: "testServer",
endpoint: .batch,
body: nil
)
expect {
try request.generateUrlRequest()
}.toNot(throwError())
}
}
}
}
}
}

48
SessionMessagingKitTests/Contacts/BlindedIdMappingSpec.swift

@ -0,0 +1,48 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import Quick
import Nimble
@testable import SessionMessagingKit
class BlindedIdMappingSpec: QuickSpec {
// MARK: - Spec
override func spec() {
describe("a BlindedIdMapping") {
context("when initializing") {
it("sets the values correctly") {
let mapping: BlindedIdMapping = BlindedIdMapping(
blindedId: "testBlindedId",
sessionId: "testSessionId",
serverPublicKey: "testPublicKey"
)
expect(mapping.blindedId).to(equal("testBlindedId"))
expect(mapping.sessionId).to(equal("testSessionId"))
expect(mapping.serverPublicKey).to(equal("testPublicKey"))
}
}
context("when NSCoding") {
// Note: Unit testing NSCoder is horrible so we won't do it properly - wait until we refactor it to Codable
it("successfully encodes and decodes") {
let mappingToEncode: BlindedIdMapping = BlindedIdMapping(
blindedId: "testBlindedId",
sessionId: "testSessionId",
serverPublicKey: "testPublicKey"
)
let encodedData: Data = try! NSKeyedArchiver.archivedData(withRootObject: mappingToEncode, requiringSecureCoding: false)
let mapping: BlindedIdMapping? = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(encodedData) as? BlindedIdMapping
expect(mapping).toNot(beNil())
expect(mapping?.blindedId).to(equal("testBlindedId"))
expect(mapping?.sessionId).to(equal("testSessionId"))
expect(mapping?.serverPublicKey).to(equal("testPublicKey"))
}
}
}
}
}

385
SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift

@ -0,0 +1,385 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import PromiseKit
import SessionSnodeKit
import Quick
import Nimble
@testable import SessionMessagingKit
import AVFoundation
class BatchRequestInfoSpec: QuickSpec {
struct TestType: Codable, Equatable {
let stringValue: String
}
// MARK: - Spec
override func spec() {
// MARK: - BatchSubRequest
describe("a BatchSubRequest") {
var subRequest: OpenGroupAPI.BatchSubRequest!
context("when initializing") {
it("sets the headers to nil if there aren't any") {
subRequest = OpenGroupAPI.BatchSubRequest(
request: Request<NoBody, OpenGroupAPI.Endpoint>(
server: "testServer",
endpoint: .batch
)
)
expect(subRequest.headers).to(beNil())
}
it("converts the headers to HTTP headers") {
subRequest = OpenGroupAPI.BatchSubRequest(
request: Request<NoBody, OpenGroupAPI.Endpoint>(
method: .get,
server: "testServer",
endpoint: .batch,
queryParameters: [:],
headers: [.authorization: "testAuth"],
body: nil
)
)
expect(subRequest.headers).to(equal(["Authorization": "testAuth"]))
}
}
context("when encoding") {
it("successfully encodes a string body") {
subRequest = OpenGroupAPI.BatchSubRequest(
request: Request<String, OpenGroupAPI.Endpoint>(
method: .get,
server: "testServer",
endpoint: .batch,
queryParameters: [:],
headers: [:],
body: "testBody"
)
)
let subRequestData: Data = try! JSONEncoder().encode(subRequest)
let subRequestString: String? = String(data: subRequestData, encoding: .utf8)
expect(subRequestString)
.to(equal("{\"path\":\"\\/batch\",\"method\":\"GET\",\"b64\":\"testBody\"}"))
}
it("successfully encodes a byte body") {
subRequest = OpenGroupAPI.BatchSubRequest(
request: Request<[UInt8], OpenGroupAPI.Endpoint>(
method: .get,
server: "testServer",
endpoint: .batch,
queryParameters: [:],
headers: [:],
body: [1, 2, 3]
)
)
let subRequestData: Data = try! JSONEncoder().encode(subRequest)