Promise chain in the HttpRequester did not handle connection errors correctly. It was mapping all errors in the catch block to "No response" or the equivalent of "request failed and it does not have a status code" whereas in reality the errors in that catch block did have a status code and should be passed into the completion handler for re-attestation or similar. Fix checks for a valid status code from the error object in the catch block and if present passes it to the completion handler as a .success, otherwise, return a connection error sans the statusCode.
706 lines
33 KiB
Swift
706 lines
33 KiB
Swift
//
|
|
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import MobileCoin
|
|
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 .mobileCoinAlphaNet
|
|
}
|
|
}
|
|
|
|
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 consensusUrl: String
|
|
let fogUrl: String
|
|
|
|
static var signalMainNet: MobileCoinNetworkConfig {
|
|
let consensusUrl = "mc://node1.prod.mobilecoinww.com"
|
|
let fogUrl = "fog://service.fog.mob.production.namda.net"
|
|
return MobileCoinNetworkConfig(consensusUrl: consensusUrl, fogUrl: fogUrl)
|
|
}
|
|
|
|
static var mobileCoinMainNet: MobileCoinNetworkConfig {
|
|
let consensusUrl = "mc://node1.prod.mobilecoinww.com"
|
|
let fogUrl = "fog://fog.prod.mobilecoinww.com"
|
|
return MobileCoinNetworkConfig(consensusUrl: consensusUrl, fogUrl: fogUrl)
|
|
}
|
|
|
|
static var signalTestNet: MobileCoinNetworkConfig {
|
|
let consensusUrl = "mc://node1.test.mobilecoin.com"
|
|
let fogUrl = "fog://service.fog.mob.staging.namda.net"
|
|
return MobileCoinNetworkConfig(consensusUrl: consensusUrl, fogUrl: fogUrl)
|
|
}
|
|
|
|
static var mobileCoinTestNet: MobileCoinNetworkConfig {
|
|
let consensusUrl = "mc://node1.test.mobilecoin.com"
|
|
let fogUrl = "fog://fog.test.mobilecoin.com"
|
|
return MobileCoinNetworkConfig(consensusUrl: consensusUrl, fogUrl: fogUrl)
|
|
}
|
|
|
|
static var mobileCoinAlphaNet: MobileCoinNetworkConfig {
|
|
let consensusUrl = "mc://consensus.alpha.mobilecoin.com"
|
|
let fogUrl = "fog://fog.alpha.mobilecoin.com"
|
|
return MobileCoinNetworkConfig(consensusUrl: consensusUrl, fogUrl: fogUrl)
|
|
}
|
|
|
|
static var mobileCoinMobileDev: MobileCoinNetworkConfig {
|
|
let consensusUrl = "mc://consensus.mobiledev.mobilecoin.com"
|
|
let fogUrl = "fog://fog.mobiledev.mobilecoin.com"
|
|
return MobileCoinNetworkConfig(consensusUrl: consensusUrl, fogUrl: fogUrl)
|
|
}
|
|
|
|
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
|
|
|
|
private struct AttestationInfo {
|
|
let productId: UInt16
|
|
let minimumSecurityVersion: UInt16
|
|
let allowedConfigAdvisories: [String]
|
|
let allowedHardeningAdvisories: [String]
|
|
|
|
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 allowedHardeningAdvisories: [String] { ["INTEL-SA-00334"] }
|
|
|
|
init(productId: UInt16,
|
|
minimumSecurityVersion: UInt16,
|
|
allowedConfigAdvisories: [String] = [],
|
|
allowedHardeningAdvisories: [String] = []) {
|
|
|
|
self.productId = productId
|
|
self.minimumSecurityVersion = minimumSecurityVersion
|
|
self.allowedConfigAdvisories = allowedConfigAdvisories
|
|
self.allowedHardeningAdvisories = allowedHardeningAdvisories
|
|
}
|
|
|
|
static var consensus: AttestationInfo {
|
|
.init(productId: CONSENSUS_PRODUCT_ID,
|
|
minimumSecurityVersion: CONSENSUS_SECURITY_VERSION,
|
|
allowedHardeningAdvisories: Self.allowedHardeningAdvisories)
|
|
}
|
|
|
|
static var fogView: AttestationInfo {
|
|
.init(productId: FOG_VIEW_PRODUCT_ID,
|
|
minimumSecurityVersion: FOG_VIEW_SECURITY_VERSION,
|
|
allowedHardeningAdvisories: Self.allowedHardeningAdvisories)
|
|
}
|
|
|
|
static var fogKeyImage: AttestationInfo {
|
|
.init(productId: FOG_LEDGER_PRODUCT_ID,
|
|
minimumSecurityVersion: FOG_LEDGER_SECURITY_VERSION,
|
|
allowedHardeningAdvisories: Self.allowedHardeningAdvisories)
|
|
}
|
|
|
|
static var fogMerkleProof: AttestationInfo {
|
|
.init(productId: FOG_LEDGER_PRODUCT_ID,
|
|
minimumSecurityVersion: FOG_LEDGER_SECURITY_VERSION,
|
|
allowedHardeningAdvisories: Self.allowedHardeningAdvisories)
|
|
}
|
|
|
|
static var fogReport: AttestationInfo {
|
|
.init(productId: FOG_REPORT_PRODUCT_ID,
|
|
minimumSecurityVersion: FOG_REPORT_SECURITY_VERSION,
|
|
allowedHardeningAdvisories: Self.allowedHardeningAdvisories)
|
|
}
|
|
}
|
|
|
|
// MARK: - AttestationType
|
|
|
|
private enum AttestationType {
|
|
case mrSigner(mrSignerData: Data)
|
|
case mrEnclave(mrEnclaveData: Data)
|
|
case mrEnclaves(mrEnclaveDatas: [Data])
|
|
case mrSignerAndMrEnclave(mrSignerData: Data, mrEnclaveData: Data)
|
|
}
|
|
|
|
// MARK: - OWSAttestationConfig
|
|
|
|
private struct OWSAttestationConfig {
|
|
let consensus: Attestation
|
|
let fogView: Attestation
|
|
let fogKeyImage: Attestation
|
|
let fogMerkleProof: Attestation
|
|
let fogReport: Attestation
|
|
|
|
private static func buildMrSigner(mrSignerData: Data,
|
|
attestationInfo: AttestationInfo) throws -> MobileCoin.Attestation.MrSigner {
|
|
let result = MobileCoin.Attestation.MrSigner.make(mrSigner: mrSignerData,
|
|
productId: attestationInfo.productId,
|
|
minimumSecurityVersion: attestationInfo.minimumSecurityVersion,
|
|
allowedConfigAdvisories: attestationInfo.allowedConfigAdvisories,
|
|
allowedHardeningAdvisories: attestationInfo.allowedHardeningAdvisories)
|
|
switch result {
|
|
case .success(let mrSigner):
|
|
return mrSigner
|
|
case .failure(let error):
|
|
owsFailDebug("Error: \(error)")
|
|
throw error
|
|
}
|
|
}
|
|
|
|
private static func buildMrEnclave(mrEnclaveData: Data,
|
|
attestationInfo: AttestationInfo) throws -> MobileCoin.Attestation.MrEnclave {
|
|
let result = MobileCoin.Attestation.MrEnclave.make(mrEnclave: mrEnclaveData,
|
|
allowedConfigAdvisories: attestationInfo.allowedConfigAdvisories,
|
|
allowedHardeningAdvisories: attestationInfo.allowedHardeningAdvisories)
|
|
switch result {
|
|
case .success(let mrEnclave):
|
|
return mrEnclave
|
|
case .failure(let error):
|
|
owsFailDebug("Error: \(error)")
|
|
throw error
|
|
}
|
|
}
|
|
|
|
private static func buildMrEnclaves(mrEnclaveDatas: [Data],
|
|
attestationInfo: AttestationInfo) throws -> [MobileCoin.Attestation.MrEnclave] {
|
|
try mrEnclaveDatas.map {
|
|
try buildMrEnclave(mrEnclaveData: $0,
|
|
attestationInfo: attestationInfo)
|
|
}
|
|
}
|
|
|
|
private static func buildAttestation(attestationType: AttestationType,
|
|
attestationInfo: AttestationInfo) throws -> MobileCoin.Attestation {
|
|
switch attestationType {
|
|
case .mrSigner(let mrSignerData):
|
|
let mrSigner = try buildMrSigner(mrSignerData: mrSignerData,
|
|
attestationInfo: attestationInfo)
|
|
return MobileCoin.Attestation(mrSigners: [mrSigner])
|
|
case .mrEnclave(let mrEnclaveData):
|
|
let mrEnclave = try buildMrEnclave(mrEnclaveData: mrEnclaveData,
|
|
attestationInfo: attestationInfo)
|
|
return MobileCoin.Attestation(mrEnclaves: [mrEnclave])
|
|
case .mrEnclaves(let mrEnclaveDatas):
|
|
let mrEnclaves = try buildMrEnclaves(mrEnclaveDatas: mrEnclaveDatas,
|
|
attestationInfo: attestationInfo)
|
|
return MobileCoin.Attestation(mrEnclaves: mrEnclaves)
|
|
case .mrSignerAndMrEnclave(let mrSignerData, let mrEnclaveData):
|
|
let mrSigner = try buildMrSigner(mrSignerData: mrSignerData,
|
|
attestationInfo: attestationInfo)
|
|
let mrEnclave = try buildMrEnclave(mrEnclaveData: mrEnclaveData,
|
|
attestationInfo: attestationInfo)
|
|
return MobileCoin.Attestation(mrEnclaves: [mrEnclave], mrSigners: [mrSigner])
|
|
}
|
|
}
|
|
|
|
private static func buildAttestationConfig(mrSigner mrSignerData: Data) -> OWSAttestationConfig {
|
|
do {
|
|
let attestationType: AttestationType = .mrSigner(mrSignerData: mrSignerData)
|
|
func _buildAttestation(attestationInfo: AttestationInfo) throws -> MobileCoin.Attestation {
|
|
try buildAttestation(attestationType: attestationType, attestationInfo: attestationInfo)
|
|
}
|
|
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)")
|
|
}
|
|
}
|
|
|
|
private static func buildAttestationConfig(mrEnclaveConsensus: [Data],
|
|
mrEnclaveFogView: [Data],
|
|
mrEnclaveFogKeyImage: [Data],
|
|
mrEnclaveFogMerkleProof: [Data],
|
|
mrEnclaveFogReport: [Data]) -> OWSAttestationConfig {
|
|
do {
|
|
return OWSAttestationConfig(
|
|
consensus: try buildAttestation(attestationType: .mrEnclaves(mrEnclaveDatas: mrEnclaveConsensus),
|
|
attestationInfo: .consensus),
|
|
fogView: try buildAttestation(attestationType: .mrEnclaves(mrEnclaveDatas: mrEnclaveFogView),
|
|
attestationInfo: .fogView),
|
|
fogKeyImage: try buildAttestation(attestationType: .mrEnclaves(mrEnclaveDatas: mrEnclaveFogKeyImage),
|
|
attestationInfo: .fogKeyImage),
|
|
fogMerkleProof: try buildAttestation(attestationType: .mrEnclaves(mrEnclaveDatas: mrEnclaveFogMerkleProof),
|
|
attestationInfo: .fogMerkleProof),
|
|
fogReport: try buildAttestation(attestationType: .mrEnclaves(mrEnclaveDatas: mrEnclaveFogReport),
|
|
attestationInfo: .fogReport)
|
|
)
|
|
} catch {
|
|
owsFail("Invalid attestationConfig: \(error)")
|
|
}
|
|
}
|
|
|
|
static var mobileCoinMainNet: OWSAttestationConfig {
|
|
// These networks currently share the same attestation config.
|
|
signalMainNet
|
|
}
|
|
|
|
// consensus-enclave.css
|
|
// MRSIGNER: 0x2c1a561c4ab64cbc04bfa445cdf7bed9b2ad6f6b04d38d3137f3622b29fdb30e,
|
|
// MRENCLAVE: 0x653228afd2b02a6c28f1dc3b108b1dfa457d170b32ae8ec2978f941bd1655c83,
|
|
// ingest-enclave.css
|
|
// MRSIGNER: 0x2c1a561c4ab64cbc04bfa445cdf7bed9b2ad6f6b04d38d3137f3622b29fdb30e,
|
|
// MRENCLAVE: 0xf3f7e9a674c55fb2af543513527b6a7872de305bac171783f6716a0bf6919499,
|
|
// ledger-enclave.css
|
|
// MRSIGNER: 0x2c1a561c4ab64cbc04bfa445cdf7bed9b2ad6f6b04d38d3137f3622b29fdb30e,
|
|
// MRENCLAVE: 0x89db0d1684fcc98258295c39f4ab68f7de5917ef30f0004d9a86f29930cebbbd,
|
|
// view-enclave.css
|
|
// MRSIGNER: 0x2c1a561c4ab64cbc04bfa445cdf7bed9b2ad6f6b04d38d3137f3622b29fdb30e,
|
|
// MRENCLAVE: 0xdd84abda7f05116e21fcd1ee6361b0ec29445fff0472131eaf37bf06255b567a,
|
|
static var signalMainNet: OWSAttestationConfig {
|
|
// We need the old and new enclave values here.
|
|
let mrEnclaveConsensus = [
|
|
// ~June 23, 2021
|
|
Data.data(fromHex: "653228afd2b02a6c28f1dc3b108b1dfa457d170b32ae8ec2978f941bd1655c83")!
|
|
]
|
|
let mrEnclaveFogView = [
|
|
// ~June 23, 2021
|
|
Data.data(fromHex: "dd84abda7f05116e21fcd1ee6361b0ec29445fff0472131eaf37bf06255b567a")!
|
|
]
|
|
// Report aka Ingest.
|
|
let mrEnclaveFogReport = [
|
|
// ~June 23, 2021
|
|
Data.data(fromHex: "f3f7e9a674c55fb2af543513527b6a7872de305bac171783f6716a0bf6919499")!
|
|
]
|
|
let mrEnclaveFogLedger = [
|
|
// ~June 23, 2021
|
|
Data.data(fromHex: "89db0d1684fcc98258295c39f4ab68f7de5917ef30f0004d9a86f29930cebbbd")!
|
|
]
|
|
return buildAttestationConfig(mrEnclaveConsensus: mrEnclaveConsensus,
|
|
mrEnclaveFogView: mrEnclaveFogView,
|
|
mrEnclaveFogKeyImage: mrEnclaveFogLedger,
|
|
mrEnclaveFogMerkleProof: mrEnclaveFogLedger,
|
|
mrEnclaveFogReport: mrEnclaveFogReport)
|
|
}
|
|
|
|
// consensus
|
|
// MRSIGNER: 0xbf7fa957a6a94acb588851bc8767e0ca57706c79f4fc2aa6bcb993012c3c386c,
|
|
// MRENCLAVE: 0x9659ea738275b3999bf1700398b60281be03af5cb399738a89b49ea2496595af,
|
|
// ingest
|
|
// MRSIGNER: 0xbf7fa957a6a94acb588851bc8767e0ca57706c79f4fc2aa6bcb993012c3c386c,
|
|
// MRENCLAVE: 0xa4764346f91979b4906d4ce26102228efe3aba39216dec1e7d22e6b06f919f11,
|
|
// ledger
|
|
// MRSIGNER: 0xbf7fa957a6a94acb588851bc8767e0ca57706c79f4fc2aa6bcb993012c3c386c,
|
|
// MRENCLAVE: 0x768f7bea6171fb83d775ee8485e4b5fcebf5f664ca7e8b9ceef9c7c21e9d9bf3,
|
|
// view
|
|
// MRSIGNER: 0xbf7fa957a6a94acb588851bc8767e0ca57706c79f4fc2aa6bcb993012c3c386c,
|
|
// MRENCLAVE: 0xe154f108c7758b5aa7161c3824c176f0c20f63012463bf3cc5651e678f02fb9e,
|
|
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 = [
|
|
// ~June 2, 2021
|
|
Data.data(fromHex: "9659ea738275b3999bf1700398b60281be03af5cb399738a89b49ea2496595af")!
|
|
]
|
|
let mrEnclaveFogView = [
|
|
// ~June 2, 2021
|
|
Data.data(fromHex: "e154f108c7758b5aa7161c3824c176f0c20f63012463bf3cc5651e678f02fb9e")!
|
|
]
|
|
// Report aka Ingest.
|
|
let mrEnclaveFogReport = [
|
|
// ~June 2, 2021
|
|
Data.data(fromHex: "a4764346f91979b4906d4ce26102228efe3aba39216dec1e7d22e6b06f919f11")!
|
|
]
|
|
let mrEnclaveFogLedger = [
|
|
// ~June 2, 2021
|
|
Data.data(fromHex: "768f7bea6171fb83d775ee8485e4b5fcebf5f664ca7e8b9ceef9c7c21e9d9bf3")!
|
|
]
|
|
return buildAttestationConfig(mrEnclaveConsensus: mrEnclaveConsensus,
|
|
mrEnclaveFogView: mrEnclaveFogView,
|
|
mrEnclaveFogKeyImage: mrEnclaveFogLedger,
|
|
mrEnclaveFogMerkleProof: mrEnclaveFogLedger,
|
|
mrEnclaveFogReport: 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 = "user1"
|
|
private static let testAuthPassword = "user1:1602029157:9bdcd071b4d7b276a4a6"
|
|
|
|
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
|
|
|
|
@objc
|
|
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
|
|
|
|
@objc
|
|
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
|
|
|
|
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(consensusUrl: networkConfig.consensusUrl,
|
|
consensusAttestation: attestationConfig.consensus,
|
|
fogUrl: 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 .mobileCoinTestNet:
|
|
return Certificates.certificateData(forService: "authority-mobilecoin-testnet", type: "pem", certificateBundle: .ssk)
|
|
case .signalTestNet:
|
|
return Data(base64Encoded: "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoCMq8nnjTq5EEQ4EI7yr" +
|
|
"ABL9P4y4h1P/h0DepWgXx+w/fywcfRSZINxbaMpvcV3uSJayExrpV1KmaS2wfASe" +
|
|
"YhSj+rEzAm0XUOw3Q94NOx5A/dOQag/d1SS6/QpF3PQYZTULnRFetmM4yzEnXsXc" +
|
|
"WtzEu0hh02wYJbLeAq4CCcPTPe2qckrbUP9sD18/KOzzNeypF4p5dQ2m/ezfxtga" +
|
|
"LvdUMVDVIAs2v9a5iu6ce4bIcwTIUXgX0w3+UKRx8zqowc3HIqo9yeaGn4ZOwQHv" +
|
|
"AJZecPmb2pH1nK+BtDUvHpvf+Y3/NJxwh+IPp6Ef8aoUxs2g5oIBZ3Q31fjS2Bh2" +
|
|
"gmwoVooyytEysPAHvRPVBxXxLi36WpKfk1Vq8K7cgYh3IraOkH2/l2Pyi8EYYFkW" +
|
|
"sLYofYogaiPzVoq2ZdcizfoJWIYei5mgq+8m0ZKZYLebK1i2GdseBJNIbSt3wCNX" +
|
|
"ZxyN6uqFHOCB29gmA5cbKvs/j9mDz64PJe9LCanqcDQV1U5l9dt9UdmUt7Ab1PjB" +
|
|
"toIFaP+u473Z0hmZdCgAivuiBMMYMqt2V2EIw4IXLASE3roLOYp0p7h0IQHb+lVI" +
|
|
"uEl0ZmwAI30ZmzgcWc7RBeWD1/zNt55zzhfPRLx/DfDY5Kdp6oFHWMvI2r1/oZkd" +
|
|
"hjFp7pV6qrl7vOyR5QqmuRkCAwEAAQ==")!
|
|
case .mobileCoinMainNet:
|
|
owsFail("TODO: Set this value.")
|
|
case .signalMainNet:
|
|
return Data(base64Encoded: "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxaNIOgcoQtq0S64dFVha6rn0hDv/ec+W0cKRdFKygiyp5xuWdW3YKVAkK1PPgSDD2dwmMN/1xcGWrPMqezx1h1xCzbr7HL7XvLyFyoiMB2JYd7aoIuGIbHpCOlpm8ulVnkOX7BNuo0Hi2F0AAHyTPwmtVMt6RZmae1Z/Pl2I06+GgWN6vufV7jcjiLT3yQPsn1kVSj+DYCf3zq+1sCknKIvoRPMdQh9Vi3I/fqNXz00DSB7lt3v5/FQ6sPbjljqdGD/qUl4xKRW+EoDLlAUfzahomQOLXVAlxcws3Ua5cZUhaJi6U5jVfw5Ng2N7FwX/D5oX82r9o3xcFqhWpGnfSxSrAudv1X7WskXomKhUzMl/0exWpcJbdrQWB/qshzi9Et7HEDNY+xEDiwGiikj5f0Lb+QA4mBMlAhY/cmWec8NKi1gf3Dmubh6c3sNteb9OpZ/irA3AfE8jI37K1rvezDI8kbNtmYgvyhfz0lZzRT2WAfffiTe565rJglvKa8rh8eszKk2HC9DyxUb/TcyL/OjGhe2fDYO2t6brAXCqjPZAEkVJq3I30NmnPdE19SQeP7wuaUIb3U7MGxoZC/NuJoxZh8svvZ8cyqVjG+dOQ6/UfrFY0jiswT8AsrfqBis/ZV5EFukZr+zbPtg2MH0H3tSJ14BCLduvc7FY6lAZmOcCAwEAAQ==")!
|
|
}
|
|
}
|
|
|
|
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.fogUrl,
|
|
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: MobileCoin.HTTPMethod,
|
|
headers: [String: String]?,
|
|
body: Data?,
|
|
completion: @escaping (Result<MobileCoin.HTTPResponse, Error>) -> Void
|
|
) {
|
|
var request = URLRequest(url: url.absoluteURL)
|
|
request.httpMethod = method.rawValue
|
|
request.httpBody = body
|
|
|
|
if let headers = headers {
|
|
var owsHeaders = OWSHttpHeaders(httpHeaders: headers)
|
|
owsHeaders.addHeaderMap(headers, overwriteOnConflict: true)
|
|
owsAssertDebug(owsHeaders.headers.count == headers.count)
|
|
headers.forEach({ key, value in
|
|
request.setValue(value, forHTTPHeaderField: key)
|
|
})
|
|
}
|
|
|
|
let owsUrlSession = OWSURLSession(securityPolicy: securityPolicy, configuration: Self.defaultConfiguration)
|
|
|
|
firstly(on: .sharedUtility) {
|
|
owsUrlSession.dataTaskPromise(url.absoluteString, method: method.sskHTTPMethod, headers: headers, body: body)
|
|
}.done { response in
|
|
let headerFields = response.responseHeaders
|
|
let statusCode = response.responseStatusCode
|
|
let responseData = response.responseBodyData
|
|
let url = response.requestUrl
|
|
let httpResponse = MobileCoin.HTTPResponse(statusCode: statusCode, url: url, allHeaderFields: headerFields, responseData: responseData)
|
|
completion(.success(httpResponse))
|
|
}.catch { error in
|
|
if let statusCode = error.httpStatusCode {
|
|
completion(.success(MobileCoin.HTTPResponse(statusCode: statusCode, url: nil, allHeaderFields: [:], responseData: nil)))
|
|
} else {
|
|
owsFailDebug("MobileCoin http request failed \(error)")
|
|
completion(.failure(ConnectionError.invalidServerResponse("No Response")))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extension MobileCoin.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
|
|
}
|
|
}
|
|
}
|