Przeglądaj źródła

Merge pull request #922 from btoms20/feature/asn1+der

Simple ASN1 Encoder and Decoder & DERCodable Protocol
Nathan Fallet 3 lat temu
rodzic
commit
0c1dbde144

+ 87 - 0
Sources/CryptoSwift/ASN1/ASN1.swift

@@ -0,0 +1,87 @@
+//
+//  CryptoSwift
+//
+//  Copyright (C) Marcin Krzyżanowski <marcin@krzyzanowskim.com>
+//  This software is provided 'as-is', without any express or implied warranty.
+//
+//  In no event will the authors be held liable for any damages arising from the use of this software.
+//
+//  Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
+//
+//  - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required.
+//  - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+//  - This notice may not be removed or altered from any source or binary distribution.
+//
+//  ASN1 Code inspired by Asn1Parser.swift from SwiftyRSA
+
+import Foundation
+
+/// A Partial ASN.1 (Abstract Syntax Notation 1) Encoder & Decoder Implementation.
+///
+/// - Note: This implementation is limited to a few core types and is not an exhaustive / complete ASN1 implementation
+/// - Warning: This implementation has been developed for encoding and decoding DER & PEM files specifically. If you're using this Encoder/Decoder on other ASN1 structures, make sure you test the expected behavior appropriately.
+enum ASN1 {
+  internal enum IDENTIFIERS: UInt8, Equatable {
+    case SEQUENCE = 0x30
+    case INTERGER = 0x02
+    case OBJECTID = 0x06
+    case NULL = 0x05
+    case BITSTRING = 0x03
+    case OCTETSTRING = 0x04
+
+    static func == (lhs: UInt8, rhs: IDENTIFIERS) -> Bool {
+      lhs == rhs.rawValue
+    }
+
+    var bytes: [UInt8] {
+      switch self {
+        case .NULL:
+          return [self.rawValue, 0x00]
+        default:
+          return [self.rawValue]
+      }
+    }
+  }
+
+  /// An ASN1 node
+  internal enum Node: CustomStringConvertible {
+    /// An array of more `ASN1.Node`s
+    case sequence(nodes: [Node])
+    /// An integer
+    /// - Note: This ASN1 Encoder makes no assumptions about the sign and bit order of the integers passed in. The conversion from Integer to Data is your responsiblity.
+    case integer(data: Data)
+    /// An objectIdentifier
+    case objectIdentifier(data: Data)
+    /// A null object
+    case null
+    /// A bitString
+    case bitString(data: Data)
+    /// An octetString
+    case octetString(data: Data)
+
+    var description: String {
+      ASN1.printNode(self, level: 0)
+    }
+  }
+
+  internal static func printNode(_ node: ASN1.Node, level: Int) -> String {
+    var str: [String] = []
+    let prefix = String(repeating: "\t", count: level)
+    switch node {
+      case .integer(let int):
+        str.append("\(prefix)Integer: \(int.toHexString())")
+      case .bitString(let bs):
+        str.append("\(prefix)BitString: \(bs.toHexString())")
+      case .null:
+        str.append("\(prefix)NULL")
+      case .objectIdentifier(let oid):
+        str.append("\(prefix)ObjectID: \(oid.toHexString())")
+      case .octetString(let os):
+        str.append("\(prefix)OctetString: \(os.toHexString())")
+      case .sequence(let nodes):
+        str.append("\(prefix)Sequence:")
+        nodes.forEach { str.append(printNode($0, level: level + 1)) }
+    }
+    return str.joined(separator: "\n")
+  }
+}

+ 107 - 0
Sources/CryptoSwift/ASN1/ASN1Decoder.swift

@@ -0,0 +1,107 @@
+//
+//  CryptoSwift
+//
+//  Copyright (C) Marcin Krzyżanowski <marcin@krzyzanowskim.com>
+//  This software is provided 'as-is', without any express or implied warranty.
+//
+//  In no event will the authors be held liable for any damages arising from the use of this software.
+//
+//  Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
+//
+//  - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required.
+//  - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+//  - This notice may not be removed or altered from any source or binary distribution.
+//
+//  ASN1 Code inspired by Asn1Parser.swift from SwiftyRSA
+
+import Foundation
+
+extension ASN1 {
+  /// A simple ASN1 parser that will recursively iterate over a root node and return a Node tree.
+  /// The root node can be any of the supported nodes described in `Node`. If the parser encounters a sequence
+  /// it will recursively parse its children.
+  enum Decoder {
+
+    enum DecodingError: Error {
+      case noType
+      case invalidType(value: UInt8)
+    }
+
+    /// Parses ASN1 data and returns its root node.
+    ///
+    /// - Parameter data: ASN1 data to parse
+    /// - Returns: Root ASN1 Node
+    /// - Throws: A DecodingError if anything goes wrong, or if an unknown node was encountered
+    static func decode(data: Data) throws -> Node {
+      let scanner = ASN1.Scanner(data: data)
+      let node = try decodeNode(scanner: scanner)
+      return node
+    }
+
+    /// Parses an ASN1 given an existing scanner.
+    /// @warning: this will modify the state (ie: position) of the provided scanner.
+    ///
+    /// - Parameter scanner: Scanner to use to consume the data
+    /// - Returns: Parsed node
+    /// - Throws: A DecodingError if anything goes wrong, or if an unknown node was encountered
+    private static func decodeNode(scanner: ASN1.Scanner) throws -> Node {
+
+      let firstByte = try scanner.consume(length: 1).firstByte
+
+      switch firstByte {
+        case IDENTIFIERS.SEQUENCE.rawValue:
+          let length = try scanner.consumeLength()
+          let data = try scanner.consume(length: length)
+          let nodes = try decodeSequence(data: data)
+          return .sequence(nodes: nodes)
+
+        case IDENTIFIERS.INTERGER.rawValue:
+          let length = try scanner.consumeLength()
+          let data = try scanner.consume(length: length)
+          return .integer(data: data)
+
+        case IDENTIFIERS.OBJECTID.rawValue:
+          let length = try scanner.consumeLength()
+          let data = try scanner.consume(length: length)
+          return .objectIdentifier(data: data)
+
+        case IDENTIFIERS.NULL.rawValue:
+          _ = try scanner.consume(length: 1)
+          return .null
+
+        case IDENTIFIERS.BITSTRING.rawValue:
+          let length = try scanner.consumeLength()
+
+          // There's an extra byte (0x00) after the bit string length in all the keys I've encountered.
+          // I couldn't find a specification that referenced this extra byte, but let's consume it and discard it.
+          _ = try scanner.consume(length: 1)
+
+          let data = try scanner.consume(length: length - 1)
+          return .bitString(data: data)
+
+        case IDENTIFIERS.OCTETSTRING.rawValue:
+          let length = try scanner.consumeLength()
+          let data = try scanner.consume(length: length)
+          return .octetString(data: data)
+
+        default:
+          throw DecodingError.invalidType(value: firstByte)
+      }
+    }
+
+    /// Parses an ASN1 sequence and returns its child nodes
+    ///
+    /// - Parameter data: ASN1 data
+    /// - Returns: A list of ASN1 nodes
+    /// - Throws: A DecodingError if anything goes wrong, or if an unknown node was encountered
+    private static func decodeSequence(data: Data) throws -> [Node] {
+      let scanner = ASN1.Scanner(data: data)
+      var nodes: [Node] = []
+      while !scanner.isComplete {
+        let node = try decodeNode(scanner: scanner)
+        nodes.append(node)
+      }
+      return nodes
+    }
+  }
+}

+ 69 - 0
Sources/CryptoSwift/ASN1/ASN1Encoder.swift

@@ -0,0 +1,69 @@
+//
+//  CryptoSwift
+//
+//  Copyright (C) Marcin Krzyżanowski <marcin@krzyzanowskim.com>
+//  This software is provided 'as-is', without any express or implied warranty.
+//
+//  In no event will the authors be held liable for any damages arising from the use of this software.
+//
+//  Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
+//
+//  - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required.
+//  - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+//  - This notice may not be removed or altered from any source or binary distribution.
+//
+//  ASN1 Code inspired by Asn1Parser.swift from SwiftyRSA
+
+import Foundation
+
+extension ASN1 {
+  enum Encoder {
+    /// Encodes an ASN1Node into it's byte representation
+    ///
+    /// - Parameter node: The Node to encode
+    /// - Returns: The encoded bytes as a UInt8 array
+    ///
+    /// - Warning: This ASN.1 encoder has only been tested to work on certain ASN.1 data structures such as DER and PEM files. Before using this encoder for another application, ensure you test it's behavior accordingly.
+    /// - Warning: This encoder makes no assumptions regarding Integer bit layout and signage. The proper serialization of Integers is left up to the user.
+    public static func encode(_ node: ASN1.Node) -> [UInt8] {
+      switch node {
+        case .integer(let integer):
+          return IDENTIFIERS.INTERGER.bytes + self.asn1LengthPrefixed(integer.bytes)
+        case .bitString(let bits):
+          return IDENTIFIERS.BITSTRING.bytes + self.asn1LengthPrefixed([0x00] + bits.bytes)
+        case .octetString(let octet):
+          return IDENTIFIERS.OCTETSTRING.bytes + self.asn1LengthPrefixed(octet.bytes)
+        case .null:
+          return IDENTIFIERS.NULL.bytes
+        case .objectIdentifier(let oid):
+          return IDENTIFIERS.OBJECTID.bytes + self.asn1LengthPrefixed(oid.bytes)
+        case .sequence(let nodes):
+          return IDENTIFIERS.SEQUENCE.bytes + self.asn1LengthPrefixed( nodes.reduce(into: Array<UInt8>(), { partialResult, node in
+            partialResult += encode(node)
+          }))
+      }
+    }
+
+    /// Calculates and returns the ASN.1 length Prefix for a chunk of data
+    ///
+    /// - Parameter bytes: The bytes to be length prefixed
+    /// - Returns: The ASN.1 length Prefix for this chuck of data (excluding the passed in data)
+    private static func asn1LengthPrefix(_ bytes: [UInt8]) -> [UInt8] {
+      if bytes.count >= 0x80 {
+        var lengthAsBytes = withUnsafeBytes(of: bytes.count.bigEndian, Array<UInt8>.init)
+        while lengthAsBytes.first == 0 { lengthAsBytes.removeFirst() }
+        return [0x80 + UInt8(lengthAsBytes.count)] + lengthAsBytes
+      } else {
+        return [UInt8(bytes.count)]
+      }
+    }
+
+    /// Prefixes the provided bytes with the appropriate ASN.1 length prefix
+    ///
+    /// - Parameter bytes: The bytes to be length prefixed
+    /// - Returns: The provided bytes with the appropriate ASN.1 length prefix prepended
+    private static func asn1LengthPrefixed(_ bytes: [UInt8]) -> [UInt8] {
+      self.asn1LengthPrefix(bytes) + bytes
+    }
+  }
+}

+ 123 - 0
Sources/CryptoSwift/ASN1/ASN1Scanner.swift

@@ -0,0 +1,123 @@
+//
+//  CryptoSwift
+//
+//  Copyright (C) Marcin Krzyżanowski <marcin@krzyzanowskim.com>
+//  This software is provided 'as-is', without any express or implied warranty.
+//
+//  In no event will the authors be held liable for any damages arising from the use of this software.
+//
+//  Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
+//
+//  - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required.
+//  - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+//  - This notice may not be removed or altered from any source or binary distribution.
+//
+//  ASN1 Scanner code is from Asn1Parser.swift from SwiftyRSA
+
+import Foundation
+
+extension ASN1 {
+  /// Simple data scanner that consumes bytes from a raw data and keeps an updated position.
+  internal class Scanner {
+
+    enum ScannerError: Error {
+      case outOfBounds
+    }
+
+    let data: Data
+    var index: Int = 0
+
+    /// Returns whether there is no more data to consume
+    var isComplete: Bool {
+      return self.index >= self.data.count
+    }
+
+    /// Creates a scanner with provided data
+    ///
+    /// - Parameter data: Data to consume
+    init(data: Data) {
+      self.data = data
+    }
+
+    /// Consumes data of provided length and returns it
+    ///
+    /// - Parameter length: length of the data to consume
+    /// - Returns: data consumed
+    /// - Throws: ScannerError.outOfBounds error if asked to consume too many bytes
+    func consume(length: Int) throws -> Data {
+
+      guard length > 0 else {
+        return Data()
+      }
+
+      guard self.index + length <= self.data.count else {
+        throw ScannerError.outOfBounds
+      }
+
+      let subdata = self.data.subdata(in: self.index..<self.index + length)
+      self.index += length
+      return subdata
+    }
+
+    /// Consumes a primitive, definite ASN1 length and returns its value.
+    ///
+    /// See http://luca.ntop.org/Teaching/Appunti/asn1.html,
+    ///
+    /// - Short form. One octet. Bit 8 has value "0" and bits 7-1 give the length.
+    /// - Long form. Two to 127 octets. Bit 8 of first octet has value "1" and
+    ///   bits 7-1 give the number of additional length octets.
+    ///   Second and following octets give the length, base 256, most significant digit first.
+    ///
+    /// - Returns: Length that was consumed
+    /// - Throws: ScannerError.outOfBounds error if asked to consume too many bytes
+    func consumeLength() throws -> Int {
+
+      let lengthByte = try consume(length: 1).firstByte
+
+      // If the first byte's value is less than 0x80, it directly contains the length
+      // so we can return it
+      guard lengthByte >= 0x80 else {
+        return Int(lengthByte)
+      }
+
+      // If the first byte's value is more than 0x80, it indicates how many following bytes
+      // will describe the length. For instance, 0x85 indicates that 0x85 - 0x80 = 0x05 = 5
+      // bytes will describe the length, so we need to read the 5 next bytes and get their integer
+      // value to determine the length.
+      let nextByteCount = lengthByte - 0x80
+      let length = try consume(length: Int(nextByteCount))
+
+      return length.integer
+    }
+  }
+}
+
+internal extension Data {
+
+  /// Returns the first byte of the current data
+  var firstByte: UInt8 {
+    var byte: UInt8 = 0
+    copyBytes(to: &byte, count: MemoryLayout<UInt8>.size)
+    return byte
+  }
+
+  /// Returns the integer value of the current data.
+  /// - Warning: This only supports data up to 4 bytes, as we can only extract 32-bit integers.
+  var integer: Int {
+
+    guard count > 0 else {
+      return 0
+    }
+
+    var int: UInt32 = 0
+    var offset: Int32 = Int32(count - 1)
+    forEach { byte in
+      let byte32 = UInt32(byte)
+      let shifted = byte32 << (UInt32(offset) * 8)
+      int = int | shifted
+      offset -= 1
+    }
+
+    return Int(int)
+  }
+}

+ 130 - 0
Sources/CryptoSwift/PEM/DER.swift

@@ -0,0 +1,130 @@
+//
+//  CryptoSwift
+//
+//  Copyright (C) Marcin Krzyżanowski <marcin@krzyzanowskim.com>
+//  This software is provided 'as-is', without any express or implied warranty.
+//
+//  In no event will the authors be held liable for any damages arising from the use of this software.
+//
+//  Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
+//
+//  - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required.
+//  - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+//  - This notice may not be removed or altered from any source or binary distribution.
+//
+
+import Foundation
+
+/// Conform to this protocol if your type can both be instantiated and expressed as an ASN1 DER representation.
+internal protocol DERCodable: DERDecodable, DEREncodable { }
+
+/// Conform to this protocol if your type can be instantiated from a ASN1 DER representation
+internal protocol DERDecodable {
+  /// The keys primary ASN1 object identifier (ex: for RSA Keys --> 'rsaEncryption' --> [0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01])
+  static var primaryObjectIdentifier: Array<UInt8> { get }
+  /// The keys secondary ASN1 object identifier (ex: for RSA Keys --> 'null' --> nil)
+  static var secondaryObjectIdentifier: Array<UInt8>? { get }
+
+  /// Attempts to instantiate an instance of your Public Key when given a DER representation of your Public Key
+  init(publicDER: Array<UInt8>) throws
+  /// Attempts to instantiate an instance of your Private Key when given a DER representation of your Private Key
+  init(privateDER: Array<UInt8>) throws
+
+  /// Attempts to instantiate a Key when given the ASN1 DER encoded external representation of the Key
+  ///
+  /// An example of importing a SecKey RSA key (from Apple's `Security` framework) for use within CryptoSwift
+  /// ```
+  /// /// Starting with a SecKey RSA Key
+  /// let rsaSecKey:SecKey
+  ///
+  /// /// Copy the External Representation
+  /// var externalRepError:Unmanaged<CFError>?
+  /// guard let externalRep = SecKeyCopyExternalRepresentation(rsaSecKey, &externalRepError) as? Data else {
+  ///     /// Failed to copy external representation for RSA SecKey
+  ///     return
+  /// }
+  ///
+  /// /// Instantiate the RSA Key from the raw external representation
+  /// let rsaKey = try RSA(rawRepresentation: externalRep)
+  ///
+  /// /// You now have a CryptoSwift RSA Key
+  /// // rsaKey.encrypt(...)
+  /// // rsaKey.decrypt(...)
+  /// // rsaKey.sign(...)
+  /// // rsaKey.verify(...)
+  /// ```
+  init(rawRepresentation: Data) throws
+}
+
+extension DERDecodable {
+  public init(rawRepresentation raw: Data) throws {
+    /// The default implementation that makes the original internal initializer publicly available
+    do { try self.init(privateDER: raw.bytes) } catch {
+      try self.init(publicDER: raw.bytes)
+    }
+  }
+}
+
+/// Conform to this protocol if your type can be described in an ASN1 DER representation
+internal protocol DEREncodable {
+  /// The keys primary ASN1 object identifier (ex: for RSA Keys --> 'rsaEncryption' --> [0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01])
+  static var primaryObjectIdentifier: Array<UInt8> { get }
+  /// The keys secondary ASN1 object identifier (ex: for RSA Keys --> 'null' --> nil)
+  static var secondaryObjectIdentifier: Array<UInt8>? { get }
+
+  /// Returns the DER encoded representation of the Public Key
+  func publicKeyDER() throws -> Array<UInt8>
+  /// Returns the DER encoded representation of the Private Key
+  func privateKeyDER() throws -> Array<UInt8>
+
+  /// A semantically similar function that mimics the `SecKeyCopyExternalRepresentation` function from Apple's `Security` framework
+  /// - Note: If called on a Private Key, this method will return the Private Keys DER Representation. Likewise, if called on a Public Key, this method will return the Public Keys DER Representation
+  /// - Note: If you'd like to export the Public Keys DER from a Private Key, use the `publicKeyExternalRepresentation()` function
+  func externalRepresentation() throws -> Data
+  /// A semantically similar function that mimics the `SecKeyCopyExternalRepresentation` function from Apple's `Security` framework
+  /// - Note: This function only ever exports the Public Key's DER representation. If called on a Private Key, the corresponding Public Key will be extracted and exported.
+  func publicKeyExternalRepresentation() throws -> Data
+}
+
+extension DEREncodable {
+  public func externalRepresentation() throws -> Data {
+    // The default implementation that makes the original internal function publicly available
+    do { return try Data(self.privateKeyDER()) } catch {
+      return try Data(self.publicKeyDER())
+    }
+  }
+
+  public func publicKeyExternalRepresentation() throws -> Data {
+    // The default implementation that makes the original internal function publicly available
+    try Data(self.publicKeyDER())
+  }
+}
+
+struct DER {
+  /// Integer to Octet String Primitive
+  /// - Parameters:
+  ///   - x: nonnegative integer to be converted
+  ///   - size: intended length of the resulting octet string
+  /// - Returns: corresponding octet string of length xLen
+  /// - Note: https://datatracker.ietf.org/doc/html/rfc3447#section-4.1
+  internal static func i2osp(x: [UInt8], size: Int) -> [UInt8] {
+    var modulus = x
+    while modulus.count < size {
+      modulus.insert(0x00, at: 0)
+    }
+    if modulus[0] >= 0x80 {
+      modulus.insert(0x00, at: 0)
+    }
+    return modulus
+  }
+
+  /// Integer to Octet String Primitive
+  /// - Parameters:
+  ///   - x: nonnegative integer to be converted
+  ///   - size: intended length of the resulting octet string
+  /// - Returns: corresponding octet string of length xLen
+  /// - Note: https://datatracker.ietf.org/doc/html/rfc3447#section-4.1
+  internal static func i2ospData(x: [UInt8], size: Int) -> Data {
+    return Data(DER.i2osp(x: x, size: size))
+  }
+}

+ 521 - 0
Tests/CryptoSwiftTests/ASN1Tests.swift

@@ -0,0 +1,521 @@
+//
+//  CryptoSwift
+//
+//  Copyright (C) 2014-2021 Marcin Krzyżanowski <marcin@krzyzanowskim.com>
+//  This software is provided 'as-is', without any express or implied warranty.
+//
+//  In no event will the authors be held liable for any damages arising from the use of this software.
+//
+//  Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
+//
+//  - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required.
+//  - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+//  - This notice may not be removed or altered from any source or binary distribution.
+//
+
+import XCTest
+@testable import CryptoSwift
+
+final class ASN1Tests: XCTestCase {
+
+  /// This test ensures that we get the same data out as we put in before and after encoding
+  ///
+  /// This test enforces that
+  /// 1) The data provided to each of the `ASN1.Node`s is preserved across encoding and decoding.
+  func testASN1NonDestructiveEncoding() throws {
+    let arbitraryData = Data(hex: "0123456789")
+
+    // Encode the serialized BigInteger
+    let node: ASN1.Node = .sequence(nodes: [
+      .integer(data: arbitraryData),
+      .bitString(data: arbitraryData),
+      .octetString(data: arbitraryData),
+      .null,
+      .objectIdentifier(data: arbitraryData)
+    ])
+
+    let encoded = ASN1.Encoder.encode(node)
+
+    // Decode the Encoding
+    let decoded = try ASN1.Decoder.decode(data: Data(encoded))
+    guard case .sequence(let sequence) = decoded else {
+      XCTFail("Failed to recover encoded SEQUENCE")
+      return
+    }
+
+    XCTAssertEqual(sequence.count, 5)
+
+    guard case .integer(let integerData) = sequence[0] else {
+      XCTFail("Failed to recover encoded INTEGER")
+      return
+    }
+    XCTAssertEqual(integerData, arbitraryData)
+
+    guard case .bitString(let bitStringData) = sequence[1] else {
+      XCTFail("Failed to recover encoded BITSTRING")
+      return
+    }
+    XCTAssertEqual(bitStringData, arbitraryData)
+
+    guard case .octetString(let octetData) = sequence[2] else {
+      XCTFail("Failed to recover encoded OCTETSTRING")
+      return
+    }
+    XCTAssertEqual(octetData, arbitraryData)
+
+    guard case .null = sequence[3] else {
+      XCTFail("Failed to recover encoded NULL")
+      return
+    }
+
+    guard case .objectIdentifier(let objIDData) = sequence[4] else {
+      XCTFail("Failed to recover encoded OBJECTIDENTIFIER")
+      return
+    }
+    XCTAssertEqual(objIDData, arbitraryData)
+  }
+
+  /// The ASN1 Encoder / Decoder doesn't handle encoding / decoding Integers directly, it's your responsibility to accurately serialize your integers.
+  /// - Note: In this example we're using BigInteger's serialization technique to encode / decode Integers, which isn't the default method for handling integers. https://www.strozhevsky.com/free_docs/asn1_by_simple_words.pdf
+  ///
+  /// This test enforces that
+  /// 1) The data provided to the `ASN1.Node.integer` Node is preserved across encoding and decoding.
+  func testASN1DecodePrimitiveInteger() throws {
+    let tests = [
+      0,
+      -0,
+      128,
+      -128,
+      136,
+      -136,
+      8388607,
+      -8388607,
+      3409934108352718734,
+      -3409934108352718734
+    ]
+
+    for test in tests {
+      let number = BigInteger(test)
+
+      // Encode the serialized Integer
+      let encoded = ASN1.Encoder.encode(.integer(data: number.serialize()))
+
+      // Ensure the Integer Prefix was added
+      XCTAssertEqual(Array<UInt8>(arrayLiteral: 0x02), Array(encoded.prefix(1)))
+
+      // Decode the Encoding
+      let decoded = try ASN1.Decoder.decode(data: Data(encoded))
+      guard case .integer(let num) = decoded else {
+        XCTFail("Failed to recover encoded integer")
+        return
+      }
+
+      // Ensure the original BigInteger was recovered
+      XCTAssertEqual(BigInteger(num), number)
+    }
+  }
+
+  /// Another test showing that Integers are stored as arbitrary data and the proper serialization and signage are the responsibilities of the user and their application.
+  ///
+  /// This test enforces that
+  /// 1) The data provided to the `ASN1.Node.integer` Node is preserved across encoding and decoding.
+  func testASN1DecodeLargeBigInteger() throws {
+    // Because INTEGERS are stored as arbitrary data we should be able to store an integer that would otherwise overflow by using the BigInteger library
+    let largeBigInt = BigInteger("1541235134652345698374107823450134507610354876134950342785028743653")
+
+    // Encode the serialized BigInteger
+    let encoded = ASN1.Encoder.encode(.integer(data: largeBigInt.serialize()))
+
+    // Decode the Encoding
+    let decoded = try ASN1.Decoder.decode(data: Data(encoded))
+    guard case .integer(let num) = decoded else {
+      XCTFail("Failed to recover encoded integer")
+      return
+    }
+
+    // Ensure the original BigInteger was recovered
+    XCTAssertEqual(BigInteger(num), largeBigInt)
+  }
+
+  /// This tests decodes an RSA Public Key from a Base64 DER Encoded representation
+  ///
+  /// This test enforces that
+  /// 1) The decoding process yeilds the expected format.
+  /// 2) The re-encoding of the data yeilds the exact same starting data.
+  func testANS1DERDecodingPublicKey() throws {
+    /// An example of an RSA Public Key ASN1 DER Encoding
+    ///
+    /// [IETF Spec RFC2313](https://datatracker.ietf.org/doc/html/rfc2313#section-7.1)
+    /// ```
+    /// =========================
+    ///  RSA PublicKey Structure
+    /// =========================
+    ///
+    /// RSAPublicKey ::= SEQUENCE {
+    ///   modulus           INTEGER,  -- n
+    ///   publicExponent    INTEGER,  -- e
+    /// }
+    /// ```
+    let publicDER = """
+    MIGJAoGBAMGeZvIG84vyKAATwKkKz2g+PeNaZ63rxk/zEnLGxkMCVKnUZ6jPAtzYOKM24949yIhfxYBC/bOCPwRK4wbr4YyIx3WB2v+Zcqe8pRM/BThUpNIx3K2+jbJBhAopf1GXJ3i31RuiLMh9HWhxzkVamz1KnDjCuTZguCRRHIv+r3XTAgMBAAE=
+    """
+
+    guard let publicDERData = Data(base64Encoded: publicDER) else {
+      XCTFail("Failed to convert base64 string into data for decoding.")
+      return
+    }
+
+    let decoded = try ASN1.Decoder.decode(data: publicDERData)
+
+    // Ensure the first node is of type Sequence
+    guard case .sequence(let nodes) = decoded else {
+      XCTFail("Expected the top level node to be a sequence and it wasn't")
+      return
+    }
+
+    // Ensure there are two nodes within the top level sequence (our integers, n and e)
+    XCTAssertEqual(nodes.count, 2)
+
+    // Ensure that the first node within our sequence is of type Integer
+    guard case .integer(let n) = nodes[0] else {
+      XCTFail("Expected an integer within our sequence and it wasn't")
+      return
+    }
+
+    // Ensure the second node within our sequence is of type Integer
+    guard case .integer(let e) = nodes[1] else {
+      XCTFail("Expected an integer within our sequence and it wasn't")
+      return
+    }
+
+    // Ensure that n contains the data we expected
+    XCTAssertEqual(n.toHexString(), "00c19e66f206f38bf2280013c0a90acf683e3de35a67adebc64ff31272c6c6430254a9d467a8cf02dcd838a336e3de3dc8885fc58042fdb3823f044ae306ebe18c88c77581daff9972a7bca5133f053854a4d231dcadbe8db241840a297f51972778b7d51ba22cc87d1d6871ce455a9b3d4a9c38c2b93660b824511c8bfeaf75d3")
+
+    // Ensure that e contains the data we expected
+    XCTAssertEqual(e.toHexString(), "010001") // 65537
+
+    // Re Encode the data
+    let asn1: ASN1.Node = .sequence(nodes: [
+      .integer(data: n),
+      .integer(data: e)
+    ])
+
+    let encoded = ASN1.Encoder.encode(asn1)
+
+    // Ensure the re-encoded data matches the original exactly
+    XCTAssertEqual(Data(encoded), publicDERData)
+    XCTAssertEqual(encoded, publicDERData.bytes)
+    XCTAssertEqual(encoded.toBase64(), publicDER)
+  }
+
+  /// This tests decodes an RSA Private Key from a Base64 DER Encoded representation
+  ///
+  /// This test enforces that
+  /// 1) The decoding process yeilds the expected format.
+  /// 2) The re-encoding of the data yeilds the exact same starting data.
+  func testANS1DERDecodingPrivateKey() throws {
+    /// An example of an RSA Private Key ASN1 DER Encoding
+    ///
+    /// [IETF Spec RFC2313](https://datatracker.ietf.org/doc/html/rfc2313#section-7.2)
+    /// ```
+    /// ==========================
+    ///  RSA PrivateKey Structure
+    /// ==========================
+    ///
+    /// RSAPrivateKey ::= SEQUENCE {
+    ///   version           Version,
+    ///   modulus           INTEGER,  -- n
+    ///   publicExponent    INTEGER,  -- e
+    ///   privateExponent   INTEGER,  -- d
+    ///   prime1            INTEGER,  -- p
+    ///   prime2            INTEGER,  -- q
+    ///   exponent1         INTEGER,  -- d mod (p-1)
+    ///   exponent2         INTEGER,  -- d mod (q-1)
+    ///   coefficient       INTEGER,  -- (inverse of q) mod p
+    ///   otherPrimeInfos   OtherPrimeInfos OPTIONAL
+    /// }
+    /// ```
+    let privateDER = """
+    MIICXQIBAAKBgQDBnmbyBvOL8igAE8CpCs9oPj3jWmet68ZP8xJyxsZDAlSp1GeozwLc2DijNuPePciIX8WAQv2zgj8ESuMG6+GMiMd1gdr/mXKnvKUTPwU4VKTSMdytvo2yQYQKKX9Rlyd4t9UboizIfR1occ5FWps9Spw4wrk2YLgkURyL/q910wIDAQABAoGAGJkNLxZe/pqHJmtcAJ3U98NgjW/A2EGp8iJJZ7eFHKJBK0pG2RVjobb+iw3AKU3kGh9AsijQnmufoeX5rblt7/ojgpfVhS7NHsKCi8Nx7U92bNnP0RP4mogpvzGWVknUdv6jW7dX83FKgEywbNKa5CPQk1XinqXL33gNjWdOh/ECQQDjdE4kNdVwKA59ddWRShvJiOMOG8+TjE5HvcZzKQ+UMlBwbknL5tIJE7KnN9ZEfNihVmyrMAzJAfe2PCyZAip/AkEA2esFkG+ScgeVYlGrUqrqUkvzj1j6F8R+8rGvCjq2WnDL8TzO7NoT7qivW/+6E9osX1WwWAtj/84eN7dvLLxCrQJBAN7GomZq58MzKIYPLH9iI3cwAJtn99ZfHKi9oipW9DBFW23TR6pTSDKlvVx0nwNzeEYFPOgqZstVhwZRR6kRawcCQHx/u0QTmjUvg/cR9bFbGFhAMDxbdzaQ+n4paXmMpZXyD3IZbZb/2JdnJBiJd4PUB7nHuOH0UANbfQQT9p42SFkCQQCcdFRTZEZv5TjmcUn0GBUzRmnswiRc1YEg81DSDlvD3dEIVSl6PLkzcNNItrgD5SfC5MxCv6PIUlJVhnkavEjS
+    """
+
+    guard let privateDERData = Data(base64Encoded: privateDER) else {
+      XCTFail("Failed to convert base64 string into data for decoding.")
+      return
+    }
+
+    let decoded = try ASN1.Decoder.decode(data: privateDERData)
+
+    // Ensure the first Node is of type SEQUENCE
+    guard case .sequence(let nodes) = decoded else {
+      XCTFail("Expected the top level node to be a sequence and it wasn't")
+      return
+    }
+
+    // Ensure there are nine (9) Nodes within the top level SEQUENCE
+    XCTAssertEqual(nodes.count, 9)
+
+    // Deconstruct the parameters
+    guard case .integer(let version) = nodes[0] else {
+      XCTFail("Expected an integer within our sequence and it wasn't")
+      return
+    }
+    XCTAssertEqual(version, Data(hex: "00"))
+
+    guard case .integer(let n) = nodes[1] else {
+      XCTFail("Expected an integer within our sequence and it wasn't")
+      return
+    }
+    XCTAssertEqual(n, Data(hex: "00c19e66f206f38bf2280013c0a90acf683e3de35a67adebc64ff31272c6c6430254a9d467a8cf02dcd838a336e3de3dc8885fc58042fdb3823f044ae306ebe18c88c77581daff9972a7bca5133f053854a4d231dcadbe8db241840a297f51972778b7d51ba22cc87d1d6871ce455a9b3d4a9c38c2b93660b824511c8bfeaf75d3"))
+
+    guard case .integer(let e) = nodes[2] else {
+      XCTFail("Expected an integer within our sequence and it wasn't")
+      return
+    }
+    XCTAssertEqual(e, Data(hex: "010001"))
+
+    guard case .integer(let d) = nodes[3] else {
+      XCTFail("Expected an integer within our sequence and it wasn't")
+      return
+    }
+    XCTAssertEqual(d, Data(hex: "18990d2f165efe9a87266b5c009dd4f7c3608d6fc0d841a9f2224967b7851ca2412b4a46d91563a1b6fe8b0dc0294de41a1f40b228d09e6b9fa1e5f9adb96deffa238297d5852ecd1ec2828bc371ed4f766cd9cfd113f89a8829bf31965649d476fea35bb757f3714a804cb06cd29ae423d09355e29ea5cbdf780d8d674e87f1"))
+
+    guard case .integer(let p) = nodes[4] else {
+      XCTFail("Expected an integer within our sequence and it wasn't")
+      return
+    }
+    XCTAssertEqual(p, Data(hex: "00e3744e2435d570280e7d75d5914a1bc988e30e1bcf938c4e47bdc673290f943250706e49cbe6d20913b2a737d6447cd8a1566cab300cc901f7b63c2c99022a7f"))
+
+    guard case .integer(let q) = nodes[5] else {
+      XCTFail("Expected an integer within our sequence and it wasn't")
+      return
+    }
+    XCTAssertEqual(q, Data(hex: "00d9eb05906f927207956251ab52aaea524bf38f58fa17c47ef2b1af0a3ab65a70cbf13cceecda13eea8af5bffba13da2c5f55b0580b63ffce1e37b76f2cbc42ad"))
+
+    guard case .integer(let exp1) = nodes[6] else {
+      XCTFail("Expected an integer within our sequence and it wasn't")
+      return
+    }
+    XCTAssertEqual(exp1, Data(hex: "00dec6a2666ae7c33328860f2c7f62237730009b67f7d65f1ca8bda22a56f430455b6dd347aa534832a5bd5c749f03737846053ce82a66cb5587065147a9116b07"))
+
+    guard case .integer(let exp2) = nodes[7] else {
+      XCTFail("Expected an integer within our sequence and it wasn't")
+      return
+    }
+    XCTAssertEqual(exp2, Data(hex: "7c7fbb44139a352f83f711f5b15b185840303c5b773690fa7e2969798ca595f20f72196d96ffd897672418897783d407b9c7b8e1f450035b7d0413f69e364859"))
+
+    guard case .integer(let coefficient) = nodes[8] else {
+      XCTFail("Expected an integer within our sequence and it wasn't")
+      return
+    }
+    XCTAssertEqual(coefficient, Data(hex: "009c74545364466fe538e67149f41815334669ecc2245cd58120f350d20e5bc3ddd10855297a3cb93370d348b6b803e527c2e4cc42bfa3c852525586791abc48d2"))
+
+    // Ensure re-encoding the data yeilds the exact same starting data
+    let asn: ASN1.Node = .sequence(nodes: [
+      .integer(data: version),
+      .integer(data: n),
+      .integer(data: e),
+      .integer(data: d),
+      .integer(data: p),
+      .integer(data: q),
+      .integer(data: exp1),
+      .integer(data: exp2),
+      .integer(data: coefficient)
+    ])
+
+    // Encode the ASN1 Nodes
+    let encodedData = ASN1.Encoder.encode(asn)
+
+    // Ensure the re-encoded data matches the original data exactly
+    XCTAssertEqual(Data(encodedData), privateDERData)
+    XCTAssertEqual(encodedData, privateDERData.bytes)
+    XCTAssertEqual(encodedData.toBase64(), privateDER.replacingOccurrences(of: "\n", with: ""))
+  }
+
+  /// This tests decodes an Encrypted RSA Private Key from a Base64 PEM Encoded representation
+  ///
+  /// This test enforces that
+  /// 1) The decoding process yeilds the expected format.
+  /// 2) The re-encoding of the data yeilds the exact same starting data.
+  func testASN1DecodingEncryptedPEM() throws {
+    /// ==========================
+    ///  Encrypted PEM Structure
+    /// ==========================
+    ///
+    /// EncryptedPrivateKey ::= SEQUENCE {
+    ///
+    ///   PEMStructure ::= SEQUENCE {
+    ///     pemObjID  OBJECTIDENTIFIER
+    ///
+    ///     EncryptionAlgorithms ::= SEQUENCE {
+    ///
+    ///       PBKDFAlgorithms ::= SEQUENCE {
+    ///         pbkdfObjID  OBJECTIDENTIFIER
+    ///         PBKDFParams ::= SEQUENCE {
+    ///           salt OCTETSTRING,
+    ///           iterations INTEGER
+    ///         }
+    ///       },
+    ///
+    ///       CIPHERAlgorithms ::= SEQUENCE {
+    ///         cipherObjID  OBJECTIDENTIFIER,
+    ///         iv           OCTETSTRING
+    ///       }
+    ///     }
+    ///   }
+    ///   encryptedData OCTETSTRING
+    /// }
+    ///
+    /// /*
+    ///  * Generated with
+    ///  * openssl genpkey -algorithm RSA
+    ///  *   -pkeyopt rsa_keygen_bits:1024
+    ///  *   -pkeyopt rsa_keygen_pubexp:65537
+    ///  *   -out foo.pem
+    ///  * openssl pkcs8 -in foo.pem -topk8 -v2 aes-128-cbc -passout pass:mypassword
+    ///  */
+    let encryptedPEMFormat = """
+    MIICzzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIP5QK2RfqUl4CAggA
+    MB0GCWCGSAFlAwQBAgQQj3OyM9gnW2dd/eRHkxjGrgSCAoCpM5GZB0v27cxzZsGc
+    O4/xqgwB0c/bSJ6QogtYU2KVoc7ZNQ5q9jtzn3I4ONvneOkpm9arzYz0FWnJi2C3
+    BPiF0D1NkfvjvMLv56bwiG2A1oBECacyAb2pXYeJY7SdtYKvcbgs3jx65uCm6TF2
+    BylteH+n1ewTQN9DLfASp1n81Ajq9lQGaK03SN2MUtcAPp7N9gnxJrlmDGeqlPRs
+    KpQYRcot+kE6Ew8a5jAr7mAxwpqvr3SM4dMvADZmRQsM4Uc/9+YMUdI52DG87EWc
+    0OUB+fnQ8jw4DZgOE9KKM5/QTWc3aEw/dzXr/YJsrv01oLazhqVHnEMG0Nfr0+DP
+    q+qac1AsCsOb71VxaRlRZcVEkEfAq3gidSPD93qmlDrCnmLYTilcLanXUepda7ez
+    qhjkHtpwBLN5xRZxOn3oUuLGjk8VRwfmFX+RIMYCyihjdmbEDYpNUVkQVYFGi/F/
+    1hxOyl9yhGdL0hb9pKHH10GGIgoqo4jSTLlb4ennihGMHCjehAjLdx/GKJkOWShy
+    V9hj8rAuYnRNb+tUW7ChXm1nLq14x9x1tX0ciVVn3ap/NoMkbFTr8M3pJ4bQlpAn
+    wCT2erYqwQtgSpOJcrFeph9TjIrNRVE7Zlmr7vayJrB/8/oPssVdhf82TXkna4fB
+    PcmO0YWLa117rfdeNM/Duy0ThSdTl39Qd+4FxqRZiHjbt+l0iSa/nOjTv1TZ/QqF
+    wqrO6EtcM45fbFJ1Y79o2ptC2D6MB4HKJq9WCt064/8zQCVx3XPbb3X8Z5o/6koy
+    ePGbz+UtSb9xczvqpRCOiFLh2MG1dUgWuHazjOtUcVWvilKnkjCMzZ9s1qG0sUDj
+    nPyn
+    """
+
+    guard let pemData = Data(base64Encoded: encryptedPEMFormat.replacingOccurrences(of: "\n", with: "")) else {
+      XCTFail("Failed to convert base64 string to data")
+      return
+    }
+
+    let asn = try ASN1.Decoder.decode(data: pemData)
+
+    // Ensure the first node is a Sequence
+    guard case .sequence(let encryptedPEMWrapper) = asn else {
+      XCTFail("Expected top level Node to be a SEQUENCE object")
+      return
+    }
+    // Ensure the first Sequence contains exactly 2 nodes (another Sequence and our OctetString of encrypted data)
+    XCTAssertEqual(encryptedPEMWrapper.count, 2)
+
+    // Ensure the first node within the top level sequence is another sequence
+    guard case .sequence(let encryptionInfoWrapper) = encryptedPEMWrapper[0] else {
+      XCTFail("Expected the first Node within our top level SEQUENCE to be a SEQUENCE object and it wasn't")
+      return
+    }
+    // Ensure this sequence contains exactly two nodes (the PEMs ObjectIdentifier and another sequence that houses the encryption algorithms)
+    XCTAssertEqual(encryptionInfoWrapper.count, 2)
+
+    // Ensure the first Node is the PEM's OBJECTIDENTIFIER Node
+    guard case .objectIdentifier(let pemObjID) = encryptionInfoWrapper[0] else {
+      XCTFail("Expected an OBJECTIDENTIFIER and it wasn't")
+      return
+    }
+
+    // Ensure the second Node is another SEQUENCE Node
+    guard case .sequence(let encryptionAlgorithmsWrapper) = encryptionInfoWrapper[1] else {
+      XCTFail("Expected another SEQUENCE Node and it wasn't")
+      return
+    }
+    // Ensure this sequence contains exactly two nodes (the PEMs ObjectIdentifier and another sequence that houses the encryption algorithms)
+    XCTAssertEqual(encryptionAlgorithmsWrapper.count, 2)
+
+    // Ensure the first Node is another SEQUENCE Node
+    guard case .sequence(let pbkdfAlgorithmWrapper) = encryptionAlgorithmsWrapper[0] else {
+      XCTFail("Expected another SEQUENCE Node and it wasn't")
+      return
+    }
+    // Ensure this sequence contains exactly two nodes (the PBKDF ObjectIdentifier and another sequence that houses the PBKDF params)
+    XCTAssertEqual(pbkdfAlgorithmWrapper.count, 2)
+
+    guard case .objectIdentifier(let pbkdfObjID) = pbkdfAlgorithmWrapper[0] else {
+      XCTFail("Expected an OBJECTIDENTIFIER and it wasn't")
+      return
+    }
+    guard case .sequence(let pbkdfParamsWrapper) = pbkdfAlgorithmWrapper[1] else {
+      XCTFail("Expected an OCTETSTRING and it wasn't")
+      return
+    }
+    // Ensure this sequence contains exactly two nodes (the PBKDF Salt as an OCTETSTRING and the PBKDF Iterations as an INTEGER)
+    XCTAssertEqual(pbkdfParamsWrapper.count, 2)
+    guard case .octetString(let pbkdfSalt) = pbkdfParamsWrapper[0] else {
+      XCTFail("Expected an OCTETSTRING and it wasn't")
+      return
+    }
+    guard case .integer(let pbkdfIterations) = pbkdfParamsWrapper[1] else {
+      XCTFail("Expected an INTEGER and it wasn't")
+      return
+    }
+
+    // Ensure the second Node is another SEQUENCE Node
+    guard case .sequence(let cipherAlgorithmWrapper) = encryptionAlgorithmsWrapper[1] else {
+      XCTFail("Expected another SEQUENCE Node and it wasn't")
+      return
+    }
+    // Ensure this sequence contains exactly two nodes (the CIPHER ObjectIdentifier and an OCTETSTRING that contains the CIPHERs InitialVector)
+    XCTAssertEqual(cipherAlgorithmWrapper.count, 2)
+
+    guard case .objectIdentifier(let cipherObjID) = cipherAlgorithmWrapper[0] else {
+      XCTFail("Expected an OBJECTIDENTIFIER and it wasn't")
+      return
+    }
+    guard case .octetString(let cipherInitialVector) = cipherAlgorithmWrapper[1] else {
+      XCTFail("Expected an OCTETSTRING and it wasn't")
+      return
+    }
+
+    // Ensure the last (2nd) Node in the top level SEQUENCE Node is an OCTETSTRING that contains the encrypted key data
+    guard case .octetString(let encryptedData) = encryptedPEMWrapper[1] else {
+      XCTFail("Expected the last Node in the first SEQUENCE Node to be an OCTETSTRING and it wasn't")
+      return
+    }
+
+    // Now lets ensure we can re encode the object and get back the exact same data
+    let nodes: ASN1.Node = .sequence(nodes: [
+      .sequence(nodes: [
+        .objectIdentifier(data: pemObjID),
+        .sequence(nodes: [
+          .sequence(nodes: [
+            .objectIdentifier(data: pbkdfObjID),
+            .sequence(nodes: [
+              .octetString(data: pbkdfSalt),
+              .integer(data: pbkdfIterations)
+            ])
+          ]),
+          .sequence(nodes: [
+            .objectIdentifier(data: cipherObjID),
+            .octetString(data: cipherInitialVector)
+          ])
+        ])
+      ]),
+      .octetString(data: encryptedData)
+    ])
+
+    // Encode the ASN1 Nodes
+    let encodedData = ASN1.Encoder.encode(nodes)
+
+    // Ensure the re-encoded data matches the original data exactly
+    XCTAssertEqual(Data(encodedData), pemData)
+    XCTAssertEqual(encodedData, pemData.bytes)
+    XCTAssertEqual(encodedData.toBase64(), encryptedPEMFormat.replacingOccurrences(of: "\n", with: ""))
+  }
+
+  static let allTests = [
+    ("testASN1NonDestructiveEncoding", testASN1NonDestructiveEncoding),
+    ("testASN1DecodePrimitiveInteger", testASN1DecodePrimitiveInteger),
+    ("testASN1DecodeLargeBigInteger", testASN1DecodeLargeBigInteger),
+    ("testANS1DERDecodingPublicKey", testANS1DERDecodingPublicKey),
+    ("testANS1DERDecodingPrivateKey", testANS1DERDecodingPrivateKey),
+    ("testASN1DecodingEncryptedPEM", testASN1DecodingEncryptedPEM)
+  ]
+}