mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Merge branch 'charlesmchen/deviceNames'
This commit is contained in:
commit
6edf9e585f
2
Pods
2
Pods
|
@ -1 +1 @@
|
|||
Subproject commit 80ecd88615e75d270f1aa5f5a0e8d6060cec334f
|
||||
Subproject commit 871532ee0e74da1c00a6b0bad81478298bd59c6d
|
|
@ -26,7 +26,10 @@ class ImageEditorTest: SignalBaseTest {
|
|||
}
|
||||
|
||||
func testImageEditorContents() {
|
||||
let contents = ImageEditorContents()
|
||||
let imagePath = writeDummyImage()
|
||||
|
||||
let contents = ImageEditorContents(imagePath: imagePath,
|
||||
imageSizePixels: CGSize(width: 1, height: 1))
|
||||
XCTAssertEqual(0, contents.itemMap.count)
|
||||
|
||||
let item = ImageEditorItem(itemType: .test)
|
||||
|
|
|
@ -33,3 +33,13 @@ message BackupSnapshot {
|
|||
|
||||
repeated BackupEntity entity = 1;
|
||||
}
|
||||
|
||||
message DeviceName {
|
||||
// @required
|
||||
optional bytes ephemeralPublic = 1;
|
||||
// @required
|
||||
optional bytes syntheticIv = 2;
|
||||
// @required
|
||||
optional bytes ciphertext = 3;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#import "YapDatabaseTransaction.h"
|
||||
#import <Mantle/MTLValueTransformer.h>
|
||||
#import <SignalCoreKit/NSDate+OWS.h>
|
||||
#import <SignalServiceKit/OWSIdentityManager.h>
|
||||
#import <SignalServiceKit/SignalServiceKit-Swift.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
@ -130,6 +131,13 @@ NSString *const kOWSPrimaryStorage_MayHaveLinkedDevices = @"kTSStorageManager_Ma
|
|||
return TSAccountManager.sharedInstance;
|
||||
}
|
||||
|
||||
- (OWSIdentityManager *)identityManager
|
||||
{
|
||||
OWSAssertDebug(SSKEnvironment.shared.identityManager);
|
||||
|
||||
return SSKEnvironment.shared.identityManager;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
|
@ -275,6 +283,20 @@ NSString *const kOWSPrimaryStorage_MayHaveLinkedDevices = @"kTSStorageManager_Ma
|
|||
- (NSString *)displayName
|
||||
{
|
||||
if (self.name) {
|
||||
ECKeyPair *_Nullable identityKeyPair = self.identityManager.identityKeyPair;
|
||||
OWSAssertDebug(identityKeyPair);
|
||||
if (identityKeyPair) {
|
||||
NSError *error;
|
||||
NSString *_Nullable decryptedName =
|
||||
[DeviceNames decryptDeviceNameWithBase64String:self.name identityKeyPair:identityKeyPair error:&error];
|
||||
if (error) {
|
||||
// Not necessarily an error; might be a legacy device name.
|
||||
OWSLogError(@"Could not decrypt device name: %@", error);
|
||||
} else if (decryptedName) {
|
||||
return decryptedName;
|
||||
}
|
||||
}
|
||||
|
||||
return self.name;
|
||||
}
|
||||
|
||||
|
|
|
@ -132,6 +132,50 @@ struct IOSProtos_BackupSnapshot {
|
|||
init() {}
|
||||
}
|
||||
|
||||
struct IOSProtos_DeviceName {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
/// @required
|
||||
var ephemeralPublic: Data {
|
||||
get {return _ephemeralPublic ?? SwiftProtobuf.Internal.emptyData}
|
||||
set {_ephemeralPublic = newValue}
|
||||
}
|
||||
/// Returns true if `ephemeralPublic` has been explicitly set.
|
||||
var hasEphemeralPublic: Bool {return self._ephemeralPublic != nil}
|
||||
/// Clears the value of `ephemeralPublic`. Subsequent reads from it will return its default value.
|
||||
mutating func clearEphemeralPublic() {self._ephemeralPublic = nil}
|
||||
|
||||
/// @required
|
||||
var syntheticIv: Data {
|
||||
get {return _syntheticIv ?? SwiftProtobuf.Internal.emptyData}
|
||||
set {_syntheticIv = newValue}
|
||||
}
|
||||
/// Returns true if `syntheticIv` has been explicitly set.
|
||||
var hasSyntheticIv: Bool {return self._syntheticIv != nil}
|
||||
/// Clears the value of `syntheticIv`. Subsequent reads from it will return its default value.
|
||||
mutating func clearSyntheticIv() {self._syntheticIv = nil}
|
||||
|
||||
/// @required
|
||||
var ciphertext: Data {
|
||||
get {return _ciphertext ?? SwiftProtobuf.Internal.emptyData}
|
||||
set {_ciphertext = newValue}
|
||||
}
|
||||
/// Returns true if `ciphertext` has been explicitly set.
|
||||
var hasCiphertext: Bool {return self._ciphertext != nil}
|
||||
/// Clears the value of `ciphertext`. Subsequent reads from it will return its default value.
|
||||
mutating func clearCiphertext() {self._ciphertext = nil}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
|
||||
fileprivate var _ephemeralPublic: Data? = nil
|
||||
fileprivate var _syntheticIv: Data? = nil
|
||||
fileprivate var _ciphertext: Data? = nil
|
||||
}
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
||||
fileprivate let _protobuf_package = "IOSProtos"
|
||||
|
@ -222,3 +266,44 @@ extension IOSProtos_BackupSnapshot.BackupEntity.TypeEnum: SwiftProtobuf._ProtoNa
|
|||
5: .same(proto: "MISC"),
|
||||
]
|
||||
}
|
||||
|
||||
extension IOSProtos_DeviceName: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = _protobuf_package + ".DeviceName"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "ephemeralPublic"),
|
||||
2: .same(proto: "syntheticIv"),
|
||||
3: .same(proto: "ciphertext"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularBytesField(value: &self._ephemeralPublic)
|
||||
case 2: try decoder.decodeSingularBytesField(value: &self._syntheticIv)
|
||||
case 3: try decoder.decodeSingularBytesField(value: &self._ciphertext)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if let v = self._ephemeralPublic {
|
||||
try visitor.visitSingularBytesField(value: v, fieldNumber: 1)
|
||||
}
|
||||
if let v = self._syntheticIv {
|
||||
try visitor.visitSingularBytesField(value: v, fieldNumber: 2)
|
||||
}
|
||||
if let v = self._ciphertext {
|
||||
try visitor.visitSingularBytesField(value: v, fieldNumber: 3)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: IOSProtos_DeviceName, rhs: IOSProtos_DeviceName) -> Bool {
|
||||
if lhs._ephemeralPublic != rhs._ephemeralPublic {return false}
|
||||
if lhs._syntheticIv != rhs._syntheticIv {return false}
|
||||
if lhs._ciphertext != rhs._ciphertext {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -280,3 +280,130 @@ extension SignalIOSProtoBackupSnapshot.SignalIOSProtoBackupSnapshotBuilder {
|
|||
}
|
||||
|
||||
#endif
|
||||
|
||||
// MARK: - SignalIOSProtoDeviceName
|
||||
|
||||
@objc public class SignalIOSProtoDeviceName: NSObject {
|
||||
|
||||
// MARK: - SignalIOSProtoDeviceNameBuilder
|
||||
|
||||
@objc public class func builder(ephemeralPublic: Data, syntheticIv: Data, ciphertext: Data) -> SignalIOSProtoDeviceNameBuilder {
|
||||
return SignalIOSProtoDeviceNameBuilder(ephemeralPublic: ephemeralPublic, syntheticIv: syntheticIv, ciphertext: ciphertext)
|
||||
}
|
||||
|
||||
// asBuilder() constructs a builder that reflects the proto's contents.
|
||||
@objc public func asBuilder() -> SignalIOSProtoDeviceNameBuilder {
|
||||
let builder = SignalIOSProtoDeviceNameBuilder(ephemeralPublic: ephemeralPublic, syntheticIv: syntheticIv, ciphertext: ciphertext)
|
||||
return builder
|
||||
}
|
||||
|
||||
@objc public class SignalIOSProtoDeviceNameBuilder: NSObject {
|
||||
|
||||
private var proto = IOSProtos_DeviceName()
|
||||
|
||||
@objc fileprivate override init() {}
|
||||
|
||||
@objc fileprivate init(ephemeralPublic: Data, syntheticIv: Data, ciphertext: Data) {
|
||||
super.init()
|
||||
|
||||
setEphemeralPublic(ephemeralPublic)
|
||||
setSyntheticIv(syntheticIv)
|
||||
setCiphertext(ciphertext)
|
||||
}
|
||||
|
||||
@objc public func setEphemeralPublic(_ valueParam: Data) {
|
||||
proto.ephemeralPublic = valueParam
|
||||
}
|
||||
|
||||
@objc public func setSyntheticIv(_ valueParam: Data) {
|
||||
proto.syntheticIv = valueParam
|
||||
}
|
||||
|
||||
@objc public func setCiphertext(_ valueParam: Data) {
|
||||
proto.ciphertext = valueParam
|
||||
}
|
||||
|
||||
@objc public func build() throws -> SignalIOSProtoDeviceName {
|
||||
return try SignalIOSProtoDeviceName.parseProto(proto)
|
||||
}
|
||||
|
||||
@objc public func buildSerializedData() throws -> Data {
|
||||
return try SignalIOSProtoDeviceName.parseProto(proto).serializedData()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate let proto: IOSProtos_DeviceName
|
||||
|
||||
@objc public let ephemeralPublic: Data
|
||||
|
||||
@objc public let syntheticIv: Data
|
||||
|
||||
@objc public let ciphertext: Data
|
||||
|
||||
private init(proto: IOSProtos_DeviceName,
|
||||
ephemeralPublic: Data,
|
||||
syntheticIv: Data,
|
||||
ciphertext: Data) {
|
||||
self.proto = proto
|
||||
self.ephemeralPublic = ephemeralPublic
|
||||
self.syntheticIv = syntheticIv
|
||||
self.ciphertext = ciphertext
|
||||
}
|
||||
|
||||
@objc
|
||||
public func serializedData() throws -> Data {
|
||||
return try self.proto.serializedData()
|
||||
}
|
||||
|
||||
@objc public class func parseData(_ serializedData: Data) throws -> SignalIOSProtoDeviceName {
|
||||
let proto = try IOSProtos_DeviceName(serializedData: serializedData)
|
||||
return try parseProto(proto)
|
||||
}
|
||||
|
||||
fileprivate class func parseProto(_ proto: IOSProtos_DeviceName) throws -> SignalIOSProtoDeviceName {
|
||||
guard proto.hasEphemeralPublic else {
|
||||
throw SignalIOSProtoError.invalidProtobuf(description: "\(logTag) missing required field: ephemeralPublic")
|
||||
}
|
||||
let ephemeralPublic = proto.ephemeralPublic
|
||||
|
||||
guard proto.hasSyntheticIv else {
|
||||
throw SignalIOSProtoError.invalidProtobuf(description: "\(logTag) missing required field: syntheticIv")
|
||||
}
|
||||
let syntheticIv = proto.syntheticIv
|
||||
|
||||
guard proto.hasCiphertext else {
|
||||
throw SignalIOSProtoError.invalidProtobuf(description: "\(logTag) missing required field: ciphertext")
|
||||
}
|
||||
let ciphertext = proto.ciphertext
|
||||
|
||||
// MARK: - Begin Validation Logic for SignalIOSProtoDeviceName -
|
||||
|
||||
// MARK: - End Validation Logic for SignalIOSProtoDeviceName -
|
||||
|
||||
let result = SignalIOSProtoDeviceName(proto: proto,
|
||||
ephemeralPublic: ephemeralPublic,
|
||||
syntheticIv: syntheticIv,
|
||||
ciphertext: ciphertext)
|
||||
return result
|
||||
}
|
||||
|
||||
@objc public override var debugDescription: String {
|
||||
return "\(proto)"
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
extension SignalIOSProtoDeviceName {
|
||||
@objc public func serializedDataIgnoringErrors() -> Data? {
|
||||
return try! self.serializedData()
|
||||
}
|
||||
}
|
||||
|
||||
extension SignalIOSProtoDeviceName.SignalIOSProtoDeviceNameBuilder {
|
||||
@objc public func buildIgnoringErrors() -> SignalIOSProtoDeviceName? {
|
||||
return try! self.build()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
220
SignalServiceKit/src/Util/DeviceNames.swift
Normal file
220
SignalServiceKit/src/Util/DeviceNames.swift
Normal file
|
@ -0,0 +1,220 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Curve25519Kit
|
||||
import AxolotlKit
|
||||
|
||||
@objc
|
||||
public enum DeviceNameError: Int, Error {
|
||||
case assertionFailure
|
||||
case invalidInput
|
||||
}
|
||||
|
||||
@objc
|
||||
public class DeviceNames: NSObject {
|
||||
// Never instantiate this class.
|
||||
private override init() {}
|
||||
|
||||
private static let syntheticIVLength: UInt = 16
|
||||
|
||||
@objc
|
||||
public class func encryptDeviceName(plaintext: String,
|
||||
identityKeyPair: ECKeyPair) throws -> Data {
|
||||
|
||||
guard let plaintextData = plaintext.data(using: .utf8) else {
|
||||
owsFailDebug("Could not convert text to UTF-8.")
|
||||
throw DeviceNameError.invalidInput
|
||||
}
|
||||
|
||||
let ephemeralKeyPair = Curve25519.generateKeyPair()
|
||||
|
||||
// master_secret = ECDH(ephemeral_private, identity_public).
|
||||
let masterSecret: Data
|
||||
do {
|
||||
masterSecret = try Curve25519.generateSharedSecret(fromPublicKey: identityKeyPair.publicKey,
|
||||
privateKey: ephemeralKeyPair.privateKey)
|
||||
} catch {
|
||||
Logger.error("Could not generate shared secret: \(error)")
|
||||
throw error
|
||||
}
|
||||
|
||||
// synthetic_iv = HmacSHA256(key=HmacSHA256(key=master_secret, input=“auth”), input=plaintext)[0:16]
|
||||
let syntheticIV = try computeSyntheticIV(masterSecret: masterSecret,
|
||||
plaintextData: plaintextData)
|
||||
|
||||
// cipher_key = HmacSHA256(key=HmacSHA256(key=master_secret, “cipher”), input=synthetic_iv)
|
||||
let cipherKey = try computeCipherKey(masterSecret: masterSecret, syntheticIV: syntheticIV)
|
||||
|
||||
// cipher_text = AES-CTR(key=cipher_key, input=plaintext, counter=0)
|
||||
//
|
||||
// An all-zeros IV corresponds to an AES CTR counter of zero.
|
||||
let ciphertextIV = Data(count: Int(kAES256CTR_IVLength))
|
||||
guard let ciphertextKey = OWSAES256Key(data: cipherKey) else {
|
||||
owsFailDebug("Invalid cipher key.")
|
||||
throw DeviceNameError.assertionFailure
|
||||
}
|
||||
guard let ciphertext: AES256CTREncryptionResult = Cryptography.encryptAESCTR(plaintextData: plaintextData, initializationVector: ciphertextIV, key: ciphertextKey) else {
|
||||
owsFailDebug("Could not encrypt cipher text.")
|
||||
throw DeviceNameError.assertionFailure
|
||||
}
|
||||
|
||||
guard let keyData = (ephemeralKeyPair.publicKey as NSData).prependKeyType() else {
|
||||
owsFailDebug("Could not prepend key type.")
|
||||
throw DeviceNameError.assertionFailure
|
||||
}
|
||||
let protoBuilder = SignalIOSProtoDeviceName.builder(ephemeralPublic: keyData as Data,
|
||||
syntheticIv: syntheticIV,
|
||||
ciphertext: ciphertext.ciphertext)
|
||||
let protoData = try protoBuilder.buildSerializedData()
|
||||
|
||||
// NOTE: This uses Data's foundation method rather than the NSData's SSK method.
|
||||
let protoDataBase64 = protoData.base64EncodedData()
|
||||
|
||||
return protoDataBase64
|
||||
}
|
||||
|
||||
private class func computeSyntheticIV(masterSecret: Data,
|
||||
plaintextData: Data) throws -> Data {
|
||||
// synthetic_iv = HmacSHA256(key=HmacSHA256(key=master_secret, input=“auth”), input=plaintext)[0:16]
|
||||
guard let syntheticIVInput = "auth".data(using: .utf8) else {
|
||||
owsFailDebug("Could not convert text to UTF-8.")
|
||||
throw DeviceNameError.assertionFailure
|
||||
}
|
||||
guard let syntheticIVKey = Cryptography.computeSHA256HMAC(syntheticIVInput, withHMACKey: masterSecret) else {
|
||||
owsFailDebug("Could not compute synthetic IV key.")
|
||||
throw DeviceNameError.assertionFailure
|
||||
}
|
||||
guard let syntheticIV = Cryptography.truncatedSHA256HMAC(plaintextData, withHMACKey: syntheticIVKey, truncation: syntheticIVLength) else {
|
||||
owsFailDebug("Could not compute synthetic IV.")
|
||||
throw DeviceNameError.assertionFailure
|
||||
}
|
||||
return syntheticIV
|
||||
}
|
||||
|
||||
private class func computeCipherKey(masterSecret: Data,
|
||||
syntheticIV: Data) throws -> Data {
|
||||
// cipher_key = HmacSHA256(key=HmacSHA256(key=master_secret, “cipher”), input=synthetic_iv)
|
||||
guard let cipherKeyInput = "cipher".data(using: .utf8) else {
|
||||
owsFailDebug("Could not convert text to UTF-8.")
|
||||
throw DeviceNameError.assertionFailure
|
||||
}
|
||||
guard let cipherKeyKey = Cryptography.computeSHA256HMAC(cipherKeyInput, withHMACKey: masterSecret) else {
|
||||
owsFailDebug("Could not compute cipher key key.")
|
||||
throw DeviceNameError.assertionFailure
|
||||
}
|
||||
guard let cipherKey = Cryptography.computeSHA256HMAC(syntheticIV, withHMACKey: cipherKeyKey) else {
|
||||
owsFailDebug("Could not compute cipher key.")
|
||||
throw DeviceNameError.assertionFailure
|
||||
}
|
||||
return cipherKey
|
||||
}
|
||||
|
||||
@objc
|
||||
public class func decryptDeviceName(base64String: String,
|
||||
identityKeyPair: ECKeyPair) throws -> String {
|
||||
|
||||
guard let protoData = Data(base64Encoded: base64String) else {
|
||||
// Not necessarily an error; might be a legacy device name.
|
||||
throw DeviceNameError.invalidInput
|
||||
}
|
||||
|
||||
return try decryptDeviceName(protoData: protoData,
|
||||
identityKeyPair: identityKeyPair)
|
||||
}
|
||||
|
||||
@objc
|
||||
public class func decryptDeviceName(base64Data: Data,
|
||||
identityKeyPair: ECKeyPair) throws -> String {
|
||||
|
||||
guard let protoData = Data(base64Encoded: base64Data) else {
|
||||
// Not necessarily an error; might be a legacy device name.
|
||||
throw DeviceNameError.invalidInput
|
||||
}
|
||||
|
||||
return try decryptDeviceName(protoData: protoData,
|
||||
identityKeyPair: identityKeyPair)
|
||||
}
|
||||
|
||||
@objc
|
||||
public class func decryptDeviceName(protoData: Data,
|
||||
identityKeyPair: ECKeyPair) throws -> String {
|
||||
|
||||
let proto: SignalIOSProtoDeviceName
|
||||
do {
|
||||
proto = try SignalIOSProtoDeviceName.parseData(protoData)
|
||||
} catch {
|
||||
// Not necessarily an error; might be a legacy device name.
|
||||
Logger.error("failed to parse proto")
|
||||
throw DeviceNameError.invalidInput
|
||||
}
|
||||
|
||||
let ephemeralPublicData = proto.ephemeralPublic
|
||||
let receivedSyntheticIV = proto.syntheticIv
|
||||
let ciphertext = proto.ciphertext
|
||||
|
||||
let ephemeralPublic: Data
|
||||
do {
|
||||
ephemeralPublic = try (ephemeralPublicData as NSData).removeKeyType() as Data
|
||||
} catch {
|
||||
owsFailDebug("failed to remove key type")
|
||||
throw DeviceNameError.invalidInput
|
||||
}
|
||||
|
||||
guard ephemeralPublic.count > 0 else {
|
||||
owsFailDebug("Invalid ephemeral public.")
|
||||
throw DeviceNameError.assertionFailure
|
||||
}
|
||||
guard receivedSyntheticIV.count == syntheticIVLength else {
|
||||
owsFailDebug("Invalid synthetic IV.")
|
||||
throw DeviceNameError.assertionFailure
|
||||
}
|
||||
guard ciphertext.count > 0 else {
|
||||
owsFailDebug("Invalid cipher text.")
|
||||
throw DeviceNameError.assertionFailure
|
||||
}
|
||||
|
||||
// master_secret = ECDH(identity_private, ephemeral_public)
|
||||
let masterSecret: Data
|
||||
do {
|
||||
masterSecret = try Curve25519.generateSharedSecret(fromPublicKey: ephemeralPublic,
|
||||
privateKey: identityKeyPair.privateKey)
|
||||
} catch {
|
||||
Logger.error("Could not generate shared secret: \(error)")
|
||||
throw error
|
||||
}
|
||||
|
||||
// cipher_key = HmacSHA256(key=HmacSHA256(key=master_secret, input=“cipher”), input=synthetic_iv)
|
||||
let cipherKey = try computeCipherKey(masterSecret: masterSecret, syntheticIV: receivedSyntheticIV)
|
||||
|
||||
// plaintext = AES-CTR(key=cipher_key, input=ciphertext, counter=0)
|
||||
//
|
||||
// An all-zeros IV corresponds to an AES CTR counter of zero.
|
||||
let ciphertextIV = Data(count: Int(kAES256CTR_IVLength))
|
||||
guard let ciphertextKey = OWSAES256Key(data: cipherKey) else {
|
||||
owsFailDebug("Invalid cipher key.")
|
||||
throw DeviceNameError.assertionFailure
|
||||
}
|
||||
guard let plaintextData = Cryptography.decryptAESCTR(cipherText: ciphertext, initializationVector: ciphertextIV, key: ciphertextKey) else {
|
||||
owsFailDebug("Could not decrypt cipher text.")
|
||||
throw DeviceNameError.assertionFailure
|
||||
}
|
||||
|
||||
// Verify the synthetic IV was correct.
|
||||
// constant_time_compare(HmacSHA256(key=HmacSHA256(key=master_secret, input=”auth”), input=plaintext)[0:16], synthetic_iv) == true
|
||||
let computedSyntheticIV = try computeSyntheticIV(masterSecret: masterSecret,
|
||||
plaintextData: plaintextData)
|
||||
guard receivedSyntheticIV.ows_constantTimeIsEqual(to: computedSyntheticIV) else {
|
||||
owsFailDebug("Synthetic IV did not match.")
|
||||
throw DeviceNameError.assertionFailure
|
||||
}
|
||||
|
||||
guard let plaintext = String(bytes: plaintextData, encoding: .utf8) else {
|
||||
owsFailDebug("Invalid plaintext.")
|
||||
throw DeviceNameError.invalidInput
|
||||
}
|
||||
|
||||
return plaintext
|
||||
}
|
||||
}
|
90
SignalServiceKit/tests/Util/DeviceNamesTest.swift
Normal file
90
SignalServiceKit/tests/Util/DeviceNamesTest.swift
Normal file
|
@ -0,0 +1,90 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
import Curve25519Kit
|
||||
|
||||
@testable import SignalServiceKit
|
||||
|
||||
class DeviceNamesTest: SSKBaseTestSwift {
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
// MARK:
|
||||
|
||||
func testNotEncrypted1() {
|
||||
|
||||
let identityKeyPair = Curve25519.generateKeyPair()
|
||||
|
||||
let plaintext = "alice"
|
||||
guard let plaintextData = plaintext.data(using: .utf8) else {
|
||||
XCTFail("Could not convert text to UTF-8.")
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
_ = try DeviceNames.decryptDeviceName(base64Data: plaintextData,
|
||||
identityKeyPair: identityKeyPair)
|
||||
XCTFail("Unexpectedly did not throw error.")
|
||||
} catch DeviceNameError.invalidInput {
|
||||
// Expected error.
|
||||
} catch {
|
||||
owsFailDebug("Unexpected \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testNotEncrypted2() {
|
||||
|
||||
let identityKeyPair = Curve25519.generateKeyPair()
|
||||
|
||||
let plaintext = "alice"
|
||||
guard let plaintextData = plaintext.data(using: .utf8) else {
|
||||
XCTFail("Could not convert text to UTF-8.")
|
||||
return
|
||||
}
|
||||
let base64Data = plaintextData.base64EncodedData()
|
||||
|
||||
do {
|
||||
_ = try DeviceNames.decryptDeviceName(base64Data: base64Data,
|
||||
identityKeyPair: identityKeyPair)
|
||||
XCTFail("Unexpectedly did not throw error.")
|
||||
} catch DeviceNameError.invalidInput {
|
||||
// Expected error.
|
||||
} catch {
|
||||
owsFailDebug("Unexpected \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testSimple() {
|
||||
|
||||
let identityKeyPair = Curve25519.generateKeyPair()
|
||||
|
||||
let plaintext = "alice"
|
||||
let encrypted: Data
|
||||
do {
|
||||
encrypted = try DeviceNames.encryptDeviceName(plaintext: plaintext,
|
||||
identityKeyPair: identityKeyPair)
|
||||
} catch {
|
||||
XCTFail("Failed with error: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
let decrypted: String
|
||||
do {
|
||||
decrypted = try DeviceNames.decryptDeviceName(base64Data: encrypted,
|
||||
identityKeyPair: identityKeyPair)
|
||||
} catch {
|
||||
XCTFail("Failed with error: \(error)")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(plaintext, decrypted)
|
||||
}
|
||||
}
|
|
@ -3,11 +3,6 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import SignalServiceKit
|
||||
|
|
Loading…
Reference in a new issue