ssh.py 52 KB

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