319 lines
7.8 KiB
Go
319 lines
7.8 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/sha256"
|
|
"crypto/sha512"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/urfave/cli/v2"
|
|
)
|
|
|
|
type ErrorResponse struct {
|
|
Success bool `json:"success"`
|
|
Error string `json:"error"`
|
|
}
|
|
|
|
type ExternalSpecResponse struct {
|
|
Success bool `json:"success"`
|
|
Agent string `json:"agent"`
|
|
Specification string `json:"specification"`
|
|
Commands []string `json:"commands"`
|
|
}
|
|
|
|
type ExternalPublicKeyResponse struct {
|
|
Success bool `json:"success"`
|
|
PublicKeyBase64 string `json:"public_key_base64"`
|
|
}
|
|
|
|
type ExternalSignatureResponse struct {
|
|
Success bool `json:"success"`
|
|
SignatureBase64 string `json:"signature_base64"`
|
|
}
|
|
|
|
func printResponse(response any) error {
|
|
errorMessageJsonBytes, jsonErr := json.Marshal(response)
|
|
if jsonErr != nil {
|
|
return jsonErr
|
|
} else {
|
|
fmt.Println(string(errorMessageJsonBytes))
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func printResponseSlient(response any) {
|
|
if err := printResponse(response); err != nil {
|
|
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
err := innerMain()
|
|
if err != nil {
|
|
errorMessage := &ErrorResponse{
|
|
Success: false,
|
|
Error: fmt.Sprintf("%v", err),
|
|
}
|
|
if printResponse(errorMessage) != nil {
|
|
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
|
|
}
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func innerMain() error {
|
|
app := cli.App{
|
|
Name: "external-signer-soft",
|
|
Usage: "External signer software",
|
|
Commands: []*cli.Command{
|
|
buildGenerateKeypair(),
|
|
buildExternalSpecCommand(),
|
|
buildExternalPublicKeyCommand(),
|
|
buildExternalSignCommand(),
|
|
},
|
|
Action: func(ctx *cli.Context) error {
|
|
fmt.Println("External signer software, specification: https://openwebstandard.org/rfc1")
|
|
return nil
|
|
},
|
|
}
|
|
|
|
if err := app.Run(os.Args); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func buildGenerateKeypair() *cli.Command {
|
|
return &cli.Command{
|
|
Name: "generate_keypair",
|
|
Usage: "Generate keypair",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "type",
|
|
Usage: "Type, enums: rsa2048, rsa3072, rsa4096, p256, p384, p521",
|
|
Required: true,
|
|
},
|
|
},
|
|
Action: func(ctx *cli.Context) error {
|
|
keyType := ctx.String("type")
|
|
privateKeyDer, err := generateKeypair(keyType)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("Private Key:\n%s\n", base64.StdEncoding.EncodeToString(privateKeyDer))
|
|
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func generateKeypair(keyType string) ([]byte, error) {
|
|
keyType = strings.ToLower(keyType)
|
|
if strings.HasPrefix(keyType, "rsa") {
|
|
var bitLen int
|
|
if keyType == "rsa2048" {
|
|
bitLen = 2048
|
|
} else if keyType == "rsa3072" {
|
|
bitLen = 3072
|
|
} else if keyType == "rsa4096" {
|
|
bitLen = 4096
|
|
} else {
|
|
return nil, fmt.Errorf("bad key type: %s", keyType)
|
|
}
|
|
privateKey, err := rsa.GenerateKey(rand.Reader, bitLen)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return x509.MarshalPKCS8PrivateKey(privateKey)
|
|
}
|
|
var curve elliptic.Curve
|
|
if keyType == "p256" {
|
|
curve = elliptic.P256()
|
|
} else if keyType == "p384" {
|
|
curve = elliptic.P384()
|
|
} else if keyType == "p521" {
|
|
curve = elliptic.P521()
|
|
} else {
|
|
return nil, fmt.Errorf("bad key type: %s", keyType)
|
|
}
|
|
privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return x509.MarshalPKCS8PrivateKey(privateKey)
|
|
}
|
|
|
|
func buildExternalSpecCommand() *cli.Command {
|
|
return &cli.Command{
|
|
Name: "external_spec",
|
|
Usage: "External specification",
|
|
Flags: []cli.Flag{},
|
|
Action: func(ctx *cli.Context) error {
|
|
externalSpecResponse := &ExternalSpecResponse{
|
|
Success: true,
|
|
Agent: "external-signer-soft/0.1.0",
|
|
Specification: "External/1.0.0-alpha",
|
|
Commands: []string{
|
|
"external_public_key",
|
|
"external_sign",
|
|
},
|
|
}
|
|
printResponseSlient(externalSpecResponse)
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func buildExternalPublicKeyCommand() *cli.Command {
|
|
return &cli.Command{
|
|
Name: "external_public_key",
|
|
Usage: "External public key",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "parameter",
|
|
Usage: "Parameter",
|
|
Required: true,
|
|
},
|
|
},
|
|
Action: func(ctx *cli.Context) error {
|
|
parameter := ctx.String("parameter")
|
|
privateKeyDer, err := base64.StdEncoding.DecodeString(parameter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
privateKey, err := x509.ParsePKCS8PrivateKey(privateKeyDer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var publicKeyDer []byte
|
|
var publicKeyErr error
|
|
if rsaPrivateKey, ok := privateKey.(*rsa.PrivateKey); ok {
|
|
publicKey := rsaPrivateKey.PublicKey
|
|
publicKeyDer, publicKeyErr = x509.MarshalPKIXPublicKey(&publicKey)
|
|
} else if ecdsaPrivateKey, ok := privateKey.(*ecdsa.PrivateKey); ok {
|
|
publicKey := ecdsaPrivateKey.PublicKey
|
|
publicKeyDer, publicKeyErr = x509.MarshalPKIXPublicKey(&publicKey)
|
|
} else {
|
|
return fmt.Errorf("bad paramater")
|
|
}
|
|
|
|
if publicKeyErr != nil {
|
|
return publicKeyErr
|
|
}
|
|
|
|
externalPublicKeyResponse := &ExternalPublicKeyResponse{
|
|
Success: true,
|
|
PublicKeyBase64: base64.StdEncoding.EncodeToString(publicKeyDer),
|
|
}
|
|
|
|
printResponseSlient(externalPublicKeyResponse)
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func buildExternalSignCommand() *cli.Command {
|
|
return &cli.Command{
|
|
Name: "external_sign",
|
|
Usage: "External sign",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "parameter",
|
|
Usage: "Parameter",
|
|
Required: true,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "alg",
|
|
Usage: "Algorithm",
|
|
Required: true,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "message-base64",
|
|
Usage: "Message base64 encoded",
|
|
Required: true,
|
|
},
|
|
},
|
|
Action: func(ctx *cli.Context) error {
|
|
parameter := ctx.String("parameter")
|
|
alg := ctx.String("alg")
|
|
messageBase64 := ctx.String("message-base64")
|
|
message, err := base64.StdEncoding.DecodeString(messageBase64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
privateKeyDer, err := base64.StdEncoding.DecodeString(parameter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
privateKey, err := x509.ParsePKCS8PrivateKey(privateKeyDer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var digest []byte
|
|
var hash crypto.Hash
|
|
if alg == "ES256" || alg == "RS256" {
|
|
digest32 := sha256.Sum256(message)
|
|
digest = digest32[:]
|
|
hash = crypto.SHA256
|
|
} else if alg == "ES384" || alg == "RS384" {
|
|
sha384 := crypto.SHA384.New()
|
|
digest = sha384.Sum(message)
|
|
hash = crypto.SHA384
|
|
} else if alg == "ES512" || alg == "RS512" {
|
|
digest64 := sha512.Sum512(message)
|
|
digest = digest64[:]
|
|
hash = crypto.SHA512
|
|
} else {
|
|
return fmt.Errorf("invalid algorithm: %s", alg)
|
|
}
|
|
|
|
var signature []byte
|
|
var signatureErr error
|
|
if rsaPrivateKey, ok := privateKey.(*rsa.PrivateKey); ok {
|
|
if !strings.HasPrefix(alg, "RS") {
|
|
return fmt.Errorf("invalid algorithm for RSA: %s", alg)
|
|
}
|
|
signature, signatureErr = rsaPrivateKey.Sign(rand.Reader, digest, hash)
|
|
} else if ecdsaPrivateKey, ok := privateKey.(*ecdsa.PrivateKey); ok {
|
|
paramsName := ecdsaPrivateKey.Params().Name
|
|
if paramsName == "P-256" && alg != "ES256" {
|
|
return fmt.Errorf("invalid algorithm for P256: %s", alg)
|
|
}
|
|
if paramsName == "P-384" && alg != "ES384" {
|
|
return fmt.Errorf("invalid algorithm for P384: %s", alg)
|
|
}
|
|
if paramsName == "P-521" && alg != "ES521" {
|
|
return fmt.Errorf("invalid algorithm for P521: %s", alg)
|
|
}
|
|
signature, signatureErr = ecdsaPrivateKey.Sign(rand.Reader, digest, hash)
|
|
} else {
|
|
return fmt.Errorf("bad paramater")
|
|
}
|
|
if signatureErr != nil {
|
|
return fmt.Errorf("sign with private key failed: %v", signatureErr)
|
|
}
|
|
|
|
externalSignatureResponse := ExternalSignatureResponse{
|
|
Success: true,
|
|
SignatureBase64: base64.StdEncoding.EncodeToString(signature),
|
|
}
|
|
|
|
printResponseSlient(externalSignatureResponse)
|
|
return nil
|
|
},
|
|
}
|
|
}
|