Files
OrderScheduling/Pods/SwiftyRSA/Source/Asn1Parser.swift
2023-10-08 16:18:31 +08:00

221 lines
7.1 KiB
Swift

//
// Asn1Parser.swift
// SwiftyRSA
//
// Created by Lois Di Qual on 5/9/17.
// Copyright © 2017 Scoop. All rights reserved.
//
import Foundation
/// Simple data scanner that consumes bytes from a raw data and keeps an updated position.
private 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 index >= 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 index + length <= data.count else {
throw ScannerError.outOfBounds
}
let subdata = data.subdata(in: index..<index + length)
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
}
}
private 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)
}
}
/// 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 Asn1Parser {
/// An ASN1 node
enum Node {
case sequence(nodes: [Node])
case integer(data: Data)
case objectIdentifier(data: Data)
case null
case bitString(data: Data)
case octetString(data: Data)
}
enum ParserError: 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 ParserError if anything goes wrong, or if an unknown node was encountered
static func parse(data: Data) throws -> Node {
let scanner = Scanner(data: data)
let node = try parseNode(scanner: scanner)
return node
}
/// Parses an ASN1 given an existing scanne.
/// @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 ParserError if anything goes wrong, or if an unknown node was encountered
private static func parseNode(scanner: Scanner) throws -> Node {
let firstByte = try scanner.consume(length: 1).firstByte
// Sequence
if firstByte == 0x30 {
let length = try scanner.consumeLength()
let data = try scanner.consume(length: length)
let nodes = try parseSequence(data: data)
return .sequence(nodes: nodes)
}
// Integer
if firstByte == 0x02 {
let length = try scanner.consumeLength()
let data = try scanner.consume(length: length)
return .integer(data: data)
}
// Object identifier
if firstByte == 0x06 {
let length = try scanner.consumeLength()
let data = try scanner.consume(length: length)
return .objectIdentifier(data: data)
}
// Null
if firstByte == 0x05 {
_ = try scanner.consume(length: 1)
return .null
}
// Bit String
if firstByte == 0x03 {
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)
}
// Octet String
if firstByte == 0x04 {
let length = try scanner.consumeLength()
let data = try scanner.consume(length: length)
return .octetString(data: data)
}
throw ParserError.invalidType(value: firstByte)
}
/// Parses an ASN1 sequence and returns its child nodes
///
/// - Parameter data: ASN1 data
/// - Returns: A list of ASN1 nodes
/// - Throws: A ParserError if anything goes wrong, or if an unknown node was encountered
private static func parseSequence(data: Data) throws -> [Node] {
let scanner = Scanner(data: data)
var nodes: [Node] = []
while !scanner.isComplete {
let node = try parseNode(scanner: scanner)
nodes.append(node)
}
return nodes
}
}