ssh.py 52 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619
  1. # This file is dual licensed under the terms of the Apache License, Version
  2. # 2.0, and the BSD License. See the LICENSE file in the root of this repository
  3. # for complete details.
  4. from __future__ import annotations
  5. import binascii
  6. import enum
  7. import os
  8. import re
  9. import typing
  10. import warnings
  11. from base64 import encodebytes as _base64_encode
  12. from dataclasses import dataclass
  13. from cryptography import utils
  14. from cryptography.exceptions import UnsupportedAlgorithm
  15. from cryptography.hazmat.primitives import hashes
  16. from cryptography.hazmat.primitives.asymmetric import (
  17. dsa,
  18. ec,
  19. ed25519,
  20. padding,
  21. rsa,
  22. )
  23. from cryptography.hazmat.primitives.asymmetric import utils as asym_utils
  24. from cryptography.hazmat.primitives.ciphers import (
  25. AEADDecryptionContext,
  26. Cipher,
  27. algorithms,
  28. modes,
  29. )
  30. from cryptography.hazmat.primitives.serialization import (
  31. Encoding,
  32. KeySerializationEncryption,
  33. NoEncryption,
  34. PrivateFormat,
  35. PublicFormat,
  36. _KeySerializationEncryption,
  37. )
  38. try:
  39. from bcrypt import kdf as _bcrypt_kdf
  40. _bcrypt_supported = True
  41. except ImportError:
  42. _bcrypt_supported = False
  43. def _bcrypt_kdf(
  44. password: bytes,
  45. salt: bytes,
  46. desired_key_bytes: int,
  47. rounds: int,
  48. ignore_few_rounds: bool = False,
  49. ) -> bytes:
  50. raise UnsupportedAlgorithm("Need bcrypt module")
  51. _SSH_ED25519 = b"ssh-ed25519"
  52. _SSH_RSA = b"ssh-rsa"
  53. _SSH_DSA = b"ssh-dss"
  54. _ECDSA_NISTP256 = b"ecdsa-sha2-nistp256"
  55. _ECDSA_NISTP384 = b"ecdsa-sha2-nistp384"
  56. _ECDSA_NISTP521 = b"ecdsa-sha2-nistp521"
  57. _CERT_SUFFIX = b"-cert-v01@openssh.com"
  58. # U2F application string suffixed pubkey
  59. _SK_SSH_ED25519 = b"sk-ssh-ed25519@openssh.com"
  60. _SK_SSH_ECDSA_NISTP256 = b"sk-ecdsa-sha2-nistp256@openssh.com"
  61. # These are not key types, only algorithms, so they cannot appear
  62. # as a public key type
  63. _SSH_RSA_SHA256 = b"rsa-sha2-256"
  64. _SSH_RSA_SHA512 = b"rsa-sha2-512"
  65. _SSH_PUBKEY_RC = re.compile(rb"\A(\S+)[ \t]+(\S+)")
  66. _SK_MAGIC = b"openssh-key-v1\0"
  67. _SK_START = b"-----BEGIN OPENSSH PRIVATE KEY-----"
  68. _SK_END = b"-----END OPENSSH PRIVATE KEY-----"
  69. _BCRYPT = b"bcrypt"
  70. _NONE = b"none"
  71. _DEFAULT_CIPHER = b"aes256-ctr"
  72. _DEFAULT_ROUNDS = 16
  73. # re is only way to work on bytes-like data
  74. _PEM_RC = re.compile(_SK_START + b"(.*?)" + _SK_END, re.DOTALL)
  75. # padding for max blocksize
  76. _PADDING = memoryview(bytearray(range(1, 1 + 16)))
  77. @dataclass
  78. class _SSHCipher:
  79. alg: type[algorithms.AES]
  80. key_len: int
  81. mode: type[modes.CTR] | type[modes.CBC] | type[modes.GCM]
  82. block_len: int
  83. iv_len: int
  84. tag_len: int | None
  85. is_aead: bool
  86. # ciphers that are actually used in key wrapping
  87. _SSH_CIPHERS: dict[bytes, _SSHCipher] = {
  88. b"aes256-ctr": _SSHCipher(
  89. alg=algorithms.AES,
  90. key_len=32,
  91. mode=modes.CTR,
  92. block_len=16,
  93. iv_len=16,
  94. tag_len=None,
  95. is_aead=False,
  96. ),
  97. b"aes256-cbc": _SSHCipher(
  98. alg=algorithms.AES,
  99. key_len=32,
  100. mode=modes.CBC,
  101. block_len=16,
  102. iv_len=16,
  103. tag_len=None,
  104. is_aead=False,
  105. ),
  106. b"aes256-gcm@openssh.com": _SSHCipher(
  107. alg=algorithms.AES,
  108. key_len=32,
  109. mode=modes.GCM,
  110. block_len=16,
  111. iv_len=12,
  112. tag_len=16,
  113. is_aead=True,
  114. ),
  115. }
  116. # map local curve name to key type
  117. _ECDSA_KEY_TYPE = {
  118. "secp256r1": _ECDSA_NISTP256,
  119. "secp384r1": _ECDSA_NISTP384,
  120. "secp521r1": _ECDSA_NISTP521,
  121. }
  122. def _get_ssh_key_type(key: SSHPrivateKeyTypes | SSHPublicKeyTypes) -> bytes:
  123. if isinstance(key, ec.EllipticCurvePrivateKey):
  124. key_type = _ecdsa_key_type(key.public_key())
  125. elif isinstance(key, ec.EllipticCurvePublicKey):
  126. key_type = _ecdsa_key_type(key)
  127. elif isinstance(key, (rsa.RSAPrivateKey, rsa.RSAPublicKey)):
  128. key_type = _SSH_RSA
  129. elif isinstance(key, (dsa.DSAPrivateKey, dsa.DSAPublicKey)):
  130. key_type = _SSH_DSA
  131. elif isinstance(
  132. key, (ed25519.Ed25519PrivateKey, ed25519.Ed25519PublicKey)
  133. ):
  134. key_type = _SSH_ED25519
  135. else:
  136. raise ValueError("Unsupported key type")
  137. return key_type
  138. def _ecdsa_key_type(public_key: ec.EllipticCurvePublicKey) -> bytes:
  139. """Return SSH key_type and curve_name for private key."""
  140. curve = public_key.curve
  141. if curve.name not in _ECDSA_KEY_TYPE:
  142. raise ValueError(
  143. f"Unsupported curve for ssh private key: {curve.name!r}"
  144. )
  145. return _ECDSA_KEY_TYPE[curve.name]
  146. def _ssh_pem_encode(
  147. data: utils.Buffer,
  148. prefix: bytes = _SK_START + b"\n",
  149. suffix: bytes = _SK_END + b"\n",
  150. ) -> bytes:
  151. return b"".join([prefix, _base64_encode(data), suffix])
  152. def _check_block_size(data: utils.Buffer, block_len: int) -> None:
  153. """Require data to be full blocks"""
  154. if not data or len(data) % block_len != 0:
  155. raise ValueError("Corrupt data: missing padding")
  156. def _check_empty(data: utils.Buffer) -> None:
  157. """All data should have been parsed."""
  158. if data:
  159. raise ValueError("Corrupt data: unparsed data")
  160. def _init_cipher(
  161. ciphername: bytes,
  162. password: bytes | None,
  163. salt: bytes,
  164. rounds: int,
  165. ) -> Cipher[modes.CBC | modes.CTR | modes.GCM]:
  166. """Generate key + iv and return cipher."""
  167. if not password:
  168. raise TypeError(
  169. "Key is password-protected, but password was not provided."
  170. )
  171. ciph = _SSH_CIPHERS[ciphername]
  172. seed = _bcrypt_kdf(
  173. password, salt, ciph.key_len + ciph.iv_len, rounds, True
  174. )
  175. return Cipher(
  176. ciph.alg(seed[: ciph.key_len]),
  177. ciph.mode(seed[ciph.key_len :]),
  178. )
  179. def _get_u32(data: memoryview) -> tuple[int, memoryview]:
  180. """Uint32"""
  181. if len(data) < 4:
  182. raise ValueError("Invalid data")
  183. return int.from_bytes(data[:4], byteorder="big"), data[4:]
  184. def _get_u64(data: memoryview) -> tuple[int, memoryview]:
  185. """Uint64"""
  186. if len(data) < 8:
  187. raise ValueError("Invalid data")
  188. return int.from_bytes(data[:8], byteorder="big"), data[8:]
  189. def _get_sshstr(data: memoryview) -> tuple[memoryview, memoryview]:
  190. """Bytes with u32 length prefix"""
  191. n, data = _get_u32(data)
  192. if n > len(data):
  193. raise ValueError("Invalid data")
  194. return data[:n], data[n:]
  195. def _get_mpint(data: memoryview) -> tuple[int, memoryview]:
  196. """Big integer."""
  197. val, data = _get_sshstr(data)
  198. if val and val[0] > 0x7F:
  199. raise ValueError("Invalid data")
  200. return int.from_bytes(val, "big"), data
  201. def _to_mpint(val: int) -> bytes:
  202. """Storage format for signed bigint."""
  203. if val < 0:
  204. raise ValueError("negative mpint not allowed")
  205. if not val:
  206. return b""
  207. nbytes = (val.bit_length() + 8) // 8
  208. return utils.int_to_bytes(val, nbytes)
  209. class _FragList:
  210. """Build recursive structure without data copy."""
  211. flist: list[utils.Buffer]
  212. def __init__(self, init: list[utils.Buffer] | None = None) -> None:
  213. self.flist = []
  214. if init:
  215. self.flist.extend(init)
  216. def put_raw(self, val: utils.Buffer) -> None:
  217. """Add plain bytes"""
  218. self.flist.append(val)
  219. def put_u32(self, val: int) -> None:
  220. """Big-endian uint32"""
  221. self.flist.append(val.to_bytes(length=4, byteorder="big"))
  222. def put_u64(self, val: int) -> None:
  223. """Big-endian uint64"""
  224. self.flist.append(val.to_bytes(length=8, byteorder="big"))
  225. def put_sshstr(self, val: bytes | _FragList) -> None:
  226. """Bytes prefixed with u32 length"""
  227. if isinstance(val, (bytes, memoryview, bytearray)):
  228. self.put_u32(len(val))
  229. self.flist.append(val)
  230. else:
  231. self.put_u32(val.size())
  232. self.flist.extend(val.flist)
  233. def put_mpint(self, val: int) -> None:
  234. """Big-endian bigint prefixed with u32 length"""
  235. self.put_sshstr(_to_mpint(val))
  236. def size(self) -> int:
  237. """Current number of bytes"""
  238. return sum(map(len, self.flist))
  239. def render(self, dstbuf: memoryview, pos: int = 0) -> int:
  240. """Write into bytearray"""
  241. for frag in self.flist:
  242. flen = len(frag)
  243. start, pos = pos, pos + flen
  244. dstbuf[start:pos] = frag
  245. return pos
  246. def tobytes(self) -> bytes:
  247. """Return as bytes"""
  248. buf = memoryview(bytearray(self.size()))
  249. self.render(buf)
  250. return buf.tobytes()
  251. class _SSHFormatRSA:
  252. """Format for RSA keys.
  253. Public:
  254. mpint e, n
  255. Private:
  256. mpint n, e, d, iqmp, p, q
  257. """
  258. def get_public(
  259. self, data: memoryview
  260. ) -> tuple[tuple[int, int], memoryview]:
  261. """RSA public fields"""
  262. e, data = _get_mpint(data)
  263. n, data = _get_mpint(data)
  264. return (e, n), data
  265. def load_public(
  266. self, data: memoryview
  267. ) -> tuple[rsa.RSAPublicKey, memoryview]:
  268. """Make RSA public key from data."""
  269. (e, n), data = self.get_public(data)
  270. public_numbers = rsa.RSAPublicNumbers(e, n)
  271. public_key = public_numbers.public_key()
  272. return public_key, data
  273. def load_private(
  274. self, data: memoryview, pubfields, unsafe_skip_rsa_key_validation: bool
  275. ) -> tuple[rsa.RSAPrivateKey, memoryview]:
  276. """Make RSA private key from data."""
  277. n, data = _get_mpint(data)
  278. e, data = _get_mpint(data)
  279. d, data = _get_mpint(data)
  280. iqmp, data = _get_mpint(data)
  281. p, data = _get_mpint(data)
  282. q, data = _get_mpint(data)
  283. if (e, n) != pubfields:
  284. raise ValueError("Corrupt data: rsa field mismatch")
  285. dmp1 = rsa.rsa_crt_dmp1(d, p)
  286. dmq1 = rsa.rsa_crt_dmq1(d, q)
  287. public_numbers = rsa.RSAPublicNumbers(e, n)
  288. private_numbers = rsa.RSAPrivateNumbers(
  289. p, q, d, dmp1, dmq1, iqmp, public_numbers
  290. )
  291. private_key = private_numbers.private_key(
  292. unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation
  293. )
  294. return private_key, data
  295. def encode_public(
  296. self, public_key: rsa.RSAPublicKey, f_pub: _FragList
  297. ) -> None:
  298. """Write RSA public key"""
  299. pubn = public_key.public_numbers()
  300. f_pub.put_mpint(pubn.e)
  301. f_pub.put_mpint(pubn.n)
  302. def encode_private(
  303. self, private_key: rsa.RSAPrivateKey, f_priv: _FragList
  304. ) -> None:
  305. """Write RSA private key"""
  306. private_numbers = private_key.private_numbers()
  307. public_numbers = private_numbers.public_numbers
  308. f_priv.put_mpint(public_numbers.n)
  309. f_priv.put_mpint(public_numbers.e)
  310. f_priv.put_mpint(private_numbers.d)
  311. f_priv.put_mpint(private_numbers.iqmp)
  312. f_priv.put_mpint(private_numbers.p)
  313. f_priv.put_mpint(private_numbers.q)
  314. class _SSHFormatDSA:
  315. """Format for DSA keys.
  316. Public:
  317. mpint p, q, g, y
  318. Private:
  319. mpint p, q, g, y, x
  320. """
  321. def get_public(self, data: memoryview) -> tuple[tuple, memoryview]:
  322. """DSA public fields"""
  323. p, data = _get_mpint(data)
  324. q, data = _get_mpint(data)
  325. g, data = _get_mpint(data)
  326. y, data = _get_mpint(data)
  327. return (p, q, g, y), data
  328. def load_public(
  329. self, data: memoryview
  330. ) -> tuple[dsa.DSAPublicKey, memoryview]:
  331. """Make DSA public key from data."""
  332. (p, q, g, y), data = self.get_public(data)
  333. parameter_numbers = dsa.DSAParameterNumbers(p, q, g)
  334. public_numbers = dsa.DSAPublicNumbers(y, parameter_numbers)
  335. self._validate(public_numbers)
  336. public_key = public_numbers.public_key()
  337. return public_key, data
  338. def load_private(
  339. self, data: memoryview, pubfields, unsafe_skip_rsa_key_validation: bool
  340. ) -> tuple[dsa.DSAPrivateKey, memoryview]:
  341. """Make DSA private key from data."""
  342. (p, q, g, y), data = self.get_public(data)
  343. x, data = _get_mpint(data)
  344. if (p, q, g, y) != pubfields:
  345. raise ValueError("Corrupt data: dsa field mismatch")
  346. parameter_numbers = dsa.DSAParameterNumbers(p, q, g)
  347. public_numbers = dsa.DSAPublicNumbers(y, parameter_numbers)
  348. self._validate(public_numbers)
  349. private_numbers = dsa.DSAPrivateNumbers(x, public_numbers)
  350. private_key = private_numbers.private_key()
  351. return private_key, data
  352. def encode_public(
  353. self, public_key: dsa.DSAPublicKey, f_pub: _FragList
  354. ) -> None:
  355. """Write DSA public key"""
  356. public_numbers = public_key.public_numbers()
  357. parameter_numbers = public_numbers.parameter_numbers
  358. self._validate(public_numbers)
  359. f_pub.put_mpint(parameter_numbers.p)
  360. f_pub.put_mpint(parameter_numbers.q)
  361. f_pub.put_mpint(parameter_numbers.g)
  362. f_pub.put_mpint(public_numbers.y)
  363. def encode_private(
  364. self, private_key: dsa.DSAPrivateKey, f_priv: _FragList
  365. ) -> None:
  366. """Write DSA private key"""
  367. self.encode_public(private_key.public_key(), f_priv)
  368. f_priv.put_mpint(private_key.private_numbers().x)
  369. def _validate(self, public_numbers: dsa.DSAPublicNumbers) -> None:
  370. parameter_numbers = public_numbers.parameter_numbers
  371. if parameter_numbers.p.bit_length() != 1024:
  372. raise ValueError("SSH supports only 1024 bit DSA keys")
  373. class _SSHFormatECDSA:
  374. """Format for ECDSA keys.
  375. Public:
  376. str curve
  377. bytes point
  378. Private:
  379. str curve
  380. bytes point
  381. mpint secret
  382. """
  383. def __init__(self, ssh_curve_name: bytes, curve: ec.EllipticCurve):
  384. self.ssh_curve_name = ssh_curve_name
  385. self.curve = curve
  386. def get_public(
  387. self, data: memoryview
  388. ) -> tuple[tuple[memoryview, memoryview], memoryview]:
  389. """ECDSA public fields"""
  390. curve, data = _get_sshstr(data)
  391. point, data = _get_sshstr(data)
  392. if curve != self.ssh_curve_name:
  393. raise ValueError("Curve name mismatch")
  394. if point[0] != 4:
  395. raise NotImplementedError("Need uncompressed point")
  396. return (curve, point), data
  397. def load_public(
  398. self, data: memoryview
  399. ) -> tuple[ec.EllipticCurvePublicKey, memoryview]:
  400. """Make ECDSA public key from data."""
  401. (_, point), data = self.get_public(data)
  402. public_key = ec.EllipticCurvePublicKey.from_encoded_point(
  403. self.curve, point.tobytes()
  404. )
  405. return public_key, data
  406. def load_private(
  407. self, data: memoryview, pubfields, unsafe_skip_rsa_key_validation: bool
  408. ) -> tuple[ec.EllipticCurvePrivateKey, memoryview]:
  409. """Make ECDSA private key from data."""
  410. (curve_name, point), data = self.get_public(data)
  411. secret, data = _get_mpint(data)
  412. if (curve_name, point) != pubfields:
  413. raise ValueError("Corrupt data: ecdsa field mismatch")
  414. private_key = ec.derive_private_key(secret, self.curve)
  415. return private_key, data
  416. def encode_public(
  417. self, public_key: ec.EllipticCurvePublicKey, f_pub: _FragList
  418. ) -> None:
  419. """Write ECDSA public key"""
  420. point = public_key.public_bytes(
  421. Encoding.X962, PublicFormat.UncompressedPoint
  422. )
  423. f_pub.put_sshstr(self.ssh_curve_name)
  424. f_pub.put_sshstr(point)
  425. def encode_private(
  426. self, private_key: ec.EllipticCurvePrivateKey, f_priv: _FragList
  427. ) -> None:
  428. """Write ECDSA private key"""
  429. public_key = private_key.public_key()
  430. private_numbers = private_key.private_numbers()
  431. self.encode_public(public_key, f_priv)
  432. f_priv.put_mpint(private_numbers.private_value)
  433. class _SSHFormatEd25519:
  434. """Format for Ed25519 keys.
  435. Public:
  436. bytes point
  437. Private:
  438. bytes point
  439. bytes secret_and_point
  440. """
  441. def get_public(
  442. self, data: memoryview
  443. ) -> tuple[tuple[memoryview], memoryview]:
  444. """Ed25519 public fields"""
  445. point, data = _get_sshstr(data)
  446. return (point,), data
  447. def load_public(
  448. self, data: memoryview
  449. ) -> tuple[ed25519.Ed25519PublicKey, memoryview]:
  450. """Make Ed25519 public key from data."""
  451. (point,), data = self.get_public(data)
  452. public_key = ed25519.Ed25519PublicKey.from_public_bytes(
  453. point.tobytes()
  454. )
  455. return public_key, data
  456. def load_private(
  457. self, data: memoryview, pubfields, unsafe_skip_rsa_key_validation: bool
  458. ) -> tuple[ed25519.Ed25519PrivateKey, memoryview]:
  459. """Make Ed25519 private key from data."""
  460. (point,), data = self.get_public(data)
  461. keypair, data = _get_sshstr(data)
  462. secret = keypair[:32]
  463. point2 = keypair[32:]
  464. if point != point2 or (point,) != pubfields:
  465. raise ValueError("Corrupt data: ed25519 field mismatch")
  466. private_key = ed25519.Ed25519PrivateKey.from_private_bytes(secret)
  467. return private_key, data
  468. def encode_public(
  469. self, public_key: ed25519.Ed25519PublicKey, f_pub: _FragList
  470. ) -> None:
  471. """Write Ed25519 public key"""
  472. raw_public_key = public_key.public_bytes(
  473. Encoding.Raw, PublicFormat.Raw
  474. )
  475. f_pub.put_sshstr(raw_public_key)
  476. def encode_private(
  477. self, private_key: ed25519.Ed25519PrivateKey, f_priv: _FragList
  478. ) -> None:
  479. """Write Ed25519 private key"""
  480. public_key = private_key.public_key()
  481. raw_private_key = private_key.private_bytes(
  482. Encoding.Raw, PrivateFormat.Raw, NoEncryption()
  483. )
  484. raw_public_key = public_key.public_bytes(
  485. Encoding.Raw, PublicFormat.Raw
  486. )
  487. f_keypair = _FragList([raw_private_key, raw_public_key])
  488. self.encode_public(public_key, f_priv)
  489. f_priv.put_sshstr(f_keypair)
  490. def load_application(data) -> tuple[memoryview, memoryview]:
  491. """
  492. U2F application strings
  493. """
  494. application, data = _get_sshstr(data)
  495. if not application.tobytes().startswith(b"ssh:"):
  496. raise ValueError(
  497. "U2F application string does not start with b'ssh:' "
  498. f"({application})"
  499. )
  500. return application, data
  501. class _SSHFormatSKEd25519:
  502. """
  503. The format of a sk-ssh-ed25519@openssh.com public key is:
  504. string "sk-ssh-ed25519@openssh.com"
  505. string public key
  506. string application (user-specified, but typically "ssh:")
  507. """
  508. def load_public(
  509. self, data: memoryview
  510. ) -> tuple[ed25519.Ed25519PublicKey, memoryview]:
  511. """Make Ed25519 public key from data."""
  512. public_key, data = _lookup_kformat(_SSH_ED25519).load_public(data)
  513. _, data = load_application(data)
  514. return public_key, data
  515. def get_public(self, data: memoryview) -> typing.NoReturn:
  516. # Confusingly `get_public` is an entry point used by private key
  517. # loading.
  518. raise UnsupportedAlgorithm(
  519. "sk-ssh-ed25519 private keys cannot be loaded"
  520. )
  521. class _SSHFormatSKECDSA:
  522. """
  523. The format of a sk-ecdsa-sha2-nistp256@openssh.com public key is:
  524. string "sk-ecdsa-sha2-nistp256@openssh.com"
  525. string curve name
  526. ec_point Q
  527. string application (user-specified, but typically "ssh:")
  528. """
  529. def load_public(
  530. self, data: memoryview
  531. ) -> tuple[ec.EllipticCurvePublicKey, memoryview]:
  532. """Make ECDSA public key from data."""
  533. public_key, data = _lookup_kformat(_ECDSA_NISTP256).load_public(data)
  534. _, data = load_application(data)
  535. return public_key, data
  536. def get_public(self, data: memoryview) -> typing.NoReturn:
  537. # Confusingly `get_public` is an entry point used by private key
  538. # loading.
  539. raise UnsupportedAlgorithm(
  540. "sk-ecdsa-sha2-nistp256 private keys cannot be loaded"
  541. )
  542. _KEY_FORMATS = {
  543. _SSH_RSA: _SSHFormatRSA(),
  544. _SSH_DSA: _SSHFormatDSA(),
  545. _SSH_ED25519: _SSHFormatEd25519(),
  546. _ECDSA_NISTP256: _SSHFormatECDSA(b"nistp256", ec.SECP256R1()),
  547. _ECDSA_NISTP384: _SSHFormatECDSA(b"nistp384", ec.SECP384R1()),
  548. _ECDSA_NISTP521: _SSHFormatECDSA(b"nistp521", ec.SECP521R1()),
  549. _SK_SSH_ED25519: _SSHFormatSKEd25519(),
  550. _SK_SSH_ECDSA_NISTP256: _SSHFormatSKECDSA(),
  551. }
  552. def _lookup_kformat(key_type: utils.Buffer):
  553. """Return valid format or throw error"""
  554. if not isinstance(key_type, bytes):
  555. key_type = memoryview(key_type).tobytes()
  556. if key_type in _KEY_FORMATS:
  557. return _KEY_FORMATS[key_type]
  558. raise UnsupportedAlgorithm(f"Unsupported key type: {key_type!r}")
  559. SSHPrivateKeyTypes = typing.Union[
  560. ec.EllipticCurvePrivateKey,
  561. rsa.RSAPrivateKey,
  562. dsa.DSAPrivateKey,
  563. ed25519.Ed25519PrivateKey,
  564. ]
  565. def load_ssh_private_key(
  566. data: utils.Buffer,
  567. password: bytes | None,
  568. backend: typing.Any = None,
  569. *,
  570. unsafe_skip_rsa_key_validation: bool = False,
  571. ) -> SSHPrivateKeyTypes:
  572. """Load private key from OpenSSH custom encoding."""
  573. utils._check_byteslike("data", data)
  574. if password is not None:
  575. utils._check_bytes("password", password)
  576. m = _PEM_RC.search(data)
  577. if not m:
  578. raise ValueError("Not OpenSSH private key format")
  579. p1 = m.start(1)
  580. p2 = m.end(1)
  581. data = binascii.a2b_base64(memoryview(data)[p1:p2])
  582. if not data.startswith(_SK_MAGIC):
  583. raise ValueError("Not OpenSSH private key format")
  584. data = memoryview(data)[len(_SK_MAGIC) :]
  585. # parse header
  586. ciphername, data = _get_sshstr(data)
  587. kdfname, data = _get_sshstr(data)
  588. kdfoptions, data = _get_sshstr(data)
  589. nkeys, data = _get_u32(data)
  590. if nkeys != 1:
  591. raise ValueError("Only one key supported")
  592. # load public key data
  593. pubdata, data = _get_sshstr(data)
  594. pub_key_type, pubdata = _get_sshstr(pubdata)
  595. kformat = _lookup_kformat(pub_key_type)
  596. pubfields, pubdata = kformat.get_public(pubdata)
  597. _check_empty(pubdata)
  598. if ciphername != _NONE or kdfname != _NONE:
  599. ciphername_bytes = ciphername.tobytes()
  600. if ciphername_bytes not in _SSH_CIPHERS:
  601. raise UnsupportedAlgorithm(
  602. f"Unsupported cipher: {ciphername_bytes!r}"
  603. )
  604. if kdfname != _BCRYPT:
  605. raise UnsupportedAlgorithm(f"Unsupported KDF: {kdfname!r}")
  606. blklen = _SSH_CIPHERS[ciphername_bytes].block_len
  607. tag_len = _SSH_CIPHERS[ciphername_bytes].tag_len
  608. # load secret data
  609. edata, data = _get_sshstr(data)
  610. # see https://bugzilla.mindrot.org/show_bug.cgi?id=3553 for
  611. # information about how OpenSSH handles AEAD tags
  612. if _SSH_CIPHERS[ciphername_bytes].is_aead:
  613. tag = bytes(data)
  614. if len(tag) != tag_len:
  615. raise ValueError("Corrupt data: invalid tag length for cipher")
  616. else:
  617. _check_empty(data)
  618. _check_block_size(edata, blklen)
  619. salt, kbuf = _get_sshstr(kdfoptions)
  620. rounds, kbuf = _get_u32(kbuf)
  621. _check_empty(kbuf)
  622. ciph = _init_cipher(ciphername_bytes, password, salt.tobytes(), rounds)
  623. dec = ciph.decryptor()
  624. edata = memoryview(dec.update(edata))
  625. if _SSH_CIPHERS[ciphername_bytes].is_aead:
  626. assert isinstance(dec, AEADDecryptionContext)
  627. _check_empty(dec.finalize_with_tag(tag))
  628. else:
  629. # _check_block_size requires data to be a full block so there
  630. # should be no output from finalize
  631. _check_empty(dec.finalize())
  632. else:
  633. if password:
  634. raise TypeError(
  635. "Password was given but private key is not encrypted."
  636. )
  637. # load secret data
  638. edata, data = _get_sshstr(data)
  639. _check_empty(data)
  640. blklen = 8
  641. _check_block_size(edata, blklen)
  642. ck1, edata = _get_u32(edata)
  643. ck2, edata = _get_u32(edata)
  644. if ck1 != ck2:
  645. raise ValueError("Corrupt data: broken checksum")
  646. # load per-key struct
  647. key_type, edata = _get_sshstr(edata)
  648. if key_type != pub_key_type:
  649. raise ValueError("Corrupt data: key type mismatch")
  650. private_key, edata = kformat.load_private(
  651. edata,
  652. pubfields,
  653. unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation,
  654. )
  655. # We don't use the comment
  656. _, edata = _get_sshstr(edata)
  657. # yes, SSH does padding check *after* all other parsing is done.
  658. # need to follow as it writes zero-byte padding too.
  659. if edata != _PADDING[: len(edata)]:
  660. raise ValueError("Corrupt data: invalid padding")
  661. if isinstance(private_key, dsa.DSAPrivateKey):
  662. warnings.warn(
  663. "SSH DSA keys are deprecated and will be removed in a future "
  664. "release.",
  665. utils.DeprecatedIn40,
  666. stacklevel=2,
  667. )
  668. return private_key
  669. def _serialize_ssh_private_key(
  670. private_key: SSHPrivateKeyTypes,
  671. password: bytes,
  672. encryption_algorithm: KeySerializationEncryption,
  673. ) -> bytes:
  674. """Serialize private key with OpenSSH custom encoding."""
  675. utils._check_bytes("password", password)
  676. if isinstance(private_key, dsa.DSAPrivateKey):
  677. warnings.warn(
  678. "SSH DSA key support is deprecated and will be "
  679. "removed in a future release",
  680. utils.DeprecatedIn40,
  681. stacklevel=4,
  682. )
  683. key_type = _get_ssh_key_type(private_key)
  684. kformat = _lookup_kformat(key_type)
  685. # setup parameters
  686. f_kdfoptions = _FragList()
  687. if password:
  688. ciphername = _DEFAULT_CIPHER
  689. blklen = _SSH_CIPHERS[ciphername].block_len
  690. kdfname = _BCRYPT
  691. rounds = _DEFAULT_ROUNDS
  692. if (
  693. isinstance(encryption_algorithm, _KeySerializationEncryption)
  694. and encryption_algorithm._kdf_rounds is not None
  695. ):
  696. rounds = encryption_algorithm._kdf_rounds
  697. salt = os.urandom(16)
  698. f_kdfoptions.put_sshstr(salt)
  699. f_kdfoptions.put_u32(rounds)
  700. ciph = _init_cipher(ciphername, password, salt, rounds)
  701. else:
  702. ciphername = kdfname = _NONE
  703. blklen = 8
  704. ciph = None
  705. nkeys = 1
  706. checkval = os.urandom(4)
  707. comment = b""
  708. # encode public and private parts together
  709. f_public_key = _FragList()
  710. f_public_key.put_sshstr(key_type)
  711. kformat.encode_public(private_key.public_key(), f_public_key)
  712. f_secrets = _FragList([checkval, checkval])
  713. f_secrets.put_sshstr(key_type)
  714. kformat.encode_private(private_key, f_secrets)
  715. f_secrets.put_sshstr(comment)
  716. f_secrets.put_raw(_PADDING[: blklen - (f_secrets.size() % blklen)])
  717. # top-level structure
  718. f_main = _FragList()
  719. f_main.put_raw(_SK_MAGIC)
  720. f_main.put_sshstr(ciphername)
  721. f_main.put_sshstr(kdfname)
  722. f_main.put_sshstr(f_kdfoptions)
  723. f_main.put_u32(nkeys)
  724. f_main.put_sshstr(f_public_key)
  725. f_main.put_sshstr(f_secrets)
  726. # copy result info bytearray
  727. slen = f_secrets.size()
  728. mlen = f_main.size()
  729. buf = memoryview(bytearray(mlen + blklen))
  730. f_main.render(buf)
  731. ofs = mlen - slen
  732. # encrypt in-place
  733. if ciph is not None:
  734. ciph.encryptor().update_into(buf[ofs:mlen], buf[ofs:])
  735. return _ssh_pem_encode(buf[:mlen])
  736. SSHPublicKeyTypes = typing.Union[
  737. ec.EllipticCurvePublicKey,
  738. rsa.RSAPublicKey,
  739. dsa.DSAPublicKey,
  740. ed25519.Ed25519PublicKey,
  741. ]
  742. SSHCertPublicKeyTypes = typing.Union[
  743. ec.EllipticCurvePublicKey,
  744. rsa.RSAPublicKey,
  745. ed25519.Ed25519PublicKey,
  746. ]
  747. class SSHCertificateType(enum.Enum):
  748. USER = 1
  749. HOST = 2
  750. class SSHCertificate:
  751. def __init__(
  752. self,
  753. _nonce: memoryview,
  754. _public_key: SSHPublicKeyTypes,
  755. _serial: int,
  756. _cctype: int,
  757. _key_id: memoryview,
  758. _valid_principals: list[bytes],
  759. _valid_after: int,
  760. _valid_before: int,
  761. _critical_options: dict[bytes, bytes],
  762. _extensions: dict[bytes, bytes],
  763. _sig_type: memoryview,
  764. _sig_key: memoryview,
  765. _inner_sig_type: memoryview,
  766. _signature: memoryview,
  767. _tbs_cert_body: memoryview,
  768. _cert_key_type: bytes,
  769. _cert_body: memoryview,
  770. ):
  771. self._nonce = _nonce
  772. self._public_key = _public_key
  773. self._serial = _serial
  774. try:
  775. self._type = SSHCertificateType(_cctype)
  776. except ValueError:
  777. raise ValueError("Invalid certificate type")
  778. self._key_id = _key_id
  779. self._valid_principals = _valid_principals
  780. self._valid_after = _valid_after
  781. self._valid_before = _valid_before
  782. self._critical_options = _critical_options
  783. self._extensions = _extensions
  784. self._sig_type = _sig_type
  785. self._sig_key = _sig_key
  786. self._inner_sig_type = _inner_sig_type
  787. self._signature = _signature
  788. self._cert_key_type = _cert_key_type
  789. self._cert_body = _cert_body
  790. self._tbs_cert_body = _tbs_cert_body
  791. @property
  792. def nonce(self) -> bytes:
  793. return bytes(self._nonce)
  794. def public_key(self) -> SSHCertPublicKeyTypes:
  795. # make mypy happy until we remove DSA support entirely and
  796. # the underlying union won't have a disallowed type
  797. return typing.cast(SSHCertPublicKeyTypes, self._public_key)
  798. @property
  799. def serial(self) -> int:
  800. return self._serial
  801. @property
  802. def type(self) -> SSHCertificateType:
  803. return self._type
  804. @property
  805. def key_id(self) -> bytes:
  806. return bytes(self._key_id)
  807. @property
  808. def valid_principals(self) -> list[bytes]:
  809. return self._valid_principals
  810. @property
  811. def valid_before(self) -> int:
  812. return self._valid_before
  813. @property
  814. def valid_after(self) -> int:
  815. return self._valid_after
  816. @property
  817. def critical_options(self) -> dict[bytes, bytes]:
  818. return self._critical_options
  819. @property
  820. def extensions(self) -> dict[bytes, bytes]:
  821. return self._extensions
  822. def signature_key(self) -> SSHCertPublicKeyTypes:
  823. sigformat = _lookup_kformat(self._sig_type)
  824. signature_key, sigkey_rest = sigformat.load_public(self._sig_key)
  825. _check_empty(sigkey_rest)
  826. return signature_key
  827. def public_bytes(self) -> bytes:
  828. return (
  829. bytes(self._cert_key_type)
  830. + b" "
  831. + binascii.b2a_base64(bytes(self._cert_body), newline=False)
  832. )
  833. def verify_cert_signature(self) -> None:
  834. signature_key = self.signature_key()
  835. if isinstance(signature_key, ed25519.Ed25519PublicKey):
  836. signature_key.verify(
  837. bytes(self._signature), bytes(self._tbs_cert_body)
  838. )
  839. elif isinstance(signature_key, ec.EllipticCurvePublicKey):
  840. # The signature is encoded as a pair of big-endian integers
  841. r, data = _get_mpint(self._signature)
  842. s, data = _get_mpint(data)
  843. _check_empty(data)
  844. computed_sig = asym_utils.encode_dss_signature(r, s)
  845. hash_alg = _get_ec_hash_alg(signature_key.curve)
  846. signature_key.verify(
  847. computed_sig, bytes(self._tbs_cert_body), ec.ECDSA(hash_alg)
  848. )
  849. else:
  850. assert isinstance(signature_key, rsa.RSAPublicKey)
  851. if self._inner_sig_type == _SSH_RSA:
  852. hash_alg = hashes.SHA1()
  853. elif self._inner_sig_type == _SSH_RSA_SHA256:
  854. hash_alg = hashes.SHA256()
  855. else:
  856. assert self._inner_sig_type == _SSH_RSA_SHA512
  857. hash_alg = hashes.SHA512()
  858. signature_key.verify(
  859. bytes(self._signature),
  860. bytes(self._tbs_cert_body),
  861. padding.PKCS1v15(),
  862. hash_alg,
  863. )
  864. def _get_ec_hash_alg(curve: ec.EllipticCurve) -> hashes.HashAlgorithm:
  865. if isinstance(curve, ec.SECP256R1):
  866. return hashes.SHA256()
  867. elif isinstance(curve, ec.SECP384R1):
  868. return hashes.SHA384()
  869. else:
  870. assert isinstance(curve, ec.SECP521R1)
  871. return hashes.SHA512()
  872. def _load_ssh_public_identity(
  873. data: utils.Buffer,
  874. _legacy_dsa_allowed=False,
  875. ) -> SSHCertificate | SSHPublicKeyTypes:
  876. utils._check_byteslike("data", data)
  877. m = _SSH_PUBKEY_RC.match(data)
  878. if not m:
  879. raise ValueError("Invalid line format")
  880. key_type = orig_key_type = m.group(1)
  881. key_body = m.group(2)
  882. with_cert = False
  883. if key_type.endswith(_CERT_SUFFIX):
  884. with_cert = True
  885. key_type = key_type[: -len(_CERT_SUFFIX)]
  886. if key_type == _SSH_DSA and not _legacy_dsa_allowed:
  887. raise UnsupportedAlgorithm(
  888. "DSA keys aren't supported in SSH certificates"
  889. )
  890. kformat = _lookup_kformat(key_type)
  891. try:
  892. rest = memoryview(binascii.a2b_base64(key_body))
  893. except (TypeError, binascii.Error):
  894. raise ValueError("Invalid format")
  895. if with_cert:
  896. cert_body = rest
  897. inner_key_type, rest = _get_sshstr(rest)
  898. if inner_key_type != orig_key_type:
  899. raise ValueError("Invalid key format")
  900. if with_cert:
  901. nonce, rest = _get_sshstr(rest)
  902. public_key, rest = kformat.load_public(rest)
  903. if with_cert:
  904. serial, rest = _get_u64(rest)
  905. cctype, rest = _get_u32(rest)
  906. key_id, rest = _get_sshstr(rest)
  907. principals, rest = _get_sshstr(rest)
  908. valid_principals = []
  909. while principals:
  910. principal, principals = _get_sshstr(principals)
  911. valid_principals.append(bytes(principal))
  912. valid_after, rest = _get_u64(rest)
  913. valid_before, rest = _get_u64(rest)
  914. crit_options, rest = _get_sshstr(rest)
  915. critical_options = _parse_exts_opts(crit_options)
  916. exts, rest = _get_sshstr(rest)
  917. extensions = _parse_exts_opts(exts)
  918. # Get the reserved field, which is unused.
  919. _, rest = _get_sshstr(rest)
  920. sig_key_raw, rest = _get_sshstr(rest)
  921. sig_type, sig_key = _get_sshstr(sig_key_raw)
  922. if sig_type == _SSH_DSA and not _legacy_dsa_allowed:
  923. raise UnsupportedAlgorithm(
  924. "DSA signatures aren't supported in SSH certificates"
  925. )
  926. # Get the entire cert body and subtract the signature
  927. tbs_cert_body = cert_body[: -len(rest)]
  928. signature_raw, rest = _get_sshstr(rest)
  929. _check_empty(rest)
  930. inner_sig_type, sig_rest = _get_sshstr(signature_raw)
  931. # RSA certs can have multiple algorithm types
  932. if (
  933. sig_type == _SSH_RSA
  934. and inner_sig_type
  935. not in [_SSH_RSA_SHA256, _SSH_RSA_SHA512, _SSH_RSA]
  936. ) or (sig_type != _SSH_RSA and inner_sig_type != sig_type):
  937. raise ValueError("Signature key type does not match")
  938. signature, sig_rest = _get_sshstr(sig_rest)
  939. _check_empty(sig_rest)
  940. return SSHCertificate(
  941. nonce,
  942. public_key,
  943. serial,
  944. cctype,
  945. key_id,
  946. valid_principals,
  947. valid_after,
  948. valid_before,
  949. critical_options,
  950. extensions,
  951. sig_type,
  952. sig_key,
  953. inner_sig_type,
  954. signature,
  955. tbs_cert_body,
  956. orig_key_type,
  957. cert_body,
  958. )
  959. else:
  960. _check_empty(rest)
  961. return public_key
  962. def load_ssh_public_identity(
  963. data: utils.Buffer,
  964. ) -> SSHCertificate | SSHPublicKeyTypes:
  965. return _load_ssh_public_identity(data)
  966. def _parse_exts_opts(exts_opts: memoryview) -> dict[bytes, bytes]:
  967. result: dict[bytes, bytes] = {}
  968. last_name = None
  969. while exts_opts:
  970. name, exts_opts = _get_sshstr(exts_opts)
  971. bname: bytes = bytes(name)
  972. if bname in result:
  973. raise ValueError("Duplicate name")
  974. if last_name is not None and bname < last_name:
  975. raise ValueError("Fields not lexically sorted")
  976. value, exts_opts = _get_sshstr(exts_opts)
  977. if len(value) > 0:
  978. value, extra = _get_sshstr(value)
  979. if len(extra) > 0:
  980. raise ValueError("Unexpected extra data after value")
  981. result[bname] = bytes(value)
  982. last_name = bname
  983. return result
  984. def ssh_key_fingerprint(
  985. key: SSHPublicKeyTypes,
  986. hash_algorithm: hashes.MD5 | hashes.SHA256,
  987. ) -> bytes:
  988. if not isinstance(hash_algorithm, (hashes.MD5, hashes.SHA256)):
  989. raise TypeError("hash_algorithm must be either MD5 or SHA256")
  990. key_type = _get_ssh_key_type(key)
  991. kformat = _lookup_kformat(key_type)
  992. f_pub = _FragList()
  993. f_pub.put_sshstr(key_type)
  994. kformat.encode_public(key, f_pub)
  995. ssh_binary_data = f_pub.tobytes()
  996. # Hash the binary data
  997. hash_obj = hashes.Hash(hash_algorithm)
  998. hash_obj.update(ssh_binary_data)
  999. return hash_obj.finalize()
  1000. def load_ssh_public_key(
  1001. data: utils.Buffer, backend: typing.Any = None
  1002. ) -> SSHPublicKeyTypes:
  1003. cert_or_key = _load_ssh_public_identity(data, _legacy_dsa_allowed=True)
  1004. public_key: SSHPublicKeyTypes
  1005. if isinstance(cert_or_key, SSHCertificate):
  1006. public_key = cert_or_key.public_key()
  1007. else:
  1008. public_key = cert_or_key
  1009. if isinstance(public_key, dsa.DSAPublicKey):
  1010. warnings.warn(
  1011. "SSH DSA keys are deprecated and will be removed in a future "
  1012. "release.",
  1013. utils.DeprecatedIn40,
  1014. stacklevel=2,
  1015. )
  1016. return public_key
  1017. def serialize_ssh_public_key(public_key: SSHPublicKeyTypes) -> bytes:
  1018. """One-line public key format for OpenSSH"""
  1019. if isinstance(public_key, dsa.DSAPublicKey):
  1020. warnings.warn(
  1021. "SSH DSA key support is deprecated and will be "
  1022. "removed in a future release",
  1023. utils.DeprecatedIn40,
  1024. stacklevel=4,
  1025. )
  1026. key_type = _get_ssh_key_type(public_key)
  1027. kformat = _lookup_kformat(key_type)
  1028. f_pub = _FragList()
  1029. f_pub.put_sshstr(key_type)
  1030. kformat.encode_public(public_key, f_pub)
  1031. pub = binascii.b2a_base64(f_pub.tobytes()).strip()
  1032. return b"".join([key_type, b" ", pub])
  1033. SSHCertPrivateKeyTypes = typing.Union[
  1034. ec.EllipticCurvePrivateKey,
  1035. rsa.RSAPrivateKey,
  1036. ed25519.Ed25519PrivateKey,
  1037. ]
  1038. # This is an undocumented limit enforced in the openssh codebase for sshd and
  1039. # ssh-keygen, but it is undefined in the ssh certificates spec.
  1040. _SSHKEY_CERT_MAX_PRINCIPALS = 256
  1041. class SSHCertificateBuilder:
  1042. def __init__(
  1043. self,
  1044. _public_key: SSHCertPublicKeyTypes | None = None,
  1045. _serial: int | None = None,
  1046. _type: SSHCertificateType | None = None,
  1047. _key_id: bytes | None = None,
  1048. _valid_principals: list[bytes] = [],
  1049. _valid_for_all_principals: bool = False,
  1050. _valid_before: int | None = None,
  1051. _valid_after: int | None = None,
  1052. _critical_options: list[tuple[bytes, bytes]] = [],
  1053. _extensions: list[tuple[bytes, bytes]] = [],
  1054. ):
  1055. self._public_key = _public_key
  1056. self._serial = _serial
  1057. self._type = _type
  1058. self._key_id = _key_id
  1059. self._valid_principals = _valid_principals
  1060. self._valid_for_all_principals = _valid_for_all_principals
  1061. self._valid_before = _valid_before
  1062. self._valid_after = _valid_after
  1063. self._critical_options = _critical_options
  1064. self._extensions = _extensions
  1065. def public_key(
  1066. self, public_key: SSHCertPublicKeyTypes
  1067. ) -> SSHCertificateBuilder:
  1068. if not isinstance(
  1069. public_key,
  1070. (
  1071. ec.EllipticCurvePublicKey,
  1072. rsa.RSAPublicKey,
  1073. ed25519.Ed25519PublicKey,
  1074. ),
  1075. ):
  1076. raise TypeError("Unsupported key type")
  1077. if self._public_key is not None:
  1078. raise ValueError("public_key already set")
  1079. return SSHCertificateBuilder(
  1080. _public_key=public_key,
  1081. _serial=self._serial,
  1082. _type=self._type,
  1083. _key_id=self._key_id,
  1084. _valid_principals=self._valid_principals,
  1085. _valid_for_all_principals=self._valid_for_all_principals,
  1086. _valid_before=self._valid_before,
  1087. _valid_after=self._valid_after,
  1088. _critical_options=self._critical_options,
  1089. _extensions=self._extensions,
  1090. )
  1091. def serial(self, serial: int) -> SSHCertificateBuilder:
  1092. if not isinstance(serial, int):
  1093. raise TypeError("serial must be an integer")
  1094. if not 0 <= serial < 2**64:
  1095. raise ValueError("serial must be between 0 and 2**64")
  1096. if self._serial is not None:
  1097. raise ValueError("serial already set")
  1098. return SSHCertificateBuilder(
  1099. _public_key=self._public_key,
  1100. _serial=serial,
  1101. _type=self._type,
  1102. _key_id=self._key_id,
  1103. _valid_principals=self._valid_principals,
  1104. _valid_for_all_principals=self._valid_for_all_principals,
  1105. _valid_before=self._valid_before,
  1106. _valid_after=self._valid_after,
  1107. _critical_options=self._critical_options,
  1108. _extensions=self._extensions,
  1109. )
  1110. def type(self, type: SSHCertificateType) -> SSHCertificateBuilder:
  1111. if not isinstance(type, SSHCertificateType):
  1112. raise TypeError("type must be an SSHCertificateType")
  1113. if self._type is not None:
  1114. raise ValueError("type already set")
  1115. return SSHCertificateBuilder(
  1116. _public_key=self._public_key,
  1117. _serial=self._serial,
  1118. _type=type,
  1119. _key_id=self._key_id,
  1120. _valid_principals=self._valid_principals,
  1121. _valid_for_all_principals=self._valid_for_all_principals,
  1122. _valid_before=self._valid_before,
  1123. _valid_after=self._valid_after,
  1124. _critical_options=self._critical_options,
  1125. _extensions=self._extensions,
  1126. )
  1127. def key_id(self, key_id: bytes) -> SSHCertificateBuilder:
  1128. if not isinstance(key_id, bytes):
  1129. raise TypeError("key_id must be bytes")
  1130. if self._key_id is not None:
  1131. raise ValueError("key_id already set")
  1132. return SSHCertificateBuilder(
  1133. _public_key=self._public_key,
  1134. _serial=self._serial,
  1135. _type=self._type,
  1136. _key_id=key_id,
  1137. _valid_principals=self._valid_principals,
  1138. _valid_for_all_principals=self._valid_for_all_principals,
  1139. _valid_before=self._valid_before,
  1140. _valid_after=self._valid_after,
  1141. _critical_options=self._critical_options,
  1142. _extensions=self._extensions,
  1143. )
  1144. def valid_principals(
  1145. self, valid_principals: list[bytes]
  1146. ) -> SSHCertificateBuilder:
  1147. if self._valid_for_all_principals:
  1148. raise ValueError(
  1149. "Principals can't be set because the cert is valid "
  1150. "for all principals"
  1151. )
  1152. if (
  1153. not all(isinstance(x, bytes) for x in valid_principals)
  1154. or not valid_principals
  1155. ):
  1156. raise TypeError(
  1157. "principals must be a list of bytes and can't be empty"
  1158. )
  1159. if self._valid_principals:
  1160. raise ValueError("valid_principals already set")
  1161. if len(valid_principals) > _SSHKEY_CERT_MAX_PRINCIPALS:
  1162. raise ValueError(
  1163. "Reached or exceeded the maximum number of valid_principals"
  1164. )
  1165. return SSHCertificateBuilder(
  1166. _public_key=self._public_key,
  1167. _serial=self._serial,
  1168. _type=self._type,
  1169. _key_id=self._key_id,
  1170. _valid_principals=valid_principals,
  1171. _valid_for_all_principals=self._valid_for_all_principals,
  1172. _valid_before=self._valid_before,
  1173. _valid_after=self._valid_after,
  1174. _critical_options=self._critical_options,
  1175. _extensions=self._extensions,
  1176. )
  1177. def valid_for_all_principals(self):
  1178. if self._valid_principals:
  1179. raise ValueError(
  1180. "valid_principals already set, can't set "
  1181. "valid_for_all_principals"
  1182. )
  1183. if self._valid_for_all_principals:
  1184. raise ValueError("valid_for_all_principals already set")
  1185. return SSHCertificateBuilder(
  1186. _public_key=self._public_key,
  1187. _serial=self._serial,
  1188. _type=self._type,
  1189. _key_id=self._key_id,
  1190. _valid_principals=self._valid_principals,
  1191. _valid_for_all_principals=True,
  1192. _valid_before=self._valid_before,
  1193. _valid_after=self._valid_after,
  1194. _critical_options=self._critical_options,
  1195. _extensions=self._extensions,
  1196. )
  1197. def valid_before(self, valid_before: int | float) -> SSHCertificateBuilder:
  1198. if not isinstance(valid_before, (int, float)):
  1199. raise TypeError("valid_before must be an int or float")
  1200. valid_before = int(valid_before)
  1201. if valid_before < 0 or valid_before >= 2**64:
  1202. raise ValueError("valid_before must [0, 2**64)")
  1203. if self._valid_before is not None:
  1204. raise ValueError("valid_before already set")
  1205. return SSHCertificateBuilder(
  1206. _public_key=self._public_key,
  1207. _serial=self._serial,
  1208. _type=self._type,
  1209. _key_id=self._key_id,
  1210. _valid_principals=self._valid_principals,
  1211. _valid_for_all_principals=self._valid_for_all_principals,
  1212. _valid_before=valid_before,
  1213. _valid_after=self._valid_after,
  1214. _critical_options=self._critical_options,
  1215. _extensions=self._extensions,
  1216. )
  1217. def valid_after(self, valid_after: int | float) -> SSHCertificateBuilder:
  1218. if not isinstance(valid_after, (int, float)):
  1219. raise TypeError("valid_after must be an int or float")
  1220. valid_after = int(valid_after)
  1221. if valid_after < 0 or valid_after >= 2**64:
  1222. raise ValueError("valid_after must [0, 2**64)")
  1223. if self._valid_after is not None:
  1224. raise ValueError("valid_after already set")
  1225. return SSHCertificateBuilder(
  1226. _public_key=self._public_key,
  1227. _serial=self._serial,
  1228. _type=self._type,
  1229. _key_id=self._key_id,
  1230. _valid_principals=self._valid_principals,
  1231. _valid_for_all_principals=self._valid_for_all_principals,
  1232. _valid_before=self._valid_before,
  1233. _valid_after=valid_after,
  1234. _critical_options=self._critical_options,
  1235. _extensions=self._extensions,
  1236. )
  1237. def add_critical_option(
  1238. self, name: bytes, value: bytes
  1239. ) -> SSHCertificateBuilder:
  1240. if not isinstance(name, bytes) or not isinstance(value, bytes):
  1241. raise TypeError("name and value must be bytes")
  1242. # This is O(n**2)
  1243. if name in [name for name, _ in self._critical_options]:
  1244. raise ValueError("Duplicate critical option name")
  1245. return SSHCertificateBuilder(
  1246. _public_key=self._public_key,
  1247. _serial=self._serial,
  1248. _type=self._type,
  1249. _key_id=self._key_id,
  1250. _valid_principals=self._valid_principals,
  1251. _valid_for_all_principals=self._valid_for_all_principals,
  1252. _valid_before=self._valid_before,
  1253. _valid_after=self._valid_after,
  1254. _critical_options=[*self._critical_options, (name, value)],
  1255. _extensions=self._extensions,
  1256. )
  1257. def add_extension(
  1258. self, name: bytes, value: bytes
  1259. ) -> SSHCertificateBuilder:
  1260. if not isinstance(name, bytes) or not isinstance(value, bytes):
  1261. raise TypeError("name and value must be bytes")
  1262. # This is O(n**2)
  1263. if name in [name for name, _ in self._extensions]:
  1264. raise ValueError("Duplicate extension name")
  1265. return SSHCertificateBuilder(
  1266. _public_key=self._public_key,
  1267. _serial=self._serial,
  1268. _type=self._type,
  1269. _key_id=self._key_id,
  1270. _valid_principals=self._valid_principals,
  1271. _valid_for_all_principals=self._valid_for_all_principals,
  1272. _valid_before=self._valid_before,
  1273. _valid_after=self._valid_after,
  1274. _critical_options=self._critical_options,
  1275. _extensions=[*self._extensions, (name, value)],
  1276. )
  1277. def sign(self, private_key: SSHCertPrivateKeyTypes) -> SSHCertificate:
  1278. if not isinstance(
  1279. private_key,
  1280. (
  1281. ec.EllipticCurvePrivateKey,
  1282. rsa.RSAPrivateKey,
  1283. ed25519.Ed25519PrivateKey,
  1284. ),
  1285. ):
  1286. raise TypeError("Unsupported private key type")
  1287. if self._public_key is None:
  1288. raise ValueError("public_key must be set")
  1289. # Not required
  1290. serial = 0 if self._serial is None else self._serial
  1291. if self._type is None:
  1292. raise ValueError("type must be set")
  1293. # Not required
  1294. key_id = b"" if self._key_id is None else self._key_id
  1295. # A zero length list is valid, but means the certificate
  1296. # is valid for any principal of the specified type. We require
  1297. # the user to explicitly set valid_for_all_principals to get
  1298. # that behavior.
  1299. if not self._valid_principals and not self._valid_for_all_principals:
  1300. raise ValueError(
  1301. "valid_principals must be set if valid_for_all_principals "
  1302. "is False"
  1303. )
  1304. if self._valid_before is None:
  1305. raise ValueError("valid_before must be set")
  1306. if self._valid_after is None:
  1307. raise ValueError("valid_after must be set")
  1308. if self._valid_after > self._valid_before:
  1309. raise ValueError("valid_after must be earlier than valid_before")
  1310. # lexically sort our byte strings
  1311. self._critical_options.sort(key=lambda x: x[0])
  1312. self._extensions.sort(key=lambda x: x[0])
  1313. key_type = _get_ssh_key_type(self._public_key)
  1314. cert_prefix = key_type + _CERT_SUFFIX
  1315. # Marshal the bytes to be signed
  1316. nonce = os.urandom(32)
  1317. kformat = _lookup_kformat(key_type)
  1318. f = _FragList()
  1319. f.put_sshstr(cert_prefix)
  1320. f.put_sshstr(nonce)
  1321. kformat.encode_public(self._public_key, f)
  1322. f.put_u64(serial)
  1323. f.put_u32(self._type.value)
  1324. f.put_sshstr(key_id)
  1325. fprincipals = _FragList()
  1326. for p in self._valid_principals:
  1327. fprincipals.put_sshstr(p)
  1328. f.put_sshstr(fprincipals.tobytes())
  1329. f.put_u64(self._valid_after)
  1330. f.put_u64(self._valid_before)
  1331. fcrit = _FragList()
  1332. for name, value in self._critical_options:
  1333. fcrit.put_sshstr(name)
  1334. if len(value) > 0:
  1335. foptval = _FragList()
  1336. foptval.put_sshstr(value)
  1337. fcrit.put_sshstr(foptval.tobytes())
  1338. else:
  1339. fcrit.put_sshstr(value)
  1340. f.put_sshstr(fcrit.tobytes())
  1341. fext = _FragList()
  1342. for name, value in self._extensions:
  1343. fext.put_sshstr(name)
  1344. if len(value) > 0:
  1345. fextval = _FragList()
  1346. fextval.put_sshstr(value)
  1347. fext.put_sshstr(fextval.tobytes())
  1348. else:
  1349. fext.put_sshstr(value)
  1350. f.put_sshstr(fext.tobytes())
  1351. f.put_sshstr(b"") # RESERVED FIELD
  1352. # encode CA public key
  1353. ca_type = _get_ssh_key_type(private_key)
  1354. caformat = _lookup_kformat(ca_type)
  1355. caf = _FragList()
  1356. caf.put_sshstr(ca_type)
  1357. caformat.encode_public(private_key.public_key(), caf)
  1358. f.put_sshstr(caf.tobytes())
  1359. # Sigs according to the rules defined for the CA's public key
  1360. # (RFC4253 section 6.6 for ssh-rsa, RFC5656 for ECDSA,
  1361. # and RFC8032 for Ed25519).
  1362. if isinstance(private_key, ed25519.Ed25519PrivateKey):
  1363. signature = private_key.sign(f.tobytes())
  1364. fsig = _FragList()
  1365. fsig.put_sshstr(ca_type)
  1366. fsig.put_sshstr(signature)
  1367. f.put_sshstr(fsig.tobytes())
  1368. elif isinstance(private_key, ec.EllipticCurvePrivateKey):
  1369. hash_alg = _get_ec_hash_alg(private_key.curve)
  1370. signature = private_key.sign(f.tobytes(), ec.ECDSA(hash_alg))
  1371. r, s = asym_utils.decode_dss_signature(signature)
  1372. fsig = _FragList()
  1373. fsig.put_sshstr(ca_type)
  1374. fsigblob = _FragList()
  1375. fsigblob.put_mpint(r)
  1376. fsigblob.put_mpint(s)
  1377. fsig.put_sshstr(fsigblob.tobytes())
  1378. f.put_sshstr(fsig.tobytes())
  1379. else:
  1380. assert isinstance(private_key, rsa.RSAPrivateKey)
  1381. # Just like Golang, we're going to use SHA512 for RSA
  1382. # https://cs.opensource.google/go/x/crypto/+/refs/tags/
  1383. # v0.4.0:ssh/certs.go;l=445
  1384. # RFC 8332 defines SHA256 and 512 as options
  1385. fsig = _FragList()
  1386. fsig.put_sshstr(_SSH_RSA_SHA512)
  1387. signature = private_key.sign(
  1388. f.tobytes(), padding.PKCS1v15(), hashes.SHA512()
  1389. )
  1390. fsig.put_sshstr(signature)
  1391. f.put_sshstr(fsig.tobytes())
  1392. cert_data = binascii.b2a_base64(f.tobytes()).strip()
  1393. # load_ssh_public_identity returns a union, but this is
  1394. # guaranteed to be an SSHCertificate, so we cast to make
  1395. # mypy happy.
  1396. return typing.cast(
  1397. SSHCertificate,
  1398. load_ssh_public_identity(b"".join([cert_prefix, b" ", cert_data])),
  1399. )