From c1dd5b55602d911789b8f871b67feb21a3533be5 Mon Sep 17 00:00:00 2001 From: Konrad Rosenbaum Date: Mon, 10 Nov 2014 20:51:06 +0100 Subject: [PATCH] simple basic DER parser, initial version --- .gitignore | 8 + derparser.pro | 5 + main.cpp | 407 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 420 insertions(+), 0 deletions(-) create mode 100644 .gitignore create mode 100644 derparser.pro create mode 100644 main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..586ad18 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.kdev* +*.der +*.pem +derparser +*.exe +*.o +*.obj +Makefile* \ No newline at end of file diff --git a/derparser.pro b/derparser.pro new file mode 100644 index 0000000..7ac6cbd --- /dev/null +++ b/derparser.pro @@ -0,0 +1,5 @@ +TARGET = derparser +CONFIG += c++11 +QT -= gui + +SOURCES = main.cpp diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..14a6ea4 --- /dev/null +++ b/main.cpp @@ -0,0 +1,407 @@ +#include +#include +#include +#include +#include +#include + +#include + + +class DataRunner +{ + QByteArray mdata; + int mstart,mend,mpos; + bool mwarn=false; + bool mabort=false; + DataRunner(nullptr_t):mabort(true){mstart=mend=mpos=0;} + public: + DataRunner(const QByteArray&a,int start=0,int size=-1) + :mdata(a),mstart(start) + { + if(mstart<0)mstart=0; + else if(mstart>mdata.size())mstart=mdata.size(); + if(size<0) + mend=mdata.size(); + else + mend=mstart+size; + if(mend>mdata.size())mend=mdata.size(); + mpos=mstart; + } + DataRunner(const DataRunner&)=default; + DataRunner(DataRunner&&)=default; + + void abort(){mabort=true;} + + QByteArray getData(int bytes) + { + if(bytes<=0)return QByteArray(); + if(mabort){ + qDebug()<<"Warning: requesting data from aborted data stream!"; + return QByteArray(bytes,char(0)); + } + int sbytes=bytes; + if((mpos+bytes)>mend){ + qDebug()<<"Warning: requesting more data than available! Requested"<=mend){ + qDebug()<<"Warning: requesting at least one more byte than available!"; + mwarn=true; + return 0; + } + if(mabort){ + qDebug()<<"Warning: requesting data from aborted data stream!"; + return 0; + } + return (quint8)mdata[mpos++]; + } + + void skipBytes(int bytes) + { + if(bytes<0)return; + if(mabort){ + qDebug()<<"Warning: skipping on aborted data stream!"; + return; + } + if((mpos+bytes)>mend){ + qDebug()<<"Warning: skipping past end of data! Requested"<mend){ + qDebug()<<"Warning: requesting subrunner with more data then available! Requested"<=mend;} +}; + +inline const char* getIndent(int indent,QString c=">") +{ + static QByteArray tmp; + tmp=(QString("%1%2 ").arg(QString(2*indent,QChar('-'))).arg(c)).toLatin1(); + return tmp.data(); +} + +inline bool isComposite(quint8 td) +{ + return (td&0x20) != 0; +} + +inline QString dataType(quint64 tc,quint8 td) +{ + QString ret; + if((td&0xc0)==0){ + switch(tc){ + case 0:ret="EOC (End-of-Content)";break; + case 1:ret="BOOLEAN";break; + case 2:ret="INTEGER";break; + case 3:ret="BIT STRING";break; + case 4:ret="OCTET STRING";break; + case 5:ret="NULL";break; + case 6:ret="OBJECT IDENTIFIER";break; + case 7:ret="Object Descriptor";break; + case 8:ret="EXTERNAL";break; + case 9:ret="REAL (float)";break; + case 10:ret="ENUMERATED";break; + case 11:ret="EMBEDDED PDV";break; + case 12:ret="UTF8String";break; + case 13:ret="RELATIVE-OID";break; + case 14:ret="(reserved 14)";break; + case 15:ret="(reserved 15)";break; + case 16:ret="SEQUENCE";break; + case 17:ret="SET";break; + case 18:ret="NumericString";break; + case 19:ret="PrintableString";break; + case 20:ret="T61String";break; + case 21:ret="VideotexString";break; + case 22:ret="IA5String";break; + case 23:ret="UTCTime";break; + case 24:ret="GeneralizedTime";break; + case 25:ret="GraphicString";break; + case 26:ret="VisibleString";break; + case 27:ret="GeneralString";break; + case 28:ret="UniversalString";break; + case 29:ret="CHARACTER STRING";break; + case 30:ret="BMPString";break; + default:ret=QString("Unknown universal type %1").arg(tc);break; + } + }else ret=QString("Non-universal type code %1").arg(tc); + if((td&0x1f)==0x1f){ + ret+=" (long type code)"; + if(tc<31)ret+=" [Ill-Formed!]"; + } + switch((td>>6)&3){ + case 0:ret+=" Universal";break; + case 1:ret+=" Application";break; + case 2:ret+=" Context-specific";break; + case 3:ret+=" Private";break; + } + if(isComposite(td))ret+=" Constructed"; + else ret+=" Primitive"; + return ret; +} + +inline QString decodeOID(DataRunner&dr) +{ + //first two digits + quint8 first=dr.getByte(); + QString ret=QString("%1.%2").arg(first/40).arg(first%40); + //remainder + quint64 value=0; + while(!dr.atEnd()){ + quint8 byte=dr.getByte(); + value<<=7; + value|=byte&0x7f; + if(byte<0x80){ + ret+=QString(".%1").arg(value); + value=0; + } + } + if(value>0){ + ret+=QString("[.%1]").arg(value); + qDebug()<<"Warning: OID with invalid encoding of last value."; + } + return ret; +} + +inline QString decodeData(quint8 td,quint64 tc,DataRunner&dr) +{ + if((td&0xc0)==0){ + switch(tc){ + case 1://bool + while(!dr.atEnd()) + if(dr.getByte()!=0)return "True"; + return "False"; + break; + case 2:{//int (assuming unsigned?) + if(dr.available()<=8){ + quint64 val=0; + while(!dr.atEnd()){ + val<<=8; + val|=dr.getByte(); + } + return QString::number(val); + }else return QString("large int 0x%1").arg(QString(dr.getData(dr.available()).toHex())); + } + case 6:return decodeOID(dr);//OID + case 12:return QString::fromUtf8(dr.getData(dr.available()));//UTF string + case 18://numeric string + case 19://printable string + case 22://IA5string + case 26://visible string + case 27://general string + case 28://universal string + case 29://character string, assuming ASCII for all of them + return QString::fromLatin1(dr.getData(dr.available())); + case 23:{//UTC time + QString utime=QString::fromLatin1(dr.getData(dr.available())); + if(utime.size()<11)return utime; + QString ret; + //year + if(utime[0]<'5' || utime.left(2)=="50")ret="20"+utime.left(2); + else ret="19"+utime.left(2); + //month/day/hour/minute + ret+=QString("-%1-%2 %3:%4").arg(utime.mid(2,2)).arg(utime.mid(4,2)).arg(utime.mid(6,2)).arg(utime.mid(8,2)); + //optional seconds + if(utime[10]>='0' && utime[10]<='9'){ + ret+=":"+utime.mid(10,2); + utime=utime.mid(12); + }else utime=utime.mid(10); + if(utime.size()>0){ + if(utime=="Z")ret+=" UTC"; + else ret=utime.left(3)+":"+utime.mid(3); + } + return ret; + } + case 30:{//BMP string (UCS2) + QTextCodec*codec=QTextCodec::codecForName("UCS2"); + if(codec) + return codec->toUnicode(dr.getData(dr.available())); + else + return QString("UCS2 string, Raw data: %1").arg(QString(dr.getData(dr.available()).toHex())); + } + default:break;//fall thru to general hex decoding + } + } + //fall-back: hex + return QString("Raw data: %1").arg(QString(dr.getData(dr.available()).toHex())); +} + +inline bool isEOC(quint8 td,quint64 tc) +{ + return (td&0xc0)==0 && tc==0; +} + +bool parseTLV(DataRunner&dr,int indent=0) +{ + //get type descriptor + const quint8 typed=dr.getByte(); + quint64 typecode=typed&0x1f; + if(typecode==0x1f){ + typecode=0; + quint8 byte; + do{ + if(typecode&0xfe00000000000000L) + qDebug()<<"Warning: excessively large type code detected (more than 64 bits)!"; + byte=dr.getByte(); + typecode<<=7; + typecode|=byte&0x7f; + }while(byte>=0x80); + } + qDebug()<>")<<"No data."; + return isEOC(typed,typecode); + } + if(length==0x80){ + qDebug()<>")<<"Indefinite Length Type detected"; + if(!isComposite(typed)){ + qDebug()<>")<<"Error: Indefinite Length is only valid for Composite Data Types"; + return isEOC(typed,typecode); + } + while(!parseTLV(dr,indent+1)); + return isEOC(typed,typecode); + } + if(length==0xff){ + qDebug()<<"Error: illegal Length Code 0xFF found!"; + dr.abort(); + return true;//fake EOC to force abort of parsing + } + if(length>0x80){ + int bytes=length&0x7f; + length=0; + for(int i=0;i>")<<"Error: excessively big length value detected!"; + dr.abort(); + return true;//fake EOC to abort parent item + } + length<<=8; + length|=dr.getByte(); + } + if(length<0){ + qDebug()<>")<<"Internal Error: somehow wound up with negative length. Aborting."; + dr.abort(); + return true;//fake EOC + } + } + qDebug()<>")<<"Length:"<>")<<"Warning: End-Of-Content found in non-Indefinite Composite."<>")<<"Data: "<0) + qDebug()<<"Warning:"<lines=ar.split('\n'); + QByteArray ar2; + int block=0; + for(const QByteArray&line:lines){ + if(line.left(10)=="-----BEGIN"){ + block++; + ar2.clear(); + qDebug()<<"Parsing PEM block"<0){ + qDebug()<<"Last PEM block unfinished, may result in parser errors."; + parseRawDER(QByteArray::fromBase64(ar2)); + } + }else{ //assume raw DER + qDebug()<<"Parsing File"<