pax_global_header00006660000000000000000000000064143703764310014522gustar00rootroot0000000000000052 comment=536c9663cc006a2a30a53a6377239997be4a3558 golang-step-crypto-0.24.0/000077500000000000000000000000001437037643100153435ustar00rootroot00000000000000golang-step-crypto-0.24.0/.github/000077500000000000000000000000001437037643100167035ustar00rootroot00000000000000golang-step-crypto-0.24.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001437037643100210665ustar00rootroot00000000000000golang-step-crypto-0.24.0/.github/ISSUE_TEMPLATE/bug-report.yml000066400000000000000000000026761437037643100237120ustar00rootroot00000000000000name: Bug Report description: File a bug report title: "[Bug]: " labels: ["bug", "needs triage"] body: - type: markdown attributes: value: | Thanks for taking the time to fill out this bug report! - type: textarea id: steps attributes: label: Steps to Reproduce description: Tell us how to reproduce this issue. placeholder: These are the steps! validations: required: true - type: textarea id: your-env attributes: label: Your Environment value: |- * OS - * Version - validations: required: true - type: textarea id: expected-behavior attributes: label: Expected Behavior description: What did you expect to happen? validations: required: true - type: textarea id: actual-behavior attributes: label: Actual Behavior description: What happens instead? validations: required: true - type: textarea id: context attributes: label: Additional Context description: Add any other context about the problem here. validations: required: false - type: textarea id: contributing attributes: label: Contributing value: | Vote on this issue by adding a ๐Ÿ‘ reaction. To contribute a fix for this issue, leave a comment (and link to your pull request, if you've opened one already). validations: required: false golang-step-crypto-0.24.0/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000004741437037643100230630ustar00rootroot00000000000000blank_issues_enabled: true contact_links: - name: Ask on Discord url: https://discord.gg/7xgjhVAg6g about: You can ask for help here! - name: Want to contribute to step crypto? url: https://github.com/smallstep/cli/blob/master/docs/CONTRIBUTING.md about: Be sure to read contributing guidelines! golang-step-crypto-0.24.0/.github/ISSUE_TEMPLATE/documentation-request.md000066400000000000000000000006531437037643100257530ustar00rootroot00000000000000--- name: Documentation Request about: Request documentation for a feature title: '' labels: docs, needs triage assignees: '' --- golang-step-crypto-0.24.0/.github/ISSUE_TEMPLATE/enhancement.md000066400000000000000000000003001437037643100236660ustar00rootroot00000000000000--- name: Enhancement about: Suggest an enhancement to step crypto title: '' labels: enhancement, needs triage assignees: '' --- ### What would you like to be added ### Why this is needed golang-step-crypto-0.24.0/.github/PULL_REQUEST_TEMPLATE000066400000000000000000000010771437037643100221120ustar00rootroot00000000000000 #### Name of feature: #### Pain or issue this feature alleviates: #### Why is this important to the project (if not answered above): #### Is there documentation on how to use this feature? If so, where? #### In what environments or workflows is this feature supported? #### In what environments or workflows is this feature explicitly NOT supported (if any)? #### Supporting links/other PRs/issues: ๐Ÿ’”Thank you! golang-step-crypto-0.24.0/.github/dependabot.yml000066400000000000000000000007701437037643100215370ustar00rootroot00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "gomod" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" golang-step-crypto-0.24.0/.github/workflows/000077500000000000000000000000001437037643100207405ustar00rootroot00000000000000golang-step-crypto-0.24.0/.github/workflows/ci.yml000066400000000000000000000006601437037643100220600ustar00rootroot00000000000000name: CI on: push: tags-ignore: - 'v*' branches: - "master" pull_request: workflow_call: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: ci: uses: smallstep/workflows/.github/workflows/goCI.yml@main with: os-dependencies: "libpcsclite-dev" run-build: false run-codeql: true secrets: inherit golang-step-crypto-0.24.0/.github/workflows/code-scan-cron.yml000066400000000000000000000003141437037643100242540ustar00rootroot00000000000000on: schedule: - cron: '0 0 * * *' jobs: code-scan: uses: smallstep/workflows/.github/workflows/code-scan.yml@main secrets: GITLEAKS_LICENSE_KEY: ${{ secrets.GITLEAKS_LICENSE_KEY }} golang-step-crypto-0.24.0/.github/workflows/dependabot-auto-merge.yml000066400000000000000000000011301437037643100256260ustar00rootroot00000000000000name: Dependabot auto-merge on: pull_request permissions: contents: write pull-requests: write jobs: dependabot: runs-on: ubuntu-latest if: ${{ github.actor == 'dependabot[bot]' }} steps: - name: Dependabot metadata id: metadata uses: dependabot/fetch-metadata@v1.1.1 with: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Enable auto-merge for Dependabot PRs run: gh pr merge --auto --merge "$PR_URL" env: PR_URL: ${{github.event.pull_request.html_url}} GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} golang-step-crypto-0.24.0/.github/workflows/release.yml000066400000000000000000000017611437037643100231100ustar00rootroot00000000000000name: Create Release on: push: # Sequence of patterns matched against refs/tags tags: - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 jobs: ci: uses: smallstep/crypto/.github/workflows/ci.yml@master secrets: inherit create_release: name: Create Release needs: ci runs-on: ubuntu-latest steps: - name: Is Pre-release id: is_prerelease run: | set +e echo ${{ github.ref }} | grep "\-rc.*" OUT=$? if [ $OUT -eq 0 ]; then IS_PRERELEASE=true; else IS_PRERELEASE=false; fi echo "IS_PRERELEASE=${IS_PRERELEASE}" >> ${GITHUB_OUTPUT} - name: Create Release id: create_release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ github.ref }} release_name: Release ${{ github.ref }} draft: false prerelease: ${{ steps.is_prerelease.outputs.IS_PRERELEASE }} golang-step-crypto-0.24.0/.github/workflows/triage.yml000066400000000000000000000004051437037643100227350ustar00rootroot00000000000000name: Add Issues and PRs to Triage on: issues: types: - opened - reopened pull_request_target: types: - opened - reopened jobs: triage: uses: smallstep/workflows/.github/workflows/triage.yml@main secrets: inherit golang-step-crypto-0.24.0/.gitignore000066400000000000000000000004151437037643100173330ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ golang-step-crypto-0.24.0/LICENSE000066400000000000000000000261351437037643100163570ustar00rootroot00000000000000 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. golang-step-crypto-0.24.0/Makefile000066400000000000000000000023531437037643100170060ustar00rootroot00000000000000# Set V to 1 for verbose output from the Makefile Q=$(if $V,,@) SRC=$(shell find . -type f -name '*.go') all: lint test ci: test .PHONY: all ci ######################################### # Bootstrapping ######################################### bootstra%: $Q curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $$(go env GOPATH)/bin latest $Q go install golang.org/x/vuln/cmd/govulncheck@latest $Q go install gotest.tools/gotestsum@latest .PHONY: bootstrap ######################################### # Test ######################################### test: $Q $(GOFLAGS) gotestsum -- -coverprofile=coverage.out -short -covermode=atomic ./... race: $Q $(GOFLAGS) gotestsum -- -race ./... .PHONY: test race ######################################### # Linting ######################################### fmt: $Q goimports -l -w $(SRC) lint: SHELL:=/bin/bash lint: $Q LOG_LEVEL=error golangci-lint run --config <(curl -s https://raw.githubusercontent.com/smallstep/workflows/master/.golangci.yml) --timeout=30m $Q govulncheck ./... .PHONY: fmt lint ######################################### # Go generate ######################################### generate: $Q go generate ./... .PHONY: generate golang-step-crypto-0.24.0/README.md000066400000000000000000000053211437037643100166230ustar00rootroot00000000000000# crypto [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Go Report Card](https://goreportcard.com/badge/github.com/smallstep/crypto)](https://goreportcard.com/report/github.com/smallstep/crypto) [![CI](https://github.com/smallstep/crypto/actions/workflows/ci.yml/badge.svg)](https://github.com/smallstep/crypto/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/smallstep/crypto/branch/master/graph/badge.svg)](https://codecov.io/gh/smallstep/crypto) [![Documentation](https://godoc.org/go.step.sm/crypto?status.svg)](https://pkg.go.dev/mod/go.step.sm/crypto) Crypto is a collection of packages used in [smallstep](https://smallstep.com) products. See: * [step](https://github.com/smallstep/cli): A zero trust swiss army knife for working with X509, OAuth, JWT, OATH OTP, etc. * [step-ca](https://github.com/smallstep/certificates): A private certificate authority (X.509 & SSH) & ACME server for secure automated certificate management, so you can use TLS everywhere & SSO for SSH. ## Usage To add this to a project just run: ```sh go get go.step.sm/crypto ``` ## Packages ### x509util Package `x509util` implements utilities to build X.509 certificates based on JSON templates. ### sshutil Package `sshutil` implements utilities to build SSH certificates based on JSON templates. ### keyutil Package `keyutil` implements utilities to generate cryptographic keys. ### pemutil Package `pemutil` implements utilities to parse keys and certificates. It also includes a method to serialize keys, X.509 certificates and certificate requests to PEM. ### randutil Package `randutil` provides methods to generate random strings and salts. ### tlsutil Package `tlsutil` provides utilities to configure tls client and servers. ### jose Package `jose` is a wrapper for `gopkg.in/square/go-jose.v2` and implements utilities to parse and generate JWT, JWK and JWKSets. ### x25519 Package `x25519` adds support for X25519 keys and the [XEdDSA](https://signal.org/docs/specifications/xeddsa/) signature scheme. ### minica Package `minica` implements a simple certificate authority. ### kms Package `kms` implements interfaces to perform cryptographic operations like signing certificates using cloud-based key management systems, PKCS #11 modules, or just a YubiKey or an ssh-agent. On the cloud it supports: * [Amazon AWS KMS](https://aws.amazon.com/kms/) * [Google Cloud Key Management](https://cloud.google.com/security-key-management) * [Microsoft Azure Key Vault](https://azure.microsoft.com/en-us/services/key-vault/) ### fingerprint Package `fingerprint` provides methods for creating and encoding X.509 certificate, SSH certificate and SSH key fingerprints. golang-step-crypto-0.24.0/SECURITY.md000066400000000000000000000006701437037643100171370ustar00rootroot00000000000000We appreciate any effort to discover and disclose security vulnerabilities responsibly. If you would like to report a vulnerability in one of our projects, or have security concerns regarding Smallstep software, please email security@smallstep.com. In order for us to best respond to your report, please include any of the following: * Steps to reproduce or proof-of-concept * Any relevant tools, including versions used * Tool output golang-step-crypto-0.24.0/fingerprint/000077500000000000000000000000001437037643100176725ustar00rootroot00000000000000golang-step-crypto-0.24.0/fingerprint/fingerprint.go000066400000000000000000000045171437037643100225570ustar00rootroot00000000000000package fingerprint import ( "crypto" "encoding/base64" "encoding/hex" "fmt" "strings" "go.step.sm/crypto/internal/emoji" ) // Encoding defines the supported encodings for certificates and key // fingerprints. // // This type is the base for sshutil.FingerprintEncoding and // x509util.FingerprintEncoding types. type Encoding int const ( // HexFingerprint represents the hex encoding of the fingerprint. // // This is the default encoding for an X.509 certificate. HexFingerprint Encoding = iota + 1 // Base64Fingerprint represents the base64 encoding of the fingerprint. // // This is the default encoding for a public key. Base64Fingerprint // Base64URLFingerprint represents the base64URL encoding of the fingerprint. Base64URLFingerprint // Base64RawFingerprint represents the base64RawStd encoding of the // fingerprint. // // This is the default encoding for an SSH key and certificate. Base64RawFingerprint // Base64RawURLFingerprint represents the base64RawURL encoding of the fingerprint. Base64RawURLFingerprint // EmojiFingerprint represents the emoji encoding of the fingerprint. EmojiFingerprint ) // New creates a fingerprint of the given data by hashing it and returns it in // the encoding format. func New(data []byte, h crypto.Hash, encoding Encoding) (string, error) { if !h.Available() { return "", fmt.Errorf("hash function %q is not available", h.String()) } hash := h.New() if _, err := hash.Write(data); err != nil { return "", fmt.Errorf("error creating hash: %w", err) } fp := Fingerprint(hash.Sum(nil), encoding) if fp == "" { return "", fmt.Errorf("unknown encoding value %d", encoding) } return fp, nil } // Fingerprint encodes the given digest using the encoding format. If an invalid // encoding is passed, the return value will be an empty string. func Fingerprint(digest []byte, encoding Encoding) string { switch encoding { case HexFingerprint: return strings.ToLower(hex.EncodeToString(digest)) case Base64Fingerprint: return base64.StdEncoding.EncodeToString(digest) case Base64URLFingerprint: return base64.URLEncoding.EncodeToString(digest) case Base64RawFingerprint: return base64.RawStdEncoding.EncodeToString(digest) case Base64RawURLFingerprint: return base64.RawURLEncoding.EncodeToString(digest) case EmojiFingerprint: return emoji.Emoji(digest) default: return "" } } golang-step-crypto-0.24.0/fingerprint/fingerprint_test.go000066400000000000000000000054211437037643100236110ustar00rootroot00000000000000package fingerprint import ( "crypto" "errors" "hash" "testing" ) type failHash struct{} func (h failHash) Write(b []byte) (int, error) { return 0, errors.New("failed to write") } func (h failHash) Sum(b []byte) []byte { return nil } func (h failHash) Reset() {} func (h failHash) Size() int { return 32 } func (h failHash) BlockSize() int { return 32 } func TestNew(t *testing.T) { data := []byte(`Lorem ipsum dolor sit amet`) // This overwrites MD4 with failHash crypto.RegisterHash(1, func() hash.Hash { return failHash{} }) type args struct { data []byte h crypto.Hash encoding Encoding } tests := []struct { name string args args want string wantErr bool }{ {"sha256", args{data, crypto.SHA256, HexFingerprint}, "16aba5393ad72c0041f5600ad3c2c52ec437a2f0c7fc08fadfc3c0fe9641d7a3", false}, {"unavailable", args{data, crypto.Hash(1000), HexFingerprint}, "", true}, {"fail encoding", args{data, crypto.SHA256, Encoding(1000)}, "", true}, {"fail write", args{data, crypto.Hash(1), HexFingerprint}, "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := New(tt.args.data, tt.args.h, tt.args.encoding) if (err != nil) != tt.wantErr { t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("New() = %v, want %v", got, tt.want) } }) } } func TestFingerprint(t *testing.T) { digest := []byte{ 0x38, 0x01, 0x16, 0x21, 0xec, 0xdc, 0xc2, 0x17, 0x2e, 0x93, 0x3a, 0x1e, 0xf2, 0x31, 0x7e, 0xfc, 0x53, 0x5a, 0x16, 0x1c, 0x00, 0x33, 0x3a, 0xee, 0x3f, 0x84, 0xab, 0xfa, 0xb4, 0xe6, 0x40, 0xbf, } type args struct { digest []byte encoding Encoding } tests := []struct { name string args args want string }{ {"HexFingerprint", args{digest, HexFingerprint}, "38011621ecdcc2172e933a1ef2317efc535a161c00333aee3f84abfab4e640bf"}, {"Base64Fingerprint", args{digest, Base64Fingerprint}, "OAEWIezcwhcukzoe8jF+/FNaFhwAMzruP4Sr+rTmQL8="}, {"Base64URLFingerprint", args{digest, Base64URLFingerprint}, "OAEWIezcwhcukzoe8jF-_FNaFhwAMzruP4Sr-rTmQL8="}, {"Base64RawFingerprint", args{digest, Base64RawFingerprint}, "OAEWIezcwhcukzoe8jF+/FNaFhwAMzruP4Sr+rTmQL8"}, {"Base64RawURLFingerprint", args{digest, Base64RawURLFingerprint}, "OAEWIezcwhcukzoe8jF-_FNaFhwAMzruP4Sr-rTmQL8"}, {"EmojiFingerprint", args{digest, EmojiFingerprint}, "๐Ÿ’จ๐ŸŽฑ๐ŸŒผ๐Ÿ“†๐ŸŽ ๐ŸŽ‰๐ŸŽฟ๐Ÿš™๐Ÿช๐ŸŒŠโ™ฆ๏ธ๐Ÿ’กโœŒ๏ธ๐Ÿฎ๐Ÿ”’โŒ๐Ÿ–•๐Ÿ˜ฌ๐ŸŒผ๐Ÿ‘ฆ๐Ÿ‘๐Ÿ‘‘โ™ฆ๏ธ๐Ÿ‡ฌ๐Ÿ‡ง๐Ÿ‘‚๐Ÿ”ฌ๐Ÿ“Œโ™ฟ๐Ÿš€๐Ÿšœ๐Ÿ†๐Ÿ‘"}, {"Unknown", args{digest, 0}, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := Fingerprint(tt.args.digest, tt.args.encoding); got != tt.want { t.Errorf("Fingerprint() = %v, want %v", got, tt.want) } }) } } golang-step-crypto-0.24.0/go.mod000066400000000000000000000055511437037643100164570ustar00rootroot00000000000000module go.step.sm/crypto go 1.18 require ( cloud.google.com/go/kms v1.8.0 filippo.io/edwards25519 v1.0.0 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible github.com/Azure/go-autorest/autorest v0.11.28 github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 github.com/Azure/go-autorest/autorest/date v0.3.0 github.com/Masterminds/sprig/v3 v3.2.3 github.com/ThalesIgnite/crypto11 v1.2.5 github.com/aws/aws-sdk-go v1.44.195 github.com/go-piv/piv-go v1.10.0 github.com/golang/mock v1.6.0 github.com/googleapis/gax-go/v2 v2.7.0 github.com/pkg/errors v0.9.1 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 github.com/stretchr/testify v1.8.1 golang.org/x/crypto v0.5.0 golang.org/x/net v0.5.0 golang.org/x/sys v0.4.0 google.golang.org/api v0.109.0 google.golang.org/grpc v1.52.3 gopkg.in/square/go-jose.v2 v2.6.0 ) require ( cloud.google.com/go/compute v1.14.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v0.8.0 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect github.com/golang-jwt/jwt/v4 v4.2.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/miekg/pkcs11 v1.0.3 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/spf13/cast v1.4.1 // indirect github.com/thales-e-security/pool v0.0.2 // indirect go.opencensus.io v0.24.0 // indirect golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect golang.org/x/text v0.6.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-step-crypto-0.24.0/go.sum000066400000000000000000000633241437037643100165060ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww= cloud.google.com/go/compute v1.14.0 h1:hfm2+FfxVmnRlh6LpB7cg1ZNU+5edAHmW679JePztk0= cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/iam v0.8.0 h1:E2osAkZzxI/+8pZcxVLcDtAQx/u+hZXVryUaYQ5O0Kk= cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= cloud.google.com/go/kms v1.8.0 h1:VrJLOsMRzW7IqTTYn+OYupqF3iKSE060Nrn+PECrYjg= cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg= cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc= github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM= github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA= github.com/Azure/go-autorest/autorest/adal v0.9.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ= github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 h1:wkAZRgT/pn8HhFyzfe9UnqOjJYqlembgCTi72Bm/xKk= github.com/Azure/go-autorest/autorest/azure/auth v0.5.12/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg= github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 h1:0W/yGmFdTIT77fvdlGZ0LMISoLHFJ7Tx4U0yeB+uFs4= github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac= github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/ThalesIgnite/crypto11 v1.2.5 h1:1IiIIEqYmBvUYFeMnHqRft4bwf/O36jryEUpY+9ef8E= github.com/ThalesIgnite/crypto11 v1.2.5/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE= github.com/aws/aws-sdk-go v1.44.195 h1:d5xFL0N83Fpsq2LFiHgtBUHknCRUPGHdOlCWt/jtOJs= github.com/aws/aws-sdk-go v1.44.195/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/go-piv/piv-go v1.10.0 h1:P1Y1VjBI5DnXW0+YkKmTuh5opWnMIrKriUaIOblee9Q= github.com/go-piv/piv-go v1.10.0/go.mod h1:NZ2zmjVkfFaL/CF8cVQ/pXdXtuj110zEKGdJM6fJZZM= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.2.1 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg= github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.0.3 h1:iMwmD7I5225wv84WxIG/bmxz9AXjWvTWIbM/TYHvWtw= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg= github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.109.0 h1:sW9hgHyX497PP5//NUM7nqfV8D0iDfBApqq7sOh1XR8= google.golang.org/api v0.109.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef h1:uQ2vjV/sHTsWSqdKeLqmwitzgvjMl7o4IdtHwUDXSJY= google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.52.3 h1:pf7sOysg4LdgBqduXveGKrcEwbStiK2rtfghdzlUYDQ= google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= golang-step-crypto-0.24.0/internal/000077500000000000000000000000001437037643100171575ustar00rootroot00000000000000golang-step-crypto-0.24.0/internal/bcrypt_pbkdf/000077500000000000000000000000001437037643100216305ustar00rootroot00000000000000golang-step-crypto-0.24.0/internal/bcrypt_pbkdf/LICENSE000066400000000000000000000025411437037643100226370ustar00rootroot00000000000000Copyright (c) 2014 Dmitry Chestnykh Copyright (c) 2010 The Go Authors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. golang-step-crypto-0.24.0/internal/bcrypt_pbkdf/README000066400000000000000000000012641437037643100225130ustar00rootroot00000000000000Go implementation of bcrypt_pbkdf(3) from OpenBSD (a variant of PBKDF2 with bcrypt-based PRF). USAGE func Key(password, salt []byte, rounds, keyLen int) ([]byte, error) Key derives a key from the password, salt and rounds count, returning a []byte of length keyLen that can be used as cryptographic key. Remember to get a good random salt of at least 16 bytes. Using a higher rounds count will increase the cost of an exhaustive search but will also make derivation proportionally slower. REFERENCES * https://github.com/dchest/bcrypt_pbkdf * http://www.tedunangst.com/flak/post/bcrypt-pbkdf * http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/lib/libutil/bcrypt_pbkdf.c golang-step-crypto-0.24.0/internal/bcrypt_pbkdf/bcrypt_pbkdf.go000066400000000000000000000054061437037643100246350ustar00rootroot00000000000000// Copyright 2014 Dmitry Chestnykh. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package bcrypt_pbkdf implements password-based key derivation function based // on bcrypt compatible with bcrypt_pbkdf(3) from OpenBSD. // //nolint:revive,stylecheck // ignore underscore in package package bcrypt_pbkdf import ( "crypto/sha512" "errors" // NOTE! Requires blowfish package version from Aug 1, 2014 or later. // Will produce incorrect results if the package is older. // See commit message for details: http://goo.gl/wx6g8O //nolint:staticcheck // needs insecure package "golang.org/x/crypto/blowfish" ) // Key derives a key from the password, salt and rounds count, returning a // []byte of length keyLen that can be used as cryptographic key. // // Remember to get a good random salt of at least 16 bytes. Using a higher // rounds count will increase the cost of an exhaustive search but will also // make derivation proportionally slower. func Key(password, salt []byte, rounds, keyLen int) ([]byte, error) { if rounds < 1 { return nil, errors.New("bcrypt_pbkdf: number of rounds is too small") } if len(password) == 0 { return nil, errors.New("bcrypt_pbkdf: empty password") } if len(salt) == 0 || len(salt) > 1<<20 { return nil, errors.New("bcrypt_pbkdf: bad salt length") } if keyLen > 1024 { return nil, errors.New("bcrypt_pbkdf: keyLen is too large") } var shapass, shasalt [sha512.Size]byte var out, tmp [32]byte var cnt [4]byte numBlocks := (keyLen + len(out) - 1) / len(out) key := make([]byte, numBlocks*len(out)) h := sha512.New() h.Write(password) h.Sum(shapass[:0]) for block := 1; block <= numBlocks; block++ { h.Reset() h.Write(salt) cnt[0] = byte(block >> 24) cnt[1] = byte(block >> 16) cnt[2] = byte(block >> 8) cnt[3] = byte(block) h.Write(cnt[:]) bcryptHash(tmp[:], shapass[:], h.Sum(shasalt[:0])) copy(out[:], tmp[:]) for i := 2; i <= rounds; i++ { h.Reset() h.Write(tmp[:]) bcryptHash(tmp[:], shapass[:], h.Sum(shasalt[:0])) for j := 0; j < len(out); j++ { out[j] ^= tmp[j] } } for i, v := range out { key[i*numBlocks+(block-1)] = v } } return key[:keyLen], nil } var magic = []byte("OxychromaticBlowfishSwatDynamite") func bcryptHash(out, shapass, shasalt []byte) { c, err := blowfish.NewSaltedCipher(shapass, shasalt) if err != nil { panic(err) } for i := 0; i < 64; i++ { blowfish.ExpandKey(shasalt, c) blowfish.ExpandKey(shapass, c) } copy(out, magic) for i := 0; i < 32; i += 8 { for j := 0; j < 64; j++ { c.Encrypt(out[i:i+8], out[i:i+8]) } } // Swap bytes due to different endianness. for i := 0; i < 32; i += 4 { out[i+3], out[i+2], out[i+1], out[i] = out[i], out[i+1], out[i+2], out[i+3] } } golang-step-crypto-0.24.0/internal/bcrypt_pbkdf/bcrypt_pbkdf_test.go000066400000000000000000000051761437037643100257000ustar00rootroot00000000000000// Copyright 2014 Dmitry Chestnykh. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // //nolint:revive,stylecheck // ignore underscore in package package bcrypt_pbkdf import ( "bytes" "testing" ) // Test vectors generated by the reference implementation from OpenBSD. var golden = []struct { rounds int password, salt, result []byte }{ { 12, []byte("password"), []byte("salt"), []byte{ 0x1a, 0xe4, 0x2c, 0x05, 0xd4, 0x87, 0xbc, 0x02, 0xf6, 0x49, 0x21, 0xa4, 0xeb, 0xe4, 0xea, 0x93, 0xbc, 0xac, 0xfe, 0x13, 0x5f, 0xda, 0x99, 0x97, 0x4c, 0x06, 0xb7, 0xb0, 0x1f, 0xae, 0x14, 0x9a, }, }, { 3, []byte("passwordy\x00PASSWORD\x00"), []byte("salty\x00SALT\x00"), []byte{ 0x7f, 0x31, 0x0b, 0xd3, 0xe7, 0x8c, 0x32, 0x80, 0xc5, 0x9c, 0xe4, 0x59, 0x52, 0x11, 0xa2, 0x92, 0x8e, 0x8d, 0x4e, 0xc7, 0x44, 0xc1, 0xed, 0x2e, 0xfc, 0x9f, 0x76, 0x4e, 0x33, 0x88, 0xe0, 0xad, }, }, { // See http://thread.gmane.org/gmane.os.openbsd.bugs/20542 8, []byte("ัะตะบั€ะตั‚ะฝะพะต ัะปะพะฒะพ"), []byte("ะฟะพัะพะปะธั‚ัŒ ะฝะตะผะฝะพะถะบะพ"), []byte{ 0x8d, 0xf4, 0x3f, 0xc6, 0xfe, 0x13, 0x1f, 0xc4, 0x7f, 0x0c, 0x9e, 0x39, 0x22, 0x4b, 0xd9, 0x4c, 0x70, 0xb6, 0xfc, 0xc8, 0xee, 0x81, 0x35, 0xfa, 0xdd, 0xf6, 0x11, 0x56, 0xe6, 0xcb, 0x27, 0x33, 0xea, 0x76, 0x5f, 0x31, 0x5a, 0x3e, 0x1e, 0x4a, 0xfc, 0x35, 0xbf, 0x86, 0x87, 0xd1, 0x89, 0x25, 0x4c, 0x1e, 0x05, 0xa6, 0xfe, 0x80, 0xc0, 0x61, 0x7f, 0x91, 0x83, 0xd6, 0x72, 0x60, 0xd6, 0xa1, 0x15, 0xc6, 0xc9, 0x4e, 0x36, 0x03, 0xe2, 0x30, 0x3f, 0xbb, 0x43, 0xa7, 0x6a, 0x64, 0x52, 0x3f, 0xfd, 0xa6, 0x86, 0xb1, 0xd4, 0x51, 0x85, 0x43, }, }, } func TestKey(t *testing.T) { for i, v := range golden { k, err := Key(v.password, v.salt, v.rounds, len(v.result)) if err != nil { t.Errorf("%d: %s", i, err) continue } if !bytes.Equal(k, v.result) { t.Errorf("%d: expected\n%x\n, got\n%x\n", i, v.result, k) } } } func TestBcryptHash(t *testing.T) { good := []byte{ 0x87, 0x90, 0x48, 0x70, 0xee, 0xf9, 0xde, 0xdd, 0xf8, 0xe7, 0x61, 0x1a, 0x14, 0x01, 0x06, 0xe6, 0xaa, 0xf1, 0xa3, 0x63, 0xd9, 0xa2, 0xc5, 0x04, 0xdb, 0x35, 0x64, 0x43, 0x72, 0x1e, 0xb5, 0x55, } var pass, salt [64]byte var result [32]byte for i := 0; i < 64; i++ { pass[i] = byte(i) salt[i] = byte(i + 64) } bcryptHash(result[:], pass[:], salt[:]) if !bytes.Equal(result[:], good) { t.Errorf("expected %x, got %x", good, result) } } func BenchmarkKey(b *testing.B) { pass := []byte("password") salt := []byte("salt") for i := 0; i < b.N; i++ { Key(pass, salt, 10, 32) } } golang-step-crypto-0.24.0/internal/emoji/000077500000000000000000000000001437037643100202625ustar00rootroot00000000000000golang-step-crypto-0.24.0/internal/emoji/emoji.go000066400000000000000000000346161437037643100217260ustar00rootroot00000000000000package emoji import "strings" func Emoji(input []byte) string { var b strings.Builder for _, r := range input { b.WriteString(emojiCodeMap[r]) } return b.String() } // emojiCodeMap is a mapping from byte to emoji. // // The mapping is based on draft+2 of https://github.com/emojisum/emojisum. // (see: https://github.com/emojisum/emojisum/releases/tag/draft%2B2) var emojiCodeMap = []string{ "\U0001f44d", // ๐Ÿ‘ :+1: "\U0001f3b1", // ๐ŸŽฑ :8ball: "\u2708\ufe0f", // โœˆ๏ธ :airplane: "\U0001f47d", // ๐Ÿ‘ฝ :alien: "\u2693", // โš“ :anchor: "\U0001f47c", // ๐Ÿ‘ผ :angel: "\U0001f620", // ๐Ÿ˜  :angry: "\U0001f41c", // ๐Ÿœ :ant: "\U0001f34e", // ๐ŸŽ :apple: "\U0001f3a8", // ๐ŸŽจ :art: "\U0001f476", // ๐Ÿ‘ถ :baby: "\U0001f37c", // ๐Ÿผ :baby_bottle: "\U0001f519", // ๐Ÿ”™ :back: "\U0001f38d", // ๐ŸŽ :bamboo: "\U0001f34c", // ๐ŸŒ :banana: "\U0001f488", // ๐Ÿ’ˆ :barber: "\U0001f6c1", // ๐Ÿ› :bathtub: "\U0001f37a", // ๐Ÿบ :beer: "\U0001f514", // ๐Ÿ”” :bell: "\U0001f6b4\u200d\u2642\ufe0f", // ๐Ÿšดโ€โ™‚๏ธ :bicyclist: "\U0001f426", // ๐Ÿฆ :bird: "\U0001f382", // ๐ŸŽ‚ :birthday: "\U0001f33c", // ๐ŸŒผ :blossom: "\U0001f699", // ๐Ÿš™ :blue_car: "\U0001f417", // ๐Ÿ— :boar: "\U0001f4a3", // ๐Ÿ’ฃ :bomb: "\U0001f4a5", // ๐Ÿ’ฅ :boom: "\U0001f647\u200d\u2642\ufe0f", // ๐Ÿ™‡โ€โ™‚๏ธ :bow: "\U0001f466", // ๐Ÿ‘ฆ :boy: "\U0001f494", // ๐Ÿ’” :broken_heart: "\U0001f4a1", // ๐Ÿ’ก :bulb: "\U0001f68c", // ๐ŸšŒ :bus: "\U0001f335", // ๐ŸŒต :cactus: "\U0001f4c6", // ๐Ÿ“† :calendar: "\U0001f4f7", // ๐Ÿ“ท :camera: "\U0001f36c", // ๐Ÿฌ :candy: "\U0001f431", // ๐Ÿฑ :cat: "\U0001f352", // ๐Ÿ’ :cherries: "\U0001f6b8", // ๐Ÿšธ :children_crossing: "\U0001f36b", // ๐Ÿซ :chocolate_bar: "\U0001f44f", // ๐Ÿ‘ :clap: "\u2601\ufe0f", // โ˜๏ธ :cloud: "\u2663\ufe0f", // โ™ฃ๏ธ :clubs: "\U0001f1e8\U0001f1f3", // ๐Ÿ‡จ๐Ÿ‡ณ :cn: "\u2615", // โ˜• :coffee: "\U0001f6a7", // ๐Ÿšง :construction: "\U0001f36a", // ๐Ÿช :cookie: "\u00a9\ufe0f", // ยฉ๏ธ :copyright: "\U0001f33d", // ๐ŸŒฝ :corn: "\U0001f42e", // ๐Ÿฎ :cow: "\U0001f319", // ๐ŸŒ™ :crescent_moon: "\U0001f451", // ๐Ÿ‘‘ :crown: "\U0001f622", // ๐Ÿ˜ข :cry: "\U0001f52e", // ๐Ÿ”ฎ :crystal_ball: "\u27b0", // โžฐ :curly_loop: "\U0001f46f\u200d\u2640\ufe0f", // ๐Ÿ‘ฏโ€โ™€๏ธ :dancers: "\U0001f4a8", // ๐Ÿ’จ :dash: "\U0001f1e9\U0001f1ea", // ๐Ÿ‡ฉ๐Ÿ‡ช :de: "\u2666\ufe0f", // โ™ฆ๏ธ :diamonds: "\U0001f436", // ๐Ÿถ :dog: "\U0001f369", // ๐Ÿฉ :doughnut: "\U0001f409", // ๐Ÿ‰ :dragon: "\U0001f4c0", // ๐Ÿ“€ :dvd: "\U0001f442", // ๐Ÿ‘‚ :ear: "\U0001f346", // ๐Ÿ† :eggplant: "\U0001f418", // ๐Ÿ˜ :elephant: "\U0001f51a", // ๐Ÿ”š :end: "\u2709", // โœ‰ :envelope: "\U0001f1ea\U0001f1f8", // ๐Ÿ‡ช๐Ÿ‡ธ :es: "\U0001f440", // ๐Ÿ‘€ :eyes: "\U0001f44a", // ๐Ÿ‘Š :facepunch: "\U0001f468\u200d\U0001f469\u200d\U0001f466", // ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ :family: "\U0001f3a1", // ๐ŸŽก :ferris_wheel: "\U0001f630", // ๐Ÿ˜ฐ :cold_sweat: "\U0001f525", // ๐Ÿ”ฅ :fire: "\U0001f386", // ๐ŸŽ† :fireworks: "\U0001f4be", // ๐Ÿ’พ :floppy_disk: "\U0001f3c8", // ๐Ÿˆ :football: "\U0001f374", // ๐Ÿด :fork_and_knife: "\U0001f340", // ๐Ÿ€ :four_leaf_clover: "\U0001f1eb\U0001f1f7", // ๐Ÿ‡ซ๐Ÿ‡ท :fr: "\U0001f35f", // ๐ŸŸ :fries: "\U0001f95c", // ๐Ÿฅœ :peanuts: "\U0001f595", // ๐Ÿ–• :fu: "\U0001f315", // ๐ŸŒ• :full_moon: "\U0001f3b2", // ๐ŸŽฒ :game_die: "\U0001f1ea\U0001f1fa", // ๐Ÿ‡ช๐Ÿ‡บ :eu: "\U0001f48e", // ๐Ÿ’Ž :gem: "\U0001f467", // ๐Ÿ‘ง :girl: "\U0001f410", // ๐Ÿ :goat: "\U0001f62c", // ๐Ÿ˜ฌ :grimacing: "\U0001f601", // ๐Ÿ˜ :grin: "\U0001f482\u200d\u2642\ufe0f", // ๐Ÿ’‚โ€โ™‚๏ธ :guardsman: "\U0001f3b8", // ๐ŸŽธ :guitar: "\U0001f52b", // ๐Ÿ”ซ :gun: "\U0001f354", // ๐Ÿ” :hamburger: "\U0001f528", // ๐Ÿ”จ :hammer: "\U0001f439", // ๐Ÿน :hamster: "\U0001f649", // ๐Ÿ™‰ :hear_no_evil: "\u2764\ufe0f", // โค๏ธ :heart: "\U0001f63b", // ๐Ÿ˜ป :heart_eyes_cat: "\u2763\ufe0f", // โฃ๏ธ :heavy_heart_exclamation: "\u2714\ufe0f", // โœ”๏ธ :heavy_check_mark: "\U0001f5ff", // ๐Ÿ—ฟ :moyai: "\U0001f3ee", // ๐Ÿฎ :izakaya_lantern: "\U0001f681", // ๐Ÿš :helicopter: "\U0001f52a", // ๐Ÿ”ช :hocho: "\U0001f41d", // ๐Ÿ :honeybee: "\U0001f434", // ๐Ÿด :horse: "\U0001f3c7", // ๐Ÿ‡ :horse_racing: "\u231b", // โŒ› :hourglass: "\U0001f3e0", // ๐Ÿ  :house: "\U0001f575\ufe0f\u200d\u2640\ufe0f", // ๐Ÿ•ต๏ธโ€โ™€๏ธ :female_detective: "\U0001f366", // ๐Ÿฆ :icecream: "\U0001f47f", // ๐Ÿ‘ฟ :imp: "\U0001f1ee\U0001f1f9", // ๐Ÿ‡ฎ๐Ÿ‡น :it: "\U0001f383", // ๐ŸŽƒ :jack_o_lantern: "\U0001f47a", // ๐Ÿ‘บ :japanese_goblin: "\U0001f1ef\U0001f1f5", // ๐Ÿ‡ฏ๐Ÿ‡ต :jp: "\U0001f511", // ๐Ÿ”‘ :key: "\U0001f48b", // ๐Ÿ’‹ :kiss: "\U0001f63d", // ๐Ÿ˜ฝ :kissing_cat: "\U0001f428", // ๐Ÿจ :koala: "\U0001f1f0\U0001f1f7", // ๐Ÿ‡ฐ๐Ÿ‡ท :kr: "\U0001f34b", // ๐Ÿ‹ :lemon: "\U0001f484", // ๐Ÿ’„ :lipstick: "\U0001f512", // ๐Ÿ”’ :lock: "\U0001f36d", // ๐Ÿญ :lollipop: "\U0001f468", // ๐Ÿ‘จ :man: "\U0001f341", // ๐Ÿ :maple_leaf: "\U0001f637", // ๐Ÿ˜ท :mask: "\U0001f918", // ๐Ÿค˜ :metal: "\U0001f52c", // ๐Ÿ”ฌ :microscope: "\U0001f4b0", // ๐Ÿ’ฐ :moneybag: "\U0001f412", // ๐Ÿ’ :monkey: "\U0001f5fb", // ๐Ÿ—ป :mount_fuji: "\U0001f4aa", // ๐Ÿ’ช :muscle: "\U0001f344", // ๐Ÿ„ :mushroom: "\U0001f3b9", // ๐ŸŽน :musical_keyboard: "\U0001f3bc", // ๐ŸŽผ :musical_score: "\U0001f485", // ๐Ÿ’… :nail_care: "\U0001f311", // ๐ŸŒ‘ :new_moon: "\u26d4", // โ›” :no_entry: "\U0001f443", // ๐Ÿ‘ƒ :nose: "\U0001f39b\ufe0f", // ๐ŸŽ›๏ธ :control_knobs: "\U0001f529", // ๐Ÿ”ฉ :nut_and_bolt: "\u2b55", // โญ• :o: "\U0001f30a", // ๐ŸŒŠ :ocean: "\U0001f44c", // ๐Ÿ‘Œ :ok_hand: "\U0001f51b", // ๐Ÿ”› :on: "\U0001f4e6", // ๐Ÿ“ฆ :package: "\U0001f334", // ๐ŸŒด :palm_tree: "\U0001f43c", // ๐Ÿผ :panda_face: "\U0001f4ce", // ๐Ÿ“Ž :paperclip: "\u26c5", // โ›… :partly_sunny: "\U0001f6c2", // ๐Ÿ›‚ :passport_control: "\U0001f43e", // ๐Ÿพ :paw_prints: "\U0001f351", // ๐Ÿ‘ :peach: "\U0001f427", // ๐Ÿง :penguin: "\u260e\ufe0f", // โ˜Ž๏ธ :phone: "\U0001f437", // ๐Ÿท :pig: "\U0001f48a", // ๐Ÿ’Š :pill: "\U0001f34d", // ๐Ÿ :pineapple: "\U0001f355", // ๐Ÿ• :pizza: "\U0001f448", // ๐Ÿ‘ˆ :point_left: "\U0001f449", // ๐Ÿ‘‰ :point_right: "\U0001f4a9", // ๐Ÿ’ฉ :poop: "\U0001f357", // ๐Ÿ— :poultry_leg: "\U0001f64f", // ๐Ÿ™ :pray: "\U0001f478", // ๐Ÿ‘ธ :princess: "\U0001f45b", // ๐Ÿ‘› :purse: "\U0001f4cc", // ๐Ÿ“Œ :pushpin: "\U0001f430", // ๐Ÿฐ :rabbit: "\U0001f308", // ๐ŸŒˆ :rainbow: "\u270b", // โœ‹ :raised_hand: "\u267b\ufe0f", // โ™ป๏ธ :recycle: "\U0001f697", // ๐Ÿš— :red_car: "\u00ae\ufe0f", // ยฎ๏ธ :registered: "\U0001f380", // ๐ŸŽ€ :ribbon: "\U0001f35a", // ๐Ÿš :rice: "\U0001f680", // ๐Ÿš€ :rocket: "\U0001f3a2", // ๐ŸŽข :roller_coaster: "\U0001f413", // ๐Ÿ“ :rooster: "\U0001f1f7\U0001f1fa", // ๐Ÿ‡ท๐Ÿ‡บ :ru: "\u26f5", // โ›ต :sailboat: "\U0001f385", // ๐ŸŽ… :santa: "\U0001f6f0\ufe0f", // ๐Ÿ›ฐ๏ธ :satellite: "\U0001f606", // ๐Ÿ˜† :satisfied: "\U0001f3b7", // ๐ŸŽท :saxophone: "\u2702\ufe0f", // โœ‚๏ธ :scissors: "\U0001f648", // ๐Ÿ™ˆ :see_no_evil: "\U0001f411", // ๐Ÿ‘ :sheep: "\U0001f41a", // ๐Ÿš :shell: "\U0001f45e", // ๐Ÿ‘ž :shoe: "\U0001f3bf", // ๐ŸŽฟ :ski: "\U0001f480", // ๐Ÿ’€ :skull: "\U0001f62a", // ๐Ÿ˜ช :sleepy: "\U0001f604", // ๐Ÿ˜„ :smile: "\U0001f63a", // ๐Ÿ˜บ :smiley_cat: "\U0001f60f", // ๐Ÿ˜ :smirk: "\U0001f6ac", // ๐Ÿšฌ :smoking: "\U0001f40c", // ๐ŸŒ :snail: "\U0001f40d", // ๐Ÿ :snake: "\u2744\ufe0f", // โ„๏ธ :snowflake: "\u26bd", // โšฝ :soccer: "\U0001f51c", // ๐Ÿ”œ :soon: "\U0001f47e", // ๐Ÿ‘พ :space_invader: "\u2660\ufe0f", // โ™ ๏ธ :spades: "\U0001f64a", // ๐Ÿ™Š :speak_no_evil: "\u2b50", // โญ :star: "\u26f2", // โ›ฒ :fountain: "\U0001f5fd", // ๐Ÿ—ฝ :statue_of_liberty: "\U0001f682", // ๐Ÿš‚ :steam_locomotive: "\U0001f33b", // ๐ŸŒป :sunflower: "\U0001f60e", // ๐Ÿ˜Ž :sunglasses: "\u2600\ufe0f", // โ˜€๏ธ :sunny: "\U0001f305", // ๐ŸŒ… :sunrise: "\U0001f3c4\u200d\u2642\ufe0f", // ๐Ÿ„โ€โ™‚๏ธ :surfer: "\U0001f3ca\u200d\u2642\ufe0f", // ๐ŸŠโ€โ™‚๏ธ :swimmer: "\U0001f489", // ๐Ÿ’‰ :syringe: "\U0001f389", // ๐ŸŽ‰ :tada: "\U0001f34a", // ๐ŸŠ :tangerine: "\U0001f695", // ๐Ÿš• :taxi: "\U0001f3be", // ๐ŸŽพ :tennis: "\u26fa", // โ›บ :tent: "\U0001f4ad", // ๐Ÿ’ญ :thought_balloon: "\u2122\ufe0f", // โ„ข๏ธ :tm: "\U0001f6bd", // ๐Ÿšฝ :toilet: "\U0001f445", // ๐Ÿ‘… :tongue: "\U0001f3a9", // ๐ŸŽฉ :tophat: "\U0001f69c", // ๐Ÿšœ :tractor: "\U0001f68e", // ๐ŸšŽ :trolleybus: "\U0001f922", // ๐Ÿคข :nauseated_face: "\U0001f3c6", // ๐Ÿ† :trophy: "\U0001f3ba", // ๐ŸŽบ :trumpet: "\U0001f422", // ๐Ÿข :turtle: "\U0001f3a0", // ๐ŸŽ  :carousel_horse: "\U0001f46d", // ๐Ÿ‘ญ :two_women_holding_hands: "\U0001f1ec\U0001f1e7", // ๐Ÿ‡ฌ๐Ÿ‡ง :uk: "\u2602\ufe0f", // โ˜‚๏ธ :umbrella: "\U0001f513", // ๐Ÿ”“ :unlock: "\U0001f1fa\U0001f1f8", // ๐Ÿ‡บ๐Ÿ‡ธ :us: "\u270c\ufe0f", // โœŒ๏ธ :v: "\U0001f4fc", // ๐Ÿ“ผ :vhs: "\U0001f3bb", // ๐ŸŽป :violin: "\u26a0\ufe0f", // โš ๏ธ :warning: "\U0001f349", // ๐Ÿ‰ :watermelon: "\U0001f44b", // ๐Ÿ‘‹ :wave: "\u3030\ufe0f", // ใ€ฐ๏ธ :wavy_dash: "\U0001f6be", // ๐Ÿšพ :wc: "\u267f", // โ™ฟ :wheelchair: "\U0001f469", // ๐Ÿ‘ฉ :woman: "\u274c", // โŒ :x: "\U0001f60b", // ๐Ÿ˜‹ :yum: "\u26a1", // โšก :zap: "\U0001f4a4", // ๐Ÿ’ค :zzz: } golang-step-crypto-0.24.0/internal/emoji/emoji_test.go000066400000000000000000000007601437037643100227560ustar00rootroot00000000000000package emoji import "testing" func TestEmoji(t *testing.T) { type args struct { input []byte } tests := []struct { name string args args want string }{ {"ok", args{[]byte{0x00, 0x10, 0x50, 0xAA, 0xFF}}, "๐Ÿ‘๐Ÿ›๐Ÿ‡ซ๐Ÿ‡ท๐Ÿ‘›๐Ÿ’ค"}, {"empty", args{[]byte{}}, ""}, {"nil", args{nil}, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := Emoji(tt.args.input); got != tt.want { t.Errorf("Emoji() = %v, want %v", got, tt.want) } }) } } golang-step-crypto-0.24.0/internal/step/000077500000000000000000000000001437037643100201325ustar00rootroot00000000000000golang-step-crypto-0.24.0/internal/step/config.go000066400000000000000000000050131437037643100217250ustar00rootroot00000000000000package step import ( "log" "os" "os/user" "path/filepath" "strings" ) // PathEnv defines the name of the environment variable that can overwrite // the default configuration path. const PathEnv = "STEPPATH" // HomeEnv defines the name of the environment variable that can overwrite the // default home directory. const HomeEnv = "HOME" // stepPath will be populated in init() with the proper STEPPATH. var stepPath string // homePath will be populated in init() with the proper HOME. var homePath string // Path returns the path for the step configuration directory, this is // defined by the environment variable STEPPATH or if this is not set it will // default to '$HOME/.step'. func Path() string { return stepPath } // Home returns the user home directory using the environment variable HOME or // the os/user package. func Home() string { return homePath } // Abs returns the given path relative to the StepPath if it's not an absolute // path, relative to the home directory using the special string "~/", or // relative to the working directory using "./" // // Relative paths like 'certs/root_ca.crt' will be converted to // '$STEPPATH/certs/root_ca.crt', but paths like './certs/root_ca.crt' will be // relative to the current directory. Home relative paths like // ~/certs/root_ca.crt will be converted to '$HOME/certs/root_ca.crt'. And // absolute paths like '/certs/root_ca.crt' will remain the same. func Abs(path string) string { if filepath.IsAbs(path) { return path } // Windows accept both \ and / slashed := filepath.ToSlash(path) switch { case strings.HasPrefix(slashed, "~/"): return filepath.Join(homePath, path[2:]) case strings.HasPrefix(slashed, "./"), strings.HasPrefix(slashed, "../"): if abs, err := filepath.Abs(path); err == nil { return abs } return path default: return filepath.Join(stepPath, path) } } func init() { l := log.New(os.Stderr, "", 0) // Get home path from environment or from the user object. homePath = os.Getenv(HomeEnv) if homePath == "" { if homePath = getUserHomeDir(); homePath == "" { l.Fatalf("Error obtaining home directory, please define environment variable %s.", HomeEnv) } } // Get step path from environment or relative to home. stepPath = os.Getenv(PathEnv) if stepPath == "" { stepPath = filepath.Join(homePath, ".step") } // cleanup homePath = filepath.Clean(homePath) stepPath = filepath.Clean(stepPath) } func getUserHomeDir() string { usr, err := user.Current() if err == nil && usr.HomeDir != "" { return usr.HomeDir } return "" } golang-step-crypto-0.24.0/internal/step/config_test.go000066400000000000000000000037111437037643100227670ustar00rootroot00000000000000package step import ( "os" "os/user" "path/filepath" "testing" ) func mustHome(t *testing.T) string { t.Helper() homePath = os.Getenv(HomeEnv) if homePath != "" { return homePath } usr, err := user.Current() if err == nil && usr.HomeDir != "" { return usr.HomeDir } t.Fatal("error obtaining home directory") return "" } func TestPath(t *testing.T) { tmp := stepPath home := mustHome(t) t.Cleanup(func() { stepPath = tmp }) tests := []struct { name string want string }{ {"default", filepath.Join(home, ".step")}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := Path(); got != tt.want { t.Errorf("Path() = %v, want %v", got, tt.want) } }) } } func TestHome(t *testing.T) { tests := []struct { name string want string }{ {"default", mustHome(t)}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := Home(); got != tt.want { t.Errorf("Home() = %v, want %v", got, tt.want) } }) } } func TestAbs(t *testing.T) { home := mustHome(t) abs, err := filepath.Abs("./foo/bar/zar") if err != nil { t.Fatal(err) } type args struct { path string } tests := []struct { name string args args want string }{ {"abs", args{"/foo/bar/zar"}, "/foo/bar/zar"}, {"home", args{"~/foo/bar/zar"}, filepath.Join(home, "foo", "bar", "zar")}, {"relative", args{"./foo/bar/zar"}, abs}, {"step", args{"foo/bar/zar"}, filepath.Join(home, ".step", "foo", "bar", "zar")}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := Abs(tt.args.path); got != tt.want { t.Errorf("Abs() = %v, want %v", got, tt.want) } }) } } func Test_getUserHomeDir(t *testing.T) { tests := []struct { name string want string }{ {"ok", os.Getenv("HOME")}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := getUserHomeDir(); got != tt.want { t.Errorf("getUserHomeDir() = %v, want %v", got, tt.want) } }) } } golang-step-crypto-0.24.0/internal/templates/000077500000000000000000000000001437037643100211555ustar00rootroot00000000000000golang-step-crypto-0.24.0/internal/templates/funcmap.go000066400000000000000000000012331437037643100231340ustar00rootroot00000000000000package templates import ( "errors" "text/template" "github.com/Masterminds/sprig/v3" ) // GetFuncMap returns the list of functions provided by sprig. It changes the // function "fail" to set the given string, this way we can report template // errors directly to the template without having the wrapper that text/template // adds. // // sprig "env" and "expandenv" functions are removed to avoid the leak of // information. func GetFuncMap(failMessage *string) template.FuncMap { m := sprig.TxtFuncMap() delete(m, "env") delete(m, "expandenv") m["fail"] = func(msg string) (string, error) { *failMessage = msg return "", errors.New(msg) } return m } golang-step-crypto-0.24.0/internal/templates/funcmap_test.go000066400000000000000000000010321437037643100241700ustar00rootroot00000000000000package templates import ( "errors" "testing" ) func Test_GetFuncMap_fail(t *testing.T) { var failMesage string fns := GetFuncMap(&failMesage) fail := fns["fail"].(func(s string) (string, error)) s, err := fail("the fail message") if err == nil { t.Errorf("fail() error = %v, wantErr %v", err, errors.New("the fail message")) } if s != "" { t.Errorf("fail() = \"%s\", want \"the fail message\"", s) } if failMesage != "the fail message" { t.Errorf("fail() message = \"%s\", want \"the fail message\"", failMesage) } } golang-step-crypto-0.24.0/internal/templates/validate.go000066400000000000000000000020771437037643100233030ustar00rootroot00000000000000package templates import ( "encoding/json" "errors" "fmt" "text/template" ) // ValidateTemplate validates a text template results in valid JSON // when it's executed with empty template data. If template execution // results in invalid JSON, the template is invalid. When the template // is valid, it can be used safely. A valid template can still result // in invalid JSON when non-empty template data is provided. func ValidateTemplate(data []byte) error { if len(data) == 0 { return nil } // get the default supported functions var failMessage string funcMap := GetFuncMap(&failMessage) // prepare the template with our template functions _, err := template.New("template").Funcs(funcMap).Parse(string(data)) if err != nil { return fmt.Errorf("error parsing template: %w", err) } return nil } // ValidateTemplateData validates that template data is // valid JSON. func ValidateTemplateData(data []byte) error { if len(data) == 0 { return nil } if ok := json.Valid(data); !ok { return errors.New("error validating json template data") } return nil } golang-step-crypto-0.24.0/internal/templates/validate_test.go000066400000000000000000000137631437037643100243460ustar00rootroot00000000000000package templates import ( "errors" "testing" "github.com/stretchr/testify/assert" ) func TestValidateTemplate(t *testing.T) { tests := []struct { name string data []byte err error }{ { name: "ok/default-leaf-template", data: []byte(`{ "subject": {{ toJson .Subject }}, "sans": {{ toJson .SANs }}, {{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }} "keyUsage": ["keyEncipherment", "digitalSignature"], {{- else }} "keyUsage": ["digitalSignature"], {{- end }} "extKeyUsage": ["serverAuth", "clientAuth"] }`), err: nil, }, { name: "ok/default-x509-iid-template", data: []byte(`{ "subject": {"commonName": {{ toJson .Insecure.CR.Subject.CommonName }}}, {{- if .SANs }} "sans": {{ toJson .SANs }}, {{- else }} "dnsNames": {{ toJson .Insecure.CR.DNSNames }}, "emailAddresses": {{ toJson .Insecure.CR.EmailAddresses }}, "ipAddresses": {{ toJson .Insecure.CR.IPAddresses }}, "uris": {{ toJson .Insecure.CR.URIs }}, {{- end }} {{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }} "keyUsage": ["keyEncipherment", "digitalSignature"], {{- else }} "keyUsage": ["digitalSignature"], {{- end }} "extKeyUsage": ["serverAuth", "clientAuth"] }`), err: nil, }, { name: "ok/default-x509-adobe", data: []byte(`{ "test:": "default-x509-adobe", "subject": {{ toJson .Token.email }}, "sans": {{ toJson .SANs }}, {{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }} "keyUsage": ["dataEncipherment", "digitalSignature", "keyAgreement"], {{- else }} {{ fail "Key type must be RSA. Try again with --kty=RSA" }} {{- end }} "extensions": [{"id": "1.2.840.113583.1.1.10", "value": "BQA="}] }`), err: nil, }, { name: "ok/range-subdomains-regex", data: []byte(`{ {{ range .SANs }} {{ if not (and (regexMatch ".*\\.smallstep\\.com" .Value) (eq .Type "dns")) }} {{ fail "Not a *.smallstep.com host" }} {{ end }} {{ end }} "subject": {{ toJson .Subject }}, "sans": {{ toJson .SANs }}, "keyUsage": ["digitalSignature", "keyEncipherment", "keyAgreement"], "extKeyUsage": ["serverAuth"] }`), err: nil, }, { name: "ok/default-ssh-iid-template", data: []byte(`{ "type": {{ toJson .Type }}, "keyId": {{ toJson .KeyID }}, {{- if .Insecure.CR.Principals }} "principals": {{ toJson .Insecure.CR.Principals }}, {{- else }} "principals": {{ toJson .Principals }}, {{- end }} "extensions": {{ toJson .Extensions }} }`), err: nil, }, { name: "ok/ssh-cr-template", data: []byte(`{ "type": {{ toJson .Insecure.CR.Type }}, "keyId": {{ toJson .Insecure.CR.KeyID }}, "principals": {{ toJson .Insecure.CR.Principals }} {{- if eq .Insecure.CR.Type "user" }} , "extensions": { "permit-X11-forwarding": "", "permit-agent-forwarding": "", "permit-port-forwarding": "", "permit-pty": "", "permit-user-rc": "" } {{- end }} }`), err: nil, }, { name: "ok/ssh-github-token", data: []byte(`{ "type": {{ toJson .Type }}, "keyId": {{ toJson .KeyID }}, "principals": {{ toJson .Principals }}, "criticalOptions": {{ toJson .CriticalOptions }}, {{ if .Token.ghu }} "extensions": { "login@github.com": {{ toJson .Token.ghu }} } {{ else }} "extensions": {{ toJson .Extensions }} {{ end }} }`), err: nil, }, { name: "ok/empty-template", data: []byte(""), err: nil, }, { name: "ok/nil-template", data: nil, err: nil, }, { name: "ok/template-with-nested-property", data: []byte(` {{ if not .Token.ghu.foo }} {{ toJson "token has no GitHub username" }} {{ end }} `), err: nil, }, { name: "fail/template-parsing-unterminated-quoted-string", data: []byte(` {{ if not .Token.ghu }} {{ fail "token has no GitHub username }} {{ end }} `), err: errors.New("error parsing template: template: template:3: unterminated quoted string"), }, { name: "fail/template-parsing-unknown-function", data: []byte(`{ "subject": {{ unknownFunction .Subject }} }`), err: errors.New("error parsing template: template: template:2: function \"unknownFunction\" not defined"), }, { name: "fail/template-parsing-missing-closing-brace", data: []byte(`{ "subject": {{ toJson .Subject }}, "sans": {{ toJson .SANs } }`), err: errors.New("error parsing template: template: template:3: unexpected \"}\" in operand"), }, { name: "ok/json-extraneous-trailing-brace", data: []byte(` { "subject": {{ toJson .Subject }}}, "issuer": {{ toJson .Subject }} } `), err: nil, // NOTE: ideally we would like to catch this, but without validating the JSON, this seems hard }, { name: "ok/json-missing-trailing-comma", data: []byte(`{ "subject": {{ toJson .Subject }} "sans": {{ toJson .SANs }} }`), err: nil, // NOTE: ideally we would like to catch this, but without validating the JSON, this seems hard }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := ValidateTemplate(tt.data) if tt.err != nil { assert.Error(t, err) assert.EqualError(t, err, tt.err.Error()) return } assert.Nil(t, err) }) } } func TestValidateTemplateData(t *testing.T) { tests := []struct { name string data []byte err error }{ { name: "ok", data: []byte(`{ "x": 1, "y": 2, "z": {"a":null, "b":"c"} }`), err: nil, }, { name: "ok empty", data: []byte(`{}`), err: nil, }, { name: "ok nil", data: nil, err: nil, }, { name: "fail/missing-comma-trailing-comma", data: []byte(`{ "x": 1 "y": 2 }`), err: errors.New("error validating json template data"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := ValidateTemplateData(tt.data) if tt.err != nil { assert.Error(t, err) assert.EqualError(t, err, tt.err.Error()) return } assert.Nil(t, err) }) } } golang-step-crypto-0.24.0/internal/utils/000077500000000000000000000000001437037643100203175ustar00rootroot00000000000000golang-step-crypto-0.24.0/internal/utils/io.go000066400000000000000000000025451437037643100212630ustar00rootroot00000000000000package utils import ( "bytes" "os" "unicode" "github.com/pkg/errors" ) func maybeUnwrap(err error) error { if wrapped := errors.Unwrap(err); wrapped != nil { return wrapped } return err } // ReadFile reads the file named by filename and returns the contents. // // It wraps os.ReadFile wrapping the errors. func ReadFile(filename string) ([]byte, error) { b, err := os.ReadFile(filename) if err != nil { return nil, errors.Wrapf(maybeUnwrap(err), "error reading %s", filename) } return b, nil } // ReadPasswordFromFile reads and returns the password from the given filename. // The contents of the file will be trimmed at the right. func ReadPasswordFromFile(filename string) ([]byte, error) { password, err := ReadFile(filename) if err != nil { return nil, errors.Wrapf(err, "error reading %s", filename) } password = bytes.TrimRightFunc(password, unicode.IsSpace) return password, nil } // WriteFile writes data to a file named by filename. // If the file does not exist, WriteFile creates it with permissions perm // (before umask); otherwise WriteFile truncates it before writing. // // It wraps os.WriteFile wrapping the errors. func WriteFile(filename string, data []byte, perm os.FileMode) error { if err := os.WriteFile(filename, data, perm); err != nil { return errors.Wrapf(maybeUnwrap(err), "error writing %s", filename) } return nil } golang-step-crypto-0.24.0/internal/utils/io_test.go000066400000000000000000000054641437037643100223250ustar00rootroot00000000000000package utils import ( "fmt" "os" "path/filepath" "reflect" "testing" "github.com/pkg/errors" ) func TestReadFile(t *testing.T) { type args struct { filename string } tests := []struct { name string args args want []byte wantErr bool }{ {"ok", args{"testdata/pass1.txt"}, []byte("brandy-guidon-basin-ishmael-sedge-ducting"), false}, {"missing", args{"testdata/missing.txt"}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ReadFile(tt.args.filename) if (err != nil) != tt.wantErr { t.Errorf("ReadFile() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ReadFile() = %v, want %v", got, tt.want) } }) } } func TestReadPasswordFromFile(t *testing.T) { type args struct { filename string } tests := []struct { name string args args want []byte wantErr bool }{ {"ok", args{"testdata/pass1.txt"}, []byte("brandy-guidon-basin-ishmael-sedge-ducting"), false}, {"trim", args{"testdata/pass2.txt"}, []byte("benumb-eyepiece-stale-revers-marital-mimesis"), false}, {"missing", args{"testdata/missing.txt"}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ReadPasswordFromFile(tt.args.filename) if (err != nil) != tt.wantErr { t.Errorf("ReadPasswordFromFile() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ReadPasswordFromFile() = %v, want %v", got, tt.want) } }) } } func TestWriteFile(t *testing.T) { tmpDir, err := os.MkdirTemp(os.TempDir(), "go-tests") if err != nil { t.Fatal(err) } t.Cleanup(func() { os.RemoveAll(tmpDir) }) type args struct { filename string data []byte perm os.FileMode } tests := []struct { name string args args wantErr bool }{ {"ok", args{filepath.Join(tmpDir, "test.txt"), []byte("foo"), 0600}, false}, {"fail", args{tmpDir, []byte("foo"), 0600}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := WriteFile(tt.args.filename, tt.args.data, tt.args.perm); (err != nil) != tt.wantErr { t.Errorf("WriteFile() error = %v, wantErr %v", err, tt.wantErr) } }) } } func Test_maybeUnwrap(t *testing.T) { wantErr := fmt.Errorf("the error") type args struct { err error } tests := []struct { name string args args wantErr error }{ {"wrapped", args{errors.WithMessage(wantErr, "wrapped error")}, wantErr}, {"not wrapped", args{wantErr}, wantErr}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := maybeUnwrap(tt.args.err) if !reflect.DeepEqual(err, tt.wantErr) { //nolint:govet // legacy deep equal error check t.Errorf("maybeUnwrap() error = %v, wantErr %v", err, tt.wantErr) } }) } } golang-step-crypto-0.24.0/internal/utils/testdata/000077500000000000000000000000001437037643100221305ustar00rootroot00000000000000golang-step-crypto-0.24.0/internal/utils/testdata/pass1.txt000066400000000000000000000000511437037643100237140ustar00rootroot00000000000000brandy-guidon-basin-ishmael-sedge-ductinggolang-step-crypto-0.24.0/internal/utils/testdata/pass2.txt000066400000000000000000000000651437037643100237220ustar00rootroot00000000000000benumb-eyepiece-stale-revers-marital-mimesis golang-step-crypto-0.24.0/jose/000077500000000000000000000000001437037643100163035ustar00rootroot00000000000000golang-step-crypto-0.24.0/jose/encrypt.go000066400000000000000000000073241437037643100203240ustar00rootroot00000000000000package jose import ( "encoding/json" "github.com/pkg/errors" "go.step.sm/crypto/randutil" ) // MaxDecryptTries is the maximum number of attempts to decrypt a file. const MaxDecryptTries = 3 // PasswordPrompter defines the function signature for the PromptPassword // callback. type PasswordPrompter func(s string) ([]byte, error) // PromptPassword is a method used to prompt for a password to decode encrypted // keys. If this method is not defined and the key or password are not passed, // the parse of the key will fail. var PromptPassword PasswordPrompter // Encrypt returns the given data encrypted with the default encryption // algorithm (PBES2-HS256+A128KW). func Encrypt(data []byte, opts ...Option) (*JSONWebEncryption, error) { ctx, err := new(context).apply(opts...) if err != nil { return nil, err } var passphrase []byte switch { case len(ctx.password) > 0: passphrase = ctx.password case ctx.passwordPrompter != nil: if passphrase, err = ctx.passwordPrompter(ctx.passwordPrompt); err != nil { return nil, err } case PromptPassword != nil: if passphrase, err = PromptPassword("Please enter the password to encrypt the data"); err != nil { return nil, err } default: return nil, errors.New("failed to encrypt the data: missing password") } salt, err := randutil.Salt(PBKDF2SaltSize) if err != nil { return nil, err } // Encrypt private key using PBES2 recipient := Recipient{ Algorithm: PBES2_HS256_A128KW, Key: passphrase, PBES2Count: PBKDF2Iterations, PBES2Salt: salt, } encrypterOptions := new(EncrypterOptions) if ctx.contentType != "" { encrypterOptions.WithContentType(ContentType(ctx.contentType)) } encrypter, err := NewEncrypter(DefaultEncAlgorithm, recipient, encrypterOptions) if err != nil { return nil, errors.Wrap(err, "error creating cipher") } jwe, err := encrypter.Encrypt(data) if err != nil { return nil, errors.Wrap(err, "error encrypting data") } return jwe, nil } // EncryptJWK returns the given JWK encrypted with the default encryption // algorithm (PBES2-HS256+A128KW). func EncryptJWK(jwk *JSONWebKey, passphrase []byte) (*JSONWebEncryption, error) { b, err := json.Marshal(jwk) if err != nil { return nil, errors.Wrap(err, "error marshaling JWK") } return Encrypt(b, WithPassword(passphrase), WithContentType("jwk+json")) } // Decrypt returns the decrypted version of the given data if it's encrypted, // it will return the raw data if it's not encrypted or the format is not // valid. func Decrypt(data []byte, opts ...Option) ([]byte, error) { ctx, err := new(context).apply(opts...) if err != nil { return nil, err } enc, err := ParseEncrypted(string(data)) if err != nil { return data, nil //nolint:nilerr // Return the given data if we cannot parse it as encrypted. } // Try with the given password. if len(ctx.password) > 0 { if data, err = enc.Decrypt(ctx.password); err == nil { return data, nil } return nil, errors.New("failed to decrypt JWE: invalid password") } // Try with a given password prompter. if ctx.passwordPrompter != nil || PromptPassword != nil { var pass []byte for i := 0; i < MaxDecryptTries; i++ { switch { case ctx.passwordPrompter != nil: if pass, err = ctx.passwordPrompter(ctx.passwordPrompt); err != nil { return nil, err } case ctx.filename != "": if pass, err = PromptPassword("Please enter the password to decrypt " + ctx.filename); err != nil { return nil, err } default: if pass, err = PromptPassword("Please enter the password to decrypt the JWE"); err != nil { return nil, err } } if data, err = enc.Decrypt(pass); err == nil { return data, nil } } } return nil, errors.New("failed to decrypt JWE: invalid password") } golang-step-crypto-0.24.0/jose/encrypt_test.go000066400000000000000000000311351437037643100213600ustar00rootroot00000000000000package jose import ( "bytes" "crypto" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/json" "io" "reflect" "testing" "github.com/pkg/errors" "github.com/smallstep/assert" "go.step.sm/crypto/randutil" jose "gopkg.in/square/go-jose.v2" ) var testPassword = []byte("Supercalifragilisticexpialidocious") // mustTeeReader returns a buffer that can be used to capture reads from // rand.Reader and replay them if we set `rand.Reader` to that buffer. // mustTeeReader resets rand.Reader on cleanup. func mustTeeReader(t *testing.T) *bytes.Buffer { t.Helper() reader := rand.Reader t.Cleanup(func() { rand.Reader = reader jose.RandReader = reader }) buf := new(bytes.Buffer) rand.Reader = io.TeeReader(reader, buf) jose.RandReader = rand.Reader return buf } func mustGenerateJWK(t *testing.T, kty, crv, alg, use, kid string, size int) *JSONWebKey { t.Helper() jwk, err := GenerateJWK(kty, crv, alg, use, kid, size) if err != nil { t.Fatal(err) } return jwk } func mustEncryptJWK(t *testing.T, jwk *JSONWebKey, passphrase []byte) *JSONWebEncryption { t.Helper() data, err := json.Marshal(jwk) if err != nil { t.Fatal(err) } return mustEncryptData(t, data, passphrase) } func mustEncryptData(t *testing.T, data, passphrase []byte) *JSONWebEncryption { t.Helper() salt, err := randutil.Salt(PBKDF2SaltSize) if err != nil { t.Fatal(err) } recipient := Recipient{ Algorithm: PBES2_HS256_A128KW, Key: passphrase, PBES2Count: PBKDF2Iterations, PBES2Salt: salt, } opts := new(EncrypterOptions) if bytes.HasPrefix(data, []byte("{")) { opts.WithContentType(ContentType("jwk+json")) } encrypter, err := NewEncrypter(DefaultEncAlgorithm, recipient, opts) if err != nil { t.Fatal(err) } jwe, err := encrypter.Encrypt(data) if err != nil { t.Fatal(err) } return jwe } func fixJWK(jwk *JSONWebKey) *JSONWebKey { jwk.Certificates = []*x509.Certificate{} jwk.CertificatesURL = nil jwk.CertificateThumbprintSHA1 = []uint8{} jwk.CertificateThumbprintSHA256 = []uint8{} return jwk } // rsaEqual reports whether priv and x have equivalent values. It ignores // Precomputed values. func rsaEqual(priv *rsa.PrivateKey, x crypto.PrivateKey) bool { xx, ok := x.(*rsa.PrivateKey) if !ok { return false } if !(priv.PublicKey.N.Cmp(xx.N) == 0 && priv.PublicKey.E == xx.E) || priv.D.Cmp(xx.D) != 0 { return false } if len(priv.Primes) != len(xx.Primes) { return false } for i := range priv.Primes { if priv.Primes[i].Cmp(xx.Primes[i]) != 0 { return false } } return true } func TestEncrypt(t *testing.T) { jwk := fixJWK(mustGenerateJWK(t, "EC", "P-256", "ES256", "", "", 0)) data, err := json.Marshal(jwk) if err != nil { t.Fatal(err) } type args struct { data []byte opts []Option } tests := []struct { name string args args wantFn func(t *testing.T) *JSONWebEncryption wantErr bool }{ {"ok", args{data, []Option{WithPassword([]byte("password")), WithContentType("jwk+json")}}, func(t *testing.T) *JSONWebEncryption { reader := mustTeeReader(t) jwe := mustEncryptJWK(t, jwk, []byte("password")) rand.Reader = reader jose.RandReader = reader return jwe }, false}, {"ok WithPasswordPrompter", args{data, []Option{ WithContentType("jwk+json"), WithPasswordPrompter("Enter the password", func(s string) ([]byte, error) { return []byte("password"), nil })}}, func(t *testing.T) *JSONWebEncryption { reader := mustTeeReader(t) jwe := mustEncryptJWK(t, jwk, []byte("password")) rand.Reader = reader jose.RandReader = reader return jwe }, false}, {"ok with PromptPassword", args{data, []Option{WithContentType("jwk+json")}}, func(t *testing.T) *JSONWebEncryption { tmp := PromptPassword t.Cleanup(func() { PromptPassword = tmp }) PromptPassword = func(s string) ([]byte, error) { return []byte("password"), nil } reader := mustTeeReader(t) jwe := mustEncryptJWK(t, jwk, []byte("password")) rand.Reader = reader jose.RandReader = reader return jwe }, false}, {"fail apply", args{data, []Option{WithPasswordFile("testdata/missing.txt")}}, func(t *testing.T) *JSONWebEncryption { return nil }, true}, {"fail WithPasswordPrompter", args{data, []Option{ WithContentType("jwk+json"), WithPasswordPrompter("Enter the password", func(s string) ([]byte, error) { return nil, errors.New("test error") })}}, func(t *testing.T) *JSONWebEncryption { return nil }, true}, {"fail with PromptPassword", args{data, []Option{WithContentType("jwk+json")}}, func(t *testing.T) *JSONWebEncryption { tmp := PromptPassword t.Cleanup(func() { PromptPassword = tmp }) PromptPassword = func(s string) ([]byte, error) { return nil, errors.New("test error") } return nil }, true}, {"fail no passowrd", args{data, nil}, func(t *testing.T) *JSONWebEncryption { return nil }, true}, {"fail encrypt", args{data, []Option{WithPassword([]byte("password"))}}, func(t *testing.T) *JSONWebEncryption { reader := mustTeeReader(t) _, _ = randutil.Salt(PBKDF2SaltSize) rand.Reader = reader jose.RandReader = reader return nil }, true}, {"fail salt", args{data, []Option{WithPassword([]byte("password"))}}, func(t *testing.T) *JSONWebEncryption { reader := mustTeeReader(t) rand.Reader = reader jose.RandReader = reader return nil }, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { want := tt.wantFn(t) got, err := Encrypt(tt.args.data, tt.args.opts...) if (err != nil) != tt.wantErr { t.Errorf("Encrypt() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, want) { t.Errorf("Encrypt() = %v, want %v", got, want) } }) } } func TestEncryptJWK(t *testing.T) { jwk := fixJWK(mustGenerateJWK(t, "EC", "P-256", "ES256", "", "", 0)) type args struct { jwk *JSONWebKey passphrase []byte } tests := []struct { name string args args wantFn func(t *testing.T) *JSONWebEncryption wantErr bool }{ {"ok", args{jwk, []byte("planned password")}, func(t *testing.T) *JSONWebEncryption { reader := mustTeeReader(t) jwe := mustEncryptJWK(t, jwk, []byte("planned password")) rand.Reader = reader jose.RandReader = reader return jwe }, false}, {"fail marshal", args{&JSONWebKey{Key: "a string"}, []byte("planned password")}, func(t *testing.T) *JSONWebEncryption { return nil }, true}, {"fail encrypt", args{jwk, []byte("planned password")}, func(t *testing.T) *JSONWebEncryption { reader := mustTeeReader(t) _, _ = randutil.Salt(PBKDF2SaltSize) rand.Reader = reader jose.RandReader = reader return nil }, true}, {"fail salt", args{jwk, []byte("planned password")}, func(t *testing.T) *JSONWebEncryption { reader := mustTeeReader(t) rand.Reader = reader jose.RandReader = reader return nil }, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { want := tt.wantFn(t) got, err := EncryptJWK(tt.args.jwk, tt.args.passphrase) if (err != nil) != tt.wantErr { t.Errorf("EncryptJWK() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, want) { t.Errorf("EncryptJWK() = %v, want %v", got, want) } }) } } func TestEncryptDecryptJWK(t *testing.T) { t.Parallel() ecKey := fixJWK(mustGenerateJWK(t, "EC", "P-256", "ES256", "enc", "", 0)) rsaKey := fixJWK(mustGenerateJWK(t, "RSA", "", "RS256", "sig", "", 2048)) rsaPSSKey := fixJWK(mustGenerateJWK(t, "RSA", "", "PS256", "enc", "", 2048)) edKey := fixJWK(mustGenerateJWK(t, "OKP", "Ed25519", "EdDSA", "sig", "", 0)) octKey := fixJWK(mustGenerateJWK(t, "oct", "", "HS256", "sig", "", 64)) type args struct { jwk JSONWebKey passphrase []byte } tests := []struct { name string args args wantErr bool }{ {"ok EC", args{*ecKey, testPassword}, false}, {"ok EC pub", args{ecKey.Public(), testPassword}, false}, {"ok RSA", args{*rsaKey, testPassword}, false}, {"ok RSA pub", args{rsaKey.Public(), testPassword}, false}, {"ok RSA-PSS", args{*rsaPSSKey, testPassword}, false}, {"ok RSA-PSS pub", args{rsaPSSKey.Public(), testPassword}, false}, {"ok Ed25519", args{*edKey, testPassword}, false}, {"ok Ed25519 pub", args{edKey.Public(), testPassword}, false}, {"ok oct", args{*octKey, testPassword}, false}, } for _, tc := range tests { tt := tc t.Run(tt.name, func(t *testing.T) { t.Parallel() got, err := EncryptJWK(&tt.args.jwk, tt.args.passphrase) if (err != nil) != tt.wantErr { t.Errorf("EncryptJWK() error = %v, wantErr %v", err, tt.wantErr) return } s, err := got.CompactSerialize() if err != nil { t.Errorf("EncrypCompactSerializetJWK() error = %v", err) return } data, err := Decrypt([]byte(s), WithPassword(tt.args.passphrase)) if err != nil { t.Errorf("Decrypt() error = %v", err) return } var jwk JSONWebKey if err := json.Unmarshal(data, &jwk); err != nil { t.Errorf("json.Unmarshal() error = %v", err) return } // Make the rsa keys equal if they are if k, ok := tt.args.jwk.Key.(*rsa.PrivateKey); ok { if !rsaEqual(k, jwk.Key) { t.Errorf("Decrypt() got = %v, want %v", jwk.Key, tt.args.jwk.Key) return } jwk.Key = k } if !reflect.DeepEqual(jwk, tt.args.jwk) { t.Errorf("Decrypt() got = %v, want %v", jwk, tt.args.jwk) } }) } } func TestDecrypt(t *testing.T) { data := []byte("the-plain-data") jwe := mustEncryptData(t, data, testPassword) s, err := jwe.CompactSerialize() assert.FatalError(t, err) encryptedData := []byte(s) // Create wrong encrypted data m := make(map[string]interface{}) if err := json.Unmarshal([]byte(jwe.FullSerialize()), &m); err != nil { t.Fatal(err) } m["iv"] = "bad-iv" badEncryptedData, err := json.Marshal(m) if err != nil { t.Fatal(err) } type args struct { data []byte opts []Option passwordPrompter PasswordPrompter } tests := []struct { name string args args want []byte wantErr bool }{ {"ok not encrypted", args{[]byte("foobar"), nil, nil}, []byte("foobar"), false}, {"ok WithPassword", args{encryptedData, []Option{WithPassword(testPassword)}, nil}, data, false}, {"ok WithPasswordFile", args{encryptedData, []Option{WithPasswordFile("testdata/passphrase.txt")}, nil}, data, false}, {"ok WithPasswordPrompter", args{encryptedData, []Option{WithPasswordPrompter("What's the password?", func(s string) ([]byte, error) { return testPassword, nil })}, nil}, data, false}, {"ok PasswordPrompter", args{encryptedData, []Option{}, func(s string) ([]byte, error) { return testPassword, nil }}, data, false}, {"ok WithFilename and PasswordPrompter", args{encryptedData, []Option{WithFilename("test.jwk")}, func(s string) ([]byte, error) { return testPassword, nil }}, data, false}, {"fail bad data", args{badEncryptedData, []Option{WithPassword(testPassword)}, nil}, nil, true}, {"fail WithPassword", args{encryptedData, []Option{WithPassword([]byte("bad-password"))}, nil}, nil, true}, {"fail WithPasswordFile", args{encryptedData, []Option{WithPasswordFile("testdata/oct.txt")}, nil}, nil, true}, {"fail WithPasswordPrompter", args{encryptedData, []Option{WithPasswordPrompter("What's the password?", func(s string) ([]byte, error) { return []byte("bad-password"), nil })}, nil}, nil, true}, {"fail PasswordPrompter", args{encryptedData, []Option{}, func(s string) ([]byte, error) { return []byte("bad-password"), nil }}, nil, true}, {"fail apply WithPassword", args{encryptedData, []Option{WithPasswordFile("testdata/missing.txt")}, nil}, nil, true}, {"fail apply WithPasswordPrompter", args{encryptedData, []Option{WithPasswordPrompter("What's the password?", func(s string) ([]byte, error) { return nil, errors.New("unexpected error") })}, nil}, nil, true}, {"fail PasswordPrompter", args{encryptedData, []Option{}, func(s string) ([]byte, error) { return nil, errors.New("unexpected error") }}, nil, true}, {"fail WithFilename and PasswordPrompter", args{encryptedData, []Option{WithFilename("test.jwk")}, func(s string) ([]byte, error) { return nil, errors.New("unexpected error") }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.name == "okGlobalPasswordPrompter" { t.Log("foo") } tmp := PromptPassword t.Cleanup(func() { PromptPassword = tmp }) PromptPassword = tt.args.passwordPrompter got, err := Decrypt(tt.args.data, tt.args.opts...) if (err != nil) != tt.wantErr { t.Errorf("Decrypt() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("Decrypt() = %v, want %v", got, tt.want) } }) } } golang-step-crypto-0.24.0/jose/generate.go000066400000000000000000000124111437037643100204230ustar00rootroot00000000000000package jose import ( "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" "crypto/x509" "encoding/base64" "github.com/pkg/errors" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x25519" ) const ( jwksUsageSig = "sig" jwksUsageEnc = "enc" // defaultKeyType is the default type of the one-time token key. defaultKeyType = EC // defaultKeyCurve is the default curve of the one-time token key. defaultKeyCurve = P256 // defaultKeyAlg is the default algorithm of the one-time token key. defaultKeyAlg = ES256 // defaultKeySize is the default size of the one-time token key. defaultKeySize = 0 ) var ( errAmbiguousCertKeyUsage = errors.New("jose/generate: certificate's key usage is ambiguous, it should be for signature or encipherment, but not both (use --subtle to ignore usage field)") errNoCertKeyUsage = errors.New("jose/generate: certificate doesn't contain any key usage (use --subtle to ignore usage field)") ) // Thumbprint computes the JWK Thumbprint of a key using SHA256 as the hash // algorithm. It returns the hash encoded in the Base64 raw url encoding. func Thumbprint(jwk *JSONWebKey) (string, error) { var sum []byte var err error switch key := jwk.Key.(type) { case x25519.PublicKey: sum, err = x25519Thumbprint(key, crypto.SHA256) case x25519.PrivateKey: var pub x25519.PublicKey if pub, err = key.PublicKey(); err == nil { sum, err = x25519Thumbprint(pub, crypto.SHA256) } default: sum, err = jwk.Thumbprint(crypto.SHA256) } if err != nil { return "", errors.Wrap(err, "error generating JWK thumbprint") } return base64.RawURLEncoding.EncodeToString(sum), nil } // GenerateDefaultKeyPair generates an asymmetric public/private key pair. // Returns the public key as a JWK and the private key as an encrypted JWE. func GenerateDefaultKeyPair(passphrase []byte) (*JSONWebKey, *JSONWebEncryption, error) { if len(passphrase) == 0 { return nil, nil, errors.New("step-jose: password cannot be empty when encryptying a JWK") } // Generate the OTT key jwk, err := GenerateJWK(defaultKeyType, defaultKeyCurve, defaultKeyAlg, jwksUsageSig, "", defaultKeySize) if err != nil { return nil, nil, err } jwk.KeyID, err = Thumbprint(jwk) if err != nil { return nil, nil, err } jwe, err := EncryptJWK(jwk, passphrase) if err != nil { return nil, nil, err } public := jwk.Public() return &public, jwe, nil } // GenerateJWK generates a JWK given the key type, curve, alg, use, kid and // the size of the RSA or oct keys if necessary. func GenerateJWK(kty, crv, alg, use, kid string, size int) (jwk *JSONWebKey, err error) { if kty == "OKP" && use == "enc" && (crv == "" || crv == "Ed25519") { return nil, errors.New("invalid algorithm: Ed25519 cannot be used for encryption") } switch { case kty == "EC" && crv == "": crv = P256 case kty == "OKP" && crv == "": crv = Ed25519 case kty == "RSA" && size == 0: size = DefaultRSASize case kty == "oct" && size == 0: size = DefaultOctSize } key, err := keyutil.GenerateKey(kty, crv, size) if err != nil { return nil, err } jwk = &JSONWebKey{ Key: key, KeyID: kid, Use: use, Algorithm: alg, } guessJWKAlgorithm(&context{alg: alg}, jwk) if jwk.KeyID == "" && kty != "oct" { jwk.KeyID, err = Thumbprint(jwk) } return jwk, err } // GenerateJWKFromPEM returns an incomplete JSONWebKey using the key from a // PEM file. func GenerateJWKFromPEM(filename string, subtle bool) (*JSONWebKey, error) { key, err := pemutil.Read(filename) if err != nil { return nil, err } switch key := key.(type) { case *rsa.PrivateKey, *rsa.PublicKey: return &JSONWebKey{ Key: key, }, nil case *ecdsa.PrivateKey, *ecdsa.PublicKey, ed25519.PrivateKey, ed25519.PublicKey: return &JSONWebKey{ Key: key, Algorithm: algForKey(key), }, nil case *x509.Certificate: var use string if !subtle { use, err = keyUsageForCert(key) if err != nil { return nil, err } } return &JSONWebKey{ Key: key.PublicKey, Certificates: []*x509.Certificate{key}, Algorithm: algForKey(key.PublicKey), Use: use, }, nil default: return nil, errors.Errorf("error parsing %s: unsupported key type '%T'", filename, key) } } func algForKey(key crypto.PublicKey) string { switch key := key.(type) { case *ecdsa.PrivateKey: return getECAlgorithm(key.Curve) case *ecdsa.PublicKey: return getECAlgorithm(key.Curve) case ed25519.PrivateKey, ed25519.PublicKey: return EdDSA default: return "" } } func keyUsageForCert(cert *x509.Certificate) (string, error) { isDigitalSignature := containsUsage(cert.KeyUsage, x509.KeyUsageDigitalSignature, x509.KeyUsageContentCommitment, x509.KeyUsageCertSign, x509.KeyUsageCRLSign, ) isEncipherment := containsUsage(cert.KeyUsage, x509.KeyUsageKeyEncipherment, x509.KeyUsageDataEncipherment, x509.KeyUsageKeyAgreement, x509.KeyUsageEncipherOnly, x509.KeyUsageDecipherOnly, ) if isDigitalSignature && isEncipherment { return "", errAmbiguousCertKeyUsage } if isDigitalSignature { return jwksUsageSig, nil } if isEncipherment { return jwksUsageEnc, nil } return "", errNoCertKeyUsage } func containsUsage(usage x509.KeyUsage, queries ...x509.KeyUsage) bool { for _, query := range queries { if usage&query == query { return true } } return false } golang-step-crypto-0.24.0/jose/generate_test.go000066400000000000000000000340001437037643100214600ustar00rootroot00000000000000package jose import ( "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/json" "encoding/pem" "io" "math/big" "os" "reflect" "testing" "github.com/smallstep/assert" "go.step.sm/crypto/pemutil" jose "gopkg.in/square/go-jose.v2" ) func TestThumbprint(t *testing.T) { parse := func(filename string) *JSONWebKey { jwk, err := ReadKey(filename) if err != nil { t.Fatal(err) } return jwk } type args struct { jwk *JSONWebKey } tests := []struct { name string args args want string wantErr bool }{ {"ec", args{parse("testdata/p256.priv.json")}, "V93A-Yh7Bhw1W2E0igFciviJzX4PXPswoVgriehm9Co", false}, {"ec pub", args{parse("testdata/p256.pub.json")}, "V93A-Yh7Bhw1W2E0igFciviJzX4PXPswoVgriehm9Co", false}, {"rsa", args{parse("testdata/rsa.priv.json")}, "CIsktcixZ5GyfkoWFyEV0tp5foASmBV4D-W7clYrCu8", false}, {"rsa pub", args{parse("testdata/rsa.pub.json")}, "CIsktcixZ5GyfkoWFyEV0tp5foASmBV4D-W7clYrCu8", false}, {"okp", args{parse("testdata/okp.priv.json")}, "qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM", false}, {"okp pub", args{parse("testdata/okp.pub.json")}, "qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM", false}, {"fail oct", args{parse("testdata/oct.json")}, "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := Thumbprint(tt.args.jwk) if (err != nil) != tt.wantErr { t.Errorf("Thumbprint() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("Thumbprint() = %v, want %v", got, tt.want) } }) } } func TestGenerateJWK(t *testing.T) { t.Parallel() tests := []struct { kty, crv, alg, use, kid string size int expectedAlg string expectedSize int expectedType interface{} ok bool }{ {"EC", "", "", "", "", 0, "ES256", 256, &ecdsa.PrivateKey{}, true}, {"EC", "P-256", "", "sig", "", 0, "ES256", 256, &ecdsa.PrivateKey{}, true}, {"EC", "P-384", "", "sig", "a-kid", 0, "ES384", 384, &ecdsa.PrivateKey{}, true}, {"EC", "P-521", "ES521", "sig", "a-kid", 0, "ES521", 521, &ecdsa.PrivateKey{}, true}, {"EC", "P-256", "", "enc", "a-kid", 0, "ECDH-ES", 256, &ecdsa.PrivateKey{}, true}, {"EC", "P-256", "ECDH-ES+A128KW", "enc", "a-kid", 0, "ECDH-ES+A128KW", 256, &ecdsa.PrivateKey{}, true}, {"EC", "P-256", "ECDH-ES+A192KW", "enc", "a-kid", 0, "ECDH-ES+A192KW", 256, &ecdsa.PrivateKey{}, true}, {"EC", "P-256", "ECDH-ES+A256KW", "enc", "a-kid", 0, "ECDH-ES+A256KW", 256, &ecdsa.PrivateKey{}, true}, {"RSA", "", "", "", "", 0, "RS256", 2048, &rsa.PrivateKey{}, true}, {"RSA", "", "", "", "", 4096, "RS256", 4096, &rsa.PrivateKey{}, true}, {"RSA", "", "RS384", "sig", "", 2048, "RS384", 2048, &rsa.PrivateKey{}, true}, {"RSA", "", "RS521", "sig", "a-kid", 2048, "RS521", 2048, &rsa.PrivateKey{}, true}, {"RSA", "", "", "enc", "a-kid", 2048, "RSA-OAEP-256", 2048, &rsa.PrivateKey{}, true}, {"RSA", "", "RSA-OAEP-256", "enc", "a-kid", 2048, "RSA-OAEP-256", 2048, &rsa.PrivateKey{}, true}, {"RSA", "", "RSA1_5", "enc", "a-kid", 2048, "RSA1_5", 2048, &rsa.PrivateKey{}, true}, {"RSA", "", "RSA-OAEP", "enc", "a-kid", 2048, "RSA-OAEP", 2048, &rsa.PrivateKey{}, true}, {"OKP", "", "", "", "", 0, "EdDSA", 64, ed25519.PrivateKey{}, true}, {"OKP", "", "", "", "sig", 0, "EdDSA", 64, ed25519.PrivateKey{}, true}, {"OKP", "", "", "EdDSA", "sig", 0, "EdDSA", 64, ed25519.PrivateKey{}, true}, {"oct", "", "", "", "", 0, "HS256", 32, []byte{}, true}, {"oct", "", "", "sig", "", 0, "HS256", 32, []byte{}, true}, {"oct", "", "HS384", "sig", "a-kid", 16, "HS384", 16, []byte{}, true}, {"oct", "", "HS521", "sig", "a-kid", 64, "HS521", 64, []byte{}, true}, {"oct", "", "", "enc", "a-kid", 64, "A256GCMKW", 64, []byte{}, true}, {"oct", "", "dir", "enc", "a-kid", 0, "dir", 32, []byte{}, true}, {"oct", "", "A128KW", "enc", "a-kid", 0, "A128KW", 32, []byte{}, true}, {"oct", "", "A192KW", "enc", "a-kid", 0, "A192KW", 32, []byte{}, true}, {"oct", "", "A256KW", "enc", "a-kid", 0, "A256KW", 32, []byte{}, true}, {"oct", "", "A128GCMKW", "enc", "a-kid", 0, "A128GCMKW", 32, []byte{}, true}, {"oct", "", "A192GCMKW", "enc", "a-kid", 0, "A192GCMKW", 32, []byte{}, true}, {"oct", "", "A256GCMKW", "enc", "a-kid", 0, "A256GCMKW", 32, []byte{}, true}, {"fail", "", "", "", "", 0, "", 0, nil, false}, } for _, tt := range tests { tc := tt t.Run(tc.kty, func(t *testing.T) { t.Parallel() jwk, err := GenerateJWK(tc.kty, tc.crv, tc.alg, tc.use, tc.kid, tc.size) if !tc.ok { assert.Error(t, err) assert.Nil(t, jwk) return } assert.NoError(t, err) if tc.kid != "" { assert.Equals(t, tc.kid, jwk.KeyID) } assert.Equals(t, tc.expectedAlg, jwk.Algorithm) assert.Type(t, tc.expectedType, jwk.Key) switch key := jwk.Key.(type) { case *ecdsa.PrivateKey: switch tc.expectedSize { case 256: assert.Equals(t, elliptic.P256(), key.Curve) case 384: assert.Equals(t, elliptic.P384(), key.Curve) case 521: assert.Equals(t, elliptic.P521(), key.Curve) default: t.Errorf("unexpected size %d", tc.expectedSize) } case *rsa.PrivateKey: assert.Equals(t, tc.expectedSize, key.N.BitLen()) case ed25519.PrivateKey: assert.Equals(t, tc.expectedSize, len(key)) case []byte: assert.Equals(t, tc.expectedSize, len(key)) default: t.Errorf("unexpected key type %T", key) } }) } } func TestKeyUsageForCert(t *testing.T) { tests := []struct { Cert *x509.Certificate ExpectUse string ExpectErr error }{ { Cert: &x509.Certificate{ KeyUsage: x509.KeyUsageDigitalSignature, }, ExpectUse: jwksUsageSig, }, { Cert: &x509.Certificate{ KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageContentCommitment, }, ExpectUse: jwksUsageSig, }, { Cert: &x509.Certificate{ KeyUsage: x509.KeyUsageDataEncipherment | x509.KeyUsageKeyAgreement, }, ExpectUse: jwksUsageEnc, }, { Cert: &x509.Certificate{ KeyUsage: x509.KeyUsageDataEncipherment, }, ExpectUse: jwksUsageEnc, }, { Cert: &x509.Certificate{}, ExpectErr: errNoCertKeyUsage, }, { Cert: &x509.Certificate{ KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageDataEncipherment, }, ExpectErr: errAmbiguousCertKeyUsage, }, } for _, tt := range tests { use, err := keyUsageForCert(tt.Cert) if tt.ExpectErr != nil { assert.Equals(t, tt.ExpectErr, err) } else { assert.Equals(t, tt.ExpectUse, use) } } } func TestGenerateJWKFromPEM(t *testing.T) { t.Parallel() mustKey := func(filename string) interface{} { key, err := pemutil.Read(filename) assert.FatalError(t, err) return key } mustCert := func(filename string) *x509.Certificate { cert, err := pemutil.ReadCertificate(filename) assert.FatalError(t, err) return cert } type args struct { filename string subtle bool } tests := []struct { name string args args want *JSONWebKey wantErr bool }{ {"p256", args{"../pemutil/testdata/openssl.p256.pem", false}, &JSONWebKey{ Key: mustKey("../pemutil/testdata/openssl.p256.pem"), Algorithm: ES256, }, false}, {"p384", args{"../pemutil/testdata/openssl.p384.pem", false}, &JSONWebKey{ Key: mustKey("../pemutil/testdata/openssl.p384.pem"), Algorithm: ES384, }, false}, {"p521", args{"../pemutil/testdata/openssl.p521.pem", false}, &JSONWebKey{ Key: mustKey("../pemutil/testdata/openssl.p521.pem"), Algorithm: ES512, }, false}, {"ed25519", args{"../pemutil/testdata/pkcs8/openssl.ed25519.pem", false}, &JSONWebKey{ Key: mustKey("../pemutil/testdata/pkcs8/openssl.ed25519.pem"), Algorithm: EdDSA, }, false}, {"rsa", args{"../pemutil/testdata/openssl.rsa2048.pem", false}, &JSONWebKey{ Key: mustKey("../pemutil/testdata/openssl.rsa2048.pem"), }, false}, {"p256 pub", args{"../pemutil/testdata/openssl.p256.pub.pem", false}, &JSONWebKey{ Key: mustKey("../pemutil/testdata/openssl.p256.pub.pem"), Algorithm: ES256, }, false}, {"p384 pub", args{"../pemutil/testdata/openssl.p384.pub.pem", false}, &JSONWebKey{ Key: mustKey("../pemutil/testdata/openssl.p384.pub.pem"), Algorithm: ES384, }, false}, {"p521 pub", args{"../pemutil/testdata/openssl.p521.pub.pem", false}, &JSONWebKey{ Key: mustKey("../pemutil/testdata/openssl.p521.pub.pem"), Algorithm: ES512, }, false}, {"ed25519 pub", args{"../pemutil/testdata/pkcs8/openssl.ed25519.pub.pem", false}, &JSONWebKey{ Key: mustKey("../pemutil/testdata/pkcs8/openssl.ed25519.pub.pem"), Algorithm: EdDSA, }, false}, {"rsa pub", args{"../pemutil/testdata/openssl.rsa2048.pub.pem", false}, &JSONWebKey{ Key: mustKey("../pemutil/testdata/openssl.rsa2048.pub.pem"), }, false}, {"rsa cert", args{"testdata/rsa2048.crt", true}, &JSONWebKey{ Key: mustCert("testdata/rsa2048.crt").PublicKey, Certificates: []*x509.Certificate{mustCert("testdata/rsa2048.crt")}, }, false}, {"ed25519 cert", args{"../x509util/testdata/ed25519.crt", true}, &JSONWebKey{ Key: mustCert("../x509util/testdata/ed25519.crt").PublicKey, Certificates: []*x509.Certificate{mustCert("../x509util/testdata/ed25519.crt")}, Algorithm: EdDSA, }, false}, {"p256 cert", args{"../x509util/testdata/google.crt", false}, &JSONWebKey{ Key: mustCert("../x509util/testdata/google.crt").PublicKey, Certificates: []*x509.Certificate{mustCert("../x509util/testdata/google.crt")}, Algorithm: ES256, Use: "sig", }, false}, {"fail missing", args{"testdata/missing.txt", false}, nil, true}, {"fail no subtle", args{"testdata/rsa2048.crt", false}, nil, true}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() got, err := GenerateJWKFromPEM(tt.args.filename, tt.args.subtle) if (err != nil) != tt.wantErr { t.Errorf("GenerateJWKFromPEM() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("GenerateJWKFromPEM() = %v, want %v", got, tt.want) } }) } } func TestGenerateJWKFromPEMSubtle(t *testing.T) { tests := []struct { Description string KeyUsage x509.KeyUsage Subtle bool ExpectErr error ExpectSig string }{ { Description: "single key usage without subtle", KeyUsage: x509.KeyUsageDigitalSignature, ExpectSig: jwksUsageSig, }, { Description: "single key usage with subtle", KeyUsage: x509.KeyUsageDigitalSignature, Subtle: true, }, { Description: "multiple key usage without subtle", KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, ExpectErr: errAmbiguousCertKeyUsage, }, { Description: "multiple key usage with subtle", KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, Subtle: true, }, } for _, tt := range tests { t.Run(tt.Description, func(t *testing.T) { f, cleanup := tempFile(t) defer cleanup() err := pem.Encode(f, &pem.Block{ Type: "CERTIFICATE", Bytes: newCert(t, tt.KeyUsage), }) assert.NoError(t, err) jwk, err := GenerateJWKFromPEM(f.Name(), tt.Subtle) if tt.ExpectErr != nil { assert.Equals(t, tt.ExpectErr, err) return } assert.NoError(t, err) assert.Equals(t, tt.ExpectSig, jwk.Use) assert.Equals(t, ES256, jwk.Algorithm) assert.Equals(t, 1, len(jwk.Certificates)) }) } } func newCert(t *testing.T, keyUsage x509.KeyUsage) []byte { key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) assert.NoError(t, err) tmpl := x509.Certificate{ SerialNumber: big.NewInt(1), KeyUsage: keyUsage, } cert, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, key.Public(), key) assert.NoError(t, err) return cert } func tempFile(t *testing.T) (*os.File, func()) { f, err := os.CreateTemp("", "jose-generate-test") assert.NoError(t, err) return f, func() { f.Close() os.Remove(f.Name()) } } type mockReader struct{} func (mockReader) Read(buf []byte) (int, error) { for i := range buf { buf[i] = byte(i % 256) } return len(buf), nil } type eofReader struct{} func (eofReader) Read(buf []byte) (int, error) { return 0, io.EOF } func TestGenerateDefaultKeyPair(t *testing.T) { rr := rand.Reader t.Cleanup(func() { rand.Reader = rr jose.RandReader = rr }) rand.Reader = mockReader{} jose.RandReader = mockReader{} jwk := mustGenerateJWK(t, "EC", "P-256", "ES256", "sig", "", 0) jwe := mustEncryptJWK(t, jwk, []byte("planned password")) var err error if jwk.KeyID, err = Thumbprint(jwk); err != nil { t.Fatal(err) } jwkPub := jwk.Public() type args struct { passphrase []byte randReader io.Reader } tests := []struct { name string args args want *JSONWebKey want1 *JSONWebEncryption want1Decrypted *JSONWebKey wantErr bool }{ {"ok", args{[]byte("planned password"), mockReader{}}, &jwkPub, jwe, jwk, false}, {"failEmptyPassword", args{[]byte(""), rr}, nil, nil, nil, true}, {"failNilPassword", args{nil, rr}, nil, nil, nil, true}, {"failEOF", args{[]byte("planned password"), eofReader{}}, nil, nil, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rand.Reader = tt.args.randReader jose.RandReader = tt.args.randReader got, got1, err := GenerateDefaultKeyPair(tt.args.passphrase) if (err != nil) != tt.wantErr { t.Errorf("GenerateDefaultKeyPair() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("GenerateDefaultKeyPair() got = %#v, want %#v", got, tt.want) } if !reflect.DeepEqual(got1, tt.want1) { data, err := Decrypt([]byte(got1.FullSerialize()), WithPassword(tt.args.passphrase)) if err != nil { t.Fatalf("json.Marshal() error = %v", err) } var jwk JSONWebKey if err = json.Unmarshal(data, &jwk); err != nil { t.Log(string(data)) t.Fatalf("json.Unmarshal() error = %v", err) } if !reflect.DeepEqual(&jwk, fixJWK(tt.want1Decrypted)) { t.Errorf("GenerateDefaultKeyPair() jwk = %#v, want %#v", &jwk, fixJWK(tt.want1Decrypted)) } } }) } } golang-step-crypto-0.24.0/jose/options.go000066400000000000000000000053421437037643100203310ustar00rootroot00000000000000package jose import ( "go.step.sm/crypto/internal/utils" ) type context struct { filename string use, alg, kid string subtle, insecure bool noDefaults bool password []byte passwordPrompt string passwordPrompter PasswordPrompter contentType string } // apply the options to the context and returns an error if one of the options // fails. func (ctx *context) apply(opts ...Option) (*context, error) { for _, opt := range opts { if err := opt(ctx); err != nil { return nil, err } } return ctx, nil } // Option is the type used to add attributes to the context. type Option func(ctx *context) error // WithFilename adds the given filename to the context. func WithFilename(filename string) Option { return func(ctx *context) error { ctx.filename = filename return nil } } // WithUse adds the use claim to the context. func WithUse(use string) Option { return func(ctx *context) error { ctx.use = use return nil } } // WithAlg adds the alg claim to the context. func WithAlg(alg string) Option { return func(ctx *context) error { ctx.alg = alg return nil } } // WithKid adds the kid property to the context. func WithKid(kid string) Option { return func(ctx *context) error { ctx.kid = kid return nil } } // WithSubtle marks the context as subtle. func WithSubtle(subtle bool) Option { return func(ctx *context) error { ctx.subtle = subtle return nil } } // WithInsecure marks the context as insecure. func WithInsecure(insecure bool) Option { return func(ctx *context) error { ctx.insecure = insecure return nil } } // WithNoDefaults avoids that the parser loads defaults values, specially the // default algorithms. func WithNoDefaults(val bool) Option { return func(ctx *context) error { ctx.noDefaults = val return nil } } // WithPassword is a method that adds the given password to the context. func WithPassword(pass []byte) Option { return func(ctx *context) error { ctx.password = pass return nil } } // WithPasswordFile is a method that adds the password in a file to the context. func WithPasswordFile(filename string) Option { return func(ctx *context) error { b, err := utils.ReadPasswordFromFile(filename) if err != nil { return err } ctx.password = b return nil } } // WithPasswordPrompter defines a method that can be used to prompt for the // password to decrypt an encrypted JWE. func WithPasswordPrompter(prompt string, fn PasswordPrompter) Option { return func(ctx *context) error { ctx.passwordPrompt = prompt ctx.passwordPrompter = fn return nil } } // WithContentType adds the content type when encrypting data. func WithContentType(cty string) Option { return func(ctx *context) error { ctx.contentType = cty return nil } } golang-step-crypto-0.24.0/jose/parse.go000066400000000000000000000262771437037643100177620ustar00rootroot00000000000000package jose import ( "bytes" "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rsa" "crypto/x509" "encoding/base64" "encoding/json" "io" "net/http" "os" "strings" "time" "github.com/pkg/errors" "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x25519" ) type keyType int const ( jwkKeyType keyType = iota pemKeyType octKeyType ) // read returns the bytes from reading a file, or from a url if the filename has // the prefix https:// func read(filename string) ([]byte, error) { if strings.HasPrefix(filename, "https://") { resp, err := http.Get(filename) //nolint:gosec // no SSRF if err != nil { return nil, errors.Wrapf(err, "error retrieving %s", filename) } defer resp.Body.Close() if resp.StatusCode >= 400 { return nil, errors.Errorf("error retrieving %s: status code %d", filename, resp.StatusCode) } b, err := io.ReadAll(resp.Body) return b, errors.Wrapf(err, "error retrieving %s", filename) } b, err := os.ReadFile(filename) if err != nil { return nil, errors.Wrapf(err, "error reading %s", filename) } return b, nil } // ReadKey returns a JSONWebKey from the given JWK or PEM file. If the file is // password protected, and no password or prompt password function is given it // will fail. func ReadKey(filename string, opts ...Option) (*JSONWebKey, error) { b, err := read(filename) if err != nil { return nil, err } opts = append(opts, WithFilename(filename)) return ParseKey(b, opts...) } // ParseKey returns a JSONWebKey from the given JWK file or a PEM file. If the // file is password protected, and no password or prompt password function is // given it will fail. func ParseKey(b []byte, opts ...Option) (*JSONWebKey, error) { ctx, err := new(context).apply(opts...) if err != nil { return nil, err } if ctx.filename == "" { ctx.filename = "key" } jwk := new(JSONWebKey) switch guessKeyType(ctx, b) { case jwkKeyType: // Attempt to parse an encrypted file if b, err = Decrypt(b, opts...); err != nil { return nil, err } // Unmarshal the plain (or decrypted JWK) if err = json.Unmarshal(b, jwk); err != nil { return nil, errors.Errorf("error reading %s: unsupported format", ctx.filename) } // If KeyID not set by environment, then use the default. // NOTE: we do not set this value by default in the case of jwkKeyType // because it is assumed to have been left empty on purpose. case pemKeyType: pemOptions := []pemutil.Options{ pemutil.WithFilename(ctx.filename), } if ctx.password != nil { pemOptions = append(pemOptions, pemutil.WithPassword(ctx.password)) } if ctx.passwordPrompter != nil { pemOptions = append(pemOptions, pemutil.WithPasswordPrompt(ctx.passwordPrompt, pemutil.PasswordPrompter(ctx.passwordPrompter))) } if pemutil.PromptPassword == nil && PromptPassword != nil { pemutil.PromptPassword = pemutil.PasswordPrompter(PromptPassword) } jwk.Key, err = pemutil.ParseKey(b, pemOptions...) if err != nil { return nil, err } if ctx.kid == "" { if jwk.KeyID, err = Thumbprint(jwk); err != nil { return nil, err } } case octKeyType: jwk.Key = b } // Validate key id if ctx.kid != "" && jwk.KeyID != "" && ctx.kid != jwk.KeyID { return nil, errors.Errorf("kid %s does not match the kid on %s", ctx.kid, ctx.filename) } if jwk.KeyID == "" { jwk.KeyID = ctx.kid } if jwk.Use == "" { jwk.Use = ctx.use } // Set the algorithm if empty guessJWKAlgorithm(ctx, jwk) // Validate alg: if the flag '--subtle' is passed we will allow to overwrite it if !ctx.subtle && ctx.alg != "" && jwk.Algorithm != "" && ctx.alg != jwk.Algorithm { return nil, errors.Errorf("alg %s does not match the alg on %s", ctx.alg, ctx.filename) } if ctx.subtle && ctx.alg != "" { jwk.Algorithm = ctx.alg } return jwk, nil } // ReadKeySet reads a JWK Set from a URL or filename. URLs must start with // "https://". func ReadKeySet(filename string, opts ...Option) (*JSONWebKey, error) { b, err := read(filename) if err != nil { return nil, err } opts = append(opts, WithFilename(filename)) return ParseKeySet(b, opts...) } // ParseKeySet returns the JWK with the given key after parsing a JWKSet from // a given file. func ParseKeySet(b []byte, opts ...Option) (*JSONWebKey, error) { ctx, err := new(context).apply(opts...) if err != nil { return nil, err } // Attempt to parse an encrypted file if b, err = Decrypt(b, opts...); err != nil { return nil, err } // Unmarshal the plain or decrypted JWKSet jwkSet := new(JSONWebKeySet) if err := json.Unmarshal(b, jwkSet); err != nil { return nil, errors.Errorf("error reading %s: unsupported format", ctx.filename) } jwks := jwkSet.Key(ctx.kid) switch len(jwks) { case 0: return nil, errors.Errorf("cannot find key with kid %s on %s", ctx.kid, ctx.filename) case 1: jwk := &jwks[0] // Set the algorithm if empty guessJWKAlgorithm(ctx, jwk) // Validate alg: if the flag '--subtle' is passed we will allow the // overwrite of the alg if !ctx.subtle && ctx.alg != "" && jwk.Algorithm != "" && ctx.alg != jwk.Algorithm { return nil, errors.Errorf("alg %s does not match the alg on %s", ctx.alg, ctx.filename) } if ctx.subtle && ctx.alg != "" { jwk.Algorithm = ctx.alg } return jwk, nil default: return nil, errors.Errorf("multiple keys with kid %s have been found on %s", ctx.kid, ctx.filename) } } func decodeCerts(l []interface{}) ([]*x509.Certificate, error) { certs := make([]*x509.Certificate, len(l)) for i, j := range l { certStr, ok := j.(string) if !ok { return nil, errors.Errorf("wrong type in x5c header list; expected string but %T", i) } certB, err := base64.StdEncoding.DecodeString(certStr) if err != nil { return nil, errors.Wrap(err, "error decoding base64 encoded x5c cert") } cert, err := x509.ParseCertificate(certB) if err != nil { return nil, errors.Wrap(err, "error parsing x5c cert") } certs[i] = cert } return certs, nil } // X5cInsecureKey is the key used to store the x5cInsecure cert chain in the JWT header. var X5cInsecureKey = "x5cInsecure" // GetX5cInsecureHeader extracts the x5cInsecure certificate chain from the token. func GetX5cInsecureHeader(jwt *JSONWebToken) ([]*x509.Certificate, error) { x5cVal, ok := jwt.Headers[0].ExtraHeaders[HeaderKey(X5cInsecureKey)] if !ok { return nil, errors.New("ssh check-host token missing x5cInsecure header") } interfaces, ok := x5cVal.([]interface{}) if !ok { return nil, errors.Errorf("ssh check-host token x5cInsecure header has wrong type; expected []string, but got %T", x5cVal) } chain, err := decodeCerts(interfaces) if err != nil { return nil, errors.Wrap(err, "error decoding x5cInsecure header certs") } return chain, nil } // ParseX5cInsecure parses an x5cInsecure token, validates the certificate chain // in the token, and returns the JWT struct along with all the verified chains. func ParseX5cInsecure(tok string, roots []*x509.Certificate) (*JSONWebToken, [][]*x509.Certificate, error) { jwt, err := ParseSigned(tok) if err != nil { return nil, nil, errors.Wrapf(err, "error parsing x5cInsecure token") } chain, err := GetX5cInsecureHeader(jwt) if err != nil { return nil, nil, errors.Wrap(err, "error extracting x5cInsecure cert chain") } leaf := chain[0] interPool := x509.NewCertPool() for _, crt := range chain[1:] { interPool.AddCert(crt) } rootPool := x509.NewCertPool() for _, crt := range roots { rootPool.AddCert(crt) } // Correctly parse and validate the x5c certificate chain. verifiedChains, err := leaf.Verify(x509.VerifyOptions{ Roots: rootPool, Intermediates: interPool, // A hack so we skip validity period validation. CurrentTime: leaf.NotAfter.Add(-1 * time.Minute), }) if err != nil { return nil, nil, errors.Wrap(err, "error verifying x5cInsecure certificate chain") } leaf = verifiedChains[0][0] if leaf.KeyUsage&x509.KeyUsageDigitalSignature == 0 { return nil, nil, errors.New("certificate used to sign x5cInsecure token cannot be used for digital signature") } return jwt, verifiedChains, nil } // guessKeyType returns the key type of the given data. Key types are JWK, PEM // or oct. func guessKeyType(ctx *context, data []byte) keyType { switch ctx.alg { // jwk or file with oct data case "HS256", "HS384", "HS512": // Encrypted JWK ? if _, err := ParseEncrypted(string(data)); err == nil { return jwkKeyType } // JSON JWK ? if err := json.Unmarshal(data, &JSONWebKey{}); err == nil { return jwkKeyType } // Default to oct return octKeyType default: // PEM or default to JWK if bytes.HasPrefix(data, []byte("-----BEGIN ")) { return pemKeyType } return jwkKeyType } } // guessJWKAlgorithm set the algorithm if it's not set and we can guess it func guessJWKAlgorithm(ctx *context, jwk *JSONWebKey) { if jwk.Algorithm == "" { // Force default algorithm if passed. if ctx.alg != "" { jwk.Algorithm = ctx.alg return } // Guess only fixed algorithms if no defaults is enabled if ctx.noDefaults { guessKnownJWKAlgorithm(ctx, jwk) return } // Use defaults for each key type switch k := jwk.Key.(type) { case []byte: if jwk.Use == "enc" { jwk.Algorithm = string(DefaultOctKeyAlgorithm) } else { jwk.Algorithm = string(DefaultOctSigAlgorithm) } case *ecdsa.PrivateKey: if jwk.Use == "enc" { jwk.Algorithm = string(DefaultECKeyAlgorithm) } else { jwk.Algorithm = getECAlgorithm(k.Curve) } case *ecdsa.PublicKey: if jwk.Use == "enc" { jwk.Algorithm = string(DefaultECKeyAlgorithm) } else { jwk.Algorithm = getECAlgorithm(k.Curve) } case *rsa.PrivateKey, *rsa.PublicKey: if jwk.Use == "enc" { jwk.Algorithm = string(DefaultRSAKeyAlgorithm) } else { jwk.Algorithm = string(DefaultRSASigAlgorithm) } // Ed25519 can only be used for signing operations case ed25519.PrivateKey, ed25519.PublicKey: jwk.Algorithm = EdDSA case x25519.PrivateKey, x25519.PublicKey: jwk.Algorithm = XEdDSA } } } // guessSignatureAlgorithm returns the signature algorithm for a given private key. func guessSignatureAlgorithm(key crypto.PrivateKey) SignatureAlgorithm { switch k := key.(type) { case []byte: return DefaultOctSigAlgorithm case *ecdsa.PrivateKey: return SignatureAlgorithm(getECAlgorithm(k.Curve)) case *rsa.PrivateKey: return DefaultRSASigAlgorithm case ed25519.PrivateKey: return EdDSA case x25519.PrivateKey, X25519Signer: return XEdDSA default: return "" } } // guessKnownJWKAlgorithm sets the algorithm for keys that only have one // possible algorithm. func guessKnownJWKAlgorithm(ctx *context, jwk *JSONWebKey) { if jwk.Algorithm == "" && jwk.Use != "enc" { switch k := jwk.Key.(type) { case *ecdsa.PrivateKey: jwk.Algorithm = getECAlgorithm(k.Curve) case *ecdsa.PublicKey: jwk.Algorithm = getECAlgorithm(k.Curve) case ed25519.PrivateKey, ed25519.PublicKey: jwk.Algorithm = EdDSA case x25519.PrivateKey, x25519.PublicKey: jwk.Algorithm = XEdDSA } } } // getECAlgorithm returns the JWA algorithm name for the given elliptic curve. // If the curve is not supported it will return an empty string. // // Supported curves are P-256, P-384, and P-521. func getECAlgorithm(crv elliptic.Curve) string { switch crv.Params().Name { case P256: return ES256 case P384: return ES384 case P521: return ES512 default: return "" } } golang-step-crypto-0.24.0/jose/parse_test.go000066400000000000000000000670511437037643100210140ustar00rootroot00000000000000package jose import ( "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/base64" "encoding/json" "fmt" "net/http" "net/http/httptest" "os" "reflect" "strings" "testing" "github.com/smallstep/assert" "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x25519" ) const ( ecdsaPublicKey keyType = iota ecdsaPrivateKey ed25519PublicKey ed25519PrivateKey rsaPublicKey rsaPrivateKey octKey ) type testdata struct { typ keyType encrypted bool } var files = map[string]testdata{ "testdata/oct.json": {octKey, false}, "testdata/oct.enc.json": {octKey, true}, "testdata/okp.pub.json": {ed25519PublicKey, false}, "testdata/okp.priv.json": {ed25519PrivateKey, false}, "testdata/okp.enc.priv.json": {ed25519PrivateKey, true}, "testdata/p256.pub.json": {ecdsaPublicKey, false}, "testdata/p256.priv.json": {ecdsaPrivateKey, false}, "testdata/p256.enc.priv.json": {ecdsaPrivateKey, true}, "testdata/rsa.pub.json": {rsaPublicKey, false}, "testdata/rsa.priv.json": {rsaPrivateKey, false}, "testdata/rsa.enc.priv.json": {rsaPrivateKey, true}, } var pemFiles = map[string]testdata{ "../pemutil/testdata/openssl.p256.pem": {ecdsaPrivateKey, false}, "../pemutil/testdata/openssl.p256.pub.pem": {ecdsaPublicKey, false}, "../pemutil/testdata/openssl.p256.enc.pem": {ecdsaPrivateKey, true}, "../pemutil/testdata/openssl.p384.pem": {ecdsaPrivateKey, false}, "../pemutil/testdata/openssl.p384.pub.pem": {ecdsaPublicKey, false}, "../pemutil/testdata/openssl.p384.enc.pem": {ecdsaPrivateKey, true}, "../pemutil/testdata/openssl.p521.pem": {ecdsaPrivateKey, false}, "../pemutil/testdata/openssl.p521.pub.pem": {ecdsaPublicKey, false}, "../pemutil/testdata/openssl.p521.enc.pem": {ecdsaPrivateKey, true}, "../pemutil/testdata/openssl.rsa1024.pem": {rsaPrivateKey, false}, "../pemutil/testdata/openssl.rsa1024.pub.pem": {rsaPublicKey, false}, "../pemutil/testdata/openssl.rsa1024.enc.pem": {rsaPrivateKey, true}, "../pemutil/testdata/openssl.rsa2048.pem": {rsaPrivateKey, false}, "../pemutil/testdata/openssl.rsa2048.pub.pem": {rsaPublicKey, false}, "../pemutil/testdata/openssl.rsa2048.enc.pem": {rsaPrivateKey, true}, "../pemutil/testdata/pkcs8/openssl.ed25519.pem": {ed25519PrivateKey, false}, "../pemutil/testdata/pkcs8/openssl.ed25519.pub.pem": {ed25519PublicKey, false}, "../pemutil/testdata/pkcs8/openssl.ed25519.enc.pem": {ed25519PrivateKey, true}, "../pemutil/testdata/pkcs8/openssl.p256.pem": {ecdsaPrivateKey, false}, "../pemutil/testdata/pkcs8/openssl.p256.pub.pem": {ecdsaPublicKey, false}, "../pemutil/testdata/pkcs8/openssl.p256.enc.pem": {ecdsaPrivateKey, true}, "../pemutil/testdata/pkcs8/openssl.p384.pem": {ecdsaPrivateKey, false}, "../pemutil/testdata/pkcs8/openssl.p384.pub.pem": {ecdsaPublicKey, false}, "../pemutil/testdata/pkcs8/openssl.p384.enc.pem": {ecdsaPrivateKey, true}, "../pemutil/testdata/pkcs8/openssl.p521.pem": {ecdsaPrivateKey, false}, "../pemutil/testdata/pkcs8/openssl.p521.pub.pem": {ecdsaPublicKey, false}, "../pemutil/testdata/pkcs8/openssl.p521.enc.pem": {ecdsaPrivateKey, true}, "../pemutil/testdata/pkcs8/openssl.rsa2048.pem": {rsaPrivateKey, false}, "../pemutil/testdata/pkcs8/openssl.rsa2048.pub.pem": {rsaPublicKey, false}, "../pemutil/testdata/pkcs8/openssl.rsa2048.enc.pem": {rsaPrivateKey, true}, "../pemutil/testdata/pkcs8/openssl.rsa4096.pem": {rsaPrivateKey, false}, "../pemutil/testdata/pkcs8/openssl.rsa4096.pub.pem": {rsaPublicKey, false}, } func validateReadKey(t *testing.T, fn, pass string, td testdata) { var err error var jwk *JSONWebKey if td.encrypted { jwk, err = ReadKey(fn, WithPassword([]byte(pass))) } else { jwk, err = ReadKey(fn) } assert.NoError(t, err) assert.NoError(t, ValidateJWK(jwk)) switch td.typ { case ecdsaPublicKey: assert.Type(t, &ecdsa.PublicKey{}, jwk.Key) case ecdsaPrivateKey: assert.Type(t, &ecdsa.PrivateKey{}, jwk.Key) case ed25519PublicKey: assert.Type(t, ed25519.PublicKey{}, jwk.Key) case ed25519PrivateKey: assert.Type(t, ed25519.PrivateKey{}, jwk.Key) case rsaPublicKey: assert.Type(t, &rsa.PublicKey{}, jwk.Key) case rsaPrivateKey: assert.Type(t, &rsa.PrivateKey{}, jwk.Key) case octKey: assert.Type(t, []byte{}, jwk.Key) default: t.Errorf("type %T not supported", jwk.Key) } if jwk.IsPublic() == false && jwk.KeyID != "" { hash, err := jwk.Thumbprint(crypto.SHA256) assert.NoError(t, err) assert.Equals(t, base64.RawURLEncoding.EncodeToString(hash), jwk.KeyID) } if td.encrypted { jwkPriv, err := ReadKey(strings.Replace(fn, ".enc", "", 1)) assert.NoError(t, err) assert.Equals(t, jwkPriv, jwk) } } func TestReadKey(t *testing.T) { for fn, td := range files { fn, td := fn, td t.Run(fn, func(t *testing.T) { validateReadKey(t, fn, "password", td) }) } for fn, td := range pemFiles { fn, td := fn, td t.Run(fn, func(t *testing.T) { validateReadKey(t, fn, "mypassword", td) }) } if _, err := ReadKey("testdata/missing.txt"); err == nil { t.Errorf("ReadKey() error = %v, wantErr true", err) } } func TestReadKey_https(t *testing.T) { ok, err := os.ReadFile("testdata/okp.pub.json") assert.FatalError(t, err) key, err := base64.RawURLEncoding.DecodeString("L4WYxHsMVaspyhWuSp84v2meEYMEUdYnrn-w-jqP6iw") assert.FatalError(t, err) srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.RequestURI { case "/ok": w.Header().Set("ContentType", "application/jwk-set+json") w.WriteHeader(http.StatusOK) fmt.Fprintln(w, string(ok)) case "/empty": w.Header().Set("ContentType", "application/jwk-set+json") w.WriteHeader(http.StatusOK) fmt.Fprintln(w, "{}") default: w.WriteHeader(http.StatusNotFound) fmt.Fprintln(w, http.StatusText(http.StatusNotFound)) } })) srvClient := srv.Client() defer func() { srv.Close() http.DefaultClient = &http.Client{} }() type args struct { client *http.Client filename string opts []Option } tests := []struct { name string args args want *JSONWebKey wantErr bool }{ {"ok", args{srvClient, srv.URL + "/ok", nil}, &JSONWebKey{ Key: ed25519.PublicKey(key), KeyID: "qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM", Algorithm: "EdDSA", Use: "sig", Certificates: []*x509.Certificate{}, CertificateThumbprintSHA1: []byte{}, CertificateThumbprintSHA256: []byte{}, }, false}, {"failWithKid", args{srvClient, srv.URL + "/ok", []Option{WithKid("foobar")}}, nil, true}, {"failEmpty", args{srvClient, srv.URL + "/empty", nil}, nil, true}, {"failNotFound", args{srvClient, srv.URL + "/notFound", nil}, nil, true}, {"failClient", args{&http.Client{}, srv.URL + "/ok", nil}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { http.DefaultClient = tt.args.client got, err := ReadKey(tt.args.filename, tt.args.opts...) if (err != nil) != tt.wantErr { t.Errorf("ReadKeySet() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ReadKeySet() = \n%#v, want \n%#v", got, tt.want) } }) } } func TestReadKeyPasswordFile(t *testing.T) { jwk, err := ReadKey("testdata/oct.txt", WithAlg("HS256"), WithUse("sig"), WithKid("the-kid")) assert.FatalError(t, err) assert.Equals(t, []byte("a true random password"), jwk.Key) assert.Equals(t, HS256, jwk.Algorithm) assert.Equals(t, "sig", jwk.Use) assert.Equals(t, "the-kid", jwk.KeyID) } func TestParseKey(t *testing.T) { t.Parallel() marshal := func(i interface{}) []byte { b, err := json.Marshal(i) if err != nil { t.Fatal(err) } return b } read := func(filename string) []byte { b, err := os.ReadFile(filename) if err != nil { t.Fatal(err) } return b } ecKey := fixJWK(mustGenerateJWK(t, "EC", "P-256", "ES256", "enc", "", 0)) rsaKey := fixJWK(mustGenerateJWK(t, "RSA", "", "RS256", "sig", "", 2048)) rsaPSSKey := fixJWK(mustGenerateJWK(t, "RSA", "", "PS256", "enc", "", 2048)) edKey := fixJWK(mustGenerateJWK(t, "OKP", "Ed25519", "EdDSA", "sig", "", 0)) octKey := fixJWK(mustGenerateJWK(t, "oct", "", "HS256", "sig", "", 64)) encKey, err := EncryptJWK(edKey, testPassword) assert.FatalError(t, err) encKeyCompact, err := encKey.CompactSerialize() assert.FatalError(t, err) pemKey, err := pemutil.Read("../pemutil/testdata/pkcs8/openssl.ed25519.pem") assert.FatalError(t, err) ecKey.KeyID, err = Thumbprint(ecKey) assert.FatalError(t, err) type args struct { b []byte opts []Option } tests := []struct { name string args args want *JSONWebKey wantErr bool }{ {"ec", args{marshal(ecKey), nil}, ecKey, false}, {"rsa", args{marshal(rsaKey), nil}, rsaKey, false}, {"rsa-pss", args{marshal(rsaPSSKey), nil}, rsaPSSKey, false}, {"okp", args{marshal(edKey), nil}, edKey, false}, {"oct", args{marshal(octKey), nil}, octKey, false}, {"encryptedCompactWithPassword", args{[]byte(encKeyCompact), []Option{WithPassword(testPassword)}}, edKey, false}, {"encryptedFullWithPasswordFile", args{[]byte(encKey.FullSerialize()), []Option{WithPasswordFile("testdata/passphrase.txt")}}, edKey, false}, {"pemPrivate", args{read("../pemutil/testdata/pkcs8/openssl.ed25519.pem"), nil}, &JSONWebKey{ Key: pemKey, KeyID: "vEk4UARa85PrW0eea2zeVLqGBF-n5Jzd9GVmKAc0AHQ", Algorithm: "EdDSA", }, false}, {"pemPublic", args{read("../pemutil/testdata/pkcs8/openssl.ed25519.pub.pem"), nil}, &JSONWebKey{ Key: pemKey.(ed25519.PrivateKey).Public(), KeyID: "vEk4UARa85PrW0eea2zeVLqGBF-n5Jzd9GVmKAc0AHQ", Algorithm: "EdDSA", }, false}, {"pemPrivateWithPassword", args{read("../pemutil/testdata/pkcs8/openssl.ed25519.enc.pem"), []Option{WithPassword([]byte("mypassword"))}}, &JSONWebKey{ Key: pemKey, KeyID: "vEk4UARa85PrW0eea2zeVLqGBF-n5Jzd9GVmKAc0AHQ", Algorithm: "EdDSA", }, false}, {"pemPrivateWithPasswordFile", args{read("../pemutil/testdata/pkcs8/openssl.ed25519.enc.pem"), []Option{WithPasswordFile("../pemutil/testdata/password.txt")}}, &JSONWebKey{ Key: pemKey, KeyID: "vEk4UARa85PrW0eea2zeVLqGBF-n5Jzd9GVmKAc0AHQ", Algorithm: "EdDSA", }, false}, {"pemPrivateWithPasswordPrompter", args{read("../pemutil/testdata/pkcs8/openssl.ed25519.enc.pem"), []Option{WithPasswordPrompter("What's the password", func(s string) ([]byte, error) { return []byte("mypassword"), nil })}}, &JSONWebKey{ Key: pemKey, KeyID: "vEk4UARa85PrW0eea2zeVLqGBF-n5Jzd9GVmKAc0AHQ", Algorithm: "EdDSA", }, false}, {"pemPrivateWithKid", args{read("../pemutil/testdata/pkcs8/openssl.ed25519.pem"), []Option{WithKid("foobarzar")}}, &JSONWebKey{ Key: pemKey, KeyID: "foobarzar", Algorithm: "EdDSA", }, false}, {"pemPrivateWithUse", args{read("../pemutil/testdata/pkcs8/openssl.ed25519.pem"), []Option{WithUse("enc")}}, &JSONWebKey{ Key: pemKey, KeyID: "vEk4UARa85PrW0eea2zeVLqGBF-n5Jzd9GVmKAc0AHQ", Use: "enc", Algorithm: "EdDSA", }, false}, {"pemPrivateWithAlg", args{read("../pemutil/testdata/pkcs8/openssl.ed25519.pem"), []Option{WithAlg("EdDSA")}}, &JSONWebKey{ Key: pemKey, KeyID: "vEk4UARa85PrW0eea2zeVLqGBF-n5Jzd9GVmKAc0AHQ", Algorithm: "EdDSA", }, false}, {"pemPrivateWithAlgWithSubtle", args{read("../pemutil/testdata/pkcs8/openssl.ed25519.pem"), []Option{WithAlg("FOOBAR"), WithSubtle(true)}}, &JSONWebKey{ Key: pemKey, KeyID: "vEk4UARa85PrW0eea2zeVLqGBF-n5Jzd9GVmKAc0AHQ", Algorithm: "FOOBAR", }, false}, {"octPrivateWithAlg", args{testPassword, []Option{WithAlg("HS256")}}, &JSONWebKey{ Key: testPassword, Algorithm: "HS256", }, false}, {"octPrivateWithAlgWithKid", args{testPassword, []Option{WithAlg("HS256"), WithKid("foobarzar")}}, &JSONWebKey{ Key: testPassword, KeyID: "foobarzar", Algorithm: "HS256", }, false}, {"failPassword", args{[]byte(encKeyCompact), []Option{WithPassword([]byte("bad password"))}}, nil, true}, {"failMissingFile", args{[]byte(encKeyCompact), []Option{WithPasswordFile("testdata/missing.txt")}}, nil, true}, {"failPEMPassword", args{read("../pemutil/testdata/pkcs8/openssl.ed25519.enc.pem"), []Option{WithPassword([]byte("bad password"))}}, nil, true}, {"failECBWongAlg", args{marshal(ecKey), []Option{WithAlg("FOOBAR")}}, nil, true}, {"failECWrongKid", args{marshal(ecKey), []Option{WithKid("foobarzar")}}, nil, true}, {"failOCTMissingOptions", args{testPassword, nil}, nil, true}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() got, err := ParseKey(tt.args.b, tt.args.opts...) if (err != nil) != tt.wantErr { t.Errorf("ParseKey() error = %v, wantErr %v", err, tt.wantErr) return } // Make the rsa keys equal if they are if tt.want != nil { if k, ok := tt.want.Key.(*rsa.PrivateKey); ok { if !rsaEqual(k, got.Key) { t.Errorf("ParseKey() got = %v, want %v", got, tt.want) return } got.Key = k } } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ParseKey() = %v, want %v", got, tt.want) } }) } } func TestParseKeyPemutilPromptPassword(t *testing.T) { pemKey, err := pemutil.Read("../pemutil/testdata/pkcs8/openssl.ed25519.pem") assert.FatalError(t, err) pemBytes, err := os.ReadFile("../pemutil/testdata/pkcs8/openssl.ed25519.enc.pem") assert.FatalError(t, err) tmp0 := pemutil.PromptPassword tmp1 := PromptPassword t.Cleanup(func() { pemutil.PromptPassword = tmp0 PromptPassword = tmp1 }) tests := []struct { name string promptPassword PasswordPrompter want *JSONWebKey wantErr bool }{ {"ok", func(s string) ([]byte, error) { return []byte("mypassword"), nil }, &JSONWebKey{ Key: pemKey, KeyID: "vEk4UARa85PrW0eea2zeVLqGBF-n5Jzd9GVmKAc0AHQ", Algorithm: "EdDSA", }, false}, {"fail", func(s string) ([]byte, error) { return []byte("not-mypassword"), nil }, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { PromptPassword = tt.promptPassword pemutil.PromptPassword = nil got, err := ParseKey(pemBytes) if (err != nil) != tt.wantErr { t.Errorf("ParseKey() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ParseKey() = %v, want %v", got, tt.want) } }) } } func TestReadKeySet(t *testing.T) { jwk, err := ReadKeySet("testdata/jwks.json", WithKid("qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM")) assert.NoError(t, err) assert.Type(t, ed25519.PublicKey{}, jwk.Key) assert.Equals(t, "qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM", jwk.KeyID) jwk, err = ReadKeySet("testdata/jwks.json", WithKid("V93A-Yh7Bhw1W2E0igFciviJzX4PXPswoVgriehm9Co")) assert.NoError(t, err) assert.Type(t, &ecdsa.PublicKey{}, jwk.Key) assert.Equals(t, "V93A-Yh7Bhw1W2E0igFciviJzX4PXPswoVgriehm9Co", jwk.KeyID) jwk, err = ReadKeySet("testdata/jwks.json", WithKid("duplicated")) assert.Error(t, err) assert.Equals(t, "multiple keys with kid duplicated have been found on testdata/jwks.json", err.Error()) assert.Nil(t, jwk) jwk, err = ReadKeySet("testdata/jwks.json", WithKid("missing")) assert.Error(t, err) assert.Equals(t, "cannot find key with kid missing on testdata/jwks.json", err.Error()) assert.Nil(t, jwk) jwk, err = ReadKeySet("testdata/empty.json", WithKid("missing")) assert.Error(t, err) assert.Equals(t, "cannot find key with kid missing on testdata/empty.json", err.Error()) assert.Nil(t, jwk) } func TestReadKeySet_https(t *testing.T) { ok, err := os.ReadFile("testdata/jwks.json") assert.FatalError(t, err) key, err := base64.RawURLEncoding.DecodeString("L4WYxHsMVaspyhWuSp84v2meEYMEUdYnrn-w-jqP6iw") assert.FatalError(t, err) srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.RequestURI { case "/ok": w.Header().Set("ContentType", "application/jwk-set+json") w.WriteHeader(http.StatusOK) fmt.Fprintln(w, string(ok)) case "/empty": w.Header().Set("ContentType", "application/jwk-set+json") w.WriteHeader(http.StatusOK) fmt.Fprintln(w, "{}") default: w.WriteHeader(http.StatusNotFound) fmt.Fprintln(w, http.StatusText(http.StatusNotFound)) } })) srvClient := srv.Client() defer func() { srv.Close() http.DefaultClient = &http.Client{} }() type args struct { client *http.Client filename string opts []Option } tests := []struct { name string args args want *JSONWebKey wantErr bool }{ {"ok", args{srvClient, srv.URL + "/ok", []Option{WithKid("qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM")}}, &JSONWebKey{ Key: ed25519.PublicKey(key), KeyID: "qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM", Algorithm: "EdDSA", Use: "sig", Certificates: []*x509.Certificate{}, CertificateThumbprintSHA1: []byte{}, CertificateThumbprintSHA256: []byte{}, }, false}, {"failEmpty", args{srvClient, srv.URL + "/empty", []Option{WithKid("qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM")}}, nil, true}, {"failNotFound", args{srvClient, srv.URL + "/notFound", []Option{WithKid("qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM")}}, nil, true}, {"failClient", args{&http.Client{}, srv.URL + "/ok", []Option{WithKid("qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM")}}, nil, true}, {"failNoOptions", args{srvClient, srv.URL + "/ok", nil}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { http.DefaultClient = tt.args.client got, err := ReadKeySet(tt.args.filename, tt.args.opts...) if (err != nil) != tt.wantErr { t.Errorf("ReadKeySet() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ReadKeySet() = \n%#v, want \n%#v", got, tt.want) } }) } } func TestGuessJWKAlgorithm(t *testing.T) { p256, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) assert.FatalError(t, err) p384, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) assert.FatalError(t, err) p521, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) assert.FatalError(t, err) rsa2k, err := rsa.GenerateKey(rand.Reader, 2048) assert.FatalError(t, err) edPub, edPriv, err := ed25519.GenerateKey(rand.Reader) assert.FatalError(t, err) xPub, xPriv, err := x25519.GenerateKey(rand.Reader) assert.FatalError(t, err) tests := []struct { jwk *JSONWebKey expected string }{ {&JSONWebKey{Key: []byte{}, Use: ""}, HS256}, {&JSONWebKey{Key: []byte{}, Use: "sig"}, HS256}, {&JSONWebKey{Key: []byte{}, Use: "enc"}, "A256GCMKW"}, {&JSONWebKey{Key: p256, Use: ""}, ES256}, {&JSONWebKey{Key: p384, Use: "sig"}, ES384}, {&JSONWebKey{Key: p521, Use: "enc"}, "ECDH-ES"}, {&JSONWebKey{Key: p256.Public(), Use: ""}, ES256}, {&JSONWebKey{Key: p384.Public(), Use: "sig"}, ES384}, {&JSONWebKey{Key: p521.Public(), Use: "enc"}, "ECDH-ES"}, {&JSONWebKey{Key: rsa2k, Use: ""}, RS256}, {&JSONWebKey{Key: rsa2k, Use: "sig"}, RS256}, {&JSONWebKey{Key: rsa2k, Use: "enc"}, "RSA-OAEP-256"}, {&JSONWebKey{Key: rsa2k.Public(), Use: ""}, RS256}, {&JSONWebKey{Key: rsa2k.Public(), Use: "sig"}, RS256}, {&JSONWebKey{Key: rsa2k.Public(), Use: "enc"}, "RSA-OAEP-256"}, {&JSONWebKey{Key: edPub, Use: ""}, EdDSA}, {&JSONWebKey{Key: edPub, Use: "sig"}, EdDSA}, {&JSONWebKey{Key: edPriv, Use: ""}, EdDSA}, {&JSONWebKey{Key: edPriv, Use: "sig"}, EdDSA}, {&JSONWebKey{Key: xPub, Use: ""}, XEdDSA}, {&JSONWebKey{Key: xPub, Use: "sig"}, XEdDSA}, {&JSONWebKey{Key: xPriv, Use: ""}, XEdDSA}, {&JSONWebKey{Key: xPriv, Use: "sig"}, XEdDSA}, } // With context ctx, err := new(context).apply(WithAlg(HS256)) assert.NoError(t, err) jwk := &JSONWebKey{Key: []byte("password")} guessJWKAlgorithm(ctx, jwk) assert.Equals(t, HS256, jwk.Algorithm) // With algorithm set ctx, err = new(context).apply(WithAlg(HS256)) assert.NoError(t, err) jwk = &JSONWebKey{Key: []byte("password"), Algorithm: HS384} guessJWKAlgorithm(ctx, jwk) assert.Equals(t, HS384, jwk.Algorithm) // With no defaults ctx, err = new(context).apply(WithNoDefaults(true)) assert.NoError(t, err) jwk = mustGenerateJWK(t, "EC", "P-256", "ES256", "sig", "", 0) jwk.Algorithm = "" guessJWKAlgorithm(ctx, jwk) assert.Equals(t, ES256, jwk.Algorithm) pub := jwk.Public() pub.Algorithm = "" guessJWKAlgorithm(ctx, &pub) assert.Equals(t, ES256, pub.Algorithm) jwk = mustGenerateJWK(t, "OKP", "Ed25519", "EdDSA", "sig", "", 0) jwk.Algorithm = "" guessJWKAlgorithm(ctx, jwk) assert.Equals(t, EdDSA, jwk.Algorithm) pub = jwk.Public() pub.Algorithm = "" guessJWKAlgorithm(ctx, &pub) assert.Equals(t, EdDSA, pub.Algorithm) jwk = &JSONWebKey{Key: xPriv, Algorithm: "", Use: "sig"} guessJWKAlgorithm(ctx, jwk) assert.Equals(t, XEdDSA, jwk.Algorithm) jwk = &JSONWebKey{Key: xPub, Algorithm: "", Use: "sig"} guessJWKAlgorithm(ctx, jwk) assert.Equals(t, XEdDSA, jwk.Algorithm) // Defaults for _, tc := range tests { guessJWKAlgorithm(new(context), tc.jwk) assert.Equals(t, tc.expected, tc.jwk.Algorithm) } } func TestParseKeySet(t *testing.T) { b, err := os.ReadFile("testdata/jwks.json") assert.FatalError(t, err) key, err := base64.RawURLEncoding.DecodeString("L4WYxHsMVaspyhWuSp84v2meEYMEUdYnrn-w-jqP6iw") assert.FatalError(t, err) jwe := mustEncryptData(t, b, testPassword) encryptedJSON := []byte(jwe.FullSerialize()) encryptedCompact, err := jwe.CompactSerialize() assert.FatalError(t, err) type args struct { b []byte opts []Option } tests := []struct { name string args args want *JSONWebKey wantErr bool }{ {"ok", args{b, []Option{WithKid("qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM")}}, &JSONWebKey{ Key: ed25519.PublicKey(key), KeyID: "qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM", Algorithm: "EdDSA", Use: "sig", Certificates: []*x509.Certificate{}, CertificateThumbprintSHA1: []byte{}, CertificateThumbprintSHA256: []byte{}, }, false}, {"okEncryptedJSON", args{encryptedJSON, []Option{WithKid("qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM"), WithPassword(testPassword)}}, &JSONWebKey{ Key: ed25519.PublicKey(key), KeyID: "qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM", Algorithm: "EdDSA", Use: "sig", Certificates: []*x509.Certificate{}, CertificateThumbprintSHA1: []byte{}, CertificateThumbprintSHA256: []byte{}, }, false}, {"okEncryptedCompact", args{[]byte(encryptedCompact), []Option{WithKid("qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM"), WithPasswordFile("testdata/passphrase.txt")}}, &JSONWebKey{ Key: ed25519.PublicKey(key), KeyID: "qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM", Algorithm: "EdDSA", Use: "sig", Certificates: []*x509.Certificate{}, CertificateThumbprintSHA1: []byte{}, CertificateThumbprintSHA256: []byte{}, }, false}, {"okWithAlgSubtle", args{b, []Option{WithKid("qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM"), WithSubtle(true), WithAlg("FOOBAR")}}, &JSONWebKey{ Key: ed25519.PublicKey(key), KeyID: "qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM", Algorithm: "FOOBAR", Use: "sig", Certificates: []*x509.Certificate{}, CertificateThumbprintSHA1: []byte{}, CertificateThumbprintSHA256: []byte{}, }, false}, {"failOptions", args{b, []Option{WithPasswordFile("testdata/missing.txt")}}, nil, true}, {"failDecrypt", args{encryptedJSON, []Option{WithKid("qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM"), WithPassword([]byte("bad-password"))}}, nil, true}, {"failNoOptions", args{b, []Option{}}, nil, true}, {"failBadData", args{[]byte("foo"), []Option{}}, nil, true}, {"failEmpty", args{[]byte("{}"), []Option{WithKid("qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM")}}, nil, true}, {"failDuplicated", args{b, []Option{WithKid("duplicated")}}, nil, true}, {"failWithAlg", args{b, []Option{WithKid("qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM"), WithAlg("FOOBAR")}}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ParseKeySet(tt.args.b, tt.args.opts...) if (err != nil) != tt.wantErr { t.Errorf("ParseKeySet() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ParseKeySet() = %v, want %v", got, tt.want) } }) } } func Test_guessKeyType(t *testing.T) { marshal := func(i interface{}) []byte { b, err := json.Marshal(i) if err != nil { t.Fatal(err) } return b } ecKey := mustGenerateJWK(t, "EC", "P-256", "ES256", "enc", "", 0) rsaKey := mustGenerateJWK(t, "RSA", "", "RS256", "sig", "", 2048) rsaPSSKey := mustGenerateJWK(t, "RSA", "", "PS256", "enc", "", 2048) edKey := mustGenerateJWK(t, "OKP", "Ed25519", "EdDSA", "sig", "", 0) octKey := mustGenerateJWK(t, "oct", "", "HS256", "sig", "", 64) octHS384 := mustGenerateJWK(t, "oct", "", "HS384", "sig", "", 64) rsaPEM, err := os.ReadFile("../pemutil/testdata/openssl.p256.pem") assert.FatalError(t, err) type args struct { ctx *context data []byte } tests := []struct { name string args args want keyType }{ {"ec", args{&context{}, marshal(ecKey)}, jwkKeyType}, {"rsaKey", args{&context{}, marshal(rsaKey)}, jwkKeyType}, {"rsaPSSKey", args{&context{}, marshal(rsaPSSKey)}, jwkKeyType}, {"edKey", args{&context{}, marshal(edKey)}, jwkKeyType}, {"octKey", args{&context{}, marshal(octKey)}, jwkKeyType}, {"encrypted", args{&context{}, marshal(mustEncryptJWK(t, ecKey, testPassword))}, jwkKeyType}, {"rsaPEM", args{&context{}, rsaPEM}, pemKeyType}, {"encryptedAlgHS256", args{&context{alg: "HS256"}, []byte(mustEncryptJWK(t, octKey, testPassword).FullSerialize())}, jwkKeyType}, {"jwkAlgHS384", args{&context{alg: "HS384"}, marshal(octHS384)}, jwkKeyType}, {"bloblAlgHS512", args{&context{alg: "HS512"}, testPassword}, octKeyType}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := guessKeyType(tt.args.ctx, tt.args.data); got != tt.want { t.Errorf("guessKeyType() = %v, want %v", got, tt.want) } }) } } func Test_guessSignatureAlgorithm(t *testing.T) { must := func(args ...interface{}) crypto.PrivateKey { last := len(args) - 1 if err := args[last]; err != nil { t.Fatal(err) } return args[last-1] } _, x25519Key, err := x25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } type args struct { key crypto.PrivateKey } tests := []struct { name string args args want SignatureAlgorithm }{ {"byte", args{[]byte("the-key")}, HS256}, {"ES256", args{must(ecdsa.GenerateKey(elliptic.P256(), rand.Reader))}, ES256}, {"ES384", args{must(ecdsa.GenerateKey(elliptic.P384(), rand.Reader))}, ES384}, {"ES512", args{must(ecdsa.GenerateKey(elliptic.P521(), rand.Reader))}, ES512}, {"RS256", args{must(rsa.GenerateKey(rand.Reader, 2048))}, RS256}, {"EdDSA", args{must(ed25519.GenerateKey(rand.Reader))}, EdDSA}, {"XEdDSA", args{x25519Key}, XEdDSA}, {"XEdDSA with X25519Signer", args{X25519Signer(x25519Key)}, XEdDSA}, {"empty", args{must(ecdsa.GenerateKey(elliptic.P224(), rand.Reader))}, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := guessSignatureAlgorithm(tt.args.key); !reflect.DeepEqual(got, tt.want) { t.Errorf("guessSignatureAlgorithm() = %v, want %v", got, tt.want) } }) } } golang-step-crypto-0.24.0/jose/testdata/000077500000000000000000000000001437037643100201145ustar00rootroot00000000000000golang-step-crypto-0.24.0/jose/testdata/bad-rsa.crt000066400000000000000000000022301437037643100221340ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDODCCAiACCQC6wmhtiX91ITANBgkqhkiG9w0BAQsFADBeMQswCQYDVQQGEwJV UzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xFjAUBgNVBAoM DUV4YW1wbGUsIEluYy4xEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMDA0MjIwMTE3 NDNaFw0yMDA1MjIwMTE3NDNaMF4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEW MBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEWMBQGA1UECgwNRXhhbXBsZSwgSW5jLjES MBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC AQEA8k+4ZQO7jugI0K23YJxyCw2MD2ucWCL4RI4vmxwlFsNfy/jLqqWo1ihr+7aE CIqHoZGLs3hYJ5PkD83kFa873XGOBCQhBhN+MV3IE5V/GVT6o7c4KtLfvn9weHn7 UZqOf5A8BqZ5b0H1DzWsWziY4MJIYWRBIvS5P6SEmrM3pvfQJRWK8ue7mcq8REye uzcQLCohIuH4Ea9ljXtRIUlpRKFVZ+vG6/A4agv4g1vfc1ZRg915ffRpg5bjRdJX 9uDSAJaq5+JqYLibwXkELnD8JCnTRUw3hR7xUvtqjr1Y9vZFiKHinSuluYs2S89S U0viuR2kjFflGA1K4rEFOD+7swIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBFBC0C wpD1uSOOp1VXjb0Pg3pLRRgtrN82S/6b22H0Leg+0XNcNaTI0uLMlC1dFR7koA8T Nys38jqLjufuoBXAJhKfRl4Y4WXMW2c5iHnAgk3KPurVMdNjpIxRp7Hitk4hu0R+ aqAnqJuwUp2L7f8RMS2YASR3odX5I7Qszw6zgiIWJkT793ZPBhTjQp4/ahSp/Sxy GEv2aeO1OKsLTO5CItKF7xtbHcZrgdFcHBNF+gRMT+G3AnVTtEUgkC32yyKrtvaQ kXdxA0qB0q0xEs9QoClbdrHE9oIA3pUSFMDuF5YqaidUZ7xM5YuJYw8m1ihOXaPg FTAGBZzwXRGT7Xhx -----END CERTIFICATE----- golang-step-crypto-0.24.0/jose/testdata/bad-rsa.key000066400000000000000000000032501437037643100221370ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDyT7hlA7uO6AjQ rbdgnHILDYwPa5xYIvhEji+bHCUWw1/L+MuqpajWKGv7toQIioehkYuzeFgnk+QP zeQVrzvdcY4EJCEGE34xXcgTlX8ZVPqjtzgq0t++f3B4eftRmo5/kDwGpnlvQfUP NaxbOJjgwkhhZEEi9Lk/pISaszem99AlFYry57uZyrxETJ67NxAsKiEi4fgRr2WN e1EhSWlEoVVn68br8DhqC/iDW99zVlGD3Xl99GmDluNF0lf24NIAlqrn4mpguJvB eQQucPwkKdNFTDeFHvFS+2qOvVj29kWIoeKdK6W5izZLz1JTS+K5HaSMV+UYDUri sQU4P7uzAgMBAAECggEAe7wN0zd2vWDrr3Ql4iXBbrd6QB2ZdsBUut1InhJfNiKK dqQTjI6PityVV7I59gqXe//QsNLRYVR570AaKqCTF193P9IbMvkdRaQ4GRgMESl9 28Ah5GxOaP62ti7EEUW1YpqKrHKyLAgoVZ3455QdLRjI0ULaqDkDp+Yg+MZvssh2 hcvAndhE5mGrduudJBoWxSmeuTVLidqkS+Sc8aqyWJORzRYRsGpapq7Ij6hgovvv OSRF9jgtq+txl8enf3dXO7aXv2VyKgv+9TszZx7s0tyDGaMT1DWMPWY12RUUdweX Glyy3bcdzjfdN40EpkdjPAgl7RDqupgn9heXuDfIgQKBgQD/SZujqmDpgtU5N1gk +bKY+1IbuvL+BTynQEJywoPEyln6dBmBQ+KUyQQmAG82J9QMIKGV7z5i4NrloEpw U3PnAWOPG/xQfzX0qAz3s1Le3yQQCgRBsJrc0M0L7uuYqfjfh/PgN74FZIZQiunr EqHKsbmHAR/nfsc7lNW0SDgK0wKBgQDy/NdokFlkVDJvJFbNcbkiPZXC9KOnTWBS dEIt7GYNteJ8QOoh/ZMxNt/WEp8vMpj5LK5hBY2u2UB4MssWuHClRVEZVVop22eg dySog5srcC+2jCacv7bDO37s2ah+MUR2OFbjB6US7XJaBBKcVKElyYa0/ud1Kj3D bxb77+0/oQKBgQDxqGYymdg/c+AP13oTFmOgOVfSdDgNijuOP2AnbkZ6BOsEEN8L 5I0aNuc8afDcnPs3t7P+UIkH5L6R3BhbGESBCmmKUP2Z3bHftS0BVbk+zJBAtpqs 7FbMbMONZk+TJmM2hmWvfFTemfgjSjyAkBSj4XU0fTYMV5CVsCBUFoEmjQKBgB/X WjLtl4k6L1G5JVbOrD/8af+eJ0PpM9IaQgHalJT/XKqDpyrFG+C7HCHlVs7MhpdA b6lvHN3owjX+EfbsPHar2rDDKomJos64Til29YJ/tQDq5LRtuvlidoN7EkVXF22W fGxLuCn+y5lYJ0gtHuDgw7I8JQCJhIZzrENfcWuBAoGBAJC235n75zdlnbWJ9i6U a3Q8maXdsMABDfrVF9a9DPgY9djiG5SAulNnTri4+CtblXAPZ1Wc3fX/hsHrfsAp RsnJpzBDHGrV5LJ33wlNkqHESuEDLZCJBRvrFVPkz/u7wnJ0PIJbIEpD3+6BElko trP9GV/B84/OupCdc2VosOn+ -----END PRIVATE KEY----- golang-step-crypto-0.24.0/jose/testdata/empty.json000066400000000000000000000000201437037643100221350ustar00rootroot00000000000000{ "keys": [] }golang-step-crypto-0.24.0/jose/testdata/empty.jwks.json000066400000000000000000000003521437037643100231220ustar00rootroot00000000000000{ "keys": [ { "use": "sig", "kty": "OKP", "kid": "qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM", "crv": "Ed25519", "alg": "EdDSA", "x": "kqyKV4yXV8viKLPp4HazFzvBkZi0wMKZ3iLXwtWqgvA" } ] }golang-step-crypto-0.24.0/jose/testdata/invalid.crt000066400000000000000000000000401437037643100222460ustar00rootroot00000000000000this is not a valid certificate golang-step-crypto-0.24.0/jose/testdata/jwks.json000066400000000000000000000014671437037643100217750ustar00rootroot00000000000000{ "keys": [ { "use": "sig", "kty": "OKP", "kid": "qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM", "crv": "Ed25519", "alg": "EdDSA", "x": "L4WYxHsMVaspyhWuSp84v2meEYMEUdYnrn-w-jqP6iw" }, { "use": "sig", "kty": "EC", "kid": "V93A-Yh7Bhw1W2E0igFciviJzX4PXPswoVgriehm9Co", "crv": "P-256", "alg": "ES256", "x": "JtPSvIKayHsCHobDnNWtOdoroh-MDwKQSYMW6Mo4cfU", "y": "tP7xR5rpu6azzZsozdmzouyVByuTUDYSSAELTOAtu7g" }, { "use": "sig", "kty": "oct", "alg": "HS256", "kid": "duplicated", "k": "TUE5VnJDa3gzbDRtc3lWa1liRW1pUDVZNHBBRnNUNXo" }, { "use": "sig", "kty": "oct", "alg": "HS256", "kid": "duplicated", "k": "SzhwbXo1bXNZMWtmeVhrTnpHNDZZNkdLUE1NTTRWYUw" } ] }golang-step-crypto-0.24.0/jose/testdata/oct.enc.json000066400000000000000000000006141437037643100223410ustar00rootroot00000000000000{ "protected": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6ImpFUURBanFfVWx2WXZPNDZ0R2JxN1EifQ", "encrypted_key": "RAlAOaVm7ycTK2lQz90ltiJfrBpLq8Ze", "iv": "3PLGXbsdIyvMRAzi", "ciphertext": "HiLoDqUWM7TLOeEH2ihUpFMELS0OM1_0ZHb2dIA5W9HfEIsz7WAbPyTDA7iCCAJNr6NkEm8yYCKzQyhmcYhw-lU9fJ1HqfoiyPh1Bl56uiNRHnD_MeTEqKE", "tag": "j1T3WboWkkygIW53QxPG0g" }golang-step-crypto-0.24.0/jose/testdata/oct.json000066400000000000000000000001521437037643100215720ustar00rootroot00000000000000{ "use": "sig", "kty": "oct", "alg": "HS256", "k": "TUE5VnJDa3gzbDRtc3lWa1liRW1pUDVZNHBBRnNUNXo" }golang-step-crypto-0.24.0/jose/testdata/oct.txt000066400000000000000000000000261437037643100214400ustar00rootroot00000000000000a true random passwordgolang-step-crypto-0.24.0/jose/testdata/okp.enc.priv.json000066400000000000000000000010771437037643100233300ustar00rootroot00000000000000{ "protected": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMjU2R0NNIiwicDJjIjoxMDAwMDAsInAycyI6ImxUMTJjQ29FUEdYWXV2V0JqNHFZZ1EifQ", "encrypted_key": "_029j7ZniKPgkRbVzwjgPPUtCdQacFMLB-k9p-INRksow1a1-wHGTA", "iv": "WyrGXSosdyfBNWMW", "ciphertext": "5DNleiF85yWEDZFXQj-XSZ6JlepRei3k8V15oMecxXB_XnT7CL5ufZ9e884P3RGinA0gl_x3ViYFdFJthqJ79aHw7VtRVfe3Ar3USUcttPHxZmtrXd2yGWn1Osw3jyADeuXXaSED2EWNHiXUsjC3IGoGMWbMtKW7KuckZudkanSzYFRvuCPdLrDrvnugoMZX-he-NM1181OiOvZjtA-3TPKVLk7tuSnTOcqQAvpKoAy18NS6orslnVfFMz6tyYq3RvD8lbc-RN3o6op4Cphz", "tag": "rCmSF2Ts2Q61KubS_Bmceg" }golang-step-crypto-0.24.0/jose/testdata/okp.priv.json000066400000000000000000000003451437037643100225610ustar00rootroot00000000000000{ "use": "sig", "kty": "OKP", "kid": "qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM", "crv": "Ed25519", "alg": "EdDSA", "x": "L4WYxHsMVaspyhWuSp84v2meEYMEUdYnrn-w-jqP6iw", "d": "kqyKV4yXV8viKLPp4HazFzvBkZi0wMKZ3iLXwtWqgvA" }golang-step-crypto-0.24.0/jose/testdata/okp.pub.json000066400000000000000000000002661437037643100223710ustar00rootroot00000000000000{ "use": "sig", "kty": "OKP", "kid": "qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM", "crv": "Ed25519", "alg": "EdDSA", "x": "L4WYxHsMVaspyhWuSp84v2meEYMEUdYnrn-w-jqP6iw" }golang-step-crypto-0.24.0/jose/testdata/p256.enc.priv.json000066400000000000000000000011501437037643100232230ustar00rootroot00000000000000{ "protected": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6InFMYmNET3Yxczc3TlRGcnlKVUUtbmcifQ", "encrypted_key": "hY7Q7izJs6V1XvZI1l0RXJZ72XGg4anK", "iv": "gZb91xunOTgPqVF2", "ciphertext": "N-J28rcs385tnLyM32ynQpeRSn3nkcaaSoO9o0H7dxrsLJ7nV53DKGprDjAWhVWonbf3BxSIj1K5CUgMP8GPqUvJnZldgYILzvTRbGSPjYrGd5m2kUCAxIuRFbHNA35Lv02rfbNH9jvfdRvKhWvgunHS-a8vJCBCyk-Bd16Cp05G7tXp_EeVQXpUvNtQLd0v9NhWQ-tBeU86E3peCaJ97qipu7Xf23Axbng5azFzCGbSbmj4e0HN58HmHcpMDI8TgXrQ5-vDsRIE8U8YKJGqw5OVlRRM_gusQyZTBRHDFb5xkyFbD1SMpCRDdFTT0qS0k5vcH8ijpjSIs3K-sFo", "tag": "WG-RibO1NdWRaqHRHUwl3A" }golang-step-crypto-0.24.0/jose/testdata/p256.priv.json000066400000000000000000000004271437037643100224650ustar00rootroot00000000000000{ "use": "sig", "kty": "EC", "kid": "V93A-Yh7Bhw1W2E0igFciviJzX4PXPswoVgriehm9Co", "crv": "P-256", "alg": "ES256", "x": "JtPSvIKayHsCHobDnNWtOdoroh-MDwKQSYMW6Mo4cfU", "y": "tP7xR5rpu6azzZsozdmzouyVByuTUDYSSAELTOAtu7g", "d": "lgzkahW28vY8qXRFQd_Uphvl0Rfs9GyOj_ICkwy4V4s" }golang-step-crypto-0.24.0/jose/testdata/p256.pub.json000066400000000000000000000003511437037643100222670ustar00rootroot00000000000000{ "use": "sig", "kty": "EC", "kid": "V93A-Yh7Bhw1W2E0igFciviJzX4PXPswoVgriehm9Co", "crv": "P-256", "alg": "ES256", "x": "JtPSvIKayHsCHobDnNWtOdoroh-MDwKQSYMW6Mo4cfU", "y": "tP7xR5rpu6azzZsozdmzouyVByuTUDYSSAELTOAtu7g" }golang-step-crypto-0.24.0/jose/testdata/passphrase.txt000066400000000000000000000000421437037643100230220ustar00rootroot00000000000000Supercalifragilisticexpialidociousgolang-step-crypto-0.24.0/jose/testdata/rsa.enc.priv.json000066400000000000000000000034331437037643100233220ustar00rootroot00000000000000{ "protected": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6ImVoWDFmNldfXzRieF9GZlNHTFNpLXcifQ", "encrypted_key": "vKLfYBmT9hmhExKrNcDrOBlgmUZFvCxt", "iv": "wlzL3r2Kd-FAbVat", "ciphertext": "75c0DAGsgbqfnQctGw4aczBYvWAJ4yOixKpeJnbZehn7wbyuEmVa5glgmO-cqNjv-6Ev2959etaD_hjVvFTMKPi7K6GvZt42b_4YNZjqjL15UkbV_fvIf3BUODV7RxlTNwY4DbWzUZeNk-RUMpxnH9K3sHYeEBwAI9jAdu6On8LImP5-VbVlqNHCySIKtA35rr7AhtXxdZph5oYY_P9CY4LYVzedi_KmwW5mHr3CJUXaanQWc8oGnpqR0K0eLFLR7vLxcSYnt7VozcIVOopRMvhdHrLMD2HB3QrNKSp1l062BMu8HnDJ8FJNEoWLaOMv70QtaNGLBZ_RyjAxXmdU3o8AZNP9Q1dcvwF0RztjPuBtaMgK2L3iZ9YQAAipGQp-epD_Cn1Oxe9BWepJxE-EK1SkugZYp0rx-ZQaxf5jykV08Bnl--KZfE4cf-SWB0kt6EPg00NXMTr-a1rQE-yW6cnhnKOYkOL1Z6rjRvqu0x1Tg9ca3zzgeeiReRpDiNGKpi57J023bosIydmMDxeErjNlSlMCmHD2r4_-Y2jsSOtniDlPv33eJfBT4RdzGfhKQqCkWem8Omy5NpvdteJOLKLq3JOcM7sUJFXKKWJu0kTJzYowS80aFXpTUUbncPAolXHqJbhbK1DyV0wAasaDQvCbF7q12bQap9s1Vy2y6cHBFDQvjiiDokMnzDOymgYmvtXY5nXv_udrIMXLOgIQm8napHPrkHVtOz49iMTAb59xXdxlf02z8l4jCEhcsAvE57WlanD-S7nHiOzFHq5PcPG2BiplSYRxViv8yP8XGhK-npFQhyEvVuwSNweZaHGz_brtPMFv7gpcJhS8tJes0yQFmfH_tmy-rvyROjOTjZjKNZlfJbRyCvsJtaGx2Y5QNl686nBKbB5mH3so1gvXmITGU3FWVeAoP2xVLv-hfQii5pfs38B5NfYQ5I0AbklSc-ZrsIi-1Ghffkwy6Kk_zSmNtxvZXjUm7e6_sKgso6jlvucAxAEtKT5pLf26fZmrHFI0IFNnMlH4hGZfY2EALYkJfWP8CfREAWVB5F4zQZoPyZ3KCk0TvS_BgqwcFL4aJAPtnz0zlCLPfPY6B2FyMvktnWFL09oeGWt4tTEzCbWnaziKAMboq9WbUIjfR5TlOrKlJaqOKbgbjsMSE0YoHj1-QS3DI5hShEcTMmk13nA38T8juvMEYWuP-X3BhXYiI6lPxVgpACw5E87zoY72xL__rsQ4l2ykB3H2Rn88hBm3rOoSz_IfiYxgunten-CX02w-UN7yDZI4No12yy-A7BgY1tikhdVWvhLhAERBsbSptd_fIZGDdu5YiCFCd21Es6T6YDUJV5QPpH4PcdU_se5zdRiVPitUB0NhsgwdgyBtyzty8uWsdzYeR9f4-8TyDQJDpfswg3x-f0O2cIjto3B4p0p1TWrY5g-R-i_waHbPqiCSsEIIpK2Xa_5sz0rD3pTDD1AAmSHEoPv2CR3s-0YmO1ic5aYZGb3epWDX0y5ZERc7uB9FolAz_rijuR2W4ZMZhQ", "tag": "iMXAGrj6xHaxuVL49vn-pQ" }golang-step-crypto-0.24.0/jose/testdata/rsa.priv.json000066400000000000000000000022401437037643100225510ustar00rootroot00000000000000{ "use": "sig", "kty": "RSA", "kid": "CIsktcixZ5GyfkoWFyEV0tp5foASmBV4D-W7clYrCu8", "alg": "RS256", "n": "u1pASewznHZwedqjq85vEKRALbEBy57G4VWTrCY0wdFDThAtv3LwZaG1r9b79yDuqroz7PTT2vVQmzXwDQx78gix48XORLToUZrhcWcEhXxsYyTSJuD8groxHPNS_wTxrIH4ZKtFgJhdaOdSs9iFJpFEoq7DaVH89AWOEtAURek-KbkPci50IiQm-3Zwl9CBvDRs8528fGCthBFerm0kCXLw42oIeLJFC_iEHnR1NFzDuVZq6xjK1qp7vLoKMNUkkxmBFMduYOZth9Mf72i-l0VOZVt3gbHirR6RwXXH_K-NS6amGyB82w16g657p7rE3NXyEMMGXjOuObukMTd1jQ", "e": "AQAB", "d": "M_UYhSezPH4APVrsLxZl6MiUX9eJ9u1GnHE-Ley-js25C6oi9cgrcRQCrgxB_kwsxD41bk6LflqwCwtPUl8W9I2Cv_c4eAdvsknwoaF_OIHEEU7B1TRp8tsuCahVaRH27-9vcoOpF7uplBEq92NhsctxrGgpG0k4jHgJ6Z-5L5XAowfhyYx_VOKsAXP65270CKzEfibcCVmZGAbQ-ZR-ByDZQRV7ULNGiG1-GxTPiIkKH_pDV3TFk6tzxg2OE0Pym6EuoUbE1YESRJ2mY7VDi_Rg-S2lbjivtw5YUJSp4N2J6_iF0UrVvCzBvpsFQ3jD-Yur78NIcsSU0lA7LGuXJQ", "p": "8pebnVh_9ckrPCW4XEU_MK0KB0NQeur0LQJw9VRrkL739RnPfrdYh02B4gapkE0O0QK6PFQqhYyrf5WNlYGnn0Nil2KOdm1ZfD0Vnqyv7B5Ov0NSjWyRd6qlU0orEfcR4IeLeuBKM0_It89RepEU9ci4nnBIsf8KLCJ0RwVrT6M", "q": "xbURQBsHoHwhD9lrQL3fD5FjCCKnvMFG1OkgY-_v-Wohu9r5ezsU0ZDwfdRPp0LvbTLdb0JRoLXcV9Abn0CmeQyPD9xfyToFQyHA1DfIVy5TheKZnjT8Y4UZg-S05LxA_yhjF19H1A78usT5IJq5l6mmHbr_UeC_DrETQXEruQ8" }golang-step-crypto-0.24.0/jose/testdata/rsa.pub.json000066400000000000000000000007341437037643100223650ustar00rootroot00000000000000{ "use": "sig", "kty": "RSA", "kid": "CIsktcixZ5GyfkoWFyEV0tp5foASmBV4D-W7clYrCu8", "alg": "RS256", "n": "u1pASewznHZwedqjq85vEKRALbEBy57G4VWTrCY0wdFDThAtv3LwZaG1r9b79yDuqroz7PTT2vVQmzXwDQx78gix48XORLToUZrhcWcEhXxsYyTSJuD8groxHPNS_wTxrIH4ZKtFgJhdaOdSs9iFJpFEoq7DaVH89AWOEtAURek-KbkPci50IiQm-3Zwl9CBvDRs8528fGCthBFerm0kCXLw42oIeLJFC_iEHnR1NFzDuVZq6xjK1qp7vLoKMNUkkxmBFMduYOZth9Mf72i-l0VOZVt3gbHirR6RwXXH_K-NS6amGyB82w16g657p7rE3NXyEMMGXjOuObukMTd1jQ", "e": "AQAB" }golang-step-crypto-0.24.0/jose/testdata/rsa2048.crt000066400000000000000000000021331437037643100217300ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDCTCCAfGgAwIBAgIQIdY8a5pFZ/FGUowvuGdJvTANBgkqhkiG9w0BAQsFADAP MQ0wCwYDVQQDEwR0ZXN0MB4XDTIwMDQyMTA0MDg0MFoXDTIwMDQyMjA0MDg0MFow DzENMAsGA1UEAxMEdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB AL0zsTneONS0eNto7LMlD8GtcUYXqILSEWD07v0HgbIguBT+yC8BozpAT3lyB+oB BZkFLzfEHAteULngPwlq0R5hsEZJ6lcL1Z9WXwyLE4nkEndIPMA+zQmHnOzoqgKy 7pIqUnFqSGXtGp384fFF3Y0/qjeFciLnmf+Wn0PneaToY1rDj2Eb9sFf5UDiVaSL T1NzpSyXOS5uGbGplPe+WE8uEb3u3Vg2VGbEPau2l5MPYroCwSyxqlpKsmzJ558u vjQ7KpRExSNdb6f0iRfdRMbw3LahrxhbKV1mmM6GD5onmbgBCZpw5htOJj1MzVFZ OdnoTHmMl/Y/IUdMjv0jG/UCAwEAAaNhMF8wDgYDVR0PAQH/BAQDAgWgMB0GA1Ud JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUXlQCQL6RymQZnvqY 15F/GlE3H4UwDwYDVR0RBAgwBoIEdGVzdDANBgkqhkiG9w0BAQsFAAOCAQEArVmO L5L+NVsnCcUWfOVXQYg/6P8AGdPJBECk+BE5pbtMg0GuH5Ml/vCTBqD+diWC4O0T ZDxPMhXH5Ehl+67hcqeu4riwB2WvvKOlAqHqIuqVDRHtxwknvS1efstBKVdDC6aA fIa5f2dmCSxvd8elpcnufEefLGALTSPxg4uMVvpfWUkkmpmvOUpI3gNrlvP2H4KZ k7hKYz+J4x2jv2pdPWUAtt1U4M8oQ4BCPrrHSxznw2Q5mdCMIB64ZeYnZ+rAMQS6 WnZy1fTC3d0pCs0UCXH5JefBpha1clqHDUkxHA6/1EYYsSlKGFaPEmfv2uw7MFz0 o+yntG34KVdsC8HO3g== -----END CERTIFICATE----- golang-step-crypto-0.24.0/jose/testdata/rsa2048.key000066400000000000000000000032171437037643100217340ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAvTOxOd441LR422jssyUPwa1xRheogtIRYPTu/QeBsiC4FP7I LwGjOkBPeXIH6gEFmQUvN8QcC15QueA/CWrRHmGwRknqVwvVn1ZfDIsTieQSd0g8 wD7NCYec7OiqArLukipScWpIZe0anfzh8UXdjT+qN4VyIueZ/5afQ+d5pOhjWsOP YRv2wV/lQOJVpItPU3OlLJc5Lm4ZsamU975YTy4Rve7dWDZUZsQ9q7aXkw9iugLB LLGqWkqybMnnny6+NDsqlETFI11vp/SJF91ExvDctqGvGFspXWaYzoYPmieZuAEJ mnDmG04mPUzNUVk52ehMeYyX9j8hR0yO/SMb9QIDAQABAoIBAQCC1562UDGpF22E /pjCgtzUqaduO6ozXibakEg9/9T3ZJ0pF9FEgLNq81KCwBMtBqviWK2AuURTsFPP V38AejhH9HK9CRzgObTvzgFJYVyPvYPvrasln8iX0Ir7YyM/XpKRzmbZPBD5O/Fu VJXEDvbaWa7TWpvF+7iKApX9lbSQD10/bG5d9cmuQeomckEWGeJ9fC0DaqOLZt9y ylqryRhLTLyXBa7MbxupsduDBshGZTY1AGxj4YD6QS/a6UUCWRLEBz8iuhaYGcfK jngSMNFjT7DTsgTlg8Fhuez5VYVIB9L4YCyO4LAKWaPAbvfdH4r6Gpi1KuZPT5Pz avZcujBBAoGBANeKD55KmXGrblhZkf3OcqEmPgnndq1eg7g5rljmFcwdM7Bruurt ei/FNzkf45w/8cFR8R4JQntn2kAcf6A8lCDkYObABtv7sa0PFOkCgjr0H+/JWKjg gaKjkagpFugF6FgVda6xrwGojGPgJkEx5oWbljce5R/Vkcu7+3axlt89AoGBAOC3 9uN79JwWBmExk+D9j/vqFyt0h/PyeHUjeFcLl992dzB8XI96R7GUFm1MOIAAJ2Yh WLLOONn+0dwEwKBFjDop6tqE5YE18rpnrIOwwzuBuHeCjAXqcHt0WSTo+c9OrJ3O OSF1VFofZgcJne6HrU/iJruCyHtbfVZuWKR7CXsZAoGBAKwBVW1xAvsfX7PJ5yOw uPG5XxDwUlkQb/V4spXnJ8X4F+PWVRhd44Bz1hoURMPQk9E+3zilExUAT7+R/peV QHDvUBVVcSPkvGnq+zjn852wbDwjZkl+wHVWK0sTX2BSNpT0qzF6QrsEEuUxX9Xv SJOfEkVytk6tVOhNX+Pc3RXRAoGASKMuatnmd42nAkPESTWtFQDV/HXufCwkQ7jz bS+SJ+ljHYkVYktkYUI8/Zdlq/pgweKFiUafmdeEfloK6cORUDu8bmYGyX1muFpK qUa51MJZnkfUexUMxtsU9ZlZJmmH4nqGfQov+fGsSRoSsseofencG0BrDXTFTurc PE6JAgECgYEAy48pOfdVzO1sGmpc9ihLM6nRbuYU0zzE9fd6gF/ACZcYawtBUArg /7CSuVsbkJnLP+voiC+B8On3ZkpYA8E6nBOcbEft8JF9aeRzC5rqNxxphgdK7QCi fqS0MskeuMNysBPwlWlWJnBe4QSOSixrkQD3Ymm0i75aUWeTKQy4Lp8= -----END RSA PRIVATE KEY----- golang-step-crypto-0.24.0/jose/types.go000066400000000000000000000246721437037643100200110ustar00rootroot00000000000000// Package jose is a wrapper for gopkg.in/square/go-jose.v2 and implements // utilities to parse and generate JWT, JWK and JWKSets. package jose import ( "crypto" "errors" "strings" "time" "go.step.sm/crypto/x25519" jose "gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2/cryptosigner" "gopkg.in/square/go-jose.v2/jwt" ) // SupportsPBKDF2 constant to know if the underlaying library supports // password based cryptography algorithms. const SupportsPBKDF2 = true // PBKDF2SaltSize is the default size of the salt for PBKDF2, 128-bit salt. const PBKDF2SaltSize = 16 // PBKDF2Iterations is the default number of iterations for PBKDF2, 100k // iterations. Nist recommends at least 10k, 1Passsword uses 100k. const PBKDF2Iterations = 100000 // JSONWebSignature represents a signed JWS object after parsing. type JSONWebSignature = jose.JSONWebSignature // JSONWebToken represents a JSON Web Token (as specified in RFC7519). type JSONWebToken = jwt.JSONWebToken // JSONWebKey represents a public or private key in JWK format. type JSONWebKey = jose.JSONWebKey // JSONWebKeySet represents a JWK Set object. type JSONWebKeySet = jose.JSONWebKeySet // JSONWebEncryption represents an encrypted JWE object after parsing. type JSONWebEncryption = jose.JSONWebEncryption // Recipient represents an algorithm/key to encrypt messages to. type Recipient = jose.Recipient // EncrypterOptions represents options that can be set on new encrypters. type EncrypterOptions = jose.EncrypterOptions // Encrypter represents an encrypter which produces an encrypted JWE object. type Encrypter = jose.Encrypter // ContentType represents type of the contained data. type ContentType = jose.ContentType // KeyAlgorithm represents a key management algorithm. type KeyAlgorithm = jose.KeyAlgorithm // ContentEncryption represents a content encryption algorithm. type ContentEncryption = jose.ContentEncryption // SignatureAlgorithm represents a signature (or MAC) algorithm. type SignatureAlgorithm = jose.SignatureAlgorithm // Signature represents a signature. type Signature = jose.Signature // ErrCryptoFailure indicates an error in a cryptographic primitive. var ErrCryptoFailure = jose.ErrCryptoFailure // Claims represents public claim values (as specified in RFC 7519). type Claims = jwt.Claims // Builder is a utility for making JSON Web Tokens. Calls can be chained, and // errors are accumulated until the final call to CompactSerialize/FullSerialize. type Builder = jwt.Builder // NumericDate represents date and time as the number of seconds since the // epoch, including leap seconds. Non-integer values can be represented // in the serialized format, but we round to the nearest second. type NumericDate = jwt.NumericDate // Audience represents the recipients that the token is intended for. type Audience = jwt.Audience // Expected defines values used for protected claims validation. // If field has zero value then validation is skipped. type Expected = jwt.Expected // Signer represents a signer which takes a payload and produces a signed JWS object. type Signer = jose.Signer // OpaqueSigner represents a jose.Signer that wraps a crypto.Signer type OpaqueSigner = jose.OpaqueSigner // SigningKey represents an algorithm/key used to sign a message. type SigningKey = jose.SigningKey // SignerOptions represents options that can be set when creating signers. type SignerOptions = jose.SignerOptions // Header represents the read-only JOSE header for JWE/JWS objects. type Header = jose.Header // HeaderKey represents the type used as a key in the protected header of a JWS // object. type HeaderKey = jose.HeaderKey // ErrInvalidIssuer indicates invalid iss claim. var ErrInvalidIssuer = jwt.ErrInvalidIssuer // ErrInvalidAudience indicated invalid aud claim. var ErrInvalidAudience = jwt.ErrInvalidAudience // ErrNotValidYet indicates that token is used before time indicated in nbf claim. var ErrNotValidYet = jwt.ErrNotValidYet // ErrExpired indicates that token is used after expiry time indicated in exp claim. var ErrExpired = jwt.ErrExpired // ErrInvalidSubject indicates invalid sub claim. var ErrInvalidSubject = jwt.ErrInvalidSubject // ErrInvalidID indicates invalid jti claim. var ErrInvalidID = jwt.ErrInvalidID // ErrIssuedInTheFuture indicates that the iat field is in the future. var ErrIssuedInTheFuture = jwt.ErrIssuedInTheFuture // Key management algorithms // //nolint:stylecheck,revive // use standard names in upper-case const ( RSA1_5 = KeyAlgorithm("RSA1_5") // RSA-PKCS1v1.5 RSA_OAEP = KeyAlgorithm("RSA-OAEP") // RSA-OAEP-SHA1 RSA_OAEP_256 = KeyAlgorithm("RSA-OAEP-256") // RSA-OAEP-SHA256 A128KW = KeyAlgorithm("A128KW") // AES key wrap (128) A192KW = KeyAlgorithm("A192KW") // AES key wrap (192) A256KW = KeyAlgorithm("A256KW") // AES key wrap (256) DIRECT = KeyAlgorithm("dir") // Direct encryption ECDH_ES = KeyAlgorithm("ECDH-ES") // ECDH-ES ECDH_ES_A128KW = KeyAlgorithm("ECDH-ES+A128KW") // ECDH-ES + AES key wrap (128) ECDH_ES_A192KW = KeyAlgorithm("ECDH-ES+A192KW") // ECDH-ES + AES key wrap (192) ECDH_ES_A256KW = KeyAlgorithm("ECDH-ES+A256KW") // ECDH-ES + AES key wrap (256) A128GCMKW = KeyAlgorithm("A128GCMKW") // AES-GCM key wrap (128) A192GCMKW = KeyAlgorithm("A192GCMKW") // AES-GCM key wrap (192) A256GCMKW = KeyAlgorithm("A256GCMKW") // AES-GCM key wrap (256) PBES2_HS256_A128KW = KeyAlgorithm("PBES2-HS256+A128KW") // PBES2 + HMAC-SHA256 + AES key wrap (128) PBES2_HS384_A192KW = KeyAlgorithm("PBES2-HS384+A192KW") // PBES2 + HMAC-SHA384 + AES key wrap (192) PBES2_HS512_A256KW = KeyAlgorithm("PBES2-HS512+A256KW") // PBES2 + HMAC-SHA512 + AES key wrap (256) ) // Signature algorithms const ( HS256 = "HS256" // HMAC using SHA-256 HS384 = "HS384" // HMAC using SHA-384 HS512 = "HS512" // HMAC using SHA-512 RS256 = "RS256" // RSASSA-PKCS-v1.5 using SHA-256 RS384 = "RS384" // RSASSA-PKCS-v1.5 using SHA-384 RS512 = "RS512" // RSASSA-PKCS-v1.5 using SHA-512 ES256 = "ES256" // ECDSA using P-256 and SHA-256 ES384 = "ES384" // ECDSA using P-384 and SHA-384 ES512 = "ES512" // ECDSA using P-521 and SHA-512 PS256 = "PS256" // RSASSA-PSS using SHA256 and MGF1-SHA256 PS384 = "PS384" // RSASSA-PSS using SHA384 and MGF1-SHA384 PS512 = "PS512" // RSASSA-PSS using SHA512 and MGF1-SHA512 EdDSA = "EdDSA" // Ed25519 with EdDSA signature schema XEdDSA = "XEdDSA" // X25519 with XEdDSA signature schema ) // Content encryption algorithms // //nolint:revive,stylecheck // use standard names in upper-case const ( A128CBC_HS256 = ContentEncryption("A128CBC-HS256") // AES-CBC + HMAC-SHA256 (128) A192CBC_HS384 = ContentEncryption("A192CBC-HS384") // AES-CBC + HMAC-SHA384 (192) A256CBC_HS512 = ContentEncryption("A256CBC-HS512") // AES-CBC + HMAC-SHA512 (256) A128GCM = ContentEncryption("A128GCM") // AES-GCM (128) A192GCM = ContentEncryption("A192GCM") // AES-GCM (192) A256GCM = ContentEncryption("A256GCM") // AES-GCM (256) ) // Elliptic curves const ( P256 = "P-256" // P-256 curve (FIPS 186-3) P384 = "P-384" // P-384 curve (FIPS 186-3) P521 = "P-521" // P-521 curve (FIPS 186-3) ) // Key types const ( EC = "EC" // Elliptic curves RSA = "RSA" // RSA OKP = "OKP" // Ed25519 OCT = "oct" // Octet sequence ) // Ed25519 is the EdDSA signature scheme using SHA-512/256 and Curve25519 const Ed25519 = "Ed25519" // Default key management, signature, and content encryption algorithms to use if none is specified. const ( // Key management algorithms DefaultECKeyAlgorithm = ECDH_ES DefaultRSAKeyAlgorithm = RSA_OAEP_256 DefaultOctKeyAlgorithm = A256GCMKW // Signature algorithms DefaultRSASigAlgorithm = RS256 DefaultOctSigAlgorithm = HS256 // Content encryption algorithm DefaultEncAlgorithm = A256GCM ) // Default sizes const ( DefaultRSASize = 2048 DefaultOctSize = 32 ) // ParseEncrypted parses an encrypted message in compact or full serialization format. func ParseEncrypted(input string) (*JSONWebEncryption, error) { return jose.ParseEncrypted(input) } // NewEncrypter creates an appropriate encrypter based on the key type. func NewEncrypter(enc ContentEncryption, rcpt Recipient, opts *EncrypterOptions) (Encrypter, error) { return jose.NewEncrypter(enc, rcpt, opts) } // NewNumericDate constructs NumericDate from time.Time value. func NewNumericDate(t time.Time) *NumericDate { return jwt.NewNumericDate(t) } // UnixNumericDate returns a NumericDate from the given seconds since the UNIX // Epoch time. For backward compatibility is s is 0, a nil value will be returned. func UnixNumericDate(s int64) *NumericDate { if s == 0 { return nil } out := NumericDate(s) return &out } // NewSigner creates an appropriate signer based on the key type func NewSigner(sig SigningKey, opts *SignerOptions) (Signer, error) { if k, ok := sig.Key.(x25519.PrivateKey); ok { sig.Key = X25519Signer(k) } if sig.Algorithm == "" { sig.Algorithm = guessSignatureAlgorithm(sig.Key) } return jose.NewSigner(sig, opts) } // NewOpaqueSigner creates a new OpaqueSigner for JWT signing from a crypto.Signer func NewOpaqueSigner(signer crypto.Signer) OpaqueSigner { return cryptosigner.Opaque(signer) } // Verify validates the token payload with the given public key and deserializes // the token into the destination. func Verify(token *JSONWebToken, publicKey interface{}, dest ...interface{}) error { if k, ok := publicKey.(x25519.PublicKey); ok { publicKey = X25519Verifier(k) } return token.Claims(publicKey, dest...) } // ParseSigned parses token from JWS form. func ParseSigned(s string) (*JSONWebToken, error) { return jwt.ParseSigned(s) } // Signed creates builder for signed tokens. func Signed(sig Signer) Builder { return jwt.Signed(sig) } // ParseJWS parses a signed message in compact or full serialization format. func ParseJWS(s string) (*JSONWebSignature, error) { return jose.ParseSigned(s) } // Determine whether a JSONWebKey is symmetric func IsSymmetric(k *JSONWebKey) bool { switch k.Key.(type) { case []byte: return true default: return false } } // Determine whether a JSONWebKey is asymmetric func IsAsymmetric(k *JSONWebKey) bool { return !IsSymmetric(k) } // TrimPrefix removes the string "square/go-jose" from all errors. func TrimPrefix(err error) error { if err == nil { return nil } return errors.New(strings.TrimPrefix(err.Error(), "square/go-jose: ")) } golang-step-crypto-0.24.0/jose/types_test.go000066400000000000000000000122601437037643100210360ustar00rootroot00000000000000package jose import ( "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" "reflect" "testing" "time" "github.com/pkg/errors" "go.step.sm/crypto/x25519" ) func TestNumericDate(t *testing.T) { now := time.Now() // NewNumericDate wantNumericDate := NumericDate(now.Unix()) if got := NewNumericDate(now); !reflect.DeepEqual(got, &wantNumericDate) { t.Errorf("NewNumericDate() = %v, want %v", got, &wantNumericDate) } if got := NewNumericDate(time.Time{}); !reflect.DeepEqual(got, (*NumericDate)(nil)) { t.Errorf("NewNumericDate() = %v, want %v", got, nil) } // UnixNumericDate if got := UnixNumericDate(now.Unix()); !reflect.DeepEqual(got, &wantNumericDate) { t.Errorf("UnixNumericDate() = %v, want %v", got, &wantNumericDate) } if got := UnixNumericDate(0); !reflect.DeepEqual(got, (*NumericDate)(nil)) { t.Errorf("UnixNumericDate() = %v, want %v", got, nil) } } func TestIsSymmetric(t *testing.T) { type args struct { k *JSONWebKey } tests := []struct { name string args args want bool }{ {"EC", args{mustGenerateJWK(t, "EC", "P-256", "ES256", "enc", "", 0)}, false}, {"RSA", args{mustGenerateJWK(t, "RSA", "", "RS256", "sig", "", 2048)}, false}, {"RSA", args{mustGenerateJWK(t, "RSA", "", "PS256", "enc", "", 2048)}, false}, {"OKP", args{mustGenerateJWK(t, "OKP", "Ed25519", "EdDSA", "sig", "", 0)}, false}, {"oct", args{mustGenerateJWK(t, "oct", "", "HS256", "sig", "", 64)}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := IsSymmetric(tt.args.k); got != tt.want { t.Errorf("IsSymmetric() = %v, want %v", got, tt.want) } }) } } func TestIsAsymmetric(t *testing.T) { type args struct { k *JSONWebKey } tests := []struct { name string args args want bool }{ {"EC", args{mustGenerateJWK(t, "EC", "P-256", "ES256", "enc", "", 0)}, true}, {"RSA", args{mustGenerateJWK(t, "RSA", "", "RS256", "sig", "", 2048)}, true}, {"RSA", args{mustGenerateJWK(t, "RSA", "", "PS256", "enc", "", 2048)}, true}, {"OKP", args{mustGenerateJWK(t, "OKP", "Ed25519", "EdDSA", "sig", "", 0)}, true}, {"oct", args{mustGenerateJWK(t, "oct", "", "HS256", "sig", "", 64)}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := IsAsymmetric(tt.args.k); got != tt.want { t.Errorf("IsAsymmetric() = %v, want %v", got, tt.want) } }) } } func TestTrimPrefix(t *testing.T) { type args struct { err error } tests := []struct { name string args args wantErr error }{ {"nil", args{nil}, nil}, {"trim", args{errors.New("square/go-jose: an error")}, errors.New("an error")}, {"no trim", args{errors.New("json: an error")}, errors.New("json: an error")}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := TrimPrefix(tt.args.err); !reflect.DeepEqual(err, tt.wantErr) && err.Error() != tt.wantErr.Error() { //nolint:govet // variable names match crypto formulae docs t.Errorf("TrimPrefix() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestSignVerify(t *testing.T) { must := func(args ...interface{}) crypto.Signer { last := len(args) - 1 if err := args[last]; err != nil { t.Fatal(err) } return args[last-1].(crypto.Signer) } p224 := must(ecdsa.GenerateKey(elliptic.P224(), rand.Reader)) p256 := must(ecdsa.GenerateKey(elliptic.P256(), rand.Reader)) p384 := must(ecdsa.GenerateKey(elliptic.P384(), rand.Reader)) p521 := must(ecdsa.GenerateKey(elliptic.P521(), rand.Reader)) rsa2048 := must(rsa.GenerateKey(rand.Reader, 2048)) edKey := must(ed25519.GenerateKey(rand.Reader)) xKey := must(x25519.GenerateKey(rand.Reader)) type args struct { sig SigningKey opts *SignerOptions } tests := []struct { name string args args wantErr bool }{ {"byte", args{SigningKey{Key: []byte("the-key")}, nil}, false}, {"P256", args{SigningKey{Key: p256}, nil}, false}, {"P384", args{SigningKey{Key: p384}, nil}, false}, {"P521", args{SigningKey{Key: p521}, nil}, false}, {"rsa2048", args{SigningKey{Key: rsa2048}, nil}, false}, {"ed", args{SigningKey{Key: edKey}, nil}, false}, {"x25519", args{SigningKey{Key: xKey}, nil}, false}, {"fail P224", args{SigningKey{Key: p224}, nil}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := NewSigner(tt.args.sig, tt.args.opts) if (err != nil) != tt.wantErr { t.Errorf("NewSigner() error = %v, wantErr %v", err, tt.wantErr) return } if !tt.wantErr { payload := []byte(`{"sub": "sub"}`) jws, err := got.Sign(payload) if err != nil { t.Errorf("Signer.Sign() error = %v", err) return } jwt, err := ParseSigned(jws.FullSerialize()) if err != nil { t.Errorf("ParseSigned() error = %v", err) return } var claims Claims if signer, ok := tt.args.sig.Key.(crypto.Signer); ok { err = Verify(jwt, signer.Public(), &claims) } else { err = Verify(jwt, tt.args.sig.Key, &claims) } if err != nil { t.Errorf("JSONWebSignature.Verify() error = %v", err) return } want := Claims{Subject: "sub"} if !reflect.DeepEqual(claims, want) { t.Errorf("JSONWebSignature.Verify() claims = %v, want %v", claims, want) } } }) } } golang-step-crypto-0.24.0/jose/validate.go000066400000000000000000000141121437037643100204220ustar00rootroot00000000000000package jose import ( "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" "crypto/sha1" //nolint:gosec // RFC 7515 - X.509 Certificate SHA-1 Thumbprint "crypto/x509" "encoding/base64" "fmt" "os" "github.com/pkg/errors" "go.step.sm/crypto/keyutil" "golang.org/x/crypto/ssh" ) // ValidateSSHPOP validates the given SSH certificate and key for use in an // sshpop header. func ValidateSSHPOP(certFile string, key interface{}) (string, error) { if certFile == "" { return "", errors.New("ssh certfile cannot be empty") } certBytes, err := os.ReadFile(certFile) if err != nil { return "", errors.Wrapf(err, "error reading ssh certificate from %s", certFile) } sshpub, _, _, _, err := ssh.ParseAuthorizedKey(certBytes) if err != nil { return "", errors.Wrapf(err, "error parsing ssh public key from %s", certFile) } cert, ok := sshpub.(*ssh.Certificate) if !ok { return "", errors.New("error casting ssh public key to ssh certificate") } pubkey, err := keyutil.ExtractKey(cert) if err != nil { return "", errors.Wrap(err, "error extracting public key from ssh public key interface") } if err = keyutil.VerifyPair(pubkey, key); err != nil { return "", errors.Wrap(err, "error verifying ssh key pair") } return base64.StdEncoding.EncodeToString(cert.Marshal()), nil } func validateX5(certs []*x509.Certificate, key interface{}) error { if len(certs) == 0 { return errors.New("certs cannot be empty") } // Compare public keys if we have an opaque signer, otherwise check that private/public match if opaqueSigner, isOpaqueSigner := key.(OpaqueSigner); isOpaqueSigner { signerPub, ok := opaqueSigner.Public().Key.(crypto.PublicKey) if !ok { return errors.Errorf("opaqueSigner public key type %T is not supported", signerPub) } pub, ok := certs[0].PublicKey.(interface{ Equal(crypto.PublicKey) bool }) if ok { if !pub.Equal(signerPub) { return fmt.Errorf("public keys do not match on certificate and key") } } else { return errors.Errorf("unsupported public key type %T", certs[0].PublicKey) } } else { if err := keyutil.VerifyPair(certs[0].PublicKey, key); err != nil { return errors.Wrap(err, "error verifying certificate and key") } } if certs[0].KeyUsage&x509.KeyUsageDigitalSignature == 0 { return errors.New("certificate/private-key pair used to sign " + "token is not approved for digital signature") } return nil } // ValidateX5C validates the given certificate chain and key for use as a token // signer and x5t header. func ValidateX5C(certs []*x509.Certificate, key interface{}) ([]string, error) { if err := validateX5(certs, key); err != nil { return nil, errors.Wrap(err, "ValidateX5C") } strs := make([]string, len(certs)) for i, cert := range certs { strs[i] = base64.StdEncoding.EncodeToString(cert.Raw) } return strs, nil } // ValidateX5T validates the given certificate and key for use as a token signer // and x5t header. func ValidateX5T(certs []*x509.Certificate, key interface{}) (string, error) { if err := validateX5(certs, key); err != nil { return "", errors.Wrap(err, "ValidateX5T") } // x5t is the base64 URL encoded SHA1 thumbprint // (see https://tools.ietf.org/html/rfc7515#section-4.1.7) //nolint:gosec // RFC 7515 - X.509 Certificate SHA-1 Thumbprint fingerprint := sha1.Sum(certs[0].Raw) return base64.URLEncoding.EncodeToString(fingerprint[:]), nil } // ValidateJWK validates the given JWK. func ValidateJWK(jwk *JSONWebKey) error { switch jwk.Use { case "sig": return validateSigJWK(jwk) case "enc": return validateEncJWK(jwk) default: return validateGeneric(jwk) } } // validateSigJWK validates the given JWK for signature operations. func validateSigJWK(jwk *JSONWebKey) error { if jwk.Algorithm == "" { return errors.New("flag '--alg' is required with the given key") } errctx := "the given key" switch k := jwk.Key.(type) { case []byte: switch jwk.Algorithm { case HS256, HS384, HS512: return nil } errctx = "kty 'oct'" case *rsa.PrivateKey, *rsa.PublicKey: switch jwk.Algorithm { case RS256, RS384, RS512: return nil case PS256, PS384, PS512: return nil } errctx = "kty 'RSA'" case *ecdsa.PrivateKey: curve := k.Params().Name switch { case jwk.Algorithm == ES256 && curve == P256: return nil case jwk.Algorithm == ES384 && curve == P384: return nil case jwk.Algorithm == ES512 && curve == P521: return nil } errctx = fmt.Sprintf("kty 'EC' and crv '%s'", curve) case *ecdsa.PublicKey: curve := k.Params().Name switch { case jwk.Algorithm == ES256 && curve == P256: return nil case jwk.Algorithm == ES384 && curve == P384: return nil case jwk.Algorithm == ES512 && curve == P521: return nil } errctx = fmt.Sprintf("kty 'EC' and crv '%s'", curve) case ed25519.PrivateKey, ed25519.PublicKey: if jwk.Algorithm == EdDSA { return nil } errctx = "kty 'OKP' and crv 'Ed25519'" } return errors.Errorf("alg '%s' is not compatible with %s", jwk.Algorithm, errctx) } // validatesEncJWK validates the given JWK for encryption operations. func validateEncJWK(jwk *JSONWebKey) error { alg := KeyAlgorithm(jwk.Algorithm) var kty string switch jwk.Key.(type) { case []byte: switch alg { case DIRECT, A128GCMKW, A192GCMKW, A256GCMKW, A128KW, A192KW, A256KW: return nil } kty = "oct" case *rsa.PrivateKey, *rsa.PublicKey: switch alg { case RSA1_5, RSA_OAEP, RSA_OAEP_256: return nil } kty = "RSA" case *ecdsa.PrivateKey, *ecdsa.PublicKey: switch alg { case ECDH_ES, ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW: return nil } kty = "EC" case ed25519.PrivateKey, ed25519.PublicKey: return errors.New("key Ed25519 cannot be used for encryption") } return errors.Errorf("alg '%s' is not compatible with kty '%s'", jwk.Algorithm, kty) } // validateGeneric validates just the supported key types. func validateGeneric(jwk *JSONWebKey) error { switch jwk.Key.(type) { case []byte: return nil case *rsa.PrivateKey, *rsa.PublicKey: return nil case *ecdsa.PrivateKey, *ecdsa.PublicKey: return nil case ed25519.PrivateKey, ed25519.PublicKey: return nil } return errors.Errorf("unsupported key type '%T'", jwk.Key) } golang-step-crypto-0.24.0/jose/validate_test.go000066400000000000000000000124561437037643100214720ustar00rootroot00000000000000package jose import ( "crypto" "crypto/sha1" // nolint:gosec // RFC 7515 - X.509 Certificate SHA-1 Thumbprint "crypto/x509" "encoding/base64" "testing" "github.com/pkg/errors" "github.com/smallstep/assert" "go.step.sm/crypto/pemutil" ) var ( badCertFile = "./testdata/bad-rsa.crt" badKeyFile = "./testdata/bad-rsa.key" certFile = "./testdata/rsa2048.crt" keyFile = "./testdata/rsa2048.key" ) func Test_validateX5(t *testing.T) { type test struct { certs []*x509.Certificate key interface{} err error } tests := map[string]func() test{ "fail/empty-certs": func() test { return test{ certs: []*x509.Certificate{}, key: nil, err: errors.New("certs cannot be empty"), } }, "fail/bad-key": func() test { certs, err := pemutil.ReadCertificateBundle(certFile) assert.FatalError(t, err) return test{ certs: certs, key: nil, err: errors.New("error verifying certificate and key"), } }, "fail/cert-not-approved-for-digital-signature": func() test { certs, err := pemutil.ReadCertificateBundle(badCertFile) assert.FatalError(t, err) k, err := pemutil.Read(badKeyFile) assert.FatalError(t, err) return test{ certs: certs, key: k, err: errors.New("certificate/private-key pair used to sign " + "token is not approved for digital signature"), } }, "ok": func() test { certs, err := pemutil.ReadCertificateBundle(certFile) assert.FatalError(t, err) k, err := pemutil.Read(keyFile) assert.FatalError(t, err) return test{ certs: certs, key: k, } }, } for name, run := range tests { t.Run(name, func(t *testing.T) { tc := run() if err := validateX5(tc.certs, tc.key); err != nil { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { assert.Nil(t, tc.err) } }) } } func TestValidateX5T(t *testing.T) { type test struct { certs []*x509.Certificate key interface{} fp string err error } tests := map[string]func() test{ "fail/validateX5-error": func() test { return test{ certs: []*x509.Certificate{}, key: nil, err: errors.New("ValidateX5T: certs cannot be empty"), } }, "ok": func() test { certs, err := pemutil.ReadCertificateBundle(certFile) assert.FatalError(t, err) k, err := pemutil.Read(keyFile) assert.FatalError(t, err) cert, err := pemutil.ReadCertificate(certFile) assert.FatalError(t, err) // x5t is the base64 URL encoded SHA1 thumbprint // (see https://tools.ietf.org/html/rfc7515#section-4.1.7) // nolint:gosec // RFC 7515 - X.509 Certificate SHA-1 Thumbprint fp := sha1.Sum(cert.Raw) return test{ certs: certs, key: k, fp: base64.URLEncoding.EncodeToString(fp[:]), } }, } for name, run := range tests { t.Run(name, func(t *testing.T) { tc := run() if fingerprint, err := ValidateX5T(tc.certs, tc.key); err != nil { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { assert.Nil(t, tc.err) assert.Equals(t, tc.fp, fingerprint) } }) } } func TestValidateX5C(t *testing.T) { type test struct { certs []*x509.Certificate key interface{} err error } tests := map[string]func() test{ "fail/validateX5-error": func() test { return test{ certs: []*x509.Certificate{}, key: nil, err: errors.New("ValidateX5C: certs cannot be empty"), } }, "ok": func() test { certs, err := pemutil.ReadCertificateBundle(certFile) assert.FatalError(t, err) k, err := pemutil.Read(keyFile) assert.FatalError(t, err) return test{ certs: certs, key: k, } }, "ok/opaque": func() test { certs, err := pemutil.ReadCertificateBundle(certFile) assert.FatalError(t, err) k, err := pemutil.Read(keyFile) assert.FatalError(t, err) sig, ok := k.(crypto.Signer) assert.True(t, ok) op := NewOpaqueSigner(sig) return test{ certs: certs, key: op, } }, } for name, run := range tests { t.Run(name, func(t *testing.T) { tc := run() if certs, err := ValidateX5C(tc.certs, tc.key); err != nil { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { assert.Nil(t, tc.err) assert.Equals(t, []string{`MIIDCTCCAfGgAwIBAgIQIdY8a5pFZ/FGUowvuGdJvTANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDEwR0ZXN0MB4XDTIwMDQyMTA0MDg0MFoXDTIwMDQyMjA0MDg0MFowDzENMAsGA1UEAxMEdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL0zsTneONS0eNto7LMlD8GtcUYXqILSEWD07v0HgbIguBT+yC8BozpAT3lyB+oBBZkFLzfEHAteULngPwlq0R5hsEZJ6lcL1Z9WXwyLE4nkEndIPMA+zQmHnOzoqgKy7pIqUnFqSGXtGp384fFF3Y0/qjeFciLnmf+Wn0PneaToY1rDj2Eb9sFf5UDiVaSLT1NzpSyXOS5uGbGplPe+WE8uEb3u3Vg2VGbEPau2l5MPYroCwSyxqlpKsmzJ558uvjQ7KpRExSNdb6f0iRfdRMbw3LahrxhbKV1mmM6GD5onmbgBCZpw5htOJj1MzVFZOdnoTHmMl/Y/IUdMjv0jG/UCAwEAAaNhMF8wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUXlQCQL6RymQZnvqY15F/GlE3H4UwDwYDVR0RBAgwBoIEdGVzdDANBgkqhkiG9w0BAQsFAAOCAQEArVmOL5L+NVsnCcUWfOVXQYg/6P8AGdPJBECk+BE5pbtMg0GuH5Ml/vCTBqD+diWC4O0TZDxPMhXH5Ehl+67hcqeu4riwB2WvvKOlAqHqIuqVDRHtxwknvS1efstBKVdDC6aAfIa5f2dmCSxvd8elpcnufEefLGALTSPxg4uMVvpfWUkkmpmvOUpI3gNrlvP2H4KZk7hKYz+J4x2jv2pdPWUAtt1U4M8oQ4BCPrrHSxznw2Q5mdCMIB64ZeYnZ+rAMQS6WnZy1fTC3d0pCs0UCXH5JefBpha1clqHDUkxHA6/1EYYsSlKGFaPEmfv2uw7MFz0o+yntG34KVdsC8HO3g==`}, certs) } }) } } golang-step-crypto-0.24.0/jose/x25519.go000066400000000000000000000037471437037643100175220ustar00rootroot00000000000000package jose import ( "crypto" "crypto/rand" "encoding/base64" "fmt" "github.com/pkg/errors" "go.step.sm/crypto/x25519" ) const x25519ThumbprintTemplate = `{"crv":"X25519","kty":"OKP","x":%q}` func x25519Thumbprint(key x25519.PublicKey, hash crypto.Hash) ([]byte, error) { if len(key) != 32 { return nil, errors.New("invalid elliptic key") } h := hash.New() fmt.Fprintf(h, x25519ThumbprintTemplate, base64.RawURLEncoding.EncodeToString(key)) return h.Sum(nil), nil } // X25519Signer implements the jose.OpaqueSigner using an X25519 key and XEdDSA // as the signing algorithm. type X25519Signer x25519.PrivateKey // Public returns the public key of the current signing key. func (s X25519Signer) Public() *JSONWebKey { return &JSONWebKey{ Key: x25519.PrivateKey(s).Public(), } } // Algs returns a list of supported signing algorithms, in this case only // XEdDSA. func (s X25519Signer) Algs() []SignatureAlgorithm { return []SignatureAlgorithm{ XEdDSA, } } // SignPayload signs a payload with the current signing key using the given // algorithm, it will fail if it's not XEdDSA. func (s X25519Signer) SignPayload(payload []byte, alg SignatureAlgorithm) ([]byte, error) { if alg != XEdDSA { return nil, errors.Errorf("x25519 key does not support the signature algorithm %s", alg) } return x25519.PrivateKey(s).Sign(rand.Reader, payload, crypto.Hash(0)) } // X25519Verifier implements the jose.OpaqueVerifier interface using an X25519 // key and XEdDSA as a signing algorithm. type X25519Verifier x25519.PublicKey // VerifyPayload verifies the given signature using the X25519 public key, it // will fail if the signature algorithm is not XEdDSA. func (v X25519Verifier) VerifyPayload(payload, signature []byte, alg SignatureAlgorithm) error { if alg != XEdDSA { return errors.Errorf("x25519 key does not support the signature algorithm %s", alg) } if !x25519.Verify(x25519.PublicKey(v), payload, signature) { return errors.New("failed to verify XEdDSA signature") } return nil } golang-step-crypto-0.24.0/jose/x25519_test.go000066400000000000000000000252421437037643100205530ustar00rootroot00000000000000package jose import ( "crypto" "crypto/ed25519" "crypto/rand" "reflect" "testing" "time" "go.step.sm/crypto/x25519" ) func Test_x25519Thumbprint(t *testing.T) { type args struct { key x25519.PublicKey hash crypto.Hash } tests := []struct { name string args args want []byte wantErr bool }{ {"ok", args{[]byte{ 0x0f, 0xf9, 0x2b, 0x4a, 0xa2, 0x9d, 0x86, 0xc5, 0x27, 0xbf, 0x44, 0x24, 0xbc, 0x5c, 0xae, 0x7d, 0x48, 0x1d, 0xcb, 0x61, 0x8d, 0xea, 0x2f, 0x9a, 0x88, 0xe4, 0x3c, 0xf5, 0xc4, 0xc7, 0xdb, 0x11, }, crypto.SHA256}, []byte{ 0x24, 0x2d, 0xa5, 0xce, 0xcd, 0x0a, 0x21, 0x5d, 0x3e, 0xc8, 0x4d, 0x2e, 0xe9, 0x1b, 0x6c, 0x70, 0xc2, 0xa9, 0xca, 0x2b, 0x45, 0x4b, 0xcd, 0x53, 0x79, 0xfa, 0xce, 0x2e, 0xde, 0xe6, 0x1c, 0x79, }, false}, {"fail too short", args{[]byte{ 0x0f, 0xf9, 0x2b, 0x4a, 0xa2, 0x9d, 0x86, 0xc5, 0x27, 0xbf, 0x44, 0x24, 0xbc, 0x5c, 0xae, 0x7d, 0x48, 0x1d, 0xcb, 0x61, 0x8d, 0xea, 0x2f, 0x9a, 0x88, 0xe4, 0x3c, 0xf5, 0xc4, 0xc7, 0xdb, }, crypto.SHA256}, nil, true}, {"fail too long", args{[]byte{ 0x0f, 0xf9, 0x2b, 0x4a, 0xa2, 0x9d, 0x86, 0xc5, 0x27, 0xbf, 0x44, 0x24, 0xbc, 0x5c, 0xae, 0x7d, 0x48, 0x1d, 0xcb, 0x61, 0x8d, 0xea, 0x2f, 0x9a, 0x88, 0xe4, 0x3c, 0xf5, 0xc4, 0xc7, 0xdb, 0x11, 0x12, }, crypto.SHA256}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := x25519Thumbprint(tt.args.key, tt.args.hash) if (err != nil) != tt.wantErr { t.Errorf("x25519Thumbprint() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("x25519Thumbprint() = %v, want %v", got, tt.want) } }) } } func TestX25519Signer_SignVerify(t *testing.T) { pub, priv, err := x25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } edPub, edPriv, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } type args struct { sig SigningKey opts *SignerOptions payload []byte publicKey crypto.PublicKey } tests := []struct { name string args args want Claims wantErr bool }{ {"ok", args{ SigningKey{ Algorithm: XEdDSA, Key: x25519.PrivateKey{ 0x4e, 0x6c, 0xd9, 0xe8, 0x40, 0x4d, 0xe1, 0xe0, 0xc5, 0x22, 0x72, 0x8f, 0x78, 0xde, 0x88, 0x26, 0x58, 0x4b, 0x44, 0xef, 0xbc, 0xf6, 0xac, 0x10, 0x97, 0x67, 0x60, 0xcc, 0x41, 0xf1, 0x82, 0x0b, }, }, new(SignerOptions).WithType("JWT"), []byte(`{"iss":"test-iss","sub":"test-sub","aud":"test-aud","exp":1234,"nbf":1200,"iat":1000,"jti":"test-jti"}`), x25519.PublicKey{ 0x51, 0xf3, 0xfe, 0x72, 0xb4, 0x35, 0x66, 0x52, 0x16, 0x16, 0x38, 0xfb, 0x9b, 0xf7, 0x17, 0x06, 0x87, 0xb6, 0x35, 0x53, 0xc1, 0x63, 0x9c, 0x73, 0xa7, 0x79, 0x1e, 0xd2, 0xba, 0xf8, 0xcb, 0x67, }, }, Claims{ Issuer: "test-iss", Subject: "test-sub", Audience: []string{"test-aud"}, Expiry: NewNumericDate(time.Unix(1234, 0)), NotBefore: NewNumericDate(time.Unix(1200, 0)), IssuedAt: NewNumericDate(time.Unix(1000, 0)), ID: "test-jti", }, false}, {"ok empty", args{ SigningKey{ Algorithm: XEdDSA, Key: x25519.PrivateKey{ 0x4e, 0x6c, 0xd9, 0xe8, 0x40, 0x4d, 0xe1, 0xe0, 0xc5, 0x22, 0x72, 0x8f, 0x78, 0xde, 0x88, 0x26, 0x58, 0x4b, 0x44, 0xef, 0xbc, 0xf6, 0xac, 0x10, 0x97, 0x67, 0x60, 0xcc, 0x41, 0xf1, 0x82, 0x0b, }, }, new(SignerOptions).WithType("JWT"), []byte(`{}`), x25519.PublicKey{ 0x51, 0xf3, 0xfe, 0x72, 0xb4, 0x35, 0x66, 0x52, 0x16, 0x16, 0x38, 0xfb, 0x9b, 0xf7, 0x17, 0x06, 0x87, 0xb6, 0x35, 0x53, 0xc1, 0x63, 0x9c, 0x73, 0xa7, 0x79, 0x1e, 0xd2, 0xba, 0xf8, 0xcb, 0x67, }, }, Claims{}, false}, {"ok random", args{ SigningKey{Algorithm: XEdDSA, Key: priv}, new(SignerOptions).WithType("JWT"), []byte(`{"iss":"test-iss","sub":"test-sub","aud":"test-aud","exp":1234,"nbf":1200,"iat":1000,"jti":"test-jti"}`), pub, }, Claims{ Issuer: "test-iss", Subject: "test-sub", Audience: []string{"test-aud"}, Expiry: NewNumericDate(time.Unix(1234, 0)), NotBefore: NewNumericDate(time.Unix(1200, 0)), IssuedAt: NewNumericDate(time.Unix(1000, 0)), ID: "test-jti", }, false}, {"ok ed25519", args{ SigningKey{Algorithm: EdDSA, Key: edPriv}, new(SignerOptions).WithType("JWT"), []byte(`{"iss":"test-iss","sub":"test-sub","aud":"test-aud","exp":1234,"nbf":1200,"iat":1000,"jti":"test-jti"}`), edPub, }, Claims{ Issuer: "test-iss", Subject: "test-sub", Audience: []string{"test-aud"}, Expiry: NewNumericDate(time.Unix(1234, 0)), NotBefore: NewNumericDate(time.Unix(1200, 0)), IssuedAt: NewNumericDate(time.Unix(1000, 0)), ID: "test-jti", }, false}, {"fail algorithm", args{ SigningKey{Algorithm: EdDSA, Key: priv}, new(SignerOptions).WithType("JWT"), []byte(`{"iss":"test-iss","sub":"test-sub","aud":"test-aud","exp":1234,"nbf":1200,"iat":1000,"jti":"test-jti"}`), pub, }, Claims{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { signer, err := NewSigner(tt.args.sig, tt.args.opts) if (err != nil) != tt.wantErr { t.Errorf("NewSigner() error = %v, wantErr %v", err, tt.wantErr) return } if !tt.wantErr { var got Claims sig, err := signer.Sign(tt.args.payload) if err != nil { t.Errorf("Signer.Sign() error = %v", err) return } jwt, err := ParseSigned(sig.FullSerialize()) if err != nil { t.Errorf("ParseSigned() error = %v", err) return } if err := Verify(jwt, tt.args.publicKey, &got); err != nil { t.Errorf("Verify() error = %v", err) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("JSONWebSignature.CompactSerialize() = %v, want %v", got, tt.want) } } }) } } func TestX25519Signer_Public(t *testing.T) { pub, priv, err := x25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } tests := []struct { name string s X25519Signer want *JSONWebKey }{ {"ok", X25519Signer(priv), &JSONWebKey{Key: pub}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.s.Public(); !reflect.DeepEqual(got, tt.want) { t.Errorf("X25519Signer.Public() = %v, want %v", got, tt.want) } }) } } func TestX25519Signer_Algs(t *testing.T) { _, priv, err := x25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } tests := []struct { name string s X25519Signer want []SignatureAlgorithm }{ {"ok", X25519Signer(priv), []SignatureAlgorithm{XEdDSA}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.s.Algs(); !reflect.DeepEqual(got, tt.want) { t.Errorf("X25519Signer.Algs() = %v, want %v", got, tt.want) } }) } } func TestX25519Signer_SignPayload(t *testing.T) { buf := mustTeeReader(t) rand.Reader = buf // Random buf.Write([]byte{ 0x4e, 0x6c, 0xd9, 0xe8, 0x40, 0x4d, 0xe1, 0xe0, 0xc5, 0x22, 0x72, 0x8f, 0x78, 0xde, 0x88, 0x26, 0x58, 0x4b, 0x44, 0xef, 0xbc, 0xf6, 0xac, 0x10, 0x97, 0x67, 0x60, 0xcc, 0x41, 0xf1, 0x82, 0xb, 0xb8, 0x69, 0x6, 0xf3, 0xad, 0xa5, 0x46, 0x6f, 0xc2, 0xf3, 0x9, 0x78, 0x7b, 0x3c, 0x6f, 0xd6, 0x39, 0x19, 0xb1, 0x48, 0xd6, 0x6, 0xb7, 0x79, 0xfb, 0xf7, 0xda, 0x61, 0xee, 0x57, 0x7b, 0xe6, }) signer := X25519Signer{ 0xb0, 0x3d, 0x85, 0x79, 0x6d, 0x92, 0x89, 0x78, 0x26, 0xaf, 0x9d, 0xb9, 0x13, 0x98, 0xf3, 0xf9, 0x73, 0x7d, 0x5f, 0x5c, 0xde, 0x76, 0xd1, 0xc4, 0x4c, 0x3a, 0x3f, 0xa9, 0x6e, 0xe5, 0x19, 0x46, } type args struct { payload []byte alg SignatureAlgorithm } tests := []struct { name string s X25519Signer args args want []byte wantErr bool }{ {"ok", signer, args{[]byte(`{}`), XEdDSA}, []byte{ 0xe5, 0x1a, 0xc0, 0x63, 0xd7, 0xfa, 0xf9, 0x0e, 0xe1, 0xfd, 0xfa, 0x03, 0xcb, 0x14, 0x1a, 0xc1, 0x63, 0x15, 0x83, 0x69, 0xf5, 0xf9, 0x3f, 0xa9, 0x8b, 0xa2, 0xc2, 0x6a, 0xc3, 0x8c, 0x23, 0xec, 0xa6, 0xa7, 0xea, 0x17, 0x82, 0x80, 0xcf, 0x02, 0x4a, 0x61, 0x93, 0x99, 0x74, 0x85, 0xa4, 0x20, 0xe0, 0xa0, 0xe0, 0x26, 0x28, 0x79, 0xbd, 0xf3, 0x59, 0x6e, 0xbe, 0x40, 0xbe, 0xb0, 0xf8, 0x08, }, false}, {"fail", signer, args{[]byte(`{}`), EdDSA}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.s.SignPayload(tt.args.payload, tt.args.alg) if (err != nil) != tt.wantErr { t.Errorf("X25519Signer.SignPayload() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("X25519Signer.SignPayload() = %x, want %v", got, tt.want) } }) } } func TestX25519Verifier_VerifyPayload(t *testing.T) { verifier := X25519Verifier{ 0x47, 0x0e, 0x9b, 0xf6, 0x21, 0x5e, 0xbd, 0x7e, 0xc9, 0xaa, 0x9f, 0xb7, 0xfa, 0xd1, 0xc7, 0xdd, 0xaf, 0x6e, 0x12, 0x44, 0x9e, 0xac, 0xd7, 0x43, 0x53, 0xe4, 0x0e, 0x3f, 0xfa, 0x24, 0x0b, 0x3c, } type args struct { payload []byte signature []byte alg SignatureAlgorithm } tests := []struct { name string v X25519Verifier args args wantErr bool }{ {"ok", verifier, args{[]byte(`{}`), []byte{ 0xe5, 0x1a, 0xc0, 0x63, 0xd7, 0xfa, 0xf9, 0x0e, 0xe1, 0xfd, 0xfa, 0x03, 0xcb, 0x14, 0x1a, 0xc1, 0x63, 0x15, 0x83, 0x69, 0xf5, 0xf9, 0x3f, 0xa9, 0x8b, 0xa2, 0xc2, 0x6a, 0xc3, 0x8c, 0x23, 0xec, 0xa6, 0xa7, 0xea, 0x17, 0x82, 0x80, 0xcf, 0x02, 0x4a, 0x61, 0x93, 0x99, 0x74, 0x85, 0xa4, 0x20, 0xe0, 0xa0, 0xe0, 0x26, 0x28, 0x79, 0xbd, 0xf3, 0x59, 0x6e, 0xbe, 0x40, 0xbe, 0xb0, 0xf8, 0x08, }, XEdDSA}, false}, {"fail signature", verifier, args{[]byte(`{}`), []byte{ 0x00, 0x1a, 0xc0, 0x63, 0xd7, 0xfa, 0xf9, 0x0e, 0xe1, 0xfd, 0xfa, 0x03, 0xcb, 0x14, 0x1a, 0xc1, 0x63, 0x15, 0x83, 0x69, 0xf5, 0xf9, 0x3f, 0xa9, 0x8b, 0xa2, 0xc2, 0x6a, 0xc3, 0x8c, 0x23, 0xec, 0xa6, 0xa7, 0xea, 0x17, 0x82, 0x80, 0xcf, 0x02, 0x4a, 0x61, 0x93, 0x99, 0x74, 0x85, 0xa4, 0x20, 0xe0, 0xa0, 0xe0, 0x26, 0x28, 0x79, 0xbd, 0xf3, 0x59, 0x6e, 0xbe, 0x40, 0xbe, 0xb0, 0xf8, 0x08, }, XEdDSA}, true}, {"fail algorithm", verifier, args{[]byte(`{}`), []byte{ 0xe5, 0x1a, 0xc0, 0x63, 0xd7, 0xfa, 0xf9, 0x0e, 0xe1, 0xfd, 0xfa, 0x03, 0xcb, 0x14, 0x1a, 0xc1, 0x63, 0x15, 0x83, 0x69, 0xf5, 0xf9, 0x3f, 0xa9, 0x8b, 0xa2, 0xc2, 0x6a, 0xc3, 0x8c, 0x23, 0xec, 0xa6, 0xa7, 0xea, 0x17, 0x82, 0x80, 0xcf, 0x02, 0x4a, 0x61, 0x93, 0x99, 0x74, 0x85, 0xa4, 0x20, 0xe0, 0xa0, 0xe0, 0x26, 0x28, 0x79, 0xbd, 0xf3, 0x59, 0x6e, 0xbe, 0x40, 0xbe, 0xb0, 0xf8, 0x08, }, EdDSA}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := tt.v.VerifyPayload(tt.args.payload, tt.args.signature, tt.args.alg); (err != nil) != tt.wantErr { t.Errorf("X25519Verifier.VerifyPayload() error = %v, wantErr %v", err, tt.wantErr) } }) } } golang-step-crypto-0.24.0/keyutil/000077500000000000000000000000001437037643100170315ustar00rootroot00000000000000golang-step-crypto-0.24.0/keyutil/key.go000066400000000000000000000155501437037643100201560ustar00rootroot00000000000000// Package keyutil implements utilities to generate cryptographic keys. package keyutil import ( "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/x509" "math/big" "sync/atomic" "github.com/pkg/errors" "go.step.sm/crypto/x25519" "golang.org/x/crypto/ssh" ) var ( // DefaultKeyType is the default type of a private key. DefaultKeyType = "EC" // DefaultKeySize is the default size (in # of bits) of a private key. DefaultKeySize = 2048 // DefaultKeyCurve is the default curve of a private key. DefaultKeyCurve = "P-256" // DefaultSignatureAlgorithm is the default signature algorithm used on a // certificate with the default key type. DefaultSignatureAlgorithm = x509.ECDSAWithSHA256 // MinRSAKeyBytes is the minimum acceptable size (in bytes) for RSA keys // signed by the authority. MinRSAKeyBytes = 256 ) type atomicBool int32 func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 } func (b *atomicBool) setTrue() { atomic.StoreInt32((*int32)(b), 1) } func (b *atomicBool) setFalse() { atomic.StoreInt32((*int32)(b), 0) } var insecureMode atomicBool // Insecure enables the insecure mode in this package and returns a function to // revert the configuration. The insecure mode removes the minimum limits when // generating RSA keys. func Insecure() (revert func()) { insecureMode.setTrue() return func() { insecureMode.setFalse() } } // PublicKey extracts a public key from a private key. func PublicKey(priv interface{}) (crypto.PublicKey, error) { switch k := priv.(type) { case *rsa.PrivateKey: return &k.PublicKey, nil case *ecdsa.PrivateKey: return &k.PublicKey, nil case ed25519.PrivateKey: return k.Public(), nil case x25519.PrivateKey: return k.Public(), nil case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey, x25519.PublicKey: return k, nil default: return nil, errors.Errorf("unrecognized key type: %T", priv) } } // GenerateDefaultKey generates a public/private key pair using sane defaults // for key type, curve, and size. func GenerateDefaultKey() (crypto.PrivateKey, error) { return GenerateKey(DefaultKeyType, DefaultKeyCurve, DefaultKeySize) } // GenerateDefaultKeyPair generates a public/private key pair using configured // default values for key type, curve, and size. func GenerateDefaultKeyPair() (crypto.PublicKey, crypto.PrivateKey, error) { return GenerateKeyPair(DefaultKeyType, DefaultKeyCurve, DefaultKeySize) } // GenerateKey generates a key of the given type (kty). func GenerateKey(kty, crv string, size int) (crypto.PrivateKey, error) { switch kty { case "EC", "RSA", "OKP": return GenerateSigner(kty, crv, size) case "oct": return generateOctKey(size) default: return nil, errors.Errorf("unrecognized key type: %s", kty) } } // GenerateKeyPair creates an asymmetric crypto keypair using input // configuration. func GenerateKeyPair(kty, crv string, size int) (crypto.PublicKey, crypto.PrivateKey, error) { signer, err := GenerateSigner(kty, crv, size) if err != nil { return nil, nil, err } return signer.Public(), signer, nil } // GenerateDefaultSigner returns an asymmetric crypto key that implements // crypto.Signer using sane defaults. func GenerateDefaultSigner() (crypto.Signer, error) { return GenerateSigner(DefaultKeyType, DefaultKeyCurve, DefaultKeySize) } // GenerateSigner creates an asymmetric crypto key that implements // crypto.Signer. func GenerateSigner(kty, crv string, size int) (crypto.Signer, error) { switch kty { case "EC": return generateECKey(crv) case "RSA": return generateRSAKey(size) case "OKP": return generateOKPKey(crv) default: return nil, errors.Errorf("unrecognized key type: %s", kty) } } // ExtractKey returns the given public or private key or extracts the public key // if a x509.Certificate or x509.CertificateRequest is given. func ExtractKey(in interface{}) (interface{}, error) { switch k := in.(type) { case *rsa.PublicKey, *rsa.PrivateKey, *ecdsa.PublicKey, *ecdsa.PrivateKey, ed25519.PublicKey, ed25519.PrivateKey, x25519.PublicKey, x25519.PrivateKey: return in, nil case []byte: return in, nil case *x509.Certificate: return k.PublicKey, nil case *x509.CertificateRequest: return k.PublicKey, nil case ssh.CryptoPublicKey: return k.CryptoPublicKey(), nil case *ssh.Certificate: return ExtractKey(k.Key) default: return nil, errors.Errorf("cannot extract the key from type '%T'", k) } } // VerifyPair that the public key matches the given private key. func VerifyPair(pubkey crypto.PublicKey, key crypto.PrivateKey) error { switch pub := pubkey.(type) { case *rsa.PublicKey: priv, ok := key.(*rsa.PrivateKey) if !ok { return errors.New("private key type does not match public key type") } if !pub.Equal(priv.Public()) { return errors.New("private key does not match public key") } case *ecdsa.PublicKey: priv, ok := key.(*ecdsa.PrivateKey) if !ok { return errors.New("private key type does not match public key type") } if !pub.Equal(priv.Public()) { return errors.New("private key does not match public key") } case ed25519.PublicKey: priv, ok := key.(ed25519.PrivateKey) if !ok { return errors.New("private key type does not match public key type") } if !pub.Equal(priv.Public()) { return errors.New("private key does not match public key") } default: return errors.Errorf("unsupported public key type %T", pub) } return nil } func generateECKey(crv string) (crypto.Signer, error) { var c elliptic.Curve switch crv { case "P-256": c = elliptic.P256() case "P-384": c = elliptic.P384() case "P-521": c = elliptic.P521() default: return nil, errors.Errorf("invalid value for argument crv (crv: '%s')", crv) } key, err := ecdsa.GenerateKey(c, rand.Reader) if err != nil { return nil, errors.Wrap(err, "error generating EC key") } return key, nil } func generateRSAKey(bits int) (crypto.Signer, error) { if min := MinRSAKeyBytes * 8; !insecureMode.isSet() && bits < min { return nil, errors.Errorf("the size of the RSA key should be at least %d bits", min) } key, err := rsa.GenerateKey(rand.Reader, bits) if err != nil { return nil, errors.Wrap(err, "error generating RSA key") } return key, nil } func generateOKPKey(crv string) (crypto.Signer, error) { switch crv { case "Ed25519": _, key, err := ed25519.GenerateKey(rand.Reader) if err != nil { return nil, errors.Wrap(err, "error generating Ed25519 key") } return key, nil default: return nil, errors.Errorf("missing or invalid value for argument 'crv'. "+ "expected 'Ed25519', but got '%s'", crv) } } func generateOctKey(size int) (interface{}, error) { const chars = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" result := make([]byte, size) for i := range result { num, err := rand.Int(rand.Reader, big.NewInt(int64(len(chars)))) if err != nil { return nil, err } result[i] = chars[num.Int64()] } return result, nil } golang-step-crypto-0.24.0/keyutil/key_test.go000066400000000000000000000513771437037643100212240ustar00rootroot00000000000000package keyutil import ( "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" "io" "reflect" "testing" "github.com/smallstep/assert" "go.step.sm/crypto/x25519" "golang.org/x/crypto/ssh" ) const ( testCRT = `-----BEGIN CERTIFICATE----- MIICLjCCAdSgAwIBAgIQBvswFbAODY9xtJ/myiuEHzAKBggqhkjOPQQDAjAkMSIw IAYDVQQDExlTbWFsbHN0ZXAgSW50ZXJtZWRpYXRlIENBMB4XDTE4MTEzMDE5NTkw OVoXDTE4MTIwMTE5NTkwOVowHjEcMBoGA1UEAxMTaGVsbG8uc21hbGxzdGVwLmNv bTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABIqPQy8roJTMWpEt8NNA1CnRm3l1 wdjH4OrVaH3l2Gp/UW737Wbn4sqSAFahmajuwkfRG5KMh2/+xnCkGuR2fayjge0w geowDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD AjAdBgNVHQ4EFgQU5bqyXvZaEmtZ3OpZapq7pBIkVvgwHwYDVR0jBBgwFoAUu97P aFQPfuyKOeew7Hg45WFIAVMwHgYDVR0RBBcwFYITaGVsbG8uc21hbGxzdGVwLmNv bTBZBgwrBgEEAYKkZMYoQAEESTBHAgEBBBVtYXJpYW5vQHNtYWxsc3RlcC5jb20E K2pPMzdkdERia3UtUW5hYnM1VlIwWXc2WUZGdjl3ZUExOGRwM2h0dmRFanMwCgYI KoZIzj0EAwIDSAAwRQIhALKeC2q0HWyHoZobZFK9HQynLbPOOtAK437RaetlX5ty AiBXQzvaLlDprQu+THj18aDYLnHA//5mdD3HPJV6KmgdDg== -----END CERTIFICATE-----` testCSR = `-----BEGIN CERTIFICATE REQUEST----- MIHYMIGAAgEAMB4xHDAaBgNVBAMTE2hlbGxvLnNtYWxsc3RlcC5jb20wWTATBgcq hkjOPQIBBggqhkjOPQMBBwNCAASKj0MvK6CUzFqRLfDTQNQp0Zt5dcHYx+Dq1Wh9 5dhqf1Fu9+1m5+LKkgBWoZmo7sJH0RuSjIdv/sZwpBrkdn2soAAwCgYIKoZIzj0E AwIDRwAwRAIgZgz9gdx9inOp6bSX4EkYiUCyLV9xGvabovu5C9UkRr8CIBGBbkp0 l4tesAKoXelsLygJjPuUGRLK+OtdjPBIN1Zo -----END CERTIFICATE REQUEST-----` testSSHPub = `ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFqJfS311sNwYHqZQXSkOTiAqId6jcgX+qIiG/23m/6UxaksvOilHvhOGOUkpRM9paoO/ViKLGYJB20gbJlO4Ro= jane@doe.com` testSSHCert = `ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgKWZGQ1gramJ1ayV7UiNJLmE5PGMpRjEoIjI6Jz9GUmUAAAAIbmlzdHAyNTYAAABBBFqJfS311sNwYHqZQXSkOTiAqId6jcgX+qIiG/23m/6UxaksvOilHvhOGOUkpRM9paoO/ViKLGYJB20gbJlO4RqCjePcANhGLAAAAAEAAAAMamFuZUBkb2UuY29tAAAAEQAAAARqYW5lAAAABWFkbWluAAAAAF80Q9gAAAAAXzUlFAAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMopHG2SUfV6NXyLKrSGkUyFJ8Zp8zFY+3wilPruczJbxY0kSAcr+cwfppEmO6SsPcClXcy59lx71eSDdGURqv8AAABkAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAABJAAAAIQCrKn7AJCX88hCZr1e3zfKZf18GmU2Rvqvmd0XKsl9J5gAAACAj2mnrgq0ScI4foEGv3gxglehBhrev02sqjiRchty7Pg== jane@doe.com` testSSHPubPEM = `-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWol9LfXWw3BgeplBdKQ5OICoh3qN yBf6oiIb/beb/pTFqSy86KUe+E4Y5SSlEz2lqg79WIosZgkHbSBsmU7hGg== -----END PUBLIC KEY-----` ) type badSSHPublicKey struct{} func (k *badSSHPublicKey) Type() string { return "foo" } func (k *badSSHPublicKey) Marshal() []byte { return []byte("bar") } func (k *badSSHPublicKey) Verify(data []byte, sig *ssh.Signature) error { return nil } func must(args ...interface{}) interface{} { if err := args[len(args)-1]; err != nil { panic(err) } return args[0] } var randReader = rand.Reader func cleanupRandReader(t *testing.T) { rr := rand.Reader t.Cleanup(func() { rand.Reader = rr }) } type zeroReader struct{} func (zeroReader) Read(buf []byte) (int, error) { for i := range buf { buf[i] = 0 } return len(buf), nil } type eofReader struct{} func (eofReader) Read(buf []byte) (int, error) { return 0, io.EOF } func verifyKeyPair(h crypto.Hash, priv, pub interface{}) error { s, ok := priv.(crypto.Signer) if !ok { return fmt.Errorf("type %T is not a crypto.Signer", priv) } var sum []byte if h == crypto.Hash(0) { sum = []byte("a message") } else { sum = h.New().Sum([]byte("a message")) } sig, err := s.Sign(randReader, sum, h) if err != nil { return fmt.Errorf("%T.Sign() error = %w", s, err) } switch p := pub.(type) { case *ecdsa.PublicKey: if !ecdsa.VerifyASN1(p, sum, sig) { return fmt.Errorf("ecdsa.VerifyASN1 failed") } case *rsa.PublicKey: if err := rsa.VerifyPKCS1v15(p, h, sig, sum); err != nil { return fmt.Errorf("rsa.VerifyPKCS1v15 failed") } case ed25519.PublicKey: if !ed25519.Verify(p, sum, sig) { return fmt.Errorf("ed25519.Verify failed") } default: return fmt.Errorf("unsupported public key type %T", pub) } return nil } func verifyPrivateKey(h crypto.Hash, priv interface{}) error { s, ok := priv.(crypto.Signer) if !ok { return fmt.Errorf("type %T is not a crypto.Signer", priv) } return verifyKeyPair(h, priv, s.Public()) } func TestPublicKey(t *testing.T) { ecdsaKey := must(generateECKey("P-256")).(*ecdsa.PrivateKey) rsaKey := must(generateRSAKey(2048)).(*rsa.PrivateKey) ed25519Key := must(generateOKPKey("Ed25519")).(ed25519.PrivateKey) x25519Pub, x25519Priv, err := x25519.GenerateKey(rand.Reader) assert.FatalError(t, err) type args struct { priv interface{} } tests := []struct { name string args args want crypto.PublicKey wantErr bool }{ {"ecdsa", args{ecdsaKey}, ecdsaKey.Public(), false}, {"ecdsaPublic", args{&ecdsaKey.PublicKey}, ecdsaKey.Public(), false}, {"rsa", args{rsaKey}, rsaKey.Public(), false}, {"rsaPublic", args{&rsaKey.PublicKey}, rsaKey.Public(), false}, {"ed25519", args{ed25519Key}, ed25519Key.Public(), false}, {"ed25519Public", args{ed25519.PublicKey(ed25519Key[32:])}, ed25519Key.Public(), false}, {"x25519", args{x25519Priv}, x25519Pub, false}, {"x25519Public", args{x25519Pub}, x25519Pub, false}, {"fail", args{[]byte("octkey")}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := PublicKey(tt.args.priv) if (err != nil) != tt.wantErr { t.Errorf("PublicKey() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("PublicKey() = %v, want %v", got, tt.want) } }) } } func TestGenerateDefaultKey(t *testing.T) { cleanupRandReader(t) tests := []struct { name string rr io.Reader assertion func(t *testing.T, got interface{}) wantErr bool }{ {"ok", randReader, func(t *testing.T, got interface{}) { t.Helper() if err := verifyPrivateKey(crypto.SHA256, got); err != nil { t.Errorf("GenerateDefaultKey() error = %v", err) } }, false}, {"eof", eofReader{}, func(t *testing.T, got interface{}) { if !reflect.DeepEqual(got, nil) { t.Errorf("GenerateDefaultKey() got = %v, want nil", got) } }, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rand.Reader = tt.rr got, err := GenerateDefaultKey() if (err != nil) != tt.wantErr { t.Errorf("GenerateDefaultKey() error = %v, wantErr %v", err, tt.wantErr) return } tt.assertion(t, got) }) } } func TestGenerateDefaultKeyPair(t *testing.T) { cleanupRandReader(t) assertKey := func(h crypto.Hash) func(t *testing.T, got, got1 interface{}) { return func(t *testing.T, got, got1 interface{}) { t.Helper() if err := verifyKeyPair(h, got1, got); err != nil { t.Errorf("GenerateDefaultKeyPair() error = %v", err) } } } assertNil := func() func(t *testing.T, got, got1 interface{}) { return func(t *testing.T, got, got1 interface{}) { t.Helper() if !reflect.DeepEqual(got, nil) { t.Errorf("GenerateDefaultKeyPair() got = %v, want nil", got) } if !reflect.DeepEqual(got1, nil) { t.Errorf("GenerateDefaultKeyPair() got1 = %v, want nil", got1) } } } tests := []struct { name string rr io.Reader assertion func(t *testing.T, got, got1 interface{}) wantErr bool }{ {"ok", randReader, assertKey(crypto.SHA256), false}, {"eof", eofReader{}, assertNil(), true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rand.Reader = tt.rr got, got1, err := GenerateDefaultKeyPair() if (err != nil) != tt.wantErr { t.Errorf("GenerateDefaultKeyPair() error = %v, wantErr %v", err, tt.wantErr) return } tt.assertion(t, got, got1) }) } } func TestGenerateKey(t *testing.T) { cleanupRandReader(t) assertKey := func(t *testing.T, h crypto.Hash, key interface{}) { t.Helper() if err := verifyPrivateKey(h, key); err != nil { t.Errorf("GenerateKey() error = %v", err) } } octKey := make([]byte, 32) for i := range octKey { octKey[i] = 'a' } assertOCT := func(t *testing.T, h crypto.Hash, key interface{}) { t.Helper() if !reflect.DeepEqual(key, octKey) { t.Errorf("GenerateKey() got = %v, want %v", key, octKey) } } type args struct { kty string crv string size int } tests := []struct { name string rr io.Reader args args assert func(t *testing.T, h crypto.Hash, key interface{}) hash crypto.Hash wantErr bool }{ {"P-256", randReader, args{"EC", "P-256", 0}, assertKey, crypto.SHA256, false}, {"P-384", randReader, args{"EC", "P-384", 0}, assertKey, crypto.SHA384, false}, {"P-521", randReader, args{"EC", "P-521", 0}, assertKey, crypto.SHA512, false}, {"Ed25519", randReader, args{"OKP", "Ed25519", 0}, assertKey, crypto.Hash(0), false}, {"OCT", zeroReader{}, args{"oct", "", 32}, assertOCT, crypto.Hash(0), false}, {"eof EC", eofReader{}, args{"EC", "P-256", 0}, nil, 0, true}, {"eof RSA", eofReader{}, args{"RSA", "", 1024}, nil, 0, true}, {"eof OKP", eofReader{}, args{"OKP", "Ed25519", 0}, nil, 0, true}, {"eof oct", eofReader{}, args{"oct", "", 32}, nil, 0, true}, {"unknown EC curve", randReader, args{"EC", "P-128", 0}, nil, 0, true}, {"unknown OKP curve", randReader, args{"OKP", "Edward", 0}, nil, 0, true}, {"unknown type", randReader, args{"FOO", "", 1024}, nil, 0, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rand.Reader = tt.rr got, err := GenerateKey(tt.args.kty, tt.args.crv, tt.args.size) if (err != nil) != tt.wantErr { t.Errorf("GenerateKey() error = %v, wantErr %v", err, tt.wantErr) return } if !tt.wantErr { tt.assert(t, tt.hash, got) } }) } } func TestGenerateKey_rsa(t *testing.T) { type args struct { kty string crv string size int } tests := []struct { name string args args wantType reflect.Type wantErr bool }{ {"RSA2048", args{"RSA", "", 2048}, reflect.TypeOf(&rsa.PrivateKey{}), false}, {"RSA3072", args{"RSA", "", 3072}, reflect.TypeOf(&rsa.PrivateKey{}), false}, {"fail", args{"RSA", "", 1}, nil, true}, {"fail size", args{"RSA", "", 1024}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Get rid of size validation for coverage purposes. if tt.args.size == 1 { tmp := MinRSAKeyBytes MinRSAKeyBytes = 0 t.Cleanup(func() { MinRSAKeyBytes = tmp }) } got, err := GenerateKey(tt.args.kty, tt.args.crv, tt.args.size) if (err != nil) != tt.wantErr { t.Errorf("GenerateKey() error = %v, wantErr %v", err, tt.wantErr) return } if reflect.TypeOf(got) != tt.wantType { t.Errorf("GenerateKey() = %v, want %v", got, tt.wantType) } if k, ok := got.(*rsa.PrivateKey); ok { if k.Size() != tt.args.size/8 { t.Errorf("GenerateKey() size = %d, want %d", k.Size(), tt.args.size/8) } } }) } } func TestGenerateKeyPair(t *testing.T) { cleanupRandReader(t) assertKey := func(h crypto.Hash) func(t *testing.T, got, got1 interface{}) { return func(t *testing.T, got, got1 interface{}) { t.Helper() if err := verifyKeyPair(h, got1, got); err != nil { t.Errorf("GenerateKeyPair() error = %v", err) } } } assertNil := func() func(t *testing.T, got, got1 interface{}) { return func(t *testing.T, got, got1 interface{}) { t.Helper() if !reflect.DeepEqual(got, nil) { t.Errorf("GenerateKeyPair() got = %v, want nil", got) } if !reflect.DeepEqual(got1, nil) { t.Errorf("GenerateKeyPair() got1 = %v, want nil", got1) } } } type args struct { kty string crv string size int } tests := []struct { name string rr io.Reader args args assertion func(t *testing.T, got, got1 interface{}) wantErr bool }{ {"P-256", randReader, args{"EC", "P-256", 0}, assertKey(crypto.SHA256), false}, {"P-384", randReader, args{"EC", "P-384", 0}, assertKey(crypto.SHA384), false}, {"P-521", randReader, args{"EC", "P-521", 0}, assertKey(crypto.SHA512), false}, {"Ed25519", randReader, args{"OKP", "Ed25519", 0}, assertKey(crypto.Hash(0)), false}, {"OCT", zeroReader{}, args{"oct", "", 32}, assertNil(), true}, {"eof", eofReader{}, args{"EC", "P-256", 0}, assertNil(), true}, {"unknown", randReader, args{"EC", "P-128", 0}, assertNil(), true}, {"unknown", randReader, args{"FOO", "", 1024}, assertNil(), true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rand.Reader = tt.rr got, got1, err := GenerateKeyPair(tt.args.kty, tt.args.crv, tt.args.size) if (err != nil) != tt.wantErr { t.Errorf("GenerateKeyPair() error = %v, wantErr %v", err, tt.wantErr) return } tt.assertion(t, got, got1) }) } } func TestGenerateKeyPair_rsa(t *testing.T) { pubType := reflect.TypeOf(&rsa.PublicKey{}) privType := reflect.TypeOf(&rsa.PrivateKey{}) type args struct { kty string crv string size int } tests := []struct { name string args args wantType reflect.Type wantType1 reflect.Type wantErr bool }{ {"RSA2048", args{"RSA", "", 2048}, pubType, privType, false}, {"RSA3072", args{"RSA", "", 3072}, pubType, privType, false}, {"fail", args{"RSA", "", 1024}, nil, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, got1, err := GenerateKeyPair(tt.args.kty, tt.args.crv, tt.args.size) if (err != nil) != tt.wantErr { t.Errorf("GenerateKeyPair() error = %v, wantErr %v", err, tt.wantErr) return } if reflect.TypeOf(got) != tt.wantType { t.Errorf("GenerateKey() = %v, want %v", got, tt.wantType) } if reflect.TypeOf(got1) != tt.wantType1 { t.Errorf("GenerateKey() = %v, want %v", got, tt.wantType) } if k, ok := got.(*rsa.PublicKey); ok { if k.Size() != tt.args.size/8 { t.Errorf("GenerateKey() size = %d, want %d", k.Size(), tt.args.size/8) } } if k, ok := got1.(*rsa.PrivateKey); ok { if k.Size() != tt.args.size/8 { t.Errorf("GenerateKey() size = %d, want %d", k.Size(), tt.args.size/8) } } }) } } func TestGenerateDefaultSigner(t *testing.T) { cleanupRandReader(t) tests := []struct { name string rr io.Reader assertion func(t *testing.T, got crypto.Signer) wantErr bool }{ {"ok", randReader, func(t *testing.T, got crypto.Signer) { t.Helper() if err := verifyPrivateKey(crypto.SHA256, got); err != nil { t.Errorf("GenerateDefaultSigner() error = %v", err) } }, false}, {"eof", eofReader{}, func(t *testing.T, got crypto.Signer) { if !reflect.DeepEqual(got, nil) { t.Errorf("GenerateDefaultSigner() got = %v, want nil", got) } }, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rand.Reader = tt.rr got, err := GenerateDefaultSigner() if (err != nil) != tt.wantErr { t.Errorf("GenerateDefaultSigner() error = %v, wantErr %v", err, tt.wantErr) return } tt.assertion(t, got) }) } } func TestGenerateSigner(t *testing.T) { assertSigner := func(h crypto.Hash) func(t *testing.T, got crypto.Signer) { return func(t *testing.T, got crypto.Signer) { t.Helper() if err := verifyPrivateKey(h, got); err != nil { t.Errorf("GenerateSigner() error = %v", err) } } } assertNil := func() func(t *testing.T, got crypto.Signer) { return func(t *testing.T, got crypto.Signer) { t.Helper() if !reflect.DeepEqual(got, nil) { t.Errorf("GenerateSigner() got = %v, want nil", got) } } } type args struct { kty string crv string size int } tests := []struct { name string args args assertion func(t *testing.T, got crypto.Signer) wantErr bool }{ {"P-256", args{"EC", "P-256", 0}, assertSigner(crypto.SHA256), false}, {"P-384", args{"EC", "P-384", 0}, assertSigner(crypto.SHA384), false}, {"P-521", args{"EC", "P-521", 0}, assertSigner(crypto.SHA512), false}, {"Ed25519", args{"OKP", "Ed25519", 0}, assertSigner(crypto.Hash(0)), false}, {"OCT", args{"oct", "", 32}, assertNil(), true}, {"unknown", args{"EC", "P-128", 0}, assertNil(), true}, {"unknown", args{"FOO", "", 1024}, assertNil(), true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := GenerateSigner(tt.args.kty, tt.args.crv, tt.args.size) if (err != nil) != tt.wantErr { t.Errorf("GenerateSigner() error = %v, wantErr %v", err, tt.wantErr) return } tt.assertion(t, got) }) } } func TestExtractKey(t *testing.T) { rsaKey := must(generateRSAKey(2048)).(*rsa.PrivateKey) ecKey := must(generateECKey("P-256")).(*ecdsa.PrivateKey) edKey := must(generateOKPKey("Ed25519")).(ed25519.PrivateKey) octKey := must(generateOctKey(64)).([]byte) b, _ := pem.Decode([]byte(testCRT)) cert, err := x509.ParseCertificate(b.Bytes) assert.FatalError(t, err) b, _ = pem.Decode([]byte(testCSR)) csr, err := x509.ParseCertificateRequest(b.Bytes) assert.FatalError(t, err) b, _ = pem.Decode([]byte(testSSHPubPEM)) sshKey, err := x509.ParsePKIXPublicKey(b.Bytes) assert.FatalError(t, err) sshPub, _, _, _, err := ssh.ParseAuthorizedKey([]byte(testSSHPub)) assert.FatalError(t, err) sshCert, _, _, _, err := ssh.ParseAuthorizedKey([]byte(testSSHCert)) assert.FatalError(t, err) type args struct { in interface{} } tests := []struct { name string args args want interface{} wantErr bool }{ {"RSA private key", args{rsaKey}, rsaKey, false}, {"RSA public key", args{rsaKey.Public()}, rsaKey.Public(), false}, {"EC private key", args{ecKey}, ecKey, false}, {"EC public key", args{ecKey.Public()}, ecKey.Public(), false}, {"OKP private key", args{edKey}, edKey, false}, {"OKP public key", args{edKey.Public()}, edKey.Public(), false}, {"oct key", args{octKey}, octKey, false}, {"certificate", args{cert}, cert.PublicKey, false}, {"csr", args{csr}, csr.PublicKey, false}, {"ssh public key", args{sshPub}, sshKey, false}, {"ssh cert", args{sshCert}, sshKey, false}, {"fail string", args{"fooo"}, nil, true}, {"fail bad ssh.Certificate.Key", args{&ssh.Certificate{Key: new(badSSHPublicKey)}}, nil, true}, {"fail bad ssh.PublicKey", args{new(badSSHPublicKey)}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ExtractKey(tt.args.in) if (err != nil) != tt.wantErr { t.Errorf("ExtractKey() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ExtractKey() = %v, want %v", got, tt.want) } }) } } func TestVerifyPair(t *testing.T) { ecdsaKey := must(generateECKey("P-256")).(*ecdsa.PrivateKey) rsaKey := must(generateRSAKey(2048)).(*rsa.PrivateKey) ed25519Key := must(generateOKPKey("Ed25519")).(ed25519.PrivateKey) ecdsaKey1 := must(generateECKey("P-256")).(*ecdsa.PrivateKey) rsaKey1 := must(generateRSAKey(2048)).(*rsa.PrivateKey) ed25519Key1 := must(generateOKPKey("Ed25519")).(ed25519.PrivateKey) type args struct { pubkey interface{} key interface{} } tests := []struct { name string args args wantErr bool }{ {"ecdsa", args{ecdsaKey.Public(), ecdsaKey}, false}, {"rsa", args{rsaKey.Public(), rsaKey}, false}, {"ed25519", args{ed25519Key.Public(), ed25519Key}, false}, // wrong private type {"fail ecdsa", args{ecdsaKey.Public(), ecdsaKey.Public()}, true}, {"fail rsa", args{rsaKey.Public(), rsaKey.Public()}, true}, {"fail ed25519", args{ed25519Key.Public(), ed25519Key.Public()}, true}, // wrong private key {"fail ecdsa key", args{ecdsaKey.Public(), ecdsaKey1}, true}, {"fail rsa key", args{rsaKey.Public(), rsaKey1}, true}, {"fail ed25519 key", args{ed25519Key.Public(), ed25519Key1}, true}, // wrong public type {"fail type", args{[]byte("foo"), []byte("foo")}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := VerifyPair(tt.args.pubkey, tt.args.key); (err != nil) != tt.wantErr { t.Errorf("VerifyPair() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestInsecure(t *testing.T) { tests := []struct { name string run func(t *testing.T) error wantErr bool }{ {"ok RSA 2048", func(t *testing.T) (err error) { _, err = GenerateKey("RSA", "", 2048) return }, false}, {"fail RSA 1024", func(t *testing.T) (err error) { _, err = GenerateKey("RSA", "", 1024) return }, true}, {"ok RSA 2048 insecure", func(t *testing.T) (err error) { revert := Insecure() t.Cleanup(revert) _, err = GenerateKey("RSA", "", 2048) return }, false}, {"ok RSA 1024 insecure", func(t *testing.T) (err error) { revert := Insecure() t.Cleanup(revert) _, err = GenerateKey("RSA", "", 1024) return }, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := tt.run(t) if (err != nil) != tt.wantErr { t.Errorf("Insecure() error = %v, wantErr %v", err, tt.wantErr) return } }) } } golang-step-crypto-0.24.0/kms/000077500000000000000000000000001437037643100161355ustar00rootroot00000000000000golang-step-crypto-0.24.0/kms/apiv1/000077500000000000000000000000001437037643100171555ustar00rootroot00000000000000golang-step-crypto-0.24.0/kms/apiv1/options.go000066400000000000000000000111651437037643100212030ustar00rootroot00000000000000package apiv1 import ( "crypto" "crypto/x509" "fmt" "strings" "go.step.sm/crypto/kms/uri" ) // KeyManager is the interface implemented by all the KMS. type KeyManager interface { GetPublicKey(req *GetPublicKeyRequest) (crypto.PublicKey, error) CreateKey(req *CreateKeyRequest) (*CreateKeyResponse, error) CreateSigner(req *CreateSignerRequest) (crypto.Signer, error) Close() error } // Decrypter is an interface implemented by KMSes that are used // in operations that require decryption type Decrypter interface { CreateDecrypter(req *CreateDecrypterRequest) (crypto.Decrypter, error) } // CertificateManager is the interface implemented by the KMS that can load and // store x509.Certificates. type CertificateManager interface { LoadCertificate(req *LoadCertificateRequest) (*x509.Certificate, error) StoreCertificate(req *StoreCertificateRequest) error } // NameValidator is an interface that KeyManager can implement to validate a // given name or URI. type NameValidator interface { ValidateName(s string) error } // Attester is the interface implemented by the KMS that can respond with an // attestation certificate or key. // // # Experimental // // Notice: This API is EXPERIMENTAL and may be changed or removed in a later // release. type Attester interface { CreateAttestation(req *CreateAttestationRequest) (*CreateAttestationResponse, error) } // NotImplementedError is the type of error returned if an operation is not // implemented. type NotImplementedError struct { Message string } func (e NotImplementedError) Error() string { if e.Message != "" { return e.Message } return "not implemented" } // AlreadyExistsError is the type of error returned if a key already exists. This // is currently only implmented on pkcs11. type AlreadyExistsError struct { Message string } func (e AlreadyExistsError) Error() string { if e.Message != "" { return e.Message } return "key already exists" } // Type represents the KMS type used. type Type string const ( // DefaultKMS is a KMS implementation using software. DefaultKMS Type = "" // SoftKMS is a KMS implementation using software. SoftKMS Type = "softkms" // CloudKMS is a KMS implementation using Google's Cloud KMS. CloudKMS Type = "cloudkms" // AmazonKMS is a KMS implementation using Amazon AWS KMS. AmazonKMS Type = "awskms" // PKCS11 is a KMS implementation using the PKCS11 standard. PKCS11 Type = "pkcs11" // YubiKey is a KMS implementation using a YubiKey PIV. YubiKey Type = "yubikey" // SSHAgentKMS is a KMS implementation using ssh-agent to access keys. SSHAgentKMS Type = "sshagentkms" // AzureKMS is a KMS implementation using Azure Key Vault. AzureKMS Type = "azurekms" // CAPIKMS CAPIKMS Type = "capi" ) // Options are the KMS options. They represent the kms object in the ca.json. type Options struct { // The type of the KMS to use. Type Type `json:"type"` // Path to the credentials file used in CloudKMS and AmazonKMS. CredentialsFile string `json:"credentialsFile,omitempty"` // URI is based on the PKCS #11 URI Scheme defined in // https://tools.ietf.org/html/rfc7512 and represents the configuration used // to connect to the KMS. // // Used by: pkcs11 URI string `json:"uri,omitempty"` // Pin used to access the PKCS11 module. It can be defined in the URI using // the pin-value or pin-source properties. Pin string `json:"pin,omitempty"` // ManagementKey used in YubiKeys. Default management key is the hexadecimal // string 010203040506070801020304050607080102030405060708: // []byte{ // 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // } ManagementKey string `json:"managementKey,omitempty"` // Region to use in AmazonKMS. Region string `json:"region,omitempty"` // Profile to use in AmazonKMS. Profile string `json:"profile,omitempty"` } // Validate checks the fields in Options. func (o *Options) Validate() error { if o == nil { return nil } typ := strings.ToLower(string(o.Type)) switch Type(typ) { case DefaultKMS, SoftKMS: // Go crypto based kms. case CloudKMS, AmazonKMS, AzureKMS: // Cloud based kms. case YubiKey, PKCS11: // Hardware based kms. case SSHAgentKMS, CAPIKMS: // Others default: return fmt.Errorf("unsupported kms type %s", o.Type) } return nil } // GetType returns the type in the type property or the one present in the URI. func (o *Options) GetType() (Type, error) { if o.Type != "" { return o.Type, nil } if o.URI != "" { u, err := uri.Parse(o.URI) if err != nil { return DefaultKMS, err } return Type(strings.ToLower(u.Scheme)), nil } return SoftKMS, nil } golang-step-crypto-0.24.0/kms/apiv1/options_test.go000066400000000000000000000060021437037643100222340ustar00rootroot00000000000000package apiv1 import ( "testing" ) func TestOptions_Validate(t *testing.T) { tests := []struct { name string options *Options wantErr bool }{ {"nil", nil, false}, {"softkms", &Options{Type: "softkms"}, false}, {"cloudkms", &Options{Type: "cloudkms"}, false}, {"awskms", &Options{Type: "awskms"}, false}, {"sshagentkms", &Options{Type: "sshagentkms"}, false}, {"pkcs11", &Options{Type: "pkcs11"}, false}, {"unsupported", &Options{Type: "unsupported"}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := tt.options.Validate(); (err != nil) != tt.wantErr { t.Errorf("Options.Validate() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestOptions_GetType(t *testing.T) { type fields struct { Type Type URI string } tests := []struct { name string fields fields want Type wantErr bool }{ {"ok", fields{PKCS11, ""}, PKCS11, false}, {"ok default", fields{"", ""}, SoftKMS, false}, {"ok by uri", fields{"", "PKCS11:foo=bar"}, PKCS11, false}, {"ok by uri", fields{"", "softkms:foo=bar"}, SoftKMS, false}, {"ok by uri", fields{"", "cloudkms:foo=bar"}, CloudKMS, false}, {"ok by uri", fields{"", "awskms:foo=bar"}, AmazonKMS, false}, {"ok by uri", fields{"", "pkcs11:foo=bar"}, PKCS11, false}, {"ok by uri", fields{"", "yubikey:foo=bar"}, YubiKey, false}, {"ok by uri", fields{"", "sshagentkms:foo=bar"}, SSHAgentKMS, false}, {"ok by uri", fields{"", "azurekms:foo=bar"}, AzureKMS, false}, {"fail uri", fields{"", "foo=bar"}, DefaultKMS, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { o := &Options{ Type: tt.fields.Type, URI: tt.fields.URI, } got, err := o.GetType() if (err != nil) != tt.wantErr { t.Errorf("Options.GetType() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("Options.GetType() = %v, want %v", got, tt.want) } }) } } func TestErrNotImplemented_Error(t *testing.T) { type fields struct { msg string } tests := []struct { name string fields fields want string }{ {"default", fields{}, "not implemented"}, {"custom", fields{"custom message: not implemented"}, "custom message: not implemented"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { e := NotImplementedError{ Message: tt.fields.msg, } if got := e.Error(); got != tt.want { t.Errorf("ErrNotImplemented.Error() = %v, want %v", got, tt.want) } }) } } func TestErrAlreadyExists_Error(t *testing.T) { type fields struct { msg string } tests := []struct { name string fields fields want string }{ {"default", fields{}, "key already exists"}, {"custom", fields{"custom message: key already exists"}, "custom message: key already exists"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { e := AlreadyExistsError{ Message: tt.fields.msg, } if got := e.Error(); got != tt.want { t.Errorf("ErrAlreadyExists.Error() = %v, want %v", got, tt.want) } }) } } golang-step-crypto-0.24.0/kms/apiv1/registry.go000066400000000000000000000012071437037643100213540ustar00rootroot00000000000000package apiv1 import ( "context" "sync" ) var registry = new(sync.Map) // KeyManagerNewFunc is the type that represents the method to initialize a new // KeyManager. type KeyManagerNewFunc func(ctx context.Context, opts Options) (KeyManager, error) // Register adds to the registry a method to create a KeyManager of type t. func Register(t Type, fn KeyManagerNewFunc) { registry.Store(t, fn) } // LoadKeyManagerNewFunc returns the function initialize a KayManager. func LoadKeyManagerNewFunc(t Type) (KeyManagerNewFunc, bool) { v, ok := registry.Load(t) if !ok { return nil, false } fn, ok := v.(KeyManagerNewFunc) return fn, ok } golang-step-crypto-0.24.0/kms/apiv1/requests.go000066400000000000000000000140331437037643100213600ustar00rootroot00000000000000package apiv1 import ( "crypto" "crypto/x509" "fmt" ) // ProtectionLevel specifies on some KMS how cryptographic operations are // performed. type ProtectionLevel int const ( // Protection level not specified. UnspecifiedProtectionLevel ProtectionLevel = iota // Crypto operations are performed in software. Software // Crypto operations are performed in a Hardware Security Module. HSM ) // PINPolicy represents PIN requirements when signing or decrypting with an // asymmetric key in a given slot. PINPolicy is used by the YubiKey KMS. type PINPolicy int // PIN policies supported by this package. The values must match the ones in // github.com/go-piv/piv-go/piv. // // Caching for PINPolicyOnce isn't supported on YubiKey // versions older than 4.3.0 due to issues with verifying if a PIN is needed. // If specified, a PIN will be required for every operation. const ( PINPolicyNever PINPolicy = iota + 1 PINPolicyOnce PINPolicyAlways ) // TouchPolicy represents proof-of-presence requirements when signing or // decrypting with asymmetric key in a given slot. TouchPolicy is used by the // YubiKey KMS. type TouchPolicy int // Touch policies supported by this package. The values must match the ones in // github.com/go-piv/piv-go/piv. const ( TouchPolicyNever TouchPolicy = iota + 1 TouchPolicyAlways TouchPolicyCached ) // String returns a string representation of p. func (p ProtectionLevel) String() string { switch p { case UnspecifiedProtectionLevel: return "unspecified" case Software: return "software" case HSM: return "hsm" default: return fmt.Sprintf("unknown(%d)", p) } } // SignatureAlgorithm used for cryptographic signing. type SignatureAlgorithm int const ( // Not specified. UnspecifiedSignAlgorithm SignatureAlgorithm = iota // RSASSA-PKCS1-v1_5 key and a SHA256 digest. SHA256WithRSA // RSASSA-PKCS1-v1_5 key and a SHA384 digest. SHA384WithRSA // RSASSA-PKCS1-v1_5 key and a SHA512 digest. SHA512WithRSA // RSASSA-PSS key with a SHA256 digest. SHA256WithRSAPSS // RSASSA-PSS key with a SHA384 digest. SHA384WithRSAPSS // RSASSA-PSS key with a SHA512 digest. SHA512WithRSAPSS // ECDSA on the NIST P-256 curve with a SHA256 digest. ECDSAWithSHA256 // ECDSA on the NIST P-384 curve with a SHA384 digest. ECDSAWithSHA384 // ECDSA on the NIST P-521 curve with a SHA512 digest. ECDSAWithSHA512 // EdDSA on Curve25519 with a SHA512 digest. PureEd25519 ) // String returns a string representation of s. func (s SignatureAlgorithm) String() string { switch s { case UnspecifiedSignAlgorithm: return "unspecified" case SHA256WithRSA: return "SHA256-RSA" case SHA384WithRSA: return "SHA384-RSA" case SHA512WithRSA: return "SHA512-RSA" case SHA256WithRSAPSS: return "SHA256-RSAPSS" case SHA384WithRSAPSS: return "SHA384-RSAPSS" case SHA512WithRSAPSS: return "SHA512-RSAPSS" case ECDSAWithSHA256: return "ECDSA-SHA256" case ECDSAWithSHA384: return "ECDSA-SHA384" case ECDSAWithSHA512: return "ECDSA-SHA512" case PureEd25519: return "Ed25519" default: return fmt.Sprintf("unknown(%d)", s) } } // GetPublicKeyRequest is the parameter used in the kms.GetPublicKey method. type GetPublicKeyRequest struct { Name string } // CreateKeyRequest is the parameter used in the kms.CreateKey method. type CreateKeyRequest struct { // Name represents the key name or label used to identify a key. // // Used by: awskms, cloudkms, azurekms, pkcs11, yubikey. Name string // SignatureAlgorithm represents the type of key to create. SignatureAlgorithm SignatureAlgorithm // Bits is the number of bits on RSA keys. Bits int // ProtectionLevel specifies how cryptographic operations are performed. // Used by: cloudkms, azurekms. ProtectionLevel ProtectionLevel // Extractable defines if the new key may be exported from the HSM under a // wrap key. On pkcs11 sets the CKA_EXTRACTABLE bit. // // Used by: pkcs11 Extractable bool // PINPolicy defines PIN requirements when signing or decrypting with an // asymmetric key. // // Used by: yubikey PINPolicy PINPolicy // TouchPolicy represents proof-of-presence requirements when signing or // decrypting with asymmetric key in a given slot. // // Used by: yubikey TouchPolicy TouchPolicy } // CreateKeyResponse is the response value of the kms.CreateKey method. type CreateKeyResponse struct { Name string PublicKey crypto.PublicKey PrivateKey crypto.PrivateKey CreateSignerRequest CreateSignerRequest } // CreateSignerRequest is the parameter used in the kms.CreateSigner method. type CreateSignerRequest struct { Signer crypto.Signer SigningKey string SigningKeyPEM []byte TokenLabel string PublicKey string PublicKeyPEM []byte Password []byte } // CreateDecrypterRequest is the parameter used in the kms.Decrypt method. type CreateDecrypterRequest struct { Decrypter crypto.Decrypter DecryptionKey string DecryptionKeyPEM []byte Password []byte } // LoadCertificateRequest is the parameter used in the LoadCertificate method of // a CertificateManager. type LoadCertificateRequest struct { Name string } // StoreCertificateRequest is the parameter used in the StoreCertificate method // of a CertificateManager. type StoreCertificateRequest struct { Name string Certificate *x509.Certificate // Extractable defines if the new certificate may be exported from the HSM // under a wrap key. On pkcs11 sets the CKA_EXTRACTABLE bit. // // Used by: pkcs11 Extractable bool } // CreateAttestationRequest is the parameter used in the kms.CreateAttestation // method. // // # Experimental // // Notice: This API is EXPERIMENTAL and may be changed or removed in a later // release. type CreateAttestationRequest struct { Name string } // CreateAttestationResponse is the response value of the kms.CreateAttestation // method. // // # Experimental // // Notice: This API is EXPERIMENTAL and may be changed or removed in a later // release. type CreateAttestationResponse struct { Certificate *x509.Certificate CertificateChain []*x509.Certificate PublicKey crypto.PublicKey } golang-step-crypto-0.24.0/kms/apiv1/requests_test.go000066400000000000000000000027721437037643100224260ustar00rootroot00000000000000package apiv1 import "testing" func TestProtectionLevel_String(t *testing.T) { tests := []struct { name string p ProtectionLevel want string }{ {"unspecified", UnspecifiedProtectionLevel, "unspecified"}, {"software", Software, "software"}, {"hsm", HSM, "hsm"}, {"unknown", ProtectionLevel(100), "unknown(100)"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.p.String(); got != tt.want { t.Errorf("ProtectionLevel.String() = %v, want %v", got, tt.want) } }) } } func TestSignatureAlgorithm_String(t *testing.T) { tests := []struct { name string s SignatureAlgorithm want string }{ {"UnspecifiedSignAlgorithm", UnspecifiedSignAlgorithm, "unspecified"}, {"SHA256WithRSA", SHA256WithRSA, "SHA256-RSA"}, {"SHA384WithRSA", SHA384WithRSA, "SHA384-RSA"}, {"SHA512WithRSA", SHA512WithRSA, "SHA512-RSA"}, {"SHA256WithRSAPSS", SHA256WithRSAPSS, "SHA256-RSAPSS"}, {"SHA384WithRSAPSS", SHA384WithRSAPSS, "SHA384-RSAPSS"}, {"SHA512WithRSAPSS", SHA512WithRSAPSS, "SHA512-RSAPSS"}, {"ECDSAWithSHA256", ECDSAWithSHA256, "ECDSA-SHA256"}, {"ECDSAWithSHA384", ECDSAWithSHA384, "ECDSA-SHA384"}, {"ECDSAWithSHA512", ECDSAWithSHA512, "ECDSA-SHA512"}, {"PureEd25519", PureEd25519, "Ed25519"}, {"unknown", SignatureAlgorithm(100), "unknown(100)"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.s.String(); got != tt.want { t.Errorf("SignatureAlgorithm.String() = %v, want %v", got, tt.want) } }) } } golang-step-crypto-0.24.0/kms/awskms/000077500000000000000000000000001437037643100174425ustar00rootroot00000000000000golang-step-crypto-0.24.0/kms/awskms/awskms.go000066400000000000000000000171451437037643100213060ustar00rootroot00000000000000//go:build !noawskms // +build !noawskms package awskms import ( "context" "crypto" "net/url" "strings" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/kms" "github.com/pkg/errors" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/kms/uri" "go.step.sm/crypto/pemutil" ) // Scheme is the scheme used in uris. const Scheme = "awskms" // KMS implements a KMS using AWS Key Management Service. type KMS struct { session *session.Session service KeyManagementClient } // KeyManagementClient defines the methods on KeyManagementClient that this // package will use. This interface will be used for unit testing. type KeyManagementClient interface { GetPublicKeyWithContext(ctx aws.Context, input *kms.GetPublicKeyInput, opts ...request.Option) (*kms.GetPublicKeyOutput, error) CreateKeyWithContext(ctx aws.Context, input *kms.CreateKeyInput, opts ...request.Option) (*kms.CreateKeyOutput, error) CreateAliasWithContext(ctx aws.Context, input *kms.CreateAliasInput, opts ...request.Option) (*kms.CreateAliasOutput, error) SignWithContext(ctx aws.Context, input *kms.SignInput, opts ...request.Option) (*kms.SignOutput, error) } // customerMasterKeySpecMapping is a mapping between the step signature algorithm, // and bits for RSA keys, with awskms CustomerMasterKeySpec. var customerMasterKeySpecMapping = map[apiv1.SignatureAlgorithm]interface{}{ apiv1.UnspecifiedSignAlgorithm: kms.CustomerMasterKeySpecEccNistP256, apiv1.SHA256WithRSA: map[int]string{ 0: kms.CustomerMasterKeySpecRsa3072, 2048: kms.CustomerMasterKeySpecRsa2048, 3072: kms.CustomerMasterKeySpecRsa3072, 4096: kms.CustomerMasterKeySpecRsa4096, }, apiv1.SHA512WithRSA: map[int]string{ 0: kms.CustomerMasterKeySpecRsa4096, 4096: kms.CustomerMasterKeySpecRsa4096, }, apiv1.SHA256WithRSAPSS: map[int]string{ 0: kms.CustomerMasterKeySpecRsa3072, 2048: kms.CustomerMasterKeySpecRsa2048, 3072: kms.CustomerMasterKeySpecRsa3072, 4096: kms.CustomerMasterKeySpecRsa4096, }, apiv1.SHA512WithRSAPSS: map[int]string{ 0: kms.CustomerMasterKeySpecRsa4096, 4096: kms.CustomerMasterKeySpecRsa4096, }, apiv1.ECDSAWithSHA256: kms.CustomerMasterKeySpecEccNistP256, apiv1.ECDSAWithSHA384: kms.CustomerMasterKeySpecEccNistP384, apiv1.ECDSAWithSHA512: kms.CustomerMasterKeySpecEccNistP521, } // New creates a new AWSKMS. By default, sessions will be created using the // credentials in `~/.aws/credentials`, but this can be overridden using the // CredentialsFile option, the Region and Profile can also be configured as // options. // // AWS sessions can also be configured with environment variables, see docs at // https://docs.aws.amazon.com/sdk-for-go/api/aws/session/ for all the options. func New(ctx context.Context, opts apiv1.Options) (*KMS, error) { var o session.Options if opts.URI != "" { u, err := uri.ParseWithScheme(Scheme, opts.URI) if err != nil { return nil, err } o.Profile = u.Get("profile") if v := u.Get("region"); v != "" { o.Config.Region = new(string) *o.Config.Region = v } if f := u.Get("credentials-file"); f != "" { o.SharedConfigFiles = []string{f} } } // Deprecated way to set configuration parameters. if opts.Region != "" { o.Config.Region = &opts.Region } if opts.Profile != "" { o.Profile = opts.Profile } if opts.CredentialsFile != "" { o.SharedConfigFiles = []string{opts.CredentialsFile} } sess, err := session.NewSessionWithOptions(o) if err != nil { return nil, errors.Wrap(err, "error creating AWS session") } return &KMS{ session: sess, service: kms.New(sess), }, nil } func init() { apiv1.Register(apiv1.AmazonKMS, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) { return New(ctx, opts) }) } // GetPublicKey returns a public key from KMS. func (k *KMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) { if req.Name == "" { return nil, errors.New("getPublicKey 'name' cannot be empty") } keyID, err := parseKeyID(req.Name) if err != nil { return nil, err } ctx, cancel := defaultContext() defer cancel() resp, err := k.service.GetPublicKeyWithContext(ctx, &kms.GetPublicKeyInput{ KeyId: &keyID, }) if err != nil { return nil, errors.Wrap(err, "awskms GetPublicKeyWithContext failed") } return pemutil.ParseDER(resp.PublicKey) } // CreateKey generates a new key in KMS and returns the public key version // of it. func (k *KMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) { if req.Name == "" { return nil, errors.New("createKeyRequest 'name' cannot be empty") } keySpec, err := getCustomerMasterKeySpecMapping(req.SignatureAlgorithm, req.Bits) if err != nil { return nil, err } tag := new(kms.Tag) tag.SetTagKey("name") tag.SetTagValue(req.Name) input := &kms.CreateKeyInput{ Description: &req.Name, CustomerMasterKeySpec: &keySpec, Tags: []*kms.Tag{tag}, } input.SetKeyUsage(kms.KeyUsageTypeSignVerify) ctx, cancel := defaultContext() defer cancel() resp, err := k.service.CreateKeyWithContext(ctx, input) if err != nil { return nil, errors.Wrap(err, "awskms CreateKeyWithContext failed") } if err := k.createKeyAlias(*resp.KeyMetadata.KeyId, req.Name); err != nil { return nil, err } // Create uri for key name := uri.New("awskms", url.Values{ "key-id": []string{*resp.KeyMetadata.KeyId}, }).String() publicKey, err := k.GetPublicKey(&apiv1.GetPublicKeyRequest{ Name: name, }) if err != nil { return nil, err } // Names uses Amazon Resource Name // https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html return &apiv1.CreateKeyResponse{ Name: name, PublicKey: publicKey, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: name, }, }, nil } func (k *KMS) createKeyAlias(keyID, alias string) error { alias = "alias/" + alias + "-" + keyID[:8] ctx, cancel := defaultContext() defer cancel() _, err := k.service.CreateAliasWithContext(ctx, &kms.CreateAliasInput{ AliasName: &alias, TargetKeyId: &keyID, }) if err != nil { return errors.Wrap(err, "awskms CreateAliasWithContext failed") } return nil } // CreateSigner creates a new crypto.Signer with a previously configured key. func (k *KMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) { if req.SigningKey == "" { return nil, errors.New("createSigner 'signingKey' cannot be empty") } return NewSigner(k.service, req.SigningKey) } // Close closes the connection of the KMS client. func (k *KMS) Close() error { return nil } func defaultContext() (context.Context, context.CancelFunc) { return context.WithTimeout(context.Background(), 15*time.Second) } // parseKeyID extracts the key-id from an uri. func parseKeyID(name string) (string, error) { name = strings.ToLower(name) if strings.HasPrefix(name, "awskms:") || strings.HasPrefix(name, "aws:") { u, err := uri.Parse(name) if err != nil { return "", err } if k := u.Get("key-id"); k != "" { return k, nil } return "", errors.Errorf("failed to get key-id from %s", name) } return name, nil } func getCustomerMasterKeySpecMapping(alg apiv1.SignatureAlgorithm, bits int) (string, error) { v, ok := customerMasterKeySpecMapping[alg] if !ok { return "", errors.Errorf("awskms does not support signature algorithm '%s'", alg) } switch v := v.(type) { case string: return v, nil case map[int]string: s, ok := v[bits] if !ok { return "", errors.Errorf("awskms does not support signature algorithm '%s' with '%d' bits", alg, bits) } return s, nil default: return "", errors.Errorf("unexpected error: this should not happen") } } golang-step-crypto-0.24.0/kms/awskms/awskms_test.go000066400000000000000000000246771437037643100223550ustar00rootroot00000000000000package awskms import ( "context" "crypto" "fmt" "os" "path/filepath" "reflect" "testing" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/kms" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/pemutil" ) func TestNew(t *testing.T) { ctx := context.Background() sess, err := session.NewSessionWithOptions(session.Options{}) if err != nil { t.Fatal(err) } expected := &KMS{ session: sess, service: kms.New(sess), } // This will force an error in the session creation. // It does not fail with missing credentials. forceError := func(t *testing.T) { key := "AWS_CA_BUNDLE" value := os.Getenv(key) t.Setenv(key, filepath.Join(os.TempDir(), "missing-ca.crt")) t.Cleanup(func() { if value == "" { os.Unsetenv(key) } else { t.Setenv(key, value) } }) } type args struct { ctx context.Context opts apiv1.Options } tests := []struct { name string args args want *KMS wantErr bool }{ {"ok", args{ctx, apiv1.Options{}}, expected, false}, {"ok with options", args{ctx, apiv1.Options{ Region: "us-east-1", Profile: "smallstep", CredentialsFile: "~/aws/credentials", }}, expected, false}, {"ok with uri", args{ctx, apiv1.Options{ URI: "awskms:region=us-east-1;profile=smallstep;credentials-file=/var/run/aws/credentials", }}, expected, false}, {"fail", args{ctx, apiv1.Options{}}, nil, true}, {"fail uri", args{ctx, apiv1.Options{ URI: "pkcs11:region=us-east-1;profile=smallstep;credentials-file=/var/run/aws/credentials", }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Force an error in the session loading if tt.wantErr { forceError(t) } got, err := New(tt.args.ctx, tt.args.opts) if (err != nil) != tt.wantErr { t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) return } if err != nil { if !reflect.DeepEqual(got, tt.want) { //nolint:govet // variable names match crypto formulae docs t.Errorf("New() = %#v, want %#v", got, tt.want) } } else { if got.session == nil || got.service == nil { t.Errorf("New() = %#v, want %#v", got, tt.want) } } }) } } func TestKMS_GetPublicKey(t *testing.T) { okClient := getOKClient() key, err := pemutil.ParseKey([]byte(publicKey)) if err != nil { t.Fatal(err) } type fields struct { session *session.Session service KeyManagementClient } type args struct { req *apiv1.GetPublicKeyRequest } tests := []struct { name string fields fields args args want crypto.PublicKey wantErr bool }{ {"ok", fields{nil, okClient}, args{&apiv1.GetPublicKeyRequest{ Name: "awskms:key-id=be468355-ca7a-40d9-a28b-8ae1c4c7f936", }}, key, false}, {"fail empty", fields{nil, okClient}, args{&apiv1.GetPublicKeyRequest{}}, nil, true}, {"fail name", fields{nil, okClient}, args{&apiv1.GetPublicKeyRequest{ Name: "awskms:key-id=", }}, nil, true}, {"fail getPublicKey", fields{nil, &MockClient{ getPublicKeyWithContext: func(ctx aws.Context, input *kms.GetPublicKeyInput, opts ...request.Option) (*kms.GetPublicKeyOutput, error) { return nil, fmt.Errorf("an error") }, }}, args{&apiv1.GetPublicKeyRequest{ Name: "awskms:key-id=be468355-ca7a-40d9-a28b-8ae1c4c7f936", }}, nil, true}, {"fail not der", fields{nil, &MockClient{ getPublicKeyWithContext: func(ctx aws.Context, input *kms.GetPublicKeyInput, opts ...request.Option) (*kms.GetPublicKeyOutput, error) { return &kms.GetPublicKeyOutput{ KeyId: input.KeyId, PublicKey: []byte(publicKey), }, nil }, }}, args{&apiv1.GetPublicKeyRequest{ Name: "awskms:key-id=be468355-ca7a-40d9-a28b-8ae1c4c7f936", }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &KMS{ session: tt.fields.session, service: tt.fields.service, } got, err := k.GetPublicKey(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("KMS.GetPublicKey() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("KMS.GetPublicKey() = %v, want %v", got, tt.want) } }) } } func TestKMS_CreateKey(t *testing.T) { okClient := getOKClient() key, err := pemutil.ParseKey([]byte(publicKey)) if err != nil { t.Fatal(err) } type fields struct { session *session.Session service KeyManagementClient } type args struct { req *apiv1.CreateKeyRequest } tests := []struct { name string fields fields args args want *apiv1.CreateKeyResponse wantErr bool }{ {"ok", fields{nil, okClient}, args{&apiv1.CreateKeyRequest{ Name: "root", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }}, &apiv1.CreateKeyResponse{ Name: "awskms:key-id=be468355-ca7a-40d9-a28b-8ae1c4c7f936", PublicKey: key, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "awskms:key-id=be468355-ca7a-40d9-a28b-8ae1c4c7f936", }, }, false}, {"ok rsa", fields{nil, okClient}, args{&apiv1.CreateKeyRequest{ Name: "root", SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 2048, }}, &apiv1.CreateKeyResponse{ Name: "awskms:key-id=be468355-ca7a-40d9-a28b-8ae1c4c7f936", PublicKey: key, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "awskms:key-id=be468355-ca7a-40d9-a28b-8ae1c4c7f936", }, }, false}, {"fail empty", fields{nil, okClient}, args{&apiv1.CreateKeyRequest{}}, nil, true}, {"fail unsupported alg", fields{nil, okClient}, args{&apiv1.CreateKeyRequest{ Name: "root", SignatureAlgorithm: apiv1.PureEd25519, }}, nil, true}, {"fail unsupported bits", fields{nil, okClient}, args{&apiv1.CreateKeyRequest{ Name: "root", SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 1234, }}, nil, true}, {"fail createKey", fields{nil, &MockClient{ createKeyWithContext: func(ctx aws.Context, input *kms.CreateKeyInput, opts ...request.Option) (*kms.CreateKeyOutput, error) { return nil, fmt.Errorf("an error") }, createAliasWithContext: okClient.createAliasWithContext, getPublicKeyWithContext: okClient.getPublicKeyWithContext, }}, args{&apiv1.CreateKeyRequest{ Name: "root", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }}, nil, true}, {"fail createAlias", fields{nil, &MockClient{ createKeyWithContext: okClient.createKeyWithContext, createAliasWithContext: func(ctx aws.Context, input *kms.CreateAliasInput, opts ...request.Option) (*kms.CreateAliasOutput, error) { return nil, fmt.Errorf("an error") }, getPublicKeyWithContext: okClient.getPublicKeyWithContext, }}, args{&apiv1.CreateKeyRequest{ Name: "root", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }}, nil, true}, {"fail getPublicKey", fields{nil, &MockClient{ createKeyWithContext: okClient.createKeyWithContext, createAliasWithContext: okClient.createAliasWithContext, getPublicKeyWithContext: func(ctx aws.Context, input *kms.GetPublicKeyInput, opts ...request.Option) (*kms.GetPublicKeyOutput, error) { return nil, fmt.Errorf("an error") }, }}, args{&apiv1.CreateKeyRequest{ Name: "root", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &KMS{ session: tt.fields.session, service: tt.fields.service, } got, err := k.CreateKey(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("KMS.CreateKey() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("KMS.CreateKey() = %v, want %v", got, tt.want) } }) } } func TestKMS_CreateSigner(t *testing.T) { client := getOKClient() key, err := pemutil.ParseKey([]byte(publicKey)) if err != nil { t.Fatal(err) } type fields struct { session *session.Session service KeyManagementClient } type args struct { req *apiv1.CreateSignerRequest } tests := []struct { name string fields fields args args want crypto.Signer wantErr bool }{ {"ok", fields{nil, client}, args{&apiv1.CreateSignerRequest{ SigningKey: "awskms:key-id=be468355-ca7a-40d9-a28b-8ae1c4c7f936", }}, &Signer{ service: client, keyID: "be468355-ca7a-40d9-a28b-8ae1c4c7f936", publicKey: key, }, false}, {"fail empty", fields{nil, client}, args{&apiv1.CreateSignerRequest{}}, nil, true}, {"fail preload", fields{nil, client}, args{&apiv1.CreateSignerRequest{}}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &KMS{ session: tt.fields.session, service: tt.fields.service, } got, err := k.CreateSigner(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("KMS.CreateSigner() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("KMS.CreateSigner() = %v, want %v", got, tt.want) } }) } } func TestKMS_Close(t *testing.T) { type fields struct { session *session.Session service KeyManagementClient } tests := []struct { name string fields fields wantErr bool }{ {"ok", fields{nil, getOKClient()}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &KMS{ session: tt.fields.session, service: tt.fields.service, } if err := k.Close(); (err != nil) != tt.wantErr { t.Errorf("KMS.Close() error = %v, wantErr %v", err, tt.wantErr) } }) } } func Test_parseKeyID(t *testing.T) { type args struct { name string } tests := []struct { name string args args want string wantErr bool }{ {"ok uri", args{"awskms:key-id=be468355-ca7a-40d9-a28b-8ae1c4c7f936"}, "be468355-ca7a-40d9-a28b-8ae1c4c7f936", false}, {"ok key id", args{"be468355-ca7a-40d9-a28b-8ae1c4c7f936"}, "be468355-ca7a-40d9-a28b-8ae1c4c7f936", false}, {"ok arn", args{"arn:aws:kms:us-east-1:123456789:key/be468355-ca7a-40d9-a28b-8ae1c4c7f936"}, "arn:aws:kms:us-east-1:123456789:key/be468355-ca7a-40d9-a28b-8ae1c4c7f936", false}, {"fail parse", args{"awskms:key-id=%ZZ"}, "", true}, {"fail empty key", args{"awskms:key-id="}, "", true}, {"fail missing", args{"awskms:foo=bar"}, "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := parseKeyID(tt.args.name) if (err != nil) != tt.wantErr { t.Errorf("parseKeyID() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("parseKeyID() = %v, want %v", got, tt.want) } }) } } golang-step-crypto-0.24.0/kms/awskms/mock_test.go000066400000000000000000000055501437037643100217660ustar00rootroot00000000000000package awskms import ( "encoding/pem" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/service/kms" ) type MockClient struct { getPublicKeyWithContext func(ctx aws.Context, input *kms.GetPublicKeyInput, opts ...request.Option) (*kms.GetPublicKeyOutput, error) createKeyWithContext func(ctx aws.Context, input *kms.CreateKeyInput, opts ...request.Option) (*kms.CreateKeyOutput, error) createAliasWithContext func(ctx aws.Context, input *kms.CreateAliasInput, opts ...request.Option) (*kms.CreateAliasOutput, error) signWithContext func(ctx aws.Context, input *kms.SignInput, opts ...request.Option) (*kms.SignOutput, error) } func (m *MockClient) GetPublicKeyWithContext(ctx aws.Context, input *kms.GetPublicKeyInput, opts ...request.Option) (*kms.GetPublicKeyOutput, error) { return m.getPublicKeyWithContext(ctx, input, opts...) } func (m *MockClient) CreateKeyWithContext(ctx aws.Context, input *kms.CreateKeyInput, opts ...request.Option) (*kms.CreateKeyOutput, error) { return m.createKeyWithContext(ctx, input, opts...) } func (m *MockClient) CreateAliasWithContext(ctx aws.Context, input *kms.CreateAliasInput, opts ...request.Option) (*kms.CreateAliasOutput, error) { return m.createAliasWithContext(ctx, input, opts...) } func (m *MockClient) SignWithContext(ctx aws.Context, input *kms.SignInput, opts ...request.Option) (*kms.SignOutput, error) { return m.signWithContext(ctx, input, opts...) } const ( publicKey = `-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8XWlIWkOThxNjGbZLYUgRHmsvCrW KF+HLktPfPTIK3lGd1k4849WQs59XIN+LXZQ6b2eRBEBKAHEyQus8UU7gw== -----END PUBLIC KEY-----` keyID = "be468355-ca7a-40d9-a28b-8ae1c4c7f936" ) var signature = []byte{ 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55, } func getOKClient() *MockClient { return &MockClient{ getPublicKeyWithContext: func(ctx aws.Context, input *kms.GetPublicKeyInput, opts ...request.Option) (*kms.GetPublicKeyOutput, error) { block, _ := pem.Decode([]byte(publicKey)) return &kms.GetPublicKeyOutput{ KeyId: input.KeyId, PublicKey: block.Bytes, }, nil }, createKeyWithContext: func(ctx aws.Context, input *kms.CreateKeyInput, opts ...request.Option) (*kms.CreateKeyOutput, error) { md := new(kms.KeyMetadata) md.SetKeyId(keyID) return &kms.CreateKeyOutput{ KeyMetadata: md, }, nil }, createAliasWithContext: func(ctx aws.Context, input *kms.CreateAliasInput, opts ...request.Option) (*kms.CreateAliasOutput, error) { return &kms.CreateAliasOutput{}, nil }, signWithContext: func(ctx aws.Context, input *kms.SignInput, opts ...request.Option) (*kms.SignOutput, error) { return &kms.SignOutput{ Signature: signature, }, nil }, } } golang-step-crypto-0.24.0/kms/awskms/no_awskms.go000066400000000000000000000006601437037643100217740ustar00rootroot00000000000000//go:build noawskms // +build noawskms package awskms import ( "context" "os" "path/filepath" "github.com/pkg/errors" "go.step.sm/crypto/kms/apiv1" ) func init() { apiv1.Register(apiv1.AmazonKMS, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) { name := filepath.Base(os.Args[0]) return nil, errors.Errorf("unsupported kms type 'awskms': %s is compiled without Amazon KMS support", name) }) } golang-step-crypto-0.24.0/kms/awskms/signer.go000066400000000000000000000057471437037643100212750ustar00rootroot00000000000000//go:build !noawskms // +build !noawskms package awskms import ( "crypto" "crypto/ecdsa" "crypto/rsa" "io" "github.com/aws/aws-sdk-go/service/kms" "github.com/pkg/errors" "go.step.sm/crypto/pemutil" ) // Signer implements a crypto.Signer using the AWS KMS. type Signer struct { service KeyManagementClient keyID string publicKey crypto.PublicKey } // NewSigner creates a new signer using a key in the AWS KMS. func NewSigner(svc KeyManagementClient, signingKey string) (*Signer, error) { keyID, err := parseKeyID(signingKey) if err != nil { return nil, err } // Make sure that the key exists. signer := &Signer{ service: svc, keyID: keyID, } if err := signer.preloadKey(keyID); err != nil { return nil, err } return signer, nil } func (s *Signer) preloadKey(keyID string) error { ctx, cancel := defaultContext() defer cancel() resp, err := s.service.GetPublicKeyWithContext(ctx, &kms.GetPublicKeyInput{ KeyId: &keyID, }) if err != nil { return errors.Wrap(err, "awskms GetPublicKeyWithContext failed") } s.publicKey, err = pemutil.ParseDER(resp.PublicKey) return err } // Public returns the public key of this signer or an error. func (s *Signer) Public() crypto.PublicKey { return s.publicKey } // Sign signs digest with the private key stored in the AWS KMS. func (s *Signer) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { alg, err := getSigningAlgorithm(s.Public(), opts) if err != nil { return nil, err } req := &kms.SignInput{ KeyId: &s.keyID, SigningAlgorithm: &alg, Message: digest, } req.SetMessageType("DIGEST") ctx, cancel := defaultContext() defer cancel() resp, err := s.service.SignWithContext(ctx, req) if err != nil { return nil, errors.Wrap(err, "awsKMS SignWithContext failed") } return resp.Signature, nil } func getSigningAlgorithm(key crypto.PublicKey, opts crypto.SignerOpts) (string, error) { switch key.(type) { case *rsa.PublicKey: _, isPSS := opts.(*rsa.PSSOptions) switch h := opts.HashFunc(); h { case crypto.SHA256: if isPSS { return kms.SigningAlgorithmSpecRsassaPssSha256, nil } return kms.SigningAlgorithmSpecRsassaPkcs1V15Sha256, nil case crypto.SHA384: if isPSS { return kms.SigningAlgorithmSpecRsassaPssSha384, nil } return kms.SigningAlgorithmSpecRsassaPkcs1V15Sha384, nil case crypto.SHA512: if isPSS { return kms.SigningAlgorithmSpecRsassaPssSha512, nil } return kms.SigningAlgorithmSpecRsassaPkcs1V15Sha512, nil default: return "", errors.Errorf("unsupported hash function %v", h) } case *ecdsa.PublicKey: switch h := opts.HashFunc(); h { case crypto.SHA256: return kms.SigningAlgorithmSpecEcdsaSha256, nil case crypto.SHA384: return kms.SigningAlgorithmSpecEcdsaSha384, nil case crypto.SHA512: return kms.SigningAlgorithmSpecEcdsaSha512, nil default: return "", errors.Errorf("unsupported hash function %v", h) } default: return "", errors.Errorf("unsupported key type %T", key) } } golang-step-crypto-0.24.0/kms/awskms/signer_test.go000066400000000000000000000134661437037643100223310ustar00rootroot00000000000000package awskms import ( "crypto" "crypto/ecdsa" "crypto/rand" "crypto/rsa" "fmt" "io" "reflect" "testing" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/service/kms" "go.step.sm/crypto/pemutil" ) func TestNewSigner(t *testing.T) { okClient := getOKClient() key, err := pemutil.ParseKey([]byte(publicKey)) if err != nil { t.Fatal(err) } type args struct { svc KeyManagementClient signingKey string } tests := []struct { name string args args want *Signer wantErr bool }{ {"ok", args{okClient, "awskms:key-id=be468355-ca7a-40d9-a28b-8ae1c4c7f936"}, &Signer{ service: okClient, keyID: "be468355-ca7a-40d9-a28b-8ae1c4c7f936", publicKey: key, }, false}, {"fail parse", args{okClient, "awskms:key-id="}, nil, true}, {"fail preload", args{&MockClient{ getPublicKeyWithContext: func(ctx aws.Context, input *kms.GetPublicKeyInput, opts ...request.Option) (*kms.GetPublicKeyOutput, error) { return nil, fmt.Errorf("an error") }, }, "awskms:key-id=be468355-ca7a-40d9-a28b-8ae1c4c7f936"}, nil, true}, {"fail preload not der", args{&MockClient{ getPublicKeyWithContext: func(ctx aws.Context, input *kms.GetPublicKeyInput, opts ...request.Option) (*kms.GetPublicKeyOutput, error) { return &kms.GetPublicKeyOutput{ KeyId: input.KeyId, PublicKey: []byte(publicKey), }, nil }, }, "awskms:key-id=be468355-ca7a-40d9-a28b-8ae1c4c7f936"}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := NewSigner(tt.args.svc, tt.args.signingKey) if (err != nil) != tt.wantErr { t.Errorf("NewSigner() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("NewSigner() = %v, want %v", got, tt.want) } }) } } func TestSigner_Public(t *testing.T) { okClient := getOKClient() key, err := pemutil.ParseKey([]byte(publicKey)) if err != nil { t.Fatal(err) } type fields struct { service KeyManagementClient keyID string publicKey crypto.PublicKey } tests := []struct { name string fields fields want crypto.PublicKey }{ {"ok", fields{okClient, "be468355-ca7a-40d9-a28b-8ae1c4c7f936", key}, key}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := &Signer{ service: tt.fields.service, keyID: tt.fields.keyID, publicKey: tt.fields.publicKey, } if got := s.Public(); !reflect.DeepEqual(got, tt.want) { t.Errorf("Signer.Public() = %v, want %v", got, tt.want) } }) } } func TestSigner_Sign(t *testing.T) { okClient := getOKClient() key, err := pemutil.ParseKey([]byte(publicKey)) if err != nil { t.Fatal(err) } type fields struct { service KeyManagementClient keyID string publicKey crypto.PublicKey } type args struct { rand io.Reader digest []byte opts crypto.SignerOpts } tests := []struct { name string fields fields args args want []byte wantErr bool }{ {"ok", fields{okClient, "be468355-ca7a-40d9-a28b-8ae1c4c7f936", key}, args{rand.Reader, []byte("digest"), crypto.SHA256}, signature, false}, {"fail alg", fields{okClient, "be468355-ca7a-40d9-a28b-8ae1c4c7f936", key}, args{rand.Reader, []byte("digest"), crypto.MD5}, nil, true}, {"fail key", fields{okClient, "be468355-ca7a-40d9-a28b-8ae1c4c7f936", []byte("key")}, args{rand.Reader, []byte("digest"), crypto.SHA256}, nil, true}, {"fail sign", fields{&MockClient{ signWithContext: func(ctx aws.Context, input *kms.SignInput, opts ...request.Option) (*kms.SignOutput, error) { return nil, fmt.Errorf("an error") }, }, "be468355-ca7a-40d9-a28b-8ae1c4c7f936", key}, args{rand.Reader, []byte("digest"), crypto.SHA256}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := &Signer{ service: tt.fields.service, keyID: tt.fields.keyID, publicKey: tt.fields.publicKey, } got, err := s.Sign(tt.args.rand, tt.args.digest, tt.args.opts) if (err != nil) != tt.wantErr { t.Errorf("Signer.Sign() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("Signer.Sign() = %v, want %v", got, tt.want) } }) } } func Test_getSigningAlgorithm(t *testing.T) { type args struct { key crypto.PublicKey opts crypto.SignerOpts } tests := []struct { name string args args want string wantErr bool }{ {"rsa+sha256", args{&rsa.PublicKey{}, crypto.SHA256}, "RSASSA_PKCS1_V1_5_SHA_256", false}, {"rsa+sha384", args{&rsa.PublicKey{}, crypto.SHA384}, "RSASSA_PKCS1_V1_5_SHA_384", false}, {"rsa+sha512", args{&rsa.PublicKey{}, crypto.SHA512}, "RSASSA_PKCS1_V1_5_SHA_512", false}, {"pssrsa+sha256", args{&rsa.PublicKey{}, &rsa.PSSOptions{Hash: crypto.SHA256.HashFunc()}}, "RSASSA_PSS_SHA_256", false}, {"pssrsa+sha384", args{&rsa.PublicKey{}, &rsa.PSSOptions{Hash: crypto.SHA384.HashFunc()}}, "RSASSA_PSS_SHA_384", false}, {"pssrsa+sha512", args{&rsa.PublicKey{}, &rsa.PSSOptions{Hash: crypto.SHA512.HashFunc()}}, "RSASSA_PSS_SHA_512", false}, {"P256", args{&ecdsa.PublicKey{}, crypto.SHA256}, "ECDSA_SHA_256", false}, {"P384", args{&ecdsa.PublicKey{}, crypto.SHA384}, "ECDSA_SHA_384", false}, {"P521", args{&ecdsa.PublicKey{}, crypto.SHA512}, "ECDSA_SHA_512", false}, {"fail type", args{[]byte("key"), crypto.SHA256}, "", true}, {"fail rsa alg", args{&rsa.PublicKey{}, crypto.MD5}, "", true}, {"fail ecdsa alg", args{&ecdsa.PublicKey{}, crypto.MD5}, "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := getSigningAlgorithm(tt.args.key, tt.args.opts) if (err != nil) != tt.wantErr { t.Errorf("getSigningAlgorithm() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("getSigningAlgorithm() = %v, want %v", got, tt.want) } }) } } golang-step-crypto-0.24.0/kms/azurekms/000077500000000000000000000000001437037643100177765ustar00rootroot00000000000000golang-step-crypto-0.24.0/kms/azurekms/internal/000077500000000000000000000000001437037643100216125ustar00rootroot00000000000000golang-step-crypto-0.24.0/kms/azurekms/internal/mock/000077500000000000000000000000001437037643100225435ustar00rootroot00000000000000golang-step-crypto-0.24.0/kms/azurekms/internal/mock/key_vault_client.go000066400000000000000000000056051437037643100264410ustar00rootroot00000000000000// Code generated by MockGen. DO NOT EDIT. // Source: go.step.sm/crypto/kms/azurekms (interfaces: KeyVaultClient) // Package mock is a generated GoMock package. package mock import ( context "context" reflect "reflect" keyvault "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault" gomock "github.com/golang/mock/gomock" ) // KeyVaultClient is a mock of KeyVaultClient interface type KeyVaultClient struct { ctrl *gomock.Controller recorder *KeyVaultClientMockRecorder } // KeyVaultClientMockRecorder is the mock recorder for KeyVaultClient type KeyVaultClientMockRecorder struct { mock *KeyVaultClient } // NewKeyVaultClient creates a new mock instance func NewKeyVaultClient(ctrl *gomock.Controller) *KeyVaultClient { mock := &KeyVaultClient{ctrl: ctrl} mock.recorder = &KeyVaultClientMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use func (m *KeyVaultClient) EXPECT() *KeyVaultClientMockRecorder { return m.recorder } // CreateKey mocks base method func (m *KeyVaultClient) CreateKey(arg0 context.Context, arg1, arg2 string, arg3 keyvault.KeyCreateParameters) (keyvault.KeyBundle, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateKey", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(keyvault.KeyBundle) ret1, _ := ret[1].(error) return ret0, ret1 } // CreateKey indicates an expected call of CreateKey func (mr *KeyVaultClientMockRecorder) CreateKey(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateKey", reflect.TypeOf((*KeyVaultClient)(nil).CreateKey), arg0, arg1, arg2, arg3) } // GetKey mocks base method func (m *KeyVaultClient) GetKey(arg0 context.Context, arg1, arg2, arg3 string) (keyvault.KeyBundle, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetKey", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(keyvault.KeyBundle) ret1, _ := ret[1].(error) return ret0, ret1 } // GetKey indicates an expected call of GetKey func (mr *KeyVaultClientMockRecorder) GetKey(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetKey", reflect.TypeOf((*KeyVaultClient)(nil).GetKey), arg0, arg1, arg2, arg3) } // Sign mocks base method func (m *KeyVaultClient) Sign(arg0 context.Context, arg1, arg2, arg3 string, arg4 keyvault.KeySignParameters) (keyvault.KeyOperationResult, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Sign", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(keyvault.KeyOperationResult) ret1, _ := ret[1].(error) return ret0, ret1 } // Sign indicates an expected call of Sign func (mr *KeyVaultClientMockRecorder) Sign(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sign", reflect.TypeOf((*KeyVaultClient)(nil).Sign), arg0, arg1, arg2, arg3, arg4) } golang-step-crypto-0.24.0/kms/azurekms/key_vault.go000066400000000000000000000226371437037643100223420ustar00rootroot00000000000000//go:build !noazurekms // +build !noazurekms package azurekms import ( "context" "crypto" "regexp" "time" "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault" "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/azure/auth" "github.com/Azure/go-autorest/autorest/date" "github.com/pkg/errors" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/kms/uri" ) func init() { apiv1.Register(apiv1.AzureKMS, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) { return New(ctx, opts) }) } // Scheme is the scheme used for the Azure Key Vault uris. const Scheme = "azurekms" // keyIDRegexp is the regular expression that Key Vault uses on the kid. We can // extract the vault, name and version of the key. var keyIDRegexp = regexp.MustCompile(`^https://([0-9a-zA-Z-]+)\.vault\.azure\.net/keys/([0-9a-zA-Z-]+)/([0-9a-zA-Z-]+)$`) var ( valueTrue = true value2048 int32 = 2048 value3072 int32 = 3072 value4096 int32 = 4096 ) var now = func() time.Time { return time.Now().UTC() } type keyType struct { Kty keyvault.JSONWebKeyType Curve keyvault.JSONWebKeyCurveName } func (k keyType) KeyType(pl apiv1.ProtectionLevel) keyvault.JSONWebKeyType { switch k.Kty { case keyvault.EC: if pl == apiv1.HSM { return keyvault.ECHSM } return k.Kty case keyvault.RSA: if pl == apiv1.HSM { return keyvault.RSAHSM } return k.Kty default: return "" } } var signatureAlgorithmMapping = map[apiv1.SignatureAlgorithm]keyType{ apiv1.UnspecifiedSignAlgorithm: { Kty: keyvault.EC, Curve: keyvault.P256, }, apiv1.SHA256WithRSA: { Kty: keyvault.RSA, }, apiv1.SHA384WithRSA: { Kty: keyvault.RSA, }, apiv1.SHA512WithRSA: { Kty: keyvault.RSA, }, apiv1.SHA256WithRSAPSS: { Kty: keyvault.RSA, }, apiv1.SHA384WithRSAPSS: { Kty: keyvault.RSA, }, apiv1.SHA512WithRSAPSS: { Kty: keyvault.RSA, }, apiv1.ECDSAWithSHA256: { Kty: keyvault.EC, Curve: keyvault.P256, }, apiv1.ECDSAWithSHA384: { Kty: keyvault.EC, Curve: keyvault.P384, }, apiv1.ECDSAWithSHA512: { Kty: keyvault.EC, Curve: keyvault.P521, }, } // vaultResource is the value the client will use as audience. const vaultResource = "https://vault.azure.net" // KeyVaultClient is the interface implemented by keyvault.BaseClient. It will // be used for testing purposes. type KeyVaultClient interface { GetKey(ctx context.Context, vaultBaseURL string, keyName string, keyVersion string) (keyvault.KeyBundle, error) CreateKey(ctx context.Context, vaultBaseURL string, keyName string, parameters keyvault.KeyCreateParameters) (keyvault.KeyBundle, error) Sign(ctx context.Context, vaultBaseURL string, keyName string, keyVersion string, parameters keyvault.KeySignParameters) (keyvault.KeyOperationResult, error) } // KeyVault implements a KMS using Azure Key Vault. // // The URI format used in Azure Key Vault is the following: // // - azurekms:name=key-name;vault=vault-name // - azurekms:name=key-name;vault=vault-name?version=key-version // - azurekms:name=key-name;vault=vault-name?hsm=true // // The scheme is "azurekms"; "name" is the key name; "vault" is the key vault // name where the key is located; "version" is an optional parameter that // defines the version of they key, if version is not given, the latest one will // be used; "hsm" defines if an HSM want to be used for this key, this is // specially useful when this is used from `step`. // // TODO(mariano): The implementation is using /services/keyvault/v7.1/keyvault // package, at some point Azure might create a keyvault client with all the // functionality in /sdk/keyvault, we should migrate to that once available. type KeyVault struct { baseClient KeyVaultClient defaults DefaultOptions } // DefaultOptions are custom options that can be passed as defaults using the // URI in apiv1.Options. type DefaultOptions struct { Vault string ProtectionLevel apiv1.ProtectionLevel } var createClient = func(ctx context.Context, opts apiv1.Options) (KeyVaultClient, error) { baseClient := keyvault.New() // With an URI, try to log in only using client credentials in the URI. // Client credentials requires: // - client-id // - client-secret // - tenant-id // And optionally the aad-endpoint to support custom clouds: // - aad-endpoint (defaults to https://login.microsoftonline.com/) if opts.URI != "" { u, err := uri.ParseWithScheme(Scheme, opts.URI) if err != nil { return nil, err } // Required options clientID := u.Get("client-id") clientSecret := u.Get("client-secret") tenantID := u.Get("tenant-id") // optional aadEndpoint := u.Get("aad-endpoint") if clientID != "" && clientSecret != "" && tenantID != "" { s := auth.EnvironmentSettings{ Values: map[string]string{ auth.ClientID: clientID, auth.ClientSecret: clientSecret, auth.TenantID: tenantID, auth.Resource: vaultResource, }, Environment: azure.PublicCloud, } if aadEndpoint != "" { s.Environment.ActiveDirectoryEndpoint = aadEndpoint } baseClient.Authorizer, err = s.GetAuthorizer() if err != nil { return nil, err } return baseClient, nil } } // Attempt to authorize with the following methods: // 1. Environment variables. // - Client credentials // - Client certificate // - Username and password // - MSI // 2. Using Azure CLI 2.0 on local development. authorizer, err := auth.NewAuthorizerFromEnvironmentWithResource(vaultResource) if err != nil { authorizer, err = auth.NewAuthorizerFromCLIWithResource(vaultResource) if err != nil { return nil, errors.Wrap(err, "error getting authorizer for key vault") } } baseClient.Authorizer = authorizer return &baseClient, nil } // New initializes a new KMS implemented using Azure Key Vault. func New(ctx context.Context, opts apiv1.Options) (*KeyVault, error) { baseClient, err := createClient(ctx, opts) if err != nil { return nil, err } // step and step-ca do not need and URI, but having a default vault and // protection level is useful if this package is used as an api var defaults DefaultOptions if opts.URI != "" { u, err := uri.ParseWithScheme(Scheme, opts.URI) if err != nil { return nil, err } defaults.Vault = u.Get("vault") if u.GetBool("hsm") { defaults.ProtectionLevel = apiv1.HSM } } return &KeyVault{ baseClient: baseClient, defaults: defaults, }, nil } // GetPublicKey loads a public key from Azure Key Vault by its resource name. func (k *KeyVault) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) { if req.Name == "" { return nil, errors.New("getPublicKeyRequest 'name' cannot be empty") } vault, name, version, _, err := parseKeyName(req.Name, k.defaults) if err != nil { return nil, err } ctx, cancel := defaultContext() defer cancel() resp, err := k.baseClient.GetKey(ctx, vaultBaseURL(vault), name, version) if err != nil { return nil, errors.Wrap(err, "keyVault GetKey failed") } return convertKey(resp.Key) } // CreateKey creates a asymmetric key in Azure Key Vault. func (k *KeyVault) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) { if req.Name == "" { return nil, errors.New("createKeyRequest 'name' cannot be empty") } vault, name, _, hsm, err := parseKeyName(req.Name, k.defaults) if err != nil { return nil, err } // Override protection level to HSM only if it's not specified, and is given // in the uri. protectionLevel := req.ProtectionLevel if protectionLevel == apiv1.UnspecifiedProtectionLevel && hsm { protectionLevel = apiv1.HSM } kt, ok := signatureAlgorithmMapping[req.SignatureAlgorithm] if !ok { return nil, errors.Errorf("keyVault does not support signature algorithm '%s'", req.SignatureAlgorithm) } var keySize *int32 if kt.Kty == keyvault.RSA || kt.Kty == keyvault.RSAHSM { switch req.Bits { case 2048: keySize = &value2048 case 0, 3072: keySize = &value3072 case 4096: keySize = &value4096 default: return nil, errors.Errorf("keyVault does not support key size %d", req.Bits) } } created := date.UnixTime(now()) ctx, cancel := defaultContext() defer cancel() resp, err := k.baseClient.CreateKey(ctx, vaultBaseURL(vault), name, keyvault.KeyCreateParameters{ Kty: kt.KeyType(protectionLevel), KeySize: keySize, Curve: kt.Curve, KeyOps: &[]keyvault.JSONWebKeyOperation{ keyvault.Sign, keyvault.Verify, }, KeyAttributes: &keyvault.KeyAttributes{ Enabled: &valueTrue, Created: &created, NotBefore: &created, }, }) if err != nil { return nil, errors.Wrap(err, "keyVault CreateKey failed") } publicKey, err := convertKey(resp.Key) if err != nil { return nil, err } keyURI := getKeyName(vault, name, resp) return &apiv1.CreateKeyResponse{ Name: keyURI, PublicKey: publicKey, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: keyURI, }, }, nil } // CreateSigner returns a crypto.Signer from a previously created asymmetric key. func (k *KeyVault) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) { if req.SigningKey == "" { return nil, errors.New("createSignerRequest 'signingKey' cannot be empty") } return NewSigner(k.baseClient, req.SigningKey, k.defaults) } // Close closes the client connection to the Azure Key Vault. This is a noop. func (k *KeyVault) Close() error { return nil } // ValidateName validates that the given string is a valid URI. func (k *KeyVault) ValidateName(s string) error { _, _, _, _, err := parseKeyName(s, k.defaults) return err } golang-step-crypto-0.24.0/kms/azurekms/key_vault_test.go000066400000000000000000000477211437037643100234020ustar00rootroot00000000000000//go:generate mockgen -package mock -mock_names=KeyVaultClient=KeyVaultClient -destination internal/mock/key_vault_client.go go.step.sm/crypto/kms/azurekms KeyVaultClient package azurekms import ( "context" "crypto" "encoding/json" "fmt" "reflect" "testing" "time" "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault" "github.com/Azure/go-autorest/autorest/date" "github.com/golang/mock/gomock" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/kms/azurekms/internal/mock" "gopkg.in/square/go-jose.v2" ) var errTest = fmt.Errorf("test error") func mockNow(t *testing.T) time.Time { old := now t0 := time.Unix(1234567890, 123).UTC() now = func() time.Time { return t0 } t.Cleanup(func() { now = old }) return t0 } func mockClient(t *testing.T) *mock.KeyVaultClient { t.Helper() ctrl := gomock.NewController(t) t.Cleanup(func() { ctrl.Finish() }) return mock.NewKeyVaultClient(ctrl) } func createJWK(t *testing.T, pub crypto.PublicKey) *keyvault.JSONWebKey { t.Helper() b, err := json.Marshal(&jose.JSONWebKey{ Key: pub, }) if err != nil { t.Fatal(err) } key := new(keyvault.JSONWebKey) if err := json.Unmarshal(b, key); err != nil { t.Fatal(err) } return key } func Test_now(t *testing.T) { t0 := now() if loc := t0.Location(); loc != time.UTC { t.Errorf("now() Location = %v, want %v", loc, time.UTC) } } func TestNew(t *testing.T) { client := mockClient(t) old := createClient t.Cleanup(func() { createClient = old }) type args struct { ctx context.Context opts apiv1.Options } tests := []struct { name string setup func() args args want *KeyVault wantErr bool }{ {"ok", func() { createClient = func(ctx context.Context, opts apiv1.Options) (KeyVaultClient, error) { return client, nil } }, args{context.Background(), apiv1.Options{}}, &KeyVault{ baseClient: client, }, false}, {"ok with vault", func() { createClient = func(ctx context.Context, opts apiv1.Options) (KeyVaultClient, error) { return client, nil } }, args{context.Background(), apiv1.Options{ URI: "azurekms:vault=my-vault", }}, &KeyVault{ baseClient: client, defaults: DefaultOptions{ Vault: "my-vault", ProtectionLevel: apiv1.UnspecifiedProtectionLevel, }, }, false}, {"ok with vault + hsm", func() { createClient = func(ctx context.Context, opts apiv1.Options) (KeyVaultClient, error) { return client, nil } }, args{context.Background(), apiv1.Options{ URI: "azurekms:vault=my-vault;hsm=true", }}, &KeyVault{ baseClient: client, defaults: DefaultOptions{ Vault: "my-vault", ProtectionLevel: apiv1.HSM, }, }, false}, {"fail", func() { createClient = func(ctx context.Context, opts apiv1.Options) (KeyVaultClient, error) { return nil, errTest } }, args{context.Background(), apiv1.Options{}}, nil, true}, {"fail uri", func() { createClient = func(ctx context.Context, opts apiv1.Options) (KeyVaultClient, error) { return client, nil } }, args{context.Background(), apiv1.Options{ URI: "kms:vault=my-vault;hsm=true", }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.setup() got, err := New(tt.args.ctx, tt.args.opts) if (err != nil) != tt.wantErr { t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("New() = %v, want %v", got, tt.want) } }) } } func TestKeyVault_createClient(t *testing.T) { type args struct { ctx context.Context opts apiv1.Options } tests := []struct { name string args args skip bool wantErr bool }{ {"ok", args{context.Background(), apiv1.Options{}}, true, false}, {"ok with uri", args{context.Background(), apiv1.Options{ URI: "azurekms:client-id=id;client-secret=secret;tenant-id=id", }}, false, false}, {"ok with uri+aad", args{context.Background(), apiv1.Options{ URI: "azurekms:client-id=id;client-secret=secret;tenant-id=id;aad-enpoint=https%3A%2F%2Flogin.microsoftonline.us%2F", }}, false, false}, {"ok with uri no config", args{context.Background(), apiv1.Options{ URI: "azurekms:", }}, true, false}, {"fail uri", args{context.Background(), apiv1.Options{ URI: "kms:client-id=id;client-secret=secret;tenant-id=id", }}, false, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.skip { t.SkipNow() } _, err := createClient(tt.args.ctx, tt.args.opts) if (err != nil) != tt.wantErr { t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestKeyVault_GetPublicKey(t *testing.T) { key, err := keyutil.GenerateDefaultSigner() if err != nil { t.Fatal(err) } pub := key.Public() jwk := createJWK(t, pub) client := mockClient(t) client.EXPECT().GetKey(gomock.Any(), "https://my-vault.vault.azure.net/", "my-key", "").Return(keyvault.KeyBundle{ Key: jwk, }, nil) client.EXPECT().GetKey(gomock.Any(), "https://my-vault.vault.azure.net/", "my-key", "my-version").Return(keyvault.KeyBundle{ Key: jwk, }, nil) client.EXPECT().GetKey(gomock.Any(), "https://my-vault.vault.azure.net/", "not-found", "my-version").Return(keyvault.KeyBundle{}, errTest) type fields struct { baseClient KeyVaultClient } type args struct { req *apiv1.GetPublicKeyRequest } tests := []struct { name string fields fields args args want crypto.PublicKey wantErr bool }{ {"ok", fields{client}, args{&apiv1.GetPublicKeyRequest{ Name: "azurekms:vault=my-vault;name=my-key", }}, pub, false}, {"ok with version", fields{client}, args{&apiv1.GetPublicKeyRequest{ Name: "azurekms:vault=my-vault;name=my-key?version=my-version", }}, pub, false}, {"fail GetKey", fields{client}, args{&apiv1.GetPublicKeyRequest{ Name: "azurekms:vault=my-vault;name=not-found?version=my-version", }}, nil, true}, {"fail empty", fields{client}, args{&apiv1.GetPublicKeyRequest{ Name: "", }}, nil, true}, {"fail vault", fields{client}, args{&apiv1.GetPublicKeyRequest{ Name: "azurekms:vault=;name=not-found?version=my-version", }}, nil, true}, {"fail id", fields{client}, args{&apiv1.GetPublicKeyRequest{ Name: "azurekms:vault=;name=?version=my-version", }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &KeyVault{ baseClient: tt.fields.baseClient, } got, err := k.GetPublicKey(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("KeyVault.GetPublicKey() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("KeyVault.GetPublicKey() = %v, want %v", got, tt.want) } }) } } func TestKeyVault_CreateKey(t *testing.T) { ecKey, err := keyutil.GenerateDefaultSigner() if err != nil { t.Fatal(err) } rsaKey, err := keyutil.GenerateSigner("RSA", "", 2048) if err != nil { t.Fatal(err) } ecPub := ecKey.Public() rsaPub := rsaKey.Public() ecJWK := createJWK(t, ecPub) rsaJWK := createJWK(t, rsaPub) t0 := date.UnixTime(mockNow(t)) client := mockClient(t) expects := []struct { Name string Kty keyvault.JSONWebKeyType KeySize *int32 Curve keyvault.JSONWebKeyCurveName Key *keyvault.JSONWebKey }{ {"P-256", keyvault.EC, nil, keyvault.P256, ecJWK}, {"P-256 HSM", keyvault.ECHSM, nil, keyvault.P256, ecJWK}, {"P-256 HSM (uri)", keyvault.ECHSM, nil, keyvault.P256, ecJWK}, {"P-256 Default", keyvault.EC, nil, keyvault.P256, ecJWK}, {"P-384", keyvault.EC, nil, keyvault.P384, ecJWK}, {"P-521", keyvault.EC, nil, keyvault.P521, ecJWK}, {"RSA 0", keyvault.RSA, &value3072, "", rsaJWK}, {"RSA 0 HSM", keyvault.RSAHSM, &value3072, "", rsaJWK}, {"RSA 0 HSM (uri)", keyvault.RSAHSM, &value3072, "", rsaJWK}, {"RSA 2048", keyvault.RSA, &value2048, "", rsaJWK}, {"RSA 3072", keyvault.RSA, &value3072, "", rsaJWK}, {"RSA 4096", keyvault.RSA, &value4096, "", rsaJWK}, } for _, e := range expects { client.EXPECT().CreateKey(gomock.Any(), "https://my-vault.vault.azure.net/", "my-key", keyvault.KeyCreateParameters{ Kty: e.Kty, KeySize: e.KeySize, Curve: e.Curve, KeyOps: &[]keyvault.JSONWebKeyOperation{ keyvault.Sign, keyvault.Verify, }, KeyAttributes: &keyvault.KeyAttributes{ Enabled: &valueTrue, Created: &t0, NotBefore: &t0, }, }).Return(keyvault.KeyBundle{ Key: e.Key, }, nil) } client.EXPECT().CreateKey(gomock.Any(), "https://my-vault.vault.azure.net/", "not-found", gomock.Any()).Return(keyvault.KeyBundle{}, errTest) client.EXPECT().CreateKey(gomock.Any(), "https://my-vault.vault.azure.net/", "not-found", gomock.Any()).Return(keyvault.KeyBundle{ Key: nil, }, nil) type fields struct { baseClient KeyVaultClient } type args struct { req *apiv1.CreateKeyRequest } tests := []struct { name string fields fields args args want *apiv1.CreateKeyResponse wantErr bool }{ {"ok P-256", fields{client}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=my-vault;name=my-key", SignatureAlgorithm: apiv1.ECDSAWithSHA256, ProtectionLevel: apiv1.Software, }}, &apiv1.CreateKeyResponse{ Name: "azurekms:name=my-key;vault=my-vault", PublicKey: ecPub, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "azurekms:name=my-key;vault=my-vault", }, }, false}, {"ok P-256 HSM", fields{client}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=my-vault;name=my-key", SignatureAlgorithm: apiv1.ECDSAWithSHA256, ProtectionLevel: apiv1.HSM, }}, &apiv1.CreateKeyResponse{ Name: "azurekms:name=my-key;vault=my-vault", PublicKey: ecPub, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "azurekms:name=my-key;vault=my-vault", }, }, false}, {"ok P-256 HSM (uri)", fields{client}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=my-vault;name=my-key?hsm=true", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }}, &apiv1.CreateKeyResponse{ Name: "azurekms:name=my-key;vault=my-vault", PublicKey: ecPub, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "azurekms:name=my-key;vault=my-vault", }, }, false}, {"ok P-256 Default", fields{client}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=my-vault;name=my-key", }}, &apiv1.CreateKeyResponse{ Name: "azurekms:name=my-key;vault=my-vault", PublicKey: ecPub, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "azurekms:name=my-key;vault=my-vault", }, }, false}, {"ok P-384", fields{client}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=my-vault;name=my-key", SignatureAlgorithm: apiv1.ECDSAWithSHA384, }}, &apiv1.CreateKeyResponse{ Name: "azurekms:name=my-key;vault=my-vault", PublicKey: ecPub, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "azurekms:name=my-key;vault=my-vault", }, }, false}, {"ok P-521", fields{client}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=my-vault;name=my-key", SignatureAlgorithm: apiv1.ECDSAWithSHA512, }}, &apiv1.CreateKeyResponse{ Name: "azurekms:name=my-key;vault=my-vault", PublicKey: ecPub, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "azurekms:name=my-key;vault=my-vault", }, }, false}, {"ok RSA 0", fields{client}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=my-vault;name=my-key", Bits: 0, SignatureAlgorithm: apiv1.SHA256WithRSA, ProtectionLevel: apiv1.Software, }}, &apiv1.CreateKeyResponse{ Name: "azurekms:name=my-key;vault=my-vault", PublicKey: rsaPub, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "azurekms:name=my-key;vault=my-vault", }, }, false}, {"ok RSA 0 HSM", fields{client}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=my-vault;name=my-key", Bits: 0, SignatureAlgorithm: apiv1.SHA256WithRSAPSS, ProtectionLevel: apiv1.HSM, }}, &apiv1.CreateKeyResponse{ Name: "azurekms:name=my-key;vault=my-vault", PublicKey: rsaPub, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "azurekms:name=my-key;vault=my-vault", }, }, false}, {"ok RSA 0 HSM (uri)", fields{client}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=my-vault;name=my-key;hsm=true", Bits: 0, SignatureAlgorithm: apiv1.SHA256WithRSAPSS, }}, &apiv1.CreateKeyResponse{ Name: "azurekms:name=my-key;vault=my-vault", PublicKey: rsaPub, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "azurekms:name=my-key;vault=my-vault", }, }, false}, {"ok RSA 2048", fields{client}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=my-vault;name=my-key", Bits: 2048, SignatureAlgorithm: apiv1.SHA384WithRSA, }}, &apiv1.CreateKeyResponse{ Name: "azurekms:name=my-key;vault=my-vault", PublicKey: rsaPub, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "azurekms:name=my-key;vault=my-vault", }, }, false}, {"ok RSA 3072", fields{client}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=my-vault;name=my-key", Bits: 3072, SignatureAlgorithm: apiv1.SHA512WithRSA, }}, &apiv1.CreateKeyResponse{ Name: "azurekms:name=my-key;vault=my-vault", PublicKey: rsaPub, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "azurekms:name=my-key;vault=my-vault", }, }, false}, {"ok RSA 4096", fields{client}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=my-vault;name=my-key", Bits: 4096, SignatureAlgorithm: apiv1.SHA512WithRSAPSS, }}, &apiv1.CreateKeyResponse{ Name: "azurekms:name=my-key;vault=my-vault", PublicKey: rsaPub, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "azurekms:name=my-key;vault=my-vault", }, }, false}, {"fail createKey", fields{client}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=my-vault;name=not-found", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }}, nil, true}, {"fail convertKey", fields{client}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=my-vault;name=not-found", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }}, nil, true}, {"fail name", fields{client}, args{&apiv1.CreateKeyRequest{ Name: "", }}, nil, true}, {"fail vault", fields{client}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=;name=not-found?version=my-version", }}, nil, true}, {"fail id", fields{client}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=my-vault;name=?version=my-version", }}, nil, true}, {"fail SignatureAlgorithm", fields{client}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=my-vault;name=not-found", SignatureAlgorithm: apiv1.PureEd25519, }}, nil, true}, {"fail bit size", fields{client}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=my-vault;name=not-found", SignatureAlgorithm: apiv1.SHA384WithRSAPSS, Bits: 1024, }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &KeyVault{ baseClient: tt.fields.baseClient, } got, err := k.CreateKey(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("KeyVault.CreateKey() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("KeyVault.CreateKey() = %v, want %v", got, tt.want) } }) } } func TestKeyVault_CreateSigner(t *testing.T) { key, err := keyutil.GenerateDefaultSigner() if err != nil { t.Fatal(err) } pub := key.Public() jwk := createJWK(t, pub) client := mockClient(t) client.EXPECT().GetKey(gomock.Any(), "https://my-vault.vault.azure.net/", "my-key", "").Return(keyvault.KeyBundle{ Key: jwk, }, nil) client.EXPECT().GetKey(gomock.Any(), "https://my-vault.vault.azure.net/", "my-key", "my-version").Return(keyvault.KeyBundle{ Key: jwk, }, nil) client.EXPECT().GetKey(gomock.Any(), "https://my-vault.vault.azure.net/", "not-found", "my-version").Return(keyvault.KeyBundle{}, errTest) type fields struct { baseClient KeyVaultClient } type args struct { req *apiv1.CreateSignerRequest } tests := []struct { name string fields fields args args want crypto.Signer wantErr bool }{ {"ok", fields{client}, args{&apiv1.CreateSignerRequest{ SigningKey: "azurekms:vault=my-vault;name=my-key", }}, &Signer{ client: client, vaultBaseURL: "https://my-vault.vault.azure.net/", name: "my-key", version: "", publicKey: pub, }, false}, {"ok with version", fields{client}, args{&apiv1.CreateSignerRequest{ SigningKey: "azurekms:vault=my-vault;name=my-key;version=my-version", }}, &Signer{ client: client, vaultBaseURL: "https://my-vault.vault.azure.net/", name: "my-key", version: "my-version", publicKey: pub, }, false}, {"fail GetKey", fields{client}, args{&apiv1.CreateSignerRequest{ SigningKey: "azurekms:vault=my-vault;name=not-found;version=my-version", }}, nil, true}, {"fail SigningKey", fields{client}, args{&apiv1.CreateSignerRequest{ SigningKey: "", }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &KeyVault{ baseClient: tt.fields.baseClient, } got, err := k.CreateSigner(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("KeyVault.CreateSigner() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("KeyVault.CreateSigner() = %v, want %v", got, tt.want) } }) } } func TestKeyVault_Close(t *testing.T) { client := mockClient(t) type fields struct { baseClient KeyVaultClient } tests := []struct { name string fields fields wantErr bool }{ {"ok", fields{client}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &KeyVault{ baseClient: tt.fields.baseClient, } if err := k.Close(); (err != nil) != tt.wantErr { t.Errorf("KeyVault.Close() error = %v, wantErr %v", err, tt.wantErr) } }) } } func Test_keyType_KeyType(t *testing.T) { type fields struct { Kty keyvault.JSONWebKeyType Curve keyvault.JSONWebKeyCurveName } type args struct { pl apiv1.ProtectionLevel } tests := []struct { name string fields fields args args want keyvault.JSONWebKeyType }{ {"ec", fields{keyvault.EC, keyvault.P256}, args{apiv1.UnspecifiedProtectionLevel}, keyvault.EC}, {"ec software", fields{keyvault.EC, keyvault.P384}, args{apiv1.Software}, keyvault.EC}, {"ec hsm", fields{keyvault.EC, keyvault.P521}, args{apiv1.HSM}, keyvault.ECHSM}, {"rsa", fields{keyvault.RSA, keyvault.P256}, args{apiv1.UnspecifiedProtectionLevel}, keyvault.RSA}, {"rsa software", fields{keyvault.RSA, ""}, args{apiv1.Software}, keyvault.RSA}, {"rsa hsm", fields{keyvault.RSA, ""}, args{apiv1.HSM}, keyvault.RSAHSM}, {"empty", fields{"FOO", ""}, args{apiv1.UnspecifiedProtectionLevel}, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := keyType{ Kty: tt.fields.Kty, Curve: tt.fields.Curve, } if got := k.KeyType(tt.args.pl); !reflect.DeepEqual(got, tt.want) { t.Errorf("keyType.KeyType() = %v, want %v", got, tt.want) } }) } } func TestKeyVault_ValidateName(t *testing.T) { type args struct { s string } tests := []struct { name string args args wantErr bool }{ {"ok", args{"azurekms:name=my-key;vault=my-vault"}, false}, {"ok hsm", args{"azurekms:name=my-key;vault=my-vault?hsm=true"}, false}, {"fail scheme", args{"azure:name=my-key;vault=my-vault"}, true}, {"fail parse uri", args{"azurekms:name=%ZZ;vault=my-vault"}, true}, {"fail no name", args{"azurekms:vault=my-vault"}, true}, {"fail no vault", args{"azurekms:name=my-key"}, true}, {"fail empty", args{""}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &KeyVault{} if err := k.ValidateName(tt.args.s); (err != nil) != tt.wantErr { t.Errorf("KeyVault.ValidateName() error = %v, wantErr %v", err, tt.wantErr) } }) } } golang-step-crypto-0.24.0/kms/azurekms/no_azurekms.go000066400000000000000000000006661437037643100226720ustar00rootroot00000000000000//go:build noazurekms // +build noazurekms package azurekms import ( "context" "os" "path/filepath" "github.com/pkg/errors" "go.step.sm/crypto/kms/apiv1" ) func init() { apiv1.Register(apiv1.AzureKMS, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) { name := filepath.Base(os.Args[0]) return nil, errors.Errorf("unsupported kms type 'azurekms': %s is compiled without Azure KMS support", name) }) } golang-step-crypto-0.24.0/kms/azurekms/signer.go000066400000000000000000000113541437037643100216200ustar00rootroot00000000000000//go:build !noazurekms // +build !noazurekms package azurekms import ( "crypto" "crypto/ecdsa" "crypto/rsa" "encoding/base64" "io" "math/big" "time" "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault" "github.com/Azure/go-autorest/autorest/azure" "github.com/pkg/errors" "golang.org/x/crypto/cryptobyte" "golang.org/x/crypto/cryptobyte/asn1" ) // Signer implements a crypto.Signer using the AWS KMS. type Signer struct { client KeyVaultClient vaultBaseURL string name string version string publicKey crypto.PublicKey } // NewSigner creates a new signer using a key in the AWS KMS. func NewSigner(client KeyVaultClient, signingKey string, defaults DefaultOptions) (crypto.Signer, error) { vault, name, version, _, err := parseKeyName(signingKey, defaults) if err != nil { return nil, err } // Make sure that the key exists. signer := &Signer{ client: client, vaultBaseURL: vaultBaseURL(vault), name: name, version: version, } if err := signer.preloadKey(); err != nil { return nil, err } return signer, nil } func (s *Signer) preloadKey() error { ctx, cancel := defaultContext() defer cancel() resp, err := s.client.GetKey(ctx, s.vaultBaseURL, s.name, s.version) if err != nil { return errors.Wrap(err, "keyVault GetKey failed") } s.publicKey, err = convertKey(resp.Key) return err } // Public returns the public key of this signer or an error. func (s *Signer) Public() crypto.PublicKey { return s.publicKey } // Sign signs digest with the private key stored in the AWS KMS. func (s *Signer) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { alg, err := getSigningAlgorithm(s.Public(), opts) if err != nil { return nil, err } b64 := base64.RawURLEncoding.EncodeToString(digest) // Sign with retry if the key is not ready resp, err := s.signWithRetry(alg, b64, 3) if err != nil { return nil, errors.Wrap(err, "keyVault Sign failed") } sig, err := base64.RawURLEncoding.DecodeString(*resp.Result) if err != nil { return nil, errors.Wrap(err, "error decoding keyVault Sign result") } var octetSize int switch alg { case keyvault.ES256: octetSize = 32 // 256-bit, concat(R,S) = 64 bytes case keyvault.ES384: octetSize = 48 // 384-bit, concat(R,S) = 96 bytes case keyvault.ES512: octetSize = 66 // 528-bit, concat(R,S) = 132 bytes default: return sig, nil } // Convert to asn1 if len(sig) != octetSize*2 { return nil, errors.Errorf("keyVault Sign failed: unexpected signature length") } var b cryptobyte.Builder b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) { b.AddASN1BigInt(new(big.Int).SetBytes(sig[:octetSize])) // R b.AddASN1BigInt(new(big.Int).SetBytes(sig[octetSize:])) // S }) return b.Bytes() } func (s *Signer) signWithRetry(alg keyvault.JSONWebKeySignatureAlgorithm, b64 string, retryAttempts int) (keyvault.KeyOperationResult, error) { retry: ctx, cancel := defaultContext() defer cancel() resp, err := s.client.Sign(ctx, s.vaultBaseURL, s.name, s.version, keyvault.KeySignParameters{ Algorithm: alg, Value: &b64, }) if err != nil && retryAttempts > 0 { var requestError *azure.RequestError if errors.As(err, &requestError) { if se := requestError.ServiceError; se != nil && se.InnerError != nil { code, ok := se.InnerError["code"].(string) if ok && code == "KeyNotYetValid" { time.Sleep(time.Second / time.Duration(retryAttempts)) retryAttempts-- goto retry } } } } return resp, err } func getSigningAlgorithm(key crypto.PublicKey, opts crypto.SignerOpts) (keyvault.JSONWebKeySignatureAlgorithm, error) { switch key.(type) { case *rsa.PublicKey: hashFunc := opts.HashFunc() pss, isPSS := opts.(*rsa.PSSOptions) // Random salt lengths are not supported if isPSS && pss.SaltLength != rsa.PSSSaltLengthAuto && pss.SaltLength != rsa.PSSSaltLengthEqualsHash && pss.SaltLength != hashFunc.Size() { return "", errors.Errorf("unsupported RSA-PSS salt length %d", pss.SaltLength) } switch h := hashFunc; h { case crypto.SHA256: if isPSS { return keyvault.PS256, nil } return keyvault.RS256, nil case crypto.SHA384: if isPSS { return keyvault.PS384, nil } return keyvault.RS384, nil case crypto.SHA512: if isPSS { return keyvault.PS512, nil } return keyvault.RS512, nil default: return "", errors.Errorf("unsupported hash function %v", h) } case *ecdsa.PublicKey: switch h := opts.HashFunc(); h { case crypto.SHA256: return keyvault.ES256, nil case crypto.SHA384: return keyvault.ES384, nil case crypto.SHA512: return keyvault.ES512, nil default: return "", errors.Errorf("unsupported hash function %v", h) } default: return "", errors.Errorf("unsupported key type %T", key) } } golang-step-crypto-0.24.0/kms/azurekms/signer_test.go000066400000000000000000000400071437037643100226540ustar00rootroot00000000000000package azurekms import ( "crypto" "crypto/ecdsa" "crypto/rand" "crypto/rsa" "encoding/base64" "io" "reflect" "testing" "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault" "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/azure" "github.com/golang/mock/gomock" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/kms/apiv1" "golang.org/x/crypto/cryptobyte" "golang.org/x/crypto/cryptobyte/asn1" ) func TestNewSigner(t *testing.T) { key, err := keyutil.GenerateDefaultSigner() if err != nil { t.Fatal(err) } pub := key.Public() jwk := createJWK(t, pub) client := mockClient(t) client.EXPECT().GetKey(gomock.Any(), "https://my-vault.vault.azure.net/", "my-key", "").Return(keyvault.KeyBundle{ Key: jwk, }, nil) client.EXPECT().GetKey(gomock.Any(), "https://my-vault.vault.azure.net/", "my-key", "my-version").Return(keyvault.KeyBundle{ Key: jwk, }, nil) client.EXPECT().GetKey(gomock.Any(), "https://my-vault.vault.azure.net/", "my-key", "my-version").Return(keyvault.KeyBundle{ Key: jwk, }, nil) client.EXPECT().GetKey(gomock.Any(), "https://my-vault.vault.azure.net/", "not-found", "my-version").Return(keyvault.KeyBundle{}, errTest) var noOptions DefaultOptions type args struct { client KeyVaultClient signingKey string defaults DefaultOptions } tests := []struct { name string args args want crypto.Signer wantErr bool }{ {"ok", args{client, "azurekms:vault=my-vault;name=my-key", noOptions}, &Signer{ client: client, vaultBaseURL: "https://my-vault.vault.azure.net/", name: "my-key", version: "", publicKey: pub, }, false}, {"ok with version", args{client, "azurekms:name=my-key;vault=my-vault?version=my-version", noOptions}, &Signer{ client: client, vaultBaseURL: "https://my-vault.vault.azure.net/", name: "my-key", version: "my-version", publicKey: pub, }, false}, {"ok with options", args{client, "azurekms:name=my-key?version=my-version", DefaultOptions{Vault: "my-vault", ProtectionLevel: apiv1.HSM}}, &Signer{ client: client, vaultBaseURL: "https://my-vault.vault.azure.net/", name: "my-key", version: "my-version", publicKey: pub, }, false}, {"fail GetKey", args{client, "azurekms:name=not-found;vault=my-vault?version=my-version", noOptions}, nil, true}, {"fail vault", args{client, "azurekms:name=not-found;vault=", noOptions}, nil, true}, {"fail id", args{client, "azurekms:name=;vault=my-vault?version=my-version", noOptions}, nil, true}, {"fail scheme", args{client, "kms:name=not-found;vault=my-vault?version=my-version", noOptions}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := NewSigner(tt.args.client, tt.args.signingKey, tt.args.defaults) if (err != nil) != tt.wantErr { t.Errorf("NewSigner() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("NewSigner() = %v, want %v", got, tt.want) } }) } } func TestSigner_Public(t *testing.T) { key, err := keyutil.GenerateDefaultSigner() if err != nil { t.Fatal(err) } pub := key.Public() type fields struct { publicKey crypto.PublicKey } tests := []struct { name string fields fields want crypto.PublicKey }{ {"ok", fields{pub}, pub}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := &Signer{ publicKey: tt.fields.publicKey, } if got := s.Public(); !reflect.DeepEqual(got, tt.want) { t.Errorf("Signer.Public() = %v, want %v", got, tt.want) } }) } } func TestSigner_Sign(t *testing.T) { sign := func(kty, crv string, bits int, opts crypto.SignerOpts) (crypto.PublicKey, []byte, string, []byte) { key, err := keyutil.GenerateSigner(kty, crv, bits) if err != nil { t.Fatal(err) } h := opts.HashFunc().New() h.Write([]byte("random-data")) sum := h.Sum(nil) var sig, resultSig []byte if priv, ok := key.(*ecdsa.PrivateKey); ok { r, s, err := ecdsa.Sign(rand.Reader, priv, sum) if err != nil { t.Fatal(err) } curveBits := priv.Params().BitSize keyBytes := curveBits / 8 if curveBits%8 > 0 { keyBytes++ } rBytes := r.Bytes() rBytesPadded := make([]byte, keyBytes) copy(rBytesPadded[keyBytes-len(rBytes):], rBytes) sBytes := s.Bytes() sBytesPadded := make([]byte, keyBytes) copy(sBytesPadded[keyBytes-len(sBytes):], sBytes) //nolint:gocritic,makezero // rBytesPadded is initialized resultSig = append(rBytesPadded, sBytesPadded...) var b cryptobyte.Builder b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) { b.AddASN1BigInt(r) b.AddASN1BigInt(s) }) sig, err = b.Bytes() if err != nil { t.Fatal(err) } } else { sig, err = key.Sign(rand.Reader, sum, opts) if err != nil { t.Fatal(err) } resultSig = sig } return key.Public(), h.Sum(nil), base64.RawURLEncoding.EncodeToString(resultSig), sig } p256, p256Digest, p256ResultSig, p256Sig := sign("EC", "P-256", 0, crypto.SHA256) p384, p384Digest, p386ResultSig, p384Sig := sign("EC", "P-384", 0, crypto.SHA384) p521, p521Digest, p521ResultSig, p521Sig := sign("EC", "P-521", 0, crypto.SHA512) rsaSHA256, rsaSHA256Digest, rsaSHA256ResultSig, rsaSHA256Sig := sign("RSA", "", 2048, crypto.SHA256) rsaSHA384, rsaSHA384Digest, rsaSHA384ResultSig, rsaSHA384Sig := sign("RSA", "", 2048, crypto.SHA384) rsaSHA512, rsaSHA512Digest, rsaSHA512ResultSig, rsaSHA512Sig := sign("RSA", "", 2048, crypto.SHA512) rsaPSSSHA256, rsaPSSSHA256Digest, rsaPSSSHA256ResultSig, rsaPSSSHA256Sig := sign("RSA", "", 2048, &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthAuto, Hash: crypto.SHA256, }) rsaPSSSHA384, rsaPSSSHA384Digest, rsaPSSSHA384ResultSig, rsaPSSSHA384Sig := sign("RSA", "", 2048, &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthAuto, Hash: crypto.SHA512, }) rsaPSSSHA512, rsaPSSSHA512Digest, rsaPSSSHA512ResultSig, rsaPSSSHA512Sig := sign("RSA", "", 2048, &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthAuto, Hash: crypto.SHA512, }) ed25519Key, err := keyutil.GenerateSigner("OKP", "Ed25519", 0) if err != nil { t.Fatal(err) } client := mockClient(t) expects := []struct { name string keyVersion string alg keyvault.JSONWebKeySignatureAlgorithm digest []byte result keyvault.KeyOperationResult err error }{ {"P-256", "", keyvault.ES256, p256Digest, keyvault.KeyOperationResult{ Result: &p256ResultSig, }, nil}, {"P-384", "my-version", keyvault.ES384, p384Digest, keyvault.KeyOperationResult{ Result: &p386ResultSig, }, nil}, {"P-521", "my-version", keyvault.ES512, p521Digest, keyvault.KeyOperationResult{ Result: &p521ResultSig, }, nil}, {"RSA SHA256", "", keyvault.RS256, rsaSHA256Digest, keyvault.KeyOperationResult{ Result: &rsaSHA256ResultSig, }, nil}, {"RSA SHA384", "", keyvault.RS384, rsaSHA384Digest, keyvault.KeyOperationResult{ Result: &rsaSHA384ResultSig, }, nil}, {"RSA SHA512", "", keyvault.RS512, rsaSHA512Digest, keyvault.KeyOperationResult{ Result: &rsaSHA512ResultSig, }, nil}, {"RSA-PSS SHA256", "", keyvault.PS256, rsaPSSSHA256Digest, keyvault.KeyOperationResult{ Result: &rsaPSSSHA256ResultSig, }, nil}, {"RSA-PSS SHA384", "", keyvault.PS384, rsaPSSSHA384Digest, keyvault.KeyOperationResult{ Result: &rsaPSSSHA384ResultSig, }, nil}, {"RSA-PSS SHA512", "", keyvault.PS512, rsaPSSSHA512Digest, keyvault.KeyOperationResult{ Result: &rsaPSSSHA512ResultSig, }, nil}, // Errors {"fail Sign", "", keyvault.RS256, rsaSHA256Digest, keyvault.KeyOperationResult{}, errTest}, {"fail sign length", "", keyvault.ES256, p256Digest, keyvault.KeyOperationResult{ Result: &rsaSHA256ResultSig, }, nil}, {"fail base64", "", keyvault.ES256, p256Digest, keyvault.KeyOperationResult{ Result: func() *string { v := "๐Ÿ˜Ž" return &v }(), }, nil}, } for _, e := range expects { value := base64.RawURLEncoding.EncodeToString(e.digest) client.EXPECT().Sign(gomock.Any(), "https://my-vault.vault.azure.net/", "my-key", e.keyVersion, keyvault.KeySignParameters{ Algorithm: e.alg, Value: &value, }).Return(e.result, e.err) } type fields struct { client KeyVaultClient vaultBaseURL string name string version string publicKey crypto.PublicKey } type args struct { rand io.Reader digest []byte opts crypto.SignerOpts } tests := []struct { name string fields fields args args want []byte wantErr bool }{ {"ok P-256", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", p256}, args{ rand.Reader, p256Digest, crypto.SHA256, }, p256Sig, false}, {"ok P-384", fields{client, "https://my-vault.vault.azure.net/", "my-key", "my-version", p384}, args{ rand.Reader, p384Digest, crypto.SHA384, }, p384Sig, false}, {"ok P-521", fields{client, "https://my-vault.vault.azure.net/", "my-key", "my-version", p521}, args{ rand.Reader, p521Digest, crypto.SHA512, }, p521Sig, false}, {"ok RSA SHA256", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", rsaSHA256}, args{ rand.Reader, rsaSHA256Digest, crypto.SHA256, }, rsaSHA256Sig, false}, {"ok RSA SHA384", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", rsaSHA384}, args{ rand.Reader, rsaSHA384Digest, crypto.SHA384, }, rsaSHA384Sig, false}, {"ok RSA SHA512", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", rsaSHA512}, args{ rand.Reader, rsaSHA512Digest, crypto.SHA512, }, rsaSHA512Sig, false}, {"ok RSA-PSS SHA256", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", rsaPSSSHA256}, args{ rand.Reader, rsaPSSSHA256Digest, &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthAuto, Hash: crypto.SHA256, }, }, rsaPSSSHA256Sig, false}, {"ok RSA-PSS SHA384", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", rsaPSSSHA384}, args{ rand.Reader, rsaPSSSHA384Digest, &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: crypto.SHA384, }, }, rsaPSSSHA384Sig, false}, {"ok RSA-PSS SHA512", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", rsaPSSSHA512}, args{ rand.Reader, rsaPSSSHA512Digest, &rsa.PSSOptions{ SaltLength: 64, Hash: crypto.SHA512, }, }, rsaPSSSHA512Sig, false}, {"fail Sign", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", rsaSHA256}, args{ rand.Reader, rsaSHA256Digest, crypto.SHA256, }, nil, true}, {"fail sign length", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", p256}, args{ rand.Reader, p256Digest, crypto.SHA256, }, nil, true}, {"fail base64", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", p256}, args{ rand.Reader, p256Digest, crypto.SHA256, }, nil, true}, {"fail RSA-PSS salt length", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", rsaPSSSHA256}, args{ rand.Reader, rsaPSSSHA256Digest, &rsa.PSSOptions{ SaltLength: 64, Hash: crypto.SHA256, }, }, nil, true}, {"fail RSA Hash", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", rsaSHA256}, args{ rand.Reader, rsaSHA256Digest, crypto.SHA1, }, nil, true}, {"fail ECDSA Hash", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", p256}, args{ rand.Reader, p256Digest, crypto.MD5, }, nil, true}, {"fail Ed25519", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", ed25519Key}, args{ rand.Reader, []byte("message"), crypto.Hash(0), }, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := &Signer{ client: tt.fields.client, vaultBaseURL: tt.fields.vaultBaseURL, name: tt.fields.name, version: tt.fields.version, publicKey: tt.fields.publicKey, } got, err := s.Sign(tt.args.rand, tt.args.digest, tt.args.opts) if (err != nil) != tt.wantErr { t.Errorf("Signer.Sign() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("Signer.Sign() = %v, want %v", got, tt.want) } }) } } func TestSigner_Sign_signWithRetry(t *testing.T) { sign := func(kty, crv string, bits int, opts crypto.SignerOpts) (crypto.PublicKey, []byte, string, []byte) { key, err := keyutil.GenerateSigner(kty, crv, bits) if err != nil { t.Fatal(err) } h := opts.HashFunc().New() h.Write([]byte("random-data")) sum := h.Sum(nil) var sig, resultSig []byte if priv, ok := key.(*ecdsa.PrivateKey); ok { r, s, err := ecdsa.Sign(rand.Reader, priv, sum) if err != nil { t.Fatal(err) } curveBits := priv.Params().BitSize keyBytes := curveBits / 8 if curveBits%8 > 0 { keyBytes++ } rBytes := r.Bytes() rBytesPadded := make([]byte, keyBytes) copy(rBytesPadded[keyBytes-len(rBytes):], rBytes) sBytes := s.Bytes() sBytesPadded := make([]byte, keyBytes) copy(sBytesPadded[keyBytes-len(sBytes):], sBytes) //nolint:gocritic,makezero // rBytesPadded is initialized resultSig = append(rBytesPadded, sBytesPadded...) var b cryptobyte.Builder b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) { b.AddASN1BigInt(r) b.AddASN1BigInt(s) }) sig, err = b.Bytes() if err != nil { t.Fatal(err) } } else { sig, err = key.Sign(rand.Reader, sum, opts) if err != nil { t.Fatal(err) } resultSig = sig } return key.Public(), h.Sum(nil), base64.RawURLEncoding.EncodeToString(resultSig), sig } p256, p256Digest, p256ResultSig, p256Sig := sign("EC", "P-256", 0, crypto.SHA256) okResult := keyvault.KeyOperationResult{ Result: &p256ResultSig, } failResult := keyvault.KeyOperationResult{} retryError := autorest.DetailedError{ Original: &azure.RequestError{ ServiceError: &azure.ServiceError{ InnerError: map[string]interface{}{ "code": "KeyNotYetValid", }, }, }, } client := mockClient(t) expects := []struct { name string keyVersion string alg keyvault.JSONWebKeySignatureAlgorithm digest []byte result keyvault.KeyOperationResult err error }{ {"ok 1", "", keyvault.ES256, p256Digest, failResult, retryError}, {"ok 2", "", keyvault.ES256, p256Digest, failResult, retryError}, {"ok 3", "", keyvault.ES256, p256Digest, failResult, retryError}, {"ok 4", "", keyvault.ES256, p256Digest, okResult, nil}, {"fail", "fail-version", keyvault.ES256, p256Digest, failResult, retryError}, {"fail", "fail-version", keyvault.ES256, p256Digest, failResult, retryError}, {"fail", "fail-version", keyvault.ES256, p256Digest, failResult, retryError}, {"fail", "fail-version", keyvault.ES256, p256Digest, failResult, retryError}, } for _, e := range expects { value := base64.RawURLEncoding.EncodeToString(e.digest) client.EXPECT().Sign(gomock.Any(), "https://my-vault.vault.azure.net/", "my-key", e.keyVersion, keyvault.KeySignParameters{ Algorithm: e.alg, Value: &value, }).Return(e.result, e.err) } type fields struct { client KeyVaultClient vaultBaseURL string name string version string publicKey crypto.PublicKey } type args struct { rand io.Reader digest []byte opts crypto.SignerOpts } tests := []struct { name string fields fields args args want []byte wantErr bool }{ {"ok", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", p256}, args{ rand.Reader, p256Digest, crypto.SHA256, }, p256Sig, false}, {"fail", fields{client, "https://my-vault.vault.azure.net/", "my-key", "fail-version", p256}, args{ rand.Reader, p256Digest, crypto.SHA256, }, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := &Signer{ client: tt.fields.client, vaultBaseURL: tt.fields.vaultBaseURL, name: tt.fields.name, version: tt.fields.version, publicKey: tt.fields.publicKey, } got, err := s.Sign(tt.args.rand, tt.args.digest, tt.args.opts) if (err != nil) != tt.wantErr { t.Errorf("Signer.Sign() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("Signer.Sign() = %v, want %v", got, tt.want) } }) } } golang-step-crypto-0.24.0/kms/azurekms/utils.go000066400000000000000000000052071437037643100214710ustar00rootroot00000000000000//go:build !noazurekms // +build !noazurekms package azurekms import ( "context" "crypto" "encoding/json" "net/url" "time" "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault" "github.com/pkg/errors" "go.step.sm/crypto/jose" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/kms/uri" ) // defaultContext returns the default context used in requests to azure. func defaultContext() (context.Context, context.CancelFunc) { return context.WithTimeout(context.Background(), 15*time.Second) } // getKeyName returns the uri of the key vault key. func getKeyName(vault, name string, bundle keyvault.KeyBundle) string { if bundle.Key != nil && bundle.Key.Kid != nil { sm := keyIDRegexp.FindAllStringSubmatch(*bundle.Key.Kid, 1) if len(sm) == 1 && len(sm[0]) == 4 { m := sm[0] u := uri.New(Scheme, url.Values{ "vault": []string{m[1]}, "name": []string{m[2]}, }) u.RawQuery = url.Values{"version": []string{m[3]}}.Encode() return u.String() } } // Fallback to URI without id. return uri.New(Scheme, url.Values{ "vault": []string{vault}, "name": []string{name}, }).String() } // parseKeyName returns the key vault, name and version from URIs like: // // - azurekms:vault=key-vault;name=key-name // - azurekms:vault=key-vault;name=key-name?version=key-id // - azurekms:vault=key-vault;name=key-name?version=key-id&hsm=true // // The key-id defines the version of the key, if it is not passed the latest // version will be used. // // HSM can also be passed to define the protection level if this is not given in // CreateQuery. func parseKeyName(rawURI string, defaults DefaultOptions) (vault, name, version string, hsm bool, err error) { var u *uri.URI u, err = uri.ParseWithScheme(Scheme, rawURI) if err != nil { return } if name = u.Get("name"); name == "" { err = errors.Errorf("key uri %s is not valid: name is missing", rawURI) return } if vault = u.Get("vault"); vault == "" { if defaults.Vault == "" { name = "" err = errors.Errorf("key uri %s is not valid: vault is missing", rawURI) return } vault = defaults.Vault } if u.Get("hsm") == "" { hsm = (defaults.ProtectionLevel == apiv1.HSM) } else { hsm = u.GetBool("hsm") } version = u.Get("version") return } func vaultBaseURL(vault string) string { return "https://" + vault + ".vault.azure.net/" } func convertKey(key *keyvault.JSONWebKey) (crypto.PublicKey, error) { b, err := json.Marshal(key) if err != nil { return nil, errors.Wrap(err, "error marshaling key") } var jwk jose.JSONWebKey if err := jwk.UnmarshalJSON(b); err != nil { return nil, errors.Wrap(err, "error unmarshaling key") } return jwk.Key, nil } golang-step-crypto-0.24.0/kms/azurekms/utils_test.go000066400000000000000000000102401437037643100225210ustar00rootroot00000000000000package azurekms import ( "testing" "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault" "go.step.sm/crypto/kms/apiv1" ) func Test_getKeyName(t *testing.T) { getBundle := func(kid string) keyvault.KeyBundle { return keyvault.KeyBundle{ Key: &keyvault.JSONWebKey{ Kid: &kid, }, } } type args struct { vault string name string bundle keyvault.KeyBundle } tests := []struct { name string args args want string }{ {"ok", args{"my-vault", "my-key", getBundle("https://my-vault.vault.azure.net/keys/my-key/my-version")}, "azurekms:name=my-key;vault=my-vault?version=my-version"}, {"ok default", args{"my-vault", "my-key", getBundle("https://my-vault.foo.net/keys/my-key/my-version")}, "azurekms:name=my-key;vault=my-vault"}, {"ok too short", args{"my-vault", "my-key", getBundle("https://my-vault.vault.azure.net/keys/my-version")}, "azurekms:name=my-key;vault=my-vault"}, {"ok too long", args{"my-vault", "my-key", getBundle("https://my-vault.vault.azure.net/keys/my-key/my-version/sign")}, "azurekms:name=my-key;vault=my-vault"}, {"ok nil key", args{"my-vault", "my-key", keyvault.KeyBundle{}}, "azurekms:name=my-key;vault=my-vault"}, {"ok nil kid", args{"my-vault", "my-key", keyvault.KeyBundle{Key: &keyvault.JSONWebKey{}}}, "azurekms:name=my-key;vault=my-vault"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := getKeyName(tt.args.vault, tt.args.name, tt.args.bundle); got != tt.want { t.Errorf("getKeyName() = %v, want %v", got, tt.want) } }) } } func Test_parseKeyName(t *testing.T) { var noOptions DefaultOptions type args struct { rawURI string defaults DefaultOptions } tests := []struct { name string args args wantVault string wantName string wantVersion string wantHsm bool wantErr bool }{ {"ok", args{"azurekms:name=my-key;vault=my-vault?version=my-version", noOptions}, "my-vault", "my-key", "my-version", false, false}, {"ok opaque version", args{"azurekms:name=my-key;vault=my-vault;version=my-version", noOptions}, "my-vault", "my-key", "my-version", false, false}, {"ok no version", args{"azurekms:name=my-key;vault=my-vault", noOptions}, "my-vault", "my-key", "", false, false}, {"ok hsm", args{"azurekms:name=my-key;vault=my-vault?hsm=true", noOptions}, "my-vault", "my-key", "", true, false}, {"ok hsm false", args{"azurekms:name=my-key;vault=my-vault?hsm=false", noOptions}, "my-vault", "my-key", "", false, false}, {"ok default vault", args{"azurekms:name=my-key?version=my-version", DefaultOptions{Vault: "my-vault"}}, "my-vault", "my-key", "my-version", false, false}, {"ok default hsm", args{"azurekms:name=my-key;vault=my-vault?version=my-version", DefaultOptions{Vault: "other-vault", ProtectionLevel: apiv1.HSM}}, "my-vault", "my-key", "my-version", true, false}, {"fail scheme", args{"azure:name=my-key;vault=my-vault", noOptions}, "", "", "", false, true}, {"fail parse uri", args{"azurekms:name=%ZZ;vault=my-vault", noOptions}, "", "", "", false, true}, {"fail no name", args{"azurekms:vault=my-vault", noOptions}, "", "", "", false, true}, {"fail empty name", args{"azurekms:name=;vault=my-vault", noOptions}, "", "", "", false, true}, {"fail no vault", args{"azurekms:name=my-key", noOptions}, "", "", "", false, true}, {"fail empty vault", args{"azurekms:name=my-key;vault=", noOptions}, "", "", "", false, true}, {"fail empty", args{"", noOptions}, "", "", "", false, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotVault, gotName, gotVersion, gotHsm, err := parseKeyName(tt.args.rawURI, tt.args.defaults) if (err != nil) != tt.wantErr { t.Errorf("parseKeyName() error = %v, wantErr %v", err, tt.wantErr) return } if gotVault != tt.wantVault { t.Errorf("parseKeyName() gotVault = %v, want %v", gotVault, tt.wantVault) } if gotName != tt.wantName { t.Errorf("parseKeyName() gotName = %v, want %v", gotName, tt.wantName) } if gotVersion != tt.wantVersion { t.Errorf("parseKeyName() gotVersion = %v, want %v", gotVersion, tt.wantVersion) } if gotHsm != tt.wantHsm { t.Errorf("parseKeyName() gotHsm = %v, want %v", gotHsm, tt.wantHsm) } }) } } golang-step-crypto-0.24.0/kms/capi/000077500000000000000000000000001437037643100170515ustar00rootroot00000000000000golang-step-crypto-0.24.0/kms/capi/capi.go000066400000000000000000000543701437037643100203250ustar00rootroot00000000000000//go:build windows && !nocapi // +build windows,!nocapi package capi import ( "bytes" "context" "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" "crypto/x509" "encoding/binary" "encoding/hex" "fmt" "io" "math/big" "reflect" "strings" "unsafe" "github.com/pkg/errors" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/kms/uri" "go.step.sm/crypto/randutil" "golang.org/x/crypto/cryptobyte" "golang.org/x/crypto/cryptobyte/asn1" "golang.org/x/sys/windows" ) // Scheme is the scheme used in uris. const Scheme = "capi" const ( ProviderNameArg = "provider" ContainerNameArg = "key" HashArg = "sha1" StoreLocationArg = "store-location" // 'machine', 'user', etc StoreNameArg = "store" // 'MY', 'CA', 'ROOT', etc KeyIDArg = "key-id" SerialNumberArg = "serial" IssuerNameArg = "issuer" ) var signatureAlgorithmMapping = map[apiv1.SignatureAlgorithm]string{ apiv1.UnspecifiedSignAlgorithm: ALG_ECDSA_P256, apiv1.SHA256WithRSA: ALG_RSA, apiv1.SHA384WithRSA: ALG_RSA, apiv1.SHA512WithRSA: ALG_RSA, apiv1.ECDSAWithSHA256: ALG_ECDSA_P256, apiv1.ECDSAWithSHA384: ALG_ECDSA_P384, apiv1.ECDSAWithSHA512: ALG_ECDSA_P521, } // CAPIKMS implements a KMS using Windows CryptoAPI (CAPI) and Next-Gen CryptoAPI (CNG). // // The URI format used in CAPIKMS is the following: // // - capi:provider=STORAGE-PROVIDER;key=KEY-NAME // // For certificates: // - capi:store-location=[machine|user];store=My;sha1= // - capi:store-location=[machine|user];store=My;key-id= // - capi:store-location=[machine|user];store=My;issuer=;serial= // // The scheme is "capi"; // // "provider" is the provider name and can be one of: // - "Microsoft Software Key Storage Provider" // - "Microsoft Smart Card Key Storage Provider" // - "Microsoft Platform Crypto Provider" // if not set it defaults to "Microsoft Software Key Storage Provider" // // "key" key container name. If not set one is generated. // "store-location" specifies the certificate store location - "user" or "machine" // "store" certificate store name - "My", "Root", and "CA" are some examples // "sha1" sha1 thumbprint of the certificate to load in hex format // "key-id" X509v3 Subject Key Identifier of the certificate to load in hex format // "serial" serial number of the certificate to load in hex format // "issuer" Common Name of the certificate issuer type CAPIKMS struct { providerName string providerHandle uintptr pin string } func certContextToX509(certHandle *windows.CertContext) (*x509.Certificate, error) { var der []byte slice := (*reflect.SliceHeader)(unsafe.Pointer(&der)) slice.Data = uintptr(unsafe.Pointer(certHandle.EncodedCert)) slice.Len = int(certHandle.Length) slice.Cap = int(certHandle.Length) return x509.ParseCertificate(der) } func unmarshalRSA(buf []byte) (*rsa.PublicKey, error) { // BCRYPT_RSA_BLOB -- https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/ns-bcrypt-bcrypt_rsakey_blob header := struct { Magic uint32 BitLength uint32 PublicExpSize uint32 ModulusSize uint32 UnusedPrime1 uint32 UnusedPrime2 uint32 }{} r := bytes.NewReader(buf) if err := binary.Read(r, binary.LittleEndian, &header); err != nil { return nil, err } if header.Magic != rsa1Magic { return nil, fmt.Errorf("invalid header magic %x", header.Magic) } if header.PublicExpSize > 8 { return nil, fmt.Errorf("unsupported public exponent size (%d bits)", header.PublicExpSize*8) } // the exponent is in BigEndian format, so read the data into the right place in the buffer exp := make([]byte, 8) n, err := r.Read(exp[8-header.PublicExpSize:]) if err != nil { return nil, fmt.Errorf("failed to read public exponent %w", err) } if n != int(header.PublicExpSize) { return nil, fmt.Errorf("failed to read correct public exponent size, read %d expected %d", n, int(header.PublicExpSize)) } mod := make([]byte, header.ModulusSize) n, err = r.Read(mod) if err != nil { return nil, fmt.Errorf("failed to read modulus %w", err) } if n != int(header.ModulusSize) { return nil, fmt.Errorf("failed to read correct modulus size, read %d expected %d", n, int(header.ModulusSize)) } pub := &rsa.PublicKey{ N: new(big.Int).SetBytes(mod), E: int(binary.BigEndian.Uint64(exp)), } return pub, nil } func unmarshalECC(buf []byte, curve elliptic.Curve) (*ecdsa.PublicKey, error) { // BCRYPT_ECCKEY_BLOB -- https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/ns-bcrypt-bcrypt_ecckey_blob header := struct { Magic uint32 Key uint32 }{} r := bytes.NewReader(buf) if err := binary.Read(r, binary.LittleEndian, &header); err != nil { return nil, err } if expectedMagic, ok := curveMagicMap[curve.Params().Name]; ok { if expectedMagic != header.Magic { return nil, fmt.Errorf("elliptic curve blob did not contain expected magic") } } keyX := make([]byte, header.Key) n, err := r.Read(keyX) if err != nil { return nil, fmt.Errorf("failed to read key X %w", err) } if n != int(header.Key) { return nil, fmt.Errorf("failed to read key X size, read %d expected %d", n, int(header.Key)) } keyY := make([]byte, header.Key) n, err = r.Read(keyY) if err != nil { return nil, fmt.Errorf("failed to read key Y %w", err) } if n != int(header.Key) { return nil, fmt.Errorf("failed to read key Y size, read %d expected %d", n, int(header.Key)) } pub := &ecdsa.PublicKey{ Curve: curve, X: new(big.Int).SetBytes(keyX), Y: new(big.Int).SetBytes(keyY), } return pub, nil } func getPublicKey(kh uintptr) (crypto.PublicKey, error) { algGroup, err := nCryptGetPropertyStr(kh, NCRYPT_ALGORITHM_GROUP_PROPERTY) if err != nil { return nil, fmt.Errorf("unable to get NCRYPT_ALGORITHM_GROUP_PROPERTY: %w", err) } var pub crypto.PublicKey switch algGroup { case "ECDSA": buf, err := nCryptExportKey(kh, BCRYPT_ECCPUBLIC_BLOB) if err != nil { return nil, fmt.Errorf("failed to export ECC public key: %w", err) } curveName, err := nCryptGetPropertyStr(kh, NCRYPT_ECC_CURVE_NAME_PROPERTY) if err != nil { // The smart card provider doesn't have the curve name property set, attempt to get it from // algorithm property curveName, err = nCryptGetPropertyStr(kh, NCRYPT_ALGORITHM_PROPERTY) if err != nil { return nil, fmt.Errorf("failed to retrieve ECC curve name: %w", err) } } if _, ok := curveNames[curveName]; !ok { return nil, fmt.Errorf("curveName %s not found in curvenames map", curveName) } pub, err = unmarshalECC(buf, curveNames[curveName]) if err != nil { return nil, fmt.Errorf("failed to unmarshal ECC public key: %w", err) } case "RSA": buf, err := nCryptExportKey(kh, BCRYPT_RSAPUBLIC_BLOB) if err != nil { return nil, fmt.Errorf("failed to export %v public key: %w", algGroup, err) } pub, err = unmarshalRSA(buf) if err != nil { return nil, fmt.Errorf("failed to unmarshal %v public key: %w", algGroup, err) } default: return nil, fmt.Errorf("unhandled algorithm group %v retrieved from key", algGroup) } return pub, nil } // New returns a new CAPIKMS. func New(ctx context.Context, opts apiv1.Options) (*CAPIKMS, error) { providerName := "Microsoft Software Key Storage Provider" pin := "" if opts.URI != "" { u, err := uri.ParseWithScheme(Scheme, opts.URI) if err != nil { return nil, err } if v := u.Get(ProviderNameArg); v != "" { providerName = v } pin = u.Pin() } // TODO: a provider is not necessary for certificate functions, should we move this to the key and signing functions? ph, err := nCryptOpenStorageProvider(providerName) if err != nil { return nil, fmt.Errorf("could not open nCrypt provider: %w", err) } return &CAPIKMS{ providerName: providerName, providerHandle: ph, pin: pin, }, nil } func init() { apiv1.Register(apiv1.CAPIKMS, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) { return New(ctx, opts) }) } func (k *CAPIKMS) Close() error { if k.providerHandle != 0 { return nCryptFreeObject(k.providerHandle) } return nil } // CreateSigner returns a nce crypto.Signer that will sign using the key passed in via the URI. func (k *CAPIKMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) { u, err := uri.ParseWithScheme(Scheme, req.SigningKey) if err != nil { return nil, fmt.Errorf("failed to parse URI: %w", err) } var containerName string if containerName = u.Get(ContainerNameArg); containerName == "" { // generate a uuid for the container name containerName, err = randutil.UUIDv4() if err != nil { return nil, fmt.Errorf("failed to generate uuid: %w", err) } } pinOrPass := u.Pin() if pinOrPass == "" { pinOrPass = k.pin } kh, err := nCryptOpenKey(k.providerHandle, containerName, 0, 0) if err != nil { return nil, fmt.Errorf("unable to open key: %w", err) } if pinOrPass != "" && k.providerName == ProviderMSSC { err = nCryptSetProperty(kh, NCRYPT_PIN_PROPERTY, pinOrPass, 0) if err != nil { return nil, fmt.Errorf("unable to set key NCRYPT_PIN_PROPERTY: %w", err) } } else if pinOrPass != "" && k.providerName == ProviderMSPCP { passHash, err := hashPasswordUTF16(pinOrPass) if err != nil { return nil, fmt.Errorf("unable to hash password: %w", err) } err = nCryptSetProperty(kh, NCRYPT_PCP_USAGE_AUTH_PROPERTY, passHash, 0) if err != nil { return nil, fmt.Errorf("unable to set key NCRYPT_PCP_USAGE_AUTH_PROPERTY: %w", err) } } return newCAPISigner(kh, containerName, pinOrPass) } // CreateKey generates a new key in the storage provider using nCryptCreatePersistedKey func (k *CAPIKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) { if req.Name == "" { return nil, errors.New("createKeyRequest 'name' cannot be empty") } // The MSSC provider allows you to create keys without a certificate attached, but they seem to // be lost if the smartcard is removed, so refuse to create keys as a precaution if k.providerName == ProviderMSSC { return nil, fmt.Errorf("cannot create keys on %s", ProviderMSSC) } u, err := uri.ParseWithScheme(Scheme, req.Name) if err != nil { return nil, fmt.Errorf("failed to parse URI: %w", err) } var containerName string if containerName = u.Get(ContainerNameArg); containerName == "" { // generate a uuid for the container name containerName, err = randutil.UUIDv4() if err != nil { return nil, fmt.Errorf("failed to generate uuid: %w", err) } } alg, ok := signatureAlgorithmMapping[req.SignatureAlgorithm] if !ok { return nil, fmt.Errorf("unsupported algorithm %v", req.SignatureAlgorithm) } //TODO: check whether RSA keys require legacyKeySpec set to AT_KEYEXCHANGE kh, err := nCryptCreatePersistedKey(k.providerHandle, containerName, alg, 0, 0) if err != nil { return nil, fmt.Errorf("unable to create persisted key: %w", err) } defer nCryptFreeObject(kh) if alg == "RSA" { err = nCryptSetProperty(kh, NCRYPT_LENGTH_PROPERTY, uint32(req.Bits), 0) if err != nil { return nil, fmt.Errorf("unable to set key NCRYPT_LENGTH_PROPERTY: %w", err) } } // users can store the key as a machine key by passing in storelocation = machine // 'machine' is the only valid location, otherwise the key is stored as a 'user' key storeLocation := u.Get(StoreLocationArg) if storeLocation == "machine" { err = nCryptSetProperty(kh, NCRYPT_KEY_TYPE_PROPERTY, NCRYPT_MACHINE_KEY_FLAG, 0) if err != nil { return nil, fmt.Errorf("unable to set key NCRYPT_KEY_TYPE_PROPERTY: %w", err) } } else if storeLocation != "" && storeLocation != "user" { return nil, fmt.Errorf("invalid storeLocation %v", storeLocation) } // if supplied, set the smart card pin/or PCP pass pinOrPass := u.Pin() //failover to pin set in kms instantiation if pinOrPass == "" { pinOrPass = k.pin } // TODO: investigate if there is a similar property for software backed keys if pinOrPass != "" && k.providerName == ProviderMSSC { err = nCryptSetProperty(kh, NCRYPT_PIN_PROPERTY, pinOrPass, 0) if err != nil { return nil, fmt.Errorf("unable to set key NCRYPT_PIN_PROPERTY: %w", err) } } else if pinOrPass != "" && k.providerName == ProviderMSPCP { pwHash, err := hashPasswordUTF16(pinOrPass) // we have to SHA1 hash over the utf16 string if err != nil { return nil, fmt.Errorf("unable to hash pin: %w", err) } err = nCryptSetProperty(kh, NCRYPT_PCP_USAGE_AUTH_PROPERTY, pwHash, 0) if err != nil { return nil, fmt.Errorf("unable to set key NCRYPT_PIN_PROPERTY: %w", err) } } err = nCryptFinalizeKey(kh, 0) if err != nil { return nil, fmt.Errorf("unable to finalize key: %w", err) } uc, err := nCryptGetPropertyStr(kh, NCRYPT_UNIQUE_NAME_PROPERTY) if err != nil { return nil, fmt.Errorf("unable to retrieve NCRYPT_UNIQUE_NAME_PROPERTY: %w", err) } pub, err := getPublicKey(kh) if err != nil { return nil, fmt.Errorf("unable to retrieve public key: %w", err) } createdKeyURI := fmt.Sprintf("%s:%s=%s;%s=%s", Scheme, ProviderNameArg, k.providerName, ContainerNameArg, uc) return &apiv1.CreateKeyResponse{ Name: createdKeyURI, PublicKey: pub, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: createdKeyURI, }, }, nil } // GetPublicKey returns the public key from the key id (Microsoft calls it 'Key Container Name') passed in via the URI func (k *CAPIKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) { u, err := uri.ParseWithScheme(Scheme, req.Name) if err != nil { return nil, fmt.Errorf("failed to parse URI: %w", err) } var containerName string if containerName = u.Get(ContainerNameArg); containerName == "" { return nil, fmt.Errorf("%v not specified", ContainerNameArg) } kh, err := nCryptOpenKey(k.providerHandle, containerName, 0, 0) if err != nil { return nil, fmt.Errorf("unable to open key: %w", err) } defer nCryptFreeObject(kh) return getPublicKey(kh) } // LoadCertificate will return an x509.Certificate if passed a URI containing a subject key // identifier (key-id) or sha1 hash func (k *CAPIKMS) LoadCertificate(req *apiv1.LoadCertificateRequest) (*x509.Certificate, error) { u, err := uri.ParseWithScheme(Scheme, req.Name) if err != nil { return nil, fmt.Errorf("failed to parse URI: %w", err) } sha1Hash := u.Get(HashArg) keyID := u.Get(KeyIDArg) issuerName := u.Get(IssuerNameArg) serialNumber := u.Get(SerialNumberArg) // default to the user store var storeLocation string if storeLocation = u.Get(StoreLocationArg); storeLocation == "" { storeLocation = "user" } var certStoreLocation uint32 switch storeLocation { case "user": certStoreLocation = certStoreCurrentUser case "machine": certStoreLocation = certStoreLocalMachine default: return nil, fmt.Errorf("invalid cert store location %v", storeLocation) } var storeName string // default to the 'My' store if storeName = u.Get(StoreNameArg); storeName == "" { storeName = "My" } st, err := windows.CertOpenStore( certStoreProvSystem, 0, 0, certStoreLocation, uintptr(unsafe.Pointer(wide(storeName)))) if err != nil { return nil, fmt.Errorf("CertOpenStore for the %v store %v returned: %w", storeLocation, storeName, err) } var certHandle *windows.CertContext switch { case sha1Hash != "": sha1Hash = strings.TrimPrefix(sha1Hash, "0x") // Support specifying the hash as 0x like with serial sha1Bytes, err := hex.DecodeString(sha1Hash) if err != nil { return nil, fmt.Errorf("%s must be in hex format: %w", HashArg, err) } searchData := CERT_ID_KEYIDORHASH{ idChoice: CERT_ID_SHA1_HASH, KeyIDOrHash: CRYPTOAPI_BLOB{ len: uint32(len(sha1Bytes)), data: uintptr(unsafe.Pointer(&sha1Bytes[0])), }, } certHandle, err = findCertificateInStore(st, encodingX509ASN|encodingPKCS7, 0, findCertID, uintptr(unsafe.Pointer(&searchData)), nil) if err != nil { return nil, fmt.Errorf("findCertificateInStore failed: %w", err) } if certHandle == nil { return nil, fmt.Errorf("certificate with %v=%s not found", HashArg, keyID) } defer windows.CertFreeCertificateContext(certHandle) return certContextToX509(certHandle) case keyID != "": keyID = strings.TrimPrefix(keyID, "0x") // Support specifying the hash as 0x like with serial keyIDBytes, err := hex.DecodeString(keyID) if err != nil { return nil, fmt.Errorf("%v must be in hex format: %w", KeyIDArg, err) } searchData := CERT_ID_KEYIDORHASH{ idChoice: CERT_ID_KEY_IDENTIFIER, KeyIDOrHash: CRYPTOAPI_BLOB{ len: uint32(len(keyIDBytes)), data: uintptr(unsafe.Pointer(&keyIDBytes[0])), }, } certHandle, err = findCertificateInStore(st, encodingX509ASN|encodingPKCS7, 0, findCertID, uintptr(unsafe.Pointer(&searchData)), nil) if err != nil { return nil, fmt.Errorf("findCertificateInStore failed: %w", err) } if certHandle == nil { return nil, fmt.Errorf("certificate with %v=%s not found", KeyIDArg, keyID) } defer windows.CertFreeCertificateContext(certHandle) return certContextToX509(certHandle) case issuerName != "" && serialNumber != "": //TODO: Replace this search with a CERT_ID + CERT_ISSUER_SERIAL_NUMBER search instead // https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-cert_id // https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-cert_issuer_serial_number var serialBytes []byte if strings.HasPrefix(serialNumber, "0x") { serialNumber = strings.TrimPrefix(serialNumber, "0x") serialNumber = strings.TrimPrefix(serialNumber, "00") // Comparison fails if leading 00 is not removed serialBytes, err = hex.DecodeString(serialNumber) if err != nil { return nil, fmt.Errorf("invalid hex format for %v: %w", SerialNumberArg, err) } } else { bi := new(big.Int) bi, ok := bi.SetString(serialNumber, 10) if !ok { return nil, fmt.Errorf("invalid %v - must be in hex or integer format", SerialNumberArg) } serialBytes = bi.Bytes() } var prevCert *windows.CertContext for { certHandle, err = findCertificateInStore(st, encodingX509ASN|encodingPKCS7, 0, findIssuerStr, uintptr(unsafe.Pointer(wide(issuerName))), prevCert) if err != nil { return nil, fmt.Errorf("findCertificateInStore failed: %w", err) } if certHandle == nil { return nil, fmt.Errorf("certificate with %v=%v and %v=%v not found", IssuerNameArg, issuerName, SerialNumberArg, serialNumber) } x509Cert, err := certContextToX509(certHandle) if err != nil { return nil, fmt.Errorf("could not unmarshal certificate to DER: %w", err) } if bytes.Equal(x509Cert.SerialNumber.Bytes(), serialBytes) { windows.CertFreeCertificateContext(certHandle) return x509Cert, nil } prevCert = certHandle } default: return nil, fmt.Errorf("%s, %s, or %s and %s is required to find a certificate", HashArg, KeyIDArg, IssuerNameArg, SerialNumberArg) } } func (k *CAPIKMS) StoreCertificate(req *apiv1.StoreCertificateRequest) error { u, err := uri.ParseWithScheme(Scheme, req.Name) if err != nil { return fmt.Errorf("failed to parse URI: %w", err) } var storeLocation string if storeLocation = u.Get(StoreLocationArg); storeLocation == "" { storeLocation = "user" } var certStoreLocation uint32 switch storeLocation { case "user": certStoreLocation = certStoreCurrentUser case "machine": certStoreLocation = certStoreLocalMachine default: return fmt.Errorf("invalid cert store location %v", storeLocation) } var storeName string if storeName = u.Get(StoreNameArg); storeName == "" { storeName = "My" } certContext, err := windows.CertCreateCertificateContext( encodingX509ASN|encodingPKCS7, &req.Certificate.Raw[0], uint32(len(req.Certificate.Raw))) if err != nil { return fmt.Errorf("CertCreateCertificateContext returned: %w", err) } defer windows.CertFreeCertificateContext(certContext) cryptFindCertificateKeyProvInfo(certContext) // TODO: not finding the associated private key is not a dealbreaker, but maybe a warning should be issued st, err := windows.CertOpenStore( certStoreProvSystem, 0, 0, certStoreLocation, uintptr(unsafe.Pointer(wide(storeName)))) if err != nil { return fmt.Errorf("CertOpenStore for the %v store %v returned: %w", storeLocation, storeName, err) } // Add the cert context to the system certificate store if err = windows.CertAddCertificateContextToStore(st, certContext, windows.CERT_STORE_ADD_ALWAYS, nil); err != nil { return fmt.Errorf("CertAddCertificateContextToStore returned: %w", err) } return nil } type CAPISigner struct { algorithmGroup string keyHandle uintptr containerName string PublicKey crypto.PublicKey } func newCAPISigner(kh uintptr, containerName, pin string) (crypto.Signer, error) { pub, err := getPublicKey(kh) if err != nil { return nil, fmt.Errorf("unable to get public key: %w", err) } algGroup, err := nCryptGetPropertyStr(kh, NCRYPT_ALGORITHM_GROUP_PROPERTY) if err != nil { return nil, fmt.Errorf("unable to get NCRYPT_ALGORITHM_GROUP_PROPERTY: %w", err) } signer := CAPISigner{ algorithmGroup: algGroup, keyHandle: kh, containerName: containerName, PublicKey: pub, } return &signer, nil } func (s *CAPISigner) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { if _, isRSAPSS := opts.(*rsa.PSSOptions); isRSAPSS { return nil, fmt.Errorf("RSA-PSS signing is not supported") } switch s.algorithmGroup { case "ECDSA": signatureBytes, err := nCryptSignHash(s.keyHandle, digest, "") if err != nil { return nil, err } if len(signatureBytes) >= len(digest)*2 { sigR := signatureBytes[:len(digest)] sigS := signatureBytes[len(digest):] var b cryptobyte.Builder b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) { b.AddASN1BigInt(new(big.Int).SetBytes(sigR)) b.AddASN1BigInt(new(big.Int).SetBytes(sigS)) }) return b.Bytes() } return nil, fmt.Errorf("signatureBytes not long enough to encode ASN signature") case "RSA": hf := opts.HashFunc() hashAlg, ok := hashAlgorithms[hf] if !ok { return nil, fmt.Errorf("unsupported RSA hash algorithm %v", hf) } signatureBytes, err := nCryptSignHash(s.keyHandle, digest, hashAlg) if err != nil { return nil, fmt.Errorf("NCryptSignHash failed: %w", err) } return signatureBytes, nil default: return nil, fmt.Errorf("unsupported algorithm group %v", s.algorithmGroup) } } func (s *CAPISigner) Public() crypto.PublicKey { return s.PublicKey } golang-step-crypto-0.24.0/kms/capi/capi_no_windows.go000066400000000000000000000005671437037643100225720ustar00rootroot00000000000000//go:build !windows // +build !windows package capi import ( "context" "github.com/pkg/errors" "go.step.sm/crypto/kms/apiv1" ) func init() { apiv1.Register(apiv1.CAPIKMS, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) { return nil, errors.Errorf("unsupported kms type 'capi': CAPI/nCrypt is only available on Windows, and not in WSL") }) } golang-step-crypto-0.24.0/kms/capi/ncrypt_windows.go000066400000000000000000000432251437037643100224770ustar00rootroot00000000000000//go:build windows // +build windows package capi import ( "bytes" "crypto" "crypto/elliptic" "crypto/sha1" "encoding/binary" "errors" "fmt" "golang.org/x/sys/windows" "unsafe" ) const ( // Key storage properties NCRYPT_ALGORITHM_GROUP_PROPERTY = "Algorithm Group" NCRYPT_LENGTH_PROPERTY = "Length" NCRYPT_KEY_TYPE_PROPERTY = "Key Type" NCRYPT_UNIQUE_NAME_PROPERTY = "Unique Name" NCRYPT_ECC_CURVE_NAME_PROPERTY = "ECCCurveName" NCRYPT_IMPL_TYPE_PROPERTY = "Impl Type" NCRYPT_PROV_HANDLE = "Provider Handle" NCRYPT_PIN_PROPERTY = "SmartCardPin" NCRYPT_SECURE_PIN_PROPERTY = "SmartCardSecurePin" NCRYPT_READER_PROPERTY = "SmartCardReader" NCRYPT_ALGORITHM_PROPERTY = "Algorithm Name" NCRYPT_PCP_USAGE_AUTH_PROPERTY = "PCP_USAGEAUTH" // Key Storage Flags NCRYPT_MACHINE_KEY_FLAG = 0x00000001 // Errors NTE_NOT_SUPPORTED = uint32(0x80090029) NTE_INVALID_PARAMETER = uint32(0x80090027) NTE_BAD_FLAGS = uint32(0x80090009) NTE_NO_MORE_ITEMS = uint32(0x8009002A) NTE_BAD_KEYSET = uint32(0x80090016) SCARD_W_CANCELLED_BY_USER = uint32(0x8010006E) // wincrypt.h constants acquireCached = 0x1 // CRYPT_ACQUIRE_CACHE_FLAG acquireSilent = 0x40 // CRYPT_ACQUIRE_SILENT_FLAG encodingX509ASN = 1 // X509_ASN_ENCODING encodingPKCS7 = 65536 // PKCS_7_ASN_ENCODING certStoreProvSystem = 10 // CERT_STORE_PROV_SYSTEM certStoreOpenExisting = 0x00004000 // CERT_STORE_OPEN_EXISTING_FLAG certStoreCurrentUser = uint32(certStoreCurrentUserID << compareShift) // CERT_SYSTEM_STORE_CURRENT_USER certStoreLocalMachine = uint32(certStoreLocalMachineID << compareShift) // CERT_SYSTEM_STORE_LOCAL_MACHINE certStoreCurrentUserID = 1 // CERT_SYSTEM_STORE_CURRENT_USER_ID certStoreLocalMachineID = 2 // CERT_SYSTEM_STORE_LOCAL_MACHINE_ID infoIssuerFlag = 4 // CERT_INFO_ISSUER_FLAG compareName = 2 // CERT_COMPARE_NAME compareNameStrW = 8 // CERT_COMPARE_NAME_STR_A compareShift = 16 // CERT_COMPARE_SHIFT compareSHA1Hash = 1 // CERT_COMPARE_SHA1_HASH compareCertID = 16 // CERT_COMPARE_CERT_ID findIssuerStr = compareNameStrW< 0 { return nil, fmt.Errorf("test error") } return &MockClient{}, nil } type args struct { ctx context.Context opts apiv1.Options } tests := []struct { name string args args want *CloudKMS wantErr bool }{ {"ok", args{context.Background(), apiv1.Options{}}, &CloudKMS{client: &MockClient{}}, false}, {"ok with uri", args{context.Background(), apiv1.Options{URI: "cloudkms:"}}, &CloudKMS{client: &MockClient{}}, false}, {"fail credentials", args{context.Background(), apiv1.Options{CredentialsFile: "testdata/missing"}}, nil, true}, {"fail with uri", args{context.Background(), apiv1.Options{URI: "cloudkms:credentials-file=testdata/missing"}}, nil, true}, {"fail schema", args{context.Background(), apiv1.Options{URI: "pkcs11:"}}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := New(tt.args.ctx, tt.args.opts) if (err != nil) != tt.wantErr { t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("New() = %v, want %v", got, tt.want) } }) } } func TestNew_real(t *testing.T) { type args struct { ctx context.Context opts apiv1.Options } tests := []struct { name string args args want *CloudKMS wantErr bool }{ {"fail credentials", args{context.Background(), apiv1.Options{CredentialsFile: "testdata/missing"}}, nil, true}, {"fail with uri", args{context.Background(), apiv1.Options{URI: "cloudkms:credentials-file=testdata/missing"}}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := New(tt.args.ctx, tt.args.opts) if (err != nil) != tt.wantErr { t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("New() = %v, want %v", got, tt.want) } }) } } func TestNewCloudKMS(t *testing.T) { type args struct { client KeyManagementClient } tests := []struct { name string args args want *CloudKMS }{ {"ok", args{&MockClient{}}, &CloudKMS{&MockClient{}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := NewCloudKMS(tt.args.client); !reflect.DeepEqual(got, tt.want) { t.Errorf("NewCloudKMS() = %v, want %v", got, tt.want) } }) } } func TestCloudKMS_Close(t *testing.T) { type fields struct { client KeyManagementClient } tests := []struct { name string fields fields wantErr bool }{ {"ok", fields{&MockClient{close: func() error { return nil }}}, false}, {"fail", fields{&MockClient{close: func() error { return fmt.Errorf("an error") }}}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &CloudKMS{ client: tt.fields.client, } if err := k.Close(); (err != nil) != tt.wantErr { t.Errorf("CloudKMS.Close() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestCloudKMS_CreateSigner(t *testing.T) { keyName := "projects/p/locations/l/keyRings/k/cryptoKeys/c/cryptoKeyVersions/1" pemBytes, err := os.ReadFile("testdata/pub.pem") if err != nil { t.Fatal(err) } pk, err := pemutil.ParseKey(pemBytes) if err != nil { t.Fatal(err) } type fields struct { client KeyManagementClient } type args struct { req *apiv1.CreateSignerRequest } tests := []struct { name string fields fields args args want crypto.Signer wantErr bool }{ {"ok", fields{&MockClient{ getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }}, args{&apiv1.CreateSignerRequest{SigningKey: keyName}}, &Signer{client: &MockClient{}, signingKey: keyName, publicKey: pk}, false}, {"fail", fields{&MockClient{ getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { return nil, fmt.Errorf("test error") }, }}, args{&apiv1.CreateSignerRequest{SigningKey: ""}}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &CloudKMS{ client: tt.fields.client, } got, err := k.CreateSigner(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("CloudKMS.CreateSigner() error = %v, wantErr %v", err, tt.wantErr) return } if signer, ok := got.(*Signer); ok { signer.client = &MockClient{} } if !reflect.DeepEqual(got, tt.want) { t.Errorf("CloudKMS.CreateSigner() = %v, want %v", got, tt.want) } }) } } func TestCloudKMS_CreateKey(t *testing.T) { keyName := "projects/p/locations/l/keyRings/k/cryptoKeys/c" testError := fmt.Errorf("an error") alreadyExists := status.Error(codes.AlreadyExists, "already exists") pemBytes, err := os.ReadFile("testdata/pub.pem") if err != nil { t.Fatal(err) } pk, err := pemutil.ParseKey(pemBytes) if err != nil { t.Fatal(err) } var retries int type fields struct { client KeyManagementClient } type args struct { req *apiv1.CreateKeyRequest } tests := []struct { name string fields fields args args want *apiv1.CreateKeyResponse wantErr bool }{ {"ok", fields{ &MockClient{ getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) { return &kmspb.KeyRing{}, nil }, createCryptoKey: func(_ context.Context, _ *kmspb.CreateCryptoKeyRequest, _ ...gax.CallOption) (*kmspb.CryptoKey, error) { return &kmspb.CryptoKey{Name: keyName}, nil }, getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }}, args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.HSM, SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, &apiv1.CreateKeyResponse{Name: keyName + "/cryptoKeyVersions/1", PublicKey: pk, CreateSignerRequest: apiv1.CreateSignerRequest{SigningKey: keyName + "/cryptoKeyVersions/1"}}, false}, {"ok new key ring", fields{ &MockClient{ getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) { return nil, testError }, createKeyRing: func(_ context.Context, _ *kmspb.CreateKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) { return nil, alreadyExists }, createCryptoKey: func(_ context.Context, _ *kmspb.CreateCryptoKeyRequest, _ ...gax.CallOption) (*kmspb.CryptoKey, error) { return &kmspb.CryptoKey{Name: keyName}, nil }, getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }}, args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.Software, SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 3072}}, &apiv1.CreateKeyResponse{Name: keyName + "/cryptoKeyVersions/1", PublicKey: pk, CreateSignerRequest: apiv1.CreateSignerRequest{SigningKey: keyName + "/cryptoKeyVersions/1"}}, false}, {"ok new key version", fields{ &MockClient{ getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) { return &kmspb.KeyRing{}, nil }, createCryptoKey: func(_ context.Context, _ *kmspb.CreateCryptoKeyRequest, _ ...gax.CallOption) (*kmspb.CryptoKey, error) { return nil, alreadyExists }, createCryptoKeyVersion: func(_ context.Context, _ *kmspb.CreateCryptoKeyVersionRequest, _ ...gax.CallOption) (*kmspb.CryptoKeyVersion, error) { return &kmspb.CryptoKeyVersion{Name: keyName + "/cryptoKeyVersions/2"}, nil }, getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }}, args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.HSM, SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, &apiv1.CreateKeyResponse{Name: keyName + "/cryptoKeyVersions/2", PublicKey: pk, CreateSignerRequest: apiv1.CreateSignerRequest{SigningKey: keyName + "/cryptoKeyVersions/2"}}, false}, {"ok with retries", fields{ &MockClient{ getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) { return &kmspb.KeyRing{}, nil }, createCryptoKey: func(_ context.Context, _ *kmspb.CreateCryptoKeyRequest, _ ...gax.CallOption) (*kmspb.CryptoKey, error) { return &kmspb.CryptoKey{Name: keyName}, nil }, getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { if retries != 2 { retries++ return nil, status.Error(codes.FailedPrecondition, "key is not enabled, current state is: PENDING_GENERATION") } return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }}, args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.HSM, SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, &apiv1.CreateKeyResponse{Name: keyName + "/cryptoKeyVersions/1", PublicKey: pk, CreateSignerRequest: apiv1.CreateSignerRequest{SigningKey: keyName + "/cryptoKeyVersions/1"}}, false}, {"fail name", fields{&MockClient{}}, args{&apiv1.CreateKeyRequest{}}, nil, true}, {"fail protection level", fields{&MockClient{}}, args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.ProtectionLevel(100)}}, nil, true}, {"fail signature algorithm", fields{&MockClient{}}, args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.Software, SignatureAlgorithm: apiv1.SignatureAlgorithm(100)}}, nil, true}, {"fail number of bits", fields{&MockClient{}}, args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.Software, SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 1024}}, nil, true}, {"fail create key ring", fields{ &MockClient{ getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) { return nil, testError }, createKeyRing: func(_ context.Context, _ *kmspb.CreateKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) { return nil, testError }, }}, args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.HSM, SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, nil, true}, {"fail create key", fields{ &MockClient{ getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) { return &kmspb.KeyRing{}, nil }, createCryptoKey: func(_ context.Context, _ *kmspb.CreateCryptoKeyRequest, _ ...gax.CallOption) (*kmspb.CryptoKey, error) { return nil, testError }, }}, args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.HSM, SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, nil, true}, {"fail create key version", fields{ &MockClient{ getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) { return &kmspb.KeyRing{}, nil }, createCryptoKey: func(_ context.Context, _ *kmspb.CreateCryptoKeyRequest, _ ...gax.CallOption) (*kmspb.CryptoKey, error) { return nil, alreadyExists }, createCryptoKeyVersion: func(_ context.Context, _ *kmspb.CreateCryptoKeyVersionRequest, _ ...gax.CallOption) (*kmspb.CryptoKeyVersion, error) { return nil, testError }, }}, args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.HSM, SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, nil, true}, {"fail get public key", fields{ &MockClient{ getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) { return &kmspb.KeyRing{}, nil }, createCryptoKey: func(_ context.Context, _ *kmspb.CreateCryptoKeyRequest, _ ...gax.CallOption) (*kmspb.CryptoKey, error) { return &kmspb.CryptoKey{Name: keyName}, nil }, getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { return nil, testError }, }}, args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.HSM, SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &CloudKMS{ client: tt.fields.client, } got, err := k.CreateKey(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("CloudKMS.CreateKey() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("CloudKMS.CreateKey() = %v, want %v", got, tt.want) } }) } } func TestCloudKMS_GetPublicKey(t *testing.T) { keyName := "projects/p/locations/l/keyRings/k/cryptoKeys/c/cryptoKeyVersions/1" testError := fmt.Errorf("an error") pemBytes, err := os.ReadFile("testdata/pub.pem") if err != nil { t.Fatal(err) } pk, err := pemutil.ParseKey(pemBytes) if err != nil { t.Fatal(err) } var retries int type fields struct { client KeyManagementClient } type args struct { req *apiv1.GetPublicKeyRequest } tests := []struct { name string fields fields args args want crypto.PublicKey wantErr bool }{ {"ok", fields{ &MockClient{ getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }}, args{&apiv1.GetPublicKeyRequest{Name: keyName}}, pk, false}, {"ok with retries", fields{ &MockClient{ getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { if retries != 2 { retries++ return nil, status.Error(codes.FailedPrecondition, "key is not enabled, current state is: PENDING_GENERATION") } return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }}, args{&apiv1.GetPublicKeyRequest{Name: keyName}}, pk, false}, {"fail name", fields{&MockClient{}}, args{&apiv1.GetPublicKeyRequest{}}, nil, true}, {"fail get public key", fields{ &MockClient{ getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { return nil, testError }, }}, args{&apiv1.GetPublicKeyRequest{Name: keyName}}, nil, true}, {"fail parse pem", fields{ &MockClient{ getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { return &kmspb.PublicKey{Pem: string("bad pem")}, nil }, }}, args{&apiv1.GetPublicKeyRequest{Name: keyName}}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &CloudKMS{ client: tt.fields.client, } got, err := k.GetPublicKey(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("CloudKMS.GetPublicKey() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("CloudKMS.GetPublicKey() = %v, want %v", got, tt.want) } }) } } golang-step-crypto-0.24.0/kms/cloudkms/mock_test.go000066400000000000000000000041441437037643100223000ustar00rootroot00000000000000package cloudkms import ( "context" "cloud.google.com/go/kms/apiv1/kmspb" gax "github.com/googleapis/gax-go/v2" ) type MockClient struct { close func() error getPublicKey func(context.Context, *kmspb.GetPublicKeyRequest, ...gax.CallOption) (*kmspb.PublicKey, error) asymmetricSign func(context.Context, *kmspb.AsymmetricSignRequest, ...gax.CallOption) (*kmspb.AsymmetricSignResponse, error) createCryptoKey func(context.Context, *kmspb.CreateCryptoKeyRequest, ...gax.CallOption) (*kmspb.CryptoKey, error) getKeyRing func(context.Context, *kmspb.GetKeyRingRequest, ...gax.CallOption) (*kmspb.KeyRing, error) createKeyRing func(context.Context, *kmspb.CreateKeyRingRequest, ...gax.CallOption) (*kmspb.KeyRing, error) createCryptoKeyVersion func(context.Context, *kmspb.CreateCryptoKeyVersionRequest, ...gax.CallOption) (*kmspb.CryptoKeyVersion, error) } func (m *MockClient) Close() error { return m.close() } func (m *MockClient) GetPublicKey(ctx context.Context, req *kmspb.GetPublicKeyRequest, opts ...gax.CallOption) (*kmspb.PublicKey, error) { return m.getPublicKey(ctx, req, opts...) } func (m *MockClient) AsymmetricSign(ctx context.Context, req *kmspb.AsymmetricSignRequest, opts ...gax.CallOption) (*kmspb.AsymmetricSignResponse, error) { return m.asymmetricSign(ctx, req, opts...) } func (m *MockClient) CreateCryptoKey(ctx context.Context, req *kmspb.CreateCryptoKeyRequest, opts ...gax.CallOption) (*kmspb.CryptoKey, error) { return m.createCryptoKey(ctx, req, opts...) } func (m *MockClient) GetKeyRing(ctx context.Context, req *kmspb.GetKeyRingRequest, opts ...gax.CallOption) (*kmspb.KeyRing, error) { return m.getKeyRing(ctx, req, opts...) } func (m *MockClient) CreateKeyRing(ctx context.Context, req *kmspb.CreateKeyRingRequest, opts ...gax.CallOption) (*kmspb.KeyRing, error) { return m.createKeyRing(ctx, req, opts...) } func (m *MockClient) CreateCryptoKeyVersion(ctx context.Context, req *kmspb.CreateCryptoKeyVersionRequest, opts ...gax.CallOption) (*kmspb.CryptoKeyVersion, error) { return m.createCryptoKeyVersion(ctx, req, opts...) } golang-step-crypto-0.24.0/kms/cloudkms/no_cloudkms.go000066400000000000000000000007101437037643100226200ustar00rootroot00000000000000//go:build nocloudkms // +build nocloudkms package cloudkms import ( "context" "os" "path/filepath" "github.com/pkg/errors" "go.step.sm/crypto/kms/apiv1" ) func init() { apiv1.Register(apiv1.CloudKMS, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) { name := filepath.Base(os.Args[0]) return nil, errors.Errorf("unsupported kms type 'cloudkms': %s is compiled without Google Cloud Key Management support", name) }) } golang-step-crypto-0.24.0/kms/cloudkms/signer.go000066400000000000000000000045461437037643100216050ustar00rootroot00000000000000//go:build !nocloudkms // +build !nocloudkms package cloudkms import ( "crypto" "crypto/x509" "io" "cloud.google.com/go/kms/apiv1/kmspb" "github.com/pkg/errors" "go.step.sm/crypto/pemutil" ) // Signer implements a crypto.Signer using Google's Cloud KMS. type Signer struct { client KeyManagementClient signingKey string algorithm x509.SignatureAlgorithm publicKey crypto.PublicKey } // NewSigner creates a new crypto.Signer the given CloudKMS signing key. func NewSigner(c KeyManagementClient, signingKey string) (*Signer, error) { // Make sure that the key exists. signer := &Signer{ client: c, signingKey: signingKey, } if err := signer.preloadKey(signingKey); err != nil { return nil, err } return signer, nil } func (s *Signer) preloadKey(signingKey string) error { ctx, cancel := defaultContext() defer cancel() response, err := s.client.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{ Name: signingKey, }) if err != nil { return errors.Wrap(err, "cloudKMS GetPublicKey failed") } s.algorithm = cryptoKeyVersionMapping[response.Algorithm] s.publicKey, err = pemutil.ParseKey([]byte(response.Pem)) return err } // Public returns the public key of this signer or an error. func (s *Signer) Public() crypto.PublicKey { return s.publicKey } // Sign signs digest with the private key stored in Google's Cloud KMS. func (s *Signer) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { req := &kmspb.AsymmetricSignRequest{ Name: s.signingKey, Digest: &kmspb.Digest{}, } switch h := opts.HashFunc(); h { case crypto.SHA256: req.Digest.Digest = &kmspb.Digest_Sha256{ Sha256: digest, } case crypto.SHA384: req.Digest.Digest = &kmspb.Digest_Sha384{ Sha384: digest, } case crypto.SHA512: req.Digest.Digest = &kmspb.Digest_Sha512{ Sha512: digest, } default: return nil, errors.Errorf("unsupported hash function %v", h) } ctx, cancel := defaultContext() defer cancel() response, err := s.client.AsymmetricSign(ctx, req) if err != nil { return nil, errors.Wrap(err, "cloudKMS AsymmetricSign failed") } return response.Signature, nil } // SignatureAlgorithm returns the algorithm that must be specified in a // certificate to sign. This is specially important to distinguish RSA and // RSAPSS schemas. func (s *Signer) SignatureAlgorithm() x509.SignatureAlgorithm { return s.algorithm } golang-step-crypto-0.24.0/kms/cloudkms/signer_test.go000066400000000000000000000162761437037643100226470ustar00rootroot00000000000000package cloudkms import ( "context" "crypto" "crypto/rand" "crypto/x509" "fmt" "io" "os" "reflect" "testing" "cloud.google.com/go/kms/apiv1/kmspb" gax "github.com/googleapis/gax-go/v2" "go.step.sm/crypto/pemutil" ) func Test_newSigner(t *testing.T) { pemBytes, err := os.ReadFile("testdata/pub.pem") if err != nil { t.Fatal(err) } pk, err := pemutil.ParseKey(pemBytes) if err != nil { t.Fatal(err) } type args struct { c KeyManagementClient signingKey string } tests := []struct { name string args args want *Signer wantErr bool }{ {"ok", args{&MockClient{ getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }, "signingKey"}, &Signer{client: &MockClient{}, signingKey: "signingKey", publicKey: pk}, false}, {"fail get public key", args{&MockClient{ getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { return nil, fmt.Errorf("an error") }, }, "signingKey"}, nil, true}, {"fail parse pem", args{&MockClient{ getPublicKey: func(_ context.Context, _ *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { return &kmspb.PublicKey{Pem: string("bad pem")}, nil }, }, "signingKey"}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := NewSigner(tt.args.c, tt.args.signingKey) if (err != nil) != tt.wantErr { t.Errorf("NewSigner() error = %v, wantErr %v", err, tt.wantErr) return } if got != nil { got.client = &MockClient{} } if !reflect.DeepEqual(got, tt.want) { t.Errorf("NewSigner() = %v, want %v", got, tt.want) } }) } } func Test_signer_Public(t *testing.T) { pemBytes, err := os.ReadFile("testdata/pub.pem") if err != nil { t.Fatal(err) } pk, err := pemutil.ParseKey(pemBytes) if err != nil { t.Fatal(err) } type fields struct { client KeyManagementClient signingKey string publicKey crypto.PublicKey } tests := []struct { name string fields fields want crypto.PublicKey }{ {"ok", fields{&MockClient{}, "signingKey", pk}, pk}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := &Signer{ client: tt.fields.client, signingKey: tt.fields.signingKey, publicKey: tt.fields.publicKey, } if got := s.Public(); !reflect.DeepEqual(got, tt.want) { t.Errorf("signer.Public() = %v, want %v", got, tt.want) } }) } } func Test_signer_Sign(t *testing.T) { keyName := "projects/p/locations/l/keyRings/k/cryptoKeys/c/cryptoKeyVersions/1" okClient := &MockClient{ asymmetricSign: func(_ context.Context, _ *kmspb.AsymmetricSignRequest, _ ...gax.CallOption) (*kmspb.AsymmetricSignResponse, error) { return &kmspb.AsymmetricSignResponse{Signature: []byte("ok signature")}, nil }, } failClient := &MockClient{ asymmetricSign: func(_ context.Context, _ *kmspb.AsymmetricSignRequest, _ ...gax.CallOption) (*kmspb.AsymmetricSignResponse, error) { return nil, fmt.Errorf("an error") }, } type fields struct { client KeyManagementClient signingKey string } type args struct { rand io.Reader digest []byte opts crypto.SignerOpts } tests := []struct { name string fields fields args args want []byte wantErr bool }{ {"ok sha256", fields{okClient, keyName}, args{rand.Reader, []byte("digest"), crypto.SHA256}, []byte("ok signature"), false}, {"ok sha384", fields{okClient, keyName}, args{rand.Reader, []byte("digest"), crypto.SHA384}, []byte("ok signature"), false}, {"ok sha512", fields{okClient, keyName}, args{rand.Reader, []byte("digest"), crypto.SHA512}, []byte("ok signature"), false}, {"fail MD5", fields{okClient, keyName}, args{rand.Reader, []byte("digest"), crypto.MD5}, nil, true}, {"fail asymmetric sign", fields{failClient, keyName}, args{rand.Reader, []byte("digest"), crypto.SHA256}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := &Signer{ client: tt.fields.client, signingKey: tt.fields.signingKey, } got, err := s.Sign(tt.args.rand, tt.args.digest, tt.args.opts) if (err != nil) != tt.wantErr { t.Errorf("signer.Sign() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("signer.Sign() = %v, want %v", got, tt.want) } }) } } func TestSigner_SignatureAlgorithm(t *testing.T) { pemBytes, err := os.ReadFile("testdata/pub.pem") if err != nil { t.Fatal(err) } client := &MockClient{ getPublicKey: func(_ context.Context, req *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { var algorithm kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm switch req.Name { case "ECDSA-SHA256": algorithm = kmspb.CryptoKeyVersion_EC_SIGN_P256_SHA256 case "ECDSA-SHA384": algorithm = kmspb.CryptoKeyVersion_EC_SIGN_P384_SHA384 case "SHA256-RSA-2048": algorithm = kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_2048_SHA256 case "SHA256-RSA-3072": algorithm = kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_3072_SHA256 case "SHA256-RSA-4096": algorithm = kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256 case "SHA512-RSA-4096": algorithm = kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA512 case "SHA256-RSAPSS-2048": algorithm = kmspb.CryptoKeyVersion_RSA_SIGN_PSS_2048_SHA256 case "SHA256-RSAPSS-3072": algorithm = kmspb.CryptoKeyVersion_RSA_SIGN_PSS_3072_SHA256 case "SHA256-RSAPSS-4096": algorithm = kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA256 case "SHA512-RSAPSS-4096": algorithm = kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA512 } return &kmspb.PublicKey{ Pem: string(pemBytes), Algorithm: algorithm, }, nil }, } if err != nil { t.Fatal(err) } type fields struct { client KeyManagementClient signingKey string } tests := []struct { name string fields fields want x509.SignatureAlgorithm }{ {"ECDSA-SHA256", fields{client, "ECDSA-SHA256"}, x509.ECDSAWithSHA256}, {"ECDSA-SHA384", fields{client, "ECDSA-SHA384"}, x509.ECDSAWithSHA384}, {"SHA256-RSA-2048", fields{client, "SHA256-RSA-2048"}, x509.SHA256WithRSA}, {"SHA256-RSA-3072", fields{client, "SHA256-RSA-3072"}, x509.SHA256WithRSA}, {"SHA256-RSA-4096", fields{client, "SHA256-RSA-4096"}, x509.SHA256WithRSA}, {"SHA512-RSA-4096", fields{client, "SHA512-RSA-4096"}, x509.SHA512WithRSA}, {"SHA256-RSAPSS-2048", fields{client, "SHA256-RSAPSS-2048"}, x509.SHA256WithRSAPSS}, {"SHA256-RSAPSS-3072", fields{client, "SHA256-RSAPSS-3072"}, x509.SHA256WithRSAPSS}, {"SHA256-RSAPSS-4096", fields{client, "SHA256-RSAPSS-4096"}, x509.SHA256WithRSAPSS}, {"SHA512-RSAPSS-4096", fields{client, "SHA512-RSAPSS-4096"}, x509.SHA512WithRSAPSS}, {"unknown", fields{client, "UNKNOWN"}, x509.UnknownSignatureAlgorithm}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { signer, err := NewSigner(tt.fields.client, tt.fields.signingKey) if err != nil { t.Errorf("NewSigner() error = %v", err) } if got := signer.SignatureAlgorithm(); !reflect.DeepEqual(got, tt.want) { t.Errorf("Signer.SignatureAlgorithm() = %v, want %v", got, tt.want) } }) } } golang-step-crypto-0.24.0/kms/cloudkms/testdata/000077500000000000000000000000001437037643100215675ustar00rootroot00000000000000golang-step-crypto-0.24.0/kms/cloudkms/testdata/pub.pem000066400000000000000000000002621437037643100230600ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5VPD/W5RXn0lrs2MdoNteTSZ+sh1 veT13hakPZF9YzaNVZgujqK3d1nt+4jPECU+ED/WQ1GgFZiVGUo3flvB/w== -----END PUBLIC KEY----- golang-step-crypto-0.24.0/kms/kms.go000066400000000000000000000024541437037643100172630ustar00rootroot00000000000000package kms import ( "context" "github.com/pkg/errors" "go.step.sm/crypto/kms/apiv1" // Enable default implementation "go.step.sm/crypto/kms/softkms" ) // KeyManager is the interface implemented by all the KMS. type KeyManager = apiv1.KeyManager // CertificateManager is the interface implemented by the KMS that can load and // store x509.Certificates. type CertificateManager = apiv1.CertificateManager // Attester is the interface implemented by the KMS that can respond with an // attestation certificate or key. // // # Experimental // // Notice: This API is EXPERIMENTAL and may be changed or removed in a later // release. type Attester = apiv1.Attester // Options are the KMS options. They represent the kms object in the ca.json. type Options = apiv1.Options // Type represents the KMS type used. type Type = apiv1.Type // Default is the implementation of the default KMS. var Default = &softkms.SoftKMS{} // New initializes a new KMS from the given type. func New(ctx context.Context, opts apiv1.Options) (KeyManager, error) { if err := opts.Validate(); err != nil { return nil, err } typ, err := opts.GetType() if err != nil { return nil, err } fn, ok := apiv1.LoadKeyManagerNewFunc(typ) if !ok { return nil, errors.Errorf("unsupported kms type '%s'", typ) } return fn(ctx, opts) } golang-step-crypto-0.24.0/kms/kms_test.go000066400000000000000000000031501437037643100203140ustar00rootroot00000000000000package kms import ( "context" "os" "path/filepath" "reflect" "testing" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/kms/awskms" "go.step.sm/crypto/kms/cloudkms" "go.step.sm/crypto/kms/softkms" ) func TestNew(t *testing.T) { ctx := context.Background() failCloudKMS := true if home, err := os.UserHomeDir(); err == nil { file := filepath.Join(home, ".config", "gcloud", "application_default_credentials.json") if _, err := os.Stat(file); err == nil { failCloudKMS = false } } type args struct { ctx context.Context opts apiv1.Options } tests := []struct { name string skipOnCI bool args args want KeyManager wantErr bool }{ {"default", false, args{ctx, apiv1.Options{}}, &softkms.SoftKMS{}, false}, {"softkms", false, args{ctx, apiv1.Options{Type: "softkms"}}, &softkms.SoftKMS{}, false}, {"uri", false, args{ctx, apiv1.Options{URI: "softkms:foo=bar"}}, &softkms.SoftKMS{}, false}, {"awskms", false, args{ctx, apiv1.Options{Type: "awskms"}}, &awskms.KMS{}, false}, {"cloudkms", true, args{ctx, apiv1.Options{Type: "cloudkms"}}, &cloudkms.CloudKMS{}, failCloudKMS}, {"fail validation", false, args{ctx, apiv1.Options{Type: "foobar"}}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.skipOnCI && os.Getenv("CI") == "true" { t.SkipNow() } got, err := New(tt.args.ctx, tt.args.opts) if (err != nil) != tt.wantErr { t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) return } if reflect.TypeOf(got) != reflect.TypeOf(tt.want) { t.Errorf("New() = %T, want %T", got, tt.want) } }) } } golang-step-crypto-0.24.0/kms/kmsfs.go000066400000000000000000000051251437037643100176120ustar00rootroot00000000000000package kms import ( "context" "fmt" "io/fs" "go.step.sm/crypto/kms/apiv1" ) // FS adds a close method to the fs.FS interface. This new method allows to // properly close the underlying KMS. type FS interface { fs.FS Close() error } type kmsfs struct { apiv1.KeyManager } func newFS(ctx context.Context, kmsuri string) (*kmsfs, error) { if kmsuri == "" { return &kmsfs{}, nil } km, err := loadKMS(ctx, kmsuri) if err != nil { return nil, err } return &kmsfs{KeyManager: km}, nil } func (f *kmsfs) Close() error { if f != nil && f.KeyManager != nil { return f.KeyManager.Close() } return nil } func (f *kmsfs) getKMS(kmsuri string) (apiv1.KeyManager, error) { if f.KeyManager == nil { return loadKMS(context.TODO(), kmsuri) } return f.KeyManager, nil } func loadKMS(ctx context.Context, kmsuri string) (apiv1.KeyManager, error) { return New(ctx, apiv1.Options{ URI: kmsuri, }) } func openError(name string, err error) *fs.PathError { return &fs.PathError{ Path: name, Op: "open", Err: err, } } // certFS implements an io/fs to load certificates from a KMS. type certFS struct { *kmsfs } // CertFS creates a new io/fs with the given KMS URI. func CertFS(ctx context.Context, kmsuri string) (FS, error) { km, err := newFS(ctx, kmsuri) if err != nil { return nil, err } _, ok := km.KeyManager.(apiv1.CertificateManager) if !ok { return nil, fmt.Errorf("%s does not implement a CertificateManager", kmsuri) } return &certFS{kmsfs: km}, nil } // Open returns a file representing a certificate in an KMS. func (f *certFS) Open(name string) (fs.File, error) { km, err := f.getKMS(name) if err != nil { return nil, openError(name, err) } cert, err := km.(apiv1.CertificateManager).LoadCertificate(&apiv1.LoadCertificateRequest{ Name: name, }) if err != nil { return nil, openError(name, err) } return &object{ Path: name, Object: cert, }, nil } // keyFS implements an io/fs to load public keys from a KMS. type keyFS struct { *kmsfs } // KeyFS creates a new KeyFS with the given KMS URI. func KeyFS(ctx context.Context, kmsuri string) (FS, error) { km, err := newFS(ctx, kmsuri) if err != nil { return nil, err } return &keyFS{kmsfs: km}, nil } // Open returns a file representing a public key in a KMS. func (f *keyFS) Open(name string) (fs.File, error) { km, err := f.getKMS(name) if err != nil { return nil, openError(name, err) } // Attempt with a public key pub, err := km.GetPublicKey(&apiv1.GetPublicKeyRequest{ Name: name, }) if err != nil { return nil, openError(name, err) } return &object{ Path: name, Object: pub, }, nil } golang-step-crypto-0.24.0/kms/kmsfs_test.go000066400000000000000000000166401437037643100206550ustar00rootroot00000000000000package kms import ( "context" "crypto" "crypto/x509" "crypto/x509/pkix" "errors" "io/fs" "os" "reflect" "testing" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/kms/softkms" ) type fakeCM struct { softkms.SoftKMS } func (f *fakeCM) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) { if req.Name == "fail" { return nil, errors.New("an error") } return []byte(req.Name), nil } func (f *fakeCM) LoadCertificate(req *apiv1.LoadCertificateRequest) (*x509.Certificate, error) { if req.Name == "fail" { return nil, errors.New("an error") } return &x509.Certificate{Subject: pkix.Name{CommonName: req.Name}}, nil } func (f *fakeCM) StoreCertificate(req *apiv1.StoreCertificateRequest) error { if req.Name == "fail" { return errors.New("an error") } return nil } func TestMain(m *testing.M) { apiv1.Register(apiv1.Type("fake"), func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) { return &fakeCM{}, nil }) os.Exit(m.Run()) } func Test_new(t *testing.T) { ctx := context.TODO() type args struct { ctx context.Context kmsuri string } tests := []struct { name string args args want *kmsfs wantErr bool }{ {"ok empty", args{ctx, ""}, &kmsfs{}, false}, {"ok softkms", args{ctx, "softkms:"}, &kmsfs{ KeyManager: &softkms.SoftKMS{}, }, false}, {"fail", args{ctx, "fail:"}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := newFS(tt.args.ctx, tt.args.kmsuri) if (err != nil) != tt.wantErr { t.Errorf("new() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("new() = %v, want %v", got, tt.want) } if err := got.Close(); err != nil { t.Errorf("Close() error = %v, wantErr false", err) } }) } } func Test_kmsfs_getKMS(t *testing.T) { type fields struct { KeyManager apiv1.KeyManager } type args struct { kmsuri string } tests := []struct { name string fields fields args args want apiv1.KeyManager wantErr bool }{ {"ok empty", fields{nil}, args{""}, &softkms.SoftKMS{}, false}, {"ok softkms", fields{&softkms.SoftKMS{}}, args{""}, &softkms.SoftKMS{}, false}, {"ok softkms with uri", fields{nil}, args{"softkms:"}, &softkms.SoftKMS{}, false}, {"fail", fields{nil}, args{"fail:"}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { f := &kmsfs{ KeyManager: tt.fields.KeyManager, } got, err := f.getKMS(tt.args.kmsuri) if (err != nil) != tt.wantErr { t.Errorf("kmsfs.getKMS() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("kmsfs.getKMS() = %v, want %v", got, tt.want) } }) } } func Test_loadKMS(t *testing.T) { ctx := context.TODO() type args struct { ctx context.Context kmsuri string } tests := []struct { name string args args want apiv1.KeyManager wantErr bool }{ {"ok", args{ctx, "softkms:"}, &softkms.SoftKMS{}, false}, {"ok empty", args{ctx, ""}, &softkms.SoftKMS{}, false}, {"fail", args{ctx, "fail:"}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := loadKMS(tt.args.ctx, tt.args.kmsuri) if (err != nil) != tt.wantErr { t.Errorf("loadKMS() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("loadKMS() = %v, want %v", got, tt.want) } }) } } func Test_openError(t *testing.T) { type args struct { name string err error } tests := []struct { name string args args want *fs.PathError }{ {"ok", args{"name", errors.New("an error")}, &fs.PathError{ Path: "name", Op: "open", Err: errors.New("an error"), }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := openError(tt.args.name, tt.args.err); !reflect.DeepEqual(got, tt.want) { //nolint:govet // variable names match crypto formulae docs t.Errorf("openError() = %v, want %v", got, tt.want) } }) } } func TestCertFS(t *testing.T) { ctx := context.TODO() type args struct { ctx context.Context kmsuri string } tests := []struct { name string args args want fs.FS wantErr bool }{ {"ok", args{ctx, "fake:"}, &certFS{kmsfs: &kmsfs{KeyManager: &fakeCM{}}}, false}, {"fail", args{ctx, "fail:"}, nil, true}, {"fail not implemented", args{ctx, "softkms:"}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := CertFS(tt.args.ctx, tt.args.kmsuri) if (err != nil) != tt.wantErr { t.Errorf("CertFS() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("CertFS() = %v, want %v", got, tt.want) } }) } } func Test_certFS_Open(t *testing.T) { fake := &kmsfs{KeyManager: &fakeCM{}} type fields struct { kmsfs *kmsfs } type args struct { name string } tests := []struct { name string fields fields args args want fs.File wantErr bool }{ {"ok", fields{fake}, args{"foo"}, &object{ Path: "foo", Object: &x509.Certificate{Subject: pkix.Name{CommonName: "foo"}}, }, false}, {"ok load", fields{&kmsfs{}}, args{"fake:foo"}, &object{ Path: "fake:foo", Object: &x509.Certificate{Subject: pkix.Name{CommonName: "fake:foo"}}, }, false}, {"fail fake", fields{fake}, args{"fail"}, nil, true}, {"fail unregistered", fields{&kmsfs{}}, args{"fail:"}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { f := &certFS{ kmsfs: tt.fields.kmsfs, } got, err := f.Open(tt.args.name) if (err != nil) != tt.wantErr { t.Errorf("certFS.Open() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("certFS.Open() = %v, want %v", got, tt.want) } }) } } func TestKeyFS(t *testing.T) { ctx := context.TODO() type args struct { ctx context.Context kmsuri string } tests := []struct { name string args args want fs.FS wantErr bool }{ {"ok", args{ctx, "fake:"}, &keyFS{kmsfs: &kmsfs{KeyManager: &fakeCM{}}}, false}, {"fail", args{ctx, "fail:"}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := KeyFS(tt.args.ctx, tt.args.kmsuri) if (err != nil) != tt.wantErr { t.Errorf("KeyFS() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("KeyFS() = %v, want %v", got, tt.want) } }) } } func Test_keyFS_Open(t *testing.T) { fake := &kmsfs{KeyManager: &fakeCM{}} type fields struct { kmsfs *kmsfs } type args struct { name string } tests := []struct { name string fields fields args args want fs.File wantErr bool }{ {"ok", fields{fake}, args{"foo"}, &object{ Path: "foo", Object: []byte("foo"), }, false}, {"ok load", fields{&kmsfs{}}, args{"fake:foo"}, &object{ Path: "fake:foo", Object: []byte("fake:foo"), }, false}, {"fail fake", fields{fake}, args{"fail"}, nil, true}, {"fail unregistered", fields{&kmsfs{}}, args{"fail:"}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { f := &keyFS{ kmsfs: tt.fields.kmsfs, } got, err := f.Open(tt.args.name) if (err != nil) != tt.wantErr { t.Errorf("keyFS.Open() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("keyFS.Open() = %v, want %v", got, tt.want) } }) } } golang-step-crypto-0.24.0/kms/object.go000066400000000000000000000024631437037643100177370ustar00rootroot00000000000000package kms import ( "bytes" "encoding/pem" "io" "io/fs" "sync" "time" "go.step.sm/crypto/pemutil" ) // object implements the fs.File and fs.FileMode interfaces. type object struct { Path string Object interface{} once sync.Once err error pemData *bytes.Buffer } // FileMode implementation func (o *object) Name() string { return o.Path } func (o *object) Size() int64 { return int64(o.pemData.Len()) } func (o *object) Mode() fs.FileMode { return 0400 } func (o *object) ModTime() time.Time { return time.Time{} } func (o *object) IsDir() bool { return false } func (o *object) Sys() interface{} { return o.Object } func (o *object) load() error { o.once.Do(func() { b, err := pemutil.Serialize(o.Object) if err != nil { o.err = &fs.PathError{ Op: "open", Path: o.Path, Err: err, } return } o.pemData = bytes.NewBuffer(pem.EncodeToMemory(b)) }) return o.err } func (o *object) Stat() (fs.FileInfo, error) { if err := o.load(); err != nil { return nil, err } return o, nil } func (o *object) Read(b []byte) (int, error) { if err := o.load(); err != nil { return 0, err } return o.pemData.Read(b) } func (o *object) Close() error { o.Object = nil o.pemData = nil if o.err == nil { o.err = io.EOF return nil } return o.err } golang-step-crypto-0.24.0/kms/object_test.go000066400000000000000000000125201437037643100207710ustar00rootroot00000000000000package kms import ( "bytes" "crypto" "crypto/ed25519" "crypto/rand" "encoding/pem" "io" "io/fs" "reflect" "sync" "testing" "time" "go.step.sm/crypto/pemutil" ) func generateKey(t *testing.T) (crypto.PublicKey, *bytes.Buffer) { t.Helper() pub, _, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } block, err := pemutil.Serialize(pub) if err != nil { t.Fatal(err) } return pub, bytes.NewBuffer(pem.EncodeToMemory(block)) } func Test_object_FileMode(t *testing.T) { pub, pemData := generateKey(t) type fields struct { Path string Object interface{} pemData *bytes.Buffer } tests := []struct { name string fields fields wantName string wantSize int64 wantMode fs.FileMode wantModTime time.Time wantIsDir bool wantSys interface{} }{ {"ok", fields{"path", pub, pemData}, "path", int64(pemData.Len()), 0400, time.Time{}, false, pub}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { o := &object{ Path: tt.fields.Path, Object: tt.fields.Object, pemData: tt.fields.pemData, } if got := o.Name(); got != tt.wantName { t.Errorf("object.Name() = %v, want %v", got, tt.wantName) } if got := o.Size(); got != tt.wantSize { t.Errorf("object.Size() = %v, want %v", got, tt.wantSize) } if got := o.Mode(); got != tt.wantMode { t.Errorf("object.Mode() = %v, want %v", got, tt.wantMode) } if got := o.ModTime(); got != tt.wantModTime { t.Errorf("object.ModTime() = %v, want %v", got, tt.wantModTime) } if got := o.IsDir(); got != tt.wantIsDir { t.Errorf("object.IsDir() = %v, want %v", got, tt.wantIsDir) } if got := o.Sys(); !reflect.DeepEqual(got, tt.wantSys) { t.Errorf("object.Sys() = %v, want %v", got, tt.wantSys) } }) } } func Test_object_load(t *testing.T) { pub, pemData := generateKey(t) type fields struct { Path string Object interface{} } tests := []struct { name string fields fields wantPemData *bytes.Buffer wantErr bool }{ {"ok", fields{"path", pub}, pemData, false}, {"fail", fields{"path", "not a key"}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { o := &object{ Path: tt.fields.Path, Object: tt.fields.Object, } if err := o.load(); (err != nil) != tt.wantErr { t.Errorf("object.load() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(o.pemData, tt.wantPemData) { t.Errorf("object.load() pemData = %s, wantPemData %s", o.pemData, tt.wantPemData) } }) } } func Test_object_Stat(t *testing.T) { pub, pemData := generateKey(t) type fields struct { Path string Object interface{} } tests := []struct { name string fields fields want fs.FileInfo wantErr bool }{ {"ok", fields{"path", pub}, &object{ Path: "path", Object: pub, pemData: pemData, }, false}, {"fail", fields{"path", "not a key"}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { o := &object{ Path: tt.fields.Path, Object: tt.fields.Object, } got, err := o.Stat() if (err != nil) != tt.wantErr { t.Errorf("object.Stat() error = %v, wantErr %v", err, tt.wantErr) return } // Normalize if got != nil { got.(*object).once = sync.Once{} } if !reflect.DeepEqual(got, tt.want) { t.Errorf("object.Stat() = %v, want %v", got, tt.want) } }) } } func Test_object_Read(t *testing.T) { pub, pemData := generateKey(t) type fields struct { Path string Object interface{} } type args struct { b []byte } tests := []struct { name string fields fields args args wantN int wantBytes []byte wantErr bool }{ {"ok", fields{"path", pub}, args{make([]byte, pemData.Len())}, pemData.Len(), pemData.Bytes(), false}, {"empty", fields{"path", pub}, args{[]byte{}}, 0, []byte{}, false}, {"fail", fields{"path", "not a key"}, args{[]byte{}}, 0, []byte{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { o := &object{ Path: tt.fields.Path, Object: tt.fields.Object, } got, err := o.Read(tt.args.b) if (err != nil) != tt.wantErr { t.Errorf("object.Read() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.wantN { t.Errorf("object.Read() = %v, wanN %v", got, tt.wantN) } if !bytes.Equal(tt.args.b, tt.wantBytes) { t.Errorf("object.Read() = %v, wantBytes %v", tt.args.b, tt.wantBytes) } }) } } func Test_object_Close(t *testing.T) { pub, _ := generateKey(t) o := &object{ Path: "path", Object: pub, } if err := o.load(); err != nil { t.Fatal(err) } tests := []struct { name string o *object want *object wantErr bool }{ {"ok", o, &object{ Path: "path", Object: nil, pemData: nil, err: io.EOF, }, false}, {"eof", o, &object{ Path: "path", Object: nil, pemData: nil, err: io.EOF, }, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := tt.o.Close(); (err != nil) != tt.wantErr { t.Errorf("object.Close() error = %v, wantErr %v", err, tt.wantErr) } // Normalize tt.o.once = sync.Once{} if !reflect.DeepEqual(tt.o, tt.want) { //nolint:govet // variable names match crypto formulae docs t.Errorf("object.Close() = %v, want %v", tt.o, tt.want) } }) } } golang-step-crypto-0.24.0/kms/pkcs11/000077500000000000000000000000001437037643100172375ustar00rootroot00000000000000golang-step-crypto-0.24.0/kms/pkcs11/benchmark_test.go000066400000000000000000000037231437037643100225640ustar00rootroot00000000000000//go:build cgo // +build cgo package pkcs11 import ( "crypto" "crypto/rand" "crypto/rsa" "testing" "go.step.sm/crypto/kms/apiv1" ) func benchmarkSign(b *testing.B, signer crypto.Signer, opts crypto.SignerOpts) { hash := opts.HashFunc() h := hash.New() h.Write([]byte("buggy-coheir-RUBRIC-rabbet-liberal-eaglet-khartoum-stagger")) digest := h.Sum(nil) b.ResetTimer() for i := 0; i < b.N; i++ { signer.Sign(rand.Reader, digest, opts) } b.StopTimer() } func BenchmarkSignRSA(b *testing.B) { k := setupPKCS11(b) signer, err := k.CreateSigner(&apiv1.CreateSignerRequest{ SigningKey: "pkcs11:id=7371;object=rsa-key", }) if err != nil { b.Fatalf("PKCS11.CreateSigner() error = %v", err) } benchmarkSign(b, signer, crypto.SHA256) } func BenchmarkSignRSAPSS(b *testing.B) { k := setupPKCS11(b) signer, err := k.CreateSigner(&apiv1.CreateSignerRequest{ SigningKey: "pkcs11:id=7372;object=rsa-pss-key", }) if err != nil { b.Fatalf("PKCS11.CreateSigner() error = %v", err) } benchmarkSign(b, signer, &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: crypto.SHA256, }) } func BenchmarkSignP256(b *testing.B) { k := setupPKCS11(b) signer, err := k.CreateSigner(&apiv1.CreateSignerRequest{ SigningKey: "pkcs11:id=7373;object=ecdsa-p256-key", }) if err != nil { b.Fatalf("PKCS11.CreateSigner() error = %v", err) } benchmarkSign(b, signer, crypto.SHA256) } func BenchmarkSignP384(b *testing.B) { k := setupPKCS11(b) signer, err := k.CreateSigner(&apiv1.CreateSignerRequest{ SigningKey: "pkcs11:id=7374;object=ecdsa-p384-key", }) if err != nil { b.Fatalf("PKCS11.CreateSigner() error = %v", err) } benchmarkSign(b, signer, crypto.SHA384) } func BenchmarkSignP521(b *testing.B) { k := setupPKCS11(b) signer, err := k.CreateSigner(&apiv1.CreateSignerRequest{ SigningKey: "pkcs11:id=7375;object=ecdsa-p521-key", }) if err != nil { b.Fatalf("PKCS11.CreateSigner() error = %v", err) } benchmarkSign(b, signer, crypto.SHA512) } golang-step-crypto-0.24.0/kms/pkcs11/opensc_test.go000066400000000000000000000024271437037643100221210ustar00rootroot00000000000000//go:build opensc // +build opensc package pkcs11 import ( "runtime" "sync" "github.com/ThalesIgnite/crypto11" ) var softHSM2Once sync.Once // mustPKCS11 configures a *PKCS11 KMS to be used with OpenSC, using for example // a Nitrokey HSM. To initialize these tests we should run: // // sc-hsm-tool --initialize --so-pin 3537363231383830 --pin 123456 // // Or: // // pkcs11-tool --module /usr/local/lib/opensc-pkcs11.so \ // --init-token --init-pin \ // --so-pin=3537363231383830 --new-pin=123456 --pin=123456 \ // --label="pkcs11-test" func mustPKCS11(t TBTesting) *PKCS11 { t.Helper() testModule = "OpenSC" if runtime.GOARCH != "amd64" { t.Fatalf("opensc test skipped on %s:%s", runtime.GOOS, runtime.GOARCH) } var path string switch runtime.GOOS { case "darwin": path = "/usr/local/lib/opensc-pkcs11.so" case "linux": path = "/usr/local/lib/opensc-pkcs11.so" default: t.Skipf("opensc test skipped on %s", runtime.GOOS) return nil } var zero int p11, err := crypto11.Configure(&crypto11.Config{ Path: path, SlotNumber: &zero, Pin: "123456", }) if err != nil { t.Fatalf("failed to configure opensc on %s: %v", runtime.GOOS, err) } k := &PKCS11{ p11: p11, } // Setup softHSM2Once.Do(func() { teardown(t, k) setup(t, k) }) return k } golang-step-crypto-0.24.0/kms/pkcs11/other_test.go000066400000000000000000000122671437037643100217560ustar00rootroot00000000000000//go:build cgo && !softhsm2 && !yubihsm2 && !opensc // +build cgo,!softhsm2,!yubihsm2,!opensc package pkcs11 import ( "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/x509" "io" "math/big" "github.com/ThalesIgnite/crypto11" "github.com/pkg/errors" ) func mustPKCS11(t TBTesting) *PKCS11 { t.Helper() testModule = "Golang crypto" k := &PKCS11{ p11: &stubPKCS11{ signerIndex: make(map[keyType]int), certIndex: make(map[keyType]int), }, } for i := range testCerts { testCerts[i].Certificates = nil } teardown(t, k) setup(t, k) return k } type keyType struct { id string label string serial string } func newKey(id, label []byte, serial *big.Int) keyType { var serialString string if serial != nil { serialString = serial.String() } return keyType{ id: string(id), label: string(label), serial: serialString, } } type stubPKCS11 struct { signers []crypto11.Signer certs []*x509.Certificate signerIndex map[keyType]int certIndex map[keyType]int } func (s *stubPKCS11) FindKeyPair(id, label []byte) (crypto11.Signer, error) { if id == nil && label == nil { return nil, errors.New("id and label cannot both be nil") } if i, ok := s.signerIndex[newKey(id, label, nil)]; ok { return s.signers[i], nil } return nil, nil } func (s *stubPKCS11) FindCertificate(id, label []byte, serial *big.Int) (*x509.Certificate, error) { if id == nil && label == nil && serial == nil { return nil, errors.New("id, label and serial cannot both be nil") } if i, ok := s.certIndex[newKey(id, label, serial)]; ok { return s.certs[i], nil } return nil, nil } func (s *stubPKCS11) ImportCertificateWithAttributes(template crypto11.AttributeSet, cert *x509.Certificate) error { var id, label []byte if v := template[crypto11.CkaId]; v != nil { id = v.Value } if v := template[crypto11.CkaLabel]; v != nil { label = v.Value } return s.ImportCertificateWithLabel(id, label, cert) } func (s *stubPKCS11) ImportCertificateWithLabel(id, label []byte, cert *x509.Certificate) error { switch { case id == nil: return errors.New("id cannot both be nil") case label == nil: return errors.New("label cannot both be nil") case cert == nil: return errors.New("certificate cannot be nil") } i := len(s.certs) s.certs = append(s.certs, cert) s.certIndex[newKey(id, label, cert.SerialNumber)] = i s.certIndex[newKey(id, nil, nil)] = i s.certIndex[newKey(nil, label, nil)] = i s.certIndex[newKey(nil, nil, cert.SerialNumber)] = i s.certIndex[newKey(id, label, nil)] = i s.certIndex[newKey(id, nil, cert.SerialNumber)] = i s.certIndex[newKey(nil, label, cert.SerialNumber)] = i return nil } func (s *stubPKCS11) DeleteCertificate(id, label []byte, serial *big.Int) error { if id == nil && label == nil && serial == nil { return errors.New("id, label and serial cannot both be nil") } if i, ok := s.certIndex[newKey(id, label, serial)]; ok { s.certs[i] = nil } return nil } func (s *stubPKCS11) GenerateRSAKeyPairWithAttributes(public, private crypto11.AttributeSet, bits int) (crypto11.SignerDecrypter, error) { var id, label []byte if v := public[crypto11.CkaId]; v != nil { id = v.Value } if v := public[crypto11.CkaLabel]; v != nil { label = v.Value } return s.GenerateRSAKeyPairWithLabel(id, label, bits) } func (s *stubPKCS11) GenerateRSAKeyPairWithLabel(id, label []byte, bits int) (crypto11.SignerDecrypter, error) { if id == nil && label == nil { return nil, errors.New("id and label cannot both be nil") } p, err := rsa.GenerateKey(rand.Reader, bits) if err != nil { return nil, err } k := &privateKey{ Signer: p, index: len(s.signers), stub: s, } s.signers = append(s.signers, k) s.signerIndex[newKey(id, label, nil)] = k.index s.signerIndex[newKey(id, nil, nil)] = k.index s.signerIndex[newKey(nil, label, nil)] = k.index return k, nil } func (s *stubPKCS11) GenerateECDSAKeyPairWithAttributes(public, private crypto11.AttributeSet, curve elliptic.Curve) (crypto11.Signer, error) { var id, label []byte if v := public[crypto11.CkaId]; v != nil { id = v.Value } if v := public[crypto11.CkaLabel]; v != nil { label = v.Value } return s.GenerateECDSAKeyPairWithLabel(id, label, curve) } func (s *stubPKCS11) GenerateECDSAKeyPairWithLabel(id, label []byte, curve elliptic.Curve) (crypto11.Signer, error) { if id == nil && label == nil { return nil, errors.New("id and label cannot both be nil") } p, err := ecdsa.GenerateKey(curve, rand.Reader) if err != nil { return nil, err } k := &privateKey{ Signer: p, index: len(s.signers), stub: s, } s.signers = append(s.signers, k) s.signerIndex[newKey(id, label, nil)] = k.index s.signerIndex[newKey(id, nil, nil)] = k.index s.signerIndex[newKey(nil, label, nil)] = k.index return k, nil } func (s *stubPKCS11) Close() error { return nil } type privateKey struct { crypto.Signer index int stub *stubPKCS11 } func (s *privateKey) Delete() error { s.stub.signers[s.index] = nil return nil } func (s *privateKey) Decrypt(rnd io.Reader, msg []byte, opts crypto.DecrypterOpts) (plaintext []byte, err error) { k, ok := s.Signer.(*rsa.PrivateKey) if !ok { return nil, errors.New("key is not an rsa key") } return k.Decrypt(rnd, msg, opts) } golang-step-crypto-0.24.0/kms/pkcs11/pkcs11.go000066400000000000000000000272661437037643100207050ustar00rootroot00000000000000//go:build cgo && !nopkcs11 // +build cgo,!nopkcs11 package pkcs11 import ( "context" "crypto" "crypto/elliptic" "crypto/rsa" "crypto/x509" "encoding/hex" "fmt" "math/big" "strconv" "sync" "github.com/ThalesIgnite/crypto11" "github.com/pkg/errors" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/kms/uri" ) // Scheme is the scheme used in uris. const Scheme = "pkcs11" // DefaultRSASize is the number of bits of a new RSA key if no size has been // specified. const DefaultRSASize = 3072 // P11 defines the methods on crypto11.Context that this package will use. This // interface will be used for unit testing. type P11 interface { FindKeyPair(id, label []byte) (crypto11.Signer, error) FindCertificate(id, label []byte, serial *big.Int) (*x509.Certificate, error) ImportCertificateWithAttributes(template crypto11.AttributeSet, certificate *x509.Certificate) error DeleteCertificate(id, label []byte, serial *big.Int) error GenerateRSAKeyPairWithAttributes(public, private crypto11.AttributeSet, bits int) (crypto11.SignerDecrypter, error) GenerateECDSAKeyPairWithAttributes(public, private crypto11.AttributeSet, curve elliptic.Curve) (crypto11.Signer, error) Close() error } var p11Configure = func(config *crypto11.Config) (P11, error) { return crypto11.Configure(config) } // PKCS11 is the implementation of a KMS using the PKCS #11 standard. type PKCS11 struct { p11 P11 closed sync.Once } // New returns a new PKCS11 KMS. func New(ctx context.Context, opts apiv1.Options) (*PKCS11, error) { var config crypto11.Config if opts.URI != "" { u, err := uri.ParseWithScheme(Scheme, opts.URI) if err != nil { return nil, err } config.Pin = u.Pin() config.Path = u.Get("module-path") config.TokenLabel = u.Get("token") config.TokenSerial = u.Get("serial") if v := u.Get("slot-id"); v != "" { n, err := strconv.Atoi(v) if err != nil { return nil, errors.Wrap(err, "kms uri 'slot-id' is not valid") } config.SlotNumber = &n } } if config.Pin == "" && opts.Pin != "" { config.Pin = opts.Pin } switch { case config.Path == "": return nil, errors.New("kms uri 'module-path' are required") case config.TokenLabel == "" && config.TokenSerial == "" && config.SlotNumber == nil: return nil, errors.New("kms uri 'token', 'serial' or 'slot-id' are required") case config.Pin == "": return nil, errors.New("kms 'pin' cannot be empty") case config.TokenLabel != "" && config.TokenSerial != "": return nil, errors.New("kms uri 'token' and 'serial' are mutually exclusive") case config.TokenLabel != "" && config.SlotNumber != nil: return nil, errors.New("kms uri 'token' and 'slot-id' are mutually exclusive") case config.TokenSerial != "" && config.SlotNumber != nil: return nil, errors.New("kms uri 'serial' and 'slot-id' are mutually exclusive") } p11, err := p11Configure(&config) if err != nil { return nil, errors.Wrap(err, "error initializing PKCS#11") } return &PKCS11{ p11: p11, }, nil } func init() { apiv1.Register(apiv1.PKCS11, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) { return New(ctx, opts) }) } // GetPublicKey returns the public key .... func (k *PKCS11) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) { if req.Name == "" { return nil, errors.New("getPublicKeyRequest 'name' cannot be empty") } signer, err := findSigner(k.p11, req.Name) if err != nil { return nil, errors.Wrap(err, "getPublicKey failed") } return signer.Public(), nil } // CreateKey generates a new key in the PKCS#11 module and returns the public key. func (k *PKCS11) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) { switch { case req.Name == "": return nil, errors.New("createKeyRequest 'name' cannot be empty") case req.Bits < 0: return nil, errors.New("createKeyRequest 'bits' cannot be negative") } signer, err := generateKey(k.p11, req) if err != nil { return nil, errors.Wrap(err, "createKey failed") } return &apiv1.CreateKeyResponse{ Name: req.Name, PublicKey: signer.Public(), CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: req.Name, }, }, nil } // CreateSigner creates a signer using a key present in the PKCS#11 module. func (k *PKCS11) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) { if req.SigningKey == "" { return nil, errors.New("createSignerRequest 'signingKey' cannot be empty") } signer, err := findSigner(k.p11, req.SigningKey) if err != nil { return nil, errors.Wrap(err, "createSigner failed") } return signer, nil } // CreateDecrypter creates a decrypter using a key present in the PKCS#11 // module. func (k *PKCS11) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { if req.DecryptionKey == "" { return nil, errors.New("createDecrypterRequest 'decryptionKey' cannot be empty") } signer, err := findSigner(k.p11, req.DecryptionKey) if err != nil { return nil, errors.Wrap(err, "createDecrypterRequest failed") } // Only RSA keys will implement the Decrypter interface. if _, ok := signer.Public().(*rsa.PublicKey); ok { if dec, ok := signer.(crypto.Decrypter); ok { return dec, nil } } return nil, errors.New("createDecrypterRequest failed: signer does not implement crypto.Decrypter") } // LoadCertificate implements kms.CertificateManager and loads a certificate // from the YubiKey. func (k *PKCS11) LoadCertificate(req *apiv1.LoadCertificateRequest) (*x509.Certificate, error) { if req.Name == "" { return nil, errors.New("loadCertificateRequest 'name' cannot be nil") } cert, err := findCertificate(k.p11, req.Name) if err != nil { return nil, errors.Wrap(err, "loadCertificate failed") } return cert, nil } // StoreCertificate implements kms.CertificateManager and stores a certificate // in the YubiKey. func (k *PKCS11) StoreCertificate(req *apiv1.StoreCertificateRequest) error { switch { case req.Name == "": return errors.New("storeCertificateRequest 'name' cannot be empty") case req.Certificate == nil: return errors.New("storeCertificateRequest 'Certificate' cannot be nil") } id, object, err := parseObject(req.Name) if err != nil { return errors.Wrap(err, "storeCertificate failed") } // Enforce the use of both id and labels. This is not strictly necessary in // PKCS #11, but it's a good practice. if len(id) == 0 || len(object) == 0 { return errors.Errorf("key with uri %s is not valid, id and object are required", req.Name) } cert, err := k.p11.FindCertificate(id, object, nil) if err != nil { return errors.Wrap(err, "storeCertificate failed") } if cert != nil { return errors.Wrap(apiv1.AlreadyExistsError{ Message: req.Name + " already exists", }, "storeCertificate failed") } // Import certificate with the necessary attributes. template, err := crypto11.NewAttributeSetWithIDAndLabel(id, object) if err != nil { return errors.Wrap(err, "storeCertificate failed") } if req.Extractable { template.Set(crypto11.CkaExtractable, true) } if err := k.p11.ImportCertificateWithAttributes(template, req.Certificate); err != nil { return errors.Wrap(err, "storeCertificate failed") } return nil } // DeleteKey is a utility function to delete a key given an uri. func (k *PKCS11) DeleteKey(u string) error { id, object, err := parseObject(u) if err != nil { return errors.Wrap(err, "deleteKey failed") } signer, err := k.p11.FindKeyPair(id, object) if err != nil { return errors.Wrap(err, "deleteKey failed") } if signer == nil { return nil } if err := signer.Delete(); err != nil { return errors.Wrap(err, "deleteKey failed") } return nil } // DeleteCertificate is a utility function to delete a certificate given an uri. func (k *PKCS11) DeleteCertificate(u string) error { id, object, err := parseObject(u) if err != nil { return errors.Wrap(err, "deleteCertificate failed") } if err := k.p11.DeleteCertificate(id, object, nil); err != nil { return errors.Wrap(err, "deleteCertificate failed") } return nil } // Close releases the connection to the PKCS#11 module. func (k *PKCS11) Close() (err error) { k.closed.Do(func() { err = errors.Wrap(k.p11.Close(), "error closing pkcs#11 context") }) return } func toByte(s string) []byte { if s == "" { return nil } return []byte(s) } func parseObject(rawuri string) ([]byte, []byte, error) { u, err := uri.ParseWithScheme(Scheme, rawuri) if err != nil { return nil, nil, err } id := u.GetEncoded("id") object := u.Get("object") if len(id) == 0 && object == "" { return nil, nil, errors.Errorf("key with uri %s is not valid, id or object are required", rawuri) } return id, toByte(object), nil } func generateKey(ctx P11, req *apiv1.CreateKeyRequest) (crypto11.Signer, error) { id, object, err := parseObject(req.Name) if err != nil { return nil, err } signer, err := ctx.FindKeyPair(id, object) if err != nil { return nil, err } if signer != nil { return nil, apiv1.AlreadyExistsError{ Message: req.Name + " already exists", } } // Enforce the use of both id and labels. This is not strictly necessary in // PKCS #11, but it's a good practice. if len(id) == 0 || len(object) == 0 { return nil, errors.Errorf("key with uri %s is not valid, id and object are required", req.Name) } // Create template for public and private keys public, err := crypto11.NewAttributeSetWithIDAndLabel(id, object) if err != nil { return nil, err } private := public.Copy() if req.Extractable { private.Set(crypto11.CkaExtractable, true) } bits := req.Bits if bits == 0 { bits = DefaultRSASize } switch req.SignatureAlgorithm { case apiv1.UnspecifiedSignAlgorithm: return ctx.GenerateECDSAKeyPairWithAttributes(public, private, elliptic.P256()) case apiv1.SHA256WithRSA, apiv1.SHA384WithRSA, apiv1.SHA512WithRSA: return ctx.GenerateRSAKeyPairWithAttributes(public, private, bits) case apiv1.SHA256WithRSAPSS, apiv1.SHA384WithRSAPSS, apiv1.SHA512WithRSAPSS: return ctx.GenerateRSAKeyPairWithAttributes(public, private, bits) case apiv1.ECDSAWithSHA256: return ctx.GenerateECDSAKeyPairWithAttributes(public, private, elliptic.P256()) case apiv1.ECDSAWithSHA384: return ctx.GenerateECDSAKeyPairWithAttributes(public, private, elliptic.P384()) case apiv1.ECDSAWithSHA512: return ctx.GenerateECDSAKeyPairWithAttributes(public, private, elliptic.P521()) case apiv1.PureEd25519: return nil, fmt.Errorf("signature algorithm %s is not supported", req.SignatureAlgorithm) default: return nil, fmt.Errorf("signature algorithm %s is not supported", req.SignatureAlgorithm) } } func findSigner(ctx P11, rawuri string) (crypto11.Signer, error) { id, object, err := parseObject(rawuri) if err != nil { return nil, err } signer, err := ctx.FindKeyPair(id, object) if err != nil { return nil, errors.Wrapf(err, "error finding key with uri %s", rawuri) } if signer == nil { return nil, errors.Errorf("key with uri %s not found", rawuri) } return signer, nil } func findCertificate(ctx P11, rawuri string) (*x509.Certificate, error) { u, err := uri.ParseWithScheme(Scheme, rawuri) if err != nil { return nil, err } id, object, serial := u.GetEncoded("id"), u.Get("object"), u.Get("serial") if len(id) == 0 && object == "" && serial == "" { return nil, errors.Errorf("key with uri %s is not valid, id, object or serial are required", rawuri) } var serialNumber *big.Int if serial != "" { b, err := hex.DecodeString(serial) if err != nil { return nil, errors.Errorf("key with uri %s is not valid, failed to decode serial", rawuri) } serialNumber = new(big.Int).SetBytes(b) } cert, err := ctx.FindCertificate(id, toByte(object), serialNumber) if err != nil { return nil, errors.Wrapf(err, "error finding certificate with uri %s", rawuri) } if cert == nil { return nil, errors.Errorf("certificate with uri %s not found", rawuri) } return cert, nil } golang-step-crypto-0.24.0/kms/pkcs11/pkcs11_no_cgo.go000066400000000000000000000031251437037643100222150ustar00rootroot00000000000000//go:build !cgo || nopkcs11 // +build !cgo nopkcs11 package pkcs11 import ( "context" "crypto" "os" "path/filepath" "github.com/pkg/errors" "go.step.sm/crypto/kms/apiv1" ) var errUnsupported error func init() { name := filepath.Base(os.Args[0]) errUnsupported = errors.Errorf("unsupported kms type 'pkcs11': %s is compiled without cgo or PKCS#11 support", name) apiv1.Register(apiv1.PKCS11, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) { return nil, errUnsupported }) } // PKCS11 is the implementation of a KMS using the PKCS #11 standard. type PKCS11 struct{} // New implements the kms.KeyManager interface and without CGO will always // return an error. func New(ctx context.Context, opts apiv1.Options) (*PKCS11, error) { return nil, errUnsupported } // GetPublicKey implements the kms.KeyManager interface and without CGO will always // return an error. func (*PKCS11) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) { return nil, errUnsupported } // CreateKey implements the kms.KeyManager interface and without CGO will always // return an error. func (*PKCS11) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) { return nil, errUnsupported } // CreateSigner implements the kms.KeyManager interface and without CGO will always // return an error. func (*PKCS11) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) { return nil, errUnsupported } // Close implements the kms.KeyManager interface and without CGO will always // return an error. func (*PKCS11) Close() error { return errUnsupported } golang-step-crypto-0.24.0/kms/pkcs11/pkcs11_test.go000066400000000000000000000615511437037643100217370ustar00rootroot00000000000000//go:build cgo // +build cgo package pkcs11 import ( "bytes" "context" "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/rand" "crypto/rsa" "crypto/x509" "math/big" "reflect" "strings" "testing" "github.com/ThalesIgnite/crypto11" "github.com/pkg/errors" "go.step.sm/crypto/kms/apiv1" "golang.org/x/crypto/cryptobyte" "golang.org/x/crypto/cryptobyte/asn1" ) func TestNew(t *testing.T) { tmp := p11Configure t.Cleanup(func() { p11Configure = tmp }) k := mustPKCS11(t) t.Cleanup(func() { k.Close() }) p11Configure = func(config *crypto11.Config) (P11, error) { if strings.Contains(config.Path, "fail") { return nil, errors.New("an error") } return k.p11, nil } type args struct { ctx context.Context opts apiv1.Options } tests := []struct { name string args args want *PKCS11 wantErr bool }{ {"ok", args{context.Background(), apiv1.Options{ Type: "pkcs11", URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test?pin-value=password", }}, k, false}, {"ok with serial", args{context.Background(), apiv1.Options{ Type: "pkcs11", URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;serial=0123456789?pin-value=password", }}, k, false}, {"ok with slot-id", args{context.Background(), apiv1.Options{ Type: "pkcs11", URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;slot-id=0?pin-value=password", }}, k, false}, {"ok with pin", args{context.Background(), apiv1.Options{ Type: "pkcs11", URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test", Pin: "passowrd", }}, k, false}, {"fail missing module", args{context.Background(), apiv1.Options{ Type: "pkcs11", URI: "pkcs11:token=pkcs11-test", Pin: "passowrd", }}, nil, true}, {"fail missing pin", args{context.Background(), apiv1.Options{ Type: "pkcs11", URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test", }}, nil, true}, {"fail missing token/serial/slot-id", args{context.Background(), apiv1.Options{ Type: "pkcs11", URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so", Pin: "passowrd", }}, nil, true}, {"fail token+serial+slot-id", args{context.Background(), apiv1.Options{ Type: "pkcs11", URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test;serial=0123456789;slot-id=0", Pin: "passowrd", }}, nil, true}, {"fail token+serial", args{context.Background(), apiv1.Options{ Type: "pkcs11", URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test;serial=0123456789", Pin: "passowrd", }}, nil, true}, {"fail token+slot-id", args{context.Background(), apiv1.Options{ Type: "pkcs11", URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test;slot-id=0", Pin: "passowrd", }}, nil, true}, {"fail serial+slot-id", args{context.Background(), apiv1.Options{ Type: "pkcs11", URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;serial=0123456789;slot-id=0", Pin: "passowrd", }}, nil, true}, {"fail slot-id", args{context.Background(), apiv1.Options{ Type: "pkcs11", URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;slot-id=x?pin-value=password", }}, nil, true}, {"fail scheme", args{context.Background(), apiv1.Options{ Type: "pkcs11", URI: "foo:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test?pin-value=password", }}, nil, true}, {"fail configure", args{context.Background(), apiv1.Options{ Type: "pkcs11", URI: "pkcs11:module-path=/usr/local/lib/fail.so;token=pkcs11-test?pin-value=password", }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := New(tt.args.ctx, tt.args.opts) if (err != nil) != tt.wantErr { t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("New() = %v, want %v", got, tt.want) } }) } } func TestPKCS11_GetPublicKey(t *testing.T) { k := setupPKCS11(t) type args struct { req *apiv1.GetPublicKeyRequest } tests := []struct { name string args args want crypto.PublicKey wantErr bool }{ {"RSA", args{&apiv1.GetPublicKeyRequest{ Name: "pkcs11:id=7371;object=rsa-key", }}, &rsa.PublicKey{}, false}, {"RSA by id", args{&apiv1.GetPublicKeyRequest{ Name: "pkcs11:id=7371", }}, &rsa.PublicKey{}, false}, {"RSA by label", args{&apiv1.GetPublicKeyRequest{ Name: "pkcs11:object=rsa-key", }}, &rsa.PublicKey{}, false}, {"ECDSA", args{&apiv1.GetPublicKeyRequest{ Name: "pkcs11:id=7373;object=ecdsa-p256-key", }}, &ecdsa.PublicKey{}, false}, {"ECDSA by id", args{&apiv1.GetPublicKeyRequest{ Name: "pkcs11:id=7373", }}, &ecdsa.PublicKey{}, false}, {"ECDSA by label", args{&apiv1.GetPublicKeyRequest{ Name: "pkcs11:object=ecdsa-p256-key", }}, &ecdsa.PublicKey{}, false}, {"fail name", args{&apiv1.GetPublicKeyRequest{ Name: "", }}, nil, true}, {"fail uri", args{&apiv1.GetPublicKeyRequest{ Name: "https:id=9999;object=https", }}, nil, true}, {"fail missing", args{&apiv1.GetPublicKeyRequest{ Name: "pkcs11:id=9999;object=rsa-key", }}, nil, true}, {"fail FindKeyPair", args{&apiv1.GetPublicKeyRequest{ Name: "pkcs11:foo=bar", }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := k.GetPublicKey(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("PKCS11.GetPublicKey() error = %v, wantErr %v", err, tt.wantErr) return } if reflect.TypeOf(got) != reflect.TypeOf(tt.want) { t.Errorf("PKCS11.GetPublicKey() = %T, want %T", got, tt.want) } }) } } func TestPKCS11_CreateKey(t *testing.T) { k := setupPKCS11(t) // Make sure to delete the created key k.DeleteKey(testObject) type args struct { req *apiv1.CreateKeyRequest } tests := []struct { name string args args want *apiv1.CreateKeyResponse wantErr bool }{ {"default", args{&apiv1.CreateKeyRequest{ Name: testObject, }}, &apiv1.CreateKeyResponse{ Name: testObject, PublicKey: &ecdsa.PublicKey{}, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: testObject, }, }, false}, {"default extractable", args{&apiv1.CreateKeyRequest{ Name: testObject, Extractable: true, }}, &apiv1.CreateKeyResponse{ Name: testObject, PublicKey: &ecdsa.PublicKey{}, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: testObject, }, }, false}, {"RSA SHA256WithRSA", args{&apiv1.CreateKeyRequest{ Name: testObject, SignatureAlgorithm: apiv1.SHA256WithRSA, }}, &apiv1.CreateKeyResponse{ Name: testObject, PublicKey: &rsa.PublicKey{}, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: testObject, }, }, false}, {"RSA SHA384WithRSA", args{&apiv1.CreateKeyRequest{ Name: testObject, SignatureAlgorithm: apiv1.SHA384WithRSA, }}, &apiv1.CreateKeyResponse{ Name: testObject, PublicKey: &rsa.PublicKey{}, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: testObject, }, }, false}, {"RSA SHA512WithRSA", args{&apiv1.CreateKeyRequest{ Name: testObject, SignatureAlgorithm: apiv1.SHA512WithRSA, }}, &apiv1.CreateKeyResponse{ Name: testObject, PublicKey: &rsa.PublicKey{}, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: testObject, }, }, false}, {"RSA SHA256WithRSAPSS", args{&apiv1.CreateKeyRequest{ Name: testObject, SignatureAlgorithm: apiv1.SHA256WithRSAPSS, }}, &apiv1.CreateKeyResponse{ Name: testObject, PublicKey: &rsa.PublicKey{}, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: testObject, }, }, false}, {"RSA SHA384WithRSAPSS", args{&apiv1.CreateKeyRequest{ Name: testObject, SignatureAlgorithm: apiv1.SHA384WithRSAPSS, }}, &apiv1.CreateKeyResponse{ Name: testObject, PublicKey: &rsa.PublicKey{}, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: testObject, }, }, false}, {"RSA SHA512WithRSAPSS", args{&apiv1.CreateKeyRequest{ Name: testObject, SignatureAlgorithm: apiv1.SHA512WithRSAPSS, }}, &apiv1.CreateKeyResponse{ Name: testObject, PublicKey: &rsa.PublicKey{}, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: testObject, }, }, false}, {"RSA 2048", args{&apiv1.CreateKeyRequest{ Name: testObject, SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 2048, }}, &apiv1.CreateKeyResponse{ Name: testObject, PublicKey: &rsa.PublicKey{}, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: testObject, }, }, false}, {"RSA 4096", args{&apiv1.CreateKeyRequest{ Name: testObject, SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 4096, }}, &apiv1.CreateKeyResponse{ Name: testObject, PublicKey: &rsa.PublicKey{}, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: testObject, }, }, false}, {"ECDSA P256", args{&apiv1.CreateKeyRequest{ Name: testObject, SignatureAlgorithm: apiv1.ECDSAWithSHA256, }}, &apiv1.CreateKeyResponse{ Name: testObject, PublicKey: &ecdsa.PublicKey{}, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: testObject, }, }, false}, {"ECDSA P384", args{&apiv1.CreateKeyRequest{ Name: testObject, SignatureAlgorithm: apiv1.ECDSAWithSHA384, }}, &apiv1.CreateKeyResponse{ Name: testObject, PublicKey: &ecdsa.PublicKey{}, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: testObject, }, }, false}, {"ECDSA P521", args{&apiv1.CreateKeyRequest{ Name: testObject, SignatureAlgorithm: apiv1.ECDSAWithSHA512, }}, &apiv1.CreateKeyResponse{ Name: testObject, PublicKey: &ecdsa.PublicKey{}, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: testObject, }, }, false}, {"fail name", args{&apiv1.CreateKeyRequest{ Name: "", }}, nil, true}, {"fail no id", args{&apiv1.CreateKeyRequest{ Name: "pkcs11:object=create-key", }}, nil, true}, {"fail no object", args{&apiv1.CreateKeyRequest{ Name: "pkcs11:id=9999", }}, nil, true}, {"fail schema", args{&apiv1.CreateKeyRequest{ Name: "pkcs12:id=9999;object=create-key", }}, nil, true}, {"fail bits", args{&apiv1.CreateKeyRequest{ Name: "pkcs11:id=9999;object=create-key", Bits: -1, SignatureAlgorithm: apiv1.SHA256WithRSAPSS, }}, nil, true}, {"fail ed25519", args{&apiv1.CreateKeyRequest{ Name: "pkcs11:id=9999;object=create-key", SignatureAlgorithm: apiv1.PureEd25519, }}, nil, true}, {"fail unknown", args{&apiv1.CreateKeyRequest{ Name: "pkcs11:id=9999;object=create-key", SignatureAlgorithm: apiv1.SignatureAlgorithm(100), }}, nil, true}, {"fail FindKeyPair", args{&apiv1.CreateKeyRequest{ Name: "pkcs11:foo=bar", SignatureAlgorithm: apiv1.SHA256WithRSAPSS, }}, nil, true}, {"fail already exists", args{&apiv1.CreateKeyRequest{ Name: "pkcs11:id=7373;object=ecdsa-p256-key", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := k.CreateKey(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("PKCS11.CreateKey() error = %v, wantErr %v", err, tt.wantErr) return } if got != nil { got.PublicKey = tt.want.PublicKey } if !reflect.DeepEqual(got, tt.want) { t.Errorf("PKCS11.CreateKey() = %v, want %v", got, tt.want) } if got != nil { if err := k.DeleteKey(got.Name); err != nil { t.Errorf("PKCS11.DeleteKey() error = %v", err) } } }) } } func TestPKCS11_CreateSigner(t *testing.T) { k := setupPKCS11(t) data := []byte("buggy-coheir-RUBRIC-rabbet-liberal-eaglet-khartoum-stagger") // VerifyASN1 verifies the ASN.1 encoded signature, sig, of hash using the // public key, pub. Its return value records whether the signature is valid. verifyASN1 := func(pub *ecdsa.PublicKey, hash, sig []byte) bool { var ( r, s = &big.Int{}, &big.Int{} inner cryptobyte.String ) input := cryptobyte.String(sig) if !input.ReadASN1(&inner, asn1.SEQUENCE) || !input.Empty() || !inner.ReadASN1Integer(r) || !inner.ReadASN1Integer(s) || !inner.Empty() { return false } return ecdsa.Verify(pub, hash, r, s) } type args struct { req *apiv1.CreateSignerRequest } tests := []struct { name string args args algorithm apiv1.SignatureAlgorithm signerOpts crypto.SignerOpts wantErr bool }{ // SoftHSM2 {"RSA", args{&apiv1.CreateSignerRequest{ SigningKey: "pkcs11:id=7371;object=rsa-key", }}, apiv1.SHA256WithRSA, crypto.SHA256, false}, {"RSA PSS", args{&apiv1.CreateSignerRequest{ SigningKey: "pkcs11:id=7372;object=rsa-pss-key", }}, apiv1.SHA256WithRSAPSS, &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: crypto.SHA256, }, false}, {"ECDSA P256", args{&apiv1.CreateSignerRequest{ SigningKey: "pkcs11:id=7373;object=ecdsa-p256-key", }}, apiv1.ECDSAWithSHA256, crypto.SHA256, false}, {"ECDSA P384", args{&apiv1.CreateSignerRequest{ SigningKey: "pkcs11:id=7374;object=ecdsa-p384-key", }}, apiv1.ECDSAWithSHA384, crypto.SHA384, false}, {"ECDSA P521", args{&apiv1.CreateSignerRequest{ SigningKey: "pkcs11:id=7375;object=ecdsa-p521-key", }}, apiv1.ECDSAWithSHA512, crypto.SHA512, false}, {"fail SigningKey", args{&apiv1.CreateSignerRequest{ SigningKey: "", }}, 0, nil, true}, {"fail uri", args{&apiv1.CreateSignerRequest{ SigningKey: "https:id=7375;object=ecdsa-p521-key", }}, 0, nil, true}, {"fail FindKeyPair", args{&apiv1.CreateSignerRequest{ SigningKey: "pkcs11:foo=bar", }}, 0, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := k.CreateSigner(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("PKCS11.CreateSigner() error = %v, wantErr %v", err, tt.wantErr) return } if got != nil { hash := tt.signerOpts.HashFunc() h := hash.New() h.Write(data) digest := h.Sum(nil) sig, err := got.Sign(rand.Reader, digest, tt.signerOpts) if err != nil { t.Errorf("cyrpto.Signer.Sign() error = %v", err) } switch tt.algorithm { case apiv1.SHA256WithRSA, apiv1.SHA384WithRSA, apiv1.SHA512WithRSA: pub := got.Public().(*rsa.PublicKey) if err := rsa.VerifyPKCS1v15(pub, hash, digest, sig); err != nil { t.Errorf("rsa.VerifyPKCS1v15() error = %v", err) } case apiv1.UnspecifiedSignAlgorithm, apiv1.SHA256WithRSAPSS, apiv1.SHA384WithRSAPSS, apiv1.SHA512WithRSAPSS: pub := got.Public().(*rsa.PublicKey) if err := rsa.VerifyPSS(pub, hash, digest, sig, tt.signerOpts.(*rsa.PSSOptions)); err != nil { t.Errorf("rsa.VerifyPSS() error = %v", err) } case apiv1.ECDSAWithSHA256, apiv1.ECDSAWithSHA384, apiv1.ECDSAWithSHA512: pub := got.Public().(*ecdsa.PublicKey) if !verifyASN1(pub, digest, sig) { t.Error("ecdsa.VerifyASN1() failed") } default: t.Errorf("signature algorithm %s is not supported", tt.algorithm) } } }) } } func TestPKCS11_CreateDecrypter(t *testing.T) { k := setupPKCS11(t) data := []byte("buggy-coheir-RUBRIC-rabbet-liberal-eaglet-khartoum-stagger") type args struct { req *apiv1.CreateDecrypterRequest } tests := []struct { name string args args wantErr bool }{ {"RSA", args{&apiv1.CreateDecrypterRequest{ DecryptionKey: "pkcs11:id=7371;object=rsa-key", }}, false}, {"RSA PSS", args{&apiv1.CreateDecrypterRequest{ DecryptionKey: "pkcs11:id=7372;object=rsa-pss-key", }}, false}, {"ECDSA P256", args{&apiv1.CreateDecrypterRequest{ DecryptionKey: "pkcs11:id=7373;object=ecdsa-p256-key", }}, true}, {"ECDSA P384", args{&apiv1.CreateDecrypterRequest{ DecryptionKey: "pkcs11:id=7374;object=ecdsa-p384-key", }}, true}, {"ECDSA P521", args{&apiv1.CreateDecrypterRequest{ DecryptionKey: "pkcs11:id=7375;object=ecdsa-p521-key", }}, true}, {"fail DecryptionKey", args{&apiv1.CreateDecrypterRequest{ DecryptionKey: "", }}, true}, {"fail uri", args{&apiv1.CreateDecrypterRequest{ DecryptionKey: "https:id=7375;object=ecdsa-p521-key", }}, true}, {"fail FindKeyPair", args{&apiv1.CreateDecrypterRequest{ DecryptionKey: "pkcs11:foo=bar", }}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := k.CreateDecrypter(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("PKCS11.CreateDecrypter() error = %v, wantErr %v", err, tt.wantErr) return } if got != nil { pub := got.Public().(*rsa.PublicKey) // PKCS#1 v1.5 enc, err := rsa.EncryptPKCS1v15(rand.Reader, pub, data) if err != nil { t.Errorf("rsa.EncryptPKCS1v15() error = %v", err) return } dec, err := got.Decrypt(rand.Reader, enc, nil) if err != nil { t.Errorf("PKCS1v15.Decrypt() error = %v", err) } else if !bytes.Equal(dec, data) { t.Errorf("PKCS1v15.Decrypt() failed got = %s, want = %s", dec, data) } // RSA-OAEP enc, err = rsa.EncryptOAEP(crypto.SHA256.New(), rand.Reader, pub, data, []byte("label")) if err != nil { t.Errorf("rsa.EncryptOAEP() error = %v", err) return } dec, err = got.Decrypt(rand.Reader, enc, &rsa.OAEPOptions{ Hash: crypto.SHA256, Label: []byte("label"), }) if err != nil { t.Errorf("RSA-OAEP.Decrypt() error = %v", err) } else if !bytes.Equal(dec, data) { t.Errorf("RSA-OAEP.Decrypt() RSA-OAEP failed got = %s, want = %s", dec, data) } } }) } } func TestPKCS11_LoadCertificate(t *testing.T) { k := setupPKCS11(t) getCertFn := func(i, j int) func() *x509.Certificate { return func() *x509.Certificate { return testCerts[i].Certificates[j] } } type args struct { req *apiv1.LoadCertificateRequest } tests := []struct { name string args args wantFn func() *x509.Certificate wantErr bool }{ {"load", args{&apiv1.LoadCertificateRequest{ Name: "pkcs11:id=7376;object=test-root", }}, getCertFn(0, 0), false}, {"load by id", args{&apiv1.LoadCertificateRequest{ Name: "pkcs11:id=7376", }}, getCertFn(0, 0), false}, {"load by label", args{&apiv1.LoadCertificateRequest{ Name: "pkcs11:object=test-root", }}, getCertFn(0, 0), false}, {"load by serial", args{&apiv1.LoadCertificateRequest{ Name: "pkcs11:serial=64", }}, getCertFn(0, 0), false}, {"fail missing", args{&apiv1.LoadCertificateRequest{ Name: "pkcs11:id=9999;object=test-root", }}, nil, true}, {"fail name", args{&apiv1.LoadCertificateRequest{ Name: "", }}, nil, true}, {"fail scheme", args{&apiv1.LoadCertificateRequest{ Name: "foo:id=7376;object=test-root", }}, nil, true}, {"fail serial", args{&apiv1.LoadCertificateRequest{ Name: "pkcs11:serial=foo", }}, nil, true}, {"fail FindCertificate", args{&apiv1.LoadCertificateRequest{ Name: "pkcs11:foo=bar", }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := k.LoadCertificate(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("PKCS11.LoadCertificate() error = %v, wantErr %v", err, tt.wantErr) return } var want *x509.Certificate if tt.wantFn != nil { want = tt.wantFn() got.Raw, got.RawIssuer, got.RawSubject, got.RawTBSCertificate, got.RawSubjectPublicKeyInfo = nil, nil, nil, nil, nil want.Raw, want.RawIssuer, want.RawSubject, want.RawTBSCertificate, want.RawSubjectPublicKeyInfo = nil, nil, nil, nil, nil } if !reflect.DeepEqual(got, want) { t.Errorf("PKCS11.LoadCertificate() = %v, want %v", got, want) } }) } } func TestPKCS11_StoreCertificate(t *testing.T) { k := setupPKCS11(t) pub, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatalf("ed25519.GenerateKey() error = %v", err) } cert, err := generateCertificate(pub, priv) if err != nil { t.Fatalf("x509.CreateCertificate() error = %v", err) } // Make sure to delete the created certificate t.Cleanup(func() { k.DeleteCertificate(testObject) k.DeleteCertificate(testObjectAlt) }) type args struct { req *apiv1.StoreCertificateRequest } tests := []struct { name string args args wantErr bool }{ {"ok", args{&apiv1.StoreCertificateRequest{ Name: testObject, Certificate: cert, }}, false}, {"ok extractable", args{&apiv1.StoreCertificateRequest{ Name: testObjectAlt, Certificate: cert, Extractable: true, }}, false}, {"fail already exists", args{&apiv1.StoreCertificateRequest{ Name: testObject, Certificate: cert, }}, true}, {"fail name", args{&apiv1.StoreCertificateRequest{ Name: "", Certificate: cert, }}, true}, {"fail certificate", args{&apiv1.StoreCertificateRequest{ Name: testObject, Certificate: nil, }}, true}, {"fail uri", args{&apiv1.StoreCertificateRequest{ Name: "http:id=7770;object=create-cert", Certificate: cert, }}, true}, {"fail missing id", args{&apiv1.StoreCertificateRequest{ Name: "pkcs11:object=create-cert", Certificate: cert, }}, true}, {"fail missing object", args{&apiv1.StoreCertificateRequest{ Name: "pkcs11:id=7770;object=", Certificate: cert, }}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.args.req.Extractable { if testModule == "SoftHSM2" { t.Skip("Extractable certificates are not supported on SoftHSM2") } } if err := k.StoreCertificate(tt.args.req); (err != nil) != tt.wantErr { t.Errorf("PKCS11.StoreCertificate() error = %v, wantErr %v", err, tt.wantErr) } if !tt.wantErr { got, err := k.LoadCertificate(&apiv1.LoadCertificateRequest{ Name: tt.args.req.Name, }) if err != nil { t.Errorf("PKCS11.LoadCertificate() error = %v", err) } if !reflect.DeepEqual(got, cert) { t.Errorf("PKCS11.LoadCertificate() = %v, want %v", got, cert) } } }) } } func TestPKCS11_DeleteKey(t *testing.T) { k := setupPKCS11(t) type args struct { uri string } tests := []struct { name string args args wantErr bool }{ {"delete", args{testObject}, false}, {"delete by id", args{testObjectByID}, false}, {"delete by label", args{testObjectByLabel}, false}, {"delete missing", args{"pkcs11:id=9999;object=missing-key"}, false}, {"fail name", args{""}, true}, {"fail FindKeyPair", args{"pkcs11:foo=bar"}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if _, err := k.CreateKey(&apiv1.CreateKeyRequest{ Name: testObject, }); err != nil { t.Fatalf("PKCS1.CreateKey() error = %v", err) } if err := k.DeleteKey(tt.args.uri); (err != nil) != tt.wantErr { t.Errorf("PKCS11.DeleteKey() error = %v, wantErr %v", err, tt.wantErr) } if _, err := k.GetPublicKey(&apiv1.GetPublicKeyRequest{ Name: tt.args.uri, }); err == nil { t.Error("PKCS11.GetPublicKey() public key found and not expected") } // Make sure to delete the created one. if err := k.DeleteKey(testObject); err != nil { t.Errorf("PKCS11.DeleteKey() error = %v", err) } }) } } func TestPKCS11_DeleteCertificate(t *testing.T) { k := setupPKCS11(t) pub, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatalf("ed25519.GenerateKey() error = %v", err) } cert, err := generateCertificate(pub, priv) if err != nil { t.Fatalf("x509.CreateCertificate() error = %v", err) } type args struct { uri string } tests := []struct { name string args args wantErr bool }{ {"delete", args{testObject}, false}, {"delete by id", args{testObjectByID}, false}, {"delete by label", args{testObjectByLabel}, false}, {"delete missing", args{"pkcs11:id=9999;object=missing-key"}, false}, {"fail name", args{""}, true}, {"fail DeleteCertificate", args{"pkcs11:foo=bar"}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := k.StoreCertificate(&apiv1.StoreCertificateRequest{ Name: testObject, Certificate: cert, }); err != nil { t.Fatalf("PKCS11.StoreCertificate() error = %v", err) } if err := k.DeleteCertificate(tt.args.uri); (err != nil) != tt.wantErr { t.Errorf("PKCS11.DeleteCertificate() error = %v, wantErr %v", err, tt.wantErr) } if _, err := k.LoadCertificate(&apiv1.LoadCertificateRequest{ Name: tt.args.uri, }); err == nil { t.Error("PKCS11.LoadCertificate() certificate found and not expected") } // Make sure to delete the created one. if err := k.DeleteCertificate(testObject); err != nil { t.Errorf("PKCS11.DeleteCertificate() error = %v", err) } }) } } func TestPKCS11_Close(t *testing.T) { k := mustPKCS11(t) tests := []struct { name string wantErr bool }{ {"ok", false}, {"second", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := k.Close(); (err != nil) != tt.wantErr { t.Errorf("PKCS11.Close() error = %v, wantErr %v", err, tt.wantErr) } }) } } golang-step-crypto-0.24.0/kms/pkcs11/setup_test.go000066400000000000000000000075201437037643100217710ustar00rootroot00000000000000//go:build cgo // +build cgo package pkcs11 import ( "crypto" "crypto/rand" "crypto/x509" "crypto/x509/pkix" "math/big" "time" "github.com/pkg/errors" "go.step.sm/crypto/kms/apiv1" ) var ( testModule = "" testObject = "pkcs11:id=7370;object=test-name" testObjectAlt = "pkcs11:id=7377;object=alt-test-name" testObjectByID = "pkcs11:id=7370" testObjectByLabel = "pkcs11:object=test-name" testKeys = []struct { Name string SignatureAlgorithm apiv1.SignatureAlgorithm Bits int }{ {"pkcs11:id=7371;object=rsa-key", apiv1.SHA256WithRSA, 2048}, {"pkcs11:id=7372;object=rsa-pss-key", apiv1.SHA256WithRSAPSS, DefaultRSASize}, {"pkcs11:id=7373;object=ecdsa-p256-key", apiv1.ECDSAWithSHA256, 0}, {"pkcs11:id=7374;object=ecdsa-p384-key", apiv1.ECDSAWithSHA384, 0}, {"pkcs11:id=7375;object=ecdsa-p521-key", apiv1.ECDSAWithSHA512, 0}, } testCerts = []struct { Name string Key string Certificates []*x509.Certificate }{ {"pkcs11:id=7376;object=test-root", "pkcs11:id=7373;object=ecdsa-p256-key", nil}, } ) type TBTesting interface { Helper() Cleanup(f func()) Log(args ...interface{}) Errorf(format string, args ...interface{}) Fatalf(format string, args ...interface{}) Skipf(format string, args ...interface{}) } func generateCertificate(pub crypto.PublicKey, signer crypto.Signer) (*x509.Certificate, error) { now := time.Now() template := &x509.Certificate{ Subject: pkix.Name{CommonName: "Test Root Certificate"}, Issuer: pkix.Name{CommonName: "Test Root Certificate"}, IsCA: true, MaxPathLen: 1, KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, NotBefore: now, NotAfter: now.Add(time.Hour), SerialNumber: big.NewInt(100), } b, err := x509.CreateCertificate(rand.Reader, template, template, pub, signer) if err != nil { return nil, err } return x509.ParseCertificate(b) } func setup(t TBTesting, k *PKCS11) { t.Log("Running using", testModule) for _, tk := range testKeys { _, err := k.CreateKey(&apiv1.CreateKeyRequest{ Name: tk.Name, SignatureAlgorithm: tk.SignatureAlgorithm, Bits: tk.Bits, }) if err != nil && !errors.Is(errors.Cause(err), apiv1.AlreadyExistsError{ Message: tk.Name + " already exists", }) { t.Errorf("PKCS11.GetPublicKey() error = %v", err) } } for i, c := range testCerts { signer, err := k.CreateSigner(&apiv1.CreateSignerRequest{ SigningKey: c.Key, }) if err != nil { t.Errorf("PKCS11.CreateSigner() error = %v", err) continue } cert, err := generateCertificate(signer.Public(), signer) if err != nil { t.Errorf("x509.CreateCertificate() error = %v", err) continue } if err := k.StoreCertificate(&apiv1.StoreCertificateRequest{ Name: c.Name, Certificate: cert, }); err != nil && !errors.Is(errors.Cause(err), apiv1.AlreadyExistsError{ Message: c.Name + " already exists", }) { t.Errorf("PKCS1.StoreCertificate() error = %v", err) continue } testCerts[i].Certificates = append(testCerts[i].Certificates, cert) } } func teardown(t TBTesting, k *PKCS11) { testObjects := []string{testObject, testObjectByID, testObjectByLabel} for _, name := range testObjects { if err := k.DeleteKey(name); err != nil { t.Errorf("PKCS11.DeleteKey() error = %v", err) } if err := k.DeleteCertificate(name); err != nil { t.Errorf("PKCS11.DeleteCertificate() error = %v", err) } } for _, tk := range testKeys { if err := k.DeleteKey(tk.Name); err != nil { t.Errorf("PKCS11.DeleteKey() error = %v", err) } } for _, tc := range testCerts { if err := k.DeleteCertificate(tc.Name); err != nil { t.Errorf("PKCS11.DeleteCertificate() error = %v", err) } } } func setupPKCS11(t TBTesting) *PKCS11 { t.Helper() k := mustPKCS11(t) t.Cleanup(func() { k.Close() }) return k } golang-step-crypto-0.24.0/kms/pkcs11/softhsm2_test.go000066400000000000000000000023421437037643100223730ustar00rootroot00000000000000//go:build cgo && softhsm2 // +build cgo,softhsm2 package pkcs11 import ( "runtime" "sync" "github.com/ThalesIgnite/crypto11" ) var softHSM2Once sync.Once // mustPKCS11 configures a *PKCS11 KMS to be used with SoftHSM2. To initialize // these tests, we should run: // // softhsm2-util --init-token --free \ // --token pkcs11-test --label pkcs11-test \ // --so-pin password --pin password // // To delete we should run: // // softhsm2-util --delete-token --token pkcs11-test func mustPKCS11(t TBTesting) *PKCS11 { t.Helper() testModule = "SoftHSM2" if runtime.GOARCH != "amd64" { t.Fatalf("softHSM2 test skipped on %s:%s", runtime.GOOS, runtime.GOARCH) } var path string switch runtime.GOOS { case "darwin": path = "/usr/local/lib/softhsm/libsofthsm2.so" case "linux": path = "/usr/lib/softhsm/libsofthsm2.so" default: t.Skipf("softHSM2 test skipped on %s", runtime.GOOS) return nil } p11, err := crypto11.Configure(&crypto11.Config{ Path: path, TokenLabel: "pkcs11-test", Pin: "password", }) if err != nil { t.Fatalf("failed to configure softHSM2 on %s: %v", runtime.GOOS, err) } k := &PKCS11{ p11: p11, } // Setup softHSM2Once.Do(func() { teardown(t, k) setup(t, k) }) return k } golang-step-crypto-0.24.0/kms/pkcs11/yubihsm2_test.go000066400000000000000000000021031437037643100223630ustar00rootroot00000000000000//go:build cgo && yubihsm2 // +build cgo,yubihsm2 package pkcs11 import ( "runtime" "sync" "github.com/ThalesIgnite/crypto11" ) var yubiHSM2Once sync.Once // mustPKCS11 configures a *PKCS11 KMS to be used with YubiHSM2. To initialize // these tests, we should run: // // yubihsm-connector -d func mustPKCS11(t TBTesting) *PKCS11 { t.Helper() testModule = "YubiHSM2" if runtime.GOARCH != "amd64" { t.Skipf("yubiHSM2 test skipped on %s:%s", runtime.GOOS, runtime.GOARCH) } var path string switch runtime.GOOS { case "darwin": path = "/usr/local/lib/pkcs11/yubihsm_pkcs11.dylib" case "linux": path = "/usr/lib/x86_64-linux-gnu/pkcs11/yubihsm_pkcs11.so" default: t.Skipf("yubiHSM2 test skipped on %s", runtime.GOOS) return nil } p11, err := crypto11.Configure(&crypto11.Config{ Path: path, TokenLabel: "YubiHSM", Pin: "0001password", }) if err != nil { t.Fatalf("failed to configure YubiHSM2 on %s: %v", runtime.GOOS, err) } k := &PKCS11{ p11: p11, } // Setup yubiHSM2Once.Do(func() { teardown(t, k) setup(t, k) }) return k } golang-step-crypto-0.24.0/kms/softkms/000077500000000000000000000000001437037643100176235ustar00rootroot00000000000000golang-step-crypto-0.24.0/kms/softkms/softkms.go000066400000000000000000000115231437037643100216420ustar00rootroot00000000000000package softkms import ( "context" "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" "crypto/x509" "github.com/pkg/errors" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/pemutil" ) type algorithmAttributes struct { Type string Curve string } // DefaultRSAKeySize is the default size for RSA keys. const DefaultRSAKeySize = 3072 var signatureAlgorithmMapping = map[apiv1.SignatureAlgorithm]algorithmAttributes{ apiv1.UnspecifiedSignAlgorithm: {"EC", "P-256"}, apiv1.SHA256WithRSA: {"RSA", ""}, apiv1.SHA384WithRSA: {"RSA", ""}, apiv1.SHA512WithRSA: {"RSA", ""}, apiv1.SHA256WithRSAPSS: {"RSA", ""}, apiv1.SHA384WithRSAPSS: {"RSA", ""}, apiv1.SHA512WithRSAPSS: {"RSA", ""}, apiv1.ECDSAWithSHA256: {"EC", "P-256"}, apiv1.ECDSAWithSHA384: {"EC", "P-384"}, apiv1.ECDSAWithSHA512: {"EC", "P-521"}, apiv1.PureEd25519: {"OKP", "Ed25519"}, } // generateKey is used for testing purposes. var generateKey = func(kty, crv string, size int) (interface{}, interface{}, error) { if kty == "RSA" && size == 0 { size = DefaultRSAKeySize } return keyutil.GenerateKeyPair(kty, crv, size) } // SoftKMS is a key manager that uses keys stored in disk. type SoftKMS struct{} // New returns a new SoftKMS. func New(ctx context.Context, opts apiv1.Options) (*SoftKMS, error) { return &SoftKMS{}, nil } func init() { apiv1.Register(apiv1.SoftKMS, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) { return New(ctx, opts) }) } // Close is a noop that just returns nil. func (k *SoftKMS) Close() error { return nil } // CreateSigner returns a new signer configured with the given signing key. func (k *SoftKMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) { var opts []pemutil.Options if req.Password != nil { opts = append(opts, pemutil.WithPassword(req.Password)) } switch { case req.Signer != nil: return req.Signer, nil case len(req.SigningKeyPEM) != 0: v, err := pemutil.ParseKey(req.SigningKeyPEM, opts...) if err != nil { return nil, err } sig, ok := v.(crypto.Signer) if !ok { return nil, errors.New("signingKeyPEM is not a crypto.Signer") } return sig, nil case req.SigningKey != "": v, err := pemutil.Read(req.SigningKey, opts...) if err != nil { return nil, err } sig, ok := v.(crypto.Signer) if !ok { return nil, errors.New("signingKey is not a crypto.Signer") } return sig, nil default: return nil, errors.New("failed to load softKMS: please define signingKeyPEM or signingKey") } } // CreateKey generates a new key using Golang crypto and returns both public and // private key. func (k *SoftKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) { v, ok := signatureAlgorithmMapping[req.SignatureAlgorithm] if !ok { return nil, errors.Errorf("softKMS does not support signature algorithm '%s'", req.SignatureAlgorithm) } pub, priv, err := generateKey(v.Type, v.Curve, req.Bits) if err != nil { return nil, err } signer, ok := priv.(crypto.Signer) if !ok { return nil, errors.Errorf("softKMS createKey result is not a crypto.Signer: type %T", priv) } return &apiv1.CreateKeyResponse{ Name: req.Name, PublicKey: pub, PrivateKey: priv, CreateSignerRequest: apiv1.CreateSignerRequest{ Signer: signer, }, }, nil } // GetPublicKey returns the public key from the file passed in the request name. func (k *SoftKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) { v, err := pemutil.Read(req.Name) if err != nil { return nil, err } switch vv := v.(type) { case *x509.Certificate: return vv.PublicKey, nil case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey: return vv, nil default: return nil, errors.Errorf("unsupported public key type %T", v) } } // CreateDecrypter creates a new crypto.Decrypter backed by disk/software func (k *SoftKMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { var opts []pemutil.Options if req.Password != nil { opts = append(opts, pemutil.WithPassword(req.Password)) } switch { case req.Decrypter != nil: return req.Decrypter, nil case len(req.DecryptionKeyPEM) != 0: v, err := pemutil.ParseKey(req.DecryptionKeyPEM, opts...) if err != nil { return nil, err } decrypter, ok := v.(crypto.Decrypter) if !ok { return nil, errors.New("decryptorKeyPEM is not a crypto.Decrypter") } return decrypter, nil case req.DecryptionKey != "": v, err := pemutil.Read(req.DecryptionKey, opts...) if err != nil { return nil, err } decrypter, ok := v.(crypto.Decrypter) if !ok { return nil, errors.New("decryptionKey is not a crypto.Decrypter") } return decrypter, nil default: return nil, errors.New("failed to load softKMS: please define decryptionKeyPEM or decryptionKey") } } golang-step-crypto-0.24.0/kms/softkms/softkms_test.go000066400000000000000000000322031437037643100226770ustar00rootroot00000000000000package softkms import ( "context" "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" "os" "reflect" "testing" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/pemutil" ) func TestNew(t *testing.T) { type args struct { ctx context.Context opts apiv1.Options } tests := []struct { name string args args want *SoftKMS wantErr bool }{ {"ok", args{context.Background(), apiv1.Options{}}, &SoftKMS{}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := New(tt.args.ctx, tt.args.opts) if (err != nil) != tt.wantErr { t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("New() = %v, want %v", got, tt.want) } }) } } func TestSoftKMS_Close(t *testing.T) { tests := []struct { name string wantErr bool }{ {"ok", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &SoftKMS{} if err := k.Close(); (err != nil) != tt.wantErr { t.Errorf("SoftKMS.Close() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestSoftKMS_CreateSigner(t *testing.T) { pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatal(err) } pemBlock, err := pemutil.Serialize(pk) if err != nil { t.Fatal(err) } pemBlockPassword, err := pemutil.Serialize(pk, pemutil.WithPassword([]byte("pass"))) if err != nil { t.Fatal(err) } // Read and decode file using standard packages b, err := os.ReadFile("testdata/priv.pem") if err != nil { t.Fatal(err) } block, _ := pem.Decode(b) block.Bytes, err = x509.DecryptPEMBlock(block, []byte("pass")) //nolint if err != nil { t.Fatal(err) } pk2, err := x509.ParseECPrivateKey(block.Bytes) if err != nil { t.Fatal(err) } // Create a public PEM b, err = x509.MarshalPKIXPublicKey(pk.Public()) if err != nil { t.Fatal(err) } pub := pem.EncodeToMemory(&pem.Block{ Type: "PUBLIC KEY", Bytes: b, }) type args struct { req *apiv1.CreateSignerRequest } tests := []struct { name string args args want crypto.Signer wantErr bool }{ {"signer", args{&apiv1.CreateSignerRequest{Signer: pk}}, pk, false}, {"pem", args{&apiv1.CreateSignerRequest{SigningKeyPEM: pem.EncodeToMemory(pemBlock)}}, pk, false}, {"pem password", args{&apiv1.CreateSignerRequest{SigningKeyPEM: pem.EncodeToMemory(pemBlockPassword), Password: []byte("pass")}}, pk, false}, {"file", args{&apiv1.CreateSignerRequest{SigningKey: "testdata/priv.pem", Password: []byte("pass")}}, pk2, false}, {"fail", args{&apiv1.CreateSignerRequest{}}, nil, true}, {"fail bad pem", args{&apiv1.CreateSignerRequest{SigningKeyPEM: []byte("bad pem")}}, nil, true}, {"fail bad password", args{&apiv1.CreateSignerRequest{SigningKey: "testdata/priv.pem", Password: []byte("bad-pass")}}, nil, true}, {"fail not a signer", args{&apiv1.CreateSignerRequest{SigningKeyPEM: pub}}, nil, true}, {"fail not a signer from file", args{&apiv1.CreateSignerRequest{SigningKey: "testdata/pub.pem"}}, nil, true}, {"fail missing", args{&apiv1.CreateSignerRequest{SigningKey: "testdata/missing"}}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &SoftKMS{} got, err := k.CreateSigner(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("SoftKMS.CreateSigner() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("SoftKMS.CreateSigner() = %v, want %v", got, tt.want) } }) } } func restoreGenerateKey() func() { oldGenerateKey := generateKey return func() { generateKey = oldGenerateKey } } func TestSoftKMS_CreateKey(t *testing.T) { fn := restoreGenerateKey() defer fn() p256, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatal(err) } rsa2048, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { t.Fatal(err) } edpub, edpriv, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } type args struct { req *apiv1.CreateKeyRequest } type params struct { kty string crv string size int } tests := []struct { name string args args generateKey func() (interface{}, interface{}, error) want *apiv1.CreateKeyResponse wantParams params wantErr bool }{ {"p256", args{&apiv1.CreateKeyRequest{Name: "p256", SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, func() (interface{}, interface{}, error) { return p256.Public(), p256, nil //nolint:gocritic // ignore eval order warning }, &apiv1.CreateKeyResponse{Name: "p256", PublicKey: p256.Public(), PrivateKey: p256, CreateSignerRequest: apiv1.CreateSignerRequest{Signer: p256}}, params{"EC", "P-256", 0}, false}, {"rsa", args{&apiv1.CreateKeyRequest{Name: "rsa3072", SignatureAlgorithm: apiv1.SHA256WithRSA}}, func() (interface{}, interface{}, error) { return rsa2048.Public(), rsa2048, nil //nolint:gocritic // ignore eval order warning }, &apiv1.CreateKeyResponse{Name: "rsa3072", PublicKey: rsa2048.Public(), PrivateKey: rsa2048, CreateSignerRequest: apiv1.CreateSignerRequest{Signer: rsa2048}}, params{"RSA", "", 0}, false}, {"rsa2048", args{&apiv1.CreateKeyRequest{Name: "rsa2048", SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 2048}}, func() (interface{}, interface{}, error) { return rsa2048.Public(), rsa2048, nil //nolint:gocritic // ignore eval order warning }, &apiv1.CreateKeyResponse{Name: "rsa2048", PublicKey: rsa2048.Public(), PrivateKey: rsa2048, CreateSignerRequest: apiv1.CreateSignerRequest{Signer: rsa2048}}, params{"RSA", "", 2048}, false}, {"rsaPSS2048", args{&apiv1.CreateKeyRequest{Name: "rsa2048", SignatureAlgorithm: apiv1.SHA256WithRSAPSS, Bits: 2048}}, func() (interface{}, interface{}, error) { return rsa2048.Public(), rsa2048, nil //nolint:gocritic // ignore eval order warning }, &apiv1.CreateKeyResponse{Name: "rsa2048", PublicKey: rsa2048.Public(), PrivateKey: rsa2048, CreateSignerRequest: apiv1.CreateSignerRequest{Signer: rsa2048}}, params{"RSA", "", 2048}, false}, {"ed25519", args{&apiv1.CreateKeyRequest{Name: "ed25519", SignatureAlgorithm: apiv1.PureEd25519}}, func() (interface{}, interface{}, error) { return edpub, edpriv, nil }, &apiv1.CreateKeyResponse{Name: "ed25519", PublicKey: edpub, PrivateKey: edpriv, CreateSignerRequest: apiv1.CreateSignerRequest{Signer: edpriv}}, params{"OKP", "Ed25519", 0}, false}, {"default", args{&apiv1.CreateKeyRequest{Name: "default"}}, func() (interface{}, interface{}, error) { return p256.Public(), p256, nil //nolint:gocritic // ignore eval order warning }, &apiv1.CreateKeyResponse{Name: "default", PublicKey: p256.Public(), PrivateKey: p256, CreateSignerRequest: apiv1.CreateSignerRequest{Signer: p256}}, params{"EC", "P-256", 0}, false}, {"fail algorithm", args{&apiv1.CreateKeyRequest{Name: "fail", SignatureAlgorithm: apiv1.SignatureAlgorithm(100)}}, func() (interface{}, interface{}, error) { return p256.Public(), p256, nil //nolint:gocritic // ignore eval order warning }, nil, params{}, true}, {"fail generate key", args{&apiv1.CreateKeyRequest{Name: "fail", SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, func() (interface{}, interface{}, error) { return nil, nil, fmt.Errorf("an error") }, nil, params{"EC", "P-256", 0}, true}, {"fail no signer", args{&apiv1.CreateKeyRequest{Name: "fail", SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, func() (interface{}, interface{}, error) { return 1, 2, nil }, nil, params{"EC", "P-256", 0}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &SoftKMS{} generateKey = func(kty, crv string, size int) (interface{}, interface{}, error) { if tt.wantParams.kty != kty { t.Errorf("GenerateKey() kty = %s, want %s", kty, tt.wantParams.kty) } if tt.wantParams.crv != crv { t.Errorf("GenerateKey() crv = %s, want %s", crv, tt.wantParams.crv) } if tt.wantParams.size != size { t.Errorf("GenerateKey() size = %d, want %d", size, tt.wantParams.size) } return tt.generateKey() } got, err := k.CreateKey(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("SoftKMS.CreateKey() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("SoftKMS.CreateKey() = %v, want %v", got, tt.want) } }) } } func TestSoftKMS_GetPublicKey(t *testing.T) { b, err := os.ReadFile("testdata/pub.pem") if err != nil { t.Fatal(err) } block, _ := pem.Decode(b) pub, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { t.Fatal(err) } type args struct { req *apiv1.GetPublicKeyRequest } tests := []struct { name string args args want crypto.PublicKey wantErr bool }{ {"key", args{&apiv1.GetPublicKeyRequest{Name: "testdata/pub.pem"}}, pub, false}, {"cert", args{&apiv1.GetPublicKeyRequest{Name: "testdata/cert.crt"}}, pub, false}, {"fail not exists", args{&apiv1.GetPublicKeyRequest{Name: "testdata/missing"}}, nil, true}, {"fail type", args{&apiv1.GetPublicKeyRequest{Name: "testdata/cert.key"}}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &SoftKMS{} got, err := k.GetPublicKey(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("SoftKMS.GetPublicKey() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("SoftKMS.GetPublicKey() = %v, want %v", got, tt.want) } }) } } func Test_generateKey(t *testing.T) { type args struct { kty string crv string size int } tests := []struct { name string args args wantType interface{} wantType1 interface{} wantErr bool }{ {"rsa2048", args{"RSA", "", 0}, &rsa.PublicKey{}, &rsa.PrivateKey{}, false}, {"rsa2048", args{"RSA", "", 2048}, &rsa.PublicKey{}, &rsa.PrivateKey{}, false}, {"p256", args{"EC", "P-256", 0}, &ecdsa.PublicKey{}, &ecdsa.PrivateKey{}, false}, {"ed25519", args{"OKP", "Ed25519", 0}, ed25519.PublicKey{}, ed25519.PrivateKey{}, false}, {"fail kty", args{"FOO", "", 0}, nil, nil, true}, {"fail crv", args{"EC", "P-123", 0}, nil, nil, true}, {"fail size", args{"RSA", "", 1}, nil, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, got1, err := generateKey(tt.args.kty, tt.args.crv, tt.args.size) if (err != nil) != tt.wantErr { t.Errorf("generateKey() error = %v, wantErr %v", err, tt.wantErr) return } if reflect.TypeOf(got) != reflect.TypeOf(tt.wantType) { t.Errorf("generateKey() got = %T, want %T", got, tt.wantType) } if reflect.TypeOf(got1) != reflect.TypeOf(tt.wantType1) { t.Errorf("generateKey() got1 = %T, want %T", got1, tt.wantType1) } }) } } func TestSoftKMS_CreateDecrypter(t *testing.T) { privateKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { t.Fatal(err) } pemBlock, err := pemutil.Serialize(privateKey) if err != nil { t.Fatal(err) } pemBlockPassword, err := pemutil.Serialize(privateKey, pemutil.WithPassword([]byte("pass"))) if err != nil { t.Fatal(err) } ecdsaPK, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatal(err) } ecdsaPemBlock, err := pemutil.Serialize(ecdsaPK) if err != nil { t.Fatal(err) } b, err := os.ReadFile("testdata/rsa.priv.pem") if err != nil { t.Fatal(err) } block, _ := pem.Decode(b) block.Bytes, err = x509.DecryptPEMBlock(block, []byte("pass")) //nolint if err != nil { t.Fatal(err) } keyFromFile, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { t.Fatal(err) } type args struct { req *apiv1.CreateDecrypterRequest } tests := []struct { name string args args want crypto.Decrypter wantErr bool }{ {"decrypter", args{&apiv1.CreateDecrypterRequest{Decrypter: privateKey}}, privateKey, false}, {"file", args{&apiv1.CreateDecrypterRequest{DecryptionKey: "testdata/rsa.priv.pem", Password: []byte("pass")}}, keyFromFile, false}, {"pem", args{&apiv1.CreateDecrypterRequest{DecryptionKeyPEM: pem.EncodeToMemory(pemBlock)}}, privateKey, false}, {"pem password", args{&apiv1.CreateDecrypterRequest{DecryptionKeyPEM: pem.EncodeToMemory(pemBlockPassword), Password: []byte("pass")}}, privateKey, false}, {"fail none", args{&apiv1.CreateDecrypterRequest{}}, nil, true}, {"fail missing", args{&apiv1.CreateDecrypterRequest{DecryptionKey: "testdata/missing"}}, nil, true}, {"fail bad pem", args{&apiv1.CreateDecrypterRequest{DecryptionKeyPEM: []byte("bad pem")}}, nil, true}, {"fail bad password", args{&apiv1.CreateDecrypterRequest{DecryptionKeyPEM: pem.EncodeToMemory(pemBlockPassword), Password: []byte("bad-pass")}}, nil, true}, {"fail not a decrypter (ecdsa key)", args{&apiv1.CreateDecrypterRequest{DecryptionKeyPEM: pem.EncodeToMemory(ecdsaPemBlock)}}, nil, true}, {"fail not a decrypter from file", args{&apiv1.CreateDecrypterRequest{DecryptionKey: "testdata/rsa.pub.pem"}}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &SoftKMS{} got, err := k.CreateDecrypter(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("SoftKMS.CreateDecrypter(), error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("SoftKMS.CreateDecrypter() = %v, want %v", got, tt.want) } }) } } golang-step-crypto-0.24.0/kms/softkms/testdata/000077500000000000000000000000001437037643100214345ustar00rootroot00000000000000golang-step-crypto-0.24.0/kms/softkms/testdata/cert.crt000066400000000000000000000011731437037643100231050ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBpzCCAU2gAwIBAgIQWaY8KIDAfak8aYljelf8eTAKBggqhkjOPQQDAjAdMRsw GQYDVQQDExJ0ZXN0LnNtYWxsc3RlcC5jb20wHhcNMjAwMTE2MDAwNDU4WhcNMjAw MTE3MDAwNDU4WjAdMRswGQYDVQQDExJ0ZXN0LnNtYWxsc3RlcC5jb20wWTATBgcq hkjOPQIBBggqhkjOPQMBBwNCAATlU8P9blFefSWuzYx2g215NJn6yHW95PXeFqQ9 kX1jNo1VmC6Oord3We37iM8QJT4QP9ZDUaAVmJUZSjd+W8H/o28wbTAOBgNVHQ8B Af8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQW BBTn0wonKkm2lLRNYZrKhUukiynvqzAdBgNVHREEFjAUghJ0ZXN0LnNtYWxsc3Rl cC5jb20wCgYIKoZIzj0EAwIDSAAwRQIhAJ5XqryBIY1X4fl/9l0isV69eQfA0Qo5 1mjervUcEnOWAiBsmN4frz5YVw7i4UXChVBeZLZfJOKvn5eyh2gEzoq1+w== -----END CERTIFICATE----- golang-step-crypto-0.24.0/kms/softkms/testdata/cert.key000066400000000000000000000003431437037643100231030ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MHcCAQEEICB6lIrMa9fVQJtdAYS4qmdYQ1BHJsEQDx8zxL38gA8toAoGCCqGSM49 AwEHoUQDQgAE5VPD/W5RXn0lrs2MdoNteTSZ+sh1veT13hakPZF9YzaNVZgujqK3 d1nt+4jPECU+ED/WQ1GgFZiVGUo3flvB/w== -----END EC PRIVATE KEY----- golang-step-crypto-0.24.0/kms/softkms/testdata/priv.pem000066400000000000000000000004721437037643100231220ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-256-CBC,1fcec5dfbf3327f61bfe5ab6ae8a0626 V39b/pNHMbP80TXSHLsUY6UOTCzf3KwIxvj1e7S9brNMJJc9b3UiloMBJIYBkl00 NKI8JU4jSlcerR58DqsTHIELiX6a+RJLe3/iR2/5Gru+CmmWJ68jQu872WCgh6Ms o8TzhyGx74ETmdKn5CdtylsnKMa9heW3tBLFAbNCgKc= -----END EC PRIVATE KEY----- golang-step-crypto-0.24.0/kms/softkms/testdata/pub.pem000066400000000000000000000002621437037643100227250ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5VPD/W5RXn0lrs2MdoNteTSZ+sh1 veT13hakPZF9YzaNVZgujqK3d1nt+4jPECU+ED/WQ1GgFZiVGUo3flvB/w== -----END PUBLIC KEY----- golang-step-crypto-0.24.0/kms/softkms/testdata/rsa.priv.pem000066400000000000000000000033461437037643100237110ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-256-CBC,dff7bfd0e0163a4cd7ade8f68b966699 jtmOhr2zo244Oq2fVsShZAUoQZ1gi6Iwc4i0sReU66XP9CFkdvJasAfkjQGrbCEy m2+r7W6aH+L3j/4sXcJe8h4UVnnC4DHCozmtqqFCq7cFS4TiVpco26wEVH5WLm7Y 3Ew/pL0k24E+Ycf+yV5c1tQXRlmsKubjwzrZtGZP2yn3Dxsu97mzOXAfx7r+DIKI 5a4S3m1/yXw76tt6Iho9h4huA25UUDHKUQvOGd5gmOKqJRV9djoyu85ODbmz5nt0 pB2EzdHOrefgd0rcQQPI1uFBWqASJxTn+uS7ZBP4rlCcs932lI1mPerMh1ujo51F 3aibrwhKE6kaJyOOnUbvyBnaiTb5i4WwTqx/jfsOsggXQb3UlxgDph48VXw8O2jF CQmle+TR8yr1A14/Dno5Dd4cqPv6AmWWU2zolvLxKQixFcvjsyQYCDajWWRPkOgj RTKXDqL1mpjrlDqcSXzemCWk6FzqdUQhimhFgARDRfRwwDeWQN5ua4a3gnem/cpA ZS8J45H0ZC/CxGPfp+qx75n5a875+n4VMmCZerXPzEIj1CzS7D6BVAXTHJaNIB6S 0WNfQnftp09O2l6iXBE+MHt5bVxqt46+vgcceSu7Gsb3ZfD79vnQ7tR+wb+xmHKk 8rVcMrB+kDRXVguH/a3zUGYAEnb6hPkIJywJVD4G65oM+D9D67Mdka8wIMK48doV my8a0MfT/9AidR6XJVxIkHlPsPzlxirm/NKF7oSlzurcvYcPAYnHYLW2uB8dyidq 1zB+3rxbSYCVqrhqzN4prydGvkIE3/+AJyIGn7uGSTSSyF6BC9APXQaHplRGKwLz efOIMoEwXJ1DIcKmk9GB65xxrZxMu3Cclcbc4PgY4370G0PfCHuUQNQL2RUWCQn0 aax+qDiFg1LsLRaI75OaLJ+uKs6rRfytQMmFGqK/b6iVbktiYWMtrDJDo4OUTtZ6 LBBySH7sAFgI3IIxct2Fwg8X1J4kfHr9jWTLjMEIE2o8cyqvSQ8rdwA25MxRcn75 DGqSlGE6Sx0XhWCVUiZidVRSYGKmOmH9yw8cjKm17qL23t8Gwns4Xunl7V6YlTCG BPw5f1jWCQ94TwvUSuHMPYoXlYwRoe+jfDAzp2AQwXqvWX5Qno5PKz9gQ5iYacZ/ k82fyPbk2XLDkPnaNJKnyiIc252O0WffUlX6Rlv3aF8ZgVvWfZbuHEK6g1W+IKSA pXAQ+iZBl+fjs/wT0yZSNTB0P1InD9Ve536L94gxXoeMr6F0Eouk3J2R9qdFp0Av 31xylRKSmzUf87/sRxjy3FzSTjIal77y1euJoAEU/nShmNrAZ6B8wnlvHfVwbgmt xWqxYIi/j/C8Led9uhEhX2WjPsO7ckGA41Tw6hZk/5hr4jmPoZQKHf9OauJFujMh ybPRQ6SGZJaYQAgpEGHSHFm8lwf5/DcezdSMdzqAKBWJBv6MediMuS60wcJ0Tebk rdLkNE4bsxfc889BkXBrSqfd+Auu5RcF/kF44gLL7oj4ojQyV44vLZbC4+liGThT bhayYGV64hsY+zL03u5wVfF1Y+33/uc8o/0JjbfuW5AIdikVES/jnKKFXSTMNL69 -----END RSA PRIVATE KEY----- golang-step-crypto-0.24.0/kms/softkms/testdata/rsa.pub.pem000066400000000000000000000007031437037643100235110ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn2Oh7/uWB5RH40la1a43 IRaLZ8EnJVw5DCKE3BUre8xflVY2wTIS7XHcY0fEGprtq7hzFKors9AIGGn2yGrf bZX2I+1g+RtQ6cLL6koeLuhRDqCuae0lZPulWc5ixBmM9mpl4ARRcpQFldxFRhis xUaHMx8VqdZjFSDc5CJHYYK1n2G5DyuzJCk6yOfyMpwxizZJF4IUyqV7zKmZv1z9 /Xd8X0ag7jRdaTBpupJ1WLaq7LlvyB4nr47JXXkLFbRIL1F/gTcPtg0tdEZiKnxs VLKwOs3VjhEorUwhmVxr4NnNX/0tuOY1FJ0mx5jKLAevqLVwK2JIg/f3h7JcNxDy tQIDAQAB -----END PUBLIC KEY----- golang-step-crypto-0.24.0/kms/sshagentkms/000077500000000000000000000000001437037643100204645ustar00rootroot00000000000000golang-step-crypto-0.24.0/kms/sshagentkms/no_sshagentkms.go000066400000000000000000000007111437037643100240350ustar00rootroot00000000000000//go:build nosshagentkms // +build nosshagentkms package sshagentkms import ( "context" "os" "path/filepath" "github.com/pkg/errors" "go.step.sm/crypto/kms/apiv1" ) func init() { apiv1.Register(apiv1.SSHAgentKMS, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) { name := filepath.Base(os.Args[0]) return nil, errors.Errorf("unsupported kms type 'sshagentkms': %s is compiled without SSH Agent KMS support", name) }) } golang-step-crypto-0.24.0/kms/sshagentkms/sshagentkms.go000066400000000000000000000137411437037643100233500ustar00rootroot00000000000000//go:build !nosshagentkms // +build !nosshagentkms package sshagentkms import ( "context" "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" "crypto/x509" "io" "net" "os" "strings" "sync" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" "github.com/pkg/errors" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/sshutil" "go.step.sm/crypto/pemutil" ) // SSHAgentKMS is a key manager that uses keys provided by ssh-agent type SSHAgentKMS struct { agentClient agent.Agent } // New returns a new SSHAgentKMS. func New(ctx context.Context, opts apiv1.Options) (*SSHAgentKMS, error) { socket := os.Getenv("SSH_AUTH_SOCK") conn, err := net.Dial("unix", socket) if err != nil { return nil, errors.Wrap(err, "failed to open SSH_AUTH_SOCK") } agentClient := agent.NewClient(conn) return &SSHAgentKMS{ agentClient: agentClient, }, nil } // NewFromAgent initializes an SSHAgentKMS from a given agent, this method is // used for testing purposes. func NewFromAgent(ctx context.Context, opts apiv1.Options, agentClient agent.Agent) (*SSHAgentKMS, error) { return &SSHAgentKMS{ agentClient: agentClient, }, nil } func init() { apiv1.Register(apiv1.SSHAgentKMS, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) { return New(ctx, opts) }) } // Close closes the agent. This is a noop for the SSHAgentKMS. func (k *SSHAgentKMS) Close() error { return nil } // WrappedSSHSigner is a utility type to wrap a ssh.Signer as a crypto.Signer type WrappedSSHSigner struct { Signer ssh.Signer m sync.RWMutex lastSignature *ssh.Signature } // LastSignature returns the ssh.Signature in the last sign operation if any. func (s *WrappedSSHSigner) LastSignature() *ssh.Signature { s.m.RLock() defer s.m.RUnlock() return s.lastSignature } // Public returns the agent public key. The type of this public key is // *agent.Key. func (s *WrappedSSHSigner) Public() crypto.PublicKey { return s.Signer.PublicKey() } // Sign signs the given digest using the ssh agent and returns the signature. // Note that because of the way an SSH agent and x509.CreateCertificate works, // this signer can only properly sign X509 certificates if the key type is // Ed25519. func (s *WrappedSSHSigner) Sign(rand io.Reader, data []byte, opts crypto.SignerOpts) (signature []byte, err error) { if signer, ok := s.Signer.(interface { SignWithOpts(io.Reader, []byte, crypto.SignerOpts) (*ssh.Signature, error) }); ok { sig, err := signer.SignWithOpts(rand, data, opts) if err != nil { return nil, err } s.m.Lock() s.lastSignature = sig s.m.Unlock() return sig.Blob, nil } sig, err := s.Signer.Sign(rand, data) if err != nil { return nil, err } s.m.Lock() s.lastSignature = sig s.m.Unlock() return sig.Blob, nil } // NewWrappedSignerFromSSHSigner returns a new crypto signer wrapping the given // one. func NewWrappedSignerFromSSHSigner(signer ssh.Signer) crypto.Signer { return &WrappedSSHSigner{Signer: signer} } func (k *SSHAgentKMS) findKey(signingKey string) (target int, err error) { if strings.HasPrefix(signingKey, "sshagentkms:") { var key = strings.TrimPrefix(signingKey, "sshagentkms:") l, err := k.agentClient.List() if err != nil { return -1, err } for i, s := range l { if s.Comment == key { return i, nil } } } return -1, errors.Errorf("SSHAgentKMS couldn't find %s", signingKey) } // CreateSigner returns a new signer configured with the given signing key. Note // that because of the way an SSH agent and x509.CreateCertificate works, this // signer can only properly sign X509 certificates if the key type is Ed25519. func (k *SSHAgentKMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) { if req.Signer != nil { return req.Signer, nil } if strings.HasPrefix(req.SigningKey, "sshagentkms:") { target, err := k.findKey(req.SigningKey) if err != nil { return nil, err } s, err := k.agentClient.Signers() if err != nil { return nil, err } return NewWrappedSignerFromSSHSigner(s[target]), nil } // OK: We don't actually care about non-ssh certificates, // but we can't disable it in step-ca so this code is copy-pasted from // softkms just to keep step-ca happy. var opts []pemutil.Options if req.Password != nil { opts = append(opts, pemutil.WithPassword(req.Password)) } switch { case len(req.SigningKeyPEM) != 0: v, err := pemutil.ParseKey(req.SigningKeyPEM, opts...) if err != nil { return nil, err } sig, ok := v.(crypto.Signer) if !ok { return nil, errors.New("signingKeyPEM is not a crypto.Signer") } return sig, nil case req.SigningKey != "": v, err := pemutil.Read(req.SigningKey, opts...) if err != nil { return nil, err } sig, ok := v.(crypto.Signer) if !ok { return nil, errors.New("signingKey is not a crypto.Signer") } return sig, nil default: return nil, errors.New("failed to load softKMS: please define signingKeyPEM or signingKey") } } // CreateKey generates a new key and returns both public and private key. func (k *SSHAgentKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) { return nil, errors.Errorf("SSHAgentKMS doesn't support generating keys") } // GetPublicKey returns the public key from the file passed in the request name. func (k *SSHAgentKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) { var pub crypto.PublicKey if strings.HasPrefix(req.Name, "sshagentkms:") { target, err := k.findKey(req.Name) if err != nil { return nil, err } s, err := k.agentClient.Signers() if err != nil { return nil, err } sshPub := s[target].PublicKey() pub, err = sshutil.CryptoPublicKey(sshPub) if err != nil { return nil, err } } else { var err error pub, err = pemutil.Read(req.Name) if err != nil { return nil, err } } switch pk := pub.(type) { case *x509.Certificate: return pk.PublicKey, nil case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey: return pk, nil default: return nil, errors.Errorf("unsupported public key type %T", pk) } } golang-step-crypto-0.24.0/kms/sshagentkms/sshagentkms_test.go000066400000000000000000000400261437037643100244030ustar00rootroot00000000000000package sshagentkms import ( "bytes" "context" "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/x509" "encoding/pem" "net" "os" "os/exec" "path/filepath" "reflect" "strconv" "strings" "testing" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/pemutil" "go.step.sm/crypto/randutil" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" ) // Some helpers with inspiration from crypto/ssh/agent/client_test.go // startOpenSSHAgent executes ssh-agent, and returns an Agent interface to it. func startOpenSSHAgent(t *testing.T) (client agent.Agent, socket string, cleanup func()) { /* Always test with OpenSSHAgent if testing.Short() { // ssh-agent is not always available, and the key // types supported vary by platform. t.Skip("skipping test due to -short") } */ bin, err := exec.LookPath("ssh-agent") if err != nil { t.Skip("could not find ssh-agent") } cmd := exec.Command(bin, "-s") cmd.Env = []string{} // Do not let the user's environment influence ssh-agent behavior. cmd.Stderr = new(bytes.Buffer) out, err := cmd.Output() if err != nil { t.Fatalf("%s failed: %v\n%s", strings.Join(cmd.Args, " "), err, cmd.Stderr) } // Output looks like: // // SSH_AUTH_SOCK=/tmp/ssh-P65gpcqArqvH/agent.15541; export SSH_AUTH_SOCK; // SSH_AGENT_PID=15542; export SSH_AGENT_PID; // echo Agent pid 15542; fields := bytes.Split(out, []byte(";")) line := bytes.SplitN(fields[0], []byte("="), 2) line[0] = bytes.TrimLeft(line[0], "\n") if string(line[0]) != "SSH_AUTH_SOCK" { t.Fatalf("could not find key SSH_AUTH_SOCK in %q", fields[0]) } socket = string(line[1]) line = bytes.SplitN(fields[2], []byte("="), 2) line[0] = bytes.TrimLeft(line[0], "\n") if string(line[0]) != "SSH_AGENT_PID" { t.Fatalf("could not find key SSH_AGENT_PID in %q", fields[2]) } pidStr := line[1] pid, err := strconv.Atoi(string(pidStr)) if err != nil { t.Fatalf("Atoi(%q): %v", pidStr, err) } conn, err := net.Dial("unix", socket) if err != nil { t.Fatalf("net.Dial: %v", err) } ac := agent.NewClient(conn) return ac, socket, func() { proc, _ := os.FindProcess(pid) if proc != nil { proc.Kill() } conn.Close() os.RemoveAll(filepath.Dir(socket)) } } func startAgent(t *testing.T, sshagent agent.Agent) (client agent.Agent, cleanup func()) { c1, c2, err := netPipe() if err != nil { t.Fatalf("netPipe: %v", err) } go agent.ServeAgent(sshagent, c2) return agent.NewClient(c1), func() { c1.Close() c2.Close() } } // startKeyringAgent uses Keyring to simulate a ssh-agent Server and returns a client. func startKeyringAgent(t *testing.T) (client agent.Agent, cleanup func()) { return startAgent(t, agent.NewKeyring()) } type startTestAgentFunc func(t *testing.T, keysToAdd ...agent.AddedKey) (sshagent agent.Agent) func startTestOpenSSHAgent(t *testing.T, keysToAdd ...agent.AddedKey) (sshagent agent.Agent) { sshagent, _, cleanup := startOpenSSHAgent(t) for _, keyToAdd := range keysToAdd { err := sshagent.Add(keyToAdd) if err != nil { t.Fatalf("sshagent.add: %v", err) } } t.Cleanup(cleanup) //testAgentInterface(t, sshagent, key, cert, lifetimeSecs) return sshagent } func startTestKeyringAgent(t *testing.T, keysToAdd ...agent.AddedKey) (sshagent agent.Agent) { sshagent, cleanup := startKeyringAgent(t) for _, keyToAdd := range keysToAdd { err := sshagent.Add(keyToAdd) if err != nil { t.Fatalf("sshagent.add: %v", err) } } t.Cleanup(cleanup) //testAgentInterface(t, agent, key, cert, lifetimeSecs) return sshagent } // netPipe is analogous to net.Pipe, but it uses a real net.Conn, and // therefore is buffered (net.Pipe deadlocks if both sides start with // a write.) func netPipe() (net.Conn, net.Conn, error) { listener, err := netListener() if err != nil { return nil, nil, err } defer listener.Close() c1, err := net.Dial("tcp", listener.Addr().String()) if err != nil { return nil, nil, err } c2, err := listener.Accept() if err != nil { c1.Close() return nil, nil, err } return c1, c2, nil } // netListener creates a localhost network listener. func netListener() (net.Listener, error) { listener, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { listener, err = net.Listen("tcp", "[::1]:0") if err != nil { return nil, err } } return listener, nil } func TestNew(t *testing.T) { comment := "Key from OpenSSHAgent" // Ensure we don't "inherit" any SSH_AUTH_SOCK os.Unsetenv("SSH_AUTH_SOCK") sshagent, socket, cleanup := startOpenSSHAgent(t) t.Setenv("SSH_AUTH_SOCK", socket) t.Cleanup(func() { os.Unsetenv("SSH_AUTH_SOCK") cleanup() }) // Test that we can't find any signers in the agent before we have loaded them t.Run("No keys with OpenSSHAgent", func(t *testing.T) { kms, err := New(context.Background(), apiv1.Options{}) if kms == nil || err != nil { t.Errorf("New() = %v, %v", kms, err) } signer, err := kms.CreateSigner(&apiv1.CreateSignerRequest{SigningKey: "sshagentkms:" + comment}) if err == nil || signer != nil { t.Errorf("SSHAgentKMS.CreateSigner() error = \"%v\", signer = \"%v\"", err, signer) } }) // Load ssh test fixtures b, err := os.ReadFile("testdata/ssh") if err != nil { t.Fatal(err) } privateKey, err := ssh.ParseRawPrivateKey(b) if err != nil { t.Fatal(err) } // And add that key to the agent err = sshagent.Add(agent.AddedKey{PrivateKey: privateKey, Comment: comment}) if err != nil { t.Fatalf("sshagent.add: %v", err) } // And test that we can find it when it's loaded t.Run("Keys with OpenSSHAgent", func(t *testing.T) { kms, err := New(context.Background(), apiv1.Options{}) if kms == nil || err != nil { t.Errorf("New() = %v, %v", kms, err) } signer, err := kms.CreateSigner(&apiv1.CreateSignerRequest{SigningKey: "sshagentkms:" + comment}) if err != nil || signer == nil { t.Errorf("SSHAgentKMS.CreateSigner() error = \"%v\", signer = \"%v\"", err, signer) } }) } func TestNewFromAgent(t *testing.T) { type args struct { ctx context.Context opts apiv1.Options } tests := []struct { name string args args sshagentstarter startTestAgentFunc wantErr bool }{ {"ok OpenSSHAgent", args{context.Background(), apiv1.Options{}}, startTestOpenSSHAgent, false}, {"ok KeyringAgent", args{context.Background(), apiv1.Options{}}, startTestKeyringAgent, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := NewFromAgent(tt.args.ctx, tt.args.opts, tt.sshagentstarter(t)) if (err != nil) != tt.wantErr { t.Errorf("NewFromAgent() error = %v, wantErr %v", err, tt.wantErr) return } if got == nil { t.Errorf("NewFromAgent() = %v", got) } }) } } func TestSSHAgentKMS_Close(t *testing.T) { tests := []struct { name string wantErr bool }{ {"ok", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &SSHAgentKMS{} if err := k.Close(); (err != nil) != tt.wantErr { t.Errorf("SSHAgentKMS.Close() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestSSHAgentKMS_CreateSigner(t *testing.T) { pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatal(err) } pemBlock, err := pemutil.Serialize(pk) if err != nil { t.Fatal(err) } pemBlockPassword, err := pemutil.Serialize(pk, pemutil.WithPassword([]byte("pass"))) if err != nil { t.Fatal(err) } // Read and decode file using standard packages b, err := os.ReadFile("testdata/priv.pem") if err != nil { t.Fatal(err) } block, _ := pem.Decode(b) block.Bytes, err = x509.DecryptPEMBlock(block, []byte("pass")) //nolint if err != nil { t.Fatal(err) } pk2, err := x509.ParseECPrivateKey(block.Bytes) if err != nil { t.Fatal(err) } // Create a public PEM b, err = x509.MarshalPKIXPublicKey(pk.Public()) if err != nil { t.Fatal(err) } pub := pem.EncodeToMemory(&pem.Block{ Type: "PUBLIC KEY", Bytes: b, }) // Load ssh test fixtures sshPubKeyStr, err := os.ReadFile("testdata/ssh.pub") if err != nil { t.Fatal(err) } _, comment, _, _, err := ssh.ParseAuthorizedKey(sshPubKeyStr) if err != nil { t.Fatal(err) } b, err = os.ReadFile("testdata/ssh") if err != nil { t.Fatal(err) } privateKey, err := ssh.ParseRawPrivateKey(b) if err != nil { t.Fatal(err) } sshPrivateKey, err := ssh.NewSignerFromKey(privateKey) if err != nil { t.Fatal(err) } wrappedSSHPrivateKey := NewWrappedSignerFromSSHSigner(sshPrivateKey) type args struct { req *apiv1.CreateSignerRequest } tests := []struct { name string args args want crypto.Signer wantErr bool }{ {"signer", args{&apiv1.CreateSignerRequest{Signer: pk}}, pk, false}, {"pem", args{&apiv1.CreateSignerRequest{SigningKeyPEM: pem.EncodeToMemory(pemBlock)}}, pk, false}, {"pem password", args{&apiv1.CreateSignerRequest{SigningKeyPEM: pem.EncodeToMemory(pemBlockPassword), Password: []byte("pass")}}, pk, false}, {"file", args{&apiv1.CreateSignerRequest{SigningKey: "testdata/priv.pem", Password: []byte("pass")}}, pk2, false}, {"sshagent", args{&apiv1.CreateSignerRequest{SigningKey: "sshagentkms:" + comment}}, wrappedSSHPrivateKey, false}, {"sshagent Nonexistant", args{&apiv1.CreateSignerRequest{SigningKey: "sshagentkms:Nonexistant"}}, nil, true}, {"fail", args{&apiv1.CreateSignerRequest{}}, nil, true}, {"fail bad pem", args{&apiv1.CreateSignerRequest{SigningKeyPEM: []byte("bad pem")}}, nil, true}, {"fail bad password", args{&apiv1.CreateSignerRequest{SigningKey: "testdata/priv.pem", Password: []byte("bad-pass")}}, nil, true}, {"fail not a signer", args{&apiv1.CreateSignerRequest{SigningKeyPEM: pub}}, nil, true}, {"fail not a signer from file", args{&apiv1.CreateSignerRequest{SigningKey: "testdata/pub.pem"}}, nil, true}, {"fail missing", args{&apiv1.CreateSignerRequest{SigningKey: "testdata/missing"}}, nil, true}, } starters := []struct { name string starter startTestAgentFunc }{ {"startTestOpenSSHAgent", startTestOpenSSHAgent}, {"startTestKeyringAgent", startTestKeyringAgent}, } for _, starter := range starters { k, err := NewFromAgent(context.Background(), apiv1.Options{}, starter.starter(t, agent.AddedKey{PrivateKey: privateKey, Comment: comment})) if err != nil { t.Fatal(err) } for _, tt := range tests { t.Run(starter.name+"/"+tt.name, func(t *testing.T) { got, err := k.CreateSigner(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("SSHAgentKMS.CreateSigner() error = %v, wantErr %v", err, tt.wantErr) return } // nolint:gocritic switch s := got.(type) { case *WrappedSSHSigner: gotPkS := s.Signer.PublicKey().(*agent.Key).String() + "\n" wantPkS := string(sshPubKeyStr) if !reflect.DeepEqual(gotPkS, wantPkS) { t.Errorf("SSHAgentKMS.CreateSigner() = %T, want %T", gotPkS, wantPkS) t.Errorf("SSHAgentKMS.CreateSigner() = %v, want %v", gotPkS, wantPkS) } default: if !reflect.DeepEqual(got, tt.want) { t.Errorf("SSHAgentKMS.CreateSigner() = %T, want %T", got, tt.want) t.Errorf("SSHAgentKMS.CreateSigner() = %v, want %v", got, tt.want) } } }) } } } func TestSSHAgentKMS_GetPublicKey(t *testing.T) { b, err := os.ReadFile("testdata/pub.pem") if err != nil { t.Fatal(err) } block, _ := pem.Decode(b) pub, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { t.Fatal(err) } // Load ssh test fixtures b, err = os.ReadFile("testdata/ssh.pub") if err != nil { t.Fatal(err) } sshPubKey, comment, _, _, err := ssh.ParseAuthorizedKey(b) if err != nil { t.Fatal(err) } b, err = os.ReadFile("testdata/ssh") if err != nil { t.Fatal(err) } // crypto.PrivateKey sshPrivateKey, err := ssh.ParseRawPrivateKey(b) if err != nil { t.Fatal(err) } type args struct { req *apiv1.GetPublicKeyRequest } tests := []struct { name string args args want crypto.PublicKey wantErr bool }{ {"key", args{&apiv1.GetPublicKeyRequest{Name: "testdata/pub.pem"}}, pub, false}, {"cert", args{&apiv1.GetPublicKeyRequest{Name: "testdata/cert.crt"}}, pub, false}, {"sshagent", args{&apiv1.GetPublicKeyRequest{Name: "sshagentkms:" + comment}}, sshPubKey, false}, {"sshagent Nonexistant", args{&apiv1.GetPublicKeyRequest{Name: "sshagentkms:Nonexistant"}}, nil, true}, {"fail not exists", args{&apiv1.GetPublicKeyRequest{Name: "testdata/missing"}}, nil, true}, {"fail type", args{&apiv1.GetPublicKeyRequest{Name: "testdata/cert.key"}}, nil, true}, } starters := []struct { name string starter startTestAgentFunc }{ {"startTestOpenSSHAgent", startTestOpenSSHAgent}, {"startTestKeyringAgent", startTestKeyringAgent}, } for _, starter := range starters { k, err := NewFromAgent(context.Background(), apiv1.Options{}, starter.starter(t, agent.AddedKey{PrivateKey: sshPrivateKey, Comment: comment})) if err != nil { t.Fatal(err) } for _, tt := range tests { t.Run(starter.name+"/"+tt.name, func(t *testing.T) { got, err := k.GetPublicKey(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("SSHAgentKMS.GetPublicKey() error = %v, wantErr %v", err, tt.wantErr) return } // nolint:gocritic switch tt.want.(type) { case ssh.PublicKey: // If we want a ssh.PublicKey, protote got to a got, err = ssh.NewPublicKey(got) if err != nil { t.Fatal(err) } } if !reflect.DeepEqual(got, tt.want) { t.Errorf("SSHAgentKMS.GetPublicKey() = %T, want %T", got, tt.want) t.Errorf("SSHAgentKMS.GetPublicKey() = %v, want %v", got, tt.want) } }) } } } func TestSSHAgentKMS_CreateKey(t *testing.T) { starters := []struct { name string starter startTestAgentFunc }{ {"startTestOpenSSHAgent", startTestOpenSSHAgent}, {"startTestKeyringAgent", startTestKeyringAgent}, } for _, starter := range starters { k, err := NewFromAgent(context.Background(), apiv1.Options{}, starter.starter(t)) if err != nil { t.Fatal(err) } t.Run(starter.name+"/CreateKey", func(t *testing.T) { got, err := k.CreateKey(&apiv1.CreateKeyRequest{ Name: "sshagentkms:0", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }) if got != nil { t.Error("SSHAgentKMS.CreateKey() shoudn't return a value") } if err == nil { t.Error("SSHAgentKMS.CreateKey() didn't return a value") } }) } } func TestWrappedSSHSigner(t *testing.T) { pub, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } sshSigner, err := ssh.NewSignerFromSigner(priv) if err != nil { t.Fatal(err) } message, err := randutil.Salt(128) if err != nil { t.Fatal(err) } ws := NewWrappedSignerFromSSHSigner(sshSigner) if !reflect.DeepEqual(ws.Public(), sshSigner.PublicKey()) { t.Errorf("WrappedSigner.Public() = %v, want %v", ws.Public(), sshSigner.PublicKey()) } sig, err := ws.Sign(rand.Reader, message, crypto.Hash(0)) if err != nil { t.Errorf("WrappedSigner.Public() error = %v", err) } if !ed25519.Verify(pub, message, sig) { t.Error("ed25519.Verify() = false, want true") } sshSig := ws.(*WrappedSSHSigner).LastSignature() if err := sshSigner.PublicKey().Verify(message, sshSig); err != nil { t.Errorf("ssh.PublicKey.Verify() error = %v", err) } } func TestWrappedSSHSigner_agent(t *testing.T) { pub, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } sshSigner, err := ssh.NewSignerFromSigner(priv) if err != nil { t.Fatal(err) } message, err := randutil.Salt(128) if err != nil { t.Fatal(err) } sshAgent, err := NewFromAgent(context.Background(), apiv1.Options{}, startTestKeyringAgent(t, agent.AddedKey{PrivateKey: priv, Comment: "go-test-key"})) if err != nil { t.Errorf("NewFromAgent() error = %v", err) } signer, err := sshAgent.CreateSigner(&apiv1.CreateSignerRequest{ SigningKey: "sshagentkms:go-test-key", }) if err != nil { t.Errorf("SSHAgentKMS.CreateSigner() error = %v", err) } sig, err := signer.Sign(rand.Reader, message, crypto.Hash(0)) if err != nil { t.Errorf("WrappedSigner.Public() error = %v", err) } if !ed25519.Verify(pub, message, sig) { t.Error("ed25519.Verify() = false, want true") } sshSig := signer.(*WrappedSSHSigner).LastSignature() if err := sshSigner.PublicKey().Verify(message, sshSig); err != nil { t.Errorf("ssh.PublicKey.Verify() error = %v", err) } } golang-step-crypto-0.24.0/kms/sshagentkms/testdata/000077500000000000000000000000001437037643100222755ustar00rootroot00000000000000golang-step-crypto-0.24.0/kms/sshagentkms/testdata/cert.crt000066400000000000000000000011731437037643100237460ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBpzCCAU2gAwIBAgIQWaY8KIDAfak8aYljelf8eTAKBggqhkjOPQQDAjAdMRsw GQYDVQQDExJ0ZXN0LnNtYWxsc3RlcC5jb20wHhcNMjAwMTE2MDAwNDU4WhcNMjAw MTE3MDAwNDU4WjAdMRswGQYDVQQDExJ0ZXN0LnNtYWxsc3RlcC5jb20wWTATBgcq hkjOPQIBBggqhkjOPQMBBwNCAATlU8P9blFefSWuzYx2g215NJn6yHW95PXeFqQ9 kX1jNo1VmC6Oord3We37iM8QJT4QP9ZDUaAVmJUZSjd+W8H/o28wbTAOBgNVHQ8B Af8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQW BBTn0wonKkm2lLRNYZrKhUukiynvqzAdBgNVHREEFjAUghJ0ZXN0LnNtYWxsc3Rl cC5jb20wCgYIKoZIzj0EAwIDSAAwRQIhAJ5XqryBIY1X4fl/9l0isV69eQfA0Qo5 1mjervUcEnOWAiBsmN4frz5YVw7i4UXChVBeZLZfJOKvn5eyh2gEzoq1+w== -----END CERTIFICATE----- golang-step-crypto-0.24.0/kms/sshagentkms/testdata/cert.key000066400000000000000000000003431437037643100237440ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MHcCAQEEICB6lIrMa9fVQJtdAYS4qmdYQ1BHJsEQDx8zxL38gA8toAoGCCqGSM49 AwEHoUQDQgAE5VPD/W5RXn0lrs2MdoNteTSZ+sh1veT13hakPZF9YzaNVZgujqK3 d1nt+4jPECU+ED/WQ1GgFZiVGUo3flvB/w== -----END EC PRIVATE KEY----- golang-step-crypto-0.24.0/kms/sshagentkms/testdata/priv.pem000066400000000000000000000004721437037643100237630ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-256-CBC,1fcec5dfbf3327f61bfe5ab6ae8a0626 V39b/pNHMbP80TXSHLsUY6UOTCzf3KwIxvj1e7S9brNMJJc9b3UiloMBJIYBkl00 NKI8JU4jSlcerR58DqsTHIELiX6a+RJLe3/iR2/5Gru+CmmWJ68jQu872WCgh6Ms o8TzhyGx74ETmdKn5CdtylsnKMa9heW3tBLFAbNCgKc= -----END EC PRIVATE KEY----- golang-step-crypto-0.24.0/kms/sshagentkms/testdata/pub.pem000066400000000000000000000002621437037643100235660ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5VPD/W5RXn0lrs2MdoNteTSZ+sh1 veT13hakPZF9YzaNVZgujqK3d1nt+4jPECU+ED/WQ1GgFZiVGUo3flvB/w== -----END PUBLIC KEY----- golang-step-crypto-0.24.0/kms/sshagentkms/testdata/ssh000066400000000000000000000064751437037643100230310ustar00rootroot00000000000000-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAACFwAAAAdzc2gtcn NhAAAAAwEAAQAAAgEAth/d7zRDbv567o46KT6YYqC/EVdDpZ8m0rzIdroJL+RHVDXNQ1pU 3lrC9IWfkyjX+YwO9jHGbraJ+CgonAkl36mtLzNC4645QGS2/WdFqRR6mQCz7v4G6nOaFN SCeErMhg0fn4f7jdqXpd0hYozIpktRVNYcpi2RMmr8e/Kadr5EVQfbYZgdKIl1O6Ws9O3Q 1BhLGi9GipEstUTvjqxZzF7oUgWKH54j5eHNXdbFqKqnK8NNQmypNLYGDsTBQHG9zRs+o0 7C2foO9ddIO2OCarcBWZfGlY05k/ZhEmrEOONh2rSLhJwqw+EJgQeU0Poe/IqjFy7jnTRk i+tee2elBYVvHYPSofZaBmX7i21s8eBRl/ZiFx3ip6E3M54mXvKZ7SuA2qq/YW0IeKyJ5D SuL0+sRAyiSQ2Icsyb3YKv6LXojuJTmJ9Hg9v4+aOPxOQhvNfh3b7sIh/cmz1dq/babLyO ORrbHKDxIJME7VPMspmddV9wJgB4Gu1eWOiR/Cuv6jqYWTfiWJDIoqZRD5nF1tFqKtZ5iA qkflv4Kbo10tv6nTlXR6TWuPu2Z/pZpx+NN+7QxVUSlRgxb7RTVcHRvpgd0TNEXGduR8ar WVDlNewOmf5KFroW1IX/yR1OvE5RsDixxcX7Ne+uSlq9hooy9V/Ip0ffcF/Kg0NJoPwrnI MAAAdQrAxluqwMZboAAAAHc3NoLXJzYQAAAgEAth/d7zRDbv567o46KT6YYqC/EVdDpZ8m 0rzIdroJL+RHVDXNQ1pU3lrC9IWfkyjX+YwO9jHGbraJ+CgonAkl36mtLzNC4645QGS2/W dFqRR6mQCz7v4G6nOaFNSCeErMhg0fn4f7jdqXpd0hYozIpktRVNYcpi2RMmr8e/Kadr5E VQfbYZgdKIl1O6Ws9O3Q1BhLGi9GipEstUTvjqxZzF7oUgWKH54j5eHNXdbFqKqnK8NNQm ypNLYGDsTBQHG9zRs+o07C2foO9ddIO2OCarcBWZfGlY05k/ZhEmrEOONh2rSLhJwqw+EJ gQeU0Poe/IqjFy7jnTRki+tee2elBYVvHYPSofZaBmX7i21s8eBRl/ZiFx3ip6E3M54mXv KZ7SuA2qq/YW0IeKyJ5DSuL0+sRAyiSQ2Icsyb3YKv6LXojuJTmJ9Hg9v4+aOPxOQhvNfh 3b7sIh/cmz1dq/babLyOORrbHKDxIJME7VPMspmddV9wJgB4Gu1eWOiR/Cuv6jqYWTfiWJ DIoqZRD5nF1tFqKtZ5iAqkflv4Kbo10tv6nTlXR6TWuPu2Z/pZpx+NN+7QxVUSlRgxb7RT VcHRvpgd0TNEXGduR8arWVDlNewOmf5KFroW1IX/yR1OvE5RsDixxcX7Ne+uSlq9hooy9V /Ip0ffcF/Kg0NJoPwrnIMAAAADAQABAAACADQ4KONYQemGT+ssnqKKzxigbIhlVAEeA/yy omvgZZf0xTrw/jzMnr7umS2RTrLcKCjmLrgKh5HhBug/Y31x5gkeVojNEuXDY6kB97HqtX +IXqqWGAFzlroMkWZdlFc3YzMgeiu8yrTes1Kcd+EQ6ss7l0NS7P383L/vCxvi8MURQvh6 ez2dZubjmtiSZWgI9DKMEKSeX4SFoaML9AAdjNXbdJNoATWVm0djmgXI+f2liK80nWdpTo 7NjikX4y0+L6SqpigfAiGL4FQ++PgGTTOZ62or6YWh65twLl8ge8iv8bPKxqIsQNrPIHF9 of7VaKMSgTa5fAvsJNQ1lW6exiK1szJ+g+zrkHuOjDaEWyIZi24/xy6iDaT1sdcjTGPJAo WqgC9hlZQKjOOZJgwqu/kxgcsOGaGb2MD/E4xJVMvPsWYLQ5WGdiakQkVhclpcr3e0d8nw xvqCqLsasCSECKJK+k3ReqtOe6GlTSzIpFiOgFAuYp+ejRkX6bJ2DRaYkjoWWza2VCpIJC uyK7B3r1cV+g5KzvT6B+7TxVqYERisjWNvdppF87Vtx7C0p8mDzpJYpPY+yao3vEcq104+ yXuaPGEDTkTWOUB2uUS+AD9CBjkrGYFab1DBJob+L/7jNgVgWmMw1Yj9SDwXO6YBfbkhCf Irfmf9Ne5i1+2SpFWBAAABAQCud97O9xI2bMGVGfbDFiaPTYGaGZ0qurLtHPpCX/YFkdBh Z3LG7psJ/4JhkmMI3RFGhMxpUR9K22T3P/UmUt01PrDwDUpcw1JRPVIGs9AV3+GsAyyE6X MzYo+8LNcxaPjh6ECXAQLcd9g0NOCbiqrKURBEuIBkxTy8jsmmeUlDsLcs8QKCsObJ2ozO ACuFG5Z/SUeB7nhHnRUnozE8KsEWAgpys37AnJc1cQR6ALloh23L46rsWbSN5UGRgZdaUo tklsDRun3qtYkDC8dDbW2Iy5A7GUXBRIA3mDYf4GDEUQvuu5Q/A2Dsr0hVi2wNVWd5O5M0 NVhuCHJU355wbbUUAAABAQDuet4GZQImmqfj2xAMoHUfSK0WagtzynP2fOSIRtOKQ9UXJN J1CrSeu93dNACYjXt10X5ZCdZ9x/75ltyZHSUBbT1eQzPD4Jq23EcJ9ECCc4tJMpdNpJyv 8ixfeTCX0m6XP7nDDLgkuYuNTj/NTqIWotHt8/R8BA9FfTchZE+ekqj3TTIac3buU294mO /0KKGHtt+GPHSD+ES+W28KETiFcz5nSD7oUQPXEbvsJg5bOWt9kY6JBGiizJSsEuLIjcva H3UQMx6U805NjoGwIiKJyKgcmDMWVbeH87XxV6sllE8UaLUxbcOBdhmF/uJlazQsbqmF7B CJB/X7SXredw9BAAABAQDDgRzgXsvBH72PMetQpWGswXp6UVsdHUUEyDiJXc5xjiVOxAIw +pwaBRQ/6WMMJvhpZ/IFN+pAYEW5e0q2eGMpc1or4kf5eTukwJSF6VZf1Hhti6TfiStPCf KSz07jUFROahMC88BOSwHuCc66emWlsZDrXS+pht1O7yU96epTM/hT/e8Bfi+ZFCJnQoQ5 dZuONhOYUT32rFKGBwPhsi6pjMB54vqrW1xFJbwj4i4dHFzA7UUa79j7ToAs2g2q8odTCR CLUxGJ+YOkti67taOuRbzlL9wlxLGT+G2Dai9Ymbt18rmXR+2vazE0xFigYHPZb2QXeLAS u104cC7ouX7DAAAAFnNzaC50ZXN0LnNtYWxsc3RlcC5jb20BAgME -----END OPENSSH PRIVATE KEY----- golang-step-crypto-0.24.0/kms/sshagentkms/testdata/ssh.pub000066400000000000000000000013541437037643100236050ustar00rootroot00000000000000ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC2H93vNENu/nrujjopPphioL8RV0OlnybSvMh2ugkv5EdUNc1DWlTeWsL0hZ+TKNf5jA72McZuton4KCicCSXfqa0vM0LjrjlAZLb9Z0WpFHqZALPu/gbqc5oU1IJ4SsyGDR+fh/uN2pel3SFijMimS1FU1hymLZEyavx78pp2vkRVB9thmB0oiXU7paz07dDUGEsaL0aKkSy1RO+OrFnMXuhSBYofniPl4c1d1sWoqqcrw01CbKk0tgYOxMFAcb3NGz6jTsLZ+g7110g7Y4JqtwFZl8aVjTmT9mESasQ442HatIuEnCrD4QmBB5TQ+h78iqMXLuOdNGSL6157Z6UFhW8dg9Kh9loGZfuLbWzx4FGX9mIXHeKnoTczniZe8pntK4Daqr9hbQh4rInkNK4vT6xEDKJJDYhyzJvdgq/oteiO4lOYn0eD2/j5o4/E5CG81+HdvuwiH9ybPV2r9tpsvI45GtscoPEgkwTtU8yymZ11X3AmAHga7V5Y6JH8K6/qOphZN+JYkMiiplEPmcXW0Woq1nmICqR+W/gpujXS2/qdOVdHpNa4+7Zn+lmnH4037tDFVRKVGDFvtFNVwdG+mB3RM0RcZ25HxqtZUOU17A6Z/koWuhbUhf/JHU68TlGwOLHFxfs1765KWr2GijL1X8inR99wX8qDQ0mg/Cucgw== ssh.test.smallstep.com golang-step-crypto-0.24.0/kms/uri/000077500000000000000000000000001437037643100167345ustar00rootroot00000000000000golang-step-crypto-0.24.0/kms/uri/testdata/000077500000000000000000000000001437037643100205455ustar00rootroot00000000000000golang-step-crypto-0.24.0/kms/uri/testdata/pin.txt000066400000000000000000000000171437037643100220720ustar00rootroot00000000000000trim-this-pin golang-step-crypto-0.24.0/kms/uri/uri.go000066400000000000000000000066761437037643100201010ustar00rootroot00000000000000package uri import ( "bytes" "encoding/hex" "net/url" "os" "strings" "unicode" "github.com/pkg/errors" ) // URI implements a parser for a URI format based on the the PKCS #11 URI Scheme // defined in https://tools.ietf.org/html/rfc7512 // // These URIs will be used to define the key names in a KMS. type URI struct { *url.URL Values url.Values } // New creates a new URI from a scheme and key-value pairs. func New(scheme string, values url.Values) *URI { return &URI{ URL: &url.URL{ Scheme: scheme, Opaque: strings.ReplaceAll(values.Encode(), "&", ";"), }, Values: values, } } // NewFile creates an uri for a file. func NewFile(path string) *URI { return &URI{ URL: &url.URL{ Scheme: "file", Path: path, }, } } // HasScheme returns true if the given uri has the given scheme, false otherwise. func HasScheme(scheme, rawuri string) bool { u, err := url.Parse(rawuri) if err != nil { return false } return strings.EqualFold(u.Scheme, scheme) } // Parse returns the URI for the given string or an error. func Parse(rawuri string) (*URI, error) { u, err := url.Parse(rawuri) if err != nil { return nil, errors.Wrapf(err, "error parsing %s", rawuri) } if u.Scheme == "" { return nil, errors.Errorf("error parsing %s: scheme is missing", rawuri) } // Starting with Go 1.17 url.ParseQuery returns an error using semicolon as // separator. v, err := url.ParseQuery(strings.ReplaceAll(u.Opaque, ";", "&")) if err != nil { return nil, errors.Wrapf(err, "error parsing %s", rawuri) } return &URI{ URL: u, Values: v, }, nil } // ParseWithScheme returns the URI for the given string only if it has the given // scheme. func ParseWithScheme(scheme, rawuri string) (*URI, error) { u, err := Parse(rawuri) if err != nil { return nil, err } if !strings.EqualFold(u.Scheme, scheme) { return nil, errors.Errorf("error parsing %s: scheme not expected", rawuri) } return u, nil } // Get returns the first value in the uri with the given key, it will return // empty string if that field is not present. func (u *URI) Get(key string) string { v := u.Values.Get(key) if v == "" { v = u.URL.Query().Get(key) } return v } // GetBool returns true if a given key has the value "true". It returns false // otherwise. func (u *URI) GetBool(key string) bool { v := u.Values.Get(key) if v == "" { v = u.URL.Query().Get(key) } return strings.EqualFold(v, "true") } // GetEncoded returns the first value in the uri with the given key, it will // return empty nil if that field is not present or is empty. If the return // value is hex encoded it will decode it and return it. func (u *URI) GetEncoded(key string) []byte { v := u.Get(key) if v == "" { return nil } if len(v)%2 == 0 { if b, err := hex.DecodeString(v); err == nil { return b } } return []byte(v) } // Pin returns the pin encoded in the url. It will read the pin from the // pin-value or the pin-source attributes. func (u *URI) Pin() string { if value := u.Get("pin-value"); value != "" { return value } if path := u.Get("pin-source"); path != "" { if b, err := readFile(path); err == nil { return string(bytes.TrimRightFunc(b, unicode.IsSpace)) } } return "" } func readFile(path string) ([]byte, error) { u, err := url.Parse(path) if err == nil && (u.Scheme == "" || u.Scheme == "file") && u.Path != "" { path = u.Path } b, err := os.ReadFile(path) if err != nil { return nil, errors.Wrapf(err, "error reading %s", path) } return b, nil } golang-step-crypto-0.24.0/kms/uri/uri_119_test.go000066400000000000000000000034301437037643100215130ustar00rootroot00000000000000//go:build go1.19 package uri import ( "net/url" "reflect" "testing" ) func TestParse(t *testing.T) { type args struct { rawuri string } tests := []struct { name string args args want *URI wantErr bool }{ {"ok", args{"yubikey:slot-id=9a"}, &URI{ URL: &url.URL{Scheme: "yubikey", Opaque: "slot-id=9a"}, Values: url.Values{"slot-id": []string{"9a"}}, }, false}, {"ok schema", args{"cloudkms:"}, &URI{ URL: &url.URL{Scheme: "cloudkms"}, Values: url.Values{}, }, false}, {"ok query", args{"yubikey:slot-id=9a;foo=bar?pin=123456&foo=bar"}, &URI{ URL: &url.URL{Scheme: "yubikey", Opaque: "slot-id=9a;foo=bar", RawQuery: "pin=123456&foo=bar"}, Values: url.Values{"slot-id": []string{"9a"}, "foo": []string{"bar"}}, }, false}, {"ok file", args{"file:///tmp/ca.cert"}, &URI{ URL: &url.URL{Scheme: "file", Path: "/tmp/ca.cert"}, Values: url.Values{}, }, false}, {"ok file simple", args{"file:/tmp/ca.cert"}, &URI{ URL: &url.URL{Scheme: "file", Path: "/tmp/ca.cert", OmitHost: true}, Values: url.Values{}, }, false}, {"ok file host", args{"file://tmp/ca.cert"}, &URI{ URL: &url.URL{Scheme: "file", Host: "tmp", Path: "/ca.cert"}, Values: url.Values{}, }, false}, {"fail schema", args{"cloudkms"}, nil, true}, {"fail parse", args{"yubi%key:slot-id=9a"}, nil, true}, {"fail scheme", args{"yubikey"}, nil, true}, {"fail parse opaque", args{"yubikey:slot-id=%ZZ"}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := Parse(tt.args.rawuri) if (err != nil) != tt.wantErr { t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("Parse() = %#v, want %#v", got.URL, tt.want.URL) } }) } } golang-step-crypto-0.24.0/kms/uri/uri_other_test.go000066400000000000000000000034111437037643100223210ustar00rootroot00000000000000//go:build !go1.19 package uri import ( "net/url" "reflect" "testing" ) func TestParse(t *testing.T) { type args struct { rawuri string } tests := []struct { name string args args want *URI wantErr bool }{ {"ok", args{"yubikey:slot-id=9a"}, &URI{ URL: &url.URL{Scheme: "yubikey", Opaque: "slot-id=9a"}, Values: url.Values{"slot-id": []string{"9a"}}, }, false}, {"ok schema", args{"cloudkms:"}, &URI{ URL: &url.URL{Scheme: "cloudkms"}, Values: url.Values{}, }, false}, {"ok query", args{"yubikey:slot-id=9a;foo=bar?pin=123456&foo=bar"}, &URI{ URL: &url.URL{Scheme: "yubikey", Opaque: "slot-id=9a;foo=bar", RawQuery: "pin=123456&foo=bar"}, Values: url.Values{"slot-id": []string{"9a"}, "foo": []string{"bar"}}, }, false}, {"ok file", args{"file:///tmp/ca.cert"}, &URI{ URL: &url.URL{Scheme: "file", Path: "/tmp/ca.cert"}, Values: url.Values{}, }, false}, {"ok file simple", args{"file:/tmp/ca.cert"}, &URI{ URL: &url.URL{Scheme: "file", Path: "/tmp/ca.cert"}, Values: url.Values{}, }, false}, {"ok file host", args{"file://tmp/ca.cert"}, &URI{ URL: &url.URL{Scheme: "file", Host: "tmp", Path: "/ca.cert"}, Values: url.Values{}, }, false}, {"fail schema", args{"cloudkms"}, nil, true}, {"fail parse", args{"yubi%key:slot-id=9a"}, nil, true}, {"fail scheme", args{"yubikey"}, nil, true}, {"fail parse opaque", args{"yubikey:slot-id=%ZZ"}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := Parse(tt.args.rawuri) if (err != nil) != tt.wantErr { t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("Parse() = %#v, want %#v", got.URL, tt.want.URL) } }) } } golang-step-crypto-0.24.0/kms/uri/uri_test.go000066400000000000000000000171011437037643100211210ustar00rootroot00000000000000package uri import ( "net/url" "reflect" "testing" ) func TestNew(t *testing.T) { type args struct { scheme string values url.Values } tests := []struct { name string args args want *URI }{ {"ok", args{"yubikey", url.Values{"slot-id": []string{"9a"}}}, &URI{ URL: &url.URL{Scheme: "yubikey", Opaque: "slot-id=9a"}, Values: url.Values{"slot-id": []string{"9a"}}, }}, {"ok multiple", args{"yubikey", url.Values{"slot-id": []string{"9a"}, "foo": []string{"bar"}}}, &URI{ URL: &url.URL{Scheme: "yubikey", Opaque: "foo=bar;slot-id=9a"}, Values: url.Values{ "slot-id": []string{"9a"}, "foo": []string{"bar"}, }, }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := New(tt.args.scheme, tt.args.values); !reflect.DeepEqual(got, tt.want) { t.Errorf("New() = %v, want %v", got, tt.want) } }) } } func TestNewFile(t *testing.T) { type args struct { path string } tests := []struct { name string args args want *URI }{ {"ok", args{"/tmp/ca.crt"}, &URI{ URL: &url.URL{Scheme: "file", Path: "/tmp/ca.crt"}, Values: url.Values(nil), }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := NewFile(tt.args.path); !reflect.DeepEqual(got, tt.want) { t.Errorf("NewFile() = %v, want %v", got, tt.want) } }) } } func TestHasScheme(t *testing.T) { type args struct { scheme string rawuri string } tests := []struct { name string args args want bool }{ {"ok", args{"yubikey", "yubikey:slot-id=9a"}, true}, {"ok empty", args{"yubikey", "yubikey:"}, true}, {"ok letter case", args{"awsKMS", "AWSkms:key-id=abcdefg?foo=bar"}, true}, {"fail", args{"yubikey", "awskms:key-id=abcdefg"}, false}, {"fail parse", args{"yubikey", "yubi%key:slot-id=9a"}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := HasScheme(tt.args.scheme, tt.args.rawuri); got != tt.want { t.Errorf("HasScheme() = %v, want %v", got, tt.want) } }) } } func TestParseWithScheme(t *testing.T) { type args struct { scheme string rawuri string } tests := []struct { name string args args want *URI wantErr bool }{ {"ok", args{"yubikey", "yubikey:slot-id=9a"}, &URI{ URL: &url.URL{Scheme: "yubikey", Opaque: "slot-id=9a"}, Values: url.Values{"slot-id": []string{"9a"}}, }, false}, {"ok schema", args{"cloudkms", "cloudkms:"}, &URI{ URL: &url.URL{Scheme: "cloudkms"}, Values: url.Values{}, }, false}, {"ok file", args{"file", "file:///tmp/ca.cert"}, &URI{ URL: &url.URL{Scheme: "file", Path: "/tmp/ca.cert"}, Values: url.Values{}, }, false}, {"fail parse", args{"yubikey", "yubikey"}, nil, true}, {"fail scheme", args{"yubikey", "awskms:slot-id=9a"}, nil, true}, {"fail schema", args{"cloudkms", "cloudkms"}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ParseWithScheme(tt.args.scheme, tt.args.rawuri) if (err != nil) != tt.wantErr { t.Errorf("ParseWithScheme() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ParseWithScheme() = %v, want %v", got, tt.want) } }) } } func TestURI_Get(t *testing.T) { mustParse := func(s string) *URI { u, err := Parse(s) if err != nil { t.Fatal(err) } return u } type args struct { key string } tests := []struct { name string uri *URI args args want string }{ {"ok", mustParse("yubikey:slot-id=9a"), args{"slot-id"}, "9a"}, {"ok first", mustParse("yubikey:slot-id=9a;slot-id=9b"), args{"slot-id"}, "9a"}, {"ok multiple", mustParse("yubikey:slot-id=9a;foo=bar"), args{"foo"}, "bar"}, {"ok in query", mustParse("yubikey:slot-id=9a?foo=bar"), args{"foo"}, "bar"}, {"fail missing", mustParse("yubikey:slot-id=9a"), args{"foo"}, ""}, {"fail missing query", mustParse("yubikey:slot-id=9a?bar=zar"), args{"foo"}, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.uri.Get(tt.args.key); got != tt.want { t.Errorf("URI.Get() = %v, want %v", got, tt.want) } }) } } func TestURI_GetBool(t *testing.T) { mustParse := func(s string) *URI { u, err := Parse(s) if err != nil { t.Fatal(err) } return u } type args struct { key string } tests := []struct { name string uri *URI args args want bool }{ {"true", mustParse("azurekms:name=foo;vault=bar;hsm=true"), args{"hsm"}, true}, {"TRUE", mustParse("azurekms:name=foo;vault=bar;hsm=TRUE"), args{"hsm"}, true}, {"tRUe query", mustParse("azurekms:name=foo;vault=bar?hsm=tRUe"), args{"hsm"}, true}, {"false", mustParse("azurekms:name=foo;vault=bar;hsm=false"), args{"hsm"}, false}, {"false query", mustParse("azurekms:name=foo;vault=bar?hsm=false"), args{"hsm"}, false}, {"empty", mustParse("azurekms:name=foo;vault=bar;hsm=?bar=true"), args{"hsm"}, false}, {"missing", mustParse("azurekms:name=foo;vault=bar"), args{"hsm"}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.uri.GetBool(tt.args.key); got != tt.want { t.Errorf("URI.GetBool() = %v, want %v", got, tt.want) } }) } } func TestURI_GetEncoded(t *testing.T) { mustParse := func(s string) *URI { u, err := Parse(s) if err != nil { t.Fatal(err) } return u } type args struct { key string } tests := []struct { name string uri *URI args args want []byte }{ {"ok", mustParse("yubikey:slot-id=9a"), args{"slot-id"}, []byte{0x9a}}, {"ok first", mustParse("yubikey:slot-id=9a9b;slot-id=9b"), args{"slot-id"}, []byte{0x9a, 0x9b}}, {"ok percent", mustParse("yubikey:slot-id=9a;foo=%9a%9b%9c"), args{"foo"}, []byte{0x9a, 0x9b, 0x9c}}, {"ok in query", mustParse("yubikey:slot-id=9a?foo=9a"), args{"foo"}, []byte{0x9a}}, {"ok in query percent", mustParse("yubikey:slot-id=9a?foo=%9a"), args{"foo"}, []byte{0x9a}}, {"ok missing", mustParse("yubikey:slot-id=9a"), args{"foo"}, nil}, {"ok missing query", mustParse("yubikey:slot-id=9a?bar=zar"), args{"foo"}, nil}, {"ok no hex", mustParse("yubikey:slot-id=09a?bar=zar"), args{"slot-id"}, []byte{'0', '9', 'a'}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := tt.uri.GetEncoded(tt.args.key) if !reflect.DeepEqual(got, tt.want) { t.Errorf("URI.GetEncoded() = %v, want %v", got, tt.want) } }) } } func TestURI_Pin(t *testing.T) { mustParse := func(s string) *URI { u, err := Parse(s) if err != nil { t.Fatal(err) } return u } tests := []struct { name string uri *URI want string }{ {"from value", mustParse("pkcs11:id=%72%73?pin-value=0123456789"), "0123456789"}, {"from source", mustParse("pkcs11:id=%72%73?pin-source=testdata/pin.txt"), "trim-this-pin"}, {"from missing", mustParse("pkcs11:id=%72%73"), ""}, {"from source missing", mustParse("pkcs11:id=%72%73?pin-source=testdata/foo.txt"), ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.uri.Pin(); got != tt.want { t.Errorf("URI.Pin() = %v, want %v", got, tt.want) } }) } } func TestURI_String(t *testing.T) { mustParse := func(s string) *URI { u, err := Parse(s) if err != nil { t.Fatal(err) } return u } tests := []struct { name string uri *URI want string }{ {"ok new", New("yubikey", url.Values{"slot-id": []string{"9a"}, "foo": []string{"bar"}}), "yubikey:foo=bar;slot-id=9a"}, {"ok parse", mustParse("yubikey:slot-id=9a;foo=bar?bar=zar"), "yubikey:slot-id=9a;foo=bar?bar=zar"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.uri.String(); got != tt.want { t.Errorf("URI.String() = %v, want %v", got, tt.want) } }) } } golang-step-crypto-0.24.0/kms/yubikey/000077500000000000000000000000001437037643100176165ustar00rootroot00000000000000golang-step-crypto-0.24.0/kms/yubikey/yubikey.go000066400000000000000000000264241437037643100216360ustar00rootroot00000000000000//go:build cgo && !noyubikey // +build cgo,!noyubikey package yubikey import ( "context" "crypto" "crypto/x509" "encoding/hex" "net/url" "strings" "github.com/go-piv/piv-go/piv" "github.com/pkg/errors" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/kms/uri" ) // Scheme is the scheme used in uris. const Scheme = "yubikey" // YubiKey implements the KMS interface on a YubiKey. type YubiKey struct { yk pivKey pin string managementKey [24]byte } type pivKey interface { Certificate(slot piv.Slot) (*x509.Certificate, error) SetCertificate(key [24]byte, slot piv.Slot, cert *x509.Certificate) error GenerateKey(key [24]byte, slot piv.Slot, opts piv.Key) (crypto.PublicKey, error) PrivateKey(slot piv.Slot, public crypto.PublicKey, auth piv.KeyAuth) (crypto.PrivateKey, error) Attest(slot piv.Slot) (*x509.Certificate, error) Close() error } var pivCards = piv.Cards var pivOpen = func(card string) (pivKey, error) { return piv.Open(card) } // New initializes a new YubiKey. // TODO(mariano): only one card is currently supported. func New(ctx context.Context, opts apiv1.Options) (*YubiKey, error) { pin := "123456" managementKey := piv.DefaultManagementKey if opts.URI != "" { u, err := uri.ParseWithScheme(Scheme, opts.URI) if err != nil { return nil, err } if v := u.Pin(); v != "" { opts.Pin = v } if v := u.Get("management-key"); v != "" { opts.ManagementKey = v } } // Deprecated way to set configuration parameters. if opts.ManagementKey != "" { b, err := hex.DecodeString(opts.ManagementKey) if err != nil { return nil, errors.Wrap(err, "error decoding managementKey") } if len(b) != 24 { return nil, errors.New("invalid managementKey: length is not 24 bytes") } copy(managementKey[:], b[:24]) } if opts.Pin != "" { pin = opts.Pin } cards, err := pivCards() if err != nil { return nil, err } if len(cards) == 0 { return nil, errors.New("error detecting yubikey: try removing and reconnecting the device") } yk, err := pivOpen(cards[0]) if err != nil { return nil, errors.Wrap(err, "error opening yubikey") } return &YubiKey{ yk: yk, pin: pin, managementKey: managementKey, }, nil } func init() { apiv1.Register(apiv1.YubiKey, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) { return New(ctx, opts) }) } // LoadCertificate implements kms.CertificateManager and loads a certificate // from the YubiKey. func (k *YubiKey) LoadCertificate(req *apiv1.LoadCertificateRequest) (*x509.Certificate, error) { slot, err := getSlot(req.Name) if err != nil { return nil, err } cert, err := k.yk.Certificate(slot) if err != nil { return nil, errors.Wrap(err, "error retrieving certificate") } return cert, nil } // StoreCertificate implements kms.CertificateManager and stores a certificate // in the YubiKey. func (k *YubiKey) StoreCertificate(req *apiv1.StoreCertificateRequest) error { if req.Certificate == nil { return errors.New("storeCertificateRequest 'Certificate' cannot be nil") } slot, err := getSlot(req.Name) if err != nil { return err } err = k.yk.SetCertificate(k.managementKey, slot, req.Certificate) if err != nil { return errors.Wrap(err, "error storing certificate") } return nil } // GetPublicKey returns the public key present in the YubiKey signature slot. func (k *YubiKey) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) { slot, err := getSlot(req.Name) if err != nil { return nil, err } pub, err := k.getPublicKey(slot) if err != nil { return nil, err } return pub, nil } // CreateKey generates a new key in the YubiKey and returns the public key. func (k *YubiKey) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) { alg, err := getSignatureAlgorithm(req.SignatureAlgorithm, req.Bits) if err != nil { return nil, err } slot, name, err := getSlotAndName(req.Name) if err != nil { return nil, err } pinPolicy, touchPolicy := getPolicies(req) pub, err := k.yk.GenerateKey(k.managementKey, slot, piv.Key{ Algorithm: alg, PINPolicy: pinPolicy, TouchPolicy: touchPolicy, }) if err != nil { return nil, errors.Wrap(err, "error generating key") } return &apiv1.CreateKeyResponse{ Name: name, PublicKey: pub, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: name, }, }, nil } // CreateSigner creates a signer using the key present in the YubiKey signature // slot. func (k *YubiKey) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) { slot, err := getSlot(req.SigningKey) if err != nil { return nil, err } pin := k.pin if pin == "" { // Attempt to get the pin from the uri if u, err := uri.ParseWithScheme(Scheme, req.SigningKey); err == nil { pin = u.Pin() } } pub, err := k.getPublicKey(slot) if err != nil { return nil, err } priv, err := k.yk.PrivateKey(slot, pub, piv.KeyAuth{ PIN: pin, PINPolicy: piv.PINPolicyAlways, }) if err != nil { return nil, errors.Wrap(err, "error retrieving private key") } signer, ok := priv.(crypto.Signer) if !ok { return nil, errors.New("private key is not a crypto.Signer") } return signer, nil } // CreateDecrypter creates a crypto.Decrypter using the key present in the configured // Yubikey slot. func (k *YubiKey) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { slot, err := getSlot(req.DecryptionKey) if err != nil { return nil, err } pin := k.pin if pin == "" { // Attempt to get the pin from the uri if u, err := uri.ParseWithScheme(Scheme, req.DecryptionKey); err == nil { pin = u.Pin() } } pub, err := k.getPublicKey(slot) if err != nil { return nil, err } priv, err := k.yk.PrivateKey(slot, pub, piv.KeyAuth{ PIN: pin, PINPolicy: piv.PINPolicyAlways, }) if err != nil { return nil, errors.Wrap(err, "error retrieving private key") } decrypter, ok := priv.(crypto.Decrypter) if !ok { return nil, errors.New("private key is not a crypto.Decrypter") } return decrypter, nil } // CreateAttestation creates an attestation certificate from a YubiKey slot. // // # Experimental // // Notice: This API is EXPERIMENTAL and may be changed or removed in a later // release. func (k *YubiKey) CreateAttestation(req *apiv1.CreateAttestationRequest) (*apiv1.CreateAttestationResponse, error) { slot, err := getSlot(req.Name) if err != nil { return nil, err } cert, err := k.yk.Attest(slot) if err != nil { return nil, errors.Wrap(err, "error attesting slot") } intermediate, err := k.yk.Certificate(slotAttestation) if err != nil { return nil, errors.Wrap(err, "error retrieving attestation certificate") } return &apiv1.CreateAttestationResponse{ Certificate: cert, CertificateChain: []*x509.Certificate{intermediate}, PublicKey: cert.PublicKey, }, nil } // Close releases the connection to the YubiKey. func (k *YubiKey) Close() error { return errors.Wrap(k.yk.Close(), "error closing yubikey") } // getPublicKey returns the public key on a slot. First it attempts to do // attestation to get a certificate with the public key in it, if this succeeds // means that the key was generated in the device. If not we'll try to get the // key from a stored certificate in the same slot. func (k *YubiKey) getPublicKey(slot piv.Slot) (crypto.PublicKey, error) { cert, err := k.yk.Attest(slot) if err != nil { if cert, err = k.yk.Certificate(slot); err != nil { return nil, errors.Wrap(err, "error retrieving public key") } } return cert.PublicKey, nil } // signatureAlgorithmMapping is a mapping between the step signature algorithm, // and bits for RSA keys, with yubikey ones. var signatureAlgorithmMapping = map[apiv1.SignatureAlgorithm]interface{}{ apiv1.UnspecifiedSignAlgorithm: piv.AlgorithmEC256, apiv1.SHA256WithRSA: map[int]piv.Algorithm{ 0: piv.AlgorithmRSA2048, 1024: piv.AlgorithmRSA1024, 2048: piv.AlgorithmRSA2048, }, apiv1.SHA512WithRSA: map[int]piv.Algorithm{ 0: piv.AlgorithmRSA2048, 1024: piv.AlgorithmRSA1024, 2048: piv.AlgorithmRSA2048, }, apiv1.SHA256WithRSAPSS: map[int]piv.Algorithm{ 0: piv.AlgorithmRSA2048, 1024: piv.AlgorithmRSA1024, 2048: piv.AlgorithmRSA2048, }, apiv1.SHA512WithRSAPSS: map[int]piv.Algorithm{ 0: piv.AlgorithmRSA2048, 1024: piv.AlgorithmRSA1024, 2048: piv.AlgorithmRSA2048, }, apiv1.ECDSAWithSHA256: piv.AlgorithmEC256, apiv1.ECDSAWithSHA384: piv.AlgorithmEC384, apiv1.PureEd25519: piv.AlgorithmEd25519, } func getSignatureAlgorithm(alg apiv1.SignatureAlgorithm, bits int) (piv.Algorithm, error) { v, ok := signatureAlgorithmMapping[alg] if !ok { return 0, errors.Errorf("YubiKey does not support signature algorithm '%s'", alg) } switch v := v.(type) { case piv.Algorithm: return v, nil case map[int]piv.Algorithm: signatureAlgorithm, ok := v[bits] if !ok { return 0, errors.Errorf("YubiKey does not support signature algorithm '%s' with '%d' bits", alg, bits) } return signatureAlgorithm, nil default: return 0, errors.Errorf("unexpected error: this should not happen") } } var slotAttestation = piv.Slot{Key: 0xf9, Object: 0x5fff01} var slotMapping = map[string]piv.Slot{ "9a": piv.SlotAuthentication, "9c": piv.SlotSignature, "9e": piv.SlotCardAuthentication, "9d": piv.SlotKeyManagement, "82": {Key: 0x82, Object: 0x5FC10D}, "83": {Key: 0x83, Object: 0x5FC10E}, "84": {Key: 0x84, Object: 0x5FC10F}, "85": {Key: 0x85, Object: 0x5FC110}, "86": {Key: 0x86, Object: 0x5FC111}, "87": {Key: 0x87, Object: 0x5FC112}, "88": {Key: 0x88, Object: 0x5FC113}, "89": {Key: 0x89, Object: 0x5FC114}, "8a": {Key: 0x8a, Object: 0x5FC115}, "8b": {Key: 0x8b, Object: 0x5FC116}, "8c": {Key: 0x8c, Object: 0x5FC117}, "8d": {Key: 0x8d, Object: 0x5FC118}, "8e": {Key: 0x8e, Object: 0x5FC119}, "8f": {Key: 0x8f, Object: 0x5FC11A}, "90": {Key: 0x90, Object: 0x5FC11B}, "91": {Key: 0x91, Object: 0x5FC11C}, "92": {Key: 0x92, Object: 0x5FC11D}, "93": {Key: 0x93, Object: 0x5FC11E}, "94": {Key: 0x94, Object: 0x5FC11F}, "95": {Key: 0x95, Object: 0x5FC120}, } func getSlot(name string) (piv.Slot, error) { slot, _, err := getSlotAndName(name) return slot, err } func getSlotAndName(name string) (piv.Slot, string, error) { if name == "" { return piv.SlotSignature, "yubikey:slot-id=9c", nil } var slotID string name = strings.ToLower(name) if strings.HasPrefix(name, "yubikey:") { u, err := url.Parse(name) if err != nil { return piv.Slot{}, "", errors.Wrapf(err, "error parsing '%s'", name) } v, err := url.ParseQuery(u.Opaque) if err != nil { return piv.Slot{}, "", errors.Wrapf(err, "error parsing '%s'", name) } if slotID = v.Get("slot-id"); slotID == "" { return piv.Slot{}, "", errors.Errorf("error parsing '%s': slot-id is missing", name) } } else { slotID = name } s, ok := slotMapping[slotID] if !ok { return piv.Slot{}, "", errors.Errorf("unsupported slot-id '%s'", name) } name = "yubikey:slot-id=" + url.QueryEscape(slotID) return s, name, nil } // getPolicies returns the pin and touch policies from the request. If they are // not set the defaults are piv.PINPolicyAlways and piv.TouchPolicyNever. func getPolicies(req *apiv1.CreateKeyRequest) (piv.PINPolicy, piv.TouchPolicy) { pin := piv.PINPolicy(req.PINPolicy) touch := piv.TouchPolicy(req.TouchPolicy) if pin == 0 { pin = piv.PINPolicyAlways } if touch == 0 { touch = piv.TouchPolicyNever } return pin, touch } golang-step-crypto-0.24.0/kms/yubikey/yubikey_no_cgo.go000066400000000000000000000007031437037643100231520ustar00rootroot00000000000000//go:build !cgo || noyubikey // +build !cgo noyubikey package yubikey import ( "context" "os" "path/filepath" "github.com/pkg/errors" "go.step.sm/crypto/kms/apiv1" ) func init() { apiv1.Register(apiv1.YubiKey, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) { name := filepath.Base(os.Args[0]) return nil, errors.Errorf("unsupported kms type 'yubikey': %s is compiled without cgo or YubiKey support", name) }) } golang-step-crypto-0.24.0/kms/yubikey/yubikey_test.go000066400000000000000000000753051437037643100226770ustar00rootroot00000000000000//go:build cgo // +build cgo package yubikey import ( "bytes" "context" "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "reflect" "testing" "github.com/go-piv/piv-go/piv" "github.com/pkg/errors" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/minica" ) type stubPivKey struct { attestCA *minica.CA userCA *minica.CA attestMap map[piv.Slot]*x509.Certificate certMap map[piv.Slot]*x509.Certificate signerMap map[piv.Slot]interface{} keyOptionsMap map[piv.Slot]piv.Key } type symmetricAlgorithm int const ( ECDSA symmetricAlgorithm = iota RSA ) const rsaKeySize = 2048 type privateKey interface { crypto.PrivateKey Public() crypto.PublicKey } //nolint:typecheck // ignore deadcode warnings func newStubPivKey(t *testing.T, alg symmetricAlgorithm) *stubPivKey { var ( attSigner privateKey userSigner privateKey ) t.Helper() attestCA, err := minica.New() if err != nil { t.Fatal(err) } userCA, err := minica.New() if err != nil { t.Fatal(err) } switch alg { case ECDSA: attSigner, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatal(err) } userSigner, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatal(err) } case RSA: attSigner, err = rsa.GenerateKey(rand.Reader, rsaKeySize) if err != nil { t.Fatal(err) } userSigner, err = rsa.GenerateKey(rand.Reader, rsaKeySize) if err != nil { t.Fatal(err) } default: t.Fatal(errors.New("unknown alg")) } attCert, err := attestCA.Sign(&x509.Certificate{ Subject: pkix.Name{CommonName: "attested certificate"}, PublicKey: attSigner.Public(), }) if err != nil { t.Fatal(err) } userCert, err := userCA.Sign(&x509.Certificate{ Subject: pkix.Name{CommonName: "test.example.org"}, DNSNames: []string{"test.example.org"}, PublicKey: userSigner.Public(), }) if err != nil { t.Fatal(err) } return &stubPivKey{ attestCA: attestCA, userCA: userCA, attestMap: map[piv.Slot]*x509.Certificate{ piv.SlotAuthentication: attCert, // 9a }, certMap: map[piv.Slot]*x509.Certificate{ piv.SlotSignature: userCert, // 9c slotAttestation: attestCA.Intermediate, // f9 }, signerMap: map[piv.Slot]interface{}{ piv.SlotAuthentication: attSigner, // 9a piv.SlotSignature: userSigner, // 9c }, keyOptionsMap: map[piv.Slot]piv.Key{}, } } func (s *stubPivKey) Certificate(slot piv.Slot) (*x509.Certificate, error) { cert, ok := s.certMap[slot] if !ok { return nil, errors.New("certificate not found") } return cert, nil } func (s *stubPivKey) SetCertificate(key [24]byte, slot piv.Slot, cert *x509.Certificate) error { if !bytes.Equal(piv.DefaultManagementKey[:], key[:]) { return errors.New("missing or invalid management key") } s.certMap[slot] = cert return nil } func (s *stubPivKey) GenerateKey(key [24]byte, slot piv.Slot, opts piv.Key) (crypto.PublicKey, error) { if !bytes.Equal(piv.DefaultManagementKey[:], key[:]) { return nil, errors.New("missing or invalid management key") } var signer crypto.Signer switch opts.Algorithm { case piv.AlgorithmEC256: key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, err } signer = key case piv.AlgorithmEC384: key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, err } signer = key case piv.AlgorithmEd25519: _, key, err := ed25519.GenerateKey(rand.Reader) if err != nil { return nil, err } signer = key case piv.AlgorithmRSA1024: key, err := rsa.GenerateKey(rand.Reader, 1024) if err != nil { return nil, err } signer = key case piv.AlgorithmRSA2048: key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return nil, err } signer = key default: return nil, errors.New("unsupported algorithm") } s.signerMap[slot] = signer s.keyOptionsMap[slot] = opts return signer.Public(), nil } func (s *stubPivKey) PrivateKey(slot piv.Slot, public crypto.PublicKey, auth piv.KeyAuth) (crypto.PrivateKey, error) { if auth.PIN != "123456" { return nil, errors.New("missing or invalid pin") } key, ok := s.signerMap[slot] if !ok { return nil, errors.New("private key not found") } return key, nil } func (s *stubPivKey) Attest(slot piv.Slot) (*x509.Certificate, error) { cert, ok := s.attestMap[slot] if !ok { return nil, errors.New("certificate not found") } return cert, nil } func (s *stubPivKey) Close() error { return nil } func TestNew(t *testing.T) { ctx := context.Background() pOpen := pivOpen pCards := pivCards t.Cleanup(func() { pivOpen = pOpen pivCards = pCards }) yk := newStubPivKey(t, ECDSA) okPivCards := func() ([]string, error) { return []string{"Yubico YubiKey OTP+FIDO+CCID"}, nil } failPivCards := func() ([]string, error) { return nil, errors.New("error reading cards") } failNoPivCards := func() ([]string, error) { return []string{}, nil } okPivOpen := func(card string) (pivKey, error) { return yk, nil } failPivOpen := func(card string) (pivKey, error) { return nil, errors.New("error opening card") } type args struct { ctx context.Context opts apiv1.Options } tests := []struct { name string args args setup func() want *YubiKey wantErr bool }{ {"ok", args{ctx, apiv1.Options{}}, func() { pivCards = okPivCards pivOpen = okPivOpen }, &YubiKey{yk: yk, pin: "123456", managementKey: piv.DefaultManagementKey}, false}, {"ok with uri", args{ctx, apiv1.Options{ URI: "yubikey:pin-value=111111;management-key=001122334455667788990011223344556677889900112233", }}, func() { pivCards = okPivCards pivOpen = okPivOpen }, &YubiKey{yk: yk, pin: "111111", managementKey: [24]byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33}}, false}, {"ok with Pin", args{ctx, apiv1.Options{Pin: "222222"}}, func() { pivCards = okPivCards pivOpen = okPivOpen }, &YubiKey{yk: yk, pin: "222222", managementKey: piv.DefaultManagementKey}, false}, {"ok with ManagementKey", args{ctx, apiv1.Options{ManagementKey: "001122334455667788990011223344556677889900112233"}}, func() { pivCards = okPivCards pivOpen = okPivOpen }, &YubiKey{yk: yk, pin: "123456", managementKey: [24]byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33}}, false}, {"fail uri", args{ctx, apiv1.Options{URI: "badschema:"}}, func() { pivCards = okPivCards pivOpen = okPivOpen }, nil, true}, {"fail management key", args{ctx, apiv1.Options{URI: "yubikey:management-key=xxyyzz"}}, func() { pivCards = okPivCards pivOpen = okPivOpen }, nil, true}, {"fail management key size", args{ctx, apiv1.Options{URI: "yubikey:management-key=00112233"}}, func() { pivCards = okPivCards pivOpen = okPivOpen }, nil, true}, {"fail pivCards", args{ctx, apiv1.Options{}}, func() { pivCards = failPivCards pivOpen = okPivOpen }, nil, true}, {"fail no pivCards", args{ctx, apiv1.Options{}}, func() { pivCards = failNoPivCards pivOpen = okPivOpen }, nil, true}, {"fail pivOpen", args{ctx, apiv1.Options{}}, func() { pivCards = okPivCards pivOpen = failPivOpen }, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.setup() got, err := New(tt.args.ctx, tt.args.opts) if (err != nil) != tt.wantErr { t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("New() = %v, want %v", got, tt.want) } }) } } func TestYubiKey_LoadCertificate(t *testing.T) { yk := newStubPivKey(t, ECDSA) type fields struct { yk pivKey pin string managementKey [24]byte } type args struct { req *apiv1.LoadCertificateRequest } tests := []struct { name string fields fields args args want *x509.Certificate wantErr bool }{ {"ok", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.LoadCertificateRequest{ Name: "yubikey:slot-id=9c", }}, yk.certMap[piv.SlotSignature], false}, {"fail getSlot", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.LoadCertificateRequest{ Name: "slot-id=9c", }}, nil, true}, {"fail certificate", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.LoadCertificateRequest{ Name: "yubikey:slot-id=85", }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &YubiKey{ yk: tt.fields.yk, pin: tt.fields.pin, managementKey: tt.fields.managementKey, } got, err := k.LoadCertificate(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("YubiKey.LoadCertificate() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("YubiKey.LoadCertificate() = %v, want %v", got, tt.want) } }) } } func TestYubiKey_StoreCertificate(t *testing.T) { yk := newStubPivKey(t, ECDSA) signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatal(err) } cert, err := yk.userCA.Sign(&x509.Certificate{ Subject: pkix.Name{CommonName: "foo.example.org"}, DNSNames: []string{"foo.example.org"}, PublicKey: signer.Public(), }) if err != nil { t.Fatal(err) } type fields struct { yk pivKey pin string managementKey [24]byte } type args struct { req *apiv1.StoreCertificateRequest } tests := []struct { name string fields fields args args wantErr bool }{ {"ok", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.StoreCertificateRequest{ Name: "yubikey:slot-id=9c", Certificate: cert, }}, false}, {"fail nil", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.StoreCertificateRequest{ Name: "yubikey:slot-id=9c", }}, true}, {"fail getSlot", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.StoreCertificateRequest{ Name: "slot-id=9c", Certificate: cert, }}, true}, {"fail setCertificate", fields{yk, "123456", [24]byte{}}, args{&apiv1.StoreCertificateRequest{ Name: "yubikey:slot-id=9c", Certificate: cert, }}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &YubiKey{ yk: tt.fields.yk, pin: tt.fields.pin, managementKey: tt.fields.managementKey, } if err := k.StoreCertificate(tt.args.req); (err != nil) != tt.wantErr { t.Errorf("YubiKey.StoreCertificate() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestYubiKey_GetPublicKey(t *testing.T) { yk := newStubPivKey(t, ECDSA) type fields struct { yk pivKey pin string managementKey [24]byte } type args struct { req *apiv1.GetPublicKeyRequest } tests := []struct { name string fields fields args args want crypto.PublicKey wantErr bool }{ {"ok", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.GetPublicKeyRequest{ Name: "yubikey:slot-id=9c", }}, yk.certMap[piv.SlotSignature].PublicKey, false}, {"fail getSlot", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.GetPublicKeyRequest{ Name: "slot-id=9c", }}, nil, true}, {"fail getPublicKey", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.GetPublicKeyRequest{ Name: "yubikey:slot-id=85", }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &YubiKey{ yk: tt.fields.yk, pin: tt.fields.pin, managementKey: tt.fields.managementKey, } got, err := k.GetPublicKey(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("YubiKey.GetPublicKey() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("YubiKey.GetPublicKey() = %v, want %v", got, tt.want) } }) } } func TestYubiKey_CreateKey(t *testing.T) { yk := newStubPivKey(t, ECDSA) type fields struct { yk pivKey pin string managementKey [24]byte } type args struct { req *apiv1.CreateKeyRequest } tests := []struct { name string fields fields args args wantFn func() *apiv1.CreateKeyResponse wantErr bool }{ {"ok", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ Name: "82", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }}, func() *apiv1.CreateKeyResponse { return &apiv1.CreateKeyResponse{ Name: "yubikey:slot-id=82", PublicKey: yk.signerMap[slotMapping["82"]].(crypto.Signer).Public(), CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=82", }, } }, false}, {"ok default", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ SignatureAlgorithm: apiv1.ECDSAWithSHA256, }}, func() *apiv1.CreateKeyResponse { return &apiv1.CreateKeyResponse{ Name: "yubikey:slot-id=9c", PublicKey: yk.signerMap[slotMapping["9c"]].(crypto.Signer).Public(), CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=9c", }, } }, false}, {"ok p256", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ Name: "yubikey:slot-id=82", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }}, func() *apiv1.CreateKeyResponse { return &apiv1.CreateKeyResponse{ Name: "yubikey:slot-id=82", PublicKey: yk.signerMap[slotMapping["82"]].(crypto.Signer).Public(), CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=82", }, } }, false}, {"ok p384", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ Name: "yubikey:slot-id=82", SignatureAlgorithm: apiv1.ECDSAWithSHA384, }}, func() *apiv1.CreateKeyResponse { return &apiv1.CreateKeyResponse{ Name: "yubikey:slot-id=82", PublicKey: yk.signerMap[slotMapping["82"]].(crypto.Signer).Public(), CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=82", }, } }, false}, {"ok ed25519", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ Name: "yubikey:slot-id=82", SignatureAlgorithm: apiv1.PureEd25519, }}, func() *apiv1.CreateKeyResponse { return &apiv1.CreateKeyResponse{ Name: "yubikey:slot-id=82", PublicKey: yk.signerMap[slotMapping["82"]].(crypto.Signer).Public(), CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=82", }, } }, false}, {"ok rsa", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ Name: "yubikey:slot-id=82", SignatureAlgorithm: apiv1.SHA256WithRSA, }}, func() *apiv1.CreateKeyResponse { return &apiv1.CreateKeyResponse{ Name: "yubikey:slot-id=82", PublicKey: yk.signerMap[slotMapping["82"]].(crypto.Signer).Public(), CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=82", }, } }, false}, {"ok rsa 1024", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ Name: "yubikey:slot-id=82", SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 1024, }}, func() *apiv1.CreateKeyResponse { return &apiv1.CreateKeyResponse{ Name: "yubikey:slot-id=82", PublicKey: yk.signerMap[slotMapping["82"]].(crypto.Signer).Public(), CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=82", }, } }, false}, {"ok rsa 2048", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ Name: "yubikey:slot-id=82", SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 2048, }}, func() *apiv1.CreateKeyResponse { return &apiv1.CreateKeyResponse{ Name: "yubikey:slot-id=82", PublicKey: yk.signerMap[slotMapping["82"]].(crypto.Signer).Public(), CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=82", }, } }, false}, {"ok with policies", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ Name: "yubikey:slot-id=82", SignatureAlgorithm: apiv1.ECDSAWithSHA256, PINPolicy: apiv1.PINPolicyNever, TouchPolicy: apiv1.TouchPolicyAlways, }}, func() *apiv1.CreateKeyResponse { return &apiv1.CreateKeyResponse{ Name: "yubikey:slot-id=82", PublicKey: yk.signerMap[slotMapping["82"]].(crypto.Signer).Public(), CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=82", }, } }, false}, {"fail rsa 4096", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ Name: "yubikey:slot-id=82", SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 4096, }}, func() *apiv1.CreateKeyResponse { return nil }, true}, {"fail getSignatureAlgorithm", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ Name: "yubikey:slot-id=82", SignatureAlgorithm: apiv1.SignatureAlgorithm(100), }}, func() *apiv1.CreateKeyResponse { return nil }, true}, {"fail getSlotAndName", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ Name: "yubikey:foo=82", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }}, func() *apiv1.CreateKeyResponse { return nil }, true}, {"fail generateKey", fields{yk, "123456", [24]byte{}}, args{&apiv1.CreateKeyRequest{ Name: "yubikey:slot-id=82", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }}, func() *apiv1.CreateKeyResponse { return nil }, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &YubiKey{ yk: tt.fields.yk, pin: tt.fields.pin, managementKey: tt.fields.managementKey, } got, err := k.CreateKey(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("YubiKey.CreateKey() error = %v, wantErr %v", err, tt.wantErr) return } want := tt.wantFn() if !reflect.DeepEqual(got, want) { t.Errorf("YubiKey.CreateKey() = %v, want %v", got, want) } }) } } func TestYubiKey_CreateKey_policies(t *testing.T) { yk := newStubPivKey(t, ECDSA) type fields struct { yk pivKey pin string managementKey [24]byte } type args struct { req *apiv1.CreateKeyRequest } tests := []struct { name string fields fields args args wantSlot piv.Slot wantPinPolicy piv.PINPolicy wantTouchPolicy piv.TouchPolicy wantFn func() *apiv1.CreateKeyResponse wantErr bool }{ {"ok", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ Name: "yubikey:slot-id=82", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }}, slotMapping["82"], piv.PINPolicyAlways, piv.TouchPolicyNever, func() *apiv1.CreateKeyResponse { return &apiv1.CreateKeyResponse{ Name: "yubikey:slot-id=82", PublicKey: yk.signerMap[slotMapping["82"]].(crypto.Signer).Public(), CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=82", }, } }, false}, {"ok PINPolicyNever", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ Name: "yubikey:slot-id=82", SignatureAlgorithm: apiv1.ECDSAWithSHA256, PINPolicy: apiv1.PINPolicyNever, }}, slotMapping["82"], piv.PINPolicyNever, piv.TouchPolicyNever, func() *apiv1.CreateKeyResponse { return &apiv1.CreateKeyResponse{ Name: "yubikey:slot-id=82", PublicKey: yk.signerMap[slotMapping["82"]].(crypto.Signer).Public(), CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=82", }, } }, false}, {"ok PINPolicyOnce", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ Name: "yubikey:slot-id=82", SignatureAlgorithm: apiv1.ECDSAWithSHA256, PINPolicy: apiv1.PINPolicyOnce, }}, slotMapping["82"], piv.PINPolicyOnce, piv.TouchPolicyNever, func() *apiv1.CreateKeyResponse { return &apiv1.CreateKeyResponse{ Name: "yubikey:slot-id=82", PublicKey: yk.signerMap[slotMapping["82"]].(crypto.Signer).Public(), CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=82", }, } }, false}, {"ok PINPolicyAlways", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ Name: "yubikey:slot-id=82", SignatureAlgorithm: apiv1.ECDSAWithSHA256, PINPolicy: apiv1.PINPolicyAlways, }}, slotMapping["82"], piv.PINPolicyAlways, piv.TouchPolicyNever, func() *apiv1.CreateKeyResponse { return &apiv1.CreateKeyResponse{ Name: "yubikey:slot-id=82", PublicKey: yk.signerMap[slotMapping["82"]].(crypto.Signer).Public(), CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=82", }, } }, false}, {"ok TouchPolicyNever", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ Name: "yubikey:slot-id=82", SignatureAlgorithm: apiv1.ECDSAWithSHA256, TouchPolicy: apiv1.TouchPolicyNever, }}, slotMapping["82"], piv.PINPolicyAlways, piv.TouchPolicyNever, func() *apiv1.CreateKeyResponse { return &apiv1.CreateKeyResponse{ Name: "yubikey:slot-id=82", PublicKey: yk.signerMap[slotMapping["82"]].(crypto.Signer).Public(), CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=82", }, } }, false}, {"ok TouchPolicyAlways", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ Name: "yubikey:slot-id=82", SignatureAlgorithm: apiv1.ECDSAWithSHA256, TouchPolicy: apiv1.TouchPolicyAlways, }}, slotMapping["82"], piv.PINPolicyAlways, piv.TouchPolicyAlways, func() *apiv1.CreateKeyResponse { return &apiv1.CreateKeyResponse{ Name: "yubikey:slot-id=82", PublicKey: yk.signerMap[slotMapping["82"]].(crypto.Signer).Public(), CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=82", }, } }, false}, {"ok TouchPolicyCached", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ Name: "yubikey:slot-id=82", SignatureAlgorithm: apiv1.ECDSAWithSHA256, TouchPolicy: apiv1.TouchPolicyCached, }}, slotMapping["82"], piv.PINPolicyAlways, piv.TouchPolicyCached, func() *apiv1.CreateKeyResponse { return &apiv1.CreateKeyResponse{ Name: "yubikey:slot-id=82", PublicKey: yk.signerMap[slotMapping["82"]].(crypto.Signer).Public(), CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=82", }, } }, false}, {"ok both policies", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ Name: "yubikey:slot-id=82", SignatureAlgorithm: apiv1.ECDSAWithSHA256, PINPolicy: apiv1.PINPolicyNever, TouchPolicy: apiv1.TouchPolicyAlways, }}, slotMapping["82"], piv.PINPolicyNever, piv.TouchPolicyAlways, func() *apiv1.CreateKeyResponse { return &apiv1.CreateKeyResponse{ Name: "yubikey:slot-id=82", PublicKey: yk.signerMap[slotMapping["82"]].(crypto.Signer).Public(), CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=82", }, } }, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &YubiKey{ yk: tt.fields.yk, pin: tt.fields.pin, managementKey: tt.fields.managementKey, } got, err := k.CreateKey(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("YubiKey.CreateKey() error = %v, wantErr %v", err, tt.wantErr) return } if v := yk.keyOptionsMap[tt.wantSlot].PINPolicy; !reflect.DeepEqual(v, tt.wantPinPolicy) { t.Errorf("YubiKey.CreateKey() PINPolicy = %v, want %v", v, tt.wantPinPolicy) } if v := yk.keyOptionsMap[tt.wantSlot].TouchPolicy; !reflect.DeepEqual(v, tt.wantTouchPolicy) { t.Errorf("YubiKey.CreateKey() TouchPolicy = %v, want %v", v, tt.wantTouchPolicy) } want := tt.wantFn() if !reflect.DeepEqual(got, want) { t.Errorf("YubiKey.CreateKey() = %v, want %v", got, want) } }) } } func TestYubiKey_CreateSigner(t *testing.T) { yk := newStubPivKey(t, ECDSA) ykFail := newStubPivKey(t, ECDSA) ykFail.signerMap[piv.SlotSignature] = "not-a-signer" type fields struct { yk pivKey pin string managementKey [24]byte } type args struct { req *apiv1.CreateSignerRequest } tests := []struct { name string fields fields args args want crypto.Signer wantErr bool }{ {"ok", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=9c", }}, yk.signerMap[piv.SlotSignature].(crypto.Signer), false}, {"ok with pin", fields{yk, "", piv.DefaultManagementKey}, args{&apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=9c?pin-value=123456", }}, yk.signerMap[piv.SlotSignature].(crypto.Signer), false}, {"fail getSlot", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=%%FF", }}, nil, true}, {"fail getPublicKey", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=85", }}, nil, true}, {"fail privateKey", fields{yk, "654321", piv.DefaultManagementKey}, args{&apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=9c", }}, nil, true}, {"fail signer", fields{ykFail, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateSignerRequest{ SigningKey: "yubikey:slot-id=9c", }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &YubiKey{ yk: tt.fields.yk, pin: tt.fields.pin, managementKey: tt.fields.managementKey, } got, err := k.CreateSigner(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("YubiKey.CreateSigner() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("YubiKey.CreateSigner() = %v, want %v", got, tt.want) } }) } } func TestYubiKey_CreateDecrypter(t *testing.T) { yk := newStubPivKey(t, RSA) ykFail := newStubPivKey(t, RSA) ykFail.signerMap[piv.SlotSignature] = "not-a-decrypter" // interface conversion: *ecdsa.PrivateKey is not crypto.Decrypter: missing method Decrypt ykFailEC := newStubPivKey(t, ECDSA) type fields struct { yk pivKey pin string managementKey [24]byte } type args struct { req *apiv1.CreateDecrypterRequest } tests := []struct { name string fields fields args args want crypto.Decrypter wantErr bool }{ {"ok", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateDecrypterRequest{ DecryptionKey: "yubikey:slot-id=9c", }}, yk.signerMap[piv.SlotSignature].(crypto.Decrypter), false}, {"ok with pin", fields{yk, "", piv.DefaultManagementKey}, args{&apiv1.CreateDecrypterRequest{ DecryptionKey: "yubikey:slot-id=9c?pin-value=123456", }}, yk.signerMap[piv.SlotSignature].(crypto.Decrypter), false}, {"fail getSlot", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateDecrypterRequest{ DecryptionKey: "yubikey:slot-id=%%FF", }}, nil, true}, {"fail getPublicKey", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateDecrypterRequest{ DecryptionKey: "yubikey:slot-id=85", }}, nil, true}, {"fail privateKey", fields{yk, "654321", piv.DefaultManagementKey}, args{&apiv1.CreateDecrypterRequest{ DecryptionKey: "yubikey:slot-id=9c", }}, nil, true}, {"fail signer", fields{ykFail, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateDecrypterRequest{ DecryptionKey: "yubikey:slot-id=9c", }}, nil, true}, {"fail no decrypt support", fields{ykFailEC, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateDecrypterRequest{ DecryptionKey: "yubikey:slot-id=9c", }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &YubiKey{ yk: tt.fields.yk, pin: tt.fields.pin, managementKey: tt.fields.managementKey, } got, err := k.CreateDecrypter(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("YubiKey.CreateDecrypter() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("YubiKey.CreateDecrypter() = %v, want %v", got, tt.want) } }) } } func TestYubiKey_CreateAttestation(t *testing.T) { yk := newStubPivKey(t, ECDSA) ykFail := newStubPivKey(t, ECDSA) delete(ykFail.certMap, slotAttestation) type fields struct { yk pivKey pin string managementKey [24]byte } type args struct { req *apiv1.CreateAttestationRequest } tests := []struct { name string fields fields args args want *apiv1.CreateAttestationResponse wantErr bool }{ {"ok", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateAttestationRequest{ Name: "yubikey:slot-id=9a", }}, &apiv1.CreateAttestationResponse{ Certificate: yk.attestMap[piv.SlotAuthentication], CertificateChain: []*x509.Certificate{yk.attestCA.Intermediate}, PublicKey: yk.attestMap[piv.SlotAuthentication].PublicKey, }, false}, {"fail getSlot", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateAttestationRequest{ Name: "yubikey://:slot-id=9a", }}, nil, true}, {"fail attest", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateAttestationRequest{ Name: "yubikey:slot-id=85", }}, nil, true}, {"fail certificate", fields{ykFail, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateAttestationRequest{ Name: "yubikey:slot-id=9a", }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &YubiKey{ yk: tt.fields.yk, pin: tt.fields.pin, managementKey: tt.fields.managementKey, } got, err := k.CreateAttestation(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("YubiKey.CreateAttestation() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("YubiKey.CreateAttestation() = %v, want %v", got, tt.want) } }) } } func TestYubiKey_Close(t *testing.T) { yk := newStubPivKey(t, ECDSA) type fields struct { yk pivKey pin string managementKey [24]byte } tests := []struct { name string fields fields wantErr bool }{ {"ok", fields{yk, "123456", piv.DefaultManagementKey}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &YubiKey{ yk: tt.fields.yk, pin: tt.fields.pin, managementKey: tt.fields.managementKey, } if err := k.Close(); (err != nil) != tt.wantErr { t.Errorf("YubiKey.Close() error = %v, wantErr %v", err, tt.wantErr) } }) } } golang-step-crypto-0.24.0/minica/000077500000000000000000000000001437037643100166035ustar00rootroot00000000000000golang-step-crypto-0.24.0/minica/minica.go000066400000000000000000000121251437037643100203730ustar00rootroot00000000000000package minica import ( "crypto" "crypto/x509" "fmt" "time" "go.step.sm/crypto/sshutil" "go.step.sm/crypto/x509util" "golang.org/x/crypto/ssh" ) // CA is the implementation of a simple X.509 and SSH CA. type CA struct { Root *x509.Certificate RootSigner crypto.Signer Intermediate *x509.Certificate Signer crypto.Signer SSHHostSigner ssh.Signer SSHUserSigner ssh.Signer } // New creates a new MiniCA, the custom options allows to overwrite templates, // signer types and certificate names. func New(opts ...Option) (*CA, error) { now := time.Now() o := newOptions().apply(opts) // Create root rootSubject := o.Name + " Root CA" rootSigner, err := o.GetSigner() if err != nil { return nil, err } rootCR, err := x509util.CreateCertificateRequest(rootSubject, []string{}, rootSigner) if err != nil { return nil, err } cert, err := x509util.NewCertificate(rootCR, x509util.WithTemplate(o.RootTemplate, x509util.CreateTemplateData(rootSubject, []string{}))) if err != nil { return nil, err } template := cert.GetCertificate() template.NotBefore = now template.NotAfter = now.Add(24 * time.Hour) root, err := x509util.CreateCertificate(template, template, rootSigner.Public(), rootSigner) if err != nil { return nil, err } // Create intermediate intSubject := o.Name + " Intermediate CA" intSigner, err := o.GetSigner() if err != nil { return nil, err } intCR, err := x509util.CreateCertificateRequest(intSubject, []string{}, intSigner) if err != nil { return nil, err } cert, err = x509util.NewCertificate(intCR, x509util.WithTemplate(o.IntermediateTemplate, x509util.CreateTemplateData(intSubject, []string{}))) if err != nil { return nil, err } template = cert.GetCertificate() template.NotBefore = now template.NotAfter = now.Add(24 * time.Hour) intermediate, err := x509util.CreateCertificate(template, root, intSigner.Public(), rootSigner) if err != nil { return nil, err } // Ssh host signer signer, err := o.GetSigner() if err != nil { return nil, err } sshHostSigner, err := ssh.NewSignerFromSigner(signer) if err != nil { return nil, err } // Ssh user signer signer, err = o.GetSigner() if err != nil { return nil, err } sshUserSigner, err := ssh.NewSignerFromSigner(signer) if err != nil { return nil, err } return &CA{ Root: root, RootSigner: rootSigner, Intermediate: intermediate, Signer: intSigner, SSHHostSigner: sshHostSigner, SSHUserSigner: sshUserSigner, }, nil } // Sign signs an X.509 certificate template using the intermediate certificate. // Sign will automatically populate the following fields if they are not // specified: // // - NotBefore will be set to the current time. // - NotAfter will be set to 24 hours after NotBefore. // - SerialNumber will be automatically generated. // - SubjectKeyId will be automatically generated. func (c *CA) Sign(template *x509.Certificate) (*x509.Certificate, error) { mut := *template if mut.NotBefore.IsZero() { mut.NotBefore = time.Now() } if mut.NotAfter.IsZero() { mut.NotAfter = mut.NotBefore.Add(24 * time.Hour) } return x509util.CreateCertificate(&mut, c.Intermediate, mut.PublicKey, c.Signer) } // SignCSR signs an X.509 certificate signing request. The custom options allows // to change the template used to convert the CSR to a certificate. func (c *CA) SignCSR(csr *x509.CertificateRequest, opts ...SignOption) (*x509.Certificate, error) { sans := append([]string{}, csr.DNSNames...) sans = append(sans, csr.EmailAddresses...) for _, ip := range csr.IPAddresses { sans = append(sans, ip.String()) } for _, u := range csr.URIs { sans = append(sans, u.String()) } o := newSignOptions().apply(opts) crt, err := x509util.NewCertificate(csr, x509util.WithTemplate(o.Template, x509util.CreateTemplateData(csr.Subject.CommonName, sans))) if err != nil { return nil, err } cert := crt.GetCertificate() if o.Modify != nil { if err := o.Modify(cert); err != nil { return nil, err } } return c.Sign(cert) } // SignSSH signs an SSH host or user certificate. SignSSH will automatically // populate the following fields if they are not specified: // // - ValidAfter will be set to the current time unless ValidBefore is set to ssh.CertTimeInfinity. // - ValidBefore will be set to 24 hours after ValidAfter. // - Nonce will be automatically generated. // - Serial will be automatically generated. // // If the SSH signer is an RSA key, it will use rsa-sha2-256 instead of the // default ssh-rsa (SHA-1), this method is currently deprecated and // rsa-sha2-256/512 are supported since OpenSSH 7.2 (2016). func (c *CA) SignSSH(template *ssh.Certificate) (*ssh.Certificate, error) { mut := *template if mut.ValidAfter == 0 && mut.ValidBefore != ssh.CertTimeInfinity { mut.ValidAfter = uint64(time.Now().Unix()) } if mut.ValidBefore == 0 { mut.ValidBefore = mut.ValidAfter + 24*60*60 } switch mut.CertType { case ssh.HostCert: return sshutil.CreateCertificate(&mut, c.SSHHostSigner) case ssh.UserCert: return sshutil.CreateCertificate(&mut, c.SSHUserSigner) default: return nil, fmt.Errorf("unknown certificate type") } } golang-step-crypto-0.24.0/minica/minica_test.go000066400000000000000000000352621437037643100214410ustar00rootroot00000000000000package minica import ( "crypto" "crypto/ed25519" "crypto/rand" "crypto/x509" "errors" "io" "net" "reflect" "testing" "time" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/x509util" "golang.org/x/crypto/ssh" ) type badSigner struct{} func (p badSigner) Public() crypto.PublicKey { return []byte("foo") } func (p badSigner) Sign(r io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { return nil, errors.New("foo") } type mockConnMetadata string func (c mockConnMetadata) User() string { return string(c) } func (c mockConnMetadata) SessionID() []byte { return []byte{1, 2, 3} } func (c mockConnMetadata) ClientVersion() []byte { return []byte{1, 2, 3} } func (c mockConnMetadata) ServerVersion() []byte { return []byte{1, 2, 3} } func (c mockConnMetadata) RemoteAddr() net.Addr { return &net.IPAddr{IP: net.IP{1, 2, 3, 4}} } func (c mockConnMetadata) LocalAddr() net.Addr { return &net.IPAddr{IP: net.IP{1, 2, 3, 4}} } func mustCA(t *testing.T, opts ...Option) *CA { t.Helper() ca, err := New(opts...) if err != nil { t.Fatal(err) } return ca } func TestNew(t *testing.T) { _, signer, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } failGetSigner := func(n int) func() (crypto.Signer, error) { var callNumber int return func() (crypto.Signer, error) { callNumber++ if callNumber == n { return nil, errors.New("an error") } return signer, nil } } failSigner := func(n int) func() (crypto.Signer, error) { var callNumber int return func() (crypto.Signer, error) { callNumber++ if callNumber == n { return badSigner{}, nil } return signer, nil } } type args struct { opts []Option } tests := []struct { name string args args wantName string wantErr bool }{ {"ok", args{}, "MiniCA", false}, {"ok with options", args{[]Option{WithName("Test"), WithGetSignerFunc(func() (crypto.Signer, error) { _, s, err := ed25519.GenerateKey(rand.Reader) return s, err })}}, "Test", false}, {"fail root signer", args{[]Option{WithGetSignerFunc(failGetSigner(1))}}, "", true}, {"fail intermediate signer", args{[]Option{WithGetSignerFunc(failGetSigner(2))}}, "", true}, {"fail host signer", args{[]Option{WithGetSignerFunc(failGetSigner(3))}}, "", true}, {"fail user signer", args{[]Option{WithGetSignerFunc(failGetSigner(4))}}, "", true}, {"fail root template", args{[]Option{WithRootTemplate(`fail "foo"`)}}, "", true}, {"fail intermediate template", args{[]Option{WithIntermediateTemplate(`fail "foo"`)}}, "", true}, {"fail root csr", args{[]Option{WithGetSignerFunc(failSigner(1))}}, "", true}, {"fail intermediate csr", args{[]Option{WithGetSignerFunc(failSigner(2))}}, "", true}, {"fail host ssh signer", args{[]Option{WithGetSignerFunc(failSigner(3))}}, "", true}, {"fail user ssh signer", args{[]Option{WithGetSignerFunc(failSigner(4))}}, "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := New(tt.args.opts...) if (err != nil) != tt.wantErr { t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr { if got != nil { t.Errorf("New() = %v, want nil", got) } } else { if got.Root == nil { t.Error("CA.Root should not be nil") } if got.Intermediate == nil { t.Error("CA.Intermediate should not be nil") } if got.Signer == nil { t.Error("CA.Signer should not be nil") } if got.SSHHostSigner == nil { t.Error("CA.SSHHostSigner should not be nil") } if got.SSHUserSigner == nil { t.Error("CA.SSHUserSigner should not be nil") } // Check common names if cn := got.Root.Subject.CommonName; cn != tt.wantName+" Root CA" { t.Errorf("CA.Root.Subject.CommonName = %s, want %s Root CA", cn, tt.wantName) } if cn := got.Root.Issuer.CommonName; cn != tt.wantName+" Root CA" { t.Errorf("CA.Root.Issuer.CommonName = %s, want %s Root CA", cn, tt.wantName) } if cn := got.Intermediate.Subject.CommonName; cn != tt.wantName+" Intermediate CA" { t.Errorf("CA.Intermediate.Subject.CommonName = %s, want %s Intermediate CA", cn, tt.wantName) } if cn := got.Intermediate.Issuer.CommonName; cn != tt.wantName+" Root CA" { t.Errorf("CA.Root.Intermediate.Issuer.CommonName = %s, want %s Root CA", cn, tt.wantName) } // Verify intermediate pool := x509.NewCertPool() pool.AddCert(got.Root) if _, err := got.Intermediate.Verify(x509.VerifyOptions{ Roots: pool, }); err != nil { t.Errorf("CA.Intermediate.Verify() error = %v", err) } } }) } } func TestCA_Sign(t *testing.T) { signer, err := keyutil.GenerateDefaultSigner() if err != nil { t.Fatal(err) } type args struct { template *x509.Certificate } tests := []struct { name string ca *CA args args wantErr bool }{ {"ok", mustCA(t), args{&x509.Certificate{ DNSNames: []string{"leaf.test.com"}, PublicKey: signer.Public(), }}, false}, {"ok with lifetime", mustCA(t), args{&x509.Certificate{ DNSNames: []string{"leaf.test.com"}, PublicKey: signer.Public(), NotBefore: time.Now(), NotAfter: time.Now().Add(1 * time.Hour), }}, false}, {"fail", mustCA(t), args{&x509.Certificate{ DNSNames: []string{"leaf.test.com"}, PublicKey: []byte("not a key"), }}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.ca.Sign(tt.args.template) if (err != nil) != tt.wantErr { t.Errorf("CA.Sign() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr { if got != nil { t.Errorf("CA.Sign() = %v, want nil", got) } } else { roots := x509.NewCertPool() roots.AddCert(tt.ca.Root) ints := x509.NewCertPool() ints.AddCert(tt.ca.Intermediate) if _, err := got.Verify(x509.VerifyOptions{ Roots: roots, Intermediates: ints, DNSName: "leaf.test.com", KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, }); err != nil { t.Errorf("Certificate.Verify() error = %v", err) } } }) } } func TestCA_Sign_mutation(t *testing.T) { signer, err := keyutil.GenerateDefaultSigner() if err != nil { t.Fatal(err) } ca := mustCA(t) template := &x509.Certificate{ DNSNames: []string{"leaf.test.com"}, PublicKey: signer.Public(), } got, err := ca.Sign(template) if err != nil { t.Fatal(err) } // Verify certificate roots := x509.NewCertPool() roots.AddCert(ca.Root) ints := x509.NewCertPool() ints.AddCert(ca.Intermediate) if _, err := got.Verify(x509.VerifyOptions{ Roots: roots, Intermediates: ints, DNSName: "leaf.test.com", KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, }); err != nil { t.Errorf("Certificate.Verify() error = %v", err) } // Check mutation if !template.NotBefore.IsZero() { t.Error("CA.Sign() mutated template.NotBefore") } if !template.NotAfter.IsZero() { t.Error("CA.Sign() mutated template.NotAfter") } if template.SerialNumber != nil { t.Error("CA.Sign() mutated template.SerialNumber") } if template.SubjectKeyId != nil { t.Error("CA.Sign() mutated template.SubjectKeyId") } if got.NotBefore.IsZero() { t.Error("CA.Sign() got.NotBefore should not be 0") } if got.NotAfter.IsZero() { t.Error("CA.Sign() got.NotAfter should not be 0") } if got.SerialNumber == nil { t.Error("CA.Sign() got.SerialNumber should not be nil") } if got.SubjectKeyId == nil { t.Error("CA.Sign() got.SubjectKeyId should not be nil") } } func TestCA_SignCSR(t *testing.T) { signer, err := keyutil.GenerateDefaultSigner() if err != nil { t.Fatal(err) } csr, err := x509util.CreateCertificateRequest("", []string{"leaf.test.com", "127.0.0.1", "test@test.com", "uuid:64757c7c-33b0-4125-9a73-be41e17f9f98"}, signer) if err != nil { t.Fatal(err) } type args struct { csr *x509.CertificateRequest opts []SignOption } tests := []struct { name string ca *CA args args wantDNSName string wantKeyUsages []x509.ExtKeyUsage wantErr bool }{ {"ok", mustCA(t), args{csr, nil}, "leaf.test.com", []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, false}, {"ok with modify", mustCA(t), args{csr, []SignOption{WithModifyFunc(func(cert *x509.Certificate) error { cert.DNSNames = []string{"foo.test.com"} cert.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning} return nil })}}, "foo.test.com", []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, false}, {"fail new certificate", mustCA(t), args{&x509.CertificateRequest{}, nil}, "", nil, true}, {"fail modify", mustCA(t), args{csr, []SignOption{WithModifyFunc(func(cert *x509.Certificate) error { return errors.New("an error") })}}, "", nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.ca.SignCSR(tt.args.csr, tt.args.opts...) if (err != nil) != tt.wantErr { t.Errorf("CA.SignCSR() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr { if got != nil { t.Errorf("CA.Sign() = %v, want nil", got) } } else { roots := x509.NewCertPool() roots.AddCert(tt.ca.Root) ints := x509.NewCertPool() ints.AddCert(tt.ca.Intermediate) if _, err := got.Verify(x509.VerifyOptions{ Roots: roots, Intermediates: ints, DNSName: tt.wantDNSName, KeyUsages: tt.wantKeyUsages, }); err != nil { t.Errorf("Certificate.Verify() error = %v", err) } } }) } } func TestCA_SignSSH(t *testing.T) { signer, err := keyutil.GenerateDefaultSigner() if err != nil { t.Fatal(err) } publicKey, err := ssh.NewPublicKey(signer.Public()) if err != nil { t.Fatal(err) } type args struct { cert *ssh.Certificate } tests := []struct { name string ca *CA args args wantCertType uint32 wantPrincipal string wantErr bool }{ {"ok host", mustCA(t), args{&ssh.Certificate{ Key: publicKey, Serial: 1234, CertType: ssh.HostCert, KeyId: "ssh.test.com", ValidPrincipals: []string{"ssh.test.com"}, }}, ssh.HostCert, "ssh.test.com", false}, {"ok user", mustCA(t), args{&ssh.Certificate{ Key: publicKey, Serial: 1234, CertType: ssh.UserCert, KeyId: "jane@test.com", ValidPrincipals: []string{"jane"}, ValidAfter: uint64(time.Now().Unix()), ValidBefore: uint64(time.Now().Add(time.Hour).Unix()), }}, ssh.UserCert, "jane", false}, {"ok infinity", mustCA(t), args{&ssh.Certificate{ Key: publicKey, Serial: 1234, CertType: ssh.UserCert, KeyId: "jane@test.com", ValidPrincipals: []string{"jane"}, ValidBefore: ssh.CertTimeInfinity, }}, ssh.UserCert, "jane", false}, {"fail type", mustCA(t), args{&ssh.Certificate{ Key: publicKey, Serial: 1234, CertType: 100, KeyId: "jane@test.com", ValidPrincipals: []string{"jane"}, }}, 0, "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.ca.SignSSH(tt.args.cert) if (err != nil) != tt.wantErr { t.Errorf("CA.SignSSH() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr { if got != nil { t.Errorf("CA.SignSSH() = %v, want nil", got) } } else { checker := ssh.CertChecker{ IsUserAuthority: func(auth ssh.PublicKey) bool { return reflect.DeepEqual(auth, tt.ca.SSHUserSigner.PublicKey()) }, IsHostAuthority: func(auth ssh.PublicKey, address string) bool { return reflect.DeepEqual(auth, tt.ca.SSHHostSigner.PublicKey()) }, } switch tt.wantCertType { case ssh.HostCert: if err := checker.CheckHostKey(tt.wantPrincipal+":22", &net.IPAddr{IP: net.IP{1, 2, 3, 4}}, got); err != nil { t.Errorf("CertChecker.CheckHostKey() error = %v", err) } case ssh.UserCert: if _, err := checker.Authenticate(mockConnMetadata(tt.wantPrincipal), got); err != nil { t.Errorf("CertChecker.Authenticate() error = %v", err) } default: t.Fatalf("unknown cert type %v", tt.wantCertType) } } }) } } func TestCA_SignSSH_mutation(t *testing.T) { signer, err := keyutil.GenerateDefaultSigner() if err != nil { t.Fatal(err) } publicKey, err := ssh.NewPublicKey(signer.Public()) if err != nil { t.Fatal(err) } template := &ssh.Certificate{ Key: publicKey, CertType: ssh.HostCert, KeyId: "ssh.test.com", ValidPrincipals: []string{"ssh.test.com"}, } ca := mustCA(t) got, err := ca.SignSSH(template) if err != nil { t.Fatal(err) } // Validate certificate checker := ssh.CertChecker{ IsHostAuthority: func(auth ssh.PublicKey, address string) bool { return reflect.DeepEqual(auth, ca.SSHHostSigner.PublicKey()) }, } if err := checker.CheckHostKey("ssh.test.com:22", &net.IPAddr{IP: net.IP{1, 2, 3, 4}}, got); err != nil { t.Errorf("CertChecker.CheckHostKey() error = %v", err) } // Validate mutation if template.ValidAfter != 0 { t.Error("CA.SignSSH() mutated template.ValidAfter") } if template.ValidBefore != 0 { t.Error("CA.SignSSH() mutated template.ValidBefore") } if template.Nonce != nil { t.Error("CA.SignSSH() mutated template.Nonce") } if template.Serial != 0 { t.Error("CA.SignSSH() mutated template.Serial") } if got.ValidAfter == 0 { t.Error("CA.SignSSH() got.ValidAfter should not be 0") } if got.ValidBefore == 0 { t.Error("CA.SignSSH() got.ValidBefore should not be 0") } if len(got.Nonce) == 0 { t.Error("CA.SignSSH() got.Nonce should not be empty") } if got.Serial == 0 { t.Error("CA.SignSSH() got.Serial should not be 0") } } func TestCA_SignSSH_infinity(t *testing.T) { signer, err := keyutil.GenerateDefaultSigner() if err != nil { t.Fatal(err) } publicKey, err := ssh.NewPublicKey(signer.Public()) if err != nil { t.Fatal(err) } template := &ssh.Certificate{ Key: publicKey, Serial: 1234, CertType: ssh.UserCert, KeyId: "jane@test.com", ValidPrincipals: []string{"jane"}, ValidBefore: ssh.CertTimeInfinity, } ca := mustCA(t) got, err := ca.SignSSH(template) if err != nil { t.Fatal(err) } // Validate certificate checker := ssh.CertChecker{ IsUserAuthority: func(auth ssh.PublicKey) bool { return reflect.DeepEqual(auth, ca.SSHUserSigner.PublicKey()) }, } if _, err := checker.Authenticate(mockConnMetadata("jane"), got); err != nil { t.Errorf("CertChecker.Authenticate() error = %v", err) } if got.ValidAfter != 0 { t.Error("CA.SignSSH() got.ValidAfter should be 0") } if got.ValidBefore != ssh.CertTimeInfinity { t.Error("CA.SignSSH() got.ValidBefore should not be ssh.CertTimInfinity") } } golang-step-crypto-0.24.0/minica/options.go000066400000000000000000000050221437037643100206240ustar00rootroot00000000000000package minica import ( "crypto" "crypto/x509" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/x509util" ) type options struct { Name string RootTemplate string IntermediateTemplate string GetSigner func() (crypto.Signer, error) } // Option is the type used to pass custom attributes to the constructor. type Option func(o *options) func newOptions() *options { return &options{ Name: "MiniCA", RootTemplate: x509util.DefaultRootTemplate, IntermediateTemplate: x509util.DefaultIntermediateTemplate, GetSigner: keyutil.GenerateDefaultSigner, } } func (o *options) apply(opts []Option) *options { for _, fn := range opts { fn(o) } return o } // WithName is an option that allows to overwrite the default name MiniCA. With // the default templates, the root and intermediate certificate common names // would be " Root CA" and " Intermediate CA". func WithName(name string) Option { return func(o *options) { o.Name = name } } // WithRootTemplate is an option that allows to overwrite the template used to // create the root certificate. func WithRootTemplate(template string) Option { return func(o *options) { o.RootTemplate = template } } // WithIntermediateTemplate is an option that allows to overwrite the template // used to create the intermediate certificate. func WithIntermediateTemplate(template string) Option { return func(o *options) { o.IntermediateTemplate = template } } // WithGetSignerFunc is an option that allows to overwrite the default function to // create a signer. func WithGetSignerFunc(fn func() (crypto.Signer, error)) Option { return func(o *options) { o.GetSigner = fn } } type signOptions struct { Template string Modify func(*x509.Certificate) error } // SignOption is the type used to pass custom attributes when signing a // certificate request. type SignOption func(o *signOptions) func newSignOptions() *signOptions { return &signOptions{ Template: x509util.DefaultLeafTemplate, } } func (o *signOptions) apply(opts []SignOption) *signOptions { for _, fn := range opts { fn(o) } return o } // WithTemplate allows to update the template used to convert a CSR into a // certificate. func WithTemplate(template string) SignOption { return func(o *signOptions) { o.Template = template } } // WithModifyFunc allows to update the certificate template before the signing // it. func WithModifyFunc(fn func(*x509.Certificate) error) SignOption { return func(o *signOptions) { o.Modify = fn } } golang-step-crypto-0.24.0/pemutil/000077500000000000000000000000001437037643100170225ustar00rootroot00000000000000golang-step-crypto-0.24.0/pemutil/cosign.go000066400000000000000000000037671437037643100206500ustar00rootroot00000000000000package pemutil import ( "crypto" "crypto/x509" "encoding/json" "github.com/pkg/errors" "golang.org/x/crypto/nacl/secretbox" "golang.org/x/crypto/scrypt" ) type cosignEnvelope struct { KDF cosignKDF `json:"kdf"` Cipher cosignCipher `json:"cipher"` Ciphertext []byte `json:"ciphertext"` } type cosignKDF struct { Name string `json:"name"` Params cosignScryptParams `json:"params"` Salt []byte `json:"salt"` } type cosignScryptParams struct { N int `json:"N"` R int `json:"r"` P int `json:"p"` } type cosignCipher struct { Name string `json:"name"` Nonce []byte `json:"nonce"` } // ParseCosignPrivateKey returns the private key encoded using cosign envelope. // If an incorrect password is detected an x509.IncorrectPasswordError is // returned. // // Cosign keys are encrypted under a password using scrypt as a KDF and // nacl/secretbox for encryption. func ParseCosignPrivateKey(data, password []byte) (crypto.PrivateKey, error) { var env cosignEnvelope if err := json.Unmarshal(data, &env); err != nil { return nil, errors.Wrap(err, "error unmarshaling key") } if env.KDF.Name != "scrypt" { return nil, errors.Errorf("error parsing key: unsupported kdf %s", env.KDF.Name) } if env.Cipher.Name != "nacl/secretbox" { return nil, errors.Errorf("error parsing key: unsupported cipher %s", env.Cipher.Name) } if len(env.Cipher.Nonce) != 24 { return nil, errors.New("error parsing key: nonce must be 24 bytes long") } params := env.KDF.Params k, err := scrypt.Key(password, env.KDF.Salt, params.N, params.R, params.P, 32) if err != nil { return nil, errors.Wrap(err, "error generating key") } var nonce [24]byte var key [32]byte copy(nonce[:], env.Cipher.Nonce) copy(key[:], k) out, ok := secretbox.Open(nil, env.Ciphertext, &nonce, &key) if !ok { return nil, x509.IncorrectPasswordError } priv, err := x509.ParsePKCS8PrivateKey(out) if err != nil { return nil, errors.Wrap(err, "error parsing pkcs8 key") } return priv, nil } golang-step-crypto-0.24.0/pemutil/cosign_test.go000066400000000000000000000132241437037643100216740ustar00rootroot00000000000000package pemutil import ( "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/x509" "encoding/json" "encoding/pem" "errors" "math/big" "os" "reflect" "testing" "golang.org/x/crypto/nacl/secretbox" "golang.org/x/crypto/scrypt" ) func TestParseCosignPrivateKey(t *testing.T) { b, err := os.ReadFile("testdata/cosign.enc.pem") if err != nil { t.Fatal(err) } block, _ := pem.Decode(b) if block == nil { t.Fatal("error decoding testdata/cosign.enc.pem") } var env cosignEnvelope if err := json.Unmarshal(block.Bytes, &env); err != nil { t.Fatal(err) } marshalEnv := func(in cosignEnvelope) []byte { b, err := json.Marshal(in) if err != nil { t.Fatal(err) } return b } want := &ecdsa.PrivateKey{ PublicKey: ecdsa.PublicKey{ Curve: elliptic.P256(), X: big.NewInt(0).SetBytes([]byte{ 0xdb, 0xa8, 0x69, 0x99, 0xc1, 0xda, 0xd8, 0x26, 0x1a, 0xcf, 0xb9, 0x60, 0x4d, 0x62, 0x8b, 0x04, 0xd9, 0x52, 0xbf, 0x61, 0x32, 0x3f, 0x14, 0xa2, 0x2c, 0x61, 0xbc, 0xcb, 0xd5, 0xe4, 0xd9, 0x63, }), Y: big.NewInt(0).SetBytes([]byte{ 0x97, 0xab, 0x65, 0xa5, 0xd6, 0x76, 0x8f, 0xbe, 0xb5, 0xb9, 0xe5, 0x9d, 0x5f, 0x93, 0xe5, 0xde, 0x07, 0x58, 0x5e, 0xa6, 0x3a, 0x32, 0x26, 0x71, 0xba, 0x06, 0x3f, 0x0c, 0xd6, 0x5e, 0x97, 0x2a, }), }, D: big.NewInt(0).SetBytes([]byte{ 0x4c, 0x96, 0x4f, 0x6b, 0x65, 0x3b, 0x61, 0x1e, 0xe1, 0x84, 0x4b, 0xa2, 0xf7, 0x5a, 0x59, 0xf2, 0xab, 0x56, 0xf5, 0xdb, 0x6a, 0x96, 0x54, 0x6f, 0xf0, 0xd7, 0x51, 0x99, 0x0f, 0x0b, 0xa5, 0x38, }), } type args struct { data []byte password []byte } tests := []struct { name string args args want crypto.PrivateKey wantErr bool }{ {"ok", args{block.Bytes, []byte("mypassword")}, want, false}, {"fail password", args{block.Bytes, []byte("password")}, nil, true}, {"fail unmarshal", args{block.Bytes[1:], []byte("mypassword")}, nil, true}, {"fail kdf", args{func() []byte { key := env key.KDF.Name = "bcrypt" return marshalEnv(key) }(), []byte("mypassword")}, nil, true}, {"fail cipher", args{func() []byte { key := env key.Cipher.Name = "nacl/box" return marshalEnv(key) }(), []byte("mypassword")}, nil, true}, {"fail nonce too large", args{func() []byte { key := env key.Cipher.Nonce = append(key.Cipher.Nonce, '0') return marshalEnv(key) }(), []byte("mypassword")}, nil, true}, {"fail nonce too short", args{func() []byte { key := env key.Cipher.Nonce = key.Cipher.Nonce[:23] return marshalEnv(key) }(), []byte("mypassword")}, nil, true}, {"fail kdf.N", args{func() []byte { key := env key.KDF.Params.N++ return marshalEnv(key) }(), []byte("mypassword")}, nil, true}, {"fail kdf.R", args{func() []byte { key := env key.KDF.Params.R++ return marshalEnv(key) }(), []byte("mypassword")}, nil, true}, {"fail kdf.P", args{func() []byte { key := env key.KDF.Params.P++ return marshalEnv(key) }(), []byte("mypassword")}, nil, true}, {"fail kdf.Salt", args{func() []byte { key := env key.KDF.Salt[10]++ return marshalEnv(key) }(), []byte("mypassword")}, nil, true}, {"fail ciphertext", args{func() []byte { key := env key.Ciphertext[10]++ return marshalEnv(key) }(), []byte("mypassword")}, nil, true}, {"fail parsePKCS8PrivateKey", args{func() []byte { var n [24]byte var k [32]byte key := env b, err := scrypt.Key([]byte("mypassword"), key.KDF.Salt, key.KDF.Params.N, key.KDF.Params.R, key.KDF.Params.P, 32) if err != nil { t.Fatal(err) } copy(n[:], key.Cipher.Nonce) copy(k[:], b) key.Ciphertext = secretbox.Seal(nil, []byte("not a key"), &n, &k) return marshalEnv(key) }(), []byte("mypassword")}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ParseCosignPrivateKey(tt.args.data, tt.args.password) if (err != nil) != tt.wantErr { t.Errorf("ParseCosignPrivateKey() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ParseCosignPrivateKey() = %v, want %v", got, tt.want) } }) } } func TestParseCosignPrivateKey_equal(t *testing.T) { parsePem := func(fn string) []byte { b, err := os.ReadFile(fn) if err != nil { t.Fatal(err) } block, _ := pem.Decode(b) if block == nil { t.Fatalf("error decoding %s", fn) } return block.Bytes } key, err := ParseCosignPrivateKey(parsePem("testdata/cosign.enc.pem"), []byte("mypassword")) if err != nil { t.Errorf("ParseCosignPrivateKey() error = %v", err) return } priv, err := x509.ParsePKCS8PrivateKey(parsePem("testdata/cosign.pem")) if err != nil { t.Errorf("ParsePKCS8PrivateKey() error = %v", err) return } if !reflect.DeepEqual(priv, key) { t.Errorf("Private keys do not match() = %v, want %v", priv, key) } pub, err := x509.ParsePKIXPublicKey(parsePem("testdata/cosign.pub.pem")) if err != nil { t.Errorf("ParsePKIXPublicKey() error = %v", err) return } if !reflect.DeepEqual(pub, key.(crypto.Signer).Public()) { t.Errorf("Public keys do not match() = %v, want %v", pub, key.(crypto.Signer).Public()) } } func TestParseCosignPrivateKey_IncorrectPasswordError(t *testing.T) { b, err := os.ReadFile("testdata/cosign.enc.pem") if err != nil { t.Fatal(err) } block, _ := pem.Decode(b) if block == nil { t.Fatal("error decoding testdata/cosign.enc.pem") } _, err = ParseCosignPrivateKey(block.Bytes, []byte("mypassword")) if err != nil { t.Errorf("ParseCosignPrivateKey() error = %v", err) } _, err = ParseCosignPrivateKey(block.Bytes, []byte("foobar")) if !errors.Is(err, x509.IncorrectPasswordError) { t.Errorf("ParseCosignPrivateKey() error = %v, wantErr = %v", err, x509.IncorrectPasswordError) } } golang-step-crypto-0.24.0/pemutil/pem.go000066400000000000000000000464631437037643100201470ustar00rootroot00000000000000// Package pemutil implements utilities to parse keys and certificates. It also // includes a method to serialize keys, X.509 certificates and certificate // requests to PEM. package pemutil import ( "bytes" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" "math/big" "os" "github.com/pkg/errors" "go.step.sm/crypto/internal/utils" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/x25519" "golang.org/x/crypto/ssh" ) // DefaultEncCipher is the default algorithm used when encrypting sensitive // data in the PEM format. var DefaultEncCipher = x509.PEMCipherAES256 // PasswordPrompter defines the function signature for the PromptPassword // callback. type PasswordPrompter func(s string) ([]byte, error) // FileWriter defines the function signature for the WriteFile callback. type FileWriter func(filename string, data []byte, perm os.FileMode) error // PromptPassword is a method used to prompt for a password to decode encrypted // keys. If this method is not defined and the key or password are not passed, // the parse of the key will fail. var PromptPassword PasswordPrompter // WriteFile is a method used to write a file, by default it uses a wrapper over // ioutil.WriteFile, but it can be set to a custom method, that for example can // check if a file exists and prompts the user if it should be overwritten. var WriteFile FileWriter = utils.WriteFile // context add options to the pem methods. type context struct { filename string perm os.FileMode password []byte pkcs8 bool openSSH bool comment string firstBlock bool passwordPrompt string passwordPrompter PasswordPrompter } // newContext initializes the context with a filename. func newContext(name string) *context { return &context{ filename: name, perm: 0600, } } // apply the context options and return the first error if exists. func (c *context) apply(opts []Options) error { for _, fn := range opts { if err := fn(c); err != nil { return err } } return nil } // promptPassword returns the password or prompts for one. func (c *context) promptPassword() ([]byte, error) { switch { case len(c.password) > 0: return c.password, nil case c.passwordPrompter != nil: return c.passwordPrompter(c.passwordPrompt) case PromptPassword != nil: return PromptPassword(fmt.Sprintf("Please enter the password to decrypt %s", c.filename)) default: return nil, errors.Errorf("error decoding %s: key is password protected", c.filename) } } // promptEncryptPassword returns the password or prompts for one if // WithPassword, WithPasswordFile or WithPasswordPrompt have been used. This // method is used to encrypt keys, and it will only use the options passed, it // will not use the global PromptPassword. func (c *context) promptEncryptPassword() ([]byte, error) { switch { case len(c.password) > 0: return c.password, nil case c.passwordPrompter != nil: return c.passwordPrompter(c.passwordPrompt) default: return nil, nil } } // Options is the type to add attributes to the context. type Options func(o *context) error // withContext replaces the context with the given one. func withContext(c *context) Options { return func(ctx *context) error { *ctx = *c return nil } } // WithFilename is a method that adds the given filename to the context. func WithFilename(name string) Options { return func(ctx *context) error { ctx.filename = name // Default perm mode if not set if ctx.perm == 0 { ctx.perm = 0600 } return nil } } // ToFile is a method that adds the given filename and permissions to the // context. It is used in the Serialize to store PEM in disk. func ToFile(name string, perm os.FileMode) Options { return func(ctx *context) error { ctx.filename = name ctx.perm = perm return nil } } // WithPassword is a method that adds the given password to the context. func WithPassword(pass []byte) Options { return func(ctx *context) error { ctx.password = pass return nil } } // WithPasswordFile is a method that adds the password in a file to the context. func WithPasswordFile(filename string) Options { return func(ctx *context) error { b, err := utils.ReadPasswordFromFile(filename) if err != nil { return err } ctx.password = b return nil } } // WithPasswordPrompt ask the user for a password and adds it to the context. func WithPasswordPrompt(prompt string, fn PasswordPrompter) Options { return func(ctx *context) error { ctx.passwordPrompt = prompt ctx.passwordPrompter = fn return nil } } // WithPKCS8 with v set to true returns an option used in the Serialize method // to use the PKCS#8 encoding form on the private keys. With v set to false // default form will be used. func WithPKCS8(v bool) Options { return func(ctx *context) error { ctx.pkcs8 = v return nil } } // WithOpenSSH is an option used in the Serialize method to use OpenSSH encoding // form on the private keys. With v set to false default form will be used. func WithOpenSSH(v bool) Options { return func(ctx *context) error { ctx.openSSH = v return nil } } // WithComment is an option used in the Serialize method to add a comment in the // OpenSSH private keys. WithOpenSSH must be set to true too. func WithComment(comment string) Options { return func(ctx *context) error { ctx.comment = comment return nil } } // WithFirstBlock will avoid failing if a PEM contains more than one block or // certificate and it will only look at the first. func WithFirstBlock() Options { return func(ctx *context) error { ctx.firstBlock = true return nil } } // ParseCertificate extracts the first certificate from the given pem. func ParseCertificate(pemData []byte) (*x509.Certificate, error) { var block *pem.Block for len(pemData) > 0 { block, pemData = pem.Decode(pemData) if block == nil { return nil, errors.New("error decoding pem block") } if block.Type != "CERTIFICATE" || len(block.Headers) != 0 { continue } cert, err := x509.ParseCertificate(block.Bytes) if err != nil { return nil, errors.Wrap(err, "error parsing certificate") } return cert, nil } return nil, errors.New("error parsing certificate: no certificate found") } // ParseCertificateBundle extracts all the certificates in the given data. func ParseCertificateBundle(pemData []byte) ([]*x509.Certificate, error) { var block *pem.Block var certs []*x509.Certificate for len(pemData) > 0 { block, pemData = pem.Decode(pemData) if block == nil { return nil, errors.New("error decoding pem block") } if block.Type != "CERTIFICATE" || len(block.Headers) != 0 { continue } cert, err := x509.ParseCertificate(block.Bytes) if err != nil { return nil, errors.Wrap(err, "error parsing certificate") } certs = append(certs, cert) } if len(certs) == 0 { return nil, errors.New("error parsing certificate: no certificate found") } return certs, nil } // ParseCertificateRequest extracts the first certificate from the given pem. func ParseCertificateRequest(pemData []byte) (*x509.CertificateRequest, error) { var block *pem.Block for len(pemData) > 0 { block, pemData = pem.Decode(pemData) if block == nil { return nil, errors.New("error decoding pem block") } if (block.Type != "CERTIFICATE REQUEST" && block.Type != "NEW CERTIFICATE REQUEST") || len(block.Headers) != 0 { continue } csr, err := x509.ParseCertificateRequest(block.Bytes) if err != nil { return nil, errors.Wrap(err, "error parsing certificate request") } return csr, nil } return nil, errors.New("error parsing certificate request: no certificate found") } // ReadCertificate returns a *x509.Certificate from the given filename. It // supports certificates formats PEM and DER. func ReadCertificate(filename string, opts ...Options) (*x509.Certificate, error) { b, err := utils.ReadFile(filename) if err != nil { return nil, err } // PEM format if bytes.HasPrefix(b, []byte("-----BEGIN ")) { var crt interface{} crt, err = Read(filename, opts...) if err != nil { return nil, err } switch crt := crt.(type) { case *x509.Certificate: return crt, nil default: return nil, errors.Errorf("error decoding PEM: file '%s' does not contain a certificate", filename) } } // DER format (binary) crt, err := x509.ParseCertificate(b) return crt, errors.Wrapf(err, "error parsing %s", filename) } // ReadCertificateBundle returns a list of *x509.Certificate from the given // filename. It supports certificates formats PEM and DER. If a DER-formatted // file is given only one certificate will be returned. func ReadCertificateBundle(filename string) ([]*x509.Certificate, error) { b, err := utils.ReadFile(filename) if err != nil { return nil, err } // PEM format if bytes.HasPrefix(b, []byte("-----BEGIN ")) { var block *pem.Block var bundle []*x509.Certificate for len(b) > 0 { block, b = pem.Decode(b) if block == nil { break } if block.Type != "CERTIFICATE" { return nil, errors.Errorf("error decoding PEM: file '%s' is not a certificate bundle", filename) } var crt *x509.Certificate crt, err = x509.ParseCertificate(block.Bytes) if err != nil { return nil, errors.Wrapf(err, "error parsing %s", filename) } bundle = append(bundle, crt) } if len(b) > 0 { return nil, errors.Errorf("error decoding PEM: file '%s' contains unexpected data", filename) } return bundle, nil } // DER format (binary) crt, err := x509.ParseCertificate(b) if err != nil { return nil, errors.Wrapf(err, "error parsing %s", filename) } return []*x509.Certificate{crt}, nil } // ReadCertificateRequest returns a *x509.CertificateRequest from the given // filename. It supports certificates formats PEM and DER. func ReadCertificateRequest(filename string) (*x509.CertificateRequest, error) { b, err := utils.ReadFile(filename) if err != nil { return nil, err } // PEM format if bytes.HasPrefix(b, []byte("-----BEGIN ")) { csr, err := Parse(b, WithFilename(filename)) if err != nil { return nil, err } switch csr := csr.(type) { case *x509.CertificateRequest: return csr, nil default: return nil, errors.Errorf("error decoding PEM: file '%s' does not contain a certificate request", filename) } } // DER format (binary) csr, err := x509.ParseCertificateRequest(b) return csr, errors.Wrapf(err, "error parsing %s", filename) } // Parse returns the key or certificate PEM-encoded in the given bytes. func Parse(b []byte, opts ...Options) (interface{}, error) { // Populate options ctx := newContext("PEM") if err := ctx.apply(opts); err != nil { return nil, err } block, rest := pem.Decode(b) switch { case block == nil: return nil, errors.Errorf("error decoding %s: not a valid PEM encoded block", ctx.filename) case len(bytes.TrimSpace(rest)) > 0 && !ctx.firstBlock: return nil, errors.Errorf("error decoding %s: contains more than one PEM encoded block", ctx.filename) } // PEM is encrypted: ask for password if block.Headers["Proc-Type"] == "4,ENCRYPTED" || block.Type == "ENCRYPTED PRIVATE KEY" { pass, err := ctx.promptPassword() if err != nil { return nil, err } block.Bytes, err = DecryptPEMBlock(block, pass) if err != nil { return nil, errors.Wrapf(err, "error decrypting %s", ctx.filename) } } switch block.Type { case "PUBLIC KEY": pub, err := x509.ParsePKIXPublicKey(block.Bytes) return pub, errors.Wrapf(err, "error parsing %s", ctx.filename) case "RSA PRIVATE KEY": priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) return priv, errors.Wrapf(err, "error parsing %s", ctx.filename) case "EC PRIVATE KEY": priv, err := x509.ParseECPrivateKey(block.Bytes) return priv, errors.Wrapf(err, "error parsing %s", ctx.filename) case "PRIVATE KEY", "ENCRYPTED PRIVATE KEY": priv, err := x509.ParsePKCS8PrivateKey(block.Bytes) return priv, errors.Wrapf(err, "error parsing %s", ctx.filename) case "OPENSSH PRIVATE KEY": priv, err := ParseOpenSSHPrivateKey(b, withContext(ctx)) return priv, errors.Wrapf(err, "error parsing %s", ctx.filename) case "CERTIFICATE": crt, err := x509.ParseCertificate(block.Bytes) return crt, errors.Wrapf(err, "error parsing %s", ctx.filename) case "CERTIFICATE REQUEST", "NEW CERTIFICATE REQUEST": csr, err := x509.ParseCertificateRequest(block.Bytes) return csr, errors.Wrapf(err, "error parsing %s", ctx.filename) case "ENCRYPTED COSIGN PRIVATE KEY": pass, err := ctx.promptPassword() if err != nil { return nil, err } priv, err := ParseCosignPrivateKey(block.Bytes, pass) return priv, errors.Wrapf(err, "error parsing %s", ctx.filename) case "NEBULA X25519 PUBLIC KEY": if len(block.Bytes) != x25519.PublicKeySize { return nil, errors.Errorf("error parsing %s: key is not 32 bytes", ctx.filename) } return x25519.PublicKey(block.Bytes), nil case "NEBULA X25519 PRIVATE KEY": if len(block.Bytes) != x25519.PrivateKeySize { return nil, errors.Errorf("error parsing %s: key is not 32 bytes", ctx.filename) } return x25519.PrivateKey(block.Bytes), nil default: return nil, errors.Errorf("error decoding %s: contains an unexpected header '%s'", ctx.filename, block.Type) } } // ParseKey returns the key or the public key of a certificate or certificate // signing request in the given PEM-encoded bytes. func ParseKey(b []byte, opts ...Options) (interface{}, error) { k, err := Parse(b, opts...) if err != nil { return nil, err } return keyutil.ExtractKey(k) } // Read returns the key or certificate encoded in the given PEM file. // If the file is encrypted it will ask for a password and it will try // to decrypt it. // // Supported keys algorithms are RSA and EC. Supported standards for private // keys are PKCS#1, PKCS#8, RFC5915 for EC, and base64-encoded DER for // certificates and public keys. func Read(filename string, opts ...Options) (interface{}, error) { b, err := utils.ReadFile(filename) if err != nil { return nil, err } // force given filename opts = append(opts, WithFilename(filename)) return Parse(b, opts...) } // Serialize will serialize the input to a PEM formatted block and apply // modifiers. func Serialize(in interface{}, opts ...Options) (*pem.Block, error) { ctx := new(context) if err := ctx.apply(opts); err != nil { return nil, err } var p *pem.Block var isPrivateKey bool switch k := in.(type) { case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey: b, err := x509.MarshalPKIXPublicKey(k) if err != nil { return nil, errors.WithStack(err) } p = &pem.Block{ Type: "PUBLIC KEY", Bytes: b, } case *rsa.PrivateKey: isPrivateKey = true switch { case ctx.pkcs8: b, err := x509.MarshalPKCS8PrivateKey(k) if err != nil { return nil, err } p = &pem.Block{ Type: "PRIVATE KEY", Bytes: b, } case ctx.openSSH: return SerializeOpenSSHPrivateKey(k, withContext(ctx)) default: p = &pem.Block{ Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k), } } case *ecdsa.PrivateKey: isPrivateKey = true switch { case ctx.pkcs8: b, err := x509.MarshalPKCS8PrivateKey(k) if err != nil { return nil, err } p = &pem.Block{ Type: "PRIVATE KEY", Bytes: b, } case ctx.openSSH: return SerializeOpenSSHPrivateKey(k, withContext(ctx)) default: b, err := x509.MarshalECPrivateKey(k) if err != nil { return nil, errors.Wrap(err, "failed to marshal private key") } p = &pem.Block{ Type: "EC PRIVATE KEY", Bytes: b, } } case ed25519.PrivateKey: isPrivateKey = true switch { case !ctx.pkcs8 && ctx.openSSH: return SerializeOpenSSHPrivateKey(k, withContext(ctx)) default: // Ed25519 keys will use pkcs8 by default ctx.pkcs8 = true b, err := x509.MarshalPKCS8PrivateKey(k) if err != nil { return nil, err } p = &pem.Block{ Type: "PRIVATE KEY", Bytes: b, } } case *x509.Certificate: p = &pem.Block{ Type: "CERTIFICATE", Bytes: k.Raw, } case *x509.CertificateRequest: p = &pem.Block{ Type: "CERTIFICATE REQUEST", Bytes: k.Raw, } default: return nil, errors.Errorf("cannot serialize type '%T', value '%v'", k, k) } if isPrivateKey { // Request password if needed. password, err := ctx.promptEncryptPassword() if err != nil { return nil, err } // Apply options on the PEM blocks. if password != nil { if ctx.pkcs8 { var err error p, err = EncryptPKCS8PrivateKey(rand.Reader, p.Bytes, password, DefaultEncCipher) if err != nil { return nil, err } } else { var err error //nolint:staticcheck // required for legacy compatibility p, err = x509.EncryptPEMBlock(rand.Reader, p.Type, p.Bytes, password, DefaultEncCipher) if err != nil { return nil, errors.Wrap(err, "failed to serialize to PEM") } } } } if ctx.filename != "" { if err := WriteFile(ctx.filename, pem.EncodeToMemory(p), ctx.perm); err != nil { return nil, err } } return p, nil } // ParseDER parses the given DER-encoded bytes and results the public or private // key encoded. func ParseDER(b []byte) (interface{}, error) { // Try private keys key, err := x509.ParsePKCS8PrivateKey(b) if err != nil { if key, err = x509.ParseECPrivateKey(b); err != nil { key, err = x509.ParsePKCS1PrivateKey(b) } } // Try public key if err != nil { if key, err = x509.ParsePKIXPublicKey(b); err != nil { if key, err = x509.ParsePKCS1PublicKey(b); err != nil { return nil, errors.New("error decoding DER; bad format") } } } return key, nil } // ParseSSH parses parses a public key from an authorized_keys file used in // OpenSSH according to the sshd(8) manual page. func ParseSSH(b []byte) (interface{}, error) { key, _, _, _, err := ssh.ParseAuthorizedKey(b) if err != nil { return nil, errors.Wrap(err, "error parsing OpenSSH key") } if cert, ok := key.(*ssh.Certificate); ok { key = cert.Key } switch key.Type() { case ssh.KeyAlgoRSA: var w struct { Name string E *big.Int N *big.Int } if err := ssh.Unmarshal(key.Marshal(), &w); err != nil { return nil, errors.Wrap(err, "error unmarshaling key") } if w.E.BitLen() > 24 { return nil, errors.New("error unmarshaling key: exponent too large") } e := w.E.Int64() if e < 3 || e&1 == 0 { return nil, errors.New("error unmarshaling key: incorrect exponent") } key := new(rsa.PublicKey) key.E = int(e) key.N = w.N return key, nil case ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521: var w struct { Name string ID string KeyBytes []byte } if err := ssh.Unmarshal(key.Marshal(), &w); err != nil { return nil, errors.Wrap(err, "error unmarshaling key") } key := new(ecdsa.PublicKey) switch w.Name { case ssh.KeyAlgoECDSA256: key.Curve = elliptic.P256() case ssh.KeyAlgoECDSA384: key.Curve = elliptic.P384() case ssh.KeyAlgoECDSA521: key.Curve = elliptic.P521() default: return nil, errors.Errorf("unsupported ecdsa curve %s", w.Name) } key.X, key.Y = elliptic.Unmarshal(key.Curve, w.KeyBytes) if key.X == nil || key.Y == nil { return nil, errors.New("invalid ecdsa curve point") } return key, nil case ssh.KeyAlgoED25519: var w struct { Name string KeyBytes []byte } if err := ssh.Unmarshal(key.Marshal(), &w); err != nil { return nil, errors.Wrap(err, "error unmarshaling key") } return ed25519.PublicKey(w.KeyBytes), nil case ssh.KeyAlgoDSA: return nil, errors.Errorf("step does not support DSA keys") default: return nil, errors.Errorf("unsupported key type %T", key) } } golang-step-crypto-0.24.0/pemutil/pem_test.go000066400000000000000000001111331437037643100211710ustar00rootroot00000000000000package pemutil import ( "bytes" "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/pem" "math/big" "os" "reflect" "strings" "testing" "github.com/pkg/errors" "github.com/smallstep/assert" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/x25519" "golang.org/x/crypto/ssh" ) type keyType int const ( ecdsaPublicKey keyType = iota ecdsaPrivateKey ed25519PublicKey ed25519PrivateKey rsaPublicKey rsaPrivateKey x25519PublicKey x25519PrivateKey ) const ( testCRT = `-----BEGIN CERTIFICATE----- MIICLjCCAdSgAwIBAgIQBvswFbAODY9xtJ/myiuEHzAKBggqhkjOPQQDAjAkMSIw IAYDVQQDExlTbWFsbHN0ZXAgSW50ZXJtZWRpYXRlIENBMB4XDTE4MTEzMDE5NTkw OVoXDTE4MTIwMTE5NTkwOVowHjEcMBoGA1UEAxMTaGVsbG8uc21hbGxzdGVwLmNv bTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABIqPQy8roJTMWpEt8NNA1CnRm3l1 wdjH4OrVaH3l2Gp/UW737Wbn4sqSAFahmajuwkfRG5KMh2/+xnCkGuR2fayjge0w geowDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD AjAdBgNVHQ4EFgQU5bqyXvZaEmtZ3OpZapq7pBIkVvgwHwYDVR0jBBgwFoAUu97P aFQPfuyKOeew7Hg45WFIAVMwHgYDVR0RBBcwFYITaGVsbG8uc21hbGxzdGVwLmNv bTBZBgwrBgEEAYKkZMYoQAEESTBHAgEBBBVtYXJpYW5vQHNtYWxsc3RlcC5jb20E K2pPMzdkdERia3UtUW5hYnM1VlIwWXc2WUZGdjl3ZUExOGRwM2h0dmRFanMwCgYI KoZIzj0EAwIDSAAwRQIhALKeC2q0HWyHoZobZFK9HQynLbPOOtAK437RaetlX5ty AiBXQzvaLlDprQu+THj18aDYLnHA//5mdD3HPJV6KmgdDg== -----END CERTIFICATE-----` testCSR = `-----BEGIN CERTIFICATE REQUEST----- MIHYMIGAAgEAMB4xHDAaBgNVBAMTE2hlbGxvLnNtYWxsc3RlcC5jb20wWTATBgcq hkjOPQIBBggqhkjOPQMBBwNCAASKj0MvK6CUzFqRLfDTQNQp0Zt5dcHYx+Dq1Wh9 5dhqf1Fu9+1m5+LKkgBWoZmo7sJH0RuSjIdv/sZwpBrkdn2soAAwCgYIKoZIzj0E AwIDRwAwRAIgZgz9gdx9inOp6bSX4EkYiUCyLV9xGvabovu5C9UkRr8CIBGBbkp0 l4tesAKoXelsLygJjPuUGRLK+OtdjPBIN1Zo -----END CERTIFICATE REQUEST-----` testCSRKeytool = `-----BEGIN NEW CERTIFICATE REQUEST----- MIHYMIGAAgEAMB4xHDAaBgNVBAMTE2hlbGxvLnNtYWxsc3RlcC5jb20wWTATBgcq hkjOPQIBBggqhkjOPQMBBwNCAASKj0MvK6CUzFqRLfDTQNQp0Zt5dcHYx+Dq1Wh9 5dhqf1Fu9+1m5+LKkgBWoZmo7sJH0RuSjIdv/sZwpBrkdn2soAAwCgYIKoZIzj0E AwIDRwAwRAIgZgz9gdx9inOp6bSX4EkYiUCyLV9xGvabovu5C9UkRr8CIBGBbkp0 l4tesAKoXelsLygJjPuUGRLK+OtdjPBIN1Zo -----END NEW CERTIFICATE REQUEST-----` ) type testdata struct { typ keyType encrypted bool } var files = map[string]testdata{ "testdata/openssl.p256.pem": {ecdsaPrivateKey, false}, "testdata/openssl.p256.pub.pem": {ecdsaPublicKey, false}, "testdata/openssl.p256.enc.pem": {ecdsaPrivateKey, true}, "testdata/openssl.p384.pem": {ecdsaPrivateKey, false}, "testdata/openssl.p384.pub.pem": {ecdsaPublicKey, false}, "testdata/openssl.p384.enc.pem": {ecdsaPrivateKey, true}, "testdata/openssl.p521.pem": {ecdsaPrivateKey, false}, "testdata/openssl.p521.pub.pem": {ecdsaPublicKey, false}, "testdata/openssl.p521.enc.pem": {ecdsaPrivateKey, true}, "testdata/openssl.rsa1024.pem": {rsaPrivateKey, false}, "testdata/openssl.rsa1024.pub.pem": {rsaPublicKey, false}, "testdata/openssl.rsa1024.enc.pem": {rsaPrivateKey, true}, "testdata/openssl.rsa2048.pem": {rsaPrivateKey, false}, "testdata/openssl.rsa2048.pub.pem": {rsaPublicKey, false}, "testdata/openssl.rsa2048.enc.pem": {rsaPrivateKey, true}, "testdata/openssh.ed25519.enc.pem": {ed25519PrivateKey, true}, "testdata/openssh.ed25519.pem": {ed25519PrivateKey, false}, "testdata/openssh.ed25519.pub.pem": {ed25519PublicKey, false}, "testdata/openssh.p256.enc.pem": {ecdsaPrivateKey, true}, "testdata/openssh.p256.pem": {ecdsaPrivateKey, false}, "testdata/openssh.p256.pub.pem": {ecdsaPublicKey, false}, "testdata/openssh.p384.enc.pem": {ecdsaPrivateKey, true}, "testdata/openssh.p384.pem": {ecdsaPrivateKey, false}, "testdata/openssh.p384.pub.pem": {ecdsaPublicKey, false}, "testdata/openssh.p521.enc.pem": {ecdsaPrivateKey, true}, "testdata/openssh.p521.pem": {ecdsaPrivateKey, false}, "testdata/openssh.p521.pub.pem": {ecdsaPublicKey, false}, "testdata/openssh.rsa1024.enc.pem": {rsaPrivateKey, true}, "testdata/openssh.rsa1024.pem": {rsaPrivateKey, false}, "testdata/openssh.rsa1024.pub.pem": {rsaPublicKey, false}, "testdata/openssh.rsa2048.enc.pem": {rsaPrivateKey, true}, "testdata/openssh.rsa2048.pem": {rsaPrivateKey, false}, "testdata/openssh.rsa2048.pub.pem": {rsaPublicKey, false}, "testdata/pkcs8/openssl.ed25519.pem": {ed25519PrivateKey, false}, "testdata/pkcs8/openssl.ed25519.pub.pem": {ed25519PublicKey, false}, "testdata/pkcs8/openssl.ed25519.enc.pem": {ed25519PrivateKey, true}, "testdata/pkcs8/openssl.p256.pem": {ecdsaPrivateKey, false}, "testdata/pkcs8/openssl.p256.pub.pem": {ecdsaPublicKey, false}, "testdata/pkcs8/openssl.p256.enc.pem": {ecdsaPrivateKey, true}, "testdata/pkcs8/openssl.p384.pem": {ecdsaPrivateKey, false}, "testdata/pkcs8/openssl.p384.pub.pem": {ecdsaPublicKey, false}, "testdata/pkcs8/openssl.p384.enc.pem": {ecdsaPrivateKey, true}, "testdata/pkcs8/openssl.p521.pem": {ecdsaPrivateKey, false}, "testdata/pkcs8/openssl.p521.pub.pem": {ecdsaPublicKey, false}, "testdata/pkcs8/openssl.p521.enc.pem": {ecdsaPrivateKey, true}, "testdata/pkcs8/openssl.rsa2048.pem": {rsaPrivateKey, false}, "testdata/pkcs8/openssl.rsa2048.pub.pem": {rsaPublicKey, false}, "testdata/pkcs8/openssl.rsa2048.enc.pem": {rsaPrivateKey, true}, "testdata/pkcs8/openssl.rsa4096.pem": {rsaPrivateKey, false}, "testdata/pkcs8/openssl.rsa4096.pub.pem": {rsaPublicKey, false}, "testdata/cosign.pub.pem": {ecdsaPublicKey, false}, "testdata/cosign.enc.pem": {ecdsaPrivateKey, true}, "testdata/nebula.pub": {x25519PublicKey, false}, "testdata/nebula.key": {x25519PrivateKey, false}, } func readOrParseSSH(fn string) (interface{}, error) { if strings.HasPrefix(fn, "testdata/openssh") && strings.HasSuffix(fn, ".pub.pem") { b, err := os.ReadFile(fn) if err != nil { return nil, err } return ParseSSH(b) } return Read(fn) } func TestRead(t *testing.T) { var err error var key interface{} for fn, td := range files { t.Run(fn, func(t *testing.T) { if td.encrypted { key, err = Read(fn, WithPassword([]byte("mypassword"))) } else { key, err = readOrParseSSH(fn) } assert.NotNil(t, key) assert.NoError(t, err) switch td.typ { case ecdsaPublicKey: assert.Type(t, &ecdsa.PublicKey{}, key) case ecdsaPrivateKey: assert.Type(t, &ecdsa.PrivateKey{}, key) case ed25519PublicKey: assert.Type(t, ed25519.PublicKey{}, key) case ed25519PrivateKey: assert.Type(t, ed25519.PrivateKey{}, key) case rsaPublicKey: assert.Type(t, &rsa.PublicKey{}, key) case rsaPrivateKey: assert.Type(t, &rsa.PrivateKey{}, key) case x25519PublicKey: assert.Type(t, x25519.PublicKey{}, key) case x25519PrivateKey: assert.Type(t, x25519.PrivateKey{}, key) default: t.Errorf("type %T not supported", key) } // Check encrypted against non-encrypted if td.encrypted { k, err := Read(strings.Replace(fn, ".enc", "", 1)) assert.NoError(t, err) assert.Equals(t, k, key) } // Check against public switch td.typ { case ecdsaPrivateKey, ed25519PrivateKey, rsaPrivateKey: pub := strings.Replace(fn, ".enc", "", 1) pub = strings.Replace(pub, "pem", "pub.pem", 1) k, err := readOrParseSSH(pub) assert.NoError(t, err) if pk, ok := key.(crypto.Signer); ok { assert.Equals(t, k, pk.Public()) var signature, digest []byte message := []byte("message") if _, ok := pk.(ed25519.PrivateKey); ok { signature, err = pk.Sign(rand.Reader, message, crypto.Hash(0)) } else { sum := sha256.Sum256(message) digest = sum[:] signature, err = pk.Sign(rand.Reader, digest, crypto.SHA256) } assert.NoError(t, err) switch k := k.(type) { case *ecdsa.PublicKey: // See ecdsa.Sign https://golang.org/pkg/crypto/ecdsa/#Sign ecdsaSignature := struct { R, S *big.Int }{} _, err := asn1.Unmarshal(signature, &ecdsaSignature) assert.NoError(t, err) verified := ecdsa.Verify(k, digest, ecdsaSignature.R, ecdsaSignature.S) assert.True(t, verified) case ed25519.PublicKey: verified := ed25519.Verify(k, []byte("message"), signature) assert.True(t, verified) case *rsa.PublicKey: err := rsa.VerifyPKCS1v15(k, crypto.SHA256, digest, signature) assert.NoError(t, err) } } else { t.Errorf("key for %s does not satisfies the crypto.Signer interface", fn) } } }) } } func TestParseCertificate(t *testing.T) { tests := []struct { fn string err error }{ {"testdata/ca.crt", nil}, {"testdata/bundle.crt", nil}, {"testdata/badca.crt", errors.New("error parsing certificate")}, {"testdata/badpem.crt", errors.New("error decoding pem block")}, {"testdata/badder.crt", errors.New("error decoding pem block")}, {"testdata/openssl.p256.pem", errors.New("error parsing certificate: no certificate found")}, } for _, tc := range tests { t.Run(tc.fn, func(t *testing.T) { b, err := os.ReadFile(tc.fn) if err != nil { t.Fatal(err) } crt, err := ParseCertificate(b) if tc.err != nil { if assert.Error(t, err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { assert.NoError(t, err) assert.Type(t, &x509.Certificate{}, crt) } }) } } func TestParseCertificateBundle(t *testing.T) { tests := []struct { fn string len int err error }{ {"testdata/ca.crt", 1, nil}, {"testdata/bundle.crt", 2, nil}, {"testdata/badca.crt", 0, errors.New("error parsing certificate")}, {"testdata/badpem.crt", 0, errors.New("error decoding pem block")}, {"testdata/badder.crt", 0, errors.New("error decoding pem block")}, {"testdata/openssl.p256.pem", 0, errors.New("error parsing certificate: no certificate found")}, } for _, tc := range tests { t.Run(tc.fn, func(t *testing.T) { b, err := os.ReadFile(tc.fn) if err != nil { t.Fatal(err) } crts, err := ParseCertificateBundle(b) if tc.err != nil { if assert.Error(t, err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { assert.NoError(t, err) assert.Type(t, []*x509.Certificate{}, crts) } assert.Len(t, tc.len, crts) }) } } func TestParseCertificateRequest(t *testing.T) { tests := []struct { fn string opts []Options err error }{ {"testdata/test.csr", nil, nil}, {"testdata/badpem.csr", nil, errors.New("error parsing certificate request")}, {"testdata/bad.csr", nil, errors.New("error decoding pem block")}, {"testdata/ca.crt", nil, errors.New("error parsing certificate request: no certificate found")}, } for _, tc := range tests { t.Run(tc.fn, func(t *testing.T) { b, err := os.ReadFile(tc.fn) if err != nil { t.Fatal(err) } csr, err := ParseCertificateRequest(b) if tc.err != nil { if assert.Error(t, err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { assert.NoError(t, err) assert.Type(t, &x509.CertificateRequest{}, csr) } }) } } func TestReadCertificate(t *testing.T) { tests := []struct { fn string opts []Options err error }{ {"testdata/ca.crt", nil, nil}, {"testdata/ca.der", nil, nil}, {"testdata/bundle.crt", []Options{WithFirstBlock()}, nil}, {"testdata/bundle.crt", nil, errors.New("error decoding testdata/bundle.crt: contains more than one PEM encoded block")}, {"testdata/notexists.crt", nil, errors.New("error reading testdata/notexists.crt: no such file or directory")}, {"testdata/badca.crt", nil, errors.New("error parsing testdata/badca.crt")}, {"testdata/badpem.crt", nil, errors.New("error decoding testdata/badpem.crt: not a valid PEM encoded block")}, {"testdata/badder.crt", nil, errors.New("error parsing testdata/badder.crt")}, {"testdata/openssl.p256.pem", nil, errors.New("error decoding PEM: file 'testdata/openssl.p256.pem' does not contain a certificate")}, } for _, tc := range tests { t.Run(tc.fn, func(t *testing.T) { crt, err := ReadCertificate(tc.fn, tc.opts...) if tc.err != nil { if assert.Error(t, err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { assert.NoError(t, err) assert.Type(t, &x509.Certificate{}, crt) } }) } } func TestReadCertificateBundle(t *testing.T) { tests := []struct { fn string len int err error }{ {"testdata/ca.crt", 1, nil}, {"testdata/ca.der", 1, nil}, {"testdata/bundle.crt", 2, nil}, {"testdata/notexists.crt", 0, errors.New("error reading testdata/notexists.crt: no such file or directory")}, {"testdata/badca.crt", 0, errors.New("error parsing testdata/badca.crt")}, {"testdata/badpem.crt", 0, errors.New("error decoding PEM: file 'testdata/badpem.crt' contains unexpected data")}, {"testdata/badder.crt", 0, errors.New("error parsing testdata/badder.crt")}, {"testdata/openssl.p256.pem", 0, errors.New("error decoding PEM: file 'testdata/openssl.p256.pem' is not a certificate bundle")}, } for _, tc := range tests { certs, err := ReadCertificateBundle(tc.fn) if tc.err != nil { if assert.Error(t, err, tc.fn) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { assert.NoError(t, err) assert.Len(t, tc.len, certs, tc.fn) for i := range certs { assert.Type(t, &x509.Certificate{}, certs[i]) } } } } func TestParse(t *testing.T) { type ParseTest struct { in []byte opts []Options cmpType interface{} err error } tests := map[string]func(t *testing.T) *ParseTest{ "success-ecdsa-public-key": func(t *testing.T) *ParseTest { b, err := os.ReadFile("testdata/openssl.p256.pub.pem") assert.FatalError(t, err) return &ParseTest{ in: b, opts: nil, cmpType: &ecdsa.PublicKey{}, } }, "success-rsa-public-key": func(t *testing.T) *ParseTest { b, err := os.ReadFile("testdata/openssl.rsa1024.pub.pem") assert.FatalError(t, err) return &ParseTest{ in: b, opts: nil, cmpType: &rsa.PublicKey{}, } }, "success-rsa-private-key": func(t *testing.T) *ParseTest { b, err := os.ReadFile("testdata/openssl.rsa1024.pem") assert.FatalError(t, err) return &ParseTest{ in: b, opts: nil, cmpType: &rsa.PrivateKey{}, } }, "success-ecdsa-private-key": func(t *testing.T) *ParseTest { b, err := os.ReadFile("testdata/openssl.p256.pem") assert.FatalError(t, err) return &ParseTest{ in: b, opts: nil, cmpType: &ecdsa.PrivateKey{}, } }, "success-ed25519-private-key": func(t *testing.T) *ParseTest { b, err := os.ReadFile("testdata/pkcs8/openssl.ed25519.pem") assert.FatalError(t, err) return &ParseTest{ in: b, opts: nil, cmpType: ed25519.PrivateKey{}, } }, "success-ed25519-enc-private-key": func(t *testing.T) *ParseTest { b, err := os.ReadFile("testdata/pkcs8/openssl.ed25519.enc.pem") assert.FatalError(t, err) return &ParseTest{ in: b, opts: []Options{WithPassword([]byte("mypassword"))}, cmpType: ed25519.PrivateKey{}, } }, "success-x509-crt": func(t *testing.T) *ParseTest { b, err := os.ReadFile("testdata/ca.crt") assert.FatalError(t, err) return &ParseTest{ in: b, opts: nil, cmpType: &x509.Certificate{}, } }, "success-x509-crt-trim-spaces": func(t *testing.T) *ParseTest { b, err := os.ReadFile("testdata/ca.crt") assert.FatalError(t, err) b = append(b, []byte(" \n \n ")...) return &ParseTest{ in: b, opts: nil, cmpType: &x509.Certificate{}, } }, "fail-options": func(t *testing.T) *ParseTest { b, err := os.ReadFile("testdata/ca.crt") assert.FatalError(t, err) err = errors.New("an error") return &ParseTest{ in: b, opts: []Options{func(ctx *context) error { return err }}, cmpType: err, err: err, } }, "fail-password": func(t *testing.T) *ParseTest { b, err := os.ReadFile("testdata/openssl.p256.enc.pem") assert.FatalError(t, err) return &ParseTest{ in: b, opts: []Options{WithPassword([]byte("badpassword"))}, cmpType: ecdsa.PrivateKey{}, err: errors.New("error decrypting PEM"), } }, "fail-pkcs8-password": func(t *testing.T) *ParseTest { b, err := os.ReadFile("testdata/pkcs8/openssl.ed25519.enc.pem") assert.FatalError(t, err) return &ParseTest{ in: b, opts: []Options{WithPassword([]byte("badpassword"))}, cmpType: ed25519.PrivateKey{}, err: errors.New("error decrypting PEM: x509: decryption password incorrect"), } }, "fail-type": func(t *testing.T) *ParseTest { b, err := os.ReadFile("testdata/openssl.p256.pub.pem") assert.FatalError(t, err) b = bytes.ReplaceAll(b, []byte("PUBLIC KEY"), []byte("EC PUBLIC KEY")) return &ParseTest{ in: b, opts: []Options{}, cmpType: nil, err: errors.New("error decoding PEM: contains an unexpected header 'EC PUBLIC KEY'"), } }, "fail-nebula-pub-size": func(t *testing.T) *ParseTest { b, err := os.ReadFile("testdata/badnebula.pub") assert.FatalError(t, err) return &ParseTest{ in: b, opts: []Options{}, cmpType: nil, err: errors.New("error parsing PEM: key is not 32 bytes"), } }, "fail-nebula-key-size": func(t *testing.T) *ParseTest { b, err := os.ReadFile("testdata/badnebula.key") assert.FatalError(t, err) return &ParseTest{ in: b, opts: []Options{}, cmpType: nil, err: errors.New("error parsing PEM: key is not 32 bytes"), } }, } for name, genTestCase := range tests { t.Run(name, func(t *testing.T) { tc := genTestCase(t) i, err := Parse(tc.in, tc.opts...) if err != nil { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { if assert.Nil(t, tc.err) { assert.Type(t, i, tc.cmpType) } } }) } } //nolint:staticcheck // required for legacy compatibility func TestSerialize(t *testing.T) { tests := map[string]struct { in func() (interface{}, error) pass string pkcs8 bool file string err error }{ "unrecognized key type": { in: func() (interface{}, error) { return "shake and bake", nil }, err: errors.New("cannot serialize type 'string', value 'shake and bake'"), }, "RSA Private Key success": { in: func() (interface{}, error) { return keyutil.GenerateKey("RSA", "", 2048) }, }, "RSA Public Key success": { in: func() (interface{}, error) { pub, _, err := keyutil.GenerateKeyPair("RSA", "", 2048) return pub, err }, }, "EC Private Key success": { in: func() (interface{}, error) { return keyutil.GenerateKey("EC", "P-256", 0) }, }, "EC Private Key success - encrypt input data": { in: func() (interface{}, error) { return keyutil.GenerateKey("EC", "P-256", 0) }, pass: "pass", }, "EC Private Key success - encrypt pkcs8 data": { in: func() (interface{}, error) { return keyutil.GenerateKey("EC", "P-256", 0) }, pass: "pass", pkcs8: true, }, "EC Public Key success": { in: func() (interface{}, error) { pub, _, err := keyutil.GenerateKeyPair("EC", "P-256", 0) return pub, err }, }, "OKP Private Key success": { in: func() (interface{}, error) { return keyutil.GenerateKey("OKP", "Ed25519", 0) }, }, "OKP Public Key success": { in: func() (interface{}, error) { pub, _, err := keyutil.GenerateKeyPair("OKP", "Ed25519", 0) return pub, err }, }, "X.509 Certificate success": { in: func() (interface{}, error) { return ReadCertificate("testdata/ca.crt") }, }, "X.509 Certificate request success": { in: func() (interface{}, error) { return &x509.CertificateRequest{}, nil }, }, "propagate open key out file error": { in: func() (interface{}, error) { return keyutil.GenerateKey("EC", "P-256", 0) }, file: "./fakeDir/test.key", err: errors.New("error writing ./fakeDir/test.key: no such file or directory"), }, "ToFile Success (EC Private Key unencrypted)": { in: func() (interface{}, error) { return keyutil.GenerateKey("EC", "P-256", 0) }, file: "./test.key", }, "ToFile Success (EC Private Key encrypted)": { in: func() (interface{}, error) { return keyutil.GenerateKey("EC", "P-256", 0) }, pass: "pass", file: "./test.key", }, } for name, test := range tests { if _, err := os.Stat("./test.key"); err == nil { assert.FatalError(t, os.Remove("./test.key")) } t.Logf("Running test case: %s", name) in, err := test.in() assert.FatalError(t, err) var p *pem.Block switch { case test.pass == "" && test.file == "": p, err = Serialize(in) case test.pass != "" && test.file != "": p, err = Serialize(in, WithPassword([]byte(test.pass)), ToFile(test.file, 0600)) case test.pass != "" && test.pkcs8: p, err = Serialize(in, WithPKCS8(true), WithPasswordPrompt("Please enter the password to encrypt the key", func(prompt string) ([]byte, error) { return []byte(test.pass), nil })) case test.pass != "": p, err = Serialize(in, WithPassword([]byte(test.pass))) default: p, err = Serialize(in, ToFile(test.file, 0600)) } if err != nil { if assert.NotNil(t, test.err) { assert.HasPrefix(t, err.Error(), test.err.Error()) } } else { if assert.Nil(t, test.err) { switch k := in.(type) { case *x509.Certificate, *x509.CertificateRequest: case *rsa.PrivateKey: if test.pass == "" { assert.False(t, x509.IsEncryptedPEMBlock(p)) assert.Equals(t, p.Type, "RSA PRIVATE KEY") assert.Equals(t, p.Bytes, x509.MarshalPKCS1PrivateKey(k)) } else { assert.True(t, x509.IsEncryptedPEMBlock(p)) assert.Equals(t, p.Type, "RSA PRIVATE KEY") assert.Equals(t, p.Headers["Proc-Type"], "4,ENCRYPTED") var der []byte der, err = x509.DecryptPEMBlock(p, []byte(test.pass)) assert.FatalError(t, err) assert.Equals(t, der, x509.MarshalPKCS1PrivateKey(k)) } case *rsa.PublicKey, *ecdsa.PublicKey: assert.False(t, x509.IsEncryptedPEMBlock(p)) assert.Equals(t, p.Type, "PUBLIC KEY") var b []byte b, err = x509.MarshalPKIXPublicKey(k) assert.FatalError(t, err) assert.Equals(t, p.Bytes, b) case *ecdsa.PrivateKey: var actualBytes []byte switch { case test.pass == "": assert.Equals(t, p.Type, "EC PRIVATE KEY") assert.False(t, x509.IsEncryptedPEMBlock(p)) actualBytes = p.Bytes case test.pkcs8: assert.Equals(t, p.Type, "ENCRYPTED PRIVATE KEY") actualBytes, err = DecryptPKCS8PrivateKey(p.Bytes, []byte(test.pass)) assert.FatalError(t, err) default: assert.Equals(t, p.Type, "EC PRIVATE KEY") assert.True(t, x509.IsEncryptedPEMBlock(p)) assert.Equals(t, p.Headers["Proc-Type"], "4,ENCRYPTED") actualBytes, err = x509.DecryptPEMBlock(p, []byte(test.pass)) assert.FatalError(t, err) } var expectedBytes []byte if test.pkcs8 { expectedBytes, err = x509.MarshalPKCS8PrivateKey(k) } else { expectedBytes, err = x509.MarshalECPrivateKey(k) } assert.FatalError(t, err) assert.Equals(t, actualBytes, expectedBytes) if test.file != "" { // Check key permissions var fileInfo os.FileInfo fileInfo, err = os.Stat(test.file) assert.FatalError(t, err) assert.Equals(t, fileInfo.Mode(), os.FileMode(0600)) // Verify that key written to file is correct var keyFileBytes []byte keyFileBytes, err = os.ReadFile(test.file) assert.FatalError(t, err) pemKey, _ := pem.Decode(keyFileBytes) assert.Equals(t, pemKey.Type, "EC PRIVATE KEY") if x509.IsEncryptedPEMBlock(pemKey) { assert.Equals(t, pemKey.Headers["Proc-Type"], "4,ENCRYPTED") actualBytes, err = x509.DecryptPEMBlock(pemKey, []byte(test.pass)) assert.FatalError(t, err) } else { actualBytes = pemKey.Bytes } assert.Equals(t, actualBytes, expectedBytes) } case ed25519.PrivateKey: assert.Equals(t, p.Type, "PRIVATE KEY") var actualBytes []byte if test.pass == "" { assert.False(t, x509.IsEncryptedPEMBlock(p)) actualBytes = p.Bytes } else { assert.True(t, x509.IsEncryptedPEMBlock(p)) assert.Equals(t, p.Headers["Proc-Type"], "4,ENCRYPTED") actualBytes, err = x509.DecryptPEMBlock(p, []byte(test.pass)) assert.FatalError(t, err) } var priv pkcs8 _, err = asn1.Unmarshal(actualBytes, &priv) assert.FatalError(t, err) assert.Equals(t, priv.Version, 0) assert.Equals(t, priv.Algo, pkix.AlgorithmIdentifier{ Algorithm: asn1.ObjectIdentifier{1, 3, 101, 112}, Parameters: asn1.RawValue{}, }) assert.Equals(t, priv.PrivateKey[:2], []byte{4, 32}) assert.Equals(t, priv.PrivateKey[2:ed25519.SeedSize+2], k.Seed()) case ed25519.PublicKey: assert.Equals(t, p.Type, "PUBLIC KEY") assert.False(t, x509.IsEncryptedPEMBlock(p)) var pub publicKeyInfo _, err = asn1.Unmarshal(p.Bytes, &pub) assert.FatalError(t, err) assert.Equals(t, pub.Algo, pkix.AlgorithmIdentifier{ Algorithm: asn1.ObjectIdentifier{1, 3, 101, 112}, Parameters: asn1.RawValue{}, }) assert.Equals(t, pub.PublicKey, asn1.BitString{ Bytes: k, BitLength: ed25519.PublicKeySize * 8, }) default: t.Errorf("Unrecognized key - type: %T, value: %v", k, k) } } } if _, err := os.Stat("./test.key"); err == nil { assert.FatalError(t, os.Remove("./test.key")) } } } func TestParseDER(t *testing.T) { k1, err := Read("testdata/openssl.rsa2048.pem") assert.FatalError(t, err) k2, err := Read("testdata/openssl.p256.pem") assert.FatalError(t, err) k3, err := Read("testdata/pkcs8/openssl.ed25519.pem") assert.FatalError(t, err) rsaKey := k1.(*rsa.PrivateKey) ecdsaKey := k2.(*ecdsa.PrivateKey) edKey := k3.(ed25519.PrivateKey) // Ed25519 der files edPubDer, err := os.ReadFile("testdata/pkcs8/openssl.ed25519.pub.der") assert.FatalError(t, err) edPrivDer, err := os.ReadFile("testdata/pkcs8/openssl.ed25519.der") assert.FatalError(t, err) toDER := func(k interface{}) []byte { switch k := k.(type) { case *rsa.PublicKey, *ecdsa.PublicKey: b, err := x509.MarshalPKIXPublicKey(k) assert.FatalError(t, err) return b case *rsa.PrivateKey: return x509.MarshalPKCS1PrivateKey(k) case *ecdsa.PrivateKey: b, err := x509.MarshalECPrivateKey(k) assert.FatalError(t, err) return b default: t.Fatalf("unsupported key type %T", k) return nil } } type args struct { b []byte } tests := []struct { name string args args want interface{} wantErr bool }{ {"rsa public key", args{toDER(rsaKey.Public())}, rsaKey.Public(), false}, {"rsa private key", args{toDER(rsaKey)}, rsaKey, false}, {"rsa pkcs#1 public key", args{x509.MarshalPKCS1PublicKey(&rsaKey.PublicKey)}, rsaKey.Public(), false}, {"ecdsa public key", args{toDER(ecdsaKey.Public())}, ecdsaKey.Public(), false}, {"ecdsa private key", args{toDER(ecdsaKey)}, ecdsaKey, false}, {"ed25519 public key", args{edPubDer}, edKey.Public(), false}, {"ed25519 private key", args{edPrivDer}, edKey, false}, {"fail", args{[]byte("fooo")}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ParseDER(tt.args.b) if (err != nil) != tt.wantErr { t.Errorf("ParseDER() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ParseDER() = %v, want %v", got, tt.want) } }) } } func TestParseKey(t *testing.T) { var key interface{} for fn, td := range files { // skip ssh public keys if strings.HasPrefix(fn, "testdata/openssh") && strings.HasSuffix(fn, ".pub.pem") { continue } t.Run(fn, func(t *testing.T) { data, err := os.ReadFile(fn) assert.FatalError(t, err) if td.encrypted { key, err = ParseKey(data, WithPassword([]byte("mypassword"))) } else { key, err = ParseKey(data) } assert.NotNil(t, key) assert.NoError(t, err) switch td.typ { case ecdsaPublicKey: assert.Type(t, &ecdsa.PublicKey{}, key) case ecdsaPrivateKey: assert.Type(t, &ecdsa.PrivateKey{}, key) case ed25519PublicKey: assert.Type(t, ed25519.PublicKey{}, key) case ed25519PrivateKey: assert.Type(t, ed25519.PrivateKey{}, key) case rsaPublicKey: assert.Type(t, &rsa.PublicKey{}, key) case rsaPrivateKey: assert.Type(t, &rsa.PrivateKey{}, key) case x25519PublicKey: assert.Type(t, x25519.PublicKey{}, key) case x25519PrivateKey: assert.Type(t, x25519.PrivateKey{}, key) default: t.Errorf("type %T not supported", key) } }) } } func TestParseKey_x509(t *testing.T) { b, _ := pem.Decode([]byte(testCRT)) cert, err := x509.ParseCertificate(b.Bytes) assert.FatalError(t, err) key, err := ParseKey([]byte(testCRT)) assert.FatalError(t, err) assert.Equals(t, cert.PublicKey, key) b, _ = pem.Decode([]byte(testCSR)) csr, err := x509.ParseCertificateRequest(b.Bytes) assert.FatalError(t, err) key, err = ParseKey([]byte(testCSR)) assert.FatalError(t, err) assert.Equals(t, csr.PublicKey, key) b, _ = pem.Decode([]byte(testCSRKeytool)) csr, err = x509.ParseCertificateRequest(b.Bytes) assert.FatalError(t, err) key, err = ParseKey([]byte(testCSRKeytool)) assert.FatalError(t, err) assert.Equals(t, csr.PublicKey, key) } func TestParseSSH(t *testing.T) { var key interface{} for fn, td := range files { if !strings.HasPrefix(fn, "testdata/openssh") || !strings.HasSuffix(fn, ".pub.pem") { continue } t.Run(fn, func(t *testing.T) { data, err := os.ReadFile(fn) assert.FatalError(t, err) key, err = ParseSSH(data) assert.FatalError(t, err) assert.NotNil(t, key) switch td.typ { case ecdsaPublicKey: assert.Type(t, &ecdsa.PublicKey{}, key) case ed25519PublicKey: assert.Type(t, ed25519.PublicKey{}, key) case rsaPublicKey: assert.Type(t, &rsa.PublicKey{}, key) default: t.Errorf("type %T not supported", key) } }) } } func TestOpenSSH(t *testing.T) { t.Parallel() for fn, td := range files { if strings.HasSuffix(fn, ".pub.pem") { continue } // skip x25519 keys if td.typ == x25519PublicKey || td.typ == x25519PrivateKey { continue } // To be able to run this in parallel we need to declare local // variables. fn, td := fn, td t.Run(fn, func(t *testing.T) { t.Parallel() opts := []Options{ WithOpenSSH(true), WithComment("test@smallstep.com"), } if td.encrypted { opts = append(opts, WithPassword([]byte("mypassword"))) } key, err := Read(fn, opts...) assert.FatalError(t, err) // using their own methods block, err := SerializeOpenSSHPrivateKey(key, opts...) assert.FatalError(t, err) key2, err := ParseOpenSSHPrivateKey(pem.EncodeToMemory(block), opts...) assert.FatalError(t, err) assert.Equals(t, key, key2) // using main methods block2, err := Serialize(key2, opts...) assert.FatalError(t, err) // salt must be different assert.NotEquals(t, block, block2) key3, err := Parse(pem.EncodeToMemory(block2), opts...) assert.FatalError(t, err) assert.Equals(t, key2, key3) }) } } func TestRead_options(t *testing.T) { mustKey := func(filename string) interface{} { b, err := os.ReadFile(filename) assert.FatalError(t, err) key, err := ssh.ParseRawPrivateKey(b) assert.FatalError(t, err) return key } p256Key := mustKey("testdata/openssl.p256.pem") type args struct { filename string opts []Options } tests := []struct { name string args args want interface{} wantErr bool }{ {"withPassword", args{"testdata/openssl.p256.enc.pem", []Options{WithPassword([]byte("mypassword"))}}, p256Key, false}, {"withPasswordFile", args{"testdata/openssl.p256.enc.pem", []Options{WithPasswordFile("testdata/password.txt")}}, p256Key, false}, {"withPasswordPrompt", args{"testdata/openssl.p256.enc.pem", []Options{WithPasswordPrompt("Enter the password", func(s string) ([]byte, error) { return []byte("mypassword"), nil })}}, p256Key, false}, {"missing", args{"testdata/missing.txt", nil}, nil, true}, {"missingPassword", args{"testdata/openssl.p256.enc.pem", nil}, nil, true}, {"withPasswordError", args{"testdata/openssl.p256.enc.pem", []Options{WithPassword([]byte("badpassword"))}}, nil, true}, {"withPasswordFileError", args{"testdata/openssl.p256.enc.pem", []Options{WithPasswordFile("testdata/missing.txt")}}, nil, true}, {"withPasswordPromptError", args{"testdata/openssl.p256.enc.pem", []Options{WithPasswordPrompt("Enter the password", func(s string) ([]byte, error) { return nil, errors.New("an error") })}}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := Read(tt.args.filename, tt.args.opts...) if (err != nil) != tt.wantErr { t.Errorf("Read() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("Read() = %v, want %v", got, tt.want) } }) } } func TestRead_promptPassword(t *testing.T) { mustKey := func(filename string) interface{} { b, err := os.ReadFile(filename) assert.FatalError(t, err) key, err := ssh.ParseRawPrivateKey(b) assert.FatalError(t, err) return key } p256Key := mustKey("testdata/openssl.p256.pem") type args struct { filename string passwordPrompter PasswordPrompter } tests := []struct { name string args args want interface{} wantErr bool }{ {"PromptPassword", args{"testdata/openssl.p256.enc.pem", func(s string) ([]byte, error) { return []byte("mypassword"), nil }}, p256Key, false}, {"PromptPasswordBadPassword", args{"testdata/openssl.p256.enc.pem", func(s string) ([]byte, error) { return []byte("badPassword"), nil }}, nil, true}, {"PromptPasswordError", args{"testdata/openssl.p256.enc.pem", func(s string) ([]byte, error) { return nil, errors.New("an error") }}, nil, true}, {"PromptPasswordNil", args{"testdata/openssl.p256.enc.pem", nil}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { PromptPassword = tt.args.passwordPrompter got, err := Read(tt.args.filename) if (err != nil) != tt.wantErr { t.Errorf("Read() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("Read() = %v, want %v", got, tt.want) } }) } } func TestReadCertificateRequest(t *testing.T) { expected := &x509.CertificateRequest{ Subject: pkix.Name{ CommonName: "hello.smallstep.com", Names: []pkix.AttributeTypeAndValue{{Type: asn1.ObjectIdentifier{2, 5, 4, 3}, Value: "hello.smallstep.com"}}, }, PublicKey: &ecdsa.PublicKey{ Curve: elliptic.P256(), X: new(big.Int).SetBytes([]byte{ 0x8a, 0x8f, 0x43, 0x2f, 0x2b, 0xa0, 0x94, 0xcc, 0x5a, 0x91, 0x2d, 0xf0, 0xd3, 0x40, 0xd4, 0x29, 0xd1, 0x9b, 0x79, 0x75, 0xc1, 0xd8, 0xc7, 0xe0, 0xea, 0xd5, 0x68, 0x7d, 0xe5, 0xd8, 0x6a, 0x7f, }), Y: new(big.Int).SetBytes([]byte{ 0x51, 0x6e, 0xf7, 0xed, 0x66, 0xe7, 0xe2, 0xca, 0x92, 0x00, 0x56, 0xa1, 0x99, 0xa8, 0xee, 0xc2, 0x47, 0xd1, 0x1b, 0x92, 0x8c, 0x87, 0x6f, 0xfe, 0xc6, 0x70, 0xa4, 0x1a, 0xe4, 0x76, 0x7d, 0xac, }), }, PublicKeyAlgorithm: x509.ECDSA, SignatureAlgorithm: x509.ECDSAWithSHA256, Signature: []byte{ 0x30, 0x44, 0x02, 0x20, 0x66, 0x0c, 0xfd, 0x81, 0xdc, 0x7d, 0x8a, 0x73, 0xa9, 0xe9, 0xb4, 0x97, 0xe0, 0x49, 0x18, 0x89, 0x40, 0xb2, 0x2d, 0x5f, 0x71, 0x1a, 0xf6, 0x9b, 0xa2, 0xfb, 0xb9, 0x0b, 0xd5, 0x24, 0x46, 0xbf, 0x02, 0x20, 0x11, 0x81, 0x6e, 0x4a, 0x74, 0x97, 0x8b, 0x5e, 0xb0, 0x02, 0xa8, 0x5d, 0xe9, 0x6c, 0x2f, 0x28, 0x09, 0x8c, 0xfb, 0x94, 0x19, 0x12, 0xca, 0xf8, 0xeb, 0x5d, 0x8c, 0xf0, 0x48, 0x37, 0x56, 0x68, }, } type args struct { filename string } tests := []struct { name string args args want *x509.CertificateRequest wantErr bool }{ {"ok", args{"testdata/test.csr"}, expected, false}, {"ok der", args{"testdata/test.der"}, expected, false}, {"ok keytool", args{"testdata/keytool.csr"}, expected, false}, {"fail missing", args{"testdata/missing.csr"}, nil, true}, {"fail bad csr", args{"testdata/bad.csr"}, nil, true}, {"fail certificate", args{"testdata/ca.crt"}, nil, true}, {"fail certificate der", args{"testdata/ca.der"}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ReadCertificateRequest(tt.args.filename) if (err != nil) != tt.wantErr { t.Errorf("ReadCertificateRequest() error = %v, wantErr %v", err, tt.wantErr) return } // Cleanup raw data if got != nil { got.Raw = nil got.RawSubject = nil got.RawSubjectPublicKeyInfo = nil got.RawTBSCertificateRequest = nil } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ReadCertificateRequest() = \n%#v, want \n%#v", got, tt.want) } }) } } golang-step-crypto-0.24.0/pemutil/pkcs8.go000066400000000000000000000241731437037643100204100ustar00rootroot00000000000000package pemutil import ( "crypto/aes" "crypto/cipher" "crypto/des" //nolint:gosec // support for legacy keys "crypto/sha1" //nolint:gosec // support for legacy keys "crypto/sha256" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/pem" "hash" "io" "github.com/pkg/errors" "golang.org/x/crypto/pbkdf2" ) // PBKDF2SaltSize is the default size of the salt for PBKDF2, 128-bit salt. const PBKDF2SaltSize = 16 // PBKDF2Iterations is the default number of iterations for PBKDF2, 100k // iterations. Nist recommends at least 10k, 1Passsword uses 100k. const PBKDF2Iterations = 100000 // pkcs8 reflects an ASN.1, PKCS#8 PrivateKey. See // ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-8/pkcs-8v1_2.asn // and RFC 5208. type pkcs8 struct { Version int Algo pkix.AlgorithmIdentifier PrivateKey []byte // optional attributes omitted. } type publicKeyInfo struct { Raw asn1.RawContent Algo pkix.AlgorithmIdentifier PublicKey asn1.BitString } // Encrypted pkcs8 // Based on https://github.com/youmark/pkcs8 // MIT license type prfParam struct { Algo asn1.ObjectIdentifier NullParam asn1.RawValue } type pbkdf2Params struct { Salt []byte IterationCount int PrfParam prfParam `asn1:"optional"` } type pbkdf2Algorithms struct { Algo asn1.ObjectIdentifier PBKDF2Params pbkdf2Params } type pbkdf2Encs struct { EncryAlgo asn1.ObjectIdentifier IV []byte } type pbes2Params struct { KeyDerivationFunc pbkdf2Algorithms EncryptionScheme pbkdf2Encs } type encryptedlAlgorithmIdentifier struct { Algorithm asn1.ObjectIdentifier Parameters pbes2Params } type encryptedPrivateKeyInfo struct { Algo encryptedlAlgorithmIdentifier PrivateKey []byte } var ( // key derivation functions oidPKCS5PBKDF2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 12} oidPBES2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 13} oidHMACWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 113549, 2, 9} // encryption oidAES128CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 2} oidAES192CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 22} oidAES256CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 42} oidDESCBC = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 7} oidD3DESCBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 3, 7} ) // rfc1423Algo holds a method for enciphering a PEM block. type rfc1423Algo struct { cipher x509.PEMCipher name string cipherFunc func(key []byte) (cipher.Block, error) keySize int blockSize int identifier asn1.ObjectIdentifier } // rfc1423Algos holds a slice of the possible ways to encrypt a PEM // block. The ivSize numbers were taken from the OpenSSL source. var rfc1423Algos = []rfc1423Algo{{ cipher: x509.PEMCipherDES, name: "DES-CBC", cipherFunc: des.NewCipher, keySize: 8, blockSize: des.BlockSize, identifier: oidDESCBC, }, { cipher: x509.PEMCipher3DES, name: "DES-EDE3-CBC", cipherFunc: des.NewTripleDESCipher, keySize: 24, blockSize: des.BlockSize, identifier: oidD3DESCBC, }, { cipher: x509.PEMCipherAES128, name: "AES-128-CBC", cipherFunc: aes.NewCipher, keySize: 16, blockSize: aes.BlockSize, identifier: oidAES128CBC, }, { cipher: x509.PEMCipherAES192, name: "AES-192-CBC", cipherFunc: aes.NewCipher, keySize: 24, blockSize: aes.BlockSize, identifier: oidAES192CBC, }, { cipher: x509.PEMCipherAES256, name: "AES-256-CBC", cipherFunc: aes.NewCipher, keySize: 32, blockSize: aes.BlockSize, identifier: oidAES256CBC, }, } func cipherByKey(key x509.PEMCipher) *rfc1423Algo { for i := range rfc1423Algos { alg := &rfc1423Algos[i] if alg.cipher == key { return alg } } return nil } // deriveKey uses a key derivation function to stretch the password into a key // with the number of bits our cipher requires. This algorithm was derived from // the OpenSSL source. func (c rfc1423Algo) deriveKey(password, salt []byte, h func() hash.Hash) []byte { return pbkdf2.Key(password, salt, PBKDF2Iterations, c.keySize, h) } // DecryptPEMBlock takes a password encrypted PEM block and the password used // to encrypt it and returns a slice of decrypted DER encoded bytes. // // If the PEM blocks has the Proc-Type header set to "4,ENCRYPTED" it uses // x509.DecryptPEMBlock to decrypt the block. If not it tries to decrypt the // block using AES-128-CBC, AES-192-CBC, AES-256-CBC, DES, or 3DES using the // key derived using PBKDF2 over the given password. func DecryptPEMBlock(block *pem.Block, password []byte) ([]byte, error) { if block.Headers["Proc-Type"] == "4,ENCRYPTED" { //nolint:staticcheck // required for legacy compatibility return x509.DecryptPEMBlock(block, password) } // PKCS#8 header defined in RFC7468 section 11 if block.Type == "ENCRYPTED PRIVATE KEY" { return DecryptPKCS8PrivateKey(block.Bytes, password) } return nil, errors.New("unsupported encrypted PEM") } // DecryptPKCS8PrivateKey takes a password encrypted private key using the // PKCS#8 encoding and returns the decrypted data in PKCS#8 form. If an // incorrect password is detected an x509.IncorrectPasswordError is returned. // Because of deficiencies in the format, it's not always possible to detect an // incorrect password. In these cases no error will be returned but the // decrypted DER bytes will be random noise. // // It supports AES-128-CBC, AES-192-CBC, AES-256-CBC, DES, or 3DES encrypted // data using the key derived with PBKDF2 over the given password. func DecryptPKCS8PrivateKey(data, password []byte) ([]byte, error) { var pki encryptedPrivateKeyInfo if _, err := asn1.Unmarshal(data, &pki); err != nil { return nil, errors.Wrap(err, "failed to unmarshal private key") } if !pki.Algo.Algorithm.Equal(oidPBES2) { return nil, errors.New("unsupported encrypted PEM: only PBES2 is supported") } if !pki.Algo.Parameters.KeyDerivationFunc.Algo.Equal(oidPKCS5PBKDF2) { return nil, errors.New("unsupported encrypted PEM: only PBKDF2 is supported") } encParam := pki.Algo.Parameters.EncryptionScheme kdfParam := pki.Algo.Parameters.KeyDerivationFunc.PBKDF2Params iv := encParam.IV salt := kdfParam.Salt iter := kdfParam.IterationCount // pbkdf2 hash function keyHash := sha1.New if kdfParam.PrfParam.Algo.Equal(oidHMACWithSHA256) { keyHash = sha256.New } var symkey []byte var block cipher.Block var err error switch { // AES-128-CBC, AES-192-CBC, AES-256-CBC case encParam.EncryAlgo.Equal(oidAES128CBC): symkey = pbkdf2.Key(password, salt, iter, 16, keyHash) block, err = aes.NewCipher(symkey) case encParam.EncryAlgo.Equal(oidAES192CBC): symkey = pbkdf2.Key(password, salt, iter, 24, keyHash) block, err = aes.NewCipher(symkey) case encParam.EncryAlgo.Equal(oidAES256CBC): symkey = pbkdf2.Key(password, salt, iter, 32, keyHash) block, err = aes.NewCipher(symkey) // DES, TripleDES case encParam.EncryAlgo.Equal(oidDESCBC): symkey = pbkdf2.Key(password, salt, iter, 8, keyHash) block, err = des.NewCipher(symkey) //nolint:gosec // support for legacy keys case encParam.EncryAlgo.Equal(oidD3DESCBC): symkey = pbkdf2.Key(password, salt, iter, 24, keyHash) block, err = des.NewTripleDESCipher(symkey) //nolint:gosec // support for legacy keys default: return nil, errors.Errorf("unsupported encrypted PEM: unknown algorithm %v", encParam.EncryAlgo) } if err != nil { return nil, err } data = pki.PrivateKey mode := cipher.NewCBCDecrypter(block, iv) mode.CryptBlocks(data, data) // Blocks are padded using a scheme where the last n bytes of padding are all // equal to n. It can pad from 1 to blocksize bytes inclusive. See RFC 1423. // For example: // [x y z 2 2] // [x y 7 7 7 7 7 7 7] // If we detect a bad padding, we assume it is an invalid password. blockSize := block.BlockSize() dlen := len(data) if dlen == 0 || dlen%blockSize != 0 { return nil, errors.New("error decrypting PEM: invalid padding") } last := int(data[dlen-1]) if dlen < last { return nil, x509.IncorrectPasswordError } if last == 0 || last > blockSize { return nil, x509.IncorrectPasswordError } for _, val := range data[dlen-last:] { if int(val) != last { return nil, x509.IncorrectPasswordError } } return data[:dlen-last], nil } // EncryptPKCS8PrivateKey returns a PEM block holding the given PKCS#8 encroded // private key, encrypted with the specified algorithm and a PBKDF2 derived key // from the given password. func EncryptPKCS8PrivateKey(rand io.Reader, data, password []byte, alg x509.PEMCipher) (*pem.Block, error) { ciph := cipherByKey(alg) if ciph == nil { return nil, errors.Errorf("failed to encrypt PEM: unknown algorithm %v", alg) } salt := make([]byte, PBKDF2SaltSize) if _, err := io.ReadFull(rand, salt); err != nil { return nil, errors.Wrap(err, "failed to generate salt") } iv := make([]byte, ciph.blockSize) if _, err := io.ReadFull(rand, iv); err != nil { return nil, errors.Wrap(err, "failed to generate IV") } key := ciph.deriveKey(password, salt, sha256.New) block, err := ciph.cipherFunc(key) if err != nil { return nil, errors.Wrap(err, "failed to create cipher") } enc := cipher.NewCBCEncrypter(block, iv) pad := ciph.blockSize - len(data)%ciph.blockSize encrypted := make([]byte, len(data), len(data)+pad) // We could save this copy by encrypting all the whole blocks in // the data separately, but it doesn't seem worth the additional // code. copy(encrypted, data) // See RFC 1423, section 1.1 for i := 0; i < pad; i++ { encrypted = append(encrypted, byte(pad)) } enc.CryptBlocks(encrypted, encrypted) // Build encrypted ans1 data pki := encryptedPrivateKeyInfo{ Algo: encryptedlAlgorithmIdentifier{ Algorithm: oidPBES2, Parameters: pbes2Params{ KeyDerivationFunc: pbkdf2Algorithms{ Algo: oidPKCS5PBKDF2, PBKDF2Params: pbkdf2Params{ Salt: salt, IterationCount: PBKDF2Iterations, PrfParam: prfParam{ Algo: oidHMACWithSHA256, }, }, }, EncryptionScheme: pbkdf2Encs{ EncryAlgo: ciph.identifier, IV: iv, }, }, }, PrivateKey: encrypted, } b, err := asn1.Marshal(pki) if err != nil { return nil, errors.Wrap(err, "error marshaling encrypted key") } return &pem.Block{ Type: "ENCRYPTED PRIVATE KEY", Bytes: b, }, nil } golang-step-crypto-0.24.0/pemutil/pkcs8_test.go000066400000000000000000000142051437037643100214420ustar00rootroot00000000000000package pemutil import ( "crypto/ecdsa" "crypto/ed25519" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" "errors" "os" "reflect" "strings" "testing" "github.com/smallstep/assert" ) func TestEncryptDecryptPKCS8(t *testing.T) { t.Parallel() password := []byte("mypassword") for fn, td := range files { // skip encrypted and public keys if td.encrypted || td.typ == rsaPublicKey || td.typ == ecdsaPublicKey || td.typ == ed25519PublicKey { continue } // skip x25519 keys if td.typ == x25519PublicKey || td.typ == x25519PrivateKey { continue } // To be able to run this in parallel we need to declare local // variables. fn := fn t.Run(fn, func(t *testing.T) { t.Parallel() data, err := os.ReadFile(fn) assert.FatalError(t, err) key1, err := Parse(data) if err != nil { t.Errorf("failed to parse %s: %v", fn, err) return } data, err = x509.MarshalPKCS8PrivateKey(key1) if err != nil { t.Errorf("failed to marshal private key for %s: %v", fn, err) return } for _, alg := range rfc1423Algos { encBlock, err := EncryptPKCS8PrivateKey(rand.Reader, data, password, alg.cipher) if err != nil { t.Errorf("failed to decrypt %s with %s: %v", fn, alg.name, err) continue } assert.Equals(t, "ENCRYPTED PRIVATE KEY", encBlock.Type) assert.NotNil(t, encBlock.Bytes) assert.Nil(t, encBlock.Headers) data, err = DecryptPKCS8PrivateKey(encBlock.Bytes, password) if err != nil { t.Errorf("failed to decrypt %s with %s: %v", fn, alg.name, err) continue } key2, err := x509.ParsePKCS8PrivateKey(data) if err != nil { t.Errorf("failed to parse PKCS#8 key %s: %v", fn, err) continue } assert.Equals(t, key1, key2) } }) } } func TestSerialize_PKCS8(t *testing.T) { mustPKIX := func(pub interface{}) *pem.Block { b, err := x509.MarshalPKIXPublicKey(pub) assert.FatalError(t, err) return &pem.Block{ Type: "PUBLIC KEY", Bytes: b, } } mustPKCS8 := func(priv interface{}) *pem.Block { b, err := x509.MarshalPKCS8PrivateKey(priv) assert.FatalError(t, err) return &pem.Block{ Type: "PRIVATE KEY", Bytes: b, } } rsaKey, err := Read("testdata/openssl.rsa2048.pem") assert.FatalError(t, err) ecdsaKey, err := Read("testdata/openssl.p256.pem") assert.FatalError(t, err) edKey, err := Read("testdata/pkcs8/openssl.ed25519.pem") assert.FatalError(t, err) rsaKeyPub := rsaKey.(*rsa.PrivateKey).Public() ecdsaKeyPub := ecdsaKey.(*ecdsa.PrivateKey).Public() edKeyPub := edKey.(ed25519.PrivateKey).Public() type args struct { pub interface{} } tests := []struct { name string args args want *pem.Block wantErr bool }{ {"rsa", args{rsaKey}, mustPKCS8(rsaKey), false}, {"rsa pub", args{rsaKeyPub}, mustPKIX(rsaKeyPub), false}, {"ecdsa", args{ecdsaKey}, mustPKCS8(ecdsaKey), false}, {"ecdsa pub", args{ecdsaKeyPub}, mustPKIX(ecdsaKeyPub), false}, {"ed25519", args{edKey}, mustPKCS8(edKey), false}, {"ed25519 pub", args{edKeyPub}, mustPKIX(edKeyPub), false}, {"fail", args{[]byte("fooobar")}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // got, err := x509.MarshalPKIXPublicKey(tt.args.pub) got, err := Serialize(tt.args.pub, WithPKCS8(true)) if (err != nil) != tt.wantErr { t.Errorf("Serialize() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("Serialize() = \n got %v, \nwant %v", got, tt.want) } }) } } func TestDecryptPKCS8PrivateKey(t *testing.T) { password := []byte("mypassword") for name, td := range files { // skip non-encrypted and non pkcs8 keys if !td.encrypted || !strings.HasPrefix(name, "testdata/pkcs8/") { continue } t.Run(name, func(t *testing.T) { data, err := os.ReadFile(name) if err != nil { t.Errorf("os.ReadFile() error = %v", err) return } block, _ := pem.Decode(data) if block == nil { t.Errorf("pem.Decode() failed, block = %v", block) return } data, err = DecryptPKCS8PrivateKey(block.Bytes, password) if err != nil { t.Errorf("DecryptPKCS8PrivateKey() error = %v", err) return } // Invalid password _, err = DecryptPKCS8PrivateKey(block.Bytes, []byte("foobar")) if !errors.Is(err, x509.IncorrectPasswordError) { t.Errorf("DecryptPKCS8PrivateKey() error=%v, wantErr=%v", err, x509.IncorrectPasswordError) } _, err = x509.ParsePKCS8PrivateKey(data) if err != nil { t.Errorf("x509.ParsePKCS8PrivateKey() error = %v", err) } }) } } func TestDecryptPKCS8PrivateKey_ciphers(t *testing.T) { _, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } data, err := x509.MarshalPKCS8PrivateKey(priv) if err != nil { t.Fatal(err) } password := []byte("mypassword") for _, alg := range rfc1423Algos { t.Run(alg.name, func(t *testing.T) { encData, err := EncryptPKCS8PrivateKey(rand.Reader, data, password, alg.cipher) if err != nil { t.Errorf("EncryptPKCS8PrivateKey() error = %v", err) return } decData, err := DecryptPKCS8PrivateKey(encData.Bytes, password) if err != nil { t.Errorf("DecryptPKCS8PrivateKey() error = %v", err) return } // Invalid password. // // Because of the only way to check if the password is correct or // not is checking the padding data, it's possible and probably // enough to get a padding length of 1, with the data 01. If this // happens the DecryptPKCS8PrivateKey will not return an error, but // it will return bad data. We will check before if the data is // correct before erroring. badData, err := DecryptPKCS8PrivateKey(encData.Bytes, []byte("foobar")) if !errors.Is(err, x509.IncorrectPasswordError) { if _, err := x509.ParsePKCS8PrivateKey(badData); err == nil { t.Errorf("DecryptPKCS8PrivateKey() error=%v, wantErr=%v", err, x509.IncorrectPasswordError) } } // Check with original key key, err := x509.ParsePKCS8PrivateKey(decData) if err != nil { t.Errorf("x509.ParsePKCS8PrivateKey() error = %v", err) } if !reflect.DeepEqual(key, priv) { t.Errorf("DecryptPKCS8PrivateKey() got = %v, want = %v", key, priv) } }) } } golang-step-crypto-0.24.0/pemutil/ssh.go000066400000000000000000000157021437037643100201530ustar00rootroot00000000000000// Copyright 2012 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package pemutil import ( "crypto" "crypto/aes" "crypto/cipher" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" "encoding/binary" "encoding/pem" "math/big" "github.com/pkg/errors" bcryptpbkdf "go.step.sm/crypto/internal/bcrypt_pbkdf" "go.step.sm/crypto/randutil" "golang.org/x/crypto/ssh" ) const ( sshMagic = "openssh-key-v1\x00" sshDefaultKdf = "bcrypt" sshDefaultCiphername = "aes256-ctr" sshDefaultKeyLength = 32 sshDefaultSaltLength = 16 sshDefaultRounds = 16 ) type openSSHPrivateKey struct { CipherName string KdfName string KdfOpts string NumKeys uint32 PubKey []byte PrivKeyBlock []byte } type openSSHPrivateKeyBlock struct { Check1 uint32 Check2 uint32 Keytype string Rest []byte `ssh:"rest"` } // ParseOpenSSHPrivateKey parses a private key in OpenSSH PEM format. // // Implemented based on the documentation at // https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key // // This method is based on the implementation at // https://github.com/golang/crypto/blob/master/ssh/keys.go func ParseOpenSSHPrivateKey(pemBytes []byte, opts ...Options) (crypto.PrivateKey, error) { // Populate options ctx := newContext("PEM") if err := ctx.apply(opts); err != nil { return nil, err } block, _ := pem.Decode(pemBytes) if block == nil { return nil, errors.Errorf("error decoding %s: not a valid PEM encoded block", ctx.filename) } if len(block.Bytes) < len(sshMagic) || string(block.Bytes[:len(sshMagic)]) != sshMagic { return nil, errors.New("invalid openssh private key format") } remaining := block.Bytes[len(sshMagic):] var w openSSHPrivateKey if err := ssh.Unmarshal(remaining, &w); err != nil { return nil, errors.Wrap(err, "error unmarshaling private key") } var err error var key crypto.PrivateKey if w.KdfName != "none" || w.CipherName != "none" { password, err := ctx.promptPassword() if err != nil { return nil, err } key, err = ssh.ParseRawPrivateKeyWithPassphrase(pemBytes, password) if err != nil { return nil, errors.Wrap(err, "error parsing private key") } } else { key, err = ssh.ParseRawPrivateKey(pemBytes) if err != nil { return nil, errors.Wrap(err, "error parsing private key") } } // Convert *ed25519.PrivateKey to ed25519.PrivateKey: switch k := key.(type) { case *ed25519.PrivateKey: return *k, nil default: return k, nil } } // SerializeOpenSSHPrivateKey serialize a private key in the OpenSSH PEM format. func SerializeOpenSSHPrivateKey(key crypto.PrivateKey, opts ...Options) (*pem.Block, error) { ctx := new(context) if err := ctx.apply(opts); err != nil { return nil, err } // Random check bytes. var check uint32 if err := binary.Read(rand.Reader, binary.BigEndian, &check); err != nil { return nil, errors.Wrap(err, "error generating random check ") } w := openSSHPrivateKey{ NumKeys: 1, } pk1 := openSSHPrivateKeyBlock{ Check1: check, Check2: check, } password, err := ctx.promptEncryptPassword() if err != nil { return nil, err } var blockSize int if password == nil { w.CipherName = "none" w.KdfName = "none" blockSize = 8 } else { w.CipherName = sshDefaultCiphername w.KdfName = sshDefaultKdf blockSize = aes.BlockSize } switch k := key.(type) { case *rsa.PrivateKey: e := new(big.Int).SetInt64(int64(k.PublicKey.E)) // Marshal public key: // E and N are in reversed order in the public and private key. pubKey := struct { KeyType string E *big.Int N *big.Int }{ ssh.KeyAlgoRSA, e, k.PublicKey.N, } w.PubKey = ssh.Marshal(pubKey) // Marshal private key. key := struct { N *big.Int E *big.Int D *big.Int Iqmp *big.Int P *big.Int Q *big.Int Comment string }{ k.PublicKey.N, e, k.D, k.Precomputed.Qinv, k.Primes[0], k.Primes[1], ctx.comment, } pk1.Keytype = ssh.KeyAlgoRSA pk1.Rest = ssh.Marshal(key) case *ecdsa.PrivateKey: var curve, keyType string switch k.Curve.Params().Name { case "P-256": curve = "nistp256" keyType = ssh.KeyAlgoECDSA256 case "P-384": curve = "nistp384" keyType = ssh.KeyAlgoECDSA384 case "P-521": curve = "nistp521" keyType = ssh.KeyAlgoECDSA521 default: return nil, errors.Errorf("error serializing key: unsupported curve %s", k.Curve.Params().Name) } pub := elliptic.Marshal(k.Curve, k.PublicKey.X, k.PublicKey.Y) // Marshal public key. pubKey := struct { KeyType string Curve string Pub []byte }{ keyType, curve, pub, } w.PubKey = ssh.Marshal(pubKey) // Marshal private key. key := struct { Curve string Pub []byte D *big.Int Comment string }{ curve, pub, k.D, ctx.comment, } pk1.Keytype = keyType pk1.Rest = ssh.Marshal(key) case ed25519.PrivateKey: pub := make([]byte, ed25519.PublicKeySize) priv := make([]byte, ed25519.PrivateKeySize) copy(pub, k[ed25519.PublicKeySize:]) copy(priv, k) // Marshal public key. pubKey := struct { KeyType string Pub []byte }{ ssh.KeyAlgoED25519, pub, } w.PubKey = ssh.Marshal(pubKey) // Marshal private key. key := struct { Pub []byte Priv []byte Comment string }{ pub, priv, ctx.comment, } pk1.Keytype = ssh.KeyAlgoED25519 pk1.Rest = ssh.Marshal(key) default: return nil, errors.Errorf("unsupported key type %T", k) } w.PrivKeyBlock = ssh.Marshal(pk1) // Add padding until the private key block matches the block size, // 16 with AES encryption, 8 without. for i, l := 0, len(w.PrivKeyBlock); (l+i)%blockSize != 0; i++ { w.PrivKeyBlock = append(w.PrivKeyBlock, byte(i+1)) } if password != nil { // Create encryption key derivation the password. salt, err := randutil.Salt(sshDefaultSaltLength) if err != nil { return nil, err } kdfOpts := struct { Salt []byte Rounds uint32 }{salt, sshDefaultRounds} w.KdfOpts = string(ssh.Marshal(kdfOpts)) // Derive key to encrypt the private key block. k, err := bcryptpbkdf.Key(password, salt, sshDefaultRounds, sshDefaultKeyLength+aes.BlockSize) if err != nil { return nil, errors.Wrap(err, "error deriving decryption key") } // Encrypt the private key using the derived secret. dst := make([]byte, len(w.PrivKeyBlock)) iv := k[sshDefaultKeyLength : sshDefaultKeyLength+aes.BlockSize] block, err := aes.NewCipher(k[:sshDefaultKeyLength]) if err != nil { return nil, errors.Wrap(err, "error creating cipher") } stream := cipher.NewCTR(block, iv) stream.XORKeyStream(dst, w.PrivKeyBlock) w.PrivKeyBlock = dst } b := ssh.Marshal(w) block := &pem.Block{ Type: "OPENSSH PRIVATE KEY", Bytes: append([]byte(sshMagic), b...), } if ctx.filename != "" { if err := WriteFile(ctx.filename, pem.EncodeToMemory(block), ctx.perm); err != nil { return nil, err } } return block, nil } golang-step-crypto-0.24.0/pemutil/testdata/000077500000000000000000000000001437037643100206335ustar00rootroot00000000000000golang-step-crypto-0.24.0/pemutil/testdata/bad.csr000066400000000000000000000001351437037643100220710ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- Bad Certificate Request -----END CERTIFICATE REQUEST-----golang-step-crypto-0.24.0/pemutil/testdata/badca.crt000066400000000000000000000043651437037643100224070ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIGcDCCBFigAwIBAgIBATANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJVUzEL MAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEjAQBgNVBAoMCXNt YWxsc3RlcDEfMB0GA1UEAwwWaW50ZXJuYWwuc21hbGxzdGVwLmNvbTAeFw0xNzA2 MTQyMTU3MTRaFw0yNzA2MTIyMTU3MTRaMGcxHzAdBgNVBAMMFmludGVybmFsLnNt YWxsc3RlcC5jb20xCzAJBgNVBAYTAlVTMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2Nv MRIwEAYDVQQKDAlzbWFsbHN0ZXAxCzAJBgNVBAgMAkNBMIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEAt0M+Eu4i6hdERJ8yTrVwZz7Hds2WQrPd89NuVYP4 ouJbuG+udcGeWpJc8nIlv0w6F/8CRMxuY9FaHY2epiGb4Xw9P1a+XXIm70OpQFEQ VPJycbHyphLuGjSq3Mc3+AVKDyQZSYuW+C6XJYsI06p2Y0xm8ST2BWc7b4CuNVte e38eVaxiISs5P7GIcD6nMhTbEtC6H6j+EjEcZOboMFNPQNRXlPdtyrLT4QhDSLGi d/vTs33ttkv0ebUuCFGc/965KmP1NawKhka3x0kJ20/vXzM8XjwLSOG2041+MdnJ rgCllQkirbTBfIg0u64yDV81k853RF9ZvkuNI/FPQ5ueo1HeB2ExumQl3S6vT+nv TgoDLkwq6kgoMYXPjO4kDnklf3xiCVfmsMUmGgOYbFcRD5e4fTOHeBRGC27aklOI k5CODMcFQRvHliQZpnORWmNgzvaNLEOf5d3wk613+rj+iru//EeuMNVP+PO6k95/ g+D6nEvc9WUjiO/2vDR4Dw+LiAeWH+bXx+AVCugin7snkz0xmNUtvzne2LLqUTie s3mRxw87pjgDvTDstF7il3D83dxzJI7xp/00WlMdzwaxPT182Y45JH6mS0BNtUEo oPtfDtyE54SXa56R+tZREzw+CizAbS4FzlwQmYY5pZHuSQKAxQMFOJPbFTrEt8tk Tg0CAwEAAaOCASUwggEhMIGZBgNVHSMEgZEwgY6AFIuhuVEao1ML4RaiJbaSFlHs fDgloWukaTBnMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNh biBGcmFuY2lzY28xEjAQBgNVBAoMCXNtYWxsc3RlcDEfMB0GA1UEAwwWaW50ZXJu YWwuc21hbGxzdGVwLmNvbYIJANHnRMPsmOa0MBIGA1UdEwEB/wQIMAYBAf8CAQAw HQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA4GA1UdDwEB/wQEAwIBRjAh BgNVHREEGjAYghZpbnRlcm5hbC5zbWFsbHN0ZXAuY29tMB0GA1UdDgQWBBRFga2r OHyWFPOHK2hrFPP7PKx28DANBgkqhkiG9w0BAQsFAAOCAgEAay1ZCyfCJNwmGawA pWO8pUsmv39dUu6lfeG0yHBGQMUUrmuUrn1JbAQQ5rxXSIMWQHNskACO37l5Ol0+ UZNjo+2vnDS6+5XQJ2k68e9Ivb/dm7nrJfDrs+WcLcQ/fCn01pKA02gIESQzIHci zuSAtj8iVG7IkfWkGAK7DLsrktAcB+2+8SqzN1XwLlb2jkPOuseM+BidesvP+cQR ik614DwNAHkD5v+n6Ee2LrDFu7Qsl2fNe3OPIMe9cWFC68yL/bhXpeTYzfArHVbR imZvEB6e6wvW0J4o+e25LIFIavLuoLTt0Js+3Q2XD/HNqxBJsZe6y/LX1JMGLEe1 pWOcGt97xXL4hdqWT+M4kUgpbxmuEEretlVVydpKncxCa/ovzWyhp4TJ4RZ0J67p vUbVV+BiW3nfIbz+3pS9a0O4huvp+iNfIKo74xLYWGcT/37Le/X7nJbwLLg+fzCj 5CKgEj6YWJ5om9fD5aV3CLczsFsSkzg0R292DPYuWiWAcgPrrVa2mSaNSWacajmx 5CIBu5G/VBmGjYSHa+dnagPFExsDpksB6AzX82pthDewAikVSg3DTK4sDuKXpXFS CMRrIxHUYRUgyoMjsNUR9KQeq7hiTwCq325KpL7XWBtRCoBOwLWX1lECScJITQA4 XevZD8LWagqK8/3ZQsrBDmcmrAs+ -----END CERTIFICATE----- golang-step-crypto-0.24.0/pemutil/testdata/badder.crt000066400000000000000000000031621437037643100225700ustar00rootroot000000000000000‚p0‚X 0  *†H†๗  0g1 0 UUS1 0 U CA10U San Francisco10U smallstep10U internal.smallstep.com0 170614215714Z 270612215714Z0g10U internal.smallstep.com1 0 UUS10U San Francisco10U smallstep1 0 U CA0‚"0  *†H†๗ ‚0‚ ‚ทC>๎"๊DDŸ2Nตpg>วvอ–Bณ๓ำnUƒ๘ขโ[ธoฎuมžZ’\๒r%ฟL:DฬncัZžฆ!›แ|=?Vพ]r&๏Cฉ@QT๒rqฑ๒ฆ๎4ชว7๘J$I‹–๘.—%‹ำชvcLf๑$๖g;o€ฎ5[^{Uฌb!+9?ฑˆp>ง2ะบจ1dๆ่0SO@ิW”๗mสฒำแCHฑขw๛ำณ}ํถK๔yต.Qœน*c๕5ฌ †FทวI O๏_3<^< Hแถำ~1ูษฎฅ• "ญดม|ˆ4ปฎ2 _5“ฮwD_YพK#๑OC›žฃQa1บd%.ฏO้๏N .L*๊H(1…ฯŒ๎$y%|b Wๆฐล&˜lW—ธ}3‡xF nฺ’Sˆ“Ž วAว–$ฆs‘Zc`ฮ๖,CŸๅ๐“ญw๚ธŠปฟGฎ0ีO๘๓บ“ƒเ๚œK๕e#ˆ๏๖ผ4x‹ˆ–ๆืวเ ่"Ÿป'“=1˜ี-ฟ9ุฒ๊Q8žณy‘ว;ฆ8ฝ0์ด^โ—ps$Ž๑ง4ZSฯฑ==|ูŽ9$~ฆK@MตA( ๛_„็„—kž‘๚ึQ<> ,ภm.ฮ\™†9ฅ‘๎I€ล8“:ฤทหdN ฃ‚%0‚!0™U#‘0Ž€‹กนQฃS แข%ถ’Q์|8%กkคi0g1 0 UUS1 0 U CA10U San Francisco10U smallstep10U internal.smallstep.com‚ ั็Dร์˜ๆด0U00U%0++0UF0!U0‚internal.smallstep.com0UEญซ8|–๓‡+hk๓๛<ฌv๐0  *†H†๗  ‚k-Y 'ย$&ฌฅcผฅK&ฟ]R๎ฅ}แดศpF@ลฎk”ฎ}IlๆผWHƒ@slŽ฿นy:]>Q“cฃํฏœ4บ๛•ะ'i:๑๏Hฝฟ›น๋%๐๋ณๅœ-ฤ?|)๔ึ’€ำh$3 w"ฮไ€ถ?"Tnศ‘๕คป ป+’ะํพ๑*ณ7U๐.V๖ŽCฮบวŒ๘zหฯ๙ฤŠNตเ< yๆง่Gถ.ฐลปด,—gอ{s วฝqaB๋ฬ‹ธWฅไุอ๐+VัŠfož๋ ึะž(๙ํน,Hj๒๎ ดํะ›> —๑อซIฑ—บห๒ืิ“,Gตฅcœ฿{ลr๘…ฺ–Oใ8‘H)oฎJถUUษฺJฬBk๚/อlกง„ษแt'ฎ้ฝFีWเb[y฿!ผ”ฝkCธ†๋้๚#_ ช;ใุXg~ห{๕๛œ–๐,ธ>0ฃไ" >˜Xžh›ืรๅฅwท3ฐ[“84Gov ๖.Z%€r๋ญVถ™&Ifœj9ฑไ"ป‘ฟT†„‡k็gjลฆK่ ื๓jm„7ฐ)J รLฎ,โ—ฅqRฤk#ิa สƒ#ฐี๔คซธbOช฿nJคพืXQ €Nภต—ึQIยHM8]ู๋ยึj Š๓ูBสมg&golang-step-crypto-0.24.0/pemutil/testdata/badnebula.key000066400000000000000000000006471437037643100232710ustar00rootroot00000000000000-----BEGIN NEBULA X25519 PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACBRf06YlDi/CVnLEDMTlxWH+OFojR1Y1d3rw7Guu1EKVQAAAJgG8EtjBvBL YwAAAAtzc2gtZWQyNTUxOQAAACBRf06YlDi/CVnLEDMTlxWH+OFojR1Y1d3rw7Guu1EKVQ AAAECHHCRxaxuDUzPhwiXZovjvMLYkLiSx+ho3b+9gavl031F/TpiUOL8JWcsQMxOXFYf4 4WiNHVjV3evDsa67UQpVAAAAE21hcmlhbm9AZW5kb3IubG9jYWwBAg== -----END NEBULA X25519 PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/badnebula.pub000066400000000000000000000006451437037643100232650ustar00rootroot00000000000000-----BEGIN NEBULA X25519 PUBLIC KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACBRf06YlDi/CVnLEDMTlxWH+OFojR1Y1d3rw7Guu1EKVQAAAJgG8EtjBvBL YwAAAAtzc2gtZWQyNTUxOQAAACBRf06YlDi/CVnLEDMTlxWH+OFojR1Y1d3rw7Guu1EKVQ AAAECHHCRxaxuDUzPhwiXZovjvMLYkLiSx+ho3b+9gavl031F/TpiUOL8JWcsQMxOXFYf4 4WiNHVjV3evDsa67UQpVAAAAE21hcmlhbm9AZW5kb3IubG9jYWwBAg== -----END NEBULA X25519 PUBLIC KEY----- golang-step-crypto-0.24.0/pemutil/testdata/badpem.crt000066400000000000000000000043621437037643100226020ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIGcDCCBFigAwIBAgIBATANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJVUzEL MAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEjAQBgNVBAoMCXNt YWxsc3RlcDEfMB0GA1UEAwwWaW50ZXJuYWwuc21hbGxzdGVwLmNvbTAeFw0xNzA2 MTQyMTU3MTRaFw0yNzA2MTIyMTU3MTRaMGcxHzAdBgNVBAMMFmludGVybmFsLnNt YWxsc3RlcC5jb20xCzAJBgNVBAYTAlVTMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2Nv MRIwEAYDVQQKDAlzbWFsbHN0ZXAxCzAJBgNVBAgMAkNBMIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEAt0M+Eu4i6hdERJ8yTrVwZz7Hds2WQrPd89NuVYP4 ouJbuG+udcGeWpJc8nIlv0w6F/8CRMxuY9FaHY2epiGb4Xw9P1a+XXIm70OpQFEQ VPJycbHyphLuGjSq3Mc3+AVKDyQZSYuW+C6XJYsI06p2Y0xm8ST2BWc7b4CuNVte e38eVaxiISs5P7GIcD6nMhTbEtC6H6j+EjEcZOboMFNPQNRXlPdtyrLT4QhDSLGi d/vTs33ttkv0ebUuCFGc/965KmP1NawKhka3x0kJ20/vXzM8XjwLSOG2041+MdnJ rgCllQkirbTBfIg0u64yDV81k853RF9ZvkuNI/FPQ5ueo1HeB2ExumQl3S6vT+nv TgoDLkwq6kgoMYXPjO4kDnklf3xiCVfmsMUmGgOYbFcRD5e4fTOHeBRGC27aklOI k5CODMcFQRvHliQZpnORWmNgzvaNLEOf5d3wk613+rj+iru//EeuMNVP+PO6k95/ g+D6nEvc9WUjiO/2vDR4Dw+LiAeWH+bXx+AVCugin7snkz0xmNUtvzne2LLqUTie s3mRxw87pjgDvTDstF7il3D83dxzJI7xp/00WlMdzwaxPT182Y45JH6mS0BNtUEo oPtfDtyE54SXa56R+tZREzw+CizAbS4FzlwQmYY5pZHuSQKAxQMFOJPbFTrEt8tk Tg0CAwEAAaOCASUwggEhMIGZBgNVHSMEgZEwgY6AFIuhuVEao1ML4RaiJbaSFlHs fDgloWukaTBnMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNh biBGcmFuY2lzY28xEjAQBgNVBAoMCXNtYWxsc3RlcDEfMB0GA1UEAwwWaW50ZXJu YWwuc21hbGxzdGVwLmNvbYIJANHnRMPsmOa0MBIGA1UdEwEB/wQIMAYBAf8CAQAw HQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA4GA1UdDwEB/wQEAwIBRjAh BgNVHREEGjAYghZpbnRlcm5hbC5zbWFsbHN0ZXAuY29tMB0GA1UdDgQWBBRFga2r OHyWFPOHK2hrFPP7PKx28DANBgkqhkiG9w0BAQsFAAOCAgEAay1ZCyfCJNwmGawA pWO8pUsmv39dUu6lfeG0yHBGQMUUrmuUrn1JbAQQ5rxXSIMWQHNskACO37l5Ol0+ UZNjo+2vnDS6+5XQJ2k68e9Ivb/dm7nrJfDrs+WcLcQ/fCn01pKA02gIESQzIHci zuSAtj8iVG7IkfWkGAK7DLsrktAcB+2+8SqzN1XwLlb2jkPOuseM+BidesvP+cQR ik614DwNAHkD5v+n6Ee2LrDFu7Qsl2fNe3OPIMe9cWFC68yL/bhXpeTYzfArHVbR imZvEB6e6wvW0J4o+e25LIFIavLuoLTt0Js+3Q2XD/HNqxBJsZe6y/LX1JMGLEe1 pWOcGt97xXL4hdqWT+M4kUgpbxmuEEretlVVydpKncxCa/ovzWyhp4TJ4RZ0J67p vUbVV+BiW3nfIbz+3pS9a0O4huvp+iNfIKo74xLYWGcT/37Le/X7nJbwLLg+fzCj 5CKgEj6YWJ5om9fD5aV3CLczsFsSkzg0R292DPYuWiWAcgPrrVa2mSaNSWacajmx 5CIBu5G/VBmGjYSHa+dnagPFExsDpksB6AzX82pthDewAikVSg3DTK4sDuKXpXFS CMRrIxHUYRUgyoMjsNUR9KQeq7hiTwCq325KpL7XWBtRCoBOwLWX1lECScJITQA4 XevZD8LWagqK8/3ZQsrBDmcmr -----END CERTIFICATE----- golang-step-crypto-0.24.0/pemutil/testdata/badpem.csr000066400000000000000000000005561437037643100226020ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- MAHYMIGAAgEAMB4xHDAaBgNVBAMTE2hlbGxvLnNtYWxsc3RlcC5jb20wWTATBgcq hkjOPQIBBggqhkjOPQMBBwNCAASKj0MvK6CUzFqRLfDTQNQp0Zt5dcHYx+Dq1Wh9 5dhqf1Fu9+1m5+LKkgBWoZmo7sJH0RuSjIdv/sZwpBrkdn2soAAwCgYIKoZIzj0E AwIDRwAwRAIgZgz9gdx9inOp6bSX4EkYiUCyLV9xGvabovu5C9UkRr8CIBGBbkp0 l4tesAKoXelsLygJjPuUGRLK+OtdjPBIN1Zo -----END CERTIFICATE REQUEST-----golang-step-crypto-0.24.0/pemutil/testdata/bundle.crt000066400000000000000000000063151437037643100226230ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIEhzCCA2+gAwIBAgISA78mVnMzLbLQxw5IoWP7fRG6MA0GCSqGSIb3DQEBCwUA MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xOTAyMDgxMzA3NDRaFw0x OTA1MDkxMzA3NDRaMBgxFjAUBgNVBAMTDXNtYWxsc3RlcC5jb20wWTATBgcqhkjO PQIBBggqhkjOPQMBBwNCAATtaDvEhLijnzgpf/svy2v0lA0q1KNMmKmb8kdIgFsi Rqmzh0IPldiprW6/zIBPKC3ZWBzdw06ZuSXeuPQ0rcC1o4ICYjCCAl4wDgYDVR0P AQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMB Af8EAjAAMB0GA1UdDgQWBBQ5p9apFolkDFuITyFnBK4BxE67dDAfBgNVHSMEGDAW gBSoSmpjBH3duubRObemRWXv86jsoTBvBggrBgEFBQcBAQRjMGEwLgYIKwYBBQUH MAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlwdC5vcmcwLwYIKwYBBQUH MAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcvMBgGA1UdEQQR MA+CDXNtYWxsc3RlcC5jb20wTAYDVR0gBEUwQzAIBgZngQwBAgEwNwYLKwYBBAGC 3xMBAQEwKDAmBggrBgEFBQcCARYaaHR0cDovL2Nwcy5sZXRzZW5jcnlwdC5vcmcw ggEEBgorBgEEAdZ5AgQCBIH1BIHyAPAAdQB0ftqDMa0zEJEhnM4lT0Jwwr/9XkIg CMY3NXnmEHvMVgAAAWjNb4RTAAAEAwBGMEQCID7NdufkWtiID0FJKcXBiUnhW1OX w2eU1ZRsitnaRqL3AiBlGOiUaaWf92NGqlEkEp2/oaED0OZYbLe1LTvPnRsQoAB3 AGPy283oO8wszwtyhCdXazOkjWF3j711pjixx2hUS9iNAAABaM1vhI4AAAQDAEgw RgIhAJ8A7OHfNThbzUOiSk5Y+JOSvOiSJ1ferIOX4z3AbD7qAiEA3Aiw5ZfrXyEn PsHWofgMuz8dWvv4QxFXxLZRmXH0QDIwDQYJKoZIhvcNAQELBQADggEBAFrmkLMe OhGGuOSkY3hsUnSEUy5N1lrpGRrwyWVHTPcLJdlds5S8l5xYg2LcPfWQXkUHUYcr Fo7jT5Up4UIXYvE6Lctm48geIExlQwcOkSo3ULSQJYz9bp1tDpv9cQgyHJtwfrbR 2rxtpasLIs8znzbBcJlQ4rlodyzUMEJh8YgT9XpynDbk5K43nfsng1uRqI9J6brt AasWcqPaJ97ILTT3DNtk2cLBpAqtMwaxcROdZ1104fbWzYjGgv67W78CBgndhvbp Yx8h05Bm4vY0tz7Zv0Qd3YwFKgIZQI/BR/Mdber9P+xYU51T6xu4p4JDcQsCxtYg 9zBQ7U7V9X22RGo= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj /PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== -----END CERTIFICATE-----golang-step-crypto-0.24.0/pemutil/testdata/ca.crt000066400000000000000000000041021437037643100217250ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIF6zCCA9OgAwIBAgIRAL4t3Jo++cwAle8DdXchv/owDQYJKoZIhvcNAQELBQAw WzEMMAoGA1UEBhMDVVNBMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRIwEAYDVQQK EwlzbWFsbHN0ZXAxHzAdBgNVBAMTFmludGVybmFsLnNtYWxsc3RlcC5jb20wHhcN MTcwOTIzMDczNTA3WhcNMTgwOTIzMDczNTA3WjBbMQwwCgYDVQQGEwNVU0ExFjAU BgNVBAcTDVNhbiBGcmFuY2lzY28xEjAQBgNVBAoTCXNtYWxsc3RlcDEfMB0GA1UE AxMWaW50ZXJuYWwuc21hbGxzdGVwLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP ADCCAgoCggIBAKA+760g0MbZpFCgG6NpzRh0B8ElgQUteMjynL8ge+r8QsFCm2XY P7BYzjyyD9FdNTRw2toUB8G/t3E5jhjrE6qvG0PWsluzFEtfh0uS59BPS6YTgurY LE3PAc/+fCxEI3SfA4TCYVnzUcSkhcHNT0PtMWG8tR7S+0GFc1O22wUn2e/dKK1d fCGhEu9gzuA3TjJgpzfmXTBFUijiIRSaXHiYcUWR0FE3CKVULlM2jJ/uxXZr6kSZ STxQ/kisaIzOe7Y/uA9F4fyfCHdaCsvkv3d11d1SkOdBCY+jx+PG5uLDWxGCgZYZ dWDjOX43gquSaC3bFMi+cglF4Wx+n173elcOuoF77bVNBOOtWIbWNLYVujkvbzec Dn0NLySl79OKMuSuF995iR7Or29gcbaZz5j1NHeqbhb24HWZ+9xi3ws4ike7GZ5Q akZ3AwEcwVwbMhQ5KCoWKroSWpYUvQ58PGgy+ml5f42Cjg/e1nH1/hpnqwzzItbs 6qb9I0RV12Y6KCEqmKIrs1EdHc351aknhiZ1Zgdankhym3TiAo08mkDIqbJUKjR+ 0De7ynBKBDq79NWfb5DLdXH95z1DDZvI4FJ9X0eAlo3DbkZXFIfoeF1gL577pEES NXZKXqmY2hPcsZUKAhXIEK3zmNXJVGeqb9sNnYtBTY6zBo5sA+40WDHNAgMBAAGj gakwgaYwDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF BQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRVBozFxzNJ9pSzW4lW MZF/q+6SPTAfBgNVHSMEGDAWgBTd1DpE9Y4R5OoP7f5WT91mPCU2gDAhBgNVHREE GjAYghZpbnRlcm5hbC5zbWFsbHN0ZXAuY29tMA0GCSqGSIb3DQEBCwUAA4ICAQCj 3CT2xk9xLFTX0Ki30PWB6/0OxN5L0sKk7pJAWIzgdsKYrBbh93sA/oYGsnr0iW6F VLWkvqMmGLlp5yLg6LIQaed5C26u2fc0udzXTVfEx7QjOtLtetLt7LQ6Kzb7FOri iiUfvilLttXyESQ3WzlCTh4OlrIhNWg1w56jc0/7GAJY0LTrsCoYOSwR2qlBQiTI 41+fApAlRZKI0eNP9X4GxVkgh+wIVuF4zXr/460VkaWT9RquvS2MIaotdZ3IBTTk tCmFHvbI/eZOWo1KbjPdSByOZVI1gBfpU/eufsdysRebZwBsYRDF391QJ3aKt2cZ WnAjtYl3lfcXA/iFj2HL04vmdTweBVvl7Wa2EsM/iiEhPOWlIXdQx81FURE8nc2H DCQJgQIbwqZ4LQJrmF6tmzhmJUH2/9Vxc/rYMSx6NgT6sSoz+gXt0yDd20tF7SU6 smiL/uCGfSXAbqsI+MO8Nc7gOPhKtHeW4r2Kx/OzuFkYAFBez0GqxmnJ/xKgwfGO v+pzRC09KUgpncGZuB6S9PUWPhC15LO5bFBF1tiUy8hyzzbopfyPtLnhY8tq4u+j lGTAz5g+7chL+j6UqZZYGD5DqIiOYO/YK0wi92ov1wu6Pkvb33dy1LVWJaGjcAQv 7cuffXbN9jXxAtrFxrmY3qfXnUR4K2lCESWPjCCmaw== -----END CERTIFICATE----- golang-step-crypto-0.24.0/pemutil/testdata/ca.der000066400000000000000000000027571437037643100217250ustar00rootroot000000000000000‚๋0‚ำ พ-š>๙ฬ•๏uw!ฟ๚0  *†H†๗  0[1 0 UUSA10U San Francisco10U  smallstep10Uinternal.smallstep.com0 170923073507Z 180923073507Z0[1 0 UUSA10U San Francisco10U  smallstep10Uinternal.smallstep.com0‚"0  *†H†๗ ‚0‚ ‚ >๏ญ ะฦูคP ฃiอtม%-xศ๒œฟ {๊BมB›eุ?ฐXฮ<ฒั]54pฺฺมฟทq9Ž๋ชฏCึฒ[ณK_‡K’็ะOKฆ‚ุ๊,Mฯฯ|,D#tŸ„ยaY๓Qฤค…มอOCํ1aผตา๛A…sSถ'ู๏(ญ]|!ก๏`ฮเ7N2`ง7ๆ]0ER(โ!š\x˜qE‘ะQ7ฅT.S6ŒŸ๎ลvk๊D™IตไณนlPEึุ”หศrฯ6่ฅดนแcหjโ๏ฃ”dภฯ˜>ํศK๚>”ฉ–X>CจˆŽ`๏ุ+L"๗j/ื บ>K฿wrิตV%กฃp/ํหŸ}vอ๖5๑ฺลฦน˜งืDx+iB%Œ ฆkgolang-step-crypto-0.24.0/pemutil/testdata/cosign.enc.pem000066400000000000000000000012111437037643100233570ustar00rootroot00000000000000-----BEGIN ENCRYPTED COSIGN PRIVATE KEY----- eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6 OCwicCI6MX0sInNhbHQiOiJuaExoelFuMGFxUUVidEtCaGM0SENYRHRoNVVKaFdq Z2pTaDhnKzRXZURJPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 Iiwibm9uY2UiOiI1Uk95VEkwTHRETkdvbjJkVytLWnhrOHE0L2hQZEc0diJ9LCJj aXBoZXJ0ZXh0IjoiQi9UUjVvSE1qNEdoNmJmYlNOM0xpZnpMRjBma0dzbDVSZksx ZUs3bFlPSUZMWjdnekRZS1htbG16aGtmREM4L0ZJZWQ3eGlOUnQ1TW5YcnZFVjlU TTBkOWoxQnhyRytEM1RyNUZsNFJ0dmdaN3F1U0FJaytwWDVscVZ2ZHNMRVNiNkdQ QWxFQXYyeVl6R1JrL2tpbjQvcUtUOGlDcVYxS2RQZ2lCUjJ1QWM2VHc2OEZLMzda Q0pndHUrM0xCU2pXRENicGsweWNlcGIycFE9PSJ9 -----END ENCRYPTED COSIGN PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/cosign.pem000066400000000000000000000003611437037643100226200ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgTJZPa2U7YR7hhEui 91pZ8qtW9dtqllRv8NdRmQ8LpTihRANCAATbqGmZwdrYJhrPuWBNYosE2VK/YTI/ FKIsYbzL1eTZY5erZaXWdo++tbnlnV+T5d4HWF6mOjImcboGPwzWXpcq -----END PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/cosign.pub.pem000066400000000000000000000002621437037643100234050ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE26hpmcHa2CYaz7lgTWKLBNlSv2Ey PxSiLGG8y9Xk2WOXq2Wl1naPvrW55Z1fk+XeB1hepjoyJnG6Bj8M1l6XKg== -----END PUBLIC KEY----- golang-step-crypto-0.24.0/pemutil/testdata/generate.sh000077500000000000000000000104501437037643100227640ustar00rootroot00000000000000#!/bin/sh OPENSSL="/usr/local/Cellar/openssl@1.1/1.1.1-pre8/bin/openssl" SSH_KEYGEN="/usr/bin/ssh-keygen" ####################################### # PKCS#8 # ####################################### # EC $OPENSSL genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -pkeyopt ec_param_enc:named_curve -out pkcs8/openssl.p256.pem $OPENSSL genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -pkeyopt ec_param_enc:named_curve -out pkcs8/openssl.p384.pem $OPENSSL genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-521 -pkeyopt ec_param_enc:named_curve -out pkcs8/openssl.p521.pem # Ed25519 $OPENSSL genpkey -outform PEM -algorithm ED25519 -out pkcs8/openssl.ed25519.pem # RSA $OPENSSL genpkey -outform PEM -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out pkcs8/openssl.rsa2048.pem $OPENSSL genpkey -outform PEM -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out pkcs8/openssl.rsa4096.pem # Public NAMES="p256 p384 p521 ed25519 rsa2048 rsa4096" for name in $NAMES do $OPENSSL pkey -outform PEM -in "pkcs8/openssl.$name.pem" -pubout -out "pkcs8/openssl.$name.pub.pem" done # Encrypted $OPENSSL pkey -outform PEM -in pkcs8/openssl.p256.pem -aes-128-cbc -passout pass:mypassword -out pkcs8/openssl.p256.enc.pem $OPENSSL pkey -outform PEM -in pkcs8/openssl.ed25519.pem -aes-192-cbc -passout pass:mypassword -out pkcs8/openssl.ed25519.enc.pem $OPENSSL pkey -outform PEM -in pkcs8/openssl.rsa2048.pem -aes-256-cbc -passout pass:mypassword -out pkcs8/openssl.rsa2048.enc.pem $OPENSSL pkey -outform PEM -in pkcs8/openssl.p384.pem -des -passout pass:mypassword -out pkcs8/openssl.p384.enc.pem $OPENSSL pkey -outform PEM -in pkcs8/openssl.p521.pem -des3 -passout pass:mypassword -out pkcs8/openssl.p521.enc.pem # Ed25519 DER $OPENSSL pkey -outform DER -in pkcs8/openssl.ed25519.pem -out pkcs8/openssl.ed25519.der $OPENSSL pkey -outform DER -in pkcs8/openssl.ed25519.pem -pubout -out pkcs8/openssl.ed25519.pub.der ####################################### # PKCS#1 # ####################################### $OPENSSL genrsa -out openssl.rsa1024.pem 1024 $OPENSSL genrsa -out openssl.rsa2048.pem 2048 $OPENSSL rsa -outform PEM -in openssl.rsa1024.pem -pubout -out openssl.rsa1024.pub.pem $OPENSSL rsa -outform PEM -in openssl.rsa2048.pem -pubout -out openssl.rsa2048.pub.pem # Encrypted $OPENSSL rsa -outform PEM -in openssl.rsa1024.pem -aes-128-cbc -passout pass:mypassword -out openssl.rsa1024.enc.pem $OPENSSL rsa -outform PEM -in openssl.rsa2048.pem -aes-192-cbc -passout pass:mypassword -out openssl.rsa2048.enc.pem ####################################### # RFC 5915 # ####################################### # P-266, P-384, P-521: $OPENSSL ecparam -genkey -outform PEM -name prime256v1 -noout -out openssl.p256.pem $OPENSSL ecparam -genkey -outform PEM -name secp384r1 -noout -out openssl.p384.pem $OPENSSL ecparam -genkey -outform PEM -name secp521r1 -noout -out openssl.p521.pem $OPENSSL ec -outform PEM -in openssl.p256.pem -pubout -out openssl.p256.pub.pem $OPENSSL ec -outform PEM -in openssl.p384.pem -pubout -out openssl.p384.pub.pem $OPENSSL ec -outform PEM -in openssl.p521.pem -pubout -out openssl.p521.pub.pem $OPENSSL ec -outform PEM -in openssl.p256.pem -aes-256-cbc -passout pass:mypassword -out openssl.p256.enc.pem $OPENSSL ec -outform PEM -in openssl.p384.pem -des -passout pass:mypassword -out openssl.p384.enc.pem $OPENSSL ec -outform PEM -in openssl.p521.pem -des3 -passout pass:mypassword -out openssl.p521.enc.pem ####################################### # OPENSSH # ####################################### # EC for size in 256 384 521 do $SSH_KEYGEN -t ecdsa -b $size -f openssh.p$size.pem -N "" mv openssh.p$size.pem.pub openssh.p$size.pub.pem cp openssh.p$size.pem openssh.p$size.enc.pem $SSH_KEYGEN -p -N mypassword -f openssh.p$size.enc.pem done # Ed25519 $SSH_KEYGEN -t ed25519 -f openssh.ed25519.pem -N "" mv openssh.ed25519.pem.pub openssh.ed25519.pub.pem cp openssh.ed25519.pem openssh.ed25519.enc.pem $SSH_KEYGEN -p -N mypassword -f openssh.ed25519.enc.pem # RSA for size in 1024 2048 do $SSH_KEYGEN -t rsa -b $size -f openssh.rsa$size.pem -N "" mv openssh.rsa$size.pem.pub openssh.rsa$size.pub.pem cp openssh.rsa$size.pem openssh.rsa$size.enc.pem $SSH_KEYGEN -p -N mypassword -f openssh.rsa$size.enc.pem done golang-step-crypto-0.24.0/pemutil/testdata/keytool.csr000066400000000000000000000005661437037643100230410ustar00rootroot00000000000000-----BEGIN NEW CERTIFICATE REQUEST----- MIHYMIGAAgEAMB4xHDAaBgNVBAMTE2hlbGxvLnNtYWxsc3RlcC5jb20wWTATBgcq hkjOPQIBBggqhkjOPQMBBwNCAASKj0MvK6CUzFqRLfDTQNQp0Zt5dcHYx+Dq1Wh9 5dhqf1Fu9+1m5+LKkgBWoZmo7sJH0RuSjIdv/sZwpBrkdn2soAAwCgYIKoZIzj0E AwIDRwAwRAIgZgz9gdx9inOp6bSX4EkYiUCyLV9xGvabovu5C9UkRr8CIBGBbkp0 l4tesAKoXelsLygJjPuUGRLK+OtdjPBIN1Zo -----END NEW CERTIFICATE REQUEST-----golang-step-crypto-0.24.0/pemutil/testdata/nebula.key000066400000000000000000000001771437037643100226200ustar00rootroot00000000000000-----BEGIN NEBULA X25519 PRIVATE KEY----- sc9k10IOJEFg9QDXEAFqkDgVQ3KfuubYHfG+Xl2ODbs= -----END NEBULA X25519 PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/nebula.pub000066400000000000000000000001751437037643100226140ustar00rootroot00000000000000-----BEGIN NEBULA X25519 PUBLIC KEY----- fH8U8+JEY6azHXHOwRoburcf25WG/ueKxvQ7sQrUVA8= -----END NEBULA X25519 PUBLIC KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssh.ed25519.enc.pem000066400000000000000000000007201437037643100245550ustar00rootroot00000000000000-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABC9Pd+JT/ 54ZI1CUG/rb3D1AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIFF/TpiUOL8JWcsQ MxOXFYf44WiNHVjV3evDsa67UQpVAAAAoL35/C3KKjC+Z6fVLWlvDqYnhyNdic4H7O2XGj yg9h9BBQGKAK5MdNkE9xtSffw9v6wIJrLQTyoqvE2dyaTcqeW+vn6iDypF4f9gzrwdZz6j YD8Y392OcNwZF4JcoT3nvrkQ2sCRHDbQbYID+N80ml5VbijvRd2b6YiFJMZO2nwUtPqVCt RYRBsy1/0m6740+SRTztB4DqkNdZO41zH25WM= -----END OPENSSH PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssh.ed25519.pem000066400000000000000000000006331437037643100240140ustar00rootroot00000000000000-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACBRf06YlDi/CVnLEDMTlxWH+OFojR1Y1d3rw7Guu1EKVQAAAJgG8EtjBvBL YwAAAAtzc2gtZWQyNTUxOQAAACBRf06YlDi/CVnLEDMTlxWH+OFojR1Y1d3rw7Guu1EKVQ AAAECHHCRxaxuDUzPhwiXZovjvMLYkLiSx+ho3b+9gavl031F/TpiUOL8JWcsQMxOXFYf4 4WiNHVjV3evDsa67UQpVAAAAE21hcmlhbm9AZW5kb3IubG9jYWwBAg== -----END OPENSSH PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssh.ed25519.pub.pem000066400000000000000000000001451437037643100245770ustar00rootroot00000000000000ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFF/TpiUOL8JWcsQMxOXFYf44WiNHVjV3evDsa67UQpV mariano@endor.local golang-step-crypto-0.24.0/pemutil/testdata/openssh.p256.enc.pem000066400000000000000000000010551437037643100242550ustar00rootroot00000000000000-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABDVMVSBp4 T/VwzGf55k3unNAAAAEAAAAAEAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlz dHAyNTYAAABBBI3kt38MXLwskrTKtaD5HKGrj+J8evJDIJB5PhRdIH5QZ1Fy3uRYDSEBFt NpIKH7xT8azk74iQM2hMpWyDEpQxAAAACwjNTJDRz89Ci7Is9ngcxzZGQbJV+hFH4KA/7f YqzB+/HTCQh9F+JOcmQmiOUNJCAexzrl9o1Z3342WYllL4U7yxc7jz5a0MdCd/Bzx8TbJS JjO54BX61cpejSaBJiVDmo4kKyYY34a4NOoc4eoa74r8vN/hqOvG4J8nzBTWPbZIjEfGRA GKpoNh2nFkqFYkiciXTNldokFQ/mFV+lbGtX8VoJCRbcLgk+wtzLfIYpZ2Y= -----END OPENSSH PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssh.p256.pem000066400000000000000000000010011437037643100235000ustar00rootroot00000000000000-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS 1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQSN5Ld/DFy8LJK0yrWg+Ryhq4/ifHry QyCQeT4UXSB+UGdRct7kWA0hARbTaSCh+8U/Gs5O+IkDNoTKVsgxKUMQAAAAsO3C7nPtwu 5zAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBI3kt38MXLwskrTK taD5HKGrj+J8evJDIJB5PhRdIH5QZ1Fy3uRYDSEBFtNpIKH7xT8azk74iQM2hMpWyDEpQx AAAAAhAIHB48R+goZaiXndfYTrwk4BT1+MeLPC2/dwe0J5d1QDAAAAE21hcmlhbm9AZW5k b3IubG9jYWwBAgME -----END OPENSSH PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssh.p256.pub.pem000066400000000000000000000002651437037643100243000ustar00rootroot00000000000000ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBI3kt38MXLwskrTKtaD5HKGrj+J8evJDIJB5PhRdIH5QZ1Fy3uRYDSEBFtNpIKH7xT8azk74iQM2hMpWyDEpQxA= mariano@endor.local golang-step-crypto-0.24.0/pemutil/testdata/openssh.p384.enc.pem000066400000000000000000000012331437037643100242550ustar00rootroot00000000000000-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABA2L7LStB AFQM4HnRPCrz07AAAAEAAAAAEAAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlz dHAzODQAAABhBNlvZXMQ+zY03+LWod10pNV/CgirdN895D+RxYG+4JUl8jKC+mb0x66xrA Q+4tIRfCI0N7D5gdn3Eci8WMKZmwu4Q7O6H230qi3Aznsf7s35EMXOEzaPyEBo65PlcMsp 9QAAAOC0YUZ6dD5j/kVnc+5ahpTzv6gxwDe48RvJeA4YRgxCWKdlHT6pSRzikQVoi1K3Wo hfBWrnJ1MP1VBqYEvxA0k8POYSCJR43UW0oBP+qV3ihcxUfMpZoLVsDgmTeEnKDyoHM7Bl WO84yzp2z/gMcz3L6EDripss1X6rmrzhX42buDU7iDHj9QO6W0FplIljlf0MQIaBhpgLCW 2Cw4LLwbIzAzcDAkX0iMJw79MicZ8XXjVL0lyaq2N/YivHJqV8QAJwHR+oXuHLlt/sWg9g 2+Kjc6w9rNeW3AH2E7olol/K1w== -----END OPENSSH PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssh.p384.pem000066400000000000000000000011561437037643100235150ustar00rootroot00000000000000-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS 1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQTZb2VzEPs2NN/i1qHddKTVfwoIq3Tf PeQ/kcWBvuCVJfIygvpm9MeusawEPuLSEXwiNDew+YHZ9xHIvFjCmZsLuEOzuh9t9KotwM 57H+7N+RDFzhM2j8hAaOuT5XDLKfUAAADgn/Sny5/0p8sAAAATZWNkc2Etc2hhMi1uaXN0 cDM4NAAAAAhuaXN0cDM4NAAAAGEE2W9lcxD7NjTf4tah3XSk1X8KCKt03z3kP5HFgb7glS XyMoL6ZvTHrrGsBD7i0hF8IjQ3sPmB2fcRyLxYwpmbC7hDs7ofbfSqLcDOex/uzfkQxc4T No/IQGjrk+Vwyyn1AAAAMQDg0hwGKB/9Eq+e2FeTspi8QHW5xTD6prqsHDFx4cKk0ccgFV 61dhFhD/8SEbYlHzEAAAATbWFyaWFub0BlbmRvci5sb2NhbAECAwQ= -----END OPENSSH PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssh.p384.pub.pem000066400000000000000000000003411437037643100242750ustar00rootroot00000000000000ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBNlvZXMQ+zY03+LWod10pNV/CgirdN895D+RxYG+4JUl8jKC+mb0x66xrAQ+4tIRfCI0N7D5gdn3Eci8WMKZmwu4Q7O6H230qi3Aznsf7s35EMXOEzaPyEBo65PlcMsp9Q== mariano@endor.local golang-step-crypto-0.24.0/pemutil/testdata/openssh.p521.enc.pem000066400000000000000000000014411437037643100242470ustar00rootroot00000000000000-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAVXURLKc jVTtli0RBAkjB6AAAAEAAAAAEAAACsAAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlz dHA1MjEAAACFBAErMjdBKnVrZ7XMy6VmXW4UXVjYOe8tIjAS7zfuE5XV6lgSA294CEY2bn tO4Gpi2dl5V93anNrPsIpfo3gHfc3VcAFDk2COLuUXZ1RuaE2omgyHjeKgixA0FoF6eGZG lg/p/mTw//slkwiwuGQmTaHKRvyZpJn4Hc9jA8w0F1K8/7SwdAAAASC8XZTQI1uwNSUIMh bakjv0AIJeearpkSE7EV6AJM1GkliQd+1Y36tLF/e4H1HZzz3G2r0RfZPYGKvGUYjOQDv/ dS/rGEFbicC1hCrpjyP44wkH1pjzNFMhjei6e/oJ2/NvW6r5Hg/FEE8cqWmRajDIMCeuOB BRS77P2ArGcscYyX4zbD1Ug7at9ouYhrPpAa/P8RWMFGBRdlrVb0QJ6IIBwFig++EJ0Nb9 P6vvh//tB0TbD+1hFXB7eMeuK3dYaZig429d53Yqj8BzCkH8dXOps6nNcjF5yhNdfVwDTZ QzV/B3jHGd+70qsj5VDPI4JI0dX8FrerQ2MlYzJ00uHMx9Tm7a/yaxemG2CvZnELYxGDNK UpKQcNjgpbvQz7QVo4U= -----END OPENSSH PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssh.p521.pem000066400000000000000000000013541437037643100235060ustar00rootroot00000000000000-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS 1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQBKzI3QSp1a2e1zMulZl1uFF1Y2Dnv LSIwEu837hOV1epYEgNveAhGNm57TuBqYtnZeVfd2pzaz7CKX6N4B33N1XABQ5Ngji7lF2 dUbmhNqJoMh43ioIsQNBaBenhmRpYP6f5k8P/7JZMIsLhkJk2hykb8maSZ+B3PYwPMNBdS vP+0sHQAAAEYIsr2CCLK9ggAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ AAAIUEASsyN0EqdWtntczLpWZdbhRdWNg57y0iMBLvN+4TldXqWBIDb3gIRjZue07gamLZ 2XlX3dqc2s+wil+jeAd9zdVwAUOTYI4u5RdnVG5oTaiaDIeN4qCLEDQWgXp4ZkaWD+n+ZP D/+yWTCLC4ZCZNocpG/Jmkmfgdz2MDzDQXUrz/tLB0AAAAQgEdeH+im6iRcP/juTAoeSHo ExLtWhgL4JYqRwcOnzCKuLOPjEY/HSOuc+HRrbN9rbjsq+PcPHYe1NnkzXk0IW8hxQAAAB NtYXJpYW5vQGVuZG9yLmxvY2FsAQIDBAUGBw== -----END OPENSSH PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssh.p521.pub.pem000066400000000000000000000004211437037643100242650ustar00rootroot00000000000000ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAErMjdBKnVrZ7XMy6VmXW4UXVjYOe8tIjAS7zfuE5XV6lgSA294CEY2bntO4Gpi2dl5V93anNrPsIpfo3gHfc3VcAFDk2COLuUXZ1RuaE2omgyHjeKgixA0FoF6eGZGlg/p/mTw//slkwiwuGQmTaHKRvyZpJn4Hc9jA8w0F1K8/7SwdA== mariano@endor.local golang-step-crypto-0.24.0/pemutil/testdata/openssh.rsa1024.enc.pem000066400000000000000000000021111437037643100246470ustar00rootroot00000000000000-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABATOWWPK4 fxPP+DRLPifG2jAAAAEAAAAAEAAACXAAAAB3NzaC1yc2EAAAADAQABAAAAgQDfgz0N32eM QMILbjdo/CdndvIC3r7L12Ch0IO77B4g2aW7Mai1jNx/Hrz7ssvSujypbw6d8TBmNszbp+ J4coVHgdZZuz8sKpAKmiNgZVqtp2+SzTsPpzGvfxsIcPOLEk+2MHdUdhLWacB35ifqSW9t Y2E1nA/XVub/pPFp5dccxQAAAhCrwlwmmc1gRIrKsY+5wQ7PfC67X3tyO8377XtNvtRo3A JE0G/5qG/T3OQ+WX0tAPie7VcUsshlGsdW4KKW356krI8uhlgcpDCs2OM1O0JVK9K4erHW nxTEXmtVyp2b77oGaoKDnfDEfvzreuacHI99s48YLpPR9TgmFgK3/5bBlHZCyrlYHNMR+0 XI5/SFEsulvGSsi5xrOMHA3XVzLZjq8OJbiIqnhHaHCSDUlc32AcgY07/SgyKJ0yraBV0C oiWl2KqevwXRbfIbdEKeYE9SbTC8jFGM/TSvfY9I9mehzar/mxuaMCLctRDYBFlhaIwHIZ 06iv5lSrKiUe/LSdQ/cLRHiSArYHadpvzzu5cQKk72X4GMfAA4bp114WXdYiL3MuRUwlx2 VqKQm2aQQajbTMO/Nh5nP4yvqM00eElFGOBqW3D+6ifoiQn50p/lk4dHH20GJUduOpBmSn JJQalndr3Eynuc5DaDBfVPxzSJcR/eI0goQD3Zu96Z9Z9bbn8YS9bklw1pw2aloUL46QvU /33YGK7TSAPCEBuZt9boIs4HmNclNvWzLgPk28zglLpZm5OC9pZJwzIQUk+XukaCfUnSOf RtpvIoK2qqHjILY111KNgRNaz3imWF/BuMhfa5nEZJvHkF3BMT7TqED/N4OfFWiq7Rj3oX ENtqtPEQOgyV4gFcXsPPQYVcT3tQxpo= -----END OPENSSH PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssh.rsa1024.pem000066400000000000000000000020241437037643100241060ustar00rootroot00000000000000-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn NhAAAAAwEAAQAAAIEA34M9Dd9njEDCC243aPwnZ3byAt6+y9dgodCDu+weINmluzGotYzc fx68+7LL0ro8qW8OnfEwZjbM26fieHKFR4HWWbs/LCqQCpojYGVaradvks07D6cxr38bCH DzixJPtjB3VHYS1mnAd+Yn6klvbWNhNZwP11bm/6TxaeXXHMUAAAIIbIMl/WyDJf0AAAAH c3NoLXJzYQAAAIEA34M9Dd9njEDCC243aPwnZ3byAt6+y9dgodCDu+weINmluzGotYzcfx 68+7LL0ro8qW8OnfEwZjbM26fieHKFR4HWWbs/LCqQCpojYGVaradvks07D6cxr38bCHDz ixJPtjB3VHYS1mnAd+Yn6klvbWNhNZwP11bm/6TxaeXXHMUAAAADAQABAAAAgFd0d3A1KM QFFqf4US//8b8XGGytEUSbGlFWUCU4pzU9VA6hyJx46FHJCjMF66ChhFjbfoGoPMLR0Ghm EUQFs0XjPrPzmgHa5O16oYqgXgds4n/aDGVumAOMW1ggTjQHBfXbTL2nwoWl+v5BKnRFkN oEDQbDrw7VhPCkHbwnj3zBAAAAQCRxoNw8KVkjIDo+epgU8Bz3RqlkiozeEGfnrbwN4+5e 7yGxUlI6Q7YsfJb775lsiMBGwBQFLfj9e+wQMZZzeOYAAABBAPKB9+6c/sPWsbOlyuE3dI XvZdlcz4gK0YrtFXHpBnU0XDYNbaaWWSWbnC3NFYqdr9ps4GCQ97rMerETiJzscLUAAABB AOvyuV6E1QK+frD4YpzRz95YICDwooGsAICI6V2tx8xv/ZIfdQRBgRrOl0DoieQTUwt5io F9ZAqj4xLuR563VdEAAAATbWFyaWFub0BlbmRvci5sb2NhbA== -----END OPENSSH PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssh.rsa1024.pub.pem000066400000000000000000000003511437037643100246740ustar00rootroot00000000000000ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDfgz0N32eMQMILbjdo/CdndvIC3r7L12Ch0IO77B4g2aW7Mai1jNx/Hrz7ssvSujypbw6d8TBmNszbp+J4coVHgdZZuz8sKpAKmiNgZVqtp2+SzTsPpzGvfxsIcPOLEk+2MHdUdhLWacB35ifqSW9tY2E1nA/XVub/pPFp5dccxQ== mariano@endor.local golang-step-crypto-0.24.0/pemutil/testdata/openssh.rsa2048.enc.pem000066400000000000000000000035241437037643100246670ustar00rootroot00000000000000-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABDmbvmkzG Ta/0HnswjwUhe6AAAAEAAAAAEAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQCum4bMH4Bm /vkPlk/qbBMrFlYUzVlUQnsBYunNREZb7itdBRau9nujSYhTbdtwMgH0gEnXp9gj/Hcvz3 YdGYM/cOXZ334CbmTQ0cqiV90d2Qf03zDZS7ezv7Kamq0vdkA0Npw5lTOmZKnQcNibBAqc p2BeKSDIYGMTZMsEHOHsPX9jdwpEU4QevKAszjTF/UaPNLVpfMsEPuV9D8l02GPVg8HZXM NZt483nnN3MMnEqIuqRurEHz7xS9U5Rtqf45cQeFEcDXJa2ggVmLVfpHim4qHReA3qSz5Q igvAR0I3XrBKv5MhLH0NPDawzeAgF0HR18CAkp+/KAP3CA6FXWWtAAAD0J99ooy+BJRWof GqhVwTk/5c0fZvg/sWITECrfkobVfDvZy29xVImR/sx9hJ9cTQgISA3zbtMVFX909lTQsZ sHTKT8zknwF4KQaiKeItoF4xJSp6YhTsbPt8HocrWNdnP1CI5lF5NqQllKncTTfNmRYLTD +MGn8dfXW8wd/SLT/XRLNvZ+C9QfxRXjZyoV7EBtxRFFMelTsx3cW0tJAI5gYPmB4m7M2t zXEy9gI1+eOWWc6cJ/i/7nbb7BmTNQ/iwfAPrnmtu/rPbvjSsXiJZ3ZoC718hb2hM0UmDv Z3lkE0nL2Z6BR0Et0s7dMUbbQS1QXpUyZ2yTuRUxke/wUbcVQ4whJ8f26VdKN4L9+jTU59 cmmMfCaFUHS/sswt+5enmRjilLTfSbtzhqRBsKmWFrvw3c0/CAgVzuDpxj5fwtFDm4V3i6 hyNZR6IK1LLUM7g7B5utE3E9pbi3JZiFroAGmM+68cmiASzWUpvdvW37evpGXZIJ6KOrRj X28bhR8p7C6uXOTU4AOkSGFHxShtMtkDuV65jSu4CQo+e/TII0VnzsAACUj5EEeY0Jh36Z 0tWbmgJKVqCeQLQs6ydJFofho7cmGCSIXpPg1Rg39WJeI/UgRkNzeEdqBbUro3BifpYeFG WlA7V8DmFtJIHyjUC/FiujPFU4Y/XIJb+2Sfb8HV08fN0jwC5FUStZ73nXVkkheLtjM1yA 3DXYWGec7Dt9sVCZ2nEls3Z+mG87QKCNubPzbDok+MWk/Ts/xGA/O7TDUWQ9lZR5NEQ/QY NneNafeu5seA8hixcPWpam28SnBD5e3o8iph/YC7l2qRwlFjiPmzLmkYuyWcX6EXUrG9mE sf4H7tUjIO6OPrmkVlANxGVsgQdq0TyeCEnygYkaZpMq8hWy/OCys+ukZOXOD6DsUC0EfI bFQ3CbHr53aoTw3HqwtQ5usAa68wEfP8TJUVba4+L6EDxRIZokX+qEur/fVBBd/nQQiBVD mMq4WghC0x7VubOAApiBGjHSBlAhf0GDuZx1Lcr9BWsUPuI03jawMdlXZjZ85/Q2FvawuB Ycp5Nq1/mnmywPjxqlmJ3GbMXlM1x8P8Tg1AK8geQaqRYrOh6B168ZWoYpaGms0BABrTwh vzjzQMy6aUhQx0jMTHvswI4xmFOGi5vFxl4xc33bHp/hNchcc2qJ6fFRm98uIXgh4NpiwF 3YjB1I8pyGUusPvOodiLt3PX4H1qpow4ReytIYF3OOCS2dQ8sxQpmcNA58JVoZZrdnFXOb kSITa1p1NwHbm2PAzaZE0XBkHA+fI= -----END OPENSSH PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssh.rsa2048.pem000066400000000000000000000034471437037643100241270ustar00rootroot00000000000000-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn NhAAAAAwEAAQAAAQEArpuGzB+AZv75D5ZP6mwTKxZWFM1ZVEJ7AWLpzURGW+4rXQUWrvZ7 o0mIU23bcDIB9IBJ16fYI/x3L892HRmDP3Dl2d9+Am5k0NHKolfdHdkH9N8w2Uu3s7+ymp qtL3ZANDacOZUzpmSp0HDYmwQKnKdgXikgyGBjE2TLBBzh7D1/Y3cKRFOEHrygLM40xf1G jzS1aXzLBD7lfQ/JdNhj1YPB2VzDWbePN55zdzDJxKiLqkbqxB8+8UvVOUban+OXEHhRHA 1yWtoIFZi1X6R4puKh0XgN6ks+UIoLwEdCN16wSr+TISx9DTw2sM3gIBdB0dfAgJKfvygD 9wgOhV1lrQAAA9C8eb3xvHm98QAAAAdzc2gtcnNhAAABAQCum4bMH4Bm/vkPlk/qbBMrFl YUzVlUQnsBYunNREZb7itdBRau9nujSYhTbdtwMgH0gEnXp9gj/Hcvz3YdGYM/cOXZ334C bmTQ0cqiV90d2Qf03zDZS7ezv7Kamq0vdkA0Npw5lTOmZKnQcNibBAqcp2BeKSDIYGMTZM sEHOHsPX9jdwpEU4QevKAszjTF/UaPNLVpfMsEPuV9D8l02GPVg8HZXMNZt483nnN3MMnE qIuqRurEHz7xS9U5Rtqf45cQeFEcDXJa2ggVmLVfpHim4qHReA3qSz5QigvAR0I3XrBKv5 MhLH0NPDawzeAgF0HR18CAkp+/KAP3CA6FXWWtAAAAAwEAAQAAAQBOph+Bsm7T9eWZ78rv fN7leZospJKoMYnWhgdqPmay4gUGUVR2WvA2DNkrO0CsuNnImECqsx/Ylc/Z6Uj6spM78E 6YZOMNlUw0A0uS9KDU4P2Ef0QxnLmSbba1jRaVWl5xJmgYR+yL9qvHCA2JRbjB92KhB0WM /F2kTrJjl97r2ci5OD1EeGDRgB9KPyYGdUW2KltZ8KZWKtyKKTTdfKDWQGN50gDbMh9GPq g78M9A74pcZ14p4T8PpyiWrTJh7dmtufI+Tm6HEd+LLNm+s67rf8v+1q00PEoy520sxjRK t5n/beiwJqJO3z84bOu1EABa4b+4VwH9twIRh2dWXcrBAAAAgQCnTpbxFSd54X1tb7Hj2v aJDF0ecJfOT+gr6/CoCmXRVSllc99Fp6KR4VTECSuo0VzXUDy1YY16dG3/WrhhV5mrIYVf SP4GkNLLuOLNgkmHlnQwFgXGBPPJLHP9B+nqRFTVGGx1b4y6MITKbBOSgcJNeD+STuuIur 1R7tR4mID8KQAAAIEA4aEAUZ4ABQquCvIVfwM2JiLf0vdrHJX/m0VziZAWE2HnD+WXX3kV RVstlMUs7JvhJP4/+vlEODAtdl6eY2PrXVsJh/Zq9PRyb73k4hoqzQ5KqrBcen1ooeefx4 HpwFk5fcCNB9VQTMfK9V67CYFbCcEEYqDeO9/VmpkcVow1vIsAAACBAMYcXhsN7Qzv+RG9 cNVZFqpiM7WXSibPObdfYftKl1C0QrLVwLf8f17hCaaMDpbvWHBrfeU8f+03SFdhVmJKQl uaWCtQFmBaVGPH2LDmLItGC+XvjLG3us1lkihRWF06lzY5puNIZav91QkPFtOM343cFMQv tP6KmrQ4PIj0iBWnAAAAE21hcmlhbm9AZW5kb3IubG9jYWwBAgMEBQYH -----END OPENSSH PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssh.rsa2048.pub.pem000066400000000000000000000006211437037643100247030ustar00rootroot00000000000000ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCum4bMH4Bm/vkPlk/qbBMrFlYUzVlUQnsBYunNREZb7itdBRau9nujSYhTbdtwMgH0gEnXp9gj/Hcvz3YdGYM/cOXZ334CbmTQ0cqiV90d2Qf03zDZS7ezv7Kamq0vdkA0Npw5lTOmZKnQcNibBAqcp2BeKSDIYGMTZMsEHOHsPX9jdwpEU4QevKAszjTF/UaPNLVpfMsEPuV9D8l02GPVg8HZXMNZt483nnN3MMnEqIuqRurEHz7xS9U5Rtqf45cQeFEcDXJa2ggVmLVfpHim4qHReA3qSz5QigvAR0I3XrBKv5MhLH0NPDawzeAgF0HR18CAkp+/KAP3CA6FXWWt mariano@endor.local golang-step-crypto-0.24.0/pemutil/testdata/openssl.p256.enc.pem000066400000000000000000000004721437037643100242630ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-256-CBC,012738AA5F159CFF1B32511C1D29BCE8 ocEZDAVsZ4edbcDBHUFOgyr++IDmwEz07ECteySu8je7bwTnAqLDx++/EgydGhPx K8ndFV8t5yEUzA564+KhnkEGm2xIK/PKvy+JmkbbzwQWkt23rcVPE3ARBDhjGGaA 49pFIdMhRk/KeszzCsWWizAMaM27B7uGMheRWein3tc= -----END EC PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssl.p256.pem000066400000000000000000000003431437037643100235140ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MHcCAQEEIOWt6OMAASXS1jJ/Sn2vY8ciPDU74cqjq3J13oYUNeNuoAoGCCqGSM49 AwEHoUQDQgAEp2gPSjWPnX25PYBYpMjl+D2VAI2Smm1pxaMQw5x974BnEg25Axaf 3yN//SwJ3X1Ju9gwfhhHmpqYKzQ/reyNOw== -----END EC PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssl.p256.pub.pem000066400000000000000000000002621437037643100243010ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEp2gPSjWPnX25PYBYpMjl+D2VAI2S mm1pxaMQw5x974BnEg25Axaf3yN//SwJ3X1Ju9gwfhhHmpqYKzQ/reyNOw== -----END PUBLIC KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssl.p384.enc.pem000066400000000000000000000005331437037643100242630ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-CBC,E2C4C65A352DD9CC KohSa6rhfwyxLJYP615pxiDMjN0l0J8s2tbazOWoQkIRQIuJlU4AhL4zBlmKhzZW V9hTSI2cJf7MRI0FX34sazGfBpVHxEDYzU69igWxani2n7Jqysgu1qdPmrfVKs8A TEM3ihDg/A1YFh5H7Coe3yeMfR7CGKZN5bY+jD2uyiUmiy+7/JsYBsQ+YJeR9RYP adOARAvJzMyVMUmBG6VNGewnftUfDGxj -----END EC PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssl.p384.pem000066400000000000000000000004401437037643100235140ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MIGkAgEBBDBWB93doUgi7ZK78Q0ORM7RB9oSqT3cVb6HZvt7fF+/1Vj03Q8UlHSL Mf+4znN/hmSgBwYFK4EEACKhZANiAASKwhzVNcBMvm/lqs10b2I0wuEcEIrqgaC0 V9Y3+uhTwusUZjVPMmHSWBM2Zn10FaCXG6ypHEsiqjMA6SdJBMGm3bz4lG8wPdPk +B+zVA7chnULzBOuSgIpeehquiCJm/g= -----END EC PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssl.p384.pub.pem000066400000000000000000000003271437037643100243050ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEisIc1TXATL5v5arNdG9iNMLhHBCK6oGg tFfWN/roU8LrFGY1TzJh0lgTNmZ9dBWglxusqRxLIqozAOknSQTBpt28+JRvMD3T 5Pgfs1QO3IZ1C8wTrkoCKXnoarogiZv4 -----END PUBLIC KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssl.p521.enc.pem000066400000000000000000000006551437037643100242610ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,4C84DAF9EE70BBE8 chCsynahRaHXIo23z8k20GrQjp5y7TKpFQQp8HPL+z9Cz9br7nNixTOE4n0kgbGi o4Cl4vvkduermhDtGEoxAYd1/QWR6kPqGOv8KdukwE/2AeD5DWLv14cMxVItjOMG 2R8BsLqdx+wT+5BPyDAiNRifNh+A0CTSIsjse2hhuUvuofh5VMJwXKOC4dZI+uUY 1gFOOX6IiGFu71UM2tdCjow27IJedJLH5p5qeq7nhIw4Ep/gDXE5mUF5zMi1syyi 9pEQzL0D2vNi+GgvLfYOgxjjcmeZ0pPNNViZmFol+ts= -----END EC PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssl.p521.pem000066400000000000000000000005551437037643100235140ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MIHcAgEBBEIAZN3UZLUtCo0LFftfdbzcmEz49jtKcnuPQ+wi9ndf+W16oihuEUay 589hKy9MPSzuR2dv4+9NMLBlMS/wqg9DsMegBwYFK4EEACOhgYkDgYYABAFm5qul C8kBmuQrQxl/730Og7a4XjhNJ0pAEtmXeqIyCUaXgSakh7ET/vezAhSxYeRFuF/x cnZMOgVVX6IYnbr+lAC66zy0CovZ+3QNTmyiwEQijHx1n8PXmuSwS60ZYTc9Dii/ Hs3Df60xU/iEdOv+XtJqzhbeuhD1KU1VIt2EcAu6Ig== -----END EC PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssl.p521.pub.pem000066400000000000000000000004141437037643100242730ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBZuarpQvJAZrkK0MZf+99DoO2uF44 TSdKQBLZl3qiMglGl4EmpIexE/73swIUsWHkRbhf8XJ2TDoFVV+iGJ26/pQAuus8 tAqL2ft0DU5sosBEIox8dZ/D15rksEutGWE3PQ4ovx7Nw3+tMVP4hHTr/l7Sas4W 3roQ9SlNVSLdhHALuiI= -----END PUBLIC KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssl.rsa1024.enc.pem000066400000000000000000000017061437037643100246640ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-128-CBC,031BB2B8FFEE426F60E8200DF919F640 Pg+LE+2pEeWBJpGgXQd46boBmhv5Sa0Bf3TIv0rhpQuYxmPk5zk9mGYnRSDeFZPU tGSxV9NA+V6Kr2UYsfcF4I8o1git0c915vUTe5I0uW++vt4NZIqsxliQarFjuJCt mlZq2606BDCkKC8NgwMG4fpF4pJR4JtG10+q89P9VpfKwE91imhR4B/AYH6V0C9H XriTLlpbSz81ibBgLdVL+LI/dyfyEl80B5Yle3ZXVgES7ljThWQYMS/US1sxgVcD Q9co/RlQP0SLXE1nqMm/L8zcZVUYlwo0mPmLEd+lHiNVEkGqDuTg2m0uORKj6PxC uVowrF5iz7VlggiilC080f9apeHHVlRQkIal+N5mtnLR4BrO/xLg7riOhS1h61nB 0BYN4HjY9jjCyGUz/+Em6a3emXIJqiT+i3sMkSstSAq5Cfct3WF+aAbeGFfiGP/H 1mZmcXN79u0Db8DzXrD03TBVYi7m9jERkYtHR9sL1qjg7CghQGfL6WhFGZqQnmBW emgWMOxgkCBBjxeCOo9nLIhKrSP1QspzqFNYyXccFvxp4XgapWmhA+gp+aevuKkC R9RBfLBabppMoUQR6rQWwMGUCvUWUfFMivrPnqPN2fiFOWvp0DSORvmVxPT8LR35 Ui2ubZgTI0gVHzC4FSWQETtr2TrpwTla8jFA57KfwrwX89qaLCTokbexZtQk8oBH V8H9kQ7k+eLOISYKvXXt/gCmU7bY76nRX9U4tJg0Z2rpEp5DE4RdnNsMUV1e2eT2 X/7JW48wNudEkbkSKMrbUsZo131dZxA0mmt3KG9H5zY= -----END RSA PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssl.rsa1024.pem000066400000000000000000000015671437037643100241250ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQCztZjbIWsnC88Npdq6YHwKi41OYPM8wD4Bcn7TU4NcEh5zLiQ9 rpxoAxKmxZtSFD7PbTkQkNa8rHN5vX9qWrrwrAIxX/vAiVo+ZvSNoPeG1Bn18xJ3 4oLPUUv3IkVCSBFWcZ1NBUhde+KabeaGKNb3bHM+4btBb4TLZwhh6EC6RwIDAQAB AoGAENNX1Gx0k9tPL3/v0rNl6bbXLBd3rqBxLcGCjlarXdt0bmRLkFrg2fwvqt2l hTHQD6uyRBLLiC69QRC09Ug5aFb22BQlhcSgy8K8gRAXb9Ekz+9riTiJk/gBoVSk YjsoAIzYuE4BXz3CR2R4Sywh2v/7JDitCZsXHaVhCNpWQMECQQDjZrHOtGeUfsFo 8c9WxGmBNoGned4zaknEK+UAAGCBgR2F9ChkEQT1f1A13e2Hl0XzZmXflOvaDGBI WvzwxribAkEAyk9wbFSeSlyj5U0D2wi61oif16JAu1QUAQr82ESYXarE5pnoms39 cUwLhdCJwmpzpi+3zlz6IEmpA10HtG0xxQJAGoCffG2+HKphNC/qcDxX531IwxIK +YcLrddHyyZAGRfJLxFzm6X4I/yAhqakxka1Glb2zIX4ruL+XbBtBkrCvQJAXS4T gMHEmklq74z2TqcJrxAEVwQTPnSuNgDCjjWh29pwkCmpOcvQhKNa10pCePogxBVM Wk72oXJr1vG9P7vfZQJAduCw0ydHAg7Mh70A3qLMW7+Hj8pMNo7vNKjJE/HDtHWb tQEQ/FjyyQ3jtxoAhDx2HwxKO+ksUO/4FOtuuG3pKw== -----END RSA PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssl.rsa1024.pub.pem000066400000000000000000000004201437037643100246750ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCztZjbIWsnC88Npdq6YHwKi41O YPM8wD4Bcn7TU4NcEh5zLiQ9rpxoAxKmxZtSFD7PbTkQkNa8rHN5vX9qWrrwrAIx X/vAiVo+ZvSNoPeG1Bn18xJ34oLPUUv3IkVCSBFWcZ1NBUhde+KabeaGKNb3bHM+ 4btBb4TLZwhh6EC6RwIDAQAB -----END PUBLIC KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssl.rsa2048.enc.pem000066400000000000000000000033461437037643100246750ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-192-CBC,A8BBBFD06EA695AC6FF58050ED852856 u4CKYZAVdUWyIJMlJ4HA0k8B6Ix1qQ/PB8aShhIZ0zjkfrfO9eBVoo5Is76Thid6 dWp86pEAbYZ6vxOBXF9Gnz0KQ8GJHjmHAj/zPKdV7hTcv/e+isTCiw9JsVHy9ejN EGUDTMjSvQMKyQsbLt7g0ENwBKu1JCKEN9n6TnSzyph0NOQ5I15CgF9A3UTD6ngP ZIBTjFsPI2MoDCP4LMurICkauuoSMADh4u6gTQ8oWc5OnM+8ujOtq8kg91WM8CuO MQ1lxFzNFRWQKtM9uIRXtKifaPP7RfZmTkfTIxDaxo2YFbb3+fH2NO84xLjU/DsC iSvPpMqfqhFM7/mXNEVkoeriVqsMshxmE0z0o3Yzb7fQIm+d2HKxQ9bGDR67Q+rz WoR2QedIUiI0WEfHnJL9b6m8DVU9SzejeM6DHRTlpCO76i+gVZRwG1CeEE7P00Gx 0nKID217RwWubWOlSZA8CGO9w7DeYYL+DxSvtc8oG5E3PHhD8rpNFxhU4UnUyrjz bPSI3mfVALPPppZVqOxhTxKtXo5A+IrUbz4Wrya/7HO/07gZ7E7msXaz48cLn1yq c5LvBmpAAib9Z/Wdh1QIYFyx1iBQ8Rq7S2YhkjcT1wjdweoGF/YmQtIaSBNwCFH5 oaOf7lGOQ+Thcke6k0lWfITeYml67Pz70oJXZH4K+ZLA+ZULe+N4lTt4kcYxRinN wOXaPpo1uP53hDWW4hc0cGBUusRuWpyAIvHNk2SprKtrjAyOB5MWQHGuDzzaihEv H8PwUt6gUhq9e+0jYTj/qU1dibK/5Dlwb4tozABkT65v3vKU2OG+qlQEm6/6kKtS Xmu91m9unB+SMKa2fabW3nGiM+nvR8gdbm203mhK6/3IgduOts7Npy4L5hzuf4Zy 15ELEt6ssv1Q47Y616e5WIwAoSz51cNkfRoTxk7PjGWwIhvJtbQMgZF1c1VWP0z8 vWOUQPMTZETXwFrSwC8Tc8+utzTuJ66k7BZUTEeidudCrL3tHbk6Cc4VNPnQN/rl YkzQfc20cJVYQWcqwfbmEuH+lh+a1XMyRC6VPI3NoXRNvHBALy37TkiFROZEgFlS bj/jZEaBchOsr29v9Zn4DqpGDaCTxtigAphhyjo16/1CeKE67/DyTeBVvOj85Lx6 wEKUOBNe+ldH37H9eiObYOT8vTKZJQRYcygtWjJ3Xo1qAb+zfBm1qB97wNil7hFB CmbXk8JNPfEzUdisbTrT3JNmAFSrjPAmG291+z/185xalGcY1P9WoixgV88eMf9H a6ynWVvHmH5rKiX9yBHhsMND11GjWczEX2EuzQZW6SrjZAV+FPVZdtUDK/iXHcLM mfHIpyurEoYMQ6sjfXJf3kK2g8g/sW5zZQ/SHBSzwoQYDu8vLYSVhu/Ip+4kaTEL 3VB3eLZsdRl/X0GLDAdn+pjOkVn5zLPKb6EXvd/JWWSxpz7A92aDc685qANnGvPh tMMY9qlcaD8gjddPJeLi6oR21SDItc27TdQBF29tA/B1x6+jvBBsaDmLBv6FZwXE rOJjROMg7yyZlb+v8s7J3tOgki+ZgNEyEEY4ksQUvHDu3/gLp+ofd4JBfji7HPrt -----END RSA PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssl.rsa2048.pem000066400000000000000000000032171437037643100241260ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpgIBAAKCAQEA4uI83MBkkLHDoHmLNrd7ooIbzm+ZeX6jSB2HiwpmA7PRjpgt xBD2fjt8QZ1qXUi9GkSlajiomK5Q64+Dg+6CJp97Lkm4EybwQvHJqbzA+pHFCrNL DVBea2O/kRBMzdSr7aI0p8gwruVStwADtNYK4nrd45X08SE3Hl5iB4tMBMrOvBug v5fTaII5ZMQ6AzqqWwk/pTP/dfjKfGPO+rN7g3PQ4/B+yXjDWfAld9dFk7Wl53Po rC7oqcU4MZMyGImXgi58V7lx+XhRqvcRUx1y2CBUJu6aHgvBERsf+vXSf5q1HiIM UFYMhn+duLoTRBZj3JPDE36J6SOIHDg8xoQDnwIDAQABAoIBAQCHt7OWjZPapiuS fAJVuc5AOLovc7yH28QKqHdjKdY6Ur+BH/EIfukkO6spiOOOZ6uO4g9dCgV4R5Xq Qw/1xJ+gQPgriTeOZVWFhiMO4PVDLh2DOBsmHLROYv295dU7rwMlhEkhMHRGurEO /Pg6nWsnbT38HMDH2QmipezX/HB90D73R721ms2wffVs1DU4etTEJc/zUaWzuD2b i2UY8hne/Jv0hIvS7mb2vM/vGJOG7k4mFdKvvGOgsLBR/bwIoMIuSKcDQbf1rTDm V3NLNwA/irWR8F9SZL7QwY/8z+3vgylTYq6PlPAXrK7Fts2N3Qdp/UYrUxjI4kUe H3drLm/xAoGBAPmD9oZauAQle5MyvBfi1XU5eVZr0kRpFLoQaAeHI60Aia42+qCt RQbtk+x+XR3CjA+FoVEr+644MZWTYaKawA2Mt931OVxuQG8CkzRutBmnFK3atsNq 3yfrOXm+vyQoM5eXmFtLTJwiG8a7EjlgX3Fl4aLMRlkmdwjJy6Y0Tyr5AoGBAOjH tHBrY1FQSM5qcPeoH5ah4bGKgF6sSOZm5YOF4J42zz0BE/t1vChEZb00D9+2+lC/ vSeplLhyvKueV+sRObo8VfQAbSOOC3u/Cm9zIbkdilOW8Ux3KXtkvOLxdsTpwvZh Zqu8LwWe7WnOBXILFmBaLkuOTfe6AGBrPcbbaPFXAoGBAPebqS0zIaGbwMIWeuoJ RGMMIglM/mC9FsB+P34Y8aJhAkBMdvK0f+ecJEtwKt+5jFxq8+cliqEdSrdwhldi 0muf1WcCT2YWUwLWv1Ys9bTvRWoxvWS3zbRDjcnvLKeo7WnmGl+enevjPUU9p5wg sxZJUFzJ8pXNwhqKhvnstxOhAoGBAOczEuBliKuGlgmOZs1TyqwN9OAltAJUE8Pj hynumn4J6iOpInOrKErGRFZ7kxib4Fq7VeBC6leYfhPmnWP4I+H5c1V55uxddMJf qLmxHFmEIZOMY/WSlTzdfU3ajiBeHSog65y+t+VZSGzCF16B7KOebkTU/lOCBkW9 vgn4em7ZAoGBAJoBN2+76jXFkd98UFesXWGi9CFUFAffgAeu71RzZsxsbuwEn0z6 /4uxDDxUFLcguK1PBCljeHUFAZUS/+k8eEjsfITWzmixAPQlvYGpYDuU9Z2P4agn TM16XFyRvNMNqtuY2LovA9FuPdYceoneQj2C1AybyiskRZjUfNCGdWPD -----END RSA PRIVATE KEY----- golang-step-crypto-0.24.0/pemutil/testdata/openssl.rsa2048.pub.pem000066400000000000000000000007031437037643100247100ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4uI83MBkkLHDoHmLNrd7 ooIbzm+ZeX6jSB2HiwpmA7PRjpgtxBD2fjt8QZ1qXUi9GkSlajiomK5Q64+Dg+6C Jp97Lkm4EybwQvHJqbzA+pHFCrNLDVBea2O/kRBMzdSr7aI0p8gwruVStwADtNYK 4nrd45X08SE3Hl5iB4tMBMrOvBugv5fTaII5ZMQ6AzqqWwk/pTP/dfjKfGPO+rN7 g3PQ4/B+yXjDWfAld9dFk7Wl53PorC7oqcU4MZMyGImXgi58V7lx+XhRqvcRUx1y 2CBUJu6aHgvBERsf+vXSf5q1HiIMUFYMhn+duLoTRBZj3JPDE36J6SOIHDg8xoQD nwIDAQAB -----END PUBLIC KEY----- golang-step-crypto-0.24.0/pemutil/testdata/password.txt000066400000000000000000000000121437037643100232270ustar00rootroot00000000000000mypasswordgolang-step-crypto-0.24.0/pemutil/testdata/pkcs8/000077500000000000000000000000001437037643100216635ustar00rootroot00000000000000golang-step-crypto-0.24.0/pemutil/testdata/pkcs8/openssl.ed25519.der000066400000000000000000000000601437037643100250330ustar00rootroot000000000000000.0+ep" ะฐอ{†าXฅEoำ”h‰๎" –/-)”˜#ส SHA256: (= p or R.y >= 2|p| or s >= 2|q|: // return false // A = convert_mont(u) // if not on_curve(A): // return false // h = hash(R || A || M) (mod q) // Rcheck = sB - hA // if bytes_equal(R, Rcheck): // return true // return false func Verify(publicKey PublicKey, message, sig []byte) bool { // The following code should be equivalent to: // // pub, err := publicKey.ToEd25519() // if err != nil { // return false // } // return ed25519.Verify(pub, message, sig) if l := len(publicKey); l != PublicKeySize { panic("x25519: bad public key length: " + strconv.Itoa(l)) } if len(sig) != SignatureSize || sig[63]&0xE0 != 0 { return false } a, err := convertMont(publicKey) if err != nil { return false } hh := sha512.New() hh.Write(sig[:32]) hh.Write(a.Bytes()) hh.Write(message) hDigest := make([]byte, 0, sha512.Size) hDigest = hh.Sum(hDigest) h, err := edwards25519.NewScalar().SetUniformBytes(hDigest) if err != nil { return false } s, err := edwards25519.NewScalar().SetCanonicalBytes(sig[32:]) if err != nil { return false } minusA := (&edwards25519.Point{}).Negate(a) r := (&edwards25519.Point{}).VarTimeDoubleScalarBaseMult(h, minusA, s) return subtle.ConstantTimeCompare(sig[:32], r.Bytes()) == 1 } // calculateKeyPair converts a Montgomery private key k to a twisted Edwards // public key and private key (A, a) as defined in // https://signal.org/docs/specifications/xeddsa/#elliptic-curve-conversions // // calculate_key_pair(k): // E = kB // A.y = E.y // A.s = 0 // if E.s == 1: // a = -k (mod q) // else: // a = k (mod q) // return A, a func (p PrivateKey) calculateKeyPair() ([]byte, *edwards25519.Scalar, error) { var pA edwards25519.Point var sa edwards25519.Scalar k, err := (&edwards25519.Scalar{}).SetBytesWithClamping(p) if err != nil { return nil, nil, err } pub := pA.ScalarBaseMult(k).Bytes() signBit := (pub[31] & 0x80) >> 7 if signBit == 1 { sa.Negate(k) // Set sig bit to 0 pub[31] &= 0x7F } else { sa.Set(k) } return pub, &sa, nil } // convertMont converts from a Montgomery u-coordinate to a twisted Edwards // point P, according to // https://signal.org/docs/specifications/xeddsa/#elliptic-curve-conversions // // convert_mont(u): // umasked = u (mod 2|p|) // P.y = u_to_y(umasked) // P.s = 0 // return P func convertMont(u PublicKey) (*edwards25519.Point, error) { um, err := (&field.Element{}).SetBytes(u) if err != nil { return nil, err } // y = (u - 1)/(u + 1) a := new(field.Element).Subtract(um, one) b := new(field.Element).Add(um, one) y := new(field.Element).Multiply(a, b.Invert(b)).Bytes() // Set sign to 0 y[31] &= 0x7F return (&edwards25519.Point{}).SetBytes(y) } golang-step-crypto-0.24.0/x25519/x25519_test.go000066400000000000000000000443751437037643100205000ustar00rootroot00000000000000package x25519 import ( "bytes" "crypto" "crypto/ed25519" "crypto/rand" "io" "reflect" "testing" "golang.org/x/crypto/curve25519" ) func mustTeeReader(t *testing.T) *bytes.Buffer { t.Helper() reader := rand.Reader t.Cleanup(func() { rand.Reader = reader }) buf := new(bytes.Buffer) rand.Reader = io.TeeReader(reader, buf) return buf } func BenchmarkGenerateKey(b *testing.B) { for i := 0; i < b.N; i++ { if _, _, err := GenerateKey(rand.Reader); err != nil { b.Fatal(err) } } } func BenchmarkSignVerify(b *testing.B) { pub, priv, err := GenerateKey(rand.Reader) if err != nil { b.Fatal(err) } data := make([]byte, 1024) if _, err := io.ReadFull(rand.Reader, data); err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { sig, err := Sign(rand.Reader, priv, data) if err != nil { b.Fatal(err) } if !Verify(pub, data, sig) { b.Fatalf("failed to verify: %d", i) } } } func BenchmarkSignVerifyMethod(b *testing.B) { pub, priv, err := GenerateKey(rand.Reader) if err != nil { b.Fatal(err) } data := make([]byte, 1024) if _, err := io.ReadFull(rand.Reader, data); err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { sig, err := priv.Sign(rand.Reader, data, crypto.Hash(0)) if err != nil { b.Fatal(err) } if !Verify(pub, data, sig) { b.Fatalf("failed to verify: %d", i) } } } func TestVectors(t *testing.T) { buf := mustTeeReader(t) buf.Write(make([]byte, 64)) buf.Write([]byte{ 0x4e, 0x6c, 0xd9, 0xe8, 0x40, 0x4d, 0xe1, 0xe0, 0xc5, 0x22, 0x72, 0x8f, 0x78, 0xde, 0x88, 0x26, 0x58, 0x4b, 0x44, 0xef, 0xbc, 0xf6, 0xac, 0x10, 0x97, 0x67, 0x60, 0xcc, 0x41, 0xf1, 0x82, 0xb, 0xb8, 0x69, 0x6, 0xf3, 0xad, 0xa5, 0x46, 0x6f, 0xc2, 0xf3, 0x9, 0x78, 0x7b, 0x3c, 0x6f, 0xd6, 0x39, 0x19, 0xb1, 0x48, 0xd6, 0x6, 0xb7, 0x79, 0xfb, 0xf7, 0xda, 0x61, 0xee, 0x57, 0x7b, 0xe6, }) buf.Write([]byte{ 0x56, 0x53, 0xd8, 0x94, 0x41, 0x20, 0x4d, 0x9a, 0xd6, 0x3c, 0xfa, 0x3a, 0x78, 0x4a, 0x9e, 0x2e, 0x4d, 0x13, 0x07, 0xa1, 0x66, 0xde, 0xf7, 0xa7, 0xb4, 0x3a, 0xc8, 0x4f, 0x23, 0x2c, 0x9a, 0x6d, 0x16, 0x17, 0x10, 0xe6, 0xe7, 0x91, 0x2e, 0x38, 0x61, 0xe3, 0xe9, 0x9f, 0x90, 0x2c, 0x6d, 0x9, 0x82, 0xc6, 0x44, 0xd6, 0xeb, 0x12, 0xd0, 0x53, 0x66, 0x27, 0x61, 0xd0, 0x35, 0x33, 0x31, 0x9d, }) type args struct { rand io.Reader message []byte opts crypto.SignerOpts } tests := []struct { name string p PrivateKey args args wantSignature []byte wantErr bool }{ // Test vector from // https://github.com/signalapp/libsignal-protocol-c/blob/3a83a4f4ed2302ff6e68ab569c88793b50c22d28/src/curve25519/ed25519/tests/internal_fast_tests.c#L324 {"ok", []byte{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, }, args{buf, make([]byte, 200), crypto.Hash(0)}, []byte{ 0x11, 0xc7, 0xf3, 0xe6, 0xc4, 0xdf, 0x9e, 0x8a, 0x51, 0x50, 0xe1, 0xdb, 0x3b, 0x30, 0xf9, 0x2d, 0xe3, 0xa3, 0xb3, 0xaa, 0x43, 0x86, 0x56, 0x54, 0x5f, 0xa7, 0x39, 0x0f, 0x4b, 0xcc, 0x7b, 0xb2, 0x6c, 0x43, 0x1d, 0x9e, 0x90, 0x64, 0x3e, 0x4f, 0x0e, 0xaa, 0x0e, 0x9c, 0x55, 0x77, 0x66, 0xfa, 0x69, 0xad, 0xa5, 0x76, 0xd6, 0x3d, 0xca, 0xf2, 0xac, 0x32, 0x6c, 0x11, 0xd0, 0xb9, 0x77, 0x02, }, false}, // Test vector from // https://github.com/signalapp/libsignal-protocol-c/blob/3a83a4f4ed2302ff6e68ab569c88793b50c22d28/src/curve25519/ed25519/tests/internal_slow_tests.c#L98 {"ok", []byte{ 0xb0, 0x3d, 0x85, 0x79, 0x6d, 0x92, 0x89, 0x78, 0x26, 0xaf, 0x9d, 0xb9, 0x13, 0x98, 0xf3, 0xf9, 0x73, 0x7d, 0x5f, 0x5c, 0xde, 0x76, 0xd1, 0xc4, 0x4c, 0x3a, 0x3f, 0xa9, 0x6e, 0xe5, 0x19, 0x46, }, args{buf, []byte{ 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, }, crypto.Hash(0)}, []byte{ 0x15, 0x29, 0x03, 0x38, 0x66, 0x16, 0xcd, 0x26, 0xbb, 0x3e, 0xec, 0xe2, 0x9f, 0x72, 0xa2, 0x5c, 0x7d, 0x05, 0xc9, 0xcb, 0x84, 0x3f, 0x92, 0x96, 0xb3, 0xfb, 0xb9, 0xdd, 0xd6, 0xed, 0x99, 0x04, 0xc1, 0xa8, 0x02, 0x16, 0xcf, 0x49, 0x3f, 0xf1, 0xbe, 0x69, 0xf9, 0xf1, 0xcc, 0x16, 0xd7, 0xdc, 0x6e, 0xd3, 0x78, 0xaa, 0x04, 0xeb, 0x71, 0x51, 0x9d, 0xe8, 0x7a, 0x5b, 0xd8, 0x49, 0x7b, 0x05, }, false}, // Test vector from // https://github.com/signalapp/libsignal-protocol-c/blob/3a83a4f4ed2302ff6e68ab569c88793b50c22d28/src/curve25519/ed25519/tests/internal_slow_tests.c#L165 // // Note that byte 16 is altered before comparing it with the expected // signature, so the real value for b[16] is 0xb4 instead of 0xb5. // https://github.com/signalapp/libsignal-protocol-c/blob/3a83a4f4ed2302ff6e68ab569c88793b50c22d28/src/curve25519/ed25519/tests/internal_slow_tests.c#L211 {"ok", []byte{ 0xb8, 0x96, 0x4, 0xb2, 0xcc, 0xe1, 0x1b, 0xe9, 0xd5, 0x4a, 0xe5, 0x1, 0x1e, 0x3c, 0x2b, 0xfe, 0x24, 0x35, 0x3f, 0x6, 0x6, 0x73, 0x3e, 0xb7, 0xe4, 0xa1, 0x6f, 0xb9, 0xe9, 0xbf, 0xff, 0x64, }, args{buf, []byte{ 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, }, crypto.Hash(0)}, []byte{ 0x33, 0x50, 0xa8, 0x68, 0xcd, 0x9e, 0x74, 0x99, 0xa3, 0x5c, 0x33, 0x75, 0x2b, 0x22, 0x03, 0xf8, 0xb4, 0x0f, 0xea, 0x8c, 0x33, 0x1c, 0x68, 0x8b, 0xbb, 0xf3, 0x31, 0xcf, 0x7c, 0x42, 0x37, 0x35, 0xa0, 0x0e, 0x15, 0xb8, 0x5d, 0x2b, 0xe1, 0xa2, 0x03, 0x77, 0x94, 0x3d, 0x13, 0x5c, 0xd4, 0x9b, 0x6a, 0x31, 0xf4, 0xdc, 0xfe, 0x24, 0xad, 0x54, 0xeb, 0xd2, 0x98, 0x47, 0xf1, 0xcc, 0xbf, 0x0d, }, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotSignature, err := tt.p.Sign(tt.args.rand, tt.args.message, tt.args.opts) if (err != nil) != tt.wantErr { t.Errorf("PrivateKey.Sign() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(gotSignature, tt.wantSignature) { t.Errorf("PrivateKey.Sign() = %x, want %x", gotSignature, tt.wantSignature) } // XEdDSA Verify publicKey, err := tt.p.PublicKey() if err != nil { t.Errorf("PrivateKey.Public() error = %v", err) return } if !Verify(publicKey, tt.args.message, gotSignature) { t.Error("Verify() = false, want true") } // Ed265519 Verify ed, err := publicKey.ToEd25519() if err != nil { t.Errorf("PublicKey.ToEd25519() error = %v", err) } if !ed25519.Verify(ed, tt.args.message, gotSignature) { t.Error("ed25519.Verify() = false, want true") } }) } } func TestSignVerify(t *testing.T) { iterations := 1000 for i := 0; i < iterations; i++ { pub, priv, err := GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } data := make([]byte, 1024) if _, err := io.ReadFull(rand.Reader, data); err != nil { t.Fatal(err) } sig, err := Sign(rand.Reader, priv, data) if err != nil { t.Fatal(err) } if !Verify(pub, data, sig) { t.Fatalf("failed to verify: %d", i) } // Ed265519 Verify ed, err := pub.ToEd25519() if err != nil { t.Errorf("PublicKey.ToEd25519() error = %v", err) } if !ed25519.Verify(ed, data, sig) { t.Error("ed25519.Verify() = false, want true") } } } func TestGenerateKey(t *testing.T) { buf := mustTeeReader(t) buf.Write([]byte{ 0x4e, 0x6c, 0xd9, 0xe8, 0x40, 0x4d, 0xe1, 0xe0, 0xc5, 0x22, 0x72, 0x8f, 0x78, 0xde, 0x88, 0x26, 0x58, 0x4b, 0x44, 0xef, 0xbc, 0xf6, 0xac, 0x10, 0x97, 0x67, 0x60, 0xcc, 0x41, 0xf1, 0x82, 0xb, }) type args struct { rand io.Reader } tests := []struct { name string args args want PublicKey want1 PrivateKey wantErr bool }{ {"ok", args{buf}, []byte{ 0x51, 0xf3, 0xfe, 0x72, 0xb4, 0x35, 0x66, 0x52, 0x16, 0x16, 0x38, 0xfb, 0x9b, 0xf7, 0x17, 0x06, 0x87, 0xb6, 0x35, 0x53, 0xc1, 0x63, 0x9c, 0x73, 0xa7, 0x79, 0x1e, 0xd2, 0xba, 0xf8, 0xcb, 0x67, }, []byte{ 0x4e, 0x6c, 0xd9, 0xe8, 0x40, 0x4d, 0xe1, 0xe0, 0xc5, 0x22, 0x72, 0x8f, 0x78, 0xde, 0x88, 0x26, 0x58, 0x4b, 0x44, 0xef, 0xbc, 0xf6, 0xac, 0x10, 0x97, 0x67, 0x60, 0xcc, 0x41, 0xf1, 0x82, 0x0b, }, false}, {"fail", args{buf}, nil, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, got1, err := GenerateKey(tt.args.rand) if (err != nil) != tt.wantErr { t.Errorf("GenerateKey() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("GenerateKey() got = %x, want %v", got, tt.want) } if !reflect.DeepEqual(got1, tt.want1) { t.Errorf("GenerateKey() got1 = %v, want %v", got1, tt.want1) } }) } } func TestPublicKey_ToEd25519(t *testing.T) { tests := []struct { name string p PublicKey want ed25519.PublicKey wantErr bool }{ {"ok", []byte{ 0x59, 0x95, 0xf4, 0x64, 0xe9, 0xd3, 0x4d, 0x5c, 0xa5, 0x6b, 0x99, 0x05, 0xb9, 0xa3, 0xcc, 0x37, 0xc4, 0x56, 0xb2, 0xd8, 0xd3, 0x13, 0xed, 0xbc, 0xf5, 0x84, 0xb7, 0x05, 0xb5, 0xc0, 0x49, 0x55, }, []byte{ 0x9c, 0xdc, 0xad, 0xd4, 0x70, 0xc0, 0x55, 0xcc, 0xbf, 0x70, 0x83, 0xba, 0x09, 0x56, 0xc2, 0x45, 0xc4, 0x2c, 0xad, 0x21, 0xbd, 0x28, 0xad, 0xb8, 0x5f, 0x25, 0x7f, 0x9a, 0x9b, 0xb9, 0x31, 0x1c, }, false}, {"fail", []byte{ 0x59, 0x95, 0xf4, 0x64, 0xe9, 0xd3, 0x4d, 0x5c, 0xa5, 0x6b, 0x99, 0x05, 0xb9, 0xa3, 0xcc, 0x37, }, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.p.ToEd25519() if (err != nil) != tt.wantErr { t.Errorf("PublicKey.ToEd25519() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("PublicKey.ToEd25519() = %v, want %v", got, tt.want) } }) } } func TestPrivateKey_Public(t *testing.T) { tests := []struct { name string p PrivateKey want PublicKey }{ {"ok", []byte{ 0x4e, 0x6c, 0xd9, 0xe8, 0x40, 0x4d, 0xe1, 0xe0, 0xc5, 0x22, 0x72, 0x8f, 0x78, 0xde, 0x88, 0x26, 0x58, 0x4b, 0x44, 0xef, 0xbc, 0xf6, 0xac, 0x10, 0x97, 0x67, 0x60, 0xcc, 0x41, 0xf1, 0x82, 0x0b, }, []byte{ 0x51, 0xf3, 0xfe, 0x72, 0xb4, 0x35, 0x66, 0x52, 0x16, 0x16, 0x38, 0xfb, 0x9b, 0xf7, 0x17, 0x06, 0x87, 0xb6, 0x35, 0x53, 0xc1, 0x63, 0x9c, 0x73, 0xa7, 0x79, 0x1e, 0xd2, 0xba, 0xf8, 0xcb, 0x67, }}, {"fail", []byte{ 0x4e, 0x6c, 0xd9, 0xe8, 0x40, 0x4d, 0xe1, 0xe0, 0xc5, 0x22, 0x72, 0x8f, 0x78, 0xde, 0x88, 0x26, }, nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.p.Public(); !reflect.DeepEqual(got, tt.want) { t.Errorf("PrivateKey.Public() = %v, want %v", got, tt.want) } }) } } func TestPrivateKey_SharedKey(t *testing.T) { type args struct { peerPublicKey []byte } tests := []struct { name string p PrivateKey args args want []byte wantErr bool }{ {"ok basepoint", []byte{ 0x4e, 0x6c, 0xd9, 0xe8, 0x40, 0x4d, 0xe1, 0xe0, 0xc5, 0x22, 0x72, 0x8f, 0x78, 0xde, 0x88, 0x26, 0x58, 0x4b, 0x44, 0xef, 0xbc, 0xf6, 0xac, 0x10, 0x97, 0x67, 0x60, 0xcc, 0x41, 0xf1, 0x82, 0x0b, }, args{curve25519.Basepoint}, []byte{ 0x51, 0xf3, 0xfe, 0x72, 0xb4, 0x35, 0x66, 0x52, 0x16, 0x16, 0x38, 0xfb, 0x9b, 0xf7, 0x17, 0x06, 0x87, 0xb6, 0x35, 0x53, 0xc1, 0x63, 0x9c, 0x73, 0xa7, 0x79, 0x1e, 0xd2, 0xba, 0xf8, 0xcb, 0x67, }, false}, {"ok other", []byte{ 0x4e, 0x6c, 0xd9, 0xe8, 0x40, 0x4d, 0xe1, 0xe0, 0xc5, 0x22, 0x72, 0x8f, 0x78, 0xde, 0x88, 0x26, 0x58, 0x4b, 0x44, 0xef, 0xbc, 0xf6, 0xac, 0x10, 0x97, 0x67, 0x60, 0xcc, 0x41, 0xf1, 0x82, 0x0b, }, args{[]byte{ 0x51, 0xf3, 0xfe, 0x72, 0xb4, 0x35, 0x66, 0x52, 0x16, 0x16, 0x38, 0xfb, 0x9b, 0xf7, 0x17, 0x06, 0x87, 0xb6, 0x35, 0x53, 0xc1, 0x63, 0x9c, 0x73, 0xa7, 0x79, 0x1e, 0xd2, 0xba, 0xf8, 0xcb, 0x67, }}, []byte{ 0x73, 0xdd, 0xf1, 0x92, 0x71, 0x49, 0xdc, 0x21, 0xd6, 0x9e, 0xc1, 0x7b, 0xf9, 0x82, 0x57, 0x15, 0x53, 0xc0, 0xc7, 0xf9, 0x26, 0x0c, 0xa4, 0x24, 0x00, 0x29, 0x4b, 0xae, 0x9f, 0xc7, 0x77, 0x60, }, false}, {"zero", []byte{ 0x4e, 0x6c, 0xd9, 0xe8, 0x40, 0x4d, 0xe1, 0xe0, 0xc5, 0x22, 0x72, 0x8f, 0x78, 0xde, 0x88, 0x26, 0x58, 0x4b, 0x44, 0xef, 0xbc, 0xf6, 0xac, 0x10, 0x97, 0x67, 0x60, 0xcc, 0x41, 0xf1, 0x82, 0x0b, }, args{make([]byte, 32)}, nil, true}, {"fail", []byte{ 0x4e, 0x6c, 0xd9, 0xe8, 0x40, 0x4d, 0xe1, 0xe0, 0xc5, 0x22, 0x72, 0x8f, 0x78, 0xde, 0x88, 0x26, }, args{[]byte{ 0x51, 0xf3, 0xfe, 0x72, 0xb4, 0x35, 0x66, 0x52, 0x16, 0x16, 0x38, 0xfb, 0x9b, 0xf7, 0x17, 0x06, 0x87, 0xb6, 0x35, 0x53, 0xc1, 0x63, 0x9c, 0x73, 0xa7, 0x79, 0x1e, 0xd2, 0xba, 0xf8, 0xcb, 0x67, }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.p.SharedKey(tt.args.peerPublicKey) if (err != nil) != tt.wantErr { t.Errorf("PrivateKey.SharedKey() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("PrivateKey.SharedKey() = %x, want %v", got, tt.want) } }) } } func TestPrivateKey_Sign_error(t *testing.T) { type args struct { rand io.Reader message []byte opts crypto.SignerOpts } tests := []struct { name string p PrivateKey args args wantSignature []byte wantErr bool }{ {"fail SignerOpts", []byte{ 0x51, 0xf3, 0xfe, 0x72, 0xb4, 0x35, 0x66, 0x52, 0x16, 0x16, 0x38, 0xfb, 0x9b, 0xf7, 0x17, 0x06, 0x87, 0xb6, 0x35, 0x53, 0xc1, 0x63, 0x9c, 0x73, 0xa7, 0x79, 0x1e, 0xd2, 0xba, 0xf8, 0xcb, 0x67, }, args{rand.Reader, []byte("message"), crypto.SHA512}, nil, true}, {"panic", []byte{ 0x51, 0xf3, 0xfe, 0x72, 0xb4, 0x35, 0x66, 0x52, 0x16, 0x16, 0x38, 0xfb, 0x9b, 0xf7, 0x17, 0x06, }, args{rand.Reader, []byte("message"), crypto.Hash(0)}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.name == "panic" { defer func() { err := recover() if (err != nil) != tt.wantErr { t.Errorf("PrivateKey.Sign() recover error = %v, wantErr %v", err, tt.wantErr) return } }() } gotSignature, err := tt.p.Sign(tt.args.rand, tt.args.message, tt.args.opts) if (err != nil) != tt.wantErr { t.Errorf("PrivateKey.Sign() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(gotSignature, tt.wantSignature) { t.Errorf("PrivateKey.Sign() = %v, want %v", gotSignature, tt.wantSignature) } }) } } func TestVerify_error(t *testing.T) { type args struct { publicKey PublicKey message []byte sig []byte } tests := []struct { name string args args want bool }{ {"fail", args{[]byte{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, }, make([]byte, 199), []byte{ 0x11, 0xc7, 0xf3, 0xe6, 0xc4, 0xdf, 0x9e, 0x8a, 0x51, 0x50, 0xe1, 0xdb, 0x3b, 0x30, 0xf9, 0x2d, 0xe3, 0xa3, 0xb3, 0xaa, 0x43, 0x86, 0x56, 0x54, 0x5f, 0xa7, 0x39, 0x0f, 0x4b, 0xcc, 0x7b, 0xb2, 0x6c, 0x43, 0x1d, 0x9e, 0x90, 0x64, 0x3e, 0x4f, 0x0e, 0xaa, 0x0e, 0x9c, 0x55, 0x77, 0x66, 0xfa, 0x69, 0xad, 0xa5, 0x76, 0xd6, 0x3d, 0xca, 0xf2, 0xac, 0x32, 0x6c, 0x11, 0xd0, 0xb9, 0x77, 0x02, }}, false}, {"panic", args{[]byte{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, make([]byte, 200), []byte{ 0x11, 0xc7, 0xf3, 0xe6, 0xc4, 0xdf, 0x9e, 0x8a, 0x51, 0x50, 0xe1, 0xdb, 0x3b, 0x30, 0xf9, 0x2d, 0xe3, 0xa3, 0xb3, 0xaa, 0x43, 0x86, 0x56, 0x54, 0x5f, 0xa7, 0x39, 0x0f, 0x4b, 0xcc, 0x7b, 0xb2, 0x6c, 0x43, 0x1d, 0x9e, 0x90, 0x64, 0x3e, 0x4f, 0x0e, 0xaa, 0x0e, 0x9c, 0x55, 0x77, 0x66, 0xfa, 0x69, 0xad, 0xa5, 0x76, 0xd6, 0x3d, 0xca, 0xf2, 0xac, 0x32, 0x6c, 0x11, 0xd0, 0xb9, 0x77, 0x02, }}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.name == "panic" { defer func() { err := recover() if err == nil { t.Errorf("Verify() recover error = %v, wantErr true", err) return } }() } if got := Verify(tt.args.publicKey, tt.args.message, tt.args.sig); got != tt.want { t.Errorf("Verify() = %v, want %v", got, tt.want) } }) } } golang-step-crypto-0.24.0/x509util/000077500000000000000000000000001437037643100167465ustar00rootroot00000000000000golang-step-crypto-0.24.0/x509util/algorithms.go000066400000000000000000000046261437037643100214560ustar00rootroot00000000000000package x509util import ( "crypto/x509" "strings" "github.com/pkg/errors" ) // List of signature algorithms. const ( MD2WithRSA = "MD2-RSA" MD5WithRSA = "MD5-RSA" SHA1WithRSA = "SHA1-RSA" SHA256WithRSA = "SHA256-RSA" SHA384WithRSA = "SHA384-RSA" SHA512WithRSA = "SHA512-RSA" DSAWithSHA1 = "DSA-SHA1" DSAWithSHA256 = "DSA-SHA256" ECDSAWithSHA1 = "ECDSA-SHA1" ECDSAWithSHA256 = "ECDSA-SHA256" ECDSAWithSHA384 = "ECDSA-SHA384" ECDSAWithSHA512 = "ECDSA-SHA512" SHA256WithRSAPSS = "SHA256-RSAPSS" SHA384WithRSAPSS = "SHA384-RSAPSS" SHA512WithRSAPSS = "SHA512-RSAPSS" PureEd25519 = "Ed25519" ) var signatureAlgorithmMapping = []struct { name string value x509.SignatureAlgorithm }{ {"", x509.UnknownSignatureAlgorithm}, {MD2WithRSA, x509.MD2WithRSA}, {MD5WithRSA, x509.MD5WithRSA}, {SHA1WithRSA, x509.SHA1WithRSA}, {SHA256WithRSA, x509.SHA256WithRSA}, {SHA384WithRSA, x509.SHA384WithRSA}, {SHA512WithRSA, x509.SHA512WithRSA}, {DSAWithSHA1, x509.DSAWithSHA1}, {DSAWithSHA256, x509.DSAWithSHA256}, {ECDSAWithSHA1, x509.ECDSAWithSHA1}, {ECDSAWithSHA256, x509.ECDSAWithSHA256}, {ECDSAWithSHA384, x509.ECDSAWithSHA384}, {ECDSAWithSHA512, x509.ECDSAWithSHA512}, {SHA256WithRSAPSS, x509.SHA256WithRSAPSS}, {SHA384WithRSAPSS, x509.SHA384WithRSAPSS}, {SHA512WithRSAPSS, x509.SHA512WithRSAPSS}, {PureEd25519, x509.PureEd25519}, } // SignatureAlgorithm is the JSON representation of the X509 signature algorithms type SignatureAlgorithm x509.SignatureAlgorithm // Set sets the signature algorithm in the given certificate. func (s SignatureAlgorithm) Set(c *x509.Certificate) { c.SignatureAlgorithm = x509.SignatureAlgorithm(s) } // MarshalJSON implements the json.Marshaller interface. func (s SignatureAlgorithm) MarshalJSON() ([]byte, error) { if s == SignatureAlgorithm(x509.UnknownSignatureAlgorithm) { return []byte(`""`), nil } return []byte(`"` + x509.SignatureAlgorithm(s).String() + `"`), nil } // UnmarshalJSON implements the json.Unmarshal interface and unmarshals and // validates a string as a SignatureAlgorithm. func (s *SignatureAlgorithm) UnmarshalJSON(data []byte) error { name, err := unmarshalString(data) if err != nil { return err } for _, m := range signatureAlgorithmMapping { if strings.EqualFold(name, m.name) { *s = SignatureAlgorithm(m.value) return nil } } return errors.Errorf("unsupported signatureAlgorithm %s", name) } golang-step-crypto-0.24.0/x509util/algorithms_test.go000066400000000000000000000130621437037643100225070ustar00rootroot00000000000000package x509util import ( "crypto/x509" "reflect" "testing" ) func TestSignatureAlgorithm_Set(t *testing.T) { type args struct { c *x509.Certificate } tests := []struct { name string s SignatureAlgorithm args args want *x509.Certificate }{ {"ok", SignatureAlgorithm(x509.ECDSAWithSHA256), args{&x509.Certificate{}}, &x509.Certificate{SignatureAlgorithm: x509.ECDSAWithSHA256}}, {"ok", SignatureAlgorithm(x509.PureEd25519), args{&x509.Certificate{}}, &x509.Certificate{SignatureAlgorithm: x509.PureEd25519}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.s.Set(tt.args.c) if !reflect.DeepEqual(tt.args.c, tt.want) { t.Errorf("SignatureAlgorithm.Set() = %v, want %v", tt.args.c, tt.want) } }) } } func TestSignatureAlgorithm_MarshalJSON(t *testing.T) { tests := []struct { name string s SignatureAlgorithm want []byte wantErr bool }{ {"UnknownSignatureAlgorithm", SignatureAlgorithm(x509.UnknownSignatureAlgorithm), []byte(`""`), false}, {"MD2WithRSA", SignatureAlgorithm(x509.MD2WithRSA), []byte(`"` + MD2WithRSA + `"`), false}, {"MD5WithRSA", SignatureAlgorithm(x509.MD5WithRSA), []byte(`"` + MD5WithRSA + `"`), false}, {"SHA1WithRSA", SignatureAlgorithm(x509.SHA1WithRSA), []byte(`"` + SHA1WithRSA + `"`), false}, {"SHA256WithRSA", SignatureAlgorithm(x509.SHA256WithRSA), []byte(`"` + SHA256WithRSA + `"`), false}, {"SHA384WithRSA", SignatureAlgorithm(x509.SHA384WithRSA), []byte(`"` + SHA384WithRSA + `"`), false}, {"SHA512WithRSA", SignatureAlgorithm(x509.SHA512WithRSA), []byte(`"` + SHA512WithRSA + `"`), false}, {"DSAWithSHA1", SignatureAlgorithm(x509.DSAWithSHA1), []byte(`"` + DSAWithSHA1 + `"`), false}, {"DSAWithSHA256", SignatureAlgorithm(x509.DSAWithSHA256), []byte(`"` + DSAWithSHA256 + `"`), false}, {"ECDSAWithSHA1", SignatureAlgorithm(x509.ECDSAWithSHA1), []byte(`"` + ECDSAWithSHA1 + `"`), false}, {"ECDSAWithSHA256", SignatureAlgorithm(x509.ECDSAWithSHA256), []byte(`"` + ECDSAWithSHA256 + `"`), false}, {"ECDSAWithSHA384", SignatureAlgorithm(x509.ECDSAWithSHA384), []byte(`"` + ECDSAWithSHA384 + `"`), false}, {"ECDSAWithSHA512", SignatureAlgorithm(x509.ECDSAWithSHA512), []byte(`"` + ECDSAWithSHA512 + `"`), false}, {"SHA256WithRSAPSS", SignatureAlgorithm(x509.SHA256WithRSAPSS), []byte(`"` + SHA256WithRSAPSS + `"`), false}, {"SHA384WithRSAPSS", SignatureAlgorithm(x509.SHA384WithRSAPSS), []byte(`"` + SHA384WithRSAPSS + `"`), false}, {"SHA512WithRSAPSS", SignatureAlgorithm(x509.SHA512WithRSAPSS), []byte(`"` + SHA512WithRSAPSS + `"`), false}, {"PureEd25519", SignatureAlgorithm(x509.PureEd25519), []byte(`"` + PureEd25519 + `"`), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.s.MarshalJSON() if (err != nil) != tt.wantErr { t.Errorf("SignatureAlgorithm.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("SignatureAlgorithm.MarshalJSON() = %s, want %s", got, tt.want) } }) } } func TestSignatureAlgorithm_UnmarshalJSON(t *testing.T) { type args struct { data []byte } tests := []struct { name string args args want SignatureAlgorithm wantErr bool }{ {"MD2-RSA", args{[]byte(`"MD2-RSA"`)}, SignatureAlgorithm(x509.MD2WithRSA), false}, {"MD5-RSA", args{[]byte(`"MD5-RSA"`)}, SignatureAlgorithm(x509.MD5WithRSA), false}, {"SHA1-RSA", args{[]byte(`"SHA1-RSA"`)}, SignatureAlgorithm(x509.SHA1WithRSA), false}, {"SHA256-RSA", args{[]byte(`"SHA256-RSA"`)}, SignatureAlgorithm(x509.SHA256WithRSA), false}, {"SHA384-RSA", args{[]byte(`"SHA384-RSA"`)}, SignatureAlgorithm(x509.SHA384WithRSA), false}, {"SHA512-RSA", args{[]byte(`"SHA512-RSA"`)}, SignatureAlgorithm(x509.SHA512WithRSA), false}, {"SHA256-RSAPSS", args{[]byte(`"SHA256-RSAPSS"`)}, SignatureAlgorithm(x509.SHA256WithRSAPSS), false}, {"SHA384-RSAPSS", args{[]byte(`"SHA384-RSAPSS"`)}, SignatureAlgorithm(x509.SHA384WithRSAPSS), false}, {"SHA512-RSAPSS", args{[]byte(`"SHA512-RSAPSS"`)}, SignatureAlgorithm(x509.SHA512WithRSAPSS), false}, {"DSA-SHA1", args{[]byte(`"DSA-SHA1"`)}, SignatureAlgorithm(x509.DSAWithSHA1), false}, {"DSA-SHA256", args{[]byte(`"DSA-SHA256"`)}, SignatureAlgorithm(x509.DSAWithSHA256), false}, {"ECDSA-SHA1", args{[]byte(`"ECDSA-SHA1"`)}, SignatureAlgorithm(x509.ECDSAWithSHA1), false}, {"ECDSA-SHA256", args{[]byte(`"ECDSA-SHA256"`)}, SignatureAlgorithm(x509.ECDSAWithSHA256), false}, {"ECDSA-SHA384", args{[]byte(`"ECDSA-SHA384"`)}, SignatureAlgorithm(x509.ECDSAWithSHA384), false}, {"ECDSA-SHA512", args{[]byte(`"ECDSA-SHA512"`)}, SignatureAlgorithm(x509.ECDSAWithSHA512), false}, {"Ed25519", args{[]byte(`"Ed25519"`)}, SignatureAlgorithm(x509.PureEd25519), false}, {"lowercase", args{[]byte(`"ecdsa-sha256"`)}, SignatureAlgorithm(x509.ECDSAWithSHA256), false}, {"empty", args{[]byte(`""`)}, SignatureAlgorithm(0), false}, {"null", args{[]byte(`null`)}, SignatureAlgorithm(0), false}, {"unknown", args{[]byte(`"unknown"`)}, SignatureAlgorithm(0), true}, {"number", args{[]byte(`0`)}, SignatureAlgorithm(0), true}, {"numberString", args{[]byte(`"0"`)}, SignatureAlgorithm(0), true}, {"object", args{[]byte(`{}`)}, SignatureAlgorithm(0), true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got SignatureAlgorithm if err := got.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { t.Errorf("SignatureAlgorithm.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("SignatureAlgorithm.UnmarshalJSON() = %v, want %v", got, tt.want) } }) } } golang-step-crypto-0.24.0/x509util/certificate.go000066400000000000000000000156621437037643100215710ustar00rootroot00000000000000// Package x509util implements utilities to build X.509 certificates based on // JSON templates. package x509util import ( "crypto" "crypto/rand" "crypto/x509" "encoding/json" "github.com/pkg/errors" ) // Certificate is the JSON representation of a X.509 certificate. It is used to // build a certificate from a template. type Certificate struct { Version int `json:"version"` Subject Subject `json:"subject"` Issuer Issuer `json:"issuer"` SerialNumber SerialNumber `json:"serialNumber"` DNSNames MultiString `json:"dnsNames"` EmailAddresses MultiString `json:"emailAddresses"` IPAddresses MultiIP `json:"ipAddresses"` URIs MultiURL `json:"uris"` SANs []SubjectAlternativeName `json:"sans"` Extensions []Extension `json:"extensions"` KeyUsage KeyUsage `json:"keyUsage"` ExtKeyUsage ExtKeyUsage `json:"extKeyUsage"` UnknownExtKeyUsage UnknownExtKeyUsage `json:"unknownExtKeyUsage"` SubjectKeyID SubjectKeyID `json:"subjectKeyId"` AuthorityKeyID AuthorityKeyID `json:"authorityKeyId"` OCSPServer OCSPServer `json:"ocspServer"` IssuingCertificateURL IssuingCertificateURL `json:"issuingCertificateURL"` CRLDistributionPoints CRLDistributionPoints `json:"crlDistributionPoints"` PolicyIdentifiers PolicyIdentifiers `json:"policyIdentifiers"` BasicConstraints *BasicConstraints `json:"basicConstraints"` NameConstraints *NameConstraints `json:"nameConstraints"` SignatureAlgorithm SignatureAlgorithm `json:"signatureAlgorithm"` PublicKeyAlgorithm x509.PublicKeyAlgorithm `json:"-"` PublicKey interface{} `json:"-"` } // NewCertificate creates a new Certificate from an x509.Certificate request and // some template options. func NewCertificate(cr *x509.CertificateRequest, opts ...Option) (*Certificate, error) { if err := cr.CheckSignature(); err != nil { return nil, errors.Wrap(err, "error validating certificate request") } o, err := new(Options).apply(cr, opts) if err != nil { return nil, err } // If no template use only the certificate request with the default leaf key // usages. if o.CertBuffer == nil { return NewCertificateRequestFromX509(cr).GetLeafCertificate(), nil } // With templates var cert Certificate if err := json.NewDecoder(o.CertBuffer).Decode(&cert); err != nil { return nil, errors.Wrap(err, "error unmarshaling certificate") } // Complete with certificate request cert.PublicKey = cr.PublicKey cert.PublicKeyAlgorithm = cr.PublicKeyAlgorithm // Generate the subjectAltName extension if the certificate contains SANs // that are not supported in the Go standard library. if cert.hasExtendedSANs() && !cert.hasExtension(oidExtensionSubjectAltName) { ext, err := createCertificateSubjectAltNameExtension(cert, cert.Subject.IsEmpty()) if err != nil { return nil, err } // Prepend extension to achieve a certificate as similar as possible to // the one generated by the Go standard library. cert.Extensions = append([]Extension{ext}, cert.Extensions...) } return &cert, nil } // GetCertificate returns the x509.Certificate representation of the // certificate. func (c *Certificate) GetCertificate() *x509.Certificate { cert := new(x509.Certificate) // Unparsed data cert.PublicKey = c.PublicKey cert.PublicKeyAlgorithm = c.PublicKeyAlgorithm // Subject c.Subject.Set(cert) // When we have no extended SANs, use the golang x509 lib to create the // extension instead if !c.hasExtension(oidExtensionSubjectAltName) { cert.DNSNames = c.DNSNames cert.EmailAddresses = c.EmailAddresses cert.IPAddresses = c.IPAddresses cert.URIs = c.URIs // SANs slice. for _, san := range c.SANs { san.Set(cert) } } // Defined extensions. c.KeyUsage.Set(cert) c.ExtKeyUsage.Set(cert) c.UnknownExtKeyUsage.Set(cert) c.SubjectKeyID.Set(cert) c.AuthorityKeyID.Set(cert) c.OCSPServer.Set(cert) c.IssuingCertificateURL.Set(cert) c.CRLDistributionPoints.Set(cert) c.PolicyIdentifiers.Set(cert) if c.BasicConstraints != nil { c.BasicConstraints.Set(cert) } if c.NameConstraints != nil { c.NameConstraints.Set(cert) } // Custom Extensions. for _, e := range c.Extensions { e.Set(cert) } // Others. c.SerialNumber.Set(cert) c.SignatureAlgorithm.Set(cert) return cert } // hasExtendedSANs returns true if the certificate contains any SAN types that // are not supported by the golang x509 library (i.e. RegisteredID, OtherName, // DirectoryName, X400Address, or EDIPartyName) // // See also https://datatracker.ietf.org/doc/html/rfc5280.html#section-4.2.1.6 func (c *Certificate) hasExtendedSANs() bool { for _, san := range c.SANs { if !(san.Type == DNSType || san.Type == EmailType || san.Type == IPType || san.Type == URIType || san.Type == AutoType || san.Type == "") { return true } } return false } // hasExtension returns true if the given extension oid is in the certificate. func (c *Certificate) hasExtension(oid ObjectIdentifier) bool { for _, e := range c.Extensions { if e.ID.Equal(oid) { return true } } return false } // CreateCertificate signs the given template using the parent private key and // returns it. func CreateCertificate(template, parent *x509.Certificate, pub crypto.PublicKey, signer crypto.Signer) (*x509.Certificate, error) { var err error // Complete certificate. if template.SerialNumber == nil { if template.SerialNumber, err = generateSerialNumber(); err != nil { return nil, err } } if template.SubjectKeyId == nil { if template.SubjectKeyId, err = generateSubjectKeyID(pub); err != nil { return nil, err } } // Sign certificate asn1Data, err := x509.CreateCertificate(rand.Reader, template, parent, pub, signer) if err != nil { return nil, errors.Wrap(err, "error creating certificate") } cert, err := x509.ParseCertificate(asn1Data) if err != nil { return nil, errors.Wrap(err, "error parsing certificate") } return cert, nil } // CreateCertificateTemplate creates a X.509 certificate template from the given certificate request. func CreateCertificateTemplate(cr *x509.CertificateRequest) (*x509.Certificate, error) { if err := cr.CheckSignature(); err != nil { return nil, errors.Wrap(err, "error validating certificate request") } // Set SubjectAltName extension as critical if Subject is empty. fixSubjectAltName(cr) return &x509.Certificate{ Subject: cr.Subject, DNSNames: cr.DNSNames, EmailAddresses: cr.EmailAddresses, IPAddresses: cr.IPAddresses, URIs: cr.URIs, ExtraExtensions: cr.Extensions, PublicKey: cr.PublicKey, PublicKeyAlgorithm: cr.PublicKeyAlgorithm, SignatureAlgorithm: 0, }, nil } golang-step-crypto-0.24.0/x509util/certificate_request.go000066400000000000000000000167601437037643100233410ustar00rootroot00000000000000package x509util import ( "crypto" "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/json" "github.com/pkg/errors" ) var oidExtensionSubjectAltName = []int{2, 5, 29, 17} // CertificateRequest is the JSON representation of an X.509 certificate. It is // used to build a certificate request from a template. type CertificateRequest struct { Version int `json:"version"` Subject Subject `json:"subject"` DNSNames MultiString `json:"dnsNames"` EmailAddresses MultiString `json:"emailAddresses"` IPAddresses MultiIP `json:"ipAddresses"` URIs MultiURL `json:"uris"` SANs []SubjectAlternativeName `json:"sans"` Extensions []Extension `json:"extensions"` SignatureAlgorithm SignatureAlgorithm `json:"signatureAlgorithm"` PublicKey interface{} `json:"-"` PublicKeyAlgorithm x509.PublicKeyAlgorithm `json:"-"` Signature []byte `json:"-"` Signer crypto.Signer `json:"-"` } // NewCertificateRequest creates a certificate request from a template. func NewCertificateRequest(signer crypto.Signer, opts ...Option) (*CertificateRequest, error) { pub := signer.Public() o, err := new(Options).apply(&x509.CertificateRequest{ PublicKey: pub, }, opts) if err != nil { return nil, err } // If no template use only the certificate request with the default leaf key // usages. if o.CertBuffer == nil { return &CertificateRequest{ PublicKey: pub, Signer: signer, }, nil } // With templates var cr CertificateRequest if err := json.NewDecoder(o.CertBuffer).Decode(&cr); err != nil { return nil, errors.Wrap(err, "error unmarshaling certificate") } cr.PublicKey = pub cr.Signer = signer // Generate the subjectAltName extension if the certificate contains SANs // that are not supported in the Go standard library. if cr.hasExtendedSANs() && !cr.hasExtension(oidExtensionSubjectAltName) { ext, err := createCertificateRequestSubjectAltNameExtension(cr, cr.Subject.IsEmpty()) if err != nil { return nil, err } // Prepend extension to achieve a certificate as similar as possible to // the one generated by the Go standard library. cr.Extensions = append([]Extension{ext}, cr.Extensions...) } return &cr, nil } // NewCertificateRequestFromX509 creates a CertificateRequest from an // x509.CertificateRequest. // // This method is used to create the template variable .Insecure.CR or to // initialize the Certificate when no templates are used. // NewCertificateRequestFromX509 will always ignore the SignatureAlgorithm // because we cannot guarantee that the signer will be able to sign a // certificate template if Certificate.SignatureAlgorithm is set. func NewCertificateRequestFromX509(cr *x509.CertificateRequest) *CertificateRequest { // Set SubjectAltName extension as critical if Subject is empty. fixSubjectAltName(cr) return &CertificateRequest{ Version: cr.Version, Subject: newSubject(cr.Subject), DNSNames: cr.DNSNames, EmailAddresses: cr.EmailAddresses, IPAddresses: cr.IPAddresses, URIs: cr.URIs, Extensions: newExtensions(cr.Extensions), PublicKey: cr.PublicKey, PublicKeyAlgorithm: cr.PublicKeyAlgorithm, Signature: cr.Signature, // Do not enforce signature algorithm from the CSR, it might not // be compatible with the certificate signer. SignatureAlgorithm: 0, } } // GetCertificateRequest returns the equivalent x509.CertificateRequest. func (c *CertificateRequest) GetCertificateRequest() (*x509.CertificateRequest, error) { cert := c.GetCertificate().GetCertificate() asn1Data, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{ Subject: cert.Subject, DNSNames: cert.DNSNames, IPAddresses: cert.IPAddresses, EmailAddresses: cert.EmailAddresses, URIs: cert.URIs, ExtraExtensions: cert.ExtraExtensions, SignatureAlgorithm: x509.SignatureAlgorithm(c.SignatureAlgorithm), }, c.Signer) if err != nil { return nil, errors.Wrap(err, "error creating certificate request") } // This should not fail return x509.ParseCertificateRequest(asn1Data) } // GetCertificate returns the Certificate representation of the // CertificateRequest. // // GetCertificate will not specify a SignatureAlgorithm, it's not possible to // guarantee that the certificate signer can sign with the CertificateRequest // SignatureAlgorithm. func (c *CertificateRequest) GetCertificate() *Certificate { return &Certificate{ Subject: c.Subject, DNSNames: c.DNSNames, EmailAddresses: c.EmailAddresses, IPAddresses: c.IPAddresses, URIs: c.URIs, SANs: c.SANs, Extensions: c.Extensions, PublicKey: c.PublicKey, PublicKeyAlgorithm: c.PublicKeyAlgorithm, SignatureAlgorithm: 0, } } // GetLeafCertificate returns the Certificate representation of the // CertificateRequest, including KeyUsage and ExtKeyUsage extensions. // // GetLeafCertificate will not specify a SignatureAlgorithm, it's not possible // to guarantee that the certificate signer can sign with the CertificateRequest // SignatureAlgorithm. func (c *CertificateRequest) GetLeafCertificate() *Certificate { keyUsage := x509.KeyUsageDigitalSignature if _, ok := c.PublicKey.(*rsa.PublicKey); ok { keyUsage |= x509.KeyUsageKeyEncipherment } cert := c.GetCertificate() cert.KeyUsage = KeyUsage(keyUsage) cert.ExtKeyUsage = ExtKeyUsage([]x509.ExtKeyUsage{ x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth, }) return cert } // hasExtendedSANs returns true if the certificate contains any SAN types that // are not supported by the golang x509 library (i.e. RegisteredID, OtherName, // DirectoryName, X400Address, or EDIPartyName) // // See also https://datatracker.ietf.org/doc/html/rfc5280.html#section-4.2.1.6 func (c *CertificateRequest) hasExtendedSANs() bool { for _, san := range c.SANs { if !(san.Type == DNSType || san.Type == EmailType || san.Type == IPType || san.Type == URIType || san.Type == AutoType || san.Type == "") { return true } } return false } // hasExtension returns true if the given extension oid is in the certificate. func (c *CertificateRequest) hasExtension(oid ObjectIdentifier) bool { for _, e := range c.Extensions { if e.ID.Equal(oid) { return true } } return false } // CreateCertificateRequest creates a simple X.509 certificate request with the // given common name and sans. func CreateCertificateRequest(commonName string, sans []string, signer crypto.Signer) (*x509.CertificateRequest, error) { dnsNames, ips, emails, uris := SplitSANs(sans) asn1Data, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{ Subject: pkix.Name{ CommonName: commonName, }, DNSNames: dnsNames, IPAddresses: ips, EmailAddresses: emails, URIs: uris, }, signer) if err != nil { return nil, errors.Wrap(err, "error creating certificate request") } // This should not fail return x509.ParseCertificateRequest(asn1Data) } // fixSubjectAltName makes sure to mark the SAN extension to critical if the // subject is empty. func fixSubjectAltName(cr *x509.CertificateRequest) { if subjectIsEmpty(cr.Subject) { for i, ext := range cr.Extensions { if ext.Id.Equal(oidExtensionSubjectAltName) { cr.Extensions[i].Critical = true } } } } golang-step-crypto-0.24.0/x509util/certificate_request_test.go000066400000000000000000000460711437037643100243760ustar00rootroot00000000000000package x509util import ( "crypto" "crypto/ed25519" "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/base64" "fmt" "net" "net/url" "reflect" "testing" ) func TestNewCertificateRequest(t *testing.T) { _, signer, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } // ok extended sans sans := []SubjectAlternativeName{ {Type: DNSType, Value: "foo.com"}, {Type: EmailType, Value: "root@foo.com"}, {Type: IPType, Value: "3.14.15.92"}, {Type: URIType, Value: "mailto:root@foo.com"}, {Type: PermanentIdentifierType, Value: "123456789"}, } extendedSANs := CreateTemplateData("123456789", nil) extendedSANs.SetSubjectAlternativeNames(sans...) extendedSANsExtension, err := createSubjectAltNameExtension(nil, nil, nil, nil, sans, false) if err != nil { t.Fatal(err) } // ok extended sans and extension extendedSANsAndExtensionsTemplate := fmt.Sprintf(`{ "subject": {{ toJson .Subject }}, "sans": {{ toJson .SANs }}, "extensions": [ {"id":"2.5.29.17", "value":"%s"} ] }`, base64.StdEncoding.EncodeToString(extendedSANsExtension.Value)) // ok permanent identifier template permanentIdentifierTemplate := `{ "subject": {{ toJson .Subject }}, "sans": [{ "type": "permanentIdentifier", "value": {{ toJson .Subject.CommonName }} }] }` permanentIdentifierTemplateExtension, err := createSubjectAltNameExtension(nil, nil, nil, nil, []SubjectAlternativeName{ {Type: PermanentIdentifierType, Value: "123456789"}, }, false) if err != nil { t.Fatal(err) } // fail extended sans failExtendedSANs := CreateTemplateData("123456789", nil) failExtendedSANs.SetSubjectAlternativeNames(SubjectAlternativeName{Type: "badType", Value: "foo.com"}) type args struct { signer crypto.Signer opts []Option } tests := []struct { name string args args want *CertificateRequest wantErr bool }{ {"ok simple", args{signer, []Option{}}, &CertificateRequest{ PublicKey: signer.Public(), Signer: signer, }, false}, {"ok default", args{signer, []Option{ WithTemplate(DefaultCertificateRequestTemplate, CreateTemplateData("commonName", []string{"foo.com", "3.14.15.92", "root@foo.com", "mailto:root@foo.com"})), }}, &CertificateRequest{ Subject: Subject{CommonName: "commonName"}, SANs: []SubjectAlternativeName{ {Type: "dns", Value: "foo.com"}, {Type: "ip", Value: "3.14.15.92"}, {Type: "email", Value: "root@foo.com"}, {Type: "uri", Value: "mailto:root@foo.com"}, }, PublicKey: signer.Public(), Signer: signer, }, false}, {"ok extended sans", args{signer, []Option{ WithTemplate(DefaultCertificateRequestTemplate, extendedSANs), }}, &CertificateRequest{ Subject: Subject{CommonName: "123456789"}, SANs: []SubjectAlternativeName{ {Type: "dns", Value: "foo.com"}, {Type: "email", Value: "root@foo.com"}, {Type: "ip", Value: "3.14.15.92"}, {Type: "uri", Value: "mailto:root@foo.com"}, {Type: "permanentIdentifier", Value: "123456789"}, }, Extensions: []Extension{extendedSANsExtension}, PublicKey: signer.Public(), Signer: signer, }, false}, {"ok extended sans and extension", args{signer, []Option{ WithTemplate(extendedSANsAndExtensionsTemplate, extendedSANs), }}, &CertificateRequest{ Subject: Subject{CommonName: "123456789"}, SANs: []SubjectAlternativeName{ {Type: "dns", Value: "foo.com"}, {Type: "email", Value: "root@foo.com"}, {Type: "ip", Value: "3.14.15.92"}, {Type: "uri", Value: "mailto:root@foo.com"}, {Type: "permanentIdentifier", Value: "123456789"}, }, Extensions: []Extension{extendedSANsExtension}, PublicKey: signer.Public(), Signer: signer, }, false}, {"ok permanent identifier template", args{signer, []Option{ WithTemplate(permanentIdentifierTemplate, CreateTemplateData("123456789", []string{})), }}, &CertificateRequest{ Subject: Subject{CommonName: "123456789"}, SANs: []SubjectAlternativeName{ {Type: "permanentIdentifier", Value: "123456789"}, }, Extensions: []Extension{permanentIdentifierTemplateExtension}, PublicKey: signer.Public(), Signer: signer, }, false}, {"fail apply", args{signer, []Option{WithTemplateFile("testdata/missing.tpl", NewTemplateData())}}, nil, true}, {"fail unmarshal", args{signer, []Option{WithTemplate("{badjson", NewTemplateData())}}, nil, true}, {"fail extended sans", args{signer, []Option{WithTemplate(DefaultCertificateRequestTemplate, failExtendedSANs)}}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := NewCertificateRequest(tt.args.signer, tt.args.opts...) if (err != nil) != tt.wantErr { t.Errorf("NewCertificateRequest() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("NewCertificateRequest() = %+v, want %v", got, tt.want) } }) } } func Test_newCertificateRequest(t *testing.T) { type args struct { cr *x509.CertificateRequest } tests := []struct { name string args args want *CertificateRequest }{ {"ok", args{&x509.CertificateRequest{}}, &CertificateRequest{}}, {"complex", args{&x509.CertificateRequest{ Extensions: []pkix.Extension{{Id: []int{1, 2, 3}, Critical: true, Value: []byte{3, 2, 1}}}, Subject: pkix.Name{Province: []string{"CA"}, CommonName: "commonName"}, DNSNames: []string{"foo"}, PublicKey: []byte("publicKey"), SignatureAlgorithm: x509.PureEd25519, }}, &CertificateRequest{ Extensions: []Extension{{ID: []int{1, 2, 3}, Critical: true, Value: []byte{3, 2, 1}}}, Subject: Subject{Province: []string{"CA"}, CommonName: "commonName"}, DNSNames: []string{"foo"}, PublicKey: []byte("publicKey"), SignatureAlgorithm: SignatureAlgorithm(x509.UnknownSignatureAlgorithm), }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := NewCertificateRequestFromX509(tt.args.cr); !reflect.DeepEqual(got, tt.want) { t.Errorf("newCertificateRequest() = %v, want %v", got, tt.want) } }) } } func TestCertificateRequest_GetCertificateRequest(t *testing.T) { signer := ed25519.PrivateKey{ 0x18, 0x92, 0xea, 0xa9, 0x63, 0xfc, 0x79, 0x6a, 0xaf, 0x04, 0xd8, 0x2a, 0x6e, 0xff, 0xc0, 0x7e, 0x67, 0x2d, 0x25, 0x48, 0xb0, 0x32, 0xe7, 0x53, 0xb1, 0xe8, 0x32, 0x01, 0x68, 0xab, 0xde, 0x08, 0x79, 0x0c, 0x43, 0x95, 0xdc, 0x3a, 0x1f, 0x99, 0xed, 0xd6, 0x85, 0xe2, 0x13, 0xf3, 0x4b, 0xf9, 0x71, 0xdb, 0x2b, 0x96, 0x8c, 0x4c, 0x7e, 0x68, 0xeb, 0x39, 0x80, 0xcf, 0xab, 0xc7, 0x55, 0x12, } badSigner := createBadSigner(t) expected := &x509.CertificateRequest{ Subject: pkix.Name{ CommonName: "commonName", Names: []pkix.AttributeTypeAndValue{{Type: []int{2, 5, 4, 3}, Value: "commonName"}}, }, DNSNames: []string{"foo.com", "bar.com"}, EmailAddresses: []string{"root@foo.com"}, IPAddresses: []net.IP{net.ParseIP("::1")}, URIs: []*url.URL{{Scheme: "mailto", Opaque: "root@foo.com"}}, Extensions: []pkix.Extension{ {Id: []int{2, 5, 29, 17}, Critical: false, Value: []byte{0x30, 0x47, 0x82, 0x7, 0x66, 0x6f, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x82, 0x7, 0x62, 0x61, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x81, 0xc, 0x72, 0x6f, 0x6f, 0x74, 0x40, 0x66, 0x6f, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x87, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x86, 0x13, 0x6d, 0x61, 0x69, 0x6c, 0x74, 0x6f, 0x3a, 0x72, 0x6f, 0x6f, 0x74, 0x40, 0x66, 0x6f, 0x6f, 0x2e, 0x63, 0x6f, 0x6d}}, {Id: []int{1, 2, 3, 4}, Critical: true, Value: []byte{0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72}}, }, SignatureAlgorithm: x509.PureEd25519, PublicKey: signer.Public(), PublicKeyAlgorithm: x509.Ed25519, Signature: []byte{ 0x2d, 0xa9, 0x79, 0x56, 0xb5, 0xf1, 0xbf, 0x1d, 0xe8, 0xf9, 0xb0, 0x62, 0x8c, 0xf2, 0x36, 0x2f, 0x6f, 0x2a, 0xba, 0xd3, 0xa5, 0xd4, 0xa8, 0x6b, 0x61, 0x5a, 0xea, 0xb1, 0xea, 0xdc, 0xe4, 0x50, 0xbf, 0x2, 0x1, 0xce, 0x50, 0x89, 0xcd, 0xe3, 0xfd, 0x7b, 0x94, 0x95, 0xbd, 0xb9, 0x5a, 0xe0, 0xe, 0x58, 0x76, 0x19, 0xee, 0xa4, 0x5, 0x24, 0x41, 0x5a, 0xc2, 0x22, 0x4b, 0xc1, 0x3a, 0x1, }, } type fields struct { Subject Subject DNSNames MultiString EmailAddresses MultiString IPAddresses MultiIP URIs MultiURL SANs []SubjectAlternativeName Extensions []Extension SignatureAlgorithm SignatureAlgorithm Signer crypto.Signer } tests := []struct { name string fields fields want *x509.CertificateRequest wantErr bool }{ {"ok", fields{ Subject: Subject{CommonName: "commonName"}, DNSNames: []string{"foo.com"}, EmailAddresses: []string{"root@foo.com"}, IPAddresses: []net.IP{net.ParseIP("::1")}, URIs: []*url.URL{{Scheme: "mailto", Opaque: "root@foo.com"}}, SANs: []SubjectAlternativeName{{Type: "dns", Value: "bar.com"}}, Extensions: []Extension{{ID: []int{1, 2, 3, 4}, Critical: true, Value: []byte("foobar")}}, SignatureAlgorithm: SignatureAlgorithm(x509.PureEd25519), Signer: signer, }, expected, false}, {"fail", fields{ Subject: Subject{CommonName: "commonName"}, DNSNames: []string{"foo.com"}, EmailAddresses: []string{"root@foo.com"}, IPAddresses: []net.IP{net.ParseIP("::1")}, URIs: []*url.URL{{Scheme: "mailto", Opaque: "root@foo.com"}}, SANs: []SubjectAlternativeName{{Type: "dns", Value: "bar.com"}}, Extensions: []Extension{{ID: []int{1, 2, 3, 4}, Critical: true, Value: []byte("foobar")}}, SignatureAlgorithm: SignatureAlgorithm(x509.PureEd25519), Signer: badSigner, }, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &CertificateRequest{ Subject: tt.fields.Subject, DNSNames: tt.fields.DNSNames, EmailAddresses: tt.fields.EmailAddresses, IPAddresses: tt.fields.IPAddresses, URIs: tt.fields.URIs, SANs: tt.fields.SANs, Extensions: tt.fields.Extensions, SignatureAlgorithm: tt.fields.SignatureAlgorithm, Signer: tt.fields.Signer, } got, err := c.GetCertificateRequest() if (err != nil) != tt.wantErr { t.Errorf("CertificateRequest.GetCertificateRequest() error = %v, wantErr %v", err, tt.wantErr) return } // Remove raw data if got != nil { got.Raw = nil got.RawSubject = nil got.RawSubjectPublicKeyInfo = nil got.RawTBSCertificateRequest = nil got.Attributes = nil //nolint:staticcheck // testing legacy behavior } if !reflect.DeepEqual(got, tt.want) { t.Errorf("CertificateRequest.GetCertificateRequest() = %v, want %v", got, tt.want) } }) } } func TestCertificateRequest_GetCertificate(t *testing.T) { type fields struct { Version int Subject Subject DNSNames MultiString EmailAddresses MultiString IPAddresses MultiIP URIs MultiURL Extensions []Extension PublicKey interface{} PublicKeyAlgorithm x509.PublicKeyAlgorithm Signature []byte SignatureAlgorithm SignatureAlgorithm } tests := []struct { name string fields fields want *Certificate }{ {"ok", fields{ Version: 2, Subject: Subject{CommonName: "foo"}, DNSNames: []string{"foo"}, EmailAddresses: []string{"foo@bar.com"}, IPAddresses: []net.IP{net.ParseIP("::1")}, URIs: []*url.URL{{Scheme: "https", Host: "foo.bar"}}, Extensions: []Extension{{ID: []int{1, 2, 3}, Critical: true, Value: []byte{3, 2, 1}}}, PublicKey: []byte("publicKey"), PublicKeyAlgorithm: x509.Ed25519, Signature: []byte("signature"), SignatureAlgorithm: SignatureAlgorithm(x509.PureEd25519), }, &Certificate{ Subject: Subject{CommonName: "foo"}, DNSNames: []string{"foo"}, EmailAddresses: []string{"foo@bar.com"}, IPAddresses: []net.IP{net.ParseIP("::1")}, URIs: []*url.URL{{Scheme: "https", Host: "foo.bar"}}, Extensions: []Extension{{ID: []int{1, 2, 3}, Critical: true, Value: []byte{3, 2, 1}}}, PublicKey: []byte("publicKey"), PublicKeyAlgorithm: x509.Ed25519, SignatureAlgorithm: SignatureAlgorithm(x509.UnknownSignatureAlgorithm), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &CertificateRequest{ Version: tt.fields.Version, Subject: tt.fields.Subject, DNSNames: tt.fields.DNSNames, EmailAddresses: tt.fields.EmailAddresses, IPAddresses: tt.fields.IPAddresses, URIs: tt.fields.URIs, Extensions: tt.fields.Extensions, PublicKey: tt.fields.PublicKey, PublicKeyAlgorithm: tt.fields.PublicKeyAlgorithm, Signature: tt.fields.Signature, SignatureAlgorithm: tt.fields.SignatureAlgorithm, } if got := c.GetCertificate(); !reflect.DeepEqual(got, tt.want) { t.Errorf("CertificateRequest.GetCertificate() = \n%#v, want \n%#v", got, tt.want) } }) } } func TestCertificateRequest_GetLeafCertificate(t *testing.T) { type fields struct { Version int Subject Subject DNSNames MultiString EmailAddresses MultiString IPAddresses MultiIP URIs MultiURL Extensions []Extension PublicKey interface{} PublicKeyAlgorithm x509.PublicKeyAlgorithm Signature []byte SignatureAlgorithm SignatureAlgorithm } tests := []struct { name string fields fields want *Certificate }{ {"ok", fields{ Version: 2, Subject: Subject{CommonName: "foo"}, DNSNames: []string{"foo"}, EmailAddresses: []string{"foo@bar.com"}, IPAddresses: []net.IP{net.ParseIP("::1")}, URIs: []*url.URL{{Scheme: "https", Host: "foo.bar"}}, Extensions: []Extension{{ID: []int{1, 2, 3}, Critical: true, Value: []byte{3, 2, 1}}}, PublicKey: []byte("publicKey"), PublicKeyAlgorithm: x509.Ed25519, Signature: []byte("signature"), SignatureAlgorithm: SignatureAlgorithm(x509.PureEd25519), }, &Certificate{ Subject: Subject{CommonName: "foo"}, DNSNames: []string{"foo"}, EmailAddresses: []string{"foo@bar.com"}, IPAddresses: []net.IP{net.ParseIP("::1")}, URIs: []*url.URL{{Scheme: "https", Host: "foo.bar"}}, Extensions: []Extension{{ID: []int{1, 2, 3}, Critical: true, Value: []byte{3, 2, 1}}}, KeyUsage: KeyUsage(x509.KeyUsageDigitalSignature), ExtKeyUsage: ExtKeyUsage([]x509.ExtKeyUsage{ x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth, }), PublicKey: []byte("publicKey"), PublicKeyAlgorithm: x509.Ed25519, SignatureAlgorithm: SignatureAlgorithm(x509.UnknownSignatureAlgorithm), }, }, {"rsa", fields{ Version: 2, Subject: Subject{CommonName: "foo"}, DNSNames: []string{"foo"}, EmailAddresses: []string{"foo@bar.com"}, IPAddresses: []net.IP{net.ParseIP("::1")}, URIs: []*url.URL{{Scheme: "https", Host: "foo.bar"}}, Extensions: []Extension{{ID: []int{1, 2, 3}, Critical: true, Value: []byte{3, 2, 1}}}, PublicKey: &rsa.PublicKey{}, PublicKeyAlgorithm: x509.RSA, Signature: []byte("signature"), SignatureAlgorithm: SignatureAlgorithm(x509.SHA256WithRSA), }, &Certificate{ Subject: Subject{CommonName: "foo"}, DNSNames: []string{"foo"}, EmailAddresses: []string{"foo@bar.com"}, IPAddresses: []net.IP{net.ParseIP("::1")}, URIs: []*url.URL{{Scheme: "https", Host: "foo.bar"}}, Extensions: []Extension{{ID: []int{1, 2, 3}, Critical: true, Value: []byte{3, 2, 1}}}, KeyUsage: KeyUsage(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment), ExtKeyUsage: ExtKeyUsage([]x509.ExtKeyUsage{ x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth, }), PublicKey: &rsa.PublicKey{}, PublicKeyAlgorithm: x509.RSA, SignatureAlgorithm: SignatureAlgorithm(x509.UnknownSignatureAlgorithm), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &CertificateRequest{ Version: tt.fields.Version, Subject: tt.fields.Subject, DNSNames: tt.fields.DNSNames, EmailAddresses: tt.fields.EmailAddresses, IPAddresses: tt.fields.IPAddresses, URIs: tt.fields.URIs, Extensions: tt.fields.Extensions, PublicKey: tt.fields.PublicKey, PublicKeyAlgorithm: tt.fields.PublicKeyAlgorithm, Signature: tt.fields.Signature, SignatureAlgorithm: tt.fields.SignatureAlgorithm, } if got := c.GetLeafCertificate(); !reflect.DeepEqual(got, tt.want) { t.Errorf("CertificateRequest.GetLeafCertificate() = %v, want %v", got, tt.want) } }) } } func TestCreateCertificateRequest(t *testing.T) { pub, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } badSigner := createBadSigner(t) type args struct { commonName string sans []string signer crypto.Signer } tests := []struct { name string args args want *x509.CertificateRequest wantErr bool }{ {"ok", args{"foo.bar", []string{"foo.bar", "john@doe.com", "uri:uuid:48da8308-b399-4748-861f-cb418362f820", "1.2.3.4"}, priv}, &x509.CertificateRequest{ Version: 0, Subject: pkix.Name{ CommonName: "foo.bar", Names: []pkix.AttributeTypeAndValue{{Type: asn1.ObjectIdentifier{2, 5, 4, 3}, Value: "foo.bar"}}, }, DNSNames: []string{"foo.bar"}, EmailAddresses: []string{"john@doe.com"}, IPAddresses: []net.IP{{1, 2, 3, 4}}, URIs: []*url.URL{{Scheme: "uri", Opaque: "uuid:48da8308-b399-4748-861f-cb418362f820"}}, PublicKey: pub, SignatureAlgorithm: x509.PureEd25519, PublicKeyAlgorithm: x509.Ed25519, }, false}, {"fail ", args{"foo.bar", []string{"foo.bar"}, badSigner}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := CreateCertificateRequest(tt.args.commonName, tt.args.sans, tt.args.signer) if (err != nil) != tt.wantErr { t.Errorf("CreateCertificateRequest() error = %v, wantErr %v", err, tt.wantErr) return } if !tt.wantErr { if err := got.CheckSignature(); err != nil { t.Errorf("CheckSignature() error = %v", err) return } tt.want.Raw = got.Raw tt.want.RawSubject = got.RawSubject tt.want.RawSubjectPublicKeyInfo = got.RawSubjectPublicKeyInfo tt.want.RawTBSCertificateRequest = got.RawTBSCertificateRequest tt.want.Attributes = got.Attributes //nolint:staticcheck // testing legacy behavior tt.want.Extensions = got.Extensions tt.want.Signature = got.Signature } if !reflect.DeepEqual(got, tt.want) { t.Errorf("CreateCertificateRequest() = %v, want %v", got, tt.want) } }) } } golang-step-crypto-0.24.0/x509util/certificate_test.go000066400000000000000000000565041437037643100226300ustar00rootroot00000000000000package x509util import ( "bytes" "crypto" "crypto/ed25519" "crypto/rand" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/pem" "fmt" "io" "math/big" "net" "net/url" "reflect" "testing" "time" ) func createCertificateRequest(t *testing.T, commonName string, sans []string) (*x509.CertificateRequest, crypto.Signer) { dnsNames, ips, emails, uris := SplitSANs(sans) t.Helper() _, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } asn1Data, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{ Subject: pkix.Name{CommonName: commonName}, DNSNames: dnsNames, IPAddresses: ips, EmailAddresses: emails, URIs: uris, SignatureAlgorithm: x509.PureEd25519, }, priv) if err != nil { t.Fatal(err) } cr, err := x509.ParseCertificateRequest(asn1Data) if err != nil { t.Fatal(err) } return cr, priv } func createIssuerCertificate(t *testing.T, commonName string) (*x509.Certificate, crypto.Signer) { t.Helper() now := time.Now() pub, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } subjectKeyID, err := generateSubjectKeyID(pub) if err != nil { t.Fatal(err) } sn, err := generateSerialNumber() if err != nil { t.Fatal(err) } template := &x509.Certificate{ IsCA: true, NotBefore: now, NotAfter: now.Add(time.Hour), KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, BasicConstraintsValid: true, MaxPathLen: 0, MaxPathLenZero: true, Issuer: pkix.Name{CommonName: "issuer"}, Subject: pkix.Name{CommonName: "issuer"}, SerialNumber: sn, SubjectKeyId: subjectKeyID, } asn1Data, err := x509.CreateCertificate(rand.Reader, template, template, pub, priv) if err != nil { t.Fatal(err) } crt, err := x509.ParseCertificate(asn1Data) if err != nil { t.Fatal(err) } return crt, priv } type badSigner struct { pub crypto.PublicKey } func createBadSigner(t *testing.T) *badSigner { pub, _, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } return &badSigner{ pub: pub, } } func (b *badSigner) Public() crypto.PublicKey { return b.pub } func (b *badSigner) Sign(random io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { return nil, fmt.Errorf("๐Ÿ’ฅ") } func TestNewCertificate(t *testing.T) { cr, priv := createCertificateRequest(t, "commonName", []string{"foo.com", "root@foo.com"}) crBadSignateure, _ := createCertificateRequest(t, "fail", []string{"foo.com"}) crBadSignateure.PublicKey = priv.Public() customSANsData := CreateTemplateData("commonName", nil) customSANsData.Set(SANsKey, []SubjectAlternativeName{ {Type: PermanentIdentifierType, Value: "123456"}, {Type: "1.2.3.4", Value: "utf8:otherName"}, }) badCustomSANsData := CreateTemplateData("commonName", nil) badCustomSANsData.Set(SANsKey, []SubjectAlternativeName{ {Type: "1.2.3.4", Value: "int:not-an-int"}, }) ipNet := func(s string) *net.IPNet { _, ipNet, err := net.ParseCIDR(s) if err != nil { t.Fatal(err) } return ipNet } type args struct { cr *x509.CertificateRequest opts []Option } tests := []struct { name string args args want *Certificate wantErr bool }{ {"okSimple", args{cr, nil}, &Certificate{ Subject: Subject{CommonName: "commonName"}, DNSNames: []string{"foo.com"}, EmailAddresses: []string{"root@foo.com"}, KeyUsage: KeyUsage(x509.KeyUsageDigitalSignature), ExtKeyUsage: ExtKeyUsage([]x509.ExtKeyUsage{ x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth, }), Extensions: newExtensions(cr.Extensions), PublicKey: priv.Public(), PublicKeyAlgorithm: x509.Ed25519, SignatureAlgorithm: SignatureAlgorithm(x509.UnknownSignatureAlgorithm), }, false}, {"okDefaultTemplate", args{cr, []Option{WithTemplate(DefaultLeafTemplate, CreateTemplateData("commonName", []string{"foo.com"}))}}, &Certificate{ Subject: Subject{CommonName: "commonName"}, SANs: []SubjectAlternativeName{{Type: DNSType, Value: "foo.com"}}, KeyUsage: KeyUsage(x509.KeyUsageDigitalSignature), ExtKeyUsage: ExtKeyUsage([]x509.ExtKeyUsage{ x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth, }), PublicKey: priv.Public(), PublicKeyAlgorithm: x509.Ed25519, }, false}, {"okCustomSANs", args{cr, []Option{WithTemplate(DefaultLeafTemplate, customSANsData)}}, &Certificate{ Subject: Subject{CommonName: "commonName"}, SANs: []SubjectAlternativeName{ {Type: PermanentIdentifierType, Value: "123456"}, {Type: "1.2.3.4", Value: "utf8:otherName"}, }, Extensions: []Extension{{ ID: ObjectIdentifier{2, 5, 29, 17}, Critical: false, Value: []byte{48, 44, 160, 22, 6, 8, 43, 6, 1, 5, 5, 7, 8, 3, 160, 10, 48, 8, 12, 6, 49, 50, 51, 52, 53, 54, 160, 18, 6, 3, 42, 3, 4, 160, 11, 12, 9, 111, 116, 104, 101, 114, 78, 97, 109, 101}, }}, KeyUsage: KeyUsage(x509.KeyUsageDigitalSignature), ExtKeyUsage: ExtKeyUsage([]x509.ExtKeyUsage{ x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth, }), PublicKey: priv.Public(), PublicKeyAlgorithm: x509.Ed25519, }, false}, {"okExample", args{cr, []Option{WithTemplateFile("./testdata/example.tpl", TemplateData{ SANsKey: []SubjectAlternativeName{ {Type: "dns", Value: "foo.com"}, }, TokenKey: map[string]interface{}{ "iss": "https://iss", "sub": "sub", }, })}}, &Certificate{ Subject: Subject{CommonName: "commonName"}, SANs: []SubjectAlternativeName{{Type: DNSType, Value: "foo.com"}}, EmailAddresses: []string{"root@foo.com"}, URIs: []*url.URL{{Scheme: "https", Host: "iss", Fragment: "sub"}}, KeyUsage: KeyUsage(x509.KeyUsageDigitalSignature), ExtKeyUsage: ExtKeyUsage([]x509.ExtKeyUsage{ x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth, }), PublicKey: priv.Public(), PublicKeyAlgorithm: x509.Ed25519, }, false}, {"okFullSimple", args{cr, []Option{WithTemplateFile("./testdata/fullsimple.tpl", TemplateData{})}}, &Certificate{ Version: 3, Subject: Subject{CommonName: "subjectCommonName"}, SerialNumber: SerialNumber{big.NewInt(78187493520)}, Issuer: Issuer{CommonName: "issuerCommonName"}, DNSNames: []string{"doe.com"}, IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, EmailAddresses: []string{"jane@doe.com"}, URIs: []*url.URL{{Scheme: "https", Host: "doe.com"}}, SANs: []SubjectAlternativeName{{Type: DNSType, Value: "www.doe.com"}}, Extensions: []Extension{{ID: []int{1, 2, 3, 4}, Critical: true, Value: []byte("extension")}}, KeyUsage: KeyUsage(x509.KeyUsageDigitalSignature), ExtKeyUsage: ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}), UnknownExtKeyUsage: []asn1.ObjectIdentifier{[]int{1, 3, 6, 1, 4, 1, 44924, 1, 6}, []int{1, 3, 6, 1, 4, 1, 44924, 1, 7}}, SubjectKeyID: []byte("subjectKeyId"), AuthorityKeyID: []byte("authorityKeyId"), OCSPServer: []string{"https://ocsp.server"}, IssuingCertificateURL: []string{"https://ca.com"}, CRLDistributionPoints: []string{"https://ca.com/ca.crl"}, PolicyIdentifiers: PolicyIdentifiers{[]int{1, 2, 3, 4, 5, 6}}, BasicConstraints: &BasicConstraints{ IsCA: false, MaxPathLen: 0, }, NameConstraints: &NameConstraints{ Critical: true, PermittedDNSDomains: []string{"jane.doe.com"}, ExcludedDNSDomains: []string{"john.doe.com"}, PermittedIPRanges: []*net.IPNet{ipNet("127.0.0.1/32")}, ExcludedIPRanges: []*net.IPNet{ipNet("0.0.0.0/0")}, PermittedEmailAddresses: []string{"jane@doe.com"}, ExcludedEmailAddresses: []string{"john@doe.com"}, PermittedURIDomains: []string{"https://jane.doe.com"}, ExcludedURIDomains: []string{"https://john.doe.com"}, }, SignatureAlgorithm: SignatureAlgorithm(x509.PureEd25519), PublicKey: priv.Public(), PublicKeyAlgorithm: x509.Ed25519, }, false}, {"okOPCUA", args{cr, []Option{WithTemplateFile("./testdata/opcua.tpl", TemplateData{ SANsKey: []SubjectAlternativeName{ {Type: "dns", Value: "foo.com"}, }, TokenKey: map[string]interface{}{ "iss": "https://iss", "sub": "sub", }, })}}, &Certificate{ Subject: Subject{CommonName: ""}, SANs: []SubjectAlternativeName{{Type: DNSType, Value: "foo.com"}}, KeyUsage: KeyUsage(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageCertSign), BasicConstraints: &BasicConstraints{ IsCA: false, MaxPathLen: 0, }, ExtKeyUsage: ExtKeyUsage([]x509.ExtKeyUsage{ x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth, }), PublicKey: priv.Public(), PublicKeyAlgorithm: x509.Ed25519, }, false}, {"badSignature", args{crBadSignateure, nil}, nil, true}, {"failTemplate", args{cr, []Option{WithTemplate(`{{ fail "fatal error }}`, CreateTemplateData("commonName", []string{"foo.com"}))}}, nil, true}, {"missingTemplate", args{cr, []Option{WithTemplateFile("./testdata/missing.tpl", CreateTemplateData("commonName", []string{"foo.com"}))}}, nil, true}, {"badJson", args{cr, []Option{WithTemplate(`"this is not a json object"`, CreateTemplateData("commonName", []string{"foo.com"}))}}, nil, true}, {"failCustomSANs", args{cr, []Option{WithTemplate(DefaultLeafTemplate, badCustomSANsData)}}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := NewCertificate(tt.args.cr, tt.args.opts...) if (err != nil) != tt.wantErr { t.Errorf("NewCertificate() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("NewCertificate() = %v, want %v", got, tt.want) } }) } } func TestCertificate_GetCertificate(t *testing.T) { type fields struct { Version int Subject Subject Issuer Issuer SerialNumber SerialNumber DNSNames MultiString EmailAddresses MultiString IPAddresses MultiIP URIs MultiURL SANs []SubjectAlternativeName Extensions []Extension KeyUsage KeyUsage ExtKeyUsage ExtKeyUsage UnknownExtKeyUsage UnknownExtKeyUsage SubjectKeyID SubjectKeyID AuthorityKeyID AuthorityKeyID OCSPServer OCSPServer IssuingCertificateURL IssuingCertificateURL CRLDistributionPoints CRLDistributionPoints PolicyIdentifiers PolicyIdentifiers BasicConstraints *BasicConstraints NameConstraints *NameConstraints SignatureAlgorithm SignatureAlgorithm PublicKeyAlgorithm x509.PublicKeyAlgorithm PublicKey interface{} } tests := []struct { name string fields fields want *x509.Certificate }{ {"ok", fields{ Version: 3, Subject: Subject{CommonName: "commonName", Organization: []string{"smallstep"}}, Issuer: Issuer{CommonName: "issuer", Organization: []string{"smallstep"}}, SerialNumber: SerialNumber{big.NewInt(123)}, DNSNames: []string{"foo.bar"}, EmailAddresses: []string{"root@foo.com"}, IPAddresses: []net.IP{net.ParseIP("::1")}, URIs: []*url.URL{{Scheme: "mailto", Opaque: "root@foo.com"}}, SANs: []SubjectAlternativeName{ {Type: DNSType, Value: "www.foo.bar"}, {Type: IPType, Value: "127.0.0.1"}, {Type: EmailType, Value: "admin@foo.com"}, {Type: URIType, Value: "mailto:admin@foo.com"}, }, Extensions: []Extension{{ID: []int{1, 2, 3, 4}, Critical: true, Value: []byte("custom extension")}}, KeyUsage: KeyUsage(x509.KeyUsageDigitalSignature), ExtKeyUsage: ExtKeyUsage([]x509.ExtKeyUsage{ x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth, }), UnknownExtKeyUsage: []asn1.ObjectIdentifier{[]int{1, 3, 6, 1, 4, 1, 44924, 1, 6}, []int{1, 3, 6, 1, 4, 1, 44924, 1, 7}}, SubjectKeyID: []byte("subject-key-id"), AuthorityKeyID: []byte("authority-key-id"), OCSPServer: []string{"https://oscp.server"}, IssuingCertificateURL: []string{"https://ca.com"}, CRLDistributionPoints: []string{"https://ca.com/crl"}, PolicyIdentifiers: []asn1.ObjectIdentifier{[]int{1, 2, 3, 4}}, BasicConstraints: &BasicConstraints{IsCA: true, MaxPathLen: 0}, NameConstraints: &NameConstraints{PermittedDNSDomains: []string{"foo.bar"}}, SignatureAlgorithm: SignatureAlgorithm(x509.PureEd25519), PublicKeyAlgorithm: x509.Ed25519, PublicKey: ed25519.PublicKey("public key"), }, &x509.Certificate{ Version: 0, Subject: pkix.Name{CommonName: "commonName", Organization: []string{"smallstep"}}, Issuer: pkix.Name{}, SerialNumber: big.NewInt(123), DNSNames: []string{"foo.bar", "www.foo.bar"}, EmailAddresses: []string{"root@foo.com", "admin@foo.com"}, IPAddresses: []net.IP{net.ParseIP("::1"), net.ParseIP("127.0.0.1")}, URIs: []*url.URL{{Scheme: "mailto", Opaque: "root@foo.com"}, {Scheme: "mailto", Opaque: "admin@foo.com"}}, ExtraExtensions: []pkix.Extension{{Id: []int{1, 2, 3, 4}, Critical: true, Value: []byte("custom extension")}}, KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{ x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth, }, UnknownExtKeyUsage: []asn1.ObjectIdentifier{[]int{1, 3, 6, 1, 4, 1, 44924, 1, 6}, []int{1, 3, 6, 1, 4, 1, 44924, 1, 7}}, SubjectKeyId: []byte("subject-key-id"), AuthorityKeyId: []byte("authority-key-id"), OCSPServer: []string{"https://oscp.server"}, IssuingCertificateURL: []string{"https://ca.com"}, CRLDistributionPoints: []string{"https://ca.com/crl"}, PolicyIdentifiers: []asn1.ObjectIdentifier{[]int{1, 2, 3, 4}}, IsCA: true, MaxPathLen: 0, MaxPathLenZero: true, BasicConstraintsValid: true, PermittedDNSDomains: []string{"foo.bar"}, SignatureAlgorithm: x509.PureEd25519, PublicKeyAlgorithm: x509.Ed25519, PublicKey: ed25519.PublicKey("public key"), }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Certificate{ Version: tt.fields.Version, Subject: tt.fields.Subject, Issuer: tt.fields.Issuer, SerialNumber: tt.fields.SerialNumber, DNSNames: tt.fields.DNSNames, EmailAddresses: tt.fields.EmailAddresses, IPAddresses: tt.fields.IPAddresses, URIs: tt.fields.URIs, SANs: tt.fields.SANs, Extensions: tt.fields.Extensions, KeyUsage: tt.fields.KeyUsage, ExtKeyUsage: tt.fields.ExtKeyUsage, UnknownExtKeyUsage: tt.fields.UnknownExtKeyUsage, SubjectKeyID: tt.fields.SubjectKeyID, AuthorityKeyID: tt.fields.AuthorityKeyID, OCSPServer: tt.fields.OCSPServer, IssuingCertificateURL: tt.fields.IssuingCertificateURL, CRLDistributionPoints: tt.fields.CRLDistributionPoints, PolicyIdentifiers: tt.fields.PolicyIdentifiers, BasicConstraints: tt.fields.BasicConstraints, NameConstraints: tt.fields.NameConstraints, SignatureAlgorithm: tt.fields.SignatureAlgorithm, PublicKeyAlgorithm: tt.fields.PublicKeyAlgorithm, PublicKey: tt.fields.PublicKey, } if got := c.GetCertificate(); !reflect.DeepEqual(got, tt.want) { t.Errorf("Certificate.GetCertificate() = %v, want %v", got, tt.want) } }) } } func TestCreateCertificate(t *testing.T) { iss, issPriv := createIssuerCertificate(t, "issuer") mustSerialNumber := func() *big.Int { sn, err := generateSerialNumber() if err != nil { t.Fatal(err) } return sn } mustSubjectKeyID := func(pub crypto.PublicKey) []byte { b, err := generateSubjectKeyID(pub) if err != nil { t.Fatal(err) } return b } cr1, priv1 := createCertificateRequest(t, "commonName", []string{"foo.com"}) crt1 := NewCertificateRequestFromX509(cr1).GetLeafCertificate().GetCertificate() crt1.SerialNumber = mustSerialNumber() crt1.SubjectKeyId = mustSubjectKeyID(priv1.Public()) cr2, priv2 := createCertificateRequest(t, "commonName", []string{"foo.com"}) crt2 := NewCertificateRequestFromX509(cr2).GetLeafCertificate().GetCertificate() crt2.SerialNumber = mustSerialNumber() cr3, priv3 := createCertificateRequest(t, "commonName", []string{"foo.com"}) crt3 := NewCertificateRequestFromX509(cr3).GetLeafCertificate().GetCertificate() crt3.SubjectKeyId = mustSubjectKeyID(priv1.Public()) cr4, priv4 := createCertificateRequest(t, "commonName", []string{"foo.com"}) crt4 := NewCertificateRequestFromX509(cr4).GetLeafCertificate().GetCertificate() cr5, _ := createCertificateRequest(t, "commonName", []string{"foo.com"}) crt5 := NewCertificateRequestFromX509(cr5).GetLeafCertificate().GetCertificate() badSigner := createBadSigner(t) type args struct { template *x509.Certificate parent *x509.Certificate pub crypto.PublicKey signer crypto.Signer } tests := []struct { name string args args wantErr bool }{ {"ok", args{crt1, iss, priv1.Public(), issPriv}, false}, {"okNoSubjectKeyID", args{crt2, iss, priv2.Public(), issPriv}, false}, {"okNoSerialNumber", args{crt3, iss, priv3.Public(), issPriv}, false}, {"okNothing", args{crt4, iss, priv4.Public(), issPriv}, false}, {"failSubjectKeyID", args{crt5, iss, []byte("foo"), issPriv}, true}, {"failSign", args{crt1, iss, priv1.Public(), badSigner}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := CreateCertificate(tt.args.template, tt.args.parent, tt.args.pub, tt.args.signer) if (err != nil) != tt.wantErr { t.Errorf("CreateCertificate() error = %v, wantErr %v", err, tt.wantErr) return } if !tt.wantErr { if err := got.CheckSignatureFrom(iss); err != nil { t.Errorf("Certificate.CheckSignatureFrom() error = %v", err) } } }) } } func TestCreateCertificate_criticalSANs(t *testing.T) { cr, _ := createCertificateRequest(t, "", []string{"foo.com"}) iss, issPriv := createIssuerCertificate(t, "issuer") type args struct { cr *x509.CertificateRequest opts []Option } tests := []struct { name string args args }{ {"okNoOptions", args{cr, nil}}, {"okDefaultLeafTemplate", args{cr, []Option{WithTemplate(DefaultLeafTemplate, CreateTemplateData("", []string{"foo.com"}))}}}, {"okCertificateRequestTemplate", args{cr, []Option{WithTemplate(CertificateRequestTemplate, CreateTemplateData("", []string{"foo.com"}))}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cert, err := NewCertificate(cr, tt.args.opts...) if err != nil { t.Fatalf("NewCertificate() error = %v", err) } template := cert.GetCertificate() got, err := CreateCertificate(template, iss, template.PublicKey, issPriv) if err != nil { t.Fatalf("Certificate.CheckSignatureFrom() error = %v", err) } if err := got.CheckSignatureFrom(iss); err != nil { t.Fatalf("Certificate.CheckSignatureFrom() error = %v", err) } asn1Subject, err := asn1.Marshal(got.Subject.ToRDNSequence()) if err != nil { t.Fatalf("asn1.Marshal() error = %v", err) } if bytes.Equal(asn1Subject, emptyASN1Subject) { for _, ext := range got.Extensions { if ext.Id.Equal(oidExtensionSubjectAltName) && !ext.Critical { t.Errorf("Extension %s is not critical: %v", ext.Id, ext) } } } }) } } func TestCreateCertificateTemplate(t *testing.T) { cr1, _ := createCertificateRequest(t, "commonName", []string{"doe.com", "jane@doe.com", "1.2.3.4", "urn:uuid:2bbe86fc-a35e-4c68-a5cb-cb1060f57629"}) cr2, _ := createCertificateRequest(t, "", []string{"doe.com"}) cr3, _ := createCertificateRequest(t, "commonName", []string{}) fail, _ := createCertificateRequest(t, "commonName", []string{"doe.com"}) fail.Signature = []byte{1, 2, 3, 4} type args struct { cr *x509.CertificateRequest } tests := []struct { name string args args want *x509.Certificate wantErr bool }{ {"ok", args{cr1}, &x509.Certificate{ Subject: pkix.Name{ CommonName: "commonName", Names: []pkix.AttributeTypeAndValue{{Type: asn1.ObjectIdentifier{2, 5, 4, 3}, Value: "commonName"}}, }, PublicKey: cr1.PublicKey, PublicKeyAlgorithm: cr1.PublicKeyAlgorithm, ExtraExtensions: []pkix.Extension{ {Id: oidExtensionSubjectAltName, Critical: false, Value: cr1.Extensions[0].Value}, }, DNSNames: []string{"doe.com"}, EmailAddresses: []string{"jane@doe.com"}, IPAddresses: []net.IP{net.ParseIP("1.2.3.4").To4()}, URIs: []*url.URL{{Scheme: "urn", Opaque: "uuid:2bbe86fc-a35e-4c68-a5cb-cb1060f57629"}}, }, false}, {"ok critical", args{cr2}, &x509.Certificate{ Subject: pkix.Name{}, PublicKey: cr2.PublicKey, PublicKeyAlgorithm: cr3.PublicKeyAlgorithm, ExtraExtensions: []pkix.Extension{ {Id: oidExtensionSubjectAltName, Critical: true, Value: cr2.Extensions[0].Value}, }, DNSNames: []string{"doe.com"}, }, false}, {"ok no extensions", args{cr3}, &x509.Certificate{ Subject: pkix.Name{ CommonName: "commonName", Names: []pkix.AttributeTypeAndValue{{Type: asn1.ObjectIdentifier{2, 5, 4, 3}, Value: "commonName"}}, }, PublicKey: cr3.PublicKey, PublicKeyAlgorithm: cr3.PublicKeyAlgorithm, }, false}, {"fail", args{fail}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := CreateCertificateTemplate(tt.args.cr) if (err != nil) != tt.wantErr { t.Errorf("CreateCertificateTemplate() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("CreateCertificateTemplate() = %v, want %v", got, tt.want) } }) } } func TestCreateCertificate_debug(t *testing.T) { csr, _ := createCertificateRequest(t, "rocket", nil) iss, issPriv := createIssuerCertificate(t, "issuer") data := CreateTemplateData("rocket", nil) data.Set(SANsKey, []SubjectAlternativeName{ {Type: DirectoryNameType, ASN1Value: []byte(`{"country":"US","organization":"ACME","commonName":"rocket"}`)}, }) tests := []struct { name string sans []SubjectAlternativeName }{ {"directoryName", []SubjectAlternativeName{ {Type: DirectoryNameType, ASN1Value: []byte(`{"country":"US","organization":"ACME","commonName":"rocket"}`)}, }}, {"hardwareModuleName", []SubjectAlternativeName{ {Type: HardwareModuleNameType, ASN1Value: []byte(`{"type":"1.2.3.4","serialNumber":"MDEyMzQ1Njc4OQ=="}`)}, }}, {"permanentIdentifier", []SubjectAlternativeName{ {Type: PermanentIdentifierType, ASN1Value: []byte(`{"identifier":"0123456789","assigner":"1.2.3.4"}`)}, }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { data := CreateTemplateData("rocket", nil) data.Set(SANsKey, tt.sans) c, err := NewCertificate(csr, WithTemplate(DefaultLeafTemplate, data)) if err != nil { t.Fatal(err) } template := c.GetCertificate() cert, err := CreateCertificate(template, iss, csr.PublicKey, issPriv) if err != nil { t.Fatal(err) } t.Logf("\n%s", pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: cert.Raw, })) }) } } golang-step-crypto-0.24.0/x509util/certpool.go000066400000000000000000000022421437037643100211240ustar00rootroot00000000000000package x509util import ( "crypto/x509" "os" "path/filepath" "strings" "github.com/pkg/errors" ) // ReadCertPool loads a certificate pool from disk. The given path can be a // file, a directory, or a comma-separated list of files. func ReadCertPool(path string) (*x509.CertPool, error) { info, err := os.Stat(path) if err != nil && !os.IsNotExist(err) { return nil, errors.Wrap(err, "error reading cert pool") } var ( files []string pool = x509.NewCertPool() ) if info != nil && info.IsDir() { finfos, err := os.ReadDir(path) if err != nil { return nil, errors.Wrap(err, "error reading cert pool") } for _, finfo := range finfos { files = append(files, filepath.Join(path, finfo.Name())) } } else { files = strings.Split(path, ",") for i := range files { files[i] = strings.TrimSpace(files[i]) } } var found bool for _, f := range files { bytes, err := os.ReadFile(f) if err != nil { return nil, errors.Wrap(err, "error reading cert pool") } if ok := pool.AppendCertsFromPEM(bytes); ok { found = true } } if !found { return nil, errors.New("error reading cert pool: not certificates found") } return pool, nil } golang-step-crypto-0.24.0/x509util/certpool_test.go000066400000000000000000000033221437037643100221630ustar00rootroot00000000000000package x509util import ( "reflect" "testing" ) func TestReadCertPool(t *testing.T) { type args struct { path string } tests := []struct { name string args args wantSubjects [][]byte wantErr bool }{ {"ok dir", args{"testdata/capath"}, [][]byte{[]byte("0\x191\x170\x15\x06\x03U\x04\x03\x13\x0ESmallstep CA 1"), []byte("0\x191\x170\x15\x06\x03U\x04\x03\x13\x0ESmallstep CA 2")}, false}, {"ok dir 2", args{"testdata/capath2"}, [][]byte{[]byte("0\x191\x170\x15\x06\x03U\x04\x03\x13\x0ESmallstep CA 1"), []byte("0\x191\x170\x15\x06\x03U\x04\x03\x13\x0ESmallstep CA 2")}, false}, {"ok file", args{"testdata/capath/cert.pem"}, [][]byte{[]byte("0\x191\x170\x15\x06\x03U\x04\x03\x13\x0ESmallstep CA 1"), []byte("0\x191\x170\x15\x06\x03U\x04\x03\x13\x0ESmallstep CA 2")}, false}, {"ok files", args{"testdata/capath2/root1.crt,testdata/capath2/root2.crt"}, [][]byte{[]byte("0\x191\x170\x15\x06\x03U\x04\x03\x13\x0ESmallstep CA 1"), []byte("0\x191\x170\x15\x06\x03U\x04\x03\x13\x0ESmallstep CA 2")}, false}, {"no certs", args{"testdata/secrets"}, nil, true}, {"missing", args{"testdata/missing.pem"}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ReadCertPool(tt.args.path) if (err != nil) != tt.wantErr { t.Errorf("ReadCertPool() error = %v, wantErr %v", err, tt.wantErr) return } if got != nil { // nolint:staticcheck // there's no other way to compare two // certpools, https://github.com/golang/go/issues/46057 might // fix this. subjects := got.Subjects() if !reflect.DeepEqual(subjects, tt.wantSubjects) { t.Errorf("x509.CertPool.Subjects() got = %v, want %v", subjects, tt.wantSubjects) } } }) } } golang-step-crypto-0.24.0/x509util/extensions.go000066400000000000000000001007251437037643100215010ustar00rootroot00000000000000package x509util import ( "bytes" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/base64" "encoding/json" "fmt" "math/big" "net" "net/url" "strconv" "strings" "github.com/pkg/errors" ) func convertName(s string) string { return strings.ReplaceAll(strings.ToLower(s), "_", "") } // Names used for key usages. var ( KeyUsageDigitalSignature = convertName("DigitalSignature") KeyUsageContentCommitment = convertName("ContentCommitment") KeyUsageKeyEncipherment = convertName("KeyEncipherment") KeyUsageDataEncipherment = convertName("DataEncipherment") KeyUsageKeyAgreement = convertName("KeyAgreement") KeyUsageCertSign = convertName("CertSign") KeyUsageCRLSign = convertName("CRLSign") KeyUsageEncipherOnly = convertName("EncipherOnly") KeyUsageDecipherOnly = convertName("DecipherOnly") ) // Names used for extended key usages. var ( ExtKeyUsageAny = convertName("Any") ExtKeyUsageServerAuth = convertName("ServerAuth") ExtKeyUsageClientAuth = convertName("ClientAuth") ExtKeyUsageCodeSigning = convertName("CodeSigning") ExtKeyUsageEmailProtection = convertName("EmailProtection") ExtKeyUsageIPSECEndSystem = convertName("IPSECEndSystem") ExtKeyUsageIPSECTunnel = convertName("IPSECTunnel") ExtKeyUsageIPSECUser = convertName("IPSECUser") ExtKeyUsageTimeStamping = convertName("TimeStamping") ExtKeyUsageOCSPSigning = convertName("OCSPSigning") ExtKeyUsageMicrosoftServerGatedCrypto = convertName("MicrosoftServerGatedCrypto") ExtKeyUsageNetscapeServerGatedCrypto = convertName("NetscapeServerGatedCrypto") ExtKeyUsageMicrosoftCommercialCodeSigning = convertName("MicrosoftCommercialCodeSigning") ExtKeyUsageMicrosoftKernelCodeSigning = convertName("MicrosoftKernelCodeSigning") ) // Names used and SubjectAlternativeNames types. const ( AutoType = "auto" EmailType = "email" // also known as 'rfc822Name' in RFC 5280 DNSType = "dns" X400AddressType = "x400Address" DirectoryNameType = "dn" EDIPartyNameType = "ediPartyName" URIType = "uri" IPType = "ip" RegisteredIDType = "registeredID" PermanentIdentifierType = "permanentIdentifier" HardwareModuleNameType = "hardwareModuleName" ) //nolint:deadcode // ignore const ( // These type ids are defined in RFC 5280 page 36. nameTypeOtherName = 0 nameTypeEmail = 1 nameTypeDNS = 2 nameTypeX400 = 3 nameTypeDirectoryName = 4 nameTypeEDI = 5 nameTypeURI = 6 nameTypeIP = 7 nameTypeRegisteredID = 8 ) // sanTypeSeparator is used to set the type of otherName SANs. The format string // is "[type:]value", printable will be used as default type if none is // provided. const sanTypeSeparator = ":" // RFC 4043 - https://datatracker.ietf.org/doc/html/rfc4043 var oidPermanentIdentifier = []int{1, 3, 6, 1, 5, 5, 7, 8, 3} // RFC 4108 - https://www.rfc-editor.org/rfc/rfc4108 var oidHardwareModuleNameIdentifier = []int{1, 3, 6, 1, 5, 5, 7, 8, 4} // RFC 5280 - https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 // // OtherName ::= SEQUENCE { // type-id OBJECT IDENTIFIER, // value [0] EXPLICIT ANY DEFINED BY type-id } type otherName struct { TypeID asn1.ObjectIdentifier Value asn1.RawValue } // PermanentIdentifier is defined in RFC 4043 as an optional feature that // may be used by a CA to indicate that two or more certificates relate to the // same entity. // // In device attestation this SAN will contain the UDID (Unique Device // IDentifier) or serial number of the device. // // See https://tools.ietf.org/html/rfc4043 // // PermanentIdentifier ::= SEQUENCE { // identifierValue UTF8String OPTIONAL, // assigner OBJECT IDENTIFIER OPTIONAL // } type PermanentIdentifier struct { Identifier string `json:"identifier,omitempty"` Assigner ObjectIdentifier `json:"assigner,omitempty"` } type asn1PermanentIdentifier struct { IdentifierValue string `asn1:"utf8,optional"` Assigner asn1.ObjectIdentifier `asn1:"optional"` } func (p *PermanentIdentifier) ans1Type() asn1PermanentIdentifier { return asn1PermanentIdentifier{ IdentifierValue: p.Identifier, Assigner: asn1.ObjectIdentifier(p.Assigner), } } // HardwareModuleName is defined in RFC 4108 as an optional feature that by be // used to identify a hardware module. // // The OID defined for this SAN is "1.3.6.1.5.5.7.8.4". // // See https://www.rfc-editor.org/rfc/rfc4108#section-5 // // HardwareModuleName ::= SEQUENCE { // hwType OBJECT IDENTIFIER, // hwSerialNum OCTET STRING // } type HardwareModuleName struct { Type ObjectIdentifier `json:"type"` SerialNumber []byte `json:"serialNumber"` } type asn1HardwareModuleName struct { Type asn1.ObjectIdentifier SerialNumber []byte `asn1:"tag:4"` } func (h *HardwareModuleName) ans1Type() asn1HardwareModuleName { return asn1HardwareModuleName{ Type: asn1.ObjectIdentifier(h.Type), SerialNumber: h.SerialNumber, } } // Extension is the JSON representation of a raw X.509 extensions. type Extension struct { ID ObjectIdentifier `json:"id"` Critical bool `json:"critical"` Value []byte `json:"value"` } // newExtension creates an Extension from a standard pkix.Extension. func newExtension(e pkix.Extension) Extension { return Extension{ ID: ObjectIdentifier(e.Id), Critical: e.Critical, Value: e.Value, } } // newExtensions creates a slice of Extension from a slice of pkix.Extension. func newExtensions(extensions []pkix.Extension) []Extension { if extensions == nil { return nil } ret := make([]Extension, len(extensions)) for i, e := range extensions { ret[i] = newExtension(e) } return ret } // Set adds the extension to the given X509 certificate. func (e Extension) Set(c *x509.Certificate) { c.ExtraExtensions = append(c.ExtraExtensions, pkix.Extension{ Id: asn1.ObjectIdentifier(e.ID), Critical: e.Critical, Value: e.Value, }) } // ObjectIdentifier represents a JSON strings that unmarshals into an ASN1 // object identifier or OID. type ObjectIdentifier asn1.ObjectIdentifier // Equal reports whether o and v represent the same identifier. func (o ObjectIdentifier) Equal(v ObjectIdentifier) bool { if len(o) != len(v) { return false } for i := 0; i < len(o); i++ { if o[i] != v[i] { return false } } return true } // MarshalJSON implements the json.Marshaler interface and returns the string // version of the asn1.ObjectIdentifier. func (o ObjectIdentifier) MarshalJSON() ([]byte, error) { return json.Marshal(asn1.ObjectIdentifier(o).String()) } // UnmarshalJSON implements the json.Unmarshaler interface and coverts a strings // like "2.5.29.17" into an ASN1 object identifier. func (o *ObjectIdentifier) UnmarshalJSON(data []byte) error { s, err := unmarshalString(data) if err != nil { return err } oid, err := parseObjectIdentifier(s) if err != nil { return err } *o = ObjectIdentifier(oid) return nil } // SubjectAlternativeName represents a X.509 subject alternative name. Types // supported are "dns", "email", "ip", "uri". A special type "auto" or "" can be // used to try to guess the type of the value. // // ASN1Value can only be used for those types where the string value cannot // contain enough information to encode the value. type SubjectAlternativeName struct { Type string `json:"type"` Value string `json:"value"` ASN1Value json.RawMessage `json:"asn1Value,omitempty"` } // Set sets the subject alternative name in the given x509.Certificate. func (s SubjectAlternativeName) Set(c *x509.Certificate) { switch strings.ToLower(s.Type) { case DNSType: c.DNSNames = append(c.DNSNames, s.Value) case EmailType: c.EmailAddresses = append(c.EmailAddresses, s.Value) case IPType: // The validation of the IP would happen in the unmarshaling, but just // to be sure we are only adding valid IPs. if ip := net.ParseIP(s.Value); ip != nil { c.IPAddresses = append(c.IPAddresses, ip) } case URIType: if u, err := url.Parse(s.Value); err == nil { c.URIs = append(c.URIs, u) } case "", AutoType: dnsNames, ips, emails, uris := SplitSANs([]string{s.Value}) c.DNSNames = append(c.DNSNames, dnsNames...) c.IPAddresses = append(c.IPAddresses, ips...) c.EmailAddresses = append(c.EmailAddresses, emails...) c.URIs = append(c.URIs, uris...) default: panic(fmt.Sprintf("unsupported subject alternative name type %s", s.Type)) } } // RawValue returns the undecoded ASN.1 object for the SAN. func (s SubjectAlternativeName) RawValue() (asn1.RawValue, error) { var zero asn1.RawValue switch s.Type { case "", AutoType: // autotype requires us to find out what the type is. ip := net.ParseIP(s.Value) u, err := url.Parse(s.Value) switch { case ip != nil: return SubjectAlternativeName{Type: IPType, Value: s.Value}.RawValue() case err == nil && u.Scheme != "": return SubjectAlternativeName{Type: URIType, Value: s.Value}.RawValue() case strings.Contains(s.Value, "@"): return SubjectAlternativeName{Type: EmailType, Value: s.Value}.RawValue() default: return SubjectAlternativeName{Type: DNSType, Value: s.Value}.RawValue() } case EmailType: valid := isIA5String(s.Value) if !valid { return zero, fmt.Errorf("error converting %q to ia5", s.Value) } return asn1.RawValue{Tag: nameTypeEmail, Class: asn1.ClassContextSpecific, Bytes: []byte(s.Value)}, nil case DNSType: // use SanitizeName for DNS types because it will do some character // replacement and verify that its an acceptable hostname ia5String, err := SanitizeName(s.Value) if err != nil { return zero, errors.Wrapf(err, "error converting %q to ia5", s.Value) } return asn1.RawValue{Tag: nameTypeDNS, Class: asn1.ClassContextSpecific, Bytes: []byte(ia5String)}, nil case URIType: valid := isIA5String(s.Value) if !valid { return zero, fmt.Errorf("error converting %q to ia5", s.Value) } return asn1.RawValue{Tag: nameTypeURI, Class: asn1.ClassContextSpecific, Bytes: []byte(s.Value)}, nil case IPType: rawIP := net.ParseIP(s.Value) if rawIP == nil { return zero, fmt.Errorf("error converting %q to IP", s.Value) } ip := rawIP.To4() if ip == nil { ip = rawIP } return asn1.RawValue{Tag: nameTypeIP, Class: asn1.ClassContextSpecific, Bytes: ip}, nil case RegisteredIDType: if s.Value == "" { return zero, errors.New("error parsing RegisteredID SAN: blank value is not allowed") } oid, err := parseObjectIdentifier(s.Value) if err != nil { return zero, errors.Wrap(err, "error parsing OID for RegisteredID SAN") } rawBytes, err := asn1.MarshalWithParams(oid, "tag:8") if err != nil { return zero, errors.Wrap(err, "error marshaling RegisteredID SAN") } return asn1.RawValue{FullBytes: rawBytes}, nil case PermanentIdentifierType: var v PermanentIdentifier switch { case len(s.ASN1Value) != 0: if err := json.Unmarshal(s.ASN1Value, &v); err != nil { return zero, errors.Wrap(err, "error unmarshaling PermanentIdentifier SAN") } case s.Value != "": v.Identifier = s.Value default: // continue, both identifierValue and assigner are optional } otherName, err := marshalOtherName(oidPermanentIdentifier, v.ans1Type()) if err != nil { return zero, errors.Wrap(err, "error marshaling PermanentIdentifier SAN") } return otherName, nil case HardwareModuleNameType: if len(s.ASN1Value) == 0 { return zero, errors.New("error parsing HardwareModuleName SAN: empty asn1Value is not allowed") } var v HardwareModuleName if err := json.Unmarshal(s.ASN1Value, &v); err != nil { return zero, errors.Wrap(err, "error unmarshaling HardwareModuleName SAN") } otherName, err := marshalOtherName(oidHardwareModuleNameIdentifier, v.ans1Type()) if err != nil { return zero, errors.Wrap(err, "error marshaling HardwareModuleName SAN") } return otherName, nil case DirectoryNameType: if len(s.ASN1Value) == 0 { return zero, errors.New("error parsing DirectoryName SAN: empty asn1Value is not allowed") } var dn Name if err := json.Unmarshal(s.ASN1Value, &dn); err != nil { return zero, errors.Wrap(err, "error unmarshaling DirectoryName SAN") } rdn, err := asn1.Marshal(dn.goValue().ToRDNSequence()) if err != nil { return zero, errors.Wrap(err, "error marshaling DirectoryName SAN") } if bytes.Equal(rdn, emptyASN1Subject) { return zero, errors.New("error parsing DirectoryName SAN: empty or malformed ans1Value") } return asn1.RawValue{ Class: asn1.ClassContextSpecific, Tag: nameTypeDirectoryName, IsCompound: true, Bytes: rdn, }, nil case X400AddressType, EDIPartyNameType: return zero, fmt.Errorf("unimplemented SAN type %s", s.Type) default: // Assume otherName with a valid oid in type. oid, err := parseObjectIdentifier(s.Type) if err != nil { return zero, fmt.Errorf("unsupported SAN type %s", s.Type) } // The default type is printable, but if the value is prefixed with a // type, use that. var value, params = s.Value, "printable" if strings.Contains(value, sanTypeSeparator) { params = strings.Split(value, sanTypeSeparator)[0] value = value[len(params)+1:] } rawBytes, err := marshalExplicitValue(value, params) if err != nil { return zero, errors.Wrapf(err, "error marshaling ASN1 value %q", s.Value) } // use MarshalWithParams so we can set the context-specific tag - in this case 0 otherNameBytes, err := asn1.MarshalWithParams(otherName{ TypeID: oid, Value: asn1.RawValue{FullBytes: rawBytes}, }, "tag:0") if err != nil { return zero, errors.Wrap(err, "unable to Marshal otherName SAN") } return asn1.RawValue{FullBytes: otherNameBytes}, nil } } // marshalOtherName marshals an otherName field with the given oid and value and // returns the raw bytes to use. func marshalOtherName(oid asn1.ObjectIdentifier, value interface{}) (asn1.RawValue, error) { valueBytes, err := asn1.MarshalWithParams(value, "explicit,tag:0") if err != nil { return asn1.RawValue{}, err } b, err := asn1.MarshalWithParams(otherName{ TypeID: oid, Value: asn1.RawValue{FullBytes: valueBytes}, }, "tag:0") if err != nil { return asn1.RawValue{}, err } return asn1.RawValue{FullBytes: b}, nil } // marshalExplicitValue marshals the given value with given type and returns the // raw bytes to use. // // The return value value can be any type depending on the OID ASN supports a great // number of formats, but Golang's ans1 package supports much fewer -- for now // support anything the golang asn1 marshaller supports. // // See https://www.openssl.org/docs/man1.0.2/man3/ASN1_generate_nconf.html func marshalExplicitValue(value, typ string) ([]byte, error) { switch typ { case "int": i, err := strconv.Atoi(value) if err != nil { return nil, errors.Wrap(err, "invalid int value") } return asn1.MarshalWithParams(i, "explicit") case "oid": oid, err := parseObjectIdentifier(value) if err != nil { return nil, errors.Wrap(err, "invalid oid value") } return asn1.MarshalWithParams(oid, "explicit") case "raw": // the raw type accepts a base64 encoded byte array which is passed unaltered into the ASN // marshaller. By using this type users can add ASN1 data types manually into templates // to support some unsupported types like BMPString, Octet String, and others return base64.StdEncoding.DecodeString(value) case "utf8": if !isUTF8String(value) { return nil, fmt.Errorf("invalid utf8 value") } return asn1.MarshalWithParams(value, "explicit,utf8") case "ia5": if !isIA5String(value) { return nil, fmt.Errorf("invalid ia5 value") } return asn1.MarshalWithParams(value, "explicit,ia5") case "numeric": if !isNumericString(value) { return nil, fmt.Errorf("invalid numeric value") } return asn1.MarshalWithParams(value, "explicit,numeric") case "printable": if !isPrintableString(value, true, true) { return nil, fmt.Errorf("invalid printable value") } return asn1.MarshalWithParams(value, "explicit,printable") default: // if it's an unknown type, default to printable - but use the entire // value specified in case there is a semicolon in the value if !isPrintableString(value, true, true) { return nil, fmt.Errorf("invalid printable value") } return asn1.MarshalWithParams(value, "explicit,printable") } } // KeyUsage type represents the JSON array used to represent the key usages of a // X509 certificate. type KeyUsage x509.KeyUsage // Set sets the key usage to the given certificate. func (k KeyUsage) Set(c *x509.Certificate) { c.KeyUsage = x509.KeyUsage(k) } // UnmarshalJSON implements the json.Unmarshaler interface and coverts a string // or a list of strings into a key usage. func (k *KeyUsage) UnmarshalJSON(data []byte) error { ms, err := unmarshalMultiString(data) if err != nil { return err } *k = 0 for _, s := range ms { var ku x509.KeyUsage switch convertName(s) { case KeyUsageDigitalSignature: ku = x509.KeyUsageDigitalSignature case KeyUsageContentCommitment: ku = x509.KeyUsageContentCommitment case KeyUsageKeyEncipherment: ku = x509.KeyUsageKeyEncipherment case KeyUsageDataEncipherment: ku = x509.KeyUsageDataEncipherment case KeyUsageKeyAgreement: ku = x509.KeyUsageKeyAgreement case KeyUsageCertSign: ku = x509.KeyUsageCertSign case KeyUsageCRLSign: ku = x509.KeyUsageCRLSign case KeyUsageEncipherOnly: ku = x509.KeyUsageEncipherOnly case KeyUsageDecipherOnly: ku = x509.KeyUsageDecipherOnly default: return errors.Errorf("unsupported keyUsage %s", s) } *k |= KeyUsage(ku) } return nil } // MarshalJSON implements the json.Marshaler interface and converts a key usage // into a list of strings. func (k KeyUsage) MarshalJSON() ([]byte, error) { var usages []string if x509.KeyUsage(k)&x509.KeyUsageDigitalSignature != 0 { usages = append(usages, KeyUsageDigitalSignature) } if x509.KeyUsage(k)&x509.KeyUsageContentCommitment != 0 { usages = append(usages, KeyUsageContentCommitment) } if x509.KeyUsage(k)&x509.KeyUsageKeyEncipherment != 0 { usages = append(usages, KeyUsageKeyEncipherment) } if x509.KeyUsage(k)&x509.KeyUsageDataEncipherment != 0 { usages = append(usages, KeyUsageDataEncipherment) } if x509.KeyUsage(k)&x509.KeyUsageKeyAgreement != 0 { usages = append(usages, KeyUsageKeyAgreement) } if x509.KeyUsage(k)&x509.KeyUsageCertSign != 0 { usages = append(usages, KeyUsageCertSign) } if x509.KeyUsage(k)&x509.KeyUsageCRLSign != 0 { usages = append(usages, KeyUsageCRLSign) } if x509.KeyUsage(k)&x509.KeyUsageEncipherOnly != 0 { usages = append(usages, KeyUsageEncipherOnly) } if x509.KeyUsage(k)&x509.KeyUsageDecipherOnly != 0 { usages = append(usages, KeyUsageDecipherOnly) } if len(usages) == 0 && k != 0 { return nil, fmt.Errorf("cannot marshal key usage %v", k) } return json.Marshal(usages) } // ExtKeyUsage represents a JSON array of extended key usages. type ExtKeyUsage []x509.ExtKeyUsage // Set sets the extended key usages in the given certificate. func (k ExtKeyUsage) Set(c *x509.Certificate) { c.ExtKeyUsage = []x509.ExtKeyUsage(k) } // UnmarshalJSON implements the json.Unmarshaler interface and coverts a string // or a list of strings into a list of extended key usages. func (k *ExtKeyUsage) UnmarshalJSON(data []byte) error { ms, err := unmarshalMultiString(data) if err != nil { return err } eku := make([]x509.ExtKeyUsage, len(ms)) for i, s := range ms { var ku x509.ExtKeyUsage switch convertName(s) { case ExtKeyUsageAny: ku = x509.ExtKeyUsageAny case ExtKeyUsageServerAuth: ku = x509.ExtKeyUsageServerAuth case ExtKeyUsageClientAuth: ku = x509.ExtKeyUsageClientAuth case ExtKeyUsageCodeSigning: ku = x509.ExtKeyUsageCodeSigning case ExtKeyUsageEmailProtection: ku = x509.ExtKeyUsageEmailProtection case ExtKeyUsageIPSECEndSystem: ku = x509.ExtKeyUsageIPSECEndSystem case ExtKeyUsageIPSECTunnel: ku = x509.ExtKeyUsageIPSECTunnel case ExtKeyUsageIPSECUser: ku = x509.ExtKeyUsageIPSECUser case ExtKeyUsageTimeStamping: ku = x509.ExtKeyUsageTimeStamping case ExtKeyUsageOCSPSigning: ku = x509.ExtKeyUsageOCSPSigning case ExtKeyUsageMicrosoftServerGatedCrypto: ku = x509.ExtKeyUsageMicrosoftServerGatedCrypto case ExtKeyUsageNetscapeServerGatedCrypto: ku = x509.ExtKeyUsageNetscapeServerGatedCrypto case ExtKeyUsageMicrosoftCommercialCodeSigning: ku = x509.ExtKeyUsageMicrosoftCommercialCodeSigning case ExtKeyUsageMicrosoftKernelCodeSigning: ku = x509.ExtKeyUsageMicrosoftKernelCodeSigning default: return errors.Errorf("unsupported extKeyUsage %s", s) } eku[i] = ku } *k = ExtKeyUsage(eku) return nil } // MarshalJSON implements the json.Marshaler interface and converts a list of // extended key usages to a list of strings func (k ExtKeyUsage) MarshalJSON() ([]byte, error) { usages := make([]string, len(k)) for i, eku := range k { switch eku { case x509.ExtKeyUsageAny: usages[i] = ExtKeyUsageAny case x509.ExtKeyUsageServerAuth: usages[i] = ExtKeyUsageServerAuth case x509.ExtKeyUsageClientAuth: usages[i] = ExtKeyUsageClientAuth case x509.ExtKeyUsageCodeSigning: usages[i] = ExtKeyUsageCodeSigning case x509.ExtKeyUsageEmailProtection: usages[i] = ExtKeyUsageEmailProtection case x509.ExtKeyUsageIPSECEndSystem: usages[i] = ExtKeyUsageIPSECEndSystem case x509.ExtKeyUsageIPSECTunnel: usages[i] = ExtKeyUsageIPSECTunnel case x509.ExtKeyUsageIPSECUser: usages[i] = ExtKeyUsageIPSECUser case x509.ExtKeyUsageTimeStamping: usages[i] = ExtKeyUsageTimeStamping case x509.ExtKeyUsageOCSPSigning: usages[i] = ExtKeyUsageOCSPSigning case x509.ExtKeyUsageMicrosoftServerGatedCrypto: usages[i] = ExtKeyUsageMicrosoftServerGatedCrypto case x509.ExtKeyUsageNetscapeServerGatedCrypto: usages[i] = ExtKeyUsageNetscapeServerGatedCrypto case x509.ExtKeyUsageMicrosoftCommercialCodeSigning: usages[i] = ExtKeyUsageMicrosoftCommercialCodeSigning case x509.ExtKeyUsageMicrosoftKernelCodeSigning: usages[i] = ExtKeyUsageMicrosoftKernelCodeSigning default: return nil, fmt.Errorf("unsupported extKeyUsage %v", eku) } } return json.Marshal(usages) } // UnknownExtKeyUsage represents the list of OIDs of extended key usages unknown // to crypto/x509. type UnknownExtKeyUsage MultiObjectIdentifier // MarshalJSON implements the json.Marshaler interface in UnknownExtKeyUsage. func (u UnknownExtKeyUsage) MarshalJSON() ([]byte, error) { return MultiObjectIdentifier(u).MarshalJSON() } // UnmarshalJSON implements the json.Unmarshaler interface in UnknownExtKeyUsage. func (u *UnknownExtKeyUsage) UnmarshalJSON(data []byte) error { var v MultiObjectIdentifier if err := json.Unmarshal(data, &v); err != nil { return errors.Wrap(err, "error unmarshaling json") } *u = UnknownExtKeyUsage(v) return nil } // Set sets the policy identifiers to the given certificate. func (u UnknownExtKeyUsage) Set(c *x509.Certificate) { c.UnknownExtKeyUsage = u } // SubjectKeyID represents the binary value of the subject key identifier // extension, this should be the SHA-1 hash of the public key. In JSON this // value should be a base64-encoded string, and in most cases it should not be // set because it will be automatically generated. type SubjectKeyID []byte // Set sets the subject key identifier to the given certificate. func (id SubjectKeyID) Set(c *x509.Certificate) { c.SubjectKeyId = id } // AuthorityKeyID represents the binary value of the authority key identifier // extension. It should be the subject key identifier of the parent certificate. // In JSON this value should be a base64-encoded string, and in most cases it // should not be set, as it will be automatically provided. type AuthorityKeyID []byte // Set sets the authority key identifier to the given certificate. func (id AuthorityKeyID) Set(c *x509.Certificate) { c.AuthorityKeyId = id } // OCSPServer contains the list of OSCP servers that will be encoded in the // authority information access extension. type OCSPServer MultiString // UnmarshalJSON implements the json.Unmarshaler interface in OCSPServer. func (o *OCSPServer) UnmarshalJSON(data []byte) error { ms, err := unmarshalMultiString(data) if err != nil { return err } *o = ms return nil } // Set sets the list of OSCP servers to the given certificate. func (o OCSPServer) Set(c *x509.Certificate) { c.OCSPServer = o } // IssuingCertificateURL contains the list of the issuing certificate url that // will be encoded in the authority information access extension. type IssuingCertificateURL MultiString // UnmarshalJSON implements the json.Unmarshaler interface in IssuingCertificateURL. func (u *IssuingCertificateURL) UnmarshalJSON(data []byte) error { ms, err := unmarshalMultiString(data) if err != nil { return err } *u = ms return nil } // Set sets the list of issuing certificate urls to the given certificate. func (u IssuingCertificateURL) Set(c *x509.Certificate) { c.IssuingCertificateURL = u } // CRLDistributionPoints contains the list of CRL distribution points that will // be encoded in the CRL distribution points extension. type CRLDistributionPoints MultiString // UnmarshalJSON implements the json.Unmarshaler interface in CRLDistributionPoints. func (u *CRLDistributionPoints) UnmarshalJSON(data []byte) error { ms, err := unmarshalMultiString(data) if err != nil { return err } *u = ms return nil } // Set sets the CRL distribution points to the given certificate. func (u CRLDistributionPoints) Set(c *x509.Certificate) { c.CRLDistributionPoints = u } // PolicyIdentifiers represents the list of OIDs to set in the certificate // policies extension. type PolicyIdentifiers MultiObjectIdentifier // MarshalJSON implements the json.Marshaler interface in PolicyIdentifiers. func (p PolicyIdentifiers) MarshalJSON() ([]byte, error) { return MultiObjectIdentifier(p).MarshalJSON() } // UnmarshalJSON implements the json.Unmarshaler interface in PolicyIdentifiers. func (p *PolicyIdentifiers) UnmarshalJSON(data []byte) error { var v MultiObjectIdentifier if err := json.Unmarshal(data, &v); err != nil { return errors.Wrap(err, "error unmarshaling json") } *p = PolicyIdentifiers(v) return nil } // Set sets the policy identifiers to the given certificate. func (p PolicyIdentifiers) Set(c *x509.Certificate) { c.PolicyIdentifiers = p } // BasicConstraints represents the X509 basic constraints extension and defines // if a certificate is a CA and then maximum depth of valid certification paths // that include the certificate. A MaxPathLen of zero indicates that no non- // self-issued intermediate CA certificates may follow in a valid certification // path. To do not impose a limit the MaxPathLen should be set to -1. type BasicConstraints struct { IsCA bool `json:"isCA"` MaxPathLen int `json:"maxPathLen"` } // Set sets the basic constraints to the given certificate. func (b BasicConstraints) Set(c *x509.Certificate) { c.BasicConstraintsValid = true c.IsCA = b.IsCA if c.IsCA { switch { case b.MaxPathLen == 0: c.MaxPathLen = 0 c.MaxPathLenZero = true case b.MaxPathLen < 0: c.MaxPathLen = -1 c.MaxPathLenZero = false default: c.MaxPathLen = b.MaxPathLen c.MaxPathLenZero = false } } else { c.MaxPathLen = 0 c.MaxPathLenZero = false } } // NameConstraints represents the X509 Name constraints extension and defines a // names space within which all subject names in subsequent certificates in a // certificate path must be located. The name constraints extension must be used // only in a CA. type NameConstraints struct { Critical bool `json:"critical"` PermittedDNSDomains MultiString `json:"permittedDNSDomains"` ExcludedDNSDomains MultiString `json:"excludedDNSDomains"` PermittedIPRanges MultiIPNet `json:"permittedIPRanges"` ExcludedIPRanges MultiIPNet `json:"excludedIPRanges"` PermittedEmailAddresses MultiString `json:"permittedEmailAddresses"` ExcludedEmailAddresses MultiString `json:"excludedEmailAddresses"` PermittedURIDomains MultiString `json:"permittedURIDomains"` ExcludedURIDomains MultiString `json:"excludedURIDomains"` } // Set sets the name constraints in the given certificate. func (n NameConstraints) Set(c *x509.Certificate) { c.PermittedDNSDomainsCritical = n.Critical c.PermittedDNSDomains = n.PermittedDNSDomains c.ExcludedDNSDomains = n.ExcludedDNSDomains c.PermittedIPRanges = n.PermittedIPRanges c.ExcludedIPRanges = n.ExcludedIPRanges c.PermittedEmailAddresses = n.PermittedEmailAddresses c.ExcludedEmailAddresses = n.ExcludedEmailAddresses c.PermittedURIDomains = n.PermittedURIDomains c.ExcludedURIDomains = n.ExcludedURIDomains } // SerialNumber is the JSON representation of the X509 serial number. type SerialNumber struct { *big.Int } // Set sets the serial number in the given certificate. func (s SerialNumber) Set(c *x509.Certificate) { c.SerialNumber = s.Int } // MarshalJSON implements the json.Marshaler interface, and encodes a // SerialNumber using the big.Int marshaler. func (s *SerialNumber) MarshalJSON() ([]byte, error) { if s == nil || s.Int == nil { return []byte(`null`), nil } return s.Int.MarshalJSON() } // UnmarshalJSON implements the json.Unmarshal interface and unmarshals an // integer or a string into a serial number. If a string is used, a prefix of // โ€œ0bโ€ or โ€œ0Bโ€ selects base 2, โ€œ0โ€, โ€œ0oโ€ or โ€œ0Oโ€ selects base 8, and โ€œ0xโ€ or // โ€œ0Xโ€ selects base 16. Otherwise, the selected base is 10 and no prefix is // accepted. func (s *SerialNumber) UnmarshalJSON(data []byte) error { if sn, ok := maybeString(data); ok { // Using base 0 to accept prefixes 0b, 0o, 0x but defaults as base 10. b, ok := new(big.Int).SetString(sn, 0) if !ok { return errors.Errorf("error unmarshaling json: serialNumber %s is not valid", sn) } *s = SerialNumber{ Int: b, } return nil } // Assume a number. var i int64 if err := json.Unmarshal(data, &i); err != nil { return errors.Wrap(err, "error unmarshaling json") } *s = SerialNumber{ Int: new(big.Int).SetInt64(i), } return nil } func createCertificateSubjectAltNameExtension(c Certificate, subjectIsEmpty bool) (Extension, error) { return createSubjectAltNameExtension(c.DNSNames, c.EmailAddresses, c.IPAddresses, c.URIs, c.SANs, subjectIsEmpty) } func createCertificateRequestSubjectAltNameExtension(c CertificateRequest, subjectIsEmpty bool) (Extension, error) { return createSubjectAltNameExtension(c.DNSNames, c.EmailAddresses, c.IPAddresses, c.URIs, c.SANs, subjectIsEmpty) } // createSubjectAltNameExtension will construct an Extension containing all // SubjectAlternativeNames held in a Certificate. It implements more types than // the golang x509 library, so it is used whenever OtherName or RegisteredID // type SANs are present in the certificate. // // See also https://datatracker.ietf.org/doc/html/rfc5280.html#section-4.2.1.6 // // TODO(mariano,unreality): X400Address, DirectoryName, and EDIPartyName types // are defined in RFC5280 but are currently unimplemented func createSubjectAltNameExtension(dnsNames, emailAddresses MultiString, ipAddresses MultiIP, uris MultiURL, sans []SubjectAlternativeName, subjectIsEmpty bool) (Extension, error) { var zero Extension var rawValues []asn1.RawValue for _, dnsName := range dnsNames { rawValue, err := SubjectAlternativeName{ Type: DNSType, Value: dnsName, }.RawValue() if err != nil { return zero, err } rawValues = append(rawValues, rawValue) } for _, emailAddress := range emailAddresses { rawValue, err := SubjectAlternativeName{ Type: EmailType, Value: emailAddress, }.RawValue() if err != nil { return zero, err } rawValues = append(rawValues, rawValue) } for _, ip := range ipAddresses { rawValue, err := SubjectAlternativeName{ Type: IPType, Value: ip.String(), }.RawValue() if err != nil { return zero, err } rawValues = append(rawValues, rawValue) } for _, uri := range uris { rawValue, err := SubjectAlternativeName{ Type: URIType, Value: uri.String(), }.RawValue() if err != nil { return zero, err } rawValues = append(rawValues, rawValue) } for _, san := range sans { rawValue, err := san.RawValue() if err != nil { return zero, err } rawValues = append(rawValues, rawValue) } // Now marshal the rawValues into the ASN1 sequence, and create an Extension object to hold the extension rawBytes, err := asn1.Marshal(rawValues) if err != nil { return zero, errors.Wrap(err, "error marshaling SubjectAlternativeName extension to ASN1") } return Extension{ ID: oidExtensionSubjectAltName, Critical: subjectIsEmpty, Value: rawBytes, }, nil } golang-step-crypto-0.24.0/x509util/extensions_test.go000066400000000000000000001536041437037643100225440ustar00rootroot00000000000000package x509util import ( "bytes" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/json" "math/big" "net" "net/url" "reflect" "testing" ) func Test_convertName(t *testing.T) { type args struct { s string } tests := []struct { name string args args want string }{ {"lowerCase", args{"FooBAR"}, "foobar"}, {"underscore", args{"foo_bar"}, "foobar"}, {"mixed", args{"FOO_Bar"}, "foobar"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := convertName(tt.args.s); got != tt.want { t.Errorf("convertName() = %v, want %v", got, tt.want) } }) } } func Test_newExtension(t *testing.T) { type args struct { e pkix.Extension } tests := []struct { name string args args want Extension }{ {"ok", args{pkix.Extension{Id: []int{1, 2, 3, 4}, Value: []byte("foo")}}, Extension{ID: []int{1, 2, 3, 4}, Critical: false, Value: []byte("foo")}}, {"critical", args{pkix.Extension{Id: []int{1, 2, 3, 4}, Critical: true, Value: []byte("foo")}}, Extension{ID: []int{1, 2, 3, 4}, Critical: true, Value: []byte("foo")}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := newExtension(tt.args.e); !reflect.DeepEqual(got, tt.want) { t.Errorf("newExtension() = %v, want %v", got, tt.want) } }) } } func Test_newExtensions(t *testing.T) { ext1 := pkix.Extension{Id: []int{1, 2, 3, 4}, Value: []byte("foo")} ext2 := pkix.Extension{Id: []int{4, 3, 2, 1}, Critical: true, Value: []byte("bar")} type args struct { extensions []pkix.Extension } tests := []struct { name string args args want []Extension }{ {"ok", args{[]pkix.Extension{ext1, ext2}}, []Extension{ {ID: []int{1, 2, 3, 4}, Critical: false, Value: []byte("foo")}, {ID: []int{4, 3, 2, 1}, Critical: true, Value: []byte("bar")}, }}, {"nil", args{}, nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := newExtensions(tt.args.extensions); !reflect.DeepEqual(got, tt.want) { t.Errorf("newExtensions() = %v, want %v", got, tt.want) } }) } } func TestExtension_Set(t *testing.T) { type fields struct { ID ObjectIdentifier Critical bool Value []byte } type args struct { c *x509.Certificate } tests := []struct { name string fields fields args args want *x509.Certificate }{ {"ok", fields{[]int{1, 2, 3, 4}, true, []byte("foo")}, args{&x509.Certificate{}}, &x509.Certificate{ ExtraExtensions: []pkix.Extension{{Id: []int{1, 2, 3, 4}, Critical: true, Value: []byte("foo")}}, }}, {"existing", fields{[]int{1, 2, 3, 4}, true, []byte("foo")}, args{&x509.Certificate{ ExtraExtensions: []pkix.Extension{ {Id: []int{1, 1, 1, 1}, Critical: false, Value: []byte("foo")}, }, }}, &x509.Certificate{ ExtraExtensions: []pkix.Extension{ {Id: []int{1, 1, 1, 1}, Critical: false, Value: []byte("foo")}, {Id: []int{1, 2, 3, 4}, Critical: true, Value: []byte("foo")}, }, }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { e := Extension{ ID: tt.fields.ID, Critical: tt.fields.Critical, Value: tt.fields.Value, } e.Set(tt.args.c) if !reflect.DeepEqual(tt.args.c, tt.want) { t.Errorf("Extension.Set() = %v, want %v", tt.args.c, tt.want) } }) } } func TestObjectIdentifier_Equal(t *testing.T) { type args struct { v ObjectIdentifier } tests := []struct { name string o ObjectIdentifier args args want bool }{ {"ok", ObjectIdentifier{1, 2, 3, 4}, args{ObjectIdentifier{1, 2, 3, 4}}, true}, {"false length", ObjectIdentifier{1, 2, 3}, args{ObjectIdentifier{1, 2, 3, 4}}, false}, {"false content", ObjectIdentifier{1, 2, 3, 5}, args{ObjectIdentifier{1, 2, 3, 4}}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.o.Equal(tt.args.v); got != tt.want { t.Errorf("ObjectIdentifier.Equal() = %v, want %v", got, tt.want) } }) } } func TestObjectIdentifier_MarshalJSON(t *testing.T) { tests := []struct { name string o ObjectIdentifier want []byte wantErr bool }{ {"ok", []int{1, 2, 3, 4}, []byte(`"1.2.3.4"`), false}, {"empty", []int{}, []byte(`""`), false}, {"nil", nil, []byte(`""`), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.o.MarshalJSON() if (err != nil) != tt.wantErr { t.Errorf("ObjectIdentifier.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ObjectIdentifier.MarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestObjectIdentifier_UnmarshalJSON(t *testing.T) { type args struct { data []byte } tests := []struct { name string args args want ObjectIdentifier wantErr bool }{ {"ok", args{[]byte(`"1.2.3.4"`)}, []int{1, 2, 3, 4}, false}, {"empty", args{[]byte(`""`)}, []int{}, false}, {"null", args{[]byte(`null`)}, []int{}, false}, {"number", args{[]byte(`123`)}, nil, true}, {"badFormat", args{[]byte(`"1.2.foo.4"`)}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got ObjectIdentifier if err := got.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { t.Errorf("ObjectIdentifier.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ObjectIdentifier.UnmarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestSubjectAlternativeName_Set(t *testing.T) { panicCount := 0 type fields struct { Type string Value string } type args struct { c *x509.Certificate } tests := []struct { name string fields fields args args want *x509.Certificate }{ {"dns", fields{"dns", "foo.com"}, args{&x509.Certificate{}}, &x509.Certificate{DNSNames: []string{"foo.com"}}}, {"dnsAdd", fields{"DNS", "bar.com"}, args{&x509.Certificate{DNSNames: []string{"foo.com"}}}, &x509.Certificate{DNSNames: []string{"foo.com", "bar.com"}}}, {"email", fields{"email", "john@doe.com"}, args{&x509.Certificate{}}, &x509.Certificate{EmailAddresses: []string{"john@doe.com"}}}, {"emailAdd", fields{"EMAIL", "jane@doe.com"}, args{&x509.Certificate{EmailAddresses: []string{"john@doe.com"}}}, &x509.Certificate{EmailAddresses: []string{"john@doe.com", "jane@doe.com"}}}, {"ip", fields{"ip", "127.0.0.1"}, args{&x509.Certificate{}}, &x509.Certificate{IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}}}, {"ipAdd", fields{"IP", "::1"}, args{&x509.Certificate{IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}}}, &x509.Certificate{IPAddresses: []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")}}}, {"ipBad", fields{"IP", "fooo"}, args{&x509.Certificate{IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}}}, &x509.Certificate{IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}}}, {"uri", fields{"uri", "https://foo.com"}, args{&x509.Certificate{}}, &x509.Certificate{URIs: []*url.URL{{Scheme: "https", Host: "foo.com"}}}}, {"uriAdd", fields{"URI", "uri:foo:bar"}, args{&x509.Certificate{URIs: []*url.URL{{Scheme: "https", Host: "foo.com"}}}}, &x509.Certificate{URIs: []*url.URL{{Scheme: "https", Host: "foo.com"}, {Scheme: "uri", Opaque: "foo:bar"}}}}, {"uriBad", fields{"URI", "::1"}, args{&x509.Certificate{URIs: []*url.URL{{Scheme: "https", Host: "foo.com"}}}}, &x509.Certificate{URIs: []*url.URL{{Scheme: "https", Host: "foo.com"}}}}, {"AutoDNS", fields{"", "foo.com"}, args{&x509.Certificate{}}, &x509.Certificate{DNSNames: []string{"foo.com"}}}, {"AutoDNSAdd", fields{"auto", "bar.com"}, args{&x509.Certificate{DNSNames: []string{"foo.com"}}}, &x509.Certificate{DNSNames: []string{"foo.com", "bar.com"}}}, {"AutoEmail", fields{"AUTO", "john@doe.com"}, args{&x509.Certificate{}}, &x509.Certificate{EmailAddresses: []string{"john@doe.com"}}}, {"AutoEmailAdd", fields{"", "jane@doe.com"}, args{&x509.Certificate{EmailAddresses: []string{"john@doe.com"}}}, &x509.Certificate{EmailAddresses: []string{"john@doe.com", "jane@doe.com"}}}, {"IPAutoIP", fields{"AutO", "127.0.0.1"}, args{&x509.Certificate{}}, &x509.Certificate{IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}}}, {"AutoIPAdd", fields{"", "::1"}, args{&x509.Certificate{IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}}}, &x509.Certificate{IPAddresses: []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")}}}, {"AutoURI", fields{"Auto", "https://foo.com"}, args{&x509.Certificate{}}, &x509.Certificate{URIs: []*url.URL{{Scheme: "https", Host: "foo.com"}}}}, {"AutoURIAdd", fields{"", "uri:foo:bar"}, args{&x509.Certificate{URIs: []*url.URL{{Scheme: "https", Host: "foo.com"}}}}, &x509.Certificate{URIs: []*url.URL{{Scheme: "https", Host: "foo.com"}, {Scheme: "uri", Opaque: "foo:bar"}}}}, {"panic", fields{"panic", "foo.com"}, args{&x509.Certificate{}}, &x509.Certificate{DNSNames: []string{"foo.com"}}}, {"panicAdd", fields{"panic", "bar.com"}, args{&x509.Certificate{DNSNames: []string{"foo.com"}}}, &x509.Certificate{DNSNames: []string{"foo.com"}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { defer func() { if r := recover(); r != nil { panicCount++ } }() s := SubjectAlternativeName{ Type: tt.fields.Type, Value: tt.fields.Value, } s.Set(tt.args.c) if !reflect.DeepEqual(tt.args.c, tt.want) { t.Errorf("SubjectAlternativeName.Set() = %v, want %v", tt.args.c, tt.want) } }) } if panicCount != 2 { t.Errorf("SubjectAlternativeName.Set() number of panics = %d, want 2", panicCount) } } func TestSubjectAlternativeName_RawValue(t *testing.T) { type fields struct { Type string Value string ASN1Value json.RawMessage } tests := []struct { name string fields fields want asn1.RawValue wantErr bool }{ {"ip", fields{"auto", "1.1.1.1", nil}, asn1.RawValue{Class: 2, Tag: 7, Bytes: []byte{1, 1, 1, 1}}, false}, {"ipv6", fields{"auto", "2001:0db8:0000:0000:0000:ff00:0042:8329", nil}, asn1.RawValue{Class: 2, Tag: 7, Bytes: []byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0xff, 0, 0, 0x42, 0x83, 0x29}}, false}, {"uri", fields{"auto", "urn:smallstep:1234", nil}, asn1.RawValue{Class: 2, Tag: 6, Bytes: []byte("urn:smallstep:1234")}, false}, {"email", fields{"auto", "foo@bar.com", nil}, asn1.RawValue{Class: 2, Tag: 1, Bytes: []byte("foo@bar.com")}, false}, {"dns", fields{"auto", "bar.com", nil}, asn1.RawValue{Class: 2, Tag: 2, Bytes: []byte("bar.com")}, false}, {"registeredID", fields{"registeredID", "1.2.3.4", nil}, asn1.RawValue{ // Class 2, Type: 8 FullBytes: []byte{(2 << 6) | 8, 3, 0x20 | 1<<3 | 2, 3, 4}, }, false}, {"permanentIdentifier", fields{"permanentIdentifier", "0123456789", nil}, asn1.RawValue{ FullBytes: bytes.Join([][]byte{ {160, 26, 6, 8, 43, 6, 1, 5, 5, 7, 8, 3}, {160, 14, 0x30, 12, 12, 10}, []byte("0123456789"), }, nil), }, false}, {"permanentIdentifier with identifier", fields{"permanentIdentifier", "", []byte(`{"identifier":"0123456789"}`)}, asn1.RawValue{ FullBytes: bytes.Join([][]byte{ {160, 26, 6, 8, 43, 6, 1, 5, 5, 7, 8, 3}, {160, 14, 0x30, 12, 12, 10}, []byte("0123456789"), }, nil), }, false}, {"permanentIdentifier with assigner", fields{"permanentIdentifier", "", []byte(`{"identifier":"0123456789","assigner":"1.2.3.4"}`)}, asn1.RawValue{ FullBytes: bytes.Join([][]byte{ {160, 31, 6, 8, 43, 6, 1, 5, 5, 7, 8, 3}, {160, 19, 0x30, 17, 12, 10}, []byte("0123456789"), {asn1.TagOID, 3, 0x20 | 0x0A, 0x03, 0x04}, }, nil), }, false}, {"permanentIdentifier empty", fields{"permanentIdentifier", "", nil}, asn1.RawValue{ FullBytes: bytes.Join([][]byte{ {160, 14, 6, 8, 43, 6, 1, 5, 5, 7, 8, 3}, {160, 2, 0x30, 0}, }, nil), }, false}, {"hardwareModuleName", fields{"hardwareModuleName", "", []byte(`{"type":"1.2.3.4","serialNumber":"MDEyMzQ1Njc4OQ=="}`)}, asn1.RawValue{ FullBytes: bytes.Join([][]byte{ {160, 31, 6, 8, 43, 6, 1, 5, 5, 7, 8, 4}, {160, 19, 0x30, 17, asn1.TagOID, 3, 0x20 | 0x0A, 3, 4}, {0x80 | asn1.TagOctetString, 10}, []byte("0123456789"), }, nil), }, false}, {"directoryName", fields{"dn", "", []byte(`{"country":"US","organization":"ACME","commonName":"rocket"}`)}, asn1.RawValue{ Class: 2, Tag: 4, IsCompound: true, Bytes: bytes.Join([][]byte{ {0x30, 45, 49, 11}, {48, 9, 6, 3, 85, 4, 6, asn1.TagPrintableString, 2}, []byte("US"), {49, 13, 48, 11, 6, 3, 85, 4, 10, asn1.TagPrintableString, 4}, []byte("ACME"), {49, 15, 48, 13, 6, 3, 85, 4, 3, asn1.TagPrintableString, 6}, []byte("rocket"), }, nil), }, false}, {"otherName int", fields{"1.2.3.4", "int:1024", nil}, asn1.RawValue{ FullBytes: []byte{160, 11, 6, 3, 42, 3, 4, 160, 4, 2, 2, 4, 0}, }, false}, {"otherName oid", fields{"1.2.3.4", "oid:1.2.3.4", nil}, asn1.RawValue{ FullBytes: []byte{160, 12, 6, 3, 42, 3, 4, 160, 5, 6, 3, 42, 3, 4}, }, false}, {"otherName raw", fields{"1.2.3.4", "raw:MTIzNA==", nil}, asn1.RawValue{ FullBytes: append([]byte{160, 9, 6, 3, 42, 3, 4}, []byte("1234")...), }, false}, {"otherName utf8", fields{"1.2.3.4", "utf8:รกโˆซรง1234", nil}, asn1.RawValue{ FullBytes: append([]byte{160, 20, 6, 3, 42, 3, 4, 160, 13, 12, 11}, []byte("รกโˆซรง1234")...), }, false}, {"otherName ia5", fields{"1.2.3.4", "ia5:abc1234", nil}, asn1.RawValue{ FullBytes: append([]byte{160, 16, 6, 3, 42, 3, 4, 160, 9, 22, 7}, []byte("abc1234")...), }, false}, {"otherName numeric", fields{"1.2.3.4", "numeric:1024", nil}, asn1.RawValue{ FullBytes: append([]byte{160, 13, 6, 3, 42, 3, 4, 160, 6, 18, 4}, []byte("1024")...), }, false}, {"otherName printable", fields{"1.2.3.4", "printable:abc1234", nil}, asn1.RawValue{ FullBytes: append([]byte{160, 16, 6, 3, 42, 3, 4, 160, 9, 19, 7}, []byte("abc1234")...), }, false}, {"otherName default", fields{"1.2.3.4", "foo:abc1234", nil}, asn1.RawValue{ FullBytes: append([]byte{160, 16, 6, 3, 42, 3, 4, 160, 9, 19, 7}, []byte("abc1234")...), }, false}, {"otherName no type", fields{"1.2.3.4", "abc1234", nil}, asn1.RawValue{ FullBytes: append([]byte{160, 16, 6, 3, 42, 3, 4, 160, 9, 19, 7}, []byte("abc1234")...), }, false}, {"fail dn", fields{"dn", "1234", nil}, asn1.RawValue{}, true}, {"fail x400Address", fields{"x400Address", "1234", nil}, asn1.RawValue{}, true}, {"fail ediPartyName", fields{"ediPartyName", "1234", nil}, asn1.RawValue{}, true}, {"fail email", fields{"email", "nรถt@ia5.com", nil}, asn1.RawValue{}, true}, {"fail dns", fields{"dns", "xn--bรผcher.example.com", nil}, asn1.RawValue{}, true}, {"fail dns empty", fields{"dns", "", nil}, asn1.RawValue{}, true}, {"fail uri", fields{"uri", "urn:nรถt:ia5", nil}, asn1.RawValue{}, true}, {"fail ip", fields{"ip", "1.2.3.4.5", nil}, asn1.RawValue{}, true}, {"fail permanentIdentifier json", fields{"permanentIdentifier", "", []byte(`{"bad-json"}`)}, asn1.RawValue{}, true}, {"fail permanentIdentifier unmarshalJson", fields{"permanentIdentifier", "", []byte(`{"identifier":1234}`)}, asn1.RawValue{}, true}, {"fail permanentIdentifier oid", fields{"permanentIdentifier", "", []byte(`{"identifier":"0123456789","assigner":"3.2.3.4"}`)}, asn1.RawValue{}, true}, {"fail hardwareModuleName empty", fields{"hardwareModuleName", "", nil}, asn1.RawValue{}, true}, {"fail hardwareModuleName json", fields{"hardwareModuleName", "", []byte(`{"bad-json"}`)}, asn1.RawValue{}, true}, {"fail hardwareModuleName unmarshalJSON", fields{"hardwareModuleName", "", []byte(`{"type":1234}`)}, asn1.RawValue{}, true}, {"fail hardwareModuleName oid", fields{"hardwareModuleName", "", []byte(`{"type":"3.2.3.4","serialNumber":"MDEyMzQ1Njc4OQ=="}`)}, asn1.RawValue{}, true}, {"fail directoryName empty", fields{"dn", "", nil}, asn1.RawValue{}, true}, {"fail directoryName empty name", fields{"dn", "", []byte(`{}`)}, asn1.RawValue{}, true}, {"fail directoryName json", fields{"dn", "", []byte(`{"bad-json"}`)}, asn1.RawValue{}, true}, {"fail directoryName asn1", fields{"dn", "", []byte(`{"extraNames":[{"type":"4.3.2.1","value":"oid"}]}`)}, asn1.RawValue{}, true}, {"fail registeredID", fields{"registeredID", "4.3.2.1", nil}, asn1.RawValue{}, true}, {"fail registeredID empty", fields{"registeredID", "", nil}, asn1.RawValue{}, true}, {"fail registeredID parse", fields{"registeredID", "a.b.c.d", nil}, asn1.RawValue{}, true}, {"fail otherName parse", fields{"a.b.c.d", "foo", nil}, asn1.RawValue{}, true}, {"fail otherName marshal", fields{"1", "foo", nil}, asn1.RawValue{}, true}, {"fail otherName int", fields{"1.2.3.4", "int:abc", nil}, asn1.RawValue{}, true}, {"fail otherName oid", fields{"1.2.3.4", "oid:4.3.2.1", nil}, asn1.RawValue{}, true}, {"fail otherName oid parse", fields{"1.2.3.4", "oid:a.b.c.d", nil}, asn1.RawValue{}, true}, {"fail otherName raw", fields{"1.2.3.4", "raw:abc", nil}, asn1.RawValue{}, true}, {"fail otherName utf8", fields{"1.2.3.4", "utf8:\xff", nil}, asn1.RawValue{}, true}, {"fail otherName ia5", fields{"1.2.3.4", "ia5:nรถtia5", nil}, asn1.RawValue{}, true}, {"fail otherName numeric", fields{"1.2.3.4", "numeric:abc", nil}, asn1.RawValue{}, true}, {"fail otherName printable", fields{"1.2.3.4", "printable:nรถtprintable", nil}, asn1.RawValue{}, true}, {"fail otherName default", fields{"1.2.3.4", "foo:nรถtprintable", nil}, asn1.RawValue{}, true}, {"fail otherName no type", fields{"1.2.3.4", "nรถtprintable", nil}, asn1.RawValue{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := SubjectAlternativeName{ Type: tt.fields.Type, Value: tt.fields.Value, ASN1Value: tt.fields.ASN1Value, } got, err := s.RawValue() if (err != nil) != tt.wantErr { t.Errorf("SubjectAlternativeName.RawValue() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("SubjectAlternativeName.RawValue() = %v, want %v", got, tt.want) } }) } } func TestKeyUsage_Set(t *testing.T) { type args struct { c *x509.Certificate } tests := []struct { name string k KeyUsage args args want *x509.Certificate }{ {"ok", KeyUsage(x509.KeyUsageDigitalSignature), args{&x509.Certificate{}}, &x509.Certificate{KeyUsage: x509.KeyUsageDigitalSignature}}, {"overwrite", KeyUsage(x509.KeyUsageCRLSign | x509.KeyUsageCertSign), args{&x509.Certificate{KeyUsage: x509.KeyUsageDigitalSignature}}, &x509.Certificate{KeyUsage: x509.KeyUsageCRLSign | x509.KeyUsageCertSign}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.k.Set(tt.args.c) if !reflect.DeepEqual(tt.args.c, tt.want) { t.Errorf("KeyUsage.Set() = %v, want %v", tt.args.c, tt.want) } }) } } func TestKeyUsage_MarshalJSON(t *testing.T) { tests := []struct { name string k KeyUsage want string wantErr bool }{ {"DigitalSignature", KeyUsage(x509.KeyUsageDigitalSignature), `["digitalsignature"]`, false}, {"ContentCommitment", KeyUsage(x509.KeyUsageContentCommitment), `["contentcommitment"]`, false}, {"KeyEncipherment", KeyUsage(x509.KeyUsageKeyEncipherment), `["keyencipherment"]`, false}, {"DataEncipherment", KeyUsage(x509.KeyUsageDataEncipherment), `["dataencipherment"]`, false}, {"KeyAgreement", KeyUsage(x509.KeyUsageKeyAgreement), `["keyagreement"]`, false}, {"CertSign", KeyUsage(x509.KeyUsageCertSign), `["certsign"]`, false}, {"CRLSign", KeyUsage(x509.KeyUsageCRLSign), `["crlsign"]`, false}, {"EncipherOnly", KeyUsage(x509.KeyUsageEncipherOnly), `["encipheronly"]`, false}, {"DecipherOnly", KeyUsage(x509.KeyUsageDecipherOnly), `["decipheronly"]`, false}, {"DigitalSignature + KeyEncipherment", KeyUsage(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment), `["digitalsignature","keyencipherment"]`, false}, {"Error", KeyUsage(x509.KeyUsageDecipherOnly << 1), "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.k.MarshalJSON() if (err != nil) != tt.wantErr { t.Fatalf("KeyUsage.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) } if tt.wantErr { return } if tt.want != string(got) { t.Errorf("KeyUsage.MarshalJSON() = %q, want %q", string(got), tt.want) } var unmarshaled KeyUsage unmarshaled.UnmarshalJSON(got) if unmarshaled != tt.k { t.Errorf("KeyUsage.UnmarshalJSON(keyUsage.MarshalJSON) = %v, want %v", unmarshaled, tt.k) } }) } } func TestKeyUsage_UnmarshalJSON(t *testing.T) { type args struct { data []byte } tests := []struct { name string args args want KeyUsage wantErr bool }{ // Normalized {"DigitalSignature", args{[]byte(`"DigitalSignature"`)}, KeyUsage(x509.KeyUsageDigitalSignature), false}, {"ContentCommitment", args{[]byte(`"ContentCommitment"`)}, KeyUsage(x509.KeyUsageContentCommitment), false}, {"KeyEncipherment", args{[]byte(`"KeyEncipherment"`)}, KeyUsage(x509.KeyUsageKeyEncipherment), false}, {"DataEncipherment", args{[]byte(`"DataEncipherment"`)}, KeyUsage(x509.KeyUsageDataEncipherment), false}, {"KeyAgreement", args{[]byte(`"KeyAgreement"`)}, KeyUsage(x509.KeyUsageKeyAgreement), false}, {"CertSign", args{[]byte(`"CertSign"`)}, KeyUsage(x509.KeyUsageCertSign), false}, {"CRLSign", args{[]byte(`"CRLSign"`)}, KeyUsage(x509.KeyUsageCRLSign), false}, {"EncipherOnly", args{[]byte(`"EncipherOnly"`)}, KeyUsage(x509.KeyUsageEncipherOnly), false}, {"DecipherOnly", args{[]byte(`"DecipherOnly"`)}, KeyUsage(x509.KeyUsageDecipherOnly), false}, // Snake case {"digital_signature", args{[]byte(`"digital_signature"`)}, KeyUsage(x509.KeyUsageDigitalSignature), false}, {"content_commitment", args{[]byte(`"content_commitment"`)}, KeyUsage(x509.KeyUsageContentCommitment), false}, {"key_encipherment", args{[]byte(`"key_encipherment"`)}, KeyUsage(x509.KeyUsageKeyEncipherment), false}, {"data_encipherment", args{[]byte(`"data_encipherment"`)}, KeyUsage(x509.KeyUsageDataEncipherment), false}, {"key_agreement", args{[]byte(`"key_agreement"`)}, KeyUsage(x509.KeyUsageKeyAgreement), false}, {"cert_sign", args{[]byte(`"cert_sign"`)}, KeyUsage(x509.KeyUsageCertSign), false}, {"crl_sign", args{[]byte(`"crl_sign"`)}, KeyUsage(x509.KeyUsageCRLSign), false}, {"encipher_only", args{[]byte(`"encipher_only"`)}, KeyUsage(x509.KeyUsageEncipherOnly), false}, {"decipher_only", args{[]byte(`"decipher_only"`)}, KeyUsage(x509.KeyUsageDecipherOnly), false}, // MultiString {"DigitalSignatureAsArray", args{[]byte(`["digital_signature"]`)}, KeyUsage(x509.KeyUsageDigitalSignature), false}, {"DigitalSignature|KeyEncipherment", args{[]byte(`["DigitalSignature", "key_encipherment"]`)}, KeyUsage(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment), false}, // Errors {"invalid", args{[]byte(`"invalid"`)}, KeyUsage(0), true}, {"number", args{[]byte(`123`)}, KeyUsage(0), true}, {"object", args{[]byte(`{}`)}, KeyUsage(0), true}, {"badJSON", args{[]byte(`{`)}, KeyUsage(0), true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got KeyUsage if err := got.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { t.Errorf("KeyUsage.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("KeyUsage.UnmarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestExtKeyUsage_Set(t *testing.T) { eku1 := []x509.ExtKeyUsage{ x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth, } eku2 := []x509.ExtKeyUsage{ x509.ExtKeyUsageCodeSigning, } type args struct { c *x509.Certificate } tests := []struct { name string k ExtKeyUsage args args want *x509.Certificate }{ {"ok", ExtKeyUsage(eku1), args{&x509.Certificate{}}, &x509.Certificate{ExtKeyUsage: eku1}}, {"overwrite", ExtKeyUsage(eku2), args{&x509.Certificate{ExtKeyUsage: eku1}}, &x509.Certificate{ExtKeyUsage: eku2}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.k.Set(tt.args.c) if !reflect.DeepEqual(tt.args.c, tt.want) { t.Errorf("ExtKeyUsage.Set() = %v, want %v", tt.args.c, tt.want) } }) } } func TestExtKeyUsage_MarshalJSON(t *testing.T) { tests := []struct { name string eku ExtKeyUsage want string wantErr bool }{ {"Any", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageAny}), `["any"]`, false}, {"ServerAuth", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}), `["serverauth"]`, false}, {"ClientAuth", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}), `["clientauth"]`, false}, {"CodeSigning", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}), `["codesigning"]`, false}, {"EmailProtection", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection}), `["emailprotection"]`, false}, {"IPSECEndSystem", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageIPSECEndSystem}), `["ipsecendsystem"]`, false}, {"IPSECTunnel", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageIPSECTunnel}), `["ipsectunnel"]`, false}, {"IPSECUser", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageIPSECUser}), `["ipsecuser"]`, false}, {"TimeStamping", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageTimeStamping}), `["timestamping"]`, false}, {"OCSPSigning", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageOCSPSigning}), `["ocspsigning"]`, false}, {"MicrosoftServerGatedCrypto", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageMicrosoftServerGatedCrypto}), `["microsoftservergatedcrypto"]`, false}, {"NetscapeServerGatedCrypto", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageNetscapeServerGatedCrypto}), `["netscapeservergatedcrypto"]`, false}, {"MicrosoftCommercialCodeSigning", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageMicrosoftCommercialCodeSigning}), `["microsoftcommercialcodesigning"]`, false}, {"MicrosoftKernelCodeSigning", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageMicrosoftKernelCodeSigning}), `["microsoftkernelcodesigning"]`, false}, {"ServerAuth + ClientAuth", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}), `["serverauth","clientauth"]`, false}, {"Error", ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageMicrosoftKernelCodeSigning + 1}), "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.eku.MarshalJSON() if (err != nil) != tt.wantErr { t.Fatalf("ExtKeyUsage.MarshalJSON() = error = %v, wantErr %v", err, tt.wantErr) } if tt.wantErr { return } if tt.want != string(got) { t.Errorf("ExtKeyUsage.MarshalJSON() = %q, want %q", string(got), tt.want) } var unmarshaled ExtKeyUsage unmarshaled.UnmarshalJSON(got) if !reflect.DeepEqual(unmarshaled, tt.eku) { t.Errorf("ExtKeyUsage.UnmarshalJSON(ExtKeyUsage.MarshalJSON) = %v, want %v", unmarshaled, tt.eku) } }) } } func TestExtKeyUsage_UnmarshalJSON(t *testing.T) { type args struct { data []byte } tests := []struct { name string args args want ExtKeyUsage wantErr bool }{ // Normalized {"Any", args{[]byte(`"Any"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageAny}), false}, {"ServerAuth", args{[]byte(`"ServerAuth"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}), false}, {"ClientAuth", args{[]byte(`"ClientAuth"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}), false}, {"CodeSigning", args{[]byte(`"CodeSigning"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}), false}, {"EmailProtection", args{[]byte(`"EmailProtection"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection}), false}, {"IPSECEndSystem", args{[]byte(`"IPSECEndSystem"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageIPSECEndSystem}), false}, {"IPSECTunnel", args{[]byte(`"IPSECTunnel"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageIPSECTunnel}), false}, {"IPSECUser", args{[]byte(`"IPSECUser"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageIPSECUser}), false}, {"TimeStamping", args{[]byte(`"TimeStamping"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageTimeStamping}), false}, {"OCSPSigning", args{[]byte(`"OCSPSigning"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageOCSPSigning}), false}, {"MicrosoftServerGatedCrypto", args{[]byte(`"MicrosoftServerGatedCrypto"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageMicrosoftServerGatedCrypto}), false}, {"NetscapeServerGatedCrypto", args{[]byte(`"NetscapeServerGatedCrypto"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageNetscapeServerGatedCrypto}), false}, {"MicrosoftCommercialCodeSigning", args{[]byte(`"MicrosoftCommercialCodeSigning"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageMicrosoftCommercialCodeSigning}), false}, {"MicrosoftKernelCodeSigning", args{[]byte(`"MicrosoftKernelCodeSigning"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageMicrosoftKernelCodeSigning}), false}, // Snake case {"any", args{[]byte(`"any"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageAny}), false}, {"server_auth", args{[]byte(`"server_auth"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}), false}, {"client_auth", args{[]byte(`"client_auth"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}), false}, {"code_signing", args{[]byte(`"code_signing"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}), false}, {"email_protection", args{[]byte(`"email_protection"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection}), false}, {"ipsec_end_system", args{[]byte(`"ipsec_end_system"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageIPSECEndSystem}), false}, {"ipsec_tunnel", args{[]byte(`"ipsec_tunnel"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageIPSECTunnel}), false}, {"ipsec_user", args{[]byte(`"ipsec_user"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageIPSECUser}), false}, {"time_stamping", args{[]byte(`"time_stamping"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageTimeStamping}), false}, {"ocsp_signing", args{[]byte(`"ocsp_signing"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageOCSPSigning}), false}, {"microsoft_server_gated_crypto", args{[]byte(`"microsoft_server_gated_crypto"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageMicrosoftServerGatedCrypto}), false}, {"netscape_server_gated_crypto", args{[]byte(`"netscape_server_gated_crypto"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageNetscapeServerGatedCrypto}), false}, {"microsoft_commercial_code_signing", args{[]byte(`"microsoft_commercial_code_signing"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageMicrosoftCommercialCodeSigning}), false}, {"microsoft_kernel_code_signing", args{[]byte(`"microsoft_kernel_code_signing"`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageMicrosoftKernelCodeSigning}), false}, // Multistring {"CodeSigningAsArray", args{[]byte(`["code_signing"]`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}), false}, {"ServerAuth+ClientAuth", args{[]byte(`["ServerAuth","client_auth"]`)}, ExtKeyUsage([]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}), false}, // Errors {"invalid", args{[]byte(`"invalid"`)}, nil, true}, {"number", args{[]byte(`123`)}, nil, true}, {"object", args{[]byte(`{}`)}, nil, true}, {"badJSON", args{[]byte(`{`)}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got ExtKeyUsage if err := got.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { t.Errorf("ExtKeyUsage.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ExtKeyUsage.UnmarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestUnknownExtKeyUsage_MarshalJSON(t *testing.T) { tests := []struct { name string m UnknownExtKeyUsage want []byte wantErr bool }{ {"ok", []asn1.ObjectIdentifier{[]int{1, 2, 3, 4}, []int{5, 6, 7, 8, 9, 0}}, []byte(`["1.2.3.4","5.6.7.8.9.0"]`), false}, {"empty", []asn1.ObjectIdentifier{}, []byte(`[]`), false}, {"nil", nil, []byte(`null`), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := json.Marshal(tt.m) if (err != nil) != tt.wantErr { t.Errorf("UnknownExtKeyUsage.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("UnknownExtKeyUsage.MarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestUnknownExtKeyUsage_UnmarshalJSON(t *testing.T) { type args struct { data []byte } tests := []struct { name string args args want UnknownExtKeyUsage wantErr bool }{ {"string", args{[]byte(`"1.2.3.4"`)}, []asn1.ObjectIdentifier{[]int{1, 2, 3, 4}}, false}, {"array", args{[]byte(`["1.2.3.4", "5.6.7.8.9.0"]`)}, []asn1.ObjectIdentifier{[]int{1, 2, 3, 4}, []int{5, 6, 7, 8, 9, 0}}, false}, {"empty", args{[]byte(`[]`)}, []asn1.ObjectIdentifier{}, false}, {"null", args{[]byte(`null`)}, nil, false}, {"fail", args{[]byte(`":foo:bar"`)}, nil, true}, {"failJSON", args{[]byte(`["https://iss#sub"`)}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got UnknownExtKeyUsage if err := got.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { t.Errorf("UnknownExtKeyUsage.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("UnknownExtKeyUsage.UnmarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestUnknownExtKeyUsage_Set(t *testing.T) { type args struct { c *x509.Certificate } tests := []struct { name string o UnknownExtKeyUsage args args want *x509.Certificate }{ {"ok", []asn1.ObjectIdentifier{{1, 2, 3, 4}}, args{&x509.Certificate{}}, &x509.Certificate{UnknownExtKeyUsage: []asn1.ObjectIdentifier{{1, 2, 3, 4}}}}, {"overwrite", []asn1.ObjectIdentifier{{1, 2, 3, 4}, {4, 3, 2, 1}}, args{&x509.Certificate{UnknownExtKeyUsage: []asn1.ObjectIdentifier{{1, 2, 3, 4}}}}, &x509.Certificate{UnknownExtKeyUsage: []asn1.ObjectIdentifier{{1, 2, 3, 4}, {4, 3, 2, 1}}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.o.Set(tt.args.c) if !reflect.DeepEqual(tt.args.c, tt.want) { t.Errorf("UnknownExtKeyUsage.Set() = %v, want %v", tt.args.c, tt.want) } }) } } func TestSubjectKeyID_Set(t *testing.T) { type args struct { c *x509.Certificate } tests := []struct { name string id SubjectKeyID args args want *x509.Certificate }{ {"ok", []byte("subjectKeyID"), args{&x509.Certificate{}}, &x509.Certificate{SubjectKeyId: []byte("subjectKeyID")}}, {"overwrite", []byte("newSubjectKeyID"), args{&x509.Certificate{SubjectKeyId: []byte("subjectKeyID")}}, &x509.Certificate{SubjectKeyId: []byte("newSubjectKeyID")}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.id.Set(tt.args.c) if !reflect.DeepEqual(tt.args.c, tt.want) { t.Errorf("SubjectKeyID.Set() = %v, want %v", tt.args.c, tt.want) } }) } } func TestAuthorityKeyID_Set(t *testing.T) { type args struct { c *x509.Certificate } tests := []struct { name string id AuthorityKeyID args args want *x509.Certificate }{ {"ok", []byte("authorityKeyID"), args{&x509.Certificate{}}, &x509.Certificate{AuthorityKeyId: []byte("authorityKeyID")}}, {"overwrite", []byte("newAuthorityKeyID"), args{&x509.Certificate{AuthorityKeyId: []byte("authorityKeyID")}}, &x509.Certificate{AuthorityKeyId: []byte("newAuthorityKeyID")}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.id.Set(tt.args.c) if !reflect.DeepEqual(tt.args.c, tt.want) { t.Errorf("AuthorityKeyID.Set() = %v, want %v", tt.args.c, tt.want) } }) } } func TestOCSPServer_UnmarshalJSON(t *testing.T) { type args struct { data []byte } tests := []struct { name string args args want OCSPServer wantErr bool }{ {"string", args{[]byte(`"foo"`)}, []string{"foo"}, false}, {"array", args{[]byte(`["foo", "bar", "zar"]`)}, []string{"foo", "bar", "zar"}, false}, {"empty", args{[]byte(`[]`)}, []string{}, false}, {"null", args{[]byte(`null`)}, nil, false}, {"fail", args{[]byte(`["foo"`)}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got OCSPServer if err := got.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { t.Errorf("OCSPServer.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("OCSPServer.UnmarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestOCSPServer_Set(t *testing.T) { type args struct { c *x509.Certificate } tests := []struct { name string o OCSPServer args args want *x509.Certificate }{ {"ok", []string{"oscp.server"}, args{&x509.Certificate{}}, &x509.Certificate{OCSPServer: []string{"oscp.server"}}}, {"overwrite", []string{"oscp.server", "oscp.com"}, args{&x509.Certificate{OCSPServer: []string{"oscp.server"}}}, &x509.Certificate{OCSPServer: []string{"oscp.server", "oscp.com"}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.o.Set(tt.args.c) if !reflect.DeepEqual(tt.args.c, tt.want) { t.Errorf("OCSPServer.Set() = %v, want %v", tt.args.c, tt.want) } }) } } func TestIssuingCertificateURL_UnmarshalJSON(t *testing.T) { type args struct { data []byte } tests := []struct { name string args args want IssuingCertificateURL wantErr bool }{ {"string", args{[]byte(`"foo"`)}, []string{"foo"}, false}, {"array", args{[]byte(`["foo", "bar", "zar"]`)}, []string{"foo", "bar", "zar"}, false}, {"empty", args{[]byte(`[]`)}, []string{}, false}, {"null", args{[]byte(`null`)}, nil, false}, {"fail", args{[]byte(`["foo"`)}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got IssuingCertificateURL if err := got.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { t.Errorf("IssuingCertificateURL.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("IssuingCertificateURL.UnmarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestIssuingCertificateURL_Set(t *testing.T) { type args struct { c *x509.Certificate } tests := []struct { name string o IssuingCertificateURL args args want *x509.Certificate }{ {"ok", []string{"issuing.server"}, args{&x509.Certificate{}}, &x509.Certificate{IssuingCertificateURL: []string{"issuing.server"}}}, {"overwrite", []string{"issuing.server", "issuing.com"}, args{&x509.Certificate{IssuingCertificateURL: []string{"issuing.server"}}}, &x509.Certificate{IssuingCertificateURL: []string{"issuing.server", "issuing.com"}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.o.Set(tt.args.c) if !reflect.DeepEqual(tt.args.c, tt.want) { t.Errorf("IssuingCertificateURL.Set() = %v, want %v", tt.args.c, tt.want) } }) } } func TestCRLDistributionPoints_UnmarshalJSON(t *testing.T) { type args struct { data []byte } tests := []struct { name string args args want CRLDistributionPoints wantErr bool }{ {"string", args{[]byte(`"foo"`)}, []string{"foo"}, false}, {"array", args{[]byte(`["foo", "bar", "zar"]`)}, []string{"foo", "bar", "zar"}, false}, {"empty", args{[]byte(`[]`)}, []string{}, false}, {"null", args{[]byte(`null`)}, nil, false}, {"fail", args{[]byte(`["foo"`)}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got CRLDistributionPoints if err := got.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { t.Errorf("CRLDistributionPoints.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("CRLDistributionPoints.UnmarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestCRLDistributionPoints_Set(t *testing.T) { type args struct { c *x509.Certificate } tests := []struct { name string o CRLDistributionPoints args args want *x509.Certificate }{ {"ok", []string{"crl.server"}, args{&x509.Certificate{}}, &x509.Certificate{CRLDistributionPoints: []string{"crl.server"}}}, {"overwrite", []string{"crl.server", "crl.com"}, args{&x509.Certificate{CRLDistributionPoints: []string{"crl.server"}}}, &x509.Certificate{CRLDistributionPoints: []string{"crl.server", "crl.com"}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.o.Set(tt.args.c) if !reflect.DeepEqual(tt.args.c, tt.want) { t.Errorf("CRLDistributionPoints.Set() = %v, want %v", tt.args.c, tt.want) } }) } } func TestPolicyIdentifiers_MarshalJSON(t *testing.T) { tests := []struct { name string m PolicyIdentifiers want []byte wantErr bool }{ {"ok", []asn1.ObjectIdentifier{[]int{1, 2, 3, 4}, []int{5, 6, 7, 8, 9, 0}}, []byte(`["1.2.3.4","5.6.7.8.9.0"]`), false}, {"empty", []asn1.ObjectIdentifier{}, []byte(`[]`), false}, {"nil", nil, []byte(`null`), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := json.Marshal(tt.m) if (err != nil) != tt.wantErr { t.Errorf("PolicyIdentifiers.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("PolicyIdentifiers.MarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestPolicyIdentifiers_UnmarshalJSON(t *testing.T) { type args struct { data []byte } tests := []struct { name string args args want PolicyIdentifiers wantErr bool }{ {"string", args{[]byte(`"1.2.3.4"`)}, []asn1.ObjectIdentifier{[]int{1, 2, 3, 4}}, false}, {"array", args{[]byte(`["1.2.3.4", "5.6.7.8.9.0"]`)}, []asn1.ObjectIdentifier{[]int{1, 2, 3, 4}, []int{5, 6, 7, 8, 9, 0}}, false}, {"empty", args{[]byte(`[]`)}, []asn1.ObjectIdentifier{}, false}, {"null", args{[]byte(`null`)}, nil, false}, {"fail", args{[]byte(`":foo:bar"`)}, nil, true}, {"failJSON", args{[]byte(`["https://iss#sub"`)}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got PolicyIdentifiers if err := got.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { t.Errorf("PolicyIdentifiers.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("PolicyIdentifiers.UnmarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestPolicyIdentifiers_Set(t *testing.T) { type args struct { c *x509.Certificate } tests := []struct { name string o PolicyIdentifiers args args want *x509.Certificate }{ {"ok", []asn1.ObjectIdentifier{{1, 2, 3, 4}}, args{&x509.Certificate{}}, &x509.Certificate{PolicyIdentifiers: []asn1.ObjectIdentifier{{1, 2, 3, 4}}}}, {"overwrite", []asn1.ObjectIdentifier{{1, 2, 3, 4}, {4, 3, 2, 1}}, args{&x509.Certificate{PolicyIdentifiers: []asn1.ObjectIdentifier{{1, 2, 3, 4}}}}, &x509.Certificate{PolicyIdentifiers: []asn1.ObjectIdentifier{{1, 2, 3, 4}, {4, 3, 2, 1}}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.o.Set(tt.args.c) if !reflect.DeepEqual(tt.args.c, tt.want) { t.Errorf("PolicyIdentifiers.Set() = %v, want %v", tt.args.c, tt.want) } }) } } func TestBasicConstraints_Set(t *testing.T) { type fields struct { IsCA bool MaxPathLen int } type args struct { c *x509.Certificate } tests := []struct { name string fields fields args args want *x509.Certificate }{ {"IsCAFalse", fields{false, 0}, args{&x509.Certificate{}}, &x509.Certificate{IsCA: false, BasicConstraintsValid: true}}, {"IsCAFalseWithPathLen", fields{false, 1}, args{&x509.Certificate{}}, &x509.Certificate{IsCA: false, BasicConstraintsValid: true}}, {"IsCAFalseWithAnyPathLen", fields{false, -1}, args{&x509.Certificate{}}, &x509.Certificate{IsCA: false, BasicConstraintsValid: true}}, {"IsCATrue", fields{true, 0}, args{&x509.Certificate{}}, &x509.Certificate{IsCA: true, MaxPathLen: 0, MaxPathLenZero: true, BasicConstraintsValid: true}}, {"IsCATrueWithPathLen", fields{true, 1}, args{&x509.Certificate{}}, &x509.Certificate{IsCA: true, MaxPathLen: 1, MaxPathLenZero: false, BasicConstraintsValid: true}}, {"IsCATrueWithAnyPathLen", fields{true, -1}, args{&x509.Certificate{}}, &x509.Certificate{IsCA: true, MaxPathLen: -1, MaxPathLenZero: false, BasicConstraintsValid: true}}, {"overwriteToFalse", fields{false, 0}, args{&x509.Certificate{IsCA: true, MaxPathLen: 0, MaxPathLenZero: true, BasicConstraintsValid: true}}, &x509.Certificate{IsCA: false, BasicConstraintsValid: true}}, {"overwriteToTrue", fields{true, -100}, args{&x509.Certificate{IsCA: true, MaxPathLen: 0, MaxPathLenZero: true, BasicConstraintsValid: true}}, &x509.Certificate{IsCA: true, MaxPathLen: -1, MaxPathLenZero: false, BasicConstraintsValid: true}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b := BasicConstraints{ IsCA: tt.fields.IsCA, MaxPathLen: tt.fields.MaxPathLen, } b.Set(tt.args.c) if !reflect.DeepEqual(tt.args.c, tt.want) { t.Errorf("BasicConstraints.Set() = %v, want %v", tt.args.c, tt.want) } }) } } func TestNameConstraints_Set(t *testing.T) { ipNet := func(s string) *net.IPNet { _, ipNet, err := net.ParseCIDR(s) if err != nil { t.Fatal(err) } return ipNet } type fields struct { Critical bool PermittedDNSDomains MultiString ExcludedDNSDomains MultiString PermittedIPRanges MultiIPNet ExcludedIPRanges MultiIPNet PermittedEmailAddresses MultiString ExcludedEmailAddresses MultiString PermittedURIDomains MultiString ExcludedURIDomains MultiString } type args struct { c *x509.Certificate } tests := []struct { name string fields fields args args want *x509.Certificate }{ {"ok", fields{ Critical: true, PermittedDNSDomains: []string{"foo.com", "bar.com"}, ExcludedDNSDomains: []string{"zar.com"}, PermittedIPRanges: []*net.IPNet{ipNet("1.2.0.0/16"), ipNet("2.3.4.0/8")}, ExcludedIPRanges: []*net.IPNet{ipNet("3.0.0.0/24")}, PermittedEmailAddresses: []string{"root@foo.com"}, ExcludedEmailAddresses: []string{"admin@foo.com", "root@bar.com", "admin@bar.com"}, PermittedURIDomains: []string{".foo.com", ".bar.com"}, ExcludedURIDomains: []string{".zar.com"}, }, args{&x509.Certificate{}}, &x509.Certificate{ PermittedDNSDomainsCritical: true, PermittedDNSDomains: []string{"foo.com", "bar.com"}, ExcludedDNSDomains: []string{"zar.com"}, PermittedIPRanges: []*net.IPNet{ipNet("1.2.0.0/16"), ipNet("2.3.4.0/8")}, ExcludedIPRanges: []*net.IPNet{ipNet("3.0.0.0/24")}, PermittedEmailAddresses: []string{"root@foo.com"}, ExcludedEmailAddresses: []string{"admin@foo.com", "root@bar.com", "admin@bar.com"}, PermittedURIDomains: []string{".foo.com", ".bar.com"}, ExcludedURIDomains: []string{".zar.com"}, }}, {"overwrite", fields{}, args{&x509.Certificate{ PermittedDNSDomainsCritical: true, PermittedDNSDomains: []string{"foo.com", "bar.com"}, ExcludedDNSDomains: []string{"zar.com"}, PermittedIPRanges: []*net.IPNet{ipNet("1.2.0.0/16"), ipNet("2.3.4.0/8")}, ExcludedIPRanges: []*net.IPNet{ipNet("3.0.0.0/24")}, PermittedEmailAddresses: []string{"root@foo.com"}, ExcludedEmailAddresses: []string{"admin@foo.com", "root@bar.com", "admin@bar.com"}, PermittedURIDomains: []string{".foo.com", ".bar.com"}, ExcludedURIDomains: []string{".zar.com"}, }}, &x509.Certificate{}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { n := NameConstraints{ Critical: tt.fields.Critical, PermittedDNSDomains: tt.fields.PermittedDNSDomains, ExcludedDNSDomains: tt.fields.ExcludedDNSDomains, PermittedIPRanges: tt.fields.PermittedIPRanges, ExcludedIPRanges: tt.fields.ExcludedIPRanges, PermittedEmailAddresses: tt.fields.PermittedEmailAddresses, ExcludedEmailAddresses: tt.fields.ExcludedEmailAddresses, PermittedURIDomains: tt.fields.PermittedURIDomains, ExcludedURIDomains: tt.fields.ExcludedURIDomains, } n.Set(tt.args.c) if !reflect.DeepEqual(tt.args.c, tt.want) { t.Errorf("NameConstraints.Set() = %v, want %v", tt.args.c, tt.want) } }) } } func TestSerialNumber_Set(t *testing.T) { type fields struct { Int *big.Int } type args struct { c *x509.Certificate } tests := []struct { name string fields fields args args want *x509.Certificate }{ {"ok", fields{big.NewInt(1234)}, args{&x509.Certificate{}}, &x509.Certificate{SerialNumber: big.NewInt(1234)}}, {"overwrite", fields{big.NewInt(4321)}, args{&x509.Certificate{SerialNumber: big.NewInt(1234)}}, &x509.Certificate{SerialNumber: big.NewInt(4321)}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := SerialNumber{ Int: tt.fields.Int, } s.Set(tt.args.c) if !reflect.DeepEqual(tt.args.c, tt.want) { t.Errorf("SerialNumber.Set() = %v, want %v", tt.args.c, tt.want) } }) } } func TestSerialNumber_MarshalJSON(t *testing.T) { tests := []struct { name string sn *SerialNumber want []byte wantErr bool }{ {"ok", &SerialNumber{big.NewInt(1234)}, []byte("1234"), false}, {"nilStruct", nil, []byte("null"), false}, {"nilBigInt", &SerialNumber{}, []byte("null"), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.sn.MarshalJSON() if (err != nil) != tt.wantErr { t.Errorf("SerialNumber.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("SerialNumber.MarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestSerialNumber_UnmarshalJSON(t *testing.T) { expected := SerialNumber{big.NewInt(12345)} type args struct { data []byte } tests := []struct { name string args args want SerialNumber wantErr bool }{ {"string", args{[]byte(`"12345"`)}, expected, false}, {"stringHex", args{[]byte(`"0x3039"`)}, expected, false}, {"number", args{[]byte(`12345`)}, expected, false}, {"badString", args{[]byte(`"123s"`)}, SerialNumber{}, true}, {"object", args{[]byte(`{}`)}, SerialNumber{}, true}, {"badJSON", args{[]byte(`{`)}, SerialNumber{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var s SerialNumber if err := s.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { t.Errorf("SerialNumber.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(s, tt.want) { t.Errorf("SerialNumber.UnmarshalJSON() = %v, want %v", s, tt.want) } }) } } func Test_createSubjectAltNameExtension(t *testing.T) { type args struct { c Certificate subjectIsEmpty bool } tests := []struct { name string args args want Extension wantErr bool }{ {"ok dns", args{Certificate{ DNSNames: []string{"foo.com"}, }, false}, Extension{ ID: oidExtensionSubjectAltName, Critical: false, Value: append([]byte{0x30, 9, 0x80 | nameTypeDNS, 7}, []byte("foo.com")...), }, false}, {"ok dns critical", args{Certificate{ DNSNames: []string{"foo.com"}, }, true}, Extension{ ID: oidExtensionSubjectAltName, Critical: true, Value: append([]byte{0x30, 9, 0x80 | nameTypeDNS, 7}, []byte("foo.com")...), }, false}, {"ok email", args{Certificate{ EmailAddresses: []string{"bar@foo.com"}, }, false}, Extension{ ID: oidExtensionSubjectAltName, Critical: false, Value: append([]byte{0x30, 13, 0x80 | nameTypeEmail, 11}, []byte("bar@foo.com")...), }, false}, {"ok uri", args{Certificate{ URIs: []*url.URL{{Scheme: "urn", Opaque: "foo:bar"}}, }, false}, Extension{ ID: oidExtensionSubjectAltName, Critical: false, Value: append([]byte{0x30, 13, 0x80 | nameTypeURI, 11}, []byte("urn:foo:bar")...), }, false}, {"ok ip", args{Certificate{ IPAddresses: []net.IP{net.ParseIP("1.2.3.4")}, }, false}, Extension{ ID: oidExtensionSubjectAltName, Critical: false, Value: []byte{0x30, 6, 0x80 | nameTypeIP, 4, 1, 2, 3, 4}, }, false}, {"ok sans", args{Certificate{ SANs: []SubjectAlternativeName{ {Type: "dns", Value: "foo.com"}, {Type: "email", Value: "bar@foo.com"}, {Type: "uri", Value: "urn:foo:bar"}, {Type: "ip", Value: "1.2.3.4"}, }, }, false}, Extension{ ID: oidExtensionSubjectAltName, Critical: false, Value: bytes.Join([][]byte{ {0x30, (2 + 7) + (2 + 11) + (2 + 11) + (2 + 4)}, {0x80 | nameTypeDNS, 7}, []byte("foo.com"), {0x80 | nameTypeEmail, 11}, []byte("bar@foo.com"), {0x80 | nameTypeURI, 11}, []byte("urn:foo:bar"), {0x80 | nameTypeIP, 4, 1, 2, 3, 4}, }, nil), }, false}, {"ok otherName", args{Certificate{ SANs: []SubjectAlternativeName{ {Type: "dns", Value: "foo.com"}, {Type: "1.2.3.4", Value: "utf8:bar@foo.com"}, }, }, false}, Extension{ ID: oidExtensionSubjectAltName, Critical: false, Value: bytes.Join([][]byte{ {0x30, (2 + 7) + (2 + 20)}, {0x80 | nameTypeDNS, 7}, []byte("foo.com"), {0xA0, 20, asn1.TagOID, 3, 0x20 | 0x0A, 3, 4}, {0xA0, 13, asn1.TagUTF8String, 11}, []byte("bar@foo.com"), }, nil), }, false}, {"fail dns", args{Certificate{ DNSNames: []string{""}, }, false}, Extension{}, true}, {"fail email", args{Certificate{ EmailAddresses: []string{"nรถt@ia5.com"}, }, false}, Extension{}, true}, {"fail uri", args{Certificate{ URIs: []*url.URL{{Scheme: "urn", Opaque: "nรถt:ia5"}}, }, false}, Extension{}, true}, {"fail ip", args{Certificate{ IPAddresses: []net.IP{{1, 2, 3}}, }, false}, Extension{}, true}, {"fail otherName", args{Certificate{ SANs: []SubjectAlternativeName{ {Type: "1.2.3.4", Value: "int:bar@foo.com"}, }, }, false}, Extension{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotCert, err := createCertificateSubjectAltNameExtension(tt.args.c, tt.args.subjectIsEmpty) if (err != nil) != tt.wantErr { t.Errorf("createCertificateSubjectAltNameExtension() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(gotCert, tt.want) { t.Errorf("createCertificateSubjectAltNameExtension() = %v, want %v", gotCert, tt.want) } cr := CertificateRequest{ DNSNames: tt.args.c.DNSNames, EmailAddresses: tt.args.c.EmailAddresses, IPAddresses: tt.args.c.IPAddresses, URIs: tt.args.c.URIs, SANs: tt.args.c.SANs, } gotCSR, err := createCertificateRequestSubjectAltNameExtension(cr, tt.args.subjectIsEmpty) if (err != nil) != tt.wantErr { t.Errorf("createCertificateRequestSubjectAltNameExtension() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(gotCSR, tt.want) { t.Errorf("createCertificateRequestSubjectAltNameExtension() = %v, want %v", gotCSR, tt.want) } }) } } golang-step-crypto-0.24.0/x509util/fingerprint.go000066400000000000000000000034241437037643100216270ustar00rootroot00000000000000package x509util import ( "crypto/sha256" "crypto/x509" "go.step.sm/crypto/fingerprint" ) // FingerprintEncoding defines the supported encodings in certificate // fingerprints. type FingerprintEncoding = fingerprint.Encoding // Supported fingerprint encodings. const ( // DefaultFingerprint represents the hex encoding of the fingerprint. DefaultFingerprint = FingerprintEncoding(0) // HexFingerprint represents the hex encoding of the fingerprint. HexFingerprint = fingerprint.HexFingerprint // Base64Fingerprint represents the base64 encoding of the fingerprint. Base64Fingerprint = fingerprint.Base64Fingerprint // Base64URLFingerprint represents the base64URL encoding of the fingerprint. Base64URLFingerprint = fingerprint.Base64URLFingerprint // Base64RawFingerprint represents the base64RawStd encoding of the fingerprint. Base64RawFingerprint = fingerprint.Base64RawFingerprint // Base64RawURLFingerprint represents the base64RawURL encoding of the fingerprint. Base64RawURLFingerprint = fingerprint.Base64RawURLFingerprint // EmojiFingerprint represents the emoji encoding of the fingerprint. EmojiFingerprint = fingerprint.EmojiFingerprint ) // Fingerprint returns the SHA-256 fingerprint of the certificate. func Fingerprint(cert *x509.Certificate) string { return EncodedFingerprint(cert, DefaultFingerprint) } // EncodedFingerprint returns the SHA-256 hash of the certificate using the // specified encoding. If an invalid encoding is passed, the return value will // be an empty string. func EncodedFingerprint(cert *x509.Certificate, encoding FingerprintEncoding) string { sum := sha256.Sum256(cert.Raw) switch encoding { case DefaultFingerprint: return fingerprint.Fingerprint(sum[:], HexFingerprint) default: return fingerprint.Fingerprint(sum[:], encoding) } } golang-step-crypto-0.24.0/x509util/fingerprint_test.go000066400000000000000000000043721437037643100226710ustar00rootroot00000000000000package x509util import ( "crypto/x509" "testing" ) func TestFingerprint(t *testing.T) { ecdsaCrt := decodeCertificateFile(t, "testdata/google.crt") rsaCrt := decodeCertificateFile(t, "testdata/smallstep.crt") ed25519Crt := decodeCertificateFile(t, "testdata/ed25519.crt") type args struct { cert *x509.Certificate } tests := []struct { name string args args want string }{ {"ecdsaCert", args{ecdsaCrt}, "38011621ecdcc2172e933a1ef2317efc535a161c00333aee3f84abfab4e640bf"}, {"rsaCert", args{rsaCrt}, "5eeaf6dd1d1f064f6f95c5d74c39ad0abca33bdba59d2844d0b5e6d8453f6c4b"}, {"ed25519Cert", args{ed25519Crt}, "047b2fff20997a5009d1b36864af95b03f168c09dc2ed1a71ee36ccf973c9d31"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := Fingerprint(tt.args.cert); got != tt.want { t.Errorf("Fingerprint() = %v, want %v", got, tt.want) } }) } } func TestEncodedFingerprint(t *testing.T) { ecdsaCrt := decodeCertificateFile(t, "testdata/google.crt") type args struct { cert *x509.Certificate encoding FingerprintEncoding } tests := []struct { name string args args want string }{ {"default", args{ecdsaCrt, 0}, "38011621ecdcc2172e933a1ef2317efc535a161c00333aee3f84abfab4e640bf"}, {"HexFingerprint", args{ecdsaCrt, HexFingerprint}, "38011621ecdcc2172e933a1ef2317efc535a161c00333aee3f84abfab4e640bf"}, {"Base64Fingerprint", args{ecdsaCrt, Base64Fingerprint}, "OAEWIezcwhcukzoe8jF+/FNaFhwAMzruP4Sr+rTmQL8="}, {"Base64URLFingerprint", args{ecdsaCrt, Base64URLFingerprint}, "OAEWIezcwhcukzoe8jF-_FNaFhwAMzruP4Sr-rTmQL8="}, {"Base64RawFingerprint", args{ecdsaCrt, Base64RawFingerprint}, "OAEWIezcwhcukzoe8jF+/FNaFhwAMzruP4Sr+rTmQL8"}, {"Base64RawURLFingerprint", args{ecdsaCrt, Base64RawURLFingerprint}, "OAEWIezcwhcukzoe8jF-_FNaFhwAMzruP4Sr-rTmQL8"}, {"EmojiFingerprint", args{ecdsaCrt, EmojiFingerprint}, "๐Ÿ’จ๐ŸŽฑ๐ŸŒผ๐Ÿ“†๐ŸŽ ๐ŸŽ‰๐ŸŽฟ๐Ÿš™๐Ÿช๐ŸŒŠโ™ฆ๏ธ๐Ÿ’กโœŒ๏ธ๐Ÿฎ๐Ÿ”’โŒ๐Ÿ–•๐Ÿ˜ฌ๐ŸŒผ๐Ÿ‘ฆ๐Ÿ‘๐Ÿ‘‘โ™ฆ๏ธ๐Ÿ‡ฌ๐Ÿ‡ง๐Ÿ‘‚๐Ÿ”ฌ๐Ÿ“Œโ™ฟ๐Ÿš€๐Ÿšœ๐Ÿ†๐Ÿ‘"}, {"Unknown", args{ecdsaCrt, 100}, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := EncodedFingerprint(tt.args.cert, tt.args.encoding); got != tt.want { t.Errorf("EncodedFingerprint() = %v, want %v", got, tt.want) } }) } } golang-step-crypto-0.24.0/x509util/marshal_utils.go000066400000000000000000000115251437037643100221500ustar00rootroot00000000000000package x509util import ( "encoding/asn1" "encoding/json" "net" "net/url" "strconv" "strings" "github.com/pkg/errors" ) // MultiString is a type used to unmarshal a JSON string or an array of strings // into a []string. type MultiString []string // UnmarshalJSON implements the json.Unmarshaler interface for MultiString. func (m *MultiString) UnmarshalJSON(data []byte) error { if s, ok := maybeString(data); ok { *m = MultiString([]string{s}) return nil } var v []string if err := json.Unmarshal(data, &v); err != nil { return errors.Wrap(err, "error unmarshaling json") } *m = MultiString(v) return nil } // MultiIP is a type used to unmarshal a JSON string or an array of strings into // a []net.IP. type MultiIP []net.IP // UnmarshalJSON implements the json.Unmarshaler interface for MultiIP. func (m *MultiIP) UnmarshalJSON(data []byte) error { ms, err := unmarshalMultiString(data) if err != nil { return err } if ms != nil { ips := make([]net.IP, len(ms)) for i, s := range ms { ip := net.ParseIP(s) if ip == nil { return errors.Errorf("error unmarshaling json: ip %s is not valid", s) } ips[i] = ip } *m = MultiIP(ips) } return nil } // MultiIPNet is a type used to unmarshal a JSON string or an array of strings // into a []*net.IPNet. type MultiIPNet []*net.IPNet // MarshalJSON implements the json.Marshaler interface for MultiIPNet. func (m MultiIPNet) MarshalJSON() ([]byte, error) { if m == nil { return []byte("null"), nil } ipNets := make([]string, len(m)) for i, v := range m { ipNets[i] = v.String() } return json.Marshal(ipNets) } // UnmarshalJSON implements the json.Unmarshaler interface for MultiIPNet. func (m *MultiIPNet) UnmarshalJSON(data []byte) error { ms, err := unmarshalMultiString(data) if err != nil { return err } if ms != nil { ipNets := make([]*net.IPNet, len(ms)) for i, s := range ms { _, ipNet, err := net.ParseCIDR(s) if err != nil { return errors.Wrap(err, "error unmarshaling json") } ipNets[i] = ipNet } *m = MultiIPNet(ipNets) } return nil } // MultiURL is a type used to unmarshal a JSON string or an array of strings // into a []*url.URL. type MultiURL []*url.URL // MarshalJSON implements the json.Marshaler interface for MultiURL. func (m MultiURL) MarshalJSON() ([]byte, error) { if m == nil { return []byte("null"), nil } urls := make([]string, len(m)) for i, u := range m { urls[i] = u.String() } return json.Marshal(urls) } // UnmarshalJSON implements the json.Unmarshaler interface for MultiURL. func (m *MultiURL) UnmarshalJSON(data []byte) error { ms, err := unmarshalMultiString(data) if err != nil { return err } if ms != nil { urls := make([]*url.URL, len(ms)) for i, s := range ms { u, err := url.Parse(s) if err != nil { return errors.Wrap(err, "error unmarshaling json") } urls[i] = u } *m = MultiURL(urls) } return nil } // MultiObjectIdentifier is a type used to unmarshal a JSON string or an array // of strings into a []asn1.ObjectIdentifier. type MultiObjectIdentifier []asn1.ObjectIdentifier // MarshalJSON implements the json.Marshaler interface for MultiObjectIdentifier. func (m MultiObjectIdentifier) MarshalJSON() ([]byte, error) { if m == nil { return []byte("null"), nil } oids := make([]string, len(m)) for i, u := range m { oids[i] = u.String() } return json.Marshal(oids) } // UnmarshalJSON implements the json.Unmarshaler interface for // MultiObjectIdentifier. func (m *MultiObjectIdentifier) UnmarshalJSON(data []byte) error { ms, err := unmarshalMultiString(data) if err != nil { return err } if ms != nil { oids := make([]asn1.ObjectIdentifier, len(ms)) for i, s := range ms { oid, err := parseObjectIdentifier(s) if err != nil { return err } oids[i] = oid } *m = MultiObjectIdentifier(oids) } return nil } func maybeString(data []byte) (string, bool) { if len(data) > 0 && data[0] == '"' { var v string if err := json.Unmarshal(data, &v); err == nil { return v, true } } return "", false } func unmarshalString(data []byte) (string, error) { var v string if err := json.Unmarshal(data, &v); err != nil { return v, errors.Wrap(err, "error unmarshaling json") } return v, nil } func unmarshalMultiString(data []byte) ([]string, error) { var v MultiString if err := json.Unmarshal(data, &v); err != nil { return nil, errors.Wrap(err, "error unmarshaling json") } return []string(v), nil } func parseObjectIdentifier(oid string) (asn1.ObjectIdentifier, error) { if oid == "" { return asn1.ObjectIdentifier{}, nil } parts := strings.Split(oid, ".") oids := make([]int, len(parts)) for i, s := range parts { n, err := strconv.Atoi(s) if err != nil { return asn1.ObjectIdentifier{}, errors.Errorf("error unmarshaling json: %s is not an ASN1 object identifier", oid) } oids[i] = n } return asn1.ObjectIdentifier(oids), nil } golang-step-crypto-0.24.0/x509util/marshal_utils_test.go000066400000000000000000000212221437037643100232020ustar00rootroot00000000000000package x509util import ( "encoding/asn1" "encoding/json" "net" "net/url" "reflect" "testing" ) func TestMultiString_MarshalJSON(t *testing.T) { tests := []struct { name string m MultiString want []byte wantErr bool }{ {"ok", []string{"foo", "bar"}, []byte(`["foo","bar"]`), false}, {"empty", []string{}, []byte(`[]`), false}, {"nil", nil, []byte(`null`), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := json.Marshal(tt.m) if (err != nil) != tt.wantErr { t.Errorf("MultiIPNet.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("MultiIPNet.MarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestMultiString_UnmarshalJSON(t *testing.T) { type args struct { data []byte } tests := []struct { name string args args want MultiString wantErr bool }{ {"string", args{[]byte(`"foo"`)}, []string{"foo"}, false}, {"array", args{[]byte(`["foo", "bar", "zar"]`)}, []string{"foo", "bar", "zar"}, false}, {"empty", args{[]byte(`[]`)}, []string{}, false}, {"null", args{[]byte(`null`)}, nil, false}, {"fail", args{[]byte(`["foo"`)}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got MultiString if err := got.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { t.Errorf("MultiString.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("MultiString.UnmarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestMultiIP_MarshalJSON(t *testing.T) { tests := []struct { name string m MultiIP want []byte wantErr bool }{ {"ok", []net.IP{net.ParseIP("::1"), net.ParseIP("1.2.3.4")}, []byte(`["::1","1.2.3.4"]`), false}, {"empty", []net.IP{}, []byte(`[]`), false}, {"nil", nil, []byte(`null`), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := json.Marshal(tt.m) if (err != nil) != tt.wantErr { t.Errorf("MultiIPNet.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("MultiIPNet.MarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestMultiIP_UnmarshalJSON(t *testing.T) { type args struct { data []byte } tests := []struct { name string args args want MultiIP wantErr bool }{ {"string", args{[]byte(`"::1"`)}, []net.IP{net.ParseIP("::1")}, false}, {"array", args{[]byte(`["127.0.0.1", "::1"]`)}, []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")}, false}, {"empty", args{[]byte(`[]`)}, []net.IP{}, false}, {"null", args{[]byte(`null`)}, nil, false}, {"fail", args{[]byte(`"foo.bar"`)}, nil, true}, {"failJSON", args{[]byte(`["::1"`)}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got MultiIP if err := got.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { t.Errorf("MultiIP.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("MultiIP.UnmarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestMultiIPNet_MarshalJSON(t *testing.T) { ipNet := func(s string) *net.IPNet { _, ipNet, err := net.ParseCIDR(s) if err != nil { t.Fatal(err) } return ipNet } tests := []struct { name string m MultiIPNet want []byte wantErr bool }{ {"ok", []*net.IPNet{ipNet("1.1.0.0/16"), ipNet("2001:db8:8a2e:7334::/64")}, []byte(`["1.1.0.0/16","2001:db8:8a2e:7334::/64"]`), false}, {"empty", []*net.IPNet{}, []byte(`[]`), false}, {"nil", nil, []byte(`null`), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.m.MarshalJSON() if (err != nil) != tt.wantErr { t.Errorf("MultiIPNet.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("MultiIPNet.MarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestMultiIPNet_UnmarshalJSON(t *testing.T) { ipNet := func(s string) *net.IPNet { _, ipNet, err := net.ParseCIDR(s) if err != nil { t.Fatal(err) } return ipNet } type args struct { data []byte } tests := []struct { name string args args want MultiIPNet wantErr bool }{ {"string", args{[]byte(`"1.1.0.0/16"`)}, []*net.IPNet{ipNet("1.1.0.0/16")}, false}, {"array", args{[]byte(`["1.0.0.0/24", "2.1.0.0/16"]`)}, []*net.IPNet{ipNet("1.0.0.0/24"), ipNet("2.1.0.0/16")}, false}, {"empty", args{[]byte(`[]`)}, []*net.IPNet{}, false}, {"null", args{[]byte(`null`)}, nil, false}, {"fail", args{[]byte(`"foo.bar/16"`)}, nil, true}, {"failJSON", args{[]byte(`["1.0.0.0/24"`)}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got MultiIPNet if err := got.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { t.Errorf("MultiIPNet.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("MultiIPNet.UnmarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestMultiURL_MarshalJSON(t *testing.T) { tests := []struct { name string m MultiURL want []byte wantErr bool }{ {"ok", []*url.URL{{Scheme: "https", Host: "iss", Fragment: "sub"}, {Scheme: "uri", Opaque: "foo:bar"}}, []byte(`["https://iss#sub","uri:foo:bar"]`), false}, {"empty", []*url.URL{}, []byte(`[]`), false}, {"nil", nil, []byte(`null`), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.m.MarshalJSON() if (err != nil) != tt.wantErr { t.Errorf("MultiURL.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("MultiURL.MarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestMultiURL_UnmarshalJSON(t *testing.T) { type args struct { data []byte } tests := []struct { name string args args want MultiURL wantErr bool }{ {"string", args{[]byte(`"https://iss#sub"`)}, []*url.URL{{Scheme: "https", Host: "iss", Fragment: "sub"}}, false}, {"array", args{[]byte(`["https://iss#sub", "uri:foo:bar"]`)}, []*url.URL{{Scheme: "https", Host: "iss", Fragment: "sub"}, {Scheme: "uri", Opaque: "foo:bar"}}, false}, {"empty", args{[]byte(`[]`)}, []*url.URL{}, false}, {"null", args{[]byte(`null`)}, nil, false}, {"fail", args{[]byte(`":foo:bar"`)}, nil, true}, {"failJSON", args{[]byte(`["https://iss#sub"`)}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got MultiURL if err := got.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { t.Errorf("MultiURL.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("MultiURL.UnmarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestMultiObjectIdentifier_MarshalJSON(t *testing.T) { tests := []struct { name string m MultiObjectIdentifier want []byte wantErr bool }{ {"ok", []asn1.ObjectIdentifier{[]int{1, 2, 3, 4}, []int{5, 6, 7, 8, 9, 0}}, []byte(`["1.2.3.4","5.6.7.8.9.0"]`), false}, {"empty", []asn1.ObjectIdentifier{}, []byte(`[]`), false}, {"nil", nil, []byte(`null`), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := json.Marshal(tt.m) if (err != nil) != tt.wantErr { t.Errorf("MultiObjectIdentifier.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("MultiObjectIdentifier.MarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestMultiObjectIdentifier_UnmarshalJSON(t *testing.T) { type args struct { data []byte } tests := []struct { name string args args want MultiObjectIdentifier wantErr bool }{ {"string", args{[]byte(`"1.2.3.4"`)}, []asn1.ObjectIdentifier{[]int{1, 2, 3, 4}}, false}, {"array", args{[]byte(`["1.2.3.4", "5.6.7.8.9.0"]`)}, []asn1.ObjectIdentifier{[]int{1, 2, 3, 4}, []int{5, 6, 7, 8, 9, 0}}, false}, {"empty", args{[]byte(`[]`)}, []asn1.ObjectIdentifier{}, false}, {"null", args{[]byte(`null`)}, nil, false}, {"fail", args{[]byte(`":foo:bar"`)}, nil, true}, {"failJSON", args{[]byte(`["https://iss#sub"`)}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got MultiObjectIdentifier if err := got.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { t.Errorf("MultiObjectIdentifier.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("MultiObjectIdentifier.UnmarshalJSON() = %v, want %v", got, tt.want) } }) } } golang-step-crypto-0.24.0/x509util/name.go000066400000000000000000000135061437037643100202220ustar00rootroot00000000000000package x509util import ( "bytes" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/json" "github.com/pkg/errors" ) // attributeTypeNames are the subject attributes managed by Go and this package. // newExtraNames will populate .Insecure.CR.ExtraNames with the attributes not // present on this map. var attributeTypeNames = map[string]string{ "2.5.4.6": "C", "2.5.4.10": "O", "2.5.4.11": "OU", "2.5.4.3": "CN", "2.5.4.5": "SERIALNUMBER", "2.5.4.7": "L", "2.5.4.8": "ST", "2.5.4.9": "STREET", "2.5.4.17": "POSTALCODE", } // oidEmailAddress is the oid of the deprecated emailAddress in the subject. var oidEmailAddress = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1} // Name is the JSON representation of X.501 type Name, used in the X.509 subject // and issuer fields. type Name struct { Country MultiString `json:"country,omitempty"` Organization MultiString `json:"organization,omitempty"` OrganizationalUnit MultiString `json:"organizationalUnit,omitempty"` Locality MultiString `json:"locality,omitempty"` Province MultiString `json:"province,omitempty"` StreetAddress MultiString `json:"streetAddress,omitempty"` PostalCode MultiString `json:"postalCode,omitempty"` SerialNumber string `json:"serialNumber,omitempty"` CommonName string `json:"commonName,omitempty"` ExtraNames []DistinguishedName `json:"extraNames,omitempty"` } func newName(n pkix.Name) Name { return Name{ Country: n.Country, Organization: n.Organization, OrganizationalUnit: n.OrganizationalUnit, Locality: n.Locality, Province: n.Province, StreetAddress: n.StreetAddress, PostalCode: n.PostalCode, SerialNumber: n.SerialNumber, CommonName: n.CommonName, ExtraNames: newExtraNames(n.Names), } } // goValue converts Name to its Go representation. func (n Name) goValue() pkix.Name { return pkix.Name{ Country: n.Country, Organization: n.Organization, OrganizationalUnit: n.OrganizationalUnit, Locality: n.Locality, Province: n.Province, StreetAddress: n.StreetAddress, PostalCode: n.PostalCode, SerialNumber: n.SerialNumber, CommonName: n.CommonName, ExtraNames: fromDistinguishedNames(n.ExtraNames), } } // UnmarshalJSON implements the json.Unmarshal interface and unmarshals a JSON // object in the Name struct or a string as just the subject common name. func (n *Name) UnmarshalJSON(data []byte) error { if cn, ok := maybeString(data); ok { n.CommonName = cn return nil } type nameAlias Name var nn nameAlias if err := json.Unmarshal(data, &nn); err != nil { return errors.Wrap(err, "error unmarshaling json") } *n = Name(nn) return nil } // Subject is the JSON representation of the X.509 subject field. type Subject Name func newSubject(n pkix.Name) Subject { return Subject(newName(n)) } // UnmarshalJSON implements the json.Unmarshal interface and unmarshals a JSON // object in the Subject struct or a string as just the subject common name. func (s *Subject) UnmarshalJSON(data []byte) error { var name Name if err := name.UnmarshalJSON(data); err != nil { return err } *s = Subject(name) return nil } // Set sets the subject in the given certificate. func (s Subject) Set(c *x509.Certificate) { c.Subject = Name(s).goValue() } // IsEmpty returns if the subject is empty. Certificates with an empty subject // must have the subjectAltName extension mark as critical. func (s Subject) IsEmpty() bool { subject := Name(s).goValue() if asn1Subject, err := asn1.Marshal(subject.ToRDNSequence()); err == nil { return bytes.Equal(asn1Subject, emptyASN1Subject) } return false } // Issuer is the JSON representation of the X.509 issuer field. type Issuer Name func newIssuer(n pkix.Name) Issuer { return Issuer(newName(n)) } // UnmarshalJSON implements the json.Unmarshal interface and unmarshals a JSON // object in the Issuer struct or a string as just the subject common name. func (i *Issuer) UnmarshalJSON(data []byte) error { var name Name if err := name.UnmarshalJSON(data); err != nil { return err } *i = Issuer(name) return nil } // Set sets the issuer in the given certificate. func (i Issuer) Set(c *x509.Certificate) { c.Issuer = Name(i).goValue() } // DistinguishedName mirrors the ASN.1 structure AttributeTypeAndValue in RFC // 5280, Section 4.1.2.4. type DistinguishedName struct { Type ObjectIdentifier `json:"type"` Value interface{} `json:"value"` } // newExtraNames returns a list of DistinguishedName with the attributes not // present in attributeTypeNames. func newExtraNames(atvs []pkix.AttributeTypeAndValue) []DistinguishedName { var extraNames []DistinguishedName for _, atv := range atvs { if _, ok := attributeTypeNames[atv.Type.String()]; !ok { extraNames = append(extraNames, DistinguishedName{ Type: ObjectIdentifier(atv.Type), Value: atv.Value, }) } } return extraNames } // fromDistinguishedNames converts a list of DistinguishedName to // []pkix.AttributeTypeAndValue. Note that this method has a special case to // encode the deprecated emailAddress field (1.2.840.113549.1.9.1). func fromDistinguishedNames(dns []DistinguishedName) []pkix.AttributeTypeAndValue { var atvs []pkix.AttributeTypeAndValue for _, dn := range dns { typ := asn1.ObjectIdentifier(dn.Type) v, isString := dn.Value.(string) if typ.Equal(oidEmailAddress) && isString { atvs = append(atvs, pkix.AttributeTypeAndValue{ Type: typ, Value: asn1.RawValue{ Class: asn1.ClassUniversal, Tag: asn1.TagIA5String, Bytes: []byte(v), }, }) } else { atvs = append(atvs, pkix.AttributeTypeAndValue{ Type: typ, Value: dn.Value, }) } } return atvs } golang-step-crypto-0.24.0/x509util/name_test.go000066400000000000000000000507361437037643100212670ustar00rootroot00000000000000package x509util import ( "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "reflect" "testing" ) func Test_newName(t *testing.T) { type args struct { n pkix.Name } tests := []struct { name string args args want Name }{ {"ok", args{pkix.Name{ Country: []string{"The country"}, Organization: []string{"The organization"}, OrganizationalUnit: []string{"The organizationalUnit 1", "The organizationalUnit 2"}, Locality: []string{"The locality 1", "The locality 2"}, Province: []string{"The province"}, StreetAddress: []string{"The streetAddress"}, PostalCode: []string{"The postalCode"}, SerialNumber: "The serialNumber", CommonName: "The commonName", Names: []pkix.AttributeTypeAndValue{ {Type: asn1.ObjectIdentifier{2, 5, 4, 6}, Value: "The country"}, {Type: asn1.ObjectIdentifier{2, 5, 4, 10}, Value: "The organization"}, {Type: asn1.ObjectIdentifier{2, 5, 4, 11}, Value: "The organizationalUnit 1"}, {Type: asn1.ObjectIdentifier{2, 5, 4, 11}, Value: "The organizationalUnit 2"}, {Type: asn1.ObjectIdentifier{2, 5, 4, 3}, Value: "The commonName"}, {Type: asn1.ObjectIdentifier{2, 5, 4, 5}, Value: "The serialNumber"}, {Type: asn1.ObjectIdentifier{2, 5, 4, 7}, Value: "The locality 1"}, {Type: asn1.ObjectIdentifier{2, 5, 4, 7}, Value: "The locality 2"}, {Type: asn1.ObjectIdentifier{2, 5, 4, 8}, Value: "The province"}, {Type: asn1.ObjectIdentifier{2, 5, 4, 9}, Value: "The streetAddress"}, {Type: asn1.ObjectIdentifier{2, 5, 4, 17}, Value: "The postalCode"}, {Type: asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}, Value: asn1.RawValue{Class: asn1.ClassUniversal, Tag: asn1.TagIA5String, Bytes: []byte("jane@example.com")}}, }, }}, Name{ Country: []string{"The country"}, Organization: []string{"The organization"}, OrganizationalUnit: []string{"The organizationalUnit 1", "The organizationalUnit 2"}, Locality: []string{"The locality 1", "The locality 2"}, Province: []string{"The province"}, StreetAddress: []string{"The streetAddress"}, PostalCode: []string{"The postalCode"}, SerialNumber: "The serialNumber", CommonName: "The commonName", ExtraNames: []DistinguishedName{ {Type: ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}, Value: asn1.RawValue{Class: asn1.ClassUniversal, Tag: asn1.TagIA5String, Bytes: []byte("jane@example.com")}}, }, }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := newName(tt.args.n); !reflect.DeepEqual(got, tt.want) { t.Errorf("newName() = %v, want %v", got, tt.want) } }) } } func TestName_UnmarshalJSON(t *testing.T) { type args struct { data []byte } tests := []struct { name string args args want Name wantErr bool }{ {"null", args{[]byte("null")}, Name{}, false}, {"empty", args{[]byte("{}")}, Name{}, false}, {"commonName", args{[]byte(`"commonName"`)}, Name{CommonName: "commonName"}, false}, {"object", args{[]byte(`{ "country": "The country", "organization": "The organization", "organizationalUnit": ["The organizationalUnit 1", "The organizationalUnit 2"], "locality": ["The locality 1", "The locality 2"], "province": "The province", "streetAddress": "The streetAddress", "postalCode": "The postalCode", "serialNumber": "The serialNumber", "commonName": "The commonName", "extraNames": [{"type":"1.2.840.113549.1.9.1", "value":"jane@example.com"}] }`)}, Name{ Country: []string{"The country"}, Organization: []string{"The organization"}, OrganizationalUnit: []string{"The organizationalUnit 1", "The organizationalUnit 2"}, Locality: []string{"The locality 1", "The locality 2"}, Province: []string{"The province"}, StreetAddress: []string{"The streetAddress"}, PostalCode: []string{"The postalCode"}, SerialNumber: "The serialNumber", CommonName: "The commonName", ExtraNames: []DistinguishedName{ {Type: ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}, Value: "jane@example.com"}, }, }, false}, {"number", args{[]byte("1234")}, Name{}, true}, {"badJSON", args{[]byte("'badJSON'")}, Name{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got Name if err := got.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { t.Errorf("Name.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("Name.UnmarshalJSON() = %v, want %v", got, tt.want) } }) } } func Test_newSubject(t *testing.T) { type args struct { n pkix.Name } tests := []struct { name string args args want Subject }{ {"ok", args{pkix.Name{ Country: []string{"The country"}, Organization: []string{"The organization"}, OrganizationalUnit: []string{"The organizationalUnit 1", "The organizationalUnit 2"}, Locality: []string{"The locality 1", "The locality 2"}, Province: []string{"The province"}, StreetAddress: []string{"The streetAddress"}, PostalCode: []string{"The postalCode"}, SerialNumber: "The serialNumber", CommonName: "The commonName", Names: []pkix.AttributeTypeAndValue{ {Type: asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}, Value: asn1.RawValue{Class: asn1.ClassUniversal, Tag: asn1.TagIA5String, Bytes: []byte("jane@example.com")}}, }, }}, Subject{ Country: []string{"The country"}, Organization: []string{"The organization"}, OrganizationalUnit: []string{"The organizationalUnit 1", "The organizationalUnit 2"}, Locality: []string{"The locality 1", "The locality 2"}, Province: []string{"The province"}, StreetAddress: []string{"The streetAddress"}, PostalCode: []string{"The postalCode"}, SerialNumber: "The serialNumber", CommonName: "The commonName", ExtraNames: []DistinguishedName{ {Type: ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}, Value: asn1.RawValue{Class: asn1.ClassUniversal, Tag: asn1.TagIA5String, Bytes: []byte("jane@example.com")}}, }, }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := newSubject(tt.args.n); !reflect.DeepEqual(got, tt.want) { t.Errorf("newSubject() = %v, want %v", got, tt.want) } }) } } func TestSubject_UnmarshalJSON(t *testing.T) { type args struct { data []byte } tests := []struct { name string args args want Subject wantErr bool }{ {"null", args{[]byte("null")}, Subject{}, false}, {"empty", args{[]byte("{}")}, Subject{}, false}, {"commonName", args{[]byte(`"commonName"`)}, Subject{CommonName: "commonName"}, false}, {"object", args{[]byte(`{ "country": "The country", "organization": "The organization", "organizationalUnit": ["The organizationalUnit 1", "The organizationalUnit 2"], "locality": ["The locality 1", "The locality 2"], "province": "The province", "streetAddress": "The streetAddress", "postalCode": "The postalCode", "serialNumber": "The serialNumber", "commonName": "The commonName" }`)}, Subject{ Country: []string{"The country"}, Organization: []string{"The organization"}, OrganizationalUnit: []string{"The organizationalUnit 1", "The organizationalUnit 2"}, Locality: []string{"The locality 1", "The locality 2"}, Province: []string{"The province"}, StreetAddress: []string{"The streetAddress"}, PostalCode: []string{"The postalCode"}, SerialNumber: "The serialNumber", CommonName: "The commonName", }, false}, {"number", args{[]byte("1234")}, Subject{}, true}, {"badJSON", args{[]byte("'badJSON'")}, Subject{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got Subject if err := got.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { t.Errorf("Subject.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("Subject.UnmarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestSubject_Set(t *testing.T) { type fields struct { Country MultiString Organization MultiString OrganizationalUnit MultiString Locality MultiString Province MultiString StreetAddress MultiString PostalCode MultiString SerialNumber string CommonName string } type args struct { c *x509.Certificate } tests := []struct { name string fields fields args args want *x509.Certificate }{ {"ok", fields{ Country: []string{"The country"}, Organization: []string{"The organization"}, OrganizationalUnit: []string{"The organizationalUnit 1", "The organizationalUnit 2"}, Locality: []string{"The locality 1", "The locality 2"}, Province: []string{"The province"}, StreetAddress: []string{"The streetAddress"}, PostalCode: []string{"The postalCode"}, SerialNumber: "The serialNumber", CommonName: "The commonName", }, args{&x509.Certificate{}}, &x509.Certificate{ Subject: pkix.Name{ Country: []string{"The country"}, Organization: []string{"The organization"}, OrganizationalUnit: []string{"The organizationalUnit 1", "The organizationalUnit 2"}, Locality: []string{"The locality 1", "The locality 2"}, Province: []string{"The province"}, StreetAddress: []string{"The streetAddress"}, PostalCode: []string{"The postalCode"}, SerialNumber: "The serialNumber", CommonName: "The commonName", }, }}, {"overwrite", fields{ CommonName: "The commonName", }, args{&x509.Certificate{}}, &x509.Certificate{ Subject: pkix.Name{ CommonName: "The commonName", }, }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := Subject{ Country: tt.fields.Country, Organization: tt.fields.Organization, OrganizationalUnit: tt.fields.OrganizationalUnit, Locality: tt.fields.Locality, Province: tt.fields.Province, StreetAddress: tt.fields.StreetAddress, PostalCode: tt.fields.PostalCode, SerialNumber: tt.fields.SerialNumber, CommonName: tt.fields.CommonName, } s.Set(tt.args.c) if !reflect.DeepEqual(tt.args.c, tt.want) { t.Errorf("Subject.Set() = %v, want %v", tt.args.c, tt.want) } }) } } func TestSubject_IsEmpty(t *testing.T) { type fields struct { Country MultiString Organization MultiString OrganizationalUnit MultiString Locality MultiString Province MultiString StreetAddress MultiString PostalCode MultiString SerialNumber string CommonName string ExtraNames []DistinguishedName } tests := []struct { name string fields fields want bool }{ {"ok", fields{}, true}, {"country", fields{Country: []string{"The country"}}, false}, {"commonName", fields{CommonName: "The commonName"}, false}, {"all fields", fields{ Country: []string{"The country"}, Organization: []string{"The organization"}, OrganizationalUnit: []string{"The organizationalUnit 1", "The organizationalUnit 2"}, Locality: []string{"The locality 1", "The locality 2"}, Province: []string{"The province"}, StreetAddress: []string{"The streetAddress"}, PostalCode: []string{"The postalCode"}, SerialNumber: "The serialNumber", CommonName: "The commonName", }, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := Subject{ Country: tt.fields.Country, Organization: tt.fields.Organization, OrganizationalUnit: tt.fields.OrganizationalUnit, Locality: tt.fields.Locality, Province: tt.fields.Province, StreetAddress: tt.fields.StreetAddress, PostalCode: tt.fields.PostalCode, SerialNumber: tt.fields.SerialNumber, CommonName: tt.fields.CommonName, ExtraNames: tt.fields.ExtraNames, } if got := s.IsEmpty(); got != tt.want { t.Errorf("Subject.IsEmpty() = %v, want %v", got, tt.want) } }) } } func Test_newIssuer(t *testing.T) { type args struct { n pkix.Name } tests := []struct { name string args args want Issuer }{ {"ok", args{pkix.Name{ Country: []string{"The country"}, Organization: []string{"The organization"}, OrganizationalUnit: []string{"The organizationalUnit 1", "The organizationalUnit 2"}, Locality: []string{"The locality 1", "The locality 2"}, Province: []string{"The province"}, StreetAddress: []string{"The streetAddress"}, PostalCode: []string{"The postalCode"}, SerialNumber: "The serialNumber", CommonName: "The commonName", }}, Issuer{ Country: []string{"The country"}, Organization: []string{"The organization"}, OrganizationalUnit: []string{"The organizationalUnit 1", "The organizationalUnit 2"}, Locality: []string{"The locality 1", "The locality 2"}, Province: []string{"The province"}, StreetAddress: []string{"The streetAddress"}, PostalCode: []string{"The postalCode"}, SerialNumber: "The serialNumber", CommonName: "The commonName", }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := newIssuer(tt.args.n); !reflect.DeepEqual(got, tt.want) { t.Errorf("newIssuer() = %v, want %v", got, tt.want) } }) } } func TestIssuer_UnmarshalJSON(t *testing.T) { type args struct { data []byte } tests := []struct { name string args args want Issuer wantErr bool }{ {"null", args{[]byte("null")}, Issuer{}, false}, {"empty", args{[]byte("{}")}, Issuer{}, false}, {"commonName", args{[]byte(`"commonName"`)}, Issuer{CommonName: "commonName"}, false}, {"object", args{[]byte(`{ "country": "The country", "organization": "The organization", "organizationalUnit": ["The organizationalUnit 1", "The organizationalUnit 2"], "locality": ["The locality 1", "The locality 2"], "province": "The province", "streetAddress": "The streetAddress", "postalCode": "The postalCode", "serialNumber": "The serialNumber", "commonName": "The commonName", "extraNames": [{"type":"1.2.840.113549.1.9.1", "value":"jane@example.com"}] }`)}, Issuer{ Country: []string{"The country"}, Organization: []string{"The organization"}, OrganizationalUnit: []string{"The organizationalUnit 1", "The organizationalUnit 2"}, Locality: []string{"The locality 1", "The locality 2"}, Province: []string{"The province"}, StreetAddress: []string{"The streetAddress"}, PostalCode: []string{"The postalCode"}, SerialNumber: "The serialNumber", CommonName: "The commonName", ExtraNames: []DistinguishedName{ {Type: ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}, Value: "jane@example.com"}, }, }, false}, {"number", args{[]byte("1234")}, Issuer{}, true}, {"badJSON", args{[]byte("'badJSON'")}, Issuer{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got Issuer if err := got.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { t.Errorf("Issuer.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("Issuer.UnmarshalJSON() = %v, want %v", got, tt.want) } }) } } func TestIssuer_Set(t *testing.T) { type fields struct { Country MultiString Organization MultiString OrganizationalUnit MultiString Locality MultiString Province MultiString StreetAddress MultiString PostalCode MultiString SerialNumber string CommonName string ExtraNames []DistinguishedName } type args struct { c *x509.Certificate } tests := []struct { name string fields fields args args want *x509.Certificate }{ {"ok", fields{ Country: []string{"The country"}, Organization: []string{"The organization"}, OrganizationalUnit: []string{"The organizationalUnit 1", "The organizationalUnit 2"}, Locality: []string{"The locality 1", "The locality 2"}, Province: []string{"The province"}, StreetAddress: []string{"The streetAddress"}, PostalCode: []string{"The postalCode"}, SerialNumber: "The serialNumber", CommonName: "The commonName", ExtraNames: []DistinguishedName{ {Type: ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}, Value: "jane@example.com"}, {Type: ObjectIdentifier{1, 2, 3, 4}, Value: "custom@example.com"}, }, }, args{&x509.Certificate{}}, &x509.Certificate{ Issuer: pkix.Name{ Country: []string{"The country"}, Organization: []string{"The organization"}, OrganizationalUnit: []string{"The organizationalUnit 1", "The organizationalUnit 2"}, Locality: []string{"The locality 1", "The locality 2"}, Province: []string{"The province"}, StreetAddress: []string{"The streetAddress"}, PostalCode: []string{"The postalCode"}, SerialNumber: "The serialNumber", CommonName: "The commonName", ExtraNames: []pkix.AttributeTypeAndValue{ {Type: asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}, Value: asn1.RawValue{Class: asn1.ClassUniversal, Tag: asn1.TagIA5String, Bytes: []byte("jane@example.com")}}, {Type: asn1.ObjectIdentifier{1, 2, 3, 4}, Value: "custom@example.com"}, }, }, }}, {"overwrite", fields{ CommonName: "The commonName", }, args{&x509.Certificate{}}, &x509.Certificate{ Issuer: pkix.Name{ CommonName: "The commonName", }, }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { i := Issuer{ Country: tt.fields.Country, Organization: tt.fields.Organization, OrganizationalUnit: tt.fields.OrganizationalUnit, Locality: tt.fields.Locality, Province: tt.fields.Province, StreetAddress: tt.fields.StreetAddress, PostalCode: tt.fields.PostalCode, SerialNumber: tt.fields.SerialNumber, CommonName: tt.fields.CommonName, ExtraNames: tt.fields.ExtraNames, } i.Set(tt.args.c) if !reflect.DeepEqual(tt.args.c, tt.want) { t.Errorf("Issuer.Set() = %v, want %v", tt.args.c, tt.want) } }) } } func Test_newExtraNames(t *testing.T) { type args struct { atvs []pkix.AttributeTypeAndValue } tests := []struct { name string args args want []DistinguishedName }{ {"ok", args{[]pkix.AttributeTypeAndValue{ {Type: asn1.ObjectIdentifier{2, 5, 4, 3}, Value: "The commonName"}, {Type: asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}, Value: asn1.RawValue{Class: asn1.ClassUniversal, Tag: asn1.TagIA5String, Bytes: []byte("jane@example.com")}}, {Type: asn1.ObjectIdentifier{1, 2, 3, 4}, Value: "custom@example.com"}, }}, []DistinguishedName{ {Type: ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}, Value: asn1.RawValue{Class: asn1.ClassUniversal, Tag: asn1.TagIA5String, Bytes: []byte("jane@example.com")}}, {Type: ObjectIdentifier{1, 2, 3, 4}, Value: "custom@example.com"}, }}, {"ok nil", args{nil}, nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := newExtraNames(tt.args.atvs); !reflect.DeepEqual(got, tt.want) { t.Errorf("newDistinguisedNames() = %v, want %v", got, tt.want) } }) } } func Test_fromDistinguisedNames(t *testing.T) { type args struct { dns []DistinguishedName } tests := []struct { name string args args want []pkix.AttributeTypeAndValue }{ {"ok", args{[]DistinguishedName{ {Type: ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}, Value: "jane@example.com"}, {Type: ObjectIdentifier{1, 2, 3, 4}, Value: "custom@example.com"}, }}, []pkix.AttributeTypeAndValue{ {Type: asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}, Value: asn1.RawValue{Class: asn1.ClassUniversal, Tag: asn1.TagIA5String, Bytes: []byte("jane@example.com")}}, {Type: asn1.ObjectIdentifier{1, 2, 3, 4}, Value: "custom@example.com"}, }}, {"ok nil", args{nil}, nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := fromDistinguishedNames(tt.args.dns); !reflect.DeepEqual(got, tt.want) { t.Errorf("fromDistinguisedNames() = %v, want %v", got, tt.want) } }) } } golang-step-crypto-0.24.0/x509util/options.go000066400000000000000000000042051437037643100207710ustar00rootroot00000000000000package x509util import ( "bytes" "crypto/x509" "encoding/base64" "os" "text/template" "github.com/pkg/errors" "go.step.sm/crypto/internal/step" "go.step.sm/crypto/internal/templates" ) // Options are the options that can be passed to NewCertificate. type Options struct { CertBuffer *bytes.Buffer } func (o *Options) apply(cr *x509.CertificateRequest, opts []Option) (*Options, error) { for _, fn := range opts { if err := fn(cr, o); err != nil { return o, err } } return o, nil } // Option is the type used as a variadic argument in NewCertificate. type Option func(cr *x509.CertificateRequest, o *Options) error // WithTemplate is an options that executes the given template text with the // given data. func WithTemplate(text string, data TemplateData) Option { return func(cr *x509.CertificateRequest, o *Options) error { terr := new(TemplateError) funcMap := templates.GetFuncMap(&terr.Message) tmpl, err := template.New("template").Funcs(funcMap).Parse(text) if err != nil { return errors.Wrapf(err, "error parsing template") } buf := new(bytes.Buffer) data.SetCertificateRequest(cr) if err := tmpl.Execute(buf, data); err != nil { if terr.Message != "" { return terr } return errors.Wrapf(err, "error executing template") } o.CertBuffer = buf return nil } } // WithTemplateBase64 is an options that executes the given template base64 // string with the given data. func WithTemplateBase64(s string, data TemplateData) Option { return func(cr *x509.CertificateRequest, o *Options) error { b, err := base64.StdEncoding.DecodeString(s) if err != nil { return errors.Wrap(err, "error decoding template") } fn := WithTemplate(string(b), data) return fn(cr, o) } } // WithTemplateFile is an options that reads the template file and executes it // with the given data. func WithTemplateFile(path string, data TemplateData) Option { return func(cr *x509.CertificateRequest, o *Options) error { filename := step.Abs(path) b, err := os.ReadFile(filename) if err != nil { return errors.Wrapf(err, "error reading %s", path) } fn := WithTemplate(string(b), data) return fn(cr, o) } } golang-step-crypto-0.24.0/x509util/options_test.go000066400000000000000000000202611437037643100220300ustar00rootroot00000000000000package x509util import ( "bytes" "crypto" "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/base64" "reflect" "testing" ) func createRSACertificateRequest(t *testing.T, bits int, commonName string, sans []string) (*x509.CertificateRequest, crypto.Signer) { dnsNames, ips, emails, uris := SplitSANs(sans) t.Helper() priv, err := rsa.GenerateKey(rand.Reader, bits) if err != nil { t.Fatal(err) } asn1Data, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{ Subject: pkix.Name{CommonName: commonName}, DNSNames: dnsNames, IPAddresses: ips, EmailAddresses: emails, URIs: uris, SignatureAlgorithm: x509.SHA256WithRSAPSS, }, priv) if err != nil { t.Fatal(err) } cr, err := x509.ParseCertificateRequest(asn1Data) if err != nil { t.Fatal(err) } return cr, priv } func TestWithTemplate(t *testing.T) { cr, _ := createCertificateRequest(t, "foo", []string{"foo.com", "foo@foo.com", "::1", "https://foo.com"}) crRSA, _ := createRSACertificateRequest(t, 2048, "foo", []string{"foo.com", "foo@foo.com", "::1", "https://foo.com"}) crQuotes, _ := createCertificateRequest(t, `foo"}`, []string{"foo.com", "foo@foo.com", "::1", "https://foo.com"}) type args struct { text string data TemplateData cr *x509.CertificateRequest } tests := []struct { name string args args want Options wantErr bool }{ {"leaf", args{DefaultLeafTemplate, TemplateData{ SubjectKey: Subject{CommonName: "foo"}, SANsKey: []SubjectAlternativeName{{Type: "dns", Value: "foo.com"}}, }, cr}, Options{ CertBuffer: bytes.NewBufferString(`{ "subject": {"commonName":"foo"}, "sans": [{"type":"dns","value":"foo.com"}], "keyUsage": ["digitalSignature"], "extKeyUsage": ["serverAuth", "clientAuth"] }`), }, false}, {"leafRSA", args{DefaultLeafTemplate, TemplateData{ SubjectKey: Subject{CommonName: "foo"}, SANsKey: []SubjectAlternativeName{{Type: "dns", Value: "foo.com"}}, }, crRSA}, Options{ CertBuffer: bytes.NewBufferString(`{ "subject": {"commonName":"foo"}, "sans": [{"type":"dns","value":"foo.com"}], "keyUsage": ["keyEncipherment", "digitalSignature"], "extKeyUsage": ["serverAuth", "clientAuth"] }`), }, false}, {"iid", args{DefaultIIDLeafTemplate, TemplateData{}, cr}, Options{ CertBuffer: bytes.NewBufferString(`{ "subject": {"commonName": "foo"}, "dnsNames": ["foo.com"], "emailAddresses": ["foo@foo.com"], "ipAddresses": ["::1"], "uris": ["https://foo.com"], "keyUsage": ["digitalSignature"], "extKeyUsage": ["serverAuth", "clientAuth"] }`), }, false}, {"iidRSAAndEnforced", args{DefaultIIDLeafTemplate, TemplateData{ SANsKey: []SubjectAlternativeName{{Type: "dns", Value: "foo.com"}}, }, crRSA}, Options{ CertBuffer: bytes.NewBufferString(`{ "subject": {"commonName": "foo"}, "sans": [{"type":"dns","value":"foo.com"}], "keyUsage": ["keyEncipherment", "digitalSignature"], "extKeyUsage": ["serverAuth", "clientAuth"] }`), }, false}, {"iidEscape", args{DefaultIIDLeafTemplate, TemplateData{}, crQuotes}, Options{ CertBuffer: bytes.NewBufferString(`{ "subject": {"commonName": "foo\"}"}, "dnsNames": ["foo.com"], "emailAddresses": ["foo@foo.com"], "ipAddresses": ["::1"], "uris": ["https://foo.com"], "keyUsage": ["digitalSignature"], "extKeyUsage": ["serverAuth", "clientAuth"] }`), }, false}, {"admin", args{DefaultAdminLeafTemplate, TemplateData{}, cr}, Options{ CertBuffer: bytes.NewBufferString(`{ "subject": {"commonName":"foo"}, "dnsNames": ["foo.com"], "emailAddresses": ["foo@foo.com"], "ipAddresses": ["::1"], "uris": ["https://foo.com"], "keyUsage": ["digitalSignature"], "extKeyUsage": ["serverAuth", "clientAuth"] }`)}, false}, {"adminRSA", args{DefaultAdminLeafTemplate, TemplateData{}, crRSA}, Options{ CertBuffer: bytes.NewBufferString(`{ "subject": {"commonName":"foo"}, "dnsNames": ["foo.com"], "emailAddresses": ["foo@foo.com"], "ipAddresses": ["::1"], "uris": ["https://foo.com"], "keyUsage": ["keyEncipherment", "digitalSignature"], "extKeyUsage": ["serverAuth", "clientAuth"] }`)}, false}, {"fail", args{`{{ fail "a message" }}`, TemplateData{}, cr}, Options{}, true}, {"error", args{`{{ mustHas 3 .Data }}`, TemplateData{ "Data": 3, }, cr}, Options{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got Options fn := WithTemplate(tt.args.text, tt.args.data) if err := fn(tt.args.cr, &got); (err != nil) != tt.wantErr { t.Errorf("WithTemplate() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("WithTemplate() = %v, want %v", got, tt.want) } }) } } func TestWithTemplateBase64(t *testing.T) { cr, _ := createCertificateRequest(t, "foo", []string{"foo.com", "foo@foo.com", "::1", "https://foo.com"}) type args struct { s string data TemplateData cr *x509.CertificateRequest } tests := []struct { name string args args want Options wantErr bool }{ {"leaf", args{base64.StdEncoding.EncodeToString([]byte(DefaultLeafTemplate)), TemplateData{ SubjectKey: Subject{CommonName: "foo"}, SANsKey: []SubjectAlternativeName{{Type: "dns", Value: "foo.com"}}, }, cr}, Options{ CertBuffer: bytes.NewBufferString(`{ "subject": {"commonName":"foo"}, "sans": [{"type":"dns","value":"foo.com"}], "keyUsage": ["digitalSignature"], "extKeyUsage": ["serverAuth", "clientAuth"] }`), }, false}, {"badBase64", args{"foobar", TemplateData{}, cr}, Options{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got Options fn := WithTemplateBase64(tt.args.s, tt.args.data) if err := fn(tt.args.cr, &got); (err != nil) != tt.wantErr { t.Errorf("WithTemplateBase64() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("WithTemplateBase64() = %v, want %v", got, tt.want) } }) } } func TestWithTemplateFile(t *testing.T) { cr, _ := createCertificateRequest(t, "foo", []string{"foo.com", "foo@foo.com", "::1", "https://foo.com"}) rsa2048, _ := createRSACertificateRequest(t, 2048, "foo", []string{"foo.com", "foo@foo.com", "::1", "https://foo.com"}) rsa3072, _ := createRSACertificateRequest(t, 3072, "foo", []string{"foo.com", "foo@foo.com", "::1", "https://foo.com"}) data := TemplateData{ SANsKey: []SubjectAlternativeName{ {Type: "dns", Value: "foo.com"}, {Type: "email", Value: "root@foo.com"}, {Type: "ip", Value: "127.0.0.1"}, {Type: "uri", Value: "uri:foo:bar"}, }, TokenKey: map[string]interface{}{ "iss": "https://iss", "sub": "sub", }, } type args struct { path string data TemplateData cr *x509.CertificateRequest } tests := []struct { name string args args want Options wantErr bool }{ {"example", args{"./testdata/example.tpl", data, cr}, Options{ CertBuffer: bytes.NewBufferString(`{ "subject": {"commonName":"foo"}, "sans": [{"type":"dns","value":"foo.com"},{"type":"email","value":"root@foo.com"},{"type":"ip","value":"127.0.0.1"},{"type":"uri","value":"uri:foo:bar"}], "emailAddresses": ["foo@foo.com"], "uris": "https://iss#sub", "keyUsage": ["digitalSignature"], "extKeyUsage": ["serverAuth", "clientAuth"] }`), }, false}, {"exampleRSA3072", args{"./testdata/example.tpl", data, rsa3072}, Options{ CertBuffer: bytes.NewBufferString(`{ "subject": {"commonName":"foo"}, "sans": [{"type":"dns","value":"foo.com"},{"type":"email","value":"root@foo.com"},{"type":"ip","value":"127.0.0.1"},{"type":"uri","value":"uri:foo:bar"}], "emailAddresses": ["foo@foo.com"], "uris": "https://iss#sub", "keyUsage": ["keyEncipherment", "digitalSignature"], "extKeyUsage": ["serverAuth", "clientAuth"] }`), }, false}, {"exampleRSA2048", args{"./testdata/example.tpl", data, rsa2048}, Options{}, true}, {"missing", args{"./testdata/missing.tpl", data, cr}, Options{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got Options fn := WithTemplateFile(tt.args.path, tt.args.data) if err := fn(tt.args.cr, &got); (err != nil) != tt.wantErr { t.Errorf("WithTemplateFile() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("WithTemplateFile() = %v, want %v", got, tt.want) } }) } } golang-step-crypto-0.24.0/x509util/templates.go000066400000000000000000000166341437037643100213050ustar00rootroot00000000000000package x509util import ( "crypto/x509" "go.step.sm/crypto/internal/templates" ) // Variables used to hold template data. const ( SubjectKey = "Subject" SANsKey = "SANs" TokenKey = "Token" InsecureKey = "Insecure" UserKey = "User" CertificateRequestKey = "CR" AuthorizationCrtKey = "AuthorizationCrt" AuthorizationChainKey = "AuthorizationChain" WebhooksKey = "Webhooks" ) // TemplateError represents an error in a template produced by the fail // function. type TemplateError struct { Message string } // Error implements the error interface and returns the error string when a // template executes the `fail "message"` function. func (e *TemplateError) Error() string { return e.Message } // ValidateTemplate validates a text template. func ValidateTemplate(text []byte) error { return templates.ValidateTemplate(text) } // ValidateTemplateData validates that template data is // valid JSON. func ValidateTemplateData(data []byte) error { return templates.ValidateTemplateData(data) } // TemplateData is an alias for map[string]interface{}. It represents the data // passed to the templates. type TemplateData map[string]interface{} // NewTemplateData creates a new map for templates data. func NewTemplateData() TemplateData { return TemplateData{} } // CreateTemplateData creates a new TemplateData with the given common name and SANs. func CreateTemplateData(commonName string, sans []string) TemplateData { return TemplateData{ SubjectKey: Subject{ CommonName: commonName, }, SANsKey: CreateSANs(sans), } } // Set sets a key-value pair in the template data. func (t TemplateData) Set(key string, v interface{}) { t[key] = v } // SetInsecure sets a key-value pair in the insecure template data. func (t TemplateData) SetInsecure(key string, v interface{}) { if m, ok := t[InsecureKey].(TemplateData); ok { m[key] = v } else { t[InsecureKey] = TemplateData{key: v} } } // SetSubject sets the given subject in the template data. func (t TemplateData) SetSubject(v Subject) { t.Set(SubjectKey, v) } // SetCommonName sets the given common name in the subject in the template data. func (t TemplateData) SetCommonName(cn string) { s, _ := t[SubjectKey].(Subject) s.CommonName = cn t[SubjectKey] = s } // SetSANs sets the given SANs in the template data. func (t TemplateData) SetSANs(sans []string) { t.Set(SANsKey, CreateSANs(sans)) } // SetSubjectAlternativeNames sets the given sans in the template data. func (t TemplateData) SetSubjectAlternativeNames(sans ...SubjectAlternativeName) { t.Set(SANsKey, sans) } // SetToken sets the given token in the template data. func (t TemplateData) SetToken(v interface{}) { t.Set(TokenKey, v) } // SetUserData sets the given user provided object in the insecure template // data. func (t TemplateData) SetUserData(v interface{}) { t.SetInsecure(UserKey, v) } // SetAuthorizationCertificate sets the given certificate in the template. This certificate // is generally present in a token header. func (t TemplateData) SetAuthorizationCertificate(crt interface{}) { t.Set(AuthorizationCrtKey, crt) } // SetAuthorizationCertificateChain sets a the given certificate chain in the // template. These certificates are generally present in a token header. func (t TemplateData) SetAuthorizationCertificateChain(chain interface{}) { t.Set(AuthorizationChainKey, chain) } // SetCertificateRequest sets the given certificate request in the insecure // template data. func (t TemplateData) SetCertificateRequest(cr *x509.CertificateRequest) { t.SetInsecure(CertificateRequestKey, NewCertificateRequestFromX509(cr)) } // SetWebhook sets the given webhook response in the webhooks template data. func (t TemplateData) SetWebhook(webhookName string, data interface{}) { if webhooksMap, ok := t[WebhooksKey].(map[string]interface{}); ok { webhooksMap[webhookName] = data } else { t[WebhooksKey] = map[string]interface{}{ webhookName: data, } } } // DefaultLeafTemplate is the default template used to generate a leaf // certificate. const DefaultLeafTemplate = `{ "subject": {{ toJson .Subject }}, "sans": {{ toJson .SANs }}, {{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }} "keyUsage": ["keyEncipherment", "digitalSignature"], {{- else }} "keyUsage": ["digitalSignature"], {{- end }} "extKeyUsage": ["serverAuth", "clientAuth"] }` // DefaultIIDLeafTemplate is the template used by default on instance identity // provisioners like AWS, GCP or Azure. By default, those provisioners allow the // SANs provided in the certificate request, but the option `DisableCustomSANs` // can be provided to force only the verified domains, if the option is true // `.SANs` will be set with the verified domains. const DefaultIIDLeafTemplate = `{ "subject": {"commonName": {{ toJson .Insecure.CR.Subject.CommonName }}}, {{- if .SANs }} "sans": {{ toJson .SANs }}, {{- else }} "dnsNames": {{ toJson .Insecure.CR.DNSNames }}, "emailAddresses": {{ toJson .Insecure.CR.EmailAddresses }}, "ipAddresses": {{ toJson .Insecure.CR.IPAddresses }}, "uris": {{ toJson .Insecure.CR.URIs }}, {{- end }} {{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }} "keyUsage": ["keyEncipherment", "digitalSignature"], {{- else }} "keyUsage": ["digitalSignature"], {{- end }} "extKeyUsage": ["serverAuth", "clientAuth"] }` // DefaultAdminLeafTemplate is a template used by default by K8sSA and // admin-OIDC provisioners. This template takes all the SANs and subject from // the certificate request. const DefaultAdminLeafTemplate = `{ "subject": {{ toJson .Insecure.CR.Subject }}, "dnsNames": {{ toJson .Insecure.CR.DNSNames }}, "emailAddresses": {{ toJson .Insecure.CR.EmailAddresses }}, "ipAddresses": {{ toJson .Insecure.CR.IPAddresses }}, "uris": {{ toJson .Insecure.CR.URIs }}, {{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }} "keyUsage": ["keyEncipherment", "digitalSignature"], {{- else }} "keyUsage": ["digitalSignature"], {{- end }} "extKeyUsage": ["serverAuth", "clientAuth"] }` // DefaultIntermediateTemplate is a template that can be used to generate an // intermediate certificate. const DefaultIntermediateTemplate = `{ "subject": {{ toJson .Subject }}, "keyUsage": ["certSign", "crlSign"], "basicConstraints": { "isCA": true, "maxPathLen": 0 } }` // DefaultRootTemplate is a template that can be used to generate a root // certificate. const DefaultRootTemplate = `{ "subject": {{ toJson .Subject }}, "issuer": {{ toJson .Subject }}, "keyUsage": ["certSign", "crlSign"], "basicConstraints": { "isCA": true, "maxPathLen": 1 } }` // CertificateRequestTemplate is a template that will sign the given certificate // request. const CertificateRequestTemplate = `{{ toJson .Insecure.CR }}` // DefaultCertificateRequestTemplate is the templated used by default when // creating a new certificate request. const DefaultCertificateRequestTemplate = `{ "subject": {{ toJson .Subject }}, "sans": {{ toJson .SANs }} }` // DefaultAttestedLeafTemplate is the default template used to generate a leaf // certificate from an attestation certificate. The main difference with // "DefaultLeafTemplate" is that the extended key usage is limited to // "clientAuth". const DefaultAttestedLeafTemplate = `{ "subject": {{ toJson .Subject }}, "sans": {{ toJson .SANs }}, {{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }} "keyUsage": ["keyEncipherment", "digitalSignature"], {{- else }} "keyUsage": ["digitalSignature"], {{- end }} "extKeyUsage": ["clientAuth"] }` golang-step-crypto-0.24.0/x509util/templates_test.go000066400000000000000000000310411437037643100223310ustar00rootroot00000000000000package x509util import ( "crypto/x509" "reflect" "testing" ) func TestTemplateError_Error(t *testing.T) { type fields struct { Message string } tests := []struct { name string fields fields want string }{ {"ok", fields{"an error"}, "an error"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { e := &TemplateError{ Message: tt.fields.Message, } if got := e.Error(); got != tt.want { t.Errorf("TemplateError.Error() = %v, want %v", got, tt.want) } }) } } func TestNewTemplateData(t *testing.T) { tests := []struct { name string want TemplateData }{ {"ok", TemplateData{}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := NewTemplateData(); !reflect.DeepEqual(got, tt.want) { t.Errorf("NewTemplateData() = %v, want %v", got, tt.want) } }) } } func TestCreateTemplateData(t *testing.T) { type args struct { commonName string sans []string } tests := []struct { name string args args want TemplateData }{ {"ok", args{"jane.doe.com", []string{"jane.doe.com", "jane@doe.com", "1.1.1.1", "mailto:jane@doe.com"}}, TemplateData{ SubjectKey: Subject{CommonName: "jane.doe.com"}, SANsKey: []SubjectAlternativeName{ {Type: DNSType, Value: "jane.doe.com"}, {Type: IPType, Value: "1.1.1.1"}, {Type: EmailType, Value: "jane@doe.com"}, {Type: URIType, Value: "mailto:jane@doe.com"}, }, }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := CreateTemplateData(tt.args.commonName, tt.args.sans); !reflect.DeepEqual(got, tt.want) { t.Errorf("CreateTemplateData() = %v, want %v", got, tt.want) } }) } } func TestTemplateData_SetInsecure(t *testing.T) { type args struct { key string v interface{} } tests := []struct { name string td TemplateData args args want TemplateData }{ {"empty", TemplateData{}, args{"foo", "bar"}, TemplateData{InsecureKey: TemplateData{"foo": "bar"}}}, {"overwrite", TemplateData{InsecureKey: TemplateData{"foo": "bar"}}, args{"foo", "zar"}, TemplateData{InsecureKey: TemplateData{"foo": "zar"}}}, {"existing", TemplateData{InsecureKey: TemplateData{"foo": "bar"}}, args{"bar", "foo"}, TemplateData{InsecureKey: TemplateData{"foo": "bar", "bar": "foo"}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.td.SetInsecure(tt.args.key, tt.args.v) if !reflect.DeepEqual(tt.td, tt.want) { t.Errorf("TemplateData.SetInsecure() = %v, want %v", tt.td, tt.want) } }) } } func TestTemplateData_SetSubject(t *testing.T) { type args struct { v Subject } tests := []struct { name string td TemplateData args args want TemplateData }{ {"ok", TemplateData{}, args{Subject{CommonName: "foo"}}, TemplateData{SubjectKey: Subject{CommonName: "foo"}}}, {"overwrite", TemplateData{SubjectKey: Subject{CommonName: "foo"}}, args{Subject{Province: []string{"CA"}}}, TemplateData{SubjectKey: Subject{Province: []string{"CA"}}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.td.SetSubject(tt.args.v) if !reflect.DeepEqual(tt.td, tt.want) { t.Errorf("TemplateData.SetSubject() = %v, want %v", tt.td, tt.want) } }) } } func TestTemplateData_SetCommonName(t *testing.T) { type args struct { cn string } tests := []struct { name string td TemplateData args args want TemplateData }{ {"ok", TemplateData{}, args{"commonName"}, TemplateData{SubjectKey: Subject{CommonName: "commonName"}}}, {"overwrite", TemplateData{SubjectKey: Subject{CommonName: "foo", Province: []string{"CA"}}}, args{"commonName"}, TemplateData{SubjectKey: Subject{CommonName: "commonName", Province: []string{"CA"}}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.td.SetCommonName(tt.args.cn) if !reflect.DeepEqual(tt.td, tt.want) { t.Errorf("TemplateData.SetCommonName() = %v, want %v", tt.td, tt.want) } }) } } func TestTemplateData_SetSANs(t *testing.T) { type args struct { sans []string } tests := []struct { name string td TemplateData args args want TemplateData }{ {"ok", TemplateData{}, args{[]string{"jane.doe.com", "jane@doe.com", "1.1.1.1", "mailto:jane@doe.com"}}, TemplateData{ SANsKey: []SubjectAlternativeName{ {Type: DNSType, Value: "jane.doe.com"}, {Type: IPType, Value: "1.1.1.1"}, {Type: EmailType, Value: "jane@doe.com"}, {Type: URIType, Value: "mailto:jane@doe.com"}, }}, }, {"overwrite", TemplateData{ SubjectKey: Subject{CommonName: "foo", Province: []string{"CA"}}, SANsKey: []SubjectAlternativeName{{Type: DNSType, Value: "john.doe.com"}}, }, args{[]string{"jane.doe.com"}}, TemplateData{ SubjectKey: Subject{CommonName: "foo", Province: []string{"CA"}}, SANsKey: []SubjectAlternativeName{ {Type: DNSType, Value: "jane.doe.com"}, }}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.td.SetSANs(tt.args.sans) if !reflect.DeepEqual(tt.td, tt.want) { t.Errorf("TemplateData.SetSANs() = %v, want %v", tt.td, tt.want) } }) } } func TestTemplateData_SetSubjectAlternativeNames(t *testing.T) { type args struct { sans []SubjectAlternativeName } tests := []struct { name string td TemplateData args args want TemplateData }{ {"ok", TemplateData{}, args{[]SubjectAlternativeName{ {Type: "dns", Value: "jane.doe.com"}, {Type: "permanentIdentifier", Value: "123456789"}, }}, TemplateData{ SANsKey: []SubjectAlternativeName{ {Type: DNSType, Value: "jane.doe.com"}, {Type: "permanentIdentifier", Value: "123456789"}, }}, }, {"overwrite", TemplateData{ SubjectKey: Subject{CommonName: "foo", Province: []string{"CA"}}, SANsKey: []SubjectAlternativeName{ {Type: DNSType, Value: "jane.doe.com"}, {Type: "permanentIdentifier", Value: "123456789"}, }, }, args{[]SubjectAlternativeName{ {Type: "email", Value: "jane@doe.com"}, }}, TemplateData{ SubjectKey: Subject{CommonName: "foo", Province: []string{"CA"}}, SANsKey: []SubjectAlternativeName{ {Type: "email", Value: "jane@doe.com"}, }}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.td.SetSubjectAlternativeNames(tt.args.sans...) if !reflect.DeepEqual(tt.td, tt.want) { t.Errorf("TemplateData.SetSubjectAlternativeNames() = %v, want %v", tt.td, tt.want) } }) } } func TestTemplateData_SetToken(t *testing.T) { type args struct { v interface{} } tests := []struct { name string td TemplateData args args want TemplateData }{ {"ok", TemplateData{}, args{"token"}, TemplateData{TokenKey: "token"}}, {"overwrite", TemplateData{TokenKey: "foo"}, args{"token"}, TemplateData{TokenKey: "token"}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.td.SetToken(tt.args.v) if !reflect.DeepEqual(tt.td, tt.want) { t.Errorf("TemplateData.SetToken() = %v, want %v", tt.td, tt.want) } }) } } func TestTemplateData_SetUserData(t *testing.T) { type args struct { v interface{} } tests := []struct { name string td TemplateData args args want TemplateData }{ {"ok", TemplateData{}, args{"userData"}, TemplateData{InsecureKey: TemplateData{UserKey: "userData"}}}, {"overwrite", TemplateData{InsecureKey: TemplateData{UserKey: "foo"}}, args{"userData"}, TemplateData{InsecureKey: TemplateData{UserKey: "userData"}}}, {"existing", TemplateData{InsecureKey: TemplateData{"foo": "bar"}}, args{"userData"}, TemplateData{InsecureKey: TemplateData{"foo": "bar", UserKey: "userData"}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.td.SetUserData(tt.args.v) if !reflect.DeepEqual(tt.td, tt.want) { t.Errorf("TemplateData.SetUserData() = %v, want %v", tt.td, tt.want) } }) } } func TestTemplateData_SetAuthorizationCertificate(t *testing.T) { crt1 := Certificate{DNSNames: []string{"crt1"}} crt2 := Certificate{DNSNames: []string{"crt2"}} type args struct { crt Certificate } tests := []struct { name string t TemplateData args args want TemplateData }{ {"ok", TemplateData{}, args{crt1}, TemplateData{ AuthorizationCrtKey: crt1, }}, {"overwrite", TemplateData{ AuthorizationCrtKey: crt1, InsecureKey: TemplateData{ UserKey: "data", }, }, args{crt2}, TemplateData{ AuthorizationCrtKey: crt2, InsecureKey: TemplateData{ UserKey: "data", }, }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.t.SetAuthorizationCertificate(tt.args.crt) if !reflect.DeepEqual(tt.t, tt.want) { t.Errorf("TemplateData.SetCertificate() = %v, want %v", tt.t, tt.want) } }) } } func TestTemplateData_SetAuthorizationCertificateChain(t *testing.T) { crt1 := Certificate{DNSNames: []string{"crt1"}} crt2 := Certificate{DNSNames: []string{"crt2"}} type args struct { crt []interface{} } tests := []struct { name string t TemplateData args args want TemplateData }{ {"ok", TemplateData{}, args{[]interface{}{crt1, crt2}}, TemplateData{ AuthorizationChainKey: []interface{}{crt1, crt2}, }}, {"overwrite", TemplateData{ AuthorizationChainKey: []interface{}{crt1, crt2}, InsecureKey: TemplateData{ UserKey: "data", }, }, args{[]interface{}{crt1}}, TemplateData{ AuthorizationChainKey: []interface{}{crt1}, InsecureKey: TemplateData{ UserKey: "data", }, }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.t.SetAuthorizationCertificateChain(tt.args.crt) if !reflect.DeepEqual(tt.t, tt.want) { t.Errorf("TemplateData.SetCertificate() = %v, want %v", tt.t, tt.want) } }) } } func TestTemplateData_SetCertificateRequest(t *testing.T) { cr := &x509.CertificateRequest{ DNSNames: []string{"foo", "bar"}, } cr1 := &CertificateRequest{ DNSNames: []string{"foo", "bar"}, } cr2 := &CertificateRequest{ EmailAddresses: []string{"foo@bar.com"}, } type args struct { cr *x509.CertificateRequest } tests := []struct { name string td TemplateData args args want TemplateData }{ {"ok", TemplateData{}, args{cr}, TemplateData{InsecureKey: TemplateData{CertificateRequestKey: cr1}}}, {"overwrite", TemplateData{InsecureKey: TemplateData{CertificateRequestKey: cr2}}, args{cr}, TemplateData{InsecureKey: TemplateData{CertificateRequestKey: cr1}}}, {"existing", TemplateData{InsecureKey: TemplateData{"foo": "bar"}}, args{cr}, TemplateData{InsecureKey: TemplateData{"foo": "bar", CertificateRequestKey: cr1}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.td.SetCertificateRequest(tt.args.cr) if !reflect.DeepEqual(tt.td, tt.want) { t.Errorf("TemplateData.SetCertificateRequest() = %v, want %v", tt.td, tt.want) } }) } } func TestTemplateData_SetWebhook(t *testing.T) { type args struct { name string v interface{} } tests := []struct { name string td TemplateData args args want TemplateData }{ {"empty", TemplateData{}, args{"foo", "bar"}, TemplateData{WebhooksKey: map[string]interface{}{"foo": "bar"}}}, {"overwrite", TemplateData{WebhooksKey: map[string]interface{}{"foo": "bar"}}, args{"foo", "zar"}, TemplateData{WebhooksKey: map[string]interface{}{"foo": "zar"}}}, {"existing", TemplateData{WebhooksKey: map[string]interface{}{"foo": "bar"}}, args{"bar", "foo"}, TemplateData{WebhooksKey: map[string]interface{}{"foo": "bar", "bar": "foo"}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.td.SetWebhook(tt.args.name, tt.args.v) if !reflect.DeepEqual(tt.td, tt.want) { t.Errorf("TemplateData.SetWebhook() = %v, want %v", tt.td, tt.want) } }) } } func TestValidateTemplate(t *testing.T) { tests := []struct { name string text []byte wantErr bool }{ { name: "ok", text: []byte(DefaultLeafTemplate), wantErr: false, }, { name: "ok/invalid-json", text: []byte("{!?}"), wantErr: false, }, { name: "fail/unknown-function", text: []byte("{{ unknownFunction }}"), wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := ValidateTemplate(tt.text); (err != nil) != tt.wantErr { t.Errorf("ValidateTemplate() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestValidateTemplateData(t *testing.T) { tests := []struct { name string data []byte wantErr bool }{ { name: "ok", data: []byte("{}"), wantErr: false, }, { name: "fail", data: []byte("{!?}"), wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := ValidateTemplateData(tt.data); (err != nil) != tt.wantErr { t.Errorf("ValidateTemplateData() error = %v, wantErr %v", err, tt.wantErr) } }) } } golang-step-crypto-0.24.0/x509util/testdata/000077500000000000000000000000001437037643100205575ustar00rootroot00000000000000golang-step-crypto-0.24.0/x509util/testdata/capath/000077500000000000000000000000001437037643100220175ustar00rootroot00000000000000golang-step-crypto-0.24.0/x509util/testdata/capath/cert.pem000066400000000000000000000021531437037643100234600ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBdTCCARygAwIBAgIRAMzIwp8pYDKH01yvNcwKLqYwCgYIKoZIzj0EAwIwGTEX MBUGA1UEAxMOU21hbGxzdGVwIENBIDEwHhcNMjEwNDE0MDEyNzMwWhcNMzEwNDEy MDEyNzMwWjAZMRcwFQYDVQQDEw5TbWFsbHN0ZXAgQ0EgMTBZMBMGByqGSM49AgEG CCqGSM49AwEHA0IABPKwI/NGpH5uWYH9KYrusC3aszBIxin+mDFSt7sJiPjfkHps l8HPUWrUvjz0BuHm0ic52zCHwftQc1EXFDuDxpajRTBDMA4GA1UdDwEB/wQEAwIB BjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBTxlV+DRZS5m5HrFGf83OsG W4SYNzAKBggqhkjOPQQDAgNHADBEAiBdVZTcpwwZpfgmU6jqpO8Amn1O3ZZSHXog XWDjTaH/4gIgTaGEuNVMXoMQl/nr8lEcQtaP/p+0woHKL9JGr13ZYNI= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIBdjCCARygAwIBAgIRAMhq6mlSxCy4ZfXyvlOabZEwCgYIKoZIzj0EAwIwGTEX MBUGA1UEAxMOU21hbGxzdGVwIENBIDIwHhcNMjEwNDE0MDEyNzM5WhcNMzEwNDEy MDEyNzM5WjAZMRcwFQYDVQQDEw5TbWFsbHN0ZXAgQ0EgMjBZMBMGByqGSM49AgEG CCqGSM49AwEHA0IABDPFYafdCLBSLtjXZev0PEHJpRyYhLiFUjOGx/Dxbie0LGmU fWlU8AIh7e9jjvl+ugdhco3CoWEUoBCuD9S99DujRTBDMA4GA1UdDwEB/wQEAwIB BjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBQj++HJHgFcVIvah8WOTvMd UboxGDAKBggqhkjOPQQDAgNIADBFAiBeMmKo4n3LvQCgGF609rH/fjJbGI00ZrrS Pe9qXRrxWwIhAKBurnttPIYbOzidmrFA8fBF4w24lu0WmDOr5Cz8IfP9 -----END CERTIFICATE-----golang-step-crypto-0.24.0/x509util/testdata/capath2/000077500000000000000000000000001437037643100221015ustar00rootroot00000000000000golang-step-crypto-0.24.0/x509util/testdata/capath2/root1.crt000066400000000000000000000010651437037643100236610ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBdTCCARygAwIBAgIRAMzIwp8pYDKH01yvNcwKLqYwCgYIKoZIzj0EAwIwGTEX MBUGA1UEAxMOU21hbGxzdGVwIENBIDEwHhcNMjEwNDE0MDEyNzMwWhcNMzEwNDEy MDEyNzMwWjAZMRcwFQYDVQQDEw5TbWFsbHN0ZXAgQ0EgMTBZMBMGByqGSM49AgEG CCqGSM49AwEHA0IABPKwI/NGpH5uWYH9KYrusC3aszBIxin+mDFSt7sJiPjfkHps l8HPUWrUvjz0BuHm0ic52zCHwftQc1EXFDuDxpajRTBDMA4GA1UdDwEB/wQEAwIB BjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBTxlV+DRZS5m5HrFGf83OsG W4SYNzAKBggqhkjOPQQDAgNHADBEAiBdVZTcpwwZpfgmU6jqpO8Amn1O3ZZSHXog XWDjTaH/4gIgTaGEuNVMXoMQl/nr8lEcQtaP/p+0woHKL9JGr13ZYNI= -----END CERTIFICATE-----golang-step-crypto-0.24.0/x509util/testdata/capath2/root2.crt000066400000000000000000000010651437037643100236620ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBdjCCARygAwIBAgIRAMhq6mlSxCy4ZfXyvlOabZEwCgYIKoZIzj0EAwIwGTEX MBUGA1UEAxMOU21hbGxzdGVwIENBIDIwHhcNMjEwNDE0MDEyNzM5WhcNMzEwNDEy MDEyNzM5WjAZMRcwFQYDVQQDEw5TbWFsbHN0ZXAgQ0EgMjBZMBMGByqGSM49AgEG CCqGSM49AwEHA0IABDPFYafdCLBSLtjXZev0PEHJpRyYhLiFUjOGx/Dxbie0LGmU fWlU8AIh7e9jjvl+ugdhco3CoWEUoBCuD9S99DujRTBDMA4GA1UdDwEB/wQEAwIB BjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBQj++HJHgFcVIvah8WOTvMd UboxGDAKBggqhkjOPQQDAgNIADBFAiBeMmKo4n3LvQCgGF609rH/fjJbGI00ZrrS Pe9qXRrxWwIhAKBurnttPIYbOzidmrFA8fBF4w24lu0WmDOr5Cz8IfP9 -----END CERTIFICATE-----golang-step-crypto-0.24.0/x509util/testdata/ed25519.crt000066400000000000000000000010211437037643100222610ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBWzCCAQ2gAwIBAgIUDIPYISuCyyOYI2Pi95eKQ1vzvZIwBQYDK2VwMCMxITAf BgNVBAMMGEVkMjU1MTkgdGVzdCBjZXJ0aWZpY2F0ZTAeFw0xOTA1MDYxNzI3MTZa Fw0xOTA2MDUxNzI3MTZaMCMxITAfBgNVBAMMGEVkMjU1MTkgdGVzdCBjZXJ0aWZp Y2F0ZTAqMAUGAytlcAMhADYpxWwNTxRsgdD/ddNqcF9pzQ9NZtXamH6CSYmjijz6 o1MwUTAdBgNVHQ4EFgQUCTs6nUop2JX/aL57Q1Ry4K2i464wHwYDVR0jBBgwFoAU CTs6nUop2JX/aL57Q1Ry4K2i464wDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQBT pVgcLDsqnqydTqUdX11tprUI3hKC85cgrvrYmPQagzJrkfUkHcQgfyziTdoTO21U GtKoKNxgudT0eEs8HJEA -----END CERTIFICATE-----golang-step-crypto-0.24.0/x509util/testdata/example.tpl000066400000000000000000000012201437037643100227260ustar00rootroot00000000000000{ "subject": {{ toJson .Insecure.CR.Subject }}, "sans": {{ toJson .SANs }}, {{- if .Insecure.CR.EmailAddresses }} "emailAddresses": {{ toJson .Insecure.CR.EmailAddresses }}, {{- end }} {{- if .Token }} "uris": "{{ .Token.iss }}#{{ .Token.sub }}", {{- end }} {{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }} {{- if lt .Insecure.CR.PublicKey.Size 384 }} {{ fail "Key length must be at least 3072 bits" }} {{- end }} {{- end }} {{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }} "keyUsage": ["keyEncipherment", "digitalSignature"], {{- else }} "keyUsage": ["digitalSignature"], {{- end }} "extKeyUsage": ["serverAuth", "clientAuth"] }golang-step-crypto-0.24.0/x509util/testdata/fullsimple.tpl000066400000000000000000000025271437037643100234620ustar00rootroot00000000000000{ "version": 3, "subject": "subjectCommonName", "issuer": "issuerCommonName", "serialNumber": "0x1234567890", "dnsNames": "doe.com", "emailAddresses": "jane@doe.com", "ipAddresses": "127.0.0.1", "uris": "https://doe.com", "sans": [{"type":"dns", "value":"www.doe.com"}], "extensions": [{"id":"1.2.3.4","critical":true,"value":"ZXh0ZW5zaW9u"}], "keyUsage": ["digitalSignature"], "extKeyUsage": ["serverAuth"], "unknownExtKeyUsage": ["1.3.6.1.4.1.44924.1.6", "1.3.6.1.4.1.44924.1.7"], "subjectKeyId": "c3ViamVjdEtleUlk", "authorityKeyId": "YXV0aG9yaXR5S2V5SWQ=", "ocspServer": "https://ocsp.server", "issuingCertificateURL": "https://ca.com", "crlDistributionPoints": "https://ca.com/ca.crl", "policyIdentifiers": "1.2.3.4.5.6", "basicConstraints": { "isCA": false, "maxPathLen": 0 }, "nameConstraints": { "critical": true, "permittedDNSDomains": "jane.doe.com", "excludedDNSDomains": "john.doe.com", "permittedIPRanges": "127.0.0.1/32", "excludedIPRanges": "0.0.0.0/0", "permittedEmailAddresses": "jane@doe.com", "excludedEmailAddresses": "john@doe.com", "permittedURIDomains": "https://jane.doe.com", "excludedURIDomains": "https://john.doe.com" }, "signatureAlgorithm": "Ed25519" }golang-step-crypto-0.24.0/x509util/testdata/google.crt000066400000000000000000000032631437037643100225510ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIExjCCA66gAwIBAgIRAP1vPiSYwlsdCAAAAABH8DMwDQYJKoZIhvcNAQELBQAw QjELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFUdvb2dsZSBUcnVzdCBTZXJ2aWNlczET MBEGA1UEAxMKR1RTIENBIDFPMTAeFw0yMDA2MTcxNDMxMjJaFw0yMDA5MDkxNDMx MjJaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH Ew1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgTExDMRcwFQYDVQQDEw53 d3cuZ29vZ2xlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOFYzxk3Ghpj cPvTNffIAqyY4p62NFaISj/X66RMGO7rs0RyM2I4ch1hiEP/alWb/+81BzA+R1nK w0ZKwy86Kh6jggJaMIICVjAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYB BQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUjqNsRxKnCgdblFHWKj9y+TUG RSwwHwYDVR0jBBgwFoAUmNH4bhDrz5vsYJ8YkBug630J/SswaAYIKwYBBQUHAQEE XDBaMCsGCCsGAQUFBzABhh9odHRwOi8vb2NzcC5wa2kuZ29vZy9ndHMxbzFjb3Jl MCsGCCsGAQUFBzAChh9odHRwOi8vcGtpLmdvb2cvZ3NyMi9HVFMxTzEuY3J0MBkG A1UdEQQSMBCCDnd3dy5nb29nbGUuY29tMCEGA1UdIAQaMBgwCAYGZ4EMAQICMAwG CisGAQQB1nkCBQMwMwYDVR0fBCwwKjAooCagJIYiaHR0cDovL2NybC5wa2kuZ29v Zy9HVFMxTzFjb3JlLmNybDCCAQIGCisGAQQB1nkCBAIEgfMEgfAA7gB1ALIeBcyL os2KIE6HZvkruYolIGdr2vpw57JJUy3vi5BeAAABcsLn/z4AAAQDAEYwRAIgTB4w h0IyoPg5FFwxva6DkCumsXIIhKmHO1xk6HrOpDECIA0c5Y7Yq2mgGu03QPdbPRLb dBF5ISGltoW1ni2LkLIsAHUAXqdz+d9WwOe1Nkh90EngMnqRmgyEoRIShBh1loFx RVgAAAFywuf/bgAABAMARjBEAiBUcGdSwAP/13zLGH4xcJ5l7rYoif6lb7Ymv/4u intIHQIga1qNo582mt6FkPUYcKNq77MR9MPQYjJj+SQg266bbacwDQYJKoZIhvcN AQELBQADggEBAFKf5klhZEz9FwJlsu6/vnAn9lUKM2hKVKxO4B8T4f6PuY6dlZ3T g6mvdtsiLjAZ8v6WL6Bn0v15kTh9RhrKwEwVbLLfoXZbQBygV9B/k+HOCgMEb8U3 Nn7chIP1kTJcyy3BW7TSU0WODmNuEusX0MQcs5TNl/omwdKkzpB6NjXWhnwgzCeD 1nT33vvq1pwgg28ncIBJkqUpDca7hBDQjx1HYEq00euF5/zDWLV98mH8ORPSC2i2 N7e0OMNT3q2HJRtHor5USuzehR86u3Aie90Z6jBvG/kNsGSgjUAdj/PAlVBj9GcF KtLwFt/ee1wps0/VGR+gqkoJps0BW8Tv+1k= -----END CERTIFICATE-----golang-step-crypto-0.24.0/x509util/testdata/opcua.tpl000066400000000000000000000004261437037643100224110ustar00rootroot00000000000000{ "subject": {{ toJson .Subject }}, "sans": {{ toJson .SANs }}, "basicConstraints": { "isCA": false }, "keyUsage": ["digitalSignature", "keyEncipherment", "keyAgreement", "certSign"], "extKeyUsage": ["serverAuth", "clientAuth"] } golang-step-crypto-0.24.0/x509util/testdata/secrets/000077500000000000000000000000001437037643100222275ustar00rootroot00000000000000golang-step-crypto-0.24.0/x509util/testdata/secrets/example.key000066400000000000000000000003601437037643100243730ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgel8C9Xs8cZgTSIHw w7wEM7P2u3q2P0N+lvfQsjG4BWWhRANCAAQzxWGn3QiwUi7Y12Xr9DxByaUcmIS4 hVIzhsfw8W4ntCxplH1pVPACIe3vY475froHYXKNwqFhFKAQrg/UvfQ7 -----END PRIVATE KEY-----golang-step-crypto-0.24.0/x509util/testdata/smallstep.crt000066400000000000000000000036121437037643100232770ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFZTCCBE2gAwIBAgISBPRFhEdtcBsNSxHgkoSkHpU/MA0GCSqGSIb3DQEBCwUA MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0yMDA2MTYwNzE2NTZaFw0y MDA5MTQwNzE2NTZaMBoxGDAWBgNVBAMMDyouc21hbGxzdGVwLmNvbTCCASIwDQYJ KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMgKbXAbD84RScmR3QGVlT27U09ihM6X XkVp4Ht4fbfm2SvpmOm7kLt5EB3f+0/I3enVsupCoULisUOQpEaoY9wXTjSHRSKl X3QPzyb8eJrwltxeo0qC5SidaVl2lXcSpKrKMvbp5qfd4hLhAJudEldmTFMQjzou /FouhzDpP4UDzf8Kc9b8Px27Qw7hg/888lJwCIcTAIVgmHUOxZKmjsHe0rHTNTYi mE+76udBPyG/Kis+ld2nuufqI/gPrD6gkm5pqSekt057zbk9oJTdBZTuAtaPGlER bH4G4fbkoT6j0tD7qafeebKATGBtzsK8gqwTZ2Uh4jzd5Ppukg2x4gkCAwEAAaOC AnMwggJvMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB BQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUFlxnRkxa25R8P1zsLqtVMHBC MlUwHwYDVR0jBBgwFoAUqEpqYwR93brm0Tm3pkVl7/Oo7KEwbwYIKwYBBQUHAQEE YzBhMC4GCCsGAQUFBzABhiJodHRwOi8vb2NzcC5pbnQteDMubGV0c2VuY3J5cHQu b3JnMC8GCCsGAQUFBzAChiNodHRwOi8vY2VydC5pbnQteDMubGV0c2VuY3J5cHQu b3JnLzApBgNVHREEIjAggg8qLnNtYWxsc3RlcC5jb22CDXNtYWxsc3RlcC5jb20w TAYDVR0gBEUwQzAIBgZngQwBAgEwNwYLKwYBBAGC3xMBAQEwKDAmBggrBgEFBQcC ARYaaHR0cDovL2Nwcy5sZXRzZW5jcnlwdC5vcmcwggEEBgorBgEEAdZ5AgQCBIH1 BIHyAPAAdgDwlaRZ8gDRgkAQLS+TiI6tS/4dR+OZ4dA0prCoqo6ycwAAAXK8M+PT AAAEAwBHMEUCIFjO361bM1BiXp9Nexw1KxJX34bI98JkBsHkSz3S+nSZAiEAuBy6 KBUpYNLT71aPoVWtprZnxThUmKq2gm2Jltp5gMgAdgCyHgXMi6LNiiBOh2b5K7mK JSBna9r6cOeySVMt74uQXgAAAXK8M+PAAAAEAwBHMEUCIQCIqfGGfeGQHtYnVO4J UPTqIbNia+Lrbfw9HARElQw4iAIgSspUzNwYHY4cU/BdpfHRaTGzddrTFXhEoCQt 0pbaG0wwDQYJKoZIhvcNAQELBQADggEBAJJgEB70PUChWsbLUyFk1udDPePanU5O zzSViB5+fesWppG9IDnYnL6KSI+l1jP9jl5Zym79SQ2gSC3xwkswpT8X+Drzup/7 UV7T5NbHpzkZfg2itsx407yuxoW2L7zihZ4CWm1bodUbPKWNYJAZFrcxqcydwmFn pvZMoJDP3PW0hejz+gsLe9ZrtXb8q1BLFupHfx+bTx476qhRyL+e2fDi5wQz1Qs2 jVZGsvv/cljvlRSLZ0NVPeKcRN50f7UQ7OYw+JLR2K5+K1H7oHiu+iUX4F2/PGGm Tb4HBKgzkJSsS5b3pEp+2U77m9KOBhsiZOVvQ2ECgJ9/eDiy0QCqEyI= -----END CERTIFICATE-----golang-step-crypto-0.24.0/x509util/utils.go000066400000000000000000000133021437037643100204340ustar00rootroot00000000000000package x509util import ( "bytes" "crypto" "crypto/rand" "crypto/sha1" //nolint:gosec // SubjectKeyIdentifier by RFC 5280 "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "math/big" "net" "net/url" "strings" "unicode" "unicode/utf8" "github.com/pkg/errors" "golang.org/x/net/idna" ) var emptyASN1Subject = []byte{0x30, 0} // SanitizeName converts the given domain to its ASCII form. func SanitizeName(domain string) (string, error) { if domain == "" { return "", errors.New("empty server name") } // Note that this conversion is necessary because some server names in the handshakes // started by some clients (such as cURL) are not converted to Punycode, which will // prevent us from obtaining certificates for them. In addition, we should also treat // example.com and EXAMPLE.COM as equivalent and return the same certificate for them. // Fortunately, this conversion also helped us deal with this kind of mixedcase problems. // // Due to the "ฯƒฯ‚ฮฃ" problem (see https://unicode.org/faq/idn.html#22), we can't use // idna.Punycode.ToASCII (or just idna.ToASCII) here. name, err := idna.Lookup.ToASCII(domain) if err != nil { return "", errors.New("server name contains invalid character") } return name, nil } // SplitSANs splits a slice of Subject Alternative Names into slices of // IP Addresses and DNS Names. If an element is not an IP address, then it // is bucketed as a DNS Name. func SplitSANs(sans []string) (dnsNames []string, ips []net.IP, emails []string, uris []*url.URL) { dnsNames = []string{} ips = []net.IP{} emails = []string{} uris = []*url.URL{} for _, san := range sans { ip := net.ParseIP(san) u, err := url.Parse(san) switch { case ip != nil: ips = append(ips, ip) case err == nil && u.Scheme != "": uris = append(uris, u) case strings.Contains(san, "@"): emails = append(emails, san) default: dnsNames = append(dnsNames, san) } } return } // CreateSANs splits the given sans and returns a list of SubjectAlternativeName // structs. func CreateSANs(sans []string) []SubjectAlternativeName { dnsNames, ips, emails, uris := SplitSANs(sans) sanTypes := make([]SubjectAlternativeName, 0, len(sans)) for _, v := range dnsNames { sanTypes = append(sanTypes, SubjectAlternativeName{Type: "dns", Value: v}) } for _, v := range ips { sanTypes = append(sanTypes, SubjectAlternativeName{Type: "ip", Value: v.String()}) } for _, v := range emails { sanTypes = append(sanTypes, SubjectAlternativeName{Type: "email", Value: v}) } for _, v := range uris { sanTypes = append(sanTypes, SubjectAlternativeName{Type: "uri", Value: v.String()}) } return sanTypes } // generateSerialNumber returns a random serial number. func generateSerialNumber() (*big.Int, error) { limit := new(big.Int).Lsh(big.NewInt(1), 128) sn, err := rand.Int(rand.Reader, limit) if err != nil { return nil, errors.Wrap(err, "error generating serial number") } return sn, nil } // subjectPublicKeyInfo is a PKIX public key structure defined in RFC 5280. type subjectPublicKeyInfo struct { Algorithm pkix.AlgorithmIdentifier SubjectPublicKey asn1.BitString } // generateSubjectKeyID generates the key identifier according the the RFC 5280 // section 4.2.1.2. // // The keyIdentifier is composed of the 160-bit SHA-1 hash of the value of the // BIT STRING subjectPublicKey (excluding the tag, length, and number of unused // bits). func generateSubjectKeyID(pub crypto.PublicKey) ([]byte, error) { b, err := x509.MarshalPKIXPublicKey(pub) if err != nil { return nil, errors.Wrap(err, "error marshaling public key") } var info subjectPublicKeyInfo if _, err = asn1.Unmarshal(b, &info); err != nil { return nil, errors.Wrap(err, "error unmarshaling public key") } //nolint:gosec // SubjectKeyIdentifier by RFC 5280 hash := sha1.Sum(info.SubjectPublicKey.Bytes) return hash[:], nil } // subjectIsEmpty returns whether the given pkix.Name (aka Subject) is an empty sequence func subjectIsEmpty(s pkix.Name) bool { if asn1Subject, err := asn1.Marshal(s.ToRDNSequence()); err == nil { return bytes.Equal(asn1Subject, emptyASN1Subject) } return false } // isUTF8String reports whether the given s is a valid utf8 string func isUTF8String(s string) bool { return utf8.ValidString(s) } // isIA5String reports whether the given s is a valid ia5 string. func isIA5String(s string) bool { for _, r := range s { // Per RFC5280 "IA5String is limited to the set of ASCII characters" if r > unicode.MaxASCII { return false } } return true } // isNumeric reports whether the given s is a valid ASN1 NumericString. func isNumericString(s string) bool { for _, b := range s { valid := '0' <= b && b <= '9' || b == ' ' if !valid { return false } } return true } // isPrintableString reports whether the given s is a valid ASN.1 PrintableString. // If asterisk is allowAsterisk then '*' is also allowed, reflecting existing // practice. If ampersand is allowAmpersand then '&' is allowed as well. func isPrintableString(s string, asterisk, ampersand bool) bool { for _, b := range s { valid := 'a' <= b && b <= 'z' || 'A' <= b && b <= 'Z' || '0' <= b && b <= '9' || '\'' <= b && b <= ')' || '+' <= b && b <= '/' || b == ' ' || b == ':' || b == '=' || b == '?' || // This is technically not allowed in a PrintableString. // However, x509 certificates with wildcard strings don't // always use the correct string type so we permit it. (asterisk && b == '*') || // This is not technically allowed either. However, not // only is it relatively common, but there are also a // handful of CA certificates that contain it. At least // one of which will not expire until 2027. (ampersand && b == '&') if !valid { return false } } return true } golang-step-crypto-0.24.0/x509util/utils_test.go000066400000000000000000000161561437037643100215050ustar00rootroot00000000000000package x509util import ( "crypto" "crypto/x509" "encoding/pem" "net" "net/url" "os" "reflect" "testing" ) func decodeCertificateFile(t *testing.T, filename string) *x509.Certificate { t.Helper() b, err := os.ReadFile(filename) if err != nil { t.Fatal(err) } block, _ := pem.Decode(b) if block == nil { t.Fatal("error decoding pem") } crt, err := x509.ParseCertificate(block.Bytes) if err != nil { t.Fatal(err) } return crt } func TestSplitSANs(t *testing.T) { type args struct { sans []string } tests := []struct { name string args args wantDNSNames []string wantIps []net.IP wantEmails []string wantUris []*url.URL }{ {"nil", args{nil}, []string{}, []net.IP{}, []string{}, []*url.URL{}}, {"empty", args{[]string{}}, []string{}, []net.IP{}, []string{}, []*url.URL{}}, {"dns", args{[]string{"doe.com"}}, []string{"doe.com"}, []net.IP{}, []string{}, []*url.URL{}}, {"ip", args{[]string{"127.0.0.1", "1.2.3.4"}}, []string{}, []net.IP{ net.ParseIP("127.0.0.1"), net.ParseIP("1.2.3.4"), }, []string{}, []*url.URL{}}, {"ipv6", args{[]string{"::1", "2001:0db8:0000:0000:0000:8a2e:0370:7334", "2001:db8::8a2e:370:7334"}}, []string{}, []net.IP{ net.ParseIP("::1"), net.ParseIP("2001:0db8:0000:0000:0000:8a2e:0370:7334"), net.ParseIP("2001:db8::8a2e:370:7334"), }, []string{}, []*url.URL{}}, {"emails", args{[]string{"john@doe.com", "jane@doe.com"}}, []string{}, []net.IP{}, []string{ "john@doe.com", "jane@doe.com", }, []*url.URL{}}, {"uris", args{[]string{"https://smallstep.com/step/", "mailto:john@doe.com", "urn:uuid:ddfe62ba-7e99-4bc1-83b3-8f57fe3e9959"}}, []string{}, []net.IP{}, []string{}, []*url.URL{ {Scheme: "https", Host: "smallstep.com", Path: "/step/"}, {Scheme: "mailto", Opaque: "john@doe.com"}, {Scheme: "urn", Opaque: "uuid:ddfe62ba-7e99-4bc1-83b3-8f57fe3e9959"}, }}, {"mixed", args{[]string{ "foo.internal", "https://ca.smallstep.com", "max@smallstep.com", "urn:uuid:ddfe62ba-7e99-4bc1-83b3-8f57fe3e9959", "mariano@smallstep.com", "1.1.1.1", "bar.internal", "https://google.com/index.html", "mailto:john@doe.com", "2102:446:c001:d65e:ab1a:bf20:4b26:31f7"}}, []string{"foo.internal", "bar.internal"}, []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("2102:446:c001:d65e:ab1a:bf20:4b26:31f7")}, []string{"max@smallstep.com", "mariano@smallstep.com"}, []*url.URL{ {Scheme: "https", Host: "ca.smallstep.com"}, {Scheme: "urn", Opaque: "uuid:ddfe62ba-7e99-4bc1-83b3-8f57fe3e9959"}, {Scheme: "https", Host: "google.com", Path: "/index.html"}, {Scheme: "mailto", Opaque: "john@doe.com"}, }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotDNSNames, gotIps, gotEmails, gotUris := SplitSANs(tt.args.sans) if !reflect.DeepEqual(gotDNSNames, tt.wantDNSNames) { t.Errorf("SplitSANs() gotDNSNames = %v, want %v", gotDNSNames, tt.wantDNSNames) } if !reflect.DeepEqual(gotIps, tt.wantIps) { t.Errorf("SplitSANs() gotIps = %v, want %v", gotIps, tt.wantIps) } if !reflect.DeepEqual(gotEmails, tt.wantEmails) { t.Errorf("SplitSANs() gotEmails = %v, want %v", gotEmails, tt.wantEmails) } if !reflect.DeepEqual(gotUris, tt.wantUris) { t.Errorf("SplitSANs() gotUris = %v, want %v", gotUris, tt.wantUris) } }) } } func TestCreateSANs(t *testing.T) { type args struct { sans []string } tests := []struct { name string args args want []SubjectAlternativeName }{ {"nil", args{nil}, []SubjectAlternativeName{}}, {"empty", args{[]string{}}, []SubjectAlternativeName{}}, {"dns", args{[]string{"doe.com"}}, []SubjectAlternativeName{{Type: "dns", Value: "doe.com"}}}, {"ip", args{[]string{"127.0.0.1", "2001:0db8:0000:0000:0000:8a2e:0370:7334"}}, []SubjectAlternativeName{ {Type: "ip", Value: "127.0.0.1"}, {Type: "ip", Value: "2001:db8::8a2e:370:7334"}, }}, {"emails", args{[]string{"john@doe.com", "jane@doe.com"}}, []SubjectAlternativeName{ {Type: "email", Value: "john@doe.com"}, {Type: "email", Value: "jane@doe.com"}, }}, {"uris", args{[]string{"https://smallstep.com/step/", "mailto:john@doe.com", "urn:uuid:ddfe62ba-7e99-4bc1-83b3-8f57fe3e9959"}}, []SubjectAlternativeName{ {Type: "uri", Value: "https://smallstep.com/step/"}, {Type: "uri", Value: "mailto:john@doe.com"}, {Type: "uri", Value: "urn:uuid:ddfe62ba-7e99-4bc1-83b3-8f57fe3e9959"}, }}, {"mixed", args{[]string{ "foo.internal", "https://ca.smallstep.com", "max@smallstep.com", "urn:uuid:ddfe62ba-7e99-4bc1-83b3-8f57fe3e9959", "mariano@smallstep.com", "1.1.1.1", "bar.internal", "https://google.com/index.html", "mailto:john@doe.com", "2102:446:c001:d65e:ab1a:bf20:4b26:31f7"}}, []SubjectAlternativeName{ {Type: "dns", Value: "foo.internal"}, {Type: "dns", Value: "bar.internal"}, {Type: "ip", Value: "1.1.1.1"}, {Type: "ip", Value: "2102:446:c001:d65e:ab1a:bf20:4b26:31f7"}, {Type: "email", Value: "max@smallstep.com"}, {Type: "email", Value: "mariano@smallstep.com"}, {Type: "uri", Value: "https://ca.smallstep.com"}, {Type: "uri", Value: "urn:uuid:ddfe62ba-7e99-4bc1-83b3-8f57fe3e9959"}, {Type: "uri", Value: "https://google.com/index.html"}, {Type: "uri", Value: "mailto:john@doe.com"}, }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := CreateSANs(tt.args.sans); !reflect.DeepEqual(got, tt.want) { t.Errorf("CreateSANs() = %v, want %v", got, tt.want) } }) } } func Test_generateSubjectKeyID(t *testing.T) { ecdsaCrt := decodeCertificateFile(t, "testdata/google.crt") rsaCrt := decodeCertificateFile(t, "testdata/smallstep.crt") ed25519Crt := decodeCertificateFile(t, "testdata/ed25519.crt") type args struct { pub crypto.PublicKey } tests := []struct { name string args args want []byte wantErr bool }{ {"ecdsa", args{ecdsaCrt.PublicKey}, ecdsaCrt.SubjectKeyId, false}, {"rsa", args{rsaCrt.PublicKey}, rsaCrt.SubjectKeyId, false}, {"ed25519", args{ed25519Crt.PublicKey}, ed25519Crt.SubjectKeyId, false}, {"fail", args{[]byte("fail")}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := generateSubjectKeyID(tt.args.pub) if (err != nil) != tt.wantErr { t.Errorf("generateSubjectKeyID() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("generateSubjectKeyID() = %v, want %v", got, tt.want) } }) } } func TestSanitizeName(t *testing.T) { type args struct { domain string } tests := []struct { name string args args want string wantErr bool }{ {"ok", args{"smallstep.com"}, "smallstep.com", false}, {"ok ascii", args{"bรผcher.example.com"}, "xn--bcher-kva.example.com", false}, {"fail", args{"xn--bรผcher.example.com"}, "", true}, {"fail with port", args{"smallstep.com:8080"}, "", true}, {"fail empty", args{""}, "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := SanitizeName(tt.args.domain) if (err != nil) != tt.wantErr { t.Errorf("SanitizeName() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("SanitizeName() = %v, want %v", got, tt.want) } }) } }