--- /dev/null
+#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