pax_global_header00006660000000000000000000000064145433372400014517gustar00rootroot0000000000000052 comment=c865baf4058b0ae6529eeb82fbe86bd8c21f4a36 nkeys-0.4.7/000077500000000000000000000000001454333724000126605ustar00rootroot00000000000000nkeys-0.4.7/.github/000077500000000000000000000000001454333724000142205ustar00rootroot00000000000000nkeys-0.4.7/.github/ISSUE_TEMPLATE/000077500000000000000000000000001454333724000164035ustar00rootroot00000000000000nkeys-0.4.7/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000005071454333724000203750ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: Discussion url: https://github.com/nats-io/nkeys/discussions about: Ideal for ideas, feedback, or longer form questions. - name: Chat url: https://slack.nats.io about: Ideal for short, one-off questions, general conversation, and meeting other NATS users! nkeys-0.4.7/.github/ISSUE_TEMPLATE/defect.yml000066400000000000000000000024441454333724000203640ustar00rootroot00000000000000--- name: Defect description: Report a defect, such as a bug or regression. labels: - defect body: - type: textarea id: versions attributes: label: What version were you using? description: Include the server version (`nats-server --version`) and any client versions when observing the issue. validations: required: true - type: textarea id: environment attributes: label: What environment was the server running in? description: This pertains to the operating system, CPU architecture, and/or Docker image that was used. validations: required: true - type: textarea id: steps attributes: label: Is this defect reproducible? description: Provide best-effort steps to showcase the defect. validations: required: true - type: textarea id: expected attributes: label: Given the capability you are leveraging, describe your expectation? description: This may be the expected behavior or performance characteristics. validations: required: true - type: textarea id: actual attributes: label: Given the expectation, what is the defect you are observing? description: This may be an unexpected behavior or regression in performance. validations: required: true nkeys-0.4.7/.github/ISSUE_TEMPLATE/proposal.yml000066400000000000000000000016671454333724000207770ustar00rootroot00000000000000--- name: Proposal description: Propose an enhancement or new feature. labels: - proposal body: - type: textarea id: usecase attributes: label: What motivated this proposal? description: Describe the use case justifying this request. validations: required: true - type: textarea id: change attributes: label: What is the proposed change? description: This could be a behavior change, enhanced API, or a branch new feature. validations: required: true - type: textarea id: benefits attributes: label: Who benefits from this change? description: Describe how this not only benefits you. validations: required: false - type: textarea id: alternates attributes: label: What alternatives have you evaluated? description: This could be using existing features or relying on an external dependency. validations: required: false nkeys-0.4.7/.github/workflows/000077500000000000000000000000001454333724000162555ustar00rootroot00000000000000nkeys-0.4.7/.github/workflows/pushes.yaml000066400000000000000000000062651454333724000204610ustar00rootroot00000000000000name: GHA Build # The `name:` here is also used in badge.svg rendering as the left-hand-side permissions: # Control the GITHUB_TOKEN permissions. # By having this block, all permissions not listed here are set to none. # Available permissions listed at: # # Which API calls need which permissions at what level, listed at: # # contents: read checks: write statuses: write on: push: branches-ignore: - 'exp' - 'exp/*' - 'exp-*' - 'exp_*' - 'wip' - 'wip/*' - 'wip-*' - 'wip_*' pull_request: jobs: test: runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental || false }} strategy: matrix: include: - go: '1.21.x' os: ubuntu-latest canonical: true - go: '1.20.x' os: ubuntu-latest canonical: false steps: - name: Checkout uses: actions/checkout@v3 - name: Set up Go uses: actions/setup-go@v3 with: go-version: ${{ matrix.go }} # We're not doing releases, just checks, so we can live without check-latest here - name: Export Go environment to Actions outputs id: go-settings run: | echo >> "$GITHUB_OUTPUT" "arch=$(go env GOARCH)" echo >> "$GITHUB_OUTPUT" "hostarch=$(go env GOHOSTARCH)" echo >> "$GITHUB_OUTPUT" "os=$(go env GOOS)" echo >> "$GITHUB_OUTPUT" "hostos=$(go env GOHOSTOS)" echo >> "$GITHUB_OUTPUT" "go-version=$(go env GOVERSION)" # Use with: # ${{ steps.go-settings.outputs.go-version }} # which will look like `go1.17.1` if matrix `1.17.x` matches `1.17.1`. # These are independent of how the matrix is setup, or if a matrix is even used. # # You can see the individual values in the "Set up Go" output, collapsed inside a "go env" group at the end. - name: Install staticcheck uses: dominikh/staticcheck-action@v1.3.0 with: version: "2022.1.1" - name: Install additional check/lint tools id: tools-install run: | go install github.com/mattn/goveralls@latest go install github.com/client9/misspell/cmd/misspell@latest - name: Basic Go integrity checks id: integrity run: | $(exit $(go fmt ./... | wc -l)) go get -t ./... go vet ./... misspell -error -locale US . misspell -error -locale US ./nk staticcheck ./... - name: Run Basic Tests id: tests run: | go test -v -vet=off --race --failfast - name: Run Coverage Tests id: coverage run: | go test -v -covermode=count -coverprofile=coverage.out - name: Upload coverage results id: coverage-upload run: | goveralls -coverprofile=coverage.out -service=github env: COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} if: matrix.canonical #EOF nkeys-0.4.7/.github/workflows/release.yaml000066400000000000000000000036231454333724000205650ustar00rootroot00000000000000name: NKeys Release on: # We use goreleaser in release mode, so if we do a workflow_dispatch rule here then we should prompt for a tag to check out. push: tags: - 'v[0-9]*' permissions: # Control the GITHUB_TOKEN permissions; GitHub's docs on which permission scopes control what are a little lacking. # By having this block, all permissions not listed here are set to none. # documents which scopes are needed for it. # # Uploading archives as release artifacts is bundled into the contents: permission key. # The packages: permission is for pushing docker images to github (ghcr.io) instead. contents: write jobs: goreleaser: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 with: # NB: the `fetch-depth: 0` setting is documented by goreleaser # as a requirement, for the changelog feature to work correctly. fetch-depth: 0 # If we do docker image builds, multi-arch, then because goreleaser can't do image builds # in the style of 'crane' or 'ko', and needs a local docker daemon, then at this point # we'd set up QEMU and Buildx; in other projects, we have success using: # docker/setup-qemu-action@v1 # docker/setup-buildx-action@v1 - name: Set up Go uses: actions/setup-go@v3 with: go-version: '1.21' check-latest: true - name: Basic integrity checks run: | go vet ./... out="$(go list -m -retracted -f '{{if .Retracted}}{{.Path}} is retracted{{end}}' all)" if [ -n "$out" ]; then printf '%s\n' "$out" exit 1 fi - name: Run GoReleaser id: goreleaser uses: goreleaser/goreleaser-action@v3 with: args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} nkeys-0.4.7/.gitignore000066400000000000000000000004411454333724000146470ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.dll *.so *.dylib build/ # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 .glide/ .idea/ nkeys-0.4.7/.goreleaser.yml000066400000000000000000000022601454333724000156110ustar00rootroot00000000000000project_name: nkeys release: github: owner: nats-io name: nkeys name_template: '{{.Tag}}' draft: true builds: - id: nk main: ./nk/main.go ldflags: "-X main.Version={{.Tag}}_{{.Commit}}" binary: nk goos: - darwin - linux - windows - freebsd goarch: - amd64 - arm - arm64 - 386 - mips64le - s390x goarm: - 6 - 7 ignore: - goos: darwin goarch: 386 - goos: freebsd goarch: arm - goos: freebsd goarch: arm64 - goos: freebsd goarch: 386 dist: build archives: - name_template: '{{ .ProjectName }}-v{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' wrap_in_directory: true format: zip files: - README.md - LICENSE checksum: name_template: '{{ .ProjectName }}-v{{ .Version }}-checksums.txt' snapshot: name_template: 'dev' nfpms: - file_name_template: '{{ .ProjectName }}-v{{ .Version }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' maintainer: nats.io description: NKeys utility cli program vendor: nats-io bindir: /usr/local/bin formats: - debnkeys-0.4.7/GOVERNANCE.md000066400000000000000000000002531454333724000146310ustar00rootroot00000000000000# NATS NKEYS Governance NATS NKEYS is part of the NATS project and is subject to the [NATS Governance](https://github.com/nats-io/nats-general/blob/master/GOVERNANCE.md).nkeys-0.4.7/LICENSE000066400000000000000000000261351454333724000136740ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. nkeys-0.4.7/MAINTAINERS.md000066400000000000000000000004661454333724000147620ustar00rootroot00000000000000# Maintainers Maintainership is on a per project basis. ### Maintainers - Derek Collison [@derekcollison](https://github.com/derekcollison) - Ivan Kozlovic [@kozlovic](https://github.com/kozlovic) - Waldemar Quevedo [@wallyqs](https://github.com/wallyqs) nkeys-0.4.7/README.md000066400000000000000000000070601454333724000141420ustar00rootroot00000000000000# NKEYS [![License Apache 2](https://img.shields.io/badge/License-Apache2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) [![Go Report Card](https://goreportcard.com/badge/github.com/nats-io/nkeys)](https://goreportcard.com/report/github.com/nats-io/nkeys) [![Build Status](https://github.com/nats-io/nkeys/actions/workflows/release.yaml/badge.svg)](https://github.com/nats-io/nkeys/actions/workflows/release.yaml/badge.svg) [![GoDoc](https://godoc.org/github.com/nats-io/nkeys?status.svg)](https://godoc.org/github.com/nats-io/nkeys) [![Coverage Status](https://coveralls.io/repos/github/nats-io/nkeys/badge.svg?branch=main&service=github)](https://coveralls.io/github/nats-io/nkeys?branch=main) A public-key signature system based on [Ed25519](https://ed25519.cr.yp.to/) for the NATS ecosystem. ## About The NATS ecosystem will be moving to [Ed25519](https://ed25519.cr.yp.to/) keys for identity, authentication and authorization for entities such as Accounts, Users, Servers and Clusters. Ed25519 is fast and resistant to side channel attacks. Generation of a seed key is all that is needed to be stored and kept safe, as the seed can generate both the public and private keys. The NATS system will utilize Ed25519 keys, meaning that NATS systems will never store or even have access to any private keys. Authentication will utilize a random challenge response mechanism. Dealing with 32 byte and 64 byte raw keys can be challenging. NKEYS is designed to formulate keys in a much friendlier fashion and references work done in cryptocurrencies, specifically [Stellar](https://www.stellar.org/). Bitcoin and others used a form of Base58 (or Base58Check) to encode raw keys. Stellar utilized a more traditional Base32 with a CRC16 and a version or prefix byte. NKEYS utilizes a similar format where the prefix will be 1 byte for public and private keys and will be 2 bytes for seeds. The base32 encoding of these prefixes will yield friendly human readable prefixes, e.g. '**N**' = server, '**C**' = cluster, '**O**' = operator, '**A**' = account, and '**U**' = user. '**P**' is used for private keys. For seeds, the first encoded prefix is '**S**', and the second character will be the type for the public key, e.g. "**SU**" is a seed for a user key pair, "**SA**" is a seed for an account key pair. ## Installation Use the `go` command: $ go get github.com/nats-io/nkeys ## nk - Command Line Utility Located under the nk [directory](https://github.com/nats-io/nkeys/tree/master/nk). ## Basic API Usage ```go // Create a new User KeyPair user, _ := nkeys.CreateUser() // Sign some data with a full key pair user. data := []byte("Hello World") sig, _ := user.Sign(data) // Verify the signature. err = user.Verify(data, sig) // Access the seed, the only thing that needs to be stored and kept safe. // seed = "SUAKYRHVIOREXV7EUZTBHUHL7NUMHPMAS7QMDU3GTIUWEI5LDNOXD43IZY" seed, _ := user.Seed() // Access the public key which can be shared. // publicKey = "UD466L6EBCM3YY5HEGHJANNTN4LSKTSUXTH7RILHCKEQMQHTBNLHJJXT" publicKey, _ := user.PublicKey() // Create a full User who can sign and verify from a private seed. user, _ = nkeys.FromSeed(seed) // Create a User who can only verify signatures via a public key. user, _ = nkeys.FromPublicKey(publicKey) // Create a User KeyPair with our own random data. var rawSeed [32]byte _, err := io.ReadFull(rand.Reader, rawSeed[:]) // Or some other random source. user2, _ := nkeys.FromRawSeed(PrefixByteUser, rawSeed) ``` ## License Unless otherwise noted, the NATS source files are distributed under the Apache Version 2.0 license found in the LICENSE file. nkeys-0.4.7/TODO.md000066400000000000000000000001141454333724000137430ustar00rootroot00000000000000 # General - [ ] Child key derivation - [ ] Hardware support, e.g. YubiHSM nkeys-0.4.7/crc16.go000066400000000000000000000062161454333724000141320ustar00rootroot00000000000000// Copyright 2018 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package nkeys // An implementation of crc16 according to CCITT standards for XMODEM. var crc16tab = [256]uint16{ 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, } // crc16 returns the 2-byte crc for the data provided. func crc16(data []byte) uint16 { var crc uint16 for _, b := range data { crc = ((crc << 8) & 0xffff) ^ crc16tab[((crc>>8)^uint16(b))&0x00FF] } return crc } // validate will check the calculated crc16 checksum for data against the expected. func validate(data []byte, expected uint16) error { if crc16(data) != expected { return ErrInvalidChecksum } return nil } nkeys-0.4.7/creds_utils.go000066400000000000000000000040111454333724000155230ustar00rootroot00000000000000package nkeys import ( "bytes" "regexp" "strings" ) var userConfigRE = regexp.MustCompile(`\s*(?:(?:[-]{3,}.*[-]{3,}\r?\n)([\w\-.=]+)(?:\r?\n[-]{3,}.*[-]{3,}\r?\n))`) // ParseDecoratedJWT takes a creds file and returns the JWT portion. func ParseDecoratedJWT(contents []byte) (string, error) { items := userConfigRE.FindAllSubmatch(contents, -1) if len(items) == 0 { return string(contents), nil } // First result should be the user JWT. // We copy here so that if the file contained a seed file too we wipe appropriately. raw := items[0][1] tmp := make([]byte, len(raw)) copy(tmp, raw) return strings.TrimSpace(string(tmp)), nil } // ParseDecoratedNKey takes a creds file, finds the NKey portion and creates a // key pair from it. func ParseDecoratedNKey(contents []byte) (KeyPair, error) { var seed []byte items := userConfigRE.FindAllSubmatch(contents, -1) if len(items) > 1 { seed = items[1][1] } else { lines := bytes.Split(contents, []byte("\n")) for _, line := range lines { if bytes.HasPrefix(bytes.TrimSpace(line), []byte("SO")) || bytes.HasPrefix(bytes.TrimSpace(line), []byte("SA")) || bytes.HasPrefix(bytes.TrimSpace(line), []byte("SU")) { seed = line break } } } if seed == nil { return nil, ErrNoSeedFound } if !bytes.HasPrefix(seed, []byte("SO")) && !bytes.HasPrefix(seed, []byte("SA")) && !bytes.HasPrefix(seed, []byte("SU")) { return nil, ErrInvalidNkeySeed } kp, err := FromSeed(seed) if err != nil { return nil, err } return kp, nil } // ParseDecoratedUserNKey takes a creds file, finds the NKey portion and creates a // key pair from it. Similar to ParseDecoratedNKey but fails for non-user keys. func ParseDecoratedUserNKey(contents []byte) (KeyPair, error) { nk, err := ParseDecoratedNKey(contents) if err != nil { return nil, err } seed, err := nk.Seed() if err != nil { return nil, err } if !bytes.HasPrefix(seed, []byte("SU")) { return nil, ErrInvalidUserSeed } kp, err := FromSeed(seed) if err != nil { return nil, err } return kp, nil } nkeys-0.4.7/creds_utils_test.go000066400000000000000000000054501454333724000165720ustar00rootroot00000000000000package nkeys import ( "bytes" "testing" ) func Test_ParseDecoratedJWTBad(t *testing.T) { v, err := ParseDecoratedJWT([]byte("foo")) if err != nil { t.Fatal(err) } if v != "foo" { t.Fatal("unexpected input was not returned") } } func Test_ParseDecoratedSeedBad(t *testing.T) { if _, err := ParseDecoratedNKey([]byte("foo")); err == nil { t.Fatal("Expected error") } else if err.Error() != "nkeys: no nkey seed found" { t.Fatal(err) } } const ( credsSeed = `SUAOTBNEUHZDFJT3EUMELT7MQTP24JF3XVCXQNDSCU74G5IU6VAJBKH5LI` credsJwt = `eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJHVDROVU5NRUY3Wk1XQ1JCWFZWVURLUVQ2WllQWjc3VzRKUlFYRDNMMjRIS1VKRUNRSDdRIiwiaWF0IjoxNTkwNzgxNTkzLCJpc3MiOiJBQURXTFRISUNWNFNVQUdGNkVLTlZFVzVCQlA3WVJESUJHV0dHSFo1SkJET1FZQTdHVUZNNkFRVSIsIm5hbWUiOiJPUEVSQVRPUiIsInN1YiI6IlVERTZXVEdMVFRQQ1JKUkpDS0JKUkdWTlpUTElWUjdMRUVFTFI0Q1lXV1dCS0pTN1hZSUtYRFVVIiwibmF0cyI6eyJwdWIiOnt9LCJzdWIiOnt9LCJ0eXBlIjoidXNlciIsInZlcnNpb24iOjJ9fQ.c_XQT04wEoVVNDRjPHeKwe17BOrSpQTcftwIbB7KoNEIz6peZCJDc4-J3emVepHofUOWy7IAo9TlLwYhuGHWAQ` decoratedCreds = `-----BEGIN NATS USER JWT----- eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJHVDROVU5NRUY3Wk1XQ1JCWFZWVURLUVQ2WllQWjc3VzRKUlFYRDNMMjRIS1VKRUNRSDdRIiwiaWF0IjoxNTkwNzgxNTkzLCJpc3MiOiJBQURXTFRISUNWNFNVQUdGNkVLTlZFVzVCQlA3WVJESUJHV0dHSFo1SkJET1FZQTdHVUZNNkFRVSIsIm5hbWUiOiJPUEVSQVRPUiIsInN1YiI6IlVERTZXVEdMVFRQQ1JKUkpDS0JKUkdWTlpUTElWUjdMRUVFTFI0Q1lXV1dCS0pTN1hZSUtYRFVVIiwibmF0cyI6eyJwdWIiOnt9LCJzdWIiOnt9LCJ0eXBlIjoidXNlciIsInZlcnNpb24iOjJ9fQ.c_XQT04wEoVVNDRjPHeKwe17BOrSpQTcftwIbB7KoNEIz6peZCJDc4-J3emVepHofUOWy7IAo9TlLwYhuGHWAQ ------END NATS USER JWT------ ************************* IMPORTANT ************************* NKEY Seed printed below can be used to sign and prove identity. NKEYs are sensitive and should be treated as secrets. -----BEGIN USER NKEY SEED----- SUAOTBNEUHZDFJT3EUMELT7MQTP24JF3XVCXQNDSCU74G5IU6VAJBKH5LI ------END USER NKEY SEED------ ************************************************************* ` ) func Test_ParseDecoratedSeedAndJWT(t *testing.T) { // test with and without \r\n for _, creds := range [][]byte{[]byte(decoratedCreds), bytes.ReplaceAll([]byte(decoratedCreds), []byte{'\n'}, []byte{'\r', '\n'})} { kp, err := ParseDecoratedUserNKey(creds) if err != nil { t.Fatal(err) } pu, err := kp.Seed() if err != nil { t.Fatal(err) } if !bytes.Equal(pu, []byte(credsSeed)) { t.Fatal("seeds don't match") } kp, err = ParseDecoratedNKey(creds) if err != nil { t.Fatal(err) } pu, err = kp.Seed() if err != nil { t.Fatal(err) } if !bytes.Equal(pu, []byte(credsSeed)) { t.Fatal("seeds don't match") } jwt, err := ParseDecoratedJWT(creds) if err != nil { t.Fatal(err) } if !bytes.Equal([]byte(jwt), []byte(credsJwt)) { t.Fatal("jwt don't match") } } } nkeys-0.4.7/dependencies.md000066400000000000000000000010071454333724000156260ustar00rootroot00000000000000# External Dependencies This file lists the dependencies used in this repository. | Dependency | License | |-|-| | Go | BSD 3-Clause "New" or "Revised" License | | golang.org/x/crypto v0.3.0 | BSD 3-Clause "New" or "Revised" License | | golang.org/x/net v0.2.0 | BSD 3-Clause "New" or "Revised" License | | golang.org/x/sys v0.2.0 | BSD 3-Clause "New" or "Revised" License | | golang.org/x/term v0.2.0 | BSD 3-Clause "New" or "Revised" License | | golang.org/x/text v0.4.0 | BSD 3-Clause "New" or "Revised" License | nkeys-0.4.7/errors.go000066400000000000000000000053021454333724000145230ustar00rootroot00000000000000// Copyright 2022 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package nkeys // Errors const ( ErrInvalidPrefixByte = nkeysError("nkeys: invalid prefix byte") ErrInvalidKey = nkeysError("nkeys: invalid key") ErrInvalidPublicKey = nkeysError("nkeys: invalid public key") ErrInvalidPrivateKey = nkeysError("nkeys: invalid private key") ErrInvalidSeedLen = nkeysError("nkeys: invalid seed length") ErrInvalidSeed = nkeysError("nkeys: invalid seed") ErrInvalidEncoding = nkeysError("nkeys: invalid encoded key") ErrInvalidSignature = nkeysError("nkeys: signature verification failed") ErrCannotSign = nkeysError("nkeys: can not sign, no private key available") ErrPublicKeyOnly = nkeysError("nkeys: no seed or private key available") ErrIncompatibleKey = nkeysError("nkeys: incompatible key") ErrInvalidChecksum = nkeysError("nkeys: invalid checksum") ErrNoSeedFound = nkeysError("nkeys: no nkey seed found") ErrInvalidNkeySeed = nkeysError("nkeys: doesn't contain a seed nkey") ErrInvalidUserSeed = nkeysError("nkeys: doesn't contain an user seed nkey") ErrInvalidRecipient = nkeysError("nkeys: not a valid recipient public curve key") ErrInvalidSender = nkeysError("nkeys: not a valid sender public curve key") ErrInvalidCurveKey = nkeysError("nkeys: not a valid curve key") ErrInvalidCurveSeed = nkeysError("nkeys: not a valid curve seed") ErrInvalidEncrypted = nkeysError("nkeys: encrypted input is not valid") ErrInvalidEncVersion = nkeysError("nkeys: encrypted input wrong version") ErrCouldNotDecrypt = nkeysError("nkeys: could not decrypt input") ErrInvalidCurveKeyOperation = nkeysError("nkeys: curve key is not valid for sign/verify") ErrInvalidNKeyOperation = nkeysError("nkeys: only curve key can seal/open") ErrCannotOpen = nkeysError("nkeys: cannot open no private curve key available") ErrCannotSeal = nkeysError("nkeys: cannot seal no private curve key available") ) type nkeysError string func (e nkeysError) Error() string { return string(e) } nkeys-0.4.7/go.mod000066400000000000000000000001741454333724000137700ustar00rootroot00000000000000module github.com/nats-io/nkeys go 1.20 require golang.org/x/crypto v0.17.0 require golang.org/x/sys v0.15.0 // indirect nkeys-0.4.7/go.sum000066400000000000000000000004701454333724000140140ustar00rootroot00000000000000golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= nkeys-0.4.7/keypair.go000066400000000000000000000073601454333724000146610ustar00rootroot00000000000000// Copyright 2018-2022 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package nkeys import ( "bytes" "crypto/rand" "io" "golang.org/x/crypto/ed25519" ) // kp is the internal struct for a kepypair using seed. type kp struct { seed []byte } // All seeds are 32 bytes long. const seedLen = 32 // CreatePair will create a KeyPair based on the rand entropy and a type/prefix byte. func CreatePair(prefix PrefixByte) (KeyPair, error) { return CreatePairWithRand(prefix, rand.Reader) } // CreatePair will create a KeyPair based on the rand reader and a type/prefix byte. rand can be nil. func CreatePairWithRand(prefix PrefixByte, rr io.Reader) (KeyPair, error) { if prefix == PrefixByteCurve { return CreateCurveKeysWithRand(rr) } if rr == nil { rr = rand.Reader } var rawSeed [seedLen]byte _, err := io.ReadFull(rr, rawSeed[:]) if err != nil { return nil, err } seed, err := EncodeSeed(prefix, rawSeed[:]) if err != nil { return nil, err } return &kp{seed}, nil } // rawSeed will return the raw, decoded 64 byte seed. func (pair *kp) rawSeed() ([]byte, error) { _, raw, err := DecodeSeed(pair.seed) return raw, err } // keys will return a 32 byte public key and a 64 byte private key utilizing the seed. func (pair *kp) keys() (ed25519.PublicKey, ed25519.PrivateKey, error) { raw, err := pair.rawSeed() if err != nil { return nil, nil, err } return ed25519.GenerateKey(bytes.NewReader(raw)) } // Wipe will randomize the contents of the seed key func (pair *kp) Wipe() { io.ReadFull(rand.Reader, pair.seed) pair.seed = nil } // Seed will return the encoded seed. func (pair *kp) Seed() ([]byte, error) { return pair.seed, nil } // PublicKey will return the encoded public key associated with the KeyPair. // All KeyPairs have a public key. func (pair *kp) PublicKey() (string, error) { public, raw, err := DecodeSeed(pair.seed) if err != nil { return "", err } pub, _, err := ed25519.GenerateKey(bytes.NewReader(raw)) if err != nil { return "", err } pk, err := Encode(public, pub) if err != nil { return "", err } return string(pk), nil } // PrivateKey will return the encoded private key for KeyPair. func (pair *kp) PrivateKey() ([]byte, error) { _, priv, err := pair.keys() if err != nil { return nil, err } return Encode(PrefixBytePrivate, priv) } // Sign will sign the input with KeyPair's private key. func (pair *kp) Sign(input []byte) ([]byte, error) { _, priv, err := pair.keys() if err != nil { return nil, err } return ed25519.Sign(priv, input), nil } // Verify will verify the input against a signature utilizing the public key. func (pair *kp) Verify(input []byte, sig []byte) error { pub, _, err := pair.keys() if err != nil { return err } if !ed25519.Verify(pub, input, sig) { return ErrInvalidSignature } return nil } // Seal is only supported on CurveKeyPair func (pair *kp) Seal(input []byte, recipient string) ([]byte, error) { return nil, ErrInvalidNKeyOperation } // SealWithRand is only supported on CurveKeyPair func (pair *kp) SealWithRand(input []byte, recipient string, rr io.Reader) ([]byte, error) { return nil, ErrInvalidNKeyOperation } // Open is only supported on CurveKey func (pair *kp) Open(input []byte, sender string) ([]byte, error) { return nil, ErrInvalidNKeyOperation } nkeys-0.4.7/nk/000077500000000000000000000000001454333724000132705ustar00rootroot00000000000000nkeys-0.4.7/nk/README.md000066400000000000000000000033141454333724000145500ustar00rootroot00000000000000# nk - Nkeys utility program [![License Apache 2](https://img.shields.io/badge/License-Apache2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) ## SYNOPSIS ```bash nk [-gen type] [-sign file] [-verify file] [-inkey keyfile] [-pubin keyfile] [-pubout] [-e entropy] ``` ## DESCRIPTION The nk utility program can be used to generate nkeys, signing, and verify signatures. ## COMMAND OPTIONS -gen type Used to create an Nkey Seed of a given type. Type can be **User**, **Account**, **Server**, **Cluster**, or **Operator** -sign file Used to sign the contents of file. -inkey is also required. -verify file -sigfile sig Used to verify a file with a given signature. -inkey or -pubin also required. ## Examples Create a user keypair. The result will be an encoded seed. Seeds are prefixed with an 'S', and followed by the type, e.g. U = user. ```bash > nk -gen user > user.seed > cat user.seed SUAKYRHVIOREXV7EUZTBHUHL7NUMHPMAS7QMDU3GTIUWEI5LDNOXD43IZY ``` You can obtain the public key for an nkey seed as follows. ```bash > nk -inkey user.seed -pubout > user.pub > cat user.pub UD466L6EBCM3YY5HEGHJANNTN4LSKTSUXTH7RILHCKEQMQHTBNLHJJXT ``` Signing the contents of a file ```bash > cat > some.txt Hello World! > nk -sign some.txt -inkey user.seed > some.sig > cat some.sig 0CK1XmkxNfUGfudxliWTWeoETgIo23m9qowS9yTfYFSrjR8HgAW63jQ3NxPU_jG38hZPW61IZSun37N690CkDg ``` Verifying a signature. You can use the seed or the public key. ```bash > nk -verify some.txt -sigfile some.sig -inkey user.seed Verified OK > nk -verify some.txt -sigfile some.sig -pubin user.pub Verified OK ``` ## License Unless otherwise noted, the NATS source files are distributed under the Apache Version 2.0 license found in the LICENSE file. nkeys-0.4.7/nk/main.go000066400000000000000000000174641454333724000145570ustar00rootroot00000000000000// Copyright 2018-2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "bytes" "crypto/rand" "encoding/base32" "encoding/base64" "flag" "fmt" "log" "os" "runtime" "strings" "github.com/nats-io/nkeys" ) // this will be set during compilation when a release is made on tools var Version string const defaultVanMax = 10_000_000 func usage() { log.Fatalf(`Usage: nk [options] -v Show version -gen Generate key for [user|account|server|cluster|operator|curve|x25519] -sign Sign with -inkey -verify Verfify with -inkey or -pubin and -sigfile -inkey Input key file (seed/private key) -pubin Public key file -sigfile Signature file -pubout Output public key -e Entropy file, e.g. /dev/urandom -pre Attempt to generate public key given prefix, e.g. nk -gen user -pre derek -maxpre Maximum attempts at generating the correct key prefix, default is 10,000,000 `) } type KeyPair interface { Seed() ([]byte, error) PublicKey() (string, error) } func main() { var entropy = flag.String("e", "", "Entropy file, e.g. /dev/urandom") var keyFile = flag.String("inkey", "", "Input key file (seed/private key)") var pubFile = flag.String("pubin", "", "Public key file") var signFile = flag.String("sign", "", "Sign with -inkey ") var sigFile = flag.String("sigfile", "", "Signature file") var verifyFile = flag.String("verify", "", "Verfify with -inkey or -pubin and -sigfile ") var keyType = flag.String("gen", "", "Generate key for , e.g. nk -gen user") var pubout = flag.Bool("pubout", false, "Output public key") var version = flag.Bool("v", false, "Show version") var vanPre = flag.String("pre", "", "Attempt to generate public key given prefix, e.g. nk -gen user -pre derek") var vanMax = flag.Int("maxpre", defaultVanMax, "Maximum attempts at generating the correct key prefix") log.SetFlags(0) log.SetOutput(os.Stdout) flag.Usage = usage flag.Parse() if *version { fmt.Printf("nk version %s\n", Version) return } // Create Key if *keyType != "" { var kp KeyPair // Check to see if we are trying to do a vanity public key. if *vanPre != "" { kp = createVanityKey(*keyType, *vanPre, *entropy, *vanMax) } else { kp = genKeyPair(preForType(*keyType), *entropy) } seed, err := kp.Seed() if err != nil { log.Fatal(err) } log.Printf("%s", seed) if *pubout || *vanPre != "" { pub, _ := kp.PublicKey() log.Printf("%s", pub) } return } if *entropy != "" { log.Fatalf("Entropy file only used when creating keys with -gen") } // Sign if *signFile != "" { sign(*signFile, *keyFile) return } // Verfify if *verifyFile != "" { verify(*verifyFile, *keyFile, *pubFile, *sigFile) return } // Show public key from seed/private if *keyFile != "" && *pubout { printPublicFromSeed(*keyFile) return } usage() } func printPublicFromSeed(keyFile string) { seed := readKeyFile(keyFile) kp, err := nkeys.FromSeed(seed) if err != nil { log.Fatal(err) } pub, _ := kp.PublicKey() log.Printf("%s", pub) } func sign(fname, keyFile string) { if keyFile == "" { log.Fatalf("Sign requires a seed/private key via -inkey ") } seed := readKeyFile(keyFile) kp, err := nkeys.FromSeed(seed) if err != nil { log.Fatal(err) } content, err := os.ReadFile(fname) if err != nil { log.Fatal(err) } sigraw, err := kp.Sign(content) if err != nil { log.Fatal(err) } log.Printf("%s", base64.RawURLEncoding.EncodeToString(sigraw)) } func verify(fname, keyFile, pubFile, sigFile string) { if keyFile == "" && pubFile == "" { log.Fatalf("Verify requires a seed key via -inkey or a public key via -pubin") } if sigFile == "" { log.Fatalf("Verify requires a signature via -sigfile") } var err error var kp nkeys.KeyPair if keyFile != "" { seed := readKeyFile(keyFile) kp, err = nkeys.FromSeed(seed) } else { // Public Key public := readKeyFile(pubFile) kp, err = nkeys.FromPublicKey(string(public)) } if err != nil { log.Fatal(err) } content, err := os.ReadFile(fname) if err != nil { log.Fatal(err) } sigEnc, err := os.ReadFile(sigFile) if err != nil { log.Fatal(err) } sig, err := base64.RawURLEncoding.DecodeString(string(sigEnc)) if err != nil { log.Fatal(err) } if err := kp.Verify(content, sig); err != nil { log.Fatal(err) } log.Printf("Verified OK") } func preForType(keyType string) nkeys.PrefixByte { keyType = strings.ToLower(keyType) switch keyType { case "user": return nkeys.PrefixByteUser case "account": return nkeys.PrefixByteAccount case "server": return nkeys.PrefixByteServer case "cluster": return nkeys.PrefixByteCluster case "operator": return nkeys.PrefixByteOperator case "curve", "x25519": return nkeys.PrefixByteCurve default: log.Fatalf("Usage: nk -gen [user|account|server|cluster|operator|curve|x25519]\n") } return nkeys.PrefixByte(0) } func genKeyPair(pre nkeys.PrefixByte, entropy string) KeyPair { // See if we override entropy. ef := rand.Reader if entropy != "" { r, err := os.Open(entropy) if err != nil { log.Fatal(err) } ef = r } var kp KeyPair var err error if pre == nkeys.PrefixByteCurve { kp, err = nkeys.CreateCurveKeysWithRand(ef) if err != nil { log.Fatalf("Error creating %c: %v", pre, err) } } else { kp, err = nkeys.CreatePairWithRand(pre, ef) if err != nil { log.Fatalf("Error creating %c: %v", pre, err) } } return kp } var b32Enc = base32.StdEncoding.WithPadding(base32.NoPadding) func createVanityKey(keyType, vanity, entropy string, max int) KeyPair { spinners := []rune(`⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏`) pre := preForType(keyType) vanity = strings.ToUpper(vanity) // Check to make sure we can base32 into it by trying to decode it. _, err := b32Enc.DecodeString(vanity) if err != nil { log.Fatalf("Can not generate base32 encoded strings to match '%s'", vanity) } ncpu := runtime.NumCPU() // Work channel wch := make(chan struct{}) defer close(wch) // Found solution found := make(chan KeyPair) // Start NumCPU go routines. for i := 0; i < ncpu; i++ { go func() { for range wch { kp := genKeyPair(pre, entropy) pub, _ := kp.PublicKey() if strings.HasPrefix(pub[1:], vanity) { found <- kp return } } }() } // Run through max iterations. for i := 0; i < max; i++ { spin := spinners[i%len(spinners)] fmt.Fprintf(os.Stderr, "\r\033[mcomputing\033[m %s ", string(spin)) wch <- struct{}{} select { case kp := <-found: fmt.Fprintf(os.Stderr, "\r") return kp default: } } fmt.Fprintf(os.Stderr, "\r") log.Fatalf("Failed to generate prefix after %d attempts", max) return nil } func readKeyFile(filename string) []byte { var key []byte contents, err := os.ReadFile(filename) if err != nil { log.Fatal(err) } defer wipeSlice(contents) lines := bytes.Split(contents, []byte("\n")) for _, line := range lines { if nkeys.IsValidEncoding(line) { key = make([]byte, len(line)) copy(key, line) return key } } if key == nil { log.Fatalf("Could not find a valid key") } return key } func wipeSlice(buf []byte) { for i := range buf { buf[i] = 'x' } } nkeys-0.4.7/nkeys.go000066400000000000000000000061741454333724000143500ustar00rootroot00000000000000// Copyright 2018-2019 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package nkeys is an Ed25519 based public-key signature system that simplifies keys and seeds // and performs signing and verification. // It also supports encryption via x25519 keys and is compatible with https://pkg.go.dev/golang.org/x/crypto/nacl/box. package nkeys import "io" // Version is our current version const Version = "0.4.7" // KeyPair provides the central interface to nkeys. type KeyPair interface { Seed() ([]byte, error) PublicKey() (string, error) PrivateKey() ([]byte, error) // Sign is only supported on Non CurveKeyPairs Sign(input []byte) ([]byte, error) // Verify is only supported on Non CurveKeyPairs Verify(input []byte, sig []byte) error Wipe() // Seal is only supported on CurveKeyPair Seal(input []byte, recipient string) ([]byte, error) // SealWithRand is only supported on CurveKeyPair SealWithRand(input []byte, recipient string, rr io.Reader) ([]byte, error) // Open is only supported on CurveKey Open(input []byte, sender string) ([]byte, error) } // CreateUser will create a User typed KeyPair. func CreateUser() (KeyPair, error) { return CreatePair(PrefixByteUser) } // CreateAccount will create an Account typed KeyPair. func CreateAccount() (KeyPair, error) { return CreatePair(PrefixByteAccount) } // CreateServer will create a Server typed KeyPair. func CreateServer() (KeyPair, error) { return CreatePair(PrefixByteServer) } // CreateCluster will create a Cluster typed KeyPair. func CreateCluster() (KeyPair, error) { return CreatePair(PrefixByteCluster) } // CreateOperator will create an Operator typed KeyPair. func CreateOperator() (KeyPair, error) { return CreatePair(PrefixByteOperator) } // FromPublicKey will create a KeyPair capable of verifying signatures. func FromPublicKey(public string) (KeyPair, error) { raw, err := decode([]byte(public)) if err != nil { return nil, err } pre := PrefixByte(raw[0]) if err := checkValidPublicPrefixByte(pre); err != nil { return nil, ErrInvalidPublicKey } return &pub{pre, raw[1:]}, nil } // FromSeed will create a KeyPair capable of signing and verifying signatures. func FromSeed(seed []byte) (KeyPair, error) { prefix, _, err := DecodeSeed(seed) if err != nil { return nil, err } if prefix == PrefixByteCurve { return FromCurveSeed(seed) } copy := append([]byte{}, seed...) return &kp{copy}, nil } // FromRawSeed will create a KeyPair from the raw 32 byte seed for a given type. func FromRawSeed(prefix PrefixByte, rawSeed []byte) (KeyPair, error) { seed, err := EncodeSeed(prefix, rawSeed) if err != nil { return nil, err } return &kp{seed}, nil } nkeys-0.4.7/nkeys_test.go000066400000000000000000000467451454333724000154170ustar00rootroot00000000000000// Copyright 2018 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package nkeys import ( "bytes" "crypto/rand" "encoding/base64" "io" "os" "regexp" "testing" "golang.org/x/crypto/ed25519" ) func TestVersion(t *testing.T) { // Semantic versioning verRe := regexp.MustCompile(`\d+.\d+.\d+(-\S+)?`) if !verRe.MatchString(Version) { t.Fatalf("Version not compatible with semantic versioning: %q", Version) } } func TestVersionMatchesTag(t *testing.T) { tag := os.Getenv("TRAVIS_TAG") if tag == "" { t.SkipNow() } // We expect a tag of the form vX.Y.Z. If that's not the case, // we need someone to have a look. So fail if first letter is not // a `v` if tag[0] != 'v' { t.Fatalf("Expect tag to start with `v`, tag is: %s", tag) } // Strip the `v` from the tag for the version comparison. if Version != tag[1:] { t.Fatalf("Version (%s) does not match tag (%s)", Version, tag[1:]) } } func TestEncode(t *testing.T) { var rawKey [32]byte _, err := io.ReadFull(rand.Reader, rawKey[:]) if err != nil { t.Fatalf("Unexpected error reading from crypto/rand: %v", err) } _, err = Encode(PrefixByteUser, rawKey[:]) if err != nil { t.Fatalf("Unexpected error from Encode: %v", err) } str, err := Encode(22<<3, rawKey[:]) if err == nil { t.Fatal("Expected an error from Encode but received nil") } if str != nil { t.Fatalf("Expected empty string from Encode: got %s", str) } } func TestDecode(t *testing.T) { var rawKey [32]byte _, err := io.ReadFull(rand.Reader, rawKey[:]) if err != nil { t.Fatalf("Unexpected error reading from crypto/rand: %v", err) } str, err := Encode(PrefixByteUser, rawKey[:]) if err != nil { t.Fatalf("Unexpected error from Encode: %v", err) } decoded, err := Decode(PrefixByteUser, str) if err != nil { t.Fatalf("Unexpected error from Decode: %v", err) } if !bytes.Equal(decoded, rawKey[:]) { t.Fatalf("Decoded does not match the original") } } func TestSeed(t *testing.T) { var rawKeyShort [16]byte _, err := io.ReadFull(rand.Reader, rawKeyShort[:]) if err != nil { t.Fatalf("Unexpected error reading from crypto/rand: %v", err) } // Seeds need to be 64 bytes if _, err := EncodeSeed(PrefixByteUser, rawKeyShort[:]); err != ErrInvalidSeedLen { t.Fatalf("Did not receive ErrInvalidSeed error, received %v", err) } // Seeds need to be typed with only public types. if _, err := EncodeSeed(PrefixByteSeed, rawKeyShort[:]); err != ErrInvalidPrefixByte { t.Fatalf("Did not receive ErrInvalidPrefixByte error, received %v", err) } var rawSeed [ed25519.SeedSize]byte _, err = io.ReadFull(rand.Reader, rawSeed[:]) if err != nil { t.Fatalf("Unexpected error reading from crypto/rand: %v", err) } seed, err := EncodeSeed(PrefixByteUser, rawSeed[:]) if err != nil { t.Fatalf("EncodeSeed received an error: %v", err) } pre, decoded, err := DecodeSeed(seed) if err != nil { t.Fatalf("Got an unexpected error from DecodeSeed: %v", err) } if pre != PrefixByteUser { t.Fatalf("Expected the prefix to be PrefixByteUser(%v), got %v", PrefixByteUser, pre) } if !bytes.Equal(decoded, rawSeed[:]) { t.Fatalf("Decoded seed does not match the original") } } func TestAccount(t *testing.T) { account, err := CreateAccount() if err != nil { t.Fatalf("Expected non-nil error on CreateAccount, received %v", err) } if account == nil { t.Fatal("Expect a non-nil account") } seed, err := account.Seed() if err != nil { t.Fatalf("Unexpected error retrieving seed: %v", err) } _, err = Decode(PrefixByteSeed, seed) if err != nil { t.Fatalf("Expected a proper seed string, got %s", seed) } // Check Public public, err := account.PublicKey() if err != nil { t.Fatalf("Received an error retrieving public key: %v", err) } if public[0] != 'A' { t.Fatalf("Expected a prefix of 'A' but got %c", public[0]) } if !IsValidPublicAccountKey(public) { t.Fatalf("Not a valid public account key") } // Check Private private, err := account.PrivateKey() if err != nil { t.Fatalf("Received an error retrieving private key: %v", err) } if private[0] != 'P' { t.Fatalf("Expected a prefix of 'P' but got %v", private[0]) } // Check Sign and Verify data := []byte("Hello World") sig, err := account.Sign(data) if err != nil { t.Fatalf("Unexpected error signing from account: %v", err) } if len(sig) != ed25519.SignatureSize { t.Fatalf("Expected signature size of %d but got %d", ed25519.SignatureSize, len(sig)) } err = account.Verify(data, sig) if err != nil { t.Fatalf("Unexpected error verifying signature: %v", err) } } func TestUser(t *testing.T) { user, err := CreateUser() if err != nil { t.Fatalf("Expected non-nil error on CreateUser, received %v", err) } if user == nil { t.Fatal("Expect a non-nil user") } // Check Public public, err := user.PublicKey() if err != nil { t.Fatalf("Received an error retrieving public key: %v", err) } if public[0] != 'U' { t.Fatalf("Expected a prefix of 'U' but got %c", public[0]) } if !IsValidPublicUserKey(public) { t.Fatalf("Not a valid public user key") } } func TestOperator(t *testing.T) { operator, err := CreateOperator() if err != nil { t.Fatalf("Expected non-nil error on CreateOperator, received %v", err) } if operator == nil { t.Fatal("Expect a non-nil operator") } // Check Public public, err := operator.PublicKey() if err != nil { t.Fatalf("Received an error retrieving public key: %v", err) } if public[0] != 'O' { t.Fatalf("Expected a prefix of 'O' but got %c", public[0]) } if !IsValidPublicOperatorKey(public) { t.Fatalf("Not a valid public cluster key") } } func TestCluster(t *testing.T) { cluster, err := CreateCluster() if err != nil { t.Fatalf("Expected non-nil error on CreateCluster, received %v", err) } if cluster == nil { t.Fatal("Expect a non-nil cluster") } // Check Public public, err := cluster.PublicKey() if err != nil { t.Fatalf("Received an error retrieving public key: %v", err) } if public[0] != 'C' { t.Fatalf("Expected a prefix of 'C' but got %c", public[0]) } if !IsValidPublicClusterKey(public) { t.Fatalf("Not a valid public cluster key") } } func TestServer(t *testing.T) { server, err := CreateServer() if err != nil { t.Fatalf("Expected non-nil error on CreateServer, received %v", err) } if server == nil { t.Fatal("Expect a non-nil server") } // Check Public public, err := server.PublicKey() if err != nil { t.Fatalf("Received an error retrieving public key: %v", err) } if public[0] != 'N' { t.Fatalf("Expected a prefix of 'N' but got %c", public[0]) } if !IsValidPublicServerKey(public) { t.Fatalf("Not a valid public server key") } } func TestPrefixByte(t *testing.T) { user, _ := CreateUser() pub, _ := user.PublicKey() if pre := Prefix(pub); pre != PrefixByteUser { t.Fatalf("Expected %s, got %s\n", PrefixByteUser, pre) } seed, _ := user.Seed() if pre := Prefix(string(seed)); pre != PrefixByteSeed { t.Fatalf("Expected %s, got %s\n", PrefixByteSeed, pre) } if pre := Prefix("SEED"); pre != PrefixByteUnknown { t.Fatalf("Expected %s, got %s\n", PrefixByteUnknown, pre) } account, _ := CreateAccount() pub, _ = account.PublicKey() if pre := Prefix(pub); pre != PrefixByteAccount { t.Fatalf("Expected %s, got %s\n", PrefixByteAccount, pre) } } func TestIsValidPublic(t *testing.T) { user, _ := CreateUser() pub, _ := user.PublicKey() if !IsValidPublicKey(pub) { t.Fatalf("Expected pub to be a valid public key") } seed, _ := user.Seed() if IsValidPublicKey(string(seed)) { t.Fatalf("Expected seed to not be a valid public key") } if IsValidPublicKey("BAD") { t.Fatalf("Expected BAD to not be a valid public key") } account, _ := CreateAccount() pub, _ = account.PublicKey() if !IsValidPublicKey(pub) { t.Fatalf("Expected pub to be a valid public key") } } func TestFromPublic(t *testing.T) { // Create a User user, err := CreateUser() if err != nil { t.Fatalf("Expected non-nil error on CreateUser, received %v", err) } if user == nil { t.Fatal("Expect a non-nil user") } // Now create a publickey only KeyPair publicKey, err := user.PublicKey() if err != nil { t.Fatalf("Error retrieving public key from user: %v", err) } publicKeyClone, _ := user.PublicKey() if publicKeyClone != publicKey { t.Fatalf("Expected the public keys to match: %q vs %q", publicKeyClone, publicKey) } pubUser, err := FromPublicKey(publicKey) if err != nil { t.Fatalf("Error creating public key only user: %v", err) } publicKey2, err := pubUser.PublicKey() if err != nil { t.Fatalf("Error retrieving public key from public user: %v", err) } // Make sure they match if publicKey2 != publicKey { t.Fatalf("Expected the public keys to match: %q vs %q", publicKey2, publicKey) } if _, err = pubUser.PrivateKey(); err == nil { t.Fatalf("Expected and error trying to get private key") } if _, err := pubUser.Seed(); err == nil { t.Fatalf("Expected and error trying to get seed") } data := []byte("Hello World") // Can't sign.. if _, err = pubUser.Sign(data); err != ErrCannotSign { t.Fatalf("Expected %v, but got %v", ErrCannotSign, err) } // Should be able to verify with pubUser. sig, err := user.Sign(data) if err != nil { t.Fatalf("Unexpected error signing from user: %v", err) } err = pubUser.Verify(data, sig) if err != nil { t.Fatalf("Unexpected error verifying signature: %v", err) } // Create another user to sign and make sure verify fails. user2, _ := CreateUser() sig, _ = user2.Sign(data) err = pubUser.Verify(data, sig) if err == nil { t.Fatalf("Expected verification to fail.") } } func TestFromSeed(t *testing.T) { account, err := CreateAccount() if err != nil { t.Fatalf("Expected non-nil error on CreateAccount, received %v", err) } if account == nil { t.Fatal("Expect a non-nil account") } data := []byte("Hello World") sig, err := account.Sign(data) if err != nil { t.Fatalf("Unexpected error signing from account: %v", err) } seed, err := account.Seed() if err != nil { t.Fatalf("Unexpected error retrieving seed: %v", err) } // Make sure the seed starts with SA if !bytes.HasPrefix(seed, []byte("SA")) { t.Fatalf("Expected seed to start with 'SA', go '%s'", seed[:2]) } account2, err := FromSeed(seed) if err != nil { t.Fatalf("Error recreating account from seed: %v", err) } if account2 == nil { t.Fatal("Expect a non-nil account") } err = account2.Verify(data, sig) if err != nil { t.Fatalf("Unexpected error verifying signature: %v", err) } } func TestKeyPairFailures(t *testing.T) { var tooshort [8]byte if _, err := EncodeSeed(PrefixByteUser, tooshort[:]); err == nil { t.Fatal("Expected an error with insufficient rand") } if _, err := CreatePair(PrefixBytePrivate); err == nil { t.Fatal("Expected an error with non-public prefix") } kpbad := &kp{[]byte("SEEDBAD")} if _, _, err := kpbad.keys(); err == nil { t.Fatal("Expected an error decoding keys with a bad seed") } if _, err := kpbad.PublicKey(); err == nil { t.Fatal("Expected an error getting PublicKey from KP with a bad seed") } if _, err := kpbad.PrivateKey(); err == nil { t.Fatal("Expected an error getting PrivateKey from KP with a bad seed") } if _, err := kpbad.Sign([]byte("ok")); err == nil { t.Fatal("Expected an error from Signing from KP with a bad seed") } } func TestBadDecode(t *testing.T) { if _, err := decode([]byte("foo!")); err == nil { t.Fatal("Expected an error decoding non-base32") } if _, err := decode([]byte("OK")); err == nil { t.Fatal("Expected an error decoding a too short string") } // Create invalid checksum account, _ := CreateAccount() pkey, _ := account.PublicKey() bpkey := []byte(pkey) bpkey[len(pkey)-1] = '0' bpkey[len(pkey)-2] = '0' if _, err := decode(bpkey); err == nil { t.Fatal("Expected error on decode with bad checksum") } if _, err := Decode(PrefixByteUser, []byte(pkey)); err == nil { t.Fatal("Expected error on Decode with mismatched prefix") } if _, err := Decode(PrefixByte(3<<3), []byte(pkey)); err == nil { t.Fatal("Expected error on Decode with invalid prefix") } if _, err := Decode(PrefixByteAccount, bpkey); err == nil { t.Fatal("Expected error on Decode with bad checksum") } // Seed version if _, _, err := DecodeSeed(bpkey); err == nil { t.Fatal("Expected error on DecodeSeed with bad checksum") } if _, _, err := DecodeSeed([]byte(pkey)); err == nil { t.Fatal("Expected error on DecodeSeed with bad seed type") } seed, _ := account.Seed() bseed := []byte(seed) bseed[1] = 'S' if _, _, err := DecodeSeed(bseed); err == nil { t.Fatal("Expected error on DecodeSeed with bad prefix type") } if _, err := FromSeed(bseed); err == nil { t.Fatal("Expected error on FromSeed with bad prefix type") } if _, err := FromPublicKey(string(bpkey)); err == nil { t.Fatal("Expected error on FromPublicKey with bad checksum") } if _, err := FromPublicKey(string(seed)); err == nil { t.Fatal("Expected error on FromPublicKey with bad checksum") } } func TestFromRawSeed(t *testing.T) { user, err := CreateUser() if err != nil { t.Fatalf("Expected non-nil error on CreateUser, received %v", err) } se, _ := user.Seed() _, raw, _ := DecodeSeed(se) user2, err := FromRawSeed(PrefixByteUser, raw) if err != nil { t.Fatalf("Expected non-nil error on FromRawSeed, received %v", err) } s2e, _ := user2.Seed() if !bytes.Equal(se, s2e) { t.Fatalf("Expected the seeds to be the same, got %v vs %v\n", se, s2e) } } func TestWipe(t *testing.T) { user, err := CreateUser() if err != nil { t.Fatalf("Expected non-nil error on CreateUser, received %v", err) } pubKey, err := user.PublicKey() if err != nil { t.Fatalf("Received an error retrieving public key: %v", err) } seed := user.(*kp).seed // Copy so we know the original copy := append([]byte{}, seed...) user.Wipe() // Make sure new seed is nil if wiped := user.(*kp).seed; wiped != nil { t.Fatalf("Expected the seed to be nil, got %q", wiped) } // Make sure the original seed is not equal to the seed in memory. if bytes.Equal(seed, copy) { t.Fatalf("Expected the memory for the seed to be randomized") } // Now test public user, err = FromPublicKey(pubKey) if err != nil { t.Fatalf("Received an error create KeyPair from PublicKey: %v", err) } edPub := user.(*pub).pub // Copy so we know the original copy = append([]byte{}, edPub...) user.Wipe() // First check pre was changed if user.(*pub).pre != '0' { t.Fatalf("Expected prefix to be changed") } // Make sure the original key is not equal to the one in memory. if bytes.Equal(edPub, copy) { t.Fatalf("Expected the memory for the pubKey to be randomized") } } const ( nonceRawLen = 16 nonceLen = 22 // base64.RawURLEncoding.EncodedLen(nonceRawLen) ) func BenchmarkSign(b *testing.B) { data := make([]byte, nonceRawLen) nonce := make([]byte, nonceLen) rand.Read(data) base64.RawURLEncoding.Encode(nonce, data) user, err := CreateUser() if err != nil { b.Fatalf("Error creating User Nkey: %v", err) } b.ResetTimer() for i := 0; i < b.N; i++ { if _, err := user.Sign(nonce); err != nil { b.Fatalf("Error signing nonce: %v", err) } } } func BenchmarkVerify(b *testing.B) { data := make([]byte, nonceRawLen) nonce := make([]byte, nonceLen) rand.Read(data) base64.RawURLEncoding.Encode(nonce, data) user, err := CreateUser() if err != nil { b.Fatalf("Error creating User Nkey: %v", err) } sig, err := user.Sign(nonce) if err != nil { b.Fatalf("Error sigining nonce: %v", err) } b.ResetTimer() for i := 0; i < b.N; i++ { if err := user.Verify(nonce, sig); err != nil { b.Fatalf("Error verifying nonce: %v", err) } } } func BenchmarkPublicVerify(b *testing.B) { data := make([]byte, nonceRawLen) nonce := make([]byte, nonceLen) rand.Read(data) base64.RawURLEncoding.Encode(nonce, data) user, err := CreateUser() if err != nil { b.Fatalf("Error creating User Nkey: %v", err) } sig, err := user.Sign(nonce) if err != nil { b.Fatalf("Error sigining nonce: %v", err) } pk, err := user.PublicKey() if err != nil { b.Fatalf("Could not extract public key from user: %v", err) } pub, err := FromPublicKey(pk) if err != nil { b.Fatalf("Could not create public key pair from public key string: %v", err) } b.ResetTimer() for i := 0; i < b.N; i++ { if err := pub.Verify(nonce, sig); err != nil { b.Fatalf("Error verifying nonce: %v", err) } } } func TestValidateKeyPairRole(t *testing.T) { okp, err := CreateOperator() if err != nil { t.Fatal(err) } akp, err := CreateAccount() if err != nil { t.Fatal(err) } ukp, err := CreateUser() if err != nil { t.Fatal(err) } ckp, err := CreateCluster() if err != nil { t.Fatal(err) } skp, err := CreateServer() if err != nil { t.Fatal(err) } var keyroles = []struct { kp KeyPair roles []PrefixByte ok bool name string }{ {kp: okp, name: "want operator", roles: []PrefixByte{PrefixByteOperator}, ok: true}, {kp: akp, name: "want account", roles: []PrefixByte{PrefixByteAccount}, ok: true}, {kp: ukp, name: "want user", roles: []PrefixByte{PrefixByteUser}, ok: true}, {kp: ckp, name: "want cluster", roles: []PrefixByte{PrefixByteCluster}, ok: true}, {kp: skp, name: "want server", roles: []PrefixByte{PrefixByteServer}, ok: true}, {kp: okp, name: "want account or operator", roles: []PrefixByte{PrefixByteOperator, PrefixByteAccount}, ok: true}, {kp: akp, name: "want account or operator", roles: []PrefixByte{PrefixByteOperator, PrefixByteAccount}, ok: true}, {kp: akp, name: "want operator got account", roles: []PrefixByte{PrefixByteOperator}, ok: false}, {kp: ukp, name: "want account or operator got user", roles: []PrefixByte{PrefixByteOperator, PrefixByteAccount}, ok: false}, } for _, e := range keyroles { err := CompatibleKeyPair(e.kp, e.roles...) if err == nil && !e.ok { t.Fatalf("test %q should have failed but didn't", e.name) } if err != nil && e.ok { t.Fatalf("test %q should have not failed: %v", e.name, err) } if err != nil && !e.ok && err != ErrIncompatibleKey { t.Fatalf("unexpected error type for %q: %v", e.name, err) } } } func testSealOpen(t *testing.T, prefixByte PrefixByte) { kp, err := CreatePair(prefixByte) if err != nil { t.Fatalf("Failed to create pair for %v", prefixByte) } if kp == nil { t.Fatalf("Failed to create pair for %v - nil keypair", prefixByte) } _, err = kp.Open([]byte("hello"), "ME") if err == nil { t.Fatalf("Expected Open to fail for %v", prefixByte) } if err != ErrInvalidNKeyOperation { t.Fatalf("Expected Open to fail for %v with %v: %v", prefixByte, ErrInvalidNKeyOperation, err) } _, err = kp.Seal([]byte("hello"), "ME") if err == nil { t.Fatalf("Expected Seal to fail for %v", prefixByte) } if err != ErrInvalidNKeyOperation { t.Fatalf("Expected Seal to fail for %v with %v: %v", prefixByte, ErrInvalidNKeyOperation, err) } _, err = kp.SealWithRand([]byte("hello"), "ME", nil) if err == nil { t.Fatalf("Expected SealWithRand to fail for %v", prefixByte) } if err != ErrInvalidNKeyOperation { t.Fatalf("Expected SealWithRand to fail for %v with %v: %v", prefixByte, ErrInvalidNKeyOperation, err) } } func TestSealOpen(t *testing.T) { testSealOpen(t, PrefixByteOperator) testSealOpen(t, PrefixByteAccount) testSealOpen(t, PrefixByteUser) } nkeys-0.4.7/public.go000066400000000000000000000045661454333724000145000ustar00rootroot00000000000000// Copyright 2018 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package nkeys import ( "crypto/rand" "io" "golang.org/x/crypto/ed25519" ) // A KeyPair from a public key capable of verifying only. type pub struct { pre PrefixByte pub ed25519.PublicKey } // PublicKey will return the encoded public key associated with the KeyPair. // All KeyPairs have a public key. func (p *pub) PublicKey() (string, error) { pk, err := Encode(p.pre, p.pub) if err != nil { return "", err } return string(pk), nil } // Seed will return an error since this is not available for public key only KeyPairs. func (p *pub) Seed() ([]byte, error) { return nil, ErrPublicKeyOnly } // PrivateKey will return an error since this is not available for public key only KeyPairs. func (p *pub) PrivateKey() ([]byte, error) { return nil, ErrPublicKeyOnly } // Sign will return an error since this is not available for public key only KeyPairs. func (p *pub) Sign(input []byte) ([]byte, error) { return nil, ErrCannotSign } // Verify will verify the input against a signature utilizing the public key. func (p *pub) Verify(input []byte, sig []byte) error { if !ed25519.Verify(p.pub, input, sig) { return ErrInvalidSignature } return nil } // Wipe will randomize the public key and erase the pre byte. func (p *pub) Wipe() { p.pre = '0' io.ReadFull(rand.Reader, p.pub) } func (p *pub) Seal(input []byte, recipient string) ([]byte, error) { if p.pre == PrefixByteCurve { return nil, ErrCannotSeal } return nil, ErrInvalidNKeyOperation } func (p *pub) SealWithRand(input []byte, _recipient string, rr io.Reader) ([]byte, error) { if p.pre == PrefixByteCurve { return nil, ErrCannotSeal } return nil, ErrInvalidNKeyOperation } func (p *pub) Open(input []byte, sender string) ([]byte, error) { if p.pre == PrefixByteCurve { return nil, ErrCannotOpen } return nil, ErrInvalidNKeyOperation } nkeys-0.4.7/strkey.go000066400000000000000000000216751454333724000145430ustar00rootroot00000000000000// Copyright 2018-2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package nkeys import ( "bytes" "encoding/base32" "encoding/binary" ) // PrefixByte is a lead byte representing the type. type PrefixByte byte const ( // PrefixByteSeed is the version byte used for encoded NATS Seeds PrefixByteSeed PrefixByte = 18 << 3 // Base32-encodes to 'S...' // PrefixBytePrivate is the version byte used for encoded NATS Private keys PrefixBytePrivate PrefixByte = 15 << 3 // Base32-encodes to 'P...' // PrefixByteServer is the version byte used for encoded NATS Servers PrefixByteServer PrefixByte = 13 << 3 // Base32-encodes to 'N...' // PrefixByteCluster is the version byte used for encoded NATS Clusters PrefixByteCluster PrefixByte = 2 << 3 // Base32-encodes to 'C...' // PrefixByteOperator is the version byte used for encoded NATS Operators PrefixByteOperator PrefixByte = 14 << 3 // Base32-encodes to 'O...' // PrefixByteAccount is the version byte used for encoded NATS Accounts PrefixByteAccount PrefixByte = 0 // Base32-encodes to 'A...' // PrefixByteUser is the version byte used for encoded NATS Users PrefixByteUser PrefixByte = 20 << 3 // Base32-encodes to 'U...' // PrefixByteCurve is the version byte used for encoded CurveKeys (X25519) PrefixByteCurve PrefixByte = 23 << 3 // Base32-encodes to 'X...' // PrefixByteUnknown is for unknown prefixes. PrefixByteUnknown PrefixByte = 25 << 3 // Base32-encodes to 'Z...' ) // Set our encoding to not include padding '==' var b32Enc = base32.StdEncoding.WithPadding(base32.NoPadding) // Encode will encode a raw key or seed with the prefix and crc16 and then base32 encoded. func Encode(prefix PrefixByte, src []byte) ([]byte, error) { if err := checkValidPrefixByte(prefix); err != nil { return nil, err } var raw bytes.Buffer // write prefix byte if err := raw.WriteByte(byte(prefix)); err != nil { return nil, err } // write payload if _, err := raw.Write(src); err != nil { return nil, err } // Calculate and write crc16 checksum err := binary.Write(&raw, binary.LittleEndian, crc16(raw.Bytes())) if err != nil { return nil, err } data := raw.Bytes() buf := make([]byte, b32Enc.EncodedLen(len(data))) b32Enc.Encode(buf, data) return buf[:], nil } // EncodeSeed will encode a raw key with the prefix and then seed prefix and crc16 and then base32 encoded. // `src` must be 32 bytes long (ed25519.SeedSize). func EncodeSeed(public PrefixByte, src []byte) ([]byte, error) { if err := checkValidPublicPrefixByte(public); err != nil { return nil, err } if len(src) != seedLen { return nil, ErrInvalidSeedLen } // In order to make this human printable for both bytes, we need to do a little // bit manipulation to setup for base32 encoding which takes 5 bits at a time. b1 := byte(PrefixByteSeed) | (byte(public) >> 5) b2 := (byte(public) & 31) << 3 // 31 = 00011111 var raw bytes.Buffer raw.WriteByte(b1) raw.WriteByte(b2) // write payload if _, err := raw.Write(src); err != nil { return nil, err } // Calculate and write crc16 checksum err := binary.Write(&raw, binary.LittleEndian, crc16(raw.Bytes())) if err != nil { return nil, err } data := raw.Bytes() buf := make([]byte, b32Enc.EncodedLen(len(data))) b32Enc.Encode(buf, data) return buf, nil } // IsValidEncoding will tell you if the encoding is a valid key. func IsValidEncoding(src []byte) bool { _, err := decode(src) return err == nil } // decode will decode the base32 and check crc16 and the prefix for validity. func decode(src []byte) ([]byte, error) { raw := make([]byte, b32Enc.DecodedLen(len(src))) n, err := b32Enc.Decode(raw, src) if err != nil { return nil, err } raw = raw[:n] if n < 4 { return nil, ErrInvalidEncoding } crc := binary.LittleEndian.Uint16(raw[n-2:]) // ensure checksum is valid if err := validate(raw[0:n-2], crc); err != nil { return nil, err } return raw[:n-2], nil } // Decode will decode the base32 string and check crc16 and enforce the prefix is what is expected. func Decode(expectedPrefix PrefixByte, src []byte) ([]byte, error) { if err := checkValidPrefixByte(expectedPrefix); err != nil { return nil, err } raw, err := decode(src) if err != nil { return nil, err } b1 := raw[0] & 248 // 248 = 11111000 if prefix := PrefixByte(b1); prefix != expectedPrefix { return nil, ErrInvalidPrefixByte } return raw[1:], nil } // DecodeSeed will decode the base32 string and check crc16 and enforce the prefix is a seed // and the subsequent type is a valid type. func DecodeSeed(src []byte) (PrefixByte, []byte, error) { raw, err := decode(src) if err != nil { return PrefixByteSeed, nil, err } // Need to do the reverse here to get back to internal representation. b1 := raw[0] & 248 // 248 = 11111000 b2 := (raw[0]&7)<<5 | ((raw[1] & 248) >> 3) // 7 = 00000111 if PrefixByte(b1) != PrefixByteSeed { return PrefixByteSeed, nil, ErrInvalidSeed } if checkValidPublicPrefixByte(PrefixByte(b2)) != nil { return PrefixByteSeed, nil, ErrInvalidSeed } return PrefixByte(b2), raw[2:], nil } // Prefix returns PrefixBytes of its input func Prefix(src string) PrefixByte { b, err := decode([]byte(src)) if err != nil { return PrefixByteUnknown } prefix := PrefixByte(b[0]) err = checkValidPrefixByte(prefix) if err == nil { return prefix } // Might be a seed. b1 := b[0] & 248 if PrefixByte(b1) == PrefixByteSeed { return PrefixByteSeed } return PrefixByteUnknown } // IsValidPublicKey will decode and verify that the string is a valid encoded public key. func IsValidPublicKey(src string) bool { b, err := decode([]byte(src)) if err != nil { return false } if prefix := PrefixByte(b[0]); checkValidPublicPrefixByte(prefix) != nil { return false } return true } // IsValidPublicUserKey will decode and verify the string is a valid encoded Public User Key. func IsValidPublicUserKey(src string) bool { _, err := Decode(PrefixByteUser, []byte(src)) return err == nil } // IsValidPublicAccountKey will decode and verify the string is a valid encoded Public Account Key. func IsValidPublicAccountKey(src string) bool { _, err := Decode(PrefixByteAccount, []byte(src)) return err == nil } // IsValidPublicServerKey will decode and verify the string is a valid encoded Public Server Key. func IsValidPublicServerKey(src string) bool { _, err := Decode(PrefixByteServer, []byte(src)) return err == nil } // IsValidPublicClusterKey will decode and verify the string is a valid encoded Public Cluster Key. func IsValidPublicClusterKey(src string) bool { _, err := Decode(PrefixByteCluster, []byte(src)) return err == nil } // IsValidPublicOperatorKey will decode and verify the string is a valid encoded Public Operator Key. func IsValidPublicOperatorKey(src string) bool { _, err := Decode(PrefixByteOperator, []byte(src)) return err == nil } // IsValidPublicCurveKey will decode and verify the string is a valid encoded Public Curve Key. func IsValidPublicCurveKey(src string) bool { _, err := Decode(PrefixByteCurve, []byte(src)) return err == nil } // checkValidPrefixByte returns an error if the provided value // is not one of the defined valid prefix byte constants. func checkValidPrefixByte(prefix PrefixByte) error { switch prefix { case PrefixByteOperator, PrefixByteServer, PrefixByteCluster, PrefixByteAccount, PrefixByteUser, PrefixByteSeed, PrefixBytePrivate, PrefixByteCurve: return nil } return ErrInvalidPrefixByte } // checkValidPublicPrefixByte returns an error if the provided value // is not one of the public defined valid prefix byte constants. func checkValidPublicPrefixByte(prefix PrefixByte) error { switch prefix { case PrefixByteOperator, PrefixByteServer, PrefixByteCluster, PrefixByteAccount, PrefixByteUser, PrefixByteCurve: return nil } return ErrInvalidPrefixByte } func (p PrefixByte) String() string { switch p { case PrefixByteOperator: return "operator" case PrefixByteServer: return "server" case PrefixByteCluster: return "cluster" case PrefixByteAccount: return "account" case PrefixByteUser: return "user" case PrefixByteSeed: return "seed" case PrefixBytePrivate: return "private" case PrefixByteCurve: return "x25519" } return "unknown" } // CompatibleKeyPair returns an error if the KeyPair doesn't match expected PrefixByte(s) func CompatibleKeyPair(kp KeyPair, expected ...PrefixByte) error { pk, err := kp.PublicKey() if err != nil { return err } pkType := Prefix(pk) for _, k := range expected { if pkType == k { return nil } } return ErrIncompatibleKey } nkeys-0.4.7/strkey_test.go000066400000000000000000000031161454333724000155700ustar00rootroot00000000000000// Copyright 2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package nkeys import ( "bytes" "encoding/binary" "testing" ) func BenchmarkDecode(b *testing.B) { tcs := []struct { name string f func([]byte) ([]byte, error) }{ {"original", func(src []byte) ([]byte, error) { raw := make([]byte, b32Enc.DecodedLen(len(src))) n, err := b32Enc.Decode(raw, src) if err != nil { return nil, err } raw = raw[:n] if len(raw) < 4 { return nil, ErrInvalidEncoding } var crc uint16 checksum := bytes.NewReader(raw[len(raw)-2:]) if err := binary.Read(checksum, binary.LittleEndian, &crc); err != nil { return nil, err } // ensure checksum is valid if err := validate(raw[0:len(raw)-2], crc); err != nil { return nil, err } return raw[:len(raw)-2], nil }}, {"current", decode}, } for _, tc := range tcs { b.Run(tc.name, func(b *testing.B) { for i := 0; i < b.N; i++ { if _, err := tc.f([]byte("SUAMAR5BIQ2AZCPYRTLLK7UGWMGSG2GWD2VPIXBNO7ZIB6TZAU4QAEJS2Q")); err != nil { b.Fatal(err) } } }) } } nkeys-0.4.7/xkeys.go000066400000000000000000000115521454333724000143560ustar00rootroot00000000000000// Copyright 2022-2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package nkeys import ( "bytes" "crypto/rand" "encoding/binary" "io" "golang.org/x/crypto/curve25519" "golang.org/x/crypto/nacl/box" ) // This package will support safe use of X25519 keys for asymmetric encryption. // We will be compatible with nacl.Box, but generate random nonces automatically. // We may add more advanced options in the future for group recipients and better // end to end algorithms. const ( curveKeyLen = 32 curveDecodeLen = 35 curveNonceLen = 24 ) type ckp struct { seed [curveKeyLen]byte // Private raw key. } // CreateCurveKeys will create a Curve typed KeyPair. func CreateCurveKeys() (KeyPair, error) { return CreateCurveKeysWithRand(rand.Reader) } // CreateCurveKeysWithRand will create a Curve typed KeyPair // with specified rand source. func CreateCurveKeysWithRand(rr io.Reader) (KeyPair, error) { var kp ckp _, err := io.ReadFull(rr, kp.seed[:]) if err != nil { return nil, err } return &kp, nil } // Will create a curve key pair from seed. func FromCurveSeed(seed []byte) (KeyPair, error) { pb, raw, err := DecodeSeed(seed) if err != nil { return nil, err } if pb != PrefixByteCurve || len(raw) != curveKeyLen { return nil, ErrInvalidCurveSeed } var kp ckp copy(kp.seed[:], raw) return &kp, nil } // Seed will return the encoded seed. func (pair *ckp) Seed() ([]byte, error) { return EncodeSeed(PrefixByteCurve, pair.seed[:]) } // PublicKey will return the encoded public key. func (pair *ckp) PublicKey() (string, error) { var pub [curveKeyLen]byte curve25519.ScalarBaseMult(&pub, &pair.seed) key, err := Encode(PrefixByteCurve, pub[:]) return string(key), err } // PrivateKey will return the encoded private key. func (pair *ckp) PrivateKey() ([]byte, error) { return Encode(PrefixBytePrivate, pair.seed[:]) } func decodePubCurveKey(src string, dest []byte) error { var raw [curveDecodeLen]byte // should always be 35 n, err := b32Enc.Decode(raw[:], []byte(src)) if err != nil { return err } if n != curveDecodeLen { return ErrInvalidCurveKey } // Make sure it is what we expected. if prefix := PrefixByte(raw[0]); prefix != PrefixByteCurve { return ErrInvalidPublicKey } var crc uint16 end := n - 2 sum := raw[end:n] checksum := bytes.NewReader(sum) if err := binary.Read(checksum, binary.LittleEndian, &crc); err != nil { return err } // ensure checksum is valid if err := validate(raw[:end], crc); err != nil { return err } // Copy over, ignore prefix byte. copy(dest, raw[1:end]) return nil } // Only version for now, but could add in X3DH in the future, etc. const XKeyVersionV1 = "xkv1" const vlen = len(XKeyVersionV1) // Seal is compatible with nacl.Box.Seal() and can be used in similar situations for small messages. // We generate the nonce from crypto rand by default. func (pair *ckp) Seal(input []byte, recipient string) ([]byte, error) { return pair.SealWithRand(input, recipient, rand.Reader) } func (pair *ckp) SealWithRand(input []byte, recipient string, rr io.Reader) ([]byte, error) { var ( rpub [curveKeyLen]byte nonce [curveNonceLen]byte out [vlen + curveNonceLen]byte err error ) if err = decodePubCurveKey(recipient, rpub[:]); err != nil { return nil, ErrInvalidRecipient } if _, err := io.ReadFull(rr, nonce[:]); err != nil { return nil, err } copy(out[:vlen], []byte(XKeyVersionV1)) copy(out[vlen:], nonce[:]) return box.Seal(out[:], input, &nonce, &rpub, &pair.seed), nil } func (pair *ckp) Open(input []byte, sender string) ([]byte, error) { if len(input) <= vlen+curveNonceLen { return nil, ErrInvalidEncrypted } var ( spub [curveKeyLen]byte nonce [curveNonceLen]byte err error ) if !bytes.Equal(input[:vlen], []byte(XKeyVersionV1)) { return nil, ErrInvalidEncVersion } copy(nonce[:], input[vlen:vlen+curveNonceLen]) if err = decodePubCurveKey(sender, spub[:]); err != nil { return nil, ErrInvalidSender } decrypted, ok := box.Open(nil, input[vlen+curveNonceLen:], &nonce, &spub, &pair.seed) if !ok { return nil, ErrCouldNotDecrypt } return decrypted, nil } // Wipe will randomize the contents of the secret key func (pair *ckp) Wipe() { io.ReadFull(rand.Reader, pair.seed[:]) } func (pair *ckp) Sign(_ []byte) ([]byte, error) { return nil, ErrInvalidCurveKeyOperation } func (pair *ckp) Verify(_ []byte, _ []byte) error { return ErrInvalidCurveKeyOperation } nkeys-0.4.7/xkeys_test.go000066400000000000000000000115641454333724000154200ustar00rootroot00000000000000// Copyright 2022-2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package nkeys import ( "bytes" "reflect" "testing" ) func testCurve(t *testing.T, kp KeyPair) { // Check seed seed, err := kp.Seed() if err != nil { t.Fatalf("Unexpected error retrieving seed: %v", err) } _, err = Decode(PrefixByteSeed, seed) if err != nil { t.Fatalf("Expected a proper seed string, got %s with err: %v", seed, err) } if pb, _, err := DecodeSeed(seed); err != nil || pb != PrefixByteCurve { t.Fatalf("Expected a proper seed string, got %s", seed) } // Check public key pub, err := kp.PublicKey() if err != nil { t.Fatalf("Received an error retrieving public key: %v", err) } if pub[0] != 'X' { t.Fatalf("Expected a prefix of 'X' but got %c", pub[0]) } if !IsValidPublicCurveKey(pub) { t.Fatalf("Not a valid public curve key") } // Check private key private, err := kp.PrivateKey() if err != nil { t.Fatalf("Received an error retrieving private key: %v", err) } if private[0] != 'P' { t.Fatalf("Expected a prefix of 'P' but got %v", private[0]) } // Seal rkp, _ := CreateCurveKeys() rpub, _ := rkp.PublicKey() msg := []byte("Hello xkeys!") encrypted, err := kp.Seal(msg, rpub) if err != nil { t.Fatalf("Received an error on Seal: %v", err) } decrypted, err := rkp.Open(encrypted, pub) if err != nil { t.Fatalf("Received an error on Open: %v", err) } if !bytes.Equal(decrypted, msg) { t.Fatalf("Expected %q to be %q", decrypted, msg) } } func TestCurveFromCreateCurveKeys(t *testing.T) { kp, err := CreateCurveKeys() if err != nil { t.Fatalf("Expected nil error on CreateCurveKeys, received %v", err) } if kp == nil { t.Fatal("Expected a non-nil curve key pair") } testCurve(t, kp) } func TestCurveFromCreatePair(t *testing.T) { kp, err := CreatePair(PrefixByteCurve) if err != nil { t.Fatalf("Expected nil error on CreatePair, received %v", err) } if kp == nil { t.Fatal("Expected a non-nil curve key pair") } testCurve(t, kp) } func TestCurveFromSeed(t *testing.T) { kp, _ := CreateCurveKeys() seed, _ := kp.Seed() nkp, err := FromCurveSeed(seed) if err != nil { t.Fatalf("Unexpected error deriving curve keypair: %v", err) } if !reflect.DeepEqual(kp.(*ckp), nkp.(*ckp)) { t.Fatal("Expected the curve pairs to be equal") } testCurve(t, nkp) } func TestCurveFromKeyPair(t *testing.T) { kp, _ := CreatePair(PrefixByteCurve) _, err := kp.Sign([]byte("hello")) if err == nil { t.Fatal("Expected sign to fail as it non supported operation") } if err != ErrInvalidCurveKeyOperation { t.Fatalf("Expected %v but got %v", ErrInvalidCurveKeyOperation, err) } err = kp.Verify([]byte("hello"), []byte("bad")) if err == nil { t.Fatal("Expected verify to fail as it is unsupported operation") } if err != ErrInvalidCurveKeyOperation { t.Fatalf("Expected %v but got %v", ErrInvalidCurveKeyOperation, err) } } func TestCurvePublic(t *testing.T) { kp, _ := CreatePair(PrefixByteCurve) _, err := kp.Sign([]byte("hello")) if err == nil { t.Fatal("Expected sign to fail as it non supported operation") } pk, err := kp.PublicKey() if err != nil { t.Fatalf("Unexpected public key error: %v", err) } pub, err := FromPublicKey(pk) if err != nil { t.Fatalf("Unexpected error when creating public key: %v", err) } _, err = pub.Open([]byte("hello"), "bad") if err != ErrCannotOpen { t.Fatalf("Expected %v but got %v", ErrCannotOpen, err) } _, err = pub.Seal([]byte("hello"), "bad") if err != ErrCannotSeal { t.Fatalf("Expected %v but got %v", ErrCannotSeal, err) } _, err = pub.SealWithRand([]byte("hello"), "bad", nil) if err != ErrCannotSeal { t.Fatalf("Expected %v but got %v", ErrCannotSeal, err) } } func TestCurvePublicEmptyBug(t *testing.T) { kp, _ := CreateCurveKeys() pub, _ := kp.PublicKey() rkp, _ := CreateCurveKeys() rpub, _ := rkp.PublicKey() msg := []byte("Empty public better not work!") encrypted, err := kp.Seal(msg, rpub) if err != nil { t.Fatalf("Received an error on Seal: %v", err) } decrypted, err := rkp.Open(encrypted, pub) if err != nil { t.Fatalf("Received an error on Open: %v", err) } if !bytes.Equal(decrypted, msg) { t.Fatalf("Expected %q to be %q", decrypted, msg) } // Check an empty pub key. var empty [curveKeyLen]byte epub, _ := Encode(PrefixByteCurve, empty[:]) _, err = rkp.Open(encrypted, string(epub)) if err == nil { t.Fatalf("Expected a failure with empty pub key") } }