You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

479 lines
16 KiB

  1. from __future__ import division
  2. import binascii
  3. import base64
  4. import warnings
  5. from itertools import chain
  6. from six import int2byte, text_type
  7. from ._compat import compat26_str, str_idx_as_int
  8. class UnexpectedDER(Exception):
  9. pass
  10. def encode_constructed(tag, value):
  11. return int2byte(0xA0 + tag) + encode_length(len(value)) + value
  12. def encode_implicit(tag, value, cls="context-specific"):
  13. """
  14. Encode and IMPLICIT value using :term:`DER`.
  15. :param int tag: the tag value to encode, must be between 0 an 31 inclusive
  16. :param bytes value: the data to encode
  17. :param str cls: the class of the tag to encode: "application",
  18. "context-specific", or "private"
  19. :rtype: bytes
  20. """
  21. if cls not in ("application", "context-specific", "private"):
  22. raise ValueError("invalid tag class")
  23. if tag > 31:
  24. raise ValueError("Long tags not supported")
  25. if cls == "application":
  26. tag_class = 0b01000000
  27. elif cls == "context-specific":
  28. tag_class = 0b10000000
  29. else:
  30. assert cls == "private"
  31. tag_class = 0b11000000
  32. return int2byte(tag_class + tag) + encode_length(len(value)) + value
  33. def encode_integer(r):
  34. assert r >= 0 # can't support negative numbers yet
  35. h = ("%x" % r).encode()
  36. if len(h) % 2:
  37. h = b"0" + h
  38. s = binascii.unhexlify(h)
  39. num = str_idx_as_int(s, 0)
  40. if num <= 0x7F:
  41. return b"\x02" + encode_length(len(s)) + s
  42. else:
  43. # DER integers are two's complement, so if the first byte is
  44. # 0x80-0xff then we need an extra 0x00 byte to prevent it from
  45. # looking negative.
  46. return b"\x02" + encode_length(len(s) + 1) + b"\x00" + s
  47. # sentry object to check if an argument was specified (used to detect
  48. # deprecated calling convention)
  49. _sentry = object()
  50. def encode_bitstring(s, unused=_sentry):
  51. """
  52. Encode a binary string as a BIT STRING using :term:`DER` encoding.
  53. Note, because there is no native Python object that can encode an actual
  54. bit string, this function only accepts byte strings as the `s` argument.
  55. The byte string is the actual bit string that will be encoded, padded
  56. on the right (least significant bits, looking from big endian perspective)
  57. to the first full byte. If the bit string has a bit length that is multiple
  58. of 8, then the padding should not be included. For correct DER encoding
  59. the padding bits MUST be set to 0.
  60. Number of bits of padding need to be provided as the `unused` parameter.
  61. In case they are specified as None, it means the number of unused bits
  62. is already encoded in the string as the first byte.
  63. The deprecated call convention specifies just the `s` parameters and
  64. encodes the number of unused bits as first parameter (same convention
  65. as with None).
  66. Empty string must be encoded with `unused` specified as 0.
  67. Future version of python-ecdsa will make specifying the `unused` argument
  68. mandatory.
  69. :param s: bytes to encode
  70. :type s: bytes like object
  71. :param unused: number of bits at the end of `s` that are unused, must be
  72. between 0 and 7 (inclusive)
  73. :type unused: int or None
  74. :raises ValueError: when `unused` is too large or too small
  75. :return: `s` encoded using DER
  76. :rtype: bytes
  77. """
  78. encoded_unused = b""
  79. len_extra = 0
  80. if unused is _sentry:
  81. warnings.warn(
  82. "Legacy call convention used, unused= needs to be specified",
  83. DeprecationWarning,
  84. )
  85. elif unused is not None:
  86. if not 0 <= unused <= 7:
  87. raise ValueError("unused must be integer between 0 and 7")
  88. if unused:
  89. if not s:
  90. raise ValueError("unused is non-zero but s is empty")
  91. last = str_idx_as_int(s, -1)
  92. if last & (2**unused - 1):
  93. raise ValueError("unused bits must be zeros in DER")
  94. encoded_unused = int2byte(unused)
  95. len_extra = 1
  96. return b"\x03" + encode_length(len(s) + len_extra) + encoded_unused + s
  97. def encode_octet_string(s):
  98. return b"\x04" + encode_length(len(s)) + s
  99. def encode_oid(first, second, *pieces):
  100. assert 0 <= first < 2 and 0 <= second <= 39 or first == 2 and 0 <= second
  101. body = b"".join(
  102. chain(
  103. [encode_number(40 * first + second)],
  104. (encode_number(p) for p in pieces),
  105. )
  106. )
  107. return b"\x06" + encode_length(len(body)) + body
  108. def encode_sequence(*encoded_pieces):
  109. total_len = sum([len(p) for p in encoded_pieces])
  110. return b"\x30" + encode_length(total_len) + b"".join(encoded_pieces)
  111. def encode_number(n):
  112. b128_digits = []
  113. while n:
  114. b128_digits.insert(0, (n & 0x7F) | 0x80)
  115. n = n >> 7
  116. if not b128_digits:
  117. b128_digits.append(0)
  118. b128_digits[-1] &= 0x7F
  119. return b"".join([int2byte(d) for d in b128_digits])
  120. def is_sequence(string):
  121. return string and string[:1] == b"\x30"
  122. def remove_constructed(string):
  123. s0 = str_idx_as_int(string, 0)
  124. if (s0 & 0xE0) != 0xA0:
  125. raise UnexpectedDER(
  126. "wanted type 'constructed tag' (0xa0-0xbf), got 0x%02x" % s0
  127. )
  128. tag = s0 & 0x1F
  129. length, llen = read_length(string[1:])
  130. body = string[1 + llen : 1 + llen + length]
  131. rest = string[1 + llen + length :]
  132. return tag, body, rest
  133. def remove_implicit(string, exp_class="context-specific"):
  134. """
  135. Removes an IMPLICIT tagged value from ``string`` following :term:`DER`.
  136. :param bytes string: a byte string that can have one or more
  137. DER elements.
  138. :param str exp_class: the expected tag class of the implicitly
  139. encoded value. Possible values are: "context-specific", "application",
  140. and "private".
  141. :return: a tuple with first value being the tag without indicator bits,
  142. second being the raw bytes of the value and the third one being
  143. remaining bytes (or an empty string if there are none)
  144. :rtype: tuple(int,bytes,bytes)
  145. """
  146. if exp_class not in ("context-specific", "application", "private"):
  147. raise ValueError("invalid `exp_class` value")
  148. if exp_class == "application":
  149. tag_class = 0b01000000
  150. elif exp_class == "context-specific":
  151. tag_class = 0b10000000
  152. else:
  153. assert exp_class == "private"
  154. tag_class = 0b11000000
  155. tag_mask = 0b11000000
  156. s0 = str_idx_as_int(string, 0)
  157. if (s0 & tag_mask) != tag_class:
  158. raise UnexpectedDER(
  159. "wanted class {0}, got 0x{1:02x} tag".format(exp_class, s0)
  160. )
  161. if s0 & 0b00100000 != 0:
  162. raise UnexpectedDER(
  163. "wanted type primitive, got 0x{0:02x} tag".format(s0)
  164. )
  165. tag = s0 & 0x1F
  166. length, llen = read_length(string[1:])
  167. body = string[1 + llen : 1 + llen + length]
  168. rest = string[1 + llen + length :]
  169. return tag, body, rest
  170. def remove_sequence(string):
  171. if not string:
  172. raise UnexpectedDER("Empty string does not encode a sequence")
  173. if string[:1] != b"\x30":
  174. n = str_idx_as_int(string, 0)
  175. raise UnexpectedDER("wanted type 'sequence' (0x30), got 0x%02x" % n)
  176. length, lengthlength = read_length(string[1:])
  177. if length > len(string) - 1 - lengthlength:
  178. raise UnexpectedDER("Length longer than the provided buffer")
  179. endseq = 1 + lengthlength + length
  180. return string[1 + lengthlength : endseq], string[endseq:]
  181. def remove_octet_string(string):
  182. if string[:1] != b"\x04":
  183. n = str_idx_as_int(string, 0)
  184. raise UnexpectedDER("wanted type 'octetstring' (0x04), got 0x%02x" % n)
  185. length, llen = read_length(string[1:])
  186. body = string[1 + llen : 1 + llen + length]
  187. rest = string[1 + llen + length :]
  188. return body, rest
  189. def remove_object(string):
  190. if not string:
  191. raise UnexpectedDER(
  192. "Empty string does not encode an object identifier"
  193. )
  194. if string[:1] != b"\x06":
  195. n = str_idx_as_int(string, 0)
  196. raise UnexpectedDER("wanted type 'object' (0x06), got 0x%02x" % n)
  197. length, lengthlength = read_length(string[1:])
  198. body = string[1 + lengthlength : 1 + lengthlength + length]
  199. rest = string[1 + lengthlength + length :]
  200. if not body:
  201. raise UnexpectedDER("Empty object identifier")
  202. if len(body) != length:
  203. raise UnexpectedDER(
  204. "Length of object identifier longer than the provided buffer"
  205. )
  206. numbers = []
  207. while body:
  208. n, ll = read_number(body)
  209. numbers.append(n)
  210. body = body[ll:]
  211. n0 = numbers.pop(0)
  212. if n0 < 80:
  213. first = n0 // 40
  214. else:
  215. first = 2
  216. second = n0 - (40 * first)
  217. numbers.insert(0, first)
  218. numbers.insert(1, second)
  219. return tuple(numbers), rest
  220. def remove_integer(string):
  221. if not string:
  222. raise UnexpectedDER(
  223. "Empty string is an invalid encoding of an integer"
  224. )
  225. if string[:1] != b"\x02":
  226. n = str_idx_as_int(string, 0)
  227. raise UnexpectedDER("wanted type 'integer' (0x02), got 0x%02x" % n)
  228. length, llen = read_length(string[1:])
  229. if length > len(string) - 1 - llen:
  230. raise UnexpectedDER("Length longer than provided buffer")
  231. if length == 0:
  232. raise UnexpectedDER("0-byte long encoding of integer")
  233. numberbytes = string[1 + llen : 1 + llen + length]
  234. rest = string[1 + llen + length :]
  235. msb = str_idx_as_int(numberbytes, 0)
  236. if not msb < 0x80:
  237. raise UnexpectedDER("Negative integers are not supported")
  238. # check if the encoding is the minimal one (DER requirement)
  239. if length > 1 and not msb:
  240. # leading zero byte is allowed if the integer would have been
  241. # considered a negative number otherwise
  242. smsb = str_idx_as_int(numberbytes, 1)
  243. if smsb < 0x80:
  244. raise UnexpectedDER(
  245. "Invalid encoding of integer, unnecessary "
  246. "zero padding bytes"
  247. )
  248. return int(binascii.hexlify(numberbytes), 16), rest
  249. def read_number(string):
  250. number = 0
  251. llen = 0
  252. if str_idx_as_int(string, 0) == 0x80:
  253. raise UnexpectedDER("Non minimal encoding of OID subidentifier")
  254. # base-128 big endian, with most significant bit set in all but the last
  255. # byte
  256. while True:
  257. if llen >= len(string):
  258. raise UnexpectedDER("ran out of length bytes")
  259. number = number << 7
  260. d = str_idx_as_int(string, llen)
  261. number += d & 0x7F
  262. llen += 1
  263. if not d & 0x80:
  264. break
  265. return number, llen
  266. def encode_length(l):
  267. assert l >= 0
  268. if l < 0x80:
  269. return int2byte(l)
  270. s = ("%x" % l).encode()
  271. if len(s) % 2:
  272. s = b"0" + s
  273. s = binascii.unhexlify(s)
  274. llen = len(s)
  275. return int2byte(0x80 | llen) + s
  276. def read_length(string):
  277. if not string:
  278. raise UnexpectedDER("Empty string can't encode valid length value")
  279. num = str_idx_as_int(string, 0)
  280. if not (num & 0x80):
  281. # short form
  282. return (num & 0x7F), 1
  283. # else long-form: b0&0x7f is number of additional base256 length bytes,
  284. # big-endian
  285. llen = num & 0x7F
  286. if not llen:
  287. raise UnexpectedDER("Invalid length encoding, length of length is 0")
  288. if llen > len(string) - 1:
  289. raise UnexpectedDER("Length of length longer than provided buffer")
  290. # verify that the encoding is minimal possible (DER requirement)
  291. msb = str_idx_as_int(string, 1)
  292. if not msb or llen == 1 and msb < 0x80:
  293. raise UnexpectedDER("Not minimal encoding of length")
  294. return int(binascii.hexlify(string[1 : 1 + llen]), 16), 1 + llen
  295. def remove_bitstring(string, expect_unused=_sentry):
  296. """
  297. Remove a BIT STRING object from `string` following :term:`DER`.
  298. The `expect_unused` can be used to specify if the bit string should
  299. have the amount of unused bits decoded or not. If it's an integer, any
  300. read BIT STRING that has number of unused bits different from specified
  301. value will cause UnexpectedDER exception to be raised (this is especially
  302. useful when decoding BIT STRINGS that have DER encoded object in them;
  303. DER encoding is byte oriented, so the unused bits will always equal 0).
  304. If the `expect_unused` is specified as None, the first element returned
  305. will be a tuple, with the first value being the extracted bit string
  306. while the second value will be the decoded number of unused bits.
  307. If the `expect_unused` is unspecified, the decoding of byte with
  308. number of unused bits will not be attempted and the bit string will be
  309. returned as-is, the callee will be required to decode it and verify its
  310. correctness.
  311. Future version of python will require the `expected_unused` parameter
  312. to be specified.
  313. :param string: string of bytes to extract the BIT STRING from
  314. :type string: bytes like object
  315. :param expect_unused: number of bits that should be unused in the BIT
  316. STRING, or None, to return it to caller
  317. :type expect_unused: int or None
  318. :raises UnexpectedDER: when the encoding does not follow DER.
  319. :return: a tuple with first element being the extracted bit string and
  320. the second being the remaining bytes in the string (if any); if the
  321. `expect_unused` is specified as None, the first element of the returned
  322. tuple will be a tuple itself, with first element being the bit string
  323. as bytes and the second element being the number of unused bits at the
  324. end of the byte array as an integer
  325. :rtype: tuple
  326. """
  327. if not string:
  328. raise UnexpectedDER("Empty string does not encode a bitstring")
  329. if expect_unused is _sentry:
  330. warnings.warn(
  331. "Legacy call convention used, expect_unused= needs to be"
  332. " specified",
  333. DeprecationWarning,
  334. )
  335. num = str_idx_as_int(string, 0)
  336. if string[:1] != b"\x03":
  337. raise UnexpectedDER("wanted bitstring (0x03), got 0x%02x" % num)
  338. length, llen = read_length(string[1:])
  339. if not length:
  340. raise UnexpectedDER("Invalid length of bit string, can't be 0")
  341. body = string[1 + llen : 1 + llen + length]
  342. rest = string[1 + llen + length :]
  343. if expect_unused is not _sentry:
  344. unused = str_idx_as_int(body, 0)
  345. if not 0 <= unused <= 7:
  346. raise UnexpectedDER("Invalid encoding of unused bits")
  347. if expect_unused is not None and expect_unused != unused:
  348. raise UnexpectedDER("Unexpected number of unused bits")
  349. body = body[1:]
  350. if unused:
  351. if not body:
  352. raise UnexpectedDER("Invalid encoding of empty bit string")
  353. last = str_idx_as_int(body, -1)
  354. # verify that all the unused bits are set to zero (DER requirement)
  355. if last & (2**unused - 1):
  356. raise UnexpectedDER("Non zero padding bits in bit string")
  357. if expect_unused is None:
  358. body = (body, unused)
  359. return body, rest
  360. # SEQUENCE([1, STRING(secexp), cont[0], OBJECT(curvename), cont[1], BINTSTRING)
  361. # signatures: (from RFC3279)
  362. # ansi-X9-62 OBJECT IDENTIFIER ::= {
  363. # iso(1) member-body(2) us(840) 10045 }
  364. #
  365. # id-ecSigType OBJECT IDENTIFIER ::= {
  366. # ansi-X9-62 signatures(4) }
  367. # ecdsa-with-SHA1 OBJECT IDENTIFIER ::= {
  368. # id-ecSigType 1 }
  369. # so 1,2,840,10045,4,1
  370. # so 0x42, .. ..
  371. # Ecdsa-Sig-Value ::= SEQUENCE {
  372. # r INTEGER,
  373. # s INTEGER }
  374. # id-public-key-type OBJECT IDENTIFIER ::= { ansi-X9.62 2 }
  375. #
  376. # id-ecPublicKey OBJECT IDENTIFIER ::= { id-publicKeyType 1 }
  377. # I think the secp224r1 identifier is (t=06,l=05,v=2b81040021)
  378. # secp224r1 OBJECT IDENTIFIER ::= {
  379. # iso(1) identified-organization(3) certicom(132) curve(0) 33 }
  380. # and the secp384r1 is (t=06,l=05,v=2b81040022)
  381. # secp384r1 OBJECT IDENTIFIER ::= {
  382. # iso(1) identified-organization(3) certicom(132) curve(0) 34 }
  383. def unpem(pem):
  384. if isinstance(pem, text_type): # pragma: no branch
  385. pem = pem.encode()
  386. d = b"".join(
  387. [
  388. l.strip()
  389. for l in pem.split(b"\n")
  390. if l and not l.startswith(b"-----")
  391. ]
  392. )
  393. return base64.b64decode(d)
  394. def topem(der, name):
  395. b64 = base64.b64encode(compat26_str(der))
  396. lines = [("-----BEGIN %s-----\n" % name).encode()]
  397. lines.extend(
  398. [b64[start : start + 76] + b"\n" for start in range(0, len(b64), 76)]
  399. )
  400. lines.append(("-----END %s-----\n" % name).encode())
  401. return b"".join(lines)