Files
external-signer-pkcs11/main.go
2025-05-22 23:13:05 +08:00

247 lines
5.8 KiB
Go

package main
import (
"crypto"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"os"
"github.com/ThalesGroup/crypto11"
"github.com/urfave/cli/v2"
)
const (
EnvPkcs11Pin = "PKCS11_PIN"
)
type Pkcs11Key struct {
Library string `json:"library"`
TokenLabel string `json:"token_label"`
Pin string `json:"pin"`
KeyLabel string `json:"key_label"`
}
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"`
}
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-pkc11",
Usage: "External signer PKCS#11",
Commands: []*cli.Command{
buildExternalSpecCommand(),
buildExternalPublicKeyCommand(),
buildExternalSignCommand(),
},
Action: func(ctx *cli.Context) error {
return nil
},
}
if err := app.Run(os.Args); err != nil {
return err
}
return nil
}
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-pkc11/0.1.0",
Specification: "External/1.0.0-alpha",
}
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")
return tryWithPkcs11(parameter, false, func(privateKey crypto11.Signer) error {
publicKey := privateKey.Public()
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return fmt.Errorf("marshal public key failed: %v", err)
}
externalPublicKeyResponse := &ExternalPublicKeyResponse{
Success: true,
PublicKeyBase64: base64.StdEncoding.EncodeToString(publicKeyBytes),
}
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")
messageBase64 := ctx.String("message-base64")
// TODO check --alg
message, err := base64.StdEncoding.DecodeString(messageBase64)
if err != nil {
return fmt.Errorf("unmarshal --message-base64 failed: %v", err)
}
return tryWithPkcs11(parameter, true, func(privateKey crypto11.Signer) error {
hashed := sha256.Sum256(message)
signature, err := privateKey.Sign(rand.Reader, hashed[:], crypto.SHA256)
if err != nil {
return fmt.Errorf("sign with private key failed: %v", err)
}
externalSignatureResponse := ExternalSignatureResponse{
Success: true,
SignatureBase64: base64.StdEncoding.EncodeToString(signature),
}
printResponseSlient(externalSignatureResponse)
return nil
})
},
}
}
func parseParameter(parameter string) (*Pkcs11Key, error) {
parameterBytes, err := base64.StdEncoding.DecodeString(parameter)
if err != nil {
return nil, fmt.Errorf("parse --parameter base64 failed: %v", err)
}
var pkcs11Key Pkcs11Key
err = json.Unmarshal(parameterBytes, &pkcs11Key)
if err != nil {
return nil, fmt.Errorf("parse --parameter base64 failed: %v", err)
}
return &pkcs11Key, nil
}
func getPin(pin string) (string, error) {
if pin != "" {
return pin, nil
}
envPin := os.Getenv(EnvPkcs11Pin)
if envPin != "" {
return envPin, nil
}
return "", errors.New("PIN is not set, set PIN: " + EnvPkcs11Pin)
}
func tryWithPkcs11(parameter string, requirePin bool, callback func(crypto11.Signer) error) error {
pkcs11Key, err := parseParameter(parameter)
if err != nil {
return err
}
pin, err := getPin(pkcs11Key.Pin)
if requirePin && err != nil {
return err
}
config := &crypto11.Config{
Path: pkcs11Key.Library,
TokenLabel: pkcs11Key.TokenLabel,
Pin: pin,
}
keyLabel := pkcs11Key.KeyLabel
crypto11Ctx, err := crypto11.Configure(config)
if err != nil {
return fmt.Errorf("config crypto11 failed: %v", err)
}
defer crypto11Ctx.Close()
privateKey, err := crypto11Ctx.FindKeyPair(nil, []byte(keyLabel))
if err != nil {
return fmt.Errorf("find private key failed: %v", err)
}
return callback(privateKey)
}