1*fb441a64SWill Schurman // Copyright 2015-present 650 Industries. All rights reserved. 2*fb441a64SWill Schurman 3*fb441a64SWill Schurman import Foundation 4*fb441a64SWill Schurman import CommonCrypto 5*fb441a64SWill Schurman 6*fb441a64SWill Schurman internal struct CodeSigningMetadataFields { 7*fb441a64SWill Schurman static let KeyIdFieldKey = "keyid" 8*fb441a64SWill Schurman static let AlgorithmFieldKey = "alg" 9*fb441a64SWill Schurman } 10*fb441a64SWill Schurman 11*fb441a64SWill Schurman internal enum ValidationResult { 12*fb441a64SWill Schurman case valid 13*fb441a64SWill Schurman case invalid 14*fb441a64SWill Schurman case skipped 15*fb441a64SWill Schurman } 16*fb441a64SWill Schurman 17*fb441a64SWill Schurman internal final class SignatureValidationResult { 18*fb441a64SWill Schurman private(set) internal var validationResult: ValidationResult 19*fb441a64SWill Schurman private(set) internal var expoProjectInformation: ExpoProjectInformation? 20*fb441a64SWill Schurman 21*fb441a64SWill Schurman required init(validationResult: ValidationResult, expoProjectInformation: ExpoProjectInformation?) { 22*fb441a64SWill Schurman self.validationResult = validationResult 23*fb441a64SWill Schurman self.expoProjectInformation = expoProjectInformation 24*fb441a64SWill Schurman } 25*fb441a64SWill Schurman } 26*fb441a64SWill Schurman 27*fb441a64SWill Schurman @objc(EXUpdatesCodeSigningConfiguration) 28*fb441a64SWill Schurman public final class CodeSigningConfiguration: NSObject { 29*fb441a64SWill Schurman private var embeddedCertificateString: String 30*fb441a64SWill Schurman private var keyIdFromMetadata: String 31*fb441a64SWill Schurman private var algorithmFromMetadata: CodeSigningAlgorithm 32*fb441a64SWill Schurman private var includeManifestResponseCertificateChain: Bool 33*fb441a64SWill Schurman private var allowUnsignedManifests: Bool 34*fb441a64SWill Schurman 35*fb441a64SWill Schurman internal required init( 36*fb441a64SWill Schurman embeddedCertificateString: String, 37*fb441a64SWill Schurman metadata: [String: String], 38*fb441a64SWill Schurman includeManifestResponseCertificateChain: Bool, 39*fb441a64SWill Schurman allowUnsignedManifests: Bool 40*fb441a64SWill Schurman ) throws { 41*fb441a64SWill Schurman self.embeddedCertificateString = embeddedCertificateString 42*fb441a64SWill Schurman self.keyIdFromMetadata = metadata[CodeSigningMetadataFields.KeyIdFieldKey] ?? SignatureHeaderInfo.DefaultKeyId 43*fb441a64SWill Schurman self.algorithmFromMetadata = try CodeSigningAlgorithm.parseFromString(metadata[CodeSigningMetadataFields.AlgorithmFieldKey]) 44*fb441a64SWill Schurman self.includeManifestResponseCertificateChain = includeManifestResponseCertificateChain 45*fb441a64SWill Schurman self.allowUnsignedManifests = allowUnsignedManifests 46*fb441a64SWill Schurman } 47*fb441a64SWill Schurman 48*fb441a64SWill Schurman /** 49*fb441a64SWill Schurman * String escaping is defined by https://www.rfc-editor.org/rfc/rfc8941.html#section-3.3.3 50*fb441a64SWill Schurman */ escapeStructuredHeaderStringItemnull51*fb441a64SWill Schurman private static func escapeStructuredHeaderStringItem(_ str: String) -> String { 52*fb441a64SWill Schurman return str.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\"") 53*fb441a64SWill Schurman } 54*fb441a64SWill Schurman createAcceptSignatureHeadernull55*fb441a64SWill Schurman internal func createAcceptSignatureHeader() -> String { 56*fb441a64SWill Schurman return "sig, keyid=\"\(CodeSigningConfiguration.escapeStructuredHeaderStringItem(keyIdFromMetadata))\", alg=\"\(CodeSigningConfiguration.escapeStructuredHeaderStringItem(algorithmFromMetadata.rawValue))\"" 57*fb441a64SWill Schurman } 58*fb441a64SWill Schurman 59*fb441a64SWill Schurman internal func validateSignature( 60*fb441a64SWill Schurman signature: String?, 61*fb441a64SWill Schurman signedData: Data, 62*fb441a64SWill Schurman manifestResponseCertificateChain: String? 63*fb441a64SWill Schurman ) throws -> SignatureValidationResult { 64*fb441a64SWill Schurman guard let signature = signature else { 65*fb441a64SWill Schurman if !self.allowUnsignedManifests { 66*fb441a64SWill Schurman throw CodeSigningError.SignatureHeaderMissing 67*fb441a64SWill Schurman } else { 68*fb441a64SWill Schurman // no-op 69*fb441a64SWill Schurman return SignatureValidationResult(validationResult: ValidationResult.skipped, expoProjectInformation: nil) 70*fb441a64SWill Schurman } 71*fb441a64SWill Schurman } 72*fb441a64SWill Schurman 73*fb441a64SWill Schurman return try validateSignatureInternal( 74*fb441a64SWill Schurman signatureHeaderInfo: try SignatureHeaderInfo.parseSignatureHeader(signatureHeader: signature), 75*fb441a64SWill Schurman signedData: signedData, 76*fb441a64SWill Schurman manifestResponseCertificateChain: manifestResponseCertificateChain 77*fb441a64SWill Schurman ) 78*fb441a64SWill Schurman } 79*fb441a64SWill Schurman 80*fb441a64SWill Schurman private func validateSignatureInternal( 81*fb441a64SWill Schurman signatureHeaderInfo: SignatureHeaderInfo, 82*fb441a64SWill Schurman signedData: Data, 83*fb441a64SWill Schurman manifestResponseCertificateChain: String? 84*fb441a64SWill Schurman ) throws -> SignatureValidationResult { 85*fb441a64SWill Schurman let certificateChain: CertificateChain 86*fb441a64SWill Schurman if self.includeManifestResponseCertificateChain { 87*fb441a64SWill Schurman certificateChain = try CertificateChain( 88*fb441a64SWill Schurman certificateStrings: CodeSigningConfiguration.separateCertificateChain( 89*fb441a64SWill Schurman certificateChainInManifestResponse: manifestResponseCertificateChain ?? "" 90*fb441a64SWill Schurman ) + [self.embeddedCertificateString] 91*fb441a64SWill Schurman ) 92*fb441a64SWill Schurman } else { 93*fb441a64SWill Schurman // check that the key used to sign the response is the same as the key in the code signing certificate 94*fb441a64SWill Schurman if signatureHeaderInfo.keyId != self.keyIdFromMetadata { 95*fb441a64SWill Schurman throw CodeSigningError.KeyIdMismatchError 96*fb441a64SWill Schurman } 97*fb441a64SWill Schurman 98*fb441a64SWill Schurman // note that a mismatched algorithm doesn't fail early. it still tries to verify the signature with the 99*fb441a64SWill Schurman // algorithm specified in the configuration 100*fb441a64SWill Schurman if signatureHeaderInfo.algorithm != self.algorithmFromMetadata { 101*fb441a64SWill Schurman NSLog("Key with alg=\(signatureHeaderInfo.algorithm) from signature does not match client configuration algorithm, continuing") 102*fb441a64SWill Schurman } 103*fb441a64SWill Schurman 104*fb441a64SWill Schurman certificateChain = try CertificateChain(certificateStrings: [embeddedCertificateString]) 105*fb441a64SWill Schurman } 106*fb441a64SWill Schurman 107*fb441a64SWill Schurman // For now only SHA256withRSA is supported. This technically should be `metadata.algorithm` but 108*fb441a64SWill Schurman // it breaks down when metadata is for a different key than the signing key (the case where intermediate 109*fb441a64SWill Schurman // certs are served alongside the manifest and the metadata is for the root embedded cert). 110*fb441a64SWill Schurman // In the future if more methods are added we will need to be sure that we think about how to 111*fb441a64SWill Schurman // specify what algorithm should be used in the chain case. One approach may be that in the case of 112*fb441a64SWill Schurman // chains served alongside the manifest we fork the behavior to trust the `info.algorithm` while keeping 113*fb441a64SWill Schurman // `metadata.algorithm` for the embedded case. 114*fb441a64SWill Schurman let (secCertificate, _) = try certificateChain.codeSigningCertificate() 115*fb441a64SWill Schurman 116*fb441a64SWill Schurman guard let publicKey = secCertificate.publicKey else { 117*fb441a64SWill Schurman throw CodeSigningError.CertificateMissingPublicKeyError 118*fb441a64SWill Schurman } 119*fb441a64SWill Schurman 120*fb441a64SWill Schurman guard let signatureData = Data(base64Encoded: signatureHeaderInfo.signature) else { 121*fb441a64SWill Schurman throw CodeSigningError.SignatureEncodingError 122*fb441a64SWill Schurman } 123*fb441a64SWill Schurman 124*fb441a64SWill Schurman let isValid = try self.verifyRSASHA256SignedData(signedData: signedData, signatureData: signatureData, publicKey: publicKey) 125*fb441a64SWill Schurman return SignatureValidationResult( 126*fb441a64SWill Schurman validationResult: isValid ? ValidationResult.valid : ValidationResult.invalid, 127*fb441a64SWill Schurman expoProjectInformation: try certificateChain.codeSigningCertificate().1.expoProjectInformation() 128*fb441a64SWill Schurman ) 129*fb441a64SWill Schurman } 130*fb441a64SWill Schurman verifyRSASHA256SignedDatanull131*fb441a64SWill Schurman private func verifyRSASHA256SignedData(signedData: Data, signatureData: Data, publicKey: SecKey) throws -> Bool { 132*fb441a64SWill Schurman let hashBytes = signedData.sha256() 133*fb441a64SWill Schurman var error: Unmanaged<CFError>? 134*fb441a64SWill Schurman if SecKeyVerifySignature(publicKey, .rsaSignatureDigestPKCS1v15SHA256, hashBytes as CFData, signatureData as CFData, &error) { 135*fb441a64SWill Schurman return true 136*fb441a64SWill Schurman } else { 137*fb441a64SWill Schurman if let error = error, (error.takeRetainedValue() as Error as NSError).code != errSecVerifyFailed { 138*fb441a64SWill Schurman NSLog("Sec key signature verification error: %@", error.takeRetainedValue().localizedDescription) 139*fb441a64SWill Schurman throw CodeSigningError.SecurityFrameworkError 140*fb441a64SWill Schurman } 141*fb441a64SWill Schurman return false 142*fb441a64SWill Schurman } 143*fb441a64SWill Schurman } 144*fb441a64SWill Schurman separateCertificateChainnull145*fb441a64SWill Schurman internal static func separateCertificateChain(certificateChainInManifestResponse: String) -> [String] { 146*fb441a64SWill Schurman let startDelimiter = "-----BEGIN CERTIFICATE-----" 147*fb441a64SWill Schurman let endDelimiter = "-----END CERTIFICATE-----" 148*fb441a64SWill Schurman var certificateStringList = [] as [String] 149*fb441a64SWill Schurman 150*fb441a64SWill Schurman var currStartIndex = certificateChainInManifestResponse.startIndex 151*fb441a64SWill Schurman while true { 152*fb441a64SWill Schurman let startIndex = certificateChainInManifestResponse.firstIndex(of: startDelimiter, startingAt: currStartIndex) 153*fb441a64SWill Schurman let endIndex = certificateChainInManifestResponse.firstIndex(of: endDelimiter, startingAt: currStartIndex) 154*fb441a64SWill Schurman 155*fb441a64SWill Schurman if let startIndex = startIndex, let endIndex = endIndex { 156*fb441a64SWill Schurman let newEndIndex = certificateChainInManifestResponse.index(endIndex, offsetBy: endDelimiter.count) 157*fb441a64SWill Schurman certificateStringList.append(String(certificateChainInManifestResponse[startIndex..<newEndIndex])) 158*fb441a64SWill Schurman currStartIndex = newEndIndex 159*fb441a64SWill Schurman } else { 160*fb441a64SWill Schurman break 161*fb441a64SWill Schurman } 162*fb441a64SWill Schurman } 163*fb441a64SWill Schurman 164*fb441a64SWill Schurman return certificateStringList 165*fb441a64SWill Schurman } 166*fb441a64SWill Schurman } 167*fb441a64SWill Schurman 168*fb441a64SWill Schurman private extension SecCertificate { 169*fb441a64SWill Schurman var publicKey: SecKey? { 170*fb441a64SWill Schurman SecCertificateCopyKey(self) 171*fb441a64SWill Schurman } 172*fb441a64SWill Schurman } 173*fb441a64SWill Schurman 174*fb441a64SWill Schurman internal extension OSStatus { 175*fb441a64SWill Schurman var isSuccess: Bool { 176*fb441a64SWill Schurman self == errSecSuccess 177*fb441a64SWill Schurman } 178*fb441a64SWill Schurman } 179*fb441a64SWill Schurman 180*fb441a64SWill Schurman private extension Data { sha256null181*fb441a64SWill Schurman func sha256() -> Data { 182*fb441a64SWill Schurman var digest = Data(count: Int(CC_SHA256_DIGEST_LENGTH)) 183*fb441a64SWill Schurman withUnsafeBytes { bytes in 184*fb441a64SWill Schurman digest.withUnsafeMutableBytes { mutableBytes in 185*fb441a64SWill Schurman _ = CC_SHA256(bytes.baseAddress, CC_LONG(count), mutableBytes.bindMemory(to: UInt8.self).baseAddress) 186*fb441a64SWill Schurman } 187*fb441a64SWill Schurman } 188*fb441a64SWill Schurman return digest 189*fb441a64SWill Schurman } 190*fb441a64SWill Schurman } 191*fb441a64SWill Schurman 192*fb441a64SWill Schurman private extension String { firstIndexnull193*fb441a64SWill Schurman func firstIndex(of: String, startingAt: String.Index) -> String.Index? { 194*fb441a64SWill Schurman return self[startingAt...].range(of: of)?.lowerBound 195*fb441a64SWill Schurman } 196*fb441a64SWill Schurman } 197