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