1
16
17 package main
18
19 import (
20 "crypto"
21 "crypto/ecdsa"
22 "crypto/elliptic"
23 "crypto/rand"
24 "crypto/rsa"
25 "encoding/base64"
26 "errors"
27 "fmt"
28 "io"
29 "os"
30
31 "golang.org/x/crypto/ed25519"
32
33 "gopkg.in/alecthomas/kingpin.v2"
34 "gopkg.in/square/go-jose.v2"
35 )
36
37 var (
38 app = kingpin.New("jwk-keygen", "A command-line utility to generate public/pirvate keypairs in JWK format.")
39
40 use = app.Flag("use", "Desired key use").Required().Enum("enc", "sig")
41 alg = app.Flag("alg", "Generate key to be used for ALG").Required().Enum(
42
43 string(jose.ES256), string(jose.ES384), string(jose.ES512), string(jose.EdDSA),
44 string(jose.RS256), string(jose.RS384), string(jose.RS512), string(jose.PS256), string(jose.PS384), string(jose.PS512),
45
46 string(jose.RSA1_5), string(jose.RSA_OAEP), string(jose.RSA_OAEP_256),
47 string(jose.ECDH_ES), string(jose.ECDH_ES_A128KW), string(jose.ECDH_ES_A192KW), string(jose.ECDH_ES_A256KW),
48 )
49 bits = app.Flag("bits", "Key size in bits").Int()
50 kid = app.Flag("kid", "Key ID").String()
51 kidRand = app.Flag("kid-rand", "Generate random Key ID").Bool()
52 )
53
54
55 func KeygenSig(alg jose.SignatureAlgorithm, bits int) (crypto.PublicKey, crypto.PrivateKey, error) {
56 switch alg {
57 case jose.ES256, jose.ES384, jose.ES512, jose.EdDSA:
58 keylen := map[jose.SignatureAlgorithm]int{
59 jose.ES256: 256,
60 jose.ES384: 384,
61 jose.ES512: 521,
62 jose.EdDSA: 256,
63 }
64 if bits != 0 && bits != keylen[alg] {
65 return nil, nil, errors.New("this `alg` does not support arbitrary key length")
66 }
67 case jose.RS256, jose.RS384, jose.RS512, jose.PS256, jose.PS384, jose.PS512:
68 if bits == 0 {
69 bits = 2048
70 }
71 if bits < 2048 {
72 return nil, nil, errors.New("too short key for RSA `alg`, 2048+ is required")
73 }
74 }
75 switch alg {
76 case jose.ES256:
77
78 key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
79 if err != nil {
80 return nil, nil, err
81 }
82 return key.Public(), key, err
83 case jose.ES384:
84
85 key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
86 if err != nil {
87 return nil, nil, err
88 }
89 return key.Public(), key, err
90 case jose.ES512:
91
92 key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
93 if err != nil {
94 return nil, nil, err
95 }
96 return key.Public(), key, err
97 case jose.EdDSA:
98 pub, key, err := ed25519.GenerateKey(rand.Reader)
99 return pub, key, err
100 case jose.RS256, jose.RS384, jose.RS512, jose.PS256, jose.PS384, jose.PS512:
101 key, err := rsa.GenerateKey(rand.Reader, bits)
102 if err != nil {
103 return nil, nil, err
104 }
105 return key.Public(), key, err
106 default:
107 return nil, nil, errors.New("unknown `alg` for `use` = `sig`")
108 }
109 }
110
111
112 func KeygenEnc(alg jose.KeyAlgorithm, bits int) (crypto.PublicKey, crypto.PrivateKey, error) {
113 switch alg {
114 case jose.RSA1_5, jose.RSA_OAEP, jose.RSA_OAEP_256:
115 if bits == 0 {
116 bits = 2048
117 }
118 if bits < 2048 {
119 return nil, nil, errors.New("too short key for RSA `alg`, 2048+ is required")
120 }
121 key, err := rsa.GenerateKey(rand.Reader, bits)
122 if err != nil {
123 return nil, nil, err
124 }
125 return key.Public(), key, err
126 case jose.ECDH_ES, jose.ECDH_ES_A128KW, jose.ECDH_ES_A192KW, jose.ECDH_ES_A256KW:
127 var crv elliptic.Curve
128 switch bits {
129 case 0, 256:
130 crv = elliptic.P256()
131 case 384:
132 crv = elliptic.P384()
133 case 521:
134 crv = elliptic.P521()
135 default:
136 return nil, nil, errors.New("unknown elliptic curve bit length, use one of 256, 384, 521")
137 }
138 key, err := ecdsa.GenerateKey(crv, rand.Reader)
139 if err != nil {
140 return nil, nil, err
141 }
142 return key.Public(), key, err
143 default:
144 return nil, nil, errors.New("unknown `alg` for `use` = `enc`")
145 }
146 }
147
148 func main() {
149 app.Version("v2")
150 kingpin.MustParse(app.Parse(os.Args[1:]))
151
152 var privKey crypto.PrivateKey
153 var pubKey crypto.PublicKey
154 var err error
155 switch *use {
156 case "sig":
157 pubKey, privKey, err = KeygenSig(jose.SignatureAlgorithm(*alg), *bits)
158 case "enc":
159 pubKey, privKey, err = KeygenEnc(jose.KeyAlgorithm(*alg), *bits)
160 default:
161
162
163
164 app.FatalIfError(errors.New("invalid key use. Must be \"sig\" or \"enc\""), "unable to generate key")
165 }
166 app.FatalIfError(err, "unable to generate key")
167
168 priv := jose.JSONWebKey{Key: privKey, KeyID: *kid, Algorithm: *alg, Use: *use}
169
170 if *kidRand {
171
172 if *kid == "" {
173 thumb, err := priv.Thumbprint(crypto.SHA256)
174 app.FatalIfError(err, "unable to compute thumbprint")
175 *kid = base64.URLEncoding.EncodeToString(thumb)
176 priv.KeyID = *kid
177 } else {
178 app.FatalUsage("can't combine --kid and --kid-rand")
179 }
180 }
181
182
183
184
185 pub := jose.JSONWebKey{Key: pubKey, KeyID: *kid, Algorithm: *alg, Use: *use}
186
187 if priv.IsPublic() || !pub.IsPublic() || !priv.Valid() || !pub.Valid() {
188 app.Fatalf("invalid keys were generated")
189 }
190
191 privJS, err := priv.MarshalJSON()
192 app.FatalIfError(err, "can't Marshal private key to JSON")
193 pubJS, err := pub.MarshalJSON()
194 app.FatalIfError(err, "can't Marshal public key to JSON")
195
196 if *kid == "" {
197 fmt.Printf("==> jwk_%s.pub <==\n", *alg)
198 fmt.Println(string(pubJS))
199 fmt.Printf("==> jwk_%s <==\n", *alg)
200 fmt.Println(string(privJS))
201 } else {
202 fname := fmt.Sprintf("jwk_%s_%s_%s", *use, *alg, *kid)
203 err = writeNewFile(fname+".pub", pubJS, 0444)
204 app.FatalIfError(err, "can't write public key to file %s.pub", fname)
205 fmt.Printf("Written public key to %s.pub\n", fname)
206 err = writeNewFile(fname, privJS, 0400)
207 app.FatalIfError(err, "cant' write private key to file %s", fname)
208 fmt.Printf("Written private key to %s\n", fname)
209 }
210 }
211
212
213
214 func writeNewFile(filename string, data []byte, perm os.FileMode) error {
215 f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, perm)
216 if err != nil {
217 return err
218 }
219 n, err := f.Write(data)
220 if err == nil && n < len(data) {
221 err = io.ErrShortWrite
222 }
223 if err1 := f.Close(); err == nil {
224 err = err1
225 }
226 return err
227 }
228
View as plain text