Last active
March 19, 2021 09:38
-
-
Save maxchuquimia/2daaec0715d4f6d7347534d42bfa7110 to your computer and use it in GitHub Desktop.
Decoding an ASN.1 DER Sequence in Swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
can you explain this string "MEUCIQCoXnb/HxjlLU7M3ndKpG4+wokQaOvviafLbgZT6w3/sgIgTNduGbyxjXbBOvDqBhEXy9bTyPvR2fyarCZSZjccOj8=" , it get from where