221 lines
7.1 KiB
Swift
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
|
|
}
|
|
}
|