Bump project version to 212.1.
[macgdbp.git] / dev / signer-ed25519.go
1 /*
2 * MacGDBp
3 * Copyright (c) 2019, Blue Static <https://www.bluestatic.org>
4 *
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
10 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17 /*
18 signer-ed25519 provides a sign/verify interface for ED25519 operations.
19
20 Until https://github.com/openssl/openssl/issues/6988 is fixed, OpenSSL cannot
21 be used to generate and verify signatures of files using ED25519 keys. Sparkle
22 only supports ED25519 keys, so this tool is used to bridge the gap.
23
24 Usage:
25
26 Create a new key pair:
27
28 ./signer-ed25519 -new-key
29
30 Get base64 signature:
31
32 ./signer-ed25519 -sign -key privkey.pem -file file.zip | openssl enc -a -A
33
34 Verify signature:
35
36 ./signer-ed25519 -verify -signature <(openssl enc -d -a sig.b64) -key pubkey.pem -file file.zip
37
38 Usage Notes:
39
40 - Encrypted private keys are not supported as no password input is provided.
41 */
42
43 package main
44
45 import (
46 "crypto"
47 "crypto/ed25519"
48 "crypto/x509"
49 "encoding/pem"
50 "flag"
51 "fmt"
52 "io/ioutil"
53 "os"
54 )
55
56 var (
57 keyPath = flag.String("key", "", "Path to the key file.")
58 inPath = flag.String("file", "", "Path to the file to sign/verify.")
59 sigPath = flag.String("signature", "", "Path to the signature file.")
60 doSign = flag.Bool("sign", false, "Sign the given file.")
61 doVerify = flag.Bool("verify", false, "Verify the given file.")
62 doNewKey = flag.Bool("new-key", false, "Generate a new keypair.")
63 )
64
65 func main() {
66 flag.Parse()
67
68 if *doNewKey {
69 newKey()
70 os.Exit(0)
71 }
72
73 if (!*doSign && !*doVerify) || (*doSign && *doVerify) {
74 fmt.Fprintf(os.Stderr, "Must specify either -sign or -verify.\n")
75 os.Exit(1)
76 }
77
78 keyPemData, err := ioutil.ReadFile(*keyPath)
79 if err != nil {
80 fmt.Fprintf(os.Stderr, "Failed to read key: %v.\n", err)
81 os.Exit(1)
82 }
83
84 keyPem, _ := pem.Decode(keyPemData)
85 if keyPem == nil {
86 fmt.Fprintf(os.Stderr, "Failed to decode PEM.\n", err)
87 os.Exit(1)
88 }
89
90 if *doSign {
91 sign(keyPem)
92 }
93
94 if *doVerify {
95 verify(keyPem)
96 }
97 }
98
99 func newKey() {
100 pub, priv, err := ed25519.GenerateKey(nil)
101 if err != nil {
102 fmt.Fprintf(os.Stderr, "Failed to generate new key pair: %v.\n", err)
103 os.Exit(1)
104 }
105
106 pemPub := &pem.Block{Type: "ED25519 PUBLIC KEY"}
107 pemPriv := &pem.Block{Type: "ED25519 PRIVATE KEY"}
108
109 pemPub.Bytes, err = x509.MarshalPKIXPublicKey(pub)
110 if err != nil {
111 fmt.Fprintf(os.Stderr, "Failed to marshal public key: %v.\n", err)
112 os.Exit(1)
113 }
114
115 pemPriv.Bytes, err = x509.MarshalPKCS8PrivateKey(priv)
116 if err != nil {
117 fmt.Fprintf(os.Stderr, "Failed to marshal private key: %v.\n", err)
118 os.Exit(1)
119 }
120
121 if err := pem.Encode(os.Stdout, pemPub); err != nil {
122 fmt.Fprintf(os.Stderr, "Failed to write public key: %v.\n", err)
123 }
124 if err := pem.Encode(os.Stdout, pemPriv); err != nil {
125 fmt.Fprintf(os.Stderr, "Failed to write private key: %v.\n", err)
126 }
127 }
128
129 func sign(keyPem *pem.Block) {
130 var key ed25519.PrivateKey
131
132 // The first version of this tool simply output the raw key bytes into the
133 // PEM. To be compatible with openssl, the tool now uses PKCS8 to encode
134 // the private key.
135 if keyPem.Type == "PRIVATE KEY" {
136 key = ed25519.PrivateKey(keyPem.Bytes)
137 } else if keyPem.Type == "ED25519 PRIVATE KEY" {
138 if derKey, err := x509.ParsePKCS8PrivateKey(keyPem.Bytes); err == nil {
139 key = derKey.(ed25519.PrivateKey)
140 } else {
141 fmt.Fprintf(os.Stderr, "Failed to parse private key: %v.\n", err)
142 os.Exit(1)
143 }
144 } else {
145 fmt.Fprintf(os.Stderr, "Signing expects a private key.\n")
146 os.Exit(1)
147 }
148
149 signature, err := key.Sign(nil, readInput(), crypto.Hash(0))
150 if err != nil {
151 fmt.Fprintf(os.Stderr, "Failed to sign file: %v.\n", err)
152 os.Exit(1)
153 }
154
155 if _, err := os.Stdout.Write(signature); err != nil {
156 fmt.Fprintf(os.Stderr, "Failed to write output: %v.\n", err)
157 os.Exit(1)
158 }
159 }
160
161 func verify(keyPem *pem.Block) {
162 var key ed25519.PublicKey
163
164 // The first version of this tool simply output the raw key bytes into the
165 // PEM. To be compatible with openssl, the tool now DER-encodes the public
166 // key.
167 if keyPem.Type == "PUBLIC KEY" {
168 key = ed25519.PublicKey(keyPem.Bytes)
169 } else if keyPem.Type == "ED25519 PUBLIC KEY" {
170 if derKey, err := x509.ParsePKIXPublicKey(keyPem.Bytes); err == nil {
171 key = derKey.(ed25519.PublicKey)
172 } else {
173 fmt.Fprintf(os.Stderr, "Failed to parse public key: %v.\n", err)
174 os.Exit(1)
175 }
176 } else {
177 fmt.Fprintf(os.Stderr, "Verifying expects a public key.\n")
178 os.Exit(1)
179 }
180
181 signature, err := ioutil.ReadFile(*sigPath)
182 if err != nil {
183 fmt.Fprintf(os.Stderr, "Failed to read signature file: %v.\n", err)
184 os.Exit(1)
185 }
186
187 if ed25519.Verify(key, readInput(), signature) {
188 fmt.Println("Verify OK.")
189 } else {
190 fmt.Println("Verify FAILED!")
191 }
192 }
193
194 func readInput() (fileData []byte) {
195 fileData, err := ioutil.ReadFile(*inPath)
196 if err != nil {
197 fmt.Fprintf(os.Stderr, "Failed to read input file: %v.\n", err)
198 os.Exit(1)
199 }
200 return
201 }