+class dererror(Exception):
+ pass
+
+class pemerror(Exception):
+ pass
+
+def pemdec(pem, ptypes):
+ if isinstance(ptypes, str):
+ ptypes = [ptypes]
+ p = 0
+ while True:
+ p = pem.find("-----BEGIN ", p)
+ if p < 0:
+ raise pemerror("could not find any %s in PEM-encoded data" % (ptypes,))
+ p2 = pem.find("-----", p + 11)
+ if p2 < 0:
+ raise pemerror("incomplete PEM header")
+ ptype = pem[p + 11 : p2]
+ if ptype not in ptypes:
+ p = p2 + 5
+ continue
+ p3 = pem.find("-----END " + ptype + "-----", p2 + 5)
+ if p3 < 0:
+ raise pemerror("incomplete PEM data")
+ pem = pem[p2 + 5 : p3]
+ return binascii.a2b_base64(pem)
+
+class derdecoder(object):
+ def __init__(self, data, offset=0, size=None):
+ self.data = data
+ self.offset = offset
+ self.size = len(data) if size is None else size
+
+ def end(self):
+ return self.offset >= self.size
+
+ def byte(self):
+ if self.offset >= self.size:
+ raise dererror("unexpected end-of-data")
+ ret = self.data[self.offset]
+ self.offset += 1
+ return ret
+
+ def splice(self, ln):
+ if self.offset + ln > self.size:
+ raise dererror("unexpected end-of-data")
+ ret = self.data[self.offset : self.offset + ln]
+ self.offset += ln
+ return ret
+
+ def dectag(self):
+ h = self.byte()
+ cl = (h & 0xc0) >> 6
+ cons = (h & 0x20) != 0
+ tag = h & 0x1f
+ if tag == 0x1f:
+ raise dererror("extended type tags not supported")
+ return cl, cons, tag
+
+ def declen(self):
+ h = self.byte()
+ if (h & 0x80) == 0:
+ return h
+ if h == 0x80:
+ raise dererror("indefinite lengths not supported in DER")
+ if h == 0xff:
+ raise dererror("invalid length byte")
+ n = h & 0x7f
+ ret = 0
+ for i in range(n):
+ ret = (ret << 8) + self.byte()
+ return ret
+
+ def get(self):
+ cl, cons, tag = self.dectag()
+ ln = self.declen()
+ return cons, cl, tag, self.splice(ln)
+
+ def getcons(self, ckcl, cktag):
+ cons, cl, tag, data = self.get()
+ if not cons:
+ raise dererror("expected constructed value")
+ if (ckcl != None and ckcl != cl) or (cktag != None and cktag != tag):
+ raise dererror("unexpected value tag: got (%d, %d), expected (%d, %d)" % (cl, tag, ckcl, cktag))
+ return derdecoder(data)
+
+ def getint(self):
+ cons, cl, tag, data = self.get()
+ if (cons, cl, tag) == (False, 0, 2):
+ ret = 0
+ for b in data:
+ ret = (ret << 8) + b
+ return ret
+ raise dererror("unexpected integer type: (%s, %d, %d)" % (cons, cl, tag))
+
+ def getstr(self):
+ cons, cl, tag, data = self.get()
+ if (cons, cl, tag) == (False, 0, 12):
+ return data.decode("utf-8")
+ if (cons, cl, tag) == (False, 0, 13):
+ return data.decode("us-ascii")
+ if (cons, cl, tag) == (False, 0, 22):
+ return data.decode("us-ascii")
+ if (cons, cl, tag) == (False, 0, 30):
+ return data.decode("utf-16-be")
+ raise dererror("unexpected string type: (%s, %d, %d)" % (cons, cl, tag))
+
+ def getbytes(self):
+ cons, cl, tag, data = self.get()
+ if (cons, cl, tag) == (False, 0, 4):
+ return data
+ raise dererror("unexpected byte-string type: (%s, %d, %d)" % (cons, cl, tag))
+
+ def getoid(self):
+ cons, cl, tag, data = self.get()
+ if (cons, cl, tag) == (False, 0, 6):
+ ret = []
+ ret.append(data[0] // 40)
+ ret.append(data[0] % 40)
+ p = 1
+ while p < len(data):
+ n = 0
+ v = data[p]
+ p += 1
+ while v & 0x80:
+ n = (n + (v & 0x7f)) * 128
+ v = data[p]
+ p += 1
+ n += v
+ ret.append(n)
+ return tuple(ret)
+ raise dererror("unexpected object-id type: (%s, %d, %d)" % (cons, cl, tag))
+
+ @staticmethod
+ def parsetime(data, c):
+ if c:
+ y = int(data[0:4])
+ data = data[4:]
+ else:
+ y = int(data[0:2])
+ y += 1900 if y > 50 else 2000
+ data = data[2:]
+ m = int(data[0:2])
+ d = int(data[2:4])
+ H = int(data[4:6])
+ data = data[6:]
+ if data[:1].isdigit():
+ M = int(data[0:2])
+ data = data[2:]
+ else:
+ M = 0
+ if data[:1].isdigit():
+ S = int(data[0:2])
+ data = data[2:]
+ else:
+ S = 0
+ if data[:1] == '.':
+ p = 1
+ while len(data) < p and data[p].isdigit():
+ p += 1
+ S += float("0." + data[1:p])
+ data = data[p:]
+ if len(data) < 1:
+ raise dererror("unspecified local time not supported for decoding")
+ if data[0] == 'Z':
+ tz = 0
+ elif data[0] == '+':
+ tz = (int(data[1:3]) * 60) + int(data[3:5])
+ elif data[0] == '-':
+ tz = -((int(data[1:3]) * 60) + int(data[3:5]))
+ else:
+ raise dererror("cannot parse X.690 timestamp")
+ return calendar.timegm((y, m, d, H, M, S)) - (tz * 60)
+
+ def gettime(self):
+ cons, cl, tag, data = self.get()
+ if (cons, cl, tag) == (False, 0, 23):
+ return self.parsetime(data.decode("us-ascii"), False)
+ if (cons, cl, tag) == (False, 0, 24):
+ return self.parsetime(data.decode("us-ascii"), True)
+ raise dererror("unexpected time type: (%s, %d, %d)" % (cons, cl, tag))
+
+ @classmethod
+ def frompem(cls, pem, ptypes):
+ return cls(pemdec(pem, ptypes))
+