|
| 1 | +// |
| 2 | +// Data+Extensions.swift |
| 3 | +// SwiftyAPNS |
| 4 | +// |
| 5 | +// Created by Sergii Tkachenko on 12.08.2020. |
| 6 | +// Copyright © 2020 Sergii Tkachenko. All rights reserved. |
| 7 | +// |
| 8 | + |
| 9 | +import Foundation |
| 10 | + |
| 11 | +public typealias ASN1 = Data |
| 12 | + |
| 13 | +public enum ASN1Error: LocalizedError { |
| 14 | + |
| 15 | + case invalidAsn1 |
| 16 | + |
| 17 | + public var errorDescription: String? { |
| 18 | + switch self { |
| 19 | + case .invalidAsn1: |
| 20 | + return "The ASN.1 data has invalid format." |
| 21 | + } |
| 22 | + } |
| 23 | +} |
| 24 | + |
| 25 | +extension ASN1 { |
| 26 | + private indirect enum ASN1Element { |
| 27 | + case seq(elements: [ASN1Element]) |
| 28 | + case integer(int: Int) |
| 29 | + case bytes(data: Data) |
| 30 | + case constructed(tag: Int, elem: ASN1Element) |
| 31 | + case unknown |
| 32 | + } |
| 33 | + |
| 34 | + public func toECKeyData() throws -> ECKeyData { |
| 35 | + let (result, _) = self.toASN1Element() |
| 36 | + |
| 37 | + guard case let ASN1Element.seq(elements: es) = result, |
| 38 | + case let ASN1Element.bytes(data: privateOctest) = es[2] else { |
| 39 | + throw ASN1Error.invalidAsn1 |
| 40 | + } |
| 41 | + |
| 42 | + let (octest, _) = privateOctest.toASN1Element() |
| 43 | + guard case let ASN1Element.seq(elements: seq) = octest, |
| 44 | + case let ASN1Element.bytes(data: privateKeyData) = seq[1], |
| 45 | + case let ASN1Element.constructed(tag: _, elem: publicElement) = seq[3], |
| 46 | + case let ASN1Element.bytes(data: publicKeyData) = publicElement else { |
| 47 | + throw ASN1Error.invalidAsn1 |
| 48 | + } |
| 49 | + |
| 50 | + let keyData = (publicKeyData.drop(while: { $0 == 0x00}) + privateKeyData) |
| 51 | + return keyData |
| 52 | + } |
| 53 | + |
| 54 | + // SecKeyCreateSignature seems to sometimes return a leading zero; strip it out |
| 55 | + private func dropLeadingBytes() -> Data { |
| 56 | + if self.count == 33 { |
| 57 | + return self.dropFirst() |
| 58 | + } |
| 59 | + return self |
| 60 | + } |
| 61 | + |
| 62 | + /// Convert an ASN.1 format EC signature returned by commoncrypto into a raw 64bit signature |
| 63 | + public func toRawSignature() throws -> Data { |
| 64 | + let (result, _) = self.toASN1Element() |
| 65 | + |
| 66 | + guard case let ASN1Element.seq(elements: es) = result, |
| 67 | + case let ASN1Element.bytes(data: sigR) = es[0], |
| 68 | + case let ASN1Element.bytes(data: sigS) = es[1] else { |
| 69 | + throw ASN1Error.invalidAsn1 |
| 70 | + } |
| 71 | + |
| 72 | + let rawSig = sigR.dropLeadingBytes() + sigS.dropLeadingBytes() |
| 73 | + return rawSig |
| 74 | + } |
| 75 | + |
| 76 | + private func readLength() -> (Int, Int) { |
| 77 | + if self[0] & 0x80 == 0x00 { // short form |
| 78 | + return (Int(self[0]), 1) |
| 79 | + } else { |
| 80 | + let lenghOfLength = Int(self[0] & 0x7F) |
| 81 | + var result: Int = 0 |
| 82 | + for i in 1..<(1 + lenghOfLength) { |
| 83 | + result = 256 * result + Int(self[i]) |
| 84 | + } |
| 85 | + return (result, 1 + lenghOfLength) |
| 86 | + } |
| 87 | + } |
| 88 | + |
| 89 | + private func toASN1Element() -> (ASN1Element, Int) { |
| 90 | + guard self.count >= 2 else { |
| 91 | + // format error |
| 92 | + return (.unknown, self.count) |
| 93 | + } |
| 94 | + |
| 95 | + switch self[0] { |
| 96 | + case 0x30: // sequence |
| 97 | + let (length, lengthOfLength) = self.advanced(by: 1).readLength() |
| 98 | + var result: [ASN1Element] = [] |
| 99 | + var subdata = self.advanced(by: 1 + lengthOfLength) |
| 100 | + var alreadyRead = 0 |
| 101 | + |
| 102 | + while alreadyRead < length { |
| 103 | + let (e, l) = subdata.toASN1Element() |
| 104 | + result.append(e) |
| 105 | + subdata = subdata.count > l ? subdata.advanced(by: l) : Data() |
| 106 | + alreadyRead += l |
| 107 | + } |
| 108 | + return (.seq(elements: result), 1 + lengthOfLength + length) |
| 109 | + |
| 110 | + case 0x02: // integer |
| 111 | + let (length, lengthOfLength) = self.advanced(by: 1).readLength() |
| 112 | + if (length < 8) { |
| 113 | + var result: Int = 0 |
| 114 | + let subdata = self.advanced(by: 1 + lengthOfLength) |
| 115 | + // ignore negative case |
| 116 | + for i in 0..<length { |
| 117 | + result = 256 * result + Int(subdata[i]) |
| 118 | + } |
| 119 | + return (.integer(int: result), 1 + lengthOfLength + length) |
| 120 | + } |
| 121 | + // number is too large to fit in Int; return the bytes |
| 122 | + return (.bytes(data: self.subdata(in: (1 + lengthOfLength) ..< (1 + lengthOfLength + length))), 1 + lengthOfLength + length) |
| 123 | + |
| 124 | + |
| 125 | + case let s where (s & 0xe0) == 0xa0: // constructed |
| 126 | + let tag = Int(s & 0x1f) |
| 127 | + let (length, lengthOfLength) = self.advanced(by: 1).readLength() |
| 128 | + let subdata = self.advanced(by: 1 + lengthOfLength) |
| 129 | + let (e, _) = subdata.toASN1Element() |
| 130 | + return (.constructed(tag: tag, elem: e), 1 + lengthOfLength + length) |
| 131 | + |
| 132 | + default: // octet string |
| 133 | + let (length, lengthOfLength) = self.advanced(by: 1).readLength() |
| 134 | + return (.bytes(data: self.subdata(in: (1 + lengthOfLength) ..< (1 + lengthOfLength + length))), 1 + lengthOfLength + length) |
| 135 | + } |
| 136 | + } |
| 137 | +} |
| 138 | + |
| 139 | +public typealias ECKeyData = Data |
| 140 | + |
| 141 | +extension ECKeyData { |
| 142 | + public func toPrivateKey() throws -> ECPrivateKey { |
| 143 | + var error: Unmanaged<CFError>? = nil |
| 144 | + guard let privateKey = |
| 145 | + SecKeyCreateWithData(self as CFData, |
| 146 | + [kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom, |
| 147 | + kSecAttrKeyClass: kSecAttrKeyClassPrivate, |
| 148 | + kSecAttrKeySizeInBits: 256] as CFDictionary, |
| 149 | + &error) else { |
| 150 | + throw error!.takeRetainedValue() |
| 151 | + } |
| 152 | + return privateKey |
| 153 | + } |
| 154 | +} |
| 155 | + |
| 156 | +extension Data { |
| 157 | + func base64EncodedURLString() -> String { |
| 158 | + return base64EncodedString() |
| 159 | + .replacingOccurrences(of: "=", with: "") |
| 160 | + .replacingOccurrences(of: "+", with: "-") |
| 161 | + .replacingOccurrences(of: "/", with: "_") |
| 162 | + } |
| 163 | +} |
0 commit comments