Signal-iOS/SignalUI/Payments/MobileCoinAPI+Configuration.swift
Pete Walters 0386e10726
Update mobilecoin SDK to v6.0.0
Co-authored-by: Adam Mork <adammork@gmail.com>
2024-01-30 12:32:21 -06:00

852 lines
41 KiB
Swift

//
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import MobileCoin
import LibMobileCoin
import SignalServiceKit
extension MobileCoinAPI {
// MARK: - Environment
public enum Environment: CustomStringConvertible {
case mobileCoinAlphaNet
case mobileCoinMobileDev
case signalTestNet
case mobileCoinTestNet
case signalMainNet
case mobileCoinMainNet
public static var current: Environment {
if TSConstants.isUsingProductionService {
return .signalMainNet
} else {
return .signalTestNet
}
}
public var description: String {
switch self {
case .mobileCoinAlphaNet:
return ".mobileCoinAlphaNet"
case .mobileCoinMobileDev:
return ".mobileCoinMobileDev"
case .signalTestNet:
return ".signalTestNet"
case .mobileCoinTestNet:
return ".mobileCoinTestNet"
case .signalMainNet:
return ".signalMainNet"
case .mobileCoinMainNet:
return ".mobileCoinMainNet"
}
}
}
// MARK: - MobileCoinNetworkConfig
struct MobileCoinNetworkConfig {
let consensusUrls: [String]
let fogUrl: String
let fogReportUrl: String
static var signalMainNet: MobileCoinNetworkConfig {
let consensusUrls = [
"mc://node1.consensus.mob.production.namda.net",
"mc://node2.consensus.mob.production.namda.net"
]
let fogUrl = "fog://fog.prod.mobilecoinww.com"
let fogReportUrl = fogUrl
return MobileCoinNetworkConfig(consensusUrls: consensusUrls, fogUrl: fogUrl, fogReportUrl: fogReportUrl)
}
static var mobileCoinMainNet: MobileCoinNetworkConfig {
let consensusUrls = ["mc://node1.prod.mobilecoinww.com"]
let fogUrl = "fog://fog.prod.mobilecoinww.com"
let fogReportUrl = fogUrl
return MobileCoinNetworkConfig(consensusUrls: consensusUrls, fogUrl: fogUrl, fogReportUrl: fogReportUrl)
}
static var signalTestNet: MobileCoinNetworkConfig {
let consensusUrls = [
"mc://node1.consensus.mob.staging.namda.net",
"mc://node2.consensus.mob.staging.namda.net"
]
let fogUrl = "fog://fog.test.mobilecoin.com"
let fogReportUrl = fogUrl
return MobileCoinNetworkConfig(consensusUrls: consensusUrls, fogUrl: fogUrl, fogReportUrl: fogReportUrl)
}
static var mobileCoinTestNet: MobileCoinNetworkConfig {
let consensusUrls = ["mc://node1.test.mobilecoin.com"]
let fogUrl = "fog://fog.test.mobilecoin.com"
let fogReportUrl = fogUrl
return MobileCoinNetworkConfig(consensusUrls: consensusUrls, fogUrl: fogUrl, fogReportUrl: fogReportUrl)
}
static var mobileCoinAlphaNet: MobileCoinNetworkConfig {
let consensusUrls = ["mc://node1.alpha.development.mobilecoin.com"]
let fogUrl = "fog://fog.alpha.development.mobilecoin.com"
let fogReportUrl = fogUrl
return MobileCoinNetworkConfig(consensusUrls: consensusUrls, fogUrl: fogUrl, fogReportUrl: fogReportUrl)
}
static var mobileCoinMobileDev: MobileCoinNetworkConfig {
let consensusUrls = ["mc://consensus.mobiledev.mobilecoin.com"]
let fogUrl = "fog://fog.mobiledev.mobilecoin.com"
let fogReportUrl = fogUrl
return MobileCoinNetworkConfig(consensusUrls: consensusUrls, fogUrl: fogUrl, fogReportUrl: fogReportUrl)
}
static func networkConfig(environment: Environment) -> MobileCoinNetworkConfig {
switch environment {
case .mobileCoinAlphaNet:
return MobileCoinNetworkConfig.mobileCoinAlphaNet
case .mobileCoinMobileDev:
return MobileCoinNetworkConfig.mobileCoinMobileDev
case .mobileCoinTestNet:
return MobileCoinNetworkConfig.mobileCoinTestNet
case .signalTestNet:
return MobileCoinNetworkConfig.signalTestNet
case .mobileCoinMainNet:
return MobileCoinNetworkConfig.mobileCoinMainNet
case .signalMainNet:
return MobileCoinNetworkConfig.signalMainNet
}
}
}
// MARK: - AttestationInfo
struct AttestationRawInfo {
let measurement: Data
let hardeningAdvisories: [String]
static func of(_ measurement: Data, _ hardeningAdvisories: [String] = []) -> Self {
return AttestationRawInfo(measurement: measurement, hardeningAdvisories: hardeningAdvisories)
}
}
private struct AttestationInfo {
let productId: UInt16
let minimumSecurityVersion: UInt16
let allowedConfigAdvisories: [String]
let allowedHardeningAdvisories: [String]
let measurement: Measurement
enum Measurement {
case enclave(data: Data)
case signer(data: Data)
}
static let CONSENSUS_PRODUCT_ID: UInt16 = 1
static let CONSENSUS_SECURITY_VERSION: UInt16 = 1
static let FOG_VIEW_PRODUCT_ID: UInt16 = 3
static let FOG_VIEW_SECURITY_VERSION: UInt16 = 1
static let FOG_LEDGER_PRODUCT_ID: UInt16 = 2
static let FOG_LEDGER_SECURITY_VERSION: UInt16 = 1
static let FOG_REPORT_PRODUCT_ID: UInt16 = 4
static let FOG_REPORT_SECURITY_VERSION: UInt16 = 1
static var allAllowedHardeningAdvisories: [String] { ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"] }
init(
measurement: Measurement,
productId: UInt16,
minimumSecurityVersion: UInt16,
allowedConfigAdvisories: [String] = [],
allowedHardeningAdvisories: [String] = []
) {
self.measurement = measurement
self.productId = productId
self.minimumSecurityVersion = minimumSecurityVersion
self.allowedConfigAdvisories = allowedConfigAdvisories
self.allowedHardeningAdvisories = allowedHardeningAdvisories
}
static func consensus(
measurement: Measurement,
allowedConfigAdvisories: [String] = [],
allowedHardeningAdvisories: [String] = []
) -> AttestationInfo {
.init(
measurement: measurement,
productId: CONSENSUS_PRODUCT_ID,
minimumSecurityVersion: CONSENSUS_SECURITY_VERSION,
allowedConfigAdvisories: allowedConfigAdvisories,
allowedHardeningAdvisories: allowedHardeningAdvisories)
}
static func fogView(
measurement: Measurement,
allowedConfigAdvisories: [String] = [],
allowedHardeningAdvisories: [String] = []
) -> AttestationInfo {
.init(
measurement: measurement,
productId: FOG_VIEW_PRODUCT_ID,
minimumSecurityVersion: FOG_VIEW_SECURITY_VERSION,
allowedConfigAdvisories: allowedConfigAdvisories,
allowedHardeningAdvisories: allowedHardeningAdvisories)
}
static func fogKeyImage(
measurement: Measurement,
allowedConfigAdvisories: [String] = [],
allowedHardeningAdvisories: [String] = []
) -> AttestationInfo {
.init(
measurement: measurement,
productId: FOG_LEDGER_PRODUCT_ID,
minimumSecurityVersion: FOG_LEDGER_SECURITY_VERSION,
allowedConfigAdvisories: allowedConfigAdvisories,
allowedHardeningAdvisories: allowedHardeningAdvisories)
}
static func fogMerkleProof(
measurement: Measurement,
allowedConfigAdvisories: [String] = [],
allowedHardeningAdvisories: [String] = []
) -> AttestationInfo {
.init(
measurement: measurement,
productId: FOG_LEDGER_PRODUCT_ID,
minimumSecurityVersion: FOG_LEDGER_SECURITY_VERSION,
allowedConfigAdvisories: allowedConfigAdvisories,
allowedHardeningAdvisories: allowedHardeningAdvisories)
}
static func fogReport(
measurement: Measurement,
allowedConfigAdvisories: [String] = [],
allowedHardeningAdvisories: [String] = []
) -> AttestationInfo {
.init(
measurement: measurement,
productId: FOG_REPORT_PRODUCT_ID,
minimumSecurityVersion: FOG_REPORT_SECURITY_VERSION,
allowedConfigAdvisories: allowedConfigAdvisories,
allowedHardeningAdvisories: allowedHardeningAdvisories)
}
}
// MARK: - OWSAttestationConfig
private struct OWSAttestationConfig {
let consensus: Attestation
let fogView: Attestation
let fogKeyImage: Attestation
let fogMerkleProof: Attestation
let fogReport: Attestation
private static func buildAttestation(attestationInfo: [AttestationInfo]) throws -> MobileCoin.Attestation {
do {
let mrEnclaves = try attestationInfo.compactMap { attestationInfo -> MobileCoin.Attestation.MrEnclave? in
guard case let .enclave(measurement) = attestationInfo.measurement else {
return nil
}
return try MobileCoin.Attestation.MrEnclave.make(
mrEnclave: measurement,
allowedConfigAdvisories: attestationInfo.allowedConfigAdvisories,
allowedHardeningAdvisories: attestationInfo.allowedHardeningAdvisories
).get()
}
let mrSigners = try attestationInfo.compactMap { attestationInfo -> MobileCoin.Attestation.MrSigner? in
guard case let .signer(measurement) = attestationInfo.measurement else {
return nil
}
return try MobileCoin.Attestation.MrSigner.make(
mrSigner: measurement,
productId: attestationInfo.productId,
minimumSecurityVersion: attestationInfo.minimumSecurityVersion,
allowedConfigAdvisories: attestationInfo.allowedConfigAdvisories,
allowedHardeningAdvisories: attestationInfo.allowedHardeningAdvisories
).get()
}
return MobileCoin.Attestation(mrEnclaves: mrEnclaves, mrSigners: mrSigners)
} catch {
owsFailDebug("Error: \(error)")
throw error
}
}
private static func buildAttestationConfig(
consensus: [AttestationRawInfo],
fogView: [AttestationRawInfo],
fogLedger: [AttestationRawInfo],
fogReport: [AttestationRawInfo]
) -> OWSAttestationConfig {
let consensusAttestations = consensus
.map { info -> AttestationInfo in
.consensus(
measurement: .enclave(data: info.measurement),
allowedHardeningAdvisories: info.hardeningAdvisories)
}
let fogViewAttestations = fogView
.map { info -> AttestationInfo in
.fogView(
measurement: .enclave(data: info.measurement),
allowedHardeningAdvisories: info.hardeningAdvisories)
}
let fogKeyImageAttestations = fogLedger
.map { info -> AttestationInfo in
.fogKeyImage(
measurement: .enclave(data: info.measurement),
allowedHardeningAdvisories: info.hardeningAdvisories)
}
let fogMerkleProofAttestations = fogLedger
.map { info -> AttestationInfo in
.fogMerkleProof(
measurement: .enclave(data: info.measurement),
allowedHardeningAdvisories: info.hardeningAdvisories)
}
let fogReportAttestations = fogReport
.map { info -> AttestationInfo in
.fogReport(
measurement: .enclave(data: info.measurement),
allowedHardeningAdvisories: info.hardeningAdvisories)
}
return buildAttestationConfig(
consensus: consensusAttestations,
fogView: fogViewAttestations,
fogKeyImage: fogKeyImageAttestations,
fogMerkleProof: fogMerkleProofAttestations,
fogReport: fogReportAttestations)
}
private static func buildAttestationConfig(
mrSigner mrSignerData: Data,
allowedHardeningAdvisories: [String] = AttestationInfo.allAllowedHardeningAdvisories
) -> OWSAttestationConfig {
let consensus = AttestationInfo.consensus(
measurement: .signer(data: mrSignerData),
allowedHardeningAdvisories: allowedHardeningAdvisories)
let fogView = AttestationInfo.fogView(
measurement: .signer(data: mrSignerData),
allowedHardeningAdvisories: allowedHardeningAdvisories)
let fogReport = AttestationInfo.fogReport(
measurement: .signer(data: mrSignerData),
allowedHardeningAdvisories: allowedHardeningAdvisories)
let fogMerkleProof = AttestationInfo.fogMerkleProof(
measurement: .signer(data: mrSignerData),
allowedHardeningAdvisories: allowedHardeningAdvisories)
let fogKeyImage = AttestationInfo.fogKeyImage(
measurement: .signer(data: mrSignerData),
allowedHardeningAdvisories: allowedHardeningAdvisories)
return buildAttestationConfig(
consensus: [consensus],
fogView: [fogView],
fogKeyImage: [fogKeyImage],
fogMerkleProof: [fogMerkleProof],
fogReport: [fogReport])
}
private static func buildAttestationConfig(
consensus: [AttestationInfo],
fogView: [AttestationInfo],
fogKeyImage: [AttestationInfo],
fogMerkleProof: [AttestationInfo],
fogReport: [AttestationInfo]
) -> OWSAttestationConfig {
do {
return OWSAttestationConfig(
consensus: try buildAttestation(attestationInfo: consensus),
fogView: try buildAttestation(attestationInfo: fogView),
fogKeyImage: try buildAttestation(attestationInfo: fogKeyImage),
fogMerkleProof: try buildAttestation(attestationInfo: fogMerkleProof),
fogReport: try buildAttestation(attestationInfo: fogReport)
)
} catch {
owsFail("Invalid attestationConfig: \(error)")
}
}
static var mobileCoinMainNet: OWSAttestationConfig {
// These networks currently share the same attestation config.
signalMainNet
}
static var signalMainNet: OWSAttestationConfig {
// We need the old and new enclave values here.
let mrEnclaveConsensus: [AttestationRawInfo] = [
// ~June 23, 2021
.of(Data.data(fromHex: "653228afd2b02a6c28f1dc3b108b1dfa457d170b32ae8ec2978f941bd1655c83")!, ["INTEL-SA-00334"]),
// ~July 8th, 2022
.of(Data.data(fromHex: "733080d6ece4504f66ba606fa8163dae0a5220f3dbf6ca55fbafbac12c6f1897")!, ["INTEL-SA-00334"]),
// ~August 10th, 2022
.of(Data.data(fromHex: "d6e54e43c368f0fa2c5f13361afd303ee8f890424e99bd6c367f6164b5fff1b5")!, ["INTEL-SA-00334", "INTEL-SA-00615"]),
// ~November 2nd, 2022
.of(Data.data(fromHex: "207c9705bf640fdb960034595433ee1ff914f9154fbe4bc7fc8a97e912961e5c")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"]),
// ~December 15th, 2022
.of(Data.data(fromHex: "e35bc15ee92775029a60a715dca05d310ad40993f56ad43bca7e649ccc9021b5")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"]),
// ~May 30th, 2023
.of(Data.data(fromHex: "cd86d300c78f74ec23558cdaf734f90dd3e1bcdf8ae43fc827c6b4734ccb8862")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"])
]
let mrEnclaveFogView: [AttestationRawInfo] = [
// ~June 23, 2021
.of(Data.data(fromHex: "dd84abda7f05116e21fcd1ee6361b0ec29445fff0472131eaf37bf06255b567a")!, ["INTEL-SA-00334"]),
// ~July 8th, 2022
.of(Data.data(fromHex: "c64a3b04348b10596442868758875f312dc3a755b450805149774a091d2822d3")!, ["INTEL-SA-00334"]),
// ~August 10th, 2022
.of(Data.data(fromHex: "3d6e528ee0574ae3299915ea608b71ddd17cbe855d4f5e1c46df9b0d22b04cdb")!, ["INTEL-SA-00334", "INTEL-SA-00615"]),
// ~November 2nd, 2022
.of(Data.data(fromHex: "fd4c1c82cca13fa007be15a4c90e2b506c093b21c2e7021a055cbb34aa232f3f")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"]),
// ~December 15th, 2022
.of(Data.data(fromHex: "da209f4b24e8f4471bd6440c4e9f1b3100f1da09e2836d236e285b274901ed3b")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"]),
// ~May 30th, 2023
.of(Data.data(fromHex: "e94f6e6557b3fb85b27d804e2d005ee14a564cc50fc477797f2e5f9984b0bd79")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"])
]
// Report aka Ingest.
let mrEnclaveFogReport: [AttestationRawInfo] = [
// ~June 23, 2021
.of(Data.data(fromHex: "f3f7e9a674c55fb2af543513527b6a7872de305bac171783f6716a0bf6919499")!, ["INTEL-SA-00334"]),
// ~July 8th, 2022
.of(Data.data(fromHex: "660103d766cde0fd1e1cfb443b99e52da2ce0617d0dee42f8b875f7104942c6b")!, ["INTEL-SA-00334"]),
// ~August 10th, 2022
.of(Data.data(fromHex: "3e9bf61f3191add7b054f0e591b62f832854606f6594fd63faef1e2aedec4021")!, ["INTEL-SA-00334", "INTEL-SA-00615"]),
// ~November 2nd, 2022
.of(Data.data(fromHex: "3370f131b41e5a49ed97c4188f7a976461ac6127f8d222a37929ac46b46d560e")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"]),
// ~December 15th, 2022
.of(Data.data(fromHex: "a8af815564569aae3558d8e4e4be14d1bcec896623166a10494b4eaea3e1c48c")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"]),
// ~May 30th, 2023
.of(Data.data(fromHex: "7d10f5e72cacc87a6027b2be42ed4a74a6370a03c3476be754933eb18c404b0b")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"])
]
let mrEnclaveFogLedger: [AttestationRawInfo] = [
// ~June 23, 2021
.of(Data.data(fromHex: "89db0d1684fcc98258295c39f4ab68f7de5917ef30f0004d9a86f29930cebbbd")!, ["INTEL-SA-00334"]),
// ~July 8th, 2022
.of(Data.data(fromHex: "ed8ed6e1b4b6827e5543b25c1c13b9c06b478d819f8df912eb11fa140780fc51")!, ["INTEL-SA-00334"]),
// ~August 10th, 2022
.of(Data.data(fromHex: "92fb35d0f603ceb5eaf2988b24a41d4a4a83f8fb9cd72e67c3bc37960d864ad6")!, ["INTEL-SA-00334", "INTEL-SA-00615"]),
// ~November 2nd, 2022
.of(Data.data(fromHex: "dca7521ce4564cc2e54e1637e533ea9d1901c2adcbab0e7a41055e719fb0ff9d")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"]),
// ~December 15th, 2022
.of(Data.data(fromHex: "8c80a2b95a549fa8d928dd0f0771be4f3d774408c0f98bf670b1a2c390706bf3")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"]),
// ~May 30th, 2023
.of(Data.data(fromHex: "1dee8e2e98b7dc684506991d62856b2e572a0c23f5a7d698086e62f08fb997cc")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"])
]
return buildAttestationConfig(consensus: mrEnclaveConsensus,
fogView: mrEnclaveFogView,
fogLedger: mrEnclaveFogLedger,
fogReport: mrEnclaveFogReport)
}
static var mobileCoinTestNet: OWSAttestationConfig {
// These networks currently share the same attestation config.
signalTestNet
}
static var signalTestNet: OWSAttestationConfig {
// We need the old and new enclave values here.
let mrEnclaveConsensus: [AttestationRawInfo] = [
// ~June 2, 2021
.of(Data.data(fromHex: "9659ea738275b3999bf1700398b60281be03af5cb399738a89b49ea2496595af")!, ["INTEL-SA-00334"]),
// ~July 13, 2022
.of(Data.data(fromHex: "4f134dcfd9c0885956f2f9af0f05c2050d8bdee2dc63b468a640670d7adeb7f8")!, ["INTEL-SA-00334"]),
// ~Aug 16, 2022
.of(Data.data(fromHex: "01746f4dd25f8623d603534425ed45833687eca2b3ba25bdd87180b9471dac28")!, ["INTEL-SA-00334", "INTEL-SA-00615"]),
// ~Nov 8, 2022
.of(Data.data(fromHex: "5fe2b72fe5f01c269de0a3678728e7e97d823a953b053e43fbf934f439d290e6")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"]),
// ~Feb 2, 2023
.of(Data.data(fromHex: "4f3879bfffb7b9f86a33086202b6120a32da0ca159615fbbd6fbac6aa37bbf02")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"]),
// ~May 30, 2023
.of(Data.data(fromHex: "5341c6702a3312243c0f049f87259352ff32aa80f0f6426351c3dd063d817d7a")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"])
]
let mrEnclaveFogView: [AttestationRawInfo] = [
// ~June 2, 2021
.of(Data.data(fromHex: "e154f108c7758b5aa7161c3824c176f0c20f63012463bf3cc5651e678f02fb9e")!, ["INTEL-SA-00334"]),
// ~July 13, 2022
.of(Data.data(fromHex: "719ca43abbe02f507bb91ea11ff8bc900aa86363a7d7e77b8130426fc53d8684")!, ["INTEL-SA-00334"]),
// ~Aug 16, 2022
.of(Data.data(fromHex: "3d6e528ee0574ae3299915ea608b71ddd17cbe855d4f5e1c46df9b0d22b04cdb")!, ["INTEL-SA-00334", "INTEL-SA-00615"]),
// ~Nov 8, 2022
.of(Data.data(fromHex: "be1d711887530929fbc06ef8b77b618db15e9cd1dd0265559ea45f60a532ee52")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"]),
// ~Feb 2, 2023
.of(Data.data(fromHex: "f52b3dc018195eae42f543e64e976c818c06672b5489746e2bf74438d488181b")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"]),
// ~May 30, 2023
.of(Data.data(fromHex: "ac292a1ad27c0338a5159d5fab2bed3917ea144536cb13b5c1226d09a2fbc648")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"])
]
// Report aka Ingest.
let mrEnclaveFogReport: [AttestationRawInfo] = [
// ~June 2, 2021
.of(Data.data(fromHex: "a4764346f91979b4906d4ce26102228efe3aba39216dec1e7d22e6b06f919f11")!, ["INTEL-SA-00334"]),
// ~July 13, 2022
.of(Data.data(fromHex: "8f2f3bf81f24bf493fa6d76e29e0f081815022592b1e854f95bda750aece7452")!, ["INTEL-SA-00334"]),
// ~Aug 16, 2022
.of(Data.data(fromHex: "3e9bf61f3191add7b054f0e591b62f832854606f6594fd63faef1e2aedec4021")!, ["INTEL-SA-00334", "INTEL-SA-00615"]),
// ~Nov 8, 2022
.of(Data.data(fromHex: "d901b5c4960f49871a848fd157c7c0b03351253d65bb839698ddd5df138ad7b6")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"]),
// ~Feb 2, 2023
.of(Data.data(fromHex: "16d73984c2d2712156135ab69987ca78aca67a2cf4f0f2287ea584556f9d223a")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"]),
// ~May 30, 2023
.of(Data.data(fromHex: "248356aa0d3431abc45da1773cfd6191a4f2989a4a99da31f450bd7c461e312b")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"])
]
let mrEnclaveFogLedger: [AttestationRawInfo] = [
// ~June 2, 2021
.of(Data.data(fromHex: "768f7bea6171fb83d775ee8485e4b5fcebf5f664ca7e8b9ceef9c7c21e9d9bf3")!, ["INTEL-SA-00334"]),
// ~July 13, 2022
.of(Data.data(fromHex: "685481b33f2846585f33506ab65649c98a4a6d1244989651fd0fcde904ebd82f")!, ["INTEL-SA-00334"]),
// ~Aug 16, 2022
.of(Data.data(fromHex: "92fb35d0f603ceb5eaf2988b24a41d4a4a83f8fb9cd72e67c3bc37960d864ad6")!, ["INTEL-SA-00334", "INTEL-SA-00615"]),
// ~Nov 8, 2022
.of(Data.data(fromHex: "d5159ba907066384fae65842b5311f853b028c5ee4594f3b38dfc02acddf6fe3")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"]),
// ~Feb 2, 2023
.of(Data.data(fromHex: "23ececb2482e3b1d9e284502e2beb65ae76492f2791f3bfef50852ee64b883c3")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"]),
// ~Feb 2, 2023
.of(Data.data(fromHex: "b61188a6c946557f32e612eff5615908abd1b72ec11d8b7070595a92d4abbbf1")!, ["INTEL-SA-00334", "INTEL-SA-00615", "INTEL-SA-00657"])
]
return buildAttestationConfig(consensus: mrEnclaveConsensus,
fogView: mrEnclaveFogView,
fogLedger: mrEnclaveFogLedger,
fogReport: mrEnclaveFogReport)
}
static var mobileCoinAlphaNet: OWSAttestationConfig {
let mrSigner = Data([
126, 229, 226, 157, 116, 98, 63, 219, 198, 251, 241, 69, 75, 230, 243, 187, 11, 134, 193,
35, 102, 183, 180, 120, 173, 19, 53, 62, 68, 222, 132, 17
])
return buildAttestationConfig(mrSigner: mrSigner)
}
static var mobileCoinMobileDev: OWSAttestationConfig {
let mrSigner = Data([
191, 127, 169, 87, 166, 169, 74, 203, 88, 136, 81, 188, 135, 103, 224, 202, 87, 112, 108,
121, 244, 252, 42, 166, 188, 185, 147, 1, 44, 60, 56, 108
])
return buildAttestationConfig(mrSigner: mrSigner)
}
static func attestationConfig(environment: Environment) -> OWSAttestationConfig {
switch environment {
case .mobileCoinAlphaNet:
return mobileCoinAlphaNet
case .mobileCoinMobileDev:
return mobileCoinMobileDev
case .mobileCoinTestNet:
return mobileCoinTestNet
case .signalTestNet:
return signalTestNet
case .mobileCoinMainNet:
return mobileCoinMainNet
case .signalMainNet:
return signalMainNet
}
}
}
// MARK: - OWSAuthorization
struct OWSAuthorization {
let username: String
let password: String
private static let testAuthUsername = "user20220713"
private static let testAuthPassword = "user20220713:1657845591:298d68fd6b1438082b15"
static var mobileCoinAlpha: OWSAuthorization {
OWSAuthorization(username: testAuthUsername,
password: testAuthPassword)
}
static var mobileCoinMobileDev: OWSAuthorization {
OWSAuthorization(username: testAuthUsername,
password: testAuthPassword)
}
static var mobileCoinTestNet: OWSAuthorization {
owsFail("TODO: Set this value.")
}
static var mobileCoinMainNet: OWSAuthorization {
owsFail("TODO: Set this value.")
}
}
// MARK: - Certificates
private class Certificates: NSObject {
enum CertificateBundle {
case mainApp
case ssk
}
static func certificateData(forService certFilename: String,
type: String,
certificateBundle: CertificateBundle,
verifyDer: Bool = false) -> Data {
let bundle: Bundle = {
switch certificateBundle {
case .mainApp:
return Bundle(for: self)
case .ssk:
return Bundle(for: OWSHTTPSecurityPolicy.self)
}
}()
guard let filepath = bundle.path(forResource: certFilename, ofType: type) else {
owsFail("Missing cert: \(certFilename)")
}
guard OWSFileSystem.fileOrFolderExists(atPath: filepath) else {
owsFail("Missing cert: \(certFilename)")
}
let data = try! Data(contentsOf: URL(fileURLWithPath: filepath))
guard !data.isEmpty else {
owsFail("Invalid cert: \(certFilename)")
}
if verifyDer {
guard let certificate = SecCertificateCreateWithData(nil, data as CFData) else {
owsFail("Invalid cert: \(certFilename)")
}
let derData = SecCertificateCopyData(certificate) as Data
return derData
} else {
return data
}
}
}
// MARK: - TrustRootCerts
private class TrustRootCerts: NSObject {
static func anchorCertificates_mobileCoin() -> [Data] {
[
Certificates.certificateData(forService: "isrgrootx1", type: "der", certificateBundle: .ssk, verifyDer: true)
]
}
static func pinPolicy(environment: Environment) -> Result<OWSHTTPSecurityPolicy, Error> {
let trustRootCertDatas: [Data] = anchorCertificates_mobileCoin()
guard !trustRootCertDatas.isEmpty else {
return .failure(OWSAssertionError("No certificate data"))
}
let securityPolicy = OWSHTTPSecurityPolicy(pinnedCertificates: Set(trustRootCertDatas))
return .success(securityPolicy)
}
}
// MARK: - MobileCoinAccount
struct MobileCoinAccount {
let environment: Environment
let accountKey: MobileCoin.AccountKey
var publicAddress: MobileCoin.PublicAddress {
accountKey.publicAddress
}
fileprivate func authorization(signalAuthorization: OWSAuthorization) -> OWSAuthorization {
switch environment {
case .signalTestNet, .signalMainNet:
return signalAuthorization
case .mobileCoinAlphaNet:
return OWSAuthorization.mobileCoinAlpha
case .mobileCoinMobileDev:
return OWSAuthorization.mobileCoinMobileDev
case .mobileCoinTestNet, .mobileCoinMainNet:
return signalAuthorization
}
}
func buildClient(signalAuthorization: OWSAuthorization) throws -> MobileCoinClient {
Logger.info("Environment: \(environment)")
let networkConfig = MobileCoinNetworkConfig.networkConfig(environment: environment)
let authorization = self.authorization(signalAuthorization: signalAuthorization)
let attestationConfig = OWSAttestationConfig.attestationConfig(environment: environment)
let configResult = MobileCoinClient.Config.make(consensusUrls: networkConfig.consensusUrls,
consensusAttestation: attestationConfig.consensus,
fogUrls: [networkConfig.fogUrl],
fogViewAttestation: attestationConfig.fogView,
fogKeyImageAttestation: attestationConfig.fogKeyImage,
fogMerkleProofAttestation: attestationConfig.fogMerkleProof,
fogReportAttestation: attestationConfig.fogReport,
transportProtocol: .http)
let securityPolicy: OWSHTTPSecurityPolicy
do {
securityPolicy = try TrustRootCerts.pinPolicy(environment: environment).get()
} catch {
owsFailDebug("Error: \(error)")
throw error
}
switch configResult {
case .success(var config):
config.httpRequester = MobileCoinHttpRequester(securityPolicy: securityPolicy)
let clientResult = MobileCoinClient.make(accountKey: accountKey, config: config)
switch clientResult {
case .success(let client):
// There are separate FOG and consensus auth credentials which correspond to
// the consensus URL and fog URLs.
//
// We currently use a MobileCoin Consensus node and Signal Fog; Signal Fog
// requires an auth token but MobileCoin Consensus doesn't.
//
// TODO: We'll need to setConsensusBasicAuthorization() if/when we
// switch to Signal consensus.
client.setFogBasicAuthorization(username: authorization.username,
password: authorization.password)
return client
case .failure(let error):
owsFailDebug("Error: \(error)")
throw error
}
case .failure(let error):
owsFailDebug("Error: \(error)")
throw error
}
}
}
// MARK: - Fog Authority
private static func fogAuthoritySpki(environment: Environment) -> Data {
switch environment {
case .mobileCoinAlphaNet,
.mobileCoinMobileDev:
return Data(base64Encoded: "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyFOockvCEc9TcO1NvsiUfFVzvtDsR64UIRRUl3tBM2Bh8KBA932/Up86RtgJVnbslxuUCrTJZCV4dgd5hAo/mzuJOy9lAGxUTpwWWG0zZJdpt8HJRVLX76CBpWrWEt7JMoEmduvsCR8q7WkSNgT0iIoSXgT/hfWnJ8KGZkN4WBzzTH7hPrAcxPrzMI7TwHqUFfmOX7/gc+bDV5ZyRORrpuu+OR2BVObkocgFJLGmcz7KRuN7/dYtdYFpiKearGvbYqBrEjeo/15chI0Bu/9oQkjPBtkvMBYjyJPrD7oPP67i0ZfqV6xCj4nWwAD3bVjVqsw9cCBHgaykW8ArFFa0VCMdLy7UymYU5SQsfXrw/mHpr27Pp2Z0/7wpuFgJHL+0ARU48OiUzkXSHX+sBLov9X6f9tsh4q/ZRorXhcJi7FnUoagBxewvlfwQfcnLX3hp1wqoRFC4w1DC+ki93vIHUqHkNnayRsf1n48fSu5DwaFfNvejap7HCDIOpCCJmRVR8mVuxi6jgjOUa4Vhb/GCzxfNIn5ZYym1RuoE0TsFO+TPMzjed3tQvG7KemGFz3pQIryb43SbG7Q+EOzIigxYDytzcxOO5Jx7r9i+amQEiIcjBICwyFoEUlVJTgSpqBZGNpznoQ4I2m+uJzM+wMFsinTZN3mp4FU5UHjQsHKG+ZMCAwEAAQ==")!
case .signalTestNet, .mobileCoinTestNet:
return Data(base64Encoded:
"""
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvnB9wTbTOT5uoizRYaYbw7XIEkInl8E7MGOA\
Qj+xnC+F1rIXiCnc/t1+5IIWjbRGhWzo7RAwI5sRajn2sT4rRn9NXbOzZMvIqE4hmhmEzy1YQNDnfALA\
WNQ+WBbYGW+Vqm3IlQvAFFjVN1YYIdYhbLjAPdkgeVsWfcLDforHn6rR3QBZYZIlSBQSKRMY/tywTxeT\
CvK2zWcS0kbbFPtBcVth7VFFVPAZXhPi9yy1AvnldO6n7KLiupVmojlEMtv4FQkk604nal+j/dOplTAT\
V8a9AJBbPRBZ/yQg57EG2Y2MRiHOQifJx0S5VbNyMm9bkS8TD7Goi59aCW6OT1gyeotWwLg60JRZTfyJ\
7lYWBSOzh0OnaCytRpSWtNZ6barPUeOnftbnJtE8rFhF7M4F66et0LI/cuvXYecwVwykovEVBKRF4HOK\
9GgSm17mQMtzrD7c558TbaucOWabYR04uhdAc3s10MkuONWG0wIQhgIChYVAGnFLvSpp2/aQEq3xrRSE\
TxsixUIjsZyWWROkuA0IFnc8d7AmcnUBvRW7FT/5thWyk5agdYUGZ+7C1o69ihR1YxmoGh69fLMPIEOh\
Yh572+3ckgl2SaV4uo9Gvkz8MMGRBcMIMlRirSwhCfozV2RyT5Wn1NgPpyc8zJL7QdOhL7Qxb+5WjnCV\
rQYHI2cCAwEAAQ==
"""
)!
case .mobileCoinMainNet, .signalMainNet:
let mainNetFogAuthoritySpkiB64Encoded = """
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyr/99fvxi104MLgDgvWPVt01TuTJ+rN4qcNBUbF5i3EMM5z\
DZlugFHKPYPv7flCh5yDDYyLQHfWkxPQqCBAqlhSrCakvQH3HqDSpbM5FJg7pt0k5w+UQGWvP079iSEO5fMRhjE/lOR\
kvk3/UKr2yIXjZ19iEgP8hlhk9xkI42DSg0iIhk59k3wEYPMGSkVarqlPoKBzx2+11CieXnbCkRvoNwLvdzLceY8QNo\
Lc6h2/nht4bcjDCdB0MKNSKFLVp6XNHkVF66jC7QWTZRA/d4pgI5xa+GmkQ90zDZC2sBc+xfquVIVtk0nEvqSkUDZjv\
7AcJaq/VdPu4uj773ojrZz094PI4Q6sdbg7mfWrcq3ZQG8t9RDXD+6cgugCTFx2Cq/vJhDAPbQHmCEaMoXv2sRSfOhR\
jtMP1KmKUw5zXmAZa7s88+e7UXRQC+SS77V8s3hinE/I5Gqa/lzl73smhXx8l4CwGnXzlQ5h1lgEHnYLRFnIenNw/md\
MGKlWH5HwHLX3hIujERCPAnGLDt+4MjcUiU0spDH3hC9mjPVA3ltaA3+Mk2lDw0kLrZ4Gv3/Ik9WPlYetOuWteMkR1f\
z6VOc13+WoTJPz0dVrJsK2bUz+YvdBsoHQBbUpCkmnQ5Ok+yiuWa5vYikEJ24SEr8wUiZ4Oe12KVEcjyDIxp6QoE8kC\
AwEAAQ==
"""
return Data(base64Encoded: mainNetFogAuthoritySpkiB64Encoded)!
}
}
class func buildAccount(forPaymentsEntropy paymentsEntropy: Data) throws -> MobileCoinAccount {
let environment = Environment.current
let networkConfig = MobileCoinNetworkConfig.networkConfig(environment: environment)
let accountKey = try buildAccountKey(forPaymentsEntropy: paymentsEntropy,
networkConfig: networkConfig)
return MobileCoinAccount(environment: environment,
accountKey: accountKey)
}
class func buildAccountKey(forPaymentsEntropy paymentsEntropy: Data,
networkConfig: MobileCoinNetworkConfig) throws -> MobileCoin.AccountKey {
let passphrase = try Self.passphrase(forPaymentsEntropy: paymentsEntropy)
let mnemonic = passphrase.asPassphrase
let fogAuthoritySpki = Self.fogAuthoritySpki(environment: .current)
let fogReportId = ""
let accountIndex: UInt32 = 0
let result = MobileCoin.AccountKey.make(mnemonic: mnemonic,
fogReportUrl: networkConfig.fogReportUrl,
fogReportId: fogReportId,
fogAuthoritySpki: fogAuthoritySpki,
accountIndex: accountIndex)
switch result {
case .success(let accountKey):
return accountKey
case .failure(let error):
owsFailDebug("Error: \(error)")
throw error
}
}
}
final class MobileCoinHttpRequester: NSObject, HttpRequester {
static let defaultConfiguration: URLSessionConfiguration = {
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 30
config.timeoutIntervalForResource = 30
return config
}()
private let securityPolicy: OWSHTTPSecurityPolicy
init(securityPolicy: OWSHTTPSecurityPolicy) {
self.securityPolicy = securityPolicy
}
func request(
url: URL,
method: LibMobileCoin.HTTPMethod,
headers: [String: String]?,
body: Data?,
completion: @escaping (Result<LibMobileCoin.HTTPResponse, Error>) -> Void
) {
var request = URLRequest(url: url.absoluteURL)
request.httpMethod = method.rawValue
request.httpBody = body
request.allHTTPHeaderFields = headers
let owsUrlSession = OWSURLSession(securityPolicy: securityPolicy, configuration: Self.defaultConfiguration)
let promise = firstly(on: DispatchQueue.sharedUtility) {
owsUrlSession.dataTaskPromise(url.absoluteString, method: method.sskHTTPMethod, headers: headers, body: body)
}
promise.done { response in
let headerFields = response.responseHeaders
let statusCode = response.responseStatusCode
let responseData = response.responseBodyData
let url = response.requestUrl
let httpResponse = LibMobileCoin.HTTPResponse(statusCode: statusCode, url: url, allHeaderFields: headerFields, responseData: responseData)
completion(.success(httpResponse))
}.catch { error in
if let statusCode = error.httpStatusCode {
completion(.success(LibMobileCoin.HTTPResponse(statusCode: statusCode, url: nil, allHeaderFields: [:], responseData: nil)))
} else {
Logger.warn("MobileCoin http request failed \(error)")
completion(.failure(ConnectionError.invalidServerResponse("No Response")))
}
}
}
}
extension LibMobileCoin.HTTPMethod {
var sskHTTPMethod: SignalServiceKit.HTTPMethod {
switch self {
case .GET:
return .get
case .POST:
return .post
case .PUT:
return .put
case .HEAD:
return .head
case .PATCH:
return .patch
case .DELETE:
return .delete
}
}
}