├── .dockerignore ├── .github └── workflows │ └── go.yml ├── .gitignore ├── .golangci.bck.yml ├── .golangci.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── attributes.go ├── base_objects.go ├── base_objects_test.go ├── cmd ├── kmipgen │ └── main.go └── ppkmip │ └── main.go ├── docker-compose.yml ├── docs.go ├── errors.go ├── examples_test.go ├── go.mod ├── go.sum ├── internal └── kmiputil │ ├── hex_values.go │ └── names.go ├── kmip14 ├── kmip_1_4.go ├── kmip_1_4.json └── kmip_1_4_generated.go ├── kmip20 ├── kmip_2_0_additions.go ├── kmip_2_0_additions.json ├── kmip_2_0_additions_generated.go ├── op_activate.go ├── op_destroy.go ├── op_get.go ├── op_locate.go ├── op_query.go ├── op_revoke.go ├── op_setattribute.go ├── payloads.go ├── payloads_test.go └── unique_identifier.go ├── managed_objects.go ├── op_create.go ├── op_create_key_pair.go ├── op_destroy.go ├── op_discover_versions.go ├── op_get.go ├── op_register.go ├── pykmip-server ├── Dockerfile ├── server.cert ├── server.conf └── server.key ├── requests.go ├── ttlv ├── decoder.go ├── decoder_test.go ├── docs.go ├── encoder.go ├── encoder_test.go ├── errors.go ├── examples_test.go ├── formatting.go ├── registry.go ├── registry_test.go ├── tag.go ├── tag_test.go ├── ttlv.go ├── ttlv_test.go └── types.go ├── types.go └── types_messages.go /.dockerignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .git 3 | Dockerfile 4 | docker-compose.yml 5 | .dockerignore 6 | build -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | 12 | build: 13 | name: Build 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | go: ['v1.22.x', 'oldstable', 'stable'] 18 | steps: 19 | - uses: actions/setup-go@v5 20 | with: 21 | go-version: ${{ matrix.go }} 22 | check-latest: true 23 | - uses: actions/checkout@v4 24 | - run: make build up test 25 | env: 26 | GOPATH: /home/runner/work/go 27 | - uses: golangci/golangci-lint-action@v7 28 | 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | scratch 3 | build 4 | vendor 5 | /pykmip-server/server.log 6 | /pykmip-server/server.db 7 | -------------------------------------------------------------------------------- /.golangci.bck.yml: -------------------------------------------------------------------------------- 1 | # This file contains all available configuration options 2 | # with their default values. 3 | 4 | # options for analysis running 5 | run: 6 | tests: true 7 | 8 | # all available settings of specific linters 9 | linters-settings: 10 | dupl: 11 | # tokens count to trigger issue, 150 by default 12 | threshold: 100 13 | exhaustive: 14 | default-signifies-exhaustive: true 15 | goconst: 16 | # minimal length of string constant, 3 by default 17 | min-len: 3 18 | # minimal occurrences count to trigger, 3 by default 19 | min-occurrences: 3 20 | depguard: 21 | # Rules to apply. 22 | # 23 | # Variables: 24 | # - File Variables 25 | # you can still use and exclamation mark ! in front of a variable to say not to use it. 26 | # Example !$test will match any file that is not a go test file. 27 | # 28 | # `$all` - matches all go files 29 | # `$test` - matches all go test files 30 | # 31 | # - Package Variables 32 | # 33 | # `$gostd` - matches all of go's standard library (Pulled from `GOROOT`) 34 | # 35 | # Default: Only allow $gostd in all files. 36 | rules: 37 | # Name of a rule. 38 | all: 39 | # List of file globs that will match this list of settings to compare against. 40 | # Default: $all 41 | files: 42 | - $all 43 | # List of allowed packages. 44 | # allow: 45 | # - $gostd 46 | # Packages that are not allowed where the value is a suggestion. 47 | deny: 48 | - pkg: github.com/magiconair/properties/assert 49 | desc: Use testify/assert package instead 50 | - pkg: gopkg.in/go-playground/assert.v1 51 | desc: Use testify/assert package instead 52 | - pkg: github.com/pborman/uuid 53 | desc: Use google/uuid package instead 54 | main: 55 | files: 56 | - "!$test" 57 | # todo need to check the usage 58 | - "!**authorization/conditions.go" 59 | - "!**yugotest/assertions.go" 60 | - "!**yugometrics/backendtesting/compliance.go" 61 | - "!**scopes/auth_scope.go" 62 | deny: 63 | - pkg: github.com/davecgh/go-spew/spew 64 | desc: spew is usually only used in tests 65 | - pkg: github.com/stretchr/testify 66 | desc: testify is usually only used in tests 67 | gomodguard: 68 | blocked: 69 | modules: 70 | - gopkg.in/go-playground/assert.v1: 71 | recommendations: 72 | - github.com/stretchr/testify 73 | reason: "testify is the test assertion framework we use" 74 | misspell: 75 | # Correct spellings using locale preferences for US or UK. 76 | # Default is to use a neutral variety of English. 77 | # Setting locale to US will correct the British spelling of 'colour' to 'color'. 78 | locale: US 79 | revive: 80 | ignore-generated-header: true 81 | wsl: 82 | allow-cuddle-declarations: true 83 | allow-separated-leading-comment: true 84 | allow-assign-and-anything: true 85 | 86 | linters: 87 | # to try out individual linters: golangci-lint run -E gocyclo 88 | enable: 89 | # default linters 90 | - staticcheck 91 | - errcheck 92 | - gosimple 93 | - govet 94 | - ineffassign 95 | - unused 96 | # additional linters 97 | - asciicheck 98 | - bidichk 99 | ## - bodyclose # its all false positives with requester and sling, which both close the body already 100 | - containedctx 101 | - contextcheck 102 | # - cyclop # need to analyze findings 103 | - decorder 104 | - depguard 105 | ## - dogsled # checks for too many blank identifiers. don't care 106 | - dupl 107 | - durationcheck 108 | - errchkjson 109 | - errname 110 | - errorlint 111 | - exhaustive 112 | - exportloopref 113 | - forbidigo 114 | - forcetypeassert 115 | ## - funlen # checks function length. don't care 116 | # - gci # not sure why this is complaining 117 | ## - gochecknoglobals # too common 118 | - gochecknoinits 119 | # - gocognit # too many findings, will take time to evaluate 120 | - goconst 121 | - gocritic 122 | ## - gocyclo # checks cyclomatic complexity. don't care 123 | # - godot # too many false positives 124 | # - godox # doesn't allow TODO comments. We allow those to be committed. 125 | # - goerr113 # good practice, but it doesn't recognize that we're already wrapping errors with merry 126 | ## - gofmt # checks code is formatted, handled by make prep 127 | - gofumpt 128 | - goheader 129 | ## - goimports # checks import order. We're not using goimports 130 | # - gomnd # too aggressive 131 | - gomoddirectives 132 | - gomodguard 133 | - goprintffuncname 134 | - gosec 135 | - grouper 136 | - importas 137 | # - ireturn # there are valid use cases for this pattern. too strict. 138 | ## - lll # checks line length. not enforced 139 | # - maintidx # look at this later 140 | - makezero 141 | ## - maligned # optimizies struct field order, but structs are usually ordered for legibility 142 | - misspell 143 | - nakedret 144 | # - nestif # need to evaluate the findings 145 | - nilerr 146 | - nilnil 147 | # - nlreturn # a little too aggressive. wsl covers the same ground. 148 | - noctx 149 | - nolintlint 150 | # - paralleltest # look at this later 151 | # - prealloc # slice optimizations, but promotes too much premature optimization 152 | - predeclared 153 | - promlinter 154 | - revive 155 | - rowserrcheck 156 | - sqlclosecheck 157 | - stylecheck 158 | - tagliatelle 159 | - thelper 160 | - tparallel 161 | - unconvert 162 | - unparam 163 | # - varnamelen # take a look later 164 | - wastedassign 165 | - whitespace 166 | # - wrapcheck # way too aggressive 167 | - wsl 168 | ## - unparam # too many false positives 169 | ## - whitespace # not enforced 170 | disable-all: true 171 | # presets: 172 | # - bugs 173 | # - unused 174 | # fast: false 175 | 176 | 177 | issues: 178 | # List of regexps of issue texts to exclude, empty list by default. 179 | # But independently from this option we use default exclude patterns, 180 | # it can be disabled by `exclude-use-default: false`. To list all 181 | # excluded by default patterns execute `golangci-lint run --help` 182 | # exclude: 183 | # - abcdef 184 | 185 | # Excluding configuration per-path, per-linter, per-text and per-source 186 | exclude-rules: 187 | - path: requests.go 188 | # Explicitly exclude the typecheck plugin. There is some bug in golangci which is 189 | # enabling this checker, even though it isn't listed above. 190 | # Exclude some linters from running on tests files. 191 | - path: _test\.go 192 | linters: 193 | - gocyclo 194 | - errcheck 195 | - dupl 196 | - gosec 197 | - exportloopref 198 | - gochecknoinits 199 | - gochecknoglobals 200 | - wsl 201 | - nlreturn 202 | - errchkjson 203 | - forcetypeassert 204 | - path: cmd 205 | linters: 206 | # init(), globals, and prints are pretty common in main packages 207 | - gochecknoinits 208 | - gochecknoglobals 209 | - forbidigo 210 | 211 | # Exclude known linters from partially hard-vendored code, 212 | # which is impossible to exclude via "nolint" comments. 213 | # - path: internal/hmac/ 214 | # text: "weak cryptographic primitive" 215 | # linters: 216 | # - gosec 217 | 218 | # Exclude some staticcheck messages 219 | # - linters: 220 | # - staticcheck 221 | # text: "SA9003:" 222 | 223 | 224 | # Independently from option `exclude` we use default exclude patterns, 225 | # it can be disabled by this option. To list all 226 | # excluded by default patterns execute `golangci-lint run --help`. 227 | # Default value for this option is true. 228 | # exclude-use-default: false 229 | 230 | # Maximum issues count per one linter. Set to 0 to disable. Default is 50. 231 | # max-issues-per-linter: 0 232 | 233 | # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. 234 | # max-same-issues: 0 235 | 236 | # Show only new issues: if there are unstaged changes or untracked files, 237 | # only those changes are analyzed, else only changes in HEAD~ are analyzed. 238 | # It's a super-useful option for integration of golangci-lint into existing 239 | # large codebase. It's not practical to fix all existing issues at the moment 240 | # of integration: much better don't allow issues in new code. 241 | # Default is false. 242 | new: false 243 | 244 | # Show only new issues created after git revision `REV` 245 | # new-from-rev: REV 246 | 247 | # Show only new issues created in git patch with set file path. 248 | # new-from-patch: path/to/patch/file -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | tests: true 4 | linters: 5 | default: none 6 | enable: 7 | - asciicheck 8 | - bidichk 9 | - containedctx 10 | - contextcheck 11 | - decorder 12 | - depguard 13 | - dupl 14 | - durationcheck 15 | - errcheck 16 | - errchkjson 17 | - errname 18 | - errorlint 19 | - exhaustive 20 | - forbidigo 21 | - forcetypeassert 22 | - gochecknoinits 23 | - goconst 24 | - gocritic 25 | - goheader 26 | - gomoddirectives 27 | - gomodguard 28 | - goprintffuncname 29 | - gosec 30 | - govet 31 | - grouper 32 | - importas 33 | - ineffassign 34 | - makezero 35 | - misspell 36 | - nakedret 37 | - nilerr 38 | - nilnil 39 | - noctx 40 | - nolintlint 41 | - predeclared 42 | - promlinter 43 | - revive 44 | - rowserrcheck 45 | - sqlclosecheck 46 | - staticcheck 47 | - tagliatelle 48 | - thelper 49 | - tparallel 50 | - unconvert 51 | - unparam 52 | - unused 53 | - wastedassign 54 | - whitespace 55 | settings: 56 | depguard: 57 | rules: 58 | all: 59 | files: 60 | - $all 61 | deny: 62 | - pkg: github.com/magiconair/properties/assert 63 | desc: Use testify/assert package instead 64 | - pkg: gopkg.in/go-playground/assert.v1 65 | desc: Use testify/assert package instead 66 | - pkg: github.com/pborman/uuid 67 | desc: Use google/uuid package instead 68 | main: 69 | files: 70 | - '!$test' 71 | - '!**authorization/conditions.go' 72 | - '!**yugotest/assertions.go' 73 | - '!**yugometrics/backendtesting/compliance.go' 74 | - '!**scopes/auth_scope.go' 75 | deny: 76 | - pkg: github.com/davecgh/go-spew/spew 77 | desc: spew is usually only used in tests 78 | - pkg: github.com/stretchr/testify 79 | desc: testify is usually only used in tests 80 | dupl: 81 | threshold: 100 82 | exhaustive: 83 | default-signifies-exhaustive: true 84 | goconst: 85 | min-len: 3 86 | min-occurrences: 3 87 | gomodguard: 88 | blocked: 89 | modules: 90 | - gopkg.in/go-playground/assert.v1: 91 | recommendations: 92 | - github.com/stretchr/testify 93 | reason: testify is the test assertion framework we use 94 | misspell: 95 | locale: US 96 | wsl: 97 | allow-assign-and-anything: true 98 | allow-separated-leading-comment: true 99 | allow-cuddle-declarations: true 100 | exclusions: 101 | generated: lax 102 | presets: 103 | - comments 104 | - common-false-positives 105 | - legacy 106 | - std-error-handling 107 | rules: 108 | - linters: 109 | - dupl 110 | - errcheck 111 | - errchkjson 112 | - exportloopref 113 | - forcetypeassert 114 | - gochecknoglobals 115 | - gochecknoinits 116 | - gocyclo 117 | - gosec 118 | - nlreturn 119 | - wsl 120 | path: _test\.go 121 | - linters: 122 | - forbidigo 123 | - gochecknoglobals 124 | - gochecknoinits 125 | path: cmd 126 | paths: 127 | - third_party$ 128 | - builtin$ 129 | - examples$ 130 | issues: 131 | new: false 132 | formatters: 133 | exclusions: 134 | generated: lax 135 | paths: 136 | - third_party$ 137 | - builtin$ 138 | - examples$ 139 | - requests.go 140 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine 2 | 3 | RUN apk --no-cache add make git curl bash fish 4 | 5 | WORKDIR /project 6 | 7 | COPY ./ /project 8 | RUN make tools 9 | 10 | CMD make 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Gemalto OSS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL = bash 2 | BUILD_FLAGS = 3 | TEST_FLAGS = 4 | 5 | all: tidy fmt build up test lint 6 | 7 | build: 8 | go build $(BUILD_FLAGS) ./... 9 | 10 | builddir: 11 | mkdir -p -m 0777 build 12 | 13 | install: 14 | go install ./cmd/ppkmip 15 | go install ./cmd/kmipgen 16 | 17 | ppkmip: builddir 18 | GOOS=darwin GOARCH=amd64 go build -o build/ppkmip-macos ./cmd/ppkmip 19 | GOOS=windows GOARCH=amd64 go build -o build/ppkmip-windows.exe ./cmd/ppkmip 20 | GOOS=linux GOARCH=amd64 go build -o build/ppkmip-linux ./cmd/ppkmip 21 | 22 | kmipgen: 23 | go install ./cmd/kmipgen 24 | 25 | lint: 26 | golangci-lint run 27 | 28 | clean: 29 | rm -rf build/* 30 | 31 | fmt: 32 | go fmt ./... 33 | 34 | # generates go code structures representing all the enums, mask, and tags defined 35 | # in the KMIP spec. The source specifications are stored in kmip14/kmip_1_4.json 36 | # and ttls/kmip20/kmip_2_0_additions.json. The generated .go files are named *_generated.go 37 | # 38 | # the kmipgen tool (defined in cmd/kmipgen) is used to generate the source. This tool can 39 | # be used independently to generate source for future specs or vendor extensions. 40 | # 41 | # this target only needs to be run if the json files are changed. The generated 42 | # go files should be committed to source control. 43 | generate: 44 | go generate ./... 45 | 46 | test: 47 | go test $(BUILD_FLAGS) $(TEST_FLAGS) ./... 48 | 49 | # creates a test coverage report, and produces json test output. useful for ci. 50 | cover: builddir 51 | go test $(TEST_FLAGS) -v -covermode=count -coverprofile=build/coverage.out -json ./... 52 | go tool cover -html=build/coverage.out -o build/coverage.html 53 | 54 | # brings up the projects dependencies in a compose stack 55 | up: 56 | docker compose build --pull pykmip-server 57 | docker compose run --rm dependencies 58 | 59 | # brings down the projects dependencies 60 | down: 61 | docker compose down -v --remove-orphans 62 | 63 | # runs the build inside a docker container. useful for ci to completely encapsulate the 64 | # build environment. 65 | docker: up 66 | docker compose build --pull builder 67 | docker compose run --rm builder make tidy fmt build cover lint 68 | 69 | # opens a shell into the build environment container. Useful for troubleshooting the 70 | # containerized build. 71 | bash: 72 | docker compose build --pull builder 73 | docker compose run --rm builder bash 74 | 75 | # opens a shell into the build environment container. Useful for troubleshooting the 76 | # containerized build. 77 | fish: 78 | docker compose build --pull builder 79 | docker compose run --rm builder fish 80 | 81 | tidy: 82 | go mod tidy 83 | 84 | # use go mod to update all dependencies 85 | update: 86 | go get -u ./... 87 | go mod tidy 88 | 89 | # install tools used by the build. typically only needs to be run once 90 | # to initialize a workspace. 91 | tools: kmipgen 92 | sh -c "$$(wget -O - -q https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh || echo exit 2)" -- -b $(shell go env GOPATH)/bin $(GOLANGCI_LINT_VERSION) 93 | 94 | pykmip-server: up 95 | docker compose exec pykmip-server tail -f server.log 96 | 97 | gen-certs: 98 | openssl req -x509 -newkey rsa:4096 -keyout pykmip-server/server.key -out pykmip-server/server.cert -days 3650 -nodes -subj '/CN=localhost' 99 | 100 | .PHONY: all build builddir run artifacts vet lint clean fmt test testall testreport up down pull builder runc ci bash fish image prep vendor.update vendor.ensure tools buildtools migratetool db.migrate 101 | 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | kmip-go [![GoDoc](https://godoc.org/github.com/gemalto/kmip-go?status.png)](https://godoc.org/github.com/gemalto/kmip-go) [![Go Report Card](https://goreportcard.com/badge/github.com/gemalto/kmip-go)](https://goreportcard.com/report/gemalto/kmip-go) [![Build](https://github.com/gemalto/kmip-go/workflows/Build/badge.svg)](https://github.com/gemalto/kmip-go/actions?query=branch%3Amaster+workflow%3ABuild+) 2 | ======= 3 | 4 | kmip-go is a go implemenation of KMIP protocol primitives. It supports marshaling data in TTLV, XML, or JSON 5 | encodings to and from go values and structs. It can be used to implement KMIP clients or servers. 6 | 7 | Installation 8 | ------------ 9 | 10 | go get github.com/gemalto/kmip-go 11 | 12 | Or, to just install the `ppkmip` pretty printing tool: 13 | 14 | go install github.com/gemalto/kmip-go/cmd/ppkmip 15 | 16 | Packages 17 | -------- 18 | 19 | The `ttlv` package implements the core encoder and decoder logic. 20 | 21 | The `kmip14` package contains constants for all the tags, types, enumerations and bitmasks defined in the KMIP 1.4 22 | specification. It also contains mappings from these values to the normalized names used in the JSON and XML 23 | encodings, and the canonical names used in Attribute structures. 24 | The `kmip14` definitions are all automatically registered with `ttlv.DefaultRegistry`. 25 | 26 | The `kmip20` package adds additional enumeration values from the 2.0 specification. It is meant to be registered 27 | on top of the 1.4 definitions. 28 | 29 | The root package defines golang structures for some of the significant Structure definitions in the 1.4 30 | specification, like Attributes, Request, Response, etc. It is incomplete, but can be used as an example 31 | for defining other structures. It also contains an example of a client and server. 32 | 33 | `cmd/kmipgen` is a code generation tool which generates the tag and enum constants from a JSON specification 34 | input. It can also be used independently in your own code to generate additional tags and constants. `make install` 35 | to build and install the tool. See `kmip14/kmip_1_4.go` for an example of using the tool. 36 | 37 | `cmd/kmipgen` is a tool for pretty printing kmip values. It can accept KMIP input from stdin or files, encoded 38 | in TTLV, XML, or JSON, and output in a variety of formats. `make install` to intall the tool, and 39 | `ppkmip --help` to see usage. 40 | 41 | Contributing 42 | ------------ 43 | 44 | To build, be sure to have a recent go SDK, and make. Run `make tools` to install other dependencies. 45 | 46 | There is also a dockerized build, which only requires make and docker-compose: `make docker`. You can also 47 | do `make fish` or `make bash` to shell into the docker build container. 48 | 49 | Merge requests are welcome! Before submitting, please run `make` and make sure all tests pass and there are 50 | no linter findings. -------------------------------------------------------------------------------- /attributes.go: -------------------------------------------------------------------------------- 1 | package kmip 2 | 3 | import ( 4 | "github.com/gemalto/kmip-go/kmip14" 5 | ) 6 | 7 | // 3 8 | 9 | // Name 3.2 Table 57 10 | // 11 | // The Name attribute is a structure (see Table 57) used to identify and locate an object. 12 | // This attribute is assigned by the client, and the Name Value is intended to be in a form that 13 | // humans are able to interpret. The key management system MAY specify rules by which the client 14 | // creates valid names. Clients are informed of such rules by a mechanism that is not specified by 15 | // this standard. Names SHALL be unique within a given key management domain, 16 | // but are NOT REQUIRED to be globally unique. 17 | type Name struct { 18 | NameValue string 19 | NameType kmip14.NameType 20 | } 21 | 22 | // Cryptographic Parameters 3.6 Table 65 23 | // 24 | // The Cryptographic Parameters attribute is a structure (see Table 65) that contains a set of OPTIONAL 25 | // fields that describe certain cryptographic parameters to be used when performing cryptographic operations 26 | // using the object. Specific fields MAY pertain only to certain types of Managed Cryptographic Objects. The 27 | // Cryptographic Parameters attribute of a Certificate object identifies the cryptographic parameters of the 28 | // public key contained within the Certificate. 29 | // 30 | // The Cryptographic Algorithm is also used to specify the parameters for cryptographic operations. For operations 31 | // involving digital signatures, either the Digital Signature Algorithm can be specified or the Cryptographic 32 | // Algorithm and Hashing Algorithm combination can be specified. 33 | // 34 | // Random IV can be used to request that the KMIP server generate an appropriate IV for a 35 | // cryptographic operation that uses an IV. The generated Random IV is returned in the response 36 | // to the cryptographic operation. 37 | // 38 | // IV Length is the length of the Initialization Vector in bits. This parameter SHALL be provided when the 39 | // specified Block Cipher Mode supports variable IV lengths such as CTR or GCM. 40 | // 41 | // Tag Length is the length of the authentication tag in bytes. This parameter SHALL be provided when the 42 | // Block Cipher Mode is GCM or CCM. 43 | // 44 | // The IV used with counter modes of operation (e.g., CTR and GCM) cannot repeat for a given cryptographic key. 45 | // To prevent an IV/key reuse, the IV is often constructed of three parts: a fixed field, an invocation field, 46 | // and a counter as described in [SP800-38A] and [SP800-38D]. The Fixed Field Length is the length of the fixed 47 | // field portion of the IV in bits. The Invocation Field Length is the length of the invocation field portion of 48 | // the IV in bits. The Counter Length is the length of the counter portion of the IV in bits. 49 | // 50 | // Initial Counter Value is the starting counter value for CTR mode (for [RFC3686] it is 1). 51 | type CryptographicParameters struct { 52 | BlockCipherMode kmip14.BlockCipherMode `ttlv:",omitempty"` 53 | PaddingMethod kmip14.PaddingMethod `ttlv:",omitempty"` 54 | HashingAlgorithm kmip14.HashingAlgorithm `ttlv:",omitempty"` 55 | KeyRoleType kmip14.KeyRoleType `ttlv:",omitempty"` 56 | DigitalSignatureAlgorithm kmip14.DigitalSignatureAlgorithm `ttlv:",omitempty"` 57 | CryptographicAlgorithm kmip14.CryptographicAlgorithm `ttlv:",omitempty"` 58 | RandomIV bool `ttlv:",omitempty"` 59 | IVLength int `ttlv:",omitempty"` 60 | TagLength int `ttlv:",omitempty"` 61 | FixedFieldLength int `ttlv:",omitempty"` 62 | InvocationFieldLength int `ttlv:",omitempty"` 63 | CounterLength int `ttlv:",omitempty"` 64 | InitialCounterValue int `ttlv:",omitempty"` 65 | SaltLength int `ttlv:",omitempty"` 66 | MaskGenerator kmip14.MaskGenerator `ttlv:",omitempty" default:"1"` // defaults to MGF1 67 | MaskGeneratorHashingAlgorithm kmip14.HashingAlgorithm `ttlv:",omitempty" default:"4"` // defaults to SHA-1 68 | PSource []byte `ttlv:",omitempty"` 69 | TrailerField int `ttlv:",omitempty"` 70 | } 71 | -------------------------------------------------------------------------------- /base_objects.go: -------------------------------------------------------------------------------- 1 | package kmip 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/gemalto/kmip-go/kmip14" 7 | "github.com/gemalto/kmip-go/ttlv" 8 | ) 9 | 10 | // 2.1 Base Objects 11 | // 12 | // These objects are used within the messages of the protocol, but are not objects managed by the key 13 | // management system. They are components of Managed Objects. 14 | 15 | // Attribute 2.1.1 Table 2 16 | // 17 | // An Attribute object is a structure (see Table 2) used for sending and receiving Managed Object attributes. 18 | // The Attribute Name is a text-string that is used to identify the attribute. The Attribute Index is an index 19 | // number assigned by the key management server. The Attribute Index is used to identify the particular instance. 20 | // Attribute Indices SHALL start with 0. The Attribute Index of an attribute SHALL NOT change when other instances 21 | // are added or deleted. Single-instance Attributes (attributes which an object MAY only have at most one instance 22 | // thereof) SHALL have an Attribute Index of 0. The Attribute Value is either a primitive data type or structured 23 | // object, depending on the attribute. 24 | // 25 | // When an Attribute structure is used to specify or return a particular instance of an Attribute and the Attribute 26 | // Index is not specified it SHALL be assumed to be 0. 27 | type Attribute struct { 28 | // AttributeName should contain the canonical name of a tag, e.g. "Cryptographic Algorithm" 29 | AttributeName string 30 | // AttributeIndex is typically 0 when clients use this struct to create objects or add attributes. Clients 31 | // only need to set this if modifying or deleting an existing attribute. 32 | AttributeIndex int `ttlv:",omitempty"` 33 | AttributeValue interface{} 34 | } 35 | 36 | func NewAttributeFromTag(tag ttlv.Tag, idx int, val interface{}) Attribute { 37 | return Attribute{ 38 | AttributeName: tag.CanonicalName(), 39 | AttributeIndex: idx, 40 | AttributeValue: val, 41 | } 42 | } 43 | 44 | // Credential 2.1.2 Table 3 45 | // 46 | // A Credential is a structure (see Table 3) used for client identification purposes and is not managed by the 47 | // key management system (e.g., user id/password pairs, Kerberos tokens, etc.). It MAY be used for authentication 48 | // purposes as indicated in [KMIP-Prof]. 49 | // 50 | // TODO: add an unmarshal impl to Credential to handle decoding the right kind 51 | // of credential based on the credential type value 52 | type Credential struct { 53 | CredentialType kmip14.CredentialType 54 | CredentialValue interface{} 55 | } 56 | 57 | // UsernameAndPasswordCredentialValue 2.1.2 Table 4 58 | // 59 | // If the Credential Type in the Credential is Username and Password, then Credential Value is a 60 | // structure as shown in Table 4. The Username field identifies the client, and the Password field 61 | // is a secret that authenticates the client. 62 | type UsernameAndPasswordCredentialValue struct { 63 | Username string 64 | Password string `ttlv:",omitempty"` 65 | } 66 | 67 | // DeviceCredentialValue 2.1.2 Table 5 68 | // 69 | // If the Credential Type in the Credential is Device, then Credential Value is a structure as shown in 70 | // Table 5. One or a combination of the Device Serial Number, Network Identifier, Machine Identifier, 71 | // and Media Identifier SHALL be unique. Server implementations MAY enforce policies on uniqueness for 72 | // individual fields. A shared secret or password MAY also be used to authenticate the client. 73 | // The client SHALL provide at least one field. 74 | type DeviceCredentialValue struct { 75 | DeviceSerialNumber string `ttlv:",omitempty"` 76 | Password string `ttlv:",omitempty"` 77 | DeviceIdentifier string `ttlv:",omitempty"` 78 | NetworkIdentifier string `ttlv:",omitempty"` 79 | MachineIdentifier string `ttlv:",omitempty"` 80 | MediaIdentifier string `ttlv:",omitempty"` 81 | } 82 | 83 | // AttestationCredentialValue 2.1.2 Table 6 84 | // 85 | // If the Credential Type in the Credential is Attestation, then Credential Value is a structure 86 | // as shown in Table 6. The Nonce Value is obtained from the key management server in a Nonce Object. 87 | // The Attestation Credential Object can contain a measurement from the client or an assertion from a 88 | // third party if the server is not capable or willing to verify the attestation data from the client. 89 | // Neither type of attestation data (Attestation Measurement or Attestation Assertion) is necessary to 90 | // allow the server to accept either. However, the client SHALL provide attestation data in either the 91 | // Attestation Measurement or Attestation Assertion fields. 92 | type AttestationCredentialValue struct { 93 | Nonce Nonce 94 | AttestationType kmip14.AttestationType 95 | AttestationMeasurement []byte `ttlv:",omitempty"` 96 | AttestationAssertion []byte `ttlv:",omitempty"` 97 | } 98 | 99 | // KeyBlock 2.1.3 Table 7 100 | // 101 | // A Key Block object is a structure (see Table 7) used to encapsulate all of the information that is 102 | // closely associated with a cryptographic key. It contains a Key Value of one of the following Key Format Types: 103 | // 104 | // - Raw – This is a key that contains only cryptographic key material, encoded as a string of bytes. 105 | // - Opaque – This is an encoded key for which the encoding is unknown to the key management system. 106 | // It is encoded as a string of bytes. 107 | // - PKCS1 – This is an encoded private key, expressed as a DER-encoded ASN.1 PKCS#1 object. 108 | // - PKCS8 – This is an encoded private key, expressed as a DER-encoded ASN.1 PKCS#8 object, supporting both 109 | // the RSAPrivateKey syntax and EncryptedPrivateKey. 110 | // - X.509 – This is an encoded object, expressed as a DER-encoded ASN.1 X.509 object. 111 | // - ECPrivateKey – This is an ASN.1 encoded elliptic curve private key. 112 | // - Several Transparent Key types – These are algorithm-specific structures containing defined values 113 | // for the various key types, as defined in Section 2.1.7. 114 | // - Extensions – These are vendor-specific extensions to allow for proprietary or legacy key formats. 115 | // 116 | // The Key Block MAY contain the Key Compression Type, which indicates the format of the elliptic curve public 117 | // key. By default, the public key is uncompressed. 118 | // 119 | // The Key Block also has the Cryptographic Algorithm and the Cryptographic Length of the key contained 120 | // in the Key Value field. Some example values are: 121 | // 122 | // - RSA keys are typically 1024, 2048 or 3072 bits in length. 123 | // - 3DES keys are typically from 112 to 192 bits (depending upon key length and the presence of parity bits). 124 | // - AES keys are 128, 192 or 256 bits in length. 125 | // 126 | // The Key Block SHALL contain a Key Wrapping Data structure if the key in the Key Value field is 127 | // wrapped (i.e., encrypted, or MACed/signed, or both). 128 | type KeyBlock struct { 129 | KeyFormatType kmip14.KeyFormatType 130 | KeyCompressionType kmip14.KeyCompressionType `ttlv:",omitempty"` 131 | KeyValue *KeyValue `ttlv:",omitempty"` 132 | CryptographicAlgorithm kmip14.CryptographicAlgorithm `ttlv:",omitempty"` 133 | CryptographicLength int `ttlv:",omitempty"` 134 | KeyWrappingData *KeyWrappingData 135 | } 136 | 137 | // KeyValue 2.1.4 Table 8 138 | // 139 | // The Key Value is used only inside a Key Block and is either a Byte String or a structure (see Table 8): 140 | // 141 | // - The Key Value structure contains the key material, either as a byte string or as a Transparent Key 142 | // structure (see Section 2.1.7), and OPTIONAL attribute information that is associated and encapsulated 143 | // with the key material. This attribute information differs from the attributes associated with Managed 144 | // Objects, and is obtained via the Get Attributes operation, only by the fact that it is encapsulated with 145 | // (and possibly wrapped with) the key material itself. 146 | // - The Key Value Byte String is either the wrapped TTLV-encoded (see Section 9.1) Key Value structure, or 147 | // the wrapped un-encoded value of the Byte String Key Material field. 148 | // 149 | // TODO: Unmarshaler impl which unmarshals correct KeyMaterial type. 150 | type KeyValue struct { 151 | // KeyMaterial should be []byte, one of the Transparent*Key structs, or a custom struct if KeyFormatType is 152 | // an extension. 153 | KeyMaterial interface{} 154 | Attribute []Attribute 155 | } 156 | 157 | // KeyWrappingData 2.1.5 Table 9 158 | // 159 | // The Key Block MAY also supply OPTIONAL information about a cryptographic key wrapping mechanism used 160 | // to wrap the Key Value. This consists of a Key Wrapping Data structure (see Table 9). It is only used 161 | // inside a Key Block. 162 | // 163 | // This structure contains fields for: 164 | // 165 | // - A Wrapping Method, which indicates the method used to wrap the Key Value. 166 | // - Encryption Key Information, which contains the Unique Identifier (see 3.1) value of the encryption key 167 | // and associated cryptographic parameters. 168 | // - MAC/Signature Key Information, which contains the Unique Identifier value of the MAC/signature key 169 | // and associated cryptographic parameters. 170 | // - A MAC/Signature, which contains a MAC or signature of the Key Value. 171 | // - An IV/Counter/Nonce, if REQUIRED by the wrapping method. 172 | // - An Encoding Option, specifying the encoding of the Key Material within the Key Value structure of the 173 | // Key Block that has been wrapped. If No Encoding is specified, then the Key Value structure SHALL NOT contain 174 | // any attributes. 175 | // 176 | // If wrapping is used, then the whole Key Value structure is wrapped unless otherwise specified by the 177 | // Wrapping Method. The algorithms used for wrapping are given by the Cryptographic Algorithm attributes of 178 | // the encryption key and/or MAC/signature key; the block-cipher mode, padding method, and hashing algorithm used 179 | // for wrapping are given by the Cryptographic Parameters in the Encryption Key Information and/or MAC/Signature 180 | // Key Information, or, if not present, from the Cryptographic Parameters attribute of the respective key(s). 181 | // Either the Encryption Key Information or the MAC/Signature Key Information (or both) in the Key Wrapping Data 182 | // structure SHALL be specified. 183 | // 184 | // The following wrapping methods are currently defined: 185 | // 186 | // - Encrypt only (i.e., encryption using a symmetric key or public key, or authenticated encryption algorithms that use a single key). 187 | // - MAC/sign only (i.e., either MACing the Key Value with a symmetric key, or signing the Key Value with a private key). 188 | // - Encrypt then MAC/sign. 189 | // - MAC/sign then encrypt. 190 | // - TR-31. 191 | // - Extensions. 192 | // 193 | // The following encoding options are currently defined: 194 | // 195 | // - No Encoding (i.e., the wrapped un-encoded value of the Byte String Key Material field in the Key Value structure). 196 | // - TTLV Encoding (i.e., the wrapped TTLV-encoded Key Value structure). 197 | type KeyWrappingData struct { 198 | WrappingMethod kmip14.WrappingMethod 199 | EncryptionKeyInformation *EncryptionKeyInformation 200 | MACSignatureKeyInformation *MACSignatureKeyInformation 201 | MACSignature []byte 202 | IVCounterNonce []byte 203 | EncodingOption kmip14.EncodingOption `ttlv:",omitempty" default:"TTLVEncoding"` 204 | } 205 | 206 | // EncryptionKeyInformation 2.1.5 Table 10 207 | type EncryptionKeyInformation struct { 208 | UniqueIdentifier string 209 | CryptographicParameters *CryptographicParameters 210 | } 211 | 212 | // MACSignatureKeyInformation 2.1.5 Table 11 213 | type MACSignatureKeyInformation struct { 214 | UniqueIdentifier string 215 | CryptographicParameters *CryptographicParameters 216 | } 217 | 218 | // TransparentSymmetricKey 2.1.7.1 Table 14 219 | // 220 | // If the Key Format Type in the Key Block is Transparent Symmetric Key, then Key Material is a 221 | // structure as shown in Table 14. 222 | type TransparentSymmetricKey struct { 223 | Key []byte `validate:"required"` 224 | } 225 | 226 | // TransparentDSAPrivateKey 2.1.7.2 Table 15 227 | // 228 | // If the Key Format Type in the Key Block is Transparent DSA Private Key, then Key Material is a structure as 229 | // shown in Table 15. 230 | type TransparentDSAPrivateKey struct { 231 | // TODO: should these be pointers? big package deals entirely with pointers, but these are not optional values. 232 | P *big.Int `validate:"required"` 233 | Q *big.Int `validate:"required"` 234 | G *big.Int `validate:"required"` 235 | X *big.Int `validate:"required"` 236 | } 237 | 238 | // TransparentDSAPublicKey 2.1.7.3 Table 16 239 | // 240 | // If the Key Format Type in the Key Block is Transparent DSA Public Key, then Key Material is a structure as 241 | // shown in Table 16. 242 | type TransparentDSAPublicKey struct { 243 | P *big.Int `validate:"required"` 244 | Q *big.Int `validate:"required"` 245 | G *big.Int `validate:"required"` 246 | Y *big.Int `validate:"required"` 247 | } 248 | 249 | // TransparentRSAPrivateKey 2.1.7.4 Table 17 250 | // 251 | // If the Key Format Type in the Key Block is Transparent RSA Private Key, then Key Material is a structure 252 | // as shown in Table 17. 253 | // 254 | // One of the following SHALL be present (refer to [PKCS#1]): 255 | // 256 | // - Private Exponent, 257 | // - P and Q (the first two prime factors of Modulus), or 258 | // - Prime Exponent P and Prime Exponent Q. 259 | type TransparentRSAPrivateKey struct { 260 | Modulus *big.Int `validate:"required"` 261 | PrivateExponent, PublicExponent *big.Int 262 | P, Q *big.Int 263 | PrimeExponentP, PrimeExponentQ *big.Int 264 | CRTCoefficient *big.Int 265 | } 266 | 267 | // TransparentRSAPublicKey 2.1.7.5 Table 18 268 | // 269 | // If the Key Format Type in the Key Block is Transparent RSA Public Key, then Key Material is a structure 270 | // as shown in Table 18. 271 | type TransparentRSAPublicKey struct { 272 | Modulus *big.Int `validate:"required"` 273 | PublicExponent *big.Int `validate:"required"` 274 | } 275 | 276 | // TransparentDHPrivateKey 2.1.7.6 Table 19 277 | // 278 | // If the Key Format Type in the Key Block is Transparent DH Private Key, then Key Material is a structure as shown 279 | // in Table 19. 280 | type TransparentDHPrivateKey struct { 281 | P *big.Int `validate:"required"` 282 | Q *big.Int 283 | G *big.Int `validate:"required"` 284 | J *big.Int 285 | X *big.Int `validate:"required"` 286 | } 287 | 288 | // TransparentDHPublicKey 2.1.7.7 Table 20 289 | // 290 | // If the Key Format Type in the Key Block is Transparent DH Public Key, then Key Material is a structure as 291 | // shown in Table 20. 292 | // 293 | // P, G, and Y are required. 294 | type TransparentDHPublicKey struct { 295 | P *big.Int `validate:"required"` 296 | Q *big.Int 297 | G *big.Int `validate:"required"` 298 | J *big.Int 299 | Y *big.Int `validate:"required"` 300 | } 301 | 302 | // TransparentECDSAPrivateKey 2.1.7.8 Table 21 303 | // 304 | // The Transparent ECDSA Private Key structure is deprecated as of version 1.3 of this 305 | // specification and MAY be removed from subsequent versions of the specification. The 306 | // Transparent EC Private Key structure SHOULD be used as a replacement. 307 | // 308 | // If the Key Format Type in the Key Block is Transparent ECDSA Private Key, then Key Material is a 309 | // structure as shown in Table 21. 310 | type TransparentECDSAPrivateKey struct { 311 | RecommendedCurve kmip14.RecommendedCurve 312 | D *big.Int `validate:"required"` 313 | } 314 | 315 | // TransparentECDSAPublicKey 2.1.7.9 Table 22 316 | // 317 | // The Transparent ECDSA Public Key structure is deprecated as of version 1.3 of this specification and 318 | // MAY be removed from subsequent versions of the specification. The Transparent EC Public Key structure 319 | // SHOULD be used as a replacement. 320 | // 321 | // If the Key Format Type in the Key Block is Transparent ECDSA Public Key, then Key Material is a 322 | // structure as shown in Table 22. 323 | type TransparentECDSAPublicKey struct { 324 | RecommendedCurve kmip14.RecommendedCurve 325 | QString []byte `validate:"required"` 326 | } 327 | 328 | // TransparentECDHPrivateKey 2.1.7.10 Table 23 329 | // 330 | // The Transparent ECDH Private Key structure is deprecated as of version 1.3 of this specification and 331 | // MAY be removed from subsequent versions of the specification. The Transparent EC Private Key structure 332 | // SHOULD be used as a replacement. 333 | // 334 | // If the Key Format Type in the Key Block is Transparent ECDH Private Key, then Key Material is a structure 335 | // as shown in Table 23. 336 | type TransparentECDHPrivateKey TransparentECPrivateKey 337 | 338 | // TransparentECDHPublicKey 2.1.7.11 Table 24 339 | // 340 | // The Transparent ECDH Public Key structure is deprecated as of version 1.3 of this specification and MAY 341 | // be removed from subsequent versions of the specification. The Transparent EC Public Key structure SHOULD 342 | // be used as a replacement. 343 | // 344 | // If the Key Format Type in the Key Block is Transparent ECDH Public Key, then Key Material is a structure as 345 | // shown in Table 24. 346 | type TransparentECDHPublicKey TransparentECPublicKey 347 | 348 | // TransparentECMQVPrivateKey 2.1.7.12 Table 25 349 | // 350 | // The Transparent ECMQV Private Key structure is deprecated as of version 1.3 of this specification and MAY 351 | // be removed from subsequent versions of the specification. The Transparent EC Private Key structure SHOULD 352 | // be used as a replacement. 353 | // 354 | // If the Key Format Type in the Key Block is Transparent ECMQV Private Key, then Key Material is a structure 355 | // as shown in Table 25. 356 | type TransparentECMQVPrivateKey TransparentECPrivateKey 357 | 358 | // TransparentECMQVPublicKey 2.1.7.13 Table 26 359 | // 360 | // The Transparent ECMQV Public Key structure is deprecated as of version 1.3 of this specification and MAY be 361 | // removed from subsequent versions of the specification. The Transparent EC Public Key structure SHOULD be used as 362 | // a replacement. 363 | // 364 | // If the Key Format Type in the Key Block is Transparent ECMQV Public Key, then Key Material is a structure as shown 365 | // in Table 26. 366 | type TransparentECMQVPublicKey TransparentECPublicKey 367 | 368 | // TransparentECPrivateKey 2.1.7.14 Table 27 369 | // 370 | // If the Key Format Type in the Key Block is Transparent EC Private Key, then Key Material is a structure as shown 371 | // in Table 27. 372 | type TransparentECPrivateKey struct { 373 | RecommendedCurve kmip14.RecommendedCurve 374 | D *big.Int `validate:"required"` 375 | } 376 | 377 | // TransparentECPublicKey 2.1.7.15 Table 28 378 | // 379 | // If the Key Format Type in the Key Block is Transparent EC Public Key, then Key Material is a structure as 380 | // shown in Table 28. 381 | type TransparentECPublicKey struct { 382 | RecommendedCurve kmip14.RecommendedCurve 383 | QString []byte `validate:"required"` 384 | } 385 | 386 | // TemplateAttribute 2.1.8 Table 29 387 | // 388 | // The Template Managed Object is deprecated as of version 1.3 of this specification and MAY be removed from 389 | // subsequent versions of the specification. Individual Attributes SHOULD be used in operations which currently 390 | // support use of a Name within a Template-Attribute to reference a Template. 391 | // 392 | // These structures are used in various operations to provide the desired attribute values and/or template 393 | // names in the request and to return the actual attribute values in the response. 394 | // 395 | // The Template-Attribute, Common Template-Attribute, Private Key Template-Attribute, and Public Key 396 | // Template-Attribute structures are defined identically as follows: 397 | // 398 | // type TemplateAttribute struct { 399 | // Attribute []Attribute 400 | // } 401 | type TemplateAttribute struct { 402 | Name []Name 403 | Attribute []Attribute 404 | } 405 | 406 | // Get returns a reference to the first Attribute in the list matching the name. 407 | // Returns nil if not found. 408 | func (t *TemplateAttribute) Get(s string) *Attribute { 409 | if t == nil { 410 | return nil 411 | } 412 | 413 | for i := range t.Attribute { 414 | if t.Attribute[i].AttributeName == s { 415 | return &t.Attribute[i] 416 | } 417 | } 418 | 419 | return nil 420 | } 421 | 422 | // GetIdx returns a reference to the Attribute in the list matching the name and index. 423 | // Returns nil if not found. 424 | func (t *TemplateAttribute) GetIdx(s string, idx int) *Attribute { 425 | if t == nil { 426 | return nil 427 | } 428 | 429 | for i := range t.Attribute { 430 | if t.Attribute[i].AttributeName == s && t.Attribute[i].AttributeIndex == idx { 431 | return &t.Attribute[i] 432 | } 433 | } 434 | 435 | return nil 436 | } 437 | 438 | // GetTag returns a reference to the first Attribute in the list matching the tag. 439 | // Returns nil if not found. 440 | func (t *TemplateAttribute) GetTag(tag ttlv.Tag) *Attribute { 441 | return t.Get(tag.String()) 442 | } 443 | 444 | // GetTagIdx returns a reference to the first Attribute in the list matching the tag and index. 445 | // Returns nil if not found. 446 | func (t *TemplateAttribute) GetTagIdx(tag ttlv.Tag, idx int) *Attribute { 447 | return t.GetIdx(tag.String(), idx) 448 | } 449 | 450 | func (t *TemplateAttribute) GetAll(s string) []Attribute { 451 | if t == nil { 452 | return nil 453 | } 454 | 455 | var ret []Attribute 456 | 457 | for i := range t.Attribute { 458 | if t.Attribute[i].AttributeName == s { 459 | ret = append(ret, t.Attribute[i]) 460 | } 461 | } 462 | 463 | return ret 464 | } 465 | 466 | func (t *TemplateAttribute) Append(tag ttlv.Tag, value interface{}) { 467 | t.Attribute = append(t.Attribute, NewAttributeFromTag(tag, 0, value)) 468 | } 469 | 470 | func (t *TemplateAttribute) GetAllTag(tag ttlv.Tag) []Attribute { 471 | return t.GetAll(tag.String()) 472 | } 473 | -------------------------------------------------------------------------------- /base_objects_test.go: -------------------------------------------------------------------------------- 1 | package kmip 2 | 3 | import ( 4 | "bufio" 5 | "crypto/rand" 6 | "crypto/tls" 7 | "os" 8 | "testing" 9 | 10 | "github.com/gemalto/kmip-go/kmip14" 11 | "github.com/gemalto/kmip-go/ttlv" 12 | "github.com/google/uuid" 13 | "github.com/stretchr/testify/assert" 14 | "github.com/stretchr/testify/require" 15 | ) 16 | 17 | var kmipServerAddr = "127.0.0.1:5696" 18 | 19 | func init() { 20 | // check env var for value of kmipServerAddr 21 | if addr := os.Getenv("KMIP_SERVER_ADDR"); addr != "" { 22 | kmipServerAddr = addr 23 | } 24 | } 25 | 26 | // clientConn returns a connection to the test kmip server. Should be closed at end of test. 27 | func clientConn(t *testing.T) *tls.Conn { 28 | t.Helper() 29 | 30 | cert, err := tls.LoadX509KeyPair("./pykmip-server/server.cert", "./pykmip-server/server.key") 31 | require.NoError(t, err) 32 | 33 | // the containerized pykmip we're using requires a very specific cipher suite, which isn't 34 | // enabled by go by default. 35 | tlsConfig := &tls.Config{ 36 | InsecureSkipVerify: true, 37 | Certificates: []tls.Certificate{cert}, 38 | CipherSuites: []uint16{ 39 | tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, 40 | }, 41 | } 42 | 43 | conn, err := tls.Dial("tcp", kmipServerAddr, tlsConfig) 44 | require.NoError(t, err) 45 | 46 | return conn 47 | } 48 | 49 | func TestCreateKey(t *testing.T) { 50 | conn := clientConn(t) 51 | defer conn.Close() 52 | 53 | biID := uuid.New() 54 | 55 | payload := CreateRequestPayload{ 56 | ObjectType: kmip14.ObjectTypeSymmetricKey, 57 | } 58 | 59 | payload.TemplateAttribute.Append(kmip14.TagCryptographicAlgorithm, kmip14.CryptographicAlgorithmAES) 60 | payload.TemplateAttribute.Append(kmip14.TagCryptographicLength, 256) 61 | payload.TemplateAttribute.Append(kmip14.TagCryptographicUsageMask, kmip14.CryptographicUsageMaskEncrypt|kmip14.CryptographicUsageMaskDecrypt) 62 | payload.TemplateAttribute.Append(kmip14.TagName, Name{ 63 | NameValue: "Key1", 64 | NameType: kmip14.NameTypeUninterpretedTextString, 65 | }) 66 | 67 | msg := RequestMessage{ 68 | RequestHeader: RequestHeader{ 69 | ProtocolVersion: ProtocolVersion{ 70 | ProtocolVersionMajor: 1, 71 | ProtocolVersionMinor: 4, 72 | }, 73 | BatchCount: 1, 74 | }, 75 | BatchItem: []RequestBatchItem{ 76 | { 77 | UniqueBatchItemID: biID[:], 78 | Operation: kmip14.OperationCreate, 79 | RequestPayload: &payload, 80 | }, 81 | }, 82 | } 83 | 84 | req, err := ttlv.Marshal(msg) 85 | require.NoError(t, err) 86 | 87 | t.Log(req) 88 | 89 | _, err = conn.Write(req) 90 | require.NoError(t, err) 91 | 92 | decoder := ttlv.NewDecoder(bufio.NewReader(conn)) 93 | resp, err := decoder.NextTTLV() 94 | require.NoError(t, err) 95 | 96 | t.Log(resp) 97 | 98 | var respMsg ResponseMessage 99 | err = decoder.DecodeValue(&respMsg, resp) 100 | require.NoError(t, err) 101 | 102 | assert.Equal(t, 1, respMsg.ResponseHeader.BatchCount) 103 | assert.Len(t, respMsg.BatchItem, 1) 104 | bi := respMsg.BatchItem[0] 105 | assert.Equal(t, kmip14.OperationCreate, bi.Operation) 106 | assert.NotEmpty(t, bi.UniqueBatchItemID) 107 | assert.Equal(t, kmip14.ResultStatusSuccess, bi.ResultStatus) 108 | 109 | var respPayload CreateResponsePayload 110 | err = decoder.DecodeValue(&respPayload, bi.ResponsePayload.(ttlv.TTLV)) 111 | require.NoError(t, err) 112 | 113 | assert.Equal(t, kmip14.ObjectTypeSymmetricKey, respPayload.ObjectType) 114 | assert.NotEmpty(t, respPayload.UniqueIdentifier) 115 | } 116 | 117 | func TestCreateKeyPair(t *testing.T) { 118 | conn := clientConn(t) 119 | defer conn.Close() 120 | 121 | biID := uuid.New() 122 | 123 | payload := CreateKeyPairRequestPayload{} 124 | payload.CommonTemplateAttribute = &TemplateAttribute{} 125 | payload.CommonTemplateAttribute.Append(kmip14.TagCryptographicAlgorithm, kmip14.CryptographicAlgorithmRSA) 126 | payload.CommonTemplateAttribute.Append(kmip14.TagCryptographicLength, 1024) 127 | payload.CommonTemplateAttribute.Append(kmip14.TagCryptographicUsageMask, kmip14.CryptographicUsageMaskSign|kmip14.CryptographicUsageMaskVerify) 128 | 129 | msg := RequestMessage{ 130 | RequestHeader: RequestHeader{ 131 | ProtocolVersion: ProtocolVersion{ 132 | ProtocolVersionMajor: 1, 133 | ProtocolVersionMinor: 4, 134 | }, 135 | BatchCount: 1, 136 | }, 137 | BatchItem: []RequestBatchItem{ 138 | { 139 | UniqueBatchItemID: biID[:], 140 | Operation: kmip14.OperationCreateKeyPair, 141 | RequestPayload: &payload, 142 | }, 143 | }, 144 | } 145 | 146 | req, err := ttlv.Marshal(msg) 147 | require.NoError(t, err) 148 | 149 | t.Log(req) 150 | 151 | _, err = conn.Write(req) 152 | require.NoError(t, err) 153 | 154 | decoder := ttlv.NewDecoder(bufio.NewReader(conn)) 155 | resp, err := decoder.NextTTLV() 156 | require.NoError(t, err) 157 | 158 | t.Log(resp) 159 | 160 | var respMsg ResponseMessage 161 | err = decoder.DecodeValue(&respMsg, resp) 162 | require.NoError(t, err) 163 | 164 | assert.Equal(t, 1, respMsg.ResponseHeader.BatchCount) 165 | assert.Len(t, respMsg.BatchItem, 1) 166 | bi := respMsg.BatchItem[0] 167 | assert.Equal(t, kmip14.OperationCreateKeyPair, bi.Operation) 168 | assert.NotEmpty(t, bi.UniqueBatchItemID) 169 | assert.Equal(t, kmip14.ResultStatusSuccess, bi.ResultStatus) 170 | 171 | var respPayload CreateKeyPairResponsePayload 172 | err = decoder.DecodeValue(&respPayload, bi.ResponsePayload.(ttlv.TTLV)) 173 | require.NoError(t, err) 174 | 175 | assert.NotEmpty(t, respPayload.PrivateKeyUniqueIdentifier) 176 | assert.NotEmpty(t, respPayload.PublicKeyUniqueIdentifier) 177 | } 178 | 179 | func TestRequest(t *testing.T) { 180 | conn := clientConn(t) 181 | defer conn.Close() 182 | 183 | biID := uuid.New() 184 | 185 | msg := RequestMessage{ 186 | RequestHeader: RequestHeader{ 187 | ProtocolVersion: ProtocolVersion{ 188 | ProtocolVersionMajor: 1, 189 | ProtocolVersionMinor: 2, 190 | }, 191 | BatchCount: 1, 192 | }, 193 | BatchItem: []RequestBatchItem{ 194 | { 195 | UniqueBatchItemID: biID[:], 196 | Operation: kmip14.OperationDiscoverVersions, 197 | RequestPayload: DiscoverVersionsRequestPayload{ 198 | ProtocolVersion: []ProtocolVersion{ 199 | {ProtocolVersionMajor: 1, ProtocolVersionMinor: 2}, 200 | }, 201 | }, 202 | }, 203 | }, 204 | } 205 | 206 | req, err := ttlv.Marshal(msg) 207 | require.NoError(t, err) 208 | 209 | t.Log(req) 210 | 211 | _, err = conn.Write(req) 212 | require.NoError(t, err) 213 | 214 | decoder := ttlv.NewDecoder(bufio.NewReader(conn)) 215 | resp, err := decoder.NextTTLV() 216 | require.NoError(t, err) 217 | 218 | t.Log(resp) 219 | 220 | var respMsg ResponseMessage 221 | err = decoder.DecodeValue(&respMsg, resp) 222 | require.NoError(t, err) 223 | 224 | assert.Equal(t, 1, respMsg.ResponseHeader.BatchCount) 225 | assert.Len(t, respMsg.BatchItem, 1) 226 | bi := respMsg.BatchItem[0] 227 | assert.Equal(t, kmip14.OperationDiscoverVersions, bi.Operation) 228 | assert.NotEmpty(t, bi.UniqueBatchItemID) 229 | assert.Equal(t, kmip14.ResultStatusSuccess, bi.ResultStatus) 230 | 231 | var discVerRespPayload struct { 232 | ProtocolVersion ProtocolVersion 233 | } 234 | err = decoder.DecodeValue(&discVerRespPayload, bi.ResponsePayload.(ttlv.TTLV)) 235 | require.NoError(t, err) 236 | assert.Equal(t, ProtocolVersion{ 237 | ProtocolVersionMajor: 1, 238 | ProtocolVersionMinor: 2, 239 | }, discVerRespPayload.ProtocolVersion) 240 | } 241 | 242 | func TestTemplateAttribute_marshal(t *testing.T) { 243 | tests := []struct { 244 | name string 245 | in TemplateAttribute 246 | inF func() TemplateAttribute 247 | expected ttlv.Value 248 | }{ 249 | { 250 | name: "basic", 251 | in: TemplateAttribute{ 252 | Name: []Name{ 253 | { 254 | NameValue: "first", 255 | NameType: kmip14.NameTypeUninterpretedTextString, 256 | }, 257 | { 258 | NameValue: "this is a uri", 259 | NameType: kmip14.NameTypeURI, 260 | }, 261 | }, 262 | Attribute: []Attribute{ 263 | { 264 | AttributeName: kmip14.TagAlwaysSensitive.CanonicalName(), 265 | AttributeIndex: 5, 266 | AttributeValue: true, 267 | }, 268 | }, 269 | }, 270 | expected: s(kmip14.TagTemplateAttribute, 271 | s(kmip14.TagName, 272 | v(kmip14.TagNameValue, "first"), 273 | v(kmip14.TagNameType, kmip14.NameTypeUninterpretedTextString), 274 | ), 275 | s(kmip14.TagName, 276 | v(kmip14.TagNameValue, "this is a uri"), 277 | v(kmip14.TagNameType, kmip14.NameTypeURI), 278 | ), 279 | s(kmip14.TagAttribute, 280 | v(kmip14.TagAttributeName, kmip14.TagAlwaysSensitive.CanonicalName()), 281 | v(kmip14.TagAttributeIndex, 5), 282 | v(kmip14.TagAttributeValue, true), 283 | ), 284 | ), 285 | }, 286 | { 287 | name: "noname", 288 | in: TemplateAttribute{Attribute: []Attribute{ 289 | { 290 | AttributeName: kmip14.TagAlwaysSensitive.CanonicalName(), 291 | AttributeIndex: 5, 292 | AttributeValue: true, 293 | }, 294 | }}, 295 | expected: s(kmip14.TagTemplateAttribute, 296 | s(kmip14.TagAttribute, 297 | v(kmip14.TagAttributeName, kmip14.TagAlwaysSensitive.CanonicalName()), 298 | v(kmip14.TagAttributeIndex, 5), 299 | v(kmip14.TagAttributeValue, true), 300 | ), 301 | ), 302 | }, 303 | { 304 | name: "noattribute", 305 | in: TemplateAttribute{ 306 | Name: []Name{ 307 | { 308 | NameValue: "first", 309 | NameType: kmip14.NameTypeUninterpretedTextString, 310 | }, 311 | }, 312 | }, 313 | expected: s(kmip14.TagTemplateAttribute, 314 | s(kmip14.TagName, 315 | v(kmip14.TagNameValue, "first"), 316 | v(kmip14.TagNameType, kmip14.NameTypeUninterpretedTextString), 317 | ), 318 | ), 319 | }, 320 | { 321 | name: "omitzeroindex", 322 | in: TemplateAttribute{ 323 | Attribute: []Attribute{ 324 | { 325 | AttributeName: kmip14.TagAlwaysSensitive.CanonicalName(), 326 | AttributeValue: true, 327 | }, 328 | }, 329 | }, 330 | expected: s(kmip14.TagTemplateAttribute, 331 | s(kmip14.TagAttribute, 332 | v(kmip14.TagAttributeName, kmip14.TagAlwaysSensitive.CanonicalName()), 333 | v(kmip14.TagAttributeValue, true), 334 | ), 335 | ), 336 | }, 337 | { 338 | name: "use canonical names", 339 | inF: func() TemplateAttribute { 340 | var ta TemplateAttribute 341 | ta.Append(kmip14.TagCryptographicAlgorithm, ttlv.EnumValue(kmip14.CryptographicAlgorithmBlowfish)) 342 | return ta 343 | }, 344 | expected: s(kmip14.TagTemplateAttribute, 345 | s(kmip14.TagAttribute, 346 | v(kmip14.TagAttributeName, "Cryptographic Algorithm"), 347 | v(kmip14.TagAttributeValue, ttlv.EnumValue(kmip14.CryptographicAlgorithmBlowfish)), 348 | ), 349 | ), 350 | }, 351 | } 352 | 353 | for _, test := range tests { 354 | t.Run(test.name, func(t *testing.T) { 355 | in := test.in 356 | if test.inF != nil { 357 | in = test.inF() 358 | } 359 | 360 | out, err := ttlv.Marshal(&in) 361 | require.NoError(t, err) 362 | 363 | expected, err := ttlv.Marshal(test.expected) 364 | require.NoError(t, err) 365 | 366 | require.Equal(t, out, expected) 367 | 368 | var ta TemplateAttribute 369 | err = ttlv.Unmarshal(expected, &ta) 370 | require.NoError(t, err) 371 | 372 | require.Equal(t, in, ta) 373 | }) 374 | } 375 | } 376 | 377 | func v(tag ttlv.Tag, val interface{}) ttlv.Value { 378 | return ttlv.NewValue(tag, val) 379 | } 380 | 381 | func s(tag ttlv.Tag, vals ...ttlv.Value) ttlv.Value { 382 | return ttlv.NewStruct(tag, vals...) 383 | } 384 | 385 | func TestGetResponsePayload_unmarshal(t *testing.T) { 386 | uniqueIdentifier := uuid.NewString() 387 | cryptographicLength := 256 388 | keyMaterial := RandomBytes(32) 389 | 390 | tests := []struct { 391 | name string 392 | input ttlv.Value 393 | expect GetResponsePayload 394 | }{ 395 | { 396 | name: "SymmetricKey", 397 | 398 | input: s(kmip14.TagResponsePayload, 399 | v(kmip14.TagObjectType, kmip14.ObjectTypeSymmetricKey), 400 | v(kmip14.TagUniqueIdentifier, uniqueIdentifier), 401 | s(kmip14.TagSymmetricKey, 402 | s(kmip14.TagKeyBlock, 403 | v(kmip14.TagKeyFormatType, kmip14.KeyFormatTypeRaw), 404 | s(kmip14.TagKeyValue, 405 | v(kmip14.TagKeyMaterial, keyMaterial), 406 | ), 407 | v(kmip14.TagCryptographicLength, cryptographicLength), 408 | v(kmip14.TagCryptographicAlgorithm, kmip14.CryptographicAlgorithmAES), 409 | ), 410 | ), 411 | ), 412 | 413 | expect: GetResponsePayload{ 414 | ObjectType: kmip14.ObjectTypeSymmetricKey, 415 | UniqueIdentifier: uniqueIdentifier, 416 | SymmetricKey: &SymmetricKey{ 417 | KeyBlock: KeyBlock{ 418 | KeyFormatType: kmip14.KeyFormatTypeRaw, 419 | KeyValue: &KeyValue{ 420 | KeyMaterial: keyMaterial, 421 | }, 422 | CryptographicLength: cryptographicLength, 423 | CryptographicAlgorithm: kmip14.CryptographicAlgorithmAES, 424 | }, 425 | }, 426 | }, 427 | }, 428 | } 429 | 430 | for _, test := range tests { 431 | t.Run(test.name, func(t *testing.T) { 432 | inputTTLV, err := ttlv.Marshal(test.input) 433 | require.NoError(t, err) 434 | 435 | var actualGRP GetResponsePayload 436 | err = ttlv.Unmarshal(inputTTLV, &actualGRP) 437 | require.NoError(t, err) 438 | 439 | require.Equal(t, test.expect, actualGRP) 440 | }) 441 | } 442 | } 443 | 444 | func RandomBytes(numBytes int) []byte { 445 | randomBytes := make([]byte, numBytes) 446 | if _, err := rand.Read(randomBytes); err != nil { 447 | panic(err) 448 | } 449 | return randomBytes 450 | } 451 | -------------------------------------------------------------------------------- /cmd/kmipgen/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/json" 7 | "flag" 8 | "fmt" 9 | "go/format" 10 | "log" 11 | "os" 12 | "path/filepath" 13 | "sort" 14 | "strconv" 15 | "strings" 16 | "text/template" 17 | 18 | "github.com/ansel1/merry" 19 | "github.com/gemalto/kmip-go/internal/kmiputil" 20 | ) 21 | 22 | // Specifications is the struct which the specifications JSON is unmarshaled into. 23 | type Specifications struct { 24 | // Enums is a collection of enumeration specifications, describing the name of the 25 | // enumeration value set, each of the values in the set, and the tag(s) using 26 | // this enumeration set. 27 | Enums []EnumDef `json:"enums"` 28 | // Masks is a collection of mask specifications, describing the name 29 | // of the mask set, the values, and the tag(s) using it. 30 | Masks []EnumDef `json:"masks"` 31 | // Tags is a map of names to tag values. The name should be 32 | // the full name, with spaces, from the spec. 33 | // The values may either be JSON numbers, or a JSON string 34 | // containing a hex encoded number, e.g. "0x42015E" 35 | Tags map[string]interface{} `json:"tags"` 36 | Package string `json:"-"` 37 | } 38 | 39 | // EnumDef describes a single enum or mask value. 40 | type EnumDef struct { 41 | // Name of the value. Names should be the full name 42 | // from the spec, including spaces. 43 | Name string `json:"name" validate:"required"` 44 | // Comment describing the value set. Generator will add this to 45 | // the golang source code comment on type generated for this value set. 46 | Comment string `json:"comment"` 47 | // Values is a map of names to enum values. Names should be the full name 48 | // from the spec, including spaces. 49 | // The values may either be JSON numbers, or a JSON string 50 | // containing a hex encoded number, e.g. "0x42015E" 51 | Values map[string]interface{} `json:"values"` 52 | // Tags is a list of tag names using this value set. Names should be the full name 53 | // // from the spec, including spaces. 54 | Tags []string `json:"tags"` 55 | } 56 | 57 | func main() { 58 | flag.Usage = func() { 59 | _, _ = fmt.Fprintln(flag.CommandLine.Output(), "Usage of kmipgen:") 60 | _, _ = fmt.Fprintln(flag.CommandLine.Output(), "") 61 | _, _ = fmt.Fprintln(flag.CommandLine.Output(), "Generates go code which registers tags, enumeration values, and mask values with kmip-go.") 62 | _, _ = fmt.Fprintln(flag.CommandLine.Output(), "Specifications are defined in a JSON file.") 63 | _, _ = fmt.Fprintln(flag.CommandLine.Output(), "") 64 | flag.PrintDefaults() 65 | } 66 | 67 | var inputFilename string 68 | var outputFilename string 69 | var packageName string 70 | var usage bool 71 | 72 | flag.StringVar(&inputFilename, "i", "", "Input `filename` of specifications. Required.") 73 | flag.StringVar(&outputFilename, "o", "", "Output `filename`. Defaults to standard out.") 74 | flag.StringVar(&packageName, "p", "ttlv", "Go `package` name in generated code.") 75 | flag.BoolVar(&usage, "h", false, "Show this usage message.") 76 | flag.Parse() 77 | 78 | if usage { 79 | flag.Usage() 80 | os.Exit(0) 81 | } 82 | 83 | if inputFilename == "" { 84 | fmt.Println("input file name cannot be empty") 85 | flag.Usage() 86 | os.Exit(1) 87 | } 88 | 89 | err := run(inputFilename, outputFilename, packageName) 90 | if err != nil { 91 | fmt.Println(merry.Details(err)) 92 | os.Exit(1) 93 | } 94 | } 95 | 96 | func run(inFilename, outFilename, packageName string) error { 97 | inputFile, err := os.Open(inFilename) 98 | if err != nil { 99 | fmt.Println("error opening input file: ", err.Error()) 100 | os.Exit(1) 101 | } 102 | defer inputFile.Close() 103 | 104 | specs := Specifications{ 105 | Package: packageName, 106 | } 107 | 108 | err = json.NewDecoder(bufio.NewReader(inputFile)).Decode(&specs) 109 | if err != nil { 110 | return merry.Prepend(err, "error reading input file") 111 | } 112 | 113 | var outputWriter *os.File 114 | outputWriter = os.Stdout 115 | 116 | if outFilename != "" { 117 | p, err := filepath.Abs(outFilename) 118 | if err != nil { 119 | return merry.Prepend(err, "can't resolve absolute path for output filename") 120 | } 121 | 122 | fmt.Println("writing to", p) 123 | 124 | f, err := os.Create(p) 125 | if err != nil { 126 | return merry.Prepend(err, "error creating output file") 127 | } 128 | 129 | outputWriter = f 130 | 131 | defer func() { 132 | err := f.Sync() 133 | if err != nil { 134 | fmt.Println("error syncing file: ", err.Error()) 135 | } 136 | 137 | err = f.Close() 138 | if err != nil { 139 | fmt.Println("error closing file: ", err.Error()) 140 | } 141 | }() 142 | } 143 | 144 | src, err := genCode(&specs) 145 | if err != nil { 146 | fmt.Println("error generating code: ", err.Error()) 147 | } 148 | 149 | _, err = outputWriter.WriteString(src) 150 | if err != nil { 151 | return merry.Prepend(err, "error writing to output file") 152 | } 153 | 154 | return nil 155 | } 156 | 157 | type tagVal struct { 158 | FullName string 159 | Name string 160 | Value uint32 161 | } 162 | 163 | type enumVal struct { 164 | Name string 165 | Comment string 166 | Var string 167 | TypeName string 168 | Vals []tagVal 169 | Tags []string 170 | BitMask bool 171 | } 172 | 173 | type inputs struct { 174 | Tags []tagVal 175 | Package string 176 | Imports []string 177 | TTLVPackage string 178 | Enums []enumVal 179 | Masks []enumVal 180 | } 181 | 182 | func parseUint32(v interface{}) (uint32, error) { 183 | switch n := v.(type) { 184 | case string: 185 | b, err := kmiputil.ParseHexValue(n, 4) 186 | if err != nil { 187 | return 0, err 188 | } 189 | 190 | if b != nil { 191 | return kmiputil.DecodeUint32(b), nil 192 | } 193 | 194 | i, err := strconv.ParseUint(n, 10, 32) 195 | if err != nil { 196 | return 0, merry.Prependf(err, "invalid integer value (%v)", n) 197 | } 198 | 199 | return uint32(i), nil 200 | case float64: 201 | return uint32(n), nil 202 | default: 203 | return 0, merry.New("value must be a number, or a hex string, like 0x42015E") 204 | } 205 | } 206 | 207 | func prepareInput(s *Specifications) (*inputs, error) { 208 | in := inputs{ 209 | Package: s.Package, 210 | } 211 | 212 | // prepare imports 213 | if s.Package != "ttlv" { 214 | in.Imports = append(in.Imports, "github.com/gemalto/kmip-go/ttlv") 215 | in.TTLVPackage = "ttlv." 216 | } 217 | 218 | // prepare tag inputs 219 | // normalize all the value names 220 | for key, value := range s.Tags { 221 | i, err := parseUint32(value) 222 | if err != nil { 223 | return nil, merry.Prependf(err, "invalid tag value (%v)", value) 224 | } 225 | 226 | val := tagVal{key, kmiputil.NormalizeName(key), i} 227 | in.Tags = append(in.Tags, val) 228 | } 229 | 230 | // sort tags by value 231 | sort.Slice(in.Tags, func(i, j int) bool { 232 | return in.Tags[i].Value < in.Tags[j].Value 233 | }) 234 | 235 | toEnumVal := func(v EnumDef) (enumVal, error) { 236 | ev := enumVal{ 237 | Name: v.Name, 238 | Comment: v.Comment, 239 | TypeName: kmiputil.NormalizeName(v.Name), 240 | } 241 | ev.Var = strings.ToLower(string([]rune(ev.TypeName)[:1])) 242 | 243 | // normalize all the value names 244 | for key, value := range v.Values { 245 | n := kmiputil.NormalizeName(key) 246 | 247 | i, err := parseUint32(value) 248 | if err != nil { 249 | return enumVal{}, merry.Prependf(err, "invalid tag value (%v)", value) 250 | } 251 | 252 | ev.Vals = append(ev.Vals, tagVal{key, n, i}) 253 | } 254 | 255 | // sort the vals by value order 256 | sort.Slice(ev.Vals, func(i, j int) bool { 257 | return ev.Vals[i].Value < ev.Vals[j].Value 258 | }) 259 | 260 | // normalize the tag names 261 | for _, t := range v.Tags { 262 | ev.Tags = append(ev.Tags, kmiputil.NormalizeName(t)) 263 | } 264 | 265 | return ev, nil 266 | } 267 | 268 | // prepare enum and mask values 269 | for _, v := range s.Enums { 270 | ev, err := toEnumVal(v) 271 | if err != nil { 272 | return nil, merry.Prependf(err, "error parsing enum %v", v.Name) 273 | } 274 | 275 | in.Enums = append(in.Enums, ev) 276 | } 277 | 278 | for _, v := range s.Masks { 279 | ev, err := toEnumVal(v) 280 | if err != nil { 281 | return nil, merry.Prependf(err, "error parsing mask %v", v.Name) 282 | } 283 | 284 | ev.BitMask = true 285 | in.Masks = append(in.Masks, ev) 286 | } 287 | 288 | return &in, nil 289 | } 290 | 291 | func genCode(s *Specifications) (string, error) { 292 | buf := bytes.NewBuffer(nil) 293 | 294 | in, err := prepareInput(s) 295 | if err != nil { 296 | return "", err 297 | } 298 | 299 | tmpl := template.New("root") 300 | tmpl.Funcs(template.FuncMap{ 301 | "ttlvPackage": func() string { return in.TTLVPackage }, 302 | }) 303 | template.Must(tmpl.Parse(global)) 304 | template.Must(tmpl.New("tags").Parse(tags)) 305 | template.Must(tmpl.New("base").Parse(baseTmpl)) 306 | template.Must(tmpl.New("enumeration").Parse(enumerationTmpl)) 307 | template.Must(tmpl.New("mask").Parse(maskTmpl)) 308 | 309 | err = tmpl.Execute(buf, in) 310 | 311 | if err != nil { 312 | return "", merry.Prepend(err, "executing template") 313 | } 314 | 315 | // format returns the gofmt-ed contents of the Generator's buffer. 316 | src, err := format.Source(buf.Bytes()) 317 | if err != nil { 318 | // Should never happen, but can arise when developing this code. 319 | // The user can compile the output to see the error. 320 | log.Printf("warning: internal error: invalid Go generated: %s", err) 321 | log.Printf("warning: compile the package to analyze the error") 322 | 323 | return buf.String(), nil 324 | } 325 | 326 | return string(src), nil 327 | } 328 | 329 | const global = `// Code generated by kmipgen; DO NOT EDIT. 330 | package {{.Package}} 331 | 332 | {{with .Imports}} 333 | import ( 334 | {{range .}} "{{.}}" 335 | {{end}}) 336 | {{end}} 337 | 338 | {{with .Tags}}{{template "tags" .}}{{end}} 339 | 340 | {{with .Enums}}{{range .}}{{template "enumeration" .}}{{end}}{{end}} 341 | 342 | {{with .Masks}}{{range .}}{{template "mask" .}}{{end}}{{end}} 343 | 344 | func RegisterGeneratedDefinitions(r *{{ttlvPackage}}Registry) { 345 | 346 | tags := map[{{ttlvPackage}}Tag]string { 347 | {{range .Tags}} Tag{{.Name}}: "{{.FullName}}", 348 | {{end}} 349 | } 350 | 351 | for v, name := range tags { 352 | r.RegisterTag(v, name) 353 | } 354 | 355 | enums := map[string]{{ttlvPackage}}Enum { 356 | {{range .Enums}}{{ $typeName := .TypeName }}{{range .Tags}} "{{.}}": {{$typeName}}Enum, 357 | {{end}}{{end}} 358 | {{range .Masks}}{{ $typeName := .TypeName }}{{range .Tags}} "{{.}}": {{$typeName}}Enum, 359 | {{end}}{{end}} 360 | } 361 | 362 | for tagName, enum := range enums { 363 | tag, err := {{ttlvPackage}}DefaultRegistry.ParseTag(tagName) 364 | if err != nil { 365 | panic(err) 366 | } 367 | e := enum 368 | r.RegisterEnum(tag, &e) 369 | } 370 | } 371 | ` 372 | 373 | const tags = ` 374 | const ( 375 | {{range .}} Tag{{.Name}} {{ttlvPackage}}Tag = {{.Value | printf "%#06x"}} 376 | {{end}}) 377 | ` 378 | 379 | const baseTmpl = `{{ $typeName := .TypeName }}// {{.Comment}} 380 | type {{.TypeName}} uint32 381 | 382 | const ({{range .Vals}} 383 | {{$typeName}}{{.Name}} {{$typeName}} = {{.Value | printf "%#08x"}}{{end}} 384 | ) 385 | 386 | var {{.TypeName}}Enum = New{{.TypeName}}Enum() 387 | 388 | func New{{.TypeName}}Enum() {{ttlvPackage}}Enum { 389 | m := map[{{.TypeName}}]string { 390 | {{range .Vals}} {{$typeName}}{{.Name}}: "{{.FullName}}", 391 | {{end}} 392 | } 393 | 394 | e := {{if .BitMask}}{{ttlvPackage}}NewBitmask{{else}}{{ttlvPackage}}NewEnum{{end}}() 395 | for v, name := range m { 396 | e.RegisterValue(uint32(v), name) 397 | } 398 | 399 | return e 400 | } 401 | 402 | func ({{.Var}} {{.TypeName}}) MarshalText() (text []byte, err error) { 403 | return []byte({{.Var}}.String()), nil 404 | }` 405 | 406 | const enumerationTmpl = `// {{.Name}} Enumeration 407 | {{template "base" . }} 408 | 409 | func ({{.Var}} {{.TypeName}}) MarshalTTLV(enc *{{ttlvPackage}}Encoder, tag {{ttlvPackage}}Tag) error { 410 | enc.EncodeEnumeration(tag, uint32({{.Var}})) 411 | return nil 412 | } 413 | 414 | func ({{.Var}} {{.TypeName}}) String() string { 415 | return {{ttlvPackage}}FormatEnum(uint32({{.Var}}), &{{.TypeName}}Enum) 416 | } 417 | 418 | ` 419 | 420 | const maskTmpl = `// {{.Name}} Bit Mask 421 | {{template "base" . }} 422 | 423 | func ({{.Var}} {{.TypeName}}) MarshalTTLV(enc *{{ttlvPackage}}Encoder, tag {{ttlvPackage}}Tag) error { 424 | enc.EncodeInteger(tag, int32({{.Var}})) 425 | return nil 426 | } 427 | 428 | func ({{.Var}} {{.TypeName}}) String() string { 429 | return {{ttlvPackage}}FormatInt(int32({{.Var}}), &{{.TypeName}}Enum) 430 | } 431 | 432 | ` 433 | -------------------------------------------------------------------------------- /cmd/ppkmip/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/hex" 7 | "encoding/json" 8 | "encoding/xml" 9 | "errors" 10 | "flag" 11 | "fmt" 12 | "io" 13 | "os" 14 | "strings" 15 | 16 | _ "github.com/gemalto/kmip-go/kmip14" 17 | _ "github.com/gemalto/kmip-go/kmip20" 18 | "github.com/gemalto/kmip-go/ttlv" 19 | ) 20 | 21 | const ( 22 | FormatJSON = "json" 23 | FormatXML = "xml" 24 | FormatHex = "hex" 25 | ) 26 | 27 | func main() { 28 | flag.Usage = func() { 29 | s := `ppkmip - kmip pretty printer 30 | 31 | Usage: ppkmip [options] [input] 32 | 33 | Pretty prints KMIP. Can read KMIP in hex, json, or xml formats, 34 | and print it out in pretty-printed json, xml, text, raw hex, or 35 | pretty printed hex. 36 | 37 | The input argument should be a string. If not present, input will 38 | be read from standard in. 39 | 40 | When reading hex input, any non-hex characters, such as whitespace or 41 | embedded formatting characters, will be ignored. The 'hexpretty' 42 | output format embeds such characters, but because they are ignored, 43 | 'hexpretty' output is still valid 'hex' input. 44 | 45 | The default output format is "text", which is optimized for human readability, 46 | but not for machine parsing. It can't be used as input. 47 | 48 | The json and xml input/output formats are compliant with the KMIP spec, and 49 | should be compatible with other KMIP tooling. 50 | 51 | Examples: 52 | 53 | ppkmip 420069010000002042006a0200000004000000010000000042006b02000000040000000000000000 54 | echo "420069010000002042006a0200000004000000010000000042006b02000000040000000000000000" | ppkmip 55 | 56 | Output (in 'text' format): 57 | 58 | ProtocolVersion (Structure/32): 59 | ProtocolVersionMajor (Integer/4): 1 60 | ProtocolVersionMinor (Integer/4): 0 61 | 62 | hex format: 63 | 64 | 420069010000002042006a0200000004000000010000000042006b02000000040000000000000000 65 | 66 | prettyhex format: 67 | 68 | 420069 | 01 | 00000020 69 | 42006a | 02 | 00000004 | 0000000100000000 70 | 42006b | 02 | 00000004 | 0000000000000000 71 | 72 | json format: 73 | 74 | { 75 | "tag": "ProtocolVersion", 76 | "value": [ 77 | { 78 | "tag": "ProtocolVersionMajor", 79 | "type": "Integer", 80 | "value": 1 81 | }, 82 | { 83 | "tag": "ProtocolVersionMinor", 84 | "type": "Integer", 85 | "value": 0 86 | } 87 | ] 88 | } 89 | 90 | xml format: 91 | 92 | 93 | 94 | 95 | 96 | ` 97 | _, _ = fmt.Fprintln(flag.CommandLine.Output(), s) 98 | flag.PrintDefaults() 99 | } 100 | 101 | var inFormat string 102 | var outFormat string 103 | var inFile string 104 | 105 | flag.StringVar(&inFormat, "i", "", "input format: hex|json|xml, defaults to auto detect") 106 | flag.StringVar(&outFormat, "o", "", "output format: text|hex|prettyhex|json|xml, defaults to text") 107 | flag.StringVar(&inFile, "f", "", "input file name, defaults to stdin") 108 | 109 | flag.Parse() 110 | 111 | buf := bytes.NewBuffer(nil) 112 | 113 | if inFile != "" { 114 | file, err := os.ReadFile(inFile) 115 | if err != nil { 116 | fail("error reading input file", err) 117 | } 118 | 119 | buf = bytes.NewBuffer(file) 120 | } else if inArg := flag.Arg(0); inArg != "" { 121 | buf.WriteString(inArg) 122 | } else { 123 | scanner := bufio.NewScanner(os.Stdin) 124 | 125 | for scanner.Scan() { 126 | buf.Write(scanner.Bytes()) 127 | } 128 | 129 | if err := scanner.Err(); err != nil { 130 | fail("error reading standard input", err) 131 | } 132 | } 133 | 134 | if inFormat == "" { 135 | // auto detect input format 136 | switch buf.Bytes()[0] { 137 | case '[', '{': 138 | inFormat = FormatJSON 139 | case '<': 140 | inFormat = FormatXML 141 | default: 142 | inFormat = FormatHex 143 | } 144 | } 145 | 146 | outFormat = strings.ToLower(outFormat) 147 | if outFormat == "" { 148 | outFormat = "text" 149 | } 150 | 151 | var count int 152 | 153 | switch strings.ToLower(inFormat) { 154 | case FormatJSON: 155 | var raw ttlv.TTLV 156 | 157 | decoder := json.NewDecoder(buf) 158 | 159 | for { 160 | err := decoder.Decode(&raw) 161 | 162 | switch { 163 | case errors.Is(err, io.EOF): 164 | return 165 | case err == nil: 166 | default: 167 | fail("error parsing JSON", err) 168 | } 169 | 170 | printTTLV(outFormat, raw, count) 171 | 172 | count++ 173 | } 174 | 175 | case FormatXML: 176 | var raw ttlv.TTLV 177 | decoder := xml.NewDecoder(buf) 178 | 179 | for { 180 | err := decoder.Decode(&raw) 181 | 182 | switch { 183 | case errors.Is(err, io.EOF): 184 | return 185 | case err == nil: 186 | default: 187 | fail("error parsing XML", err) 188 | } 189 | 190 | printTTLV(outFormat, raw, count) 191 | count++ 192 | } 193 | case FormatHex: 194 | raw := ttlv.TTLV(ttlv.Hex2bytes(buf.String())) 195 | 196 | for len(raw) > 0 { 197 | printTTLV(outFormat, raw, count) 198 | 199 | count++ 200 | raw = raw.Next() 201 | } 202 | default: 203 | fail("invalid input format: "+inFormat, nil) 204 | } 205 | } 206 | 207 | func printTTLV(outFormat string, raw ttlv.TTLV, count int) { 208 | if count > 0 { 209 | fmt.Println("") 210 | } 211 | 212 | switch outFormat { 213 | case "text": 214 | if err := ttlv.Print(os.Stdout, "", " ", raw); err != nil { 215 | fail("error printing", err) 216 | } 217 | case "json": 218 | s, err := json.MarshalIndent(raw, "", " ") 219 | if err != nil { 220 | fail("error printing JSON", err) 221 | } 222 | 223 | fmt.Print(string(s)) 224 | case "xml": 225 | s, err := xml.MarshalIndent(raw, "", " ") 226 | if err != nil { 227 | fail("error printing XML", err) 228 | } 229 | 230 | fmt.Print(string(s)) 231 | case "hex": 232 | fmt.Print(hex.EncodeToString(raw)) 233 | case "prettyhex": 234 | if err := ttlv.PrintPrettyHex(os.Stdout, "", " ", raw); err != nil { 235 | fail("error printing", err) 236 | } 237 | } 238 | } 239 | 240 | func fail(msg string, err error) { 241 | if err != nil { 242 | _, _ = fmt.Fprintln(os.Stderr, msg+":", err) 243 | } else { 244 | _, _ = fmt.Fprintln(os.Stderr, msg) 245 | } 246 | 247 | os.Exit(1) 248 | } 249 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | builder: 3 | cap_drop: 4 | - ALL 5 | build: 6 | context: . 7 | environment: 8 | CGO_ENABLED: 0 9 | KMIP_SERVER_ADDR: pykmip-server:5696 10 | volumes: 11 | - ./build:/project/build 12 | 13 | dependencies: 14 | image: waisbrot/wait 15 | environment: 16 | TARGETS: pykmip-server:5696 17 | depends_on: 18 | - pykmip-server 19 | 20 | pykmip-server: 21 | cap_drop: 22 | - ALL 23 | build: 24 | context: pykmip-server 25 | ports: 26 | - 5696:5696 -------------------------------------------------------------------------------- /docs.go: -------------------------------------------------------------------------------- 1 | // Package kmip is a general purpose KMIP library for implementing KMIP services and clients. 2 | // 3 | // The ttlv sub package contains the core logic for parsing the KMIP TTLV encoding formats, 4 | // and marshaling them to and from golang structs. 5 | // 6 | // This package defines structs for many of the structures defined in the KMIP Spec, such as 7 | // the different types of managed objects, request and response bodies, etc. Not all Structures 8 | // are represented here yet, but the ones that are can be used as examples. 9 | // 10 | // There is also a partial implementation of a server, and an example of a client. There is 11 | // currently no Client type for KMIP, but it is simple to open a socket overwhich you send 12 | // and receive raw KMIP requests and responses. 13 | package kmip 14 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package kmip 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/ansel1/merry" 8 | "github.com/gemalto/kmip-go/kmip14" 9 | ) 10 | 11 | func Details(err error) string { 12 | return merry.Details(err) 13 | } 14 | 15 | var ErrInvalidTag = errors.New("invalid tag") 16 | 17 | type errKey int 18 | 19 | const ( 20 | errorKeyResultReason errKey = iota 21 | ) 22 | 23 | //nolint:gochecknoinits 24 | func init() { 25 | merry.RegisterDetail("Result Reason", errorKeyResultReason) 26 | } 27 | 28 | func WithResultReason(err error, rr kmip14.ResultReason) error { 29 | return merry.WithValue(err, errorKeyResultReason, rr) 30 | } 31 | 32 | func GetResultReason(err error) kmip14.ResultReason { 33 | v := merry.Value(err, errorKeyResultReason) 34 | switch t := v.(type) { 35 | case nil: 36 | return kmip14.ResultReason(0) 37 | case kmip14.ResultReason: 38 | return t 39 | default: 40 | panic(fmt.Sprintf("err result reason attribute's value was wrong type, expected ResultReason, got %T", v)) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples_test.go: -------------------------------------------------------------------------------- 1 | package kmip_test 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "net" 7 | "time" 8 | 9 | "github.com/gemalto/kmip-go" 10 | "github.com/gemalto/kmip-go/kmip14" 11 | "github.com/gemalto/kmip-go/ttlv" 12 | "github.com/google/uuid" 13 | ) 14 | 15 | func Example_client() { 16 | conn, err := net.DialTimeout("tcp", "localhost:5696", 3*time.Second) 17 | if err != nil { 18 | panic(err) 19 | } 20 | 21 | biID := uuid.New() 22 | 23 | msg := kmip.RequestMessage{ 24 | RequestHeader: kmip.RequestHeader{ 25 | ProtocolVersion: kmip.ProtocolVersion{ 26 | ProtocolVersionMajor: 1, 27 | ProtocolVersionMinor: 2, 28 | }, 29 | BatchCount: 1, 30 | }, 31 | BatchItem: []kmip.RequestBatchItem{ 32 | { 33 | UniqueBatchItemID: biID[:], 34 | Operation: kmip14.OperationDiscoverVersions, 35 | RequestPayload: kmip.DiscoverVersionsRequestPayload{ 36 | ProtocolVersion: []kmip.ProtocolVersion{ 37 | {ProtocolVersionMajor: 1, ProtocolVersionMinor: 2}, 38 | }, 39 | }, 40 | }, 41 | }, 42 | } 43 | 44 | req, err := ttlv.Marshal(msg) 45 | if err != nil { 46 | panic(err) 47 | } 48 | 49 | fmt.Println(req) 50 | 51 | _, err = conn.Write(req) 52 | if err != nil { 53 | panic(err) 54 | } 55 | 56 | buf := make([]byte, 5000) 57 | _, err = bufio.NewReader(conn).Read(buf) 58 | if err != nil { 59 | panic(err) 60 | } 61 | 62 | resp := ttlv.TTLV(buf) 63 | fmt.Println(resp) 64 | } 65 | 66 | func ExampleServer() { 67 | listener, err := net.Listen("tcp", "0.0.0.0:5696") 68 | if err != nil { 69 | panic(err) 70 | } 71 | 72 | kmip.DefaultProtocolHandler.LogTraffic = true 73 | 74 | kmip.DefaultOperationMux.Handle(kmip14.OperationDiscoverVersions, &kmip.DiscoverVersionsHandler{ 75 | SupportedVersions: []kmip.ProtocolVersion{ 76 | { 77 | ProtocolVersionMajor: 1, 78 | ProtocolVersionMinor: 4, 79 | }, 80 | { 81 | ProtocolVersionMajor: 1, 82 | ProtocolVersionMinor: 3, 83 | }, 84 | { 85 | ProtocolVersionMajor: 1, 86 | ProtocolVersionMinor: 2, 87 | }, 88 | }, 89 | }) 90 | srv := kmip.Server{} 91 | panic(srv.Serve(listener)) 92 | } 93 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gemalto/kmip-go 2 | 3 | go 1.22.12 4 | 5 | toolchain go1.24.2 6 | 7 | require ( 8 | github.com/ansel1/merry v1.8.0 9 | github.com/gemalto/flume v1.0.0 10 | github.com/google/uuid v1.6.0 11 | github.com/stretchr/testify v1.8.4 12 | golang.org/x/text v0.22.0 13 | ) 14 | 15 | require ( 16 | github.com/ansel1/merry/v2 v2.2.1 // indirect 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/mattn/go-colorable v0.1.14 // indirect 19 | github.com/mattn/go-isatty v0.0.20 // indirect 20 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect 21 | github.com/pmezard/go-difflib v1.0.0 // indirect 22 | go.uber.org/multierr v1.11.0 // indirect 23 | go.uber.org/zap v1.27.0 // indirect 24 | golang.org/x/sys v0.30.0 // indirect 25 | gopkg.in/yaml.v3 v3.0.1 // indirect 26 | ) 27 | -------------------------------------------------------------------------------- /internal/kmiputil/hex_values.go: -------------------------------------------------------------------------------- 1 | package kmiputil 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/hex" 6 | "strings" 7 | 8 | "github.com/ansel1/merry" 9 | ) 10 | 11 | var ErrInvalidHexString = merry.New("invalid hex string") 12 | 13 | func DecodeUint32(b []byte) uint32 { 14 | // pad to 4 bytes with leading zeros 15 | return binary.BigEndian.Uint32(pad(b, 4)) 16 | } 17 | 18 | func DecodeUint64(b []byte) uint64 { 19 | // pad to 8 bytes with leading zeros 20 | return binary.BigEndian.Uint64(pad(b, 8)) 21 | } 22 | 23 | func pad(b []byte, l int) []byte { 24 | if len(b) < l { 25 | b2 := make([]byte, l) 26 | copy(b2[l-len(b):], b) 27 | b = b2 28 | } 29 | 30 | return b 31 | } 32 | 33 | // ParseHexValue attempts to parse a string formatted as a hex value 34 | // as described in the KMIP Profiles spec, in the "Hex representations" section. 35 | // 36 | // If the string doesn't start with the required prefix "0x", it is assumed the string 37 | // is not a hex representation, and nil, nil is returned. 38 | // 39 | // An ErrInvalidHexString is returned if the hex parsing fails. 40 | // If the maxLen argument is >0, ErrInvalidHexString is returned if the number of bytes parsed 41 | // is greater than maxLen, ignoring leading zeros. All bytes parsed are returned (including 42 | // leading zeros). 43 | func ParseHexValue(s string, maxLen int) ([]byte, error) { 44 | if !strings.HasPrefix(s, "0x") { 45 | return nil, nil 46 | } 47 | 48 | b, err := hex.DecodeString(s[2:]) 49 | if err != nil { 50 | return nil, merry.WithCause(ErrInvalidHexString, err).Append(err.Error()) 51 | } 52 | 53 | if maxLen > 0 { 54 | l := len(b) 55 | // minus leading zeros 56 | for i := 0; i < len(b) && b[i] == 0; i++ { 57 | l-- 58 | } 59 | 60 | if l > maxLen { 61 | return nil, merry.Appendf(ErrInvalidHexString, "must be %v bytes", maxLen) 62 | } 63 | } 64 | 65 | return b, nil 66 | } 67 | -------------------------------------------------------------------------------- /internal/kmiputil/names.go: -------------------------------------------------------------------------------- 1 | package kmiputil 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | 7 | "golang.org/x/text/cases" 8 | "golang.org/x/text/language" 9 | ) 10 | 11 | var ( 12 | nonWordAtWordBoundary = regexp.MustCompile(`(\W)([a-zA-Z][a-z])`) 13 | startingDigits = regexp.MustCompile(`^([\d]+)(.*)`) 14 | ) 15 | 16 | // NormalizeName converts a string into the CamelCase format required for the XML and JSON encoding 17 | // of KMIP values. It should be used for tag names, type names, and enumeration value names. 18 | // Implementation of 5.4.1.1 and 5.5.1.1 from the KMIP Profiles specification. 19 | func NormalizeName(s string) string { 20 | // 1. Replace round brackets ‘(‘, ‘)’ with spaces 21 | s = strings.Map(func(r rune) rune { 22 | switch r { 23 | case '(', ')': 24 | return ' ' 25 | } 26 | 27 | return r 28 | }, s) 29 | 30 | // 2. If a non-word char (not alpha, digit or underscore) is followed by a letter (either upper or lower case) then a lower case letter, replace the non-word char with space 31 | s = nonWordAtWordBoundary.ReplaceAllString(s, " $2") 32 | 33 | // 3. Replace remaining non-word chars (except whitespace) with underscore. 34 | s = strings.Map(func(r rune) rune { 35 | switch { 36 | case r >= 'a' && r <= 'z': 37 | case r >= 'A' && r <= 'Z': 38 | case r >= '0' && r <= '9': 39 | case r == '_': 40 | case r == ' ': 41 | default: 42 | return '_' 43 | } 44 | 45 | return r 46 | }, s) 47 | 48 | words := strings.Split(s, " ") 49 | 50 | for i, w := range words { 51 | if i == 0 { 52 | // 4. If the first word begins with a digit, move all digits at start of first word to end of first word 53 | w = startingDigits.ReplaceAllString(w, `$2$1`) 54 | } 55 | 56 | // 5. Capitalize the first letter of each word 57 | words[i] = cases.Title(language.AmericanEnglish, cases.NoLower).String(w) 58 | } 59 | 60 | // 6. Concatenate all words with spaces removed 61 | return strings.Join(words, "") 62 | } 63 | -------------------------------------------------------------------------------- /kmip14/kmip_1_4.go: -------------------------------------------------------------------------------- 1 | //go:generate go run ../cmd/kmipgen/main.go -o kmip_1_4_generated.go -i kmip_1_4.json -p kmip14 2 | 3 | // Package kmip14 contains tag and enumeration value definitions from the 1.4 specification. 4 | // These definitions will be registered automatically into the DefaultRegistry. 5 | // 6 | // Each tag is stored in a package constant, named Tag. 7 | // Bitmask and Enumeration values are each represented by a type, named 8 | // after the normalized name of the values set from the spec, e.g. 9 | package kmip14 10 | 11 | import ( 12 | "github.com/gemalto/kmip-go/ttlv" 13 | ) 14 | 15 | //nolint:gochecknoinits 16 | func init() { 17 | Register(&ttlv.DefaultRegistry) 18 | } 19 | 20 | // Registers the 1.4 enumeration values with the registry. 21 | func Register(registry *ttlv.Registry) { 22 | RegisterGeneratedDefinitions(registry) 23 | } 24 | -------------------------------------------------------------------------------- /kmip20/kmip_2_0_additions.go: -------------------------------------------------------------------------------- 1 | //go:generate go run ../cmd/kmipgen/main.go -o kmip_2_0_additions_generated.go -i kmip_2_0_additions.json -p kmip20 2 | 3 | // Package kmip20 contains definitions from the 2.0 specification. They should eventually 4 | // be merged into the kmip_1_4.json (and that should be renamed to kmip_2_0_specs.json), 5 | // but I didn't have time to merge them in yet. Just keeping them parked here until I have time 6 | // to incorporate them. 7 | // TODO: should the different versions of the spec be kept in separate declaration files? Or should 8 | // the ttlv package add a spec version attribute to registration, so servers/clients can configure which 9 | // spec version they want to use, and ttlv would automatically filter allowed values on that? 10 | package kmip20 11 | 12 | import ( 13 | "github.com/gemalto/kmip-go/kmip14" 14 | "github.com/gemalto/kmip-go/ttlv" 15 | ) 16 | 17 | //nolint:gochecknoinits 18 | func init() { 19 | Register(&ttlv.DefaultRegistry) 20 | } 21 | 22 | // Register registers all the additional definitions from the KMIP 2.0 spec. The registry 23 | // should already contain the 1.4 definitions. 24 | func Register(registry *ttlv.Registry) { 25 | // register new 2.0 values 26 | // KMIP 2.0 introduces a tag named "Attribute Reference", whose value is the enumeration of all Tags 27 | registry.RegisterEnum(TagAttributeReference, registry.Tags()) 28 | 29 | // KMIP 2.0 has made the value of the Extension Type tag an enumeration of all type values 30 | registry.RegisterEnum(kmip14.TagExtensionType, registry.Types()) 31 | 32 | RegisterGeneratedDefinitions(registry) 33 | } 34 | -------------------------------------------------------------------------------- /kmip20/op_activate.go: -------------------------------------------------------------------------------- 1 | //nolint:dupl 2 | package kmip20 3 | 4 | import ( 5 | "context" 6 | 7 | "github.com/gemalto/kmip-go" 8 | ) 9 | 10 | // 4.19 Activate 11 | 12 | // Table 210 13 | 14 | type ActivateRequestPayload struct { 15 | UniqueIdentifier *UniqueIdentifierValue 16 | } 17 | 18 | // Table 211 19 | 20 | type ActivateResponsePayload struct { 21 | UniqueIdentifier string 22 | } 23 | 24 | type ActivateHandler struct { 25 | Activate func(ctx context.Context, payload *ActivateRequestPayload) (*ActivateResponsePayload, error) 26 | } 27 | 28 | func (h *ActivateHandler) HandleItem(ctx context.Context, req *kmip.Request) (*kmip.ResponseBatchItem, error) { 29 | var payload ActivateRequestPayload 30 | 31 | err := req.DecodePayload(&payload) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | respPayload, err := h.Activate(ctx, &payload) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | return &kmip.ResponseBatchItem{ 42 | ResponsePayload: respPayload, 43 | }, nil 44 | } 45 | -------------------------------------------------------------------------------- /kmip20/op_destroy.go: -------------------------------------------------------------------------------- 1 | //nolint:dupl 2 | package kmip20 3 | 4 | import ( 5 | "context" 6 | 7 | "github.com/gemalto/kmip-go" 8 | ) 9 | 10 | // 6.1.15 Destroy 11 | 12 | // Table 193 13 | 14 | type DestroyRequestPayload struct { 15 | UniqueIdentifier *UniqueIdentifierValue 16 | } 17 | 18 | // Table 194 19 | 20 | type DestroyResponsePayload struct { 21 | UniqueIdentifier string 22 | } 23 | 24 | type DestroyHandler struct { 25 | Destroy func(ctx context.Context, payload *DestroyRequestPayload) (*DestroyResponsePayload, error) 26 | } 27 | 28 | func (h *DestroyHandler) HandleItem(ctx context.Context, req *kmip.Request) (*kmip.ResponseBatchItem, error) { 29 | var payload DestroyRequestPayload 30 | 31 | err := req.DecodePayload(&payload) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | respPayload, err := h.Destroy(ctx, &payload) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | // req.Key = respPayload.Key 42 | 43 | return &kmip.ResponseBatchItem{ 44 | ResponsePayload: respPayload, 45 | }, nil 46 | } 47 | -------------------------------------------------------------------------------- /kmip20/op_get.go: -------------------------------------------------------------------------------- 1 | package kmip20 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/gemalto/kmip-go" 7 | "github.com/gemalto/kmip-go/kmip14" 8 | ) 9 | 10 | // GetRequestPayload //////////////////////////////////////// 11 | type GetRequestPayload struct { 12 | UniqueIdentifier *UniqueIdentifierValue 13 | } 14 | 15 | // GetResponsePayload 16 | type GetResponsePayload struct { 17 | ObjectType kmip14.ObjectType 18 | UniqueIdentifier string 19 | Key kmip.SymmetricKey 20 | } 21 | 22 | type GetHandler struct { 23 | Get func(ctx context.Context, payload *GetRequestPayload) (*GetResponsePayload, error) 24 | } 25 | 26 | func (h *GetHandler) HandleItem(ctx context.Context, req *kmip.Request) (*kmip.ResponseBatchItem, error) { 27 | var payload GetRequestPayload 28 | 29 | err := req.DecodePayload(&payload) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | respPayload, err := h.Get(ctx, &payload) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | // req.Key = respPayload.Key 40 | req.IDPlaceholder = respPayload.UniqueIdentifier 41 | 42 | return &kmip.ResponseBatchItem{ 43 | ResponsePayload: respPayload, 44 | }, nil 45 | } 46 | -------------------------------------------------------------------------------- /kmip20/op_locate.go: -------------------------------------------------------------------------------- 1 | package kmip20 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/gemalto/kmip-go" 7 | ) 8 | 9 | // 6.1.27 Locate 10 | 11 | // Table 229 12 | 13 | type LocateRequestPayload struct { 14 | Attributes interface{} 15 | } 16 | 17 | // Table 230 18 | 19 | type LocateResponsePayload struct { 20 | UniqueIdentifier string 21 | } 22 | 23 | type LocateHandler struct { 24 | Locate func(ctx context.Context, payload *LocateRequestPayload) (*LocateResponsePayload, error) 25 | } 26 | 27 | func (h *LocateHandler) HandleItem(ctx context.Context, req *kmip.Request) (*kmip.ResponseBatchItem, error) { 28 | var payload LocateRequestPayload 29 | 30 | err := req.DecodePayload(&payload) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | respPayload, err := h.Locate(ctx, &payload) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | return &kmip.ResponseBatchItem{ 41 | ResponsePayload: respPayload, 42 | }, nil 43 | } 44 | -------------------------------------------------------------------------------- /kmip20/op_query.go: -------------------------------------------------------------------------------- 1 | package kmip20 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/gemalto/kmip-go" 7 | "github.com/gemalto/kmip-go/kmip14" 8 | ) 9 | 10 | // 7.3 Capability Information 11 | // The Capability Information base object is a structure that contains details of the supported capabilities. 12 | type CapabilityInformation struct { 13 | StreamingCapability bool // Required: No 14 | AsynchronousCapability bool // Required: No 15 | AttestationCapability bool // Required: No 16 | BatchUndoCapability bool // Required: No 17 | BatchContinueCapability bool // Required: No 18 | UnwrapMode kmip14.UnwrapMode // Required: No 19 | DestroyAction kmip14.DestroyAction // Required: No 20 | ShreddingAlgorithm kmip14.ShreddingAlgorithm // Required: No 21 | RNGMode kmip14.RNGMode // Required: No 22 | QuantumSafeCapability bool // Required: No 23 | } 24 | 25 | // 7.7 Defaults Information 26 | // The Defaults Information is a structure used in Query responses for values that servers will use if clients omit them from factory 27 | // operations requests. 28 | type DefaultsInformation struct { 29 | ObjectDefaults ObjectDefaults // Required: Yes 30 | } 31 | 32 | // 7.9 Extension Information 33 | // An Extension Information object is a structure describing Objects with Item Tag values in the Extensions range. The Extension Name 34 | // is a Text String that is used to name the Object. The Extension Tag is the Item Tag Value of the Object. The Extension Type is 35 | // the Item Type Value of the Object. 36 | type ExtensionInformation struct { 37 | ExtensionName string // Required: Yes 38 | ExtensionTag int // Required: No 39 | ExtensionType int // Required: No 40 | ExtensionEnumeration int // Required: No 41 | ExtensionAttribute bool // Required: No 42 | ExtensionParentStructureTag int // Required: No 43 | ExtensionDescription string // Required: No 44 | } 45 | 46 | // 7.18 Object Defaults 47 | // The Object Defaults is a structure that details the values that the server will use if the client omits them on factory methods for 48 | // objects. The structure list the Attributes and their values by Object Type enumeration. 49 | type ObjectDefaults struct { 50 | ObjectType kmip14.ObjectType // Required: Yes 51 | Attributes kmip.Attributes // Required: Yes 52 | } 53 | 54 | // 7.30 RNG Parameters 55 | // The RNG Parameters base object is a structure that contains a mandatory RNG Algorithm and a set of OPTIONAL fields that describe a 56 | // Random Number Generator. Specific fields pertain only to certain types of RNGs. The RNG Algorithm SHALL be specified and if the 57 | // algorithm implemented is unknown or the implementation does not want to provide the specific details of the RNG Algorithm then the 58 | // Unspecified enumeration SHALL be used. If the cryptographic building blocks used within the RNG are known they MAY be specified in 59 | // combination of the remaining fields within the RNG Parameters structure. 60 | type RNGParameters struct { 61 | RNGAlgorithm kmip14.RNGAlgorithm // Required: Yes 62 | CryptographicAlgorithm kmip14.CryptographicAlgorithm // Required: No 63 | CryptographicLength int // Required: No 64 | HashingAlgorithm kmip14.HashingAlgorithm // Required: No 65 | DRBGAlgorithm kmip14.DRBGAlgorithm // Required: No 66 | RecommendedCurve kmip14.RecommendedCurve // Required: No 67 | FIPS186Variation kmip14.FIPS186Variation // Required: No 68 | PredictionResistance bool // Required: No 69 | } 70 | 71 | // 7.31 Server Information 72 | // The Server Information base object is a structure that contains a set of OPTIONAL fields that describe server information. 73 | // Where a server supports returning information in a vendor-specific field for which there is an equivalent field within the structure, 74 | // the server SHALL provide the standardized version of the field. 75 | type ServerInformation struct { 76 | ServerName string // Required: No 77 | ServerSerialNumber string // Required: No 78 | ServerVersion string // Required: No 79 | ServerLoad string // Required: No 80 | ProductName string // Required: No 81 | BuildLevel string // Required: No 82 | BuildDate string // Required: No 83 | ClusterInfo string // Required: No 84 | AlternativeFailoverEndpoints []string // Required: No 85 | VendorSpecific []string // Required: No 86 | } 87 | 88 | // 6.1.37 Query 89 | 90 | // Table 259 91 | 92 | type QueryRequestPayload struct { 93 | QueryFunction QueryFunction 94 | } 95 | 96 | // Table 260 97 | 98 | type QueryResponsePayload struct { 99 | Operation []kmip14.Operation 100 | ObjectType []ObjectType 101 | VendorIdentification string 102 | ServerInformation []ServerInformation 103 | ApplicationNamespace []string 104 | ExtensionInformation []ExtensionInformation 105 | AttestationType kmip14.AttestationType 106 | RNGParameters []RNGParameters 107 | ProfileInformation []ProfileName 108 | ValidationInformation []kmip14.ValidationAuthorityType 109 | CapabilityInformation []CapabilityInformation 110 | ClientRegistrationMethod kmip14.ClientRegistrationMethod 111 | DefaultsInformation *DefaultsInformation 112 | ProtectionStorageMasks []ProtectionStorageMask 113 | } 114 | 115 | type QueryHandler struct { 116 | Query func(ctx context.Context, payload *QueryRequestPayload) (*QueryResponsePayload, error) 117 | } 118 | 119 | func (h *QueryHandler) HandleItem(ctx context.Context, req *kmip.Request) (*kmip.ResponseBatchItem, error) { 120 | var payload QueryRequestPayload 121 | 122 | err := req.DecodePayload(&payload) 123 | if err != nil { 124 | return nil, err 125 | } 126 | 127 | respPayload, err := h.Query(ctx, &payload) 128 | if err != nil { 129 | return nil, err 130 | } 131 | 132 | return &kmip.ResponseBatchItem{ 133 | ResponsePayload: respPayload, 134 | }, nil 135 | } 136 | -------------------------------------------------------------------------------- /kmip20/op_revoke.go: -------------------------------------------------------------------------------- 1 | package kmip20 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/gemalto/kmip-go" 8 | "github.com/gemalto/kmip-go/kmip14" 9 | ) 10 | 11 | // 6.1.40 Revoke 12 | 13 | // Table 269 14 | 15 | type RevocationReason struct { 16 | RevocationReasonCode kmip14.RevocationReasonCode 17 | } 18 | 19 | type RevokeRequestPayload struct { 20 | UniqueIdentifier *UniqueIdentifierValue 21 | RevocationReason RevocationReason 22 | CompromiseOccurrenceDate *time.Time 23 | } 24 | 25 | // Table 270 26 | 27 | type RevokeResponsePayload struct { 28 | UniqueIdentifier string 29 | } 30 | 31 | type RevokeHandler struct { 32 | Revoke func(ctx context.Context, payload *RevokeRequestPayload) (*RevokeResponsePayload, error) 33 | } 34 | 35 | func (h *RevokeHandler) HandleItem(ctx context.Context, req *kmip.Request) (*kmip.ResponseBatchItem, error) { 36 | var payload RevokeRequestPayload 37 | 38 | err := req.DecodePayload(&payload) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | respPayload, err := h.Revoke(ctx, &payload) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | return &kmip.ResponseBatchItem{ 49 | ResponsePayload: respPayload, 50 | }, nil 51 | } 52 | -------------------------------------------------------------------------------- /kmip20/op_setattribute.go: -------------------------------------------------------------------------------- 1 | package kmip20 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/gemalto/kmip-go" 7 | ) 8 | 9 | // 6.1.47 Set Attribute 10 | 11 | // Table 296 12 | 13 | type SetAttributeRequestPayload struct { 14 | UniqueIdentifier *UniqueIdentifierValue 15 | NewAttribute Attributes `ttlv:"DerivationData"` 16 | } 17 | 18 | // Table 297 19 | 20 | type SetAttributeResponsePayload struct { 21 | UniqueIdentifier string 22 | } 23 | 24 | type SetAttributeHandler struct { 25 | SetAttribute func(ctx context.Context, payload *SetAttributeRequestPayload) (*SetAttributeResponsePayload, error) 26 | } 27 | 28 | func (h *SetAttributeHandler) HandleItem(ctx context.Context, req *kmip.Request) (*kmip.ResponseBatchItem, error) { 29 | var payload SetAttributeRequestPayload 30 | 31 | err := req.DecodePayload(&payload) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | respPayload, err := h.SetAttribute(ctx, &payload) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | return &kmip.ResponseBatchItem{ 42 | ResponsePayload: respPayload, 43 | }, nil 44 | } 45 | -------------------------------------------------------------------------------- /kmip20/payloads.go: -------------------------------------------------------------------------------- 1 | package kmip20 2 | 3 | import "github.com/gemalto/kmip-go/ttlv" 4 | 5 | type Attributes struct { 6 | Values ttlv.Values 7 | } 8 | 9 | type CreateRequestPayload struct { 10 | TTLVTag struct{} `ttlv:"RequestPayload"` 11 | ObjectType ObjectType 12 | Attributes interface{} 13 | ProtectionStorageMasks ProtectionStorageMask `ttlv:",omitempty"` 14 | } 15 | 16 | type CreateResponsePayload struct { 17 | ObjectType ObjectType 18 | UniqueIdentifier string 19 | } 20 | 21 | type CreateKeyPairRequestPayload struct { 22 | CommonAttributes interface{} 23 | PrivateKeyAttributes interface{} 24 | PublicKeyAttributes interface{} 25 | CommonProtectionStorageMasks ProtectionStorageMask `ttlv:",omitempty"` 26 | PrivateProtectionStorageMasks ProtectionStorageMask `ttlv:",omitempty"` 27 | PublicProtectionStorageMasks ProtectionStorageMask `ttlv:",omitempty"` 28 | } 29 | 30 | type CreateKeyPairResponsePayload struct { 31 | PrivateKeyUniqueIdentifier string 32 | PublicKeyUniqueIdentifier string 33 | } 34 | -------------------------------------------------------------------------------- /kmip20/payloads_test.go: -------------------------------------------------------------------------------- 1 | package kmip20 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gemalto/kmip-go/kmip14" 7 | "github.com/gemalto/kmip-go/ttlv" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestCreateRequestPayload(t *testing.T) { 12 | type createReqAttrs struct { 13 | CryptographicAlgorithm CryptographicAlgorithm 14 | CryptographicLength int 15 | CryptographicUsageMask kmip14.CryptographicUsageMask 16 | } 17 | 18 | tests := []struct { 19 | name string 20 | in CreateRequestPayload 21 | expected ttlv.Value 22 | }{ 23 | { 24 | name: "structforattrs", 25 | in: CreateRequestPayload{ 26 | ObjectType: ObjectTypeSymmetricKey, 27 | Attributes: createReqAttrs{ 28 | CryptographicAlgorithm: CryptographicAlgorithmARIA, 29 | CryptographicLength: 56, 30 | CryptographicUsageMask: kmip14.CryptographicUsageMaskEncrypt | kmip14.CryptographicUsageMaskDecrypt, 31 | }, 32 | }, 33 | expected: s(kmip14.TagRequestPayload, 34 | v(kmip14.TagObjectType, ObjectTypeSymmetricKey), 35 | s(TagAttributes, 36 | v(kmip14.TagCryptographicAlgorithm, CryptographicAlgorithmARIA), 37 | v(kmip14.TagCryptographicLength, 56), 38 | v(kmip14.TagCryptographicUsageMask, kmip14.CryptographicUsageMaskEncrypt|kmip14.CryptographicUsageMaskDecrypt), 39 | ), 40 | ), 41 | }, 42 | { 43 | name: "valuesforattrs", 44 | in: CreateRequestPayload{ 45 | ObjectType: ObjectTypeSymmetricKey, 46 | Attributes: ttlv.NewStruct(ttlv.TagNone, 47 | v(kmip14.TagCryptographicAlgorithm, CryptographicAlgorithmARIA), 48 | v(kmip14.TagCryptographicLength, 56), 49 | v(kmip14.TagCryptographicUsageMask, kmip14.CryptographicUsageMaskEncrypt|kmip14.CryptographicUsageMaskDecrypt), 50 | ), 51 | }, 52 | expected: s(kmip14.TagRequestPayload, 53 | v(kmip14.TagObjectType, ObjectTypeSymmetricKey), 54 | s(TagAttributes, 55 | v(kmip14.TagCryptographicAlgorithm, CryptographicAlgorithmARIA), 56 | v(kmip14.TagCryptographicLength, 56), 57 | v(kmip14.TagCryptographicUsageMask, kmip14.CryptographicUsageMaskEncrypt|kmip14.CryptographicUsageMaskDecrypt), 58 | ), 59 | ), 60 | }, 61 | { 62 | name: "attributesstruct", 63 | in: CreateRequestPayload{ 64 | ObjectType: ObjectTypeSymmetricKey, 65 | Attributes: Attributes{ 66 | Values: ttlv.Values{ 67 | v(kmip14.TagCryptographicAlgorithm, CryptographicAlgorithmARIA), 68 | v(kmip14.TagCryptographicLength, 56), 69 | v(kmip14.TagCryptographicUsageMask, kmip14.CryptographicUsageMaskEncrypt|kmip14.CryptographicUsageMaskDecrypt), 70 | }, 71 | }, 72 | }, 73 | expected: s(kmip14.TagRequestPayload, 74 | v(kmip14.TagObjectType, ObjectTypeSymmetricKey), 75 | s(TagAttributes, 76 | v(kmip14.TagCryptographicAlgorithm, CryptographicAlgorithmARIA), 77 | v(kmip14.TagCryptographicLength, 56), 78 | v(kmip14.TagCryptographicUsageMask, kmip14.CryptographicUsageMaskEncrypt|kmip14.CryptographicUsageMaskDecrypt), 79 | ), 80 | ), 81 | }, 82 | { 83 | name: "omitempty", 84 | in: CreateRequestPayload{ 85 | ObjectType: ObjectTypeCertificate, 86 | }, 87 | expected: s(kmip14.TagRequestPayload, 88 | v(kmip14.TagObjectType, ObjectTypeCertificate), 89 | ), 90 | }, 91 | } 92 | 93 | for _, test := range tests { 94 | t.Run(test.name, func(t *testing.T) { 95 | in := test.in 96 | 97 | out, err := ttlv.Marshal(&in) 98 | require.NoError(t, err) 99 | 100 | expected, err := ttlv.Marshal(test.expected) 101 | require.NoError(t, err) 102 | 103 | require.Equal(t, expected, out) 104 | 105 | // test roundtrip by unmarshaling back into an instance of the struct, 106 | // then marshaling again. Should produce the same output. 107 | var p CreateRequestPayload 108 | err = ttlv.Unmarshal(expected, &p) 109 | require.NoError(t, err) 110 | 111 | out2, err := ttlv.Marshal(&p) 112 | require.NoError(t, err) 113 | 114 | require.Equal(t, out, out2) 115 | }) 116 | } 117 | } 118 | 119 | func v(tag ttlv.Tag, val interface{}) ttlv.Value { 120 | return ttlv.NewValue(tag, val) 121 | } 122 | 123 | func s(tag ttlv.Tag, vals ...ttlv.Value) ttlv.Value { 124 | return ttlv.NewStruct(tag, vals...) 125 | } 126 | -------------------------------------------------------------------------------- /kmip20/unique_identifier.go: -------------------------------------------------------------------------------- 1 | package kmip20 2 | 3 | import ( 4 | "github.com/ansel1/merry" 5 | "github.com/gemalto/kmip-go/ttlv" 6 | ) 7 | 8 | type UniqueIdentifierValue struct { 9 | Text string 10 | Enum UniqueIdentifier 11 | Index int32 12 | } 13 | 14 | func (u *UniqueIdentifierValue) UnmarshalTTLV(_ *ttlv.Decoder, v ttlv.TTLV) error { 15 | if len(v) == 0 { 16 | return nil 17 | } 18 | 19 | if u == nil { 20 | *u = UniqueIdentifierValue{} 21 | } 22 | 23 | switch v.Type() { 24 | case ttlv.TypeTextString: 25 | u.Text = v.String() 26 | case ttlv.TypeEnumeration: 27 | u.Enum = UniqueIdentifier(v.ValueEnumeration()) 28 | case ttlv.TypeInteger: 29 | u.Index = v.ValueInteger() 30 | default: 31 | return merry.Errorf("invalid type for UniqueIdentifier: %s", v.Type().String()) 32 | } 33 | 34 | return nil 35 | } 36 | 37 | func (u UniqueIdentifierValue) MarshalTTLV(e *ttlv.Encoder, tag ttlv.Tag) error { 38 | switch { 39 | case u.Text != "": 40 | e.EncodeTextString(tag, u.Text) 41 | case u.Enum != 0: 42 | e.EncodeEnumeration(tag, uint32(u.Enum)) 43 | case u.Index != 0: 44 | e.EncodeInteger(tag, u.Index) 45 | } 46 | 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /managed_objects.go: -------------------------------------------------------------------------------- 1 | package kmip 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/gemalto/kmip-go/kmip14" 7 | ) 8 | 9 | // 2.2 10 | 11 | // 2.2.1 12 | 13 | type Certificate struct { 14 | CertificateType kmip14.CertificateType 15 | CertificateValue []byte 16 | } 17 | 18 | // 2.2.2 19 | 20 | type SymmetricKey struct { 21 | KeyBlock KeyBlock 22 | } 23 | 24 | // 2.2.3 25 | 26 | type PublicKey struct { 27 | KeyBlock KeyBlock 28 | } 29 | 30 | // 2.2.4 31 | 32 | type PrivateKey struct { 33 | KeyBlock KeyBlock 34 | } 35 | 36 | // 2.2.5 37 | 38 | type SplitKey struct { 39 | SplitKeyParts int 40 | KeyPartIdentifier int 41 | SplitKeyThreshold int 42 | SplitKeyMethod kmip14.SplitKeyMethod 43 | PrimeFieldSize *big.Int `ttlv:",omitempty"` 44 | KeyBlock KeyBlock 45 | } 46 | 47 | // 2.2.6 48 | 49 | type Template struct { 50 | Attribute []Attribute 51 | } 52 | 53 | // 2.2.7 54 | 55 | type SecretData struct { 56 | SecretDataType kmip14.SecretDataType 57 | KeyBlock KeyBlock 58 | } 59 | 60 | // 2.2.8 61 | 62 | type OpaqueObject struct { 63 | OpaqueDataType kmip14.OpaqueDataType 64 | OpaqueDataValue []byte 65 | } 66 | 67 | // 2.2.9 68 | 69 | type PGPKey struct { 70 | PGPKeyVersion int 71 | KeyBlock KeyBlock 72 | } 73 | -------------------------------------------------------------------------------- /op_create.go: -------------------------------------------------------------------------------- 1 | package kmip 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/ansel1/merry" 7 | 8 | "github.com/gemalto/kmip-go/kmip14" 9 | ) 10 | 11 | // TODO: should request and response payloads implement validation? 12 | // Sort of makes sense to run validation over the request at this level, at least for spec 13 | // compliance, though perhaps handlers may want to be more relaxed with validation. 14 | // 15 | // Should the response object run through validation? What is a valid response may change as 16 | // the spec changes. Maybe this should just be handled by spec compliance tests. 17 | 18 | // 4.1 19 | // 20 | // This operation requests the server to generate a new symmetric key as a Managed Cryptographic Object. 21 | // This operation is not used to create a Template object (see Register operation, Section 4.3). 22 | // 23 | // The request contains information about the type of object being created, and some of the attributes to be 24 | // assigned to the object (e.g., Cryptographic Algorithm, Cryptographic Length, etc.). This information MAY be 25 | // specified by the names of Template objects that already exist. 26 | // 27 | // The response contains the Unique Identifier of the created object. The server SHALL copy the Unique Identifier 28 | // returned by this operation into the ID Placeholder variable. 29 | 30 | // CreateRequestPayload 4.1 Table 163 31 | // 32 | // TemplateAttribute MUST include CryptographicAlgorithm (3.4) and CryptographicUsageMask (3.19). 33 | type CreateRequestPayload struct { 34 | ObjectType kmip14.ObjectType 35 | TemplateAttribute TemplateAttribute 36 | } 37 | 38 | // CreateResponsePayload 4.1 Table 164 39 | type CreateResponsePayload struct { 40 | ObjectType kmip14.ObjectType 41 | UniqueIdentifier string 42 | TemplateAttribute *TemplateAttribute 43 | } 44 | 45 | type CreateHandler struct { 46 | Create func(ctx context.Context, payload *CreateRequestPayload) (*CreateResponsePayload, error) 47 | } 48 | 49 | func (h *CreateHandler) HandleItem(ctx context.Context, req *Request) (*ResponseBatchItem, error) { 50 | var payload CreateRequestPayload 51 | 52 | err := req.DecodePayload(&payload) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | respPayload, err := h.Create(ctx, &payload) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | var ok bool 63 | 64 | idAttr := respPayload.TemplateAttribute.GetTag(kmip14.TagUniqueIdentifier) 65 | 66 | req.IDPlaceholder, ok = idAttr.AttributeValue.(string) 67 | if !ok { 68 | return nil, merry.Errorf("invalid response returned by CreateHandler: unique identifier tag in attributes should have been a string, was %t", idAttr.AttributeValue) 69 | } 70 | 71 | return &ResponseBatchItem{ 72 | ResponsePayload: respPayload, 73 | }, nil 74 | } 75 | -------------------------------------------------------------------------------- /op_create_key_pair.go: -------------------------------------------------------------------------------- 1 | package kmip 2 | 3 | // CreateKeyPairRequestPayload 4 | // 4.2 Create Key Pair 5 | // This operation requests the server to generate a new public/private key pair 6 | // and register the two corresponding new Managed Cryptographic Objects. 7 | // 8 | // The request contains attributes to be assigned to the objects (e.g., 9 | // Cryptographic Algorithm, Cryptographic Length, etc.). Attributes and Template 10 | // Names MAY be specified for both keys at the same time by specifying a Common 11 | // Template-Attribute object in the request. Attributes not common to both keys 12 | // (e.g., Name, Cryptographic Usage Mask) MAY be specified using the Private Key 13 | // Template-Attribute and Public Key Template-Attribute objects in the request, 14 | // which take precedence over the Common Template-Attribute object. 15 | // 16 | // The Template Managed Object is deprecated as of version 1.3 of this 17 | // specification and MAY be removed from subsequent versions of the 18 | // specification. Individual Attributes SHOULD be used in operations which 19 | // currently support use of a Name within a Template-Attribute to reference a 20 | // Template. 21 | // 22 | // For the Private Key, the server SHALL create a Link attribute of Link Type 23 | // Public Key pointing to the Public Key. For the Public Key, the server SHALL 24 | // create a Link attribute of Link Type Private Key pointing to the Private Key. 25 | // The response contains the Unique Identifiers of both created objects. The ID 26 | // Placeholder value SHALL be set to the Unique Identifier of the Private Key. 27 | type CreateKeyPairRequestPayload struct { 28 | CommonTemplateAttribute *TemplateAttribute 29 | PrivateKeyTemplateAttribute *TemplateAttribute 30 | PublicKeyTemplateAttribute *TemplateAttribute 31 | } 32 | 33 | type CreateKeyPairResponsePayload struct { 34 | PrivateKeyUniqueIdentifier string 35 | PublicKeyUniqueIdentifier string 36 | PrivateKeyTemplateAttribute *TemplateAttribute 37 | PublicKeyTemplateAttribute *TemplateAttribute 38 | } 39 | -------------------------------------------------------------------------------- /op_destroy.go: -------------------------------------------------------------------------------- 1 | package kmip 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // DestroyRequestPayload //////////////////////////////////////// 8 | type DestroyRequestPayload struct { 9 | UniqueIdentifier string 10 | } 11 | 12 | // DestroyResponsePayload 13 | type DestroyResponsePayload struct { 14 | UniqueIdentifier string 15 | } 16 | 17 | type DestroyHandler struct { 18 | Destroy func(ctx context.Context, payload *DestroyRequestPayload) (*DestroyResponsePayload, error) 19 | } 20 | 21 | func (h *DestroyHandler) HandleItem(ctx context.Context, req *Request) (*ResponseBatchItem, error) { 22 | var payload DestroyRequestPayload 23 | 24 | err := req.DecodePayload(&payload) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | respPayload, err := h.Destroy(ctx, &payload) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | // req.Key = respPayload.Key 35 | 36 | return &ResponseBatchItem{ 37 | ResponsePayload: respPayload, 38 | }, nil 39 | } 40 | -------------------------------------------------------------------------------- /op_discover_versions.go: -------------------------------------------------------------------------------- 1 | package kmip 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // 4.26 8 | 9 | type DiscoverVersionsRequestPayload struct { 10 | ProtocolVersion []ProtocolVersion 11 | } 12 | 13 | type DiscoverVersionsResponsePayload struct { 14 | ProtocolVersion []ProtocolVersion 15 | } 16 | 17 | type DiscoverVersionsHandler struct { 18 | SupportedVersions []ProtocolVersion 19 | } 20 | 21 | func (h *DiscoverVersionsHandler) HandleItem(_ context.Context, req *Request) (item *ResponseBatchItem, err error) { 22 | var payload DiscoverVersionsRequestPayload 23 | 24 | err = req.DecodePayload(&payload) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | var respPayload DiscoverVersionsResponsePayload 30 | 31 | if len(payload.ProtocolVersion) == 0 { 32 | respPayload.ProtocolVersion = h.SupportedVersions 33 | } else { 34 | for _, v := range h.SupportedVersions { 35 | for _, cv := range payload.ProtocolVersion { 36 | if cv == v { 37 | respPayload.ProtocolVersion = append(respPayload.ProtocolVersion, v) 38 | break 39 | } 40 | } 41 | } 42 | } 43 | 44 | return &ResponseBatchItem{ 45 | ResponsePayload: respPayload, 46 | }, nil 47 | } 48 | -------------------------------------------------------------------------------- /op_get.go: -------------------------------------------------------------------------------- 1 | package kmip 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/gemalto/kmip-go/kmip14" 7 | ) 8 | 9 | // GetRequestPayload //////////////////////////////////////// 10 | type GetRequestPayload struct { 11 | UniqueIdentifier string 12 | } 13 | 14 | // GetResponsePayload 15 | type GetResponsePayload struct { 16 | ObjectType kmip14.ObjectType 17 | UniqueIdentifier string 18 | Certificate *Certificate 19 | SymmetricKey *SymmetricKey 20 | PrivateKey *PrivateKey 21 | PublicKey *PublicKey 22 | SplitKey *SplitKey 23 | Template *Template 24 | SecretData *SecretData 25 | OpaqueObject *OpaqueObject 26 | } 27 | 28 | type GetHandler struct { 29 | Get func(ctx context.Context, payload *GetRequestPayload) (*GetResponsePayload, error) 30 | } 31 | 32 | func (h *GetHandler) HandleItem(ctx context.Context, req *Request) (*ResponseBatchItem, error) { 33 | var payload GetRequestPayload 34 | 35 | err := req.DecodePayload(&payload) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | respPayload, err := h.Get(ctx, &payload) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | // req.Key = respPayload.Key 46 | 47 | return &ResponseBatchItem{ 48 | ResponsePayload: respPayload, 49 | }, nil 50 | } 51 | -------------------------------------------------------------------------------- /op_register.go: -------------------------------------------------------------------------------- 1 | package kmip 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/ansel1/merry" 7 | "github.com/gemalto/kmip-go/kmip14" 8 | ) 9 | 10 | // 4.3 11 | 12 | // Table 169 13 | 14 | type RegisterRequestPayload struct { 15 | ObjectType kmip14.ObjectType 16 | TemplateAttribute TemplateAttribute 17 | Certificate *Certificate 18 | SymmetricKey *SymmetricKey 19 | PrivateKey *PrivateKey 20 | PublicKey *PublicKey 21 | SplitKey *SplitKey 22 | Template *Template 23 | SecretData *SecretData 24 | OpaqueObject *OpaqueObject 25 | } 26 | 27 | // Table 170 28 | 29 | type RegisterResponsePayload struct { 30 | UniqueIdentifier string 31 | TemplateAttribute TemplateAttribute 32 | } 33 | 34 | type RegisterHandler struct { 35 | SkipValidation bool 36 | RegisterFunc func(context.Context, *RegisterRequestPayload) (*RegisterResponsePayload, error) 37 | } 38 | 39 | func (h *RegisterHandler) HandleItem(ctx context.Context, req *Request) (item *ResponseBatchItem, err error) { 40 | var payload RegisterRequestPayload 41 | 42 | err = req.DecodePayload(&payload) 43 | if err != nil { 44 | return nil, merry.Prepend(err, "decoding request") 45 | } 46 | 47 | if !h.SkipValidation { 48 | var payloadPresent bool 49 | 50 | switch payload.ObjectType { 51 | default: 52 | return nil, WithResultReason(merry.UserError("Object Type is not recognized"), kmip14.ResultReasonInvalidField) 53 | case kmip14.ObjectTypeCertificate: 54 | payloadPresent = payload.Certificate != nil 55 | case kmip14.ObjectTypeSymmetricKey: 56 | payloadPresent = payload.SymmetricKey != nil 57 | case kmip14.ObjectTypePrivateKey: 58 | payloadPresent = payload.PrivateKey != nil 59 | case kmip14.ObjectTypePublicKey: 60 | payloadPresent = payload.PublicKey != nil 61 | case kmip14.ObjectTypeSplitKey: 62 | payloadPresent = payload.SplitKey != nil 63 | case kmip14.ObjectTypeTemplate: 64 | payloadPresent = payload.Template != nil 65 | case kmip14.ObjectTypeSecretData: 66 | payloadPresent = payload.SecretData != nil 67 | case kmip14.ObjectTypeOpaqueObject: 68 | payloadPresent = payload.OpaqueObject != nil 69 | } 70 | 71 | if !payloadPresent { 72 | return nil, WithResultReason(merry.UserErrorf("Object Type %s does not match type of cryptographic object provided", payload.ObjectType.String()), kmip14.ResultReasonInvalidField) 73 | } 74 | } 75 | 76 | respPayload, err := h.RegisterFunc(ctx, &payload) 77 | if err != nil { 78 | return nil, err 79 | } 80 | 81 | req.IDPlaceholder = respPayload.UniqueIdentifier 82 | 83 | return &ResponseBatchItem{ 84 | ResponsePayload: respPayload, 85 | }, nil 86 | } 87 | -------------------------------------------------------------------------------- /pykmip-server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9-slim 2 | RUN pip install --no-cache-dir pykmip 3 | COPY . /work 4 | WORKDIR /work 5 | CMD pykmip-server -f ./server.conf -l ./server.log -------------------------------------------------------------------------------- /pykmip-server/server.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEpDCCAowCCQD+upGey8IGczANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls 3 | b2NhbGhvc3QwHhcNMjEwNzI2MTk1MTMwWhcNMzEwNzI0MTk1MTMwWjAUMRIwEAYD 4 | VQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCx 5 | 11sq2pr7RD6kJ1wc+bzwb7lwpL5aUg4BVl/0uC8bakDsNd/UENsw6bAcYdAbfPuP 6 | sH0GO23DwPXZWBwgqp0xIA6aIGJMwKL8VdbtzST5w+YPUeKXMClqiTH1txbcdryW 7 | a2LGaUEPYKwNZrHGbpcwgPhzXSOk5gDN2lE6oM5BehtLnJi00Ik9pnJ4vGC2yyE/ 8 | jAjmgxjp+mWrx2TJjjcNHbr44RN1FA+FJR2SncWiQAGoOS99a77fD601WEdC/Sf+ 9 | gxTUe8QOu6Db1llv8hUm+TeICmT5LZwft8NY2mRDiLZMuHpub7K/kKpXcWECu+oE 10 | xRM0h+AAYCpuDbhduSN4Ga4hafOt25JkQ5yNPKBiPs5AZVc/8JrsYogCDy82iTMd 11 | St9qUeQU/Bg8TibzHJS5//cfHuG97CoVXPQ8j5+4kQZ8skcxFLZ1cPnVhUrr8//x 12 | kjJTP6grEFRh21xX5HLScMR9YjsdM01jWQ1C0UU7Qz/8u0Q8TyWDZyIb238zS2kA 13 | 8Ja76GfKfbaXvX8I7RHi6ooErMKycOBZXO3ay4lkb+xlxm+u86eYndWFz9JB40HD 14 | K8DVnDwUHJJmuL+qDMyBNIWn4w7/JBB0H/HmCnJjcFC7MXWZZwQhQQRl5I2YzXRK 15 | jC6+6XCU0esCw8YHBgqIM92kMs9dqvlbuvTD441gEwIDAQABMA0GCSqGSIb3DQEB 16 | CwUAA4ICAQBkcZqstJfrJG3KiajmTvtT1hV7DsIcSHYZUWU+4ANytJ5XW+NiW6d7 17 | 3gtL6HrfqQBqduEzD46vdwcseUuhTbYD581eBE62iNDa1MJ8V+aVkmBeOTSV1bDL 18 | Cush5WpI/4qsZlBKyH0dkP2G82hSfq4UNCCSkIXQwW9IiWZdxedk7YoPMsgZpWFw 19 | lOHzxN92zRqQaOGCjNvfuZDsA4BpfKBna2K0m4VyPN8Bj4272lhRQ856u1qh8vO0 20 | g1aQzZVpsh/HeXmH+zsj5XVXNYx1AspLHaQNJlrGoC93y5Vrox8wJtg4oCVPHp54 21 | dHBf7Ja1tHIvywosXjZAqvhCdbwbb2pmX+7rOgdyrjc10rq2Zxa4du0bZRni8k+e 22 | HrK1mFypTRqI1Gmr3A9ix+U1uh75VB4kHX3GKWY3sbbbC4GPXdwmCiBImiKOnYLs 23 | nhE1bQWHE1RGHaD+MP2E4W2nJVofaxPgpdBlpHsW+C2lh7v6rf1NWlQdqSluMwZD 24 | uk2nvsav4/kXc5PPh1FQZVZ6oQBQ5hHB30pkvwIOyp0gTSGaR0oWk5eZKMEy4Z2J 25 | TNGLPffEENQVFze0XTN8wwnEpk9Z8UcxGw0WcC5djMdZlD92U0bgiCJTU+beMcXS 26 | uhmLO2gYMA8d2rKaG4cT4ZdY+yCDafLA0xICBIoKD8n+q4CM3lWLOg== 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /pykmip-server/server.conf: -------------------------------------------------------------------------------- 1 | [server] 2 | hostname= 3 | port=5696 4 | certificate_path=./server.cert 5 | key_path=./server.key 6 | ca_path=./server.cert 7 | auth_suite=TLS1.2 8 | policy_path=. 9 | enable_tls_client_auth=FALSE 10 | tls_cipher_suites= 11 | logging_level=DEBUG 12 | database_path=./server.db -------------------------------------------------------------------------------- /pykmip-server/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQCx11sq2pr7RD6k 3 | J1wc+bzwb7lwpL5aUg4BVl/0uC8bakDsNd/UENsw6bAcYdAbfPuPsH0GO23DwPXZ 4 | WBwgqp0xIA6aIGJMwKL8VdbtzST5w+YPUeKXMClqiTH1txbcdryWa2LGaUEPYKwN 5 | ZrHGbpcwgPhzXSOk5gDN2lE6oM5BehtLnJi00Ik9pnJ4vGC2yyE/jAjmgxjp+mWr 6 | x2TJjjcNHbr44RN1FA+FJR2SncWiQAGoOS99a77fD601WEdC/Sf+gxTUe8QOu6Db 7 | 1llv8hUm+TeICmT5LZwft8NY2mRDiLZMuHpub7K/kKpXcWECu+oExRM0h+AAYCpu 8 | DbhduSN4Ga4hafOt25JkQ5yNPKBiPs5AZVc/8JrsYogCDy82iTMdSt9qUeQU/Bg8 9 | TibzHJS5//cfHuG97CoVXPQ8j5+4kQZ8skcxFLZ1cPnVhUrr8//xkjJTP6grEFRh 10 | 21xX5HLScMR9YjsdM01jWQ1C0UU7Qz/8u0Q8TyWDZyIb238zS2kA8Ja76GfKfbaX 11 | vX8I7RHi6ooErMKycOBZXO3ay4lkb+xlxm+u86eYndWFz9JB40HDK8DVnDwUHJJm 12 | uL+qDMyBNIWn4w7/JBB0H/HmCnJjcFC7MXWZZwQhQQRl5I2YzXRKjC6+6XCU0esC 13 | w8YHBgqIM92kMs9dqvlbuvTD441gEwIDAQABAoICAD7yiC1x9Rts+3MlD2CeMPc1 14 | xICPf2T+2EA2733yV5IvmM1DAHfSg5MblB9nq76fDXe9s3MqQ97fBOYqXsJdQYhq 15 | 3WBGiCS8prQEOjAvxZ+2bE8N39M909sGtq1PYgMk7/maBvtNtB6aTCvuJoyjBla/ 16 | v7EtzXkHhE9YsBm9Y2QfsVuhERgnG/y7VpdPrjflTF5u1ZOp4X6oB6a2zADfmrLE 17 | zDJdkIKHWDr7eS01NQgJ+cwueZW+NnBD3z4bghW+/5IBmbTgHfRNC1Hk3AFYwY3q 18 | 9nHro/Pi5fkAFV/05TpWQJiFT6iZVr6hmPmcgwbxtpJ7oyGb2ninkT0lFBXwexHn 19 | V8vI0zhoO1amRVBUiv+GSCpK0AaZgdmPYPCP3dA5qnH7NREcgsmP2F09xWX6nD1M 20 | df1zusxC5LML7fulQ+KBOLdOLLmVil77LOs4j0k2WAvareiMzIbqRa5Bs8MWs8s9 21 | IEEthMZtX5+iw4vmQ6DLIcXptNSUlzYxH4CkMKhCvOEMdQu78uPkLTJP2klVHhh7 22 | 9caX9NhuYll3AG5tRRtNr91c/1bwk1RFV3sx3HCjG7W9tCzEQLOsDMP5HMP+KZ42 23 | IlXLSOlOfGXIE3UBj2MDnPsej5Y6kT0bdUfH1oZUorhqLdR8MumINpPXX92ttOZr 24 | hTRCC5SHF4/dMznD4T1JAoIBAQDhPlNy7hu7RZ3OWJrNBvsJ/HLXNrPfYkf7zr0z 25 | +Sv/hwt9kwm/wzJWoU7vyS253Rs64R091umzOyVEhv/FrrsG8H994Tzt8VcFVcqX 26 | sQ125tE81AEw6tITtpPRS4ZPMPmt9wfBOL+Y3+zQf2BUyQ6J+bXa57+Ovtd9d1N7 27 | E1r3bl+kyN6h6TtHoTCpZUtMtIqRVMkNtLxTqugzJiaq7xQG7iFs2c4c4axh8Ekk 28 | S1gp7DSmQmfMmh3CkNZDwtvcR9lW9YWLoYg7dooOue2t7BQNwi81aPqOifCRqQ1u 29 | RZrHZHbkgtn9qF8ai+QY6uEyGtmlBXr1DbHCZ+hUy+judCtNAoIBAQDKIAZJDTdU 30 | KRNLXMsSinLhFR2cI6TIJcUhmax/JTivRm7FtIIw2+vPabG30PpH16iP4O/FsUt1 31 | MJopspStE5zRy3gDdCM0pINTqS5ZHkqfYkUm/LSUEEkejmo58HPiDrIogQsiicmy 32 | xbHspd6U5tYM4ymxMlaHkYPtDCe4IaJ1GKOZUcCde8qKIdLdnuXhhuZTKEruWo6u 33 | 2iHxjaOTqESovu5tBeHmO0Ieefkaupax2+urx0LBWNbV9OUSFiovh3ziIwuDLVyE 34 | cUyaBzZtXHHOVWUQ42orZV53HRE5U5bTOtgdVWjVOzfPzcmMR12FETf2IjQWN3Zm 35 | Ij4N2Wh95kjfAoIBACNw8yB6ZtKouZPmTTVFi+qeVlOWv+a+SCVO+mZbJpkmXqB7 36 | qmUKCbmn2AvS0+cS613wXfGFB3C0NcD6nPQ18ie9X9ImxreJ6e9k+GO18DBtUS8J 37 | DMbtq/B+IfQkthVv11mQLSAyFbOwSErmP/oXSLpdGEhBftqvHKkZRYFwIjgcneAH 38 | 20AOus0ih82fqqF/Ju6HMLt/XMKteavSrPLoe3Y7sfa2rr/Mopsme9vCHzU5fW9L 39 | s5l2Boi+0XgICSA4DfKo30KBQDZbCI4Yo1VieCVSEKuOwR38hmXk+8AGI7yMCkka 40 | bcKD47MBn/prutNvdh+JJTe51+aHAv+UtioPDWECggEAPqhGyoaKTFCfZpFRcmHw 41 | 0s2U7D4wassqnQviaVfEhrEyzZ7zbOn/48BEE1N5AqRb4J4+Ne6MEbwLTjnJ1Q1r 42 | Y63LemaaM0t8WobrkzzMrTPtc/SKlgumXw6O538erUkY+W1nYkGMgRq/2ThiE46r 43 | dDtHRWLxZ/04BFfsSCxFDD+jF3g8WZVyZ/esQn9ri8ohxE2NTO5NpO6B7zuVJcTX 44 | 4+esSae1K4XbLU9qZVrKNECUgh+3FfDQSGsuR7kPQsShdgifHP8G2xe0Q1tRB/3M 45 | WcYnGmElHGbELfCY0kiTTM/FBvLfGJrO5mBIxm+pEgMQcgCQXQK8dVTQH8LxnXWx 46 | yQKCAQBSM2P+b3ddKraDGJ0cEDH/W1irpLxZGbYzvcFYl8vr/D/o7amKNZ4rVNFZ 47 | MrZYgKixBcxAXYozxEgar7NiugsNjOtbWCqL2LMkbjLS1NTgVwsYkTvLR8h4RMw7 48 | egNu5fkKCQQM5RNWl/rnOAn3J0q1MMsnmw5+/mCyPz5xag8tFPlDL8r+qIL1s4gj 49 | Lw+fjEzGStndCiBvtjMS3bAtFwXFu9SacKQhSmxXRpb1ZNGswE1iG/I8nZgC6mBC 50 | Fm6JILzs825DhEIvJ4cKsu3rAUkOmf0kMZFxMZwS5rZUt9guuUfR8TbmWacZvRQS 51 | 1BA70+AjR54oFLa7WbDidyYNX1wV 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /ttlv/decoder.go: -------------------------------------------------------------------------------- 1 | package ttlv 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "errors" 7 | "io" 8 | "reflect" 9 | 10 | "github.com/ansel1/merry" 11 | ) 12 | 13 | var ErrUnexpectedValue = errors.New("no field was found to unmarshal value into") 14 | 15 | // Unmarshal parses TTLV encoded data and stores the result 16 | // in the value pointed to by v. 17 | // 18 | // An error will be returned if v is nil or not a point, or 19 | // if b is not valid TTLV. 20 | // 21 | // Unmarshal will allocate values to store the result in, similar to the 22 | // json.Marshal. Generally, the destination value can be a pointer or 23 | // or a direct value. Currently, Unmarshal does not support anonymous fields. 24 | // They will be ignored. Private fields are ignored. 25 | // 26 | // Unmarshal maps TTLV values to golang values according to the following 27 | // rules: 28 | // 29 | // 1. If the destination value is interface{}, it will be set to the result 30 | // of TTLV.Value() 31 | // 2. If the destination implements Unmarshaler, that will be called. 32 | // 3. If the destination is a slice (except for []byte), append the 33 | // unmarshalled value to the slice 34 | // 4. Structure unmarshals into a struct. See rules 35 | // below for matching struct fields to the values in the Structure. 36 | // 5. Interval unmarshals into an int64 37 | // 6. DateTime and DateTimeExtended ummarshal into time.Time 38 | // 7. ByteString unmarshals to a []byte 39 | // 8. TextString unmarshals into a string 40 | // 9. Boolean unmarshals into a bool 41 | // 10. Enumeration can unmarshal into an int, int8, int16, int32, or their 42 | // uint counterparts. If the KMIP value overflows the destination, a 43 | // *UnmarshalerError with cause ErrIntOverflow is returned. 44 | // 11. Integer can unmarshal to the same types as Enumeration, with the 45 | // same overflow check. 46 | // 12. LongInteger unmarshals to int64 or uint64 47 | // 13. BitInteger unmarshals to big.Int. 48 | // 49 | // If the destination value is not a supported type, an *UnmarshalerError with 50 | // cause ErrUnsupportedTypeError is returned. If the source value's type is not recognized, 51 | // *UnmarshalerError with cause ErrInvalidType is returned. 52 | // 53 | // # Unmarshaling Structure 54 | // 55 | // Unmarshal will try to match the values in the Structure with the fields in the 56 | // destination struct. Structure is an array of values, while a struct is more like 57 | // a map, so not all Structure values can be accurately represented by a golang struct. 58 | // In particular, a Structure can hold the same tag multiple times, e.g. 3 TagComment values 59 | // in a row. 60 | // 61 | // For each field in the struct, Unmarshal infers a KMIP Tag by examining both the name 62 | // and type of the field. It uses the following rules, in order: 63 | // 64 | // 1. If the type of a field is a struct, and the struct contains a field named "TTLVTag", and the field 65 | // has a "ttlv" struct tag, the value of the struct tag will be parsed using ParseTag(). If 66 | // parsing fails, an error is returned. The type and value of the TTLVTag field is ignored. 67 | // 68 | // In this example, the F field will map to TagDeactivationDate. 69 | // 70 | // type Bar struct { 71 | // F Foo 72 | // } 73 | // 74 | // type Foo struct { 75 | // TTLVTag struct{} `ttlv:"DeactivationDate"` 76 | // } 77 | // 78 | // If Bar uses a struct tag on F indicating a different tag, it is an error: 79 | // 80 | // type Bar struct { 81 | // F Foo `ttlv:"DerivationData"` // this will cause an ErrTagConflict 82 | // // because conflict Bar's field tag 83 | // // conflicts with Foo's intrinsic tag 84 | // F2 Foo `ttlv:"0x420034"` // the value can also be hex 85 | // } 86 | // 87 | // 2. If the type of the field is a struct, and the struct contains a field named "TTLVTag", 88 | // and that field is of type ttlv.Tag and is not empty, the value of the field will be the 89 | // inferred Tag. For example: 90 | // 91 | // type Foo struct { 92 | // TTLVTag ttlv.Tag 93 | // } 94 | // f := Foo{TTLVTag: ttlv.TagState} 95 | // 96 | // This allows you to dynamically set the KMIP tag that a value will marshal to. 97 | // 98 | // 3. The "ttlv" struct tag can be used to indicate the tag for a field. The value will 99 | // be parsed with ParseTag(): 100 | // 101 | // type Bar struct { 102 | // F Foo `ttlv:"DerivationData"` 103 | // } 104 | // 105 | // 4. The name of the field is parsed with ParseTag(): 106 | // 107 | // type Bar struct { 108 | // DerivationData int 109 | // } 110 | // 111 | // 5. The name of the field's type is parsed with ParseTab(): 112 | // 113 | // type DerivationData int 114 | // 115 | // type Bar struct { 116 | // dd DerivationData 117 | // } 118 | // 119 | // If no tag value can be inferred, the field is ignored. Multiple fields 120 | // *cannot* map to the same KMIP tag. If they do, an ErrTagConflict will 121 | // be returned. 122 | // 123 | // Each value in the Structure will be matched against the first field 124 | // in the struct with the same inferred tag. 125 | // 126 | // If the value cannot be matched with a field, Unmarshal will look for 127 | // the first field with the "any" struct flag set and unmarshal into that: 128 | // 129 | // type Foo struct { 130 | // Comment string // the Comment will unmarshal into this 131 | // EverythingElse []interface{} `,any` // all other values will unmarshal into this 132 | // AnotherAny []interface{} `,any` // allowed, but ignored. first any field will always match 133 | // NotLegal []interface{} `TagComment,any` // you cannot specify a tag and the any flag. 134 | // // will return error 135 | // } 136 | // 137 | // If after applying these rules no destination field is found, the KMIP value is ignored. 138 | func Unmarshal(ttlv TTLV, v interface{}) error { 139 | return NewDecoder(bytes.NewReader(ttlv)).Decode(v) 140 | } 141 | 142 | // Unmarshaler knows how to unmarshal a ttlv value into itself. 143 | // The decoder argument may be used to decode the ttlv value into 144 | // intermediary values if needed. 145 | type Unmarshaler interface { 146 | UnmarshalTTLV(d *Decoder, ttlv TTLV) error 147 | } 148 | 149 | // Decoder reads KMIP values from a stream, and decodes them into golang values. 150 | // It currently only decodes TTLV encoded KMIP values. 151 | // TODO: support decoding XML and JSON, so their decoding can be configured 152 | // 153 | // If DisallowExtraValues is true, the decoder will return an error when decoding 154 | // Structures into structs and a matching field can't get found for every value. 155 | type Decoder struct { 156 | r io.Reader 157 | bufr *bufio.Reader 158 | DisallowExtraValues bool 159 | 160 | currStruct reflect.Type 161 | currField string 162 | } 163 | 164 | func NewDecoder(r io.Reader) *Decoder { 165 | return &Decoder{ 166 | r: r, 167 | bufr: bufio.NewReader(r), 168 | } 169 | } 170 | 171 | // Reset resets the internal state of the decoder for reuse. 172 | func (dec *Decoder) Reset(r io.Reader) { 173 | *dec = Decoder{ 174 | r: r, 175 | bufr: dec.bufr, 176 | } 177 | dec.bufr.Reset(r) 178 | } 179 | 180 | // Decode the first KMIP value from the reader into v. 181 | // See Unmarshal for decoding rules. 182 | func (dec *Decoder) Decode(v interface{}) error { 183 | ttlv, err := dec.NextTTLV() 184 | if err != nil { 185 | return err 186 | } 187 | 188 | return dec.DecodeValue(v, ttlv) 189 | } 190 | 191 | // DecodeValue decodes a ttlv value into v. This doesn't read anything 192 | // from the Decoder's reader. 193 | // See Unmarshal for decoding rules. 194 | func (dec *Decoder) DecodeValue(v interface{}, ttlv TTLV) error { 195 | val := reflect.ValueOf(v) 196 | if val.Kind() != reflect.Ptr { 197 | return merry.New("non-pointer passed to Decode") 198 | } 199 | 200 | return dec.unmarshal(val, ttlv) 201 | } 202 | 203 | func (dec *Decoder) unmarshal(val reflect.Value, ttlv TTLV) error { 204 | if len(ttlv) == 0 { 205 | return nil 206 | } 207 | 208 | // Load value from interface, but only if the result will be 209 | // usefully addressable. 210 | if val.Kind() == reflect.Interface && !val.IsNil() { 211 | e := val.Elem() 212 | if e.Kind() == reflect.Ptr && !e.IsNil() { 213 | val = e 214 | } 215 | } 216 | 217 | if val.Kind() == reflect.Ptr { 218 | if val.IsNil() { 219 | val.Set(reflect.New(val.Type().Elem())) 220 | } 221 | 222 | val = val.Elem() 223 | } 224 | 225 | if val.Type().Implements(unmarshalerType) { 226 | return val.Interface().(Unmarshaler).UnmarshalTTLV(dec, ttlv) //nolint:forcetypeassert 227 | } 228 | 229 | if val.CanAddr() { 230 | valAddr := val.Addr() 231 | if valAddr.CanInterface() && valAddr.Type().Implements(unmarshalerType) { 232 | return valAddr.Interface().(Unmarshaler).UnmarshalTTLV(dec, ttlv) //nolint:forcetypeassert 233 | } 234 | } 235 | 236 | switch val.Kind() { 237 | case reflect.Interface: 238 | if ttlv.Type() == TypeStructure { 239 | // if the value is a structure, set the whole TTLV 240 | // as the value. 241 | val.Set(reflect.ValueOf(ttlv)) 242 | } else { 243 | // set blank interface equal to the TTLV.Value() 244 | val.Set(reflect.ValueOf(ttlv.Value())) 245 | } 246 | 247 | return nil 248 | case reflect.Slice: 249 | typ := val.Type() 250 | if typ.Elem() == byteType { 251 | // []byte 252 | break 253 | } 254 | 255 | // Slice of element values. 256 | // Grow slice. 257 | n := val.Len() 258 | val.Set(reflect.Append(val, reflect.Zero(val.Type().Elem()))) 259 | 260 | // Recur to read element into slice. 261 | if err := dec.unmarshal(val.Index(n), ttlv); err != nil { 262 | val.SetLen(n) 263 | return err 264 | } 265 | 266 | return nil 267 | default: 268 | } 269 | 270 | typeMismatchErr := func() error { 271 | e := &UnmarshalerError{ 272 | Struct: dec.currStruct, 273 | Field: dec.currField, 274 | Tag: ttlv.Tag(), 275 | Type: ttlv.Type(), 276 | Val: val.Type(), 277 | } 278 | err := merry.WrapSkipping(e, 1).WithCause(ErrUnsupportedTypeError) 279 | 280 | return err 281 | } 282 | 283 | switch ttlv.Type() { 284 | case TypeStructure: 285 | if val.Kind() != reflect.Struct { 286 | return typeMismatchErr() 287 | } 288 | // stash currStruct 289 | currStruct := dec.currStruct 290 | err := dec.unmarshalStructure(ttlv, val) 291 | // restore currStruct 292 | dec.currStruct = currStruct 293 | 294 | return err 295 | case TypeInterval: 296 | if val.Kind() != reflect.Int64 { 297 | return typeMismatchErr() 298 | } 299 | 300 | val.SetInt(int64(ttlv.ValueInterval())) 301 | case TypeDateTime, TypeDateTimeExtended: 302 | if val.Type() != timeType { 303 | return typeMismatchErr() 304 | } 305 | 306 | val.Set(reflect.ValueOf(ttlv.ValueDateTime())) 307 | case TypeByteString: 308 | if val.Kind() != reflect.Slice && val.Type().Elem() != byteType { 309 | return typeMismatchErr() 310 | } 311 | 312 | val.SetBytes(ttlv.ValueByteString()) 313 | case TypeTextString: 314 | if val.Kind() != reflect.String { 315 | return typeMismatchErr() 316 | } 317 | 318 | val.SetString(ttlv.ValueTextString()) 319 | case TypeBoolean: 320 | if val.Kind() != reflect.Bool { 321 | return typeMismatchErr() 322 | } 323 | 324 | val.SetBool(ttlv.ValueBoolean()) 325 | //nolint:dupl 326 | case TypeEnumeration: 327 | switch val.Kind() { 328 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: 329 | i := int64(ttlv.ValueEnumeration()) 330 | if val.OverflowInt(i) { 331 | return dec.newUnmarshalerError(ttlv, val.Type(), ErrIntOverflow) 332 | } 333 | 334 | val.SetInt(i) 335 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: 336 | i := uint64(ttlv.ValueEnumeration()) 337 | if val.OverflowUint(i) { 338 | return dec.newUnmarshalerError(ttlv, val.Type(), ErrIntOverflow) 339 | } 340 | 341 | val.SetUint(i) 342 | default: 343 | return typeMismatchErr() 344 | } 345 | //nolint:dupl 346 | case TypeInteger: 347 | switch val.Kind() { 348 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: 349 | i := int64(ttlv.ValueInteger()) 350 | if val.OverflowInt(i) { 351 | return dec.newUnmarshalerError(ttlv, val.Type(), ErrIntOverflow) 352 | } 353 | val.SetInt(i) 354 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: 355 | i := uint64(ttlv.ValueInteger()) //nolint:gosec // already prevented by the check above 356 | if val.OverflowUint(i) { 357 | return dec.newUnmarshalerError(ttlv, val.Type(), ErrIntOverflow) 358 | } 359 | 360 | val.SetUint(i) 361 | default: 362 | return typeMismatchErr() 363 | } 364 | case TypeLongInteger: 365 | switch val.Kind() { 366 | case reflect.Int64: 367 | i := ttlv.ValueLongInteger() 368 | if val.OverflowInt(i) { 369 | return dec.newUnmarshalerError(ttlv, val.Type(), ErrIntOverflow) 370 | } 371 | val.SetInt(i) 372 | case reflect.Uint64: 373 | i := uint64(ttlv.ValueLongInteger()) //nolint:gosec // already prevented by the check above 374 | if val.OverflowUint(i) { 375 | return dec.newUnmarshalerError(ttlv, val.Type(), ErrUintOverflow) 376 | } 377 | val.SetUint(i) 378 | default: 379 | return typeMismatchErr() 380 | } 381 | case TypeBigInteger: 382 | if val.Type() != bigIntType { 383 | return typeMismatchErr() 384 | } 385 | 386 | val.Set(reflect.ValueOf(*ttlv.ValueBigInteger())) 387 | default: 388 | return dec.newUnmarshalerError(ttlv, val.Type(), ErrInvalidType) 389 | } 390 | 391 | return nil 392 | } 393 | 394 | func (dec *Decoder) unmarshalStructure(ttlv TTLV, val reflect.Value) error { 395 | ti, err := getTypeInfo(val.Type()) 396 | if err != nil { 397 | return dec.newUnmarshalerError(ttlv, val.Type(), err) 398 | } 399 | 400 | if ti.tagField != nil && ti.tagField.ti.typ == tagType { 401 | val.FieldByIndex(ti.tagField.index).Set(reflect.ValueOf(ttlv.Tag())) 402 | } 403 | 404 | fields := ti.valueFields 405 | 406 | // push currStruct (caller will pop) 407 | dec.currStruct = val.Type() 408 | 409 | for n := ttlv.ValueStructure(); n != nil; n = n.Next() { 410 | fldIdx := -1 411 | 412 | for i := range fields { 413 | if fields[i].flags.any() { 414 | // if this is the first any field found, keep track 415 | // of it as the current candidate match, but 416 | // keep looking for a tag match 417 | if fldIdx == -1 { 418 | fldIdx = i 419 | } 420 | } else if fields[i].tag == n.Tag() { 421 | // tag match found 422 | // we can stop looking 423 | fldIdx = i 424 | break 425 | } 426 | } 427 | 428 | if fldIdx > -1 { 429 | // push currField 430 | currField := dec.currField 431 | dec.currField = fields[fldIdx].name 432 | err := dec.unmarshal(val.FieldByIndex(fields[fldIdx].index), n) 433 | // restore currField 434 | dec.currField = currField 435 | 436 | if err != nil { 437 | return err 438 | } 439 | } else if dec.DisallowExtraValues { 440 | return dec.newUnmarshalerError(ttlv, val.Type(), ErrUnexpectedValue) 441 | } 442 | } 443 | 444 | return nil 445 | } 446 | 447 | // NextTTLV reads the next, full KMIP value off the reader. 448 | func (dec *Decoder) NextTTLV() (TTLV, error) { 449 | // first, read the header 450 | header, err := dec.bufr.Peek(8) 451 | if err != nil { 452 | return nil, merry.Wrap(err) 453 | } 454 | 455 | if err := TTLV(header).ValidHeader(); err != nil { 456 | // bad header, abort 457 | return TTLV(header), merry.Prependf(err, "invalid header: %v", TTLV(header)) 458 | } 459 | 460 | // allocate a buffer large enough for the entire message 461 | fullLen := TTLV(header).FullLen() 462 | buf := make([]byte, fullLen) 463 | 464 | var totRead int 465 | 466 | for { 467 | n, err := dec.bufr.Read(buf[totRead:]) 468 | if err != nil { 469 | return TTLV(buf), merry.Wrap(err) 470 | } 471 | 472 | totRead += n 473 | if totRead >= fullLen { 474 | // we've read off a single full message 475 | return buf, nil 476 | } // else keep reading 477 | } 478 | } 479 | 480 | func (dec *Decoder) newUnmarshalerError(ttlv TTLV, valType reflect.Type, cause error) merry.Error { 481 | e := &UnmarshalerError{ 482 | Struct: dec.currStruct, 483 | Field: dec.currField, 484 | Tag: ttlv.Tag(), 485 | Type: ttlv.Type(), 486 | Val: valType, 487 | } 488 | 489 | return merry.WrapSkipping(e, 1).WithCause(cause) 490 | } 491 | 492 | type UnmarshalerError struct { 493 | // Val is the type of the destination value 494 | Val reflect.Type 495 | // Struct is the type of the containing struct if the value is a field 496 | Struct reflect.Type 497 | // Field is the name of the value field 498 | Field string 499 | Tag Tag 500 | Type Type 501 | } 502 | 503 | func (e *UnmarshalerError) Error() string { 504 | msg := "kmip: error unmarshaling " + e.Tag.String() + " with type " + e.Type.String() + " into value of type " + e.Val.Name() 505 | if e.Struct != nil { 506 | msg += " in struct field " + e.Struct.Name() + "." + e.Field 507 | } 508 | 509 | return msg 510 | } 511 | -------------------------------------------------------------------------------- /ttlv/decoder_test.go: -------------------------------------------------------------------------------- 1 | package ttlv_test 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "math" 8 | "math/big" 9 | "reflect" 10 | "testing" 11 | "time" 12 | 13 | "github.com/ansel1/merry" 14 | . "github.com/gemalto/kmip-go/kmip14" //nolint:revive 15 | . "github.com/gemalto/kmip-go/ttlv" //nolint:revive 16 | "github.com/stretchr/testify/assert" 17 | "github.com/stretchr/testify/require" 18 | ) 19 | 20 | func TestUnmarshal_known(t *testing.T) { 21 | for _, sample := range knownGoodSamples { 22 | tname := sample.name 23 | if tname == "" { 24 | tname = fmt.Sprintf("%T", sample.v) 25 | } 26 | t.Run(tname, func(t *testing.T) { 27 | typ := reflect.ValueOf(sample.v).Type() 28 | if typ.Kind() == reflect.Ptr { 29 | typ = typ.Elem() 30 | } 31 | v := reflect.New(typ).Interface() 32 | 33 | err := Unmarshal(Hex2bytes(sample.exp), v) 34 | require.NoError(t, err) 35 | switch tv := sample.v.(type) { 36 | case *big.Int: 37 | require.Zero(t, tv.Cmp(v.(*big.Int))) 38 | case big.Int: 39 | require.Zero(t, tv.Cmp(v.(*big.Int))) 40 | default: 41 | require.Equal(t, sample.v, reflect.ValueOf(v).Elem().Interface()) 42 | } 43 | }) 44 | } 45 | } 46 | 47 | func TestUnmarshal(t *testing.T) { 48 | type unmarshalTest struct { 49 | name string 50 | in, ptr, expected interface{} 51 | err error 52 | skipSliceOfTest bool 53 | skipExactRoundtripTest bool 54 | } 55 | 56 | tests := []unmarshalTest{ 57 | { 58 | in: true, 59 | ptr: new(bool), 60 | }, 61 | { 62 | in: "red", 63 | ptr: new(string), 64 | }, 65 | { 66 | in: time.Second * 10, 67 | ptr: new(time.Duration), 68 | }, 69 | { 70 | in: parseTime("2008-03-14T11:56:40Z"), 71 | ptr: new(time.Time), 72 | }, 73 | { 74 | in: 5, 75 | ptr: new(int), 76 | }, 77 | { 78 | in: 5, 79 | ptr: new(int8), 80 | expected: int8(5), 81 | }, 82 | { 83 | name: "intoverflow", 84 | in: math.MaxInt8 + 1, 85 | ptr: new(int8), 86 | err: ErrIntOverflow, 87 | }, 88 | { 89 | in: 5, 90 | ptr: new(int16), 91 | expected: int16(5), 92 | }, 93 | { 94 | in: 5, 95 | ptr: new(int32), 96 | expected: int32(5), 97 | }, 98 | { 99 | in: 5, 100 | ptr: new(int64), 101 | err: ErrUnsupportedTypeError, 102 | }, 103 | { 104 | in: 5, 105 | ptr: new(uint), 106 | expected: uint(5), 107 | }, 108 | { 109 | in: 5, 110 | ptr: new(uint8), 111 | expected: uint8(5), 112 | skipSliceOfTest: true, // []uint8 is an alias for []byte, which is handled differently 113 | }, 114 | { 115 | in: 5, 116 | ptr: new(uint16), 117 | expected: uint16(5), 118 | }, 119 | { 120 | in: 5, 121 | ptr: new(uint32), 122 | expected: uint32(5), 123 | }, 124 | { 125 | in: 5, 126 | ptr: new(uint64), 127 | err: ErrUnsupportedTypeError, 128 | }, 129 | { 130 | name: "uintoverflow", 131 | in: math.MaxUint8 + 1, 132 | ptr: new(uint8), 133 | err: ErrIntOverflow, 134 | }, 135 | { 136 | in: int64(5), 137 | ptr: new(int64), 138 | }, 139 | { 140 | in: int64(5), 141 | ptr: new(uint64), 142 | expected: uint64(5), 143 | }, 144 | { 145 | in: []byte{0x01, 0x02, 0x03}, 146 | ptr: new([]byte), 147 | }, 148 | { 149 | in: big.NewInt(5), 150 | ptr: new(big.Int), 151 | expected: *(big.NewInt(5)), 152 | }, 153 | { 154 | in: CredentialTypeAttestation, 155 | ptr: new(CredentialType), 156 | }, 157 | { 158 | in: CredentialTypeAttestation, 159 | ptr: new(uint), 160 | expected: uint(CredentialTypeAttestation), 161 | skipExactRoundtripTest: true, 162 | }, 163 | { 164 | in: CredentialTypeAttestation, 165 | ptr: new(int64), 166 | err: ErrUnsupportedTypeError, 167 | }, 168 | { 169 | in: Value{Tag: TagBatchCount, Value: "red"}, 170 | ptr: new(interface{}), 171 | expected: "red", 172 | }, 173 | { 174 | name: "structtypeerror", 175 | in: Value{Tag: TagBatchCount, Value: Values{ 176 | Value{Tag: TagComment, Value: "red"}, 177 | }}, 178 | ptr: new(int), 179 | err: ErrUnsupportedTypeError, 180 | }, 181 | { 182 | name: "ttlvStructure", 183 | in: Value{Tag: TagBatchCount, Value: Values{ 184 | Value{Tag: TagBatchItem, Value: "red"}, 185 | Value{Tag: TagBatchContinueCapability, Value: "blue"}, 186 | }}, 187 | ptr: new(Value), 188 | expected: Value{Tag: TagBatchCount, Value: Values{ 189 | Value{Tag: TagBatchItem, Value: "red"}, 190 | Value{Tag: TagBatchContinueCapability, Value: "blue"}, 191 | }}, 192 | }, 193 | } 194 | 195 | type A struct { 196 | Comment string 197 | } 198 | 199 | tests = append(tests, 200 | unmarshalTest{ 201 | name: "simplestruct", 202 | in: Value{Tag: TagBatchCount, Value: Values{ 203 | Value{Tag: TagComment, Value: "red"}, 204 | }}, 205 | ptr: new(A), 206 | expected: A{Comment: "red"}, 207 | }, 208 | ) 209 | 210 | type B struct { 211 | S string `ttlv:"Comment"` 212 | } 213 | 214 | tests = append(tests, 215 | unmarshalTest{ 216 | name: "fieldtag", 217 | in: Value{Tag: TagBatchCount, Value: Values{ 218 | Value{Tag: TagComment, Value: "red"}, 219 | }}, 220 | ptr: new(B), 221 | expected: B{S: "red"}, 222 | }, 223 | ) 224 | 225 | type D struct { 226 | Comment string 227 | BatchCount string 228 | } 229 | 230 | tests = append(tests, 231 | unmarshalTest{ 232 | name: "multifields", 233 | in: Value{Tag: TagBatchCount, Value: Values{ 234 | Value{Tag: TagComment, Value: "red"}, 235 | Value{Tag: TagBatchCount, Value: "blue"}, 236 | }}, 237 | ptr: new(D), 238 | expected: D{Comment: "red", BatchCount: "blue"}, 239 | }, 240 | ) 241 | 242 | type E struct { 243 | Comment string 244 | BatchCount A 245 | } 246 | 247 | tests = append(tests, 248 | unmarshalTest{ 249 | name: "nested", 250 | in: Value{Tag: TagBatchCount, Value: Values{ 251 | Value{Tag: TagComment, Value: "red"}, 252 | Value{Tag: TagBatchCount, Value: Values{ 253 | Value{Tag: TagComment, Value: "blue"}, 254 | }}, 255 | }}, 256 | ptr: new(E), 257 | expected: E{Comment: "red", BatchCount: A{Comment: "blue"}}, 258 | }, 259 | ) 260 | 261 | type F struct { 262 | Comment string 263 | BatchCount *A 264 | } 265 | 266 | tests = append(tests, 267 | unmarshalTest{ 268 | name: "nestedptr", 269 | in: Value{Tag: TagBatchCount, Value: Values{ 270 | Value{Tag: TagComment, Value: "red"}, 271 | Value{Tag: TagBatchCount, Value: Values{ 272 | Value{Tag: TagComment, Value: "blue"}, 273 | }}, 274 | }}, 275 | ptr: new(F), 276 | expected: F{Comment: "red", BatchCount: &A{Comment: "blue"}}, 277 | }, 278 | ) 279 | 280 | type G struct { 281 | Comment []string 282 | } 283 | 284 | tests = append(tests, 285 | unmarshalTest{ 286 | name: "slice", 287 | in: Value{Tag: TagBatchCount, Value: Values{ 288 | Value{Tag: TagComment, Value: "red"}, 289 | Value{Tag: TagComment, Value: "blue"}, 290 | Value{Tag: TagComment, Value: "green"}, 291 | }}, 292 | ptr: new(G), 293 | expected: G{Comment: []string{"red", "blue", "green"}}, 294 | }, 295 | ) 296 | 297 | type H struct { 298 | Comment string 299 | Any1 []Value `ttlv:",any"` 300 | Any2 []Value `ttlv:",any"` 301 | AttributeValue string 302 | } 303 | 304 | tests = append(tests, 305 | unmarshalTest{ 306 | name: "anyflag", 307 | in: Value{Value: Values{ 308 | Value{Tag: TagComment, Value: "red"}, 309 | Value{Tag: TagNameType, Value: "blue"}, 310 | Value{Tag: TagName, Value: "orange"}, 311 | Value{Tag: TagAttributeValue, Value: "yellow"}, 312 | }}, 313 | ptr: new(H), 314 | expected: H{Comment: "red", AttributeValue: "yellow", Any1: []Value{ 315 | {Tag: TagNameType, Value: "blue"}, 316 | {Tag: TagName, Value: "orange"}, 317 | }}, 318 | }) 319 | 320 | for _, test := range tests { 321 | if test.name == "" { 322 | test.name = fmt.Sprintf("%T into %T", test.in, test.ptr) 323 | } 324 | t.Run(test.name, func(t *testing.T) { 325 | b, err := Marshal(Value{Tag: TagBatchCount, Value: test.in}) 326 | require.NoError(t, err) 327 | 328 | t.Log(b.String()) 329 | 330 | v := reflect.New(reflect.TypeOf(test.ptr).Elem()) 331 | expected := test.expected 332 | if expected == nil { 333 | expected = test.in 334 | } 335 | 336 | err = Unmarshal(b, v.Interface()) 337 | 338 | if test.err != nil { 339 | require.Error(t, err, "got value instead: %#v", v.Elem().Interface()) 340 | require.True(t, errors.Is(err, test.err), Details(err)) 341 | return 342 | } 343 | 344 | require.NoError(t, err, Details(err)) 345 | require.Equal(t, expected, v.Elem().Interface()) 346 | 347 | // if out type is not a slice, add a test for unmarshaling into 348 | // a slice of that type, which should work too. e.g. you should 349 | // be able to unmarshal a bool into either bool or []bool 350 | 351 | if !test.skipSliceOfTest { 352 | t.Run("sliceof", func(t *testing.T) { 353 | sltype := reflect.SliceOf(reflect.TypeOf(test.ptr).Elem()) 354 | v := reflect.New(sltype) 355 | err = Unmarshal(b, v.Interface()) 356 | require.NoError(t, err, Details(err)) 357 | expv := reflect.Zero(sltype) 358 | expv = reflect.Append(expv, reflect.ValueOf(expected)) 359 | require.Equal(t, expv.Interface(), v.Elem().Interface()) 360 | }) 361 | } 362 | 363 | t.Run("roundtrip", func(t *testing.T) { 364 | bb, err := Marshal(Value{Tag: TagBatchCount, Value: v.Elem().Interface()}) 365 | require.NoError(t, err) 366 | 367 | if !test.skipExactRoundtripTest { 368 | assert.Equal(t, b.String(), bb.String()) 369 | } 370 | 371 | t.Log(bb.String()) 372 | 373 | vv := reflect.New(reflect.TypeOf(test.ptr).Elem()) 374 | err = Unmarshal(bb, vv.Interface()) 375 | require.NoError(t, err) 376 | 377 | assert.Equal(t, v.Elem().Interface(), vv.Elem().Interface()) 378 | }) 379 | }) 380 | } 381 | } 382 | 383 | func TestUnmarshal_tagfield(t *testing.T) { 384 | // tests unmarshaling into structs which contain 385 | // a TTLVTag field. Unmarshal should record the tag of the value 386 | // in this field 387 | 388 | b, err := Marshal(Value{TagComment, Values{{TagName, "red"}}}) 389 | require.NoError(t, err) 390 | 391 | type M struct { 392 | TTLVTag Tag 393 | Name string 394 | } 395 | 396 | var m M 397 | 398 | err = Unmarshal(b, &m) 399 | require.NoError(t, err) 400 | 401 | assert.Equal(t, M{TagComment, "red"}, m) 402 | } 403 | 404 | func TestUnmarshal_tagPrecedence(t *testing.T) { 405 | // tests the order of precedence for matching a field 406 | // to a ttlv 407 | 408 | b, err := Marshal(Value{TagComment, Values{{TagName, "red"}}}) 409 | require.NoError(t, err) 410 | 411 | // lowest precedence: the name of the field 412 | type A struct { 413 | Name string 414 | } 415 | 416 | var a A 417 | 418 | err = Unmarshal(b, &a) 419 | require.NoError(t, err) 420 | 421 | assert.EqualValues(t, "red", a.Name) 422 | 423 | // next: the TTLVTag tag of the struct field 424 | 425 | type B struct { 426 | N struct { 427 | TTLVTag string `ttlv:"Name"` 428 | Value 429 | } 430 | } 431 | 432 | var bb B 433 | 434 | err = Unmarshal(b, &bb) 435 | require.NoError(t, err) 436 | 437 | assert.EqualValues(t, "red", bb.N.Value.Value) 438 | 439 | // next: the field's tag 440 | 441 | type C struct { 442 | N string `ttlv:"Name"` 443 | } 444 | 445 | var c C 446 | 447 | err = Unmarshal(b, &c) 448 | require.NoError(t, err) 449 | 450 | assert.EqualValues(t, "red", c.N) 451 | 452 | // conflicts of these result in errors 453 | cases := []struct { 454 | name string 455 | v interface{} 456 | allowed bool 457 | }{ 458 | {name: "field name and field tag", v: &struct { 459 | Name string 460 | N string `ttlv:"Name"` 461 | }{}}, 462 | {name: "field name and TTLVTag", v: &struct { 463 | Name string 464 | N struct { 465 | TTLVTag string `ttlv:"Name"` 466 | Value 467 | } 468 | }{}}, 469 | {name: "field tag and TTLVTag", v: &struct { 470 | S string `ttlv:"Name"` 471 | N struct { 472 | TTLVTag string `ttlv:"Name"` 473 | Value 474 | } 475 | }{}}, 476 | {name: "field tag and TTLVTag", v: &struct { 477 | N struct { 478 | TTLVTag string `ttlv:"Name"` 479 | Value 480 | } `ttlv:"Comment"` 481 | }{}}, 482 | { 483 | name: "field tag and TTLVTag", 484 | v: &struct { 485 | N struct { 486 | TTLVTag string `ttlv:"Name"` 487 | Value 488 | } `ttlv:"Name"` 489 | }{}, 490 | allowed: true, 491 | }, 492 | } 493 | 494 | for _, tc := range cases { 495 | t.Run(tc.name, func(t *testing.T) { 496 | err := Unmarshal(b, tc.v) 497 | if tc.allowed { 498 | require.NoError(t, err) 499 | } else { 500 | require.Error(t, err) 501 | assert.True(t, merry.Is(err, ErrTagConflict), "%+v", err) 502 | } 503 | }) 504 | } 505 | 506 | type D struct { 507 | Name string 508 | N string `ttlv:"Name"` 509 | } 510 | 511 | err = Unmarshal(b, &D{}) 512 | require.Error(t, err) 513 | assert.True(t, merry.Is(err, ErrTagConflict)) 514 | } 515 | 516 | func TestDecoder_DisallowUnknownFields(t *testing.T) { 517 | type A struct { 518 | Comment string 519 | BatchCount int 520 | } 521 | 522 | tests := []struct { 523 | name string 524 | input Value 525 | }{ 526 | { 527 | name: "middle", 528 | input: Value{ 529 | TagAlternativeName, 530 | Values{ 531 | {TagComment, "red"}, 532 | {TagArchiveDate, "blue"}, 533 | {TagBatchCount, 5}, 534 | }, 535 | }, 536 | }, 537 | { 538 | name: "first", 539 | input: Value{ 540 | TagAlternativeName, 541 | Values{ 542 | {TagArchiveDate, "blue"}, 543 | {TagComment, "red"}, 544 | {TagBatchCount, 5}, 545 | }, 546 | }, 547 | }, 548 | { 549 | name: "last", 550 | input: Value{ 551 | TagAlternativeName, 552 | Values{ 553 | {TagComment, "red"}, 554 | {TagBatchCount, 5}, 555 | {TagArchiveDate, "blue"}, 556 | }, 557 | }, 558 | }, 559 | } 560 | 561 | for _, testcase := range tests { 562 | t.Run(testcase.name, func(t *testing.T) { 563 | b, err := Marshal(testcase.input) 564 | require.NoError(t, err) 565 | 566 | // verify that it works find if flag is off 567 | dec := NewDecoder(bytes.NewReader(b)) 568 | a := A{} 569 | err = dec.Decode(&a) 570 | require.NoError(t, err) 571 | 572 | require.Equal(t, A{"red", 5}, a) 573 | 574 | // verify that it bombs is flag is on 575 | dec = NewDecoder(bytes.NewReader(b)) 576 | dec.DisallowExtraValues = true 577 | err = dec.Decode(&a) 578 | require.Error(t, err) 579 | require.True(t, merry.Is(err, ErrUnexpectedValue)) 580 | }) 581 | } 582 | } 583 | -------------------------------------------------------------------------------- /ttlv/docs.go: -------------------------------------------------------------------------------- 1 | // Package ttlv encodes and decodes the 3 wire formats defined in the KMIP specification: 2 | // 3 | // 1. TTLV (the default, binary wire format) 4 | // 2. JSON 5 | // 3. XML 6 | // 7 | // The core representation of KMIP values is the ttlv.TTLV type, which is 8 | // a []byte encoded in the TTLV binary format. The ttlv.TTLV type knows how to marshal/ 9 | // unmarshal to and from the JSON and XML encoding formats. 10 | // 11 | // This package also knows how to marshal and unmarshal ttlv.TTLV values to golang structs, 12 | // in a way similar to the json or xml packages. 13 | // 14 | // See Marshal() and Unmarshal() for the rules about how golang values map to KMIP TTLVs. 15 | // Encoder and Decoder can be used to process streams of KMIP values. 16 | // 17 | // This package holds a registry of type, tag, and enum value names, which are used to transcode 18 | // strings into these values. KMIP 1.4 names will be automatically loaded into the 19 | // DefaultRegistry. See the kmip20 package to add definitions for 2.0 names. 20 | // 21 | // Print() and PrettyPrintHex() can be used to debug TTLV values. 22 | package ttlv 23 | -------------------------------------------------------------------------------- /ttlv/encoder.go: -------------------------------------------------------------------------------- 1 | package ttlv 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "math" 10 | "math/big" 11 | "reflect" 12 | "strings" 13 | "time" 14 | 15 | "github.com/ansel1/merry" 16 | ) 17 | 18 | const structFieldTag = "ttlv" 19 | 20 | var ( 21 | ErrIntOverflow = fmt.Errorf("value exceeds max int value %d", math.MaxInt32) 22 | ErrUintOverflow = fmt.Errorf("value exceeds max uint value %d", math.MaxUint32) 23 | ErrUnsupportedEnumTypeError = errors.New("unsupported type for enums, must be string, or int types") 24 | ErrUnsupportedTypeError = errors.New("marshaling/unmarshaling is not supported for this type") 25 | ErrNoTag = errors.New("unable to determine tag for field") 26 | ErrTagConflict = errors.New("tag conflict") 27 | ) 28 | 29 | // Marshal encodes a golang value into a KMIP value. 30 | // 31 | // An error will be returned if v is an invalid pointer. 32 | // 33 | // Currently, Marshal does not support anonymous fields. 34 | // Private fields are ignored. 35 | // 36 | // Marshal maps the golang value to a KMIP tag, type, and value 37 | // encoding. To determine the KMIP tag, Marshal uses the same rules 38 | // as Unmarshal. 39 | // 40 | // The appropriate type and encoding are inferred from the golang type 41 | // and from the inferred KMIP tag, according to these rules: 42 | // 43 | // 1. If the value is a TTLV, it is copied byte for byte 44 | // 45 | // 2. If the value implements Marshaler, call that 46 | // 47 | // 3. If the struct field has an "omitempty" flag, and the value is 48 | // zero, skip the field: 49 | // 50 | // type Foo struct { 51 | // Comment string `ttlv:,omitempty` 52 | // } 53 | // 54 | // 4. If the value is a slice (except []byte) or array, marshal all 55 | // values concatenated 56 | // 57 | // 5. If a tag has not been inferred at this point, return *MarshalerError with 58 | // cause ErrNoTag 59 | // 60 | // 6. If the Tag is registered as an enum, or has the "enum" struct tag flag, attempt 61 | // to marshal as an Enumeration. int, int8, int16, int32, and their uint counterparts 62 | // can be marshaled as an Enumeration. A string can be marshaled to an Enumeration 63 | // if the string contains a number, a 4 byte (8 char) hex string with the prefix "0x", 64 | // or the normalized name of an enum value registered to this tag. Examples: 65 | // 66 | // type Foo struct { 67 | // CancellationResult string // will encode as an Enumeration because 68 | // // the tag CancellationResult is registered 69 | // // as an enum. 70 | // C int `ttlv:"Comment,enum" // The tag Comment is not registered as an enum 71 | // // but the enum flag will force this to encode 72 | // // as an enumeration. 73 | // } 74 | // 75 | // If the string can't be interpreted as an enum value, it will be encoded as a TextString. If 76 | // the "enum" struct flag is set, the value *must* successfully encode to an Enumeration using 77 | // above rules, or an error is returned. 78 | // 79 | // 7. If the Tag is registered as a bitmask, or has the "bitmask" struct tag flag, attempt 80 | // to marshal to an Integer, following the same rules as for Enumerations. The ParseInt() 81 | // function is used to parse string values. 82 | // 83 | // 9. time.Time marshals to DateTime. If the field has the "datetimeextended" struct flag, 84 | // marshal as DateTimeExtended. Example: 85 | // 86 | // type Foo struct { 87 | // ActivationDate time.Time `ttlv:",datetimeextended"` 88 | // } 89 | // 90 | // 10. big.Int marshals to BigInteger 91 | // 92 | // 11. time.Duration marshals to Interval 93 | // 94 | // 12. string marshals to TextString 95 | // 96 | // 13. []byte marshals to ByteString 97 | // 98 | // 14. all int and uint variants except int64 and uint64 marshal to Integer. If the golang 99 | // value overflows the KMIP value, *MarshalerError with cause ErrIntOverflow is returned 100 | // 101 | // 15. int64 and uint64 marshal to LongInteger 102 | // 103 | // 16. bool marshals to Boolean 104 | // 105 | // 17. structs marshal to Structure. Each field of the struct will be marshaled into the 106 | // values of the Structure according to the above rules. 107 | // 108 | // Any other golang type will return *MarshalerError with cause ErrUnsupportedTypeError. 109 | func Marshal(v interface{}) (TTLV, error) { 110 | buf := bytes.NewBuffer(nil) 111 | 112 | err := NewEncoder(buf).Encode(v) 113 | if err != nil { 114 | return nil, err 115 | } 116 | 117 | return buf.Bytes(), nil 118 | } 119 | 120 | // Marshaler knows how to encode itself to TTLV. 121 | // The implementation should use the primitive methods of the encoder, 122 | // such as EncodeInteger(), etc. 123 | // 124 | // The tag inferred by the Encoder from the field or type information is 125 | // passed as an argument, but the implementation can choose to ignore it. 126 | type Marshaler interface { 127 | MarshalTTLV(e *Encoder, tag Tag) error 128 | } 129 | 130 | func NewEncoder(w io.Writer) *Encoder { 131 | return &Encoder{w: w} 132 | } 133 | 134 | // Encode a single value and flush to the writer. The tag will be inferred from 135 | // the value. If no tag can be inferred, an error is returned. 136 | // See Marshal for encoding rules. 137 | func (e *Encoder) Encode(v interface{}) error { 138 | return e.EncodeValue(TagNone, v) 139 | } 140 | 141 | // EncodeValue encodes a single value with the given tag and flushes it 142 | // to the writer. 143 | // See Marshal for encoding rules. 144 | func (e *Encoder) EncodeValue(tag Tag, v interface{}) error { 145 | err := e.encode(tag, reflect.ValueOf(v), nil) 146 | if err != nil { 147 | return err 148 | } 149 | 150 | return e.Flush() 151 | } 152 | 153 | // EncodeStructure encodes a Structure with the given tag to the writer. 154 | // The function argument should encode the enclosed values inside the Structure. 155 | // Call Flush() to write the data to the writer. 156 | func (e *Encoder) EncodeStructure(tag Tag, f func(e *Encoder) error) error { 157 | e.encodeDepth++ 158 | i := e.encBuf.begin(tag, TypeStructure) 159 | err := f(e) 160 | e.encBuf.end(i) 161 | e.encodeDepth-- 162 | 163 | return err 164 | } 165 | 166 | // EncodeEnumeration, along with the other Encode methods, encodes a 167 | // single KMIP value with the given tag to an internal buffer. These methods 168 | // do not flush the data to the writer: call Flush() to flush the buffer. 169 | func (e *Encoder) EncodeEnumeration(tag Tag, v uint32) { 170 | e.encBuf.encodeEnum(tag, v) 171 | } 172 | 173 | func (e *Encoder) EncodeInteger(tag Tag, v int32) { 174 | e.encBuf.encodeInt(tag, v) 175 | } 176 | 177 | func (e *Encoder) EncodeLongInteger(tag Tag, v int64) { 178 | e.encBuf.encodeLongInt(tag, v) 179 | } 180 | 181 | func (e *Encoder) EncodeInterval(tag Tag, v time.Duration) { 182 | e.encBuf.encodeInterval(tag, v) 183 | } 184 | 185 | func (e *Encoder) EncodeDateTime(tag Tag, v time.Time) { 186 | e.encBuf.encodeDateTime(tag, v) 187 | } 188 | 189 | func (e *Encoder) EncodeDateTimeExtended(tag Tag, v time.Time) { 190 | e.encBuf.encodeDateTimeExtended(tag, v) 191 | } 192 | 193 | func (e *Encoder) EncodeBigInteger(tag Tag, v *big.Int) { 194 | e.encBuf.encodeBigInt(tag, v) 195 | } 196 | 197 | func (e *Encoder) EncodeBoolean(tag Tag, v bool) { 198 | e.encBuf.encodeBool(tag, v) 199 | } 200 | 201 | func (e *Encoder) EncodeTextString(tag Tag, v string) { 202 | e.encBuf.encodeTextString(tag, v) 203 | } 204 | 205 | func (e *Encoder) EncodeByteString(tag Tag, v []byte) { 206 | e.encBuf.encodeByteString(tag, v) 207 | } 208 | 209 | // Flush flushes the internal encoding buffer to the writer. 210 | func (e *Encoder) Flush() error { 211 | if e.encodeDepth > 0 { 212 | return nil 213 | } 214 | 215 | _, err := e.encBuf.WriteTo(e.w) 216 | e.encBuf.Reset() 217 | 218 | return err 219 | } 220 | 221 | type MarshalerError struct { 222 | // Type is the golang type of the value being marshaled 223 | Type reflect.Type 224 | // Struct is the name of the enclosing struct if the marshaled value is a field. 225 | Struct string 226 | // Field is the name of the field being marshaled 227 | Field string 228 | Tag Tag 229 | } 230 | 231 | func (e *MarshalerError) Error() string { 232 | msg := "kmip: error marshaling value" 233 | if e.Type != nil { 234 | msg += " of type " + e.Type.String() 235 | } 236 | 237 | if e.Struct != "" { 238 | msg += " in struct field " + e.Struct + "." + e.Field 239 | } 240 | 241 | return msg 242 | } 243 | 244 | func (e *Encoder) marshalingError(tag Tag, t reflect.Type, cause error) merry.Error { 245 | err := &MarshalerError{ 246 | Type: t, 247 | Struct: e.currStruct, 248 | Field: e.currField, 249 | Tag: tag, 250 | } 251 | 252 | return merry.WrapSkipping(err, 1).WithCause(cause) 253 | } 254 | 255 | var ( 256 | byteType = reflect.TypeOf(byte(0)) 257 | marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem() 258 | unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem() 259 | timeType = reflect.TypeOf((*time.Time)(nil)).Elem() 260 | bigIntPtrType = reflect.TypeOf((*big.Int)(nil)) 261 | bigIntType = bigIntPtrType.Elem() 262 | durationType = reflect.TypeOf(time.Nanosecond) 263 | ttlvType = reflect.TypeOf((*TTLV)(nil)).Elem() 264 | tagType = reflect.TypeOf(Tag(0)) 265 | ) 266 | 267 | var invalidValue = reflect.Value{} 268 | 269 | // indirect dives into interfaces values, and one level deep into pointers 270 | // returns an invalid value if the resolved value is nil or invalid. 271 | func indirect(v reflect.Value) reflect.Value { 272 | if !v.IsValid() { 273 | return v 274 | } 275 | 276 | if v.Kind() == reflect.Interface { 277 | v = v.Elem() 278 | } 279 | 280 | if !v.IsValid() { 281 | return v 282 | } 283 | 284 | if v.Kind() == reflect.Ptr { 285 | v = v.Elem() 286 | } 287 | 288 | switch v.Kind() { 289 | case reflect.Func, reflect.Slice, reflect.Map, reflect.Chan, reflect.Ptr, reflect.Interface: 290 | if v.IsNil() { 291 | return invalidValue 292 | } 293 | default: 294 | } 295 | 296 | return v 297 | } 298 | 299 | var zeroBigInt = big.Int{} 300 | 301 | func isEmptyValue(v reflect.Value) bool { 302 | switch v.Kind() { 303 | case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 304 | return v.Len() == 0 305 | case reflect.Bool: 306 | return !v.Bool() 307 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 308 | return v.Int() == 0 309 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 310 | return v.Uint() == 0 311 | case reflect.Float32, reflect.Float64: 312 | return v.Float() == 0 313 | case reflect.Interface, reflect.Ptr: 314 | return v.IsNil() 315 | default: 316 | } 317 | 318 | switch v.Type() { 319 | case timeType: 320 | return v.Interface().(time.Time).IsZero() //nolint:forcetypeassert 321 | case bigIntType: 322 | i := v.Interface().(big.Int) //nolint:forcetypeassert 323 | return zeroBigInt.Cmp(&i) == 0 324 | } 325 | 326 | return false 327 | } 328 | 329 | func (e *Encoder) encode(tag Tag, v reflect.Value, fi *fieldInfo) error { 330 | // if pointer or interface 331 | v = indirect(v) 332 | if !v.IsValid() { 333 | return nil 334 | } 335 | 336 | typ := v.Type() 337 | 338 | if typ == ttlvType { 339 | // fast path: if the value is TTLV, we write it directly to the output buffer 340 | _, err := e.encBuf.Write(v.Bytes()) 341 | return err 342 | } 343 | 344 | typeInfo, err := getTypeInfo(typ) 345 | if err != nil { 346 | return err 347 | } 348 | 349 | if tag == TagNone { 350 | tag = tagForMarshal(v, typeInfo, fi) 351 | } 352 | 353 | var flags fieldFlags 354 | if fi != nil { 355 | flags = fi.flags 356 | } 357 | 358 | // check for Marshaler 359 | switch { 360 | case typ.Implements(marshalerType): 361 | if flags.omitEmpty() && isEmptyValue(v) { 362 | return nil 363 | } 364 | 365 | return v.Interface().(Marshaler).MarshalTTLV(e, tag) //nolint:forcetypeassert 366 | case v.CanAddr(): 367 | pv := v.Addr() 368 | 369 | pvtyp := pv.Type() 370 | if pvtyp.Implements(marshalerType) { 371 | if flags.omitEmpty() && isEmptyValue(v) { 372 | return nil 373 | } 374 | 375 | return pv.Interface().(Marshaler).MarshalTTLV(e, tag) //nolint:forcetypeassert 376 | } 377 | } 378 | 379 | // If the type doesn't implement Marshaler, then validate the value is a supported kind 380 | switch v.Kind() { 381 | case reflect.Chan, reflect.Map, reflect.Func, reflect.Ptr, reflect.UnsafePointer, reflect.Uintptr, reflect.Float32, 382 | reflect.Float64, 383 | reflect.Complex64, 384 | reflect.Complex128, 385 | reflect.Interface: 386 | return e.marshalingError(tag, v.Type(), ErrUnsupportedTypeError) 387 | default: 388 | } 389 | 390 | // skip if value is empty and tags include omitempty 391 | if flags.omitEmpty() && isEmptyValue(v) { 392 | return nil 393 | } 394 | 395 | // recurse to handle slices of values 396 | switch v.Kind() { 397 | case reflect.Slice: 398 | if typ.Elem() == byteType { 399 | // special case, encode as a ByteString, handled below 400 | break 401 | } 402 | 403 | fallthrough 404 | case reflect.Array: 405 | for i := 0; i < v.Len(); i++ { 406 | // turn off the omit empty flag. applies at the field level, 407 | // not to each member of the slice 408 | // TODO: is this true? 409 | var fi2 *fieldInfo 410 | if fi != nil { 411 | fi2 = &fieldInfo{} 412 | // make a copy. 413 | *fi2 = *fi 414 | fi2.flags &^= fOmitEmpty 415 | } 416 | 417 | err := e.encode(tag, v.Index(i), fi2) 418 | if err != nil { 419 | return err 420 | } 421 | } 422 | 423 | return nil 424 | default: 425 | } 426 | 427 | if tag == TagNone { 428 | return e.marshalingError(tag, v.Type(), ErrNoTag) 429 | } 430 | 431 | // handle enums and bitmasks 432 | // 433 | // If the field has the "enum" or "bitmask" flag, or the tag is registered as an enum or bitmask, 434 | // attempt to interpret the go value as such. 435 | // 436 | // If the field is explicitly flag, return an error if the value can't be interpreted. Otherwise 437 | // ignore errors and let processing fallthrough to the type-based encoding. 438 | enumMap := DefaultRegistry.EnumForTag(tag) 439 | if flags.enum() || flags.bitmask() || enumMap != nil { 440 | switch typ.Kind() { 441 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: 442 | i := v.Int() 443 | 444 | if flags.bitmask() || (enumMap != nil && enumMap.Bitmask()) { 445 | e.encBuf.encodeInt(tag, int32(i)) //nolint:gosec // already prevented by the check above 446 | } else { 447 | e.encBuf.encodeEnum(tag, uint32(i)) //nolint:gosec // already prevented by the check above 448 | } 449 | 450 | return nil 451 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: 452 | i := v.Uint() 453 | 454 | if flags.bitmask() || (enumMap != nil && enumMap.Bitmask()) { 455 | e.encBuf.encodeInt(tag, int32(i)) //nolint:gosec // already prevented by the check above 456 | } else { 457 | e.encBuf.encodeEnum(tag, uint32(i)) //nolint:gosec // already prevented by the check above 458 | } 459 | 460 | return nil 461 | case reflect.String: 462 | s := v.String() 463 | 464 | if flags.bitmask() || (enumMap != nil && enumMap.Bitmask()) { 465 | i, err := ParseInt(s, enumMap) 466 | if err == nil { 467 | e.encBuf.encodeInt(tag, i) 468 | return nil 469 | } 470 | // only throw an error if the field is explicitly marked as a bitmask 471 | // otherwise just ignore it, and let it encode as a string later on. 472 | if flags.bitmask() { 473 | // if we couldn't parse the string as an enum value 474 | return e.marshalingError(tag, typ, err) 475 | } 476 | } else { 477 | i, err := ParseEnum(s, enumMap) 478 | if err == nil { 479 | e.encBuf.encodeEnum(tag, i) 480 | return nil 481 | } 482 | // only throw an error if the field is explicitly marked as an enum 483 | // otherwise just ignore it, and let it encode as a string later on. 484 | if flags.enum() { 485 | // if we couldn't parse the string as an enum value 486 | return e.marshalingError(tag, typ, err) 487 | } 488 | } 489 | default: 490 | if flags.enum() || flags.bitmask() { 491 | return e.marshalingError(tag, typ, ErrUnsupportedEnumTypeError).Append(typ.String()) 492 | } 493 | } 494 | } 495 | 496 | // handle special types 497 | switch typ { 498 | case timeType: 499 | if flags.dateTimeExt() { 500 | e.encBuf.encodeDateTimeExtended(tag, v.Interface().(time.Time)) //nolint:forcetypeassert 501 | } else { 502 | e.encBuf.encodeDateTime(tag, v.Interface().(time.Time)) //nolint:forcetypeassert 503 | } 504 | 505 | return nil 506 | case bigIntType: 507 | bi := v.Interface().(big.Int) //nolint:forcetypeassert 508 | e.encBuf.encodeBigInt(tag, &bi) 509 | 510 | return nil 511 | case bigIntPtrType: 512 | e.encBuf.encodeBigInt(tag, v.Interface().(*big.Int)) //nolint:forcetypeassert 513 | return nil 514 | case durationType: 515 | e.encBuf.encodeInterval(tag, time.Duration(v.Int())) 516 | return nil 517 | } 518 | 519 | // handle the rest of the kinds 520 | switch typ.Kind() { 521 | case reflect.Struct: 522 | // push current struct onto stack 523 | currStruct := e.currStruct 524 | e.currStruct = typ.Name() 525 | 526 | err = e.EncodeStructure(tag, func(e *Encoder) error { 527 | for _, field := range typeInfo.valueFields { 528 | fv := v.FieldByIndex(field.index) 529 | 530 | // note: we're staying in reflection world here instead of 531 | // converting back to an interface{} value and going through 532 | // the non-reflection path again. Calling Interface() 533 | // on the reflect value would make a potentially addressable value 534 | // into an unaddressable value, reducing the chances we can coerce 535 | // the value into a Marshalable. 536 | // 537 | // tl;dr 538 | // Consider a type which implements Marshaler with 539 | // a pointer receiver, and a struct with a non-pointer field of that type: 540 | // 541 | // type Wheel struct{} 542 | // func (*Wheel) MarshalTTLV(...) 543 | // 544 | // type Car struct{ 545 | // Wheel Wheel 546 | // } 547 | // 548 | // When traversing the Car struct, should the encoder invoke Wheel's 549 | // Marshaler method, or not? Technically, the type `Wheel` 550 | // doesn't implement the Marshaler interface. Only the type `*Wheel` 551 | // implements it. However, the other encoders in the SDK, like JSON 552 | // and XML, will try, if possible, to get a pointer to field values like this, in 553 | // order to invoke the Marshaler interface anyway. 554 | // 555 | // Encoders can only get a pointer to field values if the field 556 | // value is `addressable`. Addressability is explained in the docs for reflect.Value#CanAddr(). 557 | // Using reflection to turn a reflect.Value() back into an interface{} 558 | // can make a potentially addressable value (like the field of an addressable struct) 559 | // into an unaddressable value (reflect.Value#Interface{} always returns an unaddressable 560 | // copy). 561 | 562 | // push the currField 563 | currField := e.currField 564 | e.currField = field.name 565 | err := e.encode(TagNone, fv, &field) 566 | // pop the currField 567 | e.currField = currField 568 | if err != nil { 569 | return err 570 | } 571 | } 572 | 573 | return nil 574 | }) 575 | // pop current struct 576 | e.currStruct = currStruct 577 | 578 | return err 579 | case reflect.String: 580 | e.encBuf.encodeTextString(tag, v.String()) 581 | case reflect.Slice: 582 | // special case, encode as a ByteString 583 | // all slices which aren't []byte should have been handled above 584 | // the call to v.Bytes() will panic if this assumption is wrong 585 | e.encBuf.encodeByteString(tag, v.Bytes()) 586 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: 587 | i := v.Int() 588 | if i > math.MaxInt32 { 589 | return e.marshalingError(tag, typ, ErrIntOverflow) 590 | } 591 | 592 | e.encBuf.encodeInt(tag, int32(i)) //nolint:gosec // already prevented by the check above 593 | 594 | return nil 595 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: 596 | u := v.Uint() 597 | if u > math.MaxInt32 { 598 | return e.marshalingError(tag, typ, ErrIntOverflow) 599 | } 600 | 601 | e.encBuf.encodeInt(tag, int32(u)) 602 | 603 | return nil 604 | case reflect.Uint64: 605 | u := v.Uint() 606 | e.encBuf.encodeLongInt(tag, int64(u)) //nolint:gosec // even though its cast to int, the value will be encoded the same as a uint 607 | 608 | return nil 609 | case reflect.Int64: 610 | e.encBuf.encodeLongInt(tag, v.Int()) 611 | return nil 612 | case reflect.Bool: 613 | e.encBuf.encodeBool(tag, v.Bool()) 614 | default: 615 | // all kinds should have been handled by now 616 | panic(errors.New("should never get here")) 617 | } 618 | 619 | return nil 620 | } 621 | 622 | func tagForMarshal(v reflect.Value, ti typeInfo, fi *fieldInfo) Tag { 623 | // the tag on the TTLVTag field 624 | if ti.tagField != nil && ti.tagField.explicitTag != TagNone { 625 | return ti.tagField.explicitTag 626 | } 627 | 628 | // the value of the TTLVTag field of type Tag 629 | if v.IsValid() && ti.tagField != nil && ti.tagField.ti.typ == tagType { 630 | tag := v.FieldByIndex(ti.tagField.index).Interface().(Tag) //nolint:forcetypeassert 631 | if tag != TagNone { 632 | return tag 633 | } 634 | } 635 | 636 | // if value is in a struct field, infer the tag from the field 637 | // else infer from the value's type name 638 | if fi != nil { 639 | return fi.tag 640 | } 641 | 642 | return ti.inferredTag 643 | } 644 | 645 | // encBuf encodes basic KMIP types into TTLV. 646 | type encBuf struct { 647 | bytes.Buffer 648 | } 649 | 650 | func (h *encBuf) begin(tag Tag, typ Type) int { 651 | _ = h.WriteByte(byte(tag >> 16)) 652 | _ = h.WriteByte(byte(tag >> 8)) 653 | _ = h.WriteByte(byte(tag)) 654 | _ = h.WriteByte(byte(typ)) 655 | _, _ = h.Write(zeros[:4]) 656 | 657 | return h.Len() 658 | } 659 | 660 | func (h *encBuf) end(i int) { 661 | n := h.Len() - i 662 | if m := n % 8; m > 0 { 663 | _, _ = h.Write(zeros[:8-m]) 664 | } 665 | 666 | binary.BigEndian.PutUint32(h.Bytes()[i-4:], uint32(n)) //nolint:gosec 667 | } 668 | 669 | func (h *encBuf) writeLongIntVal(tag Tag, typ Type, i int64) { 670 | s := h.begin(tag, typ) 671 | ll := h.Len() 672 | _, _ = h.Write(zeros[:8]) 673 | binary.BigEndian.PutUint64(h.Bytes()[ll:], uint64(i)) //nolint:gosec 674 | h.end(s) 675 | } 676 | 677 | func (h *encBuf) writeIntVal(tag Tag, typ Type, val uint32) { 678 | s := h.begin(tag, typ) 679 | ll := h.Len() 680 | _, _ = h.Write(zeros[:4]) 681 | binary.BigEndian.PutUint32(h.Bytes()[ll:], val) 682 | h.end(s) 683 | } 684 | 685 | var ( 686 | ones = [8]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} 687 | zeros = [8]byte{} 688 | ) 689 | 690 | func (h *encBuf) encodeBigInt(tag Tag, i *big.Int) { 691 | if i == nil { 692 | return 693 | } 694 | 695 | ii := h.begin(tag, TypeBigInteger) 696 | 697 | switch i.Sign() { 698 | case 0: 699 | _, _ = h.Write(zeros[:8]) 700 | case 1: 701 | b := i.Bytes() 702 | l := len(b) 703 | // if n is positive, but the first bit is a 1, it will look like 704 | // a negative in 2's complement, so prepend zeroes in front 705 | if b[0]&0x80 > 0 { 706 | _ = h.WriteByte(byte(0)) 707 | l++ 708 | } 709 | // pad front with zeros to multiple of 8 710 | if m := l % 8; m > 0 { 711 | _, _ = h.Write(zeros[:8-m]) 712 | } 713 | 714 | _, _ = h.Write(b) 715 | case -1: 716 | length := uint(i.BitLen()/8+1) * 8 //nolint:gosec 717 | j := new(big.Int).Lsh(one, length) 718 | b := j.Add(i, j).Bytes() 719 | // When the most significant bit is on a byte 720 | // boundary, we can get some extra significant 721 | // bits, so strip them off when that happens. 722 | if len(b) >= 2 && b[0] == 0xff && b[1]&0x80 != 0 { 723 | b = b[1:] 724 | } 725 | 726 | l := len(b) 727 | // pad front with ones to multiple of 8 728 | if m := l % 8; m > 0 { 729 | _, _ = h.Write(ones[:8-m]) 730 | } 731 | 732 | _, _ = h.Write(b) 733 | } 734 | 735 | h.end(ii) 736 | } 737 | 738 | func (h *encBuf) encodeInt(tag Tag, i int32) { 739 | h.writeIntVal(tag, TypeInteger, uint32(i)) //nolint:gosec 740 | } 741 | 742 | func (h *encBuf) encodeBool(tag Tag, b bool) { 743 | if b { 744 | h.writeLongIntVal(tag, TypeBoolean, 1) 745 | } else { 746 | h.writeLongIntVal(tag, TypeBoolean, 0) 747 | } 748 | } 749 | 750 | func (h *encBuf) encodeLongInt(tag Tag, i int64) { 751 | h.writeLongIntVal(tag, TypeLongInteger, i) 752 | } 753 | 754 | func (h *encBuf) encodeDateTime(tag Tag, t time.Time) { 755 | h.writeLongIntVal(tag, TypeDateTime, t.Unix()) 756 | } 757 | 758 | func (h *encBuf) encodeDateTimeExtended(tag Tag, t time.Time) { 759 | // take unix seconds, times a million, to get microseconds, then 760 | // add nanoseconds remainder/1000 761 | // 762 | // this gives us a larger ranger of possible values than just t.UnixNano() / 1000. 763 | // see UnixNano() docs for its limits. 764 | // 765 | // this is limited to max(int64) *microseconds* from epoch, rather than 766 | // max(int64) nanoseconds like UnixNano(). 767 | m := (t.Unix() * 1000000) + int64(t.Nanosecond()/1000) 768 | h.writeLongIntVal(tag, TypeDateTimeExtended, m) 769 | } 770 | 771 | func (h *encBuf) encodeInterval(tag Tag, d time.Duration) { 772 | h.writeIntVal(tag, TypeInterval, uint32(d/time.Second)) //nolint:gosec 773 | } 774 | 775 | func (h *encBuf) encodeEnum(tag Tag, i uint32) { 776 | h.writeIntVal(tag, TypeEnumeration, i) 777 | } 778 | 779 | func (h *encBuf) encodeTextString(tag Tag, s string) { 780 | i := h.begin(tag, TypeTextString) 781 | _, _ = h.WriteString(s) 782 | h.end(i) 783 | } 784 | 785 | func (h *encBuf) encodeByteString(tag Tag, b []byte) { 786 | if b == nil { 787 | return 788 | } 789 | 790 | i := h.begin(tag, TypeByteString) 791 | _, _ = h.Write(b) 792 | h.end(i) 793 | } 794 | 795 | func getTypeInfo(typ reflect.Type) (ti typeInfo, err error) { 796 | ti.inferredTag, _ = DefaultRegistry.ParseTag(typ.Name()) 797 | ti.typ = typ 798 | err = ti.getFieldsInfo() 799 | 800 | return ti, err 801 | } 802 | 803 | var errSkip = errors.New("skip") 804 | 805 | func getFieldInfo(typ reflect.Type, sf reflect.StructField) (fieldInfo, error) { 806 | var fi fieldInfo 807 | 808 | // skip anonymous and unexported fields 809 | if sf.Anonymous || /*unexported:*/ sf.PkgPath != "" { 810 | return fi, errSkip 811 | } 812 | 813 | fi.name = sf.Name 814 | fi.structType = typ 815 | fi.index = sf.Index 816 | 817 | var anyField bool 818 | 819 | // handle field tags 820 | parts := strings.Split(sf.Tag.Get(structFieldTag), ",") 821 | for i, value := range parts { 822 | if i == 0 { 823 | switch value { 824 | case "-": 825 | // skip 826 | return fi, errSkip 827 | case "": 828 | default: 829 | var err error 830 | 831 | fi.explicitTag, err = DefaultRegistry.ParseTag(value) 832 | if err != nil { 833 | return fi, err 834 | } 835 | } 836 | } else { 837 | switch strings.ToLower(value) { 838 | case "enum": 839 | fi.flags |= fEnum 840 | case "omitempty": 841 | fi.flags |= fOmitEmpty 842 | case "datetimeextended": 843 | fi.flags |= fDateTimeExtended 844 | case "bitmask": 845 | fi.flags |= fBitBask 846 | case "any": 847 | anyField = true 848 | fi.flags |= fAny 849 | } 850 | } 851 | } 852 | 853 | if anyField && fi.explicitTag != TagNone { 854 | return fi, merry.Here(ErrTagConflict).Appendf(`field %s.%s may not specify a TTLV tag and the "any" flag`, fi.structType.Name(), fi.name) 855 | } 856 | 857 | // extract type info for the field. The KMIP tag 858 | // for this field is derived from either the field name, 859 | // the field tags, or the field type. 860 | var err error 861 | 862 | fi.ti, err = getTypeInfo(sf.Type) 863 | if err != nil { 864 | return fi, err 865 | } 866 | 867 | if fi.ti.tagField != nil && fi.ti.tagField.explicitTag != TagNone { 868 | fi.tag = fi.ti.tagField.explicitTag 869 | if fi.explicitTag != TagNone && fi.explicitTag != fi.tag { 870 | // if there was a tag on the struct field containing this value, it must 871 | // agree with the value's intrinsic tag 872 | return fi, merry.Here(ErrTagConflict).Appendf(`TTLV tag "%s" in tag of %s.%s conflicts with TTLV tag "%s" in %s.%s`, fi.explicitTag, fi.structType.Name(), fi.name, fi.ti.tagField.explicitTag, fi.ti.typ.Name(), fi.ti.tagField.name) 873 | } 874 | } 875 | 876 | // pre-calculate the tag for this field. This intentional duplicates 877 | // some of tagForMarshaling(). The value is primarily used in unmarshaling 878 | // where the dynamic value of the field is not needed. 879 | if fi.tag == TagNone { 880 | fi.tag = fi.explicitTag 881 | } 882 | 883 | if fi.tag == TagNone { 884 | fi.tag, _ = DefaultRegistry.ParseTag(fi.name) 885 | } 886 | 887 | return fi, nil 888 | } 889 | 890 | func (ti *typeInfo) getFieldsInfo() error { 891 | if ti.typ.Kind() != reflect.Struct { 892 | return nil 893 | } 894 | 895 | for i := 0; i < ti.typ.NumField(); i++ { 896 | fi, err := getFieldInfo(ti.typ, ti.typ.Field(i)) 897 | 898 | switch { 899 | case err == errSkip: //nolint:errorlint 900 | // skip 901 | case err != nil: 902 | return err 903 | case fi.name == "TTLVTag": 904 | ti.tagField = &fi 905 | default: 906 | ti.valueFields = append(ti.valueFields, fi) 907 | } 908 | } 909 | 910 | // verify that multiple fields don't have the same tag 911 | names := map[Tag]string{} 912 | 913 | for _, f := range ti.valueFields { 914 | if f.flags.any() { 915 | // ignore any fields 916 | continue 917 | } 918 | 919 | tag := f.tag 920 | if tag != TagNone { 921 | if fname, ok := names[tag]; ok { 922 | return merry.Here(ErrTagConflict).Appendf("field resolves to the same tag (%s) as other field (%s)", tag, fname) 923 | } 924 | 925 | names[tag] = f.name 926 | } 927 | } 928 | 929 | return nil 930 | } 931 | 932 | type typeInfo struct { 933 | typ reflect.Type 934 | inferredTag Tag 935 | tagField *fieldInfo 936 | valueFields []fieldInfo 937 | } 938 | 939 | const ( 940 | fOmitEmpty fieldFlags = 1 << iota 941 | fEnum 942 | fDateTimeExtended 943 | fAny 944 | fBitBask 945 | ) 946 | 947 | type fieldFlags int 948 | 949 | func (f fieldFlags) omitEmpty() bool { 950 | return f&fOmitEmpty != 0 951 | } 952 | 953 | func (f fieldFlags) any() bool { 954 | return f&fAny != 0 955 | } 956 | 957 | func (f fieldFlags) dateTimeExt() bool { 958 | return f&fDateTimeExtended != 0 959 | } 960 | 961 | func (f fieldFlags) enum() bool { 962 | return f&fEnum != 0 963 | } 964 | 965 | func (f fieldFlags) bitmask() bool { 966 | return f&fBitBask != 0 967 | } 968 | 969 | type fieldInfo struct { 970 | structType reflect.Type 971 | explicitTag, tag Tag 972 | name string 973 | index []int 974 | flags fieldFlags 975 | ti typeInfo 976 | } 977 | -------------------------------------------------------------------------------- /ttlv/errors.go: -------------------------------------------------------------------------------- 1 | package ttlv 2 | 3 | import "github.com/ansel1/merry" 4 | 5 | // Details prints details from the error, including a stacktrace when available. 6 | func Details(err error) string { 7 | return merry.Details(err) 8 | } 9 | -------------------------------------------------------------------------------- /ttlv/examples_test.go: -------------------------------------------------------------------------------- 1 | package ttlv 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "encoding/xml" 7 | "fmt" 8 | "os" 9 | ) 10 | 11 | func Example_json() { 12 | input := `{"tag":"KeyFormatType","type":"Enumeration","value":"X_509"}` 13 | var output TTLV 14 | 15 | _ = json.Unmarshal([]byte(input), &output) 16 | fmt.Println(output) 17 | 18 | b, _ := json.Marshal(output) 19 | fmt.Println(string(b)) 20 | 21 | // Output: 22 | // KeyFormatType (Enumeration/4): X_509 23 | // {"tag":"KeyFormatType","type":"Enumeration","value":"X_509"} 24 | } 25 | 26 | func Example_xml() { 27 | input := `` 28 | var output TTLV 29 | 30 | _ = xml.Unmarshal([]byte(input), &output) 31 | fmt.Println(output) 32 | 33 | b, _ := xml.Marshal(output) 34 | fmt.Println(string(b)) 35 | 36 | // Output: 37 | // Operation (Enumeration/4): Activate 38 | // 39 | } 40 | 41 | func ExamplePrintPrettyHex() { 42 | b, _ := hex.DecodeString("420069010000002042006a0200000004000000010000000042006b02000000040000000000000000") 43 | _ = PrintPrettyHex(os.Stdout, "", " ", b) 44 | 45 | // Output: 46 | // 420069 | 01 | 00000020 47 | // 42006a | 02 | 00000004 | 0000000100000000 48 | // 42006b | 02 | 00000004 | 0000000000000000 49 | } 50 | 51 | func ExamplePrint() { 52 | b, _ := hex.DecodeString("420069010000002042006a0200000004000000010000000042006b02000000040000000000000000") 53 | _ = Print(os.Stdout, "", " ", b) 54 | 55 | // Output: 56 | // ProtocolVersion (Structure/32): 57 | // ProtocolVersionMajor (Integer/4): 1 58 | // ProtocolVersionMinor (Integer/4): 0 59 | } 60 | -------------------------------------------------------------------------------- /ttlv/formatting.go: -------------------------------------------------------------------------------- 1 | package ttlv 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/ansel1/merry" 9 | "github.com/gemalto/kmip-go/internal/kmiputil" 10 | ) 11 | 12 | // FormatType formats a byte as a KMIP Type string, 13 | // as described in the KMIP Profiles spec. If the value is registered, 14 | // the normalized name of the value will be returned. 15 | // 16 | // Otherwise, a 1 byte hex string is returned, but this is not 17 | // technically a valid encoding for types in the JSON and XML encoding 18 | // specs. Hex values Should only be used for debugging. Examples: 19 | // 20 | // - Integer 21 | // - 0x42 22 | func FormatType(b byte, enumMap EnumMap) string { 23 | if enumMap != nil { 24 | if s, ok := enumMap.Name(uint32(b)); ok { 25 | return s 26 | } 27 | } 28 | 29 | return fmt.Sprintf("%#02x", b) 30 | } 31 | 32 | // FormatTag formats an uint32 as a KMIP Tag string, 33 | // as described in the KMIP Profiles spec. If the value is registered, 34 | // the normalized name of the value will be returned. Otherwise, a 35 | // 3 byte hex string is returned. Examples: 36 | // 37 | // - ActivationDate 38 | // - 0x420001 39 | func FormatTag(v uint32, enumMap EnumMap) string { 40 | if enumMap != nil { 41 | if s, ok := enumMap.Name(v); ok { 42 | return s 43 | } 44 | } 45 | 46 | return fmt.Sprintf("%#06x", v) 47 | } 48 | 49 | // FormatTagCanonical formats an uint32 as a canonical Tag name 50 | // from the KMIP spec. If the value is registered, 51 | // the canonical name of the value will be returned. Otherwise, a 52 | // 3 byte hex string is returned. Examples: 53 | // 54 | // - Activation Date 55 | // - 0x420001 56 | // 57 | // Canonical tag names are used in the AttributeName of Attribute structs. 58 | func FormatTagCanonical(v uint32, enumMap EnumMap) string { 59 | if enumMap != nil { 60 | if s, ok := enumMap.CanonicalName(v); ok { 61 | return s 62 | } 63 | } 64 | 65 | return fmt.Sprintf("%#06x", v) 66 | } 67 | 68 | // FormatEnum formats an uint32 as a KMIP Enumeration string, 69 | // as described in the KMIP Profiles spec. If the value is registered, 70 | // the normalized name of the value will be returned. Otherwise, a 71 | // four byte hex string is returned. Examples: 72 | // 73 | // - SymmetricKey 74 | // - 0x00000002 75 | func FormatEnum(v uint32, enumMap EnumMap) string { 76 | if enumMap != nil { 77 | if s, ok := enumMap.Name(v); ok { 78 | return s 79 | } 80 | } 81 | 82 | return fmt.Sprintf("%#08x", v) 83 | } 84 | 85 | // FormatInt formats an integer as a KMIP bitmask string, as 86 | // described in the KMIP Profiles spec for JSON under 87 | // the "Special case for Masks" section. Examples: 88 | // 89 | // - 0x0000100c 90 | // - Encrypt|Decrypt|CertificateSign 91 | // - CertificateSign|0x00000004|0x0000008 92 | // - CertificateSign|0x0000000c 93 | func FormatInt(i int32, enumMap EnumMap) string { 94 | if enumMap == nil { 95 | return fmt.Sprintf("%#08x", i) 96 | } 97 | 98 | values := enumMap.Values() 99 | if len(values) == 0 { 100 | return fmt.Sprintf("%#08x", i) 101 | } 102 | 103 | v := uint32(i) //nolint:gosec //nolint:gosec 104 | 105 | // bitmask 106 | // decompose mask into the names of set flags, concatenated by pipe 107 | // if remaining value (minus registered flags) is not zero, append 108 | // the remaining value as hex. 109 | 110 | var sb strings.Builder 111 | 112 | for _, v1 := range values { 113 | if v1&v == v1 { 114 | if name, ok := enumMap.Name(v1); ok { 115 | if sb.Len() > 0 { 116 | sb.WriteString("|") 117 | } 118 | 119 | sb.WriteString(name) 120 | 121 | v ^= v1 122 | } 123 | } 124 | 125 | if v == 0 { 126 | break 127 | } 128 | } 129 | 130 | if v != 0 { 131 | if sb.Len() > 0 { 132 | sb.WriteString("|") 133 | } 134 | 135 | _, _ = fmt.Fprintf(&sb, "%#08x", v) 136 | } 137 | 138 | return sb.String() 139 | } 140 | 141 | // ParseEnum parses a string into a uint32 according to the rules 142 | // in the KMIP Profiles regarding encoding enumeration values. 143 | // See FormatEnum for examples of the formats which can be parsed. 144 | // It will also parse numeric strings. Examples: 145 | // 146 | // ParseEnum("UnableToCancel", registry.EnumForTag(TagCancellationResult)) 147 | // ParseEnum("0x00000002") 148 | // ParseEnum("2") 149 | // 150 | // Returns ErrInvalidHexString if the string is invalid hex, or 151 | // if the hex value is less than 1 byte or more than 4 bytes (ignoring 152 | // leading zeroes). 153 | // 154 | // Returns ErrUnregisteredEnumName if string value is not a 155 | // registered enum value name. 156 | func ParseEnum(s string, enumMap EnumMap) (uint32, error) { 157 | u, err := strconv.ParseUint(s, 10, 32) 158 | if err == nil { 159 | // it was a raw number 160 | return uint32(u), nil 161 | } 162 | 163 | v, err := parseHexOrName(s, 4, enumMap) 164 | if err != nil { 165 | return 0, merry.Here(err) 166 | } 167 | 168 | return v, nil 169 | } 170 | 171 | // ParseInt parses a string into an int32 according the rules 172 | // in the KMIP Profiles regarding encoding integers, including 173 | // the special rules for bitmasks. See FormatInt for examples 174 | // of the formats which can be parsed. 175 | // 176 | // Returns ErrInvalidHexString if the string is invalid hex, or 177 | // if the hex value is less than 1 byte or more than 4 bytes (ignoring 178 | // leading zeroes). 179 | // 180 | // Returns ErrUnregisteredEnumName if string value is not a 181 | // registered enum value name. 182 | func ParseInt(s string, enumMap EnumMap) (int32, error) { 183 | i, err := strconv.ParseInt(s, 10, 32) 184 | if err == nil { 185 | // it was a raw number 186 | return int32(i), nil 187 | } 188 | 189 | if !strings.ContainsAny(s, "| ") { 190 | v, err := parseHexOrName(s, 4, enumMap) 191 | if err != nil { 192 | return 0, merry.Here(err) 193 | } 194 | 195 | return int32(v), nil //nolint:gosec 196 | } 197 | 198 | // split values, look up each, and recombine 199 | s = strings.ReplaceAll(s, "|", " ") 200 | parts := strings.Split(s, " ") 201 | var v uint32 202 | 203 | for _, part := range parts { 204 | if len(part) == 0 { 205 | continue 206 | } 207 | 208 | i, err := parseHexOrName(part, 4, enumMap) 209 | if err != nil { 210 | return 0, merry.Here(err) 211 | } 212 | 213 | v |= i 214 | } 215 | 216 | return int32(v), nil //nolint:gosec 217 | } 218 | 219 | func parseHexOrName(s string, maxLen int, enumMap EnumMap) (uint32, error) { 220 | b, err := kmiputil.ParseHexValue(s, maxLen) 221 | if err != nil { 222 | return 0, err 223 | } 224 | 225 | if b != nil { 226 | return kmiputil.DecodeUint32(b), nil 227 | } 228 | 229 | if enumMap != nil { 230 | if v, ok := enumMap.Value(s); ok { 231 | return v, nil 232 | } 233 | } 234 | 235 | return 0, merry.Append(ErrUnregisteredEnumName, s) 236 | } 237 | 238 | // ParseTag parses a string into Tag according the rules 239 | // in the KMIP Profiles regarding encoding tag values. 240 | // See FormatTag for examples of the formats which can be parsed. 241 | // 242 | // Returns ErrInvalidHexString if the string is invalid hex, or 243 | // if the hex value is less than 1 byte or more than 3 bytes (ignoring 244 | // leading zeroes). 245 | // 246 | // Returns ErrUnregisteredEnumName if string value is not a 247 | // registered enum value name. 248 | func ParseTag(s string, enumMap EnumMap) (Tag, error) { 249 | v, err := parseHexOrName(s, 3, enumMap) 250 | if err != nil { 251 | return 0, merry.Here(err) 252 | } 253 | 254 | return Tag(v), nil 255 | } 256 | 257 | // ParseType parses a string into Type according the rules 258 | // in the KMIP Profiles regarding encoding type values. 259 | // See FormatType for examples of the formats which can be parsed. 260 | // This also supports parsing a hex string type (e.g. "0x01"), though 261 | // this is not technically a valid encoding of a type in the spec. 262 | // 263 | // Returns ErrInvalidHexString if the string is invalid hex, or 264 | // if the hex value is less than 1 byte or more than 3 bytes (ignoring 265 | // leading zeroes). 266 | // 267 | // Returns ErrUnregisteredEnumName if string value is not a 268 | // registered enum value name. 269 | func ParseType(s string, enumMap EnumMap) (Type, error) { 270 | b, err := kmiputil.ParseHexValue(s, 1) 271 | if err != nil { 272 | return 0, merry.Here(err) 273 | } 274 | 275 | if b != nil { 276 | return Type(b[0]), nil 277 | } 278 | 279 | if enumMap != nil { 280 | if v, ok := enumMap.Value(s); ok { 281 | return Type(v), nil 282 | } 283 | } 284 | 285 | return 0, merry.Here(ErrUnregisteredEnumName).Append(s) 286 | } 287 | 288 | // EnumMap defines a set of named enumeration values. Canonical names should 289 | // be the name from the spec. Names should be in the normalized format 290 | // described in the KMIP spec (see NormalizeName()). 291 | // 292 | // Value enumerations are used for encoding and decoding KMIP Enumeration values, 293 | // KMIP Integer bitmask values, Types, and Tags. 294 | type EnumMap interface { 295 | // Name returns the normalized name for a value, e.g. AttributeName. 296 | // If the name is not registered, it returns "", false. 297 | Name(v uint32) (string, bool) 298 | // CanonicalName returns the canonical name for the value from the spec, 299 | // e.g. Attribute Name. 300 | // If the name is not registered, it returns "", false 301 | CanonicalName(v uint32) (string, bool) 302 | // Value returns the value registered for the name argument. If there is 303 | // no name registered for this value, it returns 0, false. 304 | // The name argument may be the canonical name (e.g. "Cryptographic Algorithm") or 305 | // the normalized name (e.g. "CryptographicAlgorithm"). 306 | Value(name string) (uint32, bool) 307 | // Values returns the complete set of registered values. The order 308 | // they are returned in will be the order they are encoded in when 309 | // encoding bitmasks as strings. 310 | Values() []uint32 311 | // Bitmask returns true if this is an enumeration of bitmask flags. 312 | Bitmask() bool 313 | } 314 | -------------------------------------------------------------------------------- /ttlv/registry.go: -------------------------------------------------------------------------------- 1 | package ttlv 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/ansel1/merry" 7 | "github.com/gemalto/kmip-go/internal/kmiputil" 8 | ) 9 | 10 | // DefaultRegistry holds the default mappings of types, tags, enums, and bitmasks 11 | // to canonical names and normalized names from the KMIP spec. It is pre-populated with the 1.4 spec's 12 | // values. It can be replaced, or additional values can be registered with it. 13 | // 14 | // It is not currently concurrent-safe, so replace or configure it early in your 15 | // program. 16 | var DefaultRegistry Registry 17 | 18 | //nolint:gochecknoinits 19 | func init() { 20 | RegisterTypes(&DefaultRegistry) 21 | } 22 | 23 | var ( 24 | ErrInvalidHexString = kmiputil.ErrInvalidHexString 25 | ErrUnregisteredEnumName = merry.New("unregistered enum name") 26 | ) 27 | 28 | // NormalizeName tranforms KMIP names from the spec into the 29 | // normalized form of the name. Typically, this means removing spaces, 30 | // and replacing some special characters. The normalized form of the name 31 | // is used in the JSON and XML encodings from the KMIP Profiles. 32 | // The spec describes the normalization process in 5.4.1.1 and 5.5.1.1 33 | func NormalizeName(s string) string { 34 | return kmiputil.NormalizeName(s) 35 | } 36 | 37 | // Enum represents an enumeration of KMIP values (as uint32), and maps them 38 | // to the canonical string names and the normalized string names of the 39 | // value as declared in the KMIP specs. 40 | // Enum is used to transpose values from strings to byte values, as required 41 | // by the JSON and XML encodings defined in the KMIP Profiles spec. 42 | // These mappings are also used to pretty print KMIP values, and to marshal 43 | // and unmarshal enum and bitmask values to golang string values. 44 | // 45 | // Enum currently uses plain maps, so it is not thread safe to register new values 46 | // concurrently. You should register all values at the start of your program before 47 | // using this package concurrently. 48 | // 49 | // Enums are used in the KMIP spec for two purposes: for defining the possible values 50 | // for values encoded as the KMIP Enumeration type, and for bitmask values. Bitmask 51 | // values are encoded as Integers, but are really enum values bitwise-OR'd together. 52 | // 53 | // Enums are registered with a Registry. The code to register enums is typically 54 | // generated by the kmipgen tool. 55 | type Enum struct { 56 | valuesToName map[uint32]string 57 | valuesToCanonicalName map[uint32]string 58 | nameToValue map[string]uint32 59 | canonicalNamesToValue map[string]uint32 60 | bitMask bool 61 | } 62 | 63 | func NewEnum() Enum { 64 | return Enum{} 65 | } 66 | 67 | func NewBitmask() Enum { 68 | return Enum{ 69 | bitMask: true, 70 | } 71 | } 72 | 73 | // RegisterValue adds a mapping of a uint32 value to a name. The name will be 74 | // processed by NormalizeName to produce the normalized enum value name as described 75 | // in the KMIP spec. 76 | func (e *Enum) RegisterValue(v uint32, name string) { 77 | nn := NormalizeName(name) 78 | 79 | if e.valuesToName == nil { 80 | e.valuesToName = map[uint32]string{} 81 | e.nameToValue = map[string]uint32{} 82 | e.valuesToCanonicalName = map[uint32]string{} 83 | e.canonicalNamesToValue = map[string]uint32{} 84 | } 85 | 86 | e.valuesToName[v] = nn 87 | e.nameToValue[nn] = v 88 | e.valuesToCanonicalName[v] = name 89 | e.canonicalNamesToValue[name] = v 90 | } 91 | 92 | func (e *Enum) Name(v uint32) (string, bool) { 93 | if e == nil { 94 | return "", false 95 | } 96 | 97 | name, ok := e.valuesToName[v] 98 | 99 | return name, ok 100 | } 101 | 102 | func (e *Enum) CanonicalName(v uint32) (string, bool) { 103 | if e == nil { 104 | return "", false 105 | } 106 | 107 | name, ok := e.valuesToCanonicalName[v] 108 | 109 | return name, ok 110 | } 111 | 112 | func (e *Enum) Value(name string) (uint32, bool) { 113 | if e == nil { 114 | return 0, false 115 | } 116 | 117 | v, ok := e.nameToValue[name] 118 | if !ok { 119 | v, ok = e.canonicalNamesToValue[name] 120 | } 121 | 122 | return v, ok 123 | } 124 | 125 | func (e *Enum) Values() []uint32 { 126 | values := make([]uint32, 0, len(e.valuesToName)) 127 | for v := range e.valuesToName { 128 | values = append(values, v) 129 | } 130 | // Always list them in order of value so output is stable. 131 | sort.Sort(uint32Slice(values)) 132 | 133 | return values 134 | } 135 | 136 | func (e *Enum) Bitmask() bool { 137 | if e == nil { 138 | return false 139 | } 140 | 141 | return e.bitMask 142 | } 143 | 144 | // Registry holds all the known tags, types, enums and bitmaps declared in 145 | // a KMIP spec. It's used throughout the package to map values their canonical 146 | // and normalized names. 147 | type Registry struct { 148 | enums map[Tag]EnumMap 149 | tags Enum 150 | types Enum 151 | } 152 | 153 | func (r *Registry) RegisterType(t Type, name string) { 154 | r.types.RegisterValue(uint32(t), name) 155 | } 156 | 157 | func (r *Registry) RegisterTag(t Tag, name string) { 158 | r.tags.RegisterValue(uint32(t), name) 159 | } 160 | 161 | func (r *Registry) RegisterEnum(t Tag, def EnumMap) { 162 | if r.enums == nil { 163 | r.enums = map[Tag]EnumMap{} 164 | } 165 | 166 | r.enums[t] = def 167 | } 168 | 169 | // EnumForTag returns the enum map registered for a tag. Returns 170 | // nil if no map is registered for this tag. 171 | func (r *Registry) EnumForTag(t Tag) EnumMap { 172 | if r.enums == nil { 173 | return nil 174 | } 175 | 176 | return r.enums[t] 177 | } 178 | 179 | func (r *Registry) IsBitmask(t Tag) bool { 180 | if e := r.EnumForTag(t); e != nil { 181 | return e.Bitmask() 182 | } 183 | 184 | return false 185 | } 186 | 187 | func (r *Registry) IsEnum(t Tag) bool { 188 | if e := r.EnumForTag(t); e != nil { 189 | return !e.Bitmask() 190 | } 191 | 192 | return false 193 | } 194 | 195 | func (r *Registry) Tags() EnumMap { 196 | return &r.tags 197 | } 198 | 199 | func (r *Registry) Types() EnumMap { 200 | return &r.types 201 | } 202 | 203 | func (r *Registry) FormatEnum(t Tag, v uint32) string { 204 | return FormatEnum(v, r.EnumForTag(t)) 205 | } 206 | 207 | func (r *Registry) FormatInt(t Tag, v int32) string { 208 | return FormatInt(v, r.EnumForTag(t)) 209 | } 210 | 211 | func (r *Registry) FormatTag(t Tag) string { 212 | return FormatTag(uint32(t), &r.tags) 213 | } 214 | 215 | func (r *Registry) FormatTagCanonical(t Tag) string { 216 | return FormatTagCanonical(uint32(t), &r.tags) 217 | } 218 | 219 | func (r *Registry) FormatType(t Type) string { 220 | return FormatType(byte(t), &r.types) 221 | } 222 | 223 | func (r *Registry) ParseEnum(t Tag, s string) (uint32, error) { 224 | return ParseEnum(s, r.EnumForTag(t)) 225 | } 226 | 227 | func (r *Registry) ParseInt(t Tag, s string) (int32, error) { 228 | return ParseInt(s, r.EnumForTag(t)) 229 | } 230 | 231 | // ParseTag parses a string into Tag according the rules 232 | // in the KMIP Profiles regarding encoding tag values. 233 | // Returns TagNone if not found. 234 | // Returns error if s is a malformed hex string, or a hex string of incorrect length 235 | func (r *Registry) ParseTag(s string) (Tag, error) { 236 | return ParseTag(s, &r.tags) 237 | } 238 | 239 | func (r *Registry) ParseType(s string) (Type, error) { 240 | return ParseType(s, &r.types) 241 | } 242 | 243 | // uint32Slice attaches the methods of Interface to []int, sorting in increasing order. 244 | type uint32Slice []uint32 245 | 246 | func (p uint32Slice) Len() int { return len(p) } 247 | func (p uint32Slice) Less(i, j int) bool { return p[i] < p[j] } 248 | func (p uint32Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 249 | -------------------------------------------------------------------------------- /ttlv/registry_test.go: -------------------------------------------------------------------------------- 1 | package ttlv_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/gemalto/kmip-go/kmip14" //nolint:revive 7 | . "github.com/gemalto/kmip-go/ttlv" //nolint:revive 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestBitMaskString(t *testing.T) { 13 | tests := []struct { 14 | in CryptographicUsageMask 15 | out string 16 | }{ 17 | { 18 | in: CryptographicUsageMaskSign, 19 | out: "Sign", 20 | }, 21 | { 22 | in: CryptographicUsageMask(0x00100000), 23 | out: "0x00100000", 24 | }, 25 | { 26 | in: CryptographicUsageMaskSign | CryptographicUsageMaskExport, 27 | out: "Sign|Export", 28 | }, 29 | { 30 | in: CryptographicUsageMaskSign | CryptographicUsageMaskExport | CryptographicUsageMask(0x00100000), 31 | out: "Sign|Export|0x00100000", 32 | }, 33 | { 34 | in: CryptographicUsageMaskSign | CryptographicUsageMaskExport | CryptographicUsageMask(0x00100000) | CryptographicUsageMask(0x00200000), 35 | out: "Sign|Export|0x00300000", 36 | }, 37 | } 38 | 39 | for _, testcase := range tests { 40 | t.Run(testcase.out, func(t *testing.T) { 41 | assert.Equal(t, testcase.out, testcase.in.String()) 42 | }) 43 | } 44 | } 45 | 46 | func TestParseInteger(t *testing.T) { 47 | tests := []struct { 48 | out CryptographicUsageMask 49 | in string 50 | }{ 51 | { 52 | out: CryptographicUsageMaskSign, 53 | in: "Sign", 54 | }, 55 | { 56 | out: CryptographicUsageMaskDecrypt, 57 | in: "0x00000008", 58 | }, 59 | { 60 | out: CryptographicUsageMaskDecrypt, 61 | in: "8", 62 | }, 63 | { 64 | out: CryptographicUsageMask(0x00100000), 65 | in: "0x00100000", 66 | }, 67 | { 68 | out: CryptographicUsageMask(0x00100000), 69 | in: "1048576", 70 | }, 71 | { 72 | out: CryptographicUsageMaskSign | CryptographicUsageMaskExport, 73 | in: "Sign|Export", 74 | }, 75 | { 76 | out: CryptographicUsageMaskSign | CryptographicUsageMaskExport, 77 | in: "Sign Export", 78 | }, 79 | { 80 | out: CryptographicUsageMaskSign | CryptographicUsageMaskExport, 81 | in: "0x00000001 0x00000040", 82 | }, 83 | { 84 | out: CryptographicUsageMaskSign | CryptographicUsageMaskExport, 85 | in: "0x00000001|0x00000040", 86 | }, 87 | { 88 | out: CryptographicUsageMaskSign | CryptographicUsageMaskExport | CryptographicUsageMask(0x00100000), 89 | in: "Sign|Export|0x00100000", 90 | }, 91 | { 92 | out: CryptographicUsageMaskSign | CryptographicUsageMaskExport | CryptographicUsageMask(0x00100000) | CryptographicUsageMask(0x00200000), 93 | in: "Sign|Export|0x00300000", 94 | }, 95 | } 96 | 97 | for _, testcase := range tests { 98 | t.Run(testcase.in, func(t *testing.T) { 99 | mask, e := DefaultRegistry.ParseInt(TagCryptographicUsageMask, testcase.in) 100 | require.NoError(t, e) 101 | assert.Equal(t, int32(testcase.out), mask) 102 | }) 103 | } 104 | } 105 | 106 | func TestNormalizeNames(t *testing.T) { 107 | tests := map[string]string{ 108 | "Structure": "Structure", 109 | "Date-Time": "DateTime", 110 | "Byte String": "ByteString", 111 | "Batch Error Continuation Option": "BatchErrorContinuationOption", 112 | "CRT Coefficient": "CRTCoefficient", 113 | "J": "J", 114 | "Private Key Template-Attribute": "PrivateKeyTemplateAttribute", 115 | "EC Public Key Type X9.62 Compressed Prime": "ECPublicKeyTypeX9_62CompressedPrime", 116 | "PKCS#8": "PKCS_8", 117 | "Encrypt then MAC/sign": "EncryptThenMACSign", 118 | "P-384": "P_384", 119 | "MD2 with RSA Encryption (PKCS#1 v1.5)": "MD2WithRSAEncryptionPKCS_1V1_5", 120 | "Num42bers in first word": "Num42bersInFirstWord", 121 | "Polynomial Sharing GF (2^16)": "PolynomialSharingGF2_16", 122 | "3DES": "DES3", 123 | } 124 | 125 | for input, output := range tests { 126 | t.Run(input, func(t *testing.T) { 127 | assert.Equal(t, output, NormalizeName(input)) 128 | }) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /ttlv/tag.go: -------------------------------------------------------------------------------- 1 | package ttlv 2 | 3 | const ( 4 | TagNone = Tag(0) 5 | tagAttributeName Tag = 0x42000a 6 | tagAttributeValue Tag = 0x42000b 7 | ) 8 | 9 | // Tag 10 | // 9.1.3.1 11 | type Tag uint32 12 | 13 | // String returns the normalized name of the tag. 14 | func (t Tag) String() string { 15 | return DefaultRegistry.FormatTag(t) 16 | } 17 | 18 | // CanonicalName returns the canonical name of the tag. 19 | func (t Tag) CanonicalName() string { 20 | return DefaultRegistry.FormatTagCanonical(t) 21 | } 22 | 23 | func (t Tag) MarshalText() (text []byte, err error) { 24 | return []byte(t.String()), nil 25 | } 26 | 27 | func (t *Tag) UnmarshalText(text []byte) (err error) { 28 | *t, err = DefaultRegistry.ParseTag(string(text)) 29 | return 30 | } 31 | 32 | const ( 33 | minStandardTag uint32 = 0x00420000 34 | maxStandardTag uint32 = 0x00430000 35 | minCustomTag uint32 = 0x00540000 36 | maxCustomTag uint32 = 0x00550000 37 | ) 38 | 39 | // Valid checks whether the tag's numeric value is valid according to 40 | // the ranges in the spec. 41 | func (t Tag) Valid() bool { 42 | switch { 43 | case uint32(t) >= minStandardTag && uint32(t) < maxStandardTag: 44 | return true 45 | case uint32(t) >= minCustomTag && uint32(t) < maxCustomTag: 46 | return true 47 | default: 48 | return false 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ttlv/tag_test.go: -------------------------------------------------------------------------------- 1 | package ttlv_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gemalto/kmip-go/kmip14" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestTag_CanonicalName(t *testing.T) { 11 | assert.Equal(t, "Cryptographic Algorithm", kmip14.TagCryptographicAlgorithm.CanonicalName()) 12 | } 13 | -------------------------------------------------------------------------------- /ttlv/types.go: -------------------------------------------------------------------------------- 1 | package ttlv 2 | 3 | import ( 4 | "io" 5 | "time" 6 | ) 7 | 8 | func RegisterTypes(r *Registry) { 9 | m := map[string]Type{ 10 | "BigInteger": TypeBigInteger, 11 | "Boolean": TypeBoolean, 12 | "ByteString": TypeByteString, 13 | "DateTime": TypeDateTime, 14 | "Enumeration": TypeEnumeration, 15 | "Integer": TypeInteger, 16 | "Interval": TypeInterval, 17 | "LongInteger": TypeLongInteger, 18 | "Structure": TypeStructure, 19 | "TextString": TypeTextString, 20 | "DateTimeExtended": TypeDateTimeExtended, 21 | } 22 | 23 | for name, v := range m { 24 | r.RegisterType(v, name) 25 | } 26 | } 27 | 28 | // Type describes the type of a KMIP TTLV. 29 | // 2 and 9.1.1.2 30 | type Type byte 31 | 32 | const ( 33 | TypeStructure Type = 0x01 34 | TypeInteger Type = 0x02 35 | TypeLongInteger Type = 0x03 36 | TypeBigInteger Type = 0x04 37 | TypeEnumeration Type = 0x05 38 | TypeBoolean Type = 0x06 39 | TypeTextString Type = 0x07 40 | TypeByteString Type = 0x08 41 | TypeDateTime Type = 0x09 42 | TypeInterval Type = 0x0A 43 | TypeDateTimeExtended Type = 0x0B 44 | ) 45 | 46 | // String returns the normalized name of the type. If the type 47 | // name isn't registered, it returns the hex value of the type, 48 | // e.g. "0x01" (TypeStructure). The value of String() is suitable 49 | // for use in the JSON or XML encoding of TTLV. 50 | func (t Type) String() string { 51 | return DefaultRegistry.FormatType(t) 52 | } 53 | 54 | func (t Type) MarshalText() (text []byte, err error) { 55 | return []byte(t.String()), nil 56 | } 57 | 58 | func (t *Type) UnmarshalText(text []byte) (err error) { 59 | *t, err = DefaultRegistry.ParseType(string(text)) 60 | return 61 | } 62 | 63 | // DateTimeExtended is a time wrapper which always marshals to a DateTimeExtended. 64 | type DateTimeExtended struct { 65 | time.Time 66 | } 67 | 68 | func (t *DateTimeExtended) UnmarshalTTLV(d *Decoder, ttlv TTLV) error { 69 | if len(ttlv) == 0 { 70 | return nil 71 | } 72 | 73 | if t == nil { 74 | *t = DateTimeExtended{} 75 | } 76 | 77 | err := d.DecodeValue(&t.Time, ttlv) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | return nil 83 | } 84 | 85 | func (t DateTimeExtended) MarshalTTLV(e *Encoder, tag Tag) error { 86 | e.EncodeDateTimeExtended(tag, t.Time) 87 | return nil 88 | } 89 | 90 | // Value is a go-typed mapping for a TTLV value. It holds a tag, and the value in 91 | // the form of a native go type. 92 | // 93 | // Value supports marshaling and unmarshaling, allowing a mapping between encoded TTLV 94 | // bytes and native go types. It's useful in tests, or where you want to construct 95 | // an arbitrary TTLV structure in code without declaring a bespoke type, e.g.: 96 | // 97 | // v := ttlv.Value{ 98 | // Tag: TagBatchCount, Value: Values{ 99 | // Value{Tag: TagComment, Value: "red"}, 100 | // Value{Tag: TagComment, Value: "blue"}, 101 | // Value{Tag: TagComment, Value: "green"}, 102 | // } 103 | // t, err := ttlv.Marshal(v) 104 | // 105 | // KMIP Structure types are mapped to the Values go type. When marshaling, if the Value 106 | // field is set to a Values{}, the resulting TTLV will be TypeStructure. When unmarshaling 107 | // a TTLV with TypeStructure, the Value field will be set to a Values{}. 108 | type Value struct { 109 | Tag Tag 110 | Value interface{} 111 | } 112 | 113 | // UnmarshalTTLV implements Unmarshaler 114 | func (t *Value) UnmarshalTTLV(d *Decoder, ttlv TTLV) error { 115 | t.Tag = ttlv.Tag() 116 | 117 | switch ttlv.Type() { 118 | case TypeStructure: 119 | var v Values 120 | 121 | ttlv = ttlv.ValueStructure() 122 | for ttlv.Valid() == nil { 123 | err := d.DecodeValue(&v, ttlv) 124 | if err != nil { 125 | return err 126 | } 127 | 128 | ttlv = ttlv.Next() 129 | } 130 | 131 | t.Value = v 132 | default: 133 | t.Value = ttlv.Value() 134 | } 135 | 136 | return nil 137 | } 138 | 139 | // MarshalTTLV implements Marshaler 140 | func (t Value) MarshalTTLV(e *Encoder, tag Tag) error { 141 | // if tag is set, override the suggested tag 142 | if t.Tag != TagNone { 143 | tag = t.Tag 144 | } 145 | 146 | if tvs, ok := t.Value.(Values); ok { 147 | return e.EncodeStructure(tag, func(e *Encoder) error { 148 | for _, v := range tvs { 149 | if err := e.Encode(v); err != nil { 150 | return err 151 | } 152 | } 153 | 154 | return nil 155 | }) 156 | } 157 | 158 | return e.EncodeValue(tag, t.Value) 159 | } 160 | 161 | // Values is a slice of Value objects. It represents the body of a TTLV with a type of Structure. 162 | type Values []Value 163 | 164 | // NewValue creates a new tagged value. 165 | func NewValue(tag Tag, val interface{}) Value { 166 | return Value{ 167 | Tag: tag, 168 | Value: val, 169 | } 170 | } 171 | 172 | // NewStruct creates a new tagged value which is of type struct. 173 | func NewStruct(tag Tag, vals ...Value) Value { 174 | return Value{ 175 | Tag: tag, 176 | Value: Values(vals), 177 | } 178 | } 179 | 180 | type Encoder struct { 181 | encodeDepth int 182 | w io.Writer 183 | encBuf encBuf 184 | 185 | // these fields store where the encoder is when marshaling a nested struct. its 186 | // used to construct error messages. 187 | currStruct string 188 | currField string 189 | } 190 | 191 | // EnumValue is a uint32 wrapper which always encodes as an enumeration. 192 | type EnumValue uint32 193 | 194 | func (v EnumValue) MarshalTTLV(e *Encoder, tag Tag) error { 195 | e.EncodeEnumeration(tag, uint32(v)) 196 | return nil 197 | } 198 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package kmip 2 | 3 | type Authentication struct { 4 | Credential []Credential 5 | } 6 | 7 | type Nonce struct { 8 | NonceID []byte 9 | NonceValue []byte 10 | } 11 | 12 | type ProtocolVersion struct { 13 | ProtocolVersionMajor int 14 | ProtocolVersionMinor int 15 | } 16 | 17 | type MessageExtension struct { 18 | VendorIdentification string 19 | CriticalityIndicator bool 20 | VendorExtension interface{} 21 | } 22 | 23 | type Attributes struct { 24 | Attributes []Attribute 25 | } 26 | -------------------------------------------------------------------------------- /types_messages.go: -------------------------------------------------------------------------------- 1 | package kmip 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gemalto/kmip-go/kmip14" 7 | ) 8 | 9 | // 7.1 10 | 11 | type RequestMessage struct { 12 | RequestHeader RequestHeader 13 | BatchItem []RequestBatchItem 14 | } 15 | 16 | type ResponseMessage struct { 17 | ResponseHeader ResponseHeader 18 | BatchItem []ResponseBatchItem 19 | } 20 | 21 | // 7.2 22 | 23 | type RequestHeader struct { 24 | ProtocolVersion ProtocolVersion 25 | MaximumResponseSize int `ttlv:",omitempty"` 26 | ClientCorrelationValue string `ttlv:",omitempty"` 27 | ServerCorrelationValue string `ttlv:",omitempty"` 28 | AsynchronousIndicator bool `ttlv:",omitempty"` 29 | AttestationCapableIndicator bool `ttlv:",omitempty"` 30 | AttestationType []kmip14.AttestationType 31 | Authentication *Authentication 32 | BatchErrorContinuationOption kmip14.BatchErrorContinuationOption `ttlv:",omitempty"` 33 | BatchOrderOption bool `ttlv:",omitempty"` 34 | TimeStamp *time.Time 35 | BatchCount int 36 | } 37 | 38 | type RequestBatchItem struct { 39 | Operation kmip14.Operation 40 | UniqueBatchItemID []byte `ttlv:",omitempty"` 41 | RequestPayload interface{} 42 | MessageExtension *MessageExtension `ttlv:",omitempty"` 43 | } 44 | 45 | type ResponseHeader struct { 46 | ProtocolVersion ProtocolVersion 47 | TimeStamp time.Time 48 | Nonce *Nonce 49 | AttestationType []kmip14.AttestationType 50 | ClientCorrelationValue string `ttlv:",omitempty"` 51 | ServerCorrelationValue string `ttlv:",omitempty"` 52 | BatchCount int 53 | } 54 | 55 | type ResponseBatchItem struct { 56 | Operation kmip14.Operation `ttlv:",omitempty"` 57 | UniqueBatchItemID []byte `ttlv:",omitempty"` 58 | ResultStatus kmip14.ResultStatus 59 | ResultReason kmip14.ResultReason `ttlv:",omitempty"` 60 | ResultMessage string `ttlv:",omitempty"` 61 | AsynchronousCorrelationValue []byte `ttlv:",omitempty"` 62 | ResponsePayload interface{} `ttlv:",omitempty"` 63 | MessageExtension *MessageExtension 64 | } 65 | --------------------------------------------------------------------------------