| /*- |
| * Copyright 2017 Square Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package main |
| |
| import ( |
| "crypto" |
| "crypto/ecdsa" |
| "crypto/elliptic" |
| "crypto/rand" |
| "crypto/rsa" |
| "encoding/base32" |
| "errors" |
| "fmt" |
| "golang.org/x/crypto/ed25519" |
| "io" |
| "os" |
| |
| "gopkg.in/alecthomas/kingpin.v2" |
| "gopkg.in/square/go-jose.v2" |
| ) |
| |
| var ( |
| app = kingpin.New("jwk-keygen", "A command-line utility to generate public/pirvate keypairs in JWK format.") |
| |
| use = app.Flag("use", "Desrired key use").Required().Enum("enc", "sig") |
| alg = app.Flag("alg", "Generate key to be used for ALG").Required().Enum( |
| // `sig` |
| string(jose.ES256), string(jose.ES384), string(jose.ES512), string(jose.EdDSA), |
| string(jose.RS256), string(jose.RS384), string(jose.RS512), string(jose.PS256), string(jose.PS384), string(jose.PS512), |
| // `enc` |
| string(jose.RSA1_5), string(jose.RSA_OAEP), string(jose.RSA_OAEP_256), |
| string(jose.ECDH_ES), string(jose.ECDH_ES_A128KW), string(jose.ECDH_ES_A192KW), string(jose.ECDH_ES_A256KW), |
| ) |
| bits = app.Flag("bits", "Key size in bits").Int() |
| kid = app.Flag("kid", "Key ID").String() |
| kidRand = app.Flag("kid-rand", "Generate random Key ID").Bool() |
| ) |
| |
| // KeygenSig generates keypair for corresponding SignatureAlgorithm. |
| func KeygenSig(alg jose.SignatureAlgorithm, bits int) (crypto.PublicKey, crypto.PrivateKey, error) { |
| switch alg { |
| case jose.ES256, jose.ES384, jose.ES512, jose.EdDSA: |
| keylen := map[jose.SignatureAlgorithm]int{ |
| jose.ES256: 256, |
| jose.ES384: 384, |
| jose.ES512: 521, // sic! |
| jose.EdDSA: 256, |
| } |
| if bits != 0 && bits != keylen[alg] { |
| return nil, nil, errors.New("this `alg` does not support arbitrary key length") |
| } |
| case jose.RS256, jose.RS384, jose.RS512, jose.PS256, jose.PS384, jose.PS512: |
| if bits == 0 { |
| bits = 2048 |
| } |
| if bits < 2048 { |
| return nil, nil, errors.New("too short key for RSA `alg`, 2048+ is required") |
| } |
| } |
| switch alg { |
| case jose.ES256: |
| // The cryptographic operations are implemented using constant-time algorithms. |
| key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) |
| return key.Public(), key, err |
| case jose.ES384: |
| // NB: The cryptographic operations do not use constant-time algorithms. |
| key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) |
| return key.Public(), key, err |
| case jose.ES512: |
| // NB: The cryptographic operations do not use constant-time algorithms. |
| key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) |
| return key.Public(), key, err |
| case jose.EdDSA: |
| pub, key, err := ed25519.GenerateKey(rand.Reader) |
| return pub, key, err |
| case jose.RS256, jose.RS384, jose.RS512, jose.PS256, jose.PS384, jose.PS512: |
| key, err := rsa.GenerateKey(rand.Reader, bits) |
| return key.Public(), key, err |
| default: |
| return nil, nil, errors.New("unknown `alg` for `use` = `sig`") |
| } |
| } |
| |
| // KeygenEnc generates keypair for corresponding KeyAlgorithm. |
| func KeygenEnc(alg jose.KeyAlgorithm, bits int) (crypto.PublicKey, crypto.PrivateKey, error) { |
| switch alg { |
| case jose.RSA1_5, jose.RSA_OAEP, jose.RSA_OAEP_256: |
| if bits == 0 { |
| bits = 2048 |
| } |
| if bits < 2048 { |
| return nil, nil, errors.New("too short key for RSA `alg`, 2048+ is required") |
| } |
| key, err := rsa.GenerateKey(rand.Reader, bits) |
| return key.Public(), key, err |
| case jose.ECDH_ES, jose.ECDH_ES_A128KW, jose.ECDH_ES_A192KW, jose.ECDH_ES_A256KW: |
| var crv elliptic.Curve |
| switch bits { |
| case 0, 256: |
| crv = elliptic.P256() |
| case 384: |
| crv = elliptic.P384() |
| case 521: |
| crv = elliptic.P521() |
| default: |
| return nil, nil, errors.New("unknown elliptic curve bit length, use one of 256, 384, 521") |
| } |
| key, err := ecdsa.GenerateKey(crv, rand.Reader) |
| return key.Public(), key, err |
| default: |
| return nil, nil, errors.New("unknown `alg` for `use` = `enc`") |
| } |
| } |
| |
| func main() { |
| app.Version("v2") |
| kingpin.MustParse(app.Parse(os.Args[1:])) |
| |
| if *kidRand { |
| if *kid == "" { |
| b := make([]byte, 5) |
| _, err := rand.Read(b) |
| app.FatalIfError(err, "can't Read() crypto/rand") |
| *kid = base32.StdEncoding.EncodeToString(b) |
| } else { |
| app.FatalUsage("can't combine --kid and --kid-rand") |
| } |
| } |
| |
| var privKey crypto.PublicKey |
| var pubKey crypto.PrivateKey |
| var err error |
| switch *use { |
| case "sig": |
| pubKey, privKey, err = KeygenSig(jose.SignatureAlgorithm(*alg), *bits) |
| case "enc": |
| pubKey, privKey, err = KeygenEnc(jose.KeyAlgorithm(*alg), *bits) |
| } |
| app.FatalIfError(err, "unable to generate key") |
| |
| priv := jose.JSONWebKey{Key: privKey, KeyID: *kid, Algorithm: *alg, Use: *use} |
| pub := jose.JSONWebKey{Key: pubKey, KeyID: *kid, Algorithm: *alg, Use: *use} |
| |
| if priv.IsPublic() || !pub.IsPublic() || !priv.Valid() || !pub.Valid() { |
| app.Fatalf("invalid keys were generated") |
| } |
| |
| privJS, err := priv.MarshalJSON() |
| app.FatalIfError(err, "can't Marshal private key to JSON") |
| pubJS, err := pub.MarshalJSON() |
| app.FatalIfError(err, "can't Marshal public key to JSON") |
| |
| if *kid == "" { |
| fmt.Printf("==> jwk_%s.pub <==\n", *alg) |
| fmt.Println(string(pubJS)) |
| fmt.Printf("==> jwk_%s <==\n", *alg) |
| fmt.Println(string(privJS)) |
| } else { |
| // JWK Thumbprint (RFC7638) is not used for key id because of |
| // lack of canonical representation. |
| fname := fmt.Sprintf("jwk_%s_%s_%s", *use, *alg, *kid) |
| err = writeNewFile(fname+".pub", pubJS, 0444) |
| app.FatalIfError(err, "can't write public key to file %s.pub", fname) |
| fmt.Printf("Written public key to %s.pub\n", fname) |
| err = writeNewFile(fname, privJS, 0400) |
| app.FatalIfError(err, "cant' write private key to file %s", fname) |
| fmt.Printf("Written private key to %s\n", fname) |
| } |
| } |
| |
| // writeNewFile is shameless copy-paste from ioutil.WriteFile with a bit |
| // different flags for OpenFile. |
| func writeNewFile(filename string, data []byte, perm os.FileMode) error { |
| f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, perm) |
| if err != nil { |
| return err |
| } |
| n, err := f.Write(data) |
| if err == nil && n < len(data) { |
| err = io.ErrShortWrite |
| } |
| if err1 := f.Close(); err == nil { |
| err = err1 |
| } |
| return err |
| } |