swupdate-common.bbclass 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. # Copyright (C) 2015-2022 Stefano Babic
  2. #
  3. # SPDX-License-Identifier: GPLv3
  4. inherit swupdate-lib
  5. S = "${UNPACKDIR}"
  6. DEPENDS += "\
  7. cpio-native \
  8. ${@ 'openssl-native' if d.getVar('SWUPDATE_SIGNING') or d.getVar('SWUPDATE_ENCRYPT_SWDESC') or d.getVarFlags('SWUPDATE_IMAGES_ENCRYPTED') else ''} \
  9. "
  10. do_swuimage[umask] = "022"
  11. SSTATETASKS += "do_swuimage"
  12. SSTATE_SKIP_CREATION:task-swuimage = '1'
  13. SWUDEPLOYDIR = "${WORKDIR}/deploy-${PN}-swuimage"
  14. do_swuimage[dirs] = "${SWUDEPLOYDIR}"
  15. do_swuimage[cleandirs] += "${SWUDEPLOYDIR}"
  16. do_swuimage[sstate-inputdirs] = "${SWUDEPLOYDIR}"
  17. do_swuimage[sstate-outputdirs] = "${DEPLOY_DIR_IMAGE}"
  18. do_swuimage[stamp-extra-info] = "${MACHINE}"
  19. python () {
  20. deps = " " + swupdate_getdepends(d)
  21. d.appendVarFlag('do_swuimage', 'depends', deps)
  22. d.delVarFlag('do_fetch', 'noexec')
  23. d.delVarFlag('do_unpack', 'noexec')
  24. }
  25. def get_pwd_file_args(d, passfile):
  26. pwd_args = []
  27. pwd_file = d.getVar(passfile)
  28. if pwd_file:
  29. pwd_args = ["-passin", "file:%s" % pwd_file]
  30. return pwd_args
  31. def get_certfile_args(d):
  32. extra_certs = d.getVar('SWUPDATE_CMS_EXTRA_CERTS')
  33. if not extra_certs:
  34. return []
  35. certfile_args = []
  36. extra_paths = extra_certs.split()
  37. for crt_path in extra_paths:
  38. if not os.path.exists(crt_path):
  39. bb.fatal("SWUPDATE_CMS_EXTRA_CERTS path %s doesn't exist" % (crt_path))
  40. certfile_args.extend(["-certfile", crt_path])
  41. return certfile_args
  42. def swupdate_getdepends(d):
  43. def adddep(depstr, deps):
  44. for i in (depstr or "").split():
  45. if i not in deps:
  46. deps.append(i)
  47. deps = []
  48. images = (d.getVar('IMAGE_DEPENDS') or "").split()
  49. for image in images:
  50. adddep(image , deps)
  51. depstr = ""
  52. for dep in deps:
  53. depstr += " " + dep + ":do_build"
  54. return depstr
  55. def swupdate_write_sha256(workdir):
  56. import re
  57. write_lines = []
  58. with open(os.path.join(workdir, "sw-description"), 'r') as f:
  59. for line in f:
  60. shastr = r"sha256.+=.+@(.+\")"
  61. m = re.match(r"^(?P<before_placeholder>.+)(sha256|version).+[=:].*(?P<quote>[\'\"])@(?P<filename>.*)(?P=quote)", line)
  62. if m:
  63. filename = m.group('filename')
  64. bb.warn("Syntax for sha256 changed, please use $swupdate_get_sha256(%s)" % filename)
  65. hash = swupdate_get_sha256(None, workdir, filename)
  66. write_lines.append(line.replace("@%s" % (filename), hash))
  67. else:
  68. write_lines.append(line)
  69. with open(os.path.join(workdir, "sw-description"), 'w+') as f:
  70. for line in write_lines:
  71. f.write(line)
  72. def swupdate_exec_functions(d, s, write_lines):
  73. import re
  74. for index, line in enumerate(write_lines):
  75. m = re.match(r"^(?P<before_placeholder>.+)\$(?P<bitbake_function_name>\w+)\((?P<parms>.+)\)(?P<after_placeholder>.+)$", line)
  76. if m:
  77. fun = m.group('bitbake_function_name') + "(d, \"" + s + "\", \"" + m.group('parms') + "\")"
  78. ret = eval(fun)
  79. bb.debug (2, "%s return %s " % (m.group('bitbake_function_name'), ret))
  80. line = m.group('before_placeholder') + ret + m.group('after_placeholder') + "\n"
  81. write_lines[index] = line
  82. def swupdate_expand_bitbake_variables(d, s, workdir):
  83. write_lines = []
  84. with open(os.path.join(s, "sw-description"), 'r') as f:
  85. import re
  86. for line in f:
  87. found = False
  88. while True:
  89. m = re.match(r"^(?P<before_placeholder>.*)@@(?P<bitbake_variable_name>\w+)@@(?P<after_placeholder>.*)$", line)
  90. if m:
  91. bitbake_variable_value = d.getVar(m.group('bitbake_variable_name'), True)
  92. if bitbake_variable_value is None:
  93. bitbake_variable_value = ""
  94. bb.warn("BitBake variable %s not set" % (m.group('bitbake_variable_name')))
  95. line = m.group('before_placeholder') + bitbake_variable_value + m.group('after_placeholder')
  96. found = True
  97. continue
  98. else:
  99. m = re.match(r"^(?P<before_placeholder>.*)@@(?P<bitbake_variable_name>.+)\[(?P<flag_var_name>.+)\]@@(?P<after_placeholder>.*)$", line)
  100. if m:
  101. bitbake_variable_value = (d.getVarFlag(m.group('bitbake_variable_name'), m.group('flag_var_name'), True) or "")
  102. if bitbake_variable_value is None:
  103. bitbake_variable_value = ""
  104. line = m.group('before_placeholder') + bitbake_variable_value + m.group('after_placeholder')
  105. continue
  106. if found:
  107. line = line + "\n"
  108. break
  109. write_lines.append(line)
  110. swupdate_exec_functions(d, workdir, write_lines)
  111. with open(os.path.join(workdir, "sw-description"), 'w+') as f:
  112. for line in write_lines:
  113. f.write(line)
  114. # Get all the variables referred by the sw-description at parse time.
  115. def swupdate_find_bitbake_variables(d):
  116. import re
  117. vardeps = []
  118. filespath = d.getVar('FILESPATH')
  119. sw_desc_path = bb.utils.which(filespath, "sw-description")
  120. try:
  121. with open(sw_desc_path, "r") as f:
  122. for line in f:
  123. found = False
  124. while True:
  125. m = re.match(r"^(?P<before_placeholder>.*)@@(?P<bitbake_variable_name>\w+)@@(?P<after_placeholder>.*)$", line)
  126. if m:
  127. bitbake_variable_value = m.group('bitbake_variable_name')
  128. vardeps.append(bitbake_variable_value)
  129. line = m.group('before_placeholder') + bitbake_variable_value + m.group('after_placeholder')
  130. found = True
  131. continue
  132. else:
  133. m = re.match(r"^(?P<before_placeholder>.*)@@(?P<bitbake_variable_name>.+)\[(?P<flag_var_name>.+)\]@@(?P<after_placeholder>.*)$", line)
  134. if m:
  135. bitbake_variable_value = m.group('bitbake_variable_name')
  136. vardeps.append(bitbake_variable_value)
  137. flag_name = m.group('flag_var_name')
  138. vardeps.append(flag_name)
  139. line = m.group('before_placeholder') + bitbake_variable_value + m.group('after_placeholder')
  140. continue
  141. break
  142. except IOError:
  143. pass
  144. return ' '.join(set(vardeps))
  145. def prepare_sw_description(d):
  146. import shutil
  147. import subprocess
  148. s = d.getVar('S')
  149. workdir = d.getVar('WORKDIR')
  150. swupdate_expand_bitbake_variables(d, s, workdir)
  151. swupdate_write_sha256(workdir)
  152. encrypt = d.getVar('SWUPDATE_ENCRYPT_SWDESC')
  153. if encrypt:
  154. bb.note("Encryption of sw-description")
  155. shutil.copyfile(os.path.join(workdir, 'sw-description'), os.path.join(workdir, 'sw-description.plain'))
  156. key,iv = swupdate_extract_keys(d.getVar('SWUPDATE_AES_FILE'))
  157. iv = swupdate_get_IV(d, workdir, 'sw-description')
  158. swupdate_encrypt_file(os.path.join(workdir, 'sw-description.plain'), os.path.join(workdir, 'sw-description'), key, iv)
  159. signing = d.getVar('SWUPDATE_SIGNING')
  160. if signing == "1":
  161. bb.warn('SWUPDATE_SIGNING = "1" is deprecated, falling back to "RSA". It is advised to set it to "RSA" if using RSA signing.')
  162. signing = "RSA"
  163. if signing:
  164. sw_desc_sig = os.path.join(workdir, 'sw-description.sig')
  165. sw_desc = os.path.join(workdir, 'sw-description.plain' if encrypt else 'sw-description')
  166. if signing == "CUSTOM":
  167. signcmd = []
  168. sign_tool = d.getVar('SWUPDATE_SIGN_TOOL')
  169. signtool = sign_tool.split()
  170. for i in range(len(signtool)):
  171. signcmd.append(signtool[i])
  172. if not signcmd:
  173. bb.fatal("Custom SWUPDATE_SIGN_TOOL is not given")
  174. elif signing == "RSA":
  175. privkey = d.getVar('SWUPDATE_PRIVATE_KEY')
  176. if not privkey:
  177. bb.fatal("SWUPDATE_PRIVATE_KEY isn't set")
  178. if not os.path.exists(privkey):
  179. bb.fatal("SWUPDATE_PRIVATE_KEY %s doesn't exist" % (privkey))
  180. signcmd = ["openssl", "dgst", "-sha256", "-sign", privkey] + get_pwd_file_args(d, 'SWUPDATE_PASSWORD_FILE') + ["-out", sw_desc_sig, sw_desc]
  181. elif signing == "RSA-PSS":
  182. privkey = d.getVar('SWUPDATE_PRIVATE_KEY', True)
  183. if not privkey:
  184. bb.fatal("SWUPDATE_PRIVATE_KEY isn't set")
  185. if not os.path.exists(privkey):
  186. bb.fatal("SWUPDATE_PRIVATE_KEY %s doesn't exist" % (privkey))
  187. signcmd = ["openssl", "dgst", "-sha256", "-sign", privkey] + get_pwd_file_args(d, 'SWUPDATE_PASSWORD_FILE') + \
  188. ["-sigopt", "rsa_padding_mode:pss", "-sigopt", "rsa_pss_saltlen:-2", "-out", sw_desc_sig, sw_desc]
  189. elif signing == "CMS":
  190. cms_cert = d.getVar('SWUPDATE_CMS_CERT')
  191. if not cms_cert:
  192. bb.fatal("SWUPDATE_CMS_CERT is not set")
  193. if not os.path.exists(cms_cert):
  194. bb.fatal("SWUPDATE_CMS_CERT %s doesn't exist" % (cms_cert))
  195. cms_key = d.getVar('SWUPDATE_CMS_KEY')
  196. if not cms_key:
  197. bb.fatal("SWUPDATE_CMS_KEY isn't set")
  198. if not os.path.exists(cms_key):
  199. bb.fatal("SWUPDATE_CMS_KEY %s doesn't exist" % (cms_key))
  200. signcmd = ["openssl", "cms", "-sign", "-in", sw_desc, "-out", sw_desc_sig, "-signer", cms_cert, "-inkey", cms_key] + \
  201. ["-outform", "DER", "-nosmimecap", "-binary"] + \
  202. get_pwd_file_args(d, 'SWUPDATE_PASSWORD_FILE') + \
  203. get_certfile_args(d)
  204. else:
  205. bb.fatal("Unrecognized SWUPDATE_SIGNING mechanism.")
  206. subprocess.run(' '.join(signcmd), shell=True, check=True)
  207. def swupdate_add_src_uri(d, list_for_cpio):
  208. import shutil
  209. workdir = d.getVar('WORKDIR')
  210. exclude = (d.getVar("SWUPDATE_SRC_URI_EXCLUDE") or "").split()
  211. fetch = bb.fetch2.Fetch([], d)
  212. # Add files listed in SRC_URI to the swu file
  213. for url in fetch.urls:
  214. local = fetch.localpath(url)
  215. filename = os.path.basename(local)
  216. if filename in exclude:
  217. continue
  218. aes_file = d.getVar('SWUPDATE_AES_FILE')
  219. if aes_file:
  220. key,iv = swupdate_extract_keys(d.getVar('SWUPDATE_AES_FILE'))
  221. if (filename != 'sw-description') and (os.path.isfile(local)):
  222. encrypted = (d.getVarFlag("SWUPDATE_IMAGES_ENCRYPTED", filename) or "")
  223. dst = os.path.join(workdir, "%s" % filename )
  224. if encrypted == '1':
  225. bb.note("Encryption requested for %s" %(filename))
  226. if not key or not iv:
  227. bb.fatal("Encryption required, but no key found")
  228. iv = swupdate_get_IV(d, workdir, filename)
  229. swupdate_encrypt_file(local, dst, key, iv)
  230. else:
  231. shutil.copyfile(local, dst)
  232. list_for_cpio.append(filename)
  233. def add_image_to_swu(d, deploydir, imagename, s, encrypt, list_for_cpio):
  234. import shutil
  235. src = os.path.join(deploydir, imagename)
  236. if not os.path.isfile(src):
  237. return False
  238. target_imagename = os.path.basename(imagename) # allow images in subfolders of DEPLOY_DIR_IMAGE
  239. dst = os.path.join(s, target_imagename)
  240. if encrypt == '1':
  241. key,iv = swupdate_extract_keys(d.getVar('SWUPDATE_AES_FILE'))
  242. bb.note("Encryption requested for %s" %(imagename))
  243. iv = swupdate_get_IV(d, s, imagename)
  244. swupdate_encrypt_file(src, dst, key, iv)
  245. else:
  246. shutil.copyfile(src, dst)
  247. list_for_cpio.append(target_imagename)
  248. return True
  249. def swupdate_add_artifacts(d, list_for_cpio):
  250. import shutil
  251. # Search for images listed in SWUPDATE_IMAGES in the DEPLOY directory.
  252. images = (d.getVar('SWUPDATE_IMAGES') or "").split()
  253. deploydir = d.getVar('DEPLOY_DIR_IMAGE')
  254. imgdeploydir = d.getVar('SWUDEPLOYDIR')
  255. workdir = d.getVar('WORKDIR')
  256. for image in images:
  257. fstypes = (d.getVarFlag("SWUPDATE_IMAGES_FSTYPES", image) or "").split()
  258. encrypted = (d.getVarFlag("SWUPDATE_IMAGES_ENCRYPTED", image) or "")
  259. if fstypes:
  260. noappend_machine = d.getVarFlag("SWUPDATE_IMAGES_NOAPPEND_MACHINE", image)
  261. if noappend_machine == "0": # Search for a file explicitly with MACHINE
  262. imagebases = [ image + '-' + d.getVar('MACHINE') ]
  263. elif noappend_machine == "1": # Search for a file explicitly without MACHINE
  264. imagebases = [ image ]
  265. else: # None, means auto mode. Just try to find an image file with MACHINE or without MACHINE
  266. imagebases = [ image + '-' + d.getVar('MACHINE'), image ]
  267. for fstype in fstypes:
  268. image_found = False
  269. for imagebase in imagebases:
  270. image_found = add_image_to_swu(d, deploydir, imagebase + fstype, workdir, encrypted, list_for_cpio)
  271. if image_found:
  272. break
  273. if not image_found:
  274. bb.fatal("swupdate cannot find image file: %s" % os.path.join(deploydir, imagebase + fstype))
  275. else: # Allow also complete entries like "image.ext4.gz" in SWUPDATE_IMAGES
  276. if not add_image_to_swu(d, deploydir, image, workdir, encrypted, list_for_cpio):
  277. bb.fatal("swupdate cannot find %s image file" % image)
  278. def swupdate_create_cpio(d, swudeploydir, list_for_cpio):
  279. workdir = d.getVar('WORKDIR')
  280. os.chdir(workdir)
  281. updateimage = d.getVar('IMAGE_NAME') + '.swu'
  282. line = 'for i in ' + ' '.join(list_for_cpio) + '; do echo $i;done | cpio -ov -H crc --reproducible > ' + os.path.join(swudeploydir, updateimage)
  283. os.system(line)
  284. os.chdir(swudeploydir)
  285. updateimage_link = d.getVar('IMAGE_LINK_NAME')
  286. if updateimage_link:
  287. updateimage_link += '.swu'
  288. if updateimage_link != updateimage:
  289. os.symlink(updateimage, updateimage_link)
  290. python do_swuimage () {
  291. import shutil
  292. list_for_cpio = ["sw-description"]
  293. s = d.getVar('S')
  294. imgdeploydir = d.getVar('SWUDEPLOYDIR')
  295. if d.getVar('SWUPDATE_SIGNING'):
  296. list_for_cpio.append('sw-description.sig')
  297. # Add artifacts added via SRC_URI
  298. if not d.getVar('INHIBIT_SWUPDATE_ADD_SRC_URI'):
  299. swupdate_add_src_uri(d, list_for_cpio)
  300. # Add artifacts set via SWUPDATE_IMAGES
  301. swupdate_add_artifacts(d, list_for_cpio)
  302. prepare_sw_description(d)
  303. swupdate_create_cpio(d, imgdeploydir, list_for_cpio)
  304. }