1/*-
2 * Copyright 2017 Square Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package main
18
19import (
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
37var (
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		// `sig`
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		// `enc`
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// KeygenSig generates keypair for corresponding SignatureAlgorithm.
55func 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, // sic!
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		// The cryptographic operations are implemented using constant-time algorithms.
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		// NB: The cryptographic operations do not use constant-time algorithms.
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		// NB: The cryptographic operations do not use constant-time algorithms.
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// KeygenEnc generates keypair for corresponding KeyAlgorithm.
112func 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
148func 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		// According to RFC 7517 section-8.2.  This is unlikely to change in the
162		// near future. If it were, new values could be found in the registry under
163		// "JSON Web Key Use": https://www.iana.org/assignments/jose/jose.xhtml
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		// Generate a canonical kid based on RFC 7638
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	// I'm not sure why we couldn't use `pub := priv.Public()` here as the private
183	// key should contain the public key.  In case for some reason it doesn't,
184	// this builds a public JWK from scratch.
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// writeNewFile is shameless copy-paste from ioutil.WriteFile with a bit
213// different flags for OpenFile.
214func 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