diff --git a/README.md b/README.md index 042df25..e96a7e6 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,11 @@ Cross compile: CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC="zig cc -target x86_64-linux" CXX="zig c++ -target x86_64-linux" go build -o external-signer-pkcs11-linux-x86_64 ``` +List private objects: +```shell +pkcs11-tool --module /usr/local/lib/libykcs11.dylib --login --list-objects --type privkey +``` + ```shell external-signer-pkcs11 external_spec diff --git a/main.go b/main.go index 99f9c85..7d092de 100644 --- a/main.go +++ b/main.go @@ -2,14 +2,20 @@ 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" @@ -83,11 +89,14 @@ func innerMain() error { 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 }, } @@ -98,6 +107,92 @@ func innerMain() error { 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", @@ -173,15 +268,54 @@ func buildExternalSignCommand() *cli.Command { }, Action: func(ctx *cli.Context) error { parameter := ctx.String("parameter") + alg := ctx.String("alg") 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) + 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) }