From 26396882740c1a2460e54d773b155c396454df9f Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Sun, 15 Nov 2020 17:55:41 -0500 Subject: [PATCH] signer-ed25519: DER-encode the public and private keys. The first version of the tool just put the raw key bytes into the PEM block, which is nonstandard. Use PCKS8 and PKIX to encode the private and public parts, respectively, which will be compatible with OpenSSL 3.0. This uses a separate PEM header to disambiguate between the old form and the new form. --- dev/asn1-wrap-ed25519.go | 3 +- dev/openssl-sign-ed25519.cc | 275 ------------------------------------ dev/signer-ed25519.go | 61 ++++++-- 3 files changed, 50 insertions(+), 289 deletions(-) delete mode 100644 dev/openssl-sign-ed25519.cc diff --git a/dev/asn1-wrap-ed25519.go b/dev/asn1-wrap-ed25519.go index 7fa103b..8d5c131 100644 --- a/dev/asn1-wrap-ed25519.go +++ b/dev/asn1-wrap-ed25519.go @@ -14,7 +14,6 @@ * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ - /* asn1-wrap-ed25519 takes a raw private key from signer-ed25519 and reencodes it as PEM PKCS8 (ASN.1). @@ -60,7 +59,7 @@ func main() { os.Exit(1) } - asn1Pem := &pem.Block{Type: "PRIVATE KEY", Bytes: asn1Bytes} + asn1Pem := &pem.Block{Type: "ED25519 PRIVATE KEY", Bytes: asn1Bytes} err = pem.Encode(os.Stdout, asn1Pem) if err != nil { diff --git a/dev/openssl-sign-ed25519.cc b/dev/openssl-sign-ed25519.cc deleted file mode 100644 index 10cbde3..0000000 --- a/dev/openssl-sign-ed25519.cc +++ /dev/null @@ -1,275 +0,0 @@ -// clang++ -o openssl-sign-ed25519 openssl-sign-ed25519.cc -I/opt/local/include -L/opt/local/lib -lcrypto -std=c++14 -/* - * MacGDBp - * Copyright (c) 2019, Blue Static - * - * This program is free software; you can redistribute it and/or modify it under the terms of the GNU - * General Public License as published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without - * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; if not, - * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - */ - - -/* -openssl-sign-ed25519 provides a sign/verify interface for ED25519 operations. - -Until https://github.com/openssl/openssl/issues/6988 is fixed, OpenSSL cannot -be used to generate and verify signatures of files using ED25519 keys. Sparkle -only supports ED25519 keys, so this tool is used to bridge the gap. - -Usage: - - Get base64 signature: - - ./openssl-sign-ed25519 --sign --key /path/to/key.pem --file file.zip | openssl enc -a -A - - Verify signature: - - cat sig-b64 | openssl dec -a | ./openssl-sign-ed25519 --verify - --key /path/to/key.pem --file file.zip - -Usage Notes: - - - Encrypted private keys are not supported as no password input is provided. - -Implementation Notes: - - - No resources are freed since this is a one-shot tool. -*/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -const option kOptions[] = { - {"key", required_argument, nullptr, 'k'}, - {"sign", no_argument, nullptr, 's'}, - {"verify", required_argument, nullptr, 'v'}, - {"file", required_argument, nullptr, 'i'}, - {nullptr, 0, nullptr, 0}, -}; - -void Usage(const char* prog) { - fprintf(stderr, "%s: ", prog); - for (const auto& opt : kOptions) { - if (!opt.name) - continue; - - if (opt.has_arg == optional_argument) - fprintf(stderr, "["); - fprintf(stderr, "--%s", opt.name); - if (opt.has_arg == optional_argument) - fprintf(stderr, "]"); - - if (opt.has_arg != no_argument) - fprintf(stderr, " <%s>", opt.name); - - fprintf(stderr, " "); - } - fprintf(stderr, "\n"); -} - -void CryptoError(const char* msg) { - unsigned long error = ERR_get_error(); - char buf[256]; - ERR_error_string(error, buf); - fprintf(stderr, "%s: -%ld %s\n", msg, error, buf); -} - -bool ReadEntireFile(const char* path, std::vector* data) { - int fd = -1; - if (strncmp(path, "-", 1) == 0) { - fd = STDIN_FILENO; - } else { - fd = open(path, O_RDONLY); - if (fd < 0) { - perror("open"); - return false; - } - } - - const size_t buffer_size = getpagesize(); - ssize_t bytes_read = 0; - do { - size_t current_size = data->size(); - data->resize(current_size + buffer_size); - - ssize_t bytes_read = read(fd, &(*data)[current_size], buffer_size); - if (bytes_read < 0) { - perror("read"); - return false; - } else if (bytes_read >= 0) { - data->resize(current_size + bytes_read); - } - } while (bytes_read > 0); - - return true; -} - -bool LoadKeyAndCreateContext(const char* keyfile_path, EVP_PKEY** pkey, EVP_PKEY_CTX** pctx) { - FILE* keyfile = fopen(keyfile_path, "r"); - if (!keyfile) { - perror("fopen keyfile"); - return false; - } - - *pkey = PEM_read_PrivateKey(keyfile, /*keyout=*/nullptr, /*password=*/nullptr, /*ucontext=*/nullptr); - if (!*pkey) { - CryptoError("Failed to read private key"); - return false; - } - - *pctx = EVP_PKEY_CTX_new(*pkey, nullptr); - if (!pctx) { - CryptoError("Failed to create pkey context"); - return false; - } - - return true; -} - -bool SignFile(EVP_PKEY* pkey, EVP_PKEY_CTX* pctx, const char* infile_path) { - int rv; - - std::vector data; - if (!ReadEntireFile(infile_path, &data)) - return false; - - EVP_MD_CTX* ctx = EVP_MD_CTX_new(); - rv = EVP_DigestSignInit(ctx, &pctx, /*type=*/nullptr, /*engine=*/nullptr, pkey); - if (rv != 1) { - CryptoError("Failed to initialize digest context"); - return false; - } - - size_t signature_length; - rv = EVP_DigestSign(ctx, nullptr, &signature_length, data.data(), data.size()); - if (rv != 1) { - CryptoError("Failed to sign - get length"); - return false; - } - - std::unique_ptr signature(new uint8_t[signature_length]); - rv = EVP_DigestSign(ctx, signature.get(), &signature_length, data.data(), data.size()); - if (rv < 0) { - CryptoError("Failed to sign"); - return false; - } - - for (size_t i = 0; i < signature_length; ) { - ssize_t written = write(STDOUT_FILENO, &signature.get()[i], signature_length - i); - if (written <= 0) { - perror("write"); - return false; - } - i += written; - } - - return true; -} - -bool VerifyFile(EVP_PKEY* pkey, EVP_PKEY_CTX* pctx, const char* sigfile_path, const char* infile_path) { - int rv; - - std::vector signature; - if (!ReadEntireFile(sigfile_path, &signature)) - return false; - - std::vector data; - if (!ReadEntireFile(infile_path, &data)) - return false; - - EVP_MD_CTX* ctx = EVP_MD_CTX_new(); - - rv = EVP_DigestVerifyInit(ctx, &pctx, /*type=*/nullptr, /*engine=*/nullptr, pkey); - if (rv != 1) { - CryptoError("Failed to initialize verify context"); - return false; - } - - rv = EVP_DigestVerify(ctx, signature.data(), signature.size(), data.data(), data.size()); - if (rv != 1) { - printf("Failed to verify data.\n"); - return false; - } - - printf("Verify OK.\n"); - - return true; -} - -int main(int argc, char* const argv[]) { - bool do_sign = false; - const char* keyfile = nullptr; - const char* sigfile = nullptr; - const char* infile = nullptr; - - int opt; - while ((opt = getopt_long(argc, argv, "ksvih", kOptions, nullptr)) != -1) { - switch (opt) { - case 's': - do_sign = true; - break; - case 'k': - keyfile = optarg; - break; - case 'v': - sigfile = optarg; - break; - case 'i': - infile = optarg; - break; - case 'h': - Usage(argv[0]); - return EXIT_SUCCESS; - default: - Usage(argv[0]); - return EXIT_FAILURE; - } - } - - if (!infile) { - fprintf(stderr, "No input file specified.\n"); - Usage(argv[0]); - return EXIT_FAILURE; - } - - if (!keyfile) { - fprintf(stderr, "Key must be specified.\n"); - Usage(argv[0]); - return EXIT_FAILURE; - } - - if (!(do_sign ^ (sigfile != nullptr))) { - fprintf(stderr, "Must specify one of --sign or --verify.\n"); - Usage(argv[0]); - return EXIT_FAILURE; - } - - EVP_PKEY* pkey; - EVP_PKEY_CTX* pctx; - if (!LoadKeyAndCreateContext(keyfile, &pkey, &pctx)) - return EXIT_FAILURE; - - bool ok = false; - - if (do_sign) - ok = SignFile(pkey, pctx, infile); - else - ok = VerifyFile(pkey, pctx, sigfile, infile); - - return ok ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/dev/signer-ed25519.go b/dev/signer-ed25519.go index b7e91f0..5bfe651 100644 --- a/dev/signer-ed25519.go +++ b/dev/signer-ed25519.go @@ -45,6 +45,7 @@ package main import ( "crypto" "crypto/ed25519" + "crypto/x509" "encoding/pem" "flag" "fmt" @@ -96,31 +97,55 @@ func main() { } func newKey() { - pub := &pem.Block{Type: "PUBLIC KEY"} - priv := &pem.Block{Type: "PRIVATE KEY"} - var err error - pub.Bytes, priv.Bytes, err = ed25519.GenerateKey(nil) + pub, priv, err := ed25519.GenerateKey(nil) if err != nil { fmt.Fprintf(os.Stderr, "Failed to generate new key pair: %v.\n", err) os.Exit(1) } - if err := pem.Encode(os.Stdout, pub); err != nil { + pemPub := &pem.Block{Type: "ED25519 PUBLIC KEY"} + pemPriv := &pem.Block{Type: "ED25519 PRIVATE KEY"} + + pemPub.Bytes, err = x509.MarshalPKIXPublicKey(pub) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to marshal public key: %v.\n", err) + os.Exit(1) + } + + pemPriv.Bytes, err = x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to marshal private key: %v.\n", err) + os.Exit(1) + } + + if err := pem.Encode(os.Stdout, pemPub); err != nil { fmt.Fprintf(os.Stderr, "Failed to write public key: %v.\n", err) } - if err := pem.Encode(os.Stdout, priv); err != nil { + if err := pem.Encode(os.Stdout, pemPriv); err != nil { fmt.Fprintf(os.Stderr, "Failed to write private key: %v.\n", err) } } func sign(keyPem *pem.Block) { - if keyPem.Type != "PRIVATE KEY" { + var key ed25519.PrivateKey + + // The first version of this tool simply output the raw key bytes into the + // PEM. To be compatible with openssl, the tool now uses PKCS8 to encode + // the private key. + if keyPem.Type == "PRIVATE KEY" { + key = ed25519.PrivateKey(keyPem.Bytes) + } else if keyPem.Type == "ED25519 PRIVATE KEY" { + if derKey, err := x509.ParsePKCS8PrivateKey(keyPem.Bytes); err == nil { + key = derKey.(ed25519.PrivateKey) + } else { + fmt.Fprintf(os.Stderr, "Failed to parse private key: %v.\n", err) + os.Exit(1) + } + } else { fmt.Fprintf(os.Stderr, "Signing expects a private key.\n") os.Exit(1) } - key := ed25519.PrivateKey(keyPem.Bytes) - signature, err := key.Sign(nil, readInput(), crypto.Hash(0)) if err != nil { fmt.Fprintf(os.Stderr, "Failed to sign file: %v.\n", err) @@ -134,13 +159,25 @@ func sign(keyPem *pem.Block) { } func verify(keyPem *pem.Block) { - if keyPem.Type != "PUBLIC KEY" { + var key ed25519.PublicKey + + // The first version of this tool simply output the raw key bytes into the + // PEM. To be compatible with openssl, the tool now DER-encodes the public + // key. + if keyPem.Type == "PUBLIC KEY" { + key = ed25519.PublicKey(keyPem.Bytes) + } else if keyPem.Type == "ED25519 PUBLIC KEY" { + if derKey, err := x509.ParsePKIXPublicKey(keyPem.Bytes); err == nil { + key = derKey.(ed25519.PublicKey) + } else { + fmt.Fprintf(os.Stderr, "Failed to parse public key: %v.\n", err) + os.Exit(1) + } + } else { fmt.Fprintf(os.Stderr, "Verifying expects a public key.\n") os.Exit(1) } - key := ed25519.PublicKey(keyPem.Bytes) - signature, err := ioutil.ReadFile(*sigPath) if err != nil { fmt.Fprintf(os.Stderr, "Failed to read signature file: %v.\n", err) -- 2.22.5