1 // swiftlint:disable all
2 //
3 // X509Certificate.swift
4 //
5 // Copyright © 2017 Filippo Maguolo.
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a copy
8 // of this software and associated documentation files (the "Software"), to deal
9 // in the Software without restriction, including without limitation the rights
10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 // copies of the Software, and to permit persons to whom the Software is
12 // furnished to do so, subject to the following conditions:
13 //
14 // The above copyright notice and this permission notice shall be included in all
15 // copies or substantial portions of the Software.
16 //
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 // SOFTWARE.
24
25 import Foundation
26
27 public class X509Certificate: CustomStringConvertible {
28 private let asn1: [ASN1Object]
29 private let block1: ASN1Object
30
31 private static let beginPemBlock = "-----BEGIN CERTIFICATE-----"
32 private static let endPemBlock = "-----END CERTIFICATE-----"
33
34 enum X509BlockPosition: Int {
35 case version = 0
36 case serialNumber = 1
37 case signatureAlg = 2
38 case issuer = 3
39 case dateValidity = 4
40 case subject = 5
41 case publicKey = 6
42 case extensions = 7
43 }
44
45 public convenience init(data: Data) throws {
46 if String(data: data, encoding: .utf8)?.contains(X509Certificate.beginPemBlock) ?? false {
47 try self.init(pem: data)
48 } else {
49 try self.init(der: data)
50 }
51 }
52
53 public init(der: Data) throws {
54 asn1 = try ASN1DERDecoder.decode(data: der)
55 guard asn1.count > 0,
56 let block1 = asn1.first?.sub(0) else {
57 throw ASN1Error.parseError
58 }
59
60 self.block1 = block1
61 }
62
63 public convenience init(pem: Data) throws {
64 guard let derData = X509Certificate.decodeToDER(pem: pem) else {
65 throw ASN1Error.parseError
66 }
67
68 try self.init(der: derData)
69 }
70
71 init(asn1: ASN1Object) throws {
72 guard let block1 = asn1.sub(0) else { throw ASN1Error.parseError }
73
74 self.asn1 = [asn1]
75 self.block1 = block1
76 }
77
78 public var description: String {
79 return asn1.reduce("") { $0 + "\($1.description)\n" }
80 }
81
82 /// Checks that the given date is within the certificate's validity period.
83 public func checkValidity(_ date: Date = Date()) -> Bool {
84 if let notBefore = notBefore, let notAfter = notAfter {
85 return date > notBefore && date < notAfter
86 }
87 return false
88 }
89
90 /// Gets the version (version number) value from the certificate.
91 public var version: Int? {
92 if let data = firstLeafValue(block: block1) as? Data, let value = data.uint64Value, value < Int.max {
93 return Int(value) + 1
94 }
95 return 1
96 }
97
98 /// Gets the serialNumber value from the certificate.
99 public var serialNumber: Data? {
100 return block1[X509BlockPosition.serialNumber]?.value as? Data
101 }
102
103 /// Returns the issuer (issuer distinguished name) value from the certificate as a String.
104 public var issuerDistinguishedName: String? {
105 if let issuerBlock = block1[X509BlockPosition.issuer] {
106 return ASN1DistinguishedNameFormatter.string(from: issuerBlock)
107 }
108 return nil
109 }
110
111 public var issuerOIDs: [String] {
112 var result: [String] = []
113 if let subjectBlock = block1[X509BlockPosition.issuer] {
114 for sub in subjectBlock.sub ?? [] {
115 if let value = firstLeafValue(block: sub) as? String, !result.contains(value) {
116 result.append(value)
117 }
118 }
119 }
120 return result
121 }
122
issuernull123 public func issuer(oidString: String) -> String? {
124 if let subjectBlock = block1[X509BlockPosition.issuer] {
125 if let oidBlock = subjectBlock.findOid(oidString) {
126 return oidBlock.parent?.sub?.last?.value as? String
127 }
128 }
129 return nil
130 }
131
issuernull132 public func issuer(oid: OID) -> String? {
133 return issuer(oidString: oid.rawValue)
134 }
135
136 @available(*, deprecated, message: "Use issuer(oid:) instead")
issuernull137 public func issuer(dn: ASN1DistinguishedNames) -> String? {
138 return issuer(oidString: dn.oid)
139 }
140
141 /// Returns the subject (subject distinguished name) value from the certificate as a String.
142 public var subjectDistinguishedName: String? {
143 if let subjectBlock = block1[X509BlockPosition.subject] {
144 return ASN1DistinguishedNameFormatter.string(from: subjectBlock)
145 }
146 return nil
147 }
148
149 public var subjectOIDs: [String] {
150 var result: [String] = []
151 if let subjectBlock = block1[X509BlockPosition.subject] {
152 for sub in subjectBlock.sub ?? [] {
153 if let value = firstLeafValue(block: sub) as? String, !result.contains(value) {
154 result.append(value)
155 }
156 }
157 }
158 return result
159 }
160
subjectnull161 public func subject(oidString: String) -> [String]? {
162 var result: [String]?
163 if let subjectBlock = block1[X509BlockPosition.subject] {
164 for sub in subjectBlock.sub ?? [] {
165 if let oidBlock = sub.findOid(oidString) {
166 guard let value = oidBlock.parent?.sub?.last?.value as? String else {
167 continue
168 }
169 if result == nil {
170 result = []
171 }
172 result?.append(value)
173 }
174 }
175 }
176 return result
177 }
178
subjectnull179 public func subject(oid: OID) -> [String]? {
180 return subject(oidString: oid.rawValue)
181 }
182
183 @available(*, deprecated, message: "Use subject(oid:) instead")
subjectnull184 public func subject(dn: ASN1DistinguishedNames) -> [String]? {
185 return subject(oidString: dn.oid)
186 }
187
188 /// Gets the notBefore date from the validity period of the certificate.
189 public var notBefore: Date? {
190 return block1[X509BlockPosition.dateValidity]?.sub(0)?.value as? Date
191 }
192
193 /// Gets the notAfter date from the validity period of the certificate.
194 public var notAfter: Date? {
195 return block1[X509BlockPosition.dateValidity]?.sub(1)?.value as? Date
196 }
197
198 /// Gets the signature value (the raw signature bits) from the certificate.
199 public var signature: Data? {
200 return asn1[0].sub(2)?.value as? Data
201 }
202
203 /// Gets the signature algorithm name for the certificate signature algorithm.
204 public var sigAlgName: String? {
205 return OID.description(of: sigAlgOID ?? "")
206 }
207
208 /// Gets the signature algorithm OID string from the certificate.
209 public var sigAlgOID: String? {
210 return block1.sub(2)?.sub(0)?.value as? String
211 }
212
213 /// Gets the DER-encoded signature algorithm parameters from this certificate's signature algorithm.
214 public var sigAlgParams: Data? {
215 return nil
216 }
217
218 /**
219 Gets a boolean array representing bits of the KeyUsage extension, (OID = 2.5.29.15).
220 ```
221 KeyUsage ::= BIT STRING {
222 digitalSignature (0),
223 nonRepudiation (1),
224 keyEncipherment (2),
225 dataEncipherment (3),
226 keyAgreement (4),
227 keyCertSign (5),
228 cRLSign (6),
229 encipherOnly (7),
230 decipherOnly (8)
231 }
232 ```
233 */
234 public var keyUsage: [Bool] {
235 var result: [Bool] = []
236 if let oidBlock = block1.findOid(OID.keyUsage) {
237 let data = oidBlock.parent?.sub?.last?.sub(0)?.value as? Data
238 let bits: UInt8 = data?.first ?? 0
239 for index in 0...7 {
240 let value = bits & UInt8(1 << index) != 0
241 result.insert(value, at: 0)
242 }
243 }
244 return result
245 }
246
247 /// Gets a list of Strings representing the OBJECT IDENTIFIERs of the ExtKeyUsageSyntax field of
248 /// the extended key usage extension, (OID = 2.5.29.37).
249 public var extendedKeyUsage: [String] {
250 return extensionObject(oid: OID.extKeyUsage)?.valueAsStrings ?? []
251 }
252
253 /// Gets a collection of subject alternative names from the SubjectAltName extension, (OID = 2.5.29.17).
254 public var subjectAlternativeNames: [String] {
255 return extensionObject(oid: OID.subjectAltName)?.alternativeNameAsStrings ?? []
256 }
257
258 /// Gets a collection of issuer alternative names from the IssuerAltName extension, (OID = 2.5.29.18).
259 public var issuerAlternativeNames: [String] {
260 return extensionObject(oid: OID.issuerAltName)?.alternativeNameAsStrings ?? []
261 }
262
263 /// Gets the informations of the public key from this certificate.
264 public var publicKey: X509PublicKey? {
265 return block1[X509BlockPosition.publicKey].map(X509PublicKey.init)
266 }
267
268 /// Get a list of critical extension OID codes
269 public var criticalExtensionOIDs: [String] {
270 guard let extensionBlocks = extensionBlocks else { return [] }
271 return extensionBlocks
272 .map { X509Extension(block: $0) }
273 .filter { $0.isCritical }
274 .compactMap { $0.oid }
275 }
276
277 /// Get a list of non critical extension OID codes
278 public var nonCriticalExtensionOIDs: [String] {
279 guard let extensionBlocks = extensionBlocks else { return [] }
280 return extensionBlocks
281 .map { X509Extension(block: $0) }
282 .filter { !$0.isCritical }
283 .compactMap { $0.oid }
284 }
285
286 private var extensionBlocks: [ASN1Object]? {
287 return block1[X509BlockPosition.extensions]?.sub(0)?.sub
288 }
289
290 /// Gets the extension information of the given OID enum.
extensionObjectnull291 public func extensionObject(oid: OID) -> X509Extension? {
292 return extensionObject(oid: oid.rawValue)
293 }
294
295 /// Gets the extension information of the given OID code.
extensionObjectnull296 public func extensionObject(oid: String) -> X509Extension? {
297 return block1[X509BlockPosition.extensions]?
298 .findOid(oid)?
299 .parent
300 .map { oidExtensionMap[oid]?.init(block: $0) ?? X509Extension(block: $0) }
301 }
302
303 // Association of Class decoding helper and OID
304 private let oidExtensionMap: [String: X509Extension.Type] = [
305 OID.basicConstraints.rawValue: BasicConstraintExtension.self,
306 OID.subjectKeyIdentifier.rawValue: SubjectKeyIdentifierExtension.self,
307 OID.authorityInfoAccess.rawValue: AuthorityInfoAccessExtension.self,
308 OID.authorityKeyIdentifier.rawValue: AuthorityKeyIdentifierExtension.self,
309 OID.certificatePolicies.rawValue: CertificatePoliciesExtension.self,
310 OID.cRLDistributionPoints.rawValue: CRLDistributionPointsExtension.self
311 ]
312
313 // read possibile PEM encoding
decodeToDERnull314 private static func decodeToDER(pem pemData: Data) -> Data? {
315 if
316 let pem = String(data: pemData, encoding: .ascii),
317 pem.contains(beginPemBlock) {
318
319 let lines = pem.components(separatedBy: .newlines)
320 var base64buffer = ""
321 var certLine = false
322 for line in lines {
323 if line == endPemBlock {
324 certLine = false
325 }
326 if certLine {
327 base64buffer.append(line)
328 }
329 if line == beginPemBlock {
330 certLine = true
331 }
332 }
333 if let derDataDecoded = Data(base64Encoded: base64buffer) {
334 return derDataDecoded
335 }
336 }
337
338 return nil
339 }
340 }
341
firstLeafValuenull342 func firstLeafValue(block: ASN1Object) -> Any? {
343 if let sub = block.sub?.first {
344 return firstLeafValue(block: sub)
345 }
346 return block.value
347 }
348
349 extension ASN1Object {
350 subscript(index: X509Certificate.X509BlockPosition) -> ASN1Object? {
351 guard let sub = sub else { return nil }
352 if sub.count <= 6 {
353 guard sub.indices.contains(index.rawValue-1) else { return nil }
354 return sub[index.rawValue-1]
355 } else {
356 guard sub.indices.contains(index.rawValue) else { return nil }
357 return sub[index.rawValue]
358 }
359 }
360 }
361 // swiftlint:enable all
362