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