386 lines
9.4 KiB
Go
386 lines
9.4 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/ecdsa"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/sha256"
|
|
"crypto/sha512"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"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"`
|
|
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-pkc11",
|
|
Usage: "External signer PKCS#11",
|
|
Commands: []*cli.Command{
|
|
buildParameterDecodeCommand(),
|
|
buildParameterEncodeCommand(),
|
|
buildExternalSpecCommand(),
|
|
buildExternalPublicKeyCommand(),
|
|
buildExternalSignCommand(),
|
|
},
|
|
Action: func(ctx *cli.Context) error {
|
|
fmt.Println("External signer PKCS#11, specification: https://openwebstandard.org/rfc1")
|
|
return nil
|
|
},
|
|
}
|
|
|
|
if err := app.Run(os.Args); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func buildParameterEncodeCommand() *cli.Command {
|
|
return &cli.Command{
|
|
Name: "parameter_encode",
|
|
Usage: "Encode parameter, show PKCS#11 URIs: pkcs11-tool --module /usr/local/lib/libykcs11.dylib --login --list-objects --type privkey",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "uri",
|
|
Usage: "PKCS#11 URI",
|
|
Required: true,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "library",
|
|
Usage: "Library",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "pin",
|
|
Usage: "PIN",
|
|
},
|
|
},
|
|
Action: func(ctx *cli.Context) error {
|
|
uri := ctx.String("uri")
|
|
library := ctx.String("library")
|
|
pin := ctx.String("pin")
|
|
if !strings.HasPrefix(uri, "pkcs11:") {
|
|
return fmt.Errorf("invalid uri: %s", uri)
|
|
}
|
|
if library == "" {
|
|
library = "/usr/local/lib/libykcs11.dylib"
|
|
}
|
|
uriMap := parseUri(uri)
|
|
pkcs11Key := Pkcs11Key{
|
|
Library: library,
|
|
Pin: pin,
|
|
TokenLabel: uriMap["token"],
|
|
KeyLabel: uriMap["object"],
|
|
}
|
|
parameter, err := json.Marshal(&pkcs11Key)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
fmt.Println(base64.StdEncoding.EncodeToString(parameter))
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func parseUri(uri string) map[string]string {
|
|
uriMap := map[string]string{}
|
|
pkcs11UriLeftPart := strings.TrimPrefix(uri, "pkcs11:")
|
|
keyValus := strings.Split(pkcs11UriLeftPart, ";")
|
|
for _, kv := range keyValus {
|
|
keyAndValue := strings.Split(kv, "=")
|
|
if len(keyAndValue) == 2 {
|
|
decodedValue, err := url.QueryUnescape(keyAndValue[1])
|
|
if err == nil {
|
|
uriMap[keyAndValue[0]] = decodedValue
|
|
} else {
|
|
uriMap[keyAndValue[0]] = keyAndValue[1]
|
|
}
|
|
}
|
|
}
|
|
return uriMap
|
|
}
|
|
|
|
func buildParameterDecodeCommand() *cli.Command {
|
|
return &cli.Command{
|
|
Name: "parameter_decode",
|
|
Usage: "Decode parameter",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "parameter",
|
|
Usage: "Parameter",
|
|
Required: true,
|
|
},
|
|
},
|
|
Action: func(ctx *cli.Context) error {
|
|
parameter := ctx.String("parameter")
|
|
pkcs11Key, err := parseParameter(parameter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return printResponse(pkcs11Key)
|
|
},
|
|
}
|
|
}
|
|
|
|
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-pkcs11/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")
|
|
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")
|
|
alg := ctx.String("alg")
|
|
messageBase64 := ctx.String("message-base64")
|
|
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 {
|
|
publicKey := privateKey.Public()
|
|
|
|
if ecdsaPublicKey, ok := publicKey.(*ecdsa.PublicKey); ok {
|
|
ecName := ecdsaPublicKey.Curve.Params().Name
|
|
es256Matches := alg == "ES256" && ecName == "P-256"
|
|
es384Matches := alg == "ES384" && ecName == "P-384"
|
|
es512Matches := alg == "ES512" && ecName == "P-521"
|
|
|
|
esMatches := es256Matches || es384Matches || es512Matches
|
|
|
|
if !esMatches {
|
|
return fmt.Errorf("algorithm mismatch %s vs %s", alg, ecName)
|
|
}
|
|
} else if _, ok := publicKey.(*rsa.PublicKey); ok {
|
|
rsValid := alg == "RS256" || alg == "RS384" || alg == "RS512"
|
|
if !rsValid {
|
|
return fmt.Errorf("rsa algorithm invalid %s", alg)
|
|
}
|
|
} else {
|
|
return fmt.Errorf("invalid public key type: %+v", reflect.TypeOf(publicKey))
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
signature, err := privateKey.Sign(rand.Reader, digest, hash)
|
|
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)
|
|
}
|