summaryrefslogtreecommitdiff
path: root/vendor/github.com/stefanberger/go-pkcs11uri/pkcs11uri.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/stefanberger/go-pkcs11uri/pkcs11uri.go')
-rw-r--r--vendor/github.com/stefanberger/go-pkcs11uri/pkcs11uri.go453
1 files changed, 453 insertions, 0 deletions
diff --git a/vendor/github.com/stefanberger/go-pkcs11uri/pkcs11uri.go b/vendor/github.com/stefanberger/go-pkcs11uri/pkcs11uri.go
new file mode 100644
index 000000000..39b06548e
--- /dev/null
+++ b/vendor/github.com/stefanberger/go-pkcs11uri/pkcs11uri.go
@@ -0,0 +1,453 @@
+/*
+ (c) Copyright IBM Corporation, 2020
+
+ 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 pkcs11uri
+
+import (
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net/url"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strconv"
+ "strings"
+)
+
+// Pkcs11URI holds a pkcs11 URI object
+type Pkcs11URI struct {
+ // path and query attributes may have custom attributes that either
+ // have to be in the query or in the path part, so we use two maps
+ pathAttributes map[string]string
+ queryAttributes map[string]string
+ // directories to search for pkcs11 modules
+ moduleDirectories []string
+ // file paths of allowed pkcs11 modules
+ allowedModulePaths []string
+ // whether any module is allowed to be loaded
+ allowAnyModule bool
+ // A map of environment variables needed by the pkcs11 module using this URI.
+ // This map is not needed by this implementation but is there for convenience.
+ env map[string]string
+}
+
+// upper character hex digits needed for pct-encoding
+const hex = "0123456789ABCDEF"
+
+// escapeAll pct-escapes all characters in the string
+func escapeAll(s string) string {
+ res := make([]byte, len(s)*3)
+ j := 0
+ for i := 0; i < len(s); i++ {
+ c := s[i]
+ res[j] = '%'
+ res[j+1] = hex[c>>4]
+ res[j+2] = hex[c&0xf]
+ j += 3
+ }
+ return string(res)
+}
+
+// escape pct-escapes the path and query part of the pkcs11 URI following the different rules of the
+// path and query part as decribed in RFC 7512 sec. 2.3
+func escape(s string, isPath bool) string {
+ res := make([]byte, len(s)*3)
+ j := 0
+ for i := 0; i < len(s); i++ {
+ c := s[i]
+ // unreserved per RFC 3986 sec. 2.3
+ if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') {
+ res[j] = c
+ } else if isPath && c == '&' {
+ res[j] = c
+ } else if !isPath && (c == '/' || c == '?' || c == '|') {
+ res[j] = c
+ } else {
+ switch c {
+ case '-', '.', '_', '~': // unreserved per RFC 3986 sec. 2.3
+ res[j] = c
+ case ':', '[', ']', '@', '!', '$', '\'', '(', ')', '*', '+', ',', '=':
+ res[j] = c
+ default:
+ res[j] = '%'
+ res[j+1] = hex[c>>4]
+ res[j+2] = hex[c&0xf]
+ j += 2
+ }
+ }
+ j++
+ }
+ return string(res[:j])
+}
+
+// New creates a new Pkcs11URI object
+func New() *Pkcs11URI {
+ return &Pkcs11URI{
+ pathAttributes: make(map[string]string),
+ queryAttributes: make(map[string]string),
+ env: make(map[string]string),
+ }
+}
+
+func (uri *Pkcs11URI) setAttribute(attrMap map[string]string, name, value string) error {
+ v, err := url.PathUnescape(value)
+ if err != nil {
+ return err
+ }
+ attrMap[name] = v
+ return nil
+}
+
+// GetPathAttribute returns the value of a path attribute in unescaped form or
+// pct-encoded form
+func (uri *Pkcs11URI) GetPathAttribute(name string, pctencode bool) (string, bool) {
+ v, ok := uri.pathAttributes[name]
+ if ok && pctencode {
+ v = escape(v, true)
+ }
+ return v, ok
+}
+
+// SetPathAttribute sets the value for a path attribute; this function may return an error
+// if the given value cannot be pct-unescaped
+func (uri *Pkcs11URI) SetPathAttribute(name, value string) error {
+ return uri.setAttribute(uri.pathAttributes, name, value)
+}
+
+// AddPathAttribute adds a path attribute; it returns an error if an attribute with the same
+// name already existed or if the given value cannot be pct-unescaped
+func (uri *Pkcs11URI) AddPathAttribute(name, value string) error {
+ if _, ok := uri.pathAttributes[name]; ok {
+ return errors.New("duplicate path attribute")
+ }
+ return uri.SetPathAttribute(name, value)
+}
+
+// RemovePathAttribute removes a path attribute
+func (uri *Pkcs11URI) RemovePathAttribute(name string) {
+ delete(uri.pathAttributes, name)
+}
+
+// AddEnv adds an environment variable for the pkcs11 module
+func (uri *Pkcs11URI) AddEnv(name, value string) {
+ uri.env[name] = value
+}
+
+// SetEnvMap sets the environment variables for the pkcs11 module
+func (uri *Pkcs11URI) SetEnvMap(env map[string]string) {
+ uri.env = env
+}
+
+// GetEnvMap returns the map of environment variables
+func (uri *Pkcs11URI) GetEnvMap() map[string]string {
+ return uri.env
+}
+
+// GetQueryAttribute returns the value of a query attribute in unescaped or
+// pct-encoded form
+func (uri *Pkcs11URI) GetQueryAttribute(name string, pctencode bool) (string, bool) {
+ v, ok := uri.queryAttributes[name]
+ if ok && pctencode {
+ v = escape(v, false)
+ }
+ return v, ok
+}
+
+// SetQueryAttribute sets the value for a query attribute; this function may return an error
+// if the given value cannot pct-unescaped
+func (uri *Pkcs11URI) SetQueryAttribute(name, value string) error {
+ return uri.setAttribute(uri.queryAttributes, name, value)
+}
+
+// AddQueryAttribute adds a query attribute; it returns an error if an attribute with the same
+// name already existed or if the given value cannot be pct-unescaped
+func (uri *Pkcs11URI) AddQueryAttribute(name, value string) error {
+ if _, ok := uri.queryAttributes[name]; ok {
+ return errors.New("duplicate query attribute")
+ }
+ return uri.SetQueryAttribute(name, value)
+}
+
+// RemoveQueryAttribute removes a path attribute
+func (uri *Pkcs11URI) RemoveQueryAttribute(name string) {
+ delete(uri.queryAttributes, name)
+}
+
+// Validate validates a Pkcs11URI object's attributes following RFC 7512 rules and proper formatting of
+// their values
+func (uri *Pkcs11URI) Validate() error {
+ /* RFC 7512: 2.3 */
+ /* slot-id should be DIGIT, but we go for number */
+ if v, ok := uri.pathAttributes["slot-id"]; ok {
+ if _, err := strconv.Atoi(v); err != nil {
+ return fmt.Errorf("slot-id must be a number: %s", v)
+ }
+ }
+
+ /* library-version should 1*DIGIT [ "." 1 *DIGIT ]; allow NUMBERS for DIGIT */
+ if v, ok := uri.pathAttributes["library-version"]; ok {
+ m, err := regexp.Match("^[0-9]+(\\.[0-9]+)?$", []byte(v))
+ if err != nil || !m {
+ return fmt.Errorf("Invalid format for library-version '%s'", v)
+ }
+ }
+
+ if v, ok := uri.pathAttributes["type"]; ok {
+ m, err := regexp.Match("^(public|private|cert|secret-key}data)?$", []byte(v))
+ if err != nil || !m {
+ return fmt.Errorf("Invalid type '%s'", v)
+ }
+ }
+
+ /* RFC 7512: 2.4 */
+ _, ok1 := uri.queryAttributes["pin-source"]
+ _, ok2 := uri.queryAttributes["pin-value"]
+ if ok1 && ok2 {
+ return errors.New("URI must not contain pin-source and pin-value")
+ }
+
+ if v, ok := uri.queryAttributes["module-path"]; ok {
+ if !filepath.IsAbs(v) {
+ return fmt.Errorf("path %s of module-name attribute must be absolute", v)
+ }
+ }
+
+ return nil
+}
+
+// HasPIN allows the user to check whether a PIN has been provided either by the pin-value or the pin-source
+// attributes. It should be called before GetPIN(), which may still fail getting the PIN from a file for example.
+func (uri *Pkcs11URI) HasPIN() bool {
+ _, ok := uri.queryAttributes["pin-value"]
+ if ok {
+ return true
+ }
+ _, ok = uri.queryAttributes["pin-source"]
+ return ok
+}
+
+// GetPIN gets the PIN from either the pin-value or pin-source attribute; a user may want to call HasPIN()
+// before calling this function to determine whether a PIN has been provided at all so that an error code
+// returned by this function indicates that the PIN value could not be retrieved.
+func (uri *Pkcs11URI) GetPIN() (string, error) {
+ if v, ok := uri.queryAttributes["pin-value"]; ok {
+ return v, nil
+ }
+ if v, ok := uri.queryAttributes["pin-source"]; ok {
+ pinuri, err := url.ParseRequestURI(v)
+ if err != nil {
+ return "", fmt.Errorf("Could not parse pin-source: %s ", err)
+ }
+ switch pinuri.Scheme {
+ case "", "file":
+ if !filepath.IsAbs(pinuri.Path) {
+ return "", fmt.Errorf("PIN URI path '%s' is not absolute", pinuri.Path)
+ }
+ pin, err := ioutil.ReadFile(pinuri.Path)
+ if err != nil {
+ return "", fmt.Errorf("Could not open PIN file: %s", err)
+ }
+ return string(pin), nil
+ default:
+ return "", fmt.Errorf("PIN URI scheme %s is not supported", pinuri.Scheme)
+ }
+ }
+ return "", fmt.Errorf("Neither pin-source nor pin-value are available")
+}
+
+// Parse parses a pkcs11: URI string
+func (uri *Pkcs11URI) Parse(uristring string) error {
+ if !strings.HasPrefix(uristring, "pkcs11:") {
+ return errors.New("Malformed pkcs11 URI: missing pcks11: prefix")
+ }
+
+ parts := strings.SplitN(uristring[7:], "?", 2)
+
+ uri.pathAttributes = make(map[string]string)
+ uri.queryAttributes = make(map[string]string)
+
+ if len(parts[0]) > 0 {
+ /* parse path part */
+ for _, part := range strings.Split(parts[0], ";") {
+ p := strings.SplitN(part, "=", 2)
+ if len(p) != 2 {
+ return errors.New("Malformed pkcs11 URI: malformed path attribute")
+ }
+ if err := uri.AddPathAttribute(p[0], p[1]); err != nil {
+ return fmt.Errorf("Malformed pkcs11 URI: %s", err)
+ }
+ }
+ }
+
+ if len(parts) == 2 {
+ /* parse query part */
+ for _, part := range strings.Split(parts[1], "&") {
+ p := strings.SplitN(part, "=", 2)
+ if len(p) != 2 {
+ return errors.New("Malformed pkcs11 URI: malformed query attribute")
+ }
+ if err := uri.AddQueryAttribute(p[0], p[1]); err != nil {
+ return fmt.Errorf("Malformed pkcs11 URI: %s", err)
+ }
+ }
+ }
+ return uri.Validate()
+}
+
+// formatAttribute formats attributes and escapes their values as needed
+func formatAttributes(attrMap map[string]string, ispath bool) string {
+ res := ""
+ for key, value := range attrMap {
+ switch key {
+ case "id":
+ /* id is always pct-encoded */
+ value = escapeAll(value)
+ default:
+ if ispath {
+ value = escape(value, true)
+ } else {
+ value = escape(value, false)
+ }
+ }
+ if len(res) > 0 {
+ if ispath {
+ res += ";"
+ } else {
+ res += "&"
+ }
+ }
+ res += key + "=" + value
+ }
+ return res
+}
+
+// Format formats a Pkcs11URI to it string representaion
+func (uri *Pkcs11URI) Format() (string, error) {
+ if err := uri.Validate(); err != nil {
+ return "", err
+ }
+ result := "pkcs11:" + formatAttributes(uri.pathAttributes, true)
+ if len(uri.queryAttributes) > 0 {
+ result += "?" + formatAttributes(uri.queryAttributes, false)
+ }
+ return result, nil
+}
+
+// SetModuleDirectories sets the search directories for pkcs11 modules
+func (uri *Pkcs11URI) SetModuleDirectories(moduleDirectories []string) {
+ uri.moduleDirectories = moduleDirectories
+}
+
+// GetModuleDirectories gets the search directories for pkcs11 modules
+func (uri *Pkcs11URI) GetModuleDirectories() []string {
+ return uri.moduleDirectories
+}
+
+// SetAllowedModulePaths sets allowed module paths to restrict access to modules.
+// Directory entries must end with a '/', all other ones are assumed to be file entries.
+// Allowed modules are filtered by string matching.
+func (uri *Pkcs11URI) SetAllowedModulePaths(allowedModulePaths []string) {
+ uri.allowedModulePaths = allowedModulePaths
+}
+
+// SetAllowAnyModule allows any module to be loaded; by default this is not allowed
+func (uri *Pkcs11URI) SetAllowAnyModule(allowAnyModule bool) {
+ uri.allowAnyModule = allowAnyModule
+}
+
+func (uri *Pkcs11URI) isAllowedPath(path string, allowedPaths []string) bool {
+ if uri.allowAnyModule {
+ return true
+ }
+ for _, allowedPath := range allowedPaths {
+ if allowedPath == path {
+ // exact filename match
+ return true
+ }
+ if allowedPath[len(allowedPath)-1] == '/' && strings.HasPrefix(path, allowedPath) {
+ // allowedPath no subdirectory is allowed
+ idx := strings.IndexRune(path[len(allowedPath):], os.PathSeparator)
+ if idx < 0 {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// GetModule returns the module to use or an error in case no module could be found.
+// First the module-path is checked for whether it holds an absolute that can be read
+// by the current user. If this is the case the module is returned. Otherwise either the module-path
+// is used or the user-provided module path is used to match a module containing what is set in the
+// attribute module-name.
+func (uri *Pkcs11URI) GetModule() (string, error) {
+ var searchdirs []string
+ v, ok := uri.queryAttributes["module-path"]
+
+ if ok {
+ info, err := os.Stat(v)
+ if err != nil {
+ return "", fmt.Errorf("module-path '%s' is not accessible", v)
+ }
+ if err == nil && info.Mode().IsRegular() {
+ // it's a file
+ if uri.isAllowedPath(v, uri.allowedModulePaths) {
+ return v, nil
+ }
+ return "", fmt.Errorf("module-path '%s' is not allowed by policy", v)
+ }
+ if !info.IsDir() {
+ return "", fmt.Errorf("module-path '%s' points to an invalid file type", v)
+ }
+ // v is a directory
+ searchdirs = []string{v}
+ } else {
+ searchdirs = uri.GetModuleDirectories()
+ }
+
+ moduleName, ok := uri.queryAttributes["module-name"]
+ if !ok {
+ return "", fmt.Errorf("module-name attribute is not set")
+ }
+ moduleName = strings.ToLower(moduleName)
+
+ for _, dir := range searchdirs {
+ files, err := ioutil.ReadDir(dir)
+ if err != nil {
+ continue
+ }
+ for _, file := range files {
+ fileLower := strings.ToLower(file.Name())
+
+ i := strings.Index(fileLower, moduleName)
+ if i < 0 {
+ continue
+ }
+ // we require that the fileLower ends with moduleName or that
+ // a suffix follows so that softhsm will not match libsofthsm2.so but only
+ // libsofthsm.so
+ if len(fileLower) == i+len(moduleName) || fileLower[i+len(moduleName)] == '.' {
+ f := filepath.Join(dir, file.Name())
+ if uri.isAllowedPath(f, uri.allowedModulePaths) {
+ return f, nil
+ }
+ return "", fmt.Errorf("module '%s' is not allowed by policy", f)
+ }
+ }
+ }
+ return "", fmt.Errorf("No module could be found")
+}