swupdate-common.bbclass 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. DEPENDS += "${@ 'openssl-native' if d.getVar('SWUPDATE_SIGNING', True) else ''}"
  2. do_swuimage[umask] = "022"
  3. SSTATETASKS += "do_swuimage"
  4. SSTATE_SKIP_CREATION_task-swuimage = '1'
  5. IMGDEPLOYDIR = "${WORKDIR}/deploy-${PN}-swuimage"
  6. do_swuimage[dirs] = "${TOPDIR}"
  7. do_swuimage[cleandirs] += "${S} ${IMGDEPLOYDIR}"
  8. do_swuimage[sstate-inputdirs] = "${IMGDEPLOYDIR}"
  9. do_swuimage[sstate-outputdirs] = "${DEPLOY_DIR_IMAGE}"
  10. do_swuimage[stamp-extra-info] = "${MACHINE}"
  11. python () {
  12. deps = " " + swupdate_getdepends(d)
  13. d.appendVarFlag('do_swuimage', 'depends', deps)
  14. }
  15. def swupdate_is_hash_needed(s, filename):
  16. with open(os.path.join(s, "sw-description"), 'r') as f:
  17. for line in f:
  18. if line.find("@%s" % (filename)) != -1:
  19. return True
  20. return False
  21. def swupdate_getdepends(d):
  22. def adddep(depstr, deps):
  23. for i in (depstr or "").split():
  24. if i not in deps:
  25. deps.append(i)
  26. deps = []
  27. images = (d.getVar('IMAGE_DEPENDS', True) or "").split()
  28. for image in images:
  29. adddep(image , deps)
  30. depstr = ""
  31. for dep in deps:
  32. depstr += " " + dep + ":do_build"
  33. return depstr
  34. def swupdate_get_sha256(s, filename):
  35. import hashlib
  36. m = hashlib.sha256()
  37. with open(os.path.join(s, filename), 'rb') as f:
  38. while True:
  39. data = f.read(1024)
  40. if not data:
  41. break
  42. m.update(data)
  43. return m.hexdigest()
  44. def swupdate_extract_keys(keyfile_path):
  45. try:
  46. with open(keyfile_path, 'r') as f:
  47. lines = f.readlines()
  48. except IOError:
  49. bb.fatal("Failed to open file with keys %s" % (keyfile))
  50. data = {}
  51. for _ in lines:
  52. k,v = _.split('=',maxsplit=1)
  53. data[k.rstrip()] = v
  54. key = data['key'].rstrip('\n')
  55. iv = data['iv'].rstrip('\n')
  56. return key,iv
  57. def swupdate_encrypt_file(f, out, key, ivt):
  58. import subprocess
  59. encargs = ["openssl", "enc", "-aes-256-cbc", "-in", f, "-out", out]
  60. encargs += ["-K", key, "-iv", ivt, "-nosalt"]
  61. subprocess.run(encargs, check=True)
  62. def swupdate_write_sha256(s, filename, hash):
  63. write_lines = []
  64. with open(os.path.join(s, "sw-description"), 'r') as f:
  65. for line in f:
  66. write_lines.append(line.replace("@%s" % (filename), hash))
  67. with open(os.path.join(s, "sw-description"), 'w+') as f:
  68. for line in write_lines:
  69. f.write(line)
  70. def swupdate_expand_bitbake_variables(d, s):
  71. write_lines = []
  72. with open(os.path.join(s, "sw-description"), 'r') as f:
  73. import re
  74. for line in f:
  75. found = False
  76. while True:
  77. m = re.match(r"^(?P<before_placeholder>.+)@@(?P<bitbake_variable_name>\w+)@@(?P<after_placeholder>.+)$", line)
  78. if m:
  79. bitbake_variable_value = d.getVar(m.group('bitbake_variable_name'), True)
  80. if bitbake_variable_value is None:
  81. bitbake_variable_value = ""
  82. bb.warn("BitBake variable %s not set" % (m.group('bitbake_variable_name')))
  83. line = m.group('before_placeholder') + bitbake_variable_value + m.group('after_placeholder')
  84. found = True
  85. continue
  86. else:
  87. m = re.match(r"^(?P<before_placeholder>.+)@@(?P<bitbake_variable_name>.+)\[(?P<flag_var_name>.+)\]@@(?P<after_placeholder>.+)$", line)
  88. if m:
  89. bitbake_variable_value = (d.getVarFlag(m.group('bitbake_variable_name'), m.group('flag_var_name'), True) or "")
  90. if bitbake_variable_value is None:
  91. bitbake_variable_value = ""
  92. line = m.group('before_placeholder') + bitbake_variable_value + m.group('after_placeholder')
  93. continue
  94. if found:
  95. line = line + "\n"
  96. break
  97. write_lines.append(line)
  98. with open(os.path.join(s, "sw-description"), 'w+') as f:
  99. for line in write_lines:
  100. f.write(line)
  101. def swupdate_expand_auto_versions(d, s, list_for_cpio):
  102. import re
  103. import oe.packagedata
  104. AUTO_VERSION_TAG = "@SWU_AUTO_VERSION"
  105. AUTOVERSION_REGEXP = "version\s*=\s*\"%s" % AUTO_VERSION_TAG
  106. with open(os.path.join(s, "sw-description"), 'r') as f:
  107. data = f.read()
  108. def get_package_name(group, file_list):
  109. package = None
  110. m = re.search(r"%s:(?P<package>.+?(?=[\"@]))" % (AUTOVERSION_REGEXP), group)
  111. if m:
  112. package = m.group('package')
  113. return (package, True)
  114. for filename in file_list:
  115. if filename in group:
  116. package = filename
  117. if not package:
  118. bb.fatal("Failed to find file in group %s" % (group))
  119. return (package, False)
  120. def get_packagedata_key(group):
  121. m = re.search(r"%s.+?(?<=@)(?P<key>.+?(?=\"))" % (AUTOVERSION_REGEXP), group)
  122. if m:
  123. return (m.group('key'), True)
  124. return ("PV", False)
  125. regexp = re.compile(r"\{[^\{]*%s.[^\}]*\}" % (AUTOVERSION_REGEXP))
  126. while True:
  127. m = regexp.search(data)
  128. if not m:
  129. break
  130. group = data[m.start():m.end()]
  131. (package, pkg_name_defined) = get_package_name(group, list_for_cpio)
  132. pkg_info = os.path.join(d.getVar('PKGDATA_DIR'), 'runtime-reverse', package)
  133. pkgdata = oe.packagedata.read_pkgdatafile(pkg_info)
  134. (key, key_defined) = get_packagedata_key(group)
  135. if not key in pkgdata.keys():
  136. bb.warn("\"%s\" not set for package %s - using \"1.0\"" % (key, package))
  137. version = "1.0"
  138. else:
  139. version = pkgdata[key].split('+')[0]
  140. replace_str = AUTO_VERSION_TAG
  141. if pkg_name_defined:
  142. replace_str = replace_str + ":" + package
  143. if key_defined:
  144. replace_str = replace_str + "@" + key
  145. group = group.replace(replace_str, version)
  146. data = data[:m.start()] + group + data[m.end():]
  147. with open(os.path.join(s, "sw-description"), 'w+') as f:
  148. f.write(data)
  149. def prepare_sw_description(d, s, list_for_cpio):
  150. import shutil
  151. swupdate_expand_bitbake_variables(d, s)
  152. swupdate_expand_auto_versions(d, s, list_for_cpio)
  153. for file in list_for_cpio:
  154. if file != 'sw-description' and swupdate_is_hash_needed(s, file):
  155. hash = swupdate_get_sha256(s, file)
  156. swupdate_write_sha256(s, file, hash)
  157. encrypt = d.getVar('SWUPDATE_ENCRYPT_SWDESC', True)
  158. if encrypt:
  159. bb.note("Encryption of sw-description")
  160. shutil.copyfile(os.path.join(s, 'sw-description'), os.path.join(s, 'sw-description.plain'))
  161. key,iv = swupdate_extract_keys(d.getVar('SWUPDATE_AES_FILE', True))
  162. swupdate_encrypt_file(os.path.join(s, 'sw-description.plain'), os.path.join(s, 'sw-description'), key, iv)
  163. signing = d.getVar('SWUPDATE_SIGNING', True)
  164. if signing == "1":
  165. bb.warn('SWUPDATE_SIGNING = "1" is deprecated, falling back to "RSA". It is advised to set it to "RSA" if using RSA signing.')
  166. signing = "RSA"
  167. if signing:
  168. if signing == "CUSTOM":
  169. sign_tool = d.getVar('SWUPDATE_SIGN_TOOL', True)
  170. if sign_tool:
  171. ret = os.system(sign_tool)
  172. if ret != 0:
  173. bb.fatal("Failed to sign with %s" % (sign_tool))
  174. else:
  175. bb.fatal("Custom SWUPDATE_SIGN_TOOL is not given")
  176. elif signing == "RSA":
  177. privkey = d.getVar('SWUPDATE_PRIVATE_KEY', True)
  178. if not privkey:
  179. bb.fatal("SWUPDATE_PRIVATE_KEY isn't set")
  180. if not os.path.exists(privkey):
  181. bb.fatal("SWUPDATE_PRIVATE_KEY %s doesn't exist" % (privkey))
  182. passout = d.getVar('SWUPDATE_PASSWORD_FILE', True)
  183. if passout:
  184. passout = "-passin file:'%s' " % (passout)
  185. else:
  186. passout = ""
  187. signcmd = "openssl dgst -sha256 -sign '%s' %s -out '%s' '%s'" % (
  188. privkey,
  189. passout,
  190. os.path.join(s, 'sw-description.sig'),
  191. os.path.join(s, 'sw-description.plain' if encrypt else 'sw-description'))
  192. if os.system(signcmd) != 0:
  193. bb.fatal("Failed to sign sw-description with %s" % (privkey))
  194. elif signing == "CMS":
  195. cms_cert = d.getVar('SWUPDATE_CMS_CERT', True)
  196. if not cms_cert:
  197. bb.fatal("SWUPDATE_CMS_CERT is not set")
  198. if not os.path.exists(cms_cert):
  199. bb.fatal("SWUPDATE_CMS_CERT %s doesn't exist" % (cms_cert))
  200. cms_key = d.getVar('SWUPDATE_CMS_KEY', True)
  201. if not cms_key:
  202. bb.fatal("SWUPDATE_CMS_KEY isn't set")
  203. if not os.path.exists(cms_key):
  204. bb.fatal("SWUPDATE_CMS_KEY %s doesn't exist" % (cms_key))
  205. passout = d.getVar('SWUPDATE_PASSWORD_FILE', True)
  206. if passout:
  207. passout = "-passin file:'%s' " % (passout)
  208. else:
  209. passout = ""
  210. signcmd = "openssl cms -sign -in '%s' -out '%s' -signer '%s' -inkey '%s' %s -outform DER -nosmimecap -binary" % (
  211. os.path.join(s, 'sw-description.plain' if encrypt else 'sw-description'),
  212. os.path.join(s, 'sw-description.sig'),
  213. cms_cert,
  214. cms_key,
  215. passout)
  216. if os.system(signcmd) != 0:
  217. bb.fatal("Failed to sign sw-description with %s" % (privkey))
  218. else:
  219. bb.fatal("Unrecognized SWUPDATE_SIGNING mechanism.");
  220. python do_swuimage () {
  221. import shutil
  222. workdir = d.getVar('WORKDIR', True)
  223. images = (d.getVar('SWUPDATE_IMAGES', True) or "").split()
  224. s = d.getVar('S', True)
  225. shutil.copyfile(os.path.join(workdir, "sw-description"), os.path.join(s, "sw-description"))
  226. fetch = bb.fetch2.Fetch([], d)
  227. list_for_cpio = ["sw-description"]
  228. if d.getVar('SWUPDATE_SIGNING', True):
  229. list_for_cpio.append('sw-description.sig')
  230. # Add files listed in SRC_URI to the swu file
  231. for url in fetch.urls:
  232. local = fetch.localpath(url)
  233. filename = os.path.basename(local)
  234. aes_file = d.getVar('SWUPDATE_AES_FILE', True)
  235. if aes_file:
  236. key,iv = swupdate_extract_keys(d.getVar('SWUPDATE_AES_FILE', True))
  237. if (filename != 'sw-description') and (os.path.isfile(local)):
  238. encrypted = (d.getVarFlag("SWUPDATE_IMAGES_ENCRYPTED", filename, True) or "")
  239. dst = os.path.join(s, "%s" % filename )
  240. if encrypted == '1':
  241. bb.note("Encryption requested for %s" %(filename))
  242. if not key or not iv:
  243. bb.fatal("Encryption required, but no key found")
  244. swupdate_encrypt_file(local, dst, key, iv)
  245. else:
  246. shutil.copyfile(local, dst)
  247. list_for_cpio.append(filename)
  248. def add_image_to_swu(deploydir, imagename, s, encrypt):
  249. src = os.path.join(deploydir, imagename)
  250. if not os.path.isfile(src):
  251. return False
  252. target_imagename = os.path.basename(imagename) # allow images in subfolders of DEPLOY_DIR_IMAGE
  253. dst = os.path.join(s, target_imagename)
  254. if encrypt == '1':
  255. key,iv = swupdate_extract_keys(d.getVar('SWUPDATE_AES_FILE', True))
  256. bb.note("Encryption requested for %s" %(imagename))
  257. swupdate_encrypt_file(src, dst, key, iv)
  258. else:
  259. shutil.copyfile(src, dst)
  260. list_for_cpio.append(target_imagename)
  261. return True
  262. # Search for images listed in SWUPDATE_IMAGES in the DEPLOY directory.
  263. deploydir = d.getVar('DEPLOY_DIR_IMAGE', True)
  264. imgdeploydir = d.getVar('IMGDEPLOYDIR', True)
  265. for image in images:
  266. fstypes = (d.getVarFlag("SWUPDATE_IMAGES_FSTYPES", image, True) or "").split()
  267. encrypted = (d.getVarFlag("SWUPDATE_IMAGES_ENCRYPTED", image, True) or "")
  268. if fstypes:
  269. noappend_machine = d.getVarFlag("SWUPDATE_IMAGES_NOAPPEND_MACHINE", image, True)
  270. if noappend_machine == "0": # Search for a file explicitly with MACHINE
  271. imagebases = [ image + '-' + d.getVar('MACHINE', True) ]
  272. elif noappend_machine == "1": # Search for a file explicitly without MACHINE
  273. imagebases = [ image ]
  274. else: # None, means auto mode. Just try to find an image file with MACHINE or without MACHINE
  275. imagebases = [ image + '-' + d.getVar('MACHINE', True), image ]
  276. for fstype in fstypes:
  277. image_found = False
  278. for imagebase in imagebases:
  279. image_found = add_image_to_swu(deploydir, imagebase + fstype, s, encrypted)
  280. if image_found:
  281. break
  282. if not image_found:
  283. bb.fatal("swupdate cannot find image file: %s" % os.path.join(deploydir, imagebase + fstype))
  284. else: # Allow also complete entries like "image.ext4.gz" in SWUPDATE_IMAGES
  285. if not add_image_to_swu(deploydir, image, s, encrypted):
  286. bb.fatal("swupdate cannot find %s image file" % image)
  287. prepare_sw_description(d, s, list_for_cpio)
  288. line = 'for i in ' + ' '.join(list_for_cpio) + '; do echo $i;done | cpio -ov -H crc >' + os.path.join(imgdeploydir,d.getVar('IMAGE_NAME', True) + '.swu')
  289. os.system("cd " + s + ";" + line)
  290. line = 'ln -sf ' + d.getVar('IMAGE_NAME', True) + '.swu ' + d.getVar('IMAGE_LINK_NAME', True) + '.swu'
  291. os.system("cd " + imgdeploydir + "; " + line)
  292. }