├── .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 [](https://godoc.org/github.com/gemalto/kmip-go) [](https://goreportcard.com/report/gemalto/kmip-go) [](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 |
--------------------------------------------------------------------------------