simple basic DER parser, initial version master
authorKonrad Rosenbaum <konrad@silmor.de>
Mon, 10 Nov 2014 19:51:06 +0000 (20:51 +0100)
committerKonrad Rosenbaum <konrad@silmor.de>
Mon, 10 Nov 2014 19:51:06 +0000 (20:51 +0100)
.gitignore [new file with mode: 0644]
derparser.pro [new file with mode: 0644]
main.cpp [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..586ad18
--- /dev/null
@@ -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 (file)
index 0000000..7ac6cbd
--- /dev/null
@@ -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 (file)
index 0000000..14a6ea4
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,407 @@
+#include <QCoreApplication>
+#include <QString>
+#include <QByteArray>
+#include <QFile>
+#include <QDebug>
+#include <QTextCodec>
+
+#include <unistd.h>
+
+
+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"<<bytes<<"but only"<<(mend-mpos)<<"available.";
+                                sbytes=mend-mpos;
+                                mwarn=true;
+                        }
+                        QByteArray ret=mdata.mid(mpos,sbytes);
+                        if(sbytes!=bytes)
+                                ret.append(QByteArray(bytes-sbytes,(char)0));
+                        mpos+=sbytes;
+                        return ret;
+                }
+                
+                quint8 getByte()
+                {
+                        if(mpos>=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"<<bytes<<"but only"<<(mend-mpos)<<"available.";
+                                mpos=mend;
+                                mwarn=true;
+                        }else
+                                mpos+=bytes;
+                }
+                
+                DataRunner getSubRunner(int bytes)
+                {
+                        if(bytes<=0)return DataRunner(QByteArray());
+                        if(mabort){
+                                qDebug()<<"Warning: attempting to create sub-runner of aborted data stream!";
+                                return DataRunner(nullptr);
+                        }
+                        const int pos=mpos;
+                        if((mpos+bytes)>mend){
+                                qDebug()<<"Warning: requesting subrunner with more data then available! Requested"<<bytes<<"but only"<<(mend-mpos)<<"available.";
+                                bytes=mend-mpos;
+                                mwarn=true;
+                        }
+                        mpos+=bytes;
+                        return DataRunner(mdata,pos,bytes);
+                }
+                
+                int absolutePosition()const{return mpos;}
+                int relativePosition()const{return mpos-mstart;}
+                int start()const{return mstart;}
+                int end()const{return mend;}
+                int size()const{return mend-mstart;}
+                int available()const{return mend-mpos;}
+                bool overrun()const{return mwarn;}
+                bool aborted()const{return mabort;}
+                bool atEnd()const{return mpos>=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()<<getIndent(indent)<<"Type: "<<dataType(typecode,typed);
+        //get Length
+        qint32 length=dr.getByte();
+        if(length==0){
+                qDebug()<<getIndent(indent,">>")<<"No data.";
+                return isEOC(typed,typecode);
+        }
+        if(length==0x80){
+                qDebug()<<getIndent(indent,">>")<<"Indefinite Length Type detected";
+                if(!isComposite(typed)){
+                        qDebug()<<getIndent(indent,">>")<<"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<bytes;i++){
+                        if(length&0xff800000){
+                                qDebug()<<getIndent(indent,">>")<<"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()<<getIndent(indent,">>")<<"Internal Error: somehow wound up with negative length. Aborting.";
+                        dr.abort();
+                        return true;//fake EOC
+                }
+        }
+        qDebug()<<getIndent(indent,">>")<<"Length:"<<length<<"bytes";
+        //decode
+        DataRunner sdr=dr.getSubRunner(length);
+        if(isComposite(typed)){
+                while(!sdr.atEnd())
+                        if(parseTLV(sdr,indent+1)){
+                                qDebug()<<getIndent(indent,">>")<<"Warning: End-Of-Content found in non-Indefinite Composite."<<sdr.available()<<"bytes left. Attempting to continue.";
+                                //break;
+                        }
+        }else{
+                qDebug()<<getIndent(indent,">>")<<"Data: "<<decodeData(typed,typecode,sdr);
+        }
+        //done
+        return isEOC(typed,typecode);
+}
+
+void parseRawDER(QByteArray ar)
+{
+        qDebug()<<"DER raw block size is"<<ar.size();
+        //parse raw data
+        DataRunner dr(ar);
+        parseTLV(dr);
+        if(dr.available()>0)
+                qDebug()<<"Warning:"<<dr.available()<<"bytes left at end of file.";
+        else if(dr.overrun())
+                qDebug()<<"Warning: parser tried to read past file end.";
+}
+
+void parseFile(const QString&fname)
+{
+        //read file
+        QFile fd(fname);
+        if(fname=="-"){
+                if(!fd.open(STDIN_FILENO,QIODevice::ReadOnly)){
+                        qDebug()<<"Unable to open StdIn";
+                        return;
+                }
+        }else
+                if(!fd.open(QIODevice::ReadOnly)){
+                        qDebug()<<"Unable to open file"<<fname;
+                        return;
+                }
+        QByteArray ar=fd.readAll();
+        fd.close();
+        //check for PEM
+        if(ar.left(10)=="-----BEGIN"){
+                QList<QByteArray>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"<<block<<"from file"<<fname;
+                                continue;
+                        }
+                        if(line.left(8)=="-----END"){
+                                parseRawDER(QByteArray::fromBase64(ar2));
+                                ar2.clear();
+                                continue;
+                        }
+                        ar2.append(line);
+                }
+                if(ar2.size()>0){
+                        qDebug()<<"Last PEM block unfinished, may result in parser errors.";
+                        parseRawDER(QByteArray::fromBase64(ar2));
+                }
+        }else{ //assume raw DER
+                qDebug()<<"Parsing File"<<fname<<"as raw DER";
+                parseRawDER(ar);
+        }
+}
+
+
+int main(int ac,char**av)
+{
+        QCoreApplication app(ac,av);
+        
+        for(const QString&file:app.arguments().mid(1))
+                parseFile(file);
+        
+        return 0;
+}
\ No newline at end of file