mirror of
https://github.com/Luzifer/nginx-sso.git
synced 2024-12-21 05:11:17 +00:00
201 lines
6.4 KiB
Go
201 lines
6.4 KiB
Go
|
/*-
|
||
|
* 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
|
||
|
}
|