|
|
@ -8,6 +8,7 @@ import Curve25519Kit
|
|
|
|
@objc
|
|
|
|
@objc
|
|
|
|
public enum DeviceNameError: Int, Error {
|
|
|
|
public enum DeviceNameError: Int, Error {
|
|
|
|
case assertionFailure
|
|
|
|
case assertionFailure
|
|
|
|
|
|
|
|
case invalidInput
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@objc
|
|
|
|
@objc
|
|
|
@ -15,6 +16,8 @@ public class DeviceNames: NSObject {
|
|
|
|
// Never instantiate this class.
|
|
|
|
// Never instantiate this class.
|
|
|
|
private override init() {}
|
|
|
|
private override init() {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static let syntheticIVLength: UInt = 16
|
|
|
|
|
|
|
|
|
|
|
|
@objc
|
|
|
|
@objc
|
|
|
|
public class func encryptDeviceName(plaintext: String,
|
|
|
|
public class func encryptDeviceName(plaintext: String,
|
|
|
|
identityKeyPair: ECKeyPair) throws -> Data {
|
|
|
|
identityKeyPair: ECKeyPair) throws -> Data {
|
|
|
@ -35,6 +38,39 @@ public class DeviceNames: NSObject {
|
|
|
|
throw 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)
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// TODO: Is this IV right?
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let protoBuilder = SignalIOSProtoDeviceName.builder(ephemeralPublic: ephemeralKeyPair.publicKey,
|
|
|
|
|
|
|
|
syntheticIv: syntheticIV,
|
|
|
|
|
|
|
|
ciphertext: ciphertext.ciphertext)
|
|
|
|
|
|
|
|
let protoData = try protoBuilder.buildSerializedData()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: 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]
|
|
|
|
// synthetic_iv = HmacSHA256(key=HmacSHA256(key=master_secret, input=“auth”), input=plaintext)[0:16]
|
|
|
|
guard let syntheticIVInput = "auth".data(using: .utf8) else {
|
|
|
|
guard let syntheticIVInput = "auth".data(using: .utf8) else {
|
|
|
|
owsFail("Could not convert text to UTF-8.")
|
|
|
|
owsFail("Could not convert text to UTF-8.")
|
|
|
@ -43,12 +79,15 @@ public class DeviceNames: NSObject {
|
|
|
|
owsFailDebug("Could not compute synthetic IV key.")
|
|
|
|
owsFailDebug("Could not compute synthetic IV key.")
|
|
|
|
throw DeviceNameError.assertionFailure
|
|
|
|
throw DeviceNameError.assertionFailure
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let ivLength: UInt = 16
|
|
|
|
guard let syntheticIV = Cryptography.truncatedSHA256HMAC(plaintextData, withHMACKey: syntheticIVKey, truncation: syntheticIVLength) else {
|
|
|
|
guard let syntheticIV = Cryptography.truncatedSHA256HMAC(plaintextData, withHMACKey: syntheticIVKey, truncation: ivLength) else {
|
|
|
|
|
|
|
|
owsFailDebug("Could not compute synthetic IV.")
|
|
|
|
owsFailDebug("Could not compute synthetic IV.")
|
|
|
|
throw DeviceNameError.assertionFailure
|
|
|
|
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)
|
|
|
|
// cipher_key = HmacSHA256(key=HmacSHA256(key=master_secret, “cipher”), input=synthetic_iv)
|
|
|
|
guard let cipherKeyInput = "cipher".data(using: .utf8) else {
|
|
|
|
guard let cipherKeyInput = "cipher".data(using: .utf8) else {
|
|
|
|
owsFail("Could not convert text to UTF-8.")
|
|
|
|
owsFail("Could not convert text to UTF-8.")
|
|
|
@ -61,27 +100,84 @@ public class DeviceNames: NSObject {
|
|
|
|
owsFailDebug("Could not compute cipher key.")
|
|
|
|
owsFailDebug("Could not compute cipher key.")
|
|
|
|
throw DeviceNameError.assertionFailure
|
|
|
|
throw DeviceNameError.assertionFailure
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return cipherKey
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// cipher_text = AES-CTR(key=cipher_key, input=plaintext, counter=0)
|
|
|
|
@objc
|
|
|
|
|
|
|
|
public class func decryptDeviceName(input: Data,
|
|
|
|
|
|
|
|
identityKeyPair: ECKeyPair) throws -> String {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
guard let protoData = Data(base64Encoded: input) else {
|
|
|
|
|
|
|
|
// Not necessarily an error; might be a legacy device name.
|
|
|
|
|
|
|
|
throw DeviceNameError.invalidInput
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 ephemeralPublic = proto.ephemeralPublic
|
|
|
|
|
|
|
|
let receivedSyntheticIV = proto.syntheticIv
|
|
|
|
|
|
|
|
let ciphertext = proto.ciphertext
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// TODO: Is this right?
|
|
|
|
// TODO: Is this IV right?
|
|
|
|
let cipherTextIV = Data(count: Int(kAES256CTR_IVLength))
|
|
|
|
let ciphertextIV = Data(count: Int(kAES256CTR_IVLength))
|
|
|
|
guard let cipherTextKey = OWSAES256Key(data: cipherKey) else {
|
|
|
|
guard let ciphertextKey = OWSAES256Key(data: cipherKey) else {
|
|
|
|
owsFailDebug("Invalid cipher key.")
|
|
|
|
owsFailDebug("Invalid cipher key.")
|
|
|
|
throw DeviceNameError.assertionFailure
|
|
|
|
throw DeviceNameError.assertionFailure
|
|
|
|
}
|
|
|
|
}
|
|
|
|
guard let cipherText: AES256CTREncryptionResult = Cryptography.encryptAESCTR(plaintextData: plaintextData, initializationVector: cipherTextIV, key: cipherTextKey) else {
|
|
|
|
guard let plaintextData = Cryptography.decryptAESCTR(cipherText: ciphertext, initializationVector: ciphertextIV, key: ciphertextKey) else {
|
|
|
|
owsFailDebug("Could not compute cipher text.")
|
|
|
|
owsFailDebug("Could not decrypt cipher text.")
|
|
|
|
throw DeviceNameError.assertionFailure
|
|
|
|
throw DeviceNameError.assertionFailure
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let protoBuilder = SignalIOSProtoDeviceName.builder(ephemeralPublic: ephemeralKeyPair.publicKey,
|
|
|
|
// Verify the synthetic IV was correct.
|
|
|
|
syntheticIv: syntheticIV,
|
|
|
|
// constant_time_compare(HmacSHA256(key=HmacSHA256(key=master_secret, input=”auth”), input=plaintext)[0:16], synthetic_iv) == true
|
|
|
|
ciphertext: cipherText.ciphertext)
|
|
|
|
let computedSyntheticIV = try computeSyntheticIV(masterSecret: masterSecret,
|
|
|
|
let protoData = try protoBuilder.buildSerializedData()
|
|
|
|
plaintextData: plaintextData)
|
|
|
|
|
|
|
|
guard receivedSyntheticIV.ows_constantTimeIsEqual(to: computedSyntheticIV) else {
|
|
|
|
|
|
|
|
owsFailDebug("Synthetic IV did not match.")
|
|
|
|
|
|
|
|
throw DeviceNameError.assertionFailure
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let protoDataBase64 = protoData.base64EncodedData()
|
|
|
|
guard let plaintext = String(bytes: plaintextData, encoding: .utf8) else {
|
|
|
|
|
|
|
|
owsFailDebug("Invalid plaintext.")
|
|
|
|
|
|
|
|
throw DeviceNameError.invalidInput
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return protoDataBase64
|
|
|
|
return plaintext
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|