Skip to content

Commit b86e5cf

Browse files
umbynosper1234
andauthored
Add secure boot support for compile command. (#1686)
* add flags to allow the override of the keys used to sign and encrypt a binary for the boards that support the secure boot * add integration test for ReplaceSecurityKeys() function * fix regression introduced: target platform could be nil so using before checking is not a good idea * apply suggestions from code review * rename of some flags (done to accommodate the proposed changes in platform.txt) * change approach: override keys using `builderCtx.CustomBuildProperties` * add check in the builder regarding the usage of "build.keys.type" properties * add secure boot to the platform specifications * Apply suggestions from code review Co-authored-by: per1234 <accounts@perglass.com> * modify the check on in the builder regarding the usage of "build.keys" properties: The "build.keys.type" is no longer mandatory, and the default is "public_keys" We also check if the secureboot keys are all defined or none of them is. * remove check on the flags specifying the keys, it's the tool responsibility to check if they are valid * move content to a guides section * add specifications regarding `build.keys` properties * Apply suggestions from code review Co-authored-by: per1234 <accounts@perglass.com> * add link to external resource to provide a quick explanation of the reason for an Arduino boards platform developer to add a "secure boot" capability * change `tools.imgtool.build.pattern` to `tools.imgtool.flags` The property had the same form as the special `tools.TOOL_NAME.ACTION.pattern` properties However, there is not a `build` action, the form of the property gives the impression that it is one that has special treatment by the build system. It looks like the convention is `*.flags` * add small section explaining why is recommended to use these prop names * Apply suggestions from code review Co-authored-by: per1234 <accounts@perglass.com> * Correct error message * Apply suggestions from code review Co-authored-by: per1234 <accounts@perglass.com> Co-authored-by: per1234 <accounts@perglass.com>
1 parent f12bd84 commit b86e5cf

File tree

11 files changed

+323
-51
lines changed

11 files changed

+323
-51
lines changed

cli/arguments/arguments.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,15 @@ func CheckFlagsConflicts(command *cobra.Command, flagNames ...string) {
3737
feedback.Errorf(tr("Can't use %s flags at the same time.", "--"+strings.Join(flagNames, " "+tr("and")+" --")))
3838
os.Exit(errorcodes.ErrBadArgument)
3939
}
40+
41+
// CheckFlagsMandatory is a helper function useful to report errors when at least one flag is not used in a group of "required" flags
42+
func CheckFlagsMandatory(command *cobra.Command, flagNames ...string) {
43+
for _, flagName := range flagNames {
44+
if command.Flag(flagName).Changed {
45+
continue
46+
} else {
47+
feedback.Errorf(tr("Flag %[1]s is mandatory when used in conjunction with flag %[2]s.", "--"+flagName, "--"+strings.Join(flagNames, " "+tr("and")+" --")))
48+
os.Exit(errorcodes.ErrBadArgument)
49+
}
50+
}
51+
}

cli/compile/compile.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ var (
5353
buildCachePath string // Builds of 'core.a' are saved into this path to be cached and reused.
5454
buildPath string // Path where to save compiled files.
5555
buildProperties []string // List of custom build properties separated by commas. Or can be used multiple times for multiple properties.
56+
keysKeychain string // The path of the dir where to search for the custom keys to sign and encrypt a binary. Used only by the platforms that supports it
57+
signKey string // The name of the custom signing key to use to sign a binary during the compile process. Used only by the platforms that supports it
58+
encryptKey string // The name of the custom encryption key to use to encrypt a binary during the compile process. Used only by the platforms that supports it
5659
warnings string // Used to tell gcc which warning level to use.
5760
verbose bool // Turns on verbose mode.
5861
quiet bool // Suppresses almost every output.
@@ -100,6 +103,12 @@ func NewCommand() *cobra.Command {
100103
tr("List of custom build properties separated by commas. Or can be used multiple times for multiple properties."))
101104
compileCommand.Flags().StringArrayVar(&buildProperties, "build-property", []string{},
102105
tr("Override a build property with a custom value. Can be used multiple times for multiple properties."))
106+
compileCommand.Flags().StringVar(&keysKeychain, "keys-keychain", "",
107+
tr("The path of the dir to search for the custom keys to sign and encrypt a binary. Used only by the platforms that support it."))
108+
compileCommand.Flags().StringVar(&signKey, "sign-key", "",
109+
tr("The name of the custom signing key to use to sign a binary during the compile process. Used only by the platforms that support it."))
110+
compileCommand.Flags().StringVar(&encryptKey, "encrypt-key", "",
111+
tr("The name of the custom encryption key to use to encrypt a binary during the compile process. Used only by the platforms that support it."))
103112
compileCommand.Flags().StringVar(&warnings, "warnings", "none",
104113
tr(`Optional, can be: %s. Used to tell gcc which warning level to use (-W flag).`, "none, default, more, all"))
105114
compileCommand.Flags().BoolVarP(&verbose, "verbose", "v", false, tr("Optional, turns on verbose mode."))
@@ -142,6 +151,10 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
142151

143152
sketchPath := arguments.InitSketchPath(path)
144153

154+
if keysKeychain != "" || signKey != "" || encryptKey != "" {
155+
arguments.CheckFlagsMandatory(cmd, "keys-keychain", "sign-key", "encrypt-key")
156+
}
157+
145158
var overrides map[string]string
146159
if sourceOverrides != "" {
147160
data, err := paths.New(sourceOverrides).ReadFile()
@@ -198,6 +211,9 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
198211
CreateCompilationDatabaseOnly: compilationDatabaseOnly,
199212
SourceOverride: overrides,
200213
Library: library,
214+
KeysKeychain: keysKeychain,
215+
SignKey: signKey,
216+
EncryptKey: encryptKey,
201217
}
202218
compileStdOut := new(bytes.Buffer)
203219
compileStdErr := new(bytes.Buffer)

commands/compile/compile.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,15 @@ func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream
125125
}
126126
}
127127

128+
// At the current time we do not have a way of knowing if a board supports the secure boot or not,
129+
// so, if the flags to override the default keys are used, we try override the corresponding platform property nonetheless.
130+
// It's not possible to use the default name for the keys since there could be more tools to sign and encrypt.
131+
// So it's mandatory to use all three flags to sign and encrypt the binary
132+
securityKeysOverride := []string{}
133+
if req.KeysKeychain != "" && req.SignKey != "" && req.EncryptKey != "" {
134+
securityKeysOverride = append(securityKeysOverride, "build.keys.keychain="+req.KeysKeychain, "build.keys.sign_key="+req.GetSignKey(), "build.keys.encrypt_key="+req.EncryptKey)
135+
}
136+
128137
builderCtx := &types.Context{}
129138
builderCtx.PackageManager = pm
130139
builderCtx.FQBN = fqbn
@@ -165,6 +174,7 @@ func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream
165174
builderCtx.WarningsLevel = req.GetWarnings()
166175

167176
builderCtx.CustomBuildProperties = append(req.GetBuildProperties(), "build.warn_data_percentage=75")
177+
builderCtx.CustomBuildProperties = append(req.GetBuildProperties(), securityKeysOverride...)
168178

169179
if req.GetBuildCachePath() != "" {
170180
builderCtx.BuildCachePath = paths.New(req.GetBuildCachePath())

docs/guides/secure-boot.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Secure Boot
2+
3+
A ["secure boot"](https://www.keyfactor.com/blog/what-is-secure-boot-its-where-iot-security-starts/) capability may be
4+
offered by Arduino boards platforms.
5+
6+
The compiled sketch is signed and encrypted by a [tool](../platform-specification.md#tools) before being flashed to the
7+
target board. The bootloader of the board is then responsible for starting the compiled sketch only if the matching keys
8+
are used.
9+
10+
To be able to correctly carry out all the operations at the end of the build we can leverage the
11+
[post build hooks](../platform-specification.md#pre-and-post-build-hooks-since-arduino-ide-165) to sign and encrypt a
12+
binary by using `recipe.hooks.objcopy.postobjcopy.NUMBER.pattern` key in
13+
[`platform.txt`](../platform-specification.md#platformtxt). The security keys used are defined in the
14+
[`boards.txt`](../platform-specification.md#boardstxt) file, this way there could be different keys for different
15+
boards.
16+
17+
```
18+
[...]
19+
## Create secure image (bin file)
20+
recipe.hooks.objcopy.postobjcopy.1.pattern={build.postbuild.cmd}
21+
22+
#
23+
# IMGTOOL
24+
#
25+
tools.imgtool.cmd=imgtool
26+
tools.imgtool.flags=sign --key "{build.keys.keychain}/{build.keys.sign_key}" --encrypt "{build.keys.keychain}/{build.keys.encrypt_key}" "{build.path}/{build.project_name}.bin" "{build.path}/{build.project_name}.bin" --align {build.alignment} --max-align {build.alignment} --version {build.version} --header-size {build.header_size} --pad-header --slot-size {build.slot_size}
27+
[...]
28+
29+
```
30+
31+
By having only `tools.TOOL_NAME.cmd` and `tools.TOOL_NAME.flags`, we can customize the behavior with a
32+
[custom board option](../platform-specification.md#custom-board-options). Then in the
33+
[`boards.txt`](../platform-specification.md#boardstxt) we can define the new option to use a different
34+
`build.postbuild.cmd`:
35+
36+
```
37+
[...]
38+
menu.security=Security setting
39+
40+
envie_m7.menu.security.none=None
41+
envie_m7.menu.security.sien=Signature + Encryption
42+
43+
envie_m7.menu.security.sien.build.postbuild.cmd="{tools.imgtool.cmd}" {tools.imgtool.flags}
44+
envie_m7.menu.security.none.build.postbuild.cmd="{tools.imgtool.cmd}" exit
45+
46+
envie_m7.menu.security.sien.build.keys.keychain={runtime.hardware.path}/Default_Keys
47+
envie_m7.menu.security.sien.build.keys.sign_key=default-signing-key.pem
48+
envie_m7.menu.security.sien.build.keys.encrypt_key=default-encrypt-key.pem
49+
[...]
50+
```
51+
52+
The security keys can be added with:
53+
54+
- `build.keys.keychain` indicates the path of the dir where to search for the custom keys to sign and encrypt a binary.
55+
- `build.keys.sign_key` indicates the name of the custom signing key to use to sign a binary during the compile process.
56+
- `build.keys.encrypt_key` indicates the name of the custom encryption key to use to encrypt a binary during the compile
57+
process.
58+
59+
It's suggested to use the property names mentioned before, because they can be overridden respectively with
60+
`--keys-keychain`, `--sign-key` and `--encrypt-key` Arduino CLI [compile flags](../commands/arduino-cli_compile.md).
61+
62+
For example, by using the following command, the sketch is compiled and the resulting binary is signed and encrypted
63+
with the specified keys located in `/home/user/Arduino/keys` directory:
64+
65+
```
66+
arduino-cli compile -b arduino:mbed_portenta:envie_m7:security=sien --keys-keychain /home/user/Arduino/keys --sign-key ecsdsa-p256-signing-key.pem --encrypt-key ecsdsa-p256-encrypt-key.pem /home/user/Arduino/MySketch
67+
```

docs/platform-specification.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,20 @@ the name of the architecture is set as well.
155155

156156
There are some other **{build.xxx}** properties available, that are explained in the boards.txt section of this guide.
157157

158+
#### Security credential properties
159+
160+
Some of them allow specifying trusted security credentials (signing and encryption keys) that can be used by a
161+
["secure boot" system](guides/secure-boot.md):
162+
163+
- `build.keys.keychain`: for the directory containing the keys
164+
- `build.keys.sign_key`: for the signing key
165+
- `build.keys.encrypt_key`: for the encryption key
166+
167+
If any of these properties are defined, the others are required.
168+
169+
These properties can be overwritten respectively with `--keys-keychain`, `--sign-key`, `--encrypt-key`
170+
[compile](commands/arduino-cli_compile.md) flags in the Arduino CLI.
171+
158172
#### Recipes to compile source code
159173

160174
We said that the Arduino development software determines a list of files to compile. Each file can be source code
@@ -1294,7 +1308,7 @@ It can sometimes be useful to provide user selectable configuration options for
12941308
could be provided in two or more variants with different microcontrollers, or may have different crystal speed based on
12951309
the board model, and so on...
12961310

1297-
When using Arduino CLI, the option can be selected via the FQBN.
1311+
When using Arduino CLI, the option can be selected via the FQBN, or using the `--board-options` flag
12981312

12991313
In the Arduino IDE the options add extra menu items under the "Tools" menu.
13001314

legacy/builder/setup_build_properties.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/arduino/arduino-cli/legacy/builder/types"
2727
properties "github.com/arduino/go-properties-orderedmap"
2828
timeutils "github.com/arduino/go-timeutils"
29+
"github.com/pkg/errors"
2930
)
3031

3132
type SetupBuildProperties struct{}
@@ -126,6 +127,14 @@ func (s *SetupBuildProperties) Run(ctx *types.Context) error {
126127

127128
buildProperties.Merge(ctx.PackageManager.CustomGlobalProperties)
128129

130+
keychainProp := buildProperties.ContainsKey("build.keys.keychain")
131+
signProp := buildProperties.ContainsKey("build.keys.sign_key")
132+
encryptProp := buildProperties.ContainsKey("build.keys.encrypt_key")
133+
// we verify that all the properties for the secure boot keys are defined or none of them is defined.
134+
if (keychainProp || signProp || encryptProp) && !(keychainProp && signProp && encryptProp) {
135+
return errors.Errorf("%s platform does not specify correctly default sign and encryption keys", targetPlatform.Platform)
136+
}
137+
129138
ctx.BuildProperties = buildProperties
130139

131140
return nil

0 commit comments

Comments
 (0)