diff --git a/arduino/errors.go b/arduino/errors.go index b1de65205dd..981af32fb2d 100644 --- a/arduino/errors.go +++ b/arduino/errors.go @@ -698,21 +698,22 @@ func (e *TempDirCreationFailedError) ToRPCStatus() *status.Status { return status.New(codes.Unavailable, e.Error()) } -// TempFileCreationFailedError is returned if a temp file could not be created -type TempFileCreationFailedError struct { - Cause error +// FileCreationFailedError is returned if a file could not be created +type FileCreationFailedError struct { + Message string + Cause error } -func (e *TempFileCreationFailedError) Error() string { - return composeErrorMsg(tr("Cannot create temp file"), e.Cause) +func (e *FileCreationFailedError) Error() string { + return composeErrorMsg(e.Message, e.Cause) } -func (e *TempFileCreationFailedError) Unwrap() error { +func (e *FileCreationFailedError) Unwrap() error { return e.Cause } // ToRPCStatus converts the error into a *status.Status -func (e *TempFileCreationFailedError) ToRPCStatus() *status.Status { +func (e *FileCreationFailedError) ToRPCStatus() *status.Status { return status.New(codes.Unavailable, e.Error()) } diff --git a/cli/cli.go b/cli/cli.go index 72556e2d88a..eabfe68a170 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -34,6 +34,7 @@ import ( "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/cli/generatedocs" "github.com/arduino/arduino-cli/cli/globals" + "github.com/arduino/arduino-cli/cli/keys" "github.com/arduino/arduino-cli/cli/lib" "github.com/arduino/arduino-cli/cli/monitor" "github.com/arduino/arduino-cli/cli/outdated" @@ -93,6 +94,7 @@ func createCliCommandTree(cmd *cobra.Command) { cmd.AddCommand(core.NewCommand()) cmd.AddCommand(daemon.NewCommand()) cmd.AddCommand(generatedocs.NewCommand()) + cmd.AddCommand(keys.NewCommand()) cmd.AddCommand(lib.NewCommand()) cmd.AddCommand(monitor.NewCommand()) cmd.AddCommand(outdated.NewCommand()) diff --git a/cli/keys/generate.go b/cli/keys/generate.go new file mode 100644 index 00000000000..d3cca24002d --- /dev/null +++ b/cli/keys/generate.go @@ -0,0 +1,88 @@ +// This file is part of arduino-cli. +// +// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. + +package keys + +import ( + "context" + "os" + + "github.com/arduino/arduino-cli/cli/errorcodes" + "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-cli/commands/keys" + rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + keyName []string // Name of the custom keys to generate. Can be used multiple times for multiple security keys. + algorithmType string // Algorithm type to use + keysKeychain string // Path of the dir where to save the custom keys +) + +func initGenerateCommand() *cobra.Command { + generateCommand := &cobra.Command{ + Use: "generate", + Short: tr("Generate the security keys."), + Long: tr("Generate the security keys required for secure boot"), + Example: "" + + " " + os.Args[0] + " keys generate -t ecdsa-p256 --key-name ecdsa-p256-signing-key.pem --key-name ecdsa-p256-encrypt-key.pem --keys-keychain /home/user/Arduino/MyKeys\n" + + " " + os.Args[0] + " keys generate --key-name ecdsa-p256-signing-key.pem", + Args: cobra.NoArgs, + Run: runGenerateCommand, + } + + generateCommand.Flags().StringVarP(&algorithmType, "type", "t", "ecdsa-p256", tr("Algorithm type to use")) + generateCommand.Flags().StringArrayVar(&keyName, "key-name", []string{}, tr("Name of the custom key to generate. Can be used multiple times for multiple security keys.")) + generateCommand.MarkFlagRequired("key-name") + generateCommand.RegisterFlagCompletionFunc("key-name", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + defaultKeyNames := []string{"ecdsa-p256-signing-key.pem", "ecdsa-p256-encrypt-key.pem"} + return defaultKeyNames, cobra.ShellCompDirectiveDefault + }) + generateCommand.Flags().StringVar(&keysKeychain, "keys-keychain", "", tr("The path of the dir where to save the custom keys")) + + return generateCommand +} + +func runGenerateCommand(command *cobra.Command, args []string) { + + logrus.Info("Executing `arduino-cli keys generate`") + + resp, err := keys.Generate(context.Background(), &rpc.KeysGenerateRequest{ + AlgorithmType: algorithmType, + KeyName: keyName, + KeysKeychain: keysKeychain, + }) + if err != nil { + feedback.Errorf(tr("Error during generate: %v"), err) + os.Exit(errorcodes.ErrGeneric) + } + feedback.PrintResult(result{resp}) +} + +// output from this command requires special formatting, let's create a dedicated +// feedback.Result implementation +type result struct { + Keychain *rpc.KeysGenerateResponse `json:"keys_keychain"` +} + +func (dr result) Data() interface{} { + return dr.Keychain +} + +func (dr result) String() string { + return (tr("Keys created in: %s", dr.Keychain.KeysKeychain)) +} diff --git a/cli/keys/keys.go b/cli/keys/keys.go new file mode 100644 index 00000000000..320237689f0 --- /dev/null +++ b/cli/keys/keys.go @@ -0,0 +1,39 @@ +// This file is part of arduino-cli. +// +// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. + +package keys + +import ( + "os" + + "github.com/arduino/arduino-cli/i18n" + "github.com/spf13/cobra" +) + +var tr = i18n.Tr + +// NewCommand created a new `keys` command +func NewCommand() *cobra.Command { + keysCommand := &cobra.Command{ + Use: "keys", + Short: tr("Arduino keys commands."), + Long: tr("Arduino keys commands. Useful to operate on security keys"), + Example: " " + os.Args[0] + " keys generate --key-name ecdsa-p256-signing-key.pem --keys-keychain /home/user/Arduino/MyKeys", + } + + keysCommand.AddCommand(initGenerateCommand()) + + return keysCommand +} diff --git a/commands/instances.go b/commands/instances.go index cf336ceb48d..542feea6b87 100644 --- a/commands/instances.go +++ b/commands/instances.go @@ -469,9 +469,9 @@ func UpdateIndex(ctx context.Context, req *rpc.UpdateIndexRequest, downloadCB Do var tmp *paths.Path if tmpFile, err := ioutil.TempFile("", ""); err != nil { - return nil, &arduino.TempFileCreationFailedError{Cause: err} + return nil, &arduino.FileCreationFailedError{Message: tr("Cannot create temp file"), Cause: err} } else if err := tmpFile.Close(); err != nil { - return nil, &arduino.TempFileCreationFailedError{Cause: err} + return nil, &arduino.FileCreationFailedError{Message: tr("Cannot create temp file"), Cause: err} } else { tmp = paths.New(tmpFile.Name()) } @@ -502,9 +502,9 @@ func UpdateIndex(ctx context.Context, req *rpc.UpdateIndexRequest, downloadCB Do URLSig.Path += ".sig" if t, err := ioutil.TempFile("", ""); err != nil { - return nil, &arduino.TempFileCreationFailedError{Cause: err} + return nil, &arduino.FileCreationFailedError{Message: tr("Cannot create temp file"), Cause: err} } else if err := t.Close(); err != nil { - return nil, &arduino.TempFileCreationFailedError{Cause: err} + return nil, &arduino.FileCreationFailedError{Message: tr("Cannot create temp file"), Cause: err} } else { tmpSig = paths.New(t.Name()) } diff --git a/commands/keys/generate.go b/commands/keys/generate.go new file mode 100644 index 00000000000..7d68ed5d24b --- /dev/null +++ b/commands/keys/generate.go @@ -0,0 +1,228 @@ +// This file is part of arduino-cli. +// +// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. + +package keys + +import ( + "bytes" + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "os" + "strings" + + "github.com/arduino/arduino-cli/arduino" + "github.com/arduino/arduino-cli/i18n" + rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1" + "github.com/arduino/go-paths-helper" +) + +var tr = i18n.Tr + +// Generate creates a new set of security keys via gRPC +func Generate(ctx context.Context, req *rpc.KeysGenerateRequest) (*rpc.KeysGenerateResponse, error) { + // check if the keychain is passed as argument + keysKeychainDir := paths.New(req.GetKeysKeychain()) + if keysKeychainDir == nil { + keysKeychainDir = paths.New(".") + keysKeychainDir, _ = keysKeychainDir.Abs() + } + pathExists, err := keysKeychainDir.ExistCheck() + if !pathExists { + if err = keysKeychainDir.MkdirAll(); err != nil { + return nil, &arduino.PermissionDeniedError{Message: tr("Cannot create directory"), Cause: err} + } + } + if err != nil { + return nil, &arduino.PermissionDeniedError{Message: tr("Cannot verify if the directory %s exists", keysKeychainDir), Cause: err} + } + + // check the number of keynames passed + if len(req.GetKeyName()) == 0 { + return nil, &arduino.InvalidArgumentError{Message: tr("Wrong number of key names specified, please use at least one")} + } + + // check if the algorithm has been specified, set the default if not + algorithmType := req.GetAlgorithmType() + if algorithmType == "" { + algorithmType = "ecdsa-p256" + } + + // do the actual key generation + for _, key := range req.GetKeyName() { + // build the path where to save the security key + privKeyPath := keysKeychainDir.Join("priv_" + key) + pubKeyPath := keysKeychainDir.Join("pub_" + key) + err = doKeyGen(privKeyPath, pubKeyPath, algorithmType) + if err != nil { + return nil, &arduino.FileCreationFailedError{Message: tr("Cannot create file"), Cause: err} + } + } + return &rpc.KeysGenerateResponse{KeysKeychain: keysKeychainDir.String()}, nil +} + +// adapted from https://git.furworks.de/Zephyr/mcuboot/src/commit/3869e760901a27adff47ccaea803a42f1b0169c0/imgtool/imgtool.go#L69 +// doKeyGen will take the paths of the public and private keys to write and will generate keys according to keyType +func doKeyGen(privKeyPath, pubKeyPath *paths.Path, keyType string) (err error) { + var priv509, pubAsn1 []byte + var privPemType, pubPemType string + switch keyType { + case "ecdsa-p256": + priv509, pubAsn1, err = genEcdsaP256() + privPemType = "PRIVATE KEY" + pubPemType = "PUBLIC KEY" + // support for multiple algorithms can be added there + default: + err = errors.New(tr("Unsupported algorithm: %s", keyType)) + } + + if err != nil { + return err + } + keysKeychainDir := privKeyPath.Parent() + + // create the private key file + if privKeyPath.Exist() { + return errors.New(tr("File already exists: %s", privKeyPath)) + } + privKeyFile, err := privKeyPath.Create() + if err != nil { + return err + } + defer privKeyFile.Close() + + // create the public key file + if pubKeyPath.Exist() { + return errors.New(tr("File already exists: %s", pubKeyPath)) + } + pubKeyFile, err := pubKeyPath.Create() + if err != nil { + return err + } + defer pubKeyFile.Close() + + // create the private header files + privHeader := strings.TrimSuffix(privKeyPath.Base(), privKeyPath.Ext()) + privHeaderPath := keysKeychainDir.Join(privHeader + ".h") + if privHeaderPath.Exist() { + return errors.New(tr("File already exists: %s", privHeaderPath)) + } + privHeaderFile, err := privHeaderPath.Create() + if err != nil { + return err + } + defer privHeaderFile.Close() + + // create the public header files + pubHeader := strings.TrimSuffix(pubKeyPath.Base(), pubKeyPath.Ext()) + pubHeaderPath := keysKeychainDir.Join(pubHeader + ".h") + if pubHeaderPath.Exist() { + return errors.New(tr("File already exists: %s", pubHeaderPath)) + } + pubHeaderFile, err := pubHeaderPath.Create() + if err != nil { + return err + } + defer pubHeaderFile.Close() + + privBlock := pem.Block{ + Type: privPemType, + Bytes: priv509, + } + err = pem.Encode(privKeyFile, &privBlock) + if err != nil { + return err + } + err = genCFile(privHeaderFile, priv509, "priv") + if err != nil { + return err + } + + pubBlock := pem.Block{ + Type: pubPemType, + Bytes: pubAsn1, + } + err = pem.Encode(pubKeyFile, &pubBlock) + if err != nil { + return err + } + err = genCFile(pubHeaderFile, pubAsn1, "pub") + if err != nil { + return err + } + return nil +} + +// genEcdsaP256 will generate private and public ecdsap256 keypair and return them +// it will encode the private PKCS #8, ASN.1 DER form, and for the public will use the PKIX, ASN.1 DER form +// adapted from https://git.furworks.de/Zephyr/mcuboot/src/commit/3869e760901a27adff47ccaea803a42f1b0169c0/imgtool/imgtool.go#L165 +func genEcdsaP256() ([]byte, []byte, error) { + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, nil, err + } + + keyPriv, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + return nil, nil, err + } + + pub := priv.Public() + keyPub, err := x509.MarshalPKIXPublicKey(pub) + if err != nil { + return nil, nil, err + } + + return keyPriv, keyPub, nil +} + +// genCFile will take a file as input, the byte slice that represents the key and the type of the key. +// It will then write the file and report an error if one is found +func genCFile(file *os.File, bytes []byte, t string) (err error) { + fileContent := fmt.Sprintf(`/* Autogenerated, do not edit */ +const unsigned char rsa_%s_key[] = { + // clang-format off +%s + // clang-format on +}; +const unsigned int ec_%s_key_len = %d; +`, t, formatCData(bytes), t, len(bytes)) + _, err = file.WriteString(fileContent) + return err +} + +// formatCData will take the byte slice representing a key and format correctly as "C" data. It will return it as a string +// taken and adapted from https://git.furworks.de/Zephyr/mcuboot/src/commit/3869e760901a27adff47ccaea803a42f1b0169c0/imgtool/imgtool.go#L313 +func formatCData(data []byte) string { + buf := new(bytes.Buffer) + indText := strings.Repeat(" ", 2) + for i, b := range data { + if i%8 == 0 { + fmt.Fprintf(buf, indText) + } else { + fmt.Fprintf(buf, " ") + } + fmt.Fprintf(buf, "0x%02x,", b) + if i%8 == 7 { + fmt.Fprintf(buf, "\n") + } + } + return buf.String() +} diff --git a/poetry.lock b/poetry.lock index 5fc44563030..a6a6292d984 100644 --- a/poetry.lock +++ b/poetry.lock @@ -82,6 +82,21 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "ecdsa" +version = "0.17.0" +description = "ECDSA cryptographic signature library (pure python)" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[package.dependencies] +six = ">=1.9.0" + +[package.extras] +gmpy = ["gmpy"] +gmpy2 = ["gmpy2"] + [[package]] name = "execnet" version = "1.9.0" @@ -632,7 +647,7 @@ python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*" name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" @@ -728,7 +743,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = ">=3.8, !=3.9.7" -content-hash = "73fdbf64d719d49eb1ab1dd0850172d676fd9fa88dca88f397879688e1bbf8f8" +content-hash = "01e58fbb5e356ac9623032c4b5a7d882ecc74b24283cd63aa6367c346475aa69" [metadata.files] atomicwrites = [ @@ -759,6 +774,10 @@ colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] +ecdsa = [ + {file = "ecdsa-0.17.0-py2.py3-none-any.whl", hash = "sha256:5cf31d5b33743abe0dfc28999036c849a69d548f994b535e527ee3cb7f3ef676"}, + {file = "ecdsa-0.17.0.tar.gz", hash = "sha256:b9f500bb439e4153d0330610f5d26baaf18d17b8ced1bc54410d189385ea68aa"}, +] execnet = [ {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"}, {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"}, @@ -813,12 +832,28 @@ markdown = [ {file = "Markdown-3.3.6.tar.gz", hash = "sha256:76df8ae32294ec39dcf89340382882dfa12975f87f45c3ed1ecdb1e8cefc7006"}, ] markupsafe = [ + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, @@ -827,14 +862,27 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, @@ -844,6 +892,12 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, diff --git a/pyproject.toml b/pyproject.toml index a6f06fa9494..c293efdef90 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ filelock = "^3.0.12" pytest-xdist = "^2.1.0" pytest_httpserver = "^0.3.5" GitPython = "^3.1.12" +ecdsa = "^0.17.0" [tool.poetry.dev-dependencies] mkdocs = "^1.2.1" diff --git a/rpc/cc/arduino/cli/commands/v1/keys.pb.go b/rpc/cc/arduino/cli/commands/v1/keys.pb.go new file mode 100644 index 00000000000..0b12633eede --- /dev/null +++ b/rpc/cc/arduino/cli/commands/v1/keys.pb.go @@ -0,0 +1,252 @@ +// This file is part of arduino-cli. +// +// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.17.3 +// source: cc/arduino/cli/commands/v1/keys.proto + +package commands + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type KeysGenerateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // algorithm_type is the algorithm type we want to use to generate the keys + AlgorithmType string `protobuf:"bytes,1,opt,name=algorithm_type,json=algorithmType,proto3" json:"algorithm_type,omitempty"` + // key_name is the list of keynames we want to generate + KeyName []string `protobuf:"bytes,2,rep,name=key_name,json=keyName,proto3" json:"key_name,omitempty"` + // keys_keychain is the location of the keychain we want to save the keys to + KeysKeychain string `protobuf:"bytes,3,opt,name=keys_keychain,json=keysKeychain,proto3" json:"keys_keychain,omitempty"` +} + +func (x *KeysGenerateRequest) Reset() { + *x = KeysGenerateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_cc_arduino_cli_commands_v1_keys_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *KeysGenerateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*KeysGenerateRequest) ProtoMessage() {} + +func (x *KeysGenerateRequest) ProtoReflect() protoreflect.Message { + mi := &file_cc_arduino_cli_commands_v1_keys_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use KeysGenerateRequest.ProtoReflect.Descriptor instead. +func (*KeysGenerateRequest) Descriptor() ([]byte, []int) { + return file_cc_arduino_cli_commands_v1_keys_proto_rawDescGZIP(), []int{0} +} + +func (x *KeysGenerateRequest) GetAlgorithmType() string { + if x != nil { + return x.AlgorithmType + } + return "" +} + +func (x *KeysGenerateRequest) GetKeyName() []string { + if x != nil { + return x.KeyName + } + return nil +} + +func (x *KeysGenerateRequest) GetKeysKeychain() string { + if x != nil { + return x.KeysKeychain + } + return "" +} + +type KeysGenerateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // keys_keychain contains the location of keys generated + KeysKeychain string `protobuf:"bytes,1,opt,name=keys_keychain,json=keysKeychain,proto3" json:"keys_keychain,omitempty"` +} + +func (x *KeysGenerateResponse) Reset() { + *x = KeysGenerateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_cc_arduino_cli_commands_v1_keys_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *KeysGenerateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*KeysGenerateResponse) ProtoMessage() {} + +func (x *KeysGenerateResponse) ProtoReflect() protoreflect.Message { + mi := &file_cc_arduino_cli_commands_v1_keys_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use KeysGenerateResponse.ProtoReflect.Descriptor instead. +func (*KeysGenerateResponse) Descriptor() ([]byte, []int) { + return file_cc_arduino_cli_commands_v1_keys_proto_rawDescGZIP(), []int{1} +} + +func (x *KeysGenerateResponse) GetKeysKeychain() string { + if x != nil { + return x.KeysKeychain + } + return "" +} + +var File_cc_arduino_cli_commands_v1_keys_proto protoreflect.FileDescriptor + +var file_cc_arduino_cli_commands_v1_keys_proto_rawDesc = []byte{ + 0x0a, 0x25, 0x63, 0x63, 0x2f, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2f, 0x63, 0x6c, 0x69, + 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6b, 0x65, 0x79, + 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, + 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, + 0x2e, 0x76, 0x31, 0x22, 0x7c, 0x0a, 0x13, 0x4b, 0x65, 0x79, 0x73, 0x47, 0x65, 0x6e, 0x65, 0x72, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, + 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0d, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, + 0x6b, 0x65, 0x79, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6b, 0x65, 0x79, 0x73, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x22, 0x3b, 0x0a, 0x14, 0x4b, 0x65, 0x79, 0x73, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6b, 0x65, 0x79, + 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x6b, 0x65, 0x79, 0x73, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x48, + 0x5a, 0x46, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x72, 0x64, + 0x75, 0x69, 0x6e, 0x6f, 0x2f, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2d, 0x63, 0x6c, 0x69, + 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x63, 0x2f, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2f, + 0x63, 0x6c, 0x69, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2f, 0x76, 0x31, 0x3b, + 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_cc_arduino_cli_commands_v1_keys_proto_rawDescOnce sync.Once + file_cc_arduino_cli_commands_v1_keys_proto_rawDescData = file_cc_arduino_cli_commands_v1_keys_proto_rawDesc +) + +func file_cc_arduino_cli_commands_v1_keys_proto_rawDescGZIP() []byte { + file_cc_arduino_cli_commands_v1_keys_proto_rawDescOnce.Do(func() { + file_cc_arduino_cli_commands_v1_keys_proto_rawDescData = protoimpl.X.CompressGZIP(file_cc_arduino_cli_commands_v1_keys_proto_rawDescData) + }) + return file_cc_arduino_cli_commands_v1_keys_proto_rawDescData +} + +var file_cc_arduino_cli_commands_v1_keys_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_cc_arduino_cli_commands_v1_keys_proto_goTypes = []interface{}{ + (*KeysGenerateRequest)(nil), // 0: cc.arduino.cli.commands.v1.KeysGenerateRequest + (*KeysGenerateResponse)(nil), // 1: cc.arduino.cli.commands.v1.KeysGenerateResponse +} +var file_cc_arduino_cli_commands_v1_keys_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_cc_arduino_cli_commands_v1_keys_proto_init() } +func file_cc_arduino_cli_commands_v1_keys_proto_init() { + if File_cc_arduino_cli_commands_v1_keys_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_cc_arduino_cli_commands_v1_keys_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*KeysGenerateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_cc_arduino_cli_commands_v1_keys_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*KeysGenerateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_cc_arduino_cli_commands_v1_keys_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_cc_arduino_cli_commands_v1_keys_proto_goTypes, + DependencyIndexes: file_cc_arduino_cli_commands_v1_keys_proto_depIdxs, + MessageInfos: file_cc_arduino_cli_commands_v1_keys_proto_msgTypes, + }.Build() + File_cc_arduino_cli_commands_v1_keys_proto = out.File + file_cc_arduino_cli_commands_v1_keys_proto_rawDesc = nil + file_cc_arduino_cli_commands_v1_keys_proto_goTypes = nil + file_cc_arduino_cli_commands_v1_keys_proto_depIdxs = nil +} diff --git a/rpc/cc/arduino/cli/commands/v1/keys.proto b/rpc/cc/arduino/cli/commands/v1/keys.proto new file mode 100644 index 00000000000..2b9aced485e --- /dev/null +++ b/rpc/cc/arduino/cli/commands/v1/keys.proto @@ -0,0 +1,34 @@ +// This file is part of arduino-cli. +// +// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. + +syntax = "proto3"; + +package cc.arduino.cli.commands.v1; + +option go_package = "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1;commands"; + +message KeysGenerateRequest { + // algorithm_type is the algorithm type we want to use to generate the keys + string algorithm_type = 1; + // key_name is the list of keynames we want to generate + repeated string key_name = 2; + // keys_keychain is the location of the keychain we want to save the keys to + string keys_keychain = 3; +} + +message KeysGenerateResponse { + // keys_keychain contains the location of keys generated + string keys_keychain = 1; +} \ No newline at end of file diff --git a/test/test_keys.py b/test/test_keys.py new file mode 100644 index 00000000000..0eb69441591 --- /dev/null +++ b/test/test_keys.py @@ -0,0 +1,67 @@ +# This file is part of arduino-cli. +# +# Copyright 2020 ARDUINO SA (http://www.arduino.cc/) +# +# This software is released under the GNU General Public License version 3, +# which covers the main part of arduino-cli. +# The terms of this license can be found at: +# https://www.gnu.org/licenses/gpl-3.0.en.html +# +# You can be released from the requirements of the above licenses by purchasing +# a commercial license. Buying such a license is mandatory if you want to modify or +# otherwise use the software for commercial activities involving the Arduino +# software without disclosing the source code of your own applications. To purchase +# a commercial license, send an email to license@arduino.cc. + +from ecdsa import VerifyingKey, SigningKey +from pathlib import Path + + +def test_keys_generate(run_command, working_dir): + # Create security keys without specifying the keychain dir (by default in the working directory) + sign_key_name = "ecdsa-p256-signing-key.pem" + sign_header_name = "ecdsa-p256-signing-key.h" + result = run_command(["keys", "generate", "--key-name", sign_key_name]) + assert result.ok + assert f"Keys created in: {working_dir}" in result.stdout + assert Path(working_dir, f"pub_{sign_key_name}").is_file() + assert Path(working_dir, f"priv_{sign_key_name}").is_file() + assert Path(working_dir, f"pub_{sign_header_name}").is_file() + assert Path(working_dir, f"priv_{sign_header_name}").is_file() + + # Overwrite security keys + result = run_command(["keys", "generate", "--key-name", sign_key_name]) + assert result.failed + assert f"Error during generate: Cannot create file: File already exists: {working_dir}" in result.stderr + + # Create security keys in specified directory + keychain_name = "keychain" + keychain_path = Path(working_dir, keychain_name) + result = run_command(["keys", "generate", "--key-name", sign_key_name, "--keys-keychain", keychain_path]) + assert result.ok + assert f"Keys created in: {keychain_path}" in result.stdout + assert Path(keychain_path, f"pub_{sign_key_name}").is_file() + assert Path(keychain_path, f"priv_{sign_key_name}").is_file() + assert Path(keychain_path, f"pub_{sign_header_name}").is_file() + assert Path(keychain_path, f"priv_{sign_header_name}").is_file() + + # verify that keypar is valid by signing a message and then verify it + with open(f"{keychain_path}/pub_{sign_key_name}") as f: + vk = VerifyingKey.from_pem(f.read()) + with open(f"{keychain_path}/priv_{sign_key_name}") as f1: + sk = SigningKey.from_pem(f1.read()) + + signature = sk.sign(b"message") + assert vk.verify(signature, b"message") + + # Create security keys without specifying --key-name + result = run_command(["keys", "generate", "--keys-keychain", keychain_path]) + assert result.failed + assert 'Error: required flag(s) "key-name" not set' in result.stderr + + # Create security keys with unsupported algorithm + result = run_command( + ["keys", "generate", "--key-name", sign_key_name, "--keys-keychain", keychain_path, "-t", "rsa"] + ) + assert result.failed + assert "Error during generate: Cannot create file: Unsupported algorithm: rsa" in result.stderr