Skip to content

Instantly share code, notes, and snippets.

@maxchuquimia
Last active March 19, 2021 09:38
Show Gist options
  • Save maxchuquimia/2daaec0715d4f6d7347534d42bfa7110 to your computer and use it in GitHub Desktop.
Save maxchuquimia/2daaec0715d4f6d7347534d42bfa7110 to your computer and use it in GitHub Desktop.
Decoding an ASN.1 DER Sequence in Swift
//
// Final implementation, as described in
// http://nspasteboard.com/2016/10/23/decoding-asn1-der-sequences-in-swift/
//
import Foundation
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Implementation (required)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
extension Data {
var firstByte: UInt8 {
var byte: UInt8 = 0
copyBytes(to: &byte, count: MemoryLayout<UInt8>.size)
return byte
}
}
class SimpleScanner {
let data: Data
///The current position of the scanning head
private(set) var position = 0
init(data: Data) {
self.data = data
}
/**
`true` if there are no more bytes available to scan.
*/
var isComplete: Bool {
return position >= data.count
}
/**
Roll the scan head back to the position it was at before the last command was run.
If the last command failed, calling this does nothing as the scan head was already returned to it's state before failure
*/
func rollback(distance: Int) {
position = position - distance
if position < 0 {
position = 0
}
}
/**
Scans `d` bytes, or returns `nil` and restores position if `d` is greater than the number of bytes remaining
*/
func scan(distance: Int) -> Data? {
return popByte(s: distance)
}
/**
Scans to the end of the data.
*/
func scanToEnd() -> Data? {
return scan(distance: data.count - position)
}
private func popByte(s: Int = 1) -> Data? {
guard s > 0 else { return nil }
guard position <= (data.count - s) else { return nil }
defer {
position = position + s
}
return data.subdata(in: data.startIndex.advanced(by: position)..<data.startIndex.advanced(by: position + s))
}
}
struct ASN1Object {
let type: ASN1DERDecoder.DERCode
let data: Data
}
struct ASN1DERDecoder {
enum DERCode: UInt8 {
//All sequences should begin with this
//https://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One#Example_encoded_in_DER
case Sequence = 0x30
//Type tags - add more here!
//http://www.obj-sys.com/asn1tutorial/node10.html
case Boolean = 0x01
case Integer = 0x02
case IA5String = 0x16
static func allTypes() -> [DERCode] {
return [
.Boolean,
.Integer,
.IA5String
]
}
}
static func decode(data: Data) -> [ASN1Object]? {
let scanner = SimpleScanner(data: data)
//Verify that this is actually a DER sequence
guard scanner.scan(distance: 1)?.firstByte == DERCode.Sequence.rawValue else {
return nil
}
//The second byte should equate to the length of the data, minus itself and the sequence type
guard let expectedLength = scanner.scan(distance: 1)?.firstByte, Int(expectedLength) == data.count - 2 else {
return nil
}
var output: [ASN1Object] = []
while !scanner.isComplete {
//Search the current position of the sequence for a known type
var dataType: DERCode?
for type in DERCode.allTypes() {
if scanner.scan(distance: 1)?.firstByte == type.rawValue {
dataType = type
} else {
scanner.rollback(distance: 1)
}
}
guard let type = dataType else {
//Unsupported type - add it to `DERCode.all()`
return nil
}
guard let length = scanner.scan(distance: 1) else {
//Expected a byte describing the length of the proceeding data
return nil
}
let lengthInt = length.firstByte
guard let actualData = scanner.scan(distance: Int(lengthInt)) else {
//Expected to be able to scan `lengthInt` bytes
return nil
}
let object = ASN1Object(type: type, data: actualData)
output.append(object)
}
return output
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Examples
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
func integerData(fromBase64String string: String) -> Data {
let data = Data(base64Encoded: string, options: [])!
let integerData = ASN1DERDecoder.decode(data: data)
return integerData!.reduce(Data(), { (sum, next) -> Data in
let filter = SimpleScanner(data: next.data)
if filter.scan(distance: 1)?.firstByte == 0x0 {
return sum + filter.scanToEnd()!
} else {
return sum + next.data
}
})
}
let tests = [
//"Base64 DER Sequence signature" : "Output from `openssl asn1parse -inform der -in /path/to/signature`"
"MEUCIQCoXnb/HxjlLU7M3ndKpG4+wokQaOvviafLbgZT6w3/sgIgTNduGbyxjXbBOvDqBhEXy9bTyPvR2fyarCZSZjccOj8=": "A85E76FF1F18E52D4ECCDE774AA46E3EC2891068EBEF89A7CB6E0653EB0DFFB24CD76E19BCB18D76C13AF0EA061117CBD6D3C8FBD1D9FC9AAC265266371C3A3F",
"MEQCICG1XIK8MeVI3w0Y93kEGNGysMXaqudosFm3cbJVc9nuAiBtGugcIMhyJU4qmlYCfDYMNzPy8md3Zc281bH1oDEAPg==": "21B55C82BC31E548DF0D18F7790418D1B2B0C5DAAAE768B059B771B25573D9EE6D1AE81C20C872254E2A9A56027C360C3733F2F2677765CDBCD5B1F5A031003E"
]
for test in tests {
let integer = integerData(fromBase64String: test.key)
let hex = integer.reduce("") {$0 + String(format: "%02x", $1)}
print(hex.uppercased() == test.value)
}
@phamngocanit82
Copy link

can you explain this string "MEUCIQCoXnb/HxjlLU7M3ndKpG4+wokQaOvviafLbgZT6w3/sgIgTNduGbyxjXbBOvDqBhEXy9bTyPvR2fyarCZSZjccOj8=" , it get from where

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment