pax_global_header00006660000000000000000000000064147415633160014524gustar00rootroot0000000000000052 comment=918323e7e727c59bee38fa18bb6f185c5f413997 crypto-0.57.0/000077500000000000000000000000001474156331600131355ustar00rootroot00000000000000crypto-0.57.0/.github/000077500000000000000000000000001474156331600144755ustar00rootroot00000000000000crypto-0.57.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001474156331600166605ustar00rootroot00000000000000crypto-0.57.0/.github/ISSUE_TEMPLATE/bug-report.yml000066400000000000000000000026761474156331600215040ustar00rootroot00000000000000name: 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 crypto-0.57.0/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000004741474156331600206550ustar00rootroot00000000000000blank_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! crypto-0.57.0/.github/ISSUE_TEMPLATE/documentation-request.md000066400000000000000000000006531474156331600235450ustar00rootroot00000000000000--- name: Documentation Request about: Request documentation for a feature title: '' labels: docs, needs triage assignees: '' --- crypto-0.57.0/.github/ISSUE_TEMPLATE/enhancement.md000066400000000000000000000003011474156331600214610ustar00rootroot00000000000000--- 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 crypto-0.57.0/.github/PULL_REQUEST_TEMPLATE000066400000000000000000000010771474156331600177040ustar00rootroot00000000000000 #### 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! crypto-0.57.0/.github/dependabot.yml000066400000000000000000000011321474156331600173220ustar00rootroot00000000000000# 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" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" crypto-0.57.0/.github/workflows/000077500000000000000000000000001474156331600165325ustar00rootroot00000000000000crypto-0.57.0/.github/workflows/ci.yml000066400000000000000000000007641474156331600176570ustar00rootroot00000000000000name: 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: only-latest-golang: false os-dependencies: 'libpcsclite-dev' run-build: false run-codeql: true test-command: 'V=1 make test' secrets: inherit crypto-0.57.0/.github/workflows/code-scan-cron.yml000066400000000000000000000001771474156331600220550ustar00rootroot00000000000000on: schedule: - cron: '0 0 * * *' jobs: code-scan: uses: smallstep/workflows/.github/workflows/code-scan.yml@main crypto-0.57.0/.github/workflows/dependabot-auto-merge.yml000066400000000000000000000003501474156331600234230ustar00rootroot00000000000000name: Dependabot auto-merge on: pull_request permissions: contents: write pull-requests: write jobs: dependabot-auto-merge: uses: smallstep/workflows/.github/workflows/dependabot-auto-merge.yml@main secrets: inherit crypto-0.57.0/.github/workflows/release.yml000066400000000000000000000017631474156331600207040ustar00rootroot00000000000000name: 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 }} crypto-0.57.0/.github/workflows/triage.yml000066400000000000000000000004051474156331600205270ustar00rootroot00000000000000name: 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 crypto-0.57.0/.gitignore000066400000000000000000000005251474156331600151270ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Go Workspaces go.work go.work.sum # Development .vscode/ coverage.cov # 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 crypto-0.57.0/LICENSE000066400000000000000000000261351474156331600141510ustar00rootroot00000000000000 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. crypto-0.57.0/Makefile000066400000000000000000000032751474156331600146040ustar00rootroot00000000000000# 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: defaulttest simulatortest combinecoverage defaulttest: $Q $(GOFLAGS) gotestsum -- -coverpkg=./... -coverprofile=defaultcoverage.out -covermode=atomic ./... simulatortest: $Q $(GOFLAGS) CGO_ENABLED=1 gotestsum -- -coverpkg=./tpm/...,./kms/tpmkms -coverprofile=simulatorcoverage.out -covermode=atomic -tags tpmsimulator ./tpm ./kms/tpmkms combinecoverage: cat defaultcoverage.out > coverage.out tail -n +2 simulatorcoverage.out >> coverage.out race: $Q $(GOFLAGS) gotestsum -- -race ./... .PHONY: test defaulttest simulatortest combinecoverage race ######################################### # Linting ######################################### fmt: $Q goimports -l -w $(SRC) lint: golint govulncheck golint: SHELL:=/bin/bash golint: $Q LOG_LEVEL=error golangci-lint run --config <(curl -s https://raw.githubusercontent.com/smallstep/workflows/main/.golangci.yml) --timeout=30m govulncheck: $Q govulncheck ./... .PHONY: fmt lint golint govulncheck ######################################### # Go generate ######################################### generate: $Q go generate ./... .PHONY: generate crypto-0.57.0/README.md000066400000000000000000000062441474156331600144220ustar00rootroot00000000000000# 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 `github.com/go-jose/go-jose/v3` 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. ### tpm Package `tpm` provides an abstraction over and utilities for interacting with TPMs. It can be used to retrieve TPM information, retrieve its Endorsement Keys (EK) and associated certificates, create and operate on Attestation Keys (AK), and create and operate on (attested) application keys. The `storage` subpackage provides an interface and concrete implementations offering a transparent persistence mechanism for Attestation and application keys.crypto-0.57.0/SECURITY.md000066400000000000000000000006701474156331600147310ustar00rootroot00000000000000We 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 crypto-0.57.0/fingerprint/000077500000000000000000000000001474156331600154645ustar00rootroot00000000000000crypto-0.57.0/fingerprint/fingerprint.go000066400000000000000000000045171474156331600203510ustar00rootroot00000000000000package 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 "" } } crypto-0.57.0/fingerprint/fingerprint_test.go000066400000000000000000000054211474156331600214030ustar00rootroot00000000000000package 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) } }) } } crypto-0.57.0/go.mod000066400000000000000000000107411474156331600142460ustar00rootroot00000000000000module go.step.sm/crypto go 1.22 require ( cloud.google.com/go/kms v1.20.4 filippo.io/edwards25519 v1.1.0 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 github.com/Masterminds/sprig/v3 v3.3.0 github.com/ThalesIgnite/crypto11 v1.2.5 github.com/aws/aws-sdk-go-v2/config v1.28.10 github.com/aws/aws-sdk-go-v2/service/kms v1.37.8 github.com/go-jose/go-jose/v3 v3.0.3 github.com/go-piv/piv-go/v2 v2.3.0 github.com/google/go-tpm v0.9.3 github.com/google/go-tpm-tools v0.4.4 github.com/googleapis/gax-go/v2 v2.14.1 github.com/peterbourgon/diskv/v3 v3.0.1 github.com/pkg/errors v0.9.1 github.com/schollz/jsonstore v1.1.0 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 github.com/stretchr/testify v1.10.0 go.uber.org/mock v0.5.0 golang.org/x/crypto v0.32.0 golang.org/x/net v0.34.0 golang.org/x/sys v0.29.0 google.golang.org/api v0.216.0 google.golang.org/grpc v1.69.4 google.golang.org/protobuf v1.36.2 ) require ( cloud.google.com/go v0.116.0 // indirect cloud.google.com/go/auth v0.13.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect cloud.google.com/go/compute/metadata v0.6.0 // indirect cloud.google.com/go/iam v1.2.2 // indirect cloud.google.com/go/longrunning v0.6.2 // indirect dario.cat/mergo v1.0.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/aws/aws-sdk-go-v2 v1.32.8 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.51 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.23 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.8 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.24.9 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.8 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.33.6 // indirect github.com/aws/smithy-go v1.22.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/google/btree v1.0.1 // indirect github.com/google/certificate-transparency-go v1.1.2 // indirect github.com/google/go-configfs-tsm v0.2.2 // indirect github.com/google/go-tspi v0.3.0 // indirect github.com/google/s2a-go v0.1.8 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/miekg/pkcs11 v1.0.3 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/spf13/cast v1.7.0 // indirect github.com/thales-e-security/pool v0.0.2 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect go.opentelemetry.io/otel v1.31.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/mod v0.18.0 // indirect golang.org/x/oauth2 v0.25.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.9.0 // indirect golang.org/x/tools v0.22.0 // indirect google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) crypto-0.57.0/go.sum000066400000000000000000004313041474156331600142750ustar00rootroot00000000000000bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= bitbucket.org/creachadair/shell v0.0.6/go.mod h1:8Qqi/cYk7vPnsOePHroKXDJYmb5x7ENhtiFtfZq8K+M= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= cloud.google.com/go v0.92.2/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.92.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs= cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA= cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= cloud.google.com/go/kms v1.20.4 h1:CJ0hMpOg1ANN9tx/a/GPJ+Uxudy8k6f3fvGFuTHiE5A= cloud.google.com/go/kms v1.20.4/go.mod h1:gPLsp1r4FblUgBYPOcvI/bUPpdMg2Jm1ZVKU4tQUfcc= cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc= cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= cloud.google.com/go/monitoring v0.1.0/go.mod h1:Hpm3XfzJv+UTiXzCG5Ffp0wijzHTC7Cv4eR7o3x/fEE= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/spanner v1.17.0/go.mod h1:+17t2ixFwRG4lWRwE+5kipDR9Ef07Jkmc8z0IbMDKUs= cloud.google.com/go/spanner v1.18.0/go.mod h1:LvAjUXPeJRGNuGpikMULjhLj/t9cRvdc+fxRoLiugXA= cloud.google.com/go/spanner v1.25.0/go.mod h1:kQUft3x355hzzaeFbObjsvkzZDgpDkesp3v75WBnI8w= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/trace v0.1.0/go.mod h1:wxEwsoeRVPbeSkt7ZC9nWCgmoKQRAoySN7XHW2AmI7g= code.gitea.io/sdk/gitea v0.11.3/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY= contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0= contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw= contrib.go.opencensus.io/exporter/stackdriver v0.13.5/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= contrib.go.opencensus.io/exporter/stackdriver v0.13.8/go.mod h1:huNtlWx75MwO7qMs0KrMxPZXzNNWebav1Sq/pm02JdQ= contrib.go.opencensus.io/integrations/ocsql v0.1.4/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/Azure/azure-amqp-common-go/v2 v2.1.0/go.mod h1:R8rea+gJRuJR6QxTir/XuEd+YuKoUiazDC/N96FiDEU= github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= github.com/Azure/azure-sdk-for-go v29.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v30.1.0+incompatible h1:HyYPft8wXpxMd0kfLtXo6etWcO+XuPbLkcgx9g2cqxU= github.com/Azure/azure-sdk-for-go v30.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 h1:m/sWOGCREuSBqg2htVQTBY8nOZpyajYztF0vUvSZTuM= github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0/go.mod h1:Pu5Zksi2KrU7LPbZbNINx6fuVrUp/ffvpxdDj+i8LeE= github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw= github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA= github.com/Azure/azure-service-bus-go v0.9.1/go.mod h1:yzBx6/BUGfjfeqbRZny9AQIbIe3AcV9WZbAdpkoXOa0= github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0= github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae/go.mod h1:mjwGPas4yKduTyubHvD1Atl9r1rUq8DfVy+gkVvZ+oo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= 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 v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= 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/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= github.com/apache/beam v2.28.0+incompatible/go.mod h1:/8NX3Qi8vGstDLLaeaU7+lzVEu/ACaQhYjeefzQ0y1o= github.com/apache/beam v2.32.0+incompatible/go.mod h1:/8NX3Qi8vGstDLLaeaU7+lzVEu/ACaQhYjeefzQ0y1o= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apex/log v1.1.4/go.mod h1:AlpoD9aScyQfJDVHmLMEcx4oU6LqzkWp4Mg9GdAcEvQ= github.com/apex/logs v0.0.4/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo= github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE= github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.19.45/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v1.32.8 h1:cZV+NUS/eGxKXMtmyhtYPJ7Z4YLoI/V8bkTdRZfYhGo= github.com/aws/aws-sdk-go-v2 v1.32.8/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= github.com/aws/aws-sdk-go-v2/config v1.28.10 h1:fKODZHfqQu06pCzR69KJ3GuttraRJkhlC8g80RZ0Dfg= github.com/aws/aws-sdk-go-v2/config v1.28.10/go.mod h1:PvdxRYZ5Um9QMq9PQ0zHHNdtKK+he2NHtFCUFMXWXeg= github.com/aws/aws-sdk-go-v2/credentials v1.17.51 h1:F/9Sm6Y6k4LqDesZDPJCLxQGXNNHd/ZtJiWd0lCZKRk= github.com/aws/aws-sdk-go-v2/credentials v1.17.51/go.mod h1:TKbzCHm43AoPyA+iLGGcruXd4AFhF8tOmLex2R9jWNQ= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.23 h1:IBAoD/1d8A8/1aA8g4MBVtTRHhXRiNAgwdbo/xRM2DI= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.23/go.mod h1:vfENuCM7dofkgKpYzuzf1VT1UKkA/YL3qanfBn7HCaA= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27 h1:jSJjSBzw8VDIbWv+mmvBSP8ezsztMYJGH+eKqi9AmNs= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.27/go.mod h1:/DAhLbFRgwhmvJdOfSm+WwikZrCuUJiA4WgJG0fTNSw= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27 h1:l+X4K77Dui85pIj5foXDhPlnqcNRG2QUyvca300lXh8= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.27/go.mod h1:KvZXSFEXm6x84yE8qffKvT3x8J5clWnVFXphpohhzJ8= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.8 h1:cWno7lefSH6Pp+mSznagKCgfDGeZRin66UvYUqAkyeA= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.8/go.mod h1:tPD+VjU3ABTBoEJ3nctu5Nyg4P4yjqSH5bJGGkY4+XE= github.com/aws/aws-sdk-go-v2/service/kms v1.37.8 h1:KbLZjYqhQ9hyB4HwXiheiflTlYQa0+Fz0Ms/rh5f3mk= github.com/aws/aws-sdk-go-v2/service/kms v1.37.8/go.mod h1:ANs9kBhK4Ghj9z1W+bsr3WsNaPF71qkgd6eE6Ekol/Y= github.com/aws/aws-sdk-go-v2/service/sso v1.24.9 h1:YqtxripbjWb2QLyzRK9pByfEDvgg95gpC2AyDq4hFE8= github.com/aws/aws-sdk-go-v2/service/sso v1.24.9/go.mod h1:lV8iQpg6OLOfBnqbGMBKYjilBlf633qwHnBEiMSPoHY= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.8 h1:6dBT1Lz8fK11m22R+AqfRsFn8320K0T5DTGxxOQBSMw= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.8/go.mod h1:/kiBvRQXBc6xeJTYzhSdGvJ5vm1tjaDEjH+MSeRJnlY= github.com/aws/aws-sdk-go-v2/service/sts v1.33.6 h1:VwhTrsTuVn52an4mXx29PqRzs2Dvu921NpGk7y43tAM= github.com/aws/aws-sdk-go-v2/service/sts v1.33.6/go.mod h1:+8h7PZb3yY5ftmVLD7ocEoE98hdc8PoKS0H3wfx1dlc= github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw= github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= 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/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= 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/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.3.0-java/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/gofail v0.0.0-20190801230047-ad7f989257ca/go.mod h1:49H/RkXP8pKaZy4h0d+NW16rSLhyVBt4o6VLJbmOqDE= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fullstorydev/grpcurl v1.8.0/go.mod h1:Mn2jWbdMrQGJQ8UD62uNyMumT2acsZUCkZIqFxsQf1o= github.com/fullstorydev/grpcurl v1.8.1/go.mod h1:3BWhvHZwNO7iLXaQlojdg5NA6SxUDePli4ecpK1N7gw= github.com/fullstorydev/grpcurl v1.8.2/go.mod h1:YvWNT3xRp2KIRuvCphFodG0fKkMXwaxA9CJgKCcyzUQ= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-piv/piv-go/v2 v2.3.0 h1:kKkrYlgLQTMPA6BiSL25A7/x4CEh2YCG7rtb/aTkx+g= github.com/go-piv/piv-go/v2 v2.3.0/go.mod h1:ShZi74nnrWNQEdWzRUd/3cSig3uNOcEZp+EWl0oewnI= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 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.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= 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.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 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.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 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.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/certificate-transparency-go v1.1.2-0.20210422104406-9f33727a7a18/go.mod h1:6CKh9dscIRoqc2kC6YUFICHZMT9NrClyPrRVFrdw1QQ= github.com/google/certificate-transparency-go v1.1.2-0.20210512142713-bed466244fa6/go.mod h1:aF2dp7Dh81mY8Y/zpzyXps4fQW5zQbDu2CxfpJB6NkI= github.com/google/certificate-transparency-go v1.1.2 h1:4hE0GEId6NAW28dFpC+LrRGwQX5dtmXQGDbg8+/MZOM= github.com/google/certificate-transparency-go v1.1.2/go.mod h1:3OL+HKDqHPUfdKrHVQxO6T8nDLO0HF7LRTlkIWXaWvQ= 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.4.1/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.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/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.4/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.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-configfs-tsm v0.2.2 h1:YnJ9rXIOj5BYD7/0DNnzs8AOp7UcvjfTvt215EWcs98= github.com/google/go-configfs-tsm v0.2.2/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo= github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= github.com/google/go-licenses v0.0.0-20210329231322-ce1d9163b77d/go.mod h1:+TYOmkVoJOpwnS0wfdsJCV9CoD5nJYsHoFk/0CrTK4M= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE= github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no= github.com/google/go-sev-guest v0.9.3 h1:GOJ+EipURdeWFl/YYdgcCxyPeMgQUWlI056iFkBD8UU= github.com/google/go-sev-guest v0.9.3/go.mod h1:hc1R4R6f8+NcJwITs0L90fYWTsBpd1Ix+Gur15sqHDs= github.com/google/go-tdx-guest v0.3.1 h1:gl0KvjdsD4RrJzyLefDOvFOUH3NAJri/3qvaL5m83Iw= github.com/google/go-tdx-guest v0.3.1/go.mod h1:/rc3d7rnPykOPuY8U9saMyEps0PZDThLk/RygXm04nE= github.com/google/go-tpm v0.9.3 h1:+yx0/anQuGzi+ssRqeD6WpXjW2L/V0dItUayO0i9sRc= github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98= github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY= github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus= github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/licenseclassifier v0.0.0-20210325184830-bb04aff29e72/go.mod h1:qsqn2hxC+vURpyBRygGUuinTO42MFRLcsmQ/P8v94+M= github.com/google/logger v1.1.1 h1:+6Z2geNxc9G+4D4oDO9njjjn2d0wN5d7uOo0vOIW1NQ= github.com/google/logger v1.1.1/go.mod h1:BkeJZ+1FhQ+/d087r4dzojEg1u2ZX+ZqG1jTUrLM+zQ= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg= github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/trillian v1.3.14-0.20210409160123-c5ea3abd4a41/go.mod h1:1dPv0CUjNQVFEDuAUFhZql16pw/VlPgaX8qj+g5pVzQ= github.com/google/trillian v1.3.14-0.20210511103300-67b5f349eefa/go.mod h1:s4jO3Ai4NSvxucdvqUHON0bCqJyoya32eNw6XJwsmNc= github.com/google/trillian v1.4.0/go.mod h1:1Bja2nEgMDlEJWWRXBUemSPG9qYw84ZYX2gHRVHlR+g= github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s= github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/goreleaser/goreleaser v0.134.0/go.mod h1:ZT6Y2rSYa6NxQzIsdfWWNWAlYGXGbreo66NmE+3X3WQ= github.com/goreleaser/nfpm v1.2.1/go.mod h1:TtWrABZozuLOttX2uDlYyECfQX7x5XYkVxhjYcR6G9w= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.2/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4= github.com/jhump/protoreflect v1.8.2/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= github.com/jhump/protoreflect v1.9.0/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= 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/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 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.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.1/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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo= github.com/mwitkow/go-proto-validators v0.2.0/go.mod h1:ZfA1hW+UH/2ZHOWvQ3HnQaU0DtnpXu850MZiy+YUgcc= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU= github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/pseudomuto/protoc-gen-doc v1.4.1/go.mod h1:exDTOVwqpp30eV/EDPFLZy3Pwr2sn6hBC1WIYH/UbIg= github.com/pseudomuto/protoc-gen-doc v1.5.0/go.mod h1:exDTOVwqpp30eV/EDPFLZy3Pwr2sn6hBC1WIYH/UbIg= github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I= github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E= github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 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/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 h1:kjYvkvS/Wdy0PVRDUAA0gGJIVSEZYhiAJtfwYgOYoGA= github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.5-0.20210205191134-5ec6847320e5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 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/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0= github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0= github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao= github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/xanzy/go-gitlab v0.31.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 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.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd/api/v3 v3.5.0-alpha.0/go.mod h1:mPcW6aZJukV6Aa81LSKpBjQXTWlXB5r74ymPoSWa3Sw= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0-alpha.0/go.mod h1:kdV+xzCJ3luEBSIeQyB/OEKkWKd8Zkux4sbDeANrosU= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v3 v3.5.0-alpha.0/go.mod h1:wKt7jgDgf/OfKiYmCq5WFGxOFAkVMLxiiXgLDFhECr8= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= go.etcd.io/etcd/etcdctl/v3 v3.5.0-alpha.0/go.mod h1:YPwSaBciV5G6Gpt435AasAG3ROetZsKNUzibRa/++oo= go.etcd.io/etcd/etcdctl/v3 v3.5.0/go.mod h1:vGTfKdsh87RI7kA2JHFBEGxjQEYx+pi299wqEOdi34M= go.etcd.io/etcd/etcdutl/v3 v3.5.0/go.mod h1:o98rKMCibbFAG8QS9KmvlYDGDShmmIbmRE8vSofzYNg= go.etcd.io/etcd/pkg/v3 v3.5.0-alpha.0/go.mod h1:tV31atvwzcybuqejDoY3oaNRTtlD2l/Ot78Pc9w7DMY= go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= go.etcd.io/etcd/raft/v3 v3.5.0-alpha.0/go.mod h1:FAwse6Zlm5v4tEWZaTjmNhe17Int4Oxbu7+2r0DiD3w= go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= go.etcd.io/etcd/server/v3 v3.5.0-alpha.0/go.mod h1:tsKetYpt980ZTpzl/gb+UOJj9RkIyCb1u4wjzMg90BQ= go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= go.etcd.io/etcd/tests/v3 v3.5.0-alpha.0/go.mod h1:HnrHxjyCuZ8YDt8PYVyQQ5d1ZQfzJVEtQWllr5Vp/30= go.etcd.io/etcd/tests/v3 v3.5.0/go.mod h1:f+mtZ1bE1YPvgKdOJV2BKy4JQW0nAFnQehgOE7+WyJE= go.etcd.io/etcd/v3 v3.5.0-alpha.0/go.mod h1:JZ79d3LV6NUfPjUxXrpiFAYcjhT+06qqw+i28snx8To= go.etcd.io/etcd/v3 v3.5.0/go.mod h1:FldM0/VzcxYWLvWx1sdA7ghKw7C3L2DvUTzGrcEtsC4= go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= gocloud.dev v0.19.0/go.mod h1:SmKwiR8YwIMMJvQBKLsC3fHNyMwXLw3PMDO+VVteJMI= golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210126194326-f9ce19ea3013/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190412183630-56d357773e84/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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/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/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/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-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190620070143-6f217b454f45/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/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-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/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-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191118222007-07fc4c7f2b98/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201014170642-d1624618ad65/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= 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.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.37.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.45.0/go.mod h1:ISLIJCedJolbZvDfAk+Ctuq5hf+aJ33WgtUsfyFoLXA= google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I= google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= google.golang.org/api v0.216.0 h1:xnEHy+xWFrtYInWPy8OdGFsyIfWJjtVnO39g7pz2BFY= google.golang.org/api v0.216.0/go.mod h1:K9wzQMvWi47Z9IU7OgdOofvZuw75Ge3PPITImZR/UyI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210331142528-b7513248f0ba/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210427215850-f767ed18ee4d/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d h1:xJJRGY7TJcvIlpSrN3K6LAWgNFUILlO+OMAqtg9aqnw= google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= 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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/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.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU= google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/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/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.0-20210107192922-496545a6307b/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-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= pack.ag/amqp v0.11.2/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= crypto-0.57.0/internal/000077500000000000000000000000001474156331600147515ustar00rootroot00000000000000crypto-0.57.0/internal/bcrypt_pbkdf/000077500000000000000000000000001474156331600174225ustar00rootroot00000000000000crypto-0.57.0/internal/bcrypt_pbkdf/LICENSE000066400000000000000000000025411474156331600204310ustar00rootroot00000000000000Copyright (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. crypto-0.57.0/internal/bcrypt_pbkdf/README000066400000000000000000000012641474156331600203050ustar00rootroot00000000000000Go 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 crypto-0.57.0/internal/bcrypt_pbkdf/bcrypt_pbkdf.go000066400000000000000000000054061474156331600224270ustar00rootroot00000000000000// 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] } } crypto-0.57.0/internal/bcrypt_pbkdf/bcrypt_pbkdf_test.go000066400000000000000000000052051474156331600234630ustar00rootroot00000000000000// 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) } } crypto-0.57.0/internal/darwin/000077500000000000000000000000001474156331600162355ustar00rootroot00000000000000crypto-0.57.0/internal/darwin/corefoundation/000077500000000000000000000000001474156331600212545ustar00rootroot00000000000000crypto-0.57.0/internal/darwin/corefoundation/core_foundation_darwin.go000066400000000000000000000127621474156331600263350ustar00rootroot00000000000000// Copyright (c) Smallstep Labs, Inc. // Copyright (c) Meta Platforms, Inc. and affiliates. // // 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. // // Part of this code is based on // https://github.com/facebookincubator/sks/blob/183e7561ecedc71992f23b2d37983d2948391f4c/macos/macos.go //nolint:gocritic // open issue https://github.com/go-critic/go-critic/issues/845 package corefoundation /* #cgo LDFLAGS: -framework CoreFoundation #include */ import "C" import ( "fmt" "unsafe" ) const ( nilCFData C.CFDataRef = 0 nilCFString C.CFStringRef = 0 nilCFDictionary C.CFDictionaryRef = 0 nilCFArray C.CFArrayRef = 0 nilCFError C.CFErrorRef = 0 nilCFType C.CFTypeRef = 0 ) type TypeReferer interface { TypeRef() CFTypeRef } func Release(ref TypeReferer) { C.CFRelease(ref.TypeRef()) } func Retain(ref TypeReferer) { C.CFRetain(ref.TypeRef()) } type CFTypeRef = C.CFTypeRef type CFStringRef = C.CFStringRef type CFErrorRef = C.CFErrorRef type CFDictionaryRef = C.CFDictionaryRef type CFArrayRef = C.CFArrayRef type CFDataRef = C.CFDataRef type TypeRef C.CFTypeRef func (v TypeRef) Release() { Release(v) } func (v TypeRef) TypeRef() CFTypeRef { return C.CFTypeRef(v) } type BooleanRef C.CFBooleanRef var ( True = BooleanRef(C.kCFBooleanTrue) False = BooleanRef(C.kCFBooleanFalse) ) func (v BooleanRef) TypeRef() CFTypeRef { return C.CFTypeRef(C.CFBooleanRef(v)) } type AllocatorRef = C.CFAllocatorRef var AllocatorDefault = C.kCFAllocatorDefault type NumberRef struct { Value C.CFNumberRef } func NewNumber(v int) *NumberRef { return &NumberRef{ Value: C.CFNumberCreate(0, C.kCFNumberIntType, unsafe.Pointer(&v)), } } func (v *NumberRef) Release() { Release(v) } func (v *NumberRef) TypeRef() CFTypeRef { return C.CFTypeRef(v.Value) } type DataRef struct { Value C.CFDataRef } func NewData(d []byte) (*DataRef, error) { p := (*C.uchar)(C.CBytes(d)) defer C.free(unsafe.Pointer(p)) ref := C.CFDataCreate(C.kCFAllocatorDefault, p, C.CFIndex(len(d))) if ref == nilCFData { return nil, fmt.Errorf("error creating CFData") } return &DataRef{ Value: ref, }, nil } func (v *DataRef) Bytes() []byte { return C.GoBytes( unsafe.Pointer(C.CFDataGetBytePtr(v.Value)), C.int(C.CFDataGetLength(v.Value)), ) } func (v *DataRef) Release() { Release(v) } func (v *DataRef) TypeRef() CFTypeRef { return C.CFTypeRef(v.Value) } type StringRef struct { Value C.CFStringRef } func NewString(s string) (*StringRef, error) { p := C.CString(s) defer C.free(unsafe.Pointer(p)) ref := C.CFStringCreateWithCString(C.kCFAllocatorDefault, p, C.kCFStringEncodingUTF8) if ref == nilCFString { return nil, fmt.Errorf("error creating CFString") } return &StringRef{ Value: ref, }, nil } func (v *StringRef) Release() { Release(v) } func (v *StringRef) TypeRef() CFTypeRef { return C.CFTypeRef(v.Value) } type Dictionary map[TypeRef]TypeReferer type DictionaryRef struct { Value C.CFDictionaryRef } func NewDictionary(m Dictionary) (*DictionaryRef, error) { var ( keys []unsafe.Pointer values []unsafe.Pointer ) for k, v := range m { keys = append(keys, unsafe.Pointer(C.CFTypeRef(k))) values = append(values, unsafe.Pointer(v.TypeRef())) } // If the map will contain only CFType objects, we must pass a pointer to // kCFTypeDictionaryKeyCallBacks and kCFTypeDictionaryValueCallBacks ref := C.CFDictionaryCreate(C.kCFAllocatorDefault, &keys[0], &values[0], C.CFIndex(len(m)), &C.kCFTypeDictionaryKeyCallBacks, &C.kCFTypeDictionaryValueCallBacks) if ref == nilCFDictionary { return nil, fmt.Errorf("error creating CFDictionary") } return &DictionaryRef{ Value: ref, }, nil } func NewDictionaryRef(ref TypeRef) *DictionaryRef { return &DictionaryRef{ Value: C.CFDictionaryRef(ref), } } func (v *DictionaryRef) Release() { Release(v) } func (v *DictionaryRef) TypeRef() CFTypeRef { return C.CFTypeRef(v.Value) } type ArrayRef struct { Value C.CFArrayRef } func NewArrayRef(ref TypeRef) *ArrayRef { return &ArrayRef{ Value: C.CFArrayRef(ref), } } func (v *ArrayRef) Release() { Release(v) } func (v *ArrayRef) TypeRef() CFTypeRef { return C.CFTypeRef(v.Value) } func (v *ArrayRef) Len() int { return int(C.CFArrayGetCount(v.Value)) } func (v *ArrayRef) Get(index int) TypeRef { item := C.CFArrayGetValueAtIndex(v.Value, C.CFIndex(index)) return TypeRef(item) } //nolint:errname // type name matches original name type ErrorRef C.CFErrorRef func (e ErrorRef) Error() string { ref := C.CFErrorRef(e) code := int(C.CFErrorGetCode(ref)) if desc := C.CFErrorCopyDescription(ref); desc != nilCFString { defer C.CFRelease(C.CFTypeRef(desc)) if cstr := C.CFStringGetCStringPtr(desc, C.kCFStringEncodingUTF8); cstr != nil { str := C.GoString(cstr) return fmt.Sprintf("CFError %d: %s", code, str) } } return fmt.Sprintf("CFError %d", code) } func (e ErrorRef) Release() { Release(e) } func (e ErrorRef) TypeRef() CFTypeRef { return C.CFTypeRef(C.CFErrorRef(e)) } crypto-0.57.0/internal/darwin/security/000077500000000000000000000000001474156331600201045ustar00rootroot00000000000000crypto-0.57.0/internal/darwin/security/security_darwin.go000066400000000000000000000406051474156331600236530ustar00rootroot00000000000000// Copyright (c) Smallstep Labs, Inc. // Copyright (c) Meta Platforms, Inc. and affiliates. // // 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. // // Part of this code is based on // https://github.com/facebookincubator/sks/blob/183e7561ecedc71992f23b2d37983d2948391f4c/macos/macos.go //nolint:gocritic // open issue https://github.com/go-critic/go-critic/issues/845 package security /* #cgo LDFLAGS: -framework CoreFoundation -framework Security #include #include */ import "C" import ( "errors" "fmt" "unsafe" cf "go.step.sm/crypto/internal/darwin/corefoundation" ) const ( nilSecKey C.SecKeyRef = 0 nilSecAccessControl C.SecAccessControlRef = 0 nilCFString C.CFStringRef = 0 nilCFData C.CFDataRef = 0 ) var ( ErrNotFound = errors.New("not found") ErrAlreadyExists = errors.New("already exists") ErrInvalidData = errors.New("invalid data") ) var ( KSecAttrAccessControl = cf.TypeRef(C.kSecAttrAccessControl) KSecAttrAccessGroup = cf.TypeRef(C.kSecAttrAccessGroup) KSecAttrAccessibleWhenUnlocked = cf.TypeRef(C.kSecAttrAccessibleWhenUnlocked) KSecAttrAccessibleWhenPasscodeSetThisDeviceOnly = cf.TypeRef(C.kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly) KSecAttrAccessibleWhenUnlockedThisDeviceOnly = cf.TypeRef(C.kSecAttrAccessibleWhenUnlockedThisDeviceOnly) KSecAttrAccessibleAfterFirstUnlock = cf.TypeRef(C.kSecAttrAccessibleAfterFirstUnlock) KSecAttrAccessibleAfterFirstUnlockThisDeviceOnly = cf.TypeRef(C.kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) KSecAttrApplicationLabel = cf.TypeRef(C.kSecAttrApplicationLabel) KSecAttrApplicationTag = cf.TypeRef(C.kSecAttrApplicationTag) KSecAttrIsPermanent = cf.TypeRef(C.kSecAttrIsPermanent) KSecAttrKeyClass = cf.TypeRef(C.kSecAttrKeyClass) KSecAttrKeyClassPrivate = cf.TypeRef(C.kSecAttrKeyClassPrivate) KSecAttrKeyClassPublic = cf.TypeRef(C.kSecAttrKeyClassPublic) KSecAttrKeySizeInBits = cf.TypeRef(C.kSecAttrKeySizeInBits) KSecAttrKeyType = cf.TypeRef(C.kSecAttrKeyType) KSecAttrKeyTypeECSECPrimeRandom = cf.TypeRef(C.kSecAttrKeyTypeECSECPrimeRandom) KSecAttrKeyTypeRSA = cf.TypeRef(C.kSecAttrKeyTypeRSA) KSecAttrLabel = cf.TypeRef(C.kSecAttrLabel) KSecAttrTokenID = cf.TypeRef(C.kSecAttrTokenID) KSecAttrTokenIDSecureEnclave = cf.TypeRef(C.kSecAttrTokenIDSecureEnclave) KSecAttrSerialNumber = cf.TypeRef(C.kSecAttrSerialNumber) KSecAttrSubjectKeyID = cf.TypeRef(C.kSecAttrSubjectKeyID) KSecAttrSubject = cf.TypeRef(C.kSecAttrSubject) KSecAttrIssuer = cf.TypeRef(C.kSecAttrIssuer) KSecAttrSynchronizable = cf.TypeRef(C.kSecAttrSynchronizable) KSecUseDataProtectionKeychain = cf.TypeRef(C.kSecUseDataProtectionKeychain) KSecClass = cf.TypeRef(C.kSecClass) KSecClassKey = cf.TypeRef(C.kSecClassKey) KSecClassCertificate = cf.TypeRef(C.kSecClassCertificate) KSecClassIdentity = cf.TypeRef(C.kSecClassIdentity) KSecMatchLimit = cf.TypeRef(C.kSecMatchLimit) KSecMatchLimitOne = cf.TypeRef(C.kSecMatchLimitOne) KSecMatchLimitAll = cf.TypeRef(C.kSecMatchLimitAll) KSecPublicKeyAttrs = cf.TypeRef(C.kSecPublicKeyAttrs) KSecPrivateKeyAttrs = cf.TypeRef(C.kSecPrivateKeyAttrs) KSecReturnRef = cf.TypeRef(C.kSecReturnRef) KSecReturnAttributes = cf.TypeRef(C.kSecReturnAttributes) KSecValueRef = cf.TypeRef(C.kSecValueRef) KSecValueData = cf.TypeRef(C.kSecValueData) ) type SecKeyAlgorithm = C.SecKeyAlgorithm var ( KSecKeyAlgorithmECDSASignatureDigestX962 = C.kSecKeyAlgorithmECDSASignatureDigestX962 KSecKeyAlgorithmECDSASignatureDigestX962SHA256 = C.kSecKeyAlgorithmECDSASignatureDigestX962SHA256 KSecKeyAlgorithmECDSASignatureDigestX962SHA384 = C.kSecKeyAlgorithmECDSASignatureDigestX962SHA384 KSecKeyAlgorithmECDSASignatureDigestX962SHA512 = C.kSecKeyAlgorithmECDSASignatureDigestX962SHA512 KSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA256 = C.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA256 KSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA384 = C.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA384 KSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA512 = C.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA512 KSecKeyAlgorithmRSASignatureDigestPSSSHA256 = C.kSecKeyAlgorithmRSASignatureDigestPSSSHA256 KSecKeyAlgorithmRSASignatureDigestPSSSHA384 = C.kSecKeyAlgorithmRSASignatureDigestPSSSHA384 KSecKeyAlgorithmRSASignatureDigestPSSSHA512 = C.kSecKeyAlgorithmRSASignatureDigestPSSSHA512 KSecKeyAlgorithmECDHKeyExchangeStandard = C.kSecKeyAlgorithmECDHKeyExchangeStandard ) type SecAccessControlCreateFlags = C.SecAccessControlCreateFlags const ( // Enable a private key to be used in signing a block of data or verifying a // signed block. KSecAccessControlPrivateKeyUsage = SecAccessControlCreateFlags(C.kSecAccessControlPrivateKeyUsage) // Option to use an application-provided password for data encryption key // generation. KSecAccessControlApplicationPassword = SecAccessControlCreateFlags(C.kSecAccessControlApplicationPassword) // Constraint to access an item with a passcode. KSecAccessControlDevicePasscode = SecAccessControlCreateFlags(C.kSecAccessControlDevicePasscode) // Constraint to access an item with Touch ID for any enrolled fingers, or // Face ID. KSecAccessControlBiometryAny = SecAccessControlCreateFlags(C.kSecAccessControlBiometryAny) // Constraint to access an item with Touch ID for currently enrolled // fingers, or from Face ID with the currently enrolled user. KSecAccessControlBiometryCurrentSet = SecAccessControlCreateFlags(C.kSecAccessControlBiometryCurrentSet) // Constraint to access an item with either biometry or passcode. KSecAccessControlUserPresence = SecAccessControlCreateFlags(C.kSecAccessControlUserPresence) // Constraint to access an item with a watch. KSecAccessControlWatch = SecAccessControlCreateFlags(C.kSecAccessControlWatch) // Indicates that all constraints must be satisfied. KSecAccessControlAnd = SecAccessControlCreateFlags(C.kSecAccessControlAnd) // Indicates that at least one constraint must be satisfied. KSecAccessControlOr = SecAccessControlCreateFlags(C.kSecAccessControlOr) ) type SecKeychainItemRef struct { Value C.SecKeychainItemRef } func NewSecKeychainItemRef(ref cf.TypeRef) *SecKeychainItemRef { return &SecKeychainItemRef{ Value: C.SecKeychainItemRef(ref), } } func (v *SecKeychainItemRef) Release() { cf.Release(v) } func (v *SecKeychainItemRef) TypeRef() cf.CFTypeRef { return cf.CFTypeRef(v.Value) } func (v *SecKeychainItemRef) Retain() { cf.Retain(v) } type SecKeyRef struct { Value C.SecKeyRef } func NewSecKeyRef(ref cf.TypeRef) *SecKeyRef { return &SecKeyRef{ Value: C.SecKeyRef(ref), } } func (v *SecKeyRef) Release() { cf.Release(v) } func (v *SecKeyRef) TypeRef() cf.CFTypeRef { return cf.CFTypeRef(v.Value) } func (v *SecKeyRef) Retain() { cf.Retain(v) } type SecCertificateRef struct { Value C.SecCertificateRef } func NewSecCertificateRef(ref cf.TypeRef) *SecCertificateRef { return &SecCertificateRef{ Value: C.SecCertificateRef(ref), } } func (v *SecCertificateRef) Release() { cf.Release(v) } func (v *SecCertificateRef) TypeRef() cf.CFTypeRef { return cf.CFTypeRef(v.Value) } type SecAccessControlRef struct { ref C.SecAccessControlRef } func (v *SecAccessControlRef) Release() { cf.Release(v) } func (v *SecAccessControlRef) TypeRef() cf.CFTypeRef { return cf.CFTypeRef(v.ref) } func SecItemAdd(attributes *cf.DictionaryRef, result *cf.TypeRef) error { status := C.SecItemAdd(C.CFDictionaryRef(attributes.Value), (*C.CFTypeRef)(result)) return goOSStatus(status) } func SecItemUpdate(query *cf.DictionaryRef, attributesToUpdate *cf.DictionaryRef) error { status := C.SecItemUpdate(C.CFDictionaryRef(query.Value), C.CFDictionaryRef(attributesToUpdate.Value)) return goOSStatus(status) } func SecItemDelete(query *cf.DictionaryRef) error { status := C.SecItemDelete(C.CFDictionaryRef(query.Value)) return goOSStatus(status) } func SecItemCopyMatching(query *cf.DictionaryRef, result *cf.TypeRef) error { status := C.SecItemCopyMatching(C.CFDictionaryRef(query.Value), (*C.CFTypeRef)(result)) return goOSStatus(status) } func SecKeyCreateRandomKey(parameters *cf.DictionaryRef) (*SecKeyRef, error) { var cerr C.CFErrorRef key := C.SecKeyCreateRandomKey(C.CFDictionaryRef(parameters.Value), &cerr) if err := goCFErrorRef(cerr); err != nil { return nil, err } return &SecKeyRef{ Value: key, }, nil } func SecKeyCopyPublicKey(key *SecKeyRef) (*SecKeyRef, error) { publicKey := C.SecKeyCopyPublicKey(key.Value) if publicKey == nilSecKey { return nil, ErrNotFound } return &SecKeyRef{ Value: publicKey, }, nil } func SecKeyCopyAttributes(key *SecKeyRef) *cf.DictionaryRef { attr := C.SecKeyCopyAttributes(key.Value) return &cf.DictionaryRef{ Value: cf.CFDictionaryRef(attr), } } func SecKeyCopyExternalRepresentation(key *SecKeyRef) (*cf.DataRef, error) { var cerr C.CFErrorRef data := C.SecKeyCopyExternalRepresentation(key.Value, &cerr) if err := goCFErrorRef(cerr); err != nil { return nil, err } return &cf.DataRef{ Value: cf.CFDataRef(data), }, nil } func SecAccessControlCreateWithFlags(protection cf.TypeRef, flags SecAccessControlCreateFlags) (*SecAccessControlRef, error) { var cerr C.CFErrorRef access := C.SecAccessControlCreateWithFlags(C.kCFAllocatorDefault, C.CFTypeRef(protection), flags, &cerr) if err := goCFErrorRef(cerr); err != nil { return nil, err } return &SecAccessControlRef{ ref: access, }, nil } func SecKeyCreateSignature(key *SecKeyRef, algorithm SecKeyAlgorithm, dataToSign *cf.DataRef) (*cf.DataRef, error) { var cerr C.CFErrorRef signature := C.SecKeyCreateSignature(key.Value, algorithm, C.CFDataRef(dataToSign.Value), &cerr) if err := goCFErrorRef(cerr); err != nil { return nil, err } return &cf.DataRef{ Value: cf.CFDataRef(signature), }, nil } func SecCertificateCopyData(cert *SecCertificateRef) (*cf.DataRef, error) { data := C.SecCertificateCopyData(cert.Value) if data == nilCFData { return nil, ErrInvalidData } return &cf.DataRef{ Value: cf.CFDataRef(data), }, nil } func SecCertificateCreateWithData(certData *cf.DataRef) (*SecCertificateRef, error) { certRef := C.SecCertificateCreateWithData(C.kCFAllocatorDefault, C.CFDataRef(certData.Value)) if certRef == 0 { return nil, ErrInvalidData } return &SecCertificateRef{ Value: certRef, }, nil } func SecKeyCreateWithData(keyData *cf.DataRef, attributes *cf.DictionaryRef) (*SecKeyRef, error) { var cerr C.CFErrorRef keyRef := C.SecKeyCreateWithData(C.CFDataRef(keyData.Value), C.CFDictionaryRef(attributes.Value), &cerr) if err := goCFErrorRef(cerr); err != nil { return nil, err } return &SecKeyRef{ Value: keyRef, }, nil } func SecKeyCopyKeyExchangeResult(privateKey *SecKeyRef, algorithm SecKeyAlgorithm, publicKey *SecKeyRef, parameters *cf.DictionaryRef) (*cf.DataRef, error) { var cerr C.CFErrorRef dataRef := C.SecKeyCopyKeyExchangeResult(privateKey.Value, algorithm, publicKey.Value, C.CFDictionaryRef(parameters.Value), &cerr) if err := goCFErrorRef(cerr); err != nil { return nil, err } return &cf.DataRef{ Value: cf.CFDataRef(dataRef), }, nil } func SecCopyErrorMessageString(status C.OSStatus) *cf.StringRef { s := C.SecCopyErrorMessageString(status, nil) return &cf.StringRef{ Value: cf.CFStringRef(s), } } func GetSecAttrApplicationLabel(v *cf.DictionaryRef) []byte { data := C.CFDataRef(C.CFDictionaryGetValue(C.CFDictionaryRef(v.Value), unsafe.Pointer(C.kSecAttrApplicationLabel))) return goBytes(data) } func GetSecAttrApplicationTag(v *cf.DictionaryRef) string { data := C.CFDataRef(C.CFDictionaryGetValue(C.CFDictionaryRef(v.Value), unsafe.Pointer(C.kSecAttrApplicationTag))) return string(goBytes(data)) } func GetSecAttrLabel(v *cf.DictionaryRef) string { ref := C.CFStringRef(C.CFDictionaryGetValue(C.CFDictionaryRef(v.Value), unsafe.Pointer(C.kSecAttrLabel))) return goString(ref) } func GetSecAttrTokenID(v *cf.DictionaryRef) string { ref := C.CFStringRef(C.CFDictionaryGetValue(C.CFDictionaryRef(v.Value), unsafe.Pointer(C.kSecAttrTokenID))) return goString(ref) } func GetSecAttrAccessControl(v *cf.DictionaryRef) *SecAccessControlRef { var keyAttributes unsafe.Pointer tokenID := GetSecAttrTokenID(v) if tokenID == "com.apple.setoken" { keyAttributes = C.CFDictionaryGetValue(C.CFDictionaryRef(v.Value), unsafe.Pointer(C.kSecPrivateKeyAttrs)) } else { keyAttributes = C.CFDictionaryGetValue(C.CFDictionaryRef(v.Value), unsafe.Pointer(C.kSecPublicKeyAttrs)) } if keyAttributes == nil { return nil } dv := C.CFDictionaryGetValue(C.CFDictionaryRef(keyAttributes), unsafe.Pointer(C.kSecAttrAccessControl)) if dv == nil { return nil } ref := &SecAccessControlRef{ ref: C.SecAccessControlRef(dv), } return ref } func GetSecValueData(v *cf.DictionaryRef) []byte { data := C.CFDataRef(C.CFDictionaryGetValue(C.CFDictionaryRef(v.Value), unsafe.Pointer(C.kSecValueData))) return goBytes(data) } type osStatusError struct { code int message string } func (e osStatusError) Error() string { if e.message == "" { return fmt.Sprintf("OSStatus %d: unknown error", e.code) } return fmt.Sprintf("OSStatus %d: %s", e.code, e.message) } func goOSStatus(status C.OSStatus) error { switch status { case 0: return nil case C.errSecItemNotFound: // -25300 return ErrNotFound case C.errSecDuplicateItem: // -25299 return ErrAlreadyExists } var message string if ref := SecCopyErrorMessageString(status); ref.Value != 0 { message = goString(C.CFStringRef(ref.Value)) defer ref.Release() } return osStatusError{ code: int(status), message: message, } } func goBytes(data C.CFDataRef) []byte { if data == 0 { return nil } return C.GoBytes( unsafe.Pointer(C.CFDataGetBytePtr(data)), C.int(C.CFDataGetLength(data)), ) } func goString(ref C.CFStringRef) string { if ref == 0 { return "" } // CFStringGetCStringPtr either returns the requested pointer immediately, // with no memory allocations and no copying, in constant time, or returns // NULL. if cstr := C.CFStringGetCStringPtr(ref, C.kCFStringEncodingUTF8); cstr != nil { return C.GoString(cstr) } // The documentation recommends using CFStringGetCString if the previous one // fails. length := C.CFStringGetLength(ref) buf := (*C.char)(C.malloc(C.size_t(length) + 1)) defer C.free(unsafe.Pointer(buf)) if C.CFStringGetCString(ref, buf, length+1, C.kCFStringEncodingUTF8) == 0 { return "" } return C.GoString(buf) } type cfError struct { code int message string } func (e cfError) Error() string { if e.message == "" { return fmt.Sprintf("CFError %d: unknown error", e.code) } return fmt.Sprintf("CFError %d: %s", e.code, e.message) } func goCFErrorRef(ref C.CFErrorRef) error { if ref == 0 { return nil } var message string if desc := C.CFErrorCopyDescription(ref); desc != nilCFString { defer C.CFRelease(C.CFTypeRef(desc)) if cstr := C.CFStringGetCStringPtr(desc, C.kCFStringEncodingUTF8); cstr != nil { message = C.GoString(cstr) } } return &cfError{ code: int(C.CFErrorGetCode(ref)), message: message, } } crypto-0.57.0/internal/emoji/000077500000000000000000000000001474156331600160545ustar00rootroot00000000000000crypto-0.57.0/internal/emoji/emoji.go000066400000000000000000000346161474156331600175200ustar00rootroot00000000000000package 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: } crypto-0.57.0/internal/emoji/emoji_test.go000066400000000000000000000007601474156331600205500ustar00rootroot00000000000000package 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) } }) } } crypto-0.57.0/internal/templates/000077500000000000000000000000001474156331600167475ustar00rootroot00000000000000crypto-0.57.0/internal/templates/funcmap.go000066400000000000000000000101551474156331600207310ustar00rootroot00000000000000package templates import ( "errors" "strings" "text/template" "time" "github.com/Masterminds/sprig/v3" "go.step.sm/crypto/jose" ) // GetFuncMap returns the list of functions provided by sprig. It adds the // functions "toTime", "formatTime", "parseTime", "mustParseTime", // "toTimeLayout" and changes the function "fail". // // The "toTime" function receives a time or a Unix epoch and returns a time.Time // in UTC. The "formatTime" function uses "toTime" and formats the resulting // time using RFC3339. The functions "parseTime" and "mustParseTime" parse a // string and return the time.Time it represents. The "toTimeLayout" function // converts strings like "time.RFC3339" or "UnixDate" to the actual layout // represented by the Go constant with the same name. The "fail" function sets // the provided message, so that template errors are reported directly to the // template without having the wrapper that text/template adds. // // {{ toTime }} // => time.Now().UTC() // {{ .Token.nbf | toTime }} // => time.Unix(.Token.nbf, 0).UTC() // {{ .Token.nbf | formatTime }} // => time.Unix(.Token.nbf, 0).UTC().Format(time.RFC3339) // {{ "2024-07-02T23:16:02Z" | parseTime }} // => time.Parse(time.RFC3339, "2024-07-02T23:16:02Z") // {{ parseTime "time.RFC339" "2024-07-02T23:16:02Z" }} // => time.Parse(time.RFC3339, "2024-07-02T23:16:02Z") // {{ parseTime "time.UnixDate" "Tue Jul 2 16:20:48 PDT 2024" "America/Los_Angeles" }} // => loc, _ := time.LoadLocation("America/Los_Angeles") // time.ParseInLocation(time.UnixDate, "Tue Jul 2 16:20:48 PDT 2024", loc) // {{ toTimeLayout "RFC3339" }} // => time.RFC3339 // // 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) } m["formatTime"] = formatTime m["toTime"] = toTime m["parseTime"] = parseTime m["mustParseTime"] = mustParseTime m["toTimeLayout"] = toTimeLayout return m } func toTime(v any) time.Time { var t time.Time switch date := v.(type) { case time.Time: t = date case *time.Time: t = *date case int64: t = time.Unix(date, 0) case float64: // from json t = time.Unix(int64(date), 0) case int: t = time.Unix(int64(date), 0) case int32: t = time.Unix(int64(date), 0) case jose.NumericDate: t = date.Time() case *jose.NumericDate: t = date.Time() default: t = time.Now() } return t.UTC() } func formatTime(v any) string { return toTime(v).Format(time.RFC3339) } func parseTime(v ...string) time.Time { t, _ := mustParseTime(v...) return t } func mustParseTime(v ...string) (time.Time, error) { switch len(v) { case 0: return time.Now().UTC(), nil case 1: return time.Parse(time.RFC3339, v[0]) case 2: layout := toTimeLayout(v[0]) return time.Parse(layout, v[1]) case 3: layout := toTimeLayout(v[0]) loc, err := time.LoadLocation(v[2]) if err != nil { return time.Time{}, err } return time.ParseInLocation(layout, v[1], loc) default: return time.Time{}, errors.New("unsupported number of parameters") } } func toTimeLayout(fmt string) string { switch strings.ToUpper(strings.TrimPrefix(fmt, "time.")) { case "LAYOUT": return time.Layout case "ANSIC": return time.ANSIC case "UNIXDATE": return time.UnixDate case "RUBYDATE": return time.RubyDate case "RFC822": return time.RFC822 case "RFC822Z": return time.RFC822Z case "RFC850": return time.RFC850 case "RFC1123": return time.RFC1123 case "RFC1123Z": return time.RFC1123Z case "RFC3339": return time.RFC3339 case "RFC3339NANO": return time.RFC3339Nano // From the ones below, only time.DateTime will parse a complete date. case "KITCHEN": return time.Kitchen case "STAMP": return time.Stamp case "STAMPMILLI": return time.StampMilli case "STAMPMICRO": return time.StampMicro case "STAMPNANO": return time.StampNano case "DATETIME": return time.DateTime case "DATEONLY": return time.DateOnly case "TIMEONLY": return time.TimeOnly default: return fmt } } crypto-0.57.0/internal/templates/funcmap_test.go000066400000000000000000000247241474156331600217770ustar00rootroot00000000000000package templates import ( "bytes" "errors" "strconv" "strings" "testing" "text/template" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.step.sm/crypto/jose" ) 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) } } func TestGetFuncMap_toTime_formatTime(t *testing.T) { now := time.Now().Truncate(time.Second) numericDate := jose.NewNumericDate(now) expected := now.UTC() loc, err := time.LoadLocation("America/Los_Angeles") require.NoError(t, err) type args struct { v any } tests := []struct { name string args args want time.Time }{ {"time", args{now}, expected}, {"time pointer", args{&now}, expected}, {"time UTC", args{now.UTC()}, expected}, {"time with location", args{now.In(loc)}, expected}, {"unix", args{now.Unix()}, expected}, {"unix int", args{int(now.Unix())}, expected}, {"unix int32", args{int32(now.Unix())}, expected}, {"unix float64", args{float64(now.Unix())}, expected}, {"unix float64", args{float64(now.Unix()) + 0.999}, expected}, {"jose.NumericDate", args{*numericDate}, expected}, {"jose.NumericDate pointer", args{numericDate}, expected}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var failMesage string fns := GetFuncMap(&failMesage) toTimeFunc := fns["toTime"].(func(any) time.Time) assert.Equal(t, tt.want, toTimeFunc(tt.args.v)) formatTimeFunc := fns["formatTime"].(func(any) string) assert.Equal(t, tt.want.Format(time.RFC3339), formatTimeFunc(tt.args.v)) }) } t.Run("default", func(t *testing.T) { var failMesage string fns := GetFuncMap(&failMesage) toTimeFunc := fns["toTime"].(func(any) time.Time) got := toTimeFunc(nil) assert.WithinDuration(t, time.Now(), got, time.Second) formatTimeFunc := fns["formatTime"].(func(any) string) got, err := time.Parse(time.RFC3339, formatTimeFunc(nil)) require.NoError(t, err) assert.WithinDuration(t, time.Now(), got, time.Second) assert.Equal(t, time.UTC, got.Location()) }) } func TestGetFuncMap_parseTime_mustParseTime(t *testing.T) { now := time.Now().Truncate(time.Second) loc := time.Local if zone, _ := now.Zone(); zone == "UTC" { loc = time.UTC } losAngeles, err := time.LoadLocation("America/Los_Angeles") require.NoError(t, err) type args struct { v []string } tests := []struct { name string args args want time.Time assertion assert.ErrorAssertionFunc }{ {"now", args{[]string{now.Format(time.RFC3339)}}, now.In(loc), assert.NoError}, {"with real layout", args{[]string{time.UnixDate, now.UTC().Format(time.UnixDate)}}, now.UTC(), assert.NoError}, {"with name layout", args{[]string{"time.UnixDate", now.Format(time.UnixDate)}}, now.In(loc), assert.NoError}, {"with locale UTC", args{[]string{"time.UnixDate", now.UTC().Format(time.UnixDate), "UTC"}}, now.UTC(), assert.NoError}, {"with locale other", args{[]string{"time.UnixDate", now.In(losAngeles).Format(time.UnixDate), "America/Los_Angeles"}}, now.In(losAngeles), assert.NoError}, {"fail parse", args{[]string{now.Format(time.UnixDate)}}, time.Time{}, assert.Error}, {"fail parse with layout", args{[]string{"time.UnixDate", now.Format(time.RFC3339)}}, time.Time{}, assert.Error}, {"fail parse with locale", args{[]string{"time.UnixDate", now.Format(time.RFC3339), "america/Los_Angeles"}}, time.Time{}, assert.Error}, {"fail load locale", args{[]string{"time.UnixDate", now.In(losAngeles).Format(time.UnixDate), "America/The_Angels"}}, time.Time{}, assert.Error}, {"fail arguments", args{[]string{"time.Layout", now.Format(time.Layout), "America/The_Angels", "extra"}}, time.Time{}, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var failMesage string fns := GetFuncMap(&failMesage) parseTimeFunc := fns["parseTime"].(func(...string) time.Time) assert.Equal(t, tt.want, parseTimeFunc(tt.args.v...)) mustParseTimeFunc := fns["mustParseTime"].(func(...string) (time.Time, error)) got, err := mustParseTimeFunc(tt.args.v...) tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } t.Run("default", func(t *testing.T) { var failMesage string fns := GetFuncMap(&failMesage) parseTimeFunc := fns["parseTime"].(func(...string) time.Time) got := parseTimeFunc() assert.WithinDuration(t, time.Now(), got, time.Second) mustParseTimeFunc := fns["mustParseTime"].(func(...string) (time.Time, error)) got, err := mustParseTimeFunc() require.NoError(t, err) assert.WithinDuration(t, time.Now(), got, time.Second) assert.Equal(t, time.UTC, got.Location()) }) } func TestGetFuncMap_toTimeLayout(t *testing.T) { type args struct { fmt string } tests := []struct { name string args args want string }{ {"format", args{time.RFC3339}, time.RFC3339}, {"time.Layout", args{"time.Layout"}, time.Layout}, {"time.ANSIC", args{"time.ANSIC"}, time.ANSIC}, {"time.UnixDate", args{"time.UnixDate"}, time.UnixDate}, {"time.RubyDate", args{"time.RubyDate"}, time.RubyDate}, {"time.RFC822", args{"time.RFC822"}, time.RFC822}, {"time.RFC822Z", args{"time.RFC822Z"}, time.RFC822Z}, {"time.RFC850", args{"time.RFC850"}, time.RFC850}, {"time.RFC1123", args{"time.RFC1123"}, time.RFC1123}, {"time.RFC1123Z", args{"time.RFC1123Z"}, time.RFC1123Z}, {"time.RFC3339", args{"time.RFC3339"}, time.RFC3339}, {"time.RFC3339Nano", args{"time.RFC3339Nano"}, time.RFC3339Nano}, {"time.Kitchen", args{"time.Kitchen"}, time.Kitchen}, {"time.Stamp", args{"time.Stamp"}, time.Stamp}, {"time.StampMilli", args{"time.StampMilli"}, time.StampMilli}, {"time.StampMicro", args{"time.StampMicro"}, time.StampMicro}, {"time.StampNano", args{"time.StampNano"}, time.StampNano}, {"time.DateTime", args{"time.DateTime"}, time.DateTime}, {"time.DateOnly", args{"time.DateOnly"}, time.DateOnly}, {"time.TimeOnly", args{"time.TimeOnly"}, time.TimeOnly}, {"uppercase", args{"UNIXDATE"}, time.UnixDate}, {"lowercase", args{"rfc3339"}, time.RFC3339}, {"default", args{"MyFormat"}, "MyFormat"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var failMesage string fns := GetFuncMap(&failMesage) toTimeLayoutFunc := fns["toTimeLayout"].(func(string) string) assert.Equal(t, tt.want, toTimeLayoutFunc(tt.args.fmt)) fmt := strings.TrimPrefix(tt.args.fmt, "time.") assert.Equal(t, tt.want, toTimeLayoutFunc(fmt)) }) } } func TestTemplates(t *testing.T) { now := time.Now().UTC().Truncate(time.Second) mustParse := func(t *testing.T, text string, msg *string, assertion assert.ErrorAssertionFunc) string { t.Helper() tmpl, err := template.New(t.Name()).Funcs(GetFuncMap(msg)).Parse(text) require.NoError(t, err) buf := new(bytes.Buffer) err = tmpl.Execute(buf, map[string]any{ "nbf": now.Unix(), "float64": float64(now.Unix()), "notBefore": now.Format(time.RFC3339), "notAfter": now.Add(time.Hour).Format(time.UnixDate), }) assertion(t, err) return buf.String() } type args struct { text string } tests := []struct { name string args args want string errorAssertion assert.ErrorAssertionFunc failAssertion assert.ValueAssertionFunc }{ {"toTime int64", args{`{{ .nbf | toTime }}`}, now.String(), assert.NoError, assert.Empty}, {"toTime int64 toJson", args{`{{ .nbf | toTime | toJson }}`}, strconv.Quote(now.Format(time.RFC3339)), assert.NoError, assert.Empty}, {"toTime float64 toJson", args{`{{ .float64 | toTime | toJson }}`}, strconv.Quote(now.Format(time.RFC3339)), assert.NoError, assert.Empty}, {"toTime dateModify", args{`{{ .nbf | toTime | dateModify "1h" }}`}, now.Add(time.Hour).String(), assert.NoError, assert.Empty}, {"formatTime", args{`{{ .nbf | formatTime }}`}, now.Format(time.RFC3339), assert.NoError, assert.Empty}, {"formatTime float64", args{`{{ .float64 | formatTime }}`}, now.Format(time.RFC3339), assert.NoError, assert.Empty}, {"formatTime in sprig", args{`{{ dateInZone "2006-01-02T15:04:05Z07:00" .float64 "UTC" }}`}, now.UTC().Format(time.RFC3339), assert.NoError, assert.Empty}, {"parseTime", args{`{{ .notBefore | parseTime }}`}, now.String(), assert.NoError, assert.Empty}, {"parseTime toJson", args{`{{ .notBefore | parseTime | toJson }}`}, strconv.Quote(now.Format(time.RFC3339)), assert.NoError, assert.Empty}, {"parseTime time.UnixDate", args{`{{ .notAfter | parseTime "time.UnixDate" }}`}, now.Add(time.Hour).String(), assert.NoError, assert.Empty}, {"parseTime time.UnixDate toJson", args{`{{ .notAfter | parseTime "time.UnixDate" | toJson }}`}, strconv.Quote(now.Add(time.Hour).Format(time.RFC3339)), assert.NoError, assert.Empty}, {"parseTime time.UnixDate America/Los_Angeles", args{`{{ parseTime "time.UnixDate" .notAfter "America/Los_Angeles" }}`}, now.Add(time.Hour).String(), assert.NoError, assert.Empty}, {"parseTime dateModify", args{`{{ .notBefore | parseTime | dateModify "1h" }}`}, now.Add(time.Hour).String(), assert.NoError, assert.Empty}, {"parseTime in sprig ", args{`{{ toDate "Mon Jan _2 15:04:05 MST 2006" .notAfter }}`}, now.Add(time.Hour).String(), assert.NoError, assert.Empty}, {"toTimeLayout", args{`{{ toTimeLayout "time.RFC3339" }}`}, time.RFC3339, assert.NoError, assert.Empty}, {"toTimeLayout short", args{`{{ toTimeLayout "RFC3339" }}`}, time.RFC3339, assert.NoError, assert.Empty}, {"toTime toTimeLayout date", args{`{{ .nbf | toTime | date (toTimeLayout "time.RFC3339") }}`}, now.Local().Format(time.RFC3339), assert.NoError, assert.Empty}, {"toTime toTimeLayout date", args{`{{ .nbf | toTime | date (toTimeLayout "time.RFC3339") }}`}, now.Local().Format(time.RFC3339), assert.NoError, assert.Empty}, {"parseTime error", args{`{{ parseTime "time.UnixDate" .notAfter "America/FooBar" }}`}, "0001-01-01 00:00:00 +0000 UTC", assert.NoError, assert.Empty}, {"mustParseTime error", args{`{{ mustParseTime "time.UnixDate" .notAfter "America/FooBar" }}`}, "", assert.Error, assert.Empty}, {"fail", args{`{{ fail "error" }}`}, "", assert.Error, assert.NotEmpty}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var failMesage string got := mustParse(t, tt.args.text, &failMesage, tt.errorAssertion) tt.failAssertion(t, failMesage) assert.Equal(t, tt.want, got) }) } } crypto-0.57.0/internal/templates/validate.go000066400000000000000000000017631474156331600210760ustar00rootroot00000000000000package 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, funcMap template.FuncMap) error { if len(data) == 0 { return nil } // 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 } crypto-0.57.0/internal/templates/validate_test.go000066400000000000000000000140471474156331600221340ustar00rootroot00000000000000package 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) { var failMessage string err := ValidateTemplate(tt.data, GetFuncMap(&failMessage)) 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) }) } } crypto-0.57.0/internal/utils/000077500000000000000000000000001474156331600161115ustar00rootroot00000000000000crypto-0.57.0/internal/utils/io.go000066400000000000000000000036521474156331600170550ustar00rootroot00000000000000package utils import ( "bytes" "io" "os" "unicode" "github.com/pkg/errors" "go.step.sm/crypto/internal/utils/utfbom" ) func maybeUnwrap(err error) error { if wrapped := errors.Unwrap(err); wrapped != nil { return wrapped } return err } // stdinFilename is the name of the file that is used in many command // line utilities to denote input is to be read from STDIN. const stdinFilename = "-" // stdin points to STDIN through os.Stdin. var stdin = os.Stdin // ReadFile reads the file identified by filename and returns // the contents. If filename is equal to "-", it will read from // STDIN. func ReadFile(filename string) (b []byte, err error) { if filename == stdinFilename { filename = "/dev/stdin" b, err = io.ReadAll(stdin) } else { var contents []byte contents, err = os.ReadFile(filename) if err != nil { return nil, errors.Wrapf(maybeUnwrap(err), "error reading %q", filename) } b, err = io.ReadAll(utfbom.SkipOnly(bytes.NewReader(contents))) } if err != nil { return nil, errors.Wrapf(maybeUnwrap(err), "error reading %q", filename) } return } // 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 } crypto-0.57.0/internal/utils/io_test.go000066400000000000000000000103171474156331600201100ustar00rootroot00000000000000package utils import ( "io" "os" "path/filepath" "reflect" "testing" "github.com/pkg/errors" "github.com/stretchr/testify/require" ) 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) } }) } } // Set content to be read from mock STDIN func setStdinContent(t *testing.T, content string) (cleanup func()) { f, err := os.CreateTemp("" /* dir */, "utils-read-test") require.NoError(t, err) _, err = f.WriteString(content) require.NoError(t, err) _, err = f.Seek(0, io.SeekStart) require.NoError(t, err) old := stdin stdin = f return func() { stdin = old require.NoError(t, f.Close()) require.NoError(t, os.Remove(f.Name())) } } func TestReadFromStdin(t *testing.T) { cleanup := setStdinContent(t, "input on STDIN") t.Cleanup(func() { cleanup() }) b, err := ReadFile(stdinFilename) require.NoError(t, err) require.Equal(t, "input on STDIN", string(b)) } // Sets STDIN to a file that is already closed, and thus fails // to be read from. func setFailingStdin(t *testing.T) (cleanup func()) { f, err := os.CreateTemp("" /* dir */, "utils-read-test") require.NoError(t, err) err = f.Close() require.NoError(t, err) old := stdin stdin = f return func() { stdin = old require.NoError(t, os.Remove(f.Name())) } } func TestReadFromStdinFails(t *testing.T) { cleanup := setFailingStdin(t) t.Cleanup(func() { cleanup() }) b, err := ReadFile(stdinFilename) require.Error(t, err) require.Empty(t, b) } 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 TestReadPasswordFromStdin(t *testing.T) { cleanup := setStdinContent(t, "this-is-a-secret-testing-password") t.Cleanup(func() { cleanup() }) b, err := ReadPasswordFromFile(stdinFilename) require.NoError(t, err) require.Equal(t, "this-is-a-secret-testing-password", string(b)) } func TestWriteFile(t *testing.T) { tmpDir, err := os.MkdirTemp(os.TempDir(), "go-tests") require.NoError(t, 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 := errors.New("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) require.Equal(t, tt.wantErr, err) }) } } crypto-0.57.0/internal/utils/testdata/000077500000000000000000000000001474156331600177225ustar00rootroot00000000000000crypto-0.57.0/internal/utils/testdata/pass1.txt000066400000000000000000000000511474156331600215060ustar00rootroot00000000000000brandy-guidon-basin-ishmael-sedge-ductingcrypto-0.57.0/internal/utils/testdata/pass2.txt000066400000000000000000000000651474156331600215140ustar00rootroot00000000000000benumb-eyepiece-stale-revers-marital-mimesis crypto-0.57.0/internal/utils/utfbom/000077500000000000000000000000001474156331600174055ustar00rootroot00000000000000crypto-0.57.0/internal/utils/utfbom/LICENSE000066400000000000000000000261631474156331600204220ustar00rootroot00000000000000 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 (c) 2018-2020, Dmitrij Koniajev (dimchansky@gmail.com) 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. crypto-0.57.0/internal/utils/utfbom/README.md000066400000000000000000000037201474156331600206660ustar00rootroot00000000000000# utfbom [![Godoc](https://godoc.org/github.com/dimchansky/utfbom?status.png)](https://godoc.org/github.com/dimchansky/utfbom) [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Build Status](https://travis-ci.org/dimchansky/utfbom.svg?branch=master)](https://travis-ci.org/dimchansky/utfbom) [![Go Report Card](https://goreportcard.com/badge/github.com/dimchansky/utfbom)](https://goreportcard.com/report/github.com/dimchansky/utfbom) [![Coverage Status](https://coveralls.io/repos/github/dimchansky/utfbom/badge.svg?branch=master)](https://coveralls.io/github/dimchansky/utfbom?branch=master) The package utfbom implements the detection of the BOM (Unicode Byte Order Mark) and removing as necessary. It can also return the encoding detected by the BOM. ## Installation go get -u github.com/dimchansky/utfbom ## Example ```go package main import ( "bytes" "fmt" "io/ioutil" "github.com/dimchansky/utfbom" ) func main() { trySkip([]byte("\xEF\xBB\xBFhello")) trySkip([]byte("hello")) } func trySkip(byteData []byte) { fmt.Println("Input:", byteData) // just skip BOM output, err := ioutil.ReadAll(utfbom.SkipOnly(bytes.NewReader(byteData))) if err != nil { fmt.Println(err) return } fmt.Println("ReadAll with BOM skipping", output) // skip BOM and detect encoding sr, enc := utfbom.Skip(bytes.NewReader(byteData)) fmt.Printf("Detected encoding: %s\n", enc) output, err = ioutil.ReadAll(sr) if err != nil { fmt.Println(err) return } fmt.Println("ReadAll with BOM detection and skipping", output) fmt.Println() } ``` Output: ``` $ go run main.go Input: [239 187 191 104 101 108 108 111] ReadAll with BOM skipping [104 101 108 108 111] Detected encoding: UTF8 ReadAll with BOM detection and skipping [104 101 108 108 111] Input: [104 101 108 108 111] ReadAll with BOM skipping [104 101 108 108 111] Detected encoding: Unknown ReadAll with BOM detection and skipping [104 101 108 108 111] ``` crypto-0.57.0/internal/utils/utfbom/utfbom.go000066400000000000000000000113101474156331600212240ustar00rootroot00000000000000// Package utfbom implements the detection of the BOM (Unicode Byte Order Mark) and removing as necessary. // It wraps an io.Reader object, creating another object (Reader) that also implements the io.Reader // interface but provides automatic BOM checking and removing as necessary. // // This package was copied from https://github.com/dimchansky/utfbom. Only minor changes // were made to not depend on the io/ioutil package and to make our linters pass. package utfbom import ( "errors" "io" ) // Encoding is type alias for detected UTF encoding. type Encoding int // Constants to identify detected UTF encodings. const ( // Unknown encoding, returned when no BOM was detected Unknown Encoding = iota // UTF8, BOM bytes: EF BB BF UTF8 // UTF-16, big-endian, BOM bytes: FE FF UTF16BigEndian // UTF-16, little-endian, BOM bytes: FF FE UTF16LittleEndian // UTF-32, big-endian, BOM bytes: 00 00 FE FF UTF32BigEndian // UTF-32, little-endian, BOM bytes: FF FE 00 00 UTF32LittleEndian ) // String returns a user-friendly string representation of the encoding. Satisfies fmt.Stringer interface. func (e Encoding) String() string { switch e { case UTF8: return "UTF8" case UTF16BigEndian: return "UTF16BigEndian" case UTF16LittleEndian: return "UTF16LittleEndian" case UTF32BigEndian: return "UTF32BigEndian" case UTF32LittleEndian: return "UTF32LittleEndian" default: return "Unknown" } } const maxConsecutiveEmptyReads = 100 // Skip creates Reader which automatically detects BOM (Unicode Byte Order Mark) and removes it as necessary. // It also returns the encoding detected by the BOM. // If the detected encoding is not needed, you can call the SkipOnly function. func Skip(rd io.Reader) (*Reader, Encoding) { // Is it already a Reader? b, ok := rd.(*Reader) if ok { return b, Unknown } enc, left, err := detectUtf(rd) return &Reader{ rd: rd, buf: left, err: err, }, enc } // SkipOnly creates Reader which automatically detects BOM (Unicode Byte Order Mark) and removes it as necessary. func SkipOnly(rd io.Reader) *Reader { r, _ := Skip(rd) return r } // Reader implements automatic BOM (Unicode Byte Order Mark) checking and // removing as necessary for an io.Reader object. type Reader struct { rd io.Reader // reader provided by the client buf []byte // buffered data err error // last error } // Read is an implementation of io.Reader interface. // The bytes are taken from the underlying Reader, but it checks for BOMs, removing them as necessary. func (r *Reader) Read(p []byte) (n int, err error) { if len(p) == 0 { return 0, nil } if r.buf == nil { if r.err != nil { return 0, r.readErr() } return r.rd.Read(p) } // copy as much as we can n = copy(p, r.buf) r.buf = nilIfEmpty(r.buf[n:]) return n, nil } func (r *Reader) readErr() error { err := r.err r.err = nil return err } var errNegativeRead = errors.New("utfbom: reader returned negative count from Read") func detectUtf(rd io.Reader) (enc Encoding, buf []byte, err error) { buf, err = readBOM(rd) if len(buf) >= 4 { if isUTF32BigEndianBOM4(buf) { return UTF32BigEndian, nilIfEmpty(buf[4:]), err } if isUTF32LittleEndianBOM4(buf) { return UTF32LittleEndian, nilIfEmpty(buf[4:]), err } } if len(buf) > 2 && isUTF8BOM3(buf) { return UTF8, nilIfEmpty(buf[3:]), err } if (err != nil && !errors.Is(err, io.EOF)) || (len(buf) < 2) { return Unknown, nilIfEmpty(buf), err } if isUTF16BigEndianBOM2(buf) { return UTF16BigEndian, nilIfEmpty(buf[2:]), err } if isUTF16LittleEndianBOM2(buf) { return UTF16LittleEndian, nilIfEmpty(buf[2:]), err } return Unknown, nilIfEmpty(buf), err } func readBOM(rd io.Reader) (buf []byte, err error) { const maxBOMSize = 4 var bom [maxBOMSize]byte // used to read BOM // read as many bytes as possible for nEmpty, n := 0, 0; err == nil && len(buf) < maxBOMSize; buf = bom[:len(buf)+n] { //nolint:wastedassign // copied code if n, err = rd.Read(bom[len(buf):]); n < 0 { return nil, errNegativeRead } if n > 0 { nEmpty = 0 } else { nEmpty++ if nEmpty >= maxConsecutiveEmptyReads { err = io.ErrNoProgress } } } return } func isUTF32BigEndianBOM4(buf []byte) bool { return buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0xFE && buf[3] == 0xFF } func isUTF32LittleEndianBOM4(buf []byte) bool { return buf[0] == 0xFF && buf[1] == 0xFE && buf[2] == 0x00 && buf[3] == 0x00 } func isUTF8BOM3(buf []byte) bool { return buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF } func isUTF16BigEndianBOM2(buf []byte) bool { return buf[0] == 0xFE && buf[1] == 0xFF } func isUTF16LittleEndianBOM2(buf []byte) bool { return buf[0] == 0xFF && buf[1] == 0xFE } func nilIfEmpty(buf []byte) (res []byte) { if len(buf) > 0 { res = buf } return } crypto-0.57.0/internal/utils/utfbom/utfbom_test.go000066400000000000000000000157551474156331600223040ustar00rootroot00000000000000package utfbom import ( "errors" "io" "reflect" "testing" "testing/iotest" "time" ) var testCases = []struct { name string input []byte inputError error encoding Encoding output []byte }{ {"1", []byte{}, nil, Unknown, []byte{}}, {"2", []byte("hello"), nil, Unknown, []byte("hello")}, {"3", []byte("\xEF\xBB\xBF"), nil, UTF8, []byte{}}, {"4", []byte("\xEF\xBB\xBFhello"), nil, UTF8, []byte("hello")}, {"5", []byte("\xFE\xFF"), nil, UTF16BigEndian, []byte{}}, {"6", []byte("\xFF\xFE"), nil, UTF16LittleEndian, []byte{}}, {"7", []byte("\x00\x00\xFE\xFF"), nil, UTF32BigEndian, []byte{}}, {"8", []byte("\xFF\xFE\x00\x00"), nil, UTF32LittleEndian, []byte{}}, {"5", []byte("\xFE\xFF\x00\x68\x00\x65\x00\x6C\x00\x6C\x00\x6F"), nil, UTF16BigEndian, []byte{0x00, 0x68, 0x00, 0x65, 0x00, 0x6C, 0x00, 0x6C, 0x00, 0x6F}}, {"6", []byte("\xFF\xFE\x68\x00\x65\x00\x6C\x00\x6C\x00\x6F\x00"), nil, UTF16LittleEndian, []byte{0x68, 0x00, 0x65, 0x00, 0x6C, 0x00, 0x6C, 0x00, 0x6F, 0x00}}, {"7", []byte("\x00\x00\xFE\xFF\x00\x00\x00\x68\x00\x00\x00\x65\x00\x00\x00\x6C\x00\x00\x00\x6C\x00\x00\x00\x6F"), nil, UTF32BigEndian, []byte{0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x6F}}, {"8", []byte("\xFF\xFE\x00\x00\x68\x00\x00\x00\x65\x00\x00\x00\x6C\x00\x00\x00\x6C\x00\x00\x00\x6F\x00\x00\x00"), nil, UTF32LittleEndian, []byte{0x68, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x00, 0x00}}, {"9", []byte("\xEF"), nil, Unknown, []byte("\xEF")}, {"10", []byte("\xEF\xBB"), nil, Unknown, []byte("\xEF\xBB")}, {"11", []byte("\xEF\xBB\xBF"), io.ErrClosedPipe, UTF8, []byte{}}, {"12", []byte("\xFE\xFF"), io.ErrClosedPipe, Unknown, []byte("\xFE\xFF")}, {"13", []byte("\xFE"), io.ErrClosedPipe, Unknown, []byte("\xFE")}, {"14", []byte("\xFF\xFE"), io.ErrClosedPipe, Unknown, []byte("\xFF\xFE")}, {"15", []byte("\x00\x00\xFE\xFF"), io.ErrClosedPipe, UTF32BigEndian, []byte{}}, {"16", []byte("\x00\x00\xFE"), io.ErrClosedPipe, Unknown, []byte{0x00, 0x00, 0xFE}}, {"17", []byte("\x00\x00"), io.ErrClosedPipe, Unknown, []byte{0x00, 0x00}}, {"18", []byte("\x00"), io.ErrClosedPipe, Unknown, []byte{0x00}}, {"19", []byte("\xFF\xFE\x00\x00"), io.ErrClosedPipe, UTF32LittleEndian, []byte{}}, {"20", []byte("\xFF\xFE\x00"), io.ErrClosedPipe, Unknown, []byte{0xFF, 0xFE, 0x00}}, {"21", []byte("\xFF\xFE"), io.ErrClosedPipe, Unknown, []byte{0xFF, 0xFE}}, {"22", []byte("\xFF"), io.ErrClosedPipe, Unknown, []byte{0xFF}}, {"23", []byte("\x68\x65"), nil, Unknown, []byte{0x68, 0x65}}, } type sliceReader struct { input []byte inputError error } func (r *sliceReader) Read(p []byte) (n int, err error) { if len(p) == 0 { return } if err = r.getError(); err != nil { return } n = copy(p, r.input) r.input = r.input[n:] err = r.getError() return } func (r *sliceReader) getError() (err error) { if len(r.input) == 0 { if r.inputError == nil { err = io.EOF } else { err = r.inputError } } return } var readMakers = []struct { name string fn func(io.Reader) io.Reader }{ {"full", func(r io.Reader) io.Reader { return r }}, {"byte", iotest.OneByteReader}, } func TestSkip(t *testing.T) { for _, tc := range testCases { for _, readMaker := range readMakers { r := readMaker.fn(&sliceReader{tc.input, tc.inputError}) sr, enc := Skip(r) if enc != tc.encoding { t.Fatalf("test %v reader=%s: expected encoding %v, but got %v", tc.name, readMaker.name, tc.encoding, enc) } output, err := io.ReadAll(sr) if !reflect.DeepEqual(output, tc.output) { t.Fatalf("test %v reader=%s: expected to read %+#v, but got %+#v", tc.name, readMaker.name, tc.output, output) } if !errors.Is(err, tc.inputError) { t.Fatalf("test %v reader=%s: expected to get %+#v error, but got %+#v", tc.name, readMaker.name, tc.inputError, err) } } } } func TestSkipSkip(t *testing.T) { for _, tc := range testCases { for _, readMaker := range readMakers { r := readMaker.fn(&sliceReader{tc.input, tc.inputError}) sr0, _ := Skip(r) sr, enc := Skip(sr0) if enc != Unknown { t.Fatalf("test %v reader=%s: expected encoding %v, but got %v", tc.name, readMaker.name, Unknown, enc) } output, err := io.ReadAll(sr) if !reflect.DeepEqual(output, tc.output) { t.Fatalf("test %v reader=%s: expected to read %+#v, but got %+#v", tc.name, readMaker.name, tc.output, output) } if !errors.Is(err, tc.inputError) { t.Fatalf("test %v reader=%s: expected to get %+#v error, but got %+#v", tc.name, readMaker.name, tc.inputError, err) } } } } func TestSkipOnly(t *testing.T) { for _, tc := range testCases { for _, readMaker := range readMakers { r := readMaker.fn(&sliceReader{tc.input, tc.inputError}) sr := SkipOnly(r) output, err := io.ReadAll(sr) if !reflect.DeepEqual(output, tc.output) { t.Fatalf("test %v reader=%s: expected to read %+#v, but got %+#v", tc.name, readMaker.name, tc.output, output) } if !errors.Is(err, tc.inputError) { t.Fatalf("test %v reader=%s: expected to get %+#v error, but got %+#v", tc.name, readMaker.name, tc.inputError, err) } } } } type zeroReader struct{} func (zeroReader) Read(p []byte) (int, error) { return 0, nil } type readerEncoding struct { Rd *Reader Enc Encoding } func TestSkipZeroReader(t *testing.T) { var z zeroReader c := make(chan readerEncoding) go func() { r, enc := Skip(z) c <- readerEncoding{r, enc} }() select { case re := <-c: if re.Enc != Unknown { t.Error("Unknown encoding expected") } else { var b [1]byte n, err := re.Rd.Read(b[:]) if n != 0 { t.Error("unexpected bytes count:", n) } if !errors.Is(err, io.ErrNoProgress) { t.Error("unexpected error:", err) } } case <-time.After(time.Second): t.Error("test timed out (endless loop in Skip?)") } } func TestSkipOnlyZeroReader(t *testing.T) { var z zeroReader c := make(chan *Reader) go func() { r := SkipOnly(z) c <- r }() select { case r := <-c: var b [1]byte n, err := r.Read(b[:]) if n != 0 { t.Error("unexpected bytes count:", n) } if !errors.Is(err, io.ErrNoProgress) { t.Error("unexpected error:", err) } case <-time.After(time.Second): t.Error("test timed out (endless loop in Skip?)") } } func TestReader_ReadEmpty(t *testing.T) { for _, tc := range testCases { for _, readMaker := range readMakers { r := readMaker.fn(&sliceReader{tc.input, tc.inputError}) sr := SkipOnly(r) n, err := sr.Read(nil) if n != 0 { t.Fatalf("test %v reader=%s: expected to read zero bytes, but got %v", tc.name, readMaker.name, n) } if err != nil { t.Fatalf("test %v reader=%s: expected to get error, but got %+#v", tc.name, readMaker.name, err) } } } } func TestEncoding_String(t *testing.T) { for e := Unknown; e <= UTF32LittleEndian; e++ { s := e.String() if s == "" { t.Errorf("no string for %#v", e) } } s := Encoding(999).String() if s != "Unknown" { t.Errorf("wrong string '%s' for invalid encoding", s) } } crypto-0.57.0/jose/000077500000000000000000000000001474156331600140755ustar00rootroot00000000000000crypto-0.57.0/jose/encrypt.go000066400000000000000000000073241474156331600161160ustar00rootroot00000000000000package 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") } crypto-0.57.0/jose/encrypt_test.go000066400000000000000000000322611474156331600171530ustar00rootroot00000000000000package jose import ( "bytes" "crypto" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/json" "io" "reflect" "testing" "time" jose "github.com/go-jose/go-jose/v3" "github.com/pkg/errors" "github.com/smallstep/assert" "go.step.sm/crypto/randutil" ) 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) } }) } } func TestDecrypt_highP2C(t *testing.T) { data := []byte(`{ "protected":"eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMjU2R0NNIiwicDJjIjoyMDAwMDAwMDAwMCwicDJzIjoiM3V0aFJZdHBTY09UMjR4Q3cwbTlfQSJ9", "encrypted_key":"Lqn-BuAIole2T5ubPIPXl1QYj_48JqyeEfbOLq0EkyAX96irRPHA4g", "iv":"eGaXW9_umwZvLCSP", "ciphertext":"enFrF3NyvTN_a6Y4", "tag":"VQFg97XqcRo61punp7Z3ow" }`) timer := time.AfterFunc(time.Second, func() { t.Fatal("Decrypt() took to much time") }) _, err := Decrypt(data, WithPassword([]byte("password"))) assert.Error(t, err) if !timer.Stop() { <-timer.C } } crypto-0.57.0/jose/generate.go000066400000000000000000000125211474156331600162170ustar00rootroot00000000000000package 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) } case OpaqueSigner: sum, err = key.Public().Thumbprint(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 } crypto-0.57.0/jose/generate_test.go000066400000000000000000000355521474156331600172670ustar00rootroot00000000000000package jose import ( "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/json" "encoding/pem" "io" "math/big" "os" "reflect" "testing" jose "github.com/go-jose/go-jose/v3" "github.com/smallstep/assert" "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x25519" ) func TestThumbprint(t *testing.T) { parse := func(filename string) *JSONWebKey { jwk, err := ReadKey(filename) if err != nil { t.Fatal(err) } return jwk } x25519Key := x25519.PrivateKey{ 0xe9, 0xc5, 0xce, 0xce, 0xf8, 0x2b, 0xd5, 0xee, 0x98, 0x72, 0xea, 0xd4, 0xc9, 0x66, 0xab, 0x3d, 0xc3, 0x19, 0xe5, 0xbd, 0xce, 0xe3, 0xcb, 0x03, 0x6c, 0xdc, 0xaf, 0x04, 0x61, 0xe1, 0xe3, 0x5d, } jwk := parse("testdata/p256.priv.json") s, ok := jwk.Key.(crypto.Signer) if !ok { t.Fatalf("type %T does not implement crypto.Signer", jwk.Key) } opaqueKey := &JSONWebKey{ Key: NewOpaqueSigner(s), } 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}, {"x25519", args{&JSONWebKey{Key: x25519Key}}, "ir3T3GeoaZWjfhX-K_7c6E01N4mwLPehV_FyBAk28Vk", false}, {"x25519 pub", args{&JSONWebKey{Key: x25519Key.Public()}}, "ir3T3GeoaZWjfhX-K_7c6E01N4mwLPehV_FyBAk28Vk", false}, {"opaque", args{opaqueKey}, "V93A-Yh7Bhw1W2E0igFciviJzX4PXPswoVgriehm9Co", false}, {"fail oct", args{parse("testdata/oct.json")}, "", true}, {"fail x25519", args{&JSONWebKey{Key: x25519.PrivateKey("foobar")}}, "", 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)) } } }) } } crypto-0.57.0/jose/options.go000066400000000000000000000053421474156331600161230ustar00rootroot00000000000000package 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 } } crypto-0.57.0/jose/parse.go000066400000000000000000000264021474156331600155420ustar00rootroot00000000000000package 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), KeyUsages: []x509.ExtKeyUsage{ x509.ExtKeyUsageClientAuth, }, }) 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(_ *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 "" } } crypto-0.57.0/jose/parse_test.go000066400000000000000000000670511474156331600166060ustar00rootroot00000000000000package 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) } }) } } crypto-0.57.0/jose/testdata/000077500000000000000000000000001474156331600157065ustar00rootroot00000000000000crypto-0.57.0/jose/testdata/bad-rsa.crt000066400000000000000000000022301474156331600177260ustar00rootroot00000000000000-----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----- crypto-0.57.0/jose/testdata/bad-rsa.key000066400000000000000000000032501474156331600177310ustar00rootroot00000000000000-----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----- crypto-0.57.0/jose/testdata/empty.json000066400000000000000000000000201474156331600177270ustar00rootroot00000000000000{ "keys": [] }crypto-0.57.0/jose/testdata/empty.jwks.json000066400000000000000000000003521474156331600207140ustar00rootroot00000000000000{ "keys": [ { "use": "sig", "kty": "OKP", "kid": "qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM", "crv": "Ed25519", "alg": "EdDSA", "x": "kqyKV4yXV8viKLPp4HazFzvBkZi0wMKZ3iLXwtWqgvA" } ] }crypto-0.57.0/jose/testdata/host-key000066400000000000000000000050761474156331600174040ustar00rootroot00000000000000-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn NhAAAAAwEAAQAAAYEAwwOoqJR4tRe3f7WXKEpuUT4855/k3eOPlbVGFlgS9EkNa1bsszV6 tU+4Rw0ccTX/dpghx3h4UWWDypoetxmYiUlpFZqp/AcuKskAeRKbhPB/uXMwTbTaZNRhvq u7uXjgUiIfReGYM2/4e/gcBh1OLK0YVLjhv7uA5hrBDjVZKZw9WpY4CM/b1kWrjOv2ydtD P04jS+Ni+mBHJQ6DaFfZtZJBTOjom/ulxYKPSIXeRONWO7gDxXuZJk2hx8PnZo0Bk1WKhd azoqKXu3codRCJMw1iKDhzpaZymE2jyApKCrlV4/zXxxNL7L9JbZf4WpstA+BkMfJ9F/1o uPMjZ2GZHul3Oo6VyLd2CNjPL1LWCOZkecJu50AiDB9SDW18SmpKwCKrWhKufjiLLTTtWo F+RcDMI30IWv36VJ4W8PgbcXP0LjZ7yP8DPhFW5uOGGbhxFKDfR/vS4oALokc6arS2z6wY bLMTU3L6XG4jmmBZKY/yTW75+2QpkG/7BfxpF8q1AAAFmP79Gk7+/RpOAAAAB3NzaC1yc2 EAAAGBAMMDqKiUeLUXt3+1lyhKblE+POef5N3jj5W1RhZYEvRJDWtW7LM1erVPuEcNHHE1 /3aYIcd4eFFlg8qaHrcZmIlJaRWaqfwHLirJAHkSm4Twf7lzME202mTUYb6ru7l44FIiH0 XhmDNv+Hv4HAYdTiytGFS44b+7gOYawQ41WSmcPVqWOAjP29ZFq4zr9snbQz9OI0vjYvpg RyUOg2hX2bWSQUzo6Jv7pcWCj0iF3kTjVju4A8V7mSZNocfD52aNAZNVioXWs6Kil7t3KH UQiTMNYig4c6WmcphNo8gKSgq5VeP818cTS+y/SW2X+FqbLQPgZDHyfRf9aLjzI2dhmR7p dzqOlci3dgjYzy9S1gjmZHnCbudAIgwfUg1tfEpqSsAiq1oSrn44iy007VqBfkXAzCN9CF r9+lSeFvD4G3Fz9C42e8j/Az4RVubjhhm4cRSg30f70uKAC6JHOmq0ts+sGGyzE1Ny+lxu I5pgWSmP8k1u+ftkKZBv+wX8aRfKtQAAAAMBAAEAAAGAaEWyjsEkcaAGy9BqzG5P4jznD+ pc04/znFba7OOW7uuIs1Y9Je14eS3xGHyYMqhuuhjKJv/xRYSEHXae6cRT3URVXene7hVg yBvFlvcuFL0Lbjt7HnGPPM6anPryW0+YkhIQqqrfWNGMCa02Psa4oAbFtbdNbdlyKJcxls lLrYYPADrGJ2AM6dtCyQz6XiBRKq47DT6+E6YbJGNIPCiNDhrvs5HtnknF73/J4TtDB46O 80uHKLbXwrVLxaaoOhs2RjKngZ/fyHgKbhiTRw037B7yQMjC1WK71cSkRd9HPn/JENqzQE CN9YYZUVurkZbcxyBW9Q0ZBZ47VLnJ/EICobqTpVRwWKVK8u9e7zznTNBCfJ65VYP05d9+ MFwTrxSrp98FbOXsnWI6+wx1lkIXsX6tulFQsykl0Zx/3A1C6Vq0BF4Tf6O37Xmya3afNA oMixts8qOS0Ho3LiTBhLp7oVeQazRQc/X0exYo5reMbIqa1m1Km93lT04iCPLMXhbBAAAA wDAM3hDsA1kN2hbpD5BgZ/xQeXRcWaRHKqvOFMliBk01O1m00DOWs6EiKwmjm+M/lmbyh+ Xo25am8f6xrg3xhOftwBlf7p0R50duvjAvOiEQAT5/Xcm0yhKn1YLaEW+CD0gvtKQ4AQ5i rCIAwH/lVlIqyYgWfEwrxIcVw4mgUFo8labn5sKNHo+m9Qbt09UjgNEiBZiSYX4CP3TYY6 2K+8Vr3Uok0RZjpR+1iH2Or7b0USqsSnt2BbIeXPi9LMjZhQAAAMEA+Bg1LFPXuo1lwrlD DpdZkhPYoqZWMMidMtu5LODZ7NGCwbqio+RCNpz2CC5GWC3uENQfzR6d2KSLBvWDsCq0Xl wjPV5zYQihFEvv1WU7TD7DTfFNO9wNbsB3nFTTIoTB3HXNAEr+p2DGP/3vJxmnPBLwGw9B dUql6uS1yefNwLwxq/UK0wiNQQvanwYBmoBNTi9kulfWNj7NOpPkPS57151GfsI8y4ZMjk HkgK/zASfwUSWx8fCisKYVjKNvjSHJAAAAwQDJOnUJ+SPC2ut47oL37DJwX0GqqSjSpLIM apxQ/+BCFu8Ufyn/Mruk5IPSMw7S/wR9oB2bIu6N+X4pS9igFc7Dsv/56h7j89QA/x37/r XE+MFS2UjpuhA/TULadF+6F5J4Yaf0U2L2BEHXdovdO2h+Y69YV/SJ5oUIcQeNvetePcbe o/qkhsq5QhfDTm1CtLHQs1DPH5myg/XUVYWJNWZDRExVymnaH40kX49/nMSh1MMiyQ/dFq aHC5epjtJKN40AAAAdbWFyaWFub0BvdmVybG9vay5hdHRsb2NhbC5uZXQBAgMEBQY= -----END OPENSSH PRIVATE KEY----- crypto-0.57.0/jose/testdata/host-key-cert.pub000066400000000000000000000035101474156331600211130ustar00rootroot00000000000000ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg492YHGHNhvf6aTEfaGk1zG2eTwTiOc3cXytpcBkVFQIAAAADAQABAAABgQDDA6iolHi1F7d/tZcoSm5RPjznn+Td44+VtUYWWBL0SQ1rVuyzNXq1T7hHDRxxNf92mCHHeHhRZYPKmh63GZiJSWkVmqn8By4qyQB5EpuE8H+5czBNtNpk1GG+q7u5eOBSIh9F4Zgzb/h7+BwGHU4srRhUuOG/u4DmGsEONVkpnD1aljgIz9vWRauM6/bJ20M/TiNL42L6YEclDoNoV9m1kkFM6Oib+6XFgo9Ihd5E41Y7uAPFe5kmTaHHw+dmjQGTVYqF1rOiope7dyh1EIkzDWIoOHOlpnKYTaPICkoKuVXj/NfHE0vsv0ltl/hamy0D4GQx8n0X/Wi48yNnYZke6Xc6jpXIt3YI2M8vUtYI5mR5wm7nQCIMH1INbXxKakrAIqtaEq5+OIstNO1agX5FwMwjfQha/fpUnhbw+Btxc/QuNnvI/wM+EVbm44YZuHEUoN9H+9LigAuiRzpqtLbPrBhssxNTcvpcbiOaYFkpj/JNbvn7ZCmQb/sF/GkXyrUAAAAASZYC0gAAAAIAAAAJbG9jYWxob3N0AAAADQAAAAlsb2NhbGhvc3QAAAAAAAAAAP//////////AAAAAAAAAAAAAAAAAAABlwAAAAdzc2gtcnNhAAAAAwEAAQAAAYEAsDam+5VgtC930iv802TAQBJQS4UmbrWl0fAKLn8QPLZsvyVOYtt/bBL+5GA5kfx8RDAaTjmlS/Ma8UatYURUyYo+h8PsIg1vZffuY6ArUtYeGbkW7K/4dX2Xe0glwzTYFRbDIKr6cpo4YAetiAbmmfq/h5iVK9UWfCKZzRWsfw+ygPfH97LBrjIDwgzqylnTWVmlFfci9NSV1HW4mqbrZlBcX4NjYAHyqXkGMDY7D2BgnEe59xnfUemyxAUYYq76zAs0TBTRUhGiOdlvGWAAq+hClzEWP7+251+QIwKOw6lzq/e7qFqLJzIW1lSoXE/VGhKkAKzQD5o498oqktPoI6lahz2hfqjb9rbo3+4whG5gc/KDvPSpNf1OnLAP+J0zIPESpWiVO8ppHtBQ33BobPlo4vTajpSIIrVgmSf1T1Y+JFfhBD3eCbpmJOFjarWkfN6Vk0RKfNxrAezPq1kn8V82MjhBTx6hXbSLyqntsMlTtycyYrVVITH4mGxUEtfTAAABlAAAAAxyc2Etc2hhMi01MTIAAAGAlsGPh0gScTejMNWBsEajHLdb3SrfI848lITbptqDMfjG2xEWQyp20xmt0u6+nx/UKsDtHZ9T4OM5htKvZjSLxSEE3ABQ7MgjxivTd65aRRP3cqTuwmfOTzLacHOX4nSATJ5/vRNFIRne6O8eZYMvIyRlAFWExJe1gtiQVXbTKD2WlQjSo+/JUYj/t6G9ccCT4shxWqkm019uvyNnujN6HT7f8d/AHos5jm23xHYRkhYeQLJ9yX5wR2/GEQaTGet99N27H55UXQTYsmd4JqgTuG+UAL9nxYfMp4rMEJajAhZ9Kdye2Eo+2/0WfbOYE3YxYWMOflymZevLXHjT69zMwhUtMZ8QSem9qNxJ6JEmQVA7aGEyvHNw1OV9Jd8LOtZmJDuFdAqmqN5yZKcyxssLd0Pe1H8W1XYjCLvOFov8w8F6/mL3DTsUAIfn2CpIbu3lV+1JOAJMRcsz9drDJY7++8f2qrG9uipS01n9MjjKZfY29xFVOOmB8M3uMh7kCbgl mariano@overlook.attlocal.net crypto-0.57.0/jose/testdata/host-key.pub000066400000000000000000000011071474156331600201600ustar00rootroot00000000000000ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDDA6iolHi1F7d/tZcoSm5RPjznn+Td44+VtUYWWBL0SQ1rVuyzNXq1T7hHDRxxNf92mCHHeHhRZYPKmh63GZiJSWkVmqn8By4qyQB5EpuE8H+5czBNtNpk1GG+q7u5eOBSIh9F4Zgzb/h7+BwGHU4srRhUuOG/u4DmGsEONVkpnD1aljgIz9vWRauM6/bJ20M/TiNL42L6YEclDoNoV9m1kkFM6Oib+6XFgo9Ihd5E41Y7uAPFe5kmTaHHw+dmjQGTVYqF1rOiope7dyh1EIkzDWIoOHOlpnKYTaPICkoKuVXj/NfHE0vsv0ltl/hamy0D4GQx8n0X/Wi48yNnYZke6Xc6jpXIt3YI2M8vUtYI5mR5wm7nQCIMH1INbXxKakrAIqtaEq5+OIstNO1agX5FwMwjfQha/fpUnhbw+Btxc/QuNnvI/wM+EVbm44YZuHEUoN9H+9LigAuiRzpqtLbPrBhssxNTcvpcbiOaYFkpj/JNbvn7ZCmQb/sF/GkXyrU= mariano@overlook.attlocal.net crypto-0.57.0/jose/testdata/invalid.crt000066400000000000000000000000401474156331600200400ustar00rootroot00000000000000this is not a valid certificate crypto-0.57.0/jose/testdata/jwks.json000066400000000000000000000014671474156331600175670ustar00rootroot00000000000000{ "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" } ] }crypto-0.57.0/jose/testdata/oct.enc.json000066400000000000000000000006141474156331600201330ustar00rootroot00000000000000{ "protected": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6ImpFUURBanFfVWx2WXZPNDZ0R2JxN1EifQ", "encrypted_key": "RAlAOaVm7ycTK2lQz90ltiJfrBpLq8Ze", "iv": "3PLGXbsdIyvMRAzi", "ciphertext": "HiLoDqUWM7TLOeEH2ihUpFMELS0OM1_0ZHb2dIA5W9HfEIsz7WAbPyTDA7iCCAJNr6NkEm8yYCKzQyhmcYhw-lU9fJ1HqfoiyPh1Bl56uiNRHnD_MeTEqKE", "tag": "j1T3WboWkkygIW53QxPG0g" }crypto-0.57.0/jose/testdata/oct.json000066400000000000000000000001521474156331600173640ustar00rootroot00000000000000{ "use": "sig", "kty": "oct", "alg": "HS256", "k": "TUE5VnJDa3gzbDRtc3lWa1liRW1pUDVZNHBBRnNUNXo" }crypto-0.57.0/jose/testdata/oct.txt000066400000000000000000000000261474156331600172320ustar00rootroot00000000000000a true random passwordcrypto-0.57.0/jose/testdata/okp.enc.priv.json000066400000000000000000000010771474156331600211220ustar00rootroot00000000000000{ "protected": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMjU2R0NNIiwicDJjIjoxMDAwMDAsInAycyI6ImxUMTJjQ29FUEdYWXV2V0JqNHFZZ1EifQ", "encrypted_key": "_029j7ZniKPgkRbVzwjgPPUtCdQacFMLB-k9p-INRksow1a1-wHGTA", "iv": "WyrGXSosdyfBNWMW", "ciphertext": "5DNleiF85yWEDZFXQj-XSZ6JlepRei3k8V15oMecxXB_XnT7CL5ufZ9e884P3RGinA0gl_x3ViYFdFJthqJ79aHw7VtRVfe3Ar3USUcttPHxZmtrXd2yGWn1Osw3jyADeuXXaSED2EWNHiXUsjC3IGoGMWbMtKW7KuckZudkanSzYFRvuCPdLrDrvnugoMZX-he-NM1181OiOvZjtA-3TPKVLk7tuSnTOcqQAvpKoAy18NS6orslnVfFMz6tyYq3RvD8lbc-RN3o6op4Cphz", "tag": "rCmSF2Ts2Q61KubS_Bmceg" }crypto-0.57.0/jose/testdata/okp.priv.json000066400000000000000000000003451474156331600203530ustar00rootroot00000000000000{ "use": "sig", "kty": "OKP", "kid": "qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM", "crv": "Ed25519", "alg": "EdDSA", "x": "L4WYxHsMVaspyhWuSp84v2meEYMEUdYnrn-w-jqP6iw", "d": "kqyKV4yXV8viKLPp4HazFzvBkZi0wMKZ3iLXwtWqgvA" }crypto-0.57.0/jose/testdata/okp.pub.json000066400000000000000000000002661474156331600201630ustar00rootroot00000000000000{ "use": "sig", "kty": "OKP", "kid": "qiCJG7r2L80rmWRrZMPfpanQHmZRcncOG7A7MBWn9qM", "crv": "Ed25519", "alg": "EdDSA", "x": "L4WYxHsMVaspyhWuSp84v2meEYMEUdYnrn-w-jqP6iw" }crypto-0.57.0/jose/testdata/p256.enc.priv.json000066400000000000000000000011501474156331600210150ustar00rootroot00000000000000{ "protected": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6InFMYmNET3Yxczc3TlRGcnlKVUUtbmcifQ", "encrypted_key": "hY7Q7izJs6V1XvZI1l0RXJZ72XGg4anK", "iv": "gZb91xunOTgPqVF2", "ciphertext": "N-J28rcs385tnLyM32ynQpeRSn3nkcaaSoO9o0H7dxrsLJ7nV53DKGprDjAWhVWonbf3BxSIj1K5CUgMP8GPqUvJnZldgYILzvTRbGSPjYrGd5m2kUCAxIuRFbHNA35Lv02rfbNH9jvfdRvKhWvgunHS-a8vJCBCyk-Bd16Cp05G7tXp_EeVQXpUvNtQLd0v9NhWQ-tBeU86E3peCaJ97qipu7Xf23Axbng5azFzCGbSbmj4e0HN58HmHcpMDI8TgXrQ5-vDsRIE8U8YKJGqw5OVlRRM_gusQyZTBRHDFb5xkyFbD1SMpCRDdFTT0qS0k5vcH8ijpjSIs3K-sFo", "tag": "WG-RibO1NdWRaqHRHUwl3A" }crypto-0.57.0/jose/testdata/p256.priv.json000066400000000000000000000004271474156331600202570ustar00rootroot00000000000000{ "use": "sig", "kty": "EC", "kid": "V93A-Yh7Bhw1W2E0igFciviJzX4PXPswoVgriehm9Co", "crv": "P-256", "alg": "ES256", "x": "JtPSvIKayHsCHobDnNWtOdoroh-MDwKQSYMW6Mo4cfU", "y": "tP7xR5rpu6azzZsozdmzouyVByuTUDYSSAELTOAtu7g", "d": "lgzkahW28vY8qXRFQd_Uphvl0Rfs9GyOj_ICkwy4V4s" }crypto-0.57.0/jose/testdata/p256.pub.json000066400000000000000000000003511474156331600200610ustar00rootroot00000000000000{ "use": "sig", "kty": "EC", "kid": "V93A-Yh7Bhw1W2E0igFciviJzX4PXPswoVgriehm9Co", "crv": "P-256", "alg": "ES256", "x": "JtPSvIKayHsCHobDnNWtOdoroh-MDwKQSYMW6Mo4cfU", "y": "tP7xR5rpu6azzZsozdmzouyVByuTUDYSSAELTOAtu7g" }crypto-0.57.0/jose/testdata/passphrase.txt000066400000000000000000000000421474156331600206140ustar00rootroot00000000000000Supercalifragilisticexpialidociouscrypto-0.57.0/jose/testdata/rsa.enc.priv.json000066400000000000000000000034331474156331600211140ustar00rootroot00000000000000{ "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" }crypto-0.57.0/jose/testdata/rsa.priv.json000066400000000000000000000022401474156331600203430ustar00rootroot00000000000000{ "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" }crypto-0.57.0/jose/testdata/rsa.pub.json000066400000000000000000000007341474156331600201570ustar00rootroot00000000000000{ "use": "sig", "kty": "RSA", "kid": "CIsktcixZ5GyfkoWFyEV0tp5foASmBV4D-W7clYrCu8", "alg": "RS256", "n": "u1pASewznHZwedqjq85vEKRALbEBy57G4VWTrCY0wdFDThAtv3LwZaG1r9b79yDuqroz7PTT2vVQmzXwDQx78gix48XORLToUZrhcWcEhXxsYyTSJuD8groxHPNS_wTxrIH4ZKtFgJhdaOdSs9iFJpFEoq7DaVH89AWOEtAURek-KbkPci50IiQm-3Zwl9CBvDRs8528fGCthBFerm0kCXLw42oIeLJFC_iEHnR1NFzDuVZq6xjK1qp7vLoKMNUkkxmBFMduYOZth9Mf72i-l0VOZVt3gbHirR6RwXXH_K-NS6amGyB82w16g657p7rE3NXyEMMGXjOuObukMTd1jQ", "e": "AQAB" }crypto-0.57.0/jose/testdata/rsa2048.crt000066400000000000000000000021331474156331600175220ustar00rootroot00000000000000-----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----- crypto-0.57.0/jose/testdata/rsa2048.key000066400000000000000000000032171474156331600175260ustar00rootroot00000000000000-----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----- crypto-0.57.0/jose/testdata/ssh-ca000066400000000000000000000050761474156331600170170ustar00rootroot00000000000000-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn NhAAAAAwEAAQAAAYEAsDam+5VgtC930iv802TAQBJQS4UmbrWl0fAKLn8QPLZsvyVOYtt/ bBL+5GA5kfx8RDAaTjmlS/Ma8UatYURUyYo+h8PsIg1vZffuY6ArUtYeGbkW7K/4dX2Xe0 glwzTYFRbDIKr6cpo4YAetiAbmmfq/h5iVK9UWfCKZzRWsfw+ygPfH97LBrjIDwgzqylnT WVmlFfci9NSV1HW4mqbrZlBcX4NjYAHyqXkGMDY7D2BgnEe59xnfUemyxAUYYq76zAs0TB TRUhGiOdlvGWAAq+hClzEWP7+251+QIwKOw6lzq/e7qFqLJzIW1lSoXE/VGhKkAKzQD5o4 98oqktPoI6lahz2hfqjb9rbo3+4whG5gc/KDvPSpNf1OnLAP+J0zIPESpWiVO8ppHtBQ33 BobPlo4vTajpSIIrVgmSf1T1Y+JFfhBD3eCbpmJOFjarWkfN6Vk0RKfNxrAezPq1kn8V82 MjhBTx6hXbSLyqntsMlTtycyYrVVITH4mGxUEtfTAAAFmCP+zUkj/s1JAAAAB3NzaC1yc2 EAAAGBALA2pvuVYLQvd9Ir/NNkwEASUEuFJm61pdHwCi5/EDy2bL8lTmLbf2wS/uRgOZH8 fEQwGk45pUvzGvFGrWFEVMmKPofD7CINb2X37mOgK1LWHhm5Fuyv+HV9l3tIJcM02BUWwy Cq+nKaOGAHrYgG5pn6v4eYlSvVFnwimc0VrH8PsoD3x/eywa4yA8IM6spZ01lZpRX3IvTU ldR1uJqm62ZQXF+DY2AB8ql5BjA2Ow9gYJxHufcZ31HpssQFGGKu+swLNEwU0VIRojnZbx lgAKvoQpcxFj+/tudfkCMCjsOpc6v3u6haiycyFtZUqFxP1RoSpACs0A+aOPfKKpLT6COp Woc9oX6o2/a26N/uMIRuYHPyg7z0qTX9TpywD/idMyDxEqVolTvKaR7QUN9waGz5aOL02o 6UiCK1YJkn9U9WPiRX4QQ93gm6ZiThY2q1pHzelZNESnzcawHsz6tZJ/FfNjI4QU8eoV20 i8qp7bDJU7cnMmK1VSEx+JhsVBLX0wAAAAMBAAEAAAGAPWG8RxTgy8NhPL//4O9OnMMEX1 oxag0yhu/TzCCM2mk9dlQbewo018PCMHFofHb0HpiXATrJXqx/bR6OlLxFwLK8V3SjE7WU YXO9Ac0wajmV4KX921Ls/JhUJSibpuUIhEgajo2gkj0weBsUkra8q1SSVTyMcelXXMR8yN jM7rHcH1qwuJL3LqPTdUwkmXoyygQVIpKlBZ3r10dul8ifZVOG6eqNukrTab9zrWWmqxOK c8X0fKPxi8oZcHjJ5s5GkJAzrsXsSew/OaY51Hf51R1Rr5kyrU2Uwdiv0dc1ZwFmW3iS8n 3da+NtXageVbrbIb5Z5No1w1AUoKsiGUWBTgO3Hbzx0x/Kc9FoHiYWZN7mR5qaXvSkLWnt pBpBrJRneXKuGvO/97E5inwlBMQG1FXIlh5g37grPYvnKknMwGtjOUKnJixKZc9ZEmWgdt X3o9/BZUB/5GG+AsEku2Hamp8bJwd+jWVgMAA9H4HbWZp+0JVOh6klU7/if0NUd0MZAAAA wDDojNPHsBKbGqRi6hWXSfuRvvscQXj4eOmHRIly0N8UMhEQaWaH2LOGC6Yb7R3Tnb2zOS +0n71V4Lq9nTIfWjJrEu6MPSYZtevh4xipBuUgptBrMtTt2txI4+/neH0Saqn+c5Su6R9A Z+H7xgHk2S/LnvUmPoJpZD+9VyYN6aJ4pqBkC1eJ1I3eFOh/1qWOTw76Efumf0i12uGhxH s2bqLxyS1aOECKus3i3GwXjknZkEOA5b52o54n0VbrdQlKdwAAAMEA4BZwwrhLNl7eXtKf lB9y8IDbQI5tic5gHaeq7oOQ69u6n1T+HPtZBvFdTYpAHNvig/R5ZPSvRbRP+xWSZomPK6 AffahP3kNCAKWunPT4hGt1gZ7aivhexe8RtfBhgf73FhXiotKpqHtTkNE8o8drIaO3C9Jv ccXSnb3SujIA0qF4UOzZif4V44D/SIXgOO+6jejqU8Zq1UZiLCKX9uR3mv+V5PWY1K3TAi s1V9xk0uJt6dIjihgxqRdDNUw/vhrHAAAAwQDJTt0uzZprCouKfsVDaChNWZRLpawl0erR DvOtGakwA30uJ5Ax7sGFC1Jdyp7Q4fTjxJ32YbgDH7fLg15RAkUaeTLye25bBXheeCvKIZ f1AeJoQKSAaixiVCfGMjuogehpV429Pbabj2SMSMJibP0n2O5FzcySFLqAY+H+qZ9IPDS2 QJpQJcgl734wN7sUL61V9xntnvEJkLE9Dl6iIxjpesFB9fi5u3BKu8DqSUT2SFGLH+ReXV InRdhlIm/2rpUAAAAdbWFyaWFub0BvdmVybG9vay5hdHRsb2NhbC5uZXQBAgMEBQY= -----END OPENSSH PRIVATE KEY----- crypto-0.57.0/jose/testdata/ssh-ca.pub000066400000000000000000000011071474156331600175730ustar00rootroot00000000000000ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCwNqb7lWC0L3fSK/zTZMBAElBLhSZutaXR8AoufxA8tmy/JU5i239sEv7kYDmR/HxEMBpOOaVL8xrxRq1hRFTJij6Hw+wiDW9l9+5joCtS1h4ZuRbsr/h1fZd7SCXDNNgVFsMgqvpymjhgB62IBuaZ+r+HmJUr1RZ8IpnNFax/D7KA98f3ssGuMgPCDOrKWdNZWaUV9yL01JXUdbiaputmUFxfg2NgAfKpeQYwNjsPYGCcR7n3Gd9R6bLEBRhirvrMCzRMFNFSEaI52W8ZYACr6EKXMRY/v7bnX5AjAo7DqXOr97uoWosnMhbWVKhcT9UaEqQArNAPmjj3yiqS0+gjqVqHPaF+qNv2tujf7jCEbmBz8oO89Kk1/U6csA/4nTMg8RKlaJU7ymke0FDfcGhs+Wji9NqOlIgitWCZJ/VPVj4kV+EEPd4JumYk4WNqtaR83pWTREp83GsB7M+rWSfxXzYyOEFPHqFdtIvKqe2wyVO3JzJitVUhMfiYbFQS19M= mariano@overlook.attlocal.net crypto-0.57.0/jose/types.go000066400000000000000000000252161474156331600155760ustar00rootroot00000000000000// Package jose is a wrapper for github.com/go-jose/go-jose/v3 and implements // utilities to parse and generate JWT, JWK and JWKSets. package jose import ( "crypto" "errors" "strings" "time" jose "github.com/go-jose/go-jose/v3" "github.com/go-jose/go-jose/v3/cryptosigner" "github.com/go-jose/go-jose/v3/jwt" "go.step.sm/crypto/x25519" ) // 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. // // 600k is the current OWASP recommendation (Dec 2022) // https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 // // Nist recommends at least 10k (800-63B), 1Password increased in 2023 the // number of iterations from 100k to 650k. const PBKDF2Iterations = 600000 // 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 "go-jose/go-jose" from all errors. func TrimPrefix(err error) error { if err == nil { return nil } return errors.New(strings.TrimPrefix(err.Error(), "go-jose/go-jose: ")) } crypto-0.57.0/jose/types_test.go000066400000000000000000000122611474156331600166310ustar00rootroot00000000000000package 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("go-jose/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) } } }) } } crypto-0.57.0/jose/validate.go000066400000000000000000000136431474156331600162240ustar00rootroot00000000000000package 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 = validateKeyPair(pubkey, key); err != nil { return "", errors.Wrap(err, "error verifying ssh key pair") } return base64.StdEncoding.EncodeToString(cert.Marshal()), nil } func validateKeyPair(pub crypto.PublicKey, priv crypto.PrivateKey) error { switch key := priv.(type) { case *JSONWebKey: return keyutil.VerifyPair(pub, key.Key) case OpaqueSigner: if !keyutil.Equal(pub, key.Public().Key) { return errors.New("private key does not match public key") } return nil default: return keyutil.VerifyPair(pub, priv) } } func validateX5(certs []*x509.Certificate, key interface{}) error { if len(certs) == 0 { return errors.New("certs cannot be empty") } if err := validateKeyPair(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'" case OpaqueSigner: for _, alg := range k.Algs() { if jwk.Algorithm == string(alg) { return nil } } } 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) } crypto-0.57.0/jose/validate_test.go000066400000000000000000000256031474156331600172620ustar00rootroot00000000000000package jose import ( "bytes" "crypto" "crypto/ed25519" "crypto/rand" "crypto/sha1" "crypto/x509" "encoding/base64" "os" "testing" "github.com/pkg/errors" "github.com/smallstep/assert" "go.step.sm/crypto/keyutil" "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 TestValidateSSHPOP(t *testing.T) { key, err := pemutil.Read("testdata/host-key") if err != nil { t.Fatal(err) } b, err := os.ReadFile("testdata/host-key-cert.pub") if err != nil { t.Fatal(err) } fields := bytes.Fields(b) if len(fields) != 3 { t.Fatalf("unexpected number of fields, got = %d, want 3", len(fields)) } certBase64 := string(fields[1]) _, otherKey, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } type args struct { certFile string key interface{} } tests := []struct { name string args args want string wantErr bool }{ {"ok crypto.PrivateKey", args{"testdata/host-key-cert.pub", key}, certBase64, false}, {"ok JSONWebKey", args{"testdata/host-key-cert.pub", &JSONWebKey{Key: key}}, certBase64, false}, {"ok OpaqueSigner", args{"testdata/host-key-cert.pub", NewOpaqueSigner(key.(crypto.Signer))}, certBase64, false}, {"fail certFile", args{"", key}, "", true}, {"fail missing", args{"testdata/missing", key}, "", true}, {"fail not ssh", args{"testdata/rsa2048.crt", key}, "", true}, {"fail not a cert", args{"testdata/host-key.pub", key}, "", true}, {"fail validate crypto.PrivateKey", args{"testdata/host-key-cert.pub", otherKey}, "", true}, {"fail validate JSONWebKey", args{"testdata/host-key-cert.pub", &JSONWebKey{Key: otherKey}}, "", true}, {"fail validate OpaqueSigner", args{"testdata/host-key-cert.pub", NewOpaqueSigner(otherKey)}, "", true}, {"fail bad key", args{"testdata/host-key-cert.pub", "not a key"}, "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ValidateSSHPOP(tt.args.certFile, tt.args.key) if (err != nil) != tt.wantErr { t.Errorf("ValidateSSHPOP() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("ValidateSSHPOP() = %v, want %v", got, tt.want) } }) } } 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[:]), } }, "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) 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: op, 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) } }) } } func TestValidateJWK_sig(t *testing.T) { mustSigner := func(kty, crv string, size int) crypto.Signer { signer, err := keyutil.GenerateSigner(kty, crv, size) if err != nil { t.Fatal(err) } return signer } rsaKey := mustSigner("RSA", "", 2048) p256Key := mustSigner("EC", "P-256", 0) p384key := mustSigner("EC", "P-384", 0) p521Key := mustSigner("EC", "P-521", 0) edKey := mustSigner("OKP", "Ed25519", 0) type args struct { jwk *JSONWebKey } tests := []struct { name string args args wantErr bool }{ {"ok ES256", args{&JSONWebKey{Use: "sig", Algorithm: ES256, Key: p256Key}}, false}, {"ok ES384", args{&JSONWebKey{Use: "sig", Algorithm: ES384, Key: p384key}}, false}, {"ok ES512", args{&JSONWebKey{Use: "sig", Algorithm: ES512, Key: p521Key}}, false}, {"ok ES256 pub", args{&JSONWebKey{Use: "sig", Algorithm: ES256, Key: p256Key.Public()}}, false}, {"ok ES384 pub", args{&JSONWebKey{Use: "sig", Algorithm: ES384, Key: p384key.Public()}}, false}, {"ok ES512 pub", args{&JSONWebKey{Use: "sig", Algorithm: ES512, Key: p521Key.Public()}}, false}, {"ok RS256", args{&JSONWebKey{Use: "sig", Algorithm: RS256, Key: rsaKey}}, false}, {"ok RS384", args{&JSONWebKey{Use: "sig", Algorithm: RS384, Key: rsaKey.Public()}}, false}, {"ok RS512", args{&JSONWebKey{Use: "sig", Algorithm: RS512, Key: rsaKey}}, false}, {"ok PS256", args{&JSONWebKey{Use: "sig", Algorithm: PS256, Key: rsaKey.Public()}}, false}, {"ok PS384", args{&JSONWebKey{Use: "sig", Algorithm: PS384, Key: rsaKey}}, false}, {"ok PS512", args{&JSONWebKey{Use: "sig", Algorithm: PS512, Key: rsaKey.Public()}}, false}, {"ok EdDSA", args{&JSONWebKey{Use: "sig", Algorithm: EdDSA, Key: edKey}}, false}, {"ok EdDSA pub", args{&JSONWebKey{Use: "sig", Algorithm: EdDSA, Key: edKey.Public()}}, false}, {"ok HS256", args{&JSONWebKey{Use: "sig", Algorithm: HS256, Key: []byte("raw-key")}}, false}, {"ok HS384", args{&JSONWebKey{Use: "sig", Algorithm: HS384, Key: []byte("raw-key")}}, false}, {"ok HS512", args{&JSONWebKey{Use: "sig", Algorithm: HS512, Key: []byte("raw-key")}}, false}, {"ok OpaqueSigner", args{&JSONWebKey{Use: "sig", Algorithm: ES256, Key: NewOpaqueSigner(p256Key)}}, false}, {"fail alg empty", args{&JSONWebKey{Use: "sig", Key: p256Key}}, true}, {"fail ECDSA", args{&JSONWebKey{Use: "sig", Algorithm: ES384, Key: p256Key}}, true}, {"fail ECDSA pub", args{&JSONWebKey{Use: "sig", Algorithm: ES384, Key: p256Key.Public()}}, true}, {"fail RSA", args{&JSONWebKey{Use: "sig", Algorithm: ES256, Key: rsaKey}}, true}, {"fail Ed25519", args{&JSONWebKey{Use: "sig", Algorithm: ES256, Key: edKey}}, true}, {"fail bytes", args{&JSONWebKey{Use: "sig", Algorithm: ES256, Key: []byte("raw-key")}}, true}, {"fail OpaqueSigner", args{&JSONWebKey{Use: "sig", Algorithm: RS256, Key: p256Key}}, true}, {"fail unknown", args{&JSONWebKey{Use: "sig", Algorithm: HS256, Key: "raw-key"}}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := ValidateJWK(tt.args.jwk); (err != nil) != tt.wantErr { t.Errorf("ValidateJWK() error = %v, wantErr %v", err, tt.wantErr) } }) } } crypto-0.57.0/jose/x25519.go000066400000000000000000000037471474156331600153140ustar00rootroot00000000000000package 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 } crypto-0.57.0/jose/x25519_test.go000066400000000000000000000252421474156331600163450ustar00rootroot00000000000000package 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) } }) } } crypto-0.57.0/keyutil/000077500000000000000000000000001474156331600146235ustar00rootroot00000000000000crypto-0.57.0/keyutil/fingerprint.go000066400000000000000000000051341474156331600175040ustar00rootroot00000000000000package keyutil import ( "crypto" "crypto/sha256" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "fmt" "go.step.sm/crypto/fingerprint" ) // FingerprintEncoding defines the supported encodings in certificate // fingerprints. type FingerprintEncoding = fingerprint.Encoding // Supported fingerprint encodings. const ( // DefaultFingerprint represents the base64 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 ) // subjectPublicKeyInfo is a PKIX public key structure defined in RFC 5280. type subjectPublicKeyInfo struct { Algorithm pkix.AlgorithmIdentifier SubjectPublicKey asn1.BitString } // Fingerprint returns the SHA-256 fingerprint of an public key. // // The fingerprint is calculated from the encoding of the key according to RFC // 5280 section 4.2.1.2, but using SHA-256 instead of SHA-1. func Fingerprint(pub crypto.PublicKey) (string, error) { return EncodedFingerprint(pub, DefaultFingerprint) } // EncodedFingerprint returns the SHA-256 hash of the certificate using the // specified encoding. // // The fingerprint is calculated from the encoding of the key according to RFC // 5280 section 4.2.1.2, but using SHA-256 instead of SHA-1. func EncodedFingerprint(pub crypto.PublicKey, encoding FingerprintEncoding) (string, error) { b, err := x509.MarshalPKIXPublicKey(pub) if err != nil { return "", fmt.Errorf("error marshaling public key: %w", err) } var info subjectPublicKeyInfo if _, err = asn1.Unmarshal(b, &info); err != nil { return "", fmt.Errorf("error unmarshaling public key: %w", err) } if encoding == DefaultFingerprint { encoding = Base64Fingerprint } sum := sha256.Sum256(info.SubjectPublicKey.Bytes) fp := fingerprint.Fingerprint(sum[:], encoding) if fp == "" { return "", fmt.Errorf("error formatting fingerprint: unsupported encoding") } return "SHA256:" + fp, nil } crypto-0.57.0/keyutil/fingerprint_test.go000066400000000000000000000050621474156331600205430ustar00rootroot00000000000000package keyutil import ( "crypto" "crypto/x509" "encoding/pem" "os" "testing" ) func readPublicKey(t *testing.T, filename string) crypto.PublicKey { 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") } pub, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { t.Fatal(err) } return pub } func TestFingerprint(t *testing.T) { ecdsaKey := readPublicKey(t, "testdata/p256.pub") rsaKey := readPublicKey(t, "testdata/rsa.pub") ed25519Key := readPublicKey(t, "testdata/ed25519.pub") type args struct { pub crypto.PublicKey } tests := []struct { name string args args want string wantErr bool }{ {"ecdsa", args{ecdsaKey}, "SHA256:BlA/0e0DGQ8Gcpv+EPNDp3aa8O4TZ6VDLKMIXi40qlE=", false}, {"rsa", args{rsaKey}, "SHA256:Su5MWuU91vpyPy2YlX7lqTXomZ1AoGqKbvbZbf0Ff6M=", false}, {"ed25519", args{ed25519Key}, "SHA256:r/tA+Uv4M2ff1ZrAz8l+5mu0aJ1yOGwnWV5jDotBySI=", false}, {"fail", args{[]byte("not a key")}, "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := Fingerprint(tt.args.pub) if (err != nil) != tt.wantErr { t.Errorf("Fingerprint() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("Fingerprint() = %v, want %v", got, tt.want) } }) } } func TestEncodedFingerprint(t *testing.T) { ecdsaKey := readPublicKey(t, "testdata/p256.pub") rsaKey := readPublicKey(t, "testdata/rsa.pub") ed25519Key := readPublicKey(t, "testdata/ed25519.pub") type args struct { pub crypto.PublicKey encoding FingerprintEncoding } tests := []struct { name string args args want string wantErr bool }{ {"ecdsa", args{ecdsaKey, DefaultFingerprint}, "SHA256:BlA/0e0DGQ8Gcpv+EPNDp3aa8O4TZ6VDLKMIXi40qlE=", false}, {"rsa", args{rsaKey, HexFingerprint}, "SHA256:4aee4c5ae53dd6fa723f2d98957ee5a935e8999d40a06a8a6ef6d96dfd057fa3", false}, {"ed25519", args{ed25519Key, Base64RawURLFingerprint}, "SHA256:r_tA-Uv4M2ff1ZrAz8l-5mu0aJ1yOGwnWV5jDotBySI", false}, {"fail", args{[]byte("not a key"), DefaultFingerprint}, "", true}, {"fail bad encoding", args{ed25519Key, 100}, "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := EncodedFingerprint(tt.args.pub, tt.args.encoding) if (err != nil) != tt.wantErr { t.Errorf("EncodedFingerprint() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("EncodedFingerprint() = %v, want %v", got, tt.want) } }) } } crypto-0.57.0/keyutil/key.go000066400000000000000000000165771474156331600157620ustar00rootroot00000000000000// Package keyutil implements utilities to generate cryptographic keys. package keyutil import ( "bytes" "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 case crypto.Signer: return k.Public(), 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(pub crypto.PublicKey, priv crypto.PrivateKey) error { signer, ok := priv.(crypto.Signer) if !ok { return errors.New("private key type does implement crypto.Signer") } if !Equal(pub, signer.Public()) { return errors.New("private key does not match public key") } return nil } // Equal reports if x and y are the same key. func Equal(x, y any) bool { switch xx := x.(type) { case *ecdsa.PublicKey: yy, ok := y.(*ecdsa.PublicKey) return ok && xx.Equal(yy) case *ecdsa.PrivateKey: yy, ok := y.(*ecdsa.PrivateKey) return ok && xx.Equal(yy) case *rsa.PublicKey: yy, ok := y.(*rsa.PublicKey) return ok && xx.Equal(yy) case *rsa.PrivateKey: yy, ok := y.(*rsa.PrivateKey) return ok && xx.Equal(yy) case ed25519.PublicKey: yy, ok := y.(ed25519.PublicKey) return ok && xx.Equal(yy) case ed25519.PrivateKey: yy, ok := y.(ed25519.PrivateKey) return ok && xx.Equal(yy) case x25519.PublicKey: yy, ok := y.(x25519.PublicKey) return ok && xx.Equal(yy) case x25519.PrivateKey: yy, ok := y.(x25519.PrivateKey) return ok && xx.Equal(yy) case []byte: // special case for symmetric keys yy, ok := y.([]byte) return ok && bytes.Equal(xx, yy) default: return false } } 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 minBits := MinRSAKeyBytes * 8; !insecureMode.isSet() && bits < minBits { return nil, errors.Errorf("the size of the RSA key should be at least %d bits", minBits) } 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 case "X25519": _, key, err := x25519.GenerateKey(rand.Reader) if err != nil { return nil, errors.Wrap(err, "error generating X25519 key") } return key, nil default: return nil, errors.Errorf("missing or invalid value for argument 'crv'. "+ "expected 'Ed25519' or 'X25519', 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 } crypto-0.57.0/keyutil/key_test.go000066400000000000000000000573451474156331600170170ustar00rootroot00000000000000package 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(_ []byte, _ *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 //nolint:gocritic // ignore sloppy func name due to function signature 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(_ []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") } case x25519.PublicKey: if !x25519.Verify(p, sum, sig) { return fmt.Errorf("x25519.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) { type opaqueSigner struct { crypto.Signer } ecdsaKey := must(generateECKey("P-256")).(*ecdsa.PrivateKey) ecdsaSigner := opaqueSigner{ecdsaKey} 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}, {"ecdsaSigner", args{ecdsaSigner}, ecdsaKey.Public(), 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}, {"X25519", randReader, args{"OKP", "X25519", 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 Ed25519", eofReader{}, args{"OKP", "Ed25519", 0}, nil, 0, true}, {"eof X25519", eofReader{}, args{"OKP", "X25519", 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 } }) } } func TestEqual(t *testing.T) { mustSigner := func(kty, crv string, size int) crypto.Signer { s, err := GenerateSigner(kty, crv, size) if err != nil { t.Fatal(err) } return s } mustCopy := func(key crypto.Signer) crypto.Signer { if x, ok := key.(x25519.PrivateKey); ok { return x25519.PrivateKey([]byte(x)) } b, err := x509.MarshalPKCS8PrivateKey(key) if err != nil { t.Fatal(err) } priv, err := x509.ParsePKCS8PrivateKey(b) if err != nil { t.Fatal(err) } signer, ok := priv.(crypto.Signer) if !ok { t.Fatalf("type %T is not a crypto.Signer", priv) } return signer } ecdsaKey := mustSigner("EC", "P-256", 0) rsaKey := mustSigner("RSA", "", 2048) ed25519Key := mustSigner("OKP", "Ed25519", 0) x25519Key := mustSigner("OKP", "X25519", 0) type args struct { x any y any } tests := []struct { name string args args want bool }{ {"ok ecdsaKey", args{ecdsaKey, mustCopy(ecdsaKey)}, true}, {"ok rsaKey", args{rsaKey, mustCopy(rsaKey)}, true}, {"ok ed25519Key", args{ed25519Key, mustCopy(ed25519Key)}, true}, {"ok x25519Key", args{x25519Key, mustCopy(x25519Key)}, true}, {"ok ecdsaKey pub", args{ecdsaKey.Public(), mustCopy(ecdsaKey).Public()}, true}, {"ok rsaKey pub", args{rsaKey.Public(), mustCopy(rsaKey).Public()}, true}, {"ok ed25519Key pub", args{ed25519Key.Public(), mustCopy(ed25519Key).Public()}, true}, {"ok x25519Key pub", args{x25519Key.Public(), mustCopy(x25519Key).Public()}, true}, {"ok []byte", args{[]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}}, true}, {"fail ecdsaKey", args{ecdsaKey, mustCopy(ecdsaKey).Public()}, false}, {"fail rsaKey", args{rsaKey, mustCopy(rsaKey).Public()}, false}, {"fail ed25519Key", args{ed25519Key, mustCopy(ed25519Key).Public()}, false}, {"fail x25519Key", args{x25519Key, mustCopy(x25519Key).Public()}, false}, {"fail ecdsaKey pub", args{ecdsaKey.Public(), mustCopy(ecdsaKey)}, false}, {"fail rsaKey pub", args{rsaKey.Public(), mustCopy(rsaKey)}, false}, {"fail ed25519Key pub", args{ed25519Key.Public(), mustCopy(ed25519Key)}, false}, {"fail x25519Key pub", args{x25519Key.Public(), mustCopy(x25519Key)}, false}, {"fail []byte", args{[]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}}, false}, {"fail int", args{1, 2}, false}, {"fail string", args{"foo", "foo"}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := Equal(tt.args.x, tt.args.y); got != tt.want { t.Errorf("Equal() = %v, want %v", got, tt.want) } }) } } crypto-0.57.0/keyutil/testdata/000077500000000000000000000000001474156331600164345ustar00rootroot00000000000000crypto-0.57.0/keyutil/testdata/ed25519.pub000066400000000000000000000001611474156331600201400ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAZ/fKNzve1MQ802E86hmqnAFRg8HwVUSycSyD8UhBWp0= -----END PUBLIC KEY----- crypto-0.57.0/keyutil/testdata/p256.pub000066400000000000000000000002621474156331600176400ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcoeNMyqS0/lveul4nVhxHAtpK2UJ Qkodxz3jSYKLr2u7Q5/0psUwFSmhUK4ZN+8TNKFJNV72HjAbFtcARH/WkA== -----END PUBLIC KEY----- crypto-0.57.0/keyutil/testdata/rsa.pub000066400000000000000000000011611474156331600177300ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAoifEXKTF5rOvbTXbXXFD DdXEE7iwI6S6cEf5N8pdCgbk+RdPYBwK0Y6BZU27md22ZJZmH9uts/6mx6Ajty/q YsngMEyLNhVIvu7H2KfKqed4vTRJ13/ID9HNB9eMCPnG+NUu1U/CEPk0p+/lhKM1 mQyyS6at6wmI1Wn7jiJQeCoS9fQNl5OsevnAU0304jwQHcuFhBAQTwxmLr2iG3+8 f8VHL14YlmcYXl4RWFEvgsoe8KGdQRUHaCLpCQAaO2OmEEYZGO+4UWRMS/kKUrDS RHiX9oGUDnweP2YJ4VAymOkrWpQMJeXJi+pp8fWyl/D+SWCPT/+O3IFitb/ff1QA xw81+Q2ascCmZ61nq3bvrKcGqjjMDYxrXut+LxxHnZqeTCmTShMQJTRnrzMP5le9 4FN6h6zpI6jcr6friMzec3B87z9XtFN7xlNLCOOi7LkzikvKta4flDUCEnPfZFes OOQMfkG1W8PFyWzZtEyWPneVhttncsoZsM7gM1QsGw+lAgMBAAE= -----END PUBLIC KEY----- crypto-0.57.0/kms/000077500000000000000000000000001474156331600137275ustar00rootroot00000000000000crypto-0.57.0/kms/apiv1/000077500000000000000000000000001474156331600147475ustar00rootroot00000000000000crypto-0.57.0/kms/apiv1/options.go000066400000000000000000000164531474156331600170020ustar00rootroot00000000000000package apiv1 import ( "crypto" "crypto/x509" "errors" "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 } // SearchableKeyManager is an optional interface for KMS implementations // that support searching for keys based on certain attributes. // // # Experimental // // Notice: This API is EXPERIMENTAL and may be changed or removed in a later // release. type SearchableKeyManager interface { KeyManager SearchKeys(req *SearchKeysRequest) (*SearchKeysResponse, 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 } // CertificateChainManager is the interface implemented by KMS implementations // that can load certificate chains. The LoadCertificateChain method uses the // same request object as the LoadCertificate method of the CertificateManager // interfaces. When the LoadCertificateChain method is called, the certificate // chain stored through the CertificateChain property in the StoreCertificateRequest // will be returned, partially reusing the StoreCertificateRequest object. type CertificateChainManager interface { LoadCertificateChain(req *LoadCertificateChainRequest) ([]*x509.Certificate, error) StoreCertificateChain(req *StoreCertificateChainRequest) 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" } func (e NotImplementedError) Is(target error) bool { _, ok := target.(NotImplementedError) return ok } // AlreadyExistsError is the type of error returned if a key already exists. This // is currently only implemented for pkcs11, tpmkms, and mackms. type AlreadyExistsError struct { Message string } func (e AlreadyExistsError) Error() string { if e.Message != "" { return e.Message } return "already exists" } func (e AlreadyExistsError) Is(target error) bool { _, ok := target.(AlreadyExistsError) return ok } // NotFoundError is the type of error returned if a key or certificate does not // exist. This is currently only implemented for capi and mackms. type NotFoundError struct { Message string } func (e NotFoundError) Error() string { if e.Message != "" { return e.Message } return "not found" } func (e NotFoundError) Is(target error) bool { _, ok := target.(NotFoundError) return ok } // 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" // TPMKMS TPMKMS Type = "tpmkms" // MacKMS is the KMS implementation using macOS Keychain and Secure Enclave. MacKMS Type = "mackms" ) // TypeOf returns the type of of the given uri. func TypeOf(rawuri string) (Type, error) { u, err := uri.Parse(rawuri) if err != nil { return DefaultKMS, err } t := Type(u.Scheme).normalize() if err := t.Validate(); err != nil { return DefaultKMS, err } return t, nil } func (t Type) normalize() Type { return Type(strings.ToLower(string(t))) } // Validate return an error if the type is not a supported one. func (t Type) Validate() error { typ := t.normalize() switch typ { case DefaultKMS, SoftKMS: // Go crypto based kms. return nil case CloudKMS, AmazonKMS, AzureKMS: // Cloud based kms. return nil case YubiKey, PKCS11, TPMKMS: // Hardware based kms. return nil case SSHAgentKMS, CAPIKMS, MacKMS: // Others return nil } // Check other registered types if _, ok := registry.Load(typ); ok { return nil } return fmt.Errorf("unsupported kms type %s", t) } // 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, tpmkms 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"` // StorageDirectory is the path to a directory to // store serialized TPM objects. Only used by the TPMKMS. StorageDirectory string `json:"storageDirectory,omitempty"` } // Validate checks the fields in Options. func (o *Options) Validate() error { if o == nil { return nil } return o.Type.Validate() } // 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 != "" { return TypeOf(o.URI) } return SoftKMS, nil } var ErrNonInteractivePasswordPrompt = errors.New("password required in non-interactive context") var NonInteractivePasswordPrompter = func(string) ([]byte, error) { return nil, ErrNonInteractivePasswordPrompt } type PasswordPrompter func(s string) ([]byte, error) crypto-0.57.0/kms/apiv1/options_test.go000066400000000000000000000150721474156331600200350ustar00rootroot00000000000000package apiv1 import ( "context" "crypto" "errors" "fmt" "os" "testing" "github.com/stretchr/testify/assert" ) type fakeKM struct{} func (f *fakeKM) GetPublicKey(req *GetPublicKeyRequest) (crypto.PublicKey, error) { return nil, NotImplementedError{} } func (f *fakeKM) CreateKey(req *CreateKeyRequest) (*CreateKeyResponse, error) { return nil, NotImplementedError{} } func (f *fakeKM) CreateSigner(req *CreateSignerRequest) (crypto.Signer, error) { return nil, NotImplementedError{} } func (f *fakeKM) Close() error { return NotImplementedError{} } func TestMain(m *testing.M) { Register(Type("fake"), func(ctx context.Context, opts Options) (KeyManager, error) { return &fakeKM{}, nil }) os.Exit(m.Run()) } 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{}, "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) } }) } } func TestNotFoundError_Error(t *testing.T) { type fields struct { msg string } tests := []struct { name string fields fields want string }{ {"default", fields{}, "not found"}, {"custom", fields{"custom message: not found"}, "custom message: not found"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { e := NotFoundError{ Message: tt.fields.msg, } if got := e.Error(); got != tt.want { t.Errorf("ErrAlreadyExists.Error() = %v, want %v", got, tt.want) } }) } } func TestTypeOf(t *testing.T) { type args struct { rawuri string } tests := []struct { name string args args want Type wantErr bool }{ {"ok softkms", args{"softkms:foo=bar"}, SoftKMS, false}, {"ok cloudkms", args{"CLOUDKMS:"}, CloudKMS, false}, {"ok amazonkms", args{"awskms:foo=bar"}, AmazonKMS, false}, {"ok pkcs11", args{"PKCS11:foo=bar"}, PKCS11, false}, {"ok yubikey", args{"yubikey:foo=bar"}, YubiKey, false}, {"ok sshagentkms", args{"sshagentkms:"}, SSHAgentKMS, false}, {"ok azurekms", args{"azurekms:foo=bar"}, AzureKMS, false}, {"ok capi", args{"CAPI:foo-bar"}, CAPIKMS, false}, {"ok tpmkms", args{"tpmkms:"}, TPMKMS, false}, {"ok registered", args{"FAKE:"}, Type("fake"), false}, {"fail empty", args{""}, DefaultKMS, true}, {"fail parse", args{"softkms"}, DefaultKMS, true}, {"fail kms", args{"foobar:foo=bar"}, DefaultKMS, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := TypeOf(tt.args.rawuri) if (err != nil) != tt.wantErr { t.Errorf("TypeOf() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("TypeOf() = %v, want %v", got, tt.want) } }) } } func TestError_Is(t *testing.T) { tests := []struct { name string err error target error want bool }{ {"ok not implemented", NotImplementedError{}, NotImplementedError{}, true}, {"ok not implemented with message", NotImplementedError{Message: "something"}, NotImplementedError{}, true}, {"ok already exists", AlreadyExistsError{}, AlreadyExistsError{}, true}, {"ok already exists with message", AlreadyExistsError{Message: "something"}, AlreadyExistsError{}, true}, {"ok not found", NotFoundError{}, NotFoundError{}, true}, {"ok not found with message", NotFoundError{Message: "something"}, NotFoundError{}, true}, {"fail not implemented", errors.New("not implemented"), NotImplementedError{}, false}, {"fail already exists", errors.New("already exists"), AlreadyExistsError{}, false}, {"fail not found", errors.New("not found"), NotFoundError{}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert.Equal(t, tt.want, errors.Is(tt.err, tt.target)) assert.Equal(t, tt.want, errors.Is(fmt.Errorf("wrap 1: %w", tt.err), tt.target)) assert.Equal(t, tt.want, errors.Is(fmt.Errorf("wrap 1: %w", fmt.Errorf("wrap 2: %w", tt.err)), tt.target)) }) } } crypto-0.57.0/kms/apiv1/registry.go000066400000000000000000000012371474156331600171510ustar00rootroot00000000000000package 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.normalize(), fn) } // LoadKeyManagerNewFunc returns the function initialize a KayManager. func LoadKeyManagerNewFunc(t Type) (KeyManagerNewFunc, bool) { v, ok := registry.Load(t.normalize()) if !ok { return nil, false } fn, ok := v.(KeyManagerNewFunc) return fn, ok } crypto-0.57.0/kms/apiv1/requests.go000066400000000000000000000226471474156331600171640ustar00rootroot00000000000000package apiv1 import ( "context" "crypto" "crypto/x509" "fmt" "time" ) // 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, tpmkms, mackms. 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 // DestroyRetentionPeriod is the period of time that a key spends in a // destroy scheduled state before transitioning to destroyed. // // Used by: cloudkms DestroyRetentionPeriod time.Duration } // CreateKeyResponse is the response value of the kms.CreateKey method. type CreateKeyResponse struct { Name string PublicKey crypto.PublicKey // PrivateKey is only used by softkms PrivateKey crypto.PrivateKey CreateSignerRequest CreateSignerRequest } // SearchKeysRequest is the request for the SearchKeys method. It takes // a Query string with the attributes to match when searching the // KMS. type SearchKeysRequest struct { Query string } // SearchKeyResult is a single result returned from the SearchKeys // method. type SearchKeyResult CreateKeyResponse // SearchKeysResponse is the response for the SearchKeys method. It // wraps a slice of SearchKeyResult structs. The Results slice can // be empty in case no key was found for the search query. type SearchKeysResponse struct { Results []SearchKeyResult } // 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 PasswordPrompter PasswordPrompter } // CreateDecrypterRequest is the parameter used in the kms.Decrypt method. type CreateDecrypterRequest struct { Decrypter crypto.Decrypter DecryptionKey string DecryptionKeyPEM []byte Password []byte PasswordPrompter PasswordPrompter } // LoadCertificateRequest is the parameter used in the LoadCertificate method of // a CertificateManager. type LoadCertificateRequest struct { Name string } // LoadCertificateChainRequest is the parameter used in the LoadCertificateChain method of // a CertificateChainManager. It's an alias for LoadCertificateRequest. type LoadCertificateChainRequest LoadCertificateRequest // 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 } // StoreCertificateChainRequest is the parameter used in the StoreCertificateChain method // of a CertificateChainManager. type StoreCertificateChainRequest struct { Name string CertificateChain []*x509.Certificate } // 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 AttestationClient AttestationClient // TODO(hs): a better name; Attestor perhaps, but that's already taken } // AttestationClient is an interface that provides a pluggable method for // attesting Attestation Keys (AKs). type AttestationClient interface { Attest(context.Context) ([]*x509.Certificate, error) } // CertificationParameters encapsulates the inputs for certifying an application key. // Only TPM 2.0 is supported at this point. // // This struct was copied from github.com/google/go-attestation, preventing an // additional dependency in this package. type CertificationParameters struct { // Public represents the key's canonical encoding (a TPMT_PUBLIC structure). // It includes the public key and signing parameters. Public []byte // CreateData represents the properties of a TPM 2.0 key. It is encoded // as a TPMS_CREATION_DATA structure. CreateData []byte // CreateAttestation represents an assertion as to the details of the key. // It is encoded as a TPMS_ATTEST structure. CreateAttestation []byte // CreateSignature represents a signature of the CreateAttestation structure. // It is encoded as a TPMT_SIGNATURE structure. CreateSignature []byte } // CreateAttestationResponse is the response value of the kms.CreateAttestation // method. // // If a non-empty CertificateChain is returned, the first x509.Certificate is // the same as the one in the Certificate property. // // When an attestation is created for a TPM key, the CertificationParameters // property will have a record of the certification parameters at the time of // key attestation. // // # 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 CertificationParameters *CertificationParameters PermanentIdentifier string } // DeleteKeyRequest is the parameter used in the kms.DeleteKey method. // // # Experimental // // Notice: This API is EXPERIMENTAL and may be changed or removed in a later // release. type DeleteKeyRequest struct { Name string } // DeleteCertificateRequest is the parameter used in the kms.DeleteCertificate // method. // // # Experimental // // Notice: This API is EXPERIMENTAL and may be changed or removed in a later // release. type DeleteCertificateRequest struct { Name string } crypto-0.57.0/kms/apiv1/requests_test.go000066400000000000000000000027721474156331600202200ustar00rootroot00000000000000package 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) } }) } } crypto-0.57.0/kms/awskms/000077500000000000000000000000001474156331600152345ustar00rootroot00000000000000crypto-0.57.0/kms/awskms/awskms.go000066400000000000000000000211651474156331600170750ustar00rootroot00000000000000//go:build !noawskms // +build !noawskms package awskms import ( "context" "crypto" "net/url" "strings" "time" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/kms" "github.com/aws/aws-sdk-go-v2/service/kms/types" "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, the string "awskms". const Scheme = string(apiv1.AmazonKMS) // KMS implements a KMS using AWS Key Management Service. type KMS struct { client KeyManagementClient } // KeyManagementClient defines the methods on KeyManagementClient that this // package will use. This interface will be used for unit testing. type KeyManagementClient interface { GetPublicKey(ctx context.Context, input *kms.GetPublicKeyInput, opts ...func(*kms.Options)) (*kms.GetPublicKeyOutput, error) CreateKey(ctx context.Context, input *kms.CreateKeyInput, opts ...func(*kms.Options)) (*kms.CreateKeyOutput, error) CreateAlias(ctx context.Context, input *kms.CreateAliasInput, opts ...func(*kms.Options)) (*kms.CreateAliasOutput, error) Sign(ctx context.Context, input *kms.SignInput, opts ...func(*kms.Options)) (*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: types.KeySpecEccNistP256, apiv1.SHA256WithRSA: map[int]types.KeySpec{ 0: types.KeySpecRsa3072, 2048: types.KeySpecRsa2048, 3072: types.KeySpecRsa3072, 4096: types.KeySpecRsa4096, }, apiv1.SHA384WithRSA: map[int]types.KeySpec{ 0: types.KeySpecRsa3072, 2048: types.KeySpecRsa2048, 3072: types.KeySpecRsa3072, 4096: types.KeySpecRsa4096, }, apiv1.SHA512WithRSA: map[int]types.KeySpec{ 0: types.KeySpecRsa3072, 2048: types.KeySpecRsa2048, 3072: types.KeySpecRsa3072, 4096: types.KeySpecRsa4096, }, apiv1.SHA256WithRSAPSS: map[int]types.KeySpec{ 0: types.KeySpecRsa3072, 2048: types.KeySpecRsa2048, 3072: types.KeySpecRsa3072, 4096: types.KeySpecRsa4096, }, apiv1.SHA384WithRSAPSS: map[int]types.KeySpec{ 0: types.KeySpecRsa3072, 2048: types.KeySpecRsa2048, 3072: types.KeySpecRsa3072, 4096: types.KeySpecRsa4096, }, apiv1.SHA512WithRSAPSS: map[int]types.KeySpec{ 0: types.KeySpecRsa3072, 2048: types.KeySpecRsa2048, 3072: types.KeySpecRsa3072, 4096: types.KeySpecRsa4096, }, apiv1.ECDSAWithSHA256: types.KeySpecEccNistP256, apiv1.ECDSAWithSHA384: types.KeySpecEccNistP384, apiv1.ECDSAWithSHA512: types.KeySpecEccNistP521, } // New creates a new AWSKMS. By default, clients 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 clients can also be configured with environment variables, see docs at // https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/ for all the // options. func New(ctx context.Context, opts apiv1.Options) (*KMS, error) { var configOptions []func(*config.LoadOptions) error if opts.URI != "" { u, err := uri.ParseWithScheme(Scheme, opts.URI) if err != nil { return nil, err } if v := u.Get("profile"); v != "" { configOptions = append(configOptions, config.WithSharedConfigProfile(v)) } if v := u.Get("region"); v != "" { configOptions = append(configOptions, config.WithRegion(v)) } if v := u.Get("credentials-file"); v != "" { configOptions = append(configOptions, config.WithSharedConfigFiles([]string{v})) } } // Deprecated way to set configuration parameters. if opts.Region != "" { configOptions = append(configOptions, config.WithRegion(opts.Region)) } if opts.Profile != "" { configOptions = append(configOptions, config.WithSharedConfigProfile(opts.Profile)) } if opts.CredentialsFile != "" { configOptions = append(configOptions, config.WithSharedConfigFiles([]string{opts.CredentialsFile})) } cfg, err := config.LoadDefaultConfig(ctx, configOptions...) if err != nil { return nil, errors.Wrap(err, "error loading AWS config") } return &KMS{ client: kms.NewFromConfig(cfg), }, 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.client.GetPublicKey(ctx, &kms.GetPublicKeyInput{ KeyId: &keyID, }) if err != nil { return nil, errors.Wrap(err, "awskms GetPublicKey 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") } keyName, err := parseName(req.Name) if err != nil { return nil, err } keySpec, err := getCustomerMasterKeySpecMapping(req.SignatureAlgorithm, req.Bits) if err != nil { return nil, err } tag := types.Tag{ TagKey: pointer("name"), TagValue: pointer(keyName), } input := &kms.CreateKeyInput{ Description: pointer(keyName), KeySpec: keySpec, Tags: []types.Tag{tag}, KeyUsage: types.KeyUsageTypeSignVerify, } ctx, cancel := defaultContext() defer cancel() resp, err := k.client.CreateKey(ctx, input) if err != nil { return nil, errors.Wrap(err, "awskms CreateKey failed") } if err := k.createKeyAlias(*resp.KeyMetadata.KeyId, keyName); 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 { ctx, cancel := defaultContext() defer cancel() _, err := k.client.CreateAlias(ctx, &kms.CreateAliasInput{ AliasName: pointer("alias/" + alias + "-" + keyID[:8]), TargetKeyId: pointer(keyID), }) if err != nil { return errors.Wrap(err, "awskms CreateAlias 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.client, req.SigningKey) } // Close closes the connection of the KMS client. func (k *KMS) Close() error { return nil } func pointer[T any](v T) *T { return &v } 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 } // parseName extracts the name from an uri. func parseName(rawuri string) (string, error) { if strings.HasPrefix(rawuri, "awskms:") || strings.HasPrefix(rawuri, "aws:") { u, err := uri.Parse(rawuri) if err != nil { return "", err } if k := u.Get("name"); k != "" { return k, nil } return "", errors.Errorf("failed to get name from %s", rawuri) } return rawuri, nil } func getCustomerMasterKeySpecMapping(alg apiv1.SignatureAlgorithm, bits int) (types.KeySpec, error) { v, ok := customerMasterKeySpecMapping[alg] if !ok { return "", errors.Errorf("awskms does not support signature algorithm '%s'", alg) } switch v := v.(type) { case types.KeySpec: return v, nil case map[int]types.KeySpec: ks, ok := v[bits] if !ok { return "", errors.Errorf("awskms does not support signature algorithm '%s' with '%d' bits", alg, bits) } return ks, nil default: return "", errors.Errorf("unexpected error: this should not happen") } } crypto-0.57.0/kms/awskms/awskms_test.go000066400000000000000000000327631474156331600201420ustar00rootroot00000000000000package awskms import ( "context" "crypto" "fmt" "reflect" "testing" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/kms" "github.com/aws/aws-sdk-go-v2/service/kms/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/pemutil" ) func TestRegister(t *testing.T) { fn, ok := apiv1.LoadKeyManagerNewFunc(apiv1.AmazonKMS) require.True(t, ok) _, err := fn(context.Background(), apiv1.Options{}) require.NoError(t, err) } func TestNew(t *testing.T) { ctx := context.Background() cfg, err := config.LoadDefaultConfig(context.Background()) if err != nil { t.Fatal(err) } expected := &KMS{ client: kms.NewFromConfig(cfg), } 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}, {"fail with options", args{ctx, apiv1.Options{ Region: "us-east-1", Profile: "smallstep", CredentialsFile: "~/aws/missing", }}, nil, true}, {"fail with uri", args{ctx, apiv1.Options{ URI: "awskms:region=us-east-1;profile=smallstep;credentials-file=/var/run/aws/missing", }}, nil, true}, {"fail bad 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) { 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.client == 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 { client KeyManagementClient } type args struct { req *apiv1.GetPublicKeyRequest } tests := []struct { name string fields fields args args want crypto.PublicKey wantErr bool }{ {"ok", fields{okClient}, args{&apiv1.GetPublicKeyRequest{ Name: "awskms:key-id=be468355-ca7a-40d9-a28b-8ae1c4c7f936", }}, key, false}, {"fail empty", fields{okClient}, args{&apiv1.GetPublicKeyRequest{}}, nil, true}, {"fail name", fields{okClient}, args{&apiv1.GetPublicKeyRequest{ Name: "awskms:key-id=", }}, nil, true}, {"fail getPublicKey", fields{&MockClient{ getPublicKey: func(ctx context.Context, input *kms.GetPublicKeyInput, opts ...func(*kms.Options)) (*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{&MockClient{ getPublicKey: func(ctx context.Context, input *kms.GetPublicKeyInput, opts ...func(*kms.Options)) (*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{ client: tt.fields.client, } 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 { client KeyManagementClient } type args struct { req *apiv1.CreateKeyRequest } tests := []struct { name string fields fields args args want *apiv1.CreateKeyResponse wantErr bool }{ {"ok", fields{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 with uri", fields{okClient}, args{&apiv1.CreateKeyRequest{ Name: "awskms: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{okClient}, args{&apiv1.CreateKeyRequest{}}, nil, true}, {"fail unsupported alg", fields{okClient}, args{&apiv1.CreateKeyRequest{ Name: "root", SignatureAlgorithm: apiv1.PureEd25519, }}, nil, true}, {"fail unsupported bits", fields{okClient}, args{&apiv1.CreateKeyRequest{ Name: "root", SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 1234, }}, nil, true}, {"fail uri parse", fields{okClient}, args{&apiv1.CreateKeyRequest{ Name: "awskms:%name=root", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }}, nil, true}, {"fail uri no name", fields{okClient}, args{&apiv1.CreateKeyRequest{ Name: "awskms:name", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }}, nil, true}, {"fail createKey", fields{&MockClient{ createKey: func(ctx context.Context, input *kms.CreateKeyInput, opts ...func(*kms.Options)) (*kms.CreateKeyOutput, error) { return nil, fmt.Errorf("an error") }, createAlias: okClient.createAlias, getPublicKey: okClient.getPublicKey, }}, args{&apiv1.CreateKeyRequest{ Name: "root", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }}, nil, true}, {"fail createAlias", fields{&MockClient{ createKey: okClient.createKey, createAlias: func(ctx context.Context, input *kms.CreateAliasInput, opts ...func(*kms.Options)) (*kms.CreateAliasOutput, error) { return nil, fmt.Errorf("an error") }, getPublicKey: okClient.getPublicKey, }}, args{&apiv1.CreateKeyRequest{ Name: "root", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }}, nil, true}, {"fail getPublicKey", fields{&MockClient{ createKey: okClient.createKey, createAlias: okClient.createAlias, getPublicKey: func(ctx context.Context, input *kms.GetPublicKeyInput, opts ...func(*kms.Options)) (*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{ client: tt.fields.client, } 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 { client KeyManagementClient } 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: "awskms:key-id=be468355-ca7a-40d9-a28b-8ae1c4c7f936", }}, &Signer{ client: client, keyID: "be468355-ca7a-40d9-a28b-8ae1c4c7f936", publicKey: key, }, false}, {"fail empty", fields{client}, args{&apiv1.CreateSignerRequest{}}, nil, true}, {"fail preload", fields{client}, args{&apiv1.CreateSignerRequest{}}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &KMS{ client: tt.fields.client, } 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 { client KeyManagementClient } tests := []struct { name string fields fields wantErr bool }{ {"ok", fields{getOKClient()}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &KMS{ client: tt.fields.client, } 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) } }) } } func Test_getCustomerMasterKeySpecMapping(t *testing.T) { tmp := customerMasterKeySpecMapping t.Cleanup(func() { customerMasterKeySpecMapping = tmp }) // Fail type switch customerMasterKeySpecMapping[apiv1.SignatureAlgorithm(100)] = "string" type args struct { alg apiv1.SignatureAlgorithm bits int } tests := []struct { name string args args want types.KeySpec assertion assert.ErrorAssertionFunc }{ {"UnspecifiedSignAlgorithm", args{apiv1.UnspecifiedSignAlgorithm, 0}, types.KeySpecEccNistP256, assert.NoError}, {"SHA256WithRSA", args{apiv1.SHA256WithRSA, 0}, types.KeySpecRsa3072, assert.NoError}, {"SHA256WithRSA+2048", args{apiv1.SHA256WithRSA, 2048}, types.KeySpecRsa2048, assert.NoError}, {"SHA256WithRSA+3072", args{apiv1.SHA256WithRSA, 3072}, types.KeySpecRsa3072, assert.NoError}, {"SHA256WithRSA+4096", args{apiv1.SHA256WithRSA, 4096}, types.KeySpecRsa4096, assert.NoError}, {"SHA384WithRSA", args{apiv1.SHA384WithRSA, 0}, types.KeySpecRsa3072, assert.NoError}, {"SHA384WithRSA+2048", args{apiv1.SHA384WithRSA, 2048}, types.KeySpecRsa2048, assert.NoError}, {"SHA384WithRSA+3072", args{apiv1.SHA384WithRSA, 3072}, types.KeySpecRsa3072, assert.NoError}, {"SHA384WithRSA+4096", args{apiv1.SHA384WithRSA, 4096}, types.KeySpecRsa4096, assert.NoError}, {"SHA512WithRSA", args{apiv1.SHA512WithRSA, 0}, types.KeySpecRsa3072, assert.NoError}, {"SHA512WithRSA+2048", args{apiv1.SHA512WithRSA, 2048}, types.KeySpecRsa2048, assert.NoError}, {"SHA512WithRSA+3072", args{apiv1.SHA512WithRSA, 3072}, types.KeySpecRsa3072, assert.NoError}, {"SHA512WithRSA+4096", args{apiv1.SHA512WithRSA, 4096}, types.KeySpecRsa4096, assert.NoError}, {"SHA256WithRSAPSS", args{apiv1.SHA256WithRSAPSS, 0}, types.KeySpecRsa3072, assert.NoError}, {"SHA256WithRSAPSS+2048", args{apiv1.SHA256WithRSAPSS, 2048}, types.KeySpecRsa2048, assert.NoError}, {"SHA256WithRSAPSS+3072", args{apiv1.SHA256WithRSAPSS, 3072}, types.KeySpecRsa3072, assert.NoError}, {"SHA256WithRSAPSS+4096", args{apiv1.SHA256WithRSAPSS, 4096}, types.KeySpecRsa4096, assert.NoError}, {"SHA384WithRSAPSS", args{apiv1.SHA384WithRSAPSS, 0}, types.KeySpecRsa3072, assert.NoError}, {"SHA384WithRSAPSS+2048", args{apiv1.SHA384WithRSAPSS, 2048}, types.KeySpecRsa2048, assert.NoError}, {"SHA384WithRSAPSS+3072", args{apiv1.SHA384WithRSAPSS, 3072}, types.KeySpecRsa3072, assert.NoError}, {"SHA384WithRSAPSS+4096", args{apiv1.SHA384WithRSAPSS, 4096}, types.KeySpecRsa4096, assert.NoError}, {"SHA512WithRSAPSS", args{apiv1.SHA512WithRSAPSS, 0}, types.KeySpecRsa3072, assert.NoError}, {"SHA512WithRSAPSS+2048", args{apiv1.SHA512WithRSAPSS, 2048}, types.KeySpecRsa2048, assert.NoError}, {"SHA512WithRSAPSS+3072", args{apiv1.SHA512WithRSAPSS, 3072}, types.KeySpecRsa3072, assert.NoError}, {"SHA512WithRSAPSS+4096", args{apiv1.SHA512WithRSAPSS, 4096}, types.KeySpecRsa4096, assert.NoError}, {"ECDSAWithSHA256", args{apiv1.ECDSAWithSHA256, 0}, types.KeySpecEccNistP256, assert.NoError}, {"ECDSAWithSHA384", args{apiv1.ECDSAWithSHA384, 0}, types.KeySpecEccNistP384, assert.NoError}, {"ECDSAWithSHA512", args{apiv1.ECDSAWithSHA512, 0}, types.KeySpecEccNistP521, assert.NoError}, {"fail Ed25519", args{apiv1.PureEd25519, 0}, "", assert.Error}, {"fail type switch", args{apiv1.SignatureAlgorithm(100), 0}, "", assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := getCustomerMasterKeySpecMapping(tt.args.alg, tt.args.bits) tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } } crypto-0.57.0/kms/awskms/mock_test.go000066400000000000000000000054151474156331600175600ustar00rootroot00000000000000package awskms import ( "context" "encoding/pem" "github.com/aws/aws-sdk-go-v2/service/kms" "github.com/aws/aws-sdk-go-v2/service/kms/types" ) type MockClient struct { getPublicKey func(ctx context.Context, input *kms.GetPublicKeyInput, opts ...func(*kms.Options)) (*kms.GetPublicKeyOutput, error) createKey func(ctx context.Context, input *kms.CreateKeyInput, opts ...func(*kms.Options)) (*kms.CreateKeyOutput, error) createAlias func(ctx context.Context, input *kms.CreateAliasInput, opts ...func(*kms.Options)) (*kms.CreateAliasOutput, error) sign func(ctx context.Context, input *kms.SignInput, opts ...func(*kms.Options)) (*kms.SignOutput, error) } func (m *MockClient) GetPublicKey(ctx context.Context, input *kms.GetPublicKeyInput, opts ...func(*kms.Options)) (*kms.GetPublicKeyOutput, error) { return m.getPublicKey(ctx, input, opts...) } func (m *MockClient) CreateKey(ctx context.Context, input *kms.CreateKeyInput, opts ...func(*kms.Options)) (*kms.CreateKeyOutput, error) { return m.createKey(ctx, input, opts...) } func (m *MockClient) CreateAlias(ctx context.Context, input *kms.CreateAliasInput, opts ...func(*kms.Options)) (*kms.CreateAliasOutput, error) { return m.createAlias(ctx, input, opts...) } func (m *MockClient) Sign(ctx context.Context, input *kms.SignInput, opts ...func(*kms.Options)) (*kms.SignOutput, error) { return m.sign(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{ getPublicKey: func(ctx context.Context, input *kms.GetPublicKeyInput, opts ...func(*kms.Options)) (*kms.GetPublicKeyOutput, error) { block, _ := pem.Decode([]byte(publicKey)) return &kms.GetPublicKeyOutput{ KeyId: input.KeyId, PublicKey: block.Bytes, }, nil }, createKey: func(ctx context.Context, input *kms.CreateKeyInput, opts ...func(*kms.Options)) (*kms.CreateKeyOutput, error) { return &kms.CreateKeyOutput{ KeyMetadata: &types.KeyMetadata{ KeyId: pointer(keyID), }, }, nil }, createAlias: func(ctx context.Context, input *kms.CreateAliasInput, opts ...func(*kms.Options)) (*kms.CreateAliasOutput, error) { return &kms.CreateAliasOutput{}, nil }, sign: func(ctx context.Context, input *kms.SignInput, opts ...func(*kms.Options)) (*kms.SignOutput, error) { return &kms.SignOutput{ Signature: signature, }, nil }, } } crypto-0.57.0/kms/awskms/no_awskms.go000066400000000000000000000006601474156331600175660ustar00rootroot00000000000000//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) }) } crypto-0.57.0/kms/awskms/signer.go000066400000000000000000000060631474156331600170570ustar00rootroot00000000000000//go:build !noawskms // +build !noawskms package awskms import ( "crypto" "crypto/ecdsa" "crypto/rsa" "io" "github.com/aws/aws-sdk-go-v2/service/kms" "github.com/aws/aws-sdk-go-v2/service/kms/types" "github.com/pkg/errors" "go.step.sm/crypto/pemutil" ) // Signer implements a crypto.Signer using the AWS KMS. type Signer struct { client KeyManagementClient keyID string publicKey crypto.PublicKey } // NewSigner creates a new signer using a key in the AWS KMS. func NewSigner(client KeyManagementClient, signingKey string) (*Signer, error) { keyID, err := parseKeyID(signingKey) if err != nil { return nil, err } // Make sure that the key exists. signer := &Signer{ client: client, 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.client.GetPublicKey(ctx, &kms.GetPublicKeyInput{ KeyId: pointer(keyID), }) if err != nil { return errors.Wrap(err, "awskms GetPublicKey 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(_ 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: pointer(s.keyID), SigningAlgorithm: alg, Message: digest, MessageType: types.MessageTypeDigest, } ctx, cancel := defaultContext() defer cancel() resp, err := s.client.Sign(ctx, req) if err != nil { return nil, errors.Wrap(err, "awskms Sign failed") } return resp.Signature, nil } func getSigningAlgorithm(key crypto.PublicKey, opts crypto.SignerOpts) (types.SigningAlgorithmSpec, error) { switch key.(type) { case *rsa.PublicKey: _, isPSS := opts.(*rsa.PSSOptions) switch h := opts.HashFunc(); h { case crypto.SHA256: if isPSS { return types.SigningAlgorithmSpecRsassaPssSha256, nil } return types.SigningAlgorithmSpecRsassaPkcs1V15Sha256, nil case crypto.SHA384: if isPSS { return types.SigningAlgorithmSpecRsassaPssSha384, nil } return types.SigningAlgorithmSpecRsassaPkcs1V15Sha384, nil case crypto.SHA512: if isPSS { return types.SigningAlgorithmSpecRsassaPssSha512, nil } return types.SigningAlgorithmSpecRsassaPkcs1V15Sha512, nil default: return "", errors.Errorf("unsupported hash function %v", h) } case *ecdsa.PublicKey: switch h := opts.HashFunc(); h { case crypto.SHA256: return types.SigningAlgorithmSpecEcdsaSha256, nil case crypto.SHA384: return types.SigningAlgorithmSpecEcdsaSha384, nil case crypto.SHA512: return types.SigningAlgorithmSpecEcdsaSha512, nil default: return "", errors.Errorf("unsupported hash function %v", h) } default: return "", errors.Errorf("unsupported key type %T", key) } } crypto-0.57.0/kms/awskms/signer_test.go000066400000000000000000000134651474156331600201220ustar00rootroot00000000000000package awskms import ( "context" "crypto" "crypto/ecdsa" "crypto/rand" "crypto/rsa" "fmt" "io" "reflect" "testing" "github.com/aws/aws-sdk-go-v2/service/kms" "github.com/aws/aws-sdk-go-v2/service/kms/types" "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{ client: okClient, keyID: "be468355-ca7a-40d9-a28b-8ae1c4c7f936", publicKey: key, }, false}, {"fail parse", args{okClient, "awskms:key-id="}, nil, true}, {"fail preload", args{&MockClient{ getPublicKey: func(ctx context.Context, input *kms.GetPublicKeyInput, opts ...func(*kms.Options)) (*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{ getPublicKey: func(ctx context.Context, input *kms.GetPublicKeyInput, opts ...func(*kms.Options)) (*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 { client 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{ client: tt.fields.client, 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 { client 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{ sign: func(ctx context.Context, input *kms.SignInput, opts ...func(*kms.Options)) (*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{ client: tt.fields.client, 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 types.SigningAlgorithmSpec 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) } }) } } crypto-0.57.0/kms/azurekms/000077500000000000000000000000001474156331600155705ustar00rootroot00000000000000crypto-0.57.0/kms/azurekms/internal/000077500000000000000000000000001474156331600174045ustar00rootroot00000000000000crypto-0.57.0/kms/azurekms/internal/mock/000077500000000000000000000000001474156331600203355ustar00rootroot00000000000000crypto-0.57.0/kms/azurekms/internal/mock/key_vault_client.go000066400000000000000000000063331474156331600242320ustar00rootroot00000000000000// Code generated by MockGen. DO NOT EDIT. // Source: go.step.sm/crypto/kms/azurekms (interfaces: KeyVaultClient) // // Generated by this command: // // mockgen -package mock -mock_names=KeyVaultClient=KeyVaultClient -destination internal/mock/key_vault_client.go go.step.sm/crypto/kms/azurekms KeyVaultClient // // Package mock is a generated GoMock package. package mock import ( context "context" reflect "reflect" azkeys "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys" gomock "go.uber.org/mock/gomock" ) // KeyVaultClient is a mock of KeyVaultClient interface. type KeyVaultClient struct { ctrl *gomock.Controller recorder *KeyVaultClientMockRecorder isgomock struct{} } // 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(ctx context.Context, name string, parameters azkeys.CreateKeyParameters, options *azkeys.CreateKeyOptions) (azkeys.CreateKeyResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateKey", ctx, name, parameters, options) ret0, _ := ret[0].(azkeys.CreateKeyResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // CreateKey indicates an expected call of CreateKey. func (mr *KeyVaultClientMockRecorder) CreateKey(ctx, name, parameters, options any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateKey", reflect.TypeOf((*KeyVaultClient)(nil).CreateKey), ctx, name, parameters, options) } // GetKey mocks base method. func (m *KeyVaultClient) GetKey(ctx context.Context, name, version string, options *azkeys.GetKeyOptions) (azkeys.GetKeyResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetKey", ctx, name, version, options) ret0, _ := ret[0].(azkeys.GetKeyResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // GetKey indicates an expected call of GetKey. func (mr *KeyVaultClientMockRecorder) GetKey(ctx, name, version, options any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetKey", reflect.TypeOf((*KeyVaultClient)(nil).GetKey), ctx, name, version, options) } // Sign mocks base method. func (m *KeyVaultClient) Sign(ctx context.Context, name, version string, parameters azkeys.SignParameters, options *azkeys.SignOptions) (azkeys.SignResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Sign", ctx, name, version, parameters, options) ret0, _ := ret[0].(azkeys.SignResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // Sign indicates an expected call of Sign. func (mr *KeyVaultClientMockRecorder) Sign(ctx, name, version, parameters, options any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sign", reflect.TypeOf((*KeyVaultClient)(nil).Sign), ctx, name, version, parameters, options) } crypto-0.57.0/kms/azurekms/key_vault.go000066400000000000000000000306141474156331600201260ustar00rootroot00000000000000//go:build !noazurekms // +build !noazurekms package azurekms import ( "context" "crypto" "fmt" "strings" "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys" "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, the string // "azurekms". const Scheme = string(apiv1.AzureKMS) var ( valueTrue = true value2048 int32 = 2048 value3072 int32 = 3072 value4096 int32 = 4096 ) type keyType struct { Kty azkeys.JSONWebKeyType Curve azkeys.JSONWebKeyCurveName } func (k keyType) KeyType(pl apiv1.ProtectionLevel) azkeys.JSONWebKeyType { switch k.Kty { case azkeys.JSONWebKeyTypeEC: if pl == apiv1.HSM { return azkeys.JSONWebKeyTypeECHSM } return k.Kty case azkeys.JSONWebKeyTypeRSA: if pl == apiv1.HSM { k.Kty = azkeys.JSONWebKeyTypeRSAHSM } return k.Kty case azkeys.JSONWebKeyTypeECHSM, azkeys.JSONWebKeyTypeRSAHSM: return k.Kty default: return "" } } var signatureAlgorithmMapping = map[apiv1.SignatureAlgorithm]keyType{ apiv1.UnspecifiedSignAlgorithm: { Kty: azkeys.JSONWebKeyTypeEC, Curve: azkeys.JSONWebKeyCurveNameP256, }, apiv1.SHA256WithRSA: { Kty: azkeys.JSONWebKeyTypeRSA, }, apiv1.SHA384WithRSA: { Kty: azkeys.JSONWebKeyTypeRSA, }, apiv1.SHA512WithRSA: { Kty: azkeys.JSONWebKeyTypeRSA, }, apiv1.SHA256WithRSAPSS: { Kty: azkeys.JSONWebKeyTypeRSA, }, apiv1.SHA384WithRSAPSS: { Kty: azkeys.JSONWebKeyTypeRSA, }, apiv1.SHA512WithRSAPSS: { Kty: azkeys.JSONWebKeyTypeRSA, }, apiv1.ECDSAWithSHA256: { Kty: azkeys.JSONWebKeyTypeEC, Curve: azkeys.JSONWebKeyCurveNameP256, }, apiv1.ECDSAWithSHA384: { Kty: azkeys.JSONWebKeyTypeEC, Curve: azkeys.JSONWebKeyCurveNameP384, }, apiv1.ECDSAWithSHA512: { Kty: azkeys.JSONWebKeyTypeEC, Curve: azkeys.JSONWebKeyCurveNameP521, }, } // KeyVaultClient is the interface implemented by keyvault.BaseClient. It will // be used for testing purposes. type KeyVaultClient interface { GetKey(ctx context.Context, name string, version string, options *azkeys.GetKeyOptions) (azkeys.GetKeyResponse, error) CreateKey(ctx context.Context, name string, parameters azkeys.CreateKeyParameters, options *azkeys.CreateKeyOptions) (azkeys.CreateKeyResponse, error) Sign(ctx context.Context, name string, version string, parameters azkeys.SignParameters, options *azkeys.SignOptions) (azkeys.SignResponse, error) } // KeyVault implements a KMS using Azure Key Vault. // // To initialize the client we need to define a URI with the following format: // // - azurekms: // - azurekms:vault=vault-name // - azurekms:environment=env-name // - azurekms:vault=vault-name;environment=env-name // - azurekms:vault=vault-name?hsm=true // // The scheme is "azurekms"; "vault" defines the default key vault to use; // "environment" defines the Azure Cloud environment to use, options are // "public" or "AzurePublicCloud", "usgov" or "AzureUSGovernmentCloud", "china" // or "AzureChinaCloud", "german" or "AzureGermanCloud", it will default to the // public cloud if not specified; "hsm" defines if a key will be generated by an // HSM by default. // // The URI format for a key 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 // - azurekms:name=key-name;vault=vault-name // // The "name" is the key name inside the "vault"; "version" is an optional // parameter that defines the version of they key, if version is not given, the // latest one will be used; "vault" and "hsm" will override the default value if // set. The "environment" can only be set to initialize the client. type KeyVault struct { client *lazyClient defaults defaultOptions } // defaultDNSSuffix is the suffix of the Azure Public Cloud const defaultDNSSuffix = "vault.azure.net" // defaultOptions are custom options that can be passed as defaults using the // URI in apiv1.Options. type defaultOptions struct { Vault string DNSSuffix string ProtectionLevel apiv1.ProtectionLevel } var createCredentials = func(_ context.Context, opts apiv1.Options) (azcore.TokenCredential, error) { var tenantID string var clientOptions policy.ClientOptions if opts.URI != "" { u, err := uri.ParseWithScheme(Scheme, opts.URI) if err != nil { return nil, err } // The 'environment' parameter in the URI defines the Cloud environment to // be used. By default Azure Public Cloud is used. cloudConf, err := getCloudConfiguration(u.Get("environment")) if err != nil { return nil, err } // Azure Active Directory endpoint, each environment defines one, but we // allow to update it. // // Defaults to https://login.microsoftonline.com/ // // TODO(mariano): is this option still valid? if v := u.Get("aad-endpoint"); v != "" { cloudConf.ActiveDirectoryAuthorityHost = v } clientOptions.Cloud = cloudConf.Configuration // ClientSecret credential parameters. // // TenantID can also be used when using environment variables or managed // identities to initialize the credentials. clientID := u.Get("client-id") clientSecret := u.Get("client-secret") tenantID = u.Get("tenant-id") // Try to log in only using client credentials in the URI. // Client credentials requires: // - client-id // - client-secret // - tenant-id if clientID != "" && clientSecret != "" && tenantID != "" { return azidentity.NewClientSecretCredential(tenantID, clientID, clientID, &azidentity.ClientSecretCredentialOptions{ ClientOptions: clientOptions, }) } } // Attempt to authorize with the following methods: // 1. Environment credentials // - Client credentials: AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET // - Client certificate: AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_CERTIFICATE_PATH, AZURE_CLIENT_CERTIFICATE_PASSWORD (optional) // - Username and password: AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_USERNAME, AZURE_PASSWORD // 2. Workload Identity, requires the following environment variables: // - AZURE_CLIENT_ID // - AZURE_FEDERATED_TOKEN_FILE // - AZURE_TENANT_ID // - AZURE_AUTHORITY_HOST (defaults to the environment ActiveDirectoryAuthorityHost) // 3. Managed identity credentials (MSI). // 4. Azure CLI credential return azidentity.NewDefaultAzureCredential(&azidentity.DefaultAzureCredentialOptions{ ClientOptions: clientOptions, TenantID: tenantID, }) } // New initializes a new KMS implemented using Azure Key Vault. // // The URI format used to initialized the Azure Key Vault client is the // following: // // - azurekms: // - azurekms:vault=vault-name // - azurekms:vault=vault-name;environment=env-name // - azurekms:vault=vault-name?hsm=true func New(ctx context.Context, opts apiv1.Options) (*KeyVault, error) { credential, err := createCredentials(ctx, opts) if err != nil { return nil, fmt.Errorf("error creating azure credentials: %w", err) } defaults := defaultOptions{ DNSSuffix: defaultDNSSuffix, } if opts.URI != "" { u, err := uri.ParseWithScheme(Scheme, opts.URI) if err != nil { return nil, err } cloudConf, err := getCloudConfiguration(u.Get("environment")) if err != nil { return nil, err } defaults = defaultOptions{ Vault: u.Get("vault"), DNSSuffix: cloudConf.DNSSuffix, } if u.GetBool("hsm") { defaults.ProtectionLevel = apiv1.HSM } } return &KeyVault{ client: newLazyClient(defaults.DNSSuffix, lazyClientCreator(credential)), 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") } vaultURL, name, version, _, err := parseKeyName(req.Name, k.defaults) if err != nil { return nil, err } client, err := k.client.Get(vaultURL) if err != nil { return nil, err } ctx, cancel := defaultContext() defer cancel() resp, err := client.GetKey(ctx, name, version, nil) 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 } client, err := k.client.Get(vault) 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 %q", req.SignatureAlgorithm) } var keySize *int32 if kt.Kty == azkeys.JSONWebKeyTypeRSA || kt.Kty == azkeys.JSONWebKeyTypeRSAHSM { 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) } } keyType := kt.KeyType(protectionLevel) created := now() ctx, cancel := defaultContext() defer cancel() resp, err := client.CreateKey(ctx, name, azkeys.CreateKeyParameters{ Kty: &keyType, KeySize: keySize, Curve: &kt.Curve, KeyOps: []*azkeys.JSONWebKeyOperation{ pointer(azkeys.JSONWebKeyOperationSign), pointer(azkeys.JSONWebKeyOperationVerify), }, KeyAttributes: &azkeys.KeyAttributes{ Enabled: &valueTrue, Created: &created, NotBefore: &created, }, }, nil) 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.Key) 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.client, 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 } type cloudConfiguration struct { cloud.Configuration DNSSuffix string } // getCloudConfiguration returns the cloud configuration for the different // clouds. // // Note that the German configuration does not appear on the SDK. It might not // work. func getCloudConfiguration(cloudName string) (cloudConfiguration, error) { switch strings.ToUpper(cloudName) { case "", "PUBLIC", "AZURECLOUD", "AZUREPUBLICCLOUD": return cloudConfiguration{ Configuration: cloud.AzurePublic, DNSSuffix: "vault.azure.net", }, nil case "USGOV", "AZUREUSGOVERNMENT", "AZUREUSGOVERNMENTCLOUD": return cloudConfiguration{ Configuration: cloud.AzureGovernment, DNSSuffix: "vault.usgovcloudapi.net", }, nil case "CHINA", "AZURECHINACLOUD": return cloudConfiguration{ Configuration: cloud.AzureChina, DNSSuffix: "vault.azure.cn", }, nil case "GERMAN", "GERMANY", "AZUREGERMANCLOUD": return cloudConfiguration{ Configuration: cloud.Configuration{ ActiveDirectoryAuthorityHost: "https://login.microsoftonline.de/", Services: map[cloud.ServiceName]cloud.ServiceConfiguration{}, }, DNSSuffix: "vault.microsoftazure.de", }, nil default: return cloudConfiguration{}, fmt.Errorf("unknown key vault cloud environment with name %q", cloudName) } } crypto-0.57.0/kms/azurekms/key_vault_test.go000066400000000000000000000650051474156331600211670ustar00rootroot00000000000000//go:generate go run go.uber.org/mock/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/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys" "github.com/go-jose/go-jose/v3" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/kms/azurekms/internal/mock" "go.uber.org/mock/gomock" ) 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) } type fakeTokenCredential struct{} func (fakeTokenCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) { return azcore.AccessToken{}, nil } func createJWK(t *testing.T, pub crypto.PublicKey) *azkeys.JSONWebKey { t.Helper() b, err := json.Marshal(&jose.JSONWebKey{ Key: pub, }) if err != nil { t.Fatal(err) } key := new(azkeys.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 TestRegister(t *testing.T) { fn, ok := apiv1.LoadKeyManagerNewFunc(apiv1.AzureKMS) if !ok { t.Fatal("azurekms is not registered") } k, err := fn(context.Background(), apiv1.Options{ Type: "azurekms", URI: "azurekms:", }) if err != nil { t.Fatalf("New() error = %v", err) } if k == nil { t.Fatalf("New() = %v, want &KeyVault{}", k) } } func TestNew(t *testing.T) { old := createCredentials t.Cleanup(func() { createCredentials = old }) type args struct { ctx context.Context opts apiv1.Options } tests := []struct { name string setup func() args args want *KeyVault wantErr bool }{ {"ok", func() { createCredentials = func(ctx context.Context, opts apiv1.Options) (azcore.TokenCredential, error) { return fakeTokenCredential{}, nil } }, args{context.Background(), apiv1.Options{}}, &KeyVault{ client: newLazyClient("vault.azure.net", lazyClientCreator(fakeTokenCredential{})), defaults: defaultOptions{ DNSSuffix: "vault.azure.net", }, }, false}, {"ok with vault", func() { createCredentials = func(ctx context.Context, opts apiv1.Options) (azcore.TokenCredential, error) { return fakeTokenCredential{}, nil } }, args{context.Background(), apiv1.Options{ URI: "azurekms:vault=my-vault", }}, &KeyVault{ client: newLazyClient("vault.azure.net", lazyClientCreator(fakeTokenCredential{})), defaults: defaultOptions{ Vault: "my-vault", DNSSuffix: "vault.azure.net", ProtectionLevel: apiv1.UnspecifiedProtectionLevel, }, }, false}, {"ok with vault + hsm", func() { createCredentials = func(ctx context.Context, opts apiv1.Options) (azcore.TokenCredential, error) { return fakeTokenCredential{}, nil } }, args{context.Background(), apiv1.Options{ URI: "azurekms:vault=my-vault;hsm=true", }}, &KeyVault{ client: newLazyClient("vault.azure.net", lazyClientCreator(fakeTokenCredential{})), defaults: defaultOptions{ Vault: "my-vault", DNSSuffix: "vault.azure.net", ProtectionLevel: apiv1.HSM, }, }, false}, {"ok with vault + environment", func() { createCredentials = func(ctx context.Context, opts apiv1.Options) (azcore.TokenCredential, error) { return fakeTokenCredential{}, nil } }, args{context.Background(), apiv1.Options{ URI: "azurekms:vault=my-vault;environment=usgov", }}, &KeyVault{ client: newLazyClient("vault.usgovcloudapi.net", lazyClientCreator(fakeTokenCredential{})), defaults: defaultOptions{ Vault: "my-vault", DNSSuffix: "vault.usgovcloudapi.net", ProtectionLevel: apiv1.UnspecifiedProtectionLevel, }, }, false}, {"fail", func() { createCredentials = func(ctx context.Context, opts apiv1.Options) (azcore.TokenCredential, error) { return nil, errTest } }, args{context.Background(), apiv1.Options{}}, nil, true}, {"fail uri schema", func() { createCredentials = func(ctx context.Context, opts apiv1.Options) (azcore.TokenCredential, error) { return fakeTokenCredential{}, nil } }, args{context.Background(), apiv1.Options{ URI: "kms:vault=my-vault;hsm=true", }}, nil, true}, {"fail uri environment", func() { createCredentials = func(ctx context.Context, opts apiv1.Options) (azcore.TokenCredential, error) { return fakeTokenCredential{}, nil } }, args{context.Background(), apiv1.Options{ URI: "azurekms:environment=bad-one", }}, 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 tt.want != nil && got != nil { got.client = tt.want.client } if !reflect.DeepEqual(got, tt.want) { t.Errorf("New() = %v, want %v", got, tt.want) } }) } } func TestKeyVault_createCredentials(t *testing.T) { type args struct { ctx context.Context opts apiv1.Options } tests := []struct { name string args args wantErr bool }{ {"ok", args{context.Background(), apiv1.Options{}}, false}, {"ok with uri", args{context.Background(), apiv1.Options{ URI: "azurekms:client-id=id;client-secret=secret;tenant-id=id", }}, false}, {"ok with uri+aad", args{context.Background(), apiv1.Options{ URI: "azurekms:client-id=id;client-secret=secret;tenant-id=id;aad-endpoint=https%3A%2F%2Flogin.microsoftonline.us%2F", }}, false}, {"ok with uri+environment", args{context.Background(), apiv1.Options{ URI: "azurekms:client-id=id;client-secret=secret;tenant-id=id;environment=usgov", }}, false}, {"ok with uri no config", args{context.Background(), apiv1.Options{ URI: "azurekms:", }}, false}, {"fail uri", args{context.Background(), apiv1.Options{ URI: "kms:client-id=id;client-secret=secret;tenant-id=id", }}, true}, {"ok bad environment", args{context.Background(), apiv1.Options{ URI: "azurekms:client-id=id;client-secret=secret;tenant-id=id;environment=fake", }}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := createCredentials(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) m := mockClient(t) m.EXPECT().GetKey(gomock.Any(), "my-key", "", nil).Return(azkeys.GetKeyResponse{ KeyBundle: azkeys.KeyBundle{Key: jwk}, }, nil) m.EXPECT().GetKey(gomock.Any(), "my-key", "my-version", nil).Return(azkeys.GetKeyResponse{ KeyBundle: azkeys.KeyBundle{Key: jwk}, }, nil) m.EXPECT().GetKey(gomock.Any(), "my-key", "my-version", nil).Return(azkeys.GetKeyResponse{ KeyBundle: azkeys.KeyBundle{Key: jwk}, }, nil) m.EXPECT().GetKey(gomock.Any(), "not-found", "my-version", nil).Return(azkeys.GetKeyResponse{}, errTest) client := newLazyClient("vault.azure.net", func(vaultURL string) (KeyVaultClient, error) { if vaultURL == "https://fail.vault.azure.net/" { return nil, errTest } return m, nil }) type fields struct { client *lazyClient defaults defaultOptions } type args struct { req *apiv1.GetPublicKeyRequest } tests := []struct { name string fields fields args args want crypto.PublicKey wantErr bool }{ {"ok", fields{client, defaultOptions{}}, args{&apiv1.GetPublicKeyRequest{ Name: "azurekms:vault=my-vault;name=my-key", }}, pub, false}, {"ok with version", fields{client, defaultOptions{}}, args{&apiv1.GetPublicKeyRequest{ Name: "azurekms:vault=my-vault;name=my-key?version=my-version", }}, pub, false}, {"ok with options", fields{client, defaultOptions{DNSSuffix: "vault.usgovcloudapi.net"}}, args{&apiv1.GetPublicKeyRequest{ Name: "azurekms:vault=my-vault;name=my-key?version=my-version", }}, pub, false}, {"fail GetKey", fields{client, defaultOptions{}}, args{&apiv1.GetPublicKeyRequest{ Name: "azurekms:vault=my-vault;name=not-found?version=my-version", }}, nil, true}, {"fail empty", fields{client, defaultOptions{}}, args{&apiv1.GetPublicKeyRequest{ Name: "", }}, nil, true}, {"fail vault", fields{client, defaultOptions{}}, args{&apiv1.GetPublicKeyRequest{ Name: "azurekms:vault=;name=not-found?version=my-version", }}, nil, true}, {"fail id", fields{client, defaultOptions{}}, args{&apiv1.GetPublicKeyRequest{ Name: "azurekms:vault=;name=?version=my-version", }}, nil, true}, {"fail get client", fields{client, defaultOptions{}}, args{&apiv1.GetPublicKeyRequest{ Name: "azurekms:vault=fail;name=my-key", }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &KeyVault{ client: tt.fields.client, defaults: tt.fields.defaults, } 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) expects := []struct { Name string Kty azkeys.JSONWebKeyType KeySize *int32 Curve azkeys.JSONWebKeyCurveName Key *azkeys.JSONWebKey }{ {"P-256", azkeys.JSONWebKeyTypeEC, nil, azkeys.JSONWebKeyCurveNameP256, ecJWK}, {"P-256 HSM", azkeys.JSONWebKeyTypeECHSM, nil, azkeys.JSONWebKeyCurveNameP256, ecJWK}, {"P-256 HSM (uri)", azkeys.JSONWebKeyTypeECHSM, nil, azkeys.JSONWebKeyCurveNameP256, ecJWK}, {"P-256 Default", azkeys.JSONWebKeyTypeEC, nil, azkeys.JSONWebKeyCurveNameP256, ecJWK}, {"P-384", azkeys.JSONWebKeyTypeEC, nil, azkeys.JSONWebKeyCurveNameP384, ecJWK}, {"P-521", azkeys.JSONWebKeyTypeEC, nil, azkeys.JSONWebKeyCurveNameP521, ecJWK}, {"RSA 0", azkeys.JSONWebKeyTypeRSA, &value3072, "", rsaJWK}, {"RSA 0 HSM", azkeys.JSONWebKeyTypeRSAHSM, &value3072, "", rsaJWK}, {"RSA 0 HSM (uri)", azkeys.JSONWebKeyTypeRSAHSM, &value3072, "", rsaJWK}, {"RSA 2048", azkeys.JSONWebKeyTypeRSA, &value2048, "", rsaJWK}, {"RSA 3072", azkeys.JSONWebKeyTypeRSA, &value3072, "", rsaJWK}, {"RSA 4096", azkeys.JSONWebKeyTypeRSA, &value4096, "", rsaJWK}, } t0 := mockNow(t) m := mockClient(t) for _, e := range expects { m.EXPECT().CreateKey(gomock.Any(), "my-key", azkeys.CreateKeyParameters{ Kty: pointer(e.Kty), KeySize: e.KeySize, Curve: pointer(e.Curve), KeyOps: []*azkeys.JSONWebKeyOperation{ pointer(azkeys.JSONWebKeyOperationSign), pointer(azkeys.JSONWebKeyOperationVerify), }, KeyAttributes: &azkeys.KeyAttributes{ Enabled: &valueTrue, Created: &t0, NotBefore: &t0, }, }, nil).Return(azkeys.CreateKeyResponse{ KeyBundle: azkeys.KeyBundle{Key: e.Key}, }, nil) } m.EXPECT().CreateKey(gomock.Any(), "not-found", gomock.Any(), nil).Return(azkeys.CreateKeyResponse{}, errTest) m.EXPECT().CreateKey(gomock.Any(), "not-found", gomock.Any(), nil).Return(azkeys.CreateKeyResponse{ KeyBundle: azkeys.KeyBundle{Key: nil}, }, nil) client := newLazyClient("vault.azure.net", func(vaultURL string) (KeyVaultClient, error) { if vaultURL == "https://fail.vault.azure.net/" { return nil, errTest } return m, nil }) type fields struct { client *lazyClient defaults defaultOptions } type args struct { req *apiv1.CreateKeyRequest } tests := []struct { name string fields fields args args want *apiv1.CreateKeyResponse wantErr bool }{ {"ok P-256", fields{client, defaultOptions{}}, 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, defaultOptions{}}, 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, defaultOptions{}}, 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, defaultOptions{}}, 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, defaultOptions{}}, 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, defaultOptions{}}, 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, defaultOptions{}}, 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, defaultOptions{}}, 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, defaultOptions{}}, 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, defaultOptions{}}, 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, defaultOptions{}}, 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, defaultOptions{}}, 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, defaultOptions{}}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=my-vault;name=not-found", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }}, nil, true}, {"fail convertKey", fields{client, defaultOptions{}}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=my-vault;name=not-found", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }}, nil, true}, {"fail name", fields{client, defaultOptions{}}, args{&apiv1.CreateKeyRequest{ Name: "", }}, nil, true}, {"fail vault", fields{client, defaultOptions{}}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=;name=not-found?version=my-version", }}, nil, true}, {"fail id", fields{client, defaultOptions{}}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=my-vault;name=?version=my-version", }}, nil, true}, {"fail get client", fields{client, defaultOptions{}}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=fail;name=my-key", SignatureAlgorithm: apiv1.ECDSAWithSHA256, ProtectionLevel: apiv1.Software, }}, nil, true}, {"fail SignatureAlgorithm", fields{client, defaultOptions{}}, args{&apiv1.CreateKeyRequest{ Name: "azurekms:vault=my-vault;name=not-found", SignatureAlgorithm: apiv1.PureEd25519, }}, nil, true}, {"fail bit size", fields{client, defaultOptions{}}, 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{ client: tt.fields.client, defaults: tt.fields.defaults, } 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) m := mockClient(t) m.EXPECT().GetKey(gomock.Any(), "my-key", "", nil).Return(azkeys.GetKeyResponse{ KeyBundle: azkeys.KeyBundle{Key: jwk}, }, nil) m.EXPECT().GetKey(gomock.Any(), "my-key", "my-version", nil).Return(azkeys.GetKeyResponse{ KeyBundle: azkeys.KeyBundle{Key: jwk}, }, nil) m.EXPECT().GetKey(gomock.Any(), "not-found", "my-version", nil).Return(azkeys.GetKeyResponse{}, errTest) client := newLazyClient("vault.azure.net", func(vaultURL string) (KeyVaultClient, error) { return m, nil }) type fields struct { client *lazyClient } 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: m, 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: m, 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{ client: tt.fields.client, } 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) { m := mockClient(t) client := newLazyClient("vault.azure.net", func(vaultURL string) (KeyVaultClient, error) { return m, nil }) type fields struct { client *lazyClient } 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{ client: tt.fields.client, } 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 azkeys.JSONWebKeyType Curve azkeys.JSONWebKeyCurveName } type args struct { pl apiv1.ProtectionLevel } tests := []struct { name string fields fields args args want azkeys.JSONWebKeyType }{ {"ec", fields{azkeys.JSONWebKeyTypeEC, azkeys.JSONWebKeyCurveNameP256}, args{apiv1.UnspecifiedProtectionLevel}, azkeys.JSONWebKeyTypeEC}, {"ec software", fields{azkeys.JSONWebKeyTypeEC, azkeys.JSONWebKeyCurveNameP384}, args{apiv1.Software}, azkeys.JSONWebKeyTypeEC}, {"ec hsm", fields{azkeys.JSONWebKeyTypeEC, azkeys.JSONWebKeyCurveNameP521}, args{apiv1.HSM}, azkeys.JSONWebKeyTypeECHSM}, {"ec hsm type", fields{azkeys.JSONWebKeyTypeECHSM, azkeys.JSONWebKeyCurveNameP521}, args{apiv1.UnspecifiedProtectionLevel}, azkeys.JSONWebKeyTypeECHSM}, {"rsa", fields{azkeys.JSONWebKeyTypeRSA, azkeys.JSONWebKeyCurveNameP256}, args{apiv1.UnspecifiedProtectionLevel}, azkeys.JSONWebKeyTypeRSA}, {"rsa software", fields{azkeys.JSONWebKeyTypeRSA, ""}, args{apiv1.Software}, azkeys.JSONWebKeyTypeRSA}, {"rsa hsm", fields{azkeys.JSONWebKeyTypeRSA, ""}, args{apiv1.HSM}, azkeys.JSONWebKeyTypeRSAHSM}, {"rsa hsm type", fields{azkeys.JSONWebKeyTypeRSAHSM, ""}, args{apiv1.UnspecifiedProtectionLevel}, azkeys.JSONWebKeyTypeRSAHSM}, {"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) } }) } } func Test_getCloudConfiguration(t *testing.T) { germanCloud := cloud.Configuration{ ActiveDirectoryAuthorityHost: "https://login.microsoftonline.de/", Services: map[cloud.ServiceName]cloud.ServiceConfiguration{}, } type args struct { name string } tests := []struct { name string args args want cloudConfiguration wantErr bool }{ {"empty", args{""}, cloudConfiguration{Configuration: cloud.AzurePublic, DNSSuffix: "vault.azure.net"}, false}, {"public", args{"public"}, cloudConfiguration{Configuration: cloud.AzurePublic, DNSSuffix: "vault.azure.net"}, false}, {"USGov", args{"USGov"}, cloudConfiguration{Configuration: cloud.AzureGovernment, DNSSuffix: "vault.usgovcloudapi.net"}, false}, {"China", args{"China"}, cloudConfiguration{Configuration: cloud.AzureChina, DNSSuffix: "vault.azure.cn"}, false}, {"GERMAN", args{"GERMAN"}, cloudConfiguration{Configuration: germanCloud, DNSSuffix: "vault.microsoftazure.de"}, false}, {"AzurePublicCloud", args{"AzurePublicCloud"}, cloudConfiguration{Configuration: cloud.AzurePublic, DNSSuffix: "vault.azure.net"}, false}, {"AzureUSGovernmentCloud", args{"AzureUSGovernmentCloud"}, cloudConfiguration{Configuration: cloud.AzureGovernment, DNSSuffix: "vault.usgovcloudapi.net"}, false}, {"AzureChinaCloud", args{"AzureChinaCloud"}, cloudConfiguration{Configuration: cloud.AzureChina, DNSSuffix: "vault.azure.cn"}, false}, {"AzureGermanCloud", args{"AzureGermanCloud"}, cloudConfiguration{Configuration: germanCloud, DNSSuffix: "vault.microsoftazure.de"}, false}, {"fake", args{"fake"}, cloudConfiguration{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := getCloudConfiguration(tt.args.name) if (err != nil) != tt.wantErr { t.Errorf("getCloudConfiguration() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("getCloudConfiguration() = %v, want %v", got, tt.want) } }) } } crypto-0.57.0/kms/azurekms/lazy_client.go000066400000000000000000000026561474156331600204450ustar00rootroot00000000000000//go:build !noazurekms // +build !noazurekms package azurekms import ( "fmt" "sync" "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys" ) type lazyClientFunc func(vaultURL string) (KeyVaultClient, error) type lazyClient struct { rw sync.RWMutex clients map[string]KeyVaultClient new lazyClientFunc dnsSuffix string } func newLazyClient(dnsSuffix string, fn lazyClientFunc) *lazyClient { return &lazyClient{ clients: make(map[string]KeyVaultClient), new: fn, dnsSuffix: dnsSuffix, } } func (l *lazyClient) Get(vault string) (KeyVaultClient, error) { vaultURL := vaultBaseURL(vault, l.dnsSuffix) // Get an already initialize client l.rw.RLock() c, ok := l.clients[vaultURL] l.rw.RUnlock() if ok { return c, nil } // Create a new client c, err := l.new(vaultURL) if err != nil { return nil, fmt.Errorf("error creating client for vault %q: %w", vaultURL, err) } l.rw.Lock() l.clients[vaultURL] = c l.rw.Unlock() return c, nil } func lazyClientCreator(credential azcore.TokenCredential) lazyClientFunc { return func(vaultURL string) (KeyVaultClient, error) { return azkeys.NewClient(vaultURL, credential, &azkeys.ClientOptions{ // See https://aka.ms/azsdk/blog/vault-uri DisableChallengeResourceVerification: true, }) } } func vaultBaseURL(vault, dnsSuffix string) string { return "https://" + vault + "." + dnsSuffix + "/" } crypto-0.57.0/kms/azurekms/lazy_client_test.go000066400000000000000000000035421474156331600214770ustar00rootroot00000000000000//go:build !noazurekms // +build !noazurekms package azurekms import ( "reflect" "testing" "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys" ) func Test_lazyClient_Get(t *testing.T) { client := mockClient(t) type fields struct { clients map[string]KeyVaultClient new lazyClientFunc dnsSuffix string } type args struct { vault string } tests := []struct { name string fields fields args args want KeyVaultClient wantErr bool }{ {"ok", fields{map[string]KeyVaultClient{ "https://test.vault.azure.net/": client, }, func(vaultURL string) (KeyVaultClient, error) { t.Error("call to new should not happen") return client, nil }, "vault.azure.net"}, args{"test"}, client, false}, {"ok new", fields{map[string]KeyVaultClient{}, func(vaultURL string) (KeyVaultClient, error) { return client, nil }, "vault.azure.net"}, args{"test"}, client, false}, {"fail", fields{map[string]KeyVaultClient{}, func(vaultURL string) (KeyVaultClient, error) { return nil, errTest }, "vault.azure.net"}, args{"test"}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { l := &lazyClient{ clients: tt.fields.clients, new: tt.fields.new, dnsSuffix: tt.fields.dnsSuffix, } got, err := l.Get(tt.args.vault) if (err != nil) != tt.wantErr { t.Errorf("lazyClient.Get() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("lazyClient.Get() = %v, want %v", got, tt.want) } }) } } func Test_lazyClientCreator(t *testing.T) { fn := lazyClientCreator(fakeTokenCredential{}) client, err := fn("https://test.vault.azure.net") if err != nil { t.Errorf("lazyClientCreator() error = %v", err) } if _, ok := client.(*azkeys.Client); !ok { t.Errorf("lazyClientCreator() = %T, want *azkeys.Client", client) } } crypto-0.57.0/kms/azurekms/no_azurekms.go000066400000000000000000000006661474156331600204640ustar00rootroot00000000000000//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) }) } crypto-0.57.0/kms/azurekms/signer.go000066400000000000000000000112761474156331600174150ustar00rootroot00000000000000//go:build !noazurekms // +build !noazurekms package azurekms import ( "crypto" "crypto/ecdsa" "crypto/rsa" "io" "math/big" "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys" "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 name string version string publicKey crypto.PublicKey } // NewSigner creates a new signer using a key in the AWS KMS. func NewSigner(lazyClient *lazyClient, signingKey string, defaults defaultOptions) (crypto.Signer, error) { vaultURL, name, version, _, err := parseKeyName(signingKey, defaults) if err != nil { return nil, err } client, err := lazyClient.Get(vaultURL) if err != nil { return nil, err } // Make sure that the key exists. signer := &Signer{ client: client, 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.name, s.version, nil) 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 Azure Key Vault. func (s *Signer) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { alg, err := getSigningAlgorithm(s.Public(), opts) if err != nil { return nil, err } // Sign with retry if the key is not ready resp, err := s.signWithRetry(alg, digest, 3) if err != nil { return nil, errors.Wrap(err, "keyVault Sign failed") } var octetSize int switch alg { case azkeys.JSONWebKeySignatureAlgorithmES256: octetSize = 32 // 256-bit, concat(R,S) = 64 bytes case azkeys.JSONWebKeySignatureAlgorithmES384: octetSize = 48 // 384-bit, concat(R,S) = 96 bytes case azkeys.JSONWebKeySignatureAlgorithmES512: octetSize = 66 // 528-bit, concat(R,S) = 132 bytes default: return resp.Result, nil } // Convert to asn1 if len(resp.Result) != 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(resp.Result[:octetSize])) // R b.AddASN1BigInt(new(big.Int).SetBytes(resp.Result[octetSize:])) // S }) return b.Bytes() } func (s *Signer) signWithRetry(alg azkeys.JSONWebKeySignatureAlgorithm, digest []byte, retryAttempts int) (azkeys.SignResponse, error) { retry: ctx, cancel := defaultContext() defer cancel() resp, err := s.client.Sign(ctx, s.name, s.version, azkeys.SignParameters{ Algorithm: &alg, Value: digest, }, nil) if err != nil && retryAttempts > 0 { var responseError *azcore.ResponseError if errors.As(err, &responseError) { if responseError.StatusCode == 429 { time.Sleep(time.Second / time.Duration(retryAttempts)) retryAttempts-- goto retry } } } return resp, err } func getSigningAlgorithm(key crypto.PublicKey, opts crypto.SignerOpts) (azkeys.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 azkeys.JSONWebKeySignatureAlgorithmPS256, nil } return azkeys.JSONWebKeySignatureAlgorithmRS256, nil case crypto.SHA384: if isPSS { return azkeys.JSONWebKeySignatureAlgorithmPS384, nil } return azkeys.JSONWebKeySignatureAlgorithmRS384, nil case crypto.SHA512: if isPSS { return azkeys.JSONWebKeySignatureAlgorithmPS512, nil } return azkeys.JSONWebKeySignatureAlgorithmRS512, nil default: return "", errors.Errorf("unsupported hash function %v", h) } case *ecdsa.PublicKey: switch h := opts.HashFunc(); h { case crypto.SHA256: return azkeys.JSONWebKeySignatureAlgorithmES256, nil case crypto.SHA384: return azkeys.JSONWebKeySignatureAlgorithmES384, nil case crypto.SHA512: return azkeys.JSONWebKeySignatureAlgorithmES512, nil default: return "", errors.Errorf("unsupported hash function %v", h) } default: return "", errors.Errorf("unsupported key type %T", key) } } crypto-0.57.0/kms/azurekms/signer_test.go000066400000000000000000000400331474156331600204450ustar00rootroot00000000000000package azurekms import ( "bytes" "crypto" "crypto/ecdsa" "crypto/rand" "crypto/rsa" "io" "reflect" "testing" "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/kms/apiv1" "go.uber.org/mock/gomock" "golang.org/x/crypto/cryptobyte" "golang.org/x/crypto/cryptobyte/asn1" ) type FuncMatcher func(x interface{}) bool func (f FuncMatcher) Matches(x interface{}) bool { return f(x) } func (FuncMatcher) String() string { return "matches using a function" } func TestNewSigner(t *testing.T) { key, err := keyutil.GenerateDefaultSigner() if err != nil { t.Fatal(err) } pub := key.Public() jwk := createJWK(t, pub) m := mockClient(t) m.EXPECT().GetKey(gomock.Any(), "my-key", "", nil).Return(azkeys.GetKeyResponse{ KeyBundle: azkeys.KeyBundle{ Key: jwk, }, }, nil) m.EXPECT().GetKey(gomock.Any(), "my-key", "my-version", nil).Return(azkeys.GetKeyResponse{ KeyBundle: azkeys.KeyBundle{ Key: jwk, }, }, nil) m.EXPECT().GetKey(gomock.Any(), "my-key", "my-version", nil).Return(azkeys.GetKeyResponse{ KeyBundle: azkeys.KeyBundle{ Key: jwk, }, }, nil) m.EXPECT().GetKey(gomock.Any(), "not-found", "my-version", nil).Return(azkeys.GetKeyResponse{}, errTest) client := newLazyClient("vault.azure.net", func(vaultURL string) (KeyVaultClient, error) { if vaultURL == "https://fail.vault.azure.net/" { return nil, errTest } return m, nil }) var noOptions defaultOptions type args struct { client *lazyClient 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: m, 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: m, 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: m, 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 get client", args{client, "azurekms:vault=fail;name=my-key", 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, []byte, []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), 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 azkeys.JSONWebKeySignatureAlgorithm digest []byte result azkeys.SignResponse err error }{ {"P-256", "", azkeys.JSONWebKeySignatureAlgorithmES256, p256Digest, azkeys.SignResponse{ KeyOperationResult: azkeys.KeyOperationResult{Result: p256ResultSig}, }, nil}, {"P-384", "my-version", azkeys.JSONWebKeySignatureAlgorithmES384, p384Digest, azkeys.SignResponse{ KeyOperationResult: azkeys.KeyOperationResult{Result: p386ResultSig}, }, nil}, {"P-521", "my-version", azkeys.JSONWebKeySignatureAlgorithmES512, p521Digest, azkeys.SignResponse{ KeyOperationResult: azkeys.KeyOperationResult{Result: p521ResultSig}, }, nil}, {"RSA SHA256", "", azkeys.JSONWebKeySignatureAlgorithmRS256, rsaSHA256Digest, azkeys.SignResponse{ KeyOperationResult: azkeys.KeyOperationResult{Result: rsaSHA256ResultSig}, }, nil}, {"RSA SHA384", "", azkeys.JSONWebKeySignatureAlgorithmRS384, rsaSHA384Digest, azkeys.SignResponse{ KeyOperationResult: azkeys.KeyOperationResult{Result: rsaSHA384ResultSig}, }, nil}, {"RSA SHA512", "", azkeys.JSONWebKeySignatureAlgorithmRS512, rsaSHA512Digest, azkeys.SignResponse{ KeyOperationResult: azkeys.KeyOperationResult{Result: rsaSHA512ResultSig}, }, nil}, {"RSA-PSS SHA256", "", azkeys.JSONWebKeySignatureAlgorithmPS256, rsaPSSSHA256Digest, azkeys.SignResponse{ KeyOperationResult: azkeys.KeyOperationResult{Result: rsaPSSSHA256ResultSig}, }, nil}, {"RSA-PSS SHA384", "", azkeys.JSONWebKeySignatureAlgorithmPS384, rsaPSSSHA384Digest, azkeys.SignResponse{ KeyOperationResult: azkeys.KeyOperationResult{Result: rsaPSSSHA384ResultSig}, }, nil}, {"RSA-PSS SHA512", "", azkeys.JSONWebKeySignatureAlgorithmPS512, rsaPSSSHA512Digest, azkeys.SignResponse{ KeyOperationResult: azkeys.KeyOperationResult{Result: rsaPSSSHA512ResultSig}, }, nil}, // Errors {"fail Sign", "", azkeys.JSONWebKeySignatureAlgorithmRS256, rsaSHA256Digest, azkeys.SignResponse{}, errTest}, {"fail sign length", "", azkeys.JSONWebKeySignatureAlgorithmES256, p256Digest, azkeys.SignResponse{ KeyOperationResult: azkeys.KeyOperationResult{Result: rsaSHA256ResultSig}, }, nil}, {"fail base64", "", azkeys.JSONWebKeySignatureAlgorithmES256, p256Digest, azkeys.SignResponse{ KeyOperationResult: azkeys.KeyOperationResult{Result: func() []byte { return []byte("๐Ÿ˜Ž") }()}, }, nil}, } for _, e := range expects { ee := e client.EXPECT().Sign(gomock.Any(), "my-key", e.keyVersion, FuncMatcher(func(x interface{}) bool { p, ok := x.(azkeys.SignParameters) return ok && *p.Algorithm == ee.alg && bytes.Equal(p.Value, ee.digest) }), nil).Return(e.result, e.err) } type fields struct { client KeyVaultClient 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, "my-key", "", p256}, args{ rand.Reader, p256Digest, crypto.SHA256, }, p256Sig, false}, {"ok P-384", fields{client, "my-key", "my-version", p384}, args{ rand.Reader, p384Digest, crypto.SHA384, }, p384Sig, false}, {"ok P-521", fields{client, "my-key", "my-version", p521}, args{ rand.Reader, p521Digest, crypto.SHA512, }, p521Sig, false}, {"ok RSA SHA256", fields{client, "my-key", "", rsaSHA256}, args{ rand.Reader, rsaSHA256Digest, crypto.SHA256, }, rsaSHA256Sig, false}, {"ok RSA SHA384", fields{client, "my-key", "", rsaSHA384}, args{ rand.Reader, rsaSHA384Digest, crypto.SHA384, }, rsaSHA384Sig, false}, {"ok RSA SHA512", fields{client, "my-key", "", rsaSHA512}, args{ rand.Reader, rsaSHA512Digest, crypto.SHA512, }, rsaSHA512Sig, false}, {"ok RSA-PSS SHA256", fields{client, "my-key", "", rsaPSSSHA256}, args{ rand.Reader, rsaPSSSHA256Digest, &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthAuto, Hash: crypto.SHA256, }, }, rsaPSSSHA256Sig, false}, {"ok RSA-PSS SHA384", fields{client, "my-key", "", rsaPSSSHA384}, args{ rand.Reader, rsaPSSSHA384Digest, &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: crypto.SHA384, }, }, rsaPSSSHA384Sig, false}, {"ok RSA-PSS SHA512", fields{client, "my-key", "", rsaPSSSHA512}, args{ rand.Reader, rsaPSSSHA512Digest, &rsa.PSSOptions{ SaltLength: 64, Hash: crypto.SHA512, }, }, rsaPSSSHA512Sig, false}, {"fail Sign", fields{client, "my-key", "", rsaSHA256}, args{ rand.Reader, rsaSHA256Digest, crypto.SHA256, }, nil, true}, {"fail sign length", fields{client, "my-key", "", p256}, args{ rand.Reader, p256Digest, crypto.SHA256, }, nil, true}, {"fail base64", fields{client, "my-key", "", p256}, args{ rand.Reader, p256Digest, crypto.SHA256, }, nil, true}, {"fail RSA-PSS salt length", fields{client, "my-key", "", rsaPSSSHA256}, args{ rand.Reader, rsaPSSSHA256Digest, &rsa.PSSOptions{ SaltLength: 64, Hash: crypto.SHA256, }, }, nil, true}, {"fail RSA Hash", fields{client, "my-key", "", rsaSHA256}, args{ rand.Reader, rsaSHA256Digest, crypto.SHA1, }, nil, true}, {"fail ECDSA Hash", fields{client, "my-key", "", p256}, args{ rand.Reader, p256Digest, crypto.MD5, }, nil, true}, {"fail Ed25519", fields{client, "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, 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, []byte, []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), resultSig, sig } p256, p256Digest, p256ResultSig, p256Sig := sign("EC", "P-256", 0, crypto.SHA256) okResult := azkeys.SignResponse{ KeyOperationResult: azkeys.KeyOperationResult{Result: p256ResultSig}, } failResult := azkeys.SignResponse{} retryError := &azcore.ResponseError{StatusCode: 429} client := mockClient(t) expects := []struct { name string keyVersion string alg azkeys.JSONWebKeySignatureAlgorithm digest []byte result azkeys.SignResponse err error }{ {"ok 1", "", azkeys.JSONWebKeySignatureAlgorithmES256, p256Digest, failResult, retryError}, {"ok 2", "", azkeys.JSONWebKeySignatureAlgorithmES256, p256Digest, failResult, retryError}, {"ok 3", "", azkeys.JSONWebKeySignatureAlgorithmES256, p256Digest, failResult, retryError}, {"ok 4", "", azkeys.JSONWebKeySignatureAlgorithmES256, p256Digest, okResult, nil}, {"fail", "fail-version", azkeys.JSONWebKeySignatureAlgorithmES256, p256Digest, failResult, retryError}, {"fail", "fail-version", azkeys.JSONWebKeySignatureAlgorithmES256, p256Digest, failResult, retryError}, {"fail", "fail-version", azkeys.JSONWebKeySignatureAlgorithmES256, p256Digest, failResult, retryError}, {"fail", "fail-version", azkeys.JSONWebKeySignatureAlgorithmES256, p256Digest, failResult, retryError}, } for _, e := range expects { ee := e client.EXPECT().Sign(gomock.Any(), "my-key", e.keyVersion, FuncMatcher(func(x interface{}) bool { p, ok := x.(azkeys.SignParameters) return ok && *p.Algorithm == ee.alg && bytes.Equal(p.Value, ee.digest) }), nil).Return(e.result, e.err) } type fields struct { client KeyVaultClient 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, "my-key", "", p256}, args{ rand.Reader, p256Digest, crypto.SHA256, }, p256Sig, false}, {"fail", fields{client, "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, 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) } }) } } crypto-0.57.0/kms/azurekms/utils.go000066400000000000000000000112411474156331600172560ustar00rootroot00000000000000//go:build !noazurekms // +build !noazurekms package azurekms import ( "context" "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" "fmt" "math/big" "net/url" "strings" "time" "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys" "github.com/pkg/errors" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/kms/uri" ) var now = func() time.Time { return time.Now().UTC() } // pointer returns the pointer of v. func pointer[T any](v T) *T { return &v } // 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, key *azkeys.JSONWebKey) string { if key != nil && key.KID != nil { if u, err := url.Parse(string(*key.KID)); err == nil { host := strings.SplitN(u.Host, ".", 2) path := strings.Split(u.Path, "/") if len(host) == 2 && len(path) == 4 { values := url.Values{ "vault": []string{host[0]}, "name": []string{path[2]}, } uu := uri.New(Scheme, values) uu.RawQuery = url.Values{"version": []string{path[3]}}.Encode() return uu.String() } } } // Fallback to URI without version id. // This will return the latest version of the key. values := url.Values{ "vault": []string{vault}, "name": []string{name}, } return uri.New(Scheme, values).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 %q is not valid: name is missing", rawURI) return } if vault = u.Get("vault"); vault == "" { if defaults.Vault == "" { name = "" err = errors.Errorf("key uri %q 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 convertKey(key *azkeys.JSONWebKey) (crypto.PublicKey, error) { if key == nil || key.Kty == nil { return nil, errors.New("invalid key: missing kty value") } switch *key.Kty { case azkeys.JSONWebKeyTypeEC, azkeys.JSONWebKeyTypeECHSM: return ecPublicKey(key.Crv, key.X, key.Y) case azkeys.JSONWebKeyTypeRSA, azkeys.JSONWebKeyTypeRSAHSM: return rsaPublicKey(key.N, key.E) case azkeys.JSONWebKeyTypeOct, azkeys.JSONWebKeyTypeOctHSM: return octPublicKey(key.K) default: return nil, fmt.Errorf("invalid key: unsupported kty %q", *key.Kty) } } func ecPublicKey(crv *azkeys.JSONWebKeyCurveName, x, y []byte) (crypto.PublicKey, error) { if crv == nil { return nil, errors.New("invalid EC key: missing crv value") } if len(x) == 0 || len(y) == 0 { return nil, errors.New("invalid EC key: missing x or y values") } var curve elliptic.Curve var curveSize int switch *crv { case azkeys.JSONWebKeyCurveNameP256: curve = elliptic.P256() curveSize = 32 case azkeys.JSONWebKeyCurveNameP384: curve = elliptic.P384() curveSize = 48 case azkeys.JSONWebKeyCurveNameP521: curve = elliptic.P521() curveSize = 66 // (521/8 + 1) case azkeys.JSONWebKeyCurveNameP256K: return nil, fmt.Errorf(`invalid EC key: crv %q is not supported`, *crv) default: return nil, fmt.Errorf("invalid EC key: crv %q is not supported", *crv) } if len(x) != curveSize || len(y) != curveSize { return nil, errors.New("invalid EC key: x or y length is not valid") } key := &ecdsa.PublicKey{ Curve: curve, X: new(big.Int).SetBytes(x), Y: new(big.Int).SetBytes(y), } if !curve.IsOnCurve(key.X, key.Y) { return nil, errors.New("invalid EC key: point (x, y) does not lie on the curve") } return key, nil } func rsaPublicKey(n, e []byte) (crypto.PublicKey, error) { if len(n) == 0 || len(e) == 0 { return nil, errors.New("invalid RSA key: missing n or e values") } return &rsa.PublicKey{ N: new(big.Int).SetBytes(n), E: int(new(big.Int).SetBytes(e).Int64()), }, nil } func octPublicKey(k []byte) (crypto.PublicKey, error) { if k == nil { return nil, errors.New("invalid oct key: missing k value") } return k, nil } crypto-0.57.0/kms/azurekms/utils_test.go000066400000000000000000000232011474156331600203140ustar00rootroot00000000000000package azurekms import ( "bytes" "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "encoding/binary" "math/big" "reflect" "testing" "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys" "go.step.sm/crypto/kms/apiv1" ) func Test_getKeyName(t *testing.T) { getBundle := func(kid string) *azkeys.JSONWebKey { id := azkeys.ID(kid) return &azkeys.JSONWebKey{ KID: &id, } } type args struct { vault string name string bundle *azkeys.JSONWebKey } 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 usgov", args{"my-vault", "my-key", getBundle("https://my-vault.vault.usgovcloudapi.net/keys/my-key/my-version")}, "azurekms:name=my-key;vault=my-vault?version=my-version"}, {"ok china", args{"my-vault", "my-key", getBundle("https://my-vault.vault.azure.cn/keys/my-key/my-version")}, "azurekms:name=my-key;vault=my-vault?version=my-version"}, {"ok german", args{"my-vault", "my-key", getBundle("https://my-vault.vault.microsoftazure.de/keys/my-key/my-version")}, "azurekms:name=my-key;vault=my-vault?version=my-version"}, {"ok other", args{"my-vault", "my-key", getBundle("https://my-vault.foo.net/keys/my-key/my-version")}, "azurekms:name=my-key;vault=my-vault?version=my-version"}, {"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", nil}, "azurekms:name=my-key;vault=my-vault"}, {"ok nil kid", args{"my-vault", "my-key", &azkeys.JSONWebKey{KID: nil}}, "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, publicOptions, sovereignOptions defaultOptions publicOptions.DNSSuffix = "vault.azure.net" sovereignOptions.DNSSuffix = "vault.usgovcloudapi.net" 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", publicOptions}, "my-vault", "my-key", "my-version", false, false}, {"ok no version", args{"azurekms:name=my-key;vault=my-vault", publicOptions}, "my-vault", "my-key", "", false, false}, {"ok hsm", args{"azurekms:name=my-key;vault=my-vault?hsm=true", sovereignOptions}, "my-vault", "my-key", "", true, false}, {"ok hsm false", args{"azurekms:name=my-key;vault=my-vault?hsm=false", sovereignOptions}, "my-vault", "my-key", "", false, false}, {"ok default vault", args{"azurekms:name=my-key?version=my-version", defaultOptions{Vault: "my-vault", DNSSuffix: "vault.azure.net"}}, "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, DNSSuffix: "vault.azure.net"}}, "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) } }) } } func Test_convertKey(t *testing.T) { p256, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatal(err) } p384, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) if err != nil { t.Fatal(err) } p521, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) if err != nil { t.Fatal(err) } rsaKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { t.Fatal(err) } encodeXorY := func(i *big.Int, size int) []byte { b := i.Bytes() if s := size - len(b); s > 0 { pad := make([]byte, s) //nolint:makezero // prepend with 0s return append(pad, b...) } return b } encodeE := func(v int) []byte { e := make([]byte, 8) binary.BigEndian.PutUint64(e, uint64(v)) return bytes.TrimLeft(e, "\x00") } // EC Public Key x := encodeXorY(p256.X, 32) y := encodeXorY(p256.Y, 32) // RSA Public key n := rsaKey.N.Bytes() e := encodeE(rsaKey.E) type args struct { key *azkeys.JSONWebKey } tests := []struct { name string args args want crypto.PublicKey wantErr bool }{ {"ok EC P-256", args{&azkeys.JSONWebKey{ Kty: pointer(azkeys.JSONWebKeyTypeEC), Crv: pointer(azkeys.JSONWebKeyCurveNameP256), X: x, Y: y, }}, &p256.PublicKey, false}, {"ok EC P-384", args{&azkeys.JSONWebKey{ Kty: pointer(azkeys.JSONWebKeyTypeEC), Crv: pointer(azkeys.JSONWebKeyCurveNameP384), X: encodeXorY(p384.X, 48), Y: encodeXorY(p384.Y, 48), }}, &p384.PublicKey, false}, {"ok EC P-521", args{&azkeys.JSONWebKey{ Kty: pointer(azkeys.JSONWebKeyTypeEC), Crv: pointer(azkeys.JSONWebKeyCurveNameP521), X: encodeXorY(p521.X, 66), Y: encodeXorY(p521.Y, 66), }}, &p521.PublicKey, false}, {"ok RSA", args{&azkeys.JSONWebKey{ Kty: pointer(azkeys.JSONWebKeyTypeRSA), E: e, N: n, }}, &rsaKey.PublicKey, false}, {"ok EC-HSM", args{&azkeys.JSONWebKey{ Kty: pointer(azkeys.JSONWebKeyTypeECHSM), Crv: pointer(azkeys.JSONWebKeyCurveNameP256), X: x, Y: y, }}, &p256.PublicKey, false}, {"ok RSA-HSM", args{&azkeys.JSONWebKey{ Kty: pointer(azkeys.JSONWebKeyTypeRSAHSM), E: e, N: n, }}, &rsaKey.PublicKey, false}, {"ok oct", args{&azkeys.JSONWebKey{ Kty: pointer(azkeys.JSONWebKeyTypeOct), K: []byte("a-symmetric-key"), }}, []byte("a-symmetric-key"), false}, {"fail nil", args{nil}, nil, true}, {"fail nil kty", args{&azkeys.JSONWebKey{}}, nil, true}, {"fail kty", args{&azkeys.JSONWebKey{ Kty: pointer(azkeys.JSONWebKeyType("EC-BAD")), Crv: pointer(azkeys.JSONWebKeyCurveNameP521), X: encodeXorY(p521.X, 66), Y: encodeXorY(p521.Y, 66), }}, nil, true}, {"fail nil crv", args{&azkeys.JSONWebKey{ Kty: pointer(azkeys.JSONWebKeyTypeEC), Crv: nil, X: x, Y: y, }}, nil, true}, {"fail nil x", args{&azkeys.JSONWebKey{ Kty: pointer(azkeys.JSONWebKeyTypeEC), Crv: pointer(azkeys.JSONWebKeyCurveNameP256), X: nil, Y: y, }}, nil, true}, {"fail nil y", args{&azkeys.JSONWebKey{ Kty: pointer(azkeys.JSONWebKeyTypeEC), Crv: pointer(azkeys.JSONWebKeyCurveNameP256), X: x, Y: nil, }}, nil, true}, {"fail size x", args{&azkeys.JSONWebKey{ Kty: pointer(azkeys.JSONWebKeyTypeEC), Crv: pointer(azkeys.JSONWebKeyCurveNameP256), X: encodeXorY(p256.X, 33), Y: y, }}, nil, true}, {"fail size y", args{&azkeys.JSONWebKey{ Kty: pointer(azkeys.JSONWebKeyTypeEC), Crv: pointer(azkeys.JSONWebKeyCurveNameP256), X: x, Y: encodeXorY(p256.Y, 33), }}, nil, true}, {"fail or curve", args{&azkeys.JSONWebKey{ Kty: pointer(azkeys.JSONWebKeyTypeEC), Crv: pointer(azkeys.JSONWebKeyCurveNameP256), X: y, Y: x, }}, nil, true}, {"fail or P-256k", args{&azkeys.JSONWebKey{ Kty: pointer(azkeys.JSONWebKeyTypeEC), Crv: pointer(azkeys.JSONWebKeyCurveNameP256K), X: y, Y: x, }}, nil, true}, {"fail unknown curve", args{&azkeys.JSONWebKey{ Kty: pointer(azkeys.JSONWebKeyTypeEC), Crv: pointer(azkeys.JSONWebKeyCurveName("COOL")), X: y, Y: x, }}, nil, true}, {"fail nil n", args{&azkeys.JSONWebKey{ Kty: pointer(azkeys.JSONWebKeyTypeRSA), N: nil, E: e, }}, nil, true}, {"fail nil e", args{&azkeys.JSONWebKey{ Kty: pointer(azkeys.JSONWebKeyTypeRSA), N: n, E: nil, }}, nil, true}, {"fail nil k", args{&azkeys.JSONWebKey{ Kty: pointer(azkeys.JSONWebKeyTypeOct), K: nil, }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := convertKey(tt.args.key) if (err != nil) != tt.wantErr { t.Errorf("convertKey() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("convertKey() = %v, want %v", got, tt.want) } }) } } crypto-0.57.0/kms/capi/000077500000000000000000000000001474156331600146435ustar00rootroot00000000000000crypto-0.57.0/kms/capi/capi.go000066400000000000000000000715041474156331600161150ustar00rootroot00000000000000//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, the string "capi". const Scheme = string(apiv1.CAPIKMS) 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" KeySpec = "key-spec" // 0, 1, 2; none/NONE, at_keyexchange/AT_KEYEXCHANGE, at_signature/AT_SIGNATURE SkipFindCertificateKey = "skip-find-certificate-key" // skips looking up certificate private key when storing a certificate ) 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 // "key-spec" the (legacy) KeySpec to use - 0, 1 or 2 (or none, at_keyexchange, at_signature) 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 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) } func setKeySpec(u *uri.URI) (uint32, error) { keySpec := uint32(0) // default KeySpec value is NONE value := u.Get(KeySpec) if v := strings.ReplaceAll(strings.ToLower(value), "_", ""); v != "" { switch v { case "0", "none", "null": break // already set as the default case "1", "atkeyexchange": keySpec = uint32(1) // AT_KEYEXCHANGE case "2", "atsignature": keySpec = uint32(2) // AT_SIGNATURE default: return 0, fmt.Errorf("invalid value set for key-spec: %q", value) } } return keySpec, nil } // 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) } keySpec, err := setKeySpec(u) if err != nil { return nil, fmt.Errorf("failed determining KeySpec to use: %w", err) } //TODO: check whether RSA keys require legacyKeySpec set to AT_KEYEXCHANGE kh, err := nCryptCreatePersistedKey(k.providerHandle, containerName, alg, keySpec, 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, err := u.GetHexEncoded(HashArg) if err != nil { return nil, fmt.Errorf("failed getting %s from URI %q: %w", HashArg, req.Name, err) } 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 %q", 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 %q store %q returned: %w", storeLocation, storeName, err) } var certHandle *windows.CertContext switch { case len(sha1Hash) > 0: if len(sha1Hash) != 20 { return nil, fmt.Errorf("decoded %s has length %d; expected 20 bytes for SHA-1", HashArg, len(sha1Hash)) } searchData := CERT_ID_KEYIDORHASH{ idChoice: CERT_ID_SHA1_HASH, KeyIDOrHash: CRYPTOAPI_BLOB{ len: uint32(len(sha1Hash)), data: uintptr(unsafe.Pointer(&sha1Hash[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, apiv1.NotFoundError{Message: fmt.Sprintf("certificate with %s=%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("%s 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, apiv1.NotFoundError{Message: fmt.Sprintf("certificate with %s=%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 %s: %w", SerialNumberArg, err) } } else { bi := new(big.Int) bi, ok := bi.SetString(serialNumber, 10) if !ok { return nil, fmt.Errorf("invalid %s - 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, apiv1.NotFoundError{Message: fmt.Sprintf("certificate with %s=%q and %s=%q 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("%q, %q, or %q and %q 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 %q", 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) // looking up the certificate private key is performed by default, but is made optional, // so that looking up the private key for e.g. intermediate certificates can be skipped. // If not skipped, looking up a private key can prompt the user to insert/select a smart // card, which is usually not what we want to happen. if !u.GetBool(SkipFindCertificateKey) { // TODO: not finding the associated private key is not a dealbreaker, but maybe a warning should be issued cryptFindCertificateKeyProvInfo(certContext) } st, err := windows.CertOpenStore( certStoreProvSystem, 0, 0, certStoreLocation, uintptr(unsafe.Pointer(wide(storeName)))) if err != nil { return fmt.Errorf("CertOpenStore for the %q store %q 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 } // DeleteCertificate deletes a certificate from the Windows certificate store. It uses // largely the same logic for searching for the certificate as [LoadCertificate], but // deletes it as soon as it's found. // // # Experimental // // Notice: This method is EXPERIMENTAL and may be changed or removed in a later // release. func (k *CAPIKMS) DeleteCertificate(req *apiv1.DeleteCertificateRequest) error { u, err := uri.ParseWithScheme(Scheme, req.Name) if err != nil { return fmt.Errorf("failed to parse URI: %w", err) } sha1Hash, err := u.GetHexEncoded(HashArg) if err != nil { return fmt.Errorf("failed getting %s from URI %q: %w", HashArg, req.Name, err) } keyID := u.Get(KeyIDArg) issuerName := u.Get(IssuerNameArg) serialNumber := u.Get(SerialNumberArg) 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 %q", storeLocation) } var storeName string 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 fmt.Errorf("CertOpenStore for the %q store %q returned: %w", storeLocation, storeName, err) } var certHandle *windows.CertContext switch { case len(sha1Hash) > 0: if len(sha1Hash) != 20 { return fmt.Errorf("decoded %s has length %d; expected 20 bytes for SHA-1", HashArg, len(sha1Hash)) } searchData := CERT_ID_KEYIDORHASH{ idChoice: CERT_ID_SHA1_HASH, KeyIDOrHash: CRYPTOAPI_BLOB{ len: uint32(len(sha1Hash)), data: uintptr(unsafe.Pointer(&sha1Hash[0])), }, } certHandle, err = findCertificateInStore(st, encodingX509ASN|encodingPKCS7, 0, findCertID, uintptr(unsafe.Pointer(&searchData)), nil) if err != nil { return fmt.Errorf("findCertificateInStore failed: %w", err) } if certHandle == nil { return nil } if err := windows.CertDeleteCertificateFromStore(certHandle); err != nil { return fmt.Errorf("failed removing certificate: %w", err) } return nil 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 fmt.Errorf("%s 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 fmt.Errorf("findCertificateInStore failed: %w", err) } if certHandle == nil { return nil } if err := windows.CertDeleteCertificateFromStore(certHandle); err != nil { return fmt.Errorf("failed removing certificate: %w", err) } return nil 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 fmt.Errorf("invalid hex format for %s: %w", SerialNumberArg, err) } } else { bi := new(big.Int) bi, ok := bi.SetString(serialNumber, 10) if !ok { return fmt.Errorf("invalid %s - 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 fmt.Errorf("findCertificateInStore failed: %w", err) } if certHandle == nil { return nil } x509Cert, err := certContextToX509(certHandle) if err != nil { defer windows.CertFreeCertificateContext(certHandle) return fmt.Errorf("could not unmarshal certificate to DER: %w", err) } if bytes.Equal(x509Cert.SerialNumber.Bytes(), serialBytes) { if err := windows.CertDeleteCertificateFromStore(certHandle); err != nil { return fmt.Errorf("failed removing certificate: %w", err) } return nil } prevCert = certHandle } default: return fmt.Errorf("%q, %q, or %q and %q is required to find a certificate", HashArg, KeyIDArg, IssuerNameArg, SerialNumberArg) } } 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 } var _ apiv1.CertificateManager = (*CAPIKMS)(nil) crypto-0.57.0/kms/capi/capi_no_windows.go000066400000000000000000000005701474156331600203560ustar00rootroot00000000000000//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") }) } crypto-0.57.0/kms/capi/ncrypt_windows.go000066400000000000000000000432271474156331600202730ustar00rootroot00000000000000//go:build windows // +build windows package capi import ( "bytes" "crypto" "crypto/elliptic" "crypto/sha1" "encoding/binary" "errors" "fmt" "unsafe" "golang.org/x/sys/windows" ) 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 { destroyScheduledDuration = durationpb.New(req.DestroyRetentionPeriod) } // resource is the plain Google Cloud KMS resource name resource := resourceName(req.Name) // Split `projects/PROJECT_ID/locations/global/keyRings/RING_ID/cryptoKeys/KEY_ID` // to `projects/PROJECT_ID/locations/global/keyRings/RING_ID` and `KEY_ID`. keyRing, keyID := Parent(resource) if err := k.createKeyRingIfNeeded(keyRing); err != nil { return nil, err } var cryptoKeyName string ctx, cancel := defaultContext() defer cancel() // Create private key in CloudKMS. response, err := k.client.CreateCryptoKey(ctx, &kmspb.CreateCryptoKeyRequest{ Parent: keyRing, CryptoKeyId: keyID, CryptoKey: &kmspb.CryptoKey{ Purpose: kmspb.CryptoKey_ASYMMETRIC_SIGN, VersionTemplate: &kmspb.CryptoKeyVersionTemplate{ ProtectionLevel: protectionLevel, Algorithm: signatureAlgorithm, }, DestroyScheduledDuration: destroyScheduledDuration, }, }) if err != nil { if status.Code(err) != codes.AlreadyExists { return nil, errors.Wrap(err, "cloudKMS CreateCryptoKey failed") } // Create a new version if the key already exists. // // Note that it will have the same purpose, protection level and // algorithm than as previous one. req := &kmspb.CreateCryptoKeyVersionRequest{ Parent: resource, CryptoKeyVersion: &kmspb.CryptoKeyVersion{ State: kmspb.CryptoKeyVersion_ENABLED, }, } response, err := k.client.CreateCryptoKeyVersion(ctx, req) if err != nil { return nil, errors.Wrap(err, "cloudKMS CreateCryptoKeyVersion failed") } cryptoKeyName = response.Name } else { cryptoKeyName = response.Name + "/cryptoKeyVersions/1" } // Use uri format for the keys cryptoKeyName = uri.NewOpaque(Scheme, cryptoKeyName).String() // Sleep deterministically to avoid retries because of PENDING_GENERATING. // One second is often enough. if protectionLevel == kmspb.ProtectionLevel_HSM { time.Sleep(1 * time.Second) } // Retrieve public key to add it to the response. pk, err := k.GetPublicKey(&apiv1.GetPublicKeyRequest{ Name: cryptoKeyName, }) if err != nil { return nil, errors.Wrap(err, "cloudKMS GetPublicKey failed") } return &apiv1.CreateKeyResponse{ Name: cryptoKeyName, PublicKey: pk, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: cryptoKeyName, }, }, nil } func (k *CloudKMS) createKeyRingIfNeeded(name string) error { ctx, cancel := defaultContext() defer cancel() _, err := k.client.GetKeyRing(ctx, &kmspb.GetKeyRingRequest{ Name: name, }) if err == nil { return nil } parent, child := Parent(name) _, err = k.client.CreateKeyRing(ctx, &kmspb.CreateKeyRingRequest{ Parent: parent, KeyRingId: child, }) if err != nil && status.Code(err) != codes.AlreadyExists { return errors.Wrap(err, "cloudKMS CreateKeyRing failed") } return nil } // GetPublicKey gets from Google's Cloud KMS a public key by name. Key names // follow the pattern: // // projects/([^/]+)/locations/([a-zA-Z0-9_-]{1,63})/keyRings/([a-zA-Z0-9_-]{1,63})/cryptoKeys/([a-zA-Z0-9_-]{1,63})/cryptoKeyVersions/([a-zA-Z0-9_-]{1,63}) func (k *CloudKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) { if req.Name == "" { return nil, errors.New("createKeyRequest 'name' cannot be empty") } response, err := k.getPublicKeyWithRetries(resourceName(req.Name), pendingGenerationRetries) if err != nil { return nil, errors.Wrap(err, "cloudKMS GetPublicKey failed") } pk, err := pemutil.ParseKey([]byte(response.Pem)) if err != nil { return nil, err } return pk, nil } // ErrTooManyRetries is the type of error when a method attempts too many // retries. var ErrTooManyRetries = errors.New("too many retries") // getPublicKeyWithRetries retries the request if the error is // FailedPrecondition, caused because the key is in the PENDING_GENERATION // status. func (k *CloudKMS) getPublicKeyWithRetries(name string, retries int) (*kmspb.PublicKey, error) { workFn := func() (*kmspb.PublicKey, error) { ctx, cancel := defaultContext() defer cancel() return k.client.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{ Name: name, }) } for i := 0; i < retries; i++ { response, err := workFn() switch { case err == nil: return response, nil case status.Code(err) == codes.FailedPrecondition: log.Println("Waiting for key generation ...") time.Sleep(time.Duration(i+1) * time.Second) continue default: return nil, err } } return nil, ErrTooManyRetries } func defaultContext() (context.Context, context.CancelFunc) { return context.WithTimeout(context.Background(), 15*time.Second) } // Parent splits a string in the format `key/value/key2/value2` in a parent and // child, for the previous string it will return `key/value` and `value2`. func Parent(name string) (string, string) { a, b := parent(name) a, _ = parent(a) return a, b } func parent(name string) (string, string) { i := strings.LastIndex(name, "/") switch i { case -1: return "", name case 0: return "", name[i+1:] default: return name[:i], name[i+1:] } } // resourceName returns the resource name in the given string. The resource name // can be the same string, the value of the resource field or the encoded opaque // data: // - projects/id/locations/global/keyRings/ring/cryptoKeys/root-key/cryptoKeyVersions/1 // - cloudkms:resource=projects/id/locations/global/keyRings/ring/cryptoKeys/root-key/cryptoKeyVersions/1 // - cloudkms:projects/id/locations/global/keyRings/ring/cryptoKeys/root-key/cryptoKeyVersions/1 func resourceName(name string) string { if u, err := uri.ParseWithScheme(Scheme, name); err == nil { if r := u.Get("resource"); r != "" { return r } return u.Opaque } return name } crypto-0.57.0/kms/cloudkms/cloudkms_test.go000066400000000000000000000467641474156331600210000ustar00rootroot00000000000000package cloudkms import ( "context" "crypto" "fmt" "net/url" "os" "reflect" "testing" "time" "cloud.google.com/go/kms/apiv1/kmspb" gax "github.com/googleapis/gax-go/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/kms/uri" "go.step.sm/crypto/pemutil" "google.golang.org/api/option" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/durationpb" ) func TestParent(t *testing.T) { type args struct { name string } tests := []struct { name string args args want string want1 string }{ {"zero", args{"child"}, "", "child"}, {"one", args{"parent/child"}, "", "child"}, {"two", args{"grandparent/parent/child"}, "grandparent", "child"}, {"three", args{"great-grandparent/grandparent/parent/child"}, "great-grandparent/grandparent", "child"}, {"empty", args{""}, "", ""}, {"root", args{"/"}, "", ""}, {"child", args{"/child"}, "", "child"}, {"parent", args{"parent/"}, "", ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, got1 := Parent(tt.args.name) if got != tt.want { t.Errorf("Parent() got = %v, want %v", got, tt.want) } if got1 != tt.want1 { t.Errorf("Parent() got1 = %v, want %v", got1, tt.want1) } }) } } func TestNew(t *testing.T) { tmp := newKeyManagementClient t.Cleanup(func() { newKeyManagementClient = tmp }) newKeyManagementClient = func(ctx context.Context, opts ...option.ClientOption) (KeyManagementClient, error) { if len(opts) > 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}, {"ok resource uri", args{context.Background(), apiv1.Options{URI: "cloudkms:projects/p/locations/l/keyRings/k/cryptoKeys/c/cryptoKeyVersions/1"}}, &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" keyURI := uri.NewOpaque(Scheme, keyName).String() pemBytes, err := os.ReadFile("testdata/pub.pem") require.NoError(t, err) pk, err := pemutil.ParseKey(pemBytes) require.NoError(t, 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, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }}, args{&apiv1.CreateSignerRequest{SigningKey: keyName}}, &Signer{client: &MockClient{}, signingKey: keyName, publicKey: pk}, false}, {"ok with uri", fields{&MockClient{ getPublicKey: func(_ context.Context, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }}, args{&apiv1.CreateSignerRequest{SigningKey: keyURI}}, &Signer{client: &MockClient{}, signingKey: keyName, publicKey: pk}, false}, {"fail", fields{&MockClient{ getPublicKey: func(_ context.Context, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") 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" keyURI := uri.NewOpaque(Scheme, keyName).String() testError := fmt.Errorf("an error") alreadyExists := status.Error(codes.AlreadyExists, "already exists") pemBytes, err := os.ReadFile("testdata/pub.pem") require.NoError(t, err) pk, err := pemutil.ParseKey(pemBytes) require.NoError(t, 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, req *kmspb.CreateCryptoKeyRequest, _ ...gax.CallOption) (*kmspb.CryptoKey, error) { assert.Nil(t, req.CryptoKey.DestroyScheduledDuration) return &kmspb.CryptoKey{Name: keyName}, nil }, getPublicKey: func(_ context.Context, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }}, args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.HSM, SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, &apiv1.CreateKeyResponse{Name: "cloudkms:" + keyName + "/cryptoKeyVersions/1", PublicKey: pk, CreateSignerRequest: apiv1.CreateSignerRequest{SigningKey: "cloudkms:" + keyName + "/cryptoKeyVersions/1"}}, false}, {"ok with uri and retention", fields{ &MockClient{ getKeyRing: func(_ context.Context, _ *kmspb.GetKeyRingRequest, _ ...gax.CallOption) (*kmspb.KeyRing, error) { return &kmspb.KeyRing{}, nil }, createCryptoKey: func(_ context.Context, req *kmspb.CreateCryptoKeyRequest, _ ...gax.CallOption) (*kmspb.CryptoKey, error) { assert.Equal(t, req.CryptoKey.DestroyScheduledDuration, durationpb.New(24*time.Hour)) return &kmspb.CryptoKey{Name: keyName}, nil }, getPublicKey: func(_ context.Context, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }}, args{&apiv1.CreateKeyRequest{Name: keyURI, ProtectionLevel: apiv1.HSM, SignatureAlgorithm: apiv1.ECDSAWithSHA256, DestroyRetentionPeriod: 24 * time.Hour}}, &apiv1.CreateKeyResponse{Name: "cloudkms:" + keyName + "/cryptoKeyVersions/1", PublicKey: pk, CreateSignerRequest: apiv1.CreateSignerRequest{SigningKey: "cloudkms:" + 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, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }}, args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.Software, SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 3072}}, &apiv1.CreateKeyResponse{Name: "cloudkms:" + keyName + "/cryptoKeyVersions/1", PublicKey: pk, CreateSignerRequest: apiv1.CreateSignerRequest{SigningKey: "cloudkms:" + 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, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }}, args{&apiv1.CreateKeyRequest{Name: keyName, ProtectionLevel: apiv1.HSM, SignatureAlgorithm: apiv1.ECDSAWithSHA256}}, &apiv1.CreateKeyResponse{Name: "cloudkms:" + keyName + "/cryptoKeyVersions/2", PublicKey: pk, CreateSignerRequest: apiv1.CreateSignerRequest{SigningKey: "cloudkms:" + 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, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") 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: "cloudkms:" + keyName + "/cryptoKeyVersions/1", PublicKey: pk, CreateSignerRequest: apiv1.CreateSignerRequest{SigningKey: "cloudkms:" + 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, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") 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" keyURI := uri.NewOpaque(Scheme, keyName).String() keyResource := uri.New(Scheme, url.Values{ "resource": []string{keyName}, }).String() testError := fmt.Errorf("an error") pemBytes, err := os.ReadFile("testdata/pub.pem") require.NoError(t, err) pk, err := pemutil.ParseKey(pemBytes) require.NoError(t, 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, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }}, args{&apiv1.GetPublicKeyRequest{Name: keyName}}, pk, false}, {"ok with uri", fields{ &MockClient{ getPublicKey: func(_ context.Context, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }}, args{&apiv1.GetPublicKeyRequest{Name: keyURI}}, pk, false}, {"ok with resource uri", fields{ &MockClient{ getPublicKey: func(_ context.Context, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }}, args{&apiv1.GetPublicKeyRequest{Name: keyResource}}, pk, false}, {"ok with retries", fields{ &MockClient{ getPublicKey: func(_ context.Context, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") 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, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") return nil, testError }, }}, args{&apiv1.GetPublicKeyRequest{Name: keyName}}, nil, true}, {"fail parse pem", fields{ &MockClient{ getPublicKey: func(_ context.Context, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") 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) } }) } } crypto-0.57.0/kms/cloudkms/decrypter.go000066400000000000000000000100501474156331600200740ustar00rootroot00000000000000package cloudkms import ( "crypto" "crypto/rsa" "errors" "fmt" "hash/crc32" "io" "cloud.google.com/go/kms/apiv1/kmspb" "google.golang.org/protobuf/types/known/wrapperspb" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/pemutil" ) // CreateDecrypter implements the apiv1.Decrypter interface and returns // a crypto.Decrypter backed by a decryption key in Google Cloud KMS. func (k *CloudKMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { if req.DecryptionKey == "" { return nil, errors.New("decryption key cannot be empty") } return NewDecrypter(k.client, req.DecryptionKey) } // Decrypter implements a crypto.Decrypter using Google Cloud KMS. type Decrypter struct { client KeyManagementClient decryptionKey string publicKey crypto.PublicKey } // NewDecrypter creates a new crypto.Decrypter backed by the given // Google Cloud KMS decryption key. func NewDecrypter(client KeyManagementClient, decryptionKey string) (*Decrypter, error) { // Make sure that the key exists. decrypter := &Decrypter{ client: client, decryptionKey: resourceName(decryptionKey), } if err := decrypter.preloadKey(); err != nil { // TODO(hs): (option for) lazy load instead? return nil, err } return decrypter, nil } func (d *Decrypter) preloadKey() error { ctx, cancel := defaultContext() defer cancel() response, err := d.client.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{ Name: d.decryptionKey, }) if err != nil { return fmt.Errorf("cloudKMS GetPublicKey failed: %w", err) } d.publicKey, err = pemutil.ParseKey([]byte(response.Pem)) return err } // Public returns the public key of this decrypter func (d *Decrypter) Public() crypto.PublicKey { return d.publicKey } // validateOAEPOptions validates the RSA OAEP options provided. func validateOAEPOptions(o *rsa.OAEPOptions) error { if o == nil { // var o *rsa.OAEPOptions; nothing to verify return nil } if len(o.Label) > 0 { return errors.New("cloudKMS does not support RSA-OAEP label") } switch o.Hash { case crypto.Hash(0), crypto.SHA1, crypto.SHA256, crypto.SHA512: return nil default: return fmt.Errorf("cloudKMS does not support hash algorithm %q with RSA-OAEP", o.Hash) } } // Decrypt decrypts ciphertext using the decryption key backed by Google Cloud KMS and returns // the plaintext bytes. An error is returned when decryption fails. Google Cloud KMS only supports // RSA keys with 2048, 3072 or 4096 bits and will always use OAEP. It supports SHA1, SHA256 and // SHA512. Labels are not supported. Before calling out to GCP, some validation is performed // so that known bad parameters are detected client-side and a more meaningful error is returned // for those cases. // // Also see https://cloud.google.com/kms/docs/algorithms#asymmetric_encryption_algorithms. func (d *Decrypter) Decrypt(_ io.Reader, ciphertext []byte, opts crypto.DecrypterOpts) ([]byte, error) { if opts == nil { opts = &rsa.OAEPOptions{} } switch o := opts.(type) { case *rsa.OAEPOptions: if err := validateOAEPOptions(o); err != nil { return nil, err } case *rsa.PKCS1v15DecryptOptions: return nil, errors.New("cloudKMS does not support PKCS #1 v1.5 decryption") default: return nil, errors.New("invalid options for Decrypt") } req := &kmspb.AsymmetricDecryptRequest{ Name: d.decryptionKey, Ciphertext: ciphertext, CiphertextCrc32C: wrapperspb.Int64(crc32c(ciphertext)), } ctx, cancel := defaultContext() defer cancel() response, err := d.client.AsymmetricDecrypt(ctx, req) if err != nil { return nil, fmt.Errorf("cloudKMS AsymmetricDecrypt failed: %w", err) } if !response.VerifiedCiphertextCrc32C { return nil, errors.New("cloudKMS AsymmetricDecrypt: request corrupted in-transit") } if crc32c(response.Plaintext) != response.PlaintextCrc32C.Value { return nil, errors.New("cloudKMS AsymmetricDecrypt: response corrupted in-transit") } return response.Plaintext, nil } func crc32c(data []byte) int64 { t := crc32.MakeTable(crc32.Castagnoli) return int64(crc32.Checksum(data, t)) } var _ apiv1.Decrypter = (*CloudKMS)(nil) crypto-0.57.0/kms/cloudkms/decrypter_test.go000066400000000000000000000252441474156331600211460ustar00rootroot00000000000000package cloudkms import ( "context" "crypto" "crypto/rand" "crypto/rsa" "fmt" "io" "os" "testing" "cloud.google.com/go/kms/apiv1/kmspb" gax "github.com/googleapis/gax-go/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/pemutil" "google.golang.org/protobuf/types/known/wrapperspb" ) func TestCloudKMS_CreateDecrypter(t *testing.T) { keyName := "projects/p/locations/l/keyRings/k/cryptoKeys/c/cryptoKeyVersions/1" pemBytes, err := os.ReadFile("testdata/rsapub.pem") require.NoError(t, err) pk, err := pemutil.ParseKey(pemBytes) require.NoError(t, err) type fields struct { client KeyManagementClient } type args struct { req *apiv1.CreateDecrypterRequest } tests := []struct { name string fields fields args args want crypto.Decrypter wantErr bool }{ {"ok", fields{&MockClient{ getPublicKey: func(_ context.Context, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }}, args{&apiv1.CreateDecrypterRequest{DecryptionKey: keyName}}, &Decrypter{client: &MockClient{}, decryptionKey: keyName, publicKey: pk}, false}, {"ok with uri", fields{&MockClient{ getPublicKey: func(_ context.Context, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }}, args{&apiv1.CreateDecrypterRequest{DecryptionKey: "cloudkms:resource=" + keyName}}, &Decrypter{client: &MockClient{}, decryptionKey: keyName, publicKey: pk}, false}, {"ok with opaque uri", fields{&MockClient{ getPublicKey: func(_ context.Context, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }}, args{&apiv1.CreateDecrypterRequest{DecryptionKey: "cloudkms:" + keyName}}, &Decrypter{client: &MockClient{}, decryptionKey: keyName, publicKey: pk}, false}, {"fail", fields{&MockClient{ getPublicKey: func(_ context.Context, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") return nil, fmt.Errorf("test error") }, }}, args{&apiv1.CreateDecrypterRequest{DecryptionKey: ""}}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &CloudKMS{ client: tt.fields.client, } got, err := k.CreateDecrypter(tt.args.req) if tt.wantErr { assert.Error(t, err) return } assert.NoError(t, err) assert.Equal(t, tt.want.Public(), got.Public()) }) } } func TestNewDecrypter(t *testing.T) { pemBytes, err := os.ReadFile("testdata/rsapub.pem") require.NoError(t, err) pk, err := pemutil.ParseKey(pemBytes) require.NoError(t, err) type args struct { client KeyManagementClient decryptionKey string } tests := []struct { name string args args want *Decrypter wantErr bool }{ {"ok", args{&MockClient{ getPublicKey: func(_ context.Context, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }, "decryptionKey"}, &Decrypter{client: &MockClient{}, decryptionKey: "decryptionKey", publicKey: pk}, false}, {"fail get public key", args{&MockClient{ getPublicKey: func(_ context.Context, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") return nil, fmt.Errorf("an error") }, }, "decryptionKey"}, nil, true}, {"fail parse pem", args{&MockClient{ getPublicKey: func(_ context.Context, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") return &kmspb.PublicKey{Pem: string("bad pem")}, nil }, }, "decryptionKey"}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := NewDecrypter(tt.args.client, tt.args.decryptionKey) if tt.wantErr { assert.Error(t, err) return } assert.NoError(t, err) assert.Equal(t, tt.want.decryptionKey, got.decryptionKey) assert.Equal(t, tt.want.publicKey, pk) }) } } func TestDecrypter_Public(t *testing.T) { pemBytes, err := os.ReadFile("testdata/rsapub.pem") require.NoError(t, err) pk, err := pemutil.ParseKey(pemBytes) require.NoError(t, err) type fields struct { client KeyManagementClient decryptionKey string publicKey crypto.PublicKey } tests := []struct { name string fields fields want crypto.PublicKey }{ {"ok", fields{&MockClient{}, "decryptionKey", pk}, pk}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { d := &Decrypter{ client: tt.fields.client, decryptionKey: tt.fields.decryptionKey, publicKey: tt.fields.publicKey, } got := d.Public() assert.Equal(t, pk, got) }) } } func TestDecrypter_Decrypt(t *testing.T) { pemBytes, err := os.ReadFile("testdata/rsapub.pem") require.NoError(t, err) pk, err := pemutil.ParseKey(pemBytes) require.NoError(t, err) keyName := "projects/p/locations/l/keyRings/k/cryptoKeys/c/cryptoKeyVersions/1" okClient := &MockClient{ asymmetricDecrypt: func(ctx context.Context, adr *kmspb.AsymmetricDecryptRequest, co ...gax.CallOption) (*kmspb.AsymmetricDecryptResponse, error) { assert.NotContains(t, adr.Name, "cloudkms:") return &kmspb.AsymmetricDecryptResponse{Plaintext: []byte("decrypted"), PlaintextCrc32C: wrapperspb.Int64(crc32c([]byte("decrypted"))), VerifiedCiphertextCrc32C: true}, nil }, } failClient := &MockClient{ asymmetricDecrypt: func(ctx context.Context, adr *kmspb.AsymmetricDecryptRequest, co ...gax.CallOption) (*kmspb.AsymmetricDecryptResponse, error) { assert.NotContains(t, adr.Name, "cloudkms:") return nil, fmt.Errorf("an error") }, } requestCRC32Client := &MockClient{ asymmetricDecrypt: func(ctx context.Context, adr *kmspb.AsymmetricDecryptRequest, co ...gax.CallOption) (*kmspb.AsymmetricDecryptResponse, error) { assert.NotContains(t, adr.Name, "cloudkms:") return &kmspb.AsymmetricDecryptResponse{Plaintext: []byte("decrypted"), PlaintextCrc32C: wrapperspb.Int64(crc32c([]byte("decrypted"))), VerifiedCiphertextCrc32C: false}, nil }, } responseCRC32Client := &MockClient{ asymmetricDecrypt: func(ctx context.Context, adr *kmspb.AsymmetricDecryptRequest, co ...gax.CallOption) (*kmspb.AsymmetricDecryptResponse, error) { assert.NotContains(t, adr.Name, "cloudkms:") return &kmspb.AsymmetricDecryptResponse{Plaintext: []byte("decrypted"), PlaintextCrc32C: wrapperspb.Int64(crc32c([]byte("wrong"))), VerifiedCiphertextCrc32C: true}, nil }, } var nilOpts *rsa.OAEPOptions type fields struct { client KeyManagementClient decryptionKey string publicKey crypto.PublicKey } type args struct { rand io.Reader ciphertext []byte opts crypto.DecrypterOpts } tests := []struct { name string fields fields args args want []byte wantErr bool }{ { name: "ok", fields: fields{ client: okClient, decryptionKey: keyName, publicKey: pk, }, args: args{ rand: rand.Reader, ciphertext: []byte("data"), opts: &rsa.OAEPOptions{ Hash: crypto.SHA256, Label: nil, }, }, want: []byte("decrypted"), }, { name: "ok/nil-opts", fields: fields{ client: okClient, decryptionKey: keyName, publicKey: pk, }, args: args{ rand: rand.Reader, ciphertext: []byte("data"), }, want: []byte("decrypted"), }, { name: "ok/nil-oaep-opts", fields: fields{ client: okClient, decryptionKey: keyName, publicKey: pk, }, args: args{ rand: rand.Reader, ciphertext: []byte("data"), opts: nilOpts, }, want: []byte("decrypted"), }, { name: "fail/label-unsupported", fields: fields{ client: okClient, decryptionKey: keyName, publicKey: pk, }, args: args{ rand: rand.Reader, ciphertext: []byte("data"), opts: &rsa.OAEPOptions{ Hash: crypto.SHA256, Label: []byte("label"), }, }, wantErr: true, }, { name: "fail/unsupported-hash", fields: fields{ client: okClient, decryptionKey: keyName, publicKey: pk, }, args: args{ rand: rand.Reader, ciphertext: []byte("data"), opts: &rsa.OAEPOptions{ Hash: crypto.Hash(1000), Label: nil, }, }, wantErr: true, }, { name: "fail/pkcs1v15", fields: fields{ client: okClient, decryptionKey: keyName, publicKey: pk, }, args: args{ rand: rand.Reader, ciphertext: []byte("data"), opts: &rsa.PKCS1v15DecryptOptions{}, }, wantErr: true, }, { name: "fail/decrypt-failed", fields: fields{ client: failClient, decryptionKey: keyName, publicKey: pk, }, args: args{ rand: rand.Reader, ciphertext: []byte("data"), opts: &rsa.OAEPOptions{ Hash: crypto.SHA256, Label: nil, }, }, wantErr: true, }, { name: "fail/request-crc32c-failed", fields: fields{ client: requestCRC32Client, decryptionKey: keyName, publicKey: pk, }, args: args{ rand: rand.Reader, ciphertext: []byte("data"), opts: &rsa.OAEPOptions{ Hash: crypto.SHA256, Label: nil, }, }, wantErr: true, }, { name: "fail/response-crc32c-failed", fields: fields{ client: responseCRC32Client, decryptionKey: keyName, publicKey: pk, }, args: args{ rand: rand.Reader, ciphertext: []byte("data"), opts: &rsa.OAEPOptions{ Hash: crypto.SHA256, Label: nil, }, }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { d := &Decrypter{ client: tt.fields.client, decryptionKey: tt.fields.decryptionKey, publicKey: tt.fields.publicKey, } got, err := d.Decrypt(tt.args.rand, tt.args.ciphertext, tt.args.opts) if tt.wantErr { assert.Error(t, err) return } assert.NoError(t, err) assert.Equal(t, tt.want, got) }) } } func Test_crc32c(t *testing.T) { tests := []struct { name string want int64 }{ {"123456789", 0xe3069283}, {"The quick brown fox jumps over the lazy dog", 0x22620404}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := crc32c([]byte(tt.name)); got != tt.want { t.Errorf("crc32c() = %v, want %v", got, tt.want) } }) } } crypto-0.57.0/kms/cloudkms/mock_test.go000066400000000000000000000047071474156331600200770ustar00rootroot00000000000000package 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) asymmetricDecrypt func(context.Context, *kmspb.AsymmetricDecryptRequest, ...gax.CallOption) (*kmspb.AsymmetricDecryptResponse, 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) AsymmetricDecrypt(ctx context.Context, req *kmspb.AsymmetricDecryptRequest, opts ...gax.CallOption) (*kmspb.AsymmetricDecryptResponse, error) { return m.asymmetricDecrypt(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...) } crypto-0.57.0/kms/cloudkms/no_cloudkms.go000066400000000000000000000007101474156331600204120ustar00rootroot00000000000000//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) }) } crypto-0.57.0/kms/cloudkms/signer.go000066400000000000000000000045301474156331600173700ustar00rootroot00000000000000//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: resourceName(signingKey), } if err := signer.preloadKey(); err != nil { return nil, err } return signer, nil } func (s *Signer) preloadKey() error { ctx, cancel := defaultContext() defer cancel() response, err := s.client.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{ Name: s.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(_ 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 } crypto-0.57.0/kms/cloudkms/signer_test.go000066400000000000000000000203001474156331600204200ustar00rootroot00000000000000package 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" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.step.sm/crypto/pemutil" ) func Test_NewSigner(t *testing.T) { pemBytes, err := os.ReadFile("testdata/pub.pem") require.NoError(t, err) pk, err := pemutil.ParseKey(pemBytes) require.NoError(t, 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, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }, "signingKey"}, &Signer{client: &MockClient{}, signingKey: "signingKey", publicKey: pk}, false}, {"ok with uri", args{&MockClient{ getPublicKey: func(_ context.Context, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }, "cloudkms:resource=signingKey"}, &Signer{client: &MockClient{}, signingKey: "signingKey", publicKey: pk}, false}, {"ok with opaque uri", args{&MockClient{ getPublicKey: func(_ context.Context, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") return &kmspb.PublicKey{Pem: string(pemBytes)}, nil }, }, "cloudkms:signingKey"}, &Signer{client: &MockClient{}, signingKey: "signingKey", publicKey: pk}, false}, {"fail get public key", args{&MockClient{ getPublicKey: func(_ context.Context, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") return nil, fmt.Errorf("an error") }, }, "signingKey"}, nil, true}, {"fail parse pem", args{&MockClient{ getPublicKey: func(_ context.Context, r *kmspb.GetPublicKeyRequest, _ ...gax.CallOption) (*kmspb.PublicKey, error) { assert.NotContains(t, r.Name, "cloudkms:") 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") require.NoError(t, err) pk, err := pemutil.ParseKey(pemBytes) require.NoError(t, 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, r *kmspb.AsymmetricSignRequest, _ ...gax.CallOption) (*kmspb.AsymmetricSignResponse, error) { assert.NotContains(t, r.Name, "cloudkms:") return &kmspb.AsymmetricSignResponse{Signature: []byte("ok signature")}, nil }, } failClient := &MockClient{ asymmetricSign: func(_ context.Context, r *kmspb.AsymmetricSignRequest, _ ...gax.CallOption) (*kmspb.AsymmetricSignResponse, error) { assert.NotContains(t, r.Name, "cloudkms:") 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") require.NoError(t, 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) } }) } } crypto-0.57.0/kms/cloudkms/testdata/000077500000000000000000000000001474156331600173615ustar00rootroot00000000000000crypto-0.57.0/kms/cloudkms/testdata/pub.pem000066400000000000000000000002621474156331600206520ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5VPD/W5RXn0lrs2MdoNteTSZ+sh1 veT13hakPZF9YzaNVZgujqK3d1nt+4jPECU+ED/WQ1GgFZiVGUo3flvB/w== -----END PUBLIC KEY----- crypto-0.57.0/kms/cloudkms/testdata/rsapub.pem000066400000000000000000000011611474156331600213570ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAsrAcFrWhJLBEjgrbt962 GJULrg/FZQ+MB+KQJi+mv2VoVq6Rp7g+H7EyAi8b9a9cf1X4vFLjEpEFFhhji0e7 BZIIeE96rOHdq9Rf8MEyy8YcqKe1eTeQhOTsyD7pletjyNUOC/DexN2saS7N80uj vEKpEprDNf+XCTrQqDTlBO4LWNPu7e8xjm+Wvnb9P8Kizln4Z8cfRjxRUwpYRgjh YdhHegT2a6lZodthh+iw83P5WlxJBuDNiKk+E0kIePDQLg2irjNr/gjo2O13yUNn +ZdtdRjSjOAlrW/n1rxs6TIMYv/qEGinnj2j47cTAQqDzNdw8GwbzJcA0iajNvQj Ge4RoO0p4jPiD+9H0qfraNf3e1VQcIUoRoohlwrisBJCUrpSiTKbp9orVCrY9ZvA uQyUpiLnsN7//+tQUDEX3Tw4rpl0oY48TYMqzzs9mNd3lnkJnkEecM0m+dP/8wEb Np5KtEnqmuW3UNWnvzxB9HGVIYt35fjGCHHIZl9PbnqbAgMBAAE= -----END PUBLIC KEY----- crypto-0.57.0/kms/kms.go000066400000000000000000000025701474156331600150540ustar00rootroot00000000000000package 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 // TypeOf returns the KMS type of the given uri. var TypeOf = apiv1.TypeOf // 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) } crypto-0.57.0/kms/kms_test.go000066400000000000000000000031501474156331600161060ustar00rootroot00000000000000package 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) } }) } } crypto-0.57.0/kms/kmsfs.go000066400000000000000000000051251474156331600154040ustar00rootroot00000000000000package 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 } crypto-0.57.0/kms/kmsfs_test.go000066400000000000000000000172241474156331600164460ustar00rootroot00000000000000package kms import ( "context" "crypto" "crypto/x509" "crypto/x509/pkix" "errors" "io/fs" "os" "reflect" "testing" "github.com/stretchr/testify/assert" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/kms/softkms" "go.step.sm/crypto/kms/tpmkms" ) 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 } var _ apiv1.CertificateManager = (*fakeCM)(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}, {"ok tpmkms", args{ctx, "tpmkms:"}, &kmsfs{ KeyManager: &tpmkms.TPMKMS{}, }, 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 tt.wantErr { assert.Error(t, err) assert.Nil(t, got) return } assert.NoError(t, err) if assert.NotNil(t, got) { assert.IsType(t, tt.want, got) } err = got.Close() assert.NoError(t, 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}, {"ok tpmkms", fields{&tpmkms.TPMKMS{}}, args{""}, &tpmkms.TPMKMS{}, false}, {"ok tpmkms with uri", fields{nil}, args{"tpmkms:"}, &tpmkms.TPMKMS{}, 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 tt.wantErr { assert.Error(t, err) assert.Nil(t, got) return } assert.NoError(t, err) if assert.NotNil(t, got) { assert.IsType(t, tt.want, got) } }) } } 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) } }) } } crypto-0.57.0/kms/mackms/000077500000000000000000000000001474156331600152025ustar00rootroot00000000000000crypto-0.57.0/kms/mackms/mackms.go000066400000000000000000001071411474156331600170100ustar00rootroot00000000000000//go:build darwin && cgo && !nomackms // Copyright (c) Smallstep Labs, Inc. // Copyright (c) Meta Platforms, Inc. and affiliates. // // 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. // // Part of this code is based on // https://github.com/facebookincubator/sks/blob/183e7561ecedc71992f23b2d37983d2948391f4c/macos/macos.go package mackms import ( "bytes" "context" "crypto" "crypto/ecdh" "crypto/ecdsa" "crypto/elliptic" "crypto/x509" "encoding/hex" "errors" "fmt" "math/big" "net/url" "strings" cf "go.step.sm/crypto/internal/darwin/corefoundation" "go.step.sm/crypto/internal/darwin/security" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/kms/uri" ) // Scheme is the scheme used in uris, the string "mackms". const Scheme = string(apiv1.MacKMS) // DefaultTag is the default tag attribute (kSecAttrApplicationTag) added to all // the keys. var DefaultTag = "com.smallstep.crypto" type keyAttributes struct { label string tag string hash []byte retry bool useSecureEnclave bool useBiometrics bool sigAlgorithm apiv1.SignatureAlgorithm keySize int } // retryAttributes returns the original URI attributes used to get a private // key, but only if they are different that the ones set. It will return nil, if // they are the same. The only attribute that can change is the tag. This method // would return the tag empty if it was set using the default value. func (k *keyAttributes) retryAttributes() *keyAttributes { if !k.retry { return nil } return &keyAttributes{ label: k.label, hash: k.hash, retry: false, } } type keySearchAttributes struct { label string tag string hash []byte secureEnclaveSet bool useSecureEnclave bool } type certAttributes struct { label string serialNumber *big.Int } type algorithmAttributes struct { Type string Size int } var signatureAlgorithmMapping = map[apiv1.SignatureAlgorithm]algorithmAttributes{ apiv1.UnspecifiedSignAlgorithm: {"EC", 256}, apiv1.SHA256WithRSA: {"RSA", 3072}, apiv1.SHA384WithRSA: {"RSA", 3072}, apiv1.SHA512WithRSA: {"RSA", 3072}, apiv1.SHA256WithRSAPSS: {"RSA", 3072}, apiv1.SHA384WithRSAPSS: {"RSA", 3072}, apiv1.SHA512WithRSAPSS: {"RSA", 3072}, apiv1.ECDSAWithSHA256: {"EC", 256}, apiv1.ECDSAWithSHA384: {"EC", 384}, apiv1.ECDSAWithSHA512: {"EC", 521}, } // MacKMS is a key manager that uses keys stored in macOS Keychain or in the // Secure Enclave. // // CreateKey methods can create keys with the following URIs: // - mackms:label=my-name // - mackms:label=my-name;tag=com.smallstep.crypto // - mackms;label=my-name;tag= // - mackms;label=my-name;se=true;bio=true // // GetPublicKey and CreateSigner accepts the above URIs as well as the following // ones: // - my-name // - mackms:label=my-name;tag=com.smallstep.crypto;hash=ccb792f9d9a1262bfb814a339876f825bdba1261 // // The above URIs support the following attributes: // - "label" corresponds with Apple's kSecAttrLabel. It is always required and // represents the key name. You will be able to see the keys in the Keychain, // looking for the value. // - "tag" corresponds with kSecAttrApplicationTag. It defaults to // com.smallstep.crypto. If tag is an empty string ("tag="), the attribute // will not be set. // - "se" is a boolean. If set to true, it will store the key in the // Secure Enclave. This option requires the application to be code-signed // with the appropriate entitlements. // - "bio" is a boolean value. If set to true, sign and verify operations // require Touch ID or Face ID. This options requires the key to be in the // Secure Enclave. // - "hash" corresponds with kSecAttrApplicationLabel. It is the SHA-1 of the // DER representation of an RSA public key using the PKCS #1 format or the // SHA-1 of the uncompressed ECDSA point according to SEC 1, Version 2.0, // Section 2.3.4. type MacKMS struct{} // New returns a new SoftKMS. func New(context.Context, apiv1.Options) (*MacKMS, error) { return &MacKMS{}, nil } func init() { apiv1.Register(apiv1.MacKMS, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) { return New(ctx, opts) }) } // Close is a noop that just returns nil. func (k *MacKMS) Close() error { return nil } // GetPublicKey returns the public key from the given URI in the request name. func (k *MacKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) { if req.Name == "" { return nil, fmt.Errorf("getPublicKeyRequest 'name' cannot be empty") } u, err := parseURI(req.Name) if err != nil { return nil, fmt.Errorf("mackms GetPublicKey failed: %w", err) } key, err := getPrivateKey(u) if err != nil { return nil, fmt.Errorf("mackms GetPublicKey failed: %w", apiv1Error(err)) } defer key.Release() pub, _, err := extractPublicKey(key) if err != nil { return nil, fmt.Errorf("mackms GetPublicKey failed: %w", err) } return pub, nil } // CreateKey generates a new key on the Keychain or Secure Enclave using the // Apple Security framework. func (k *MacKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) { if req.Name == "" { return nil, fmt.Errorf("createKeyRequest 'name' cannot be empty") } u, err := parseURI(req.Name) if err != nil { return nil, fmt.Errorf("mackms CreateKey failed: %w", err) } alg, ok := signatureAlgorithmMapping[req.SignatureAlgorithm] if !ok { return nil, fmt.Errorf("createKeyRequest 'signatureAlgorithm=%q' is not supported", req.SignatureAlgorithm) } if u.useSecureEnclave && req.SignatureAlgorithm != apiv1.UnspecifiedSignAlgorithm && req.SignatureAlgorithm != apiv1.ECDSAWithSHA256 { return nil, fmt.Errorf("createKeyRequest 'signatureAlgorithm=%q' is not supported on Secure Enclave", req.SignatureAlgorithm) } u.sigAlgorithm = req.SignatureAlgorithm if alg.Type == "RSA" && req.Bits > 0 { u.keySize = req.Bits } else { u.keySize = alg.Size } // Define key attributes cfLabel, err := cf.NewString(u.label) if err != nil { return nil, fmt.Errorf("mackms CreateKey failed: %w", err) } defer cfLabel.Release() keyAttributesDict := cf.Dictionary{ security.KSecAttrIsPermanent: cf.True, } if u.tag != "" { cfTag, err := cf.NewData([]byte(u.tag)) if err != nil { return nil, fmt.Errorf("mackms CreateKey failed: %w", err) } defer cfTag.Release() keyAttributesDict[security.KSecAttrApplicationTag] = cfTag } if u.useSecureEnclave { // After the first unlock, the data remains accessible until the next // restart. This is recommended for items that need to be accessed by // background applications. Items with this attribute do not migrate to // a new device. Thus, after restoring from a backup of a different // device, these items will not be present. // // TODO: make this a configuration option flags := security.KSecAccessControlPrivateKeyUsage if u.useBiometrics { flags |= security.KSecAccessControlAnd flags |= security.KSecAccessControlBiometryCurrentSet } access, err := security.SecAccessControlCreateWithFlags( security.KSecAttrAccessibleWhenUnlockedThisDeviceOnly, flags, ) if err != nil { return nil, fmt.Errorf("mackms CreateKey failed: %w", err) } defer access.Release() keyAttributesDict[security.KSecAttrAccessControl] = access } keyAttributes, err := cf.NewDictionary(keyAttributesDict) if err != nil { return nil, fmt.Errorf("mackms CreateKey failed: %w", err) } defer keyAttributes.Release() bits := cf.NewNumber(u.keySize) defer bits.Release() // Define key attributes attrsDict := cf.Dictionary{ security.KSecAttrLabel: cfLabel, security.KSecAttrKeySizeInBits: bits, security.KSecPrivateKeyAttrs: keyAttributes, } if u.useSecureEnclave { attrsDict[security.KSecAttrTokenID] = security.KSecAttrTokenIDSecureEnclave } else { attrsDict[security.KSecPublicKeyAttrs] = keyAttributes } switch u.sigAlgorithm { case apiv1.UnspecifiedSignAlgorithm: attrsDict[security.KSecAttrKeyType] = security.KSecAttrKeyTypeECSECPrimeRandom case apiv1.ECDSAWithSHA256, apiv1.ECDSAWithSHA384, apiv1.ECDSAWithSHA512: attrsDict[security.KSecAttrKeyType] = security.KSecAttrKeyTypeECSECPrimeRandom case apiv1.SHA256WithRSA, apiv1.SHA384WithRSA, apiv1.SHA512WithRSA: attrsDict[security.KSecAttrKeyType] = security.KSecAttrKeyTypeRSA case apiv1.SHA256WithRSAPSS, apiv1.SHA384WithRSAPSS, apiv1.SHA512WithRSAPSS: attrsDict[security.KSecAttrKeyType] = security.KSecAttrKeyTypeRSA default: return nil, fmt.Errorf("mackms CreateKey failed: unsupported signature algorithm %s", u.sigAlgorithm) } attrs, err := cf.NewDictionary(attrsDict) if err != nil { return nil, fmt.Errorf("mackms CreateKey failed: %w", err) } defer attrs.Release() secKeyRef, err := security.SecKeyCreateRandomKey(attrs) if err != nil { return nil, fmt.Errorf("mackms CreateKey failed: %w", apiv1Error(err)) } defer secKeyRef.Release() pub, hash, err := extractPublicKey(secKeyRef) if err != nil { return nil, fmt.Errorf("mackms CreateKey failed: %w", err) } name := uri.New(Scheme, url.Values{ "label": []string{u.label}, "tag": []string{u.tag}, "hash": []string{hex.EncodeToString(hash)}, }) if u.useSecureEnclave { name.Values.Set("se", "true") } if u.useBiometrics { name.Values.Set("bio", "true") } return &apiv1.CreateKeyResponse{ Name: name.String(), PublicKey: pub, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: name.String(), }, }, nil } // CreateSigner returns a new [crypto.Signer] from the given URI in the request // signing key. func (k *MacKMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) { if req.SigningKey == "" { return nil, fmt.Errorf("createSignerRequest 'signingKey' cannot be empty") } u, err := parseURI(req.SigningKey) if err != nil { return nil, fmt.Errorf("mackms CreateSigner failed: %w", err) } key, err := getPrivateKey(u) if err != nil { return nil, fmt.Errorf("mackms CreateSigner failed: %w", apiv1Error(err)) } defer key.Release() pub, _, err := extractPublicKey(key) if err != nil { return nil, fmt.Errorf("mackms CreateSigner failed: %w", err) } return &Signer{ keyAttributes: u, pub: pub, }, nil } // LoadCertificate returns an x509.Certificate by its label and/or serial // number. By default Apple Keychain will use the certificate common name as the // label. // // Valid names (URIs) are: // - mackms:label=test@example.com // - mackms:serial=2c273934eda8454d2595a94497e2395a // - mackms:label=test@example.com;serial=2c273934eda8454d2595a94497e2395a func (k *MacKMS) LoadCertificate(req *apiv1.LoadCertificateRequest) (*x509.Certificate, error) { if req.Name == "" { return nil, fmt.Errorf("loadCertificateRequest 'name' cannot be empty") } // Require label or serial u, err := parseCertURI(req.Name, true) if err != nil { return nil, fmt.Errorf("mackms LoadCertificate failed: %w", err) } cert, err := loadCertificate(u.label, u.serialNumber, nil) if err != nil { return nil, fmt.Errorf("mackms LoadCertificate failed: %w", apiv1Error(err)) } return cert, nil } // StoreCertificate stores a certificate in the Apple Keychain. There is no need // to provide a label in the URI as Apple will use the CommonName as the default // label, but if one is provided, the certificate in the Keychain will be // updated with the given label: // // Valid names (URIs) are: // - "" will use the common name as the label // - "mackms:" will use the common name // - "mackms:label=my-label" will use "my-label" // - "mackms:my-label" will use the "my-label" func (k *MacKMS) StoreCertificate(req *apiv1.StoreCertificateRequest) error { // There's not really need to require the name as macOS will use the common // name as default. if req.Certificate == nil { return fmt.Errorf("storeCertificateRequest 'certificate' cannot be empty") } // Do not require any parameter. Using mackms: is allowed as macOS will set // the commonName as label. u, err := parseCertURI(req.Name, false) if err != nil { return fmt.Errorf("mackms StoreCertificate failed: %w", err) } // Store the certificate and update the label if required if err := storeCertificate(u.label, req.Certificate); err != nil { return fmt.Errorf("mackms StoreCertificate failed: %w", apiv1Error(err)) } return nil } // LoadCertificateChain returns the leaf certificate by label and/or serial // number and its intermediate certificates. By default Apple Keychain will use // the certificate common name as the label. // // Valid names (URIs) are: // - mackms:label=test@example.com // - mackms:serial=2c273934eda8454d2595a94497e2395a // - mackms:label=test@example.com;serial=2c273934eda8454d2595a94497e2395a func (k *MacKMS) LoadCertificateChain(req *apiv1.LoadCertificateChainRequest) ([]*x509.Certificate, error) { if req.Name == "" { return nil, fmt.Errorf("loadCertificateChainRequest 'name' cannot be empty") } // Require label or serial u, err := parseCertURI(req.Name, true) if err != nil { return nil, fmt.Errorf("mackms LoadCertificateChain failed: %w", err) } cert, err := loadCertificate(u.label, u.serialNumber, nil) if err != nil { return nil, fmt.Errorf("mackms LoadCertificateChain failed: %w", apiv1Error(err)) } chain := []*x509.Certificate{cert} if isSelfSigned(cert) { return chain, nil } // Look for the rest of intermediates skipping the root. for { // The Keychain stores the subject as an attribute, but it saves some of // the values in uppercase. We cannot use the cert.RawIssuer to restrict // more the search with KSecAttrSubjectKeyID and kSecAttrSubject. To do // it we will need to "normalize" the subject it in the same way. parent, err := loadCertificate("", nil, cert.AuthorityKeyId) if err != nil || isSelfSigned(parent) || cert.CheckSignatureFrom(parent) != nil { break } cert = parent chain = append(chain, cert) } //nolint:nilerr // return only the intermediates present in keychain return chain, nil } // StoreCertificateChain stores a certificate chain in the Apple Keychain. There // is no need to provide a label in the URI as Apple will use the CommonName as // the default label, but if one is provided, the leaf certificate in the // Keychain will be updated with the given label: // // Valid names (URIs) are: // - "" will use the common name as the label // - "mackms:" will use the common name // - "mackms:label=my-label" will use "my-label" // - "mackms:my-label" will use the "my-label" func (k *MacKMS) StoreCertificateChain(req *apiv1.StoreCertificateChainRequest) error { // There's not really need to require the name as macOS will use the common // name as default. if len(req.CertificateChain) == 0 { return fmt.Errorf("storeCertificateChainRequest 'certificateChain' cannot be empty") } // Do not require any parameter. Using mackms: is allowed as macOS will set // the commonName as label. u, err := parseCertURI(req.Name, false) if err != nil { return fmt.Errorf("mackms StoreCertificateChain failed: %w", err) } // Store the certificate and update the label if required if err := storeCertificate(u.label, req.CertificateChain[0]); err != nil { return fmt.Errorf("mackms StoreCertificateChain failed: %w", apiv1Error(err)) } // Store the rest of the chain but do not fail if already exists for _, cert := range req.CertificateChain[1:] { if err := storeCertificate("", cert); err != nil && !errors.Is(err, security.ErrAlreadyExists) { return fmt.Errorf("mackms StoreCertificateChain failed: %w", err) } } return nil } // DeleteKey deletes the key referenced by the URI in the request name. // // # Experimental // // Notice: This API is EXPERIMENTAL and may be changed or removed in a later // release. func (*MacKMS) DeleteKey(req *apiv1.DeleteKeyRequest) error { if req.Name == "" { return fmt.Errorf("deleteKeyRequest 'name' cannot be empty") } u, err := parseURI(req.Name) if err != nil { return fmt.Errorf("mackms DeleteKey failed: %w", err) } cfLabel, err := cf.NewString(u.label) if err != nil { return fmt.Errorf("mackms DeleteKey failed: %w", err) } defer cfLabel.Release() for _, keyClass := range []cf.TypeRef{security.KSecAttrKeyClassPublic, security.KSecAttrKeyClassPrivate} { dict := cf.Dictionary{ security.KSecClass: security.KSecClassKey, security.KSecAttrLabel: cfLabel, security.KSecAttrKeyClass: keyClass, } if u.tag != "" { cfTag, err := cf.NewData([]byte(u.tag)) if err != nil { return fmt.Errorf("mackms DeleteKey failed: %w", err) } defer cfTag.Release() dict[security.KSecAttrApplicationTag] = cfTag } // Extract logic to deleteItem to avoid defer on loops if err := deleteItem(dict, u.hash); err != nil { return fmt.Errorf("mackms DeleteKey failed: %w", apiv1Error(err)) } } return nil } // DeleteCertificate deletes the certificate referenced by the URI in the // request name. // // # Experimental // // Notice: This API is EXPERIMENTAL and may be changed or removed in a later // release. func (*MacKMS) DeleteCertificate(req *apiv1.DeleteCertificateRequest) error { if req.Name == "" { return fmt.Errorf("deleteCertificateRequest 'name' cannot be empty") } u, err := parseCertURI(req.Name, true) if err != nil { return fmt.Errorf("mackms DeleteCertificate failed: %w", err) } query := cf.Dictionary{ security.KSecClass: security.KSecClassCertificate, } if u.label != "" { cfLabel, err := cf.NewString(u.label) if err != nil { return fmt.Errorf("mackms DeleteCertificate failed: %w", err) } defer cfLabel.Release() query[security.KSecAttrLabel] = cfLabel } if u.serialNumber != nil { cfSerial, err := cf.NewData(encodeSerialNumber(u.serialNumber)) if err != nil { return fmt.Errorf("mackms DeleteCertificate failed: %w", err) } defer cfSerial.Release() query[security.KSecAttrSerialNumber] = cfSerial } if err := deleteItem(query, nil); err != nil { return fmt.Errorf("mackms DeleteCertificate failed: %w", apiv1Error(err)) } return nil } // SearchKeys searches for keys according to the query URI in the request. By default, // all keys managed by the KMS using the default tag, and both Secure Enclave as well as // non-Secure Enclave keys will be returned. // // - "" will return all keys managed by the KMS (using the default tag) // - "mackms:" will return all keys managed by the KMS (using the default tag) // - "mackms:label=my-label" will return all keys using label "my-label" (and the default tag) // - "mackms:hash=the-hash" will return all keys having hash "hash" (and the default tag; generally one result) // - "mackms:tag=my-tag" will search for all keys with "my-tag" // - "mackms:se=true" will return all Secure Enclave keys managed by the KMS (using the default tag) // - "mackms:se=false" will return all non-Secure Enclave keys managed by the KMS (using the default tag) // // # Experimental // // Notice: This API is EXPERIMENTAL and may be changed or removed in a later // release. func (k *MacKMS) SearchKeys(req *apiv1.SearchKeysRequest) (*apiv1.SearchKeysResponse, error) { if req.Query == "" { return nil, fmt.Errorf("searchKeysRequest 'query' cannot be empty") } u, err := parseSearchURI(req.Query) if err != nil { return nil, fmt.Errorf("failed parsing query: %w", err) } keys, err := getPrivateKeys(u) if err != nil { return nil, fmt.Errorf("failed getting keys: %w", err) } results := make([]apiv1.SearchKeyResult, len(keys)) for i, key := range keys { d := cf.NewDictionaryRef(cf.TypeRef(key.TypeRef())) defer d.Release() name := uri.New(Scheme, url.Values{}) tokenID := security.GetSecAttrTokenID(d) keyInSecureEnclave := tokenID == "com.apple.setoken" switch { case !u.secureEnclaveSet && keyInSecureEnclave: name.Values.Set("se", "true") case !u.secureEnclaveSet && !keyInSecureEnclave: name.Values.Set("se", "false") case u.useSecureEnclave && keyInSecureEnclave: name.Values.Set("se", "true") case !u.useSecureEnclave && !keyInSecureEnclave: name.Values.Set("se", "false") default: // skip in case the query doesn't match the actual property continue } name.Values.Set("hash", hex.EncodeToString(security.GetSecAttrApplicationLabel(d))) name.Values.Set("label", security.GetSecAttrLabel(d)) name.Values.Set("tag", security.GetSecAttrApplicationTag(d)) // obtain the public key by requesting it, as the current // representation of the key includes just the attributes. pub, err := k.GetPublicKey(&apiv1.GetPublicKeyRequest{ Name: name.String(), }) if err != nil { return nil, fmt.Errorf("failed getting public key: %w", err) } results[i] = apiv1.SearchKeyResult{ Name: name.String(), PublicKey: pub, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: name.String(), }, } } return &apiv1.SearchKeysResponse{ Results: results, }, nil } var _ apiv1.SearchableKeyManager = (*MacKMS)(nil) func deleteItem(dict cf.Dictionary, hash []byte) error { if len(hash) > 0 { d, err := cf.NewData(hash) if err != nil { return err } defer d.Release() dict[security.KSecAttrApplicationLabel] = d } query, err := cf.NewDictionary(dict) if err != nil { return err } defer query.Release() if err := security.SecItemDelete(query); err != nil { if dict[security.KSecAttrKeyClass] == security.KSecAttrKeyClassPublic && errors.Is(err, security.ErrNotFound) { return nil } return err } return nil } func getPrivateKey(u *keyAttributes) (*security.SecKeyRef, error) { cfLabel, err := cf.NewString(u.label) if err != nil { return nil, err } defer cfLabel.Release() dict := cf.Dictionary{ security.KSecClass: security.KSecClassKey, security.KSecAttrLabel: cfLabel, security.KSecAttrKeyClass: security.KSecAttrKeyClassPrivate, security.KSecReturnRef: cf.True, security.KSecMatchLimit: security.KSecMatchLimitOne, } if u.tag != "" { cfTag, err := cf.NewData([]byte(u.tag)) if err != nil { return nil, err } defer cfTag.Release() dict[security.KSecAttrApplicationTag] = cfTag } if len(u.hash) > 0 { d, err := cf.NewData(u.hash) if err != nil { return nil, err } defer d.Release() dict[security.KSecAttrApplicationLabel] = d } // Get the query from the keychain query, err := cf.NewDictionary(dict) if err != nil { return nil, err } defer query.Release() var key cf.TypeRef if err := security.SecItemCopyMatching(query, &key); err != nil { // If not found retry without the tag if it wasn't set. if errors.Is(err, security.ErrNotFound) { if ru := u.retryAttributes(); ru != nil { return getPrivateKey(ru) } } return nil, fmt.Errorf("macOS SecItemCopyMatching failed: %w", err) } return security.NewSecKeyRef(key), nil } func getPrivateKeys(u *keySearchAttributes) ([]*security.SecKeychainItemRef, error) { dict := cf.Dictionary{ security.KSecClass: security.KSecClassKey, security.KSecAttrKeyClass: security.KSecAttrKeyClassPrivate, security.KSecReturnAttributes: cf.True, // return keychain attributes, i.e. tag and label security.KSecMatchLimit: security.KSecMatchLimitAll, } if u.tag != "" { cfTag, err := cf.NewData([]byte(u.tag)) if err != nil { return nil, err } defer cfTag.Release() dict[security.KSecAttrApplicationTag] = cfTag } if u.label != "" { cfLabel, err := cf.NewString(u.label) if err != nil { return nil, err } defer cfLabel.Release() dict[security.KSecAttrLabel] = cfLabel } if len(u.hash) > 0 { cfHash, err := cf.NewData(u.hash) if err != nil { return nil, err } defer cfHash.Release() dict[security.KSecAttrApplicationLabel] = cfHash } // construct the query query, err := cf.NewDictionary(dict) if err != nil { return nil, err } defer query.Release() // perform the query var result cf.TypeRef err = security.SecItemCopyMatching(query, &result) if err != nil { if errors.Is(err, security.ErrNotFound) { return []*security.SecKeychainItemRef{}, nil } return nil, fmt.Errorf("macOS SecItemCopyMatching failed: %w", err) } array := cf.NewArrayRef(result) defer array.Release() keys := make([]*security.SecKeychainItemRef, array.Len()) for i := 0; i < array.Len(); i++ { item := array.Get(i) key := security.NewSecKeychainItemRef(item) key.Retain() // retain the key, so that it's not released early keys[i] = key } return keys, nil } func extractPublicKey(secKeyRef *security.SecKeyRef) (crypto.PublicKey, []byte, error) { // Get the hash of the public key. We can also calculate this from the // external representation below, but in case Apple decides to switch from // SHA-1, let's just use what macOS sets by default. attrs := security.SecKeyCopyAttributes(secKeyRef) defer attrs.Release() hash := security.GetSecAttrApplicationLabel(attrs) // Attempt to extract the public key, it will fail if the app that created // the private key didnโ€™t also store the corresponding public key in the // keychain, or if the system canโ€™t reconstruct the corresponding public // key. if publicKey, err := security.SecKeyCopyPublicKey(secKeyRef); err == nil { defer publicKey.Release() data, err := security.SecKeyCopyExternalRepresentation(publicKey) if err != nil { return nil, nil, fmt.Errorf("macOS SecKeyCopyExternalRepresentation failed: %w", err) } defer data.Release() derBytes := data.Bytes() // ECDSA public keys are formatted as "04 || X || Y" if derBytes[0] == 0x04 { pub, err := parseECDSAPublicKey(derBytes) if err != nil { return nil, nil, fmt.Errorf("error parsing ECDSA key: %w", err) } return pub, hash, nil } // RSA public keys are formatted using PKCS #1 pub, err := x509.ParsePKCS1PublicKey(derBytes) if err != nil { return nil, nil, fmt.Errorf("error parsing RSA key: %w", err) } return pub, hash, nil } // At this point we only have the private key. data, err := security.SecKeyCopyExternalRepresentation(secKeyRef) if err != nil { return nil, nil, fmt.Errorf("macOS SecKeyCopyExternalRepresentation failed: %w", err) } defer data.Release() derBytes := data.Bytes() // ECDSA private keys are formatted as "04 || X || Y || K" if derBytes[0] == 0x04 { pub, err := parseECDSAPrivateKey(derBytes) if err != nil { return nil, nil, fmt.Errorf("error parsing ECDSA key: %w", err) } return pub, hash, nil } // RSA private keys are formatted using PKCS #1 priv, err := x509.ParsePKCS1PrivateKey(derBytes) if err != nil { return nil, nil, fmt.Errorf("error parsing key: %w", err) } return priv.Public(), hash, nil } // isSelfSigned checks if a certificate is self signed. The algorithm looks like this: // // If subject != issuer: false // ElseIf subjectKeyID != authorityKey: false // ElseIf checkSignature: true // Otherwise: false func isSelfSigned(cert *x509.Certificate) bool { if bytes.Equal(cert.RawSubject, cert.RawIssuer) { if cert.SubjectKeyId != nil && cert.AuthorityKeyId != nil && !bytes.Equal(cert.SubjectKeyId, cert.AuthorityKeyId) { return false } return cert.CheckSignature(cert.SignatureAlgorithm, cert.RawTBSCertificate, cert.Signature) == nil } return false } func loadCertificate(label string, serialNumber *big.Int, subjectKeyID []byte) (*x509.Certificate, error) { query := cf.Dictionary{ security.KSecClass: security.KSecClassCertificate, } if label != "" { cfLabel, err := cf.NewString(label) if err != nil { return nil, err } defer cfLabel.Release() query[security.KSecAttrLabel] = cfLabel } if serialNumber != nil { cfSerial, err := cf.NewData(encodeSerialNumber(serialNumber)) if err != nil { return nil, err } defer cfSerial.Release() query[security.KSecAttrSerialNumber] = cfSerial } if subjectKeyID != nil { cfSubjectKeyID, err := cf.NewData(subjectKeyID) if err != nil { return nil, err } defer cfSubjectKeyID.Release() query[security.KSecAttrSubjectKeyID] = cfSubjectKeyID } cfQuery, err := cf.NewDictionary(query) if err != nil { return nil, err } defer cfQuery.Release() var ref cf.TypeRef if err := security.SecItemCopyMatching(cfQuery, &ref); err != nil { return nil, err } defer ref.Release() data, err := security.SecCertificateCopyData(security.NewSecCertificateRef(ref)) if err != nil { return nil, err } defer data.Release() cert, err := x509.ParseCertificate(data.Bytes()) if err != nil { return nil, err } return cert, nil } func storeCertificate(label string, cert *x509.Certificate) error { cfData, err := cf.NewData(cert.Raw) if err != nil { return err } defer cfData.Release() certRef, err := security.SecCertificateCreateWithData(cfData) if err != nil { return err } defer certRef.Release() // Adding the label here doesn't have any effect. Apple Keychain always uses // the commonName. attributes, err := cf.NewDictionary(cf.Dictionary{ security.KSecClass: security.KSecClassCertificate, security.KSecValueRef: certRef, }) if err != nil { return err } defer attributes.Release() // Store the certificate if err := security.SecItemAdd(attributes, nil); err != nil { return err } // Update the label if necessary if label != "" && label != cert.Subject.CommonName { cfLabel, err := cf.NewString(label) if err != nil { return err } defer cfLabel.Release() query, err := cf.NewDictionary(cf.Dictionary{ security.KSecValueRef: certRef, }) if err != nil { return err } defer query.Release() update, err := cf.NewDictionary(cf.Dictionary{ security.KSecAttrLabel: cfLabel, }) if err != nil { return err } defer update.Release() if err := security.SecItemUpdate(query, update); err != nil { return err } } return nil } func parseURI(rawuri string) (*keyAttributes, error) { // When rawuri is just the key name if !strings.HasPrefix(strings.ToLower(rawuri), Scheme) { return &keyAttributes{ label: rawuri, tag: DefaultTag, retry: true, }, nil } // When rawuri is a mackms uri. u, err := uri.ParseWithScheme(Scheme, rawuri) if err != nil { return nil, err } // Special case for mackms:label if len(u.Values) == 1 { for k, v := range u.Values { if (len(v) == 1 && v[0] == "") || len(v) == 0 { return &keyAttributes{ label: k, tag: DefaultTag, retry: true, }, nil } } } // With regular values, uris look like this: // mackms:label=my-key;tag=my-tag;hash=010a...;se=true;bio=true label := u.Get("label") if label == "" { return nil, fmt.Errorf("error parsing %q: label is required", rawuri) } tag := u.Get("tag") if tag == "" && !u.Has("tag") { tag = DefaultTag } return &keyAttributes{ label: label, tag: tag, hash: u.GetEncoded("hash"), retry: !u.Has("tag"), useSecureEnclave: u.GetBool("se"), useBiometrics: u.GetBool("bio"), }, nil } func parseCertURI(rawuri string, requireValue bool) (*certAttributes, error) { // When rawuri is just the label if !strings.HasPrefix(strings.ToLower(rawuri), Scheme) { return &certAttributes{ label: rawuri, }, nil } // When rawuri is a mackms uri. u, err := uri.ParseWithScheme(Scheme, rawuri) if err != nil { return nil, err } // Special case for mackms:label if len(u.Values) == 1 { for k, v := range u.Values { if (len(v) == 1 && v[0] == "") || len(v) == 0 { return &certAttributes{ label: k, }, nil } } } // With regular values, uris look like this: // mackms:label=my-cert;serial=01020A0B... label := u.Get("label") serial := u.GetEncoded("serial") if requireValue && label == "" && len(serial) == 0 { return nil, fmt.Errorf("error parsing %q: label or serial are required", rawuri) } var serialNumber *big.Int if len(serial) > 0 { serialNumber = new(big.Int).SetBytes(serial) } return &certAttributes{ label: label, serialNumber: serialNumber, }, nil } func parseSearchURI(rawuri string) (*keySearchAttributes, error) { // When rawuri is just the key name if !strings.HasPrefix(strings.ToLower(rawuri), Scheme) { return &keySearchAttributes{ label: rawuri, tag: DefaultTag, }, nil } // When rawuri is a mackms uri. u, err := uri.Parse(rawuri) if err != nil { return nil, err } // Special case for mackms:label if len(u.Values) == 1 { for k, v := range u.Values { if (len(v) == 1 && v[0] == "") || len(v) == 0 { return &keySearchAttributes{ label: k, tag: DefaultTag, }, nil } } } // With regular values, uris look like this: // mackms:label=my-key;tag=my-tag;hash=010a...;se=true;bio=true label := u.Get("label") // when searching, the label can be empty tag := u.Get("tag") if tag == "" && !u.Has("tag") { tag = DefaultTag } return &keySearchAttributes{ label: label, tag: tag, hash: u.GetEncoded("hash"), secureEnclaveSet: u.Values.Has("se"), useSecureEnclave: u.GetBool("se"), }, nil } // encodeSerialNumber encodes the serial number of a certificate in ASN.1. // Negative serial numbers are not allowed. func encodeSerialNumber(s *big.Int) []byte { if s.Sign() == 0 { return []byte{0x00} } b := s.Bytes() if b[0]&0x80 != 0 { // Pad this with 0x00 in order to stop it looking like a negative number. return append([]byte{0x00}, b...) } return b } func parseECDSAPublicKey(raw []byte) (crypto.PublicKey, error) { switch len(raw) / 2 { case 32: // 65 bytes key, err := ecdh.P256().NewPublicKey(raw) if err != nil { return nil, err } return ecdhToECDSAPublicKey(key) case 48: // 97 bytes key, err := ecdh.P384().NewPublicKey(raw) if err != nil { return nil, err } return ecdhToECDSAPublicKey(key) case 66: // 133 bytes: key, err := ecdh.P521().NewPublicKey(raw) if err != nil { return nil, err } return ecdhToECDSAPublicKey(key) default: return nil, fmt.Errorf("unsupported ECDSA key with %d bytes", len(raw)) } } func parseECDSAPrivateKey(raw []byte) (crypto.PublicKey, error) { switch len(raw) / 3 { case 32: // 97 bytes key, err := ecdh.P256().NewPrivateKey(raw[65:]) if err != nil { return nil, err } return ecdhToECDSAPublicKey(key.PublicKey()) case 48: // 145 bytes key, err := ecdh.P384().NewPrivateKey(raw[97:]) if err != nil { return nil, err } return ecdhToECDSAPublicKey(key.PublicKey()) case 66: // 199 bytes: key, err := ecdh.P521().NewPrivateKey(raw[133:]) if err != nil { return nil, err } return ecdhToECDSAPublicKey(key.PublicKey()) default: return nil, fmt.Errorf("unsupported ECDSA key with %d bytes", len(raw)) } } func ecdhToECDSAPublicKey(key *ecdh.PublicKey) (*ecdsa.PublicKey, error) { rawKey := key.Bytes() switch key.Curve() { case ecdh.P256(): return &ecdsa.PublicKey{ Curve: elliptic.P256(), X: big.NewInt(0).SetBytes(rawKey[1:33]), Y: big.NewInt(0).SetBytes(rawKey[33:]), }, nil case ecdh.P384(): return &ecdsa.PublicKey{ Curve: elliptic.P384(), X: big.NewInt(0).SetBytes(rawKey[1:49]), Y: big.NewInt(0).SetBytes(rawKey[49:]), }, nil case ecdh.P521(): return &ecdsa.PublicKey{ Curve: elliptic.P521(), X: big.NewInt(0).SetBytes(rawKey[1:67]), Y: big.NewInt(0).SetBytes(rawKey[67:]), }, nil default: return nil, errors.New("failed to convert *ecdh.PublicKey to *ecdsa.PublicKey") } } func apiv1Error(err error) error { switch { case errors.Is(err, security.ErrNotFound): return apiv1.NotFoundError{ Message: err.Error(), } case errors.Is(err, security.ErrAlreadyExists): return apiv1.AlreadyExistsError{ Message: err.Error(), } default: return err } } crypto-0.57.0/kms/mackms/mackms_other.go000066400000000000000000000010651474156331600202070ustar00rootroot00000000000000//go:build !cgo || !darwin || nomackms package mackms import ( "context" "fmt" "os" "path/filepath" "runtime" "go.step.sm/crypto/kms/apiv1" ) func init() { apiv1.Register(apiv1.MacKMS, func(context.Context, apiv1.Options) (apiv1.KeyManager, error) { name := filepath.Base(os.Args[0]) switch runtime.GOOS { case "darwin": return nil, fmt.Errorf("unsupported kms type 'mackms': %q is compiled without cgo or mackms support", name) default: return nil, fmt.Errorf("unsupported kms type 'mackms': %q is not running on a macOS", name) } }) } crypto-0.57.0/kms/mackms/mackms_test.go000066400000000000000000001321321474156331600200450ustar00rootroot00000000000000//go:build darwin && cgo && !nomackms // Copyright (c) Smallstep Labs, Inc. // Copyright (c) Meta Platforms, Inc. and affiliates. // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. package mackms import ( "context" "crypto" "crypto/ecdh" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" "crypto/x509/pkix" "encoding/hex" "fmt" "io" "math/big" "net/url" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" cf "go.step.sm/crypto/internal/darwin/corefoundation" "go.step.sm/crypto/internal/darwin/security" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/kms/uri" "go.step.sm/crypto/minica" "go.step.sm/crypto/randutil" ) func createPrivateKeyOnly(t *testing.T, name string, signatureAlgorithm apiv1.SignatureAlgorithm) *apiv1.CreateKeyResponse { t.Helper() u, err := parseURI(name) require.NoError(t, err) u.sigAlgorithm = signatureAlgorithm u.keySize = signatureAlgorithmMapping[signatureAlgorithm].Size // Define key attributes cfTag, err := cf.NewData([]byte(u.tag)) require.NoError(t, err) defer cfTag.Release() cfLabel, err := cf.NewString(u.label) require.NoError(t, err) defer cfLabel.Release() keyAttributesDict := cf.Dictionary{ security.KSecAttrApplicationTag: cfTag, security.KSecAttrIsPermanent: cf.True, } keyAttributes, err := cf.NewDictionary(keyAttributesDict) require.NoError(t, err) defer keyAttributes.Release() bits := cf.NewNumber(u.keySize) defer bits.Release() // Define key attributes attrsDict := cf.Dictionary{ security.KSecAttrLabel: cfLabel, security.KSecAttrKeySizeInBits: bits, security.KSecPrivateKeyAttrs: keyAttributes, } switch u.sigAlgorithm { case apiv1.UnspecifiedSignAlgorithm: attrsDict[security.KSecAttrKeyType] = security.KSecAttrKeyTypeECSECPrimeRandom case apiv1.ECDSAWithSHA256, apiv1.ECDSAWithSHA384, apiv1.ECDSAWithSHA512: attrsDict[security.KSecAttrKeyType] = security.KSecAttrKeyTypeECSECPrimeRandom case apiv1.SHA256WithRSA, apiv1.SHA384WithRSA, apiv1.SHA512WithRSA: attrsDict[security.KSecAttrKeyType] = security.KSecAttrKeyTypeRSA case apiv1.SHA256WithRSAPSS, apiv1.SHA384WithRSAPSS, apiv1.SHA512WithRSAPSS: attrsDict[security.KSecAttrKeyType] = security.KSecAttrKeyTypeRSA default: t.Fatalf("unsupported signature algorithm %s", u.sigAlgorithm) } attrs, err := cf.NewDictionary(attrsDict) require.NoError(t, err) defer attrs.Release() secKeyRef, err := security.SecKeyCreateRandomKey(attrs) require.NoError(t, err) defer secKeyRef.Release() pub, hash, err := extractPublicKey(secKeyRef) require.NoError(t, err) return &apiv1.CreateKeyResponse{ Name: uri.New(Scheme, url.Values{ "label": []string{u.label}, "tag": []string{u.tag}, "hash": []string{hex.EncodeToString(hash)}, }).String(), PublicKey: pub, } } func TestNew(t *testing.T) { type args struct { in0 context.Context in1 apiv1.Options } tests := []struct { name string args args want *MacKMS assertion assert.ErrorAssertionFunc }{ {"ok", args{context.Background(), apiv1.Options{}}, &MacKMS{}, assert.NoError}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := New(tt.args.in0, tt.args.in1) tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } } func TestMacKMS(t *testing.T) { type verifier func(*testing.T, *MacKMS, *apiv1.CreateKeyRequest, *apiv1.CreateKeyResponse) verifyWithType := func(typ crypto.PublicKey, bits int) verifier { return func(t *testing.T, kms *MacKMS, req *apiv1.CreateKeyRequest, resp *apiv1.CreateKeyResponse) { require.NotNil(t, resp) require.NotEmpty(t, resp.Name) require.NotNil(t, resp.PublicKey) require.NotEmpty(t, resp.CreateSignerRequest) if assert.IsType(t, typ, resp.PublicKey) { switch p := resp.PublicKey.(type) { case *ecdsa.PublicKey: assert.Equal(t, bits, p.Curve.Params().BitSize) case *rsa.PublicKey: assert.Equal(t, bits, p.N.BitLen()) default: t.Fatalf("unsupported public key type %T", p) } } // GetPublicKey pub, err := kms.GetPublicKey(&apiv1.GetPublicKeyRequest{ Name: resp.Name, }) require.NoError(t, err) assert.Equal(t, resp.PublicKey, pub) // CreateSigner message, err := randutil.Bytes(256) require.NoError(t, err) var opts crypto.SignerOpts switch req.SignatureAlgorithm { case apiv1.UnspecifiedSignAlgorithm, apiv1.ECDSAWithSHA256, apiv1.SHA256WithRSA: opts = crypto.SHA256 case apiv1.ECDSAWithSHA384, apiv1.SHA384WithRSA: opts = crypto.SHA384 case apiv1.ECDSAWithSHA512, apiv1.SHA512WithRSA: opts = crypto.SHA512 case apiv1.SHA256WithRSAPSS: opts = &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: crypto.SHA256, } case apiv1.SHA384WithRSAPSS: opts = &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthAuto, Hash: crypto.SHA384, } case apiv1.SHA512WithRSAPSS: opts = &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthAuto, Hash: crypto.SHA512, } default: t.Fatalf("unsupported signature algorithm %s", req.SignatureAlgorithm) } h := opts.HashFunc().New() h.Write(message) digest := h.Sum(nil) signer, err := kms.CreateSigner(&resp.CreateSignerRequest) require.NoError(t, err) signature, err := signer.Sign(rand.Reader, digest, opts) require.NoError(t, err) assert.Equal(t, pub, signer.Public()) switch p := pub.(type) { case *ecdsa.PublicKey: assert.True(t, ecdsa.VerifyASN1(p, digest, signature)) case *rsa.PublicKey: if o, ok := opts.(*rsa.PSSOptions); ok { assert.NoError(t, rsa.VerifyPSS(p, o.HashFunc(), digest, signature, o)) } else { assert.NoError(t, rsa.VerifyPKCS1v15(p, opts.HashFunc(), digest, signature)) } } } } type args struct { req *apiv1.CreateKeyRequest } tests := []struct { name string k *MacKMS args args verify verifier assertion assert.ErrorAssertionFunc }{ {"ok default", &MacKMS{}, args{&apiv1.CreateKeyRequest{ Name: "mackms:label=test-default", SignatureAlgorithm: apiv1.UnspecifiedSignAlgorithm, }}, verifyWithType(&ecdsa.PublicKey{}, 256), assert.NoError}, {"ok p256", &MacKMS{}, args{&apiv1.CreateKeyRequest{ Name: "mackms:label=test-p256", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }}, verifyWithType(&ecdsa.PublicKey{}, 256), assert.NoError}, {"ok p384", &MacKMS{}, args{&apiv1.CreateKeyRequest{ Name: "mackms:label=test-p384", SignatureAlgorithm: apiv1.ECDSAWithSHA384, }}, verifyWithType(&ecdsa.PublicKey{}, 384), assert.NoError}, {"ok p521", &MacKMS{}, args{&apiv1.CreateKeyRequest{ Name: "mackms:label=test-p521", SignatureAlgorithm: apiv1.ECDSAWithSHA512, }}, verifyWithType(&ecdsa.PublicKey{}, 521), assert.NoError}, {"ok RSA", &MacKMS{}, args{&apiv1.CreateKeyRequest{ Name: "mackms:label=test-rsa", SignatureAlgorithm: apiv1.SHA256WithRSA, }}, verifyWithType(&rsa.PublicKey{}, 3072), assert.NoError}, {"ok RSA 2048", &MacKMS{}, args{&apiv1.CreateKeyRequest{ Name: "mackms:label=test-2048", SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 2048, }}, verifyWithType(&rsa.PublicKey{}, 2048), assert.NoError}, {"ok RSA 3072", &MacKMS{}, args{&apiv1.CreateKeyRequest{ Name: "mackms:label=test-3072", SignatureAlgorithm: apiv1.SHA384WithRSA, Bits: 3072, }}, verifyWithType(&rsa.PublicKey{}, 3072), assert.NoError}, {"ok RSA 4096", &MacKMS{}, args{&apiv1.CreateKeyRequest{ Name: "mackms:label=test-4096", SignatureAlgorithm: apiv1.SHA512WithRSA, Bits: 4096, }}, verifyWithType(&rsa.PublicKey{}, 4096), assert.NoError}, {"ok RSA-PSS 2048", &MacKMS{}, args{&apiv1.CreateKeyRequest{ Name: "mackms:label=test-2048-pss", SignatureAlgorithm: apiv1.SHA256WithRSAPSS, Bits: 2048, }}, verifyWithType(&rsa.PublicKey{}, 2048), assert.NoError}, {"ok RSA-PSS 3072", &MacKMS{}, args{&apiv1.CreateKeyRequest{ Name: "mackms:label=test-3072-pss", SignatureAlgorithm: apiv1.SHA384WithRSAPSS, Bits: 3072, }}, verifyWithType(&rsa.PublicKey{}, 3072), assert.NoError}, {"ok RSA-PSS 4096", &MacKMS{}, args{&apiv1.CreateKeyRequest{ Name: "mackms:label=test-4096-pss", SignatureAlgorithm: apiv1.SHA512WithRSAPSS, Bits: 4096, }}, verifyWithType(&rsa.PublicKey{}, 4096), assert.NoError}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &MacKMS{} t.Cleanup(func() { assert.NoError(t, k.DeleteKey(&apiv1.DeleteKeyRequest{ Name: tt.args.req.Name, })) }) got, err := k.CreateKey(tt.args.req) tt.assertion(t, err) tt.verify(t, k, tt.args.req, got) }) } } func TestMacKMS_GetPublicKey(t *testing.T) { kms := &MacKMS{} r1, err := kms.CreateKey(&apiv1.CreateKeyRequest{ Name: "mackms:label=test-p256", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }) require.NoError(t, err) // Create private keys only r2 := createPrivateKeyOnly(t, "mackms:label=test-ecdsa", apiv1.ECDSAWithSHA256) r3 := createPrivateKeyOnly(t, "mackms:label=test-rsa;tag=", apiv1.SHA256WithRSA) t.Cleanup(func() { assert.NoError(t, kms.DeleteKey(&apiv1.DeleteKeyRequest{ Name: r1.Name, })) assert.NoError(t, kms.DeleteKey(&apiv1.DeleteKeyRequest{ Name: r2.Name, })) assert.NoError(t, kms.DeleteKey(&apiv1.DeleteKeyRequest{ Name: r3.Name, })) }) type args struct { req *apiv1.GetPublicKeyRequest } tests := []struct { name string k *MacKMS args args want crypto.PublicKey assertion assert.ErrorAssertionFunc }{ {"ok", &MacKMS{}, args{&apiv1.GetPublicKeyRequest{Name: r1.Name}}, r1.PublicKey, assert.NoError}, {"ok no tag", &MacKMS{}, args{&apiv1.GetPublicKeyRequest{Name: "mackms:label=test-p256;tag="}}, r1.PublicKey, assert.NoError}, {"ok private only ECDSA ", &MacKMS{}, args{&apiv1.GetPublicKeyRequest{Name: "mackms:label=test-ecdsa"}}, r2.PublicKey, assert.NoError}, {"ok private only RSA", &MacKMS{}, args{&apiv1.GetPublicKeyRequest{Name: r3.Name}}, r3.PublicKey, assert.NoError}, {"ok private only RSA with retry", &MacKMS{}, args{&apiv1.GetPublicKeyRequest{Name: "mackms:label=test-rsa"}}, r3.PublicKey, assert.NoError}, {"ok no uri", &MacKMS{}, args{&apiv1.GetPublicKeyRequest{Name: "test-p256"}}, r1.PublicKey, assert.NoError}, {"ok uri simple", &MacKMS{}, args{&apiv1.GetPublicKeyRequest{Name: "mackms:test-p256"}}, r1.PublicKey, assert.NoError}, {"ok uri label", &MacKMS{}, args{&apiv1.GetPublicKeyRequest{Name: "mackms:label=test-p256"}}, r1.PublicKey, assert.NoError}, {"ok uri label + tag", &MacKMS{}, args{&apiv1.GetPublicKeyRequest{Name: "mackms:label=test-p256;tag=com.smallstep.crypto"}}, r1.PublicKey, assert.NoError}, {"fail bad label", &MacKMS{}, args{&apiv1.GetPublicKeyRequest{Name: "mackms:label=test-fail-p256"}}, nil, assert.Error}, {"fail bad tag", &MacKMS{}, args{&apiv1.GetPublicKeyRequest{Name: "mackms:label=test-p256;tag=com.step.crypto"}}, nil, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &MacKMS{} got, err := k.GetPublicKey(tt.args.req) tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } } func TestMacKMS_CreateKey(t *testing.T) { t.Cleanup(func() { kms := &MacKMS{} assert.NoError(t, kms.DeleteKey(&apiv1.DeleteKeyRequest{ Name: "mackms:label=test-p256", })) assert.NoError(t, kms.DeleteKey(&apiv1.DeleteKeyRequest{ Name: "mackms:label=test-p256-2;tag=", })) }) type args struct { req *apiv1.CreateKeyRequest } tests := []struct { name string k *MacKMS args args verify require.ValueAssertionFunc assertion assert.ErrorAssertionFunc }{ {"ok", &MacKMS{}, args{&apiv1.CreateKeyRequest{Name: "mackms:label=test-p256"}}, func(tt require.TestingT, i1 interface{}, i2 ...interface{}) { require.IsType(tt, &apiv1.CreateKeyResponse{}, i1) resp := i1.(*apiv1.CreateKeyResponse) require.NotEmpty(tt, resp.Name) require.NotNil(tt, resp.PublicKey) require.Nil(tt, resp.PrivateKey) require.NotEmpty(tt, resp.CreateSignerRequest) u, err := parseURI(resp.Name) require.NoError(tt, err) require.NotEmpty(tt, u.label) require.NotEmpty(tt, u.tag) require.NotEmpty(tt, u.hash) }, assert.NoError}, {"ok no tag", &MacKMS{}, args{&apiv1.CreateKeyRequest{Name: "mackms:label=test-p256-2;tag="}}, func(tt require.TestingT, i1 interface{}, i2 ...interface{}) { require.IsType(tt, &apiv1.CreateKeyResponse{}, i1) resp := i1.(*apiv1.CreateKeyResponse) require.NotEmpty(tt, resp.Name) require.NotNil(tt, resp.PublicKey) require.Nil(tt, resp.PrivateKey) require.NotEmpty(tt, resp.CreateSignerRequest) u, err := parseURI(resp.Name) require.NoError(tt, err) require.NotEmpty(tt, u.label) require.Empty(tt, u.tag) require.NotEmpty(tt, u.hash) }, assert.NoError}, {"fail name", &MacKMS{}, args{&apiv1.CreateKeyRequest{}}, require.Nil, assert.Error}, {"fail uri", &MacKMS{}, args{&apiv1.CreateKeyRequest{Name: "mackms:name=test-p256"}}, require.Nil, assert.Error}, {"fail signatureAlgorithm", &MacKMS{}, args{&apiv1.CreateKeyRequest{ Name: "mackms:label=test-p256", SignatureAlgorithm: apiv1.PureEd25519, }}, require.Nil, assert.Error}, {"fail signatureAlgorithm secureEnclave", &MacKMS{}, args{&apiv1.CreateKeyRequest{ Name: "mackms:label=test-p256;se=true", SignatureAlgorithm: apiv1.ECDSAWithSHA512, }}, require.Nil, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &MacKMS{} got, err := k.CreateKey(tt.args.req) tt.assertion(t, err) tt.verify(t, got) }) } } func TestMacKMS_CreateSigner(t *testing.T) { kms := &MacKMS{} resp, err := kms.CreateKey(&apiv1.CreateKeyRequest{ Name: "mackms:label=test-p256", SignatureAlgorithm: apiv1.SHA256WithRSA, }) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, kms.DeleteKey(&apiv1.DeleteKeyRequest{ Name: resp.Name, })) }) assertSigner := func(tt require.TestingT, i1 interface{}, i2 ...interface{}) { require.IsType(tt, &Signer{}, i1) signer := i1.(crypto.Signer) require.Equal(tt, resp.PublicKey, signer.Public()) b, err := randutil.Bytes(256) require.NoError(t, err) digest := sha256.Sum256(b) signature, err := signer.Sign(nil, digest[:], crypto.SHA256) require.NoError(tt, err) switch k := signer.Public().(type) { case *ecdsa.PublicKey: require.True(tt, ecdsa.VerifyASN1(k, digest[:], signature)) case *rsa.PublicKey: require.NoError(tt, rsa.VerifyPKCS1v15(k, crypto.SHA256, digest[:], signature)) default: tt.Errorf("unexpected key type %T", k) } } type args struct { req *apiv1.CreateSignerRequest } tests := []struct { name string k *MacKMS args args verify require.ValueAssertionFunc assertion assert.ErrorAssertionFunc }{ {"ok", &MacKMS{}, args{&apiv1.CreateSignerRequest{ SigningKey: resp.Name, }}, assertSigner, assert.NoError}, {"ok simple name", &MacKMS{}, args{&apiv1.CreateSignerRequest{ SigningKey: "mackms:label=test-p256", }}, assertSigner, assert.NoError}, {"fail signingKey", &MacKMS{}, args{&apiv1.CreateSignerRequest{}}, require.Nil, assert.Error}, {"fail uri", &MacKMS{}, args{&apiv1.CreateSignerRequest{SigningKey: "mackms:tag=foo"}}, require.Nil, assert.Error}, {"fail missing", &MacKMS{}, args{&apiv1.CreateSignerRequest{SigningKey: "mackms:label=test-p384"}}, require.Nil, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &MacKMS{} got, err := k.CreateSigner(tt.args.req) tt.assertion(t, err) tt.verify(t, got) }) } } func TestMacKMS_DeleteKey(t *testing.T) { kms := &MacKMS{} r1, err := kms.CreateKey(&apiv1.CreateKeyRequest{ Name: "mackms:label=test-p256", SignatureAlgorithm: apiv1.SHA256WithRSA, }) require.NoError(t, err) r2, err := kms.CreateKey(&apiv1.CreateKeyRequest{ Name: "mackms:label=test-rsa", SignatureAlgorithm: apiv1.SHA256WithRSA, }) require.NoError(t, err) t.Cleanup(func() { assert.Error(t, kms.DeleteKey(&apiv1.DeleteKeyRequest{ Name: r1.Name, })) assert.Error(t, kms.DeleteKey(&apiv1.DeleteKeyRequest{ Name: r2.Name, })) }) type args struct { req *apiv1.DeleteKeyRequest } tests := []struct { name string m *MacKMS args args assertion assert.ErrorAssertionFunc }{ {"ok", &MacKMS{}, args{&apiv1.DeleteKeyRequest{Name: r1.Name}}, assert.NoError}, {"ok simple name", &MacKMS{}, args{&apiv1.DeleteKeyRequest{Name: "mackms:label=test-rsa"}}, assert.NoError}, {"fail name", &MacKMS{}, args{&apiv1.DeleteKeyRequest{}}, assert.Error}, {"fail uri", &MacKMS{}, args{&apiv1.DeleteKeyRequest{Name: "mackms:hash=foo"}}, assert.Error}, {"fail missing", &MacKMS{}, args{&apiv1.DeleteKeyRequest{Name: r1.Name}}, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { m := &MacKMS{} tt.assertion(t, m.DeleteKey(tt.args.req)) }) } } func Test_parseURI(t *testing.T) { type args struct { rawuri string } tests := []struct { name string args args want *keyAttributes assertion assert.ErrorAssertionFunc }{ {"ok", args{"mackms:label=the-label;tag=the-tag;hash=0102abcd"}, &keyAttributes{label: "the-label", tag: "the-tag", hash: []byte{1, 2, 171, 205}}, assert.NoError}, {"ok label", args{"the-label"}, &keyAttributes{label: "the-label", tag: DefaultTag, retry: true}, assert.NoError}, {"ok label uri", args{"mackms:label=the-label"}, &keyAttributes{label: "the-label", tag: DefaultTag, retry: true}, assert.NoError}, {"ok label uri simple", args{"mackms:the-label"}, &keyAttributes{label: "the-label", tag: DefaultTag, retry: true}, assert.NoError}, {"ok label empty tag", args{"mackms:label=the-label;tag="}, &keyAttributes{label: "the-label", tag: ""}, assert.NoError}, {"ok label empty tag no equal", args{"mackms:label=the-label;tag"}, &keyAttributes{label: "the-label", tag: ""}, assert.NoError}, {"fail parse", args{"mackms:%label=the-label"}, nil, assert.Error}, {"fail missing label", args{"mackms:hash=0102abcd"}, nil, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := parseURI(tt.args.rawuri) tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } } func Test_parseECDSAPublicKey(t *testing.T) { p256, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) p384, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) require.NoError(t, err) p521, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) require.NoError(t, err) p224, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader) require.NoError(t, err) marshal := func(k *ecdsa.PrivateKey) []byte { byteLen := (k.Curve.Params().BitSize + 7) / 8 ret := make([]byte, 1+2*byteLen) ret[0] = 4 // uncompressed point k.X.FillBytes(ret[1 : 1+byteLen]) k.Y.FillBytes(ret[1+byteLen : 1+2*byteLen]) return ret } mustRand := func(size int) []byte { b, err := randutil.Bytes(size) require.NoError(t, err) return b } type args struct { raw []byte } tests := []struct { name string args args want crypto.PublicKey assertion assert.ErrorAssertionFunc }{ {"ok P-256", args{marshal(p256)}, p256.Public(), assert.NoError}, {"ok P-384", args{marshal(p384)}, p384.Public(), assert.NoError}, {"ok P-521", args{marshal(p521)}, p521.Public(), assert.NoError}, {"fail P-224", args{marshal(p224)}, nil, assert.Error}, {"fail P-256", args{mustRand(65)}, nil, assert.Error}, {"fail P-384", args{mustRand(97)}, nil, assert.Error}, {"fail P-521", args{mustRand(133)}, nil, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := parseECDSAPublicKey(tt.args.raw) tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } } func Test_parseECDSAPrivateKey(t *testing.T) { p256, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) p384, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) require.NoError(t, err) p521, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) require.NoError(t, err) p224, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader) require.NoError(t, err) marshal := func(k *ecdsa.PrivateKey) []byte { byteLen := (k.Curve.Params().BitSize + 7) / 8 ret := make([]byte, 1+3*byteLen) ret[0] = 4 // uncompressed point k.X.FillBytes(ret[1 : 1+byteLen]) k.Y.FillBytes(ret[1+byteLen : 1+2*byteLen]) k.D.FillBytes(ret[1+2*byteLen:]) return ret } zeroKey := func(size int) []byte { return make([]byte, size) } type args struct { raw []byte } tests := []struct { name string args args want crypto.PublicKey assertion assert.ErrorAssertionFunc }{ {"ok P-256", args{marshal(p256)}, p256.Public(), assert.NoError}, {"ok P-384", args{marshal(p384)}, p384.Public(), assert.NoError}, {"ok P-521", args{marshal(p521)}, p521.Public(), assert.NoError}, {"fail P-224", args{marshal(p224)}, nil, assert.Error}, {"fail P-256", args{zeroKey(97)}, nil, assert.Error}, {"fail P-384", args{zeroKey(145)}, nil, assert.Error}, {"fail P-521", args{zeroKey(199)}, nil, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := parseECDSAPrivateKey(tt.args.raw) tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } } func Test_ecdhToECDSAPublicKey(t *testing.T) { p256, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) p384, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) require.NoError(t, err) p521, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) require.NoError(t, err) x25519, err := ecdh.X25519().GenerateKey(rand.Reader) require.NoError(t, err) toECDH := func(k *ecdsa.PrivateKey) *ecdh.PublicKey { byteLen := (k.Curve.Params().BitSize + 7) / 8 b := make([]byte, 1+2*byteLen) b[0] = 4 // uncompressed point k.X.FillBytes(b[1 : 1+byteLen]) k.Y.FillBytes(b[1+byteLen : 1+2*byteLen]) switch k.Curve { case elliptic.P256(): key, err := ecdh.P256().NewPublicKey(b) require.NoError(t, err) return key case elliptic.P384(): key, err := ecdh.P384().NewPublicKey(b) require.NoError(t, err) return key case elliptic.P521(): key, err := ecdh.P521().NewPublicKey(b) require.NoError(t, err) return key default: return &ecdh.PublicKey{} } } type args struct { key *ecdh.PublicKey } tests := []struct { name string args args want *ecdsa.PublicKey assertion assert.ErrorAssertionFunc }{ {"ok P-256", args{toECDH(p256)}, &p256.PublicKey, assert.NoError}, {"ok P-384", args{toECDH(p384)}, &p384.PublicKey, assert.NoError}, {"ok P-521", args{toECDH(p521)}, &p521.PublicKey, assert.NoError}, {"fail X25519", args{x25519.PublicKey()}, nil, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ecdhToECDSAPublicKey(tt.args.key) tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } } func Test_encodeSerialNumber(t *testing.T) { getBigInt := func(s string) *big.Int { b, err := hex.DecodeString(s) require.NoError(t, err) return new(big.Int).SetBytes(b) } getBytes := func(s string) []byte { b, err := hex.DecodeString(s) require.NoError(t, err) return b } type args struct { s *big.Int } tests := []struct { name string args args want []byte }{ {"ok zero", args{big.NewInt(0)}, []byte{0}}, {"ok no pad", args{getBigInt("7df0e2ea242fd1a0650cf652aa31bfa0")}, getBytes("7df0e2ea242fd1a0650cf652aa31bfa0")}, {"ok with pad", args{getBigInt("c4b3e6e28985f1a012aa38e7493b6f35")}, getBytes("00c4b3e6e28985f1a012aa38e7493b6f35")}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert.Equal(t, tt.want, encodeSerialNumber(tt.args.s)) }) } } func deleteCertificate(t *testing.T, label string, cert *x509.Certificate) { if label == "" { label = cert.Subject.CommonName } kms := &MacKMS{} require.NoError(t, kms.DeleteCertificate(&apiv1.DeleteCertificateRequest{ Name: "mackms:label=" + label + ";serial=" + hex.EncodeToString(cert.SerialNumber.Bytes()), })) } func TestMacKMS_LoadCertificate(t *testing.T) { testName := t.Name() ca, err := minica.New(minica.WithName(testName)) require.NoError(t, err) key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) cert1, err := ca.Sign(&x509.Certificate{ Subject: pkix.Name{CommonName: testName + "1@example.com"}, EmailAddresses: []string{testName + "1@example.com"}, PublicKey: key.Public(), }) require.NoError(t, err) cert2, err := ca.Sign(&x509.Certificate{ Subject: pkix.Name{CommonName: testName + "2@example.com"}, EmailAddresses: []string{testName + "2@example.com"}, PublicKey: key.Public(), }) require.NoError(t, err) suffix, err := randutil.Alphanumeric(8) require.NoError(t, err) label := "test-" + suffix kms := &MacKMS{} require.NoError(t, kms.StoreCertificate(&apiv1.StoreCertificateRequest{ Name: "mackms:", Certificate: cert1, })) t.Cleanup(func() { deleteCertificate(t, "", cert1) }) require.NoError(t, kms.StoreCertificate(&apiv1.StoreCertificateRequest{ Name: "mackms:label=" + label, Certificate: cert2, })) t.Cleanup(func() { deleteCertificate(t, label, cert2) }) type args struct { req *apiv1.LoadCertificateRequest } tests := []struct { name string k *MacKMS args args want *x509.Certificate assertion assert.ErrorAssertionFunc }{ {"ok commonName", &MacKMS{}, args{&apiv1.LoadCertificateRequest{ Name: "mackms:label=" + cert1.Subject.CommonName, }}, cert1, assert.NoError}, {"ok commonName short uri", &MacKMS{}, args{&apiv1.LoadCertificateRequest{ Name: "mackms:" + cert1.Subject.CommonName, }}, cert1, assert.NoError}, {"ok commonName no uri", &MacKMS{}, args{&apiv1.LoadCertificateRequest{ Name: cert1.Subject.CommonName, }}, cert1, assert.NoError}, {"ok custom label", &MacKMS{}, args{&apiv1.LoadCertificateRequest{ Name: "mackms:label=" + label, }}, cert2, assert.NoError}, {"ok serial number", &MacKMS{}, args{&apiv1.LoadCertificateRequest{ Name: "mackms:serial=" + hex.EncodeToString(cert1.SerialNumber.Bytes()), }}, cert1, assert.NoError}, {"fail name", &MacKMS{}, args{&apiv1.LoadCertificateRequest{}}, nil, assert.Error}, {"fail uri", &MacKMS{}, args{&apiv1.LoadCertificateRequest{Name: "mackms:"}}, nil, assert.Error}, {"fail missing label", &MacKMS{}, args{&apiv1.LoadCertificateRequest{Name: "mackms:label=missing-" + suffix}}, nil, assert.Error}, {"fail missing serial", &MacKMS{}, args{&apiv1.LoadCertificateRequest{Name: "mackms:serial=010a020b030c"}}, nil, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &MacKMS{} got, err := k.LoadCertificate(tt.args.req) tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } } func TestMacKMS_StoreCertificate(t *testing.T) { testName := t.Name() ca, err := minica.New(minica.WithName(testName)) require.NoError(t, err) key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) cert, err := ca.Sign(&x509.Certificate{ Subject: pkix.Name{CommonName: testName + "@example.com"}, EmailAddresses: []string{testName + "@example.com"}, PublicKey: key.Public(), }) require.NoError(t, err) verifyCertificate := func(name, label string, cert *x509.Certificate) func(t *testing.T) { return func(t *testing.T) { t.Helper() kms := &MacKMS{} got, err := kms.LoadCertificate(&apiv1.LoadCertificateRequest{ Name: name, }) if assert.NoError(t, err) && assert.Equal(t, cert, got) { deleteCertificate(t, label, cert) } } } commonName := func(cert *x509.Certificate) string { return cert.Subject.CommonName } serial := func(cert *x509.Certificate) string { return hex.EncodeToString(cert.SerialNumber.Bytes()) } randLabel := func(n int) string { s, err := randutil.Alphanumeric(n) require.NoError(t, err) return s } rootLabel := "test-" + randLabel(8) intermediateLabel := "test-" + randLabel(8) certLabel := "test-" + randLabel(8) type args struct { req *apiv1.StoreCertificateRequest } tests := []struct { name string k *MacKMS args args verify func(*testing.T) assertion assert.ErrorAssertionFunc }{ {"ok root", &MacKMS{}, args{&apiv1.StoreCertificateRequest{ Name: "mackms:", Certificate: ca.Root, }}, verifyCertificate("mackms:label="+commonName(ca.Root), "", ca.Root), assert.NoError}, {"ok intermediate", &MacKMS{}, args{&apiv1.StoreCertificateRequest{ Name: "mackms:", Certificate: ca.Intermediate, }}, verifyCertificate("mackms:serial="+serial(ca.Intermediate), "", ca.Intermediate), assert.NoError}, {"ok cert", &MacKMS{}, args{&apiv1.StoreCertificateRequest{ Name: "mackms:", Certificate: cert, }}, verifyCertificate("mackms:label="+commonName(cert)+";serial="+serial(cert), "", cert), assert.NoError}, {"ok root with label", &MacKMS{}, args{&apiv1.StoreCertificateRequest{ Name: "mackms:label=" + rootLabel, Certificate: ca.Root, }}, verifyCertificate("mackms:label="+rootLabel, rootLabel, ca.Root), assert.NoError}, {"ok intermediate with label", &MacKMS{}, args{&apiv1.StoreCertificateRequest{ Name: intermediateLabel, Certificate: ca.Intermediate, }}, verifyCertificate("mackms:serial="+serial(ca.Intermediate), intermediateLabel, ca.Intermediate), assert.NoError}, {"ok cert with label", &MacKMS{}, args{&apiv1.StoreCertificateRequest{ Name: "mackms:" + certLabel, Certificate: cert, }}, verifyCertificate("mackms:label="+certLabel+";serial="+serial(cert), certLabel, cert), assert.NoError}, {"ok cert no name", &MacKMS{}, args{&apiv1.StoreCertificateRequest{ Certificate: cert, }}, verifyCertificate("mackms:serial="+serial(cert), "", cert), assert.NoError}, {"fail certificate", &MacKMS{}, args{&apiv1.StoreCertificateRequest{ Name: "mackms:label=my-label", }}, func(t *testing.T) {}, assert.Error}, {"fail uri", &MacKMS{}, args{&apiv1.StoreCertificateRequest{ Name: "mackms", }}, func(t *testing.T) {}, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &MacKMS{} tt.assertion(t, k.StoreCertificate(tt.args.req)) tt.verify(t) }) } } func TestMacKMS_StoreCertificate_duplicated(t *testing.T) { ca, err := minica.New(minica.WithName(t.Name())) require.NoError(t, err) suffix, err := randutil.Alphanumeric(8) require.NoError(t, err) label := "test-" + suffix type args struct { req *apiv1.StoreCertificateRequest } tests := []struct { name string k *MacKMS args args cleanup func(t *testing.T) }{ {"ok", &MacKMS{}, args{&apiv1.StoreCertificateRequest{ Name: "mackms:", Certificate: ca.Root, }}, func(t *testing.T) { deleteCertificate(t, "", ca.Root) }}, {"ok with label", &MacKMS{}, args{&apiv1.StoreCertificateRequest{ Name: "mackms:label=" + label, Certificate: ca.Intermediate, }}, func(t *testing.T) { deleteCertificate(t, label, ca.Intermediate) }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Cleanup(func() { tt.cleanup(t) }) k := &MacKMS{} if assert.NoError(t, k.StoreCertificate(tt.args.req)) { assert.Error(t, k.StoreCertificate(tt.args.req)) } }) } } func TestMacKMS_LoadCertificateChain(t *testing.T) { testName := t.Name() ca, err := minica.New(minica.WithName(testName)) require.NoError(t, err) key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) cert, err := ca.Sign(&x509.Certificate{ Subject: pkix.Name{CommonName: testName + "@example.com"}, EmailAddresses: []string{testName + "@example.com"}, PublicKey: key.Public(), }) require.NoError(t, err) kms := &MacKMS{} require.NoError(t, kms.StoreCertificate(&apiv1.StoreCertificateRequest{ Name: "mackms:", Certificate: ca.Root, })) t.Cleanup(func() { deleteCertificate(t, "", ca.Root) }) require.NoError(t, kms.StoreCertificate(&apiv1.StoreCertificateRequest{ Name: "mackms:", Certificate: ca.Intermediate, })) t.Cleanup(func() { deleteCertificate(t, "", ca.Intermediate) }) require.NoError(t, kms.StoreCertificate(&apiv1.StoreCertificateRequest{ Name: "mackms:", Certificate: cert, })) t.Cleanup(func() { deleteCertificate(t, "", cert) }) type args struct { req *apiv1.LoadCertificateChainRequest } tests := []struct { name string k *MacKMS args args want []*x509.Certificate assertion assert.ErrorAssertionFunc }{ {"ok label", &MacKMS{}, args{&apiv1.LoadCertificateChainRequest{ Name: "mackms:label=" + cert.Subject.CommonName, }}, []*x509.Certificate{cert, ca.Intermediate}, assert.NoError}, {"ok serial", &MacKMS{}, args{&apiv1.LoadCertificateChainRequest{ Name: "mackms:serial=" + hex.EncodeToString(cert.SerialNumber.Bytes()), }}, []*x509.Certificate{cert, ca.Intermediate}, assert.NoError}, {"ok label and serial", &MacKMS{}, args{&apiv1.LoadCertificateChainRequest{ Name: "mackms:labeld=" + cert.Subject.CommonName + ";serial=" + hex.EncodeToString(cert.SerialNumber.Bytes()), }}, []*x509.Certificate{cert, ca.Intermediate}, assert.NoError}, {"ok self-signed", &MacKMS{}, args{&apiv1.LoadCertificateChainRequest{ Name: "mackms:label=" + ca.Root.Subject.CommonName, }}, []*x509.Certificate{ca.Root}, assert.NoError}, {"fail name", &MacKMS{}, args{&apiv1.LoadCertificateChainRequest{}}, nil, assert.Error}, {"fail uri", &MacKMS{}, args{&apiv1.LoadCertificateChainRequest{Name: "mackms:"}}, nil, assert.Error}, {"fail missing", &MacKMS{}, args{&apiv1.LoadCertificateChainRequest{Name: "mackms:label=missing-" + testName}}, nil, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &MacKMS{} got, err := k.LoadCertificateChain(tt.args.req) tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } } func TestMacKMS_StoreCertificateChain(t *testing.T) { testName := t.Name() ca, err := minica.New(minica.WithName(testName)) require.NoError(t, err) key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) cert, err := ca.Sign(&x509.Certificate{ Subject: pkix.Name{CommonName: testName + "@example.com"}, EmailAddresses: []string{testName + "@example.com"}, PublicKey: key.Public(), }) require.NoError(t, err) verifyCertificates := func(name, label string, chain []*x509.Certificate) func(t *testing.T) { return func(t *testing.T) { t.Helper() kms := &MacKMS{} got, err := kms.LoadCertificateChain(&apiv1.LoadCertificateChainRequest{ Name: name, }) if assert.NoError(t, err) && assert.Equal(t, chain, got) { deleteCertificate(t, label, chain[0]) for _, crt := range chain[1:] { deleteCertificate(t, "", crt) } } } } suffix, err := randutil.Alphanumeric(8) require.NoError(t, err) label := "test-" + suffix type args struct { req *apiv1.StoreCertificateChainRequest } tests := []struct { name string k *MacKMS args args verify func(*testing.T) assertion assert.ErrorAssertionFunc }{ {"ok", &MacKMS{}, args{&apiv1.StoreCertificateChainRequest{ Name: "mackms:", CertificateChain: []*x509.Certificate{cert, ca.Intermediate, ca.Root}, }}, func(t *testing.T) { t.Cleanup(func() { deleteCertificate(t, "", ca.Root) }) fn := verifyCertificates("mackms:label="+cert.Subject.CommonName, "", []*x509.Certificate{cert, ca.Intermediate}) fn(t) }, assert.NoError}, {"ok leaf", &MacKMS{}, args{&apiv1.StoreCertificateChainRequest{ Name: "", CertificateChain: []*x509.Certificate{cert}, }}, verifyCertificates("mackms:label="+cert.Subject.CommonName, "", []*x509.Certificate{cert}), assert.NoError}, {"ok with label", &MacKMS{}, args{&apiv1.StoreCertificateChainRequest{ Name: "mackms:label=" + label, CertificateChain: []*x509.Certificate{cert, ca.Intermediate}, }}, verifyCertificates("mackms:label="+label, label, []*x509.Certificate{cert, ca.Intermediate}), assert.NoError}, {"ok already exists", &MacKMS{}, args{&apiv1.StoreCertificateChainRequest{ Name: "mackms:", CertificateChain: []*x509.Certificate{cert, ca.Intermediate, ca.Intermediate}, }}, verifyCertificates("mackms:label="+cert.Subject.CommonName, "", []*x509.Certificate{cert, ca.Intermediate}), assert.NoError}, {"fail certificates", &MacKMS{}, args{&apiv1.StoreCertificateChainRequest{ Name: "mackms:", }}, func(t *testing.T) {}, assert.Error}, {"fail uri", &MacKMS{}, args{&apiv1.StoreCertificateChainRequest{ Name: "mackms", CertificateChain: []*x509.Certificate{cert, ca.Intermediate}, }}, func(t *testing.T) {}, assert.Error}, {"fail store certificate", &MacKMS{}, args{&apiv1.StoreCertificateChainRequest{ Name: "mackms:", CertificateChain: []*x509.Certificate{{}, ca.Intermediate}, }}, func(t *testing.T) {}, assert.Error}, {"fail store certificate chain", &MacKMS{}, args{&apiv1.StoreCertificateChainRequest{ Name: "mackms:", CertificateChain: []*x509.Certificate{cert, {}}, }}, verifyCertificates("mackms:label="+cert.Subject.CommonName, "", []*x509.Certificate{cert}), assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.name == "fail duplicated" { t.Log("foo") } k := &MacKMS{} tt.assertion(t, k.StoreCertificateChain(tt.args.req)) tt.verify(t) }) } } func TestMacKMS_DeleteCertificate(t *testing.T) { testName := t.Name() ca, err := minica.New(minica.WithName(testName)) require.NoError(t, err) key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) cert1, err := ca.Sign(&x509.Certificate{ Subject: pkix.Name{CommonName: testName + "1@example.com"}, EmailAddresses: []string{testName + "1@example.com"}, PublicKey: key.Public(), }) require.NoError(t, err) cert2, err := ca.Sign(&x509.Certificate{ Subject: pkix.Name{CommonName: testName + "2@example.com"}, EmailAddresses: []string{testName + "2@example.com"}, PublicKey: key.Public(), }) require.NoError(t, err) suffix, err := randutil.Alphanumeric(8) require.NoError(t, err) notExistsCheck := func(cert *x509.Certificate) { kms := &MacKMS{} _, err := kms.LoadCertificate(&apiv1.LoadCertificateRequest{ Name: "mackms:serial=" + hex.EncodeToString(cert.SerialNumber.Bytes()), }) assert.ErrorIs(t, err, apiv1.NotFoundError{}) } kms := &MacKMS{} require.NoError(t, kms.StoreCertificate(&apiv1.StoreCertificateRequest{ Name: "mackms:", Certificate: ca.Root, })) t.Cleanup(func() { notExistsCheck(ca.Root) }) require.NoError(t, kms.StoreCertificate(&apiv1.StoreCertificateRequest{ Name: "mackms:label=test-intermediate-" + suffix, Certificate: ca.Intermediate, })) t.Cleanup(func() { notExistsCheck(ca.Intermediate) }) require.NoError(t, kms.StoreCertificate(&apiv1.StoreCertificateRequest{ Name: "mackms:", Certificate: cert1, })) t.Cleanup(func() { notExistsCheck(cert1) }) require.NoError(t, kms.StoreCertificate(&apiv1.StoreCertificateRequest{ Name: "mackms:label=test-leaf-" + suffix, Certificate: cert2, })) t.Cleanup(func() { notExistsCheck(cert2) }) type args struct { req *apiv1.DeleteCertificateRequest } tests := []struct { name string m *MacKMS args args assertion assert.ErrorAssertionFunc }{ {"ok", &MacKMS{}, args{&apiv1.DeleteCertificateRequest{ Name: "mackms:" + ca.Root.Subject.CommonName, }}, assert.NoError}, {"ok label", &MacKMS{}, args{&apiv1.DeleteCertificateRequest{ Name: "mackms:label=test-intermediate-" + suffix, }}, assert.NoError}, {"ok serial", &MacKMS{}, args{&apiv1.DeleteCertificateRequest{ Name: "mackms:serial=" + hex.EncodeToString(cert1.SerialNumber.Bytes()), }}, assert.NoError}, {"ok label and serial", &MacKMS{}, args{&apiv1.DeleteCertificateRequest{ Name: "mackms:label=test-leaf-" + suffix + ";serial=" + hex.EncodeToString(cert2.SerialNumber.Bytes()), }}, assert.NoError}, {"fail name", &MacKMS{}, args{&apiv1.DeleteCertificateRequest{}}, assert.Error}, {"fail uri", &MacKMS{}, args{&apiv1.DeleteCertificateRequest{Name: "mackms"}}, assert.Error}, {"fail missing", &MacKMS{}, args{&apiv1.DeleteCertificateRequest{Name: "mackms:label=" + testName}}, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { m := &MacKMS{} tt.assertion(t, m.DeleteCertificate(tt.args.req)) }) } } func Test_apiv1Error(t *testing.T) { type args struct { err error } tests := []struct { name string args args assertion assert.ErrorAssertionFunc }{ {"ok not found", args{security.ErrNotFound}, func(t assert.TestingT, err error, msg ...interface{}) bool { return assert.ErrorIs(t, err, apiv1.NotFoundError{}, msg...) }}, {"ok not found wrapped", args{fmt.Errorf("something happened: %w", security.ErrNotFound)}, func(t assert.TestingT, err error, msg ...interface{}) bool { return assert.ErrorIs(t, err, apiv1.NotFoundError{}, msg...) }}, {"ok already exists", args{security.ErrAlreadyExists}, func(t assert.TestingT, err error, msg ...interface{}) bool { return assert.ErrorIs(t, err, apiv1.AlreadyExistsError{}, msg...) }}, {"ok already exists wrapped", args{fmt.Errorf("something happened: %w", security.ErrAlreadyExists)}, func(t assert.TestingT, err error, msg ...interface{}) bool { return assert.ErrorIs(t, err, apiv1.AlreadyExistsError{}, msg...) }}, {"ok other", args{io.ErrUnexpectedEOF}, func(t assert.TestingT, err error, msg ...interface{}) bool { return assert.ErrorIs(t, err, io.ErrUnexpectedEOF, msg...) }}, {"ok other wrapped", args{fmt.Errorf("something happened: %w", io.ErrUnexpectedEOF)}, func(t assert.TestingT, err error, msg ...interface{}) bool { return assert.ErrorIs(t, err, io.ErrUnexpectedEOF, msg...) }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.assertion(t, apiv1Error(tt.args.err)) }) } } func TestMacKMS_SearchKeys(t *testing.T) { name, err := randutil.Hex(10) require.NoError(t, err) tag := fmt.Sprintf("com.smallstep.crypto.test.%s", name) // unique tag per test execution // initialize MacKMS k := &MacKMS{} // search by tag; expect 0 keys before the test got, err := k.SearchKeys(&apiv1.SearchKeysRequest{Query: fmt.Sprintf("mackms:tag=%s", tag)}) require.NoError(t, err) require.NotNil(t, got) require.Len(t, got.Results, 0) key1, err := k.CreateKey(&apiv1.CreateKeyRequest{Name: fmt.Sprintf("mackms:name=test-step-1;label=test-step-1;tag=%s;se=false", tag)}) require.NoError(t, err) key2, err := k.CreateKey(&apiv1.CreateKeyRequest{Name: fmt.Sprintf("mackms:name=test-step-2;label=test-step-2;tag=%s;se=false", tag)}) require.NoError(t, err) u1, err := uri.ParseWithScheme(Scheme, key1.Name) require.NoError(t, err) u2, err := uri.ParseWithScheme(Scheme, key2.Name) require.NoError(t, err) expectedHashes := []string{u1.Get("hash"), u2.Get("hash")} require.Len(t, expectedHashes, 2) t.Cleanup(func() { err = k.DeleteKey(&apiv1.DeleteKeyRequest{Name: key1.Name}) require.NoError(t, err) err = k.DeleteKey(&apiv1.DeleteKeyRequest{Name: key2.Name}) require.NoError(t, err) }) // search by tag got, err = k.SearchKeys(&apiv1.SearchKeysRequest{Query: fmt.Sprintf("mackms:tag=%s", tag)}) require.NoError(t, err) require.NotNil(t, got) require.Len(t, got.Results, 2) // check if the correct keys were found by comparing hashes var hashes []string for _, key := range got.Results { u, err := uri.ParseWithScheme(Scheme, key.Name) require.NoError(t, err) assert.Equal(t, tag, u.Get("tag")) if hash := u.Get("hash"); hash != "" { hashes = append(hashes, hash) } } assert.Equal(t, expectedHashes, hashes) } func Test_keyAttributes_retryAttributes(t *testing.T) { type fields struct { label string tag string hash []byte retry bool } mustFields := func(s string) fields { t.Helper() u, err := parseURI(s) require.NoError(t, err) return fields{ label: u.label, tag: u.tag, hash: u.hash, retry: u.retry, } } tests := []struct { name string fields fields want *keyAttributes }{ {"with tag", mustFields("mackms:label=label;tag=tag"), nil}, {"with tag and hash", mustFields("mackms:label=label;hash=FF00;tag=tag"), nil}, {"with empty tag", mustFields("mackms:label=label;tag="), nil}, {"with no tag", mustFields("mackms:label=label;hash=FF00"), &keyAttributes{ label: "label", hash: []byte{0xFF, 0x00}, }}, {"legacy name only", mustFields("label"), &keyAttributes{ label: "label", }}, {"legacy with schema", mustFields("mackms:label"), &keyAttributes{ label: "label", }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &keyAttributes{ label: tt.fields.label, tag: tt.fields.tag, hash: tt.fields.hash, retry: tt.fields.retry, } if tt.name == "with no tag" { t.Log("foo") } assert.Equal(t, tt.want, k.retryAttributes()) }) } } crypto-0.57.0/kms/mackms/signer.go000066400000000000000000000142561474156331600170300ustar00rootroot00000000000000//go:build darwin && cgo && !nomackms // Copyright (c) Smallstep Labs, Inc. // Copyright (c) Meta Platforms, Inc. and affiliates. // // 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. // // Part of this code is based on // https://github.com/facebookincubator/sks/blob/183e7561ecedc71992f23b2d37983d2948391f4c/macos/macos.go package mackms import ( "crypto" "crypto/ecdh" "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" "fmt" "io" cf "go.step.sm/crypto/internal/darwin/corefoundation" "go.step.sm/crypto/internal/darwin/security" ) // Signer implements the [crypto.Signer] interface using macOS Keychain or the // Secure Enclave. type Signer struct { *keyAttributes pub crypto.PublicKey } // Public returns the public key corresponding to the private key. func (s *Signer) Public() crypto.PublicKey { return s.pub } // Sign signs digest with the private key. For an RSA key, the resulting // signature will be either a PKCS #1 v1.5 or PSS signature (as indicated by // opts). For an ECDSA key, it will be a DER-serialized, ASN.1 signature // structure. func (s *Signer) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { algo, err := getSecKeyAlgorithm(s.pub, opts) if err != nil { return nil, fmt.Errorf("mackms Sign failed: %w", err) } key, err := getPrivateKey(s.keyAttributes) if err != nil { return nil, fmt.Errorf("mackms Sign failed: %w", err) } defer key.Release() cfDigest, err := cf.NewData(digest) if err != nil { return nil, fmt.Errorf("mackms Sign failed: %w", err) } defer cfDigest.Release() signature, err := security.SecKeyCreateSignature(key, algo, cfDigest) if err != nil { return nil, fmt.Errorf("mackms Sign failed: %w", err) } defer signature.Release() return signature.Bytes(), nil } // getSecKeyAlgorithm returns the appropriate SecKeyAlgorithm for the given key // and options. func getSecKeyAlgorithm(pub crypto.PublicKey, opts crypto.SignerOpts) (security.SecKeyAlgorithm, error) { switch pub.(type) { case *ecdsa.PublicKey: return security.KSecKeyAlgorithmECDSASignatureDigestX962, nil case *rsa.PublicKey: size := opts.HashFunc().Size() // RSA-PSS if _, ok := opts.(*rsa.PSSOptions); ok { switch size { case 32: // SHA256 return security.KSecKeyAlgorithmRSASignatureDigestPSSSHA256, nil case 48: // SHA384 return security.KSecKeyAlgorithmRSASignatureDigestPSSSHA384, nil case 64: // SHA512 return security.KSecKeyAlgorithmRSASignatureDigestPSSSHA512, nil default: return 0, fmt.Errorf("unsupported hash function %s", opts.HashFunc().String()) } } // RSA PKCS#1 switch size { case 32: // SHA256 return security.KSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA256, nil case 48: // SHA384 return security.KSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA384, nil case 64: // SHA512 return security.KSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA512, nil default: return 0, fmt.Errorf("unsupported hash function %s", opts.HashFunc().String()) } default: return 0, fmt.Errorf("unsupported key type %T", pub) } } // ECDH extends [Signer] with ECDH exchange method. // // # Experimental // // Notice: This API is EXPERIMENTAL and may be changed or removed in a later // release. type ECDH struct { *Signer } // ECDH performs an ECDH exchange and returns the shared secret. The private key // and public key must use the same curve. // // For NIST curves, this performs ECDH as specified in SEC 1, Version 2.0, // Section 3.3.1, and returns the x-coordinate encoded according to SEC 1, // Version 2.0, Section 2.3.5. The result is never the point at infinity. // // # Experimental // // Notice: This API is EXPERIMENTAL and may be changed or removed in a later // release. func (e *ECDH) ECDH(pub *ecdh.PublicKey) ([]byte, error) { key, err := getPrivateKey(e.Signer.keyAttributes) if err != nil { return nil, fmt.Errorf("mackms ECDH failed: %w", err) } defer key.Release() pubData, err := cf.NewData(pub.Bytes()) if err != nil { return nil, fmt.Errorf("mackms ECDH failed: %w", err) } defer pubData.Release() pubDict, err := cf.NewDictionary(cf.Dictionary{ security.KSecAttrKeyType: security.KSecAttrKeyTypeECSECPrimeRandom, security.KSecAttrKeyClass: security.KSecAttrKeyClassPublic, }) if err != nil { return nil, fmt.Errorf("mackms ECDH failed: %w", err) } defer pubDict.Release() pubRef, err := security.SecKeyCreateWithData(pubData, pubDict) if err != nil { return nil, fmt.Errorf("macOS SecKeyCreateWithData failed: %w", err) } defer pubRef.Release() sharedSecret, err := security.SecKeyCopyKeyExchangeResult(key, security.KSecKeyAlgorithmECDHKeyExchangeStandard, pubRef, &cf.DictionaryRef{}) if err != nil { return nil, fmt.Errorf("macOS SecKeyCopyKeyExchangeResult failed: %w", err) } defer sharedSecret.Release() return sharedSecret.Bytes(), nil } // Curve returns the [ecdh.Curve] of the key. If the key is not an ECDSA key it // will return nil. // // # Experimental // // Notice: This API is EXPERIMENTAL and may be changed or removed in a later // release. func (e *ECDH) Curve() ecdh.Curve { pub, ok := e.Signer.pub.(*ecdsa.PublicKey) if !ok { return nil } switch pub.Curve { case elliptic.P256(): return ecdh.P256() case elliptic.P384(): return ecdh.P384() case elliptic.P521(): return ecdh.P521() default: return nil } } // PublicKey returns the [ecdh.PublicKey] representation of the key. If the key // is not an ECDSA or it cannot be converted it will return nil. // // # Experimental // // Notice: This API is EXPERIMENTAL and may be changed or removed in a later // release. func (e *ECDH) PublicKey() *ecdh.PublicKey { pub, ok := e.Signer.pub.(*ecdsa.PublicKey) if !ok { return nil } ecdhPub, err := pub.ECDH() if err != nil { return nil } return ecdhPub } crypto-0.57.0/kms/mackms/signer_test.go000066400000000000000000000157441474156331600200720ustar00rootroot00000000000000//go:build darwin && cgo && !nomackms // Copyright (c) Smallstep Labs, Inc. // Copyright (c) Meta Platforms, Inc. and affiliates. // // 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. // // Part of this code is based on // https://github.com/facebookincubator/sks/blob/183e7561ecedc71992f23b2d37983d2948391f4c/macos/macos.go package mackms import ( "crypto" "crypto/ecdh" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.step.sm/crypto/kms/apiv1" ) func createKey(t *testing.T, name string, sa apiv1.SignatureAlgorithm) *apiv1.CreateKeyResponse { t.Helper() kms := &MacKMS{} resp, err := kms.CreateKey(&apiv1.CreateKeyRequest{ Name: "mackms:label=" + name, SignatureAlgorithm: sa, }) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, kms.DeleteKey(&apiv1.DeleteKeyRequest{ Name: resp.Name, })) }) return resp } func TestECDH_ECDH(t *testing.T) { goP256, err := ecdh.P256().GenerateKey(rand.Reader) require.NoError(t, err) goP384, err := ecdh.P384().GenerateKey(rand.Reader) require.NoError(t, err) goP521, err := ecdh.P521().GenerateKey(rand.Reader) require.NoError(t, err) goX25519, err := ecdh.X25519().GenerateKey(rand.Reader) require.NoError(t, err) kms := &MacKMS{} p256 := createKey(t, t.Name()+"-p256", apiv1.ECDSAWithSHA256) s256, err := kms.CreateSigner(&p256.CreateSignerRequest) require.NoError(t, err) p384 := createKey(t, t.Name()+"-p384", apiv1.ECDSAWithSHA384) s384, err := kms.CreateSigner(&p384.CreateSignerRequest) require.NoError(t, err) p521 := createKey(t, t.Name()+"-p521", apiv1.ECDSAWithSHA512) s521, err := kms.CreateSigner(&p521.CreateSignerRequest) require.NoError(t, err) type fields struct { Signer *Signer } type args struct { pub *ecdh.PublicKey } tests := []struct { name string fields fields args args wantFunc func(t *testing.T, got []byte) assertion assert.ErrorAssertionFunc }{ {"ok P256", fields{s256.(*Signer)}, args{goP256.PublicKey()}, func(t *testing.T, got []byte) { pub, ok := s256.Public().(*ecdsa.PublicKey) require.True(t, ok) ecdhPub, err := pub.ECDH() require.NoError(t, err) sharedSecret, err := goP256.ECDH(ecdhPub) require.NoError(t, err) assert.Equal(t, sharedSecret, got) }, assert.NoError}, {"ok P384", fields{s384.(*Signer)}, args{goP384.PublicKey()}, func(t *testing.T, got []byte) { pub, ok := s384.Public().(*ecdsa.PublicKey) require.True(t, ok) ecdhPub, err := pub.ECDH() require.NoError(t, err) sharedSecret, err := goP384.ECDH(ecdhPub) require.NoError(t, err) assert.Equal(t, sharedSecret, got) }, assert.NoError}, {"ok P521", fields{s521.(*Signer)}, args{goP521.PublicKey()}, func(t *testing.T, got []byte) { pub, ok := s521.Public().(*ecdsa.PublicKey) require.True(t, ok) ecdhPub, err := pub.ECDH() require.NoError(t, err) sharedSecret, err := goP521.ECDH(ecdhPub) require.NoError(t, err) assert.Equal(t, sharedSecret, got) }, assert.NoError}, {"fail missing", fields{&Signer{ keyAttributes: &keyAttributes{tag: DefaultTag, label: t.Name() + "-missing"}, }}, args{goP256.PublicKey()}, func(t *testing.T, got []byte) { assert.Nil(t, got) }, assert.Error}, {"fail SecKeyCreateWithData", fields{s256.(*Signer)}, args{goX25519.PublicKey()}, func(t *testing.T, got []byte) { assert.Nil(t, got) }, assert.Error}, {"fail SecKeyCopyKeyExchangeResult", fields{s256.(*Signer)}, args{goP384.PublicKey()}, func(t *testing.T, got []byte) { assert.Nil(t, got) }, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { e := &ECDH{ Signer: tt.fields.Signer, } got, err := e.ECDH(tt.args.pub) tt.assertion(t, err) tt.wantFunc(t, got) }) } } func TestECDH_Curve(t *testing.T) { kms := &MacKMS{} p256 := createKey(t, t.Name()+"-p256", apiv1.ECDSAWithSHA256) s256, err := kms.CreateSigner(&p256.CreateSignerRequest) require.NoError(t, err) p384 := createKey(t, t.Name()+"-p384", apiv1.ECDSAWithSHA384) s384, err := kms.CreateSigner(&p384.CreateSignerRequest) require.NoError(t, err) p521 := createKey(t, t.Name()+"-p521", apiv1.ECDSAWithSHA512) s521, err := kms.CreateSigner(&p521.CreateSignerRequest) require.NoError(t, err) rsaKey := createKey(t, t.Name()+"-rsa", apiv1.SHA256WithRSA) rsaSigmer, err := kms.CreateSigner(&rsaKey.CreateSignerRequest) require.NoError(t, err) p224, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader) require.NoError(t, err) type fields struct { Signer *Signer } tests := []struct { name string fields fields want ecdh.Curve }{ {"P256", fields{s256.(*Signer)}, ecdh.P256()}, {"P384", fields{s384.(*Signer)}, ecdh.P384()}, {"P521", fields{s521.(*Signer)}, ecdh.P521()}, {"P224", fields{&Signer{pub: p224.Public()}}, nil}, {"RSA", fields{rsaSigmer.(*Signer)}, nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { e := &ECDH{ Signer: tt.fields.Signer, } assert.Equal(t, tt.want, e.Curve()) }) } } func TestECDH_PublicKey(t *testing.T) { kms := &MacKMS{} p256 := createKey(t, t.Name()+"-p256", apiv1.ECDSAWithSHA256) s256, err := kms.CreateSigner(&p256.CreateSignerRequest) require.NoError(t, err) p384 := createKey(t, t.Name()+"-p384", apiv1.ECDSAWithSHA384) s384, err := kms.CreateSigner(&p384.CreateSignerRequest) require.NoError(t, err) p521 := createKey(t, t.Name()+"-p521", apiv1.ECDSAWithSHA512) s521, err := kms.CreateSigner(&p521.CreateSignerRequest) require.NoError(t, err) rsaKey := createKey(t, t.Name()+"-rsa", apiv1.SHA256WithRSA) rsaSigmer, err := kms.CreateSigner(&rsaKey.CreateSignerRequest) require.NoError(t, err) p224, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader) require.NoError(t, err) mustPublicKey := func(k crypto.PublicKey) *ecdh.PublicKey { pub, ok := k.(*ecdsa.PublicKey) require.True(t, ok) ecdhPub, err := pub.ECDH() require.NoError(t, err) return ecdhPub } type fields struct { Signer *Signer } tests := []struct { name string fields fields want *ecdh.PublicKey }{ {"P256", fields{s256.(*Signer)}, mustPublicKey(p256.PublicKey)}, {"P384", fields{s384.(*Signer)}, mustPublicKey(p384.PublicKey)}, {"P521", fields{s521.(*Signer)}, mustPublicKey(p521.PublicKey)}, {"P224", fields{&Signer{pub: p224.Public()}}, nil}, {"RSA", fields{rsaSigmer.(*Signer)}, nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { e := &ECDH{ Signer: tt.fields.Signer, } assert.Equal(t, tt.want, e.PublicKey()) }) } } crypto-0.57.0/kms/object.go000066400000000000000000000024631474156331600155310ustar00rootroot00000000000000package 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 } crypto-0.57.0/kms/object_test.go000066400000000000000000000125201474156331600165630ustar00rootroot00000000000000package 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) } }) } } crypto-0.57.0/kms/pkcs11/000077500000000000000000000000001474156331600150315ustar00rootroot00000000000000crypto-0.57.0/kms/pkcs11/benchmark_test.go000066400000000000000000000037321474156331600203560ustar00rootroot00000000000000//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) } crypto-0.57.0/kms/pkcs11/opensc_test.go000066400000000000000000000024271474156331600177130ustar00rootroot00000000000000//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 } crypto-0.57.0/kms/pkcs11/other_test.go000066400000000000000000000122611474156331600175420ustar00rootroot00000000000000//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, _ 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) } crypto-0.57.0/kms/pkcs11/pkcs11.go000066400000000000000000000342171474156331600164710ustar00rootroot00000000000000//go:build cgo && !nopkcs11 // +build cgo,!nopkcs11 package pkcs11 import ( "context" "crypto" "crypto/elliptic" "crypto/rsa" "crypto/x509" "encoding/hex" "fmt" "math/big" "runtime" "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, the string "pkcs11". const Scheme = string(apiv1.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 PKCS#11 KMS. To initialize it, you need to provide a URI // with the following format: // // - pkcs11:token=smallstep?pin-value=password // - pkcs11:serial=1a2b3c4d5e6f?pin-source=/path/to/pin.txt // - pkcs11:slot-id=5?pin-value=password // - pkcs11:module-path=/path/to/module.so;token=smallstep?pin-value=password // - pkcs11:token=smallstep;max-sessions=100?pin-value=password // // The scheme is "pkcs11"; "token", "serial", or "slot-id" defines the // cryptographic device to use. "module-path" is the path of the PKCS#11 module // to use. It will default to the proxy module of the p11-kit project if none is // specified (p11-kit-proxy.so). "pin-value" provides the user's PIN, and // "pin-source" defines a file that contains the PIN. "max-sessions" defines the // maximum number of PKCS#11 sessions, it defaults to 1024. // // A cryptographic key or object is identified by its "id" or "object" // attributes. The "id" is the key identifier for the object, it's a hexadecimal // string, and it will set the CKA_ID attribute of the object. The "object" is // the name of the object, and it will set the CKA_LABEL attribute. Only one // attribute is required to identify a key, but this package requires both to // create a new key. The complete URI for a key looks like this: // // - pkcs11:token=smallstep;id=0a10;object=ec-key?pin-value=password // - pkcs11:token=smallstep;id=%0a%10?pin-source=/path/to/pin.txt // - pkcs11:token=smallstep;object=ec-key?pin-value=password func New(_ context.Context, opts apiv1.Options) (*PKCS11, error) { if opts.URI == "" { return nil, errors.New("kms uri is required") } var config crypto11.Config u, err := uri.ParseWithScheme(Scheme, opts.URI) if err != nil { return nil, err } 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 v := u.Get("max-sessions"); v != "" { n, err := strconv.Atoi(v) if err != nil { return nil, errors.Wrap(err, "kms uri 'max-sessions' is not valid") } config.MaxSessions = n } // Get module or default to use p11-kit-proxy.so. // // pkcs11.New(module string) will use dlopen that will look for the // given library in the appropriate paths, so there's no need to provide // the full path. if config.Path = u.Get("module-path"); config.Path == "" { config.Path = defaultModule } // We will allow empty pins as some modules might not have a pin by default. // This is the case for softoken, which is used to read NSS databases. config.Pin = u.Pin() if config.Pin == "" && opts.Pin != "" { config.Pin = opts.Pin } switch { case config.TokenLabel == "" && config.TokenSerial == "" && config.SlotNumber == nil: return nil, errors.New("kms uri 'token', 'serial' or 'slot-id' are required") 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 } // defaultModule defines the defaultModule used, in this case is the // p11-kit-proxy provided by p11-kit. var defaultModule = "p11-kit-proxy.so" func init() { switch runtime.GOOS { case "darwin": defaultModule = "p11-kit-proxy.dylib" case "windows": defaultModule = "p11-kit-proxy.dll" } apiv1.Register(apiv1.PKCS11, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) { return New(ctx, opts) }) } // GetPublicKey returns the public key stored in the object identified by the name URI. 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 { if err := template.Set(crypto11.CkaExtractable, true); err != nil { return errors.Wrap(err, "storeCertificate failed") } } 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 { if err := private.Set(crypto11.CkaExtractable, true); err != nil { return nil, err } } 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 } var _ apiv1.CertificateManager = (*PKCS11)(nil) crypto-0.57.0/kms/pkcs11/pkcs11_no_cgo.go000066400000000000000000000031251474156331600200070ustar00rootroot00000000000000//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 } crypto-0.57.0/kms/pkcs11/pkcs11_test.go000066400000000000000000000633061474156331600175310ustar00rootroot00000000000000//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) { tmp0 := p11Configure t.Cleanup(func() { p11Configure = tmp0 }) 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 max-sessions", args{context.Background(), apiv1.Options{ Type: "pkcs11", URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test;max-sessions=100?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}, {"ok no pin", args{context.Background(), apiv1.Options{ Type: "pkcs11", URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test", }}, k, false}, {"ok with missing module", args{context.Background(), apiv1.Options{ Type: "pkcs11", URI: "pkcs11:token=pkcs11-test", Pin: "passowrd", }}, k, false}, {"fail missing uri", args{context.Background(), apiv1.Options{ Type: "pkcs11", }}, 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 max-sessions", args{context.Background(), apiv1.Options{ Type: "pkcs11", URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test;max-sessions=0F?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=%73%73", }}, &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}, {"default with percent URI", args{&apiv1.CreateKeyRequest{ Name: testObjectPercent, }}, &apiv1.CreateKeyResponse{ Name: testObjectPercent, PublicKey: &ecdsa.PublicKey{}, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: testObjectPercent, }, }, 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=%73%72;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=%73%74;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=%73%72;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.SHA1.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.SHA1, 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) } }) } } crypto-0.57.0/kms/pkcs11/setup_test.go000066400000000000000000000076151474156331600175700ustar00rootroot00000000000000//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" testObjectPercent = "pkcs11:id=%73%70;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=%73%72;object=rsa-pss-key", apiv1.SHA256WithRSAPSS, DefaultRSASize}, {"pkcs11:id=7373;object=ecdsa-p256-key", apiv1.ECDSAWithSHA256, 0}, {"pkcs11:id=%73%74;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 } crypto-0.57.0/kms/pkcs11/softhsm2_test.go000066400000000000000000000023421474156331600201650ustar00rootroot00000000000000//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 } crypto-0.57.0/kms/pkcs11/yubihsm2_test.go000066400000000000000000000021031474156331600201550ustar00rootroot00000000000000//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 } crypto-0.57.0/kms/softkms/000077500000000000000000000000001474156331600154155ustar00rootroot00000000000000crypto-0.57.0/kms/softkms/softkms.go000066400000000000000000000132631474156331600174370ustar00rootroot00000000000000package 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/kms/uri" "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x25519" ) type algorithmAttributes struct { Type string Curve string } // Scheme is the scheme used in uris, the string "softkms". const Scheme = string(apiv1.SoftKMS) // 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(_ context.Context, _ 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)) } else if req.PasswordPrompter != nil { opts = append(opts, pemutil.WithPasswordPrompt("Please enter the password to decrypt the signing key", pemutil.PasswordPrompter(req.PasswordPrompter))) } 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(filename(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) } name := filename(req.Name) return &apiv1.CreateKeyResponse{ Name: name, PublicKey: pub, PrivateKey: priv, CreateSignerRequest: apiv1.CreateSignerRequest{ Signer: signer, SigningKey: name, }, }, 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(filename(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, x25519.PublicKey: return vv, nil case crypto.Signer: return vv.Public(), 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)) } else if req.PasswordPrompter != nil { opts = append(opts, pemutil.WithPasswordPrompt("Please enter the password to decrypt the decryption key", pemutil.PasswordPrompter(req.PasswordPrompter))) } 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(filename(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") } } func filename(s string) string { if u, err := uri.ParseWithScheme(Scheme, s); err == nil { if f := u.Get("path"); f != "" { return f } return u.Opaque } return s } crypto-0.57.0/kms/softkms/softkms_test.go000066400000000000000000000372341474156331600205020ustar00rootroot00000000000000package 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" "go.step.sm/crypto/x25519" ) 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}, {"file uri", args{&apiv1.CreateSignerRequest{SigningKey: "softkms:testdata/priv.pem", Password: []byte("pass")}}, pk2, false}, {"path uri", args{&apiv1.CreateSignerRequest{SigningKey: "softkms:path=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, SigningKey: "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, SigningKey: "rsa3072"}}, 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, SigningKey: "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, SigningKey: "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, SigningKey: "ed25519"}}, 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, SigningKey: "default"}}, params{"EC", "P-256", 0}, false}, {"uri", args{&apiv1.CreateKeyRequest{Name: "softkms: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, SigningKey: "default"}}, params{"EC", "P-256", 0}, false}, {"path uri", args{&apiv1.CreateKeyRequest{Name: "softkms:path=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, SigningKey: "default"}}, 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) } nebulaPub := x25519.PublicKey{ 0x7c, 0x7f, 0x14, 0xf3, 0xe2, 0x44, 0x63, 0xa6, 0xb3, 0x1d, 0x71, 0xce, 0xc1, 0x1a, 0x1b, 0xba, 0xb7, 0x1f, 0xdb, 0x95, 0x86, 0xfe, 0xe7, 0x8a, 0xc6, 0xf4, 0x3b, 0xb1, 0x0a, 0xd4, 0x54, 0x0f, } 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}, {"key uri", args{&apiv1.GetPublicKeyRequest{Name: "softkms:testdata/pub.pem"}}, pub, false}, {"key path uri", args{&apiv1.GetPublicKeyRequest{Name: "softkms:path=testdata/pub.pem"}}, pub, false}, {"cert", args{&apiv1.GetPublicKeyRequest{Name: "testdata/cert.crt"}}, pub, false}, {"cert uri", args{&apiv1.GetPublicKeyRequest{Name: "softkms:testdata/cert.crt"}}, pub, false}, {"cert path uri", args{&apiv1.GetPublicKeyRequest{Name: "softkms:path=testdata/cert.crt"}}, pub, false}, {"private key", args{&apiv1.GetPublicKeyRequest{Name: "testdata/cert.key"}}, pub, false}, {"x25519 key", args{&apiv1.GetPublicKeyRequest{Name: "testdata/nebula.pem"}}, nebulaPub, false}, {"x25519 private key", args{&apiv1.GetPublicKeyRequest{Name: "testdata/nebula.key"}}, nebulaPub, false}, {"fail not exists", args{&apiv1.GetPublicKeyRequest{Name: "testdata/missing"}}, nil, true}, {"fail encrypted key", args{&apiv1.GetPublicKeyRequest{Name: "testdata/priv.pem"}}, nil, true}, {"fail unsupported key", args{&apiv1.GetPublicKeyRequest{Name: "testdata/dsa.pem"}}, 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}, {"file uri", args{&apiv1.CreateDecrypterRequest{DecryptionKey: "softkms:testdata/rsa.priv.pem", Password: []byte("pass")}}, keyFromFile, false}, {"path uri", args{&apiv1.CreateDecrypterRequest{DecryptionKey: "softkms:path=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) } }) } } crypto-0.57.0/kms/softkms/testdata/000077500000000000000000000000001474156331600172265ustar00rootroot00000000000000crypto-0.57.0/kms/softkms/testdata/cert.crt000066400000000000000000000011731474156331600206770ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBpzCCAU2gAwIBAgIQWaY8KIDAfak8aYljelf8eTAKBggqhkjOPQQDAjAdMRsw GQYDVQQDExJ0ZXN0LnNtYWxsc3RlcC5jb20wHhcNMjAwMTE2MDAwNDU4WhcNMjAw MTE3MDAwNDU4WjAdMRswGQYDVQQDExJ0ZXN0LnNtYWxsc3RlcC5jb20wWTATBgcq hkjOPQIBBggqhkjOPQMBBwNCAATlU8P9blFefSWuzYx2g215NJn6yHW95PXeFqQ9 kX1jNo1VmC6Oord3We37iM8QJT4QP9ZDUaAVmJUZSjd+W8H/o28wbTAOBgNVHQ8B Af8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQW BBTn0wonKkm2lLRNYZrKhUukiynvqzAdBgNVHREEFjAUghJ0ZXN0LnNtYWxsc3Rl cC5jb20wCgYIKoZIzj0EAwIDSAAwRQIhAJ5XqryBIY1X4fl/9l0isV69eQfA0Qo5 1mjervUcEnOWAiBsmN4frz5YVw7i4UXChVBeZLZfJOKvn5eyh2gEzoq1+w== -----END CERTIFICATE----- crypto-0.57.0/kms/softkms/testdata/cert.key000066400000000000000000000003431474156331600206750ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MHcCAQEEICB6lIrMa9fVQJtdAYS4qmdYQ1BHJsEQDx8zxL38gA8toAoGCCqGSM49 AwEHoUQDQgAE5VPD/W5RXn0lrs2MdoNteTSZ+sh1veT13hakPZF9YzaNVZgujqK3 d1nt+4jPECU+ED/WQ1GgFZiVGUo3flvB/w== -----END EC PRIVATE KEY----- crypto-0.57.0/kms/softkms/testdata/dsa.key000066400000000000000000000006231474156331600205100ustar00rootroot00000000000000-----BEGIN DSA PRIVATE KEY----- MIH3AgEAAkEAjcm6Okl66tWbbD068ymE17XI0wLNUpcIZgoCqvV24qRMT6cTl/J1 0DYkR/vTZaJ8EufEidCEf0ZHD06WoORE3QIVAI8EK3wRHdGmIX5AHBuhfj/7p3xT AkBp8s2KAL/MsA1CINi3sOiVlbYPrA/WC9joLgV257BzOYLlWfZqiyketoSsd6sd kZUqVJ02FxxLqSv8jouMWtOLAkA+ye7vjsTNnIV2xDkciQtnK1n4Di3DR15Tm0UP Kf03FPxvYobFvCDxdgdlSAxDvjcDn1Z1ot7wzXFQX5IK4ApUAhRIG+8uWBKim3Rc iUyGbE4gL/XByA== -----END DSA PRIVATE KEY-----crypto-0.57.0/kms/softkms/testdata/dsa.pem000066400000000000000000000005751474156331600205070ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIHwMIGoBgcqhkjOOAQBMIGcAkEAjcm6Okl66tWbbD068ymE17XI0wLNUpcIZgoC qvV24qRMT6cTl/J10DYkR/vTZaJ8EufEidCEf0ZHD06WoORE3QIVAI8EK3wRHdGm IX5AHBuhfj/7p3xTAkBp8s2KAL/MsA1CINi3sOiVlbYPrA/WC9joLgV257BzOYLl WfZqiyketoSsd6sdkZUqVJ02FxxLqSv8jouMWtOLA0MAAkA+ye7vjsTNnIV2xDkc iQtnK1n4Di3DR15Tm0UPKf03FPxvYobFvCDxdgdlSAxDvjcDn1Z1ot7wzXFQX5IK 4ApU -----END PUBLIC KEY-----crypto-0.57.0/kms/softkms/testdata/nebula.key000066400000000000000000000001771474156331600212130ustar00rootroot00000000000000-----BEGIN NEBULA X25519 PRIVATE KEY----- sc9k10IOJEFg9QDXEAFqkDgVQ3KfuubYHfG+Xl2ODbs= -----END NEBULA X25519 PRIVATE KEY----- crypto-0.57.0/kms/softkms/testdata/nebula.pem000066400000000000000000000001751474156331600212020ustar00rootroot00000000000000-----BEGIN NEBULA X25519 PUBLIC KEY----- fH8U8+JEY6azHXHOwRoburcf25WG/ueKxvQ7sQrUVA8= -----END NEBULA X25519 PUBLIC KEY----- crypto-0.57.0/kms/softkms/testdata/priv.pem000066400000000000000000000004721474156331600207140ustar00rootroot00000000000000-----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----- crypto-0.57.0/kms/softkms/testdata/pub.pem000066400000000000000000000002621474156331600205170ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5VPD/W5RXn0lrs2MdoNteTSZ+sh1 veT13hakPZF9YzaNVZgujqK3d1nt+4jPECU+ED/WQ1GgFZiVGUo3flvB/w== -----END PUBLIC KEY----- crypto-0.57.0/kms/softkms/testdata/rsa.priv.pem000066400000000000000000000033461474156331600215030ustar00rootroot00000000000000-----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----- crypto-0.57.0/kms/softkms/testdata/rsa.pub.pem000066400000000000000000000007031474156331600213030ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn2Oh7/uWB5RH40la1a43 IRaLZ8EnJVw5DCKE3BUre8xflVY2wTIS7XHcY0fEGprtq7hzFKors9AIGGn2yGrf bZX2I+1g+RtQ6cLL6koeLuhRDqCuae0lZPulWc5ixBmM9mpl4ARRcpQFldxFRhis xUaHMx8VqdZjFSDc5CJHYYK1n2G5DyuzJCk6yOfyMpwxizZJF4IUyqV7zKmZv1z9 /Xd8X0ag7jRdaTBpupJ1WLaq7LlvyB4nr47JXXkLFbRIL1F/gTcPtg0tdEZiKnxs VLKwOs3VjhEorUwhmVxr4NnNX/0tuOY1FJ0mx5jKLAevqLVwK2JIg/f3h7JcNxDy tQIDAQAB -----END PUBLIC KEY----- crypto-0.57.0/kms/sshagentkms/000077500000000000000000000000001474156331600162565ustar00rootroot00000000000000crypto-0.57.0/kms/sshagentkms/no_sshagentkms.go000066400000000000000000000007111474156331600216270ustar00rootroot00000000000000//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) }) } crypto-0.57.0/kms/sshagentkms/sshagentkms.go000066400000000000000000000140771474156331600211450ustar00rootroot00000000000000//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" ) // Scheme is the scheme used in uris, the string "sshagentkms". const Scheme = string(apiv1.SSHAgentKMS) // 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(_ context.Context, _ 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(_ context.Context, _ 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(_ *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) } } crypto-0.57.0/kms/sshagentkms/sshagentkms_test.go000066400000000000000000000400321474156331600221720ustar00rootroot00000000000000package 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) } } crypto-0.57.0/kms/sshagentkms/testdata/000077500000000000000000000000001474156331600200675ustar00rootroot00000000000000crypto-0.57.0/kms/sshagentkms/testdata/cert.crt000066400000000000000000000011731474156331600215400ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBpzCCAU2gAwIBAgIQWaY8KIDAfak8aYljelf8eTAKBggqhkjOPQQDAjAdMRsw GQYDVQQDExJ0ZXN0LnNtYWxsc3RlcC5jb20wHhcNMjAwMTE2MDAwNDU4WhcNMjAw MTE3MDAwNDU4WjAdMRswGQYDVQQDExJ0ZXN0LnNtYWxsc3RlcC5jb20wWTATBgcq hkjOPQIBBggqhkjOPQMBBwNCAATlU8P9blFefSWuzYx2g215NJn6yHW95PXeFqQ9 kX1jNo1VmC6Oord3We37iM8QJT4QP9ZDUaAVmJUZSjd+W8H/o28wbTAOBgNVHQ8B Af8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQW BBTn0wonKkm2lLRNYZrKhUukiynvqzAdBgNVHREEFjAUghJ0ZXN0LnNtYWxsc3Rl cC5jb20wCgYIKoZIzj0EAwIDSAAwRQIhAJ5XqryBIY1X4fl/9l0isV69eQfA0Qo5 1mjervUcEnOWAiBsmN4frz5YVw7i4UXChVBeZLZfJOKvn5eyh2gEzoq1+w== -----END CERTIFICATE----- crypto-0.57.0/kms/sshagentkms/testdata/cert.key000066400000000000000000000003431474156331600215360ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MHcCAQEEICB6lIrMa9fVQJtdAYS4qmdYQ1BHJsEQDx8zxL38gA8toAoGCCqGSM49 AwEHoUQDQgAE5VPD/W5RXn0lrs2MdoNteTSZ+sh1veT13hakPZF9YzaNVZgujqK3 d1nt+4jPECU+ED/WQ1GgFZiVGUo3flvB/w== -----END EC PRIVATE KEY----- crypto-0.57.0/kms/sshagentkms/testdata/priv.pem000066400000000000000000000004721474156331600215550ustar00rootroot00000000000000-----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----- crypto-0.57.0/kms/sshagentkms/testdata/pub.pem000066400000000000000000000002621474156331600213600ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5VPD/W5RXn0lrs2MdoNteTSZ+sh1 veT13hakPZF9YzaNVZgujqK3d1nt+4jPECU+ED/WQ1GgFZiVGUo3flvB/w== -----END PUBLIC KEY----- crypto-0.57.0/kms/sshagentkms/testdata/ssh000066400000000000000000000064751474156331600206230ustar00rootroot00000000000000-----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----- crypto-0.57.0/kms/sshagentkms/testdata/ssh.pub000066400000000000000000000013541474156331600213770ustar00rootroot00000000000000ssh-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 crypto-0.57.0/kms/tpmkms/000077500000000000000000000000001474156331600152425ustar00rootroot00000000000000crypto-0.57.0/kms/tpmkms/errors.go000066400000000000000000000007141474156331600171070ustar00rootroot00000000000000package tpmkms import "errors" var ( ErrIdentityCertificateUnavailable = errors.New("AK certificate not available") ErrIdentityCertificateNotYetValid = errors.New("AK certificate not yet valid") ErrIdentityCertificateExpired = errors.New("AK certificate has expired") ErrIdentityCertificateIsExpiring = errors.New("AK certificate will expire soon") ErrIdentityCertificateInvalid = errors.New("AK certificate does not contain valid identity") ) crypto-0.57.0/kms/tpmkms/no_tpmkms.go000066400000000000000000000006521474156331600176030ustar00rootroot00000000000000//go:build notpmkms // +build notpmkms package tpmkms import ( "context" "os" "path/filepath" "github.com/pkg/errors" "go.step.sm/crypto/kms/apiv1" ) func init() { apiv1.Register(apiv1.TPMKMS, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) { name := filepath.Base(os.Args[0]) return nil, errors.Errorf("unsupported KMS type 'tpmkms': %s is compiled without TPM KMS support", name) }) } crypto-0.57.0/kms/tpmkms/testdata/000077500000000000000000000000001474156331600170535ustar00rootroot00000000000000crypto-0.57.0/kms/tpmkms/testdata/ec-tss2.pem000066400000000000000000000006161474156331600210410ustar00rootroot00000000000000-----BEGIN TSS2 PRIVATE KEY----- MIHyBgZngQUKAQOgAwEB/wIEQAAAAQRaAFgAIwALAAQAcgAAABAAGAALAAMAEAAg ebLnGlDAN5aHdkffRTqBdsQNnO60aY+Xvg5u80sIauMAIM0E3zndp5392TPBroKz PLHEwLiUVeBmOhBG3kss/uICBIGAAH4AIIMO322TFYndManhpvTgLMiFd6Vs3HVs OrHb15qLZTDQABBMcF+L3C2322FU060DHXv56Uq87uMu/qWE3HU+r5856+70P94I 0z3Plxwln2iGhhbKZ8gQQNKhiOdE4MYDPnN1uqvcwJwd7NZ1fqnwBKks6E1vgSne tYeNooQ= -----END TSS2 PRIVATE KEY----- crypto-0.57.0/kms/tpmkms/tpmkms.go000066400000000000000000001357011474156331600171130ustar00rootroot00000000000000//go:build !notpmkms // +build !notpmkms package tpmkms import ( "bytes" "context" "crypto" "crypto/rsa" "crypto/sha1" //nolint:gosec // required for Windows key ID calculation "crypto/sha256" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/base64" "encoding/hex" "encoding/pem" "errors" "fmt" "net/url" "os" "path/filepath" "runtime" "time" "go.step.sm/crypto/fingerprint" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/kms/uri" "go.step.sm/crypto/tpm" "go.step.sm/crypto/tpm/algorithm" "go.step.sm/crypto/tpm/attestation" "go.step.sm/crypto/tpm/storage" "go.step.sm/crypto/tpm/tss2" ) func init() { apiv1.Register(apiv1.TPMKMS, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) { return New(ctx, opts) }) } // PreferredSignatureAlgorithms indicates the preferred selection of signature // algorithms when an explicit value is omitted in CreateKeyRequest var preferredSignatureAlgorithms []apiv1.SignatureAlgorithm // SetPreferredSignatureAlgorithms sets the preferred signature algorithms // to select from when explicit values are omitted in CreateKeyRequest // // # Experimental // // Notice: This method is EXPERIMENTAL and may be changed or removed in a later // release. func SetPreferredSignatureAlgorithms(algs []apiv1.SignatureAlgorithm) { preferredSignatureAlgorithms = algs } // PreferredSignatureAlgorithms returns the preferred signature algorithms // to select from when explicit values are omitted in CreateKeyRequest // // # Experimental // // Notice: This method is EXPERIMENTAL and may be changed or removed in a later // release. func PreferredSignatureAlgorithms() []apiv1.SignatureAlgorithm { return preferredSignatureAlgorithms } // Scheme is the scheme used in TPM KMS URIs, the string "tpmkms". const Scheme = string(apiv1.TPMKMS) const ( // DefaultRSASize is the number of bits of a new RSA key if no size has been // specified. Whereas we're generally defaulting to 3072 bits for new RSA keys, // 2048 is used as the default for the TPMKMS, because we've observed the TPMs // we're testing with to be supporting this as the maximum RSA key size. We might // increase the default in the (near) future, but we want to be more confident // about the supported size for a specific TPM (model) in that case. DefaultRSASize = 2048 // defaultRSAAKSize is the default number of bits for a new RSA Attestation // Key. It is currently set to 2048, because that's what's mentioned in the // TCG TPM specification and is used by the AK template in `go-attestation`. defaultRSAAKSize = 2048 ) // TPMKMS is a KMS implementation backed by a TPM. type TPMKMS struct { tpm *tpm.TPM windowsCertificateManager apiv1.CertificateManager windowsCertificateStoreLocation string windowsCertificateStore string windowsIntermediateStoreLocation string windowsIntermediateStore string attestationCABaseURL string attestationCARootFile string attestationCAInsecure bool permanentIdentifier string identityRenewalPeriodPercentage int64 identityEarlyRenewalEnabled bool } type algorithmAttributes struct { Type string Curve int Requires []algorithm.Algorithm } var signatureAlgorithmMapping = map[apiv1.SignatureAlgorithm]algorithmAttributes{ apiv1.UnspecifiedSignAlgorithm: {"RSA", -1, []algorithm.Algorithm{algorithm.AlgorithmRSA}}, apiv1.SHA256WithRSA: {"RSA", -1, []algorithm.Algorithm{algorithm.AlgorithmRSA, algorithm.AlgorithmSHA256}}, apiv1.SHA384WithRSA: {"RSA", -1, []algorithm.Algorithm{algorithm.AlgorithmRSA, algorithm.AlgorithmSHA384}}, apiv1.SHA512WithRSA: {"RSA", -1, []algorithm.Algorithm{algorithm.AlgorithmRSA, algorithm.AlgorithmSHA512}}, apiv1.SHA256WithRSAPSS: {"RSA", -1, []algorithm.Algorithm{algorithm.AlgorithmRSAPSS, algorithm.AlgorithmSHA256}}, apiv1.SHA384WithRSAPSS: {"RSA", -1, []algorithm.Algorithm{algorithm.AlgorithmRSAPSS, algorithm.AlgorithmSHA384}}, apiv1.SHA512WithRSAPSS: {"RSA", -1, []algorithm.Algorithm{algorithm.AlgorithmRSAPSS, algorithm.AlgorithmSHA512}}, apiv1.ECDSAWithSHA256: {"ECDSA", 256, []algorithm.Algorithm{algorithm.AlgorithmECDSA, algorithm.AlgorithmSHA256}}, apiv1.ECDSAWithSHA384: {"ECDSA", 384, []algorithm.Algorithm{algorithm.AlgorithmECDSA, algorithm.AlgorithmSHA384}}, apiv1.ECDSAWithSHA512: {"ECDSA", 521, []algorithm.Algorithm{algorithm.AlgorithmECDSA, algorithm.AlgorithmSHA512}}, } const ( microsoftPCP = "Microsoft Platform Crypto Provider" defaultStoreLocation = "user" defaultStore = "My" defaultIntermediateStoreLocation = "user" defaultIntermediateStore = "CA" // TODO(hs): verify "CA" works for "machine" certs too ) // New initializes a new KMS backed by a TPM. // // A new TPMKMS can be initialized with a configuration by providing // a URI in the options: // // New(ctx, &apiv1.Options{ // URI: tpmkms:device=/dev/tpmrm0;storage-directory=/path/to/tpmstorage/directory // }) // // It's also possible to set the storage directory as follows: // // New(ctx, &apiv1.Options{ // URI: tpmkms:device=/dev/tpmrm0 // StorageDirectory: /path/to/tpmstorage/directory // }) // // The default storage location for serialized TPM objects when // an instance of TPMKMS is created, is the relative path "tpm". // // The system default TPM device will be used when not configured. A // specific TPM device can be selected by setting the device: // // tpmkms:device=/dev/tpmrm0 // // By default newly created TPM objects won't be persisted, so can't // be readily used. The location for storage can be set using // storage-directory: // // tpmkms:storage-directory=/path/to/tpmstorage/directory // // On Windows the TPMKMS implementation has an option to use the native // certificate stores for certificate storage and retrieval instead of // using the storage directory for those. TPM keys will still be persisted // to the storage directory, because that's how the KMS keeps track of which // keys it manages, but it'll use the Windows certificate stores for // operations that involve certificates for TPM keys. Use the "enable-cng" // option to enable this optional integration: // // tpmkms:enable-cng=true // // If the CryptoAPI Next Generation (CNG) integration is enabled, the TPMKMS // will use an instance of the CAPIKMS to manage certificates. It'll use the // the "Personal" ("My") user certificate store by default. A different location // and store to be used for all operations against the TPMKMS can be defined as // follows: // // tpmkms:store-location=machine;store=CA // // The location and store to use can be overridden for a specific operation // against a TPMKMS instance, if required. It's not possible to change the crypto // provider to user; that will always be the "Microsoft Platform Crypto Provider" // // For operations that involve certificate chains, it's possible to set the // intermediate CA store location and store name at initialization time. The // same options can be used for a specific operation, if needed. By default the // "CA" user certificate store is used. // // tpmkms:intermediate-store-location=machine;intermediate-store=CustomCAStore // // For attestation use cases that involve the Smallstep Attestation CA // or a compatible one, several properties can be set. The following // specify the Attestation CA base URL, the path to a bundle of root CAs // to trust when setting up a TLS connection to the Attestation CA and // disable TLS certificate validation, respectively. // // tpmkms:attestation-ca-url=https://my.attestation.ca // tpmkms:attestation-ca-root=/path/to/trusted/roots.pem // tpmkms:attestation-ca-insecure=true // // The system may not always have a PermanentIdentifier assigned, so // when initializing the TPMKMS, it's possible to set this value: // // tpmkms:permanent-identifier= // // By default, an AK (identity) certificate will be renewed early // if it's expiring soon. The default certificate lifetime is 60%, // meaning that the renewal for the AK certificate will be kicked // off when it's past 60% of its lifetime. It's possible to disable // early renewal by setting disable-early-renewal to true: // // tpmkms:disable-early-renewal=true // // The default lifetime percentage can be changed by setting // renewal-percentage: // // tpmkms:renewal-percentage=70 // // Attestation support in the TPMKMS is considered EXPERIMENTAL. It // is expected that there will be changes to the configuration that // be provided and the attestation flow. // // The TPMKMS implementation is backed by an instance of the TPM from // the `tpm` package. If the TPMKMS operations aren't sufficient for // your use case, use a tpm.TPM instance instead. func New(ctx context.Context, opts apiv1.Options) (kms *TPMKMS, err error) { kms = &TPMKMS{ identityEarlyRenewalEnabled: true, identityRenewalPeriodPercentage: 60, // default to AK certificate renewal at 60% of lifetime } storageDirectory := "tpm" // store TPM objects in a relative tpm directory by default. if opts.StorageDirectory != "" { storageDirectory = opts.StorageDirectory } tpmOpts := []tpm.NewTPMOption{tpm.WithStore(storage.NewDirstore(storageDirectory))} if opts.URI != "" { u, err := uri.ParseWithScheme(Scheme, opts.URI) if err != nil { return nil, fmt.Errorf("failed parsing %q as URI: %w", opts.URI, err) } if device := u.Get("device"); device != "" { tpmOpts = append(tpmOpts, tpm.WithDeviceName(device)) } if storageDirectory := u.Get("storage-directory"); storageDirectory != "" { tpmOpts = append(tpmOpts, tpm.WithStore(storage.NewDirstore(storageDirectory))) } kms.attestationCABaseURL = u.Get("attestation-ca-url") kms.attestationCARootFile = u.Get("attestation-ca-root") kms.attestationCAInsecure = u.GetBool("attestation-ca-insecure") kms.permanentIdentifier = u.Get("permanent-identifier") // TODO(hs): determine if this is needed kms.identityEarlyRenewalEnabled = !u.GetBool("disable-early-renewal") if percentage := u.GetInt("renewal-percentage"); percentage != nil { if *percentage < 1 || *percentage > 100 { return nil, fmt.Errorf("renewal percentage must be between 1 and 100; got %d", *percentage) } kms.identityRenewalPeriodPercentage = *percentage } // opt-in for enabling CAPI integration on Windows for certificate // management. This will result in certificates being stored to or // retrieved from the Windows certificate stores. enableCNG := u.GetBool("enable-cng") // TODO(hs): maybe change the option flag or make this the default on Windows if enableCNG && runtime.GOOS != "windows" { return nil, fmt.Errorf(`"enable-cng" is not supported on %s`, runtime.GOOS) } if enableCNG { fn, ok := apiv1.LoadKeyManagerNewFunc(apiv1.CAPIKMS) if !ok { name := filepath.Base(os.Args[0]) return nil, fmt.Errorf(`unsupported KMS type "capi": %s is compiled without Microsoft CryptoAPI Next Generation (CNG) support`, name) } km, err := fn(ctx, apiv1.Options{ Type: apiv1.CAPIKMS, URI: uri.New("capi", url.Values{"provider": []string{microsoftPCP}}).String(), }) if err != nil { return nil, fmt.Errorf("failed creating CAPIKMS instance: %w", err) } kms.windowsCertificateManager, ok = km.(apiv1.CertificateManager) if !ok { return nil, fmt.Errorf("unexpected type %T; expected apiv1.CertificateManager", km) } kms.windowsCertificateStoreLocation = defaultStoreLocation if storeLocation := u.Get("store-location"); storeLocation != "" { kms.windowsCertificateStoreLocation = storeLocation } kms.windowsCertificateStore = defaultStore if store := u.Get("store"); store != "" { kms.windowsCertificateStore = store } kms.windowsIntermediateStoreLocation = defaultIntermediateStoreLocation if intermediateStoreLocation := u.Get("intermediate-store-location"); intermediateStoreLocation != "" { kms.windowsIntermediateStoreLocation = intermediateStoreLocation } kms.windowsIntermediateStore = defaultIntermediateStore if intermediateStore := u.Get("intermediate-store"); intermediateStore != "" { kms.windowsIntermediateStore = intermediateStore } } // TODO(hs): support a mode in which the TPM storage doesn't rely on JSON on Windows // at all, but directly feeds into OS native storage? Some operations can be NOOPs, such // as the ones that create AKs and keys. Is all of the data available in the keys stored // with Windows, incl. the attestation certification? } kms.tpm, err = tpm.New(tpmOpts...) if err != nil { return nil, fmt.Errorf("failed creating new TPM: %w", err) } return } // usesWindowsCertificateStore is a helper method that indicates whether // the TPMKMS should use the Windows certificate stores for certificate // operations. func (k *TPMKMS) usesWindowsCertificateStore() bool { return k.windowsCertificateManager != nil } // CreateKey generates a new key in the TPM KMS and returns the public key. // // The `name` in the [apiv1.CreateKeyRequest] can be used to specify // some key properties. These are as follows: // // - name=: specify the name to identify the key with // - ak=true: if set to true, an Attestation Key (AK) will be created instead of an application key // - tss2=true: is set to true, the PrivateKey response will contain a [tss2.TPMKey]. // - attest-by=: attest an application key at creation time with the AK identified by `akName` // - qualifying-data=: hexadecimal coded binary data that can be used to guarantee freshness when attesting creation of a key // // Some examples usages: // // Create an application key, without attesting it: // // tpmkms:name=my-key // // Create an Attestation Key (AK): // // tpmkms:name=my-ak;ak=true // // Create an application key, attested by `my-ak` with "1234" as the Qualifying Data: // // tpmkms:name=my-attested-key;attest-by=my-ak;qualifying-data=61626364 func (k *TPMKMS) 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") } properties, err := parseNameURI(req.Name) if err != nil { return nil, fmt.Errorf("failed parsing %q: %w", req.Name, err) } ctx := context.Background() caps, err := k.tpm.GetCapabilities(ctx) if err != nil { return nil, fmt.Errorf("could not get TPM capabilities: %w", err) } var ( v algorithmAttributes ok bool ) if !properties.ak && req.SignatureAlgorithm == apiv1.UnspecifiedSignAlgorithm && len(preferredSignatureAlgorithms) > 0 { for _, alg := range preferredSignatureAlgorithms { v, ok = signatureAlgorithmMapping[alg] if !ok { return nil, fmt.Errorf("TPMKMS does not support signature algorithm %q", alg) } if caps.SupportsAlgorithms(v.Requires) { break } } } else { v, ok = signatureAlgorithmMapping[req.SignatureAlgorithm] if !ok { return nil, fmt.Errorf("TPMKMS does not support signature algorithm %q", req.SignatureAlgorithm) } if !caps.SupportsAlgorithms(v.Requires) { return nil, fmt.Errorf("signature algorithm %q not supported by the TPM device", req.SignatureAlgorithm) } } if properties.ak && v.Type == "ECDSA" { return nil, errors.New("AKs must be RSA keys") } if properties.ak && req.Bits != 0 && req.Bits != defaultRSAAKSize { // 2048 return nil, fmt.Errorf("creating %d bit AKs is not supported; AKs must be RSA 2048 bits", req.Bits) } size := DefaultRSASize // defaults to 2048 if req.Bits > 0 { size = req.Bits } if v.Type == "ECDSA" { size = v.Curve } var privateKey any if properties.ak { ak, err := k.tpm.CreateAK(ctx, properties.name) // NOTE: size is never passed for AKs; it's hardcoded to 2048 in lower levels. if err != nil { if errors.Is(err, tpm.ErrExists) { return nil, apiv1.AlreadyExistsError{Message: err.Error()} } return nil, fmt.Errorf("failed creating AK: %w", err) } if properties.tss2 { tpmKey, err := ak.ToTSS2(ctx) if err != nil { return nil, fmt.Errorf("failed exporting AK to TSS2: %w", err) } privateKey = tpmKey } createdAKURI := fmt.Sprintf("tpmkms:name=%s;ak=true", ak.Name()) return &apiv1.CreateKeyResponse{ Name: createdAKURI, PublicKey: ak.Public(), PrivateKey: privateKey, }, nil } var key *tpm.Key if properties.attestBy != "" { config := tpm.AttestKeyConfig{ Algorithm: v.Type, Size: size, QualifyingData: properties.qualifyingData, } key, err = k.tpm.AttestKey(ctx, properties.attestBy, properties.name, config) if err != nil { if errors.Is(err, tpm.ErrExists) { return nil, apiv1.AlreadyExistsError{Message: err.Error()} } return nil, fmt.Errorf("failed creating attested key: %w", err) } } else { config := tpm.CreateKeyConfig{ Algorithm: v.Type, Size: size, } key, err = k.tpm.CreateKey(ctx, properties.name, config) if err != nil { if errors.Is(err, tpm.ErrExists) { return nil, apiv1.AlreadyExistsError{Message: err.Error()} } return nil, fmt.Errorf("failed creating key: %w", err) } } if properties.tss2 { tpmKey, err := key.ToTSS2(ctx) if err != nil { return nil, fmt.Errorf("failed exporting key to TSS2: %w", err) } privateKey = tpmKey } signer, err := key.Signer(ctx) if err != nil { return nil, fmt.Errorf("failed getting signer for key: %w", err) } createdKeyURI := fmt.Sprintf("tpmkms:name=%s", key.Name()) if properties.attestBy != "" { createdKeyURI = fmt.Sprintf("%s;attest-by=%s", createdKeyURI, key.AttestedBy()) } return &apiv1.CreateKeyResponse{ Name: createdKeyURI, PublicKey: signer.Public(), PrivateKey: privateKey, CreateSignerRequest: apiv1.CreateSignerRequest{ SigningKey: createdKeyURI, Signer: signer, }, }, nil } // DeleteKey deletes a key identified by name from the TPMKMS. // // # Experimental // // Notice: This method is EXPERIMENTAL and may be changed or removed in a later // release. func (k *TPMKMS) DeleteKey(req *apiv1.DeleteKeyRequest) error { if req.Name == "" { return fmt.Errorf("deleteKeyRequest 'name' cannot be empty") } properties, err := parseNameURI(req.Name) if err != nil { return fmt.Errorf("failed parsing %q: %w", req.Name, err) } ctx := context.Background() if properties.ak { if err := k.tpm.DeleteAK(ctx, properties.name); err != nil { return notFoundError(err) } } else { if err := k.tpm.DeleteKey(ctx, properties.name); err != nil { return notFoundError(err) } } return nil } // CreateSigner creates a signer using a key present in the TPM KMS. // // The `signingKey` in the [apiv1.CreateSignerRequest] can be used to specify // some key properties. These are as follows: // // - name=: specify the name to identify the key with // - path=: specify the TSS2 PEM file to use func (k *TPMKMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) { if req.Signer != nil { return req.Signer, nil } var pemBytes []byte switch { case req.SigningKey != "": properties, err := parseNameURI(req.SigningKey) if err != nil { return nil, fmt.Errorf("failed parsing %q: %w", req.SigningKey, err) } if properties.ak { return nil, fmt.Errorf("signing with an AK currently not supported") } switch { case properties.name != "": ctx := context.Background() key, err := k.getKey(ctx, properties.name) if err != nil { return nil, err } signer, err := key.Signer(ctx) if err != nil { return nil, fmt.Errorf("failed getting signer for key %q: %w", properties.name, err) } return signer, nil case properties.path != "": if pemBytes, err = os.ReadFile(properties.path); err != nil { return nil, fmt.Errorf("failed reading key from %q: %w", properties.path, err) } default: return nil, fmt.Errorf("failed parsing %q: name and path cannot be empty", req.SigningKey) } case len(req.SigningKeyPEM) > 0: pemBytes = req.SigningKeyPEM default: return nil, errors.New("createSignerRequest 'signingKey' and 'signingKeyPEM' cannot be empty") } // Create a signer from a TSS2 PEM block key, err := parseTSS2(pemBytes) if err != nil { return nil, err } ctx := context.Background() signer, err := tpm.CreateTSS2Signer(ctx, k.tpm, key) if err != nil { return nil, fmt.Errorf("failed getting signer for TSS2 PEM: %w", err) } return signer, nil } // GetPublicKey returns the public key present in the TPM KMS. // // The `name` in the [apiv1.GetPublicKeyRequest] can be used to specify some key // properties. These are as follows: // // - name=: specify the name to identify the key with // - ak=true: if set to true, an Attestation Key (AK) will be read instead of an application key // - path=: specify the TSS2 PEM file to read from func (k *TPMKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) { if req.Name == "" { return nil, errors.New("getPublicKeyRequest 'name' cannot be empty") } properties, err := parseNameURI(req.Name) if err != nil { return nil, fmt.Errorf("failed parsing %q: %w", req.Name, err) } ctx := context.Background() switch { case properties.name != "": if properties.ak { ak, err := k.getAK(ctx, properties.name) if err != nil { return nil, err } akPub := ak.Public() if akPub == nil { return nil, errors.New("failed getting AK public key") } return akPub, nil } key, err := k.getKey(ctx, properties.name) if err != nil { return nil, err } signer, err := key.Signer(ctx) if err != nil { return nil, fmt.Errorf("failed getting signer for key %q: %w", properties.name, err) } return signer.Public(), nil case properties.path != "": pemBytes, err := os.ReadFile(properties.path) if err != nil { return nil, fmt.Errorf("failed reading key from %q: %w", properties.path, err) } key, err := parseTSS2(pemBytes) if err != nil { return nil, err } pub, err := key.Public() if err != nil { return nil, fmt.Errorf("error decoding public key from %q: %w", properties.path, err) } return pub, nil default: return nil, fmt.Errorf("failed parsing %q: name and path cannot be empty", req.Name) } } // LoadCertificate loads the certificate for the key identified by name from the TPMKMS. func (k *TPMKMS) LoadCertificate(req *apiv1.LoadCertificateRequest) (cert *x509.Certificate, err error) { if req.Name == "" { return nil, errors.New("loadCertificateRequest 'name' cannot be empty") } chain, err := k.LoadCertificateChain(&apiv1.LoadCertificateChainRequest{Name: req.Name}) if err != nil { return nil, err } return chain[0], nil } // LoadCertificateChain loads the certificate chain for the key identified by // name from the TPMKMS. func (k *TPMKMS) LoadCertificateChain(req *apiv1.LoadCertificateChainRequest) ([]*x509.Certificate, error) { if req.Name == "" { return nil, errors.New("loadCertificateChainRequest 'name' cannot be empty") } if k.usesWindowsCertificateStore() { chain, err := k.loadCertificateChainFromWindowsCertificateStore(&apiv1.LoadCertificateRequest{ Name: req.Name, }) if err != nil { return nil, fmt.Errorf("failed loading certificate chain using Windows platform cryptography provider: %w", err) } return chain, nil } properties, err := parseNameURI(req.Name) if err != nil { return nil, fmt.Errorf("failed parsing %q: %w", req.Name, err) } ctx := context.Background() var chain []*x509.Certificate if properties.ak { ak, err := k.getAK(ctx, properties.name) if err != nil { return nil, err } chain = ak.CertificateChain() } else { key, err := k.getKey(ctx, properties.name) if err != nil { return nil, err } chain = key.CertificateChain() } if len(chain) == 0 { return nil, fmt.Errorf("failed getting certificate chain for %q: no certificate chain stored", properties.name) } return chain, nil } const ( // maximumIterations is the maximum number of times for the recursive // intermediate CA lookup loop. maximumIterations = 10 ) func (k *TPMKMS) loadCertificateChainFromWindowsCertificateStore(req *apiv1.LoadCertificateRequest) ([]*x509.Certificate, error) { pub, err := k.GetPublicKey(&apiv1.GetPublicKeyRequest{ Name: req.Name, }) if err != nil { return nil, fmt.Errorf("failed retrieving public key: %w", err) } o, err := parseNameURI(req.Name) if err != nil { return nil, fmt.Errorf("failed parsing %q: %w", req.Name, err) } location := k.windowsCertificateStoreLocation if o.storeLocation != "" { location = o.storeLocation } store := k.windowsCertificateStore if o.store != "" { store = o.store } subjectKeyID, err := generateWindowsSubjectKeyID(pub) if err != nil { return nil, fmt.Errorf("failed generating subject key id: %w", err) } cert, err := k.windowsCertificateManager.LoadCertificate(&apiv1.LoadCertificateRequest{ Name: fmt.Sprintf("capi:key-id=%s;store-location=%s;store=%s;", subjectKeyID, location, store), }) if err != nil { return nil, fmt.Errorf("failed retrieving certificate using Windows platform cryptography provider: %w", err) } intermediateCAStoreLocation := k.windowsIntermediateStoreLocation if o.intermediateStoreLocation != "" { intermediateCAStoreLocation = o.intermediateStoreLocation } intermediateCAStore := k.windowsIntermediateStore if o.intermediateStore != "" { intermediateCAStore = o.intermediateStore } chain := []*x509.Certificate{cert} child := cert for i := 0; i < maximumIterations; i++ { // loop a maximum number of times authorityKeyID := hex.EncodeToString(child.AuthorityKeyId) parent, err := k.windowsCertificateManager.LoadCertificate(&apiv1.LoadCertificateRequest{ Name: fmt.Sprintf("capi:key-id=%s;store-location=%s;store=%s", authorityKeyID, intermediateCAStoreLocation, intermediateCAStore), }) if err != nil { if errors.Is(err, apiv1.NotFoundError{}) { // if error indicates the parent wasn't found, assume end of chain for a specific // combination of store location and store is reached, and break from the loop break } return nil, fmt.Errorf("failed loading intermediate CA certificate using Windows platform cryptography provider: %w", err) } // if the discovered parent has a signature from itself, assume it's a root CA, // and break from the loop if parent.CheckSignatureFrom(parent) == nil { break } // ensure child has a valid signature from the parent if err := child.CheckSignatureFrom(parent); err != nil { return nil, fmt.Errorf("failed loading intermediate CA certificate using Windows platform cryptography provider: %w", err) } chain = append(chain, parent) child = parent } return chain, nil } // StoreCertificate stores the certificate for the key identified by name to the TPMKMS. func (k *TPMKMS) 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 empty") } return k.StoreCertificateChain(&apiv1.StoreCertificateChainRequest{Name: req.Name, CertificateChain: []*x509.Certificate{req.Certificate}}) } // StoreCertificateChain stores the certificate for the key identified by name to the TPMKMS. func (k *TPMKMS) StoreCertificateChain(req *apiv1.StoreCertificateChainRequest) error { switch { case req.Name == "": return errors.New("storeCertificateChainRequest 'name' cannot be empty") case len(req.CertificateChain) == 0: return errors.New("storeCertificateChainRequest 'certificateChain' cannot be empty") } if k.usesWindowsCertificateStore() { if err := k.storeCertificateChainToWindowsCertificateStore(&apiv1.StoreCertificateChainRequest{ Name: req.Name, CertificateChain: req.CertificateChain, }); err != nil { return fmt.Errorf("failed storing certificate chain using Windows platform cryptography provider: %w", err) } return nil } properties, err := parseNameURI(req.Name) if err != nil { return fmt.Errorf("failed parsing %q: %w", req.Name, err) } ctx := context.Background() if properties.ak { ak, err := k.getAK(ctx, properties.name) if err != nil { return err } err = ak.SetCertificateChain(ctx, req.CertificateChain) if err != nil { return fmt.Errorf("failed storing certificate for AK %q: %w", properties.name, err) } } else { key, err := k.getKey(ctx, properties.name) if err != nil { return err } err = key.SetCertificateChain(ctx, req.CertificateChain) if err != nil { return fmt.Errorf("failed storing certificate for key %q: %w", properties.name, err) } } return nil } func (k *TPMKMS) storeCertificateChainToWindowsCertificateStore(req *apiv1.StoreCertificateChainRequest) error { o, err := parseNameURI(req.Name) if err != nil { return fmt.Errorf("failed parsing %q: %w", req.Name, err) } location := k.windowsCertificateStoreLocation if o.storeLocation != "" { location = o.storeLocation } store := k.windowsCertificateStore if o.store != "" { store = o.store } skipFindCertificateKey := "false" if o.skipFindCertificateKey { skipFindCertificateKey = "true" } leaf := req.CertificateChain[0] fp, err := fingerprint.New(leaf.Raw, crypto.SHA1, fingerprint.HexFingerprint) if err != nil { return fmt.Errorf("failed calculating certificate SHA1 fingerprint: %w", err) } uv := url.Values{} uv.Set("sha1", fp) uv.Set("store-location", location) uv.Set("store", store) uv.Set("skip-find-certificate-key", skipFindCertificateKey) if err := k.windowsCertificateManager.StoreCertificate(&apiv1.StoreCertificateRequest{ Name: uri.New("capi", uv).String(), Certificate: leaf, }); err != nil { return fmt.Errorf("failed storing certificate using Windows platform cryptography provider: %w", err) } if len(req.CertificateChain) == 1 { // no certificate chain; return early return nil } intermediateCAStoreLocation := k.windowsIntermediateStoreLocation if o.intermediateStoreLocation != "" { intermediateCAStoreLocation = o.intermediateStoreLocation } intermediateCAStore := k.windowsIntermediateStore if o.intermediateStore != "" { intermediateCAStore = o.intermediateStore } for _, c := range req.CertificateChain[1:] { if err := validateIntermediateCertificate(c); err != nil { return fmt.Errorf("invalid intermediate certificate provided in chain: %w", err) } if err := k.storeIntermediateToWindowsCertificateStore(c, intermediateCAStoreLocation, intermediateCAStore); err != nil { return fmt.Errorf("failed storing intermediate certificate using Windows platform cryptography provider: %w", err) } } return nil } func validateIntermediateCertificate(c *x509.Certificate) error { switch { case !c.IsCA: return fmt.Errorf("certificate with serial %q is not a CA certificate", c.SerialNumber.String()) case !c.BasicConstraintsValid: return fmt.Errorf("certificate with serial %q has invalid basic constraints", c.SerialNumber.String()) case bytes.Equal(c.AuthorityKeyId, c.SubjectKeyId): return fmt.Errorf("certificate with serial %q has equal subject and authority key IDs", c.SerialNumber.String()) case c.CheckSignatureFrom(c) == nil: return fmt.Errorf("certificate with serial %q is self-signed root CA", c.SerialNumber.String()) } return nil } func (k *TPMKMS) storeIntermediateToWindowsCertificateStore(c *x509.Certificate, storeLocation, store string) error { fp, err := fingerprint.New(c.Raw, crypto.SHA1, fingerprint.HexFingerprint) if err != nil { return fmt.Errorf("failed calculating certificate SHA1 fingerprint: %w", err) } if err := k.windowsCertificateManager.StoreCertificate(&apiv1.StoreCertificateRequest{ Name: fmt.Sprintf("capi:sha1=%s;store-location=%s;store=%s;skip-find-certificate-key=true", fp, storeLocation, store), Certificate: c, }); err != nil { return err } return nil } // DeleteCertificate deletes a certificate for the key identified by name from the // TPMKMS. If the instance is configured to use the Windows certificate store, it'll // delete the certificate from the certificate store, backed by a CAPIKMS instance. // // It's possible to delete a specific certificate for a key by specifying it's SHA1 // or serial. This is only supported if the instance is configured to use the Windows // certificate store. // // # Experimental // // Notice: This method is EXPERIMENTAL and may be changed or removed in a later // release. func (k *TPMKMS) DeleteCertificate(req *apiv1.DeleteCertificateRequest) error { if req.Name == "" { return errors.New("deleteCertificateRequest 'name' cannot be empty") } if k.usesWindowsCertificateStore() { if err := k.deleteCertificateFromWindowsCertificateStore(&apiv1.DeleteCertificateRequest{ Name: req.Name, }); err != nil { return fmt.Errorf("failed deleting certificate from Windows platform cryptography provider: %w", err) } return nil } // TODO(hs): support delete by serial? If not, the behavior for TPM storage and Windows // certificate store storage will be different, and may need different behavior when // implementing certificate management. properties, err := parseNameURI(req.Name) if err != nil { return fmt.Errorf("failed parsing %q: %w", req.Name, err) } ctx := context.Background() if properties.ak { ak, err := k.getAK(ctx, properties.name) if err != nil { return err } if err := ak.SetCertificateChain(ctx, nil); err != nil { return fmt.Errorf("failed storing certificate for AK %q: %w", properties.name, err) } } else { key, err := k.getKey(ctx, properties.name) if err != nil { return err } if err := key.SetCertificateChain(ctx, nil); err != nil { return fmt.Errorf("failed storing certificate for key %q: %w", properties.name, err) } } return nil } func (k *TPMKMS) deleteCertificateFromWindowsCertificateStore(req *apiv1.DeleteCertificateRequest) error { o, err := parseNameURI(req.Name) if err != nil { return fmt.Errorf("failed parsing %q: %w", req.Name, err) } location := k.windowsCertificateStoreLocation if o.storeLocation != "" { location = o.storeLocation } store := k.windowsCertificateStore if o.store != "" { store = o.store } uv := url.Values{} uv.Set("store-location", location) uv.Set("store", store) switch { case o.serial != "": uv.Set("serial", o.serial) uv.Set("issuer", o.issuer) case o.keyID != "": uv.Set("key-id", o.keyID) case o.sha1 != "": uv.Set("sha1", o.sha1) default: return errors.New(`at least one of "serial", "key-id" or "sha1" is expected to be set`) } dk, ok := k.windowsCertificateManager.(deletingCertificateManager) if !ok { return fmt.Errorf("expected Windows certificate manager to implement DeleteCertificate") } if err := dk.DeleteCertificate(&apiv1.DeleteCertificateRequest{ Name: uri.New("capi", uv).String(), }); err != nil { return fmt.Errorf("failed deleting certificate using Windows platform cryptography provider: %w", err) } return nil } // attestationClient is a wrapper for [attestation.Client], containing // all of the required references to perform attestation against the // Smallstep Attestation CA. type attestationClient struct { c *attestation.Client t *tpm.TPM ek *tpm.EK ak *tpm.AK } // newAttestorClient creates a new [attestationClient], wrapping references // to the [tpm.TPM] instance, the EK and the AK to use when attesting. func (k *TPMKMS) newAttestorClient(ek *tpm.EK, ak *tpm.AK) (*attestationClient, error) { if k.attestationCABaseURL == "" { return nil, errors.New("failed creating attestation client: attestation CA base URL must not be empty") } // prepare a client to perform attestation with an Attestation CA attestationClientOptions := []attestation.Option{attestation.WithRootsFile(k.attestationCARootFile)} if k.attestationCAInsecure { attestationClientOptions = append(attestationClientOptions, attestation.WithInsecure()) } client, err := attestation.NewClient(k.attestationCABaseURL, attestationClientOptions...) if err != nil { return nil, fmt.Errorf("failed creating attestation client: %w", err) } return &attestationClient{ c: client, t: k.tpm, ek: ek, ak: ak, }, nil } // Attest implements the [apiv1.AttestationClient] interface, calling into the // underlying [attestation.Client] to perform an attestation flow with the // Smallstep Attestation CA. func (ac *attestationClient) Attest(ctx context.Context) ([]*x509.Certificate, error) { return ac.c.Attest(ctx, ac.t, ac.ek, ac.ak) } // CreateAttestation implements the [apiv1.Attester] interface for the TPMKMS. It // can be used to request the required information to verify that an application // key was created in and by a specific TPM. // // It is expected that an application key has been attested at creation time by // an attestation key (AK) before calling this method. An error will be returned // otherwise. // // The response will include an attestation key (AK) certificate (chain) issued // to the AK that was used to certify creation of the (application) key, as well // as the key certification parameters at the time of key creation. Together these // can be used by a relying party to attest that the key was created by a specific // TPM. // // If no valid AK certificate is available when calling CreateAttestation, an // enrolment with an instance of the Smallstep Attestation CA is performed. This // will use the TPM Endorsement Key and the AK as inputs. The Attestation CA will // return an AK certificate chain on success. // // When CreateAttestation is called for an AK, the AK certificate chain will be // returned. Currently no AK creation parameters are returned. func (k *TPMKMS) CreateAttestation(req *apiv1.CreateAttestationRequest) (*apiv1.CreateAttestationResponse, error) { if req.Name == "" { return nil, errors.New("createAttestationRequest 'name' cannot be empty") } properties, err := parseNameURI(req.Name) if err != nil { return nil, fmt.Errorf("failed parsing %q: %w", req.Name, err) } ctx := context.Background() eks, err := k.tpm.GetEKs(ctx) // TODO(hs): control the EK used as the caller of this method? if err != nil { return nil, fmt.Errorf("failed getting EKs: %w", err) } ek := getPreferredEK(eks) ekPublic := ek.Public() ekKeyID, err := generateKeyID(ekPublic) if err != nil { return nil, fmt.Errorf("failed getting EK public key ID: %w", err) } ekKeyURL := ekURL(ekKeyID) permanentIdentifier := ekKeyURL.String() // check if the derived EK URI fingerprint representation matches the provided // permanent identifier value. The current implementation requires the EK URI to // be used as the AK identity, so an error is returned if there's no match. This // could be changed in the future, so that another attestation flow takes place, // instead, for example. if k.permanentIdentifier != "" && permanentIdentifier != k.permanentIdentifier { return nil, fmt.Errorf("the provided permanent identifier %q does not match the EK URL %q", k.permanentIdentifier, permanentIdentifier) } var key *tpm.Key akName := properties.name if !properties.ak { key, err = k.getKey(ctx, properties.name) if err != nil { return nil, err } if !key.WasAttested() { return nil, fmt.Errorf("key %q was not attested", key.Name()) } akName = key.AttestedBy() } ak, err := k.getAK(ctx, akName) if err != nil { return nil, err } // check if a (valid) AK certificate (chain) is available. Perform attestation flow // otherwise. If an AK certificate is available, but not considered valid, e.g. due // to it not having the right identity, a new attestation flow will be performed and // the old certificate (chain) will be overwritten with the result of that flow. if err := k.hasValidIdentity(ak, ekKeyURL); err != nil { var ac apiv1.AttestationClient if req.AttestationClient != nil { // TODO(hs): check if it makes sense to have this; it doesn't capture all // behavior of the built-in attestorClient, but at least it does provide // a basic extension point for other ways of performing attestation that // might be useful for testing or attestation flows against other systems. // For it to be truly useful, the logic for determining the AK identity // would have to be updated too, though. ac = req.AttestationClient } else { ac, err = k.newAttestorClient(ek, ak) if err != nil { return nil, fmt.Errorf("failed creating attestor client: %w", err) } } // perform the attestation flow with a (remote) attestation CA akChain, err := ac.Attest(ctx) if err != nil { return nil, fmt.Errorf("failed performing AK attestation: %w", err) } // store the result with the AK, so that it can be reused for future // attestations. if err := ak.SetCertificateChain(ctx, akChain); err != nil { return nil, fmt.Errorf("failed storing AK certificate chain: %w", err) } } // when a new certificate was issued for the AK, it is possible the // certificate that was issued doesn't include the expected and/or required // identity, so this is checked before continuing. if err := k.hasValidIdentity(ak, ekKeyURL); err != nil { return nil, fmt.Errorf("AK certificate (chain) not valid for EK %q: %w", ekKeyURL, err) } akChain := ak.CertificateChain() if properties.ak { akPub := ak.Public() if akPub == nil { return nil, fmt.Errorf("failed getting AK public key") } // TODO(hs): decide if we want/need to return these; their purpose is slightly // different from the key certification parameters. _, err = ak.AttestationParameters(ctx) if err != nil { return nil, fmt.Errorf("failed getting AK attestation parameters: %w", err) } return &apiv1.CreateAttestationResponse{ Certificate: akChain[0], // certificate for the AK CertificateChain: akChain, // chain for the AK, including the leaf PublicKey: akPub, // returns the public key of the attestation key PermanentIdentifier: permanentIdentifier, }, nil } signer, err := key.Signer(ctx) if err != nil { return nil, fmt.Errorf("failed getting signer for key %q: %w", properties.name, err) } params, err := key.CertificationParameters(ctx) if err != nil { return nil, fmt.Errorf("failed getting key certification parameters for %q: %w", key.Name(), err) } // prepare the response to return akCert := akChain[0] return &apiv1.CreateAttestationResponse{ Certificate: akCert, // certificate for the AK that attested the key CertificateChain: akChain, // chain for the AK that attested the key, including the leaf PublicKey: signer.Public(), // returns the public key of the attested key CertificationParameters: &apiv1.CertificationParameters{ // key certification parameters Public: params.Public, CreateData: params.CreateData, CreateAttestation: params.CreateAttestation, CreateSignature: params.CreateSignature, }, PermanentIdentifier: permanentIdentifier, // NOTE: should always match the valid value of the AK identity (for now) }, nil } // Close releases the connection to the TPM. func (k *TPMKMS) Close() (err error) { return } // getPreferredEK returns the first RSA TPM EK found. If no RSA // EK exists, it returns the first ECDSA EK found. func getPreferredEK(eks []*tpm.EK) (ek *tpm.EK) { var fallback *tpm.EK for _, ek = range eks { if _, isRSA := ek.Public().(*rsa.PublicKey); isRSA { return } if fallback == nil { fallback = ek } } return fallback } // hasValidIdentity indicates if the AK has an associated certificate // that includes a valid identity. Currently we only consider certificates // that encode the TPM EK public key ID as one of its URI SANs, which is // the default behavior of the Smallstep Attestation CA. func (k *TPMKMS) hasValidIdentity(ak *tpm.AK, ekURL *url.URL) error { chain := ak.CertificateChain() if len(chain) == 0 { return ErrIdentityCertificateUnavailable } akCert := chain[0] now := time.Now() if now.Before(akCert.NotBefore) { return ErrIdentityCertificateNotYetValid } notAfter := akCert.NotAfter.Add(-1 * time.Minute).Truncate(time.Second) if now.After(notAfter) { return ErrIdentityCertificateExpired } // it's possible to disable early expiration errors for the AK identity // certificate when instantiating the TPMKMS. if k.identityEarlyRenewalEnabled { period := akCert.NotAfter.Sub(akCert.NotBefore).Truncate(time.Second) renewBefore := time.Duration(float64(period.Nanoseconds()) * (float64(k.identityRenewalPeriodPercentage) / 100)) earlyAfter := akCert.NotAfter.Add(-1 * renewBefore) if now.After(earlyAfter) { return ErrIdentityCertificateIsExpiring } } // the Smallstep Attestation CA will issue AK certifiates that // contain the EK public key ID encoded as an URN by default. for _, u := range akCert.URIs { if ekURL.String() == u.String() { return nil } } // TODO(hs): we could consider checking other values to contain // a usable identity too. return ErrIdentityCertificateInvalid } func (k *TPMKMS) getAK(ctx context.Context, name string) (*tpm.AK, error) { ak, err := k.tpm.GetAK(ctx, name) if err != nil { return nil, notFoundError(err) } return ak, nil } func (k *TPMKMS) getKey(ctx context.Context, name string) (*tpm.Key, error) { key, err := k.tpm.GetKey(ctx, name) if err != nil { return nil, notFoundError(err) } return key, nil } func notFoundError(err error) error { if errors.Is(err, tpm.ErrNotFound) { return apiv1.NotFoundError{ Message: err.Error(), } } return err } // generateKeyID generates a key identifier from the // SHA256 hash of the public key. func generateKeyID(pub crypto.PublicKey) ([]byte, error) { b, err := x509.MarshalPKIXPublicKey(pub) if err != nil { return nil, fmt.Errorf("error marshaling public key: %w", err) } hash := sha256.Sum256(b) return hash[:], nil } // ekURL generates an EK URI containing the encoded key identifier // for the EK. func ekURL(keyID []byte) *url.URL { return &url.URL{ Scheme: "urn", Opaque: "ek:sha256:" + base64.StdEncoding.EncodeToString(keyID), } } func parseTSS2(pemBytes []byte) (*tss2.TPMKey, error) { var block *pem.Block for len(pemBytes) > 0 { block, pemBytes = pem.Decode(pemBytes) if block == nil { break } if block.Type != "TSS2 PRIVATE KEY" { continue } key, err := tss2.ParsePrivateKey(block.Bytes) if err != nil { return nil, fmt.Errorf("failed parsing TSS2 PEM: %w", err) } return key, nil } return nil, fmt.Errorf("failed parsing TSS2 PEM: block not found") } type subjectPublicKeyInfo struct { Algorithm pkix.AlgorithmIdentifier SubjectPublicKey asn1.BitString } func generateWindowsSubjectKeyID(pub crypto.PublicKey) (string, error) { b, err := x509.MarshalPKIXPublicKey(pub) if err != nil { return "", err } var info subjectPublicKeyInfo if _, err = asn1.Unmarshal(b, &info); err != nil { return "", err } hash := sha1.Sum(info.SubjectPublicKey.Bytes) //nolint:gosec // required for Windows key ID calculation return hex.EncodeToString(hash[:]), nil } type deletingCertificateManager interface { apiv1.CertificateManager DeleteCertificate(req *apiv1.DeleteCertificateRequest) error } type deletingCertificateChainManager interface { apiv1.CertificateChainManager DeleteCertificate(req *apiv1.DeleteCertificateRequest) error } var _ apiv1.KeyManager = (*TPMKMS)(nil) var _ apiv1.Attester = (*TPMKMS)(nil) var _ apiv1.CertificateManager = (*TPMKMS)(nil) var _ apiv1.CertificateChainManager = (*TPMKMS)(nil) var _ deletingCertificateChainManager = (*TPMKMS)(nil) var _ apiv1.AttestationClient = (*attestationClient)(nil) crypto-0.57.0/kms/tpmkms/tpmkms_simulator_test.go000066400000000000000000001745061474156331600222570ustar00rootroot00000000000000//go:build tpmsimulator // +build tpmsimulator package tpmkms import ( "context" "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/json" "errors" "fmt" "net/http" "net/http/httptest" "net/url" "os" "path/filepath" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/smallstep/go-attestation/attest" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/minica" tpmp "go.step.sm/crypto/tpm" "go.step.sm/crypto/tpm/simulator" "go.step.sm/crypto/tpm/storage" "go.step.sm/crypto/tpm/tss2" ) type newSimulatedTPMOption any type newSimulatedTPMPreparerOption func(t *testing.T, tpm *tpmp.TPM) func withAK(name string) newSimulatedTPMPreparerOption { return func(t *testing.T, tpm *tpmp.TPM) { t.Helper() _, err := tpm.CreateAK(context.Background(), name) require.NoError(t, err) } } func withKey(name string) newSimulatedTPMPreparerOption { return func(t *testing.T, tpm *tpmp.TPM) { t.Helper() config := tpmp.CreateKeyConfig{ Algorithm: "RSA", Size: 1024, } _, err := tpm.CreateKey(context.Background(), name, config) require.NoError(t, err) } } func withCapabilities(caps *tpmp.Capabilities) tpmp.NewTPMOption { return tpmp.WithCapabilities(caps) } func newSimulatedTPM(t *testing.T, opts ...newSimulatedTPMOption) *tpmp.TPM { t.Helper() tmpDir := t.TempDir() tpmOpts := []tpmp.NewTPMOption{ withSimulator(t), tpmp.WithStore(storage.NewDirstore(tmpDir)), } var preparers []newSimulatedTPMPreparerOption for _, opt := range opts { switch o := opt.(type) { case tpmp.NewTPMOption: tpmOpts = append(tpmOpts, o) case newSimulatedTPMPreparerOption: preparers = append(preparers, o) default: require.Fail(t, "invalid TPM option type provided", `TPM option type "%T"`, o) } } tpm, err := tpmp.New(tpmOpts...) require.NoError(t, err) for _, applyTo := range preparers { applyTo(t, tpm) } return tpm } func withSimulator(t *testing.T) tpmp.NewTPMOption { t.Helper() var sim simulator.Simulator t.Cleanup(func() { if sim == nil { return } err := sim.Close() require.NoError(t, err) }) sim, err := simulator.New() require.NoError(t, err) err = sim.Open() require.NoError(t, err) return tpmp.WithSimulator(sim) } func TestTPMKMS_CreateKey_Capabilities(t *testing.T) { tpmWithNoCaps := newSimulatedTPM(t, withCapabilities(&tpmp.Capabilities{})) type fields struct { tpm *tpmp.TPM } type args struct { req *apiv1.CreateKeyRequest } tests := []struct { name string fields fields args args assertFunc assert.ValueAssertionFunc expErr error }{ { name: "fail/unsupported-algorithm", fields: fields{ tpm: tpmWithNoCaps, }, args: args{ req: &apiv1.CreateKeyRequest{ Name: "tpmkms:name=key1", SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 2048, }, }, assertFunc: func(tt assert.TestingT, i1 interface{}, i2 ...interface{}) bool { if assert.IsType(t, &apiv1.CreateKeyResponse{}, i1) { r, _ := i1.(*apiv1.CreateKeyResponse) return assert.Nil(t, r) } return false }, expErr: errors.New(`signature algorithm "SHA256-RSA" not supported by the TPM device`), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &TPMKMS{ tpm: tt.fields.tpm, } got, err := k.CreateKey(tt.args.req) if tt.expErr != nil { assert.EqualError(t, err, tt.expErr.Error()) return } assert.NoError(t, err) assert.True(t, tt.assertFunc(t, got)) }) } } func TestTPMKMS_CreateKey(t *testing.T) { tpmWithAK := newSimulatedTPM(t, withAK("ak1")) type fields struct { tpm *tpmp.TPM } type args struct { req *apiv1.CreateKeyRequest } tests := []struct { name string fields fields args args assertFunc assert.ValueAssertionFunc expErr error }{ { name: "ok/key", fields: fields{ tpm: tpmWithAK, }, args: args{ req: &apiv1.CreateKeyRequest{ Name: "tpmkms:name=key1", SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 1024, }, }, assertFunc: func(tt assert.TestingT, i1 interface{}, i2 ...interface{}) bool { if assert.IsType(t, &apiv1.CreateKeyResponse{}, i1) { r, _ := i1.(*apiv1.CreateKeyResponse) if assert.NotNil(t, r) { assert.Equal(t, "tpmkms:name=key1", r.Name) assert.Equal(t, "tpmkms:name=key1", r.CreateSignerRequest.SigningKey) if assert.NotNil(t, r.CreateSignerRequest.Signer) { assert.Implements(t, (*crypto.Signer)(nil), r.CreateSignerRequest.Signer) } return true } } return false }, }, { name: "ok/attested-key", fields: fields{ tpm: tpmWithAK, }, args: args{ req: &apiv1.CreateKeyRequest{ Name: "tpmkms:name=key2;attest-by=ak1", SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 1024, }, }, assertFunc: func(tt assert.TestingT, i1 interface{}, i2 ...interface{}) bool { if assert.IsType(t, &apiv1.CreateKeyResponse{}, i1) { r, _ := i1.(*apiv1.CreateKeyResponse) if assert.NotNil(t, r) { assert.Equal(t, "tpmkms:name=key2;attest-by=ak1", r.Name) assert.Equal(t, "tpmkms:name=key2;attest-by=ak1", r.CreateSignerRequest.SigningKey) if assert.NotNil(t, r.CreateSignerRequest.Signer) { assert.Implements(t, (*crypto.Signer)(nil), r.CreateSignerRequest.Signer) } return true } } return false }, }, { name: "ok/ak-tss2", fields: fields{ tpm: tpmWithAK, }, args: args{ req: &apiv1.CreateKeyRequest{ Name: "tpmkms:name=ak2;ak=true;tss2=true", SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 2048, }, }, assertFunc: func(tt assert.TestingT, i1 interface{}, i2 ...interface{}) bool { if assert.IsType(t, &apiv1.CreateKeyResponse{}, i1) { r, _ := i1.(*apiv1.CreateKeyResponse) if assert.NotNil(t, r) { assert.Equal(t, "tpmkms:name=ak2;ak=true", r.Name) assert.Equal(t, apiv1.CreateSignerRequest{}, r.CreateSignerRequest) if assert.NotNil(t, r.PublicKey) { assert.IsType(t, &rsa.PublicKey{}, r.PublicKey) } if assert.NotNil(t, r.PrivateKey) { assert.IsType(t, &tss2.TPMKey{}, r.PrivateKey) } return true } } return false }, }, { name: "ok/ecdsa-key-tss2", fields: fields{ tpm: tpmWithAK, }, args: args{ req: &apiv1.CreateKeyRequest{ Name: "tpmkms:name=ecdsa-key;tss2=true", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }, }, assertFunc: func(tt assert.TestingT, i1 interface{}, i2 ...interface{}) bool { if assert.IsType(t, &apiv1.CreateKeyResponse{}, i1) { r, _ := i1.(*apiv1.CreateKeyResponse) if assert.NotNil(t, r) { assert.Equal(t, "tpmkms:name=ecdsa-key", r.Name) assert.Equal(t, "tpmkms:name=ecdsa-key", r.CreateSignerRequest.SigningKey) if assert.NotNil(t, r.CreateSignerRequest.Signer) { assert.Implements(t, (*crypto.Signer)(nil), r.CreateSignerRequest.Signer) } if assert.NotNil(t, r.PublicKey) { assert.IsType(t, &ecdsa.PublicKey{}, r.PublicKey) } if assert.NotNil(t, r.PrivateKey) { assert.IsType(t, &tss2.TPMKey{}, r.PrivateKey) } return true } } return false }, }, { name: "fail/empty-name", fields: fields{ tpm: tpmWithAK, }, args: args{ req: &apiv1.CreateKeyRequest{ Name: "", }, }, assertFunc: func(tt assert.TestingT, i1 interface{}, i2 ...interface{}) bool { if assert.IsType(t, &apiv1.CreateKeyResponse{}, i1) { r, _ := i1.(*apiv1.CreateKeyResponse) return assert.Nil(t, r) } return false }, expErr: errors.New("createKeyRequest 'name' cannot be empty"), }, { name: "fail/uri", fields: fields{ tpm: tpmWithAK, }, args: args{ req: &apiv1.CreateKeyRequest{ Name: "baduri:", }, }, expErr: errors.New("failed parsing \"baduri:\": URI scheme \"baduri\" is not supported"), }, { name: "fail/negative-bits", fields: fields{ tpm: tpmWithAK, }, args: args{ req: &apiv1.CreateKeyRequest{ Name: "tpmkms:name=key1", Bits: -1, }, }, assertFunc: func(tt assert.TestingT, i1 interface{}, i2 ...interface{}) bool { if assert.IsType(t, &apiv1.CreateKeyResponse{}, i1) { r, _ := i1.(*apiv1.CreateKeyResponse) return assert.Nil(t, r) } return false }, expErr: errors.New("createKeyRequest 'bits' cannot be negative"), }, { name: "fail/ak-cannot-be-attested", fields: fields{ tpm: tpmWithAK, }, args: args{ req: &apiv1.CreateKeyRequest{ Name: "tpmkms:name=akx;ak=true;attest-by=ak1", SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 2048, }, }, assertFunc: func(tt assert.TestingT, i1 interface{}, i2 ...interface{}) bool { if assert.IsType(t, &apiv1.CreateKeyResponse{}, i1) { r, _ := i1.(*apiv1.CreateKeyResponse) return assert.Nil(t, r) } return false }, expErr: fmt.Errorf(`failed parsing "tpmkms:name=akx;ak=true;attest-by=ak1": "ak" and "attest-by" are mutually exclusive`), }, { name: "fail/invalid-algorithm", fields: fields{ tpm: tpmWithAK, }, args: args{ req: &apiv1.CreateKeyRequest{ Name: "tpmkms:name=key1", SignatureAlgorithm: apiv1.SignatureAlgorithm(-1), Bits: 1024, }, }, assertFunc: func(tt assert.TestingT, i1 interface{}, i2 ...interface{}) bool { if assert.IsType(t, &apiv1.CreateKeyResponse{}, i1) { r, _ := i1.(*apiv1.CreateKeyResponse) return assert.Nil(t, r) } return false }, expErr: errors.New(`TPMKMS does not support signature algorithm "unknown(-1)"`), }, { name: "fail/ecdsa-ak", fields: fields{ tpm: tpmWithAK, }, args: args{ req: &apiv1.CreateKeyRequest{ Name: "tpmkms:name=invalidAK;ak=true", SignatureAlgorithm: apiv1.ECDSAWithSHA256, }, }, assertFunc: func(tt assert.TestingT, i1 interface{}, i2 ...interface{}) bool { if assert.IsType(t, &apiv1.CreateKeyResponse{}, i1) { r, _ := i1.(*apiv1.CreateKeyResponse) return assert.Nil(t, r) } return false }, expErr: errors.New(`AKs must be RSA keys`), }, { name: "fail/ak-3072-bits", fields: fields{ tpm: tpmWithAK, }, args: args{ req: &apiv1.CreateKeyRequest{ Name: "tpmkms:name=invalidAK;ak=true", SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 3072, }, }, assertFunc: func(tt assert.TestingT, i1 interface{}, i2 ...interface{}) bool { if assert.IsType(t, &apiv1.CreateKeyResponse{}, i1) { r, _ := i1.(*apiv1.CreateKeyResponse) return assert.Nil(t, r) } return false }, expErr: errors.New(`creating 3072 bit AKs is not supported; AKs must be RSA 2048 bits`), }, { name: "fail/key-exists", fields: fields{ tpm: tpmWithAK, }, args: args{ req: &apiv1.CreateKeyRequest{ Name: "tpmkms:name=key1", SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 1024, }, }, assertFunc: func(tt assert.TestingT, i1 interface{}, i2 ...interface{}) bool { if assert.IsType(t, &apiv1.CreateKeyResponse{}, i1) { r, _ := i1.(*apiv1.CreateKeyResponse) return assert.Nil(t, r) } return false }, expErr: errors.New(`failed creating key "key1": already exists`), }, { name: "fail/attested-key-exists", fields: fields{ tpm: tpmWithAK, }, args: args{ req: &apiv1.CreateKeyRequest{ Name: "tpmkms:name=key2;attest-by=ak1", SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 1024, }, }, assertFunc: func(tt assert.TestingT, i1 interface{}, i2 ...interface{}) bool { if assert.IsType(t, &apiv1.CreateKeyResponse{}, i1) { r, _ := i1.(*apiv1.CreateKeyResponse) return assert.Nil(t, r) } return false }, expErr: errors.New(`failed creating key "key2": already exists`), }, { name: "fail/ak2-exists", fields: fields{ tpm: tpmWithAK, }, args: args{ req: &apiv1.CreateKeyRequest{ Name: "tpmkms:name=ak2;ak=true", SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 2048, }, }, assertFunc: func(tt assert.TestingT, i1 interface{}, i2 ...interface{}) bool { if assert.IsType(t, &apiv1.CreateKeyResponse{}, i1) { r, _ := i1.(*apiv1.CreateKeyResponse) return assert.Nil(t, r) } return false }, expErr: errors.New(`failed creating AK "ak2": already exists`), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &TPMKMS{ tpm: tt.fields.tpm, } got, err := k.CreateKey(tt.args.req) if tt.expErr != nil { assert.EqualError(t, err, tt.expErr.Error()) return } assert.NoError(t, err) assert.True(t, tt.assertFunc(t, got)) }) } } func TestTPMKMS_DeleteKey(t *testing.T) { okTPM := newSimulatedTPM(t, withAK("ak1"), withAK("ak2"), withKey("key1"), withKey("key2"), ) validatePending := func(t *testing.T, k *TPMKMS) { _, err := k.GetPublicKey(&apiv1.GetPublicKeyRequest{Name: "tpmkms:name=ak2;ak=true"}) assert.NoError(t, err) _, err = k.GetPublicKey(&apiv1.GetPublicKeyRequest{Name: "tpmkms:name=key2"}) assert.NoError(t, err) } type fields struct { tpm *tpmp.TPM } type args struct { req *apiv1.DeleteKeyRequest } tests := []struct { name string fields fields args args assertion assert.ErrorAssertionFunc validate func(*testing.T, *TPMKMS) }{ {"ok", fields{okTPM}, args{&apiv1.DeleteKeyRequest{ Name: "tpmkms:name=key1", }}, assert.NoError, func(t *testing.T, k *TPMKMS) { _, err := k.GetPublicKey(&apiv1.GetPublicKeyRequest{Name: "tpmkms:name=ak1;ak=true"}) assert.NoError(t, err) _, err = k.GetPublicKey(&apiv1.GetPublicKeyRequest{Name: "tpmkms:name=ak2;ak=true"}) assert.NoError(t, err) _, err = k.GetPublicKey(&apiv1.GetPublicKeyRequest{Name: "tpmkms:name=key1"}) assert.ErrorIs(t, err, apiv1.NotFoundError{}) _, err = k.GetPublicKey(&apiv1.GetPublicKeyRequest{Name: "tpmkms:name=key2"}) assert.NoError(t, err) }}, {"ok ak", fields{okTPM}, args{&apiv1.DeleteKeyRequest{ Name: "tpmkms:name=ak1;ak=true", }}, assert.NoError, func(t *testing.T, k *TPMKMS) { _, err := k.GetPublicKey(&apiv1.GetPublicKeyRequest{Name: "tpmkms:name=ak1;ak=true"}) assert.ErrorIs(t, err, apiv1.NotFoundError{}) _, err = k.GetPublicKey(&apiv1.GetPublicKeyRequest{Name: "tpmkms:name=ak2;ak=true"}) assert.NoError(t, err) _, err = k.GetPublicKey(&apiv1.GetPublicKeyRequest{Name: "tpmkms:name=key1"}) assert.ErrorIs(t, err, apiv1.NotFoundError{}) _, err = k.GetPublicKey(&apiv1.GetPublicKeyRequest{Name: "tpmkms:name=key2"}) assert.NoError(t, err) }}, {"fail name", fields{okTPM}, args{&apiv1.DeleteKeyRequest{Name: ""}}, assert.Error, validatePending}, {"fail not ak", fields{okTPM}, args{&apiv1.DeleteKeyRequest{Name: "tpmkms:name=ak2"}}, assert.Error, validatePending}, {"fail not key", fields{okTPM}, args{&apiv1.DeleteKeyRequest{Name: "tpmkms:name=key2;ak=true"}}, assert.Error, validatePending}, {"fail missing other", fields{okTPM}, args{&apiv1.DeleteKeyRequest{Name: "tpmkms:name=missing"}}, assert.Error, validatePending}, {"fail uri", fields{okTPM}, args{&apiv1.DeleteKeyRequest{Name: "kms:name=key2"}}, assert.Error, validatePending}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &TPMKMS{ tpm: tt.fields.tpm, } tt.assertion(t, k.DeleteKey(tt.args.req)) tt.validate(t, k) }) } } func TestTPMKMS_CreateSigner(t *testing.T) { tpmWithKey := newSimulatedTPM(t, withKey("key1")) key, err := tpmWithKey.GetKey(context.Background(), "key1") require.NoError(t, err) tss2Key, err := key.ToTSS2(context.Background()) require.NoError(t, err) pemBytes, err := tss2Key.EncodeToMemory() require.NoError(t, err) tmp := t.TempDir() require.NoError(t, os.WriteFile(filepath.Join(tmp, "tss2.pem"), pemBytes, 0600)) signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) type fields struct { tpm *tpmp.TPM } type args struct { req *apiv1.CreateSignerRequest } tests := []struct { name string fields fields args args expErr error }{ { name: "ok/signer", fields: fields{ tpm: tpmWithKey, }, args: args{ req: &apiv1.CreateSignerRequest{ Signer: signer, }, }, }, { name: "ok/signing-key", fields: fields{ tpm: tpmWithKey, }, args: args{ req: &apiv1.CreateSignerRequest{ SigningKey: "tpmkms:name=key1", }, }, }, { name: "ok/signer-path", fields: fields{ tpm: tpmWithKey, }, args: args{ req: &apiv1.CreateSignerRequest{ SigningKey: "tpmkms:path=" + filepath.Join(tmp, "tss2.pem"), }, }, }, { name: "ok/signer-pem", fields: fields{ tpm: tpmWithKey, }, args: args{ req: &apiv1.CreateSignerRequest{ SigningKeyPEM: pemBytes, }, }, }, { name: "fail/uri", fields: fields{ tpm: tpmWithKey, }, args: args{ req: &apiv1.CreateSignerRequest{ SigningKey: "baduri:", }, }, expErr: errors.New("failed parsing \"baduri:\": URI scheme \"baduri\" is not supported"), }, { name: "fail/empty", fields: fields{ tpm: tpmWithKey, }, args: args{ req: &apiv1.CreateSignerRequest{ SigningKey: "", }, }, expErr: errors.New("createSignerRequest 'signingKey' and 'signingKeyPEM' cannot be empty"), }, { name: "fail/empty-opaque", fields: fields{ tpm: tpmWithKey, }, args: args{ req: &apiv1.CreateSignerRequest{ SigningKey: "tpmkms:", }, }, expErr: errors.New("failed parsing \"tpmkms:\": name and path cannot be empty"), }, { name: "fail/missing", fields: fields{ tpm: tpmWithKey, }, args: args{ req: &apiv1.CreateSignerRequest{ SigningKey: "tpmkms:path=testdata/missing.pem", }, }, expErr: errors.New("failed reading key from \"testdata/missing.pem\": open testdata/missing.pem: no such file or directory"), }, { name: "fail/ak", fields: fields{ tpm: tpmWithKey, }, args: args{ req: &apiv1.CreateSignerRequest{ SigningKey: "tpmkms:name=ak1;ak=true", }, }, expErr: errors.New("signing with an AK currently not supported"), }, { name: "fail/unknown-key", fields: fields{ tpm: tpmWithKey, }, args: args{ req: &apiv1.CreateSignerRequest{ SigningKey: "tpmkms:name=unknown-key", }, }, expErr: fmt.Errorf(`failed getting key "unknown-key": not found`), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &TPMKMS{ tpm: tt.fields.tpm, } got, err := k.CreateSigner(tt.args.req) if tt.expErr != nil { assert.EqualError(t, err, tt.expErr.Error()) return } assert.NoError(t, err) assert.NotNil(t, got) }) } } func TestTPMKMS_GetPublicKey(t *testing.T) { tpmWithKey := newSimulatedTPM(t, withKey("key1")) _, err := tpmWithKey.CreateAK(context.Background(), "ak1") require.NoError(t, err) type fields struct { tpm *tpmp.TPM } type args struct { req *apiv1.GetPublicKeyRequest } tests := []struct { name string fields fields args args want crypto.PublicKey expErr error }{ { name: "ok/key", fields: fields{ tpm: tpmWithKey, }, args: args{ req: &apiv1.GetPublicKeyRequest{ Name: "tpmkms:name=key1", }, }, }, { name: "ok/ak", fields: fields{ tpm: tpmWithKey, }, args: args{ req: &apiv1.GetPublicKeyRequest{ Name: "tpmkms:name=ak1;ak=true", }, }, }, { name: "ok/key-path", fields: fields{ tpm: tpmWithKey, }, args: args{ req: &apiv1.GetPublicKeyRequest{ Name: "tpmkms:path=testdata/ec-tss2.pem", }, }, }, { name: "fail/empty", fields: fields{ tpm: tpmWithKey, }, args: args{ req: &apiv1.GetPublicKeyRequest{ Name: "", }, }, expErr: errors.New("getPublicKeyRequest 'name' cannot be empty"), }, { name: "fail/empty-opaque", fields: fields{ tpm: tpmWithKey, }, args: args{ req: &apiv1.GetPublicKeyRequest{ Name: "tpmkms:", }, }, expErr: errors.New("failed parsing \"tpmkms:\": name and path cannot be empty"), }, { name: "fail/uri", fields: fields{ tpm: tpmWithKey, }, args: args{ req: &apiv1.GetPublicKeyRequest{ Name: "baduri:", }, }, expErr: errors.New("failed parsing \"baduri:\": URI scheme \"baduri\" is not supported"), }, { name: "fail/missing", fields: fields{ tpm: tpmWithKey, }, args: args{ req: &apiv1.GetPublicKeyRequest{ Name: "tpmkms:path=testdata/missing.pem", }, }, expErr: errors.New("failed reading key from \"testdata/missing.pem\": open testdata/missing.pem: no such file or directory"), }, { name: "fail/unknown-key", fields: fields{ tpm: tpmWithKey, }, args: args{ req: &apiv1.GetPublicKeyRequest{ Name: "tpmkms:name=unknown-key", }, }, expErr: fmt.Errorf(`failed getting key "unknown-key": not found`), }, { name: "fail/unknown-ak", fields: fields{ tpm: tpmWithKey, }, args: args{ req: &apiv1.GetPublicKeyRequest{ Name: "tpmkms:name=unknown-ak;ak=true", }, }, expErr: fmt.Errorf(`failed getting AK "unknown-ak": not found`), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &TPMKMS{ tpm: tt.fields.tpm, } got, err := k.GetPublicKey(tt.args.req) if tt.expErr != nil { assert.EqualError(t, err, tt.expErr.Error()) return } assert.NoError(t, err) assert.NotNil(t, got) }) } } func TestTPMKMS_LoadCertificate(t *testing.T) { ctx := context.Background() tpm := newSimulatedTPM(t) config := tpmp.CreateKeyConfig{ Algorithm: "RSA", Size: 1024, } key, err := tpm.CreateKey(ctx, "key1", config) require.NoError(t, err) ak, err := tpm.CreateAK(ctx, "ak1") require.NoError(t, err) _, err = tpm.CreateKey(ctx, "keyWithoutCertificate", config) require.NoError(t, err) _, err = tpm.CreateAK(ctx, "akWithoutCertificate") require.NoError(t, err) ca, err := minica.New( minica.WithGetSignerFunc( func() (crypto.Signer, error) { return keyutil.GenerateSigner("RSA", "", 2048) }, ), ) require.NoError(t, err) signer, err := key.Signer(ctx) require.NoError(t, err) publicKey := signer.Public() template := &x509.Certificate{ Subject: pkix.Name{ CommonName: "testkey", }, PublicKey: publicKey, } cert, err := ca.Sign(template) require.NoError(t, err) require.NotNil(t, cert) err = key.SetCertificateChain(ctx, []*x509.Certificate{cert, ca.Intermediate}) require.NoError(t, err) akPub := ak.Public() require.Implements(t, (*crypto.PublicKey)(nil), akPub) template = &x509.Certificate{ Subject: pkix.Name{ CommonName: "testak", }, PublicKey: akPub, } akCert, err := ca.Sign(template) err = ak.SetCertificateChain(ctx, []*x509.Certificate{akCert, ca.Intermediate}) require.NoError(t, err) type fields struct { tpm *tpmp.TPM } type args struct { req *apiv1.LoadCertificateRequest } tests := []struct { name string fields fields args args want *x509.Certificate expErr error }{ { name: "ok/ak", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.LoadCertificateRequest{ Name: "tpmkms:name=ak1;ak=true", }, }, want: akCert, }, { name: "ok/key", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.LoadCertificateRequest{ Name: "tpmkms:name=key1", }, }, want: cert, }, { name: "fail/empty", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.LoadCertificateRequest{ Name: "", }, }, expErr: errors.New("loadCertificateRequest 'name' cannot be empty"), }, { name: "fail/unknown-ak", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.LoadCertificateRequest{ Name: "tpmkms:name=unknown-ak;ak=true", }, }, expErr: fmt.Errorf(`failed getting AK "unknown-ak": not found`), }, { name: "fail/unknown-key", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.LoadCertificateRequest{ Name: "tpmkms:name=unknown-key", }, }, expErr: fmt.Errorf(`failed getting key "unknown-key": not found`), }, { name: "fail/ak-without-certificate", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.LoadCertificateRequest{ Name: "tpmkms:name=akWithoutCertificate;ak=true", }, }, expErr: fmt.Errorf(`failed getting certificate chain for "akWithoutCertificate": no certificate chain stored`), }, { name: "fail/key-without-certificate", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.LoadCertificateRequest{ Name: "tpmkms:name=keyWithoutCertificate", }, }, expErr: fmt.Errorf(`failed getting certificate chain for "keyWithoutCertificate": no certificate chain stored`), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &TPMKMS{ tpm: tt.fields.tpm, } got, err := k.LoadCertificate(tt.args.req) if tt.expErr != nil { assert.EqualError(t, err, tt.expErr.Error()) return } assert.NoError(t, err) if assert.NotNil(t, got) { assert.Equal(t, tt.want, got) } }) } } func TestTPMKMS_LoadCertificateChain(t *testing.T) { ctx := context.Background() tpm := newSimulatedTPM(t) config := tpmp.CreateKeyConfig{ Algorithm: "RSA", Size: 1024, } key, err := tpm.CreateKey(ctx, "key1", config) require.NoError(t, err) ak, err := tpm.CreateAK(ctx, "ak1") require.NoError(t, err) _, err = tpm.CreateKey(ctx, "keyWithoutCertificate", config) require.NoError(t, err) _, err = tpm.CreateAK(ctx, "akWithoutCertificate") require.NoError(t, err) ca, err := minica.New( minica.WithGetSignerFunc( func() (crypto.Signer, error) { return keyutil.GenerateSigner("RSA", "", 2048) }, ), ) require.NoError(t, err) signer, err := key.Signer(ctx) require.NoError(t, err) publicKey := signer.Public() template := &x509.Certificate{ Subject: pkix.Name{ CommonName: "testkey", }, PublicKey: publicKey, } cert, err := ca.Sign(template) require.NoError(t, err) require.NotNil(t, cert) err = key.SetCertificateChain(ctx, []*x509.Certificate{cert, ca.Intermediate}) require.NoError(t, err) akPub := ak.Public() require.Implements(t, (*crypto.PublicKey)(nil), akPub) template = &x509.Certificate{ Subject: pkix.Name{ CommonName: "testak", }, PublicKey: akPub, } akCert, err := ca.Sign(template) err = ak.SetCertificateChain(ctx, []*x509.Certificate{akCert, ca.Intermediate}) require.NoError(t, err) type fields struct { tpm *tpmp.TPM } type args struct { req *apiv1.LoadCertificateChainRequest } tests := []struct { name string fields fields args args want []*x509.Certificate expErr error }{ { name: "ok/ak", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.LoadCertificateChainRequest{ Name: "tpmkms:name=ak1;ak=true", }, }, want: []*x509.Certificate{ akCert, ca.Intermediate, }, }, { name: "ok/key", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.LoadCertificateChainRequest{ Name: "tpmkms:name=key1", }, }, want: []*x509.Certificate{ cert, ca.Intermediate, }, }, { name: "fail/empty", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.LoadCertificateChainRequest{ Name: "", }, }, expErr: errors.New("loadCertificateChainRequest 'name' cannot be empty"), }, { name: "fail/unknown-ak", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.LoadCertificateChainRequest{ Name: "tpmkms:name=unknown-ak;ak=true", }, }, expErr: fmt.Errorf(`failed getting AK "unknown-ak": not found`), }, { name: "fail/unknown-key", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.LoadCertificateChainRequest{ Name: "tpmkms:name=unknown-key", }, }, expErr: fmt.Errorf(`failed getting key "unknown-key": not found`), }, { name: "fail/ak-without-certificate", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.LoadCertificateChainRequest{ Name: "tpmkms:name=akWithoutCertificate;ak=true", }, }, expErr: fmt.Errorf(`failed getting certificate chain for "akWithoutCertificate": no certificate chain stored`), }, { name: "fail/key-without-certificate", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.LoadCertificateChainRequest{ Name: "tpmkms:name=keyWithoutCertificate", }, }, expErr: fmt.Errorf(`failed getting certificate chain for "keyWithoutCertificate": no certificate chain stored`), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &TPMKMS{ tpm: tt.fields.tpm, } got, err := k.LoadCertificateChain(tt.args.req) if tt.expErr != nil { assert.EqualError(t, err, tt.expErr.Error()) return } assert.NoError(t, err) if assert.NotNil(t, got) { assert.Equal(t, tt.want, got) } }) } } func TestTPMKMS_StoreCertificate(t *testing.T) { ctx := context.Background() tpm := newSimulatedTPM(t) config := tpmp.CreateKeyConfig{ Algorithm: "RSA", Size: 1024, } key, err := tpm.CreateKey(ctx, "key1", config) require.NoError(t, err) ak, err := tpm.CreateAK(ctx, "ak1") require.NoError(t, err) ca, err := minica.New( minica.WithGetSignerFunc( func() (crypto.Signer, error) { return keyutil.GenerateSigner("RSA", "", 2048) }, ), ) require.NoError(t, err) signer, err := key.Signer(ctx) require.NoError(t, err) publicKey := signer.Public() template := &x509.Certificate{ Subject: pkix.Name{ CommonName: "testkey", }, PublicKey: publicKey, } cert, err := ca.Sign(template) require.NoError(t, err) require.NotNil(t, cert) anotherPublicKey, _, err := keyutil.GenerateDefaultKeyPair() require.NoError(t, err) template = &x509.Certificate{ Subject: pkix.Name{ CommonName: "testanotherkey", }, PublicKey: anotherPublicKey, } anotherCert, err := ca.Sign(template) require.NoError(t, err) require.NotNil(t, anotherCert) akPub := ak.Public() require.Implements(t, (*crypto.PublicKey)(nil), akPub) template = &x509.Certificate{ Subject: pkix.Name{ CommonName: "testak", }, PublicKey: akPub, } akCert, err := ca.Sign(template) require.NoError(t, err) require.NotNil(t, akCert) type fields struct { tpm *tpmp.TPM } type args struct { req *apiv1.StoreCertificateRequest } tests := []struct { name string fields fields args args expErr error }{ { name: "ok/ak", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.StoreCertificateRequest{ Name: "tpmkms:name=ak1;ak=true", Certificate: akCert, }, }, }, { name: "ok/key", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.StoreCertificateRequest{ Name: "tpmkms:name=key1", Certificate: cert, }, }, }, { name: "fail/empty", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.StoreCertificateRequest{ Name: "", }, }, expErr: errors.New("storeCertificateRequest 'name' cannot be empty"), }, { name: "fail/unknown-ak", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.StoreCertificateRequest{ Name: "tpmkms:name=unknown-ak;ak=true", Certificate: akCert, }, }, expErr: fmt.Errorf(`failed getting AK "unknown-ak": not found`), }, { name: "fail/unknown-key", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.StoreCertificateRequest{ Name: "tpmkms:name=unknown-key", Certificate: cert, }, }, expErr: fmt.Errorf(`failed getting key "unknown-key": not found`), }, { name: "fail/wrong-certificate-for-ak", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.StoreCertificateRequest{ Name: "tpmkms:name=ak1;ak=true", Certificate: anotherCert, }, }, expErr: errors.New(`failed storing certificate for AK "ak1": AK public key does not match the leaf certificate public key`), }, { name: "fail/wrong-certificate-for-key", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.StoreCertificateRequest{ Name: "tpmkms:name=key1", Certificate: anotherCert, }, }, expErr: errors.New(`failed storing certificate for key "key1": public key does not match the leaf certificate public key`), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &TPMKMS{ tpm: tt.fields.tpm, } err := k.StoreCertificate(tt.args.req) if tt.expErr != nil { assert.EqualError(t, err, tt.expErr.Error()) return } assert.NoError(t, err) }) } } func TestTPMKMS_StoreCertificateChain(t *testing.T) { ctx := context.Background() tpm := newSimulatedTPM(t) config := tpmp.CreateKeyConfig{ Algorithm: "RSA", Size: 1024, } key, err := tpm.CreateKey(ctx, "key1", config) require.NoError(t, err) ak, err := tpm.CreateAK(ctx, "ak1") require.NoError(t, err) ca, err := minica.New( minica.WithGetSignerFunc( func() (crypto.Signer, error) { return keyutil.GenerateSigner("RSA", "", 2048) }, ), ) require.NoError(t, err) signer, err := key.Signer(ctx) require.NoError(t, err) publicKey := signer.Public() template := &x509.Certificate{ Subject: pkix.Name{ CommonName: "testkey", }, PublicKey: publicKey, } cert, err := ca.Sign(template) require.NoError(t, err) require.NotNil(t, cert) anotherPublicKey, _, err := keyutil.GenerateDefaultKeyPair() require.NoError(t, err) template = &x509.Certificate{ Subject: pkix.Name{ CommonName: "testanotherkey", }, PublicKey: anotherPublicKey, } anotherCert, err := ca.Sign(template) require.NoError(t, err) require.NotNil(t, anotherCert) akPub := ak.Public() require.Implements(t, (*crypto.PublicKey)(nil), akPub) template = &x509.Certificate{ Subject: pkix.Name{ CommonName: "testak", }, PublicKey: akPub, } akCert, err := ca.Sign(template) require.NoError(t, err) require.NotNil(t, akCert) type fields struct { tpm *tpmp.TPM } type args struct { req *apiv1.StoreCertificateChainRequest } tests := []struct { name string fields fields args args expErr error }{ { name: "ok/ak", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.StoreCertificateChainRequest{ Name: "tpmkms:name=ak1;ak=true", CertificateChain: []*x509.Certificate{akCert, ca.Intermediate}, }, }, }, { name: "ok/key", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.StoreCertificateChainRequest{ Name: "tpmkms:name=key1", CertificateChain: []*x509.Certificate{cert, ca.Intermediate}, }, }, }, { name: "fail/empty", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.StoreCertificateChainRequest{ Name: "", }, }, expErr: errors.New("storeCertificateChainRequest 'name' cannot be empty"), }, { name: "fail/empty-chain", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.StoreCertificateChainRequest{ Name: "tpmkms:name=key1", CertificateChain: []*x509.Certificate{}, }, }, expErr: errors.New("storeCertificateChainRequest 'certificateChain' cannot be empty"), }, { name: "fail/unknown-ak", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.StoreCertificateChainRequest{ Name: "tpmkms:name=unknown-ak;ak=true", CertificateChain: []*x509.Certificate{akCert, ca.Intermediate}, }, }, expErr: fmt.Errorf(`failed getting AK "unknown-ak": not found`), }, { name: "fail/unknown-key", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.StoreCertificateChainRequest{ Name: "tpmkms:name=unknown-key", CertificateChain: []*x509.Certificate{cert, ca.Intermediate}, }, }, expErr: fmt.Errorf(`failed getting key "unknown-key": not found`), }, { name: "fail/wrong-certificate-for-ak", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.StoreCertificateChainRequest{ Name: "tpmkms:name=ak1;ak=true", CertificateChain: []*x509.Certificate{anotherCert, ca.Intermediate}, }, }, expErr: errors.New(`failed storing certificate for AK "ak1": AK public key does not match the leaf certificate public key`), }, { name: "fail/wrong-certificate-for-key", fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.StoreCertificateChainRequest{ Name: "tpmkms:name=key1", CertificateChain: []*x509.Certificate{anotherCert, ca.Intermediate}, }, }, expErr: errors.New(`failed storing certificate for key "key1": public key does not match the leaf certificate public key`), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &TPMKMS{ tpm: tt.fields.tpm, } err := k.StoreCertificateChain(tt.args.req) if tt.expErr != nil { assert.EqualError(t, err, tt.expErr.Error()) return } assert.NoError(t, err) }) } } // TODO(hs): dedupe these structs by creating some shared helper // functions for running a fake attestation ca tpm. type tpmInfo struct { Version attest.TPMVersion `json:"version,omitempty"` Manufacturer string `json:"manufacturer,omitempty"` Model string `json:"model,omitempty"` FirmwareVersion string `json:"firmwareVersion,omitempty"` } type attestationParameters struct { Public []byte `json:"public,omitempty"` UseTCSDActivationFormat bool `json:"useTCSDActivationFormat,omitempty"` CreateData []byte `json:"createData,omitempty"` CreateAttestation []byte `json:"createAttestation,omitempty"` CreateSignature []byte `json:"createSignature,omitempty"` } type attestationRequest struct { TPMInfo tpmInfo `json:"tpmInfo"` EK []byte `json:"ek,omitempty"` EKCerts [][]byte `json:"ekCerts,omitempty"` AKCert []byte `json:"akCert,omitempty"` AttestParams attestationParameters `json:"params,omitempty"` } type attestationResponse struct { Credential []byte `json:"credential"` Secret []byte `json:"secret"` // encrypted secret } type secretRequest struct { Secret []byte `json:"secret"` // decrypted secret } type secretResponse struct { CertificateChain [][]byte `json:"chain"` } type customAttestationClient struct { chain []*x509.Certificate } func (c *customAttestationClient) Attest(context.Context) ([]*x509.Certificate, error) { return c.chain, nil } func TestTPMKMS_CreateAttestation(t *testing.T) { ctx := context.Background() tpm := newSimulatedTPM(t) eks, err := tpm.GetEKs(ctx) require.NoError(t, err) ek := getPreferredEK(eks) ekKeyID, err := generateKeyID(ek.Public()) require.NoError(t, err) ekKeyURL := ekURL(ekKeyID) config := tpmp.AttestKeyConfig{ Algorithm: "RSA", Size: 1024, QualifyingData: []byte{1, 2, 3, 4}, } ca, err := minica.New( minica.WithGetSignerFunc( func() (crypto.Signer, error) { return keyutil.GenerateSigner("RSA", "", 2048) }, ), ) type fields struct { tpm *tpmp.TPM attestationCABaseURL string attestationCARootFile string attestationCAInsecure bool permanentIdentifier string } type args struct { req *apiv1.CreateAttestationRequest } type test struct { server *httptest.Server fields fields args args want *apiv1.CreateAttestationResponse expErr error } tests := map[string]func(t *testing.T) test{ "fail/empty-name": func(t *testing.T) test { return test{ args: args{ req: &apiv1.CreateAttestationRequest{ Name: "", }, }, expErr: errors.New("createAttestationRequest 'name' cannot be empty"), } }, "fail/ak-attestby-mutually-exclusive": func(t *testing.T) test { return test{ args: args{ req: &apiv1.CreateAttestationRequest{ Name: "tpmkms:name=keyx;ak=true;attest-by=ak1", }, }, expErr: errors.New(`failed parsing "tpmkms:name=keyx;ak=true;attest-by=ak1": "ak" and "attest-by" are mutually exclusive`), } }, "fail/non-matching-permanent-identifier": func(t *testing.T) test { _, err = tpm.CreateAK(ctx, "newAKWithoutCert") require.NoError(t, err) _, err = tpm.AttestKey(ctx, "newAKWithoutCert", "newkey", config) require.NoError(t, err) return test{ fields: fields{ tpm: tpm, permanentIdentifier: "wrong-provided-permanent-identifier", }, args: args{ req: &apiv1.CreateAttestationRequest{ Name: "tpmkms:name=newkey", // newkey was attested by the newAKWithoutCert at creation time }, }, expErr: fmt.Errorf(`the provided permanent identifier "wrong-provided-permanent-identifier" does not match the EK URL %q`, ekKeyURL.String()), } }, "fail/unknown-key": func(t *testing.T) test { return test{ fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.CreateAttestationRequest{ Name: "tpmkms:name=keyx", }, }, expErr: errors.New(`failed getting key "keyx": not found`), } }, "fail/non-attested-key": func(t *testing.T) test { createConfig := tpmp.CreateKeyConfig{Algorithm: "RSA", Size: 1024} _, err = tpm.CreateKey(ctx, "nonAttestedKey", createConfig) return test{ fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.CreateAttestationRequest{ Name: "tpmkms:name=nonAttestedKey", }, }, expErr: errors.New(`key "nonAttestedKey" was not attested`), } }, "fail/unknown-ak": func(t *testing.T) test { return test{ fields: fields{ tpm: tpm, }, args: args{ req: &apiv1.CreateAttestationRequest{ Name: "tpmkms:name=unknownAK;ak=true", }, }, expErr: errors.New(`failed getting AK "unknownAK": not found`), } }, "fail/create-attestor-client": func(t *testing.T) test { _, err = tpm.CreateAK(ctx, "ak2WithoutCert") require.NoError(t, err) _, err = tpm.AttestKey(ctx, "ak2WithoutCert", "key3", config) require.NoError(t, err) return test{ fields: fields{ tpm: tpm, permanentIdentifier: ekKeyURL.String(), }, args: args{ req: &apiv1.CreateAttestationRequest{ Name: "tpmkms:name=key3", // key3 was attested by the ak2WithoutCert at creation time }, }, expErr: fmt.Errorf(`failed creating attestor client: failed creating attestation client: attestation CA base URL must not be empty`), } }, "fail/attest": func(t *testing.T) test { _, err = tpm.CreateAK(ctx, "ak3WithoutCert") require.NoError(t, err) _, err = tpm.AttestKey(ctx, "ak3WithoutCert", "key4", config) require.NoError(t, err) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/attest": w.WriteHeader(http.StatusBadRequest) default: t.Errorf("unexpected %q request to %q", r.Method, r.URL) } }) s := httptest.NewServer(handler) return test{ server: s, fields: fields{ tpm: tpm, attestationCABaseURL: s.URL, permanentIdentifier: ekKeyURL.String(), }, args: args{ req: &apiv1.CreateAttestationRequest{ Name: "tpmkms:name=key4", // key4 was attested by the ak3WithoutCert at creation time }, }, expErr: fmt.Errorf(`failed performing AK attestation: failed attesting AK: POST %q failed with HTTP status "400 Bad Request"`, fmt.Sprintf("%s/attest", s.URL)), } }, "fail/set-ak-certificate-chain": func(t *testing.T) test { ak4WithoutCert, err := tpm.CreateAK(ctx, "ak4WithoutCert") require.NoError(t, err) _, err = tpm.AttestKey(ctx, "ak4WithoutCert", "key5", config) require.NoError(t, err) params, err := ak4WithoutCert.AttestationParameters(context.Background()) require.NoError(t, err) require.NotNil(t, params) activation := attest.ActivationParameters{ TPMVersion: attest.TPMVersion20, EK: ek.Public(), AK: params, } expectedSecret, encryptedCredentials, err := activation.Generate() require.NoError(t, err) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/attest": var ar attestationRequest err := json.NewDecoder(r.Body).Decode(&ar) require.NoError(t, err) parsedEK, err := x509.ParsePKIXPublicKey(ar.EK) require.NoError(t, err) assert.Equal(t, ek.Public(), parsedEK) attestParams := attest.AttestationParameters{ Public: ar.AttestParams.Public, UseTCSDActivationFormat: ar.AttestParams.UseTCSDActivationFormat, CreateData: ar.AttestParams.CreateData, CreateAttestation: ar.AttestParams.CreateAttestation, CreateSignature: ar.AttestParams.CreateSignature, } activationParams := attest.ActivationParameters{ TPMVersion: ar.TPMInfo.Version, EK: parsedEK, AK: attestParams, } assert.Equal(t, activation, activationParams) w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(&attestationResponse{ Credential: encryptedCredentials.Credential, Secret: encryptedCredentials.Secret, }) case "/secret": var sr secretRequest err := json.NewDecoder(r.Body).Decode(&sr) require.NoError(t, err) assert.Equal(t, expectedSecret, sr.Secret) w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(&secretResponse{ CertificateChain: [][]byte{ ca.Intermediate.Raw, // No leaf returned }, }) default: t.Errorf("unexpected %q request to %q", r.Method, r.URL) } }) s := httptest.NewServer(handler) return test{ server: s, fields: fields{ tpm: tpm, attestationCABaseURL: s.URL, permanentIdentifier: ekKeyURL.String(), }, args: args{ req: &apiv1.CreateAttestationRequest{ Name: "tpmkms:name=key5", // key5 was attested by ak3WithoutCert at creation time }, }, want: nil, expErr: fmt.Errorf(`failed storing AK certificate chain: AK public key does not match the leaf certificate public key`), } }, "fail/ak-certificate-chain-has-invalid-identity": func(t *testing.T) test { ak5WithoutCert, err := tpm.CreateAK(ctx, "ak5WithoutCert") require.NoError(t, err) _, err = tpm.AttestKey(ctx, "ak5WithoutCert", "key6", config) require.NoError(t, err) ak5Pub := ak5WithoutCert.Public() require.Implements(t, (*crypto.PublicKey)(nil), ak5Pub) template := &x509.Certificate{ // NOTE: missing EK URI SAN Subject: pkix.Name{ CommonName: "testinvalidak", }, PublicKey: ak5Pub, } invalidAKIdentityCert, err := ca.Sign(template) require.NoError(t, err) require.NotNil(t, invalidAKIdentityCert) params, err := ak5WithoutCert.AttestationParameters(context.Background()) require.NoError(t, err) require.NotNil(t, params) activation := attest.ActivationParameters{ TPMVersion: attest.TPMVersion20, EK: ek.Public(), AK: params, } expectedSecret, encryptedCredentials, err := activation.Generate() require.NoError(t, err) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/attest": var ar attestationRequest err := json.NewDecoder(r.Body).Decode(&ar) require.NoError(t, err) parsedEK, err := x509.ParsePKIXPublicKey(ar.EK) require.NoError(t, err) assert.Equal(t, ek.Public(), parsedEK) attestParams := attest.AttestationParameters{ Public: ar.AttestParams.Public, UseTCSDActivationFormat: ar.AttestParams.UseTCSDActivationFormat, CreateData: ar.AttestParams.CreateData, CreateAttestation: ar.AttestParams.CreateAttestation, CreateSignature: ar.AttestParams.CreateSignature, } activationParams := attest.ActivationParameters{ TPMVersion: ar.TPMInfo.Version, EK: parsedEK, AK: attestParams, } assert.Equal(t, activation, activationParams) w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(&attestationResponse{ Credential: encryptedCredentials.Credential, Secret: encryptedCredentials.Secret, }) case "/secret": var sr secretRequest err := json.NewDecoder(r.Body).Decode(&sr) require.NoError(t, err) assert.Equal(t, expectedSecret, sr.Secret) w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(&secretResponse{ CertificateChain: [][]byte{ invalidAKIdentityCert.Raw, // AK certificate without EK URI SAN ca.Intermediate.Raw, }, }) default: t.Errorf("unexpected %q request to %q", r.Method, r.URL) } }) s := httptest.NewServer(handler) return test{ server: s, fields: fields{ tpm: tpm, attestationCABaseURL: s.URL, permanentIdentifier: ekKeyURL.String(), }, args: args{ req: &apiv1.CreateAttestationRequest{ Name: "tpmkms:name=key6", // key6 was attested by ak5WithoutCert at creation time }, }, want: nil, expErr: fmt.Errorf(`AK certificate (chain) not valid for EK %q: AK certificate does not contain valid identity`, ekKeyURL.String()), } }, "ok": func(t *testing.T) test { akWithExistingCert, err := tpm.CreateAK(ctx, "akWithExistingCert") require.NoError(t, err) key, err := tpm.AttestKey(ctx, "akWithExistingCert", "key1", config) require.NoError(t, err) keyParams, err := key.CertificationParameters(ctx) require.NoError(t, err) signer, err := key.Signer(ctx) require.NoError(t, err) akPub := akWithExistingCert.Public() require.Implements(t, (*crypto.PublicKey)(nil), akPub) template := &x509.Certificate{ Subject: pkix.Name{ CommonName: "testak", }, URIs: []*url.URL{ekKeyURL}, PublicKey: akPub, } validAKCert, err := ca.Sign(template) require.NoError(t, err) require.NotNil(t, validAKCert) err = akWithExistingCert.SetCertificateChain(ctx, []*x509.Certificate{validAKCert, ca.Intermediate}) require.NoError(t, err) return test{ fields: fields{ tpm: tpm, permanentIdentifier: ekKeyURL.String(), }, args: args{ req: &apiv1.CreateAttestationRequest{ Name: "tpmkms:name=key1", // key1 was attested by the akWithExistingCert at creation time }, }, want: &apiv1.CreateAttestationResponse{ Certificate: validAKCert, CertificateChain: []*x509.Certificate{validAKCert, ca.Intermediate}, PublicKey: signer.Public(), CertificationParameters: &apiv1.CertificationParameters{ Public: keyParams.Public, CreateData: keyParams.CreateData, CreateAttestation: keyParams.CreateAttestation, CreateSignature: keyParams.CreateSignature, }, PermanentIdentifier: ekKeyURL.String(), }, expErr: nil, } }, "ok/new-chain": func(t *testing.T) test { akWithoutCert, err := tpm.CreateAK(ctx, "akWithoutCert") require.NoError(t, err) key, err := tpm.AttestKey(ctx, "akWithoutCert", "key2", config) require.NoError(t, err) keyParams, err := key.CertificationParameters(ctx) require.NoError(t, err) signer, err := key.Signer(ctx) require.NoError(t, err) akPubNew := akWithoutCert.Public() require.Implements(t, (*crypto.PublicKey)(nil), akPubNew) template := &x509.Certificate{ Subject: pkix.Name{ CommonName: "testnewak", }, URIs: []*url.URL{ekKeyURL}, PublicKey: akPubNew, } newAKCert, err := ca.Sign(template) require.NoError(t, err) require.NotNil(t, newAKCert) params, err := akWithoutCert.AttestationParameters(context.Background()) require.NoError(t, err) require.NotNil(t, params) activation := attest.ActivationParameters{ TPMVersion: attest.TPMVersion20, EK: ek.Public(), AK: params, } expectedSecret, encryptedCredentials, err := activation.Generate() require.NoError(t, err) akChain := [][]byte{ newAKCert.Raw, ca.Intermediate.Raw, } handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/attest": var ar attestationRequest err := json.NewDecoder(r.Body).Decode(&ar) require.NoError(t, err) parsedEK, err := x509.ParsePKIXPublicKey(ar.EK) require.NoError(t, err) assert.Equal(t, ek.Public(), parsedEK) attestParams := attest.AttestationParameters{ Public: ar.AttestParams.Public, UseTCSDActivationFormat: ar.AttestParams.UseTCSDActivationFormat, CreateData: ar.AttestParams.CreateData, CreateAttestation: ar.AttestParams.CreateAttestation, CreateSignature: ar.AttestParams.CreateSignature, } activationParams := attest.ActivationParameters{ TPMVersion: ar.TPMInfo.Version, EK: parsedEK, AK: attestParams, } assert.Equal(t, activation, activationParams) w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(&attestationResponse{ Credential: encryptedCredentials.Credential, Secret: encryptedCredentials.Secret, }) case "/secret": var sr secretRequest err := json.NewDecoder(r.Body).Decode(&sr) require.NoError(t, err) assert.Equal(t, expectedSecret, sr.Secret) w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(&secretResponse{ CertificateChain: akChain, }) default: t.Errorf("unexpected %q request to %q", r.Method, r.URL) } }) s := httptest.NewServer(handler) return test{ server: s, fields: fields{ tpm: tpm, attestationCABaseURL: s.URL, permanentIdentifier: ekKeyURL.String(), }, args: args{ req: &apiv1.CreateAttestationRequest{ Name: "tpmkms:name=key2", // key2 was attested by akWithoutCert at creation time }, }, want: &apiv1.CreateAttestationResponse{ Certificate: newAKCert, CertificateChain: []*x509.Certificate{newAKCert, ca.Intermediate}, PublicKey: signer.Public(), CertificationParameters: &apiv1.CertificationParameters{ Public: keyParams.Public, CreateData: keyParams.CreateData, CreateAttestation: keyParams.CreateAttestation, CreateSignature: keyParams.CreateSignature, }, PermanentIdentifier: ekKeyURL.String(), }, expErr: nil, } }, "ok/new-chain-with-custom-attestor-client": func(t *testing.T) test { ak6WithoutCert, err := tpm.CreateAK(ctx, "ak6WithoutCert") require.NoError(t, err) key, err := tpm.AttestKey(ctx, "ak6WithoutCert", "key7", config) require.NoError(t, err) keyParams, err := key.CertificationParameters(ctx) require.NoError(t, err) signer, err := key.Signer(ctx) require.NoError(t, err) ak6Pub := ak6WithoutCert.Public() require.Implements(t, (*crypto.PublicKey)(nil), ak6Pub) template := &x509.Certificate{ Subject: pkix.Name{ CommonName: "testak6", }, URIs: []*url.URL{ekKeyURL}, PublicKey: ak6Pub, } ak6Cert, err := ca.Sign(template) require.NoError(t, err) require.NotNil(t, ak6Cert) return test{ fields: fields{ tpm: tpm, permanentIdentifier: ekKeyURL.String(), }, args: args{ req: &apiv1.CreateAttestationRequest{ Name: "tpmkms:name=key7", // key7 was attested by ak6WithoutCert at creation time AttestationClient: &customAttestationClient{ chain: []*x509.Certificate{ak6Cert, ca.Intermediate}, }, }, }, want: &apiv1.CreateAttestationResponse{ Certificate: ak6Cert, CertificateChain: []*x509.Certificate{ak6Cert, ca.Intermediate}, PublicKey: signer.Public(), CertificationParameters: &apiv1.CertificationParameters{ Public: keyParams.Public, CreateData: keyParams.CreateData, CreateAttestation: keyParams.CreateAttestation, CreateSignature: keyParams.CreateSignature, }, PermanentIdentifier: ekKeyURL.String(), }, expErr: nil, } }, "ok/ak": func(t *testing.T) test { akWithCert, err := tpm.CreateAK(ctx, "akWithCert") require.NoError(t, err) akPub := akWithCert.Public() require.Implements(t, (*crypto.PublicKey)(nil), akPub) template := &x509.Certificate{ Subject: pkix.Name{ CommonName: "testak", }, URIs: []*url.URL{ekKeyURL}, PublicKey: akPub, } validAKCert, err := ca.Sign(template) require.NoError(t, err) require.NotNil(t, validAKCert) err = akWithCert.SetCertificateChain(ctx, []*x509.Certificate{validAKCert, ca.Intermediate}) require.NoError(t, err) return test{ fields: fields{ tpm: tpm, permanentIdentifier: ekKeyURL.String(), }, args: args{ req: &apiv1.CreateAttestationRequest{ Name: "tpmkms:name=akWithCert;ak=true", // key1 was attested by the akWithExistingCert at creation time }, }, want: &apiv1.CreateAttestationResponse{ Certificate: validAKCert, CertificateChain: []*x509.Certificate{validAKCert, ca.Intermediate}, PublicKey: akWithCert.Public(), PermanentIdentifier: ekKeyURL.String(), }, expErr: nil, } }, } for name, tt := range tests { tc := tt(t) t.Run(name, func(t *testing.T) { k := &TPMKMS{ tpm: tc.fields.tpm, attestationCABaseURL: tc.fields.attestationCABaseURL, attestationCARootFile: tc.fields.attestationCARootFile, attestationCAInsecure: tc.fields.attestationCAInsecure, permanentIdentifier: tc.fields.permanentIdentifier, } if tc.server != nil { defer tc.server.Close() } got, err := k.CreateAttestation(tc.args.req) if tc.expErr != nil { assert.EqualError(t, err, tc.expErr.Error()) return } assert.NoError(t, err) assert.Equal(t, tc.want, got) }) } } func Test_hasValidIdentity(t *testing.T) { k := &TPMKMS{ identityEarlyRenewalEnabled: true, identityRenewalPeriodPercentage: 60, } ctx := context.Background() tpm := newSimulatedTPM(t) eks, err := tpm.GetEKs(ctx) require.NoError(t, err) ek := getPreferredEK(eks) ekKeyID, err := generateKeyID(ek.Public()) require.NoError(t, err) ekKeyURL := ekURL(ekKeyID) ca, err := minica.New( minica.WithGetSignerFunc( func() (crypto.Signer, error) { return keyutil.GenerateSigner("RSA", "", 2048) }, ), ) type args struct { k *TPMKMS ak *tpmp.AK ekURL *url.URL } type test struct { args args expErr error } tests := map[string]func(t *testing.T) test{ "fail/no chain": func(t *testing.T) test { ak, err := tpm.CreateAK(ctx, "noChain") require.NoError(t, err) return test{ args: args{k, ak, ekKeyURL}, expErr: errors.New("AK certificate not available"), } }, "fail/not yet valid": func(t *testing.T) test { ak, err := tpm.CreateAK(ctx, "notYetValid") require.NoError(t, err) template := &x509.Certificate{ Subject: pkix.Name{ CommonName: "testak", }, URIs: []*url.URL{ekKeyURL}, PublicKey: ak.Public(), NotBefore: time.Now().Add(1 * time.Hour), } notYetValidAKCert, err := ca.Sign(template) require.NoError(t, err) require.NotNil(t, notYetValidAKCert) err = ak.SetCertificateChain(ctx, []*x509.Certificate{notYetValidAKCert, ca.Intermediate}) require.NoError(t, err) return test{ args: args{k, ak, ekKeyURL}, expErr: errors.New("AK certificate not yet valid"), } }, "fail/expired": func(t *testing.T) test { ak, err := tpm.CreateAK(ctx, "expiredAKCert") require.NoError(t, err) template := &x509.Certificate{ Subject: pkix.Name{ CommonName: "testak", }, URIs: []*url.URL{ekKeyURL}, PublicKey: ak.Public(), NotAfter: time.Now().Add(-1 * time.Hour), } expiredAKCert, err := ca.Sign(template) require.NoError(t, err) require.NotNil(t, expiredAKCert) err = ak.SetCertificateChain(ctx, []*x509.Certificate{expiredAKCert, ca.Intermediate}) require.NoError(t, err) return test{ args: args{k, ak, ekKeyURL}, expErr: errors.New("AK certificate has expired"), } }, "fail/expiring": func(t *testing.T) test { ak, err := tpm.CreateAK(ctx, "expiringAKCert") require.NoError(t, err) template := &x509.Certificate{ Subject: pkix.Name{ CommonName: "testak", }, URIs: []*url.URL{ekKeyURL}, PublicKey: ak.Public(), NotBefore: time.Now().Add(-5 * time.Hour), NotAfter: time.Now().Add(1 * time.Hour), // less than half of the total time left } expiringAKCert, err := ca.Sign(template) require.NoError(t, err) require.NotNil(t, expiringAKCert) err = ak.SetCertificateChain(ctx, []*x509.Certificate{expiringAKCert, ca.Intermediate}) require.NoError(t, err) return test{ args: args{k, ak, ekKeyURL}, expErr: errors.New("AK certificate will expire soon"), } }, "fail/no valid identity": func(t *testing.T) test { ak, err := tpm.CreateAK(ctx, "novalidIdentityAKCert") require.NoError(t, err) template := &x509.Certificate{ Subject: pkix.Name{ CommonName: "testak", }, PublicKey: ak.Public(), NotBefore: time.Now().Add(-1 * time.Minute), NotAfter: time.Now().Add(24 * time.Hour), } invalidIdentityAKCert, err := ca.Sign(template) require.NoError(t, err) require.NotNil(t, invalidIdentityAKCert) err = ak.SetCertificateChain(ctx, []*x509.Certificate{invalidIdentityAKCert, ca.Intermediate}) require.NoError(t, err) return test{ args: args{k, ak, ekKeyURL}, expErr: errors.New("AK certificate does not contain valid identity"), } }, "ok": func(t *testing.T) test { ak, err := tpm.CreateAK(ctx, "validAKCert") require.NoError(t, err) template := &x509.Certificate{ Subject: pkix.Name{ CommonName: "testak", }, URIs: []*url.URL{ekKeyURL}, PublicKey: ak.Public(), NotBefore: time.Now().Add(-1 * time.Minute), NotAfter: time.Now().Add(24 * time.Hour), } validAKCert, err := ca.Sign(template) require.NoError(t, err) require.NotNil(t, validAKCert) err = ak.SetCertificateChain(ctx, []*x509.Certificate{validAKCert, ca.Intermediate}) require.NoError(t, err) return test{ args: args{k, ak, ekKeyURL}, expErr: nil, } }, } for name, tt := range tests { tc := tt(t) t.Run(name, func(t *testing.T) { err := tc.args.k.hasValidIdentity(tc.args.ak, tc.args.ekURL) if tc.expErr != nil { assert.EqualError(t, err, tc.expErr.Error()) return } assert.NoError(t, err) }) } } crypto-0.57.0/kms/tpmkms/tpmkms_test.go000066400000000000000000000122721474156331600201470ustar00rootroot00000000000000package tpmkms import ( "context" "encoding/asn1" "errors" "fmt" "os" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/tpm" "go.step.sm/crypto/tpm/tss2" ) func TestNew(t *testing.T) { type args struct { opts apiv1.Options } tests := []struct { name string args args want *TPMKMS wantErr bool }{ {"ok/defaults", args{apiv1.Options{Type: "tpmkms"}}, &TPMKMS{identityEarlyRenewalEnabled: true, identityRenewalPeriodPercentage: 60}, false}, {"ok/uri", args{apiv1.Options{Type: "tpmkms", URI: "tpmkms:device=/dev/tpm0;storage-directory=/tmp/tpmstorage;renewal-percentage=70"}}, &TPMKMS{identityEarlyRenewalEnabled: true, identityRenewalPeriodPercentage: 70}, false}, {"fail/uri-scheme", args{apiv1.Options{Type: "tpmkms", URI: "tpmkmz://device=/dev/tpm0"}}, &TPMKMS{}, true}, {"fail/renewal-percentage-too-low", args{apiv1.Options{Type: "tpmkms", URI: "tpmkms:renewal-percentage=0"}}, &TPMKMS{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := New(context.Background(), tt.args.opts) if tt.wantErr { assert.Error(t, err) return } if assert.NotNil(t, got) { assert.NotNil(t, got.tpm) assert.Equal(t, tt.want.identityEarlyRenewalEnabled, got.identityEarlyRenewalEnabled) assert.Equal(t, tt.want.identityRenewalPeriodPercentage, got.identityRenewalPeriodPercentage) } }) } } func Test_parseTSS2(t *testing.T) { pemBytes, err := os.ReadFile("testdata/ec-tss2.pem") require.NoError(t, err) type args struct { pemBytes []byte } tests := []struct { name string args args want *tss2.TPMKey assertion assert.ErrorAssertionFunc }{ {"ok", args{pemBytes}, &tss2.TPMKey{ Type: asn1.ObjectIdentifier{2, 23, 133, 10, 1, 3}, EmptyAuth: true, Parent: 0x40000001, PublicKey: []byte{ 0x00, 0x58, 0x00, 0x23, 0x00, 0x0b, 0x00, 0x04, 0x00, 0x72, 0x00, 0x00, 0x00, 0x10, 0x00, 0x18, 0x00, 0x0b, 0x00, 0x03, 0x00, 0x10, 0x00, 0x20, 0x79, 0xb2, 0xe7, 0x1a, 0x50, 0xc0, 0x37, 0x96, 0x87, 0x76, 0x47, 0xdf, 0x45, 0x3a, 0x81, 0x76, 0xc4, 0x0d, 0x9c, 0xee, 0xb4, 0x69, 0x8f, 0x97, 0xbe, 0x0e, 0x6e, 0xf3, 0x4b, 0x08, 0x6a, 0xe3, 0x00, 0x20, 0xcd, 0x04, 0xdf, 0x39, 0xdd, 0xa7, 0x9d, 0xfd, 0xd9, 0x33, 0xc1, 0xae, 0x82, 0xb3, 0x3c, 0xb1, 0xc4, 0xc0, 0xb8, 0x94, 0x55, 0xe0, 0x66, 0x3a, 0x10, 0x46, 0xde, 0x4b, 0x2c, 0xfe, 0xe2, 0x02, }, PrivateKey: []byte{ 0x00, 0x7e, 0x00, 0x20, 0x83, 0x0e, 0xdf, 0x6d, 0x93, 0x15, 0x89, 0xdd, 0x31, 0xa9, 0xe1, 0xa6, 0xf4, 0xe0, 0x2c, 0xc8, 0x85, 0x77, 0xa5, 0x6c, 0xdc, 0x75, 0x6c, 0x3a, 0xb1, 0xdb, 0xd7, 0x9a, 0x8b, 0x65, 0x30, 0xd0, 0x00, 0x10, 0x4c, 0x70, 0x5f, 0x8b, 0xdc, 0x2d, 0xb7, 0xdb, 0x61, 0x54, 0xd3, 0xad, 0x03, 0x1d, 0x7b, 0xf9, 0xe9, 0x4a, 0xbc, 0xee, 0xe3, 0x2e, 0xfe, 0xa5, 0x84, 0xdc, 0x75, 0x3e, 0xaf, 0x9f, 0x39, 0xeb, 0xee, 0xf4, 0x3f, 0xde, 0x08, 0xd3, 0x3d, 0xcf, 0x97, 0x1c, 0x25, 0x9f, 0x68, 0x86, 0x86, 0x16, 0xca, 0x67, 0xc8, 0x10, 0x40, 0xd2, 0xa1, 0x88, 0xe7, 0x44, 0xe0, 0xc6, 0x03, 0x3e, 0x73, 0x75, 0xba, 0xab, 0xdc, 0xc0, 0x9c, 0x1d, 0xec, 0xd6, 0x75, 0x7e, 0xa9, 0xf0, 0x04, 0xa9, 0x2c, 0xe8, 0x4d, 0x6f, 0x81, 0x29, 0xde, 0xb5, 0x87, 0x8d, 0xa2, 0x84, }, }, assert.NoError}, {"fail empty", args{nil}, nil, assert.Error}, {"fail no pem", args{[]byte("not a pem")}, nil, assert.Error}, {"fail type", args{[]byte("-----BEGIN FOO-----\nMCgGBmeBBQoBA6ADAQH/AgRAAAABBAgABnB1YmxpYwQJAAdwcml2YXRl\n-----END FOO-----")}, nil, assert.Error}, {"fail parse", args{[]byte("-----BEGIN TSS2 PRIVATE KEY-----\nbm90LWEta2V5Cg==\n-----END TSS2 PRIVATE KEY-----")}, nil, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := parseTSS2(tt.args.pemBytes) tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } } func Test_notFoundError(t *testing.T) { type args struct { err error } tests := []struct { name string args args assertion assert.ErrorAssertionFunc }{ {"nil", args{nil}, assert.NoError}, {"tpm not found", args{tpm.ErrNotFound}, func(tt assert.TestingT, err error, i ...interface{}) bool { return assert.ErrorIs(t, err, apiv1.NotFoundError{}, i...) }}, {"tpm not found wrapped", args{fmt.Errorf("some error: %w", tpm.ErrNotFound)}, func(tt assert.TestingT, err error, i ...interface{}) bool { return assert.ErrorIs(t, err, apiv1.NotFoundError{}, i...) }}, {"other", args{tpm.ErrExists}, func(tt assert.TestingT, err error, i ...interface{}) bool { return assert.False(t, errors.Is(err, apiv1.NotFoundError{}), i...) }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.assertion(t, notFoundError(tt.args.err)) }) } } func Test_SetPreferredSignatureAlgorithms(t *testing.T) { old := preferredSignatureAlgorithms want := []apiv1.SignatureAlgorithm{ apiv1.ECDSAWithSHA256, } SetPreferredSignatureAlgorithms(want) assert.Equal(t, preferredSignatureAlgorithms, want) SetPreferredSignatureAlgorithms(old) } func Test_PreferredSignatureAlgorithms(t *testing.T) { assert.Equal(t, PreferredSignatureAlgorithms(), preferredSignatureAlgorithms) } crypto-0.57.0/kms/tpmkms/uri.go000066400000000000000000000044131474156331600163720ustar00rootroot00000000000000//go:build !notpmkms // +build !notpmkms package tpmkms import ( "errors" "fmt" "go.step.sm/crypto/kms/uri" ) type objectProperties struct { name string ak bool tss2 bool attestBy string qualifyingData []byte path string storeLocation string store string intermediateStoreLocation string intermediateStore string skipFindCertificateKey bool keyID string sha1 string serial string issuer string } func parseNameURI(nameURI string) (o objectProperties, err error) { if nameURI == "" { return o, errors.New("empty URI not supported") } var u *uri.URI var parseErr error if u, parseErr = uri.ParseWithScheme(Scheme, nameURI); parseErr == nil { o.path = u.Get("path") if name := u.Get("name"); name == "" && o.path == "" { if len(u.Values) == 1 { o.name = u.Opaque } else { for k, v := range u.Values { if len(v) == 1 && v[0] == "" { o.name = k break } } } } else { o.name = name } o.ak = u.GetBool("ak") o.tss2 = u.GetBool("tss2") o.attestBy = u.Get("attest-by") if qualifyingData := u.GetEncoded("qualifying-data"); qualifyingData != nil { o.qualifyingData = qualifyingData } // store location and store options are used on Windows to override // which store(s) are used for storing and loading (intermediate) certificates o.storeLocation = u.Get("store-location") o.store = u.Get("store") o.intermediateStoreLocation = u.Get("intermediate-store-location") o.intermediateStore = u.Get("intermediate-store") o.skipFindCertificateKey = u.GetBool("skip-find-certificate-key") o.keyID = u.Get("key-id") o.sha1 = u.Get("sha1") o.serial = u.Get("serial") o.issuer = u.Get("issuer") // validation if o.ak && o.attestBy != "" { return o, errors.New(`"ak" and "attest-by" are mutually exclusive`) } return } if u, parseErr := uri.Parse(nameURI); parseErr == nil { if u.Scheme != Scheme { return o, fmt.Errorf("URI scheme %q is not supported", u.Scheme) } } o.name = nameURI // assumes there's no other properties encoded; just a name return } crypto-0.57.0/kms/tpmkms/uri_test.go000066400000000000000000000024501474156331600174300ustar00rootroot00000000000000package tpmkms import ( "testing" "github.com/stretchr/testify/assert" ) func Test_parseNameURI(t *testing.T) { type args struct { nameURI string } tests := []struct { name string args args wantO objectProperties wantErr bool }{ {"ok/key-without-scheme", args{"key1"}, objectProperties{name: "key1"}, false}, {"ok/key", args{"tpmkms:name=key1"}, objectProperties{name: "key1"}, false}, {"ok/key-without-name-key", args{"tpmkms:key1"}, objectProperties{name: "key1"}, false}, {"ok/key-without-name-key-with-other-properties", args{"tpmkms:key1;attest-by=ak1"}, objectProperties{name: "key1", attestBy: "ak1"}, false}, {"ok/attested-key", args{"tpmkms:name=key2;attest-by=ak1;qualifying-data=61626364"}, objectProperties{name: "key2", attestBy: "ak1", qualifyingData: []byte{'a', 'b', 'c', 'd'}}, false}, {"ok/ak", args{"tpmkms:name=ak1;ak=true"}, objectProperties{name: "ak1", ak: true}, false}, {"fail/empty", args{""}, objectProperties{}, true}, {"fail/wrong-scheme", args{nameURI: "tpmkmz:name=bla"}, objectProperties{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotO, err := parseNameURI(tt.args.nameURI) if tt.wantErr { assert.Error(t, err) return } assert.NoError(t, err) assert.Equal(t, tt.wantO, gotO) }) } } crypto-0.57.0/kms/uri/000077500000000000000000000000001474156331600145265ustar00rootroot00000000000000crypto-0.57.0/kms/uri/testdata/000077500000000000000000000000001474156331600163375ustar00rootroot00000000000000crypto-0.57.0/kms/uri/testdata/pin.txt000066400000000000000000000000171474156331600176640ustar00rootroot00000000000000trim-this-pin crypto-0.57.0/kms/uri/uri.go000066400000000000000000000122461474156331600156610ustar00rootroot00000000000000package uri import ( "bytes" "encoding/hex" "fmt" "net/url" "os" "strconv" "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, }, } } // NewOpaque returns a uri with the given scheme and the given opaque. func NewOpaque(scheme, opaque string) *URI { return &URI{ URL: &url.URL{ Scheme: scheme, Opaque: opaque, }, } } // 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 } // String returns the string representation of the URI. func (u *URI) String() string { if len(u.Values) > 0 { u.URL.Opaque = strings.ReplaceAll(u.Values.Encode(), "&", ";") } return u.URL.String() } // Has checks whether a given key is set. func (u *URI) Has(key string) bool { return u.Values.Has(key) || u.URL.Query().Has(key) } // 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") } // GetInt returns the first integer value in the URI with the given key. It // returns nil if the field is not present or if the value can't be parsed // as an integer. func (u *URI) GetInt(key string) *int64 { v := u.Values.Get(key) if v == "" { v = u.URL.Query().Get(key) } if v == "" { return nil } if i, err := strconv.ParseInt(v, 10, 0); err == nil { return &i } return nil } // 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(strings.TrimPrefix(v, "0x")); err == nil { return b } } return []byte(v) } // GetHexEncoded returns the first value in the uri with the given key. It // returns nil if the field is not present or is empty. It will return an // error if the the value is not properly hex encoded. func (u *URI) GetHexEncoded(key string) ([]byte, error) { v := u.Get(key) if v == "" { return nil, nil } b, err := hex.DecodeString(strings.TrimPrefix(v, "0x")) if err != nil { return nil, fmt.Errorf("failed decoding %q: %w", v, err) } return b, nil } // 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 "" } // Read returns the raw content of the file in the given attribute key. This // method will return nil if the key is missing. func (u *URI) Read(key string) ([]byte, error) { path := u.Get(key) if path == "" { return nil, nil } return readFile(path) } func readFile(path string) ([]byte, error) { u, err := url.Parse(path) if err == nil && (u.Scheme == "" || u.Scheme == "file") { switch { case u.Path != "": path = u.Path case u.Opaque != "": path = u.Opaque } } b, err := os.ReadFile(path) if err != nil { return nil, errors.Wrapf(err, "error reading %s", path) } return b, nil } crypto-0.57.0/kms/uri/uri_119_test.go000066400000000000000000000034301474156331600173050ustar00rootroot00000000000000//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) } }) } } crypto-0.57.0/kms/uri/uri_other_test.go000066400000000000000000000034111474156331600201130ustar00rootroot00000000000000//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) } }) } } crypto-0.57.0/kms/uri/uri_test.go000066400000000000000000000310261474156331600167150ustar00rootroot00000000000000package uri import ( "net/url" "os" "path/filepath" "reflect" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func mustParse(t *testing.T, s string) *URI { t.Helper() u, err := Parse(s) require.NoError(t, err) return u } 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 TestNewOpaque(t *testing.T) { type args struct { scheme string opaque string } tests := []struct { name string args args want *URI }{ {"ok", args{"softkms", "/path/to/file"}, &URI{ URL: &url.URL{Scheme: "softkms", Opaque: "/path/to/file"}, Values: url.Values(nil), }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := NewOpaque(tt.args.scheme, tt.args.opaque); !reflect.DeepEqual(got, tt.want) { t.Errorf("NewOpaque() = %#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_Has(t *testing.T) { type args struct { key string } tests := []struct { name string uri *URI args args want bool }{ {"ok", mustParse(t, "yubikey:slot-id=9a"), args{"slot-id"}, true}, {"ok empty", mustParse(t, "yubikey:slot-id="), args{"slot-id"}, true}, {"ok query", mustParse(t, "yubikey:pin=123456?slot-id="), args{"slot-id"}, true}, {"ok empty no equal", mustParse(t, "yubikey:slot-id"), args{"slot-id"}, true}, {"ok query no equal", mustParse(t, "yubikey:pin=123456?slot-id"), args{"slot-id"}, true}, {"ok empty no equal other", mustParse(t, "yubikey:slot-id;pin=123456"), args{"slot-id"}, true}, {"ok query no equal other", mustParse(t, "yubikey:pin=123456?slot-id&pin=123456"), args{"slot-id"}, true}, {"fail", mustParse(t, "yubikey:pin=123456"), args{"slot-id"}, false}, {"fail with query", mustParse(t, "yubikey:pin=123456?slot=9a"), args{"slot-id"}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.uri.Has(tt.args.key); got != tt.want { t.Errorf("URI.Has() = %v, want %v", got, tt.want) } }) } } func TestURI_Get(t *testing.T) { type args struct { key string } tests := []struct { name string uri *URI args args want string }{ {"ok", mustParse(t, "yubikey:slot-id=9a"), args{"slot-id"}, "9a"}, {"ok first", mustParse(t, "yubikey:slot-id=9a;slot-id=9b"), args{"slot-id"}, "9a"}, {"ok multiple", mustParse(t, "yubikey:slot-id=9a;foo=bar"), args{"foo"}, "bar"}, {"ok in query", mustParse(t, "yubikey:slot-id=9a?foo=bar"), args{"foo"}, "bar"}, {"fail missing", mustParse(t, "yubikey:slot-id=9a"), args{"foo"}, ""}, {"fail missing query", mustParse(t, "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) { type args struct { key string } tests := []struct { name string uri *URI args args want bool }{ {"true", mustParse(t, "azurekms:name=foo;vault=bar;hsm=true"), args{"hsm"}, true}, {"TRUE", mustParse(t, "azurekms:name=foo;vault=bar;hsm=TRUE"), args{"hsm"}, true}, {"tRUe query", mustParse(t, "azurekms:name=foo;vault=bar?hsm=tRUe"), args{"hsm"}, true}, {"false", mustParse(t, "azurekms:name=foo;vault=bar;hsm=false"), args{"hsm"}, false}, {"false query", mustParse(t, "azurekms:name=foo;vault=bar?hsm=false"), args{"hsm"}, false}, {"empty", mustParse(t, "azurekms:name=foo;vault=bar;hsm=?bar=true"), args{"hsm"}, false}, {"missing", mustParse(t, "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) { type args struct { key string } tests := []struct { name string uri *URI args args want []byte }{ {"ok", mustParse(t, "yubikey:slot-id=9a"), args{"slot-id"}, []byte{0x9a}}, {"ok prefix", mustParse(t, "yubikey:slot-id=0x9a"), args{"slot-id"}, []byte{0x9a}}, {"ok first", mustParse(t, "yubikey:slot-id=9a9b;slot-id=9b"), args{"slot-id"}, []byte{0x9a, 0x9b}}, {"ok percent", mustParse(t, "yubikey:slot-id=9a;foo=%9a%9b%9c"), args{"foo"}, []byte{0x9a, 0x9b, 0x9c}}, {"ok in query", mustParse(t, "yubikey:slot-id=9a?foo=9a"), args{"foo"}, []byte{0x9a}}, {"ok in query percent", mustParse(t, "yubikey:slot-id=9a?foo=%9a"), args{"foo"}, []byte{0x9a}}, {"ok missing", mustParse(t, "yubikey:slot-id=9a"), args{"foo"}, nil}, {"ok missing query", mustParse(t, "yubikey:slot-id=9a?bar=zar"), args{"foo"}, nil}, {"ok no hex", mustParse(t, "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) { tests := []struct { name string uri *URI want string }{ {"from value", mustParse(t, "pkcs11:id=%72%73?pin-value=0123456789"), "0123456789"}, {"from source", mustParse(t, "pkcs11:id=%72%73?pin-source=testdata/pin.txt"), "trim-this-pin"}, {"from missing", mustParse(t, "pkcs11:id=%72%73"), ""}, {"from source missing", mustParse(t, "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) { 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 newOpaque", NewOpaque("cloudkms", "projects/p/locations/l/keyRings/k/cryptoKeys/c/cryptoKeyVersions/1"), "cloudkms:projects/p/locations/l/keyRings/k/cryptoKeys/c/cryptoKeyVersions/1"}, {"ok newFile", NewFile("/path/to/file.key"), "file:///path/to/file.key"}, {"ok parse", mustParse(t, "yubikey:slot-id=9a;foo=bar?bar=zar"), "yubikey:foo=bar;slot-id=9a?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) } }) } } func TestURI_GetInt(t *testing.T) { seventy := int64(70) type args struct { key string } tests := []struct { name string uri *URI args args want *int64 }{ {"ok", mustParse(t, "tpmkms:renewal-percentage=70"), args{"renewal-percentage"}, &seventy}, {"ok empty", mustParse(t, "tpmkms:empty"), args{"renewal-percentage"}, nil}, {"ok non-integer", mustParse(t, "tpmkms:renewal-percentage=not-an-integer"), args{"renewal-percentage"}, nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := tt.uri.GetInt(tt.args.key) if tt.want != nil { assert.Equal(t, *tt.want, *got) } else { assert.Nil(t, got) } }) } } func TestURI_GetHexEncoded(t *testing.T) { type args struct { key string } tests := []struct { name string uri *URI args args want []byte wantErr bool }{ {"ok", mustParse(t, "capi:sha1=9a"), args{"sha1"}, []byte{0x9a}, false}, {"ok first", mustParse(t, "capi:sha1=9a9b;sha1=9b"), args{"sha1"}, []byte{0x9a, 0x9b}, false}, {"ok prefix", mustParse(t, "capi:sha1=0x9a9b;sha1=9b"), args{"sha1"}, []byte{0x9a, 0x9b}, false}, {"ok missing", mustParse(t, "capi:foo=9a"), args{"sha1"}, nil, false}, {"fail odd hex", mustParse(t, "capi:sha1=09a?bar=zar"), args{"sha1"}, nil, true}, {"fail invalid hex", mustParse(t, "capi:sha1=9z?bar=zar"), args{"sha1"}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.uri.GetHexEncoded(tt.args.key) if tt.wantErr { assert.Error(t, err) assert.Nil(t, got) return } assert.Equal(t, tt.want, got) }) } } func TestURI_Read(t *testing.T) { // Read does not trim the contents of the file expected := []byte("trim-this-pin \n") path := filepath.Join(t.TempDir(), "management.key") require.NoError(t, os.WriteFile(path, expected, 0600)) managementKeyURI := &url.URL{ Scheme: "file", Path: path, } pathURI := &URI{ URL: &url.URL{Scheme: "yubikey"}, Values: url.Values{ "management-key-source": []string{managementKeyURI.String()}, }, } type args struct { key string } tests := []struct { name string uri *URI args args want []byte assertion assert.ErrorAssertionFunc }{ {"from attribute", mustParse(t, "yubikey:management-key-source=testdata/pin.txt"), args{"management-key-source"}, expected, assert.NoError}, {"from query attribute", mustParse(t, "yubikey:?management-key-source=testdata/pin.txt"), args{"management-key-source"}, expected, assert.NoError}, {"from uri path", pathURI, args{"management-key-source"}, expected, assert.NoError}, {"from uri opaque", mustParse(t, "yubikey:management-key-source=file:testdata/pin.txt"), args{"management-key-source"}, expected, assert.NoError}, {"from empty attribute", mustParse(t, "yubikey:management-source-key="), args{"management-key-source"}, nil, assert.NoError}, {"from missing attribute", mustParse(t, "yubikey:slot-id=82"), args{"management-key-source"}, nil, assert.NoError}, {"from missing file", mustParse(t, "yubikey:management-key-source=testdata/foo.txt"), args{"management-key-source"}, nil, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.uri.Read(tt.args.key) tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } } crypto-0.57.0/kms/yubikey/000077500000000000000000000000001474156331600154105ustar00rootroot00000000000000crypto-0.57.0/kms/yubikey/yubikey.go000066400000000000000000000404741474156331600174310ustar00rootroot00000000000000//go:build cgo && !noyubikey // +build cgo,!noyubikey package yubikey import ( "bytes" "context" "crypto" "crypto/x509" "encoding/asn1" "encoding/hex" "fmt" "io" "net/url" "strconv" "strings" "sync" "unicode" "github.com/go-piv/piv-go/v2/piv" "github.com/pkg/errors" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/kms/uri" ) // Scheme is the scheme used in uris, the string "yubikey". const Scheme = string(apiv1.YubiKey) // Yubico PIV attestation serial number, encoded as an integer. // https://developers.yubico.com/PIV/Introduction/PIV_attestation.html var oidYubicoSerialNumber = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 41482, 3, 7} // YubiKey implements the KMS interface on a YubiKey. type YubiKey struct { yk pivKey pin string card string managementKey []byte } type pivKey interface { Certificate(slot piv.Slot) (*x509.Certificate, error) SetCertificate(key []byte, slot piv.Slot, cert *x509.Certificate) error GenerateKey(key []byte, slot piv.Slot, opts piv.Key) (crypto.PublicKey, error) KeyInfo(slot piv.Slot) (piv.KeyInfo, error) PrivateKey(slot piv.Slot, public crypto.PublicKey, auth piv.KeyAuth) (crypto.PrivateKey, error) Attest(slot piv.Slot) (*x509.Certificate, error) Serial() (uint32, error) Close() error } var pivCards = piv.Cards var pivMap sync.Map // pivOpen calls piv.Open. It can be replaced by a custom functions for testing // purposes. var pivOpen = func(card string) (pivKey, error) { return piv.Open(card) } // openCard wraps pivOpen with a cache. It loads a card connection from the // cache if present. func openCard(card string) (pivKey, error) { if v, ok := pivMap.Load(card); ok { return v.(pivKey), nil } yk, err := pivOpen(card) if err != nil { return nil, err } pivMap.Store(card, yk) return yk, nil } // New initializes a new YubiKey KMS. // // The most common way to open a YubiKey is to add a URI in the options: // // New(ctx, &apiv1.Options{ // URI: yubikey:pin-value=123456, // }) // // This URI can also provide the management key in hexadecimal format if the // default one is not used, and the serial number of the card if we want to // support multiple cards at the same time. // // yubikey:management-key=001122334455667788990011223344556677889900112233?pin-value=123456 // yubikey:management-key-source=/var/run/management.key?pin-source=/var/run/yubikey.pin // yubikey:serial=112233?pin-source=/var/run/yubikey.pin // // You can also define a slot id, this will be ignored in this method but can be // useful on CLI applications. // // yubikey:slot-id=9a?pin-value=123456 // // If the pin or the management key are not provided, we will use the default // ones. func New(_ context.Context, opts apiv1.Options) (*YubiKey, error) { pin := "123456" managementKey := piv.DefaultManagementKey var serial string 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 } else if u.Has("management-key-source") { b, err := u.Read("management-key-source") if err != nil { return nil, err } if b = bytes.TrimFunc(b, unicode.IsSpace); len(b) > 0 { opts.ManagementKey = string(b) } } if v := u.Get("serial"); v != "" { serial = 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 management key") } 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") } card := cards[0] var yk pivKey if serial != "" { // Attempt to locate the yubikey with the given serial. for _, name := range cards { if k, err := openCard(name); err == nil { if s, err := k.Serial(); err == nil { if serial == strconv.FormatUint(uint64(s), 10) { yk = k card = name break } } } } if yk == nil { return nil, errors.Errorf("failed to find key with serial number %s, slot 0x9a might be empty", serial) } } else if yk, err = openCard(cards[0]); err != nil { return nil, errors.Wrap(err, "error opening yubikey") } return &YubiKey{ yk: yk, pin: pin, card: card, 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 &syncSigner{ Signer: 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 &syncDecrypter{ Decrypter: 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{cert, intermediate}, PublicKey: cert.PublicKey, PermanentIdentifier: getAttestedSerial(cert), }, nil } // Serial returns the serial number of the PIV card or and empty // string if retrieval fails func (k *YubiKey) Serial() (string, error) { serial, err := k.yk.Serial() if err != nil { return "", fmt.Errorf("error getting Yubikey's serial number: %w", err) } return strconv.FormatUint(uint64(serial), 10), nil } // Close releases the connection to the YubiKey. func (k *YubiKey) Close() error { if err := k.yk.Close(); err != nil { return errors.Wrap(err, "error closing yubikey") } pivMap.Delete(k.card) return nil } // getPublicKey returns the public key on a slot. First it attempts to use // KeyInfo to get the public key, then tries 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) { // YubiKey >= 5.3.0 (generated and imported keys) if ki, err := k.yk.KeyInfo(slot); err == nil && ki.PublicKey != nil { return ki.PublicKey, nil } // YubiKey >= 4.3.0 (generated keys) if cert, err := k.yk.Attest(slot); err == nil { return cert.PublicKey, nil } // Fallback to certificate in slot (generated and imported) cert, err := k.yk.Certificate(slot) if err != nil { if errors.Is(err, piv.ErrNotFound) { return nil, apiv1.NotFoundError{ Message: err.Error(), } } return nil, fmt.Errorf("error retrieving public key: %w", err) } 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, 3072: piv.AlgorithmRSA3072, 4096: piv.AlgorithmRSA4096, }, apiv1.SHA512WithRSA: map[int]piv.Algorithm{ 0: piv.AlgorithmRSA2048, 1024: piv.AlgorithmRSA1024, 2048: piv.AlgorithmRSA2048, 3072: piv.AlgorithmRSA3072, 4096: piv.AlgorithmRSA4096, }, apiv1.SHA256WithRSAPSS: map[int]piv.Algorithm{ 0: piv.AlgorithmRSA2048, 1024: piv.AlgorithmRSA1024, 2048: piv.AlgorithmRSA2048, 3072: piv.AlgorithmRSA3072, 4096: piv.AlgorithmRSA4096, }, apiv1.SHA512WithRSAPSS: map[int]piv.Algorithm{ 0: piv.AlgorithmRSA2048, 1024: piv.AlgorithmRSA1024, 2048: piv.AlgorithmRSA2048, 3072: piv.AlgorithmRSA3072, 4096: piv.AlgorithmRSA4096, }, 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 := uri.Parse(name) if err != nil { return piv.Slot{}, "", err } if slotID = u.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 } // getAttestedSerial returns the serial number from an attestation certificate. It // will return an empty string if the serial number extension does not exist // or if it is malformed. func getAttestedSerial(cert *x509.Certificate) string { for _, ext := range cert.Extensions { if ext.Id.Equal(oidYubicoSerialNumber) { var serialNumber int rest, err := asn1.Unmarshal(ext.Value, &serialNumber) if err != nil || len(rest) > 0 { return "" } return strconv.Itoa(serialNumber) } } return "" } // Common mutex used in syncSigner and syncDecrypter. A sync.Mutex cannot be // copied after the first use. // // By using it, synchronization becomes easier and avoids conflicts between the // two goroutines accessing the shared resources. // // This is not optimal if more than one YubiKey is used, but the overhead should // be small. var m sync.Mutex // syncSigner wraps a crypto.Signer with a mutex to avoid the error "smart card // error 6982: security status not satisfied" with two concurrent signs. type syncSigner struct { crypto.Signer } func (s *syncSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { m.Lock() defer m.Unlock() return s.Signer.Sign(rand, digest, opts) } // syncDecrypter wraps a crypto.Decrypter with a mutex to avoid the error "smart // card error 6a80: incorrect parameter in command data field" with two // concurrent decryptions. type syncDecrypter struct { crypto.Decrypter } func (s *syncDecrypter) Decrypt(rand io.Reader, msg []byte, opts crypto.DecrypterOpts) ([]byte, error) { m.Lock() defer m.Unlock() return s.Decrypter.Decrypt(rand, msg, opts) } var _ apiv1.CertificateManager = (*YubiKey)(nil) crypto-0.57.0/kms/yubikey/yubikey_no_cgo.go000066400000000000000000000007041474156331600207450ustar00rootroot00000000000000//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) }) } crypto-0.57.0/kms/yubikey/yubikey_test.go000066400000000000000000001247411474156331600204700ustar00rootroot00000000000000//go:build cgo // +build cgo package yubikey import ( "bytes" "context" "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/hex" "errors" "fmt" "os" "path/filepath" "reflect" "sync" "testing" "github.com/go-piv/piv-go/v2/piv" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/minica" "go.step.sm/crypto/randutil" ) type stubPivKey struct { attestCA *minica.CA attestSigner privateKey userCA *minica.CA keyInfoMap map[piv.Slot]piv.KeyInfo attestMap map[piv.Slot]*x509.Certificate certMap map[piv.Slot]*x509.Certificate signerMap map[piv.Slot]interface{} keyOptionsMap map[piv.Slot]piv.Key serial uint32 serialErr error closeErr error } 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) } var keyInfoAlgo piv.Algorithm switch alg { case ECDSA: keyInfoAlgo = piv.AlgorithmEC256 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: keyInfoAlgo = piv.AlgorithmRSA2048 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")) } sn := 112233 snAsn1, err := asn1.Marshal(sn) if err != nil { t.Fatal(err) } attCert, err := attestCA.Sign(&x509.Certificate{ Subject: pkix.Name{CommonName: "attested certificate"}, PublicKey: attSigner.Public(), ExtraExtensions: []pkix.Extension{ {Id: oidYubicoSerialNumber, Value: snAsn1}, }, }) 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, attestSigner: attSigner, userCA: userCA, keyInfoMap: map[piv.Slot]piv.KeyInfo{ piv.SlotKeyManagement: { PublicKey: attSigner.Public(), Algorithm: keyInfoAlgo, PINPolicy: piv.PINPolicyOnce, TouchPolicy: piv.TouchPolicyCached, Origin: piv.OriginGenerated, }, // 9d }, 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{}, serial: uint32(sn), } } func (s *stubPivKey) KeyInfo(slot piv.Slot) (piv.KeyInfo, error) { keyInfo, ok := s.keyInfoMap[slot] if !ok { return piv.KeyInfo{}, errors.New("public key not found") } return keyInfo, nil } func (s *stubPivKey) Certificate(slot piv.Slot) (*x509.Certificate, error) { cert, ok := s.certMap[slot] if !ok { if slot == slotMapping["82"] { return nil, errors.New("command failed: some error") } return nil, fmt.Errorf("command failed: %w", piv.ErrNotFound) } return cert, nil } func (s *stubPivKey) SetCertificate(key []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 []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 case piv.AlgorithmRSA3072: key, err := rsa.GenerateKey(rand.Reader, 3072) if err != nil { return nil, err } signer = key case piv.AlgorithmRSA4096: key, err := rsa.GenerateKey(rand.Reader, 4096) 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 s.closeErr } func (s *stubPivKey) Serial() (uint32, error) { if s.serialErr != nil { return 0, s.serialErr } return s.serial, nil } func TestRegister(t *testing.T) { pCards := pivCards t.Cleanup(func() { pivCards = pCards }) pivCards = func() ([]string, error) { return []string{"Yubico YubiKey OTP+FIDO+CCID"}, nil } fn, ok := apiv1.LoadKeyManagerNewFunc(apiv1.YubiKey) if !ok { t.Fatal("YubiKey is not registered") } _, _ = fn(context.Background(), apiv1.Options{ Type: "YubiKey", URI: "yubikey:", }) } func TestNew(t *testing.T) { ctx := context.Background() pOpen := pivOpen pCards := pivCards t.Cleanup(func() { pivMap = sync.Map{} pivOpen = pOpen pivCards = pCards }) managementKey, err := randutil.Salt(24) require.NoError(t, err) managementKeyFile := filepath.Join(t.TempDir(), "management.key") require.NoError(t, os.WriteFile(managementKeyFile, []byte(hex.EncodeToString(managementKey)), 0600)) yk := newStubPivKey(t, ECDSA) okPivCards := func() ([]string, error) { return []string{"Yubico YubiKey OTP+FIDO+CCID"}, nil } okMultiplePivCards := func() ([]string, error) { return []string{ "Yubico YubiKey OTP+FIDO+CCID", "Yubico YubiKey OTP+FIDO+CCID 01", }, 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() { pivMap = sync.Map{} pivCards = okPivCards pivOpen = okPivOpen }, &YubiKey{yk: yk, pin: "123456", card: "Yubico YubiKey OTP+FIDO+CCID", managementKey: piv.DefaultManagementKey}, false}, {"ok with uri", args{ctx, apiv1.Options{ URI: "yubikey:pin-value=111111;management-key=001122334455667788990011223344556677889900112233", }}, func() { pivMap = sync.Map{} pivCards = okMultiplePivCards pivOpen = okPivOpen }, &YubiKey{yk: yk, pin: "111111", card: "Yubico YubiKey OTP+FIDO+CCID", managementKey: []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 uri and serial", args{ctx, apiv1.Options{ URI: "yubikey:serial=112233?pin-value=123456", }}, func() { pivMap = sync.Map{} pivCards = okPivCards pivOpen = okPivOpen }, &YubiKey{yk: yk, pin: "123456", card: "Yubico YubiKey OTP+FIDO+CCID", managementKey: piv.DefaultManagementKey}, false}, {"ok with uri and serial from cache", args{ctx, apiv1.Options{ URI: "yubikey:serial=112233?pin-value=123456", }}, func() { pivCards = okPivCards pivOpen = okPivOpen }, &YubiKey{yk: yk, pin: "123456", card: "Yubico YubiKey OTP+FIDO+CCID", managementKey: piv.DefaultManagementKey}, false}, {"ok with management-key-source", args{ctx, apiv1.Options{ URI: fmt.Sprintf("yubikey:management-key-source=%s?pin-value=123456", managementKeyFile), }}, func() { pivMap = sync.Map{} pivCards = okPivCards pivOpen = okPivOpen }, &YubiKey{yk: yk, pin: "123456", card: "Yubico YubiKey OTP+FIDO+CCID", managementKey: managementKey}, false}, {"ok with Pin", args{ctx, apiv1.Options{Pin: "222222"}}, func() { pivMap = sync.Map{} pivCards = okPivCards pivOpen = okPivOpen }, &YubiKey{yk: yk, pin: "222222", card: "Yubico YubiKey OTP+FIDO+CCID", managementKey: piv.DefaultManagementKey}, false}, {"ok with ManagementKey", args{ctx, apiv1.Options{ManagementKey: "001122334455667788990011223344556677889900112233"}}, func() { pivMap = sync.Map{} pivCards = okPivCards pivOpen = okPivOpen }, &YubiKey{yk: yk, pin: "123456", card: "Yubico YubiKey OTP+FIDO+CCID", managementKey: []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() { pivMap = sync.Map{} pivCards = okPivCards pivOpen = okPivOpen }, nil, true}, {"fail management key", args{ctx, apiv1.Options{URI: "yubikey:management-key=xxyyzz"}}, func() { pivMap = sync.Map{} pivCards = okPivCards pivOpen = okPivOpen }, nil, true}, {"fail management key size", args{ctx, apiv1.Options{URI: "yubikey:management-key=00112233"}}, func() { pivMap = sync.Map{} pivCards = okPivCards pivOpen = okPivOpen }, nil, true}, {"fail management key source", args{ctx, apiv1.Options{URI: "yubikey:management-key-source=missing.txt"}}, func() { pivMap = sync.Map{} pivCards = okPivCards pivOpen = okPivOpen }, nil, true}, {"fail pivCards", args{ctx, apiv1.Options{}}, func() { pivMap = sync.Map{} pivCards = failPivCards pivOpen = okPivOpen }, nil, true}, {"fail no pivCards", args{ctx, apiv1.Options{}}, func() { pivMap = sync.Map{} pivCards = failNoPivCards pivOpen = okPivOpen }, nil, true}, {"fail no pivCards with serial", args{ctx, apiv1.Options{ URI: "yubikey:pin-value=111111;serial=332211?pin-value=123456", }}, func() { pivMap = sync.Map{} pivCards = okPivCards pivOpen = okPivOpen }, nil, true}, {"fail pivOpen", args{ctx, apiv1.Options{}}, func() { pivMap = sync.Map{} 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 []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 []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", []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 []byte } type args struct { req *apiv1.GetPublicKeyRequest } tests := []struct { name string fields fields args args want crypto.PublicKey wantErr bool }{ {"ok with keyInfo", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.GetPublicKeyRequest{ Name: "yubikey:slot-id=9d", }}, yk.keyInfoMap[piv.SlotKeyManagement].PublicKey, false}, {"ok with Attest", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.GetPublicKeyRequest{ Name: "yubikey:slot-id=9a", }}, yk.attestMap[piv.SlotAuthentication].PublicKey, false}, {"ok with certificate", 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=82", }}, nil, true}, {"fail getPublicKey not found", 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 []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 rsa 3072", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ Name: "yubikey:slot-id=82", SignatureAlgorithm: apiv1.SHA256WithRSAPSS, Bits: 3072, }}, 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 4096", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ Name: "yubikey:slot-id=82", SignatureAlgorithm: apiv1.SHA512WithRSAPSS, Bits: 4096, }}, 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 512", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.CreateKeyRequest{ Name: "yubikey:slot-id=82", SignatureAlgorithm: apiv1.SHA256WithRSA, Bits: 512, }}, 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", []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 []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 []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", }}, &syncSigner{Signer: 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", }}, &syncSigner{Signer: 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 []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", }}, &syncDecrypter{Decrypter: 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", }}, &syncDecrypter{Decrypter: 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 []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.attestMap[piv.SlotAuthentication], yk.attestCA.Intermediate}, PublicKey: yk.attestMap[piv.SlotAuthentication].PublicKey, PermanentIdentifier: "112233", }, 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_Serial(t *testing.T) { yk1 := newStubPivKey(t, RSA) yk2 := newStubPivKey(t, RSA) yk2.serialErr = errors.New("some error") tests := []struct { name string yk pivKey want string wantErr bool }{ {"ok", yk1, "112233", false}, {"fail", yk2, "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := &YubiKey{ yk: tt.yk, } got, err := k.Serial() if (err != nil) != tt.wantErr { t.Errorf("YubiKey.Serial() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("YubiKey.Serial() = %v, want %v", got, tt.want) } }) } } func TestYubiKey_Close(t *testing.T) { yk1 := newStubPivKey(t, ECDSA) yk2 := newStubPivKey(t, RSA) yk2.closeErr = errors.New("some error") type fields struct { yk pivKey pin string managementKey []byte } tests := []struct { name string fields fields wantErr bool }{ {"ok", fields{yk1, "123456", piv.DefaultManagementKey}, false}, {"fail", fields{yk2, "123456", piv.DefaultManagementKey}, 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.Close(); (err != nil) != tt.wantErr { t.Errorf("YubiKey.Close() error = %v, wantErr %v", err, tt.wantErr) } }) } } func Test_getAttestedSerial(t *testing.T) { serialNumber, err := asn1.Marshal(112233) if err != nil { t.Fatal(err) } printableSerialNumber, err := asn1.Marshal("112233") if err != nil { t.Fatal(err) } yk := newStubPivKey(t, RSA) okCert := yk.attestMap[piv.SlotAuthentication] printableCert := &x509.Certificate{ Subject: pkix.Name{CommonName: "attested certificate"}, PublicKey: okCert.PublicKey, Extensions: []pkix.Extension{ {Id: oidYubicoSerialNumber, Value: printableSerialNumber}, }, } restCert := &x509.Certificate{ Subject: pkix.Name{CommonName: "attested certificate"}, PublicKey: okCert.PublicKey, Extensions: []pkix.Extension{ {Id: oidYubicoSerialNumber, Value: append(serialNumber, 0)}, }, } missingCert := &x509.Certificate{ Subject: pkix.Name{CommonName: "attested certificate"}, PublicKey: okCert.PublicKey, } type args struct { cert *x509.Certificate } tests := []struct { name string args args want string }{ {"ok", args{okCert}, "112233"}, {"fail printable", args{printableCert}, ""}, {"fail rest", args{restCert}, ""}, {"fail missing", args{missingCert}, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := getAttestedSerial(tt.args.cert); got != tt.want { t.Errorf("getAttestedSerial() = %v, want %v", got, tt.want) } }) } } func Test_getSignatureAlgorithm(t *testing.T) { fake := apiv1.SignatureAlgorithm(1000) t.Cleanup(func() { delete(signatureAlgorithmMapping, fake) }) signatureAlgorithmMapping[fake] = "fake" type args struct { alg apiv1.SignatureAlgorithm bits int } tests := []struct { name string args args want piv.Algorithm wantErr bool }{ {"default", args{apiv1.UnspecifiedSignAlgorithm, 0}, piv.AlgorithmEC256, false}, {"SHA256WithRSA", args{apiv1.SHA256WithRSA, 0}, piv.AlgorithmRSA2048, false}, {"SHA512WithRSA", args{apiv1.SHA512WithRSA, 0}, piv.AlgorithmRSA2048, false}, {"SHA256WithRSAPSS", args{apiv1.SHA256WithRSAPSS, 0}, piv.AlgorithmRSA2048, false}, {"SHA512WithRSAPSS", args{apiv1.SHA512WithRSAPSS, 0}, piv.AlgorithmRSA2048, false}, {"ECDSAWithSHA256", args{apiv1.ECDSAWithSHA256, 0}, piv.AlgorithmEC256, false}, {"ECDSAWithSHA384", args{apiv1.ECDSAWithSHA384, 0}, piv.AlgorithmEC384, false}, {"PureEd25519", args{apiv1.PureEd25519, 0}, piv.AlgorithmEd25519, false}, {"SHA256WithRSA 1024", args{apiv1.SHA256WithRSA, 1024}, piv.AlgorithmRSA1024, false}, {"SHA512WithRSA 1024", args{apiv1.SHA512WithRSA, 1024}, piv.AlgorithmRSA1024, false}, {"SHA256WithRSAPSS 1024", args{apiv1.SHA256WithRSAPSS, 1024}, piv.AlgorithmRSA1024, false}, {"SHA512WithRSAPSS 1024", args{apiv1.SHA512WithRSAPSS, 1024}, piv.AlgorithmRSA1024, false}, {"SHA256WithRSA 2048", args{apiv1.SHA256WithRSA, 2048}, piv.AlgorithmRSA2048, false}, {"SHA512WithRSA 2048", args{apiv1.SHA512WithRSA, 2048}, piv.AlgorithmRSA2048, false}, {"SHA256WithRSAPSS 2048", args{apiv1.SHA256WithRSAPSS, 2048}, piv.AlgorithmRSA2048, false}, {"SHA512WithRSAPSS 2048", args{apiv1.SHA512WithRSAPSS, 2048}, piv.AlgorithmRSA2048, false}, {"SHA256WithRSA 3072", args{apiv1.SHA256WithRSA, 3072}, piv.AlgorithmRSA3072, false}, {"SHA512WithRSA 3072", args{apiv1.SHA512WithRSA, 3072}, piv.AlgorithmRSA3072, false}, {"SHA256WithRSAPSS 3072", args{apiv1.SHA256WithRSAPSS, 3072}, piv.AlgorithmRSA3072, false}, {"SHA512WithRSAPSS 3072", args{apiv1.SHA512WithRSAPSS, 3072}, piv.AlgorithmRSA3072, false}, {"SHA256WithRSA 4096", args{apiv1.SHA256WithRSA, 4096}, piv.AlgorithmRSA4096, false}, {"SHA512WithRSA 4096", args{apiv1.SHA512WithRSA, 4096}, piv.AlgorithmRSA4096, false}, {"SHA256WithRSAPSS 4096", args{apiv1.SHA256WithRSAPSS, 4096}, piv.AlgorithmRSA4096, false}, {"SHA512WithRSAPSS 4096", args{apiv1.SHA512WithRSAPSS, 4096}, piv.AlgorithmRSA4096, false}, {"fail 512", args{apiv1.SHA256WithRSA, 512}, 0, true}, {"fail unknown", args{apiv1.SignatureAlgorithm(100), 0}, 0, true}, {"fail default case", args{apiv1.SignatureAlgorithm(1000), 0}, 0, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := getSignatureAlgorithm(tt.args.alg, tt.args.bits) if (err != nil) != tt.wantErr { t.Errorf("getSignatureAlgorithm() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("getSignatureAlgorithm() = %v, want %v", got, tt.want) } }) } } func Test_syncSigner_Sign(t *testing.T) { s, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) signer := &syncSigner{Signer: s} sum := sha256.Sum256([]byte("the-data")) sig, err := signer.Sign(rand.Reader, sum[:], crypto.SHA256) require.NoError(t, err) assert.True(t, ecdsa.VerifyASN1(&s.PublicKey, sum[:], sig)) } func Test_syncDecrypter_Decrypt(t *testing.T) { d, err := rsa.GenerateKey(rand.Reader, rsaKeySize) require.NoError(t, err) label := []byte("label") data := []byte("the-data") msg, err := rsa.EncryptOAEP(crypto.SHA256.New(), rand.Reader, &d.PublicKey, data, label) require.NoError(t, err) decrypter := &syncDecrypter{Decrypter: d} plain, err := decrypter.Decrypt(rand.Reader, msg, &rsa.OAEPOptions{ Hash: crypto.SHA256, Label: label, }) assert.NoError(t, err) assert.Equal(t, data, plain) } crypto-0.57.0/minica/000077500000000000000000000000001474156331600143755ustar00rootroot00000000000000crypto-0.57.0/minica/minica.go000066400000000000000000000121251474156331600161650ustar00rootroot00000000000000package 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") } } crypto-0.57.0/minica/minica_test.go000066400000000000000000000352621474156331600172330ustar00rootroot00000000000000package 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(_ 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") } } crypto-0.57.0/minica/options.go000066400000000000000000000050221474156331600164160ustar00rootroot00000000000000package 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 } } crypto-0.57.0/pemutil/000077500000000000000000000000001474156331600146145ustar00rootroot00000000000000crypto-0.57.0/pemutil/cosign.go000066400000000000000000000037671474156331600164420ustar00rootroot00000000000000package 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 } crypto-0.57.0/pemutil/cosign_test.go000066400000000000000000000132241474156331600174660ustar00rootroot00000000000000package 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) } } crypto-0.57.0/pemutil/pem.go000066400000000000000000000554131474156331600157340ustar00rootroot00000000000000// 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/ecdh" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" "encoding/pem" "fmt" "math/big" "os" "strings" "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 // PEMBlockHeader is the expected header for any PEM formatted block. var PEMBlockHeader = []byte("-----BEGIN ") // 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 returns a list of *x509.Certificate parsed from // the given bytes. // // - supports PEM and DER certificate formats // - If a DER-formatted file is given only one certificate will be returned. func ParseCertificateBundle(data []byte) ([]*x509.Certificate, error) { var err error // PEM format if bytes.Contains(data, PEMBlockHeader) { var block *pem.Block var bundle []*x509.Certificate for len(data) > 0 { block, data = pem.Decode(data) if block == nil { break } if block.Type != "CERTIFICATE" || len(block.Headers) != 0 { continue } var crt *x509.Certificate crt, err = x509.ParseCertificate(block.Bytes) if err != nil { return nil, &InvalidPEMError{ Err: err, Type: PEMTypeCertificate, } } bundle = append(bundle, crt) } if len(bundle) == 0 { return nil, &InvalidPEMError{ Type: PEMTypeCertificate, } } return bundle, nil } // DER format (binary) crt, err := x509.ParseCertificate(data) if err != nil { return nil, &InvalidPEMError{ Message: fmt.Sprintf("error parsing certificate as DER format: %v", err), Type: PEMTypeCertificate, } } return []*x509.Certificate{crt}, nil } // ParseCertificateRequest extracts the first *x509.CertificateRequest // from the given data. // // - supports PEM and DER certificate formats // - If a DER-formatted file is given only one certificate will be returned. func ParseCertificateRequest(data []byte) (*x509.CertificateRequest, error) { // PEM format if bytes.Contains(data, PEMBlockHeader) { var block *pem.Block for len(data) > 0 { block, data = pem.Decode(data) if block == nil { break } if !strings.HasSuffix(block.Type, "CERTIFICATE REQUEST") { continue } csr, err := x509.ParseCertificateRequest(block.Bytes) if err != nil { return nil, &InvalidPEMError{ Type: PEMTypeCertificateRequest, Err: err, } } return csr, nil } } // DER format (binary) csr, err := x509.ParseCertificateRequest(data) if err != nil { return nil, &InvalidPEMError{ Message: fmt.Sprintf("error parsing certificate request as DER format: %v", err), Type: PEMTypeCertificateRequest, } } return csr, nil } // PEMType represents a PEM block type. (e.g., CERTIFICATE, CERTIFICATE REQUEST, etc.) type PEMType int func (pt PEMType) String() string { switch pt { case PEMTypeCertificate: return "certificate" case PEMTypeCertificateRequest: return "certificate request" default: return "undefined" } } const ( // PEMTypeUndefined undefined PEMTypeUndefined = iota // PEMTypeCertificate CERTIFICATE PEMTypeCertificate // PEMTypeCertificateRequest CERTIFICATE REQUEST PEMTypeCertificateRequest ) // InvalidPEMError represents an error that occurs when parsing a file with // PEM encoded data. type InvalidPEMError struct { Type PEMType File string Message string Err error } func (e *InvalidPEMError) Error() string { switch { case e.Message != "": return e.Message case e.Err != nil: return fmt.Sprintf("error decoding PEM data: %v", e.Err) default: if e.Type == PEMTypeUndefined { return "does not contain valid PEM encoded data" } return fmt.Sprintf("does not contain a valid PEM encoded %s", e.Type) } } func (e *InvalidPEMError) Unwrap() error { return e.Err } // 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) { // Populate options ctx := newContext(filename) if err := ctx.apply(opts); err != nil { return nil, err } bundle, err := ReadCertificateBundle(filename) switch { case err != nil: return nil, err case len(bundle) == 0: return nil, errors.Errorf("file %s does not contain a valid PEM or DER formatted certificate", filename) case len(bundle) > 1 && !ctx.firstBlock: return nil, errors.Errorf("error decoding %s: contains more than one PEM encoded block", filename) default: return bundle[0], nil } } // ReadCertificateBundle reads the given filename and returns a list of // *x509.Certificate. // // - supports PEM and DER certificate formats // - 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 } bundle, err := ParseCertificateBundle(b) if err != nil { return nil, fmt.Errorf("error parsing %s: %w", filename, err) } return bundle, nil } // ReadCertificateRequest reads the given filename and returns a // *x509.CertificateRequest. // // - supports PEM and DER Certificate formats. // - supports reading from STDIN with filename `-`. func ReadCertificateRequest(filename string) (*x509.CertificateRequest, error) { b, err := utils.ReadFile(filename) if err != nil { return nil, err } cr, err := ParseCertificateRequest(b) if err != nil { return nil, fmt.Errorf("error parsing %s: %w", filename, err) } return cr, nil } // 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 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") } var c ecdh.Curve switch w.Name { case ssh.KeyAlgoECDSA256: c = ecdh.P256() case ssh.KeyAlgoECDSA384: c = ecdh.P384() case ssh.KeyAlgoECDSA521: c = ecdh.P521() default: return nil, errors.Errorf("unsupported ecdsa curve %s", w.Name) } var p *ecdh.PublicKey if p, err = c.NewPublicKey(w.KeyBytes); err != nil { return nil, errors.Wrapf(err, "failed decoding %s key", w.Name) } // convert ECDH public key to ECDSA public key to keep // the returned type backwards compatible. rawKey := p.Bytes() switch p.Curve() { case ecdh.P256(): return &ecdsa.PublicKey{ Curve: elliptic.P256(), X: big.NewInt(0).SetBytes(rawKey[1:33]), Y: big.NewInt(0).SetBytes(rawKey[33:]), }, nil case ecdh.P384(): return &ecdsa.PublicKey{ Curve: elliptic.P384(), X: big.NewInt(0).SetBytes(rawKey[1:49]), Y: big.NewInt(0).SetBytes(rawKey[49:]), }, nil case ecdh.P521(): return &ecdsa.PublicKey{ Curve: elliptic.P521(), X: big.NewInt(0).SetBytes(rawKey[1:67]), Y: big.NewInt(0).SetBytes(rawKey[67:]), }, nil default: return nil, errors.New("cannot convert non-NIST *ecdh.PublicKey to *ecdsa.PublicKey") } 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("DSA keys not supported") default: return nil, errors.Errorf("unsupported key type %T", key) } } // BundleCertificate adds PEM-encoded certificates to a PEM-encoded certificate // bundle if not already in the bundle. func BundleCertificate(bundlePEM []byte, certsPEM ...[]byte) ([]byte, bool, error) { bundle, err := ParseCertificateBundle(bundlePEM) if err != nil { return nil, false, fmt.Errorf("invalid bundle: %w", err) } sums := make(map[[sha256.Size224]byte]bool, len(bundle)+len(certsPEM)) for i := range bundle { sums[sha256.Sum224(bundle[i].Raw)] = true } modified := false for i := range certsPEM { cert, err := ParseCertificate(certsPEM[i]) if err != nil { return nil, false, fmt.Errorf("invalid certificate %d: %w", i, err) } certSum := sha256.Sum224(cert.Raw) if sums[certSum] { continue } sums[certSum] = true bundlePEM = append(bundlePEM, certsPEM[i]...) modified = true } return bundlePEM, modified, nil } // UnbundleCertificate removes PEM-encoded certificates from a PEM-encoded // certificate bundle. func UnbundleCertificate(bundlePEM []byte, certsPEM ...[]byte) ([]byte, bool, error) { if len(certsPEM) == 0 { return bundlePEM, false, nil } drop := make(map[[sha256.Size224]byte]bool, len(certsPEM)) for i := range certsPEM { certs, err := ParseCertificateBundle(certsPEM[i]) if err != nil { return nil, false, fmt.Errorf("invalid certificate %d: %w", i, err) } for _, cert := range certs { drop[sha256.Sum224(cert.Raw)] = true } } var modified bool var keep []byte bundle, err := ParseCertificateBundle(bundlePEM) if err != nil { return nil, false, fmt.Errorf("invalid bundle: %w", err) } for _, cert := range bundle { sum := sha256.Sum224(cert.Raw) if drop[sum] { modified = true continue } keep = append(keep, pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: cert.Raw, })...) } return keep, modified, nil } crypto-0.57.0/pemutil/pem_test.go000066400000000000000000001244271474156331600167750ustar00rootroot00000000000000package 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 TestReadCertificate(t *testing.T) { tests := []struct { fn string opts []Options err error }{ {"testdata/ca.crt", nil, nil}, {"testdata/nonPEMHeaderCa.crt", nil, nil}, {"testdata/extrajunkbundle.crt", []Options{WithFirstBlock()}, 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 parsing testdata/badpem.crt: does not contain a valid PEM encoded certificate")}, {"testdata/badder.crt", nil, errors.New("error parsing testdata/badder.crt")}, {"testdata/openssl.p256.pem", nil, errors.New("error parsing testdata/openssl.p256.pem: does not contain a valid PEM encoded 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/nonPEMHeaderCa.crt", 1, nil}, {"testdata/ca.der", 1, nil}, {"testdata/bundle.crt", 2, nil}, {"testdata/extrajunkbundle.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 parsing testdata/badpem.crt: does not contain a valid PEM encoded certificate")}, {"testdata/badder.crt", 0, errors.New("error parsing testdata/badder.crt")}, {"testdata/openssl.p256.pem", 0, errors.New("error parsing testdata/openssl.p256.pem: does not contain a valid PEM encoded certificate")}, } for _, tc := range tests { t.Run(tc.fn, func(t *testing.T) { certs, err := ReadCertificateBundle(tc.fn) if tc.err != nil { if assert.Error(t, err, tc.fn, "- expected error but got nil") { 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 TestParseCertificateBundle(t *testing.T) { tests := []struct { name string fn string len int err error }{ {"ok cert", "testdata/ca.crt", 1, nil}, {"ok non PEM header", "testdata/nonPEMHeaderCa.crt", 1, nil}, {"ok der", "testdata/ca.der", 1, nil}, {"ok bundle", "testdata/bundle.crt", 2, nil}, {"ok extra junk in bundle", "testdata/extrajunkbundle.crt", 2, nil}, {"fail bad cert w/ file", "testdata/badca.crt", 0, errors.New("error decoding PEM data: x509: trailing data")}, {"fail no PEM", "testdata/badpem.crt", 0, errors.New("does not contain a valid PEM encoded certificate")}, {"fail no PEM", "testdata/badpem.crt", 0, errors.New("does not contain a valid PEM encoded certificate")}, {"fail bad der", "testdata/badder.crt", 0, errors.New("error parsing certificate as DER format: x509: malformed certificate")}, {"fail no valid PEM", "testdata/openssl.p256.pem", 0, errors.New("does not contain a valid PEM encoded certificate")}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { b, err := os.ReadFile(tc.fn) if err != nil { t.Fatal(err) } certs, err := ParseCertificateBundle(b) if tc.err != nil { if assert.Error(t, err, tc.fn, "- expected error but got nil") { 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 TestParseCertificateRequest(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 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) { b, err := os.ReadFile(tt.args.filename) if err != nil { t.Fatal(err) } got, err := ParseCertificateRequest(b) if (err != nil) != tt.wantErr { t.Errorf("ParseCertificateRequest() 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("ParseCertificateRequest() = \n%#v, want \n%#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) } }) } } func TestBundleCertificate(t *testing.T) { tests := []struct { name string bundle string certs []string added bool err error }{ {"append", "testdata/bundle.crt", []string{"testdata/ca.crt"}, true, nil}, {"two", "testdata/bundle.crt", []string{"testdata/ca.crt", "testdata/ca2.crt"}, true, nil}, {"none", "testdata/bundle.crt", nil, false, nil}, {"found", "testdata/ca.crt", []string{"testdata/ca.crt"}, false, nil}, {"bad cert", "testdata/bundle.crt", []string{"testdata/badca.crt"}, false, errors.New("invalid certificate 0")}, {"bad bundle", "testdata/badca.crt", []string{"testdata/ca.crt"}, false, errors.New("invalid bundle")}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { bundlePEM, err := os.ReadFile(tc.bundle) if err != nil { t.Fatal(err) } certsPEM := make([][]byte, len(tc.certs)) for i, fn := range tc.certs { certsPEM[i], err = os.ReadFile(fn) if err != nil { t.Fatal(err) } } got, added, err := BundleCertificate(bundlePEM, certsPEM...) if tc.err != nil { if assert.Error(t, err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { assert.NoError(t, err) assert.Equals(t, tc.added, added) if added { assert.NotEquals(t, bundlePEM, got) } else { assert.Equals(t, bundlePEM, got) } } }) } } func TestUnbundleCertificate(t *testing.T) { tests := []struct { name string bundle string certs []string wantBundle string modified bool err error }{ {"remove one leave one", "testdata/bundle.crt", []string{"testdata/bundle-1st.crt"}, "testdata/bundle-2nd.crt", true, nil}, {"remove two leave none", "testdata/bundle.crt", []string{"testdata/bundle-1st.crt", "testdata/bundle-2nd.crt"}, "", true, nil}, {"remove none", "testdata/bundle.crt", []string{"testdata/ca.crt"}, "testdata/bundle.crt", false, nil}, {"none to remove", "testdata/bundle.crt", []string{}, "testdata/bundle.crt", false, nil}, {"remove bundle", "testdata/bundle.crt", []string{"testdata/bundle.crt"}, "", true, nil}, {"bad cert", "testdata/bundle.crt", []string{"testdata/badca.crt"}, "", false, errors.New("invalid certificate 0")}, {"bad bundle", "testdata/badca.crt", []string{"testdata/ca.crt"}, "", false, errors.New("invalid bundle")}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { bundlePEM, err := os.ReadFile(tc.bundle) if err != nil { t.Fatal(err) } certsPEM := make([][]byte, len(tc.certs)) for i, fn := range tc.certs { certsPEM[i], err = os.ReadFile(fn) if err != nil { t.Fatal(err) } } got, modified, err := UnbundleCertificate(bundlePEM, certsPEM...) if tc.err != nil { if assert.Error(t, err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { assert.NoError(t, err) assert.Equals(t, tc.modified, modified) if tc.wantBundle == "" { assert.Nil(t, got) } else { want, err := os.ReadFile(tc.wantBundle) if err != nil { t.Fatal(err) } assert.Equals(t, strings.TrimSpace(string(want)), strings.TrimSpace(string(got))) } } }) } } crypto-0.57.0/pemutil/pkcs8.go000066400000000000000000000244571474156331600162070ustar00rootroot00000000000000package 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. // // 600k is the current OWASP recommendation (Dec 2022) // https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 // // Nist recommends at least 10k (800-63B), 1Password increased in 2023 the // number of iterations from 100k to 650k. const PBKDF2Iterations = 600000 // 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" { 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 asn1 data pki := encryptedPrivateKeyInfo{ Algo: encryptedlAlgorithmIdentifier{ Algorithm: oidPBES2, Parameters: pbes2Params{ KeyDerivationFunc: pbkdf2Algorithms{ Algo: oidPKCS5PBKDF2, PBKDF2Params: pbkdf2Params{ Salt: salt, IterationCount: PBKDF2Iterations, PrfParam: prfParam{ Algo: oidHMACWithSHA256, NullParam: asn1.NullRawValue, }, }, }, 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 } crypto-0.57.0/pemutil/pkcs8_test.go000066400000000000000000000142051474156331600172340ustar00rootroot00000000000000package 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) } }) } } crypto-0.57.0/pemutil/ssh.go000066400000000000000000000160111474156331600157370ustar00rootroot00000000000000// 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/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) } p, err := k.PublicKey.ECDH() if err != nil { return nil, errors.Wrapf(err, "failed converting *ecdsa.PublicKey to *ecdh.PublicKey") } // Marshal public key. pubKey := struct { KeyType string Curve string Pub []byte }{ keyType, curve, p.Bytes(), } w.PubKey = ssh.Marshal(pubKey) // Marshal private key. key := struct { Curve string Pub []byte D *big.Int Comment string }{ curve, p.Bytes(), 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 } crypto-0.57.0/pemutil/testdata/000077500000000000000000000000001474156331600164255ustar00rootroot00000000000000crypto-0.57.0/pemutil/testdata/bad.csr000066400000000000000000000001351474156331600176630ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- Bad Certificate Request -----END CERTIFICATE REQUEST-----crypto-0.57.0/pemutil/testdata/badca.crt000066400000000000000000000043651474156331600202010ustar00rootroot00000000000000-----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----- crypto-0.57.0/pemutil/testdata/badder.crt000066400000000000000000000031621474156331600203620ustar00rootroot000000000000000‚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&crypto-0.57.0/pemutil/testdata/badnebula.key000066400000000000000000000006471474156331600210630ustar00rootroot00000000000000-----BEGIN NEBULA X25519 PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACBRf06YlDi/CVnLEDMTlxWH+OFojR1Y1d3rw7Guu1EKVQAAAJgG8EtjBvBL YwAAAAtzc2gtZWQyNTUxOQAAACBRf06YlDi/CVnLEDMTlxWH+OFojR1Y1d3rw7Guu1EKVQ AAAECHHCRxaxuDUzPhwiXZovjvMLYkLiSx+ho3b+9gavl031F/TpiUOL8JWcsQMxOXFYf4 4WiNHVjV3evDsa67UQpVAAAAE21hcmlhbm9AZW5kb3IubG9jYWwBAg== -----END NEBULA X25519 PRIVATE KEY----- crypto-0.57.0/pemutil/testdata/badnebula.pub000066400000000000000000000006451474156331600210570ustar00rootroot00000000000000-----BEGIN NEBULA X25519 PUBLIC KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACBRf06YlDi/CVnLEDMTlxWH+OFojR1Y1d3rw7Guu1EKVQAAAJgG8EtjBvBL YwAAAAtzc2gtZWQyNTUxOQAAACBRf06YlDi/CVnLEDMTlxWH+OFojR1Y1d3rw7Guu1EKVQ AAAECHHCRxaxuDUzPhwiXZovjvMLYkLiSx+ho3b+9gavl031F/TpiUOL8JWcsQMxOXFYf4 4WiNHVjV3evDsa67UQpVAAAAE21hcmlhbm9AZW5kb3IubG9jYWwBAg== -----END NEBULA X25519 PUBLIC KEY----- crypto-0.57.0/pemutil/testdata/badpem.crt000066400000000000000000000043621474156331600203740ustar00rootroot00000000000000-----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----- crypto-0.57.0/pemutil/testdata/badpem.csr000066400000000000000000000005561474156331600203740ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- MAHYMIGAAgEAMB4xHDAaBgNVBAMTE2hlbGxvLnNtYWxsc3RlcC5jb20wWTATBgcq hkjOPQIBBggqhkjOPQMBBwNCAASKj0MvK6CUzFqRLfDTQNQp0Zt5dcHYx+Dq1Wh9 5dhqf1Fu9+1m5+LKkgBWoZmo7sJH0RuSjIdv/sZwpBrkdn2soAAwCgYIKoZIzj0E AwIDRwAwRAIgZgz9gdx9inOp6bSX4EkYiUCyLV9xGvabovu5C9UkRr8CIBGBbkp0 l4tesAKoXelsLygJjPuUGRLK+OtdjPBIN1Zo -----END CERTIFICATE REQUEST-----crypto-0.57.0/pemutil/testdata/bundle-1st.crt000066400000000000000000000031361474156331600211200ustar00rootroot00000000000000-----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-----crypto-0.57.0/pemutil/testdata/bundle-2nd.crt000066400000000000000000000031561474156331600210760ustar00rootroot00000000000000-----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-----crypto-0.57.0/pemutil/testdata/bundle.crt000066400000000000000000000063151474156331600204150ustar00rootroot00000000000000-----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-----crypto-0.57.0/pemutil/testdata/ca.crt000066400000000000000000000041021474156331600175170ustar00rootroot00000000000000-----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----- crypto-0.57.0/pemutil/testdata/ca.der000066400000000000000000000027571474156331600175170ustar00rootroot000000000000000‚๋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%Œ ฆkcrypto-0.57.0/pemutil/testdata/ca2.crt000066400000000000000000000010361474156331600176040ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBYjCCAQigAwIBAgIRAPbtfP0mbPikhw3/xm4ZOfAwCgYIKoZIzj0EAwIwDzEN MAsGA1UEAxMEcm9vdDAeFw0yMzA0MTExNTMwMTJaFw0zMzA0MDgxNTMwMTJaMA8x DTALBgNVBAMTBHJvb3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARrIw/HltUP IJijX+enYDM/ki80qz40E9JxbQTqlW/P/C/19Vj6tqWat/+YzdxVxFqf9AamBoZ6 Gjy5+VqxifcEo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIB ATAdBgNVHQ4EFgQUZX1K+aVs2nulegBQ2wgv3wkXTmAwCgYIKoZIzj0EAwIDSAAw RQIgOj+jv1dpCLWDmGbGjfwFO9ZL+NL+n2SI+qrA40ogLj8CIQDKX6EOwubb7xXj wJRCXLDKXYC/OUpLCT2FJsSTupRB9A== -----END CERTIFICATE----- crypto-0.57.0/pemutil/testdata/cosign.enc.pem000066400000000000000000000012111474156331600211510ustar00rootroot00000000000000-----BEGIN ENCRYPTED COSIGN PRIVATE KEY----- eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6 OCwicCI6MX0sInNhbHQiOiJuaExoelFuMGFxUUVidEtCaGM0SENYRHRoNVVKaFdq Z2pTaDhnKzRXZURJPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 Iiwibm9uY2UiOiI1Uk95VEkwTHRETkdvbjJkVytLWnhrOHE0L2hQZEc0diJ9LCJj aXBoZXJ0ZXh0IjoiQi9UUjVvSE1qNEdoNmJmYlNOM0xpZnpMRjBma0dzbDVSZksx ZUs3bFlPSUZMWjdnekRZS1htbG16aGtmREM4L0ZJZWQ3eGlOUnQ1TW5YcnZFVjlU TTBkOWoxQnhyRytEM1RyNUZsNFJ0dmdaN3F1U0FJaytwWDVscVZ2ZHNMRVNiNkdQ QWxFQXYyeVl6R1JrL2tpbjQvcUtUOGlDcVYxS2RQZ2lCUjJ1QWM2VHc2OEZLMzda Q0pndHUrM0xCU2pXRENicGsweWNlcGIycFE9PSJ9 -----END ENCRYPTED COSIGN PRIVATE KEY----- crypto-0.57.0/pemutil/testdata/cosign.pem000066400000000000000000000003611474156331600204120ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgTJZPa2U7YR7hhEui 91pZ8qtW9dtqllRv8NdRmQ8LpTihRANCAATbqGmZwdrYJhrPuWBNYosE2VK/YTI/ FKIsYbzL1eTZY5erZaXWdo++tbnlnV+T5d4HWF6mOjImcboGPwzWXpcq -----END PRIVATE KEY----- crypto-0.57.0/pemutil/testdata/cosign.pub.pem000066400000000000000000000002621474156331600211770ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE26hpmcHa2CYaz7lgTWKLBNlSv2Ey PxSiLGG8y9Xk2WOXq2Wl1naPvrW55Z1fk+XeB1hepjoyJnG6Bj8M1l6XKg== -----END PUBLIC KEY----- crypto-0.57.0/pemutil/testdata/extrajunkbundle.crt000066400000000000000000000036771474156331600223610ustar00rootroot00000000000000 This is junk -----BEGIN CERTIFICATE----- MIICYzCCAgmgAwIBAgIQXS+eYTIGURSsxpqk0JL1sjAKBggqhkjOPQQDAjBKMRsw GQYDVQQKExJFbnRyeVBvaW50TmV0d29ya3MxKzApBgNVBAMTIkVudHJ5UG9pbnRO ZXR3b3JrcyBJbnRlcm1lZGlhdGUgQ0EwHhcNMjQwNDEyMDQxODMwWhcNMjQwNDEz MDQxOTMwWjBBMRAwDgYDVQQGEwdFc3RvbmlhMQwwCgYDVQQDEwNmb28xHzAdBgkq hkiG9w0BCQEWEGphbmVAZXhhbXBsZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMB BwNCAAQfwDdJQSLvCSmxIURwmrhH7cypActF5D69Eqks4ifDrI6JlMt1UEGEYjMA 6QxhbwmoYOTlT2FKwuOno4b6HNgxo4HZMIHWMB8GA1UdIwQYMBaAFLA+ZPEOXQ60 VW4C9z0yADaawPTxMA4GA1UdDwEB/wQEAwIHgDAdBgNVHSUEFjAUBggrBgEFBQcD AQYIKwYBBQUHAwIwHQYDVR0OBBYEFPCfF90MSKLBT9td1DYvywcKqn1hMA4GA1Ud EQQHMAWCA2ZvbzBVBgwrBgEEAYKkZMYoQAEERTBDAgEBBBFtYXhAc21hbGxzdGVw LmNvbQQrM2lxcWtabWhTQmxLZ0RuU25SN3NUQndWVHlvdUU3NzRJam5KTy05VGd0 RTAKBggqhkjOPQQDAgNIADBFAiAXk5RA2ZkeSBSQv/ECZIhVDJ/55DllYzD3KaLL pMZNoQIhAMEnUi5YcKjhmwFHgwOfWQGO2n+K+bOzmlfX3rJ0dnxQ -----END CERTIFICATE----- -----BEGIN EC PRIVATE KEY----- MHcCAQEEIN6JaoHeDwxnp7pAh27vTdtjxpWYCzm005DcU3a/bKOIoAoGCCqGSM49 AwEHoUQDQgAEH8A3SUEi7wkpsSFEcJq4R+3MqQHLReQ+vRKpLOInw6yOiZTLdVBB hGIzAOkMYW8JqGDk5U9hSsLjp6OG+hzYMQ== -----END EC PRIVATE KEY----- This is more junk -----BEGIN CERTIFICATE----- MIIB7zCCAZagAwIBAgIQbo4vKv5fIjI6G3q/yg4m/jAKBggqhkjOPQQDAjBCMRsw GQYDVQQKExJFbnRyeVBvaW50TmV0d29ya3MxIzAhBgNVBAMTGkVudHJ5UG9pbnRO ZXR3b3JrcyBSb290IENBMB4XDTIyMDIxNTE3MDYwMVoXDTMyMDIxMzE3MDYwMVow SjEbMBkGA1UEChMSRW50cnlQb2ludE5ldHdvcmtzMSswKQYDVQQDEyJFbnRyeVBv aW50TmV0d29ya3MgSW50ZXJtZWRpYXRlIENBMFkwEwYHKoZIzj0CAQYIKoZIzj0D AQcDQgAEiRaPNJDxVKKe8yapu4d35KhPRpvsfwE+OqmJg4w027fILyUmoNE4DCqj dnSl7RUks34P3eU4/F+A3PEEkt5J66NmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1Ud EwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLA+ZPEOXQ60VW4C9z0yADaawPTxMB8G A1UdIwQYMBaAFMi91HWiK5mnfzS7MkfI1cMrTUe/MAoGCCqGSM49BAMCA0cAMEQC ICfmZ03AxBJQ93DzcAgROH2T/M8eWBEKJQzLM7lNVMlpAiB4xDPbXPaRzGpCzXSq HIn8lljM3V0hfX4DjVf4B1Mayg== -----END CERTIFICATE----- /// -----BEGIN EC PARAMETERS----- BgUrgQQACg== -----END EC PARAMETERS----- So much junk in this file crypto-0.57.0/pemutil/testdata/generate.sh000077500000000000000000000104501474156331600205560ustar00rootroot00000000000000#!/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 crypto-0.57.0/pemutil/testdata/keytool.csr000066400000000000000000000005661474156331600206330ustar00rootroot00000000000000-----BEGIN NEW CERTIFICATE REQUEST----- MIHYMIGAAgEAMB4xHDAaBgNVBAMTE2hlbGxvLnNtYWxsc3RlcC5jb20wWTATBgcq hkjOPQIBBggqhkjOPQMBBwNCAASKj0MvK6CUzFqRLfDTQNQp0Zt5dcHYx+Dq1Wh9 5dhqf1Fu9+1m5+LKkgBWoZmo7sJH0RuSjIdv/sZwpBrkdn2soAAwCgYIKoZIzj0E AwIDRwAwRAIgZgz9gdx9inOp6bSX4EkYiUCyLV9xGvabovu5C9UkRr8CIBGBbkp0 l4tesAKoXelsLygJjPuUGRLK+OtdjPBIN1Zo -----END NEW CERTIFICATE REQUEST-----crypto-0.57.0/pemutil/testdata/nebula.key000066400000000000000000000001771474156331600204120ustar00rootroot00000000000000-----BEGIN NEBULA X25519 PRIVATE KEY----- sc9k10IOJEFg9QDXEAFqkDgVQ3KfuubYHfG+Xl2ODbs= -----END NEBULA X25519 PRIVATE KEY----- crypto-0.57.0/pemutil/testdata/nebula.pub000066400000000000000000000001751474156331600204060ustar00rootroot00000000000000-----BEGIN NEBULA X25519 PUBLIC KEY----- fH8U8+JEY6azHXHOwRoburcf25WG/ueKxvQ7sQrUVA8= -----END NEBULA X25519 PUBLIC KEY----- crypto-0.57.0/pemutil/testdata/nonPEMHeaderCa.crt000066400000000000000000000037651474156331600216630ustar00rootroot00000000000000Certificate: Data: Version: 3 (0x2) Serial Number: fe:9f:60:35:b0:13:ba:d4:be:fb:84:ec:70:ed:3d:ed Signature Algorithm: ecdsa-with-SHA256 Issuer: CN = testroot Validity Not Before: Aug 15 17:50:02 2023 GMT Not After : Aug 16 17:50:02 2023 GMT Subject: CN = testroot Subject Public Key Info: Public Key Algorithm: id-ecPublicKey Public-Key: (256 bit) pub: 04:bf:f8:db:3c:7a:c2:a4:b2:f5:51:e9:43:80:98: 68:b2:1b:62:25:11:a7:de:03:74:27:6a:04:82:99: 9a:7d:da:e7:a6:a6:a5:12:ad:c9:de:65:61:12:00: a6:6d:39:b6:46:d8:f5:b4:a8:77:69:da:1f:35:2b: 28:70:33:1e:90 ASN1 OID: prime256v1 NIST CURVE: P-256 X509v3 extensions: X509v3 Key Usage: critical Certificate Sign, CRL Sign X509v3 Basic Constraints: critical CA:TRUE, pathlen:1 X509v3 Subject Key Identifier: 38:E0:29:66:91:8C:E3:42:0F:85:A5:BA:78:B6:1F:49:E3:34:33:D5 Signature Algorithm: ecdsa-with-SHA256 Signature Value: 30:45:02:20:37:78:01:d9:e7:4d:9c:2c:2f:3c:09:41:8a:2f: 3f:65:b5:6a:31:eb:10:a9:92:a0:74:98:d6:9c:50:45:1c:56: 02:21:00:bf:b5:53:37:97:f0:f5:14:df:22:00:22:47:4f:7d: 41:c5:5d:b6:24:58:e4:d2:09:b1:3d:9b:7b:0f:3f:cb:ba -----BEGIN CERTIFICATE----- MIIBajCCARCgAwIBAgIRAP6fYDWwE7rUvvuE7HDtPe0wCgYIKoZIzj0EAwIwEzER MA8GA1UEAxMIdGVzdHJvb3QwHhcNMjMwODE1MTc1MDAyWhcNMjMwODE2MTc1MDAy WjATMREwDwYDVQQDEwh0ZXN0cm9vdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA BL/42zx6wqSy9VHpQ4CYaLIbYiURp94DdCdqBIKZmn3a56ampRKtyd5lYRIApm05 tkbY9bSod2naHzUrKHAzHpCjRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8E CDAGAQH/AgEBMB0GA1UdDgQWBBQ44ClmkYzjQg+Fpbp4th9J4zQz1TAKBggqhkjO PQQDAgNIADBFAiA3eAHZ502cLC88CUGKLz9ltWox6xCpkqB0mNacUEUcVgIhAL+1 UzeX8PUU3yIAIkdPfUHFXbYkWOTSCbE9m3sPP8u6 -----END CERTIFICATE----- crypto-0.57.0/pemutil/testdata/openssh.ed25519.enc.pem000066400000000000000000000007201474156331600223470ustar00rootroot00000000000000-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABC9Pd+JT/ 54ZI1CUG/rb3D1AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIFF/TpiUOL8JWcsQ MxOXFYf44WiNHVjV3evDsa67UQpVAAAAoL35/C3KKjC+Z6fVLWlvDqYnhyNdic4H7O2XGj yg9h9BBQGKAK5MdNkE9xtSffw9v6wIJrLQTyoqvE2dyaTcqeW+vn6iDypF4f9gzrwdZz6j YD8Y392OcNwZF4JcoT3nvrkQ2sCRHDbQbYID+N80ml5VbijvRd2b6YiFJMZO2nwUtPqVCt RYRBsy1/0m6740+SRTztB4DqkNdZO41zH25WM= -----END OPENSSH PRIVATE KEY----- crypto-0.57.0/pemutil/testdata/openssh.ed25519.pem000066400000000000000000000006331474156331600216060ustar00rootroot00000000000000-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACBRf06YlDi/CVnLEDMTlxWH+OFojR1Y1d3rw7Guu1EKVQAAAJgG8EtjBvBL YwAAAAtzc2gtZWQyNTUxOQAAACBRf06YlDi/CVnLEDMTlxWH+OFojR1Y1d3rw7Guu1EKVQ AAAECHHCRxaxuDUzPhwiXZovjvMLYkLiSx+ho3b+9gavl031F/TpiUOL8JWcsQMxOXFYf4 4WiNHVjV3evDsa67UQpVAAAAE21hcmlhbm9AZW5kb3IubG9jYWwBAg== -----END OPENSSH PRIVATE KEY----- crypto-0.57.0/pemutil/testdata/openssh.ed25519.pub.pem000066400000000000000000000001451474156331600223710ustar00rootroot00000000000000ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFF/TpiUOL8JWcsQMxOXFYf44WiNHVjV3evDsa67UQpV mariano@endor.local crypto-0.57.0/pemutil/testdata/openssh.p256.enc.pem000066400000000000000000000010551474156331600220470ustar00rootroot00000000000000-----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----- crypto-0.57.0/pemutil/testdata/openssh.p256.pem000066400000000000000000000010011474156331600212720ustar00rootroot00000000000000-----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----- crypto-0.57.0/pemutil/testdata/openssh.p256.pub.pem000066400000000000000000000002651474156331600220720ustar00rootroot00000000000000ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBI3kt38MXLwskrTKtaD5HKGrj+J8evJDIJB5PhRdIH5QZ1Fy3uRYDSEBFtNpIKH7xT8azk74iQM2hMpWyDEpQxA= mariano@endor.local crypto-0.57.0/pemutil/testdata/openssh.p384.enc.pem000066400000000000000000000012331474156331600220470ustar00rootroot00000000000000-----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----- crypto-0.57.0/pemutil/testdata/openssh.p384.pem000066400000000000000000000011561474156331600213070ustar00rootroot00000000000000-----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----- crypto-0.57.0/pemutil/testdata/openssh.p384.pub.pem000066400000000000000000000003411474156331600220670ustar00rootroot00000000000000ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBNlvZXMQ+zY03+LWod10pNV/CgirdN895D+RxYG+4JUl8jKC+mb0x66xrAQ+4tIRfCI0N7D5gdn3Eci8WMKZmwu4Q7O6H230qi3Aznsf7s35EMXOEzaPyEBo65PlcMsp9Q== mariano@endor.local crypto-0.57.0/pemutil/testdata/openssh.p521.enc.pem000066400000000000000000000014411474156331600220410ustar00rootroot00000000000000-----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----- crypto-0.57.0/pemutil/testdata/openssh.p521.pem000066400000000000000000000013541474156331600213000ustar00rootroot00000000000000-----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----- crypto-0.57.0/pemutil/testdata/openssh.p521.pub.pem000066400000000000000000000004211474156331600220570ustar00rootroot00000000000000ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAErMjdBKnVrZ7XMy6VmXW4UXVjYOe8tIjAS7zfuE5XV6lgSA294CEY2bntO4Gpi2dl5V93anNrPsIpfo3gHfc3VcAFDk2COLuUXZ1RuaE2omgyHjeKgixA0FoF6eGZGlg/p/mTw//slkwiwuGQmTaHKRvyZpJn4Hc9jA8w0F1K8/7SwdA== mariano@endor.local crypto-0.57.0/pemutil/testdata/openssh.rsa1024.enc.pem000066400000000000000000000021111474156331600224410ustar00rootroot00000000000000-----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----- crypto-0.57.0/pemutil/testdata/openssh.rsa1024.pem000066400000000000000000000020241474156331600217000ustar00rootroot00000000000000-----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----- crypto-0.57.0/pemutil/testdata/openssh.rsa1024.pub.pem000066400000000000000000000003511474156331600224660ustar00rootroot00000000000000ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDfgz0N32eMQMILbjdo/CdndvIC3r7L12Ch0IO77B4g2aW7Mai1jNx/Hrz7ssvSujypbw6d8TBmNszbp+J4coVHgdZZuz8sKpAKmiNgZVqtp2+SzTsPpzGvfxsIcPOLEk+2MHdUdhLWacB35ifqSW9tY2E1nA/XVub/pPFp5dccxQ== mariano@endor.local crypto-0.57.0/pemutil/testdata/openssh.rsa2048.enc.pem000066400000000000000000000035241474156331600224610ustar00rootroot00000000000000-----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----- crypto-0.57.0/pemutil/testdata/openssh.rsa2048.pem000066400000000000000000000034471474156331600217210ustar00rootroot00000000000000-----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----- crypto-0.57.0/pemutil/testdata/openssh.rsa2048.pub.pem000066400000000000000000000006211474156331600224750ustar00rootroot00000000000000ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCum4bMH4Bm/vkPlk/qbBMrFlYUzVlUQnsBYunNREZb7itdBRau9nujSYhTbdtwMgH0gEnXp9gj/Hcvz3YdGYM/cOXZ334CbmTQ0cqiV90d2Qf03zDZS7ezv7Kamq0vdkA0Npw5lTOmZKnQcNibBAqcp2BeKSDIYGMTZMsEHOHsPX9jdwpEU4QevKAszjTF/UaPNLVpfMsEPuV9D8l02GPVg8HZXMNZt483nnN3MMnEqIuqRurEHz7xS9U5Rtqf45cQeFEcDXJa2ggVmLVfpHim4qHReA3qSz5QigvAR0I3XrBKv5MhLH0NPDawzeAgF0HR18CAkp+/KAP3CA6FXWWt mariano@endor.local crypto-0.57.0/pemutil/testdata/openssl.p256.enc.pem000066400000000000000000000004721474156331600220550ustar00rootroot00000000000000-----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----- crypto-0.57.0/pemutil/testdata/openssl.p256.pem000066400000000000000000000003431474156331600213060ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MHcCAQEEIOWt6OMAASXS1jJ/Sn2vY8ciPDU74cqjq3J13oYUNeNuoAoGCCqGSM49 AwEHoUQDQgAEp2gPSjWPnX25PYBYpMjl+D2VAI2Smm1pxaMQw5x974BnEg25Axaf 3yN//SwJ3X1Ju9gwfhhHmpqYKzQ/reyNOw== -----END EC PRIVATE KEY----- crypto-0.57.0/pemutil/testdata/openssl.p256.pub.pem000066400000000000000000000002621474156331600220730ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEp2gPSjWPnX25PYBYpMjl+D2VAI2S mm1pxaMQw5x974BnEg25Axaf3yN//SwJ3X1Ju9gwfhhHmpqYKzQ/reyNOw== -----END PUBLIC KEY----- crypto-0.57.0/pemutil/testdata/openssl.p384.enc.pem000066400000000000000000000005331474156331600220550ustar00rootroot00000000000000-----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----- crypto-0.57.0/pemutil/testdata/openssl.p384.pem000066400000000000000000000004401474156331600213060ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MIGkAgEBBDBWB93doUgi7ZK78Q0ORM7RB9oSqT3cVb6HZvt7fF+/1Vj03Q8UlHSL Mf+4znN/hmSgBwYFK4EEACKhZANiAASKwhzVNcBMvm/lqs10b2I0wuEcEIrqgaC0 V9Y3+uhTwusUZjVPMmHSWBM2Zn10FaCXG6ypHEsiqjMA6SdJBMGm3bz4lG8wPdPk +B+zVA7chnULzBOuSgIpeehquiCJm/g= -----END EC PRIVATE KEY----- crypto-0.57.0/pemutil/testdata/openssl.p384.pub.pem000066400000000000000000000003271474156331600220770ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEisIc1TXATL5v5arNdG9iNMLhHBCK6oGg tFfWN/roU8LrFGY1TzJh0lgTNmZ9dBWglxusqRxLIqozAOknSQTBpt28+JRvMD3T 5Pgfs1QO3IZ1C8wTrkoCKXnoarogiZv4 -----END PUBLIC KEY----- crypto-0.57.0/pemutil/testdata/openssl.p521.enc.pem000066400000000000000000000006551474156331600220530ustar00rootroot00000000000000-----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----- crypto-0.57.0/pemutil/testdata/openssl.p521.pem000066400000000000000000000005551474156331600213060ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MIHcAgEBBEIAZN3UZLUtCo0LFftfdbzcmEz49jtKcnuPQ+wi9ndf+W16oihuEUay 589hKy9MPSzuR2dv4+9NMLBlMS/wqg9DsMegBwYFK4EEACOhgYkDgYYABAFm5qul C8kBmuQrQxl/730Og7a4XjhNJ0pAEtmXeqIyCUaXgSakh7ET/vezAhSxYeRFuF/x cnZMOgVVX6IYnbr+lAC66zy0CovZ+3QNTmyiwEQijHx1n8PXmuSwS60ZYTc9Dii/ Hs3Df60xU/iEdOv+XtJqzhbeuhD1KU1VIt2EcAu6Ig== -----END EC PRIVATE KEY----- crypto-0.57.0/pemutil/testdata/openssl.p521.pub.pem000066400000000000000000000004141474156331600220650ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBZuarpQvJAZrkK0MZf+99DoO2uF44 TSdKQBLZl3qiMglGl4EmpIexE/73swIUsWHkRbhf8XJ2TDoFVV+iGJ26/pQAuus8 tAqL2ft0DU5sosBEIox8dZ/D15rksEutGWE3PQ4ovx7Nw3+tMVP4hHTr/l7Sas4W 3roQ9SlNVSLdhHALuiI= -----END PUBLIC KEY----- crypto-0.57.0/pemutil/testdata/openssl.rsa1024.enc.pem000066400000000000000000000017061474156331600224560ustar00rootroot00000000000000-----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----- crypto-0.57.0/pemutil/testdata/openssl.rsa1024.pem000066400000000000000000000015671474156331600217170ustar00rootroot00000000000000-----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----- crypto-0.57.0/pemutil/testdata/openssl.rsa1024.pub.pem000066400000000000000000000004201474156331600224670ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCztZjbIWsnC88Npdq6YHwKi41O YPM8wD4Bcn7TU4NcEh5zLiQ9rpxoAxKmxZtSFD7PbTkQkNa8rHN5vX9qWrrwrAIx X/vAiVo+ZvSNoPeG1Bn18xJ34oLPUUv3IkVCSBFWcZ1NBUhde+KabeaGKNb3bHM+ 4btBb4TLZwhh6EC6RwIDAQAB -----END PUBLIC KEY----- crypto-0.57.0/pemutil/testdata/openssl.rsa2048.enc.pem000066400000000000000000000033461474156331600224670ustar00rootroot00000000000000-----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----- crypto-0.57.0/pemutil/testdata/openssl.rsa2048.pem000066400000000000000000000032171474156331600217200ustar00rootroot00000000000000-----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----- crypto-0.57.0/pemutil/testdata/openssl.rsa2048.pub.pem000066400000000000000000000007031474156331600225020ustar00rootroot00000000000000-----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----- crypto-0.57.0/pemutil/testdata/password.txt000066400000000000000000000000121474156331600210210ustar00rootroot00000000000000mypasswordcrypto-0.57.0/pemutil/testdata/pkcs8/000077500000000000000000000000001474156331600174555ustar00rootroot00000000000000crypto-0.57.0/pemutil/testdata/pkcs8/openssl.ed25519.der000066400000000000000000000000601474156331600226250ustar00rootroot000000000000000.0+ep" ะฐอ{†าXฅEoำ”h‰๎" –/-)”˜#ส SHA256: ( SHA256: ( SHA256: ( 0 { return fmt.Errorf("failed deleting AK %q because %d key(s) exist that were attested by it", name, len(keys)) } if err := t.attestTPM.DeleteKey(ak.Data); err != nil { // TODO: we could add a DeleteAK to go-attestation; under the hood it's loaded the same as a key though. return fmt.Errorf("failed deleting AK %q: %w", name, err) } if err := t.store.DeleteAK(name); err != nil { return fmt.Errorf("failed deleting AK %q from storage: %w", name, err) } if err := t.store.Persist(); err != nil { return fmt.Errorf("failed persisting storage: %w", err) } return } // AttestationParameters returns information about the AK, typically used to // generate a credential activation challenge. func (ak *AK) AttestationParameters(ctx context.Context) (params attest.AttestationParameters, err error) { if ak.attestParams != nil { return *ak.attestParams, nil } if err = ak.tpm.open(ctx); err != nil { return params, fmt.Errorf("failed opening TPM: %w", err) } defer closeTPM(ctx, ak.tpm, &err) loadedAK, err := ak.tpm.attestTPM.LoadAK(ak.data) if err != nil { return params, fmt.Errorf("failed loading AK %q: %w", ak.name, err) } defer loadedAK.Close(ak.tpm.attestTPM) params = loadedAK.AttestationParameters() ak.attestParams = ¶ms return } // EncryptedCredential represents encrypted parameters which must be activated // against a key. type EncryptedCredential attest.EncryptedCredential // ActivateCredential decrypts the secret using the key to prove that the AK was // generated on the same TPM as the EK. This operation is synonymous with // TPM2_ActivateCredential. func (ak *AK) ActivateCredential(ctx context.Context, in EncryptedCredential) (secret []byte, err error) { if err := ak.tpm.open(ctx); err != nil { return secret, fmt.Errorf("failed opening TPM: %w", err) } defer closeTPM(ctx, ak.tpm, &err) loadedAK, err := ak.tpm.attestTPM.LoadAK(ak.data) if err != nil { return secret, fmt.Errorf("failed loading AK %q: %w", ak.name, err) } defer loadedAK.Close(ak.tpm.attestTPM) secret, err = loadedAK.ActivateCredential(ak.tpm.attestTPM, attest.EncryptedCredential(in)) return } // Blobs returns a container for the private and public AK blobs. // The resulting blobs are compatible with tpm2-tools, so can be used // like this (after having been written to ak.priv and ak.pub): // // tpm2_load -C 0x81000001 -u ak.pub -r ak.priv -c ak.ctx func (ak *AK) Blobs(ctx context.Context) (blobs *Blobs, err error) { if ak.blobs != nil { return ak.blobs, nil } if err = ak.tpm.open(ctx); err != nil { return nil, fmt.Errorf("failed opening TPM: %w", err) } defer closeTPM(ctx, ak.tpm, &err) aak, err := ak.tpm.attestTPM.LoadAK(ak.data) if err != nil { return nil, fmt.Errorf("failed loading AK: %w", err) } defer aak.Close(ak.tpm.attestTPM) public, private, err := aak.Blobs() if err != nil { return nil, fmt.Errorf("failed getting AK blobs: %w", err) } ak.setBlobs(private, public) return ak.blobs, nil } // SetCertificateChain associates an X.509 certificate chain with the AK. // If the AK public key doesn't match the public key in the first certificate // in the chain (the leaf), an error is returned. func (ak *AK) SetCertificateChain(ctx context.Context, chain []*x509.Certificate) (err error) { if err := ak.tpm.open(ctx); err != nil { return fmt.Errorf("failed opening TPM: %w", err) } defer closeTPM(ctx, ak.tpm, &err) akPublic, err := ak.public(internalCall(ctx)) if err != nil { return fmt.Errorf("failed getting AK public key: %w", err) } if len(chain) > 0 { leaf := chain[0] leafPK, ok := leaf.PublicKey.(crypto.PublicKey) if !ok { return fmt.Errorf("unexpected type for AK certificate public key: %T", leaf.PublicKey) } publicKey, ok := leafPK.(comparablePublicKey) if !ok { return errors.New("certificate public key can't be compared to a crypto.PublicKey") } if !publicKey.Equal(akPublic) { return errors.New("AK public key does not match the leaf certificate public key") } } ak.chain = chain // TODO(hs): deep copy, so that certs can't be changed by pointer? if err := ak.tpm.store.UpdateAK(ak.toStorage()); err != nil { return fmt.Errorf("failed updating AK %q: %w", ak.name, err) } return } // HasValidPermanentIdentifier indicates if the AK has a certificate // with the `permanentIdentifier` as one of its Subject Alternative // Names. func (ak *AK) HasValidPermanentIdentifier(permanentIdentifier string) bool { chain := ak.chain if len(chain) == 0 { return false } akCert := chain[0] // TODO(hs): before continuing, add check if the cert is still valid? var sanExtension pkix.Extension for _, ext := range akCert.Extensions { if ext.Id.Equal(oidSubjectAlternativeName) { sanExtension = ext break } } if sanExtension.Value == nil { return false } san, err := x509ext.ParseSubjectAltName(sanExtension) // TODO(hs): move to a package under our control? if err != nil { return false } // loop through the permanent identifier values and return // if the requested PermanentIdentifier was found. for _, p := range san.PermanentIdentifiers { if p.IdentifierValue == permanentIdentifier { return true } } return false } // toStorage transforms the AK to the struct used for // persisting AKs. func (ak *AK) toStorage() *storage.AK { return &storage.AK{ Name: ak.name, Data: ak.data, Chain: ak.chain, CreatedAt: ak.createdAt.UTC(), } } // akFromStorage recreates an AK from the struct used for // persisting AKs. func akFromStorage(sak *storage.AK, t *TPM) *AK { return &AK{ name: sak.Name, data: sak.Data, chain: sak.Chain, createdAt: sak.CreatedAt.Local(), tpm: t, } } crypto-0.57.0/tpm/ak_test.go000066400000000000000000000034621474156331600157230ustar00rootroot00000000000000package tpm import ( "crypto" "crypto/x509" "encoding/base64" "encoding/json" "testing" "time" "github.com/stretchr/testify/require" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/minica" "go.step.sm/crypto/x509util" ) func TestAK_MarshalJSON(t *testing.T) { ca, err := minica.New( minica.WithGetSignerFunc( func() (crypto.Signer, error) { return keyutil.GenerateSigner("RSA", "", 2048) }, ), ) require.NoError(t, err) signer, err := keyutil.GenerateSigner("RSA", "", 2048) require.NoError(t, err) cr, err := x509util.NewCertificateRequest(signer) require.NoError(t, err) cr.Subject.CommonName = "testkey" csr, err := cr.GetCertificateRequest() require.NoError(t, err) cert, err := ca.SignCSR(csr) require.NoError(t, err) ak := &AK{ name: "ak1", data: []byte{1, 2, 3, 4}, createdAt: time.Time{}, } data, err := json.Marshal(ak) require.NoError(t, err) m := map[string]any{} err = json.Unmarshal(data, &m) require.NoError(t, err) require.Equal(t, m["name"], ak.name) require.Equal(t, m["data"], base64.StdEncoding.EncodeToString(ak.data)) require.Equal(t, m["chain"], nil) require.Equal(t, m["createdAt"], ak.createdAt.Format("2006-01-02T15:04:05Z")) ak = &AK{ name: "ak2", data: []byte{1, 2, 3, 4}, chain: []*x509.Certificate{cert, ca.Intermediate}, createdAt: time.Time{}, } data, err = json.Marshal(ak) require.NoError(t, err) m = map[string]any{} err = json.Unmarshal(data, &m) require.NoError(t, err) require.Equal(t, m["name"], ak.name) require.Equal(t, m["data"], base64.StdEncoding.EncodeToString(ak.data)) require.Equal(t, m["chain"], []any{base64.StdEncoding.EncodeToString(cert.Raw), base64.StdEncoding.EncodeToString(ca.Intermediate.Raw)}) require.Equal(t, m["createdAt"], ak.createdAt.Format("2006-01-02T15:04:05Z")) } crypto-0.57.0/tpm/algorithm/000077500000000000000000000000001474156331600157235ustar00rootroot00000000000000crypto-0.57.0/tpm/algorithm/algorithm.go000066400000000000000000000061131474156331600202410ustar00rootroot00000000000000package algorithm import ( "encoding/json" ) // Supported Algorithms. const ( AlgorithmUnknown Algorithm = 0x0000 AlgorithmRSA Algorithm = 0x0001 Algorithm3DES Algorithm = 0x0003 AlgorithmSHA1 Algorithm = 0x0004 AlgorithmHMAC Algorithm = 0x0005 AlgorithmAES Algorithm = 0x0006 AlgorithmMGF1 Algorithm = 0x0007 AlgorithmKeyedHash Algorithm = 0x0008 AlgorithmXOR Algorithm = 0x000A AlgorithmSHA256 Algorithm = 0x000B AlgorithmSHA384 Algorithm = 0x000C AlgorithmSHA512 Algorithm = 0x000D AlgorithmNull Algorithm = 0x0010 AlgorithmSM3256 Algorithm = 0x0012 AlgorithmSM4 Algorithm = 0x0013 AlgorithmRSASSA Algorithm = 0x0014 AlgorithmRSAES Algorithm = 0x0015 AlgorithmRSAPSS Algorithm = 0x0016 AlgorithmOAEP Algorithm = 0x0017 AlgorithmECDSA Algorithm = 0x0018 AlgorithmECDH Algorithm = 0x0019 AlgorithmECDAA Algorithm = 0x001A AlgorithmECSchnorr Algorithm = 0x001C AlgorithmKDF1_56A Algorithm = 0x0020 AlgorithmKDF2 Algorithm = 0x0021 AlgorithmKDF1_108 Algorithm = 0x0022 AlgorithmECC Algorithm = 0x0023 AlgorithmSymCipher Algorithm = 0x0025 AlgorithmCamellia Algorithm = 0x0026 AlgorithmSHA3_256 Algorithm = 0x0027 AlgorithmSHA3_384 Algorithm = 0x0028 AlgorithmSHA3_512 Algorithm = 0x0029 AlgorithmCMAC Algorithm = 0x003F AlgorithmCTR Algorithm = 0x0040 AlgorithmOFB Algorithm = 0x0041 AlgorithmCBC Algorithm = 0x0042 AlgorithmCFB Algorithm = 0x0043 AlgorithmECB Algorithm = 0x0044 ) // https://trustedcomputinggroup.org/wp-content/uploads/TCG_TPM2_r1p59_Part2_Structures_pub.pdf var algs = map[Algorithm]string{ // object types AlgorithmRSA: "RSA", AlgorithmECC: "ECC", // encryption algs AlgorithmRSAES: "RSAES", // block ciphers Algorithm3DES: "3DES", AlgorithmAES: "AES", AlgorithmCamellia: "Camellia", AlgorithmECB: "ECB", AlgorithmCFB: "CFB", AlgorithmOFB: "OFB", AlgorithmCBC: "CBC", AlgorithmCTR: "CTR", AlgorithmSymCipher: "Symmetric Cipher", AlgorithmCMAC: "CMAC", // other ciphers AlgorithmXOR: "XOR", AlgorithmNull: "Null Cipher", // hash algs AlgorithmSHA1: "SHA-1", AlgorithmHMAC: "HMAC", AlgorithmMGF1: "MGF1", AlgorithmKeyedHash: "Keyed Hash", AlgorithmSM3256: "SM3-256", AlgorithmSHA256: "SHA-256", AlgorithmSHA384: "SHA-384", AlgorithmSHA512: "SHA-512", AlgorithmSHA3_256: "SHA3-256", AlgorithmSHA3_384: "SHA3-384", AlgorithmSHA3_512: "SHA3-512", // signature algs AlgorithmSM4: "SM4", AlgorithmRSASSA: "RSA-SSA", AlgorithmRSAPSS: "RSA-PSS", AlgorithmECDSA: "ECDSA", AlgorithmECDAA: "ECDAA", AlgorithmECSchnorr: "EC-Schnorr", // encryption schemes AlgorithmOAEP: "OAEP", AlgorithmECDH: "ECDH", // key derivation AlgorithmKDF1_56A: "KDF1-SP800-56A", AlgorithmKDF1_108: "KDF1-SP800-108", AlgorithmKDF2: "KDF2", } type Algorithm uint16 func (a Algorithm) String() string { return algs[Algorithm(int(a))] } func (a Algorithm) MarshalJSON() ([]byte, error) { return json.Marshal(a.String()) } crypto-0.57.0/tpm/algorithm/algorithm_test.go000066400000000000000000000012451474156331600213010ustar00rootroot00000000000000package algorithm import ( "encoding/json" "testing" "github.com/google/go-tpm/legacy/tpm2" "github.com/stretchr/testify/require" ) func Test_AlgorithmString(t *testing.T) { tests := []struct { name string id tpm2.Algorithm want string }{ {"ok/RSA", tpm2.AlgRSA, "RSA"}, {"ok/3DES", 0x0003, "3DES"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := Algorithm(tt.id).String(); got != tt.want { t.Errorf("String() = %v, want %v", got, tt.want) } }) } } func Test_AlgorithmMarshalJSON(t *testing.T) { b, err := json.Marshal(Algorithm(tpm2.AlgRSA)) require.NoError(t, err) require.JSONEq(t, `"RSA"`, string(b)) } crypto-0.57.0/tpm/attestation/000077500000000000000000000000001474156331600162745ustar00rootroot00000000000000crypto-0.57.0/tpm/attestation/client.go000066400000000000000000000207501474156331600201050ustar00rootroot00000000000000package attestation import ( "bytes" "context" "crypto/tls" "crypto/x509" "encoding/json" "fmt" "io" "net/http" "net/url" "os" "strconv" "time" "go.step.sm/crypto/tpm" "github.com/smallstep/go-attestation/attest" ) type Client struct { client *http.Client baseURL *url.URL } type Options struct { rootCAs *x509.CertPool insecure bool } type Option func(o *Options) error // WithRootsFile can be used to set the trusted roots when // setting up a TLS connection. An empty filename will // be ignored. func WithRootsFile(filename string) Option { return func(o *Options) error { if filename == "" { return nil } data, err := os.ReadFile(filename) if err != nil { return fmt.Errorf("failed reading %q: %w", filename, err) } pool := x509.NewCertPool() if !pool.AppendCertsFromPEM(data) { return fmt.Errorf("failed parsing %q: no certificates found", filename) } o.rootCAs = pool return nil } } // WithInsecure disables TLS server certificate chain checking. // In general this shouldn't be used, but it can be of use in // during development and testing. func WithInsecure() Option { return func(o *Options) error { o.insecure = true return nil } } // NewClient creates a new Client that can be used to perform remote // attestation. func NewClient(tpmAttestationCABaseURL string, options ...Option) (*Client, error) { u, err := url.Parse(tpmAttestationCABaseURL) if err != nil { return nil, fmt.Errorf("failed parsing attestation CA base URL: %w", err) } opts := &Options{} for _, o := range options { if err := o(opts); err != nil { return nil, fmt.Errorf("failed applying option to attestation client: %w", err) } } transport := http.DefaultTransport.(*http.Transport).Clone() transport.TLSClientConfig = &tls.Config{ RootCAs: opts.rootCAs, InsecureSkipVerify: opts.insecure, //nolint:gosec // intentional insecure if provided as option } client := &http.Client{ Timeout: 10 * time.Second, Transport: transport, } return &Client{ client: client, baseURL: u, }, nil } // Attest performs remote attestation using the AK backed by TPM t. // // TODO: support multiple EKs again? Currently selection of the EK is left // to the caller. func (ac *Client) Attest(ctx context.Context, t *tpm.TPM, ek *tpm.EK, ak *tpm.AK) ([]*x509.Certificate, error) { // TODO(hs): what about performing attestation for an existing AK identifier and/or cert, but // with a different Attestation CA? It seems sensible to enroll with that other Attestation CA, // but it needs capturing some knowledge about the Attestation CA with the AK (cert). Possible to // derive that from the intermediate and/or root CA and/or fingerprint, somehow? Or the attestation URI? info, err := t.Info(ctx) if err != nil { return nil, fmt.Errorf("failed retrieving info from TPM: %w", err) } attestParams, err := ak.AttestationParameters(ctx) if err != nil { return nil, fmt.Errorf("failed getting AK attestation parameters: %w", err) } attResp, err := ac.attest(ctx, info, ek, attestParams) if err != nil { return nil, fmt.Errorf("failed attesting AK: %w", err) } encryptedCredentials := tpm.EncryptedCredential{ Credential: attResp.Credential, Secret: attResp.Secret, } // activate the credential with the TPM secret, err := ak.ActivateCredential(ctx, encryptedCredentials) if err != nil { return nil, fmt.Errorf("failed activating credential: %w", err) } secretResp, err := ac.secret(ctx, secret) if err != nil { return nil, fmt.Errorf("failed validating secret: %w", err) } akChain := make([]*x509.Certificate, len(secretResp.CertificateChain)) for i, certBytes := range secretResp.CertificateChain { cert, err := x509.ParseCertificate(certBytes) if err != nil { return nil, fmt.Errorf("failed parsing certificate: %w", err) } akChain[i] = cert } return akChain, nil } type tpmInfo struct { Version attest.TPMVersion `json:"version,omitempty"` Manufacturer string `json:"manufacturer,omitempty"` Model string `json:"model,omitempty"` FirmwareVersion string `json:"firmwareVersion,omitempty"` } type attestationParameters struct { Public []byte `json:"public,omitempty"` UseTCSDActivationFormat bool `json:"useTCSDActivationFormat,omitempty"` CreateData []byte `json:"createData,omitempty"` CreateAttestation []byte `json:"createAttestation,omitempty"` CreateSignature []byte `json:"createSignature,omitempty"` } type attestationRequest struct { TPMInfo tpmInfo `json:"tpmInfo"` EKPub []byte `json:"ek,omitempty"` EKCerts [][]byte `json:"ekCerts,omitempty"` AKCert []byte `json:"akCert,omitempty"` AttestParams attestationParameters `json:"params"` } type attestationResponse struct { Credential []byte `json:"credential"` Secret []byte `json:"secret"` // encrypted secret } // attest performs the HTTP POST request to the `/attest` endpoint of the // Attestation CA. func (ac *Client) attest(ctx context.Context, info *tpm.Info, ek *tpm.EK, attestParams attest.AttestationParameters) (*attestationResponse, error) { var ekCerts [][]byte var ekPub []byte var err error // TODO: support multiple EKs again? Currently selection of the EK is left // to the caller. if ekCert := ek.Certificate(); ekCert != nil { ekCerts = append(ekCerts, ekCert.Raw) } if ekPub, err = x509.MarshalPKIXPublicKey(ek.Public()); err != nil { return nil, fmt.Errorf("failed marshaling public key: %w", err) } ar := attestationRequest{ TPMInfo: tpmInfo{ Version: attest.TPMVersion20, Manufacturer: strconv.FormatUint(uint64(info.Manufacturer.ID), 10), Model: info.VendorInfo, FirmwareVersion: info.FirmwareVersion.String(), }, EKCerts: ekCerts, EKPub: ekPub, AttestParams: attestationParameters{ Public: attestParams.Public, UseTCSDActivationFormat: attestParams.UseTCSDActivationFormat, CreateData: attestParams.CreateData, CreateAttestation: attestParams.CreateAttestation, CreateSignature: attestParams.CreateSignature, }, } body, err := json.Marshal(ar) if err != nil { return nil, fmt.Errorf("failed marshaling attestation request: %w", err) } attestURL := ac.baseURL.JoinPath("attest").String() req, err := newRequest(ctx, http.MethodPost, attestURL, bytes.NewReader(body)) if err != nil { return nil, fmt.Errorf("failed creating POST http request for %q: %w", attestURL, err) } resp, err := ac.client.Do(req) if err != nil { return nil, fmt.Errorf("failed performing attestation request with Attestation CA %q: %w", attestURL, err) } defer resp.Body.Close() if resp.StatusCode >= 400 { return nil, fmt.Errorf("POST %q failed with HTTP status %q", attestURL, resp.Status) } var attResp attestationResponse if err := json.NewDecoder(resp.Body).Decode(&attResp); err != nil { return nil, fmt.Errorf("failed decoding attestation response: %w", err) } return &attResp, nil } type secretRequest struct { Secret []byte `json:"secret"` // decrypted secret } type secretResponse struct { CertificateChain [][]byte `json:"chain"` } // secret performs the HTTP POST request to the `/secret` endpoint of the // Attestation CA. func (ac *Client) secret(ctx context.Context, secret []byte) (*secretResponse, error) { sr := secretRequest{ Secret: secret, } body, err := json.Marshal(sr) if err != nil { return nil, fmt.Errorf("failed marshaling secret request: %w", err) } secretURL := ac.baseURL.JoinPath("secret").String() req, err := newRequest(ctx, http.MethodPost, secretURL, bytes.NewReader(body)) if err != nil { return nil, fmt.Errorf("failed creating POST http request for %q: %w", secretURL, err) } resp, err := ac.client.Do(req) if err != nil { return nil, fmt.Errorf("failed performing secret request with attestation CA %q: %w", secretURL, err) } defer resp.Body.Close() if resp.StatusCode >= 400 { return nil, fmt.Errorf("POST %q failed with HTTP status %q", secretURL, resp.Status) } var secretResp secretResponse if err := json.NewDecoder(resp.Body).Decode(&secretResp); err != nil { return nil, fmt.Errorf("failed decoding secret response: %w", err) } return &secretResp, nil } func newRequest(ctx context.Context, method, requestURL string, body io.Reader) (*http.Request, error) { req, err := http.NewRequestWithContext(ctx, method, requestURL, body) if err != nil { return nil, err } enforceRequestID(req) setUserAgent(req) return req, nil } crypto-0.57.0/tpm/attestation/client_simulator_test.go000066400000000000000000000277231474156331600232520ustar00rootroot00000000000000//go:build tpmsimulator // +build tpmsimulator package attestation import ( "context" "crypto" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/json" "errors" "fmt" "net/http" "net/http/httptest" "net/url" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/smallstep/go-attestation/attest" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/minica" "go.step.sm/crypto/tpm" "go.step.sm/crypto/tpm/simulator" "go.step.sm/crypto/tpm/storage" ) func newSimulatedTPM(t *testing.T) *tpm.TPM { t.Helper() tmpDir := t.TempDir() instance, err := tpm.New(withSimulator(t), tpm.WithStore(storage.NewDirstore(tmpDir))) require.NoError(t, err) return instance } func withSimulator(t *testing.T) tpm.NewTPMOption { t.Helper() var sim simulator.Simulator t.Cleanup(func() { if sim == nil { return } err := sim.Close() require.NoError(t, err) }) sim, err := simulator.New() require.NoError(t, err) err = sim.Open() require.NoError(t, err) return tpm.WithSimulator(sim) } // getPreferredEK returns the first RSA TPM EK found. If no RSA // EK exists, it returns the first ECDSA EK found. func getPreferredEK(eks []*tpm.EK) (ek *tpm.EK) { var fallback *tpm.EK for _, ek = range eks { if _, isRSA := ek.Public().(*rsa.PublicKey); isRSA { return } if fallback == nil { fallback = ek } } return fallback } func mustParseURL(t *testing.T, urlString string) *url.URL { t.Helper() u, err := url.Parse(urlString) require.NoError(t, err) return u } func TestClient_Attest(t *testing.T) { ctx := context.Background() ctx = NewRequestIDContext(ctx, "requestID") instance := newSimulatedTPM(t) ak, err := instance.CreateAK(ctx, "ak1") require.NoError(t, err) require.NoError(t, err) eks, err := instance.GetEKs(ctx) require.NoError(t, err) ek := getPreferredEK(eks) ca, err := minica.New( minica.WithGetSignerFunc( func() (crypto.Signer, error) { return keyutil.GenerateSigner("RSA", "", 2048) }, ), ) require.NoError(t, err) akPub := ak.Public() require.Implements(t, (*crypto.PublicKey)(nil), akPub) template := &x509.Certificate{ Subject: pkix.Name{ CommonName: "testak", }, PublicKey: akPub, } validAKCert, err := ca.Sign(template) require.NoError(t, err) require.NotNil(t, validAKCert) type fields struct { client *http.Client baseURL *url.URL } type args struct { ctx context.Context t *tpm.TPM ek *tpm.EK ak *tpm.AK } type test struct { fields fields server *httptest.Server args args want []*x509.Certificate expErr error } tests := map[string]func(t *testing.T) test{ "ok": func(t *testing.T) test { params, err := ak.AttestationParameters(context.Background()) require.NoError(t, err) require.NotNil(t, params) activation := attest.ActivationParameters{ TPMVersion: attest.TPMVersion20, EK: ek.Public(), AK: params, } expectedSecret, encryptedCredentials, err := activation.Generate() require.NoError(t, err) akChain := [][]byte{ validAKCert.Raw, ca.Intermediate.Raw, } handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/attest": assert.Equal(t, "step-attestation-http-client/1.0", r.Header.Get("User-Agent")) assert.Equal(t, "requestID", r.Header.Get("X-Request-Id")) var ar attestationRequest err := json.NewDecoder(r.Body).Decode(&ar) require.NoError(t, err) parsedEK, err := x509.ParsePKIXPublicKey(ar.EKPub) require.NoError(t, err) assert.Equal(t, ek.Public(), parsedEK) attestParams := attest.AttestationParameters{ Public: ar.AttestParams.Public, UseTCSDActivationFormat: ar.AttestParams.UseTCSDActivationFormat, CreateData: ar.AttestParams.CreateData, CreateAttestation: ar.AttestParams.CreateAttestation, CreateSignature: ar.AttestParams.CreateSignature, } activationParams := attest.ActivationParameters{ TPMVersion: ar.TPMInfo.Version, EK: parsedEK, AK: attestParams, } assert.Equal(t, activation, activationParams) w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(&attestationResponse{ Credential: encryptedCredentials.Credential, Secret: encryptedCredentials.Secret, }) case "/secret": assert.Equal(t, "step-attestation-http-client/1.0", r.Header.Get("User-Agent")) assert.Equal(t, "requestID", r.Header.Get("X-Request-Id")) var sr secretRequest err := json.NewDecoder(r.Body).Decode(&sr) require.NoError(t, err) assert.Equal(t, expectedSecret, sr.Secret) w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(&secretResponse{ CertificateChain: akChain, }) default: t.Errorf("unexpected %q request to %q", r.Method, r.URL) } }) s := httptest.NewServer(handler) return test{ server: s, fields: fields{ client: http.DefaultClient, baseURL: mustParseURL(t, s.URL), }, args: args{ ctx: ctx, t: instance, ek: ek, ak: ak, }, want: []*x509.Certificate{ validAKCert, ca.Intermediate, }, expErr: nil, } }, "fail/attest": func(t *testing.T) test { params, err := ak.AttestationParameters(context.Background()) require.NoError(t, err) require.NotNil(t, params) activation := attest.ActivationParameters{ TPMVersion: attest.TPMVersion20, EK: ek.Public(), AK: params, } handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/attest": var ar attestationRequest err := json.NewDecoder(r.Body).Decode(&ar) require.NoError(t, err) parsedEK, err := x509.ParsePKIXPublicKey(ar.EKPub) require.NoError(t, err) assert.Equal(t, ek.Public(), parsedEK) attestParams := attest.AttestationParameters{ Public: ar.AttestParams.Public, UseTCSDActivationFormat: ar.AttestParams.UseTCSDActivationFormat, CreateData: ar.AttestParams.CreateData, CreateAttestation: ar.AttestParams.CreateAttestation, CreateSignature: ar.AttestParams.CreateSignature, } activationParams := attest.ActivationParameters{ TPMVersion: ar.TPMInfo.Version, EK: parsedEK, AK: attestParams, } assert.Equal(t, activation, activationParams) w.WriteHeader(http.StatusBadRequest) case "/secret": t.Errorf("unexpectedly requested /secret endpoint") default: t.Errorf("unexpected %q request to %q", r.Method, r.URL) } }) s := httptest.NewServer(handler) return test{ server: s, fields: fields{ client: http.DefaultClient, baseURL: mustParseURL(t, s.URL), }, args: args{ ctx: ctx, t: instance, ek: ek, ak: ak, }, want: nil, expErr: fmt.Errorf(`failed attesting AK: POST %q failed with HTTP status "400 Bad Request"`, fmt.Sprintf("%s/attest", s.URL)), } }, "fail/secret": func(t *testing.T) test { params, err := ak.AttestationParameters(context.Background()) require.NoError(t, err) require.NotNil(t, params) activation := attest.ActivationParameters{ TPMVersion: attest.TPMVersion20, EK: ek.Public(), AK: params, } expectedSecret, encryptedCredentials, err := activation.Generate() require.NoError(t, err) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/attest": var ar attestationRequest err := json.NewDecoder(r.Body).Decode(&ar) require.NoError(t, err) parsedEK, err := x509.ParsePKIXPublicKey(ar.EKPub) require.NoError(t, err) assert.Equal(t, ek.Public(), parsedEK) attestParams := attest.AttestationParameters{ Public: ar.AttestParams.Public, UseTCSDActivationFormat: ar.AttestParams.UseTCSDActivationFormat, CreateData: ar.AttestParams.CreateData, CreateAttestation: ar.AttestParams.CreateAttestation, CreateSignature: ar.AttestParams.CreateSignature, } activationParams := attest.ActivationParameters{ TPMVersion: ar.TPMInfo.Version, EK: parsedEK, AK: attestParams, } assert.Equal(t, activation, activationParams) w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(&attestationResponse{ Credential: encryptedCredentials.Credential, Secret: encryptedCredentials.Secret, }) case "/secret": var sr secretRequest err := json.NewDecoder(r.Body).Decode(&sr) require.NoError(t, err) assert.Equal(t, expectedSecret, sr.Secret) w.WriteHeader(http.StatusForbidden) default: t.Errorf("unexpected %q request to %q", r.Method, r.URL) } }) s := httptest.NewServer(handler) return test{ server: s, fields: fields{ client: http.DefaultClient, baseURL: mustParseURL(t, s.URL), }, args: args{ ctx: ctx, t: instance, ek: ek, ak: ak, }, want: nil, expErr: fmt.Errorf(`failed validating secret: POST %q failed with HTTP status "403 Forbidden"`, fmt.Sprintf("%s/secret", s.URL)), } }, "fail/pars-ak-certificate-chain": func(t *testing.T) test { params, err := ak.AttestationParameters(context.Background()) require.NoError(t, err) require.NotNil(t, params) activation := attest.ActivationParameters{ TPMVersion: attest.TPMVersion20, EK: ek.Public(), AK: params, } expectedSecret, encryptedCredentials, err := activation.Generate() require.NoError(t, err) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/attest": var ar attestationRequest err := json.NewDecoder(r.Body).Decode(&ar) require.NoError(t, err) parsedEK, err := x509.ParsePKIXPublicKey(ar.EKPub) require.NoError(t, err) assert.Equal(t, ek.Public(), parsedEK) attestParams := attest.AttestationParameters{ Public: ar.AttestParams.Public, UseTCSDActivationFormat: ar.AttestParams.UseTCSDActivationFormat, CreateData: ar.AttestParams.CreateData, CreateAttestation: ar.AttestParams.CreateAttestation, CreateSignature: ar.AttestParams.CreateSignature, } activationParams := attest.ActivationParameters{ TPMVersion: ar.TPMInfo.Version, EK: parsedEK, AK: attestParams, } assert.Equal(t, activation, activationParams) w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(&attestationResponse{ Credential: encryptedCredentials.Credential, Secret: encryptedCredentials.Secret, }) case "/secret": var sr secretRequest err := json.NewDecoder(r.Body).Decode(&sr) require.NoError(t, err) assert.Equal(t, expectedSecret, sr.Secret) w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(&secretResponse{ CertificateChain: [][]byte{[]byte("this-is-no-certificate")}, }) default: t.Errorf("unexpected %q request to %q", r.Method, r.URL) } }) s := httptest.NewServer(handler) return test{ server: s, fields: fields{ client: http.DefaultClient, baseURL: mustParseURL(t, s.URL), }, args: args{ ctx: ctx, t: instance, ek: ek, ak: ak, }, want: nil, expErr: errors.New(`failed parsing certificate: x509: malformed certificate`), } }, } for name, tt := range tests { tc := tt(t) t.Run(name, func(t *testing.T) { ac := &Client{ client: tc.fields.client, baseURL: tc.fields.baseURL, } if tc.server != nil { defer tc.server.Close() } got, err := ac.Attest(tc.args.ctx, tc.args.t, tc.args.ek, tc.args.ak) if tc.expErr != nil { assert.EqualError(t, err, tc.expErr.Error()) assert.Nil(t, got) return } assert.NoError(t, err) assert.Equal(t, tc.want, got) }) } } crypto-0.57.0/tpm/attestation/client_test.go000066400000000000000000000040641474156331600211440ustar00rootroot00000000000000package attestation import ( "testing" "time" "github.com/stretchr/testify/assert" ) func TestNewClient(t *testing.T) { baseURL := "http://localhost:1337" type args struct { tpmAttestationCABaseURL string options []Option } tests := []struct { name string args args assertFunc assert.ValueAssertionFunc wantErr bool }{ { name: "ok/no-options", args: args{ tpmAttestationCABaseURL: baseURL, options: nil, }, assertFunc: func(tt assert.TestingT, i1 any, i2 ...any) bool { if assert.IsType(t, &Client{}, i1) { c, _ := i1.(*Client) if assert.NotNil(t, c) { if assert.NotNil(t, c.baseURL) { assert.Equal(t, baseURL, c.baseURL.String()) } if assert.NotNil(t, c.client) { assert.Equal(t, 10*time.Second, c.client.Timeout) } return true } } return false }, wantErr: false, }, { name: "ok/with-options", args: args{ tpmAttestationCABaseURL: baseURL, options: []Option{WithInsecure(), WithRootsFile("testdata/roots.pem")}, }, assertFunc: func(tt assert.TestingT, i1 any, i2 ...any) bool { if assert.IsType(t, &Client{}, i1) { c, _ := i1.(*Client) if assert.NotNil(t, c) { if assert.NotNil(t, c.baseURL) { assert.Equal(t, baseURL, c.baseURL.String()) } if assert.NotNil(t, c.client) { assert.Equal(t, 10*time.Second, c.client.Timeout) } return true } } return false }, wantErr: false, }, { name: "fail/non-existing-roots", args: args{ tpmAttestationCABaseURL: baseURL, options: []Option{WithInsecure(), WithRootsFile("testdata/non-existing-roots.pem")}, }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := NewClient(tt.args.tpmAttestationCABaseURL, tt.args.options...) if tt.wantErr { assert.Error(t, err) assert.Nil(t, got) return } assert.NoError(t, err) assert.True(t, tt.assertFunc(t, got)) }) } } crypto-0.57.0/tpm/attestation/requestid.go000066400000000000000000000027231474156331600206340ustar00rootroot00000000000000package attestation import ( "context" "net/http" "go.step.sm/crypto/randutil" ) type requestIDContextKey struct{} // NewRequestIDContext returns a new context with the given request ID added to the // context. func NewRequestIDContext(ctx context.Context, requestID string) context.Context { return context.WithValue(ctx, requestIDContextKey{}, requestID) } // RequestIDFromContext returns the request ID from the context if it exists. // and is not empty. func RequestIDFromContext(ctx context.Context) (string, bool) { v, ok := ctx.Value(requestIDContextKey{}).(string) return v, ok && v != "" } // requestIDHeader is the header name used for propagating request IDs from // the attestation client to the attestation CA and back again. const requestIDHeader = "X-Request-Id" // newRequestID generates a new random UUIDv4 request ID. If it fails, // the request ID will be the empty string. func newRequestID() string { requestID, err := randutil.UUIDv4() if err != nil { return "" } return requestID } // enforceRequestID checks if the X-Request-Id HTTP header is filled. If it's // empty, the context is searched for a request ID. If that's also empty, a new // request ID is generated. func enforceRequestID(r *http.Request) { if requestID := r.Header.Get(requestIDHeader); requestID == "" { if reqID, ok := RequestIDFromContext(r.Context()); ok { requestID = reqID } else { requestID = newRequestID() } r.Header.Set(requestIDHeader, requestID) } } crypto-0.57.0/tpm/attestation/testdata/000077500000000000000000000000001474156331600201055ustar00rootroot00000000000000crypto-0.57.0/tpm/attestation/testdata/roots.pem000066400000000000000000000011171474156331600217560ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBhzCCASygAwIBAgIRANJiwPnM38wWznkJGOcIyIYwCgYIKoZIzj0EAwIwITEf MB0GA1UEAxMWU21hbGxzdGVwIFRlc3QgUm9vdCBDQTAeFw0xODA5MjcxODE4MDla Fw0yODA5MjQxODE4MDlaMCExHzAdBgNVBAMTFlNtYWxsc3RlcCBUZXN0IFJvb3Qg Q0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS15w7dx9zPjCnQ7+RlRkvUXQJN Fjk5Hg5K9nCoiiNQQhcQMw63/pXQxHNsugiMshcN59XJC8195KJPm25nXN8co0Uw QzAOBgNVHQ8BAf8EBAMCAaYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQU B2BAXUSPZbFjnY6VzbApV48Tn3owCgYIKoZIzj0EAwIDSQAwRgIhAJRTVmc2xW8c ESx4oIp2d/OX9KBZzpcNi9fHnnJCS0FXAiEA7OpFb2+b8KBzg1c02x21PS7pHoET /A8LXNH4M06A7vE= -----END CERTIFICATE----- crypto-0.57.0/tpm/attestation/useragent.go000066400000000000000000000005331474156331600206210ustar00rootroot00000000000000package attestation import "net/http" // UserAgent is the value of the User-Agent HTTP header that will // be set in HTTP requests to the attestation CA. var UserAgent = "step-attestation-http-client/1.0" // setUserAgent sets the User-Agent header in HTTP requests. func setUserAgent(r *http.Request) { r.Header.Set("User-Agent", UserAgent) } crypto-0.57.0/tpm/available/000077500000000000000000000000001474156331600156555ustar00rootroot00000000000000crypto-0.57.0/tpm/available/check.go000066400000000000000000000003621474156331600172620ustar00rootroot00000000000000package available import ( "fmt" "go.step.sm/crypto/tpm" ) func Check(opts ...tpm.NewTPMOption) error { t, err := tpm.New(opts...) if err != nil { return fmt.Errorf("failed creating TPM instance: %w", err) } return t.Available() } crypto-0.57.0/tpm/blobs.go000066400000000000000000000024441474156331600153710ustar00rootroot00000000000000package tpm import ( "bytes" "fmt" "github.com/google/go-tpm/tpmutil" ) // Blobs is a container for the private and public blobs of data // that represent a TPM2 object. type Blobs struct { private []byte public []byte } // Private returns the private data blob of a TPM2 object including // a 16-bit header. The blob can be used with tpm2-tools. func (b *Blobs) Private() (blob []byte, err error) { if blob, err = toTPM2Tools(b.private); err != nil { return nil, fmt.Errorf("failed transforming private blob bytes: %w", err) } return } // Public returns the public data blob of a TPM2 object including // a 16-bit header. The blob can be used with tpm2-tools. func (b *Blobs) Public() (blob []byte, err error) { if blob, err = toTPM2Tools(b.public); err != nil { return nil, fmt.Errorf("failed transforming public blob bytes: %w", err) } return } func toTPM2Tools(blob []byte) ([]byte, error) { var buf bytes.Buffer bytesWithHeader := tpmutil.U16Bytes(blob) if err := bytesWithHeader.TPMMarshal(&buf); err != nil { return nil, err } return buf.Bytes(), nil } func (ak *AK) setBlobs(private, public []byte) { ak.blobs = &Blobs{ private: private, public: public, } } func (k *Key) setBlobs(private, public []byte) { k.blobs = &Blobs{ private: private, public: public, } } crypto-0.57.0/tpm/caps.go000066400000000000000000000033721474156331600152170ustar00rootroot00000000000000package tpm import ( "context" "fmt" "slices" "github.com/google/go-tpm/legacy/tpm2" "go.step.sm/crypto/tpm/algorithm" ) // Capabilities represents the capabilities of the TPM. type Capabilities struct { Algorithms []algorithm.Algorithm } // SupportsAlgorithm return whether the provided algorithm // is supported by the TPM func (c *Capabilities) SupportsAlgorithm(alg algorithm.Algorithm) bool { return slices.Contains(c.Algorithms, alg) } // SupportsAlgorithms return whether all algorithms in the provided // slice are supported by the TPM func (c *Capabilities) SupportsAlgorithms(algs []algorithm.Algorithm) bool { for _, alg := range algs { if !c.SupportsAlgorithm(alg) { return false } } return true } // GetCapabilities returns the capabilities of the TPM; currently suports // enumerating the supported algorithms // // Notice: This API is EXPERIMENTAL and may be changed or removed in a later // release. func (t *TPM) GetCapabilities(ctx context.Context) (caps *Capabilities, err error) { if t.caps != nil { return t.caps, nil } if err = t.open(goTPMCall(ctx)); err != nil { return nil, fmt.Errorf("failed opening TPM: %w", err) } defer closeTPM(ctx, t, &err) current := tpm2.AlgUnknown // 0x0000, first property caps = &Capabilities{} for { var ( data []any more bool ) data, more, err := tpm2.GetCapability(t.rwc, tpm2.CapabilityAlgs, 1, uint32(current)) if err != nil { return nil, fmt.Errorf("error getting algorithms capability: %w", err) } if d, ok := data[0].(tpm2.AlgorithmDescription); ok { alg := algorithm.Algorithm(d.ID) if !slices.Contains(caps.Algorithms, alg) { caps.Algorithms = append(caps.Algorithms, alg) } } if !more { break } current++ } t.caps = caps return } crypto-0.57.0/tpm/caps_test.go000066400000000000000000000013731474156331600162550ustar00rootroot00000000000000package tpm import ( "testing" "github.com/stretchr/testify/assert" "go.step.sm/crypto/tpm/algorithm" ) func Test_Capabilities_SupportsAlgorithm(t *testing.T) { caps := &Capabilities{} assert.False(t, caps.SupportsAlgorithm(algorithm.AlgorithmRSA)) caps = &Capabilities{ Algorithms: []algorithm.Algorithm{algorithm.AlgorithmRSA}, } assert.True(t, caps.SupportsAlgorithm(algorithm.AlgorithmRSA)) } func Test_Capabilities_SupportsAlgorithms(t *testing.T) { caps := &Capabilities{} assert.False(t, caps.SupportsAlgorithms([]algorithm.Algorithm{algorithm.AlgorithmRSA})) caps = &Capabilities{ Algorithms: []algorithm.Algorithm{algorithm.AlgorithmRSA}, } assert.True(t, caps.SupportsAlgorithms([]algorithm.Algorithm{algorithm.AlgorithmRSA})) } crypto-0.57.0/tpm/context.go000066400000000000000000000016621474156331600157550ustar00rootroot00000000000000package tpm import "context" type contextKey struct{} // NewContext adds TPM `t` to the context. func NewContext(ctx context.Context, t *TPM) context.Context { return context.WithValue(ctx, contextKey{}, t) } // FromContext returns a TPM from the context. // // It panics when there's no TPM in the context. func FromContext(ctx context.Context) *TPM { return ctx.Value(contextKey{}).(*TPM) } type internalCallContextKey struct{} func internalCall(ctx context.Context) context.Context { return context.WithValue(ctx, internalCallContextKey{}, true) } func isInternalCall(ctx context.Context) bool { v, ok := ctx.Value(internalCallContextKey{}).(bool) return ok && v } type goTPMCallContextKey struct{} func goTPMCall(ctx context.Context) context.Context { return context.WithValue(ctx, goTPMCallContextKey{}, true) } func isGoTPMCall(ctx context.Context) bool { v, ok := ctx.Value(goTPMCallContextKey{}).(bool) return ok && v } crypto-0.57.0/tpm/ek.go000066400000000000000000000237441474156331600146750ustar00rootroot00000000000000package tpm import ( "bytes" "context" "crypto" "crypto/ecdsa" "crypto/rsa" "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/json" "encoding/pem" "fmt" "io" "net/http" "net/url" "path" "strings" "github.com/smallstep/go-attestation/attest" ) // EK models a TPM Endorsement Key. The EK can be used to // identify a specific TPM. The EK is certified by a TPM // manufacturer. type EK struct { public crypto.PublicKey certificate *x509.Certificate certificateURL string } // Public returns the EK public key. func (ek *EK) Public() crypto.PublicKey { return ek.public } // Certificate returns the EK certificate. This // can return nil. func (ek *EK) Certificate() *x509.Certificate { return ek.certificate } // CertificateURL returns the URL from which the EK // certificate can be retrieved. Not all EKs have a // certificate URL. func (ek *EK) CertificateURL() string { return ek.certificateURL } // Fingerprint returns the EK public key fingerprint. // The fingerprint is the base64 encoded SHA256 of // the EK public key, encoded to PKIX, ASN.1 DER format. func (ek *EK) Fingerprint() (string, error) { fp, err := generateKeyID(ek.public) if err != nil { return "", fmt.Errorf("failed generating EK public key ID: %w", err) } return "sha256:" + base64.StdEncoding.EncodeToString(fp), nil } func generateKeyID(pub crypto.PublicKey) ([]byte, error) { b, err := x509.MarshalPKIXPublicKey(pub) if err != nil { return nil, fmt.Errorf("failed marshaling public key: %w", err) } hash := sha256.Sum256(b) return hash[:], nil } func (ek *EK) FingerprintURI() (*url.URL, error) { fp, err := ek.Fingerprint() if err != nil { return nil, err } return &url.URL{ Scheme: "urn", Opaque: fmt.Sprintf("ek:%s", fp), // ek:sha256: }, nil } // MarshalJSON marshals the EK to JSON. func (ek *EK) MarshalJSON() ([]byte, error) { var der []byte if ek.certificate != nil { der = ek.certificate.Raw } fp, err := ek.Fingerprint() if err != nil { return nil, fmt.Errorf("failed getting EK fingerprint: %w", err) } fpURI, err := ek.FingerprintURI() if err != nil { return nil, fmt.Errorf("failed getting EK fingerprint URI: %w", err) } o := struct { Type string `json:"type"` Fingerprint string `json:"fingerprint"` FingerprintURI string `json:"fingerprintURI"` DER []byte `json:"der,omitempty"` // TODO: support for EK certificate chain? URL string `json:"url,omitempty"` }{ Type: ek.Type(), Fingerprint: fp, FingerprintURI: fpURI.String(), DER: der, URL: ek.certificateURL, } return json.Marshal(o) } // PEM returns the EK certificate as a PEM // formatted string. It returns an error if // the EK doesn't have a certificate. func (ek *EK) PEM() (string, error) { if ek.certificate == nil { return "", fmt.Errorf("EK %q does not have a certificate", ek.Type()) } var buf bytes.Buffer if err := pem.Encode(&buf, &pem.Block{ Type: "CERTIFICATE", Bytes: ek.certificate.Raw, }); err != nil { return "", fmt.Errorf("failed encoding EK certificate to PEM: %w", err) } return buf.String(), nil } // Type returns the EK public key type description. func (ek *EK) Type() string { return keyType(ek.public) } func keyType(p crypto.PublicKey) string { switch t := p.(type) { case *rsa.PublicKey: return fmt.Sprintf("RSA %d", t.Size()*8) case *ecdsa.PublicKey: return fmt.Sprintf("ECDSA %s", t.Curve.Params().Name) default: return fmt.Sprintf("unsupported public key type: %T", p) } } // GetEKs returns a slice of TPM EKs. It will return an error // when interaction with the TPM fails. It will loop through // the TPM EKs and download the EK certificate if it's available // online. The TPM EKs don't change after the first lookup, so // the result is cached for future lookups. func (t *TPM) GetEKs(ctx context.Context) (eks []*EK, err error) { if len(t.eks) > 0 { return t.eks, nil } if err = t.open(ctx); err != nil { return nil, fmt.Errorf("failed opening TPM: %w", err) } defer closeTPM(ctx, t, &err) aeks, err := t.attestTPM.EKs() if err != nil { return nil, fmt.Errorf("failed getting EKs: %w", err) } // an arbitrary limit, so that we don't start making a large number of HTTP requests (if needed) if len(aeks) > t.downloader.maxDownloads { return nil, fmt.Errorf("number of EKs (%d) bigger than the maximum allowed number (%d) of downloads", len(aeks), t.downloader.maxDownloads) } eks = make([]*EK, 0, len(aeks)) for _, aek := range aeks { ekCert := aek.Certificate ekURL := aek.CertificateURL // TODO(hs): handle case for which ekURL is empty, but TPM is from a manufacturer // that hosts EK certificates online. For Intel TPMs, the URL is constructed by go-attestation, // but that doesn't seem to be the case for other TPMs. Unsure if other TPMs do or do not // provide the proper URL when read. Also see https://github.com/tpm2-software/tpm2-tools/issues/3158. if ekCert == nil && ekURL != "" { u, err := t.prepareEKCertificateURL(ctx, ekURL) if err != nil { return nil, fmt.Errorf("failed preparing EK certificate URL: %w", err) } ekURL = u.String() ekCert, err = t.downloadEKCertificate(ctx, u) if err != nil { return nil, fmt.Errorf("failed downloading EK certificate: %w", err) } } eks = append(eks, &EK{ public: aek.Public, certificate: ekCert, certificateURL: ekURL, }) } // cache the result t.eks = eks return } // prepareEKCertificateURL prepares the URL from which an EK can be downloaded. // It parses the provided ekURL. If the TPM manufacturer is Intel, we patch the URL to // have the right format. This should become redundant when https://github.com/google/go-attestation/pull/310 // is merged. func (t *TPM) prepareEKCertificateURL(ctx context.Context, ekURL string) (*url.URL, error) { u, err := url.Parse(ekURL) if err != nil { return nil, fmt.Errorf("failed parsing EK certificate URL %q: %w", ekURL, err) } info, err := t.Info(internalCall(ctx)) if err != nil { return nil, fmt.Errorf("failed getting TPM info: %w", err) } if info.Manufacturer.ASCII == "INTC" { // Ensure the URL is in the right format. For Intel TPMs, the path // parameter contains the base64 encoding of the hash of the public key, // potentially containing padding characters, which will results in a 403, // if not transformed to `%3D`. The below has currently only be tested for // Intel TPMs, which connect to https://ekop.intel.com/ekcertservice. It may // be different for other TPM manufacturers. Ideally, I think this should be fixed in // the underlying TPM library to contain the right URL? The `intelEKURL` already // seems to do URLEncoding, though. // TODO: other TPM manufacturer URLs may need something different or similar. s := u.String() h := path.Base(s) h = strings.ReplaceAll(h, "=", "%3D") // TODO(hs): no better function in Go to do this in paths? https://github.com/golang/go/issues/27559; s = s[:strings.LastIndex(s, "/")+1] + h u, err = url.Parse(s) if err != nil { return nil, fmt.Errorf("failed parsing EK certificate URL %q: %w", s, err) } } return u, nil } func (t *TPM) downloadEKCertificate(ctx context.Context, ekURL *url.URL) (*x509.Certificate, error) { return t.downloader.downloadEKCertificate(ctx, ekURL) } type intelEKCertResponse struct { Pubhash string `json:"pubhash"` Certificate string `json:"certificate"` } // httpClient interface type httpClient interface { Do(req *http.Request) (*http.Response, error) } type downloader struct { enabled bool maxDownloads int client httpClient } // downloadEKCertificate attempts to download the EK certificate from ekURL. func (d *downloader) downloadEKCertificate(ctx context.Context, ekURL *url.URL) (*x509.Certificate, error) { if !d.enabled { // if downloads are disabled, don't try to download at all return nil, nil //nolint:nilnil // a nil *x509.Certificate is valid } req, err := http.NewRequestWithContext(ctx, http.MethodGet, ekURL.String(), http.NoBody) if err != nil { return nil, fmt.Errorf("failed creating request: %w", err) } r, err := d.client.Do(req) if err != nil { return nil, fmt.Errorf("failed retrieving EK certificate from %q: %w", ekURL, err) } defer r.Body.Close() if r.StatusCode != http.StatusOK { return nil, fmt.Errorf("http request to %q failed with status %d", ekURL, r.StatusCode) } var ekCert *x509.Certificate switch { case strings.Contains(ekURL.String(), "ekop.intel.com/ekcertservice"): // http and https work; http is redirected to https var c intelEKCertResponse if err := json.NewDecoder(r.Body).Decode(&c); err != nil { return nil, fmt.Errorf("failed decoding EK certificate response: %w", err) } cb, err := base64.RawURLEncoding.DecodeString(strings.ReplaceAll(c.Certificate, "%3D", "")) // strip padding; decode raw // TODO(hs): this is for Intel; might be different for others if err != nil { return nil, fmt.Errorf("failed base64 decoding EK certificate response: %w", err) } ekCert, err = attest.ParseEKCertificate(cb) if err != nil { return nil, fmt.Errorf("failed parsing EK certificate: %w", err) } case strings.Contains(ekURL.String(), "ftpm.amd.com/pki/aia"): // http and https work body, err := io.ReadAll(r.Body) if err != nil { return nil, fmt.Errorf("failed reading response body: %w", err) } ekCert, err = attest.ParseEKCertificate(body) if err != nil { return nil, fmt.Errorf("failed parsing EK certificate: %w", err) } // TODO(hs): does this need cases for ekcert.spserv.microsoft.com, maybe pki.infineon.com? // Also see https://learn.microsoft.com/en-us/mem/autopilot/networking-requirements#tpm default: // TODO(hs): assumption is this is the default logic. For AMD TPMs the same logic is used currently. body, err := io.ReadAll(r.Body) if err != nil { return nil, fmt.Errorf("failed reading response body: %w", err) } ekCert, err = attest.ParseEKCertificate(body) if err != nil { return nil, fmt.Errorf("failed parsing EK certificate: %w", err) } } return ekCert, nil } crypto-0.57.0/tpm/ek_test.go000066400000000000000000000235641474156331600157340ustar00rootroot00000000000000package tpm import ( "bytes" "context" "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/base64" "encoding/json" "encoding/pem" "errors" "fmt" "io" "net/http" "net/url" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/minica" "go.step.sm/crypto/x509util" ) func Test_keyType(t *testing.T) { r, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err) require.Equal(t, "RSA 2048", keyType(r.Public())) e, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) require.Equal(t, "ECDSA P-256", keyType(e.Public())) require.Equal(t, "unsupported public key type: ", keyType(nil)) } func TestEK_MarshalJSON(t *testing.T) { ca, err := minica.New( minica.WithGetSignerFunc( func() (crypto.Signer, error) { return keyutil.GenerateSigner("RSA", "", 2048) }, ), ) require.NoError(t, err) signer, err := keyutil.GenerateSigner("RSA", "", 2048) require.NoError(t, err) cr, err := x509util.NewCertificateRequest(signer) require.NoError(t, err) cr.Subject.CommonName = "testek" csr, err := cr.GetCertificateRequest() require.NoError(t, err) cert, err := ca.SignCSR(csr) require.NoError(t, err) ek := &EK{ public: signer.Public(), certificate: cert, certificateURL: "https://certificate.example.com", } data, err := json.Marshal(ek) require.NoError(t, err) m := map[string]any{} err = json.Unmarshal(data, &m) require.NoError(t, err) keyID, err := generateKeyID(signer.Public()) require.NoError(t, err) fp := "sha256:" + base64.StdEncoding.EncodeToString(keyID) fpURI := "urn:ek:" + fp require.Equal(t, m["type"], "RSA 2048") require.Equal(t, m["fingerprint"], fp) require.Equal(t, m["fingerprintURI"], fpURI) require.Equal(t, m["der"], base64.StdEncoding.EncodeToString(cert.Raw)) require.Equal(t, m["url"], "https://certificate.example.com") } type mockClient struct { doFunc func(req *http.Request) (*http.Response, error) } // Do is the mock client's `Do` func func (m *mockClient) Do(req *http.Request) (*http.Response, error) { if m.doFunc != nil { return m.doFunc(req) } return nil, errors.New("mocked doFunc not set") } func Test_downloader_downloadEKCertifiate(t *testing.T) { t.Parallel() client := &mockClient{ doFunc: func(req *http.Request) (*http.Response, error) { switch { case req.URL.String() == "https://ekop.intel.com/ekcertservice/WVEG2rRwkQ7m3RpXlUphgo6Y2HLxl18h6ZZkkOAdnBE%3D": r := io.NopCloser(bytes.NewReader([]byte(intelEKMockResponse))) return &http.Response{ StatusCode: 200, Body: r, }, nil case req.URL.String() == "https://ftpm.amd.com/pki/aia/264D39A23CEB5D5B49D610044EEBD121": b, err := base64.StdEncoding.DecodeString(amdEKRootMockResponse) require.NoError(t, err) r := io.NopCloser(bytes.NewReader(b)) return &http.Response{ StatusCode: 200, Body: r, }, nil } return nil, errors.New("unexpected URL") }, } tests := []struct { name string ctx context.Context ekURL string wantSubject string wantErr bool }{ { name: "intel", ctx: context.Background(), ekURL: "https://ekop.intel.com/ekcertservice/WVEG2rRwkQ7m3RpXlUphgo6Y2HLxl18h6ZZkkOAdnBE%3D", wantSubject: "", // no subject in EK certificate wantErr: false, }, { name: "amd EK CA root", ctx: context.Background(), ekURL: "https://ftpm.amd.com/pki/aia/264D39A23CEB5D5B49D610044EEBD121", // assumes AMD EK certificate responses are all in the same format wantSubject: "CN=AMDTPM,OU=Engineering,O=Advanced Micro Devices,L=Sunnyvale,ST=CA,C=US", // AMDTPM EK CA root subject wantErr: false, }, } for _, tt := range tests { tc := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() d := &downloader{enabled: true, maxDownloads: 10, client: client} ekURL, err := url.Parse(tc.ekURL) require.NoError(t, err) got, err := d.downloadEKCertificate(tc.ctx, ekURL) if (err != nil) != tc.wantErr { t.Errorf("downloader.downloadEKCertifiate() error = %v, wantErr %v", err, tc.wantErr) return } if assert.NoError(t, err) { assert.NotNil(t, got) assert.NotEmpty(t, got.Raw) if got.Subject.String() != tc.wantSubject { t.Errorf("downloader.downloadEKCertifiate() = %v, want %v", got.Subject.String(), tc.wantSubject) } } }) } } const ( // JSON response for https://ekop.intel.com/ekcertservice/WVEG2rRwkQ7m3RpXlUphgo6Y2HLxl18h6ZZkkOAdnBE%3D; also see https://github.com/tpm2-software/tpm2-tools/blob/master/test/integration/tests/getekcertificate.sh intelEKMockResponse = `{"pubhash":"WVEG2rRwkQ7m3RpXlUphgo6Y2HLxl18h6ZZkkOAdnBE%3D","certificate":"MIIEnDCCBEOgAwIBAgIEfT80-DAKBggqhkjOPQQDAjCBlTELMAkGA1UEBgwCVVMxCzAJBgNVBAgMAkNBMRQwEgYDVQQHDAtTYW50YSBDbGFyYTEaMBgGA1UECgwRSW50ZWwgQ29ycG9yYXRpb24xLzAtBgNVBAsMJlRQTSBFSyBpbnRlcm1lZGlhdGUgZm9yIFNQVEhfRVBJRF9QUk9EMRYwFAYDVQQDDA13d3cuaW50ZWwuY29tMB4XDTE1MDUyMjAwMDAwMFoXDTQ5MTIzMTIzNTk1OVowADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMMg4vJEqGAarPPgHSbGZSSZNVYt4doZfp5_B2xGlhPPtlPpjsLDhvwdEz8sjGzDOLcy8LIIvYOKh3o-W7w-HUCE6DXHyJBqHAW00tMP2-vB262VD6axZb1LaoZGAxRhZMDE9Z1IkBHvH5KN7qbpAGHz03XlZGJzFR72IiUgmL4aSrAdwKEiJ8YJ_azrEVr0CNRpOm9JkZd0aVsMErwYof9xIKczey-18ZUdi7fwlNW1VMEclSOzByn-ZHh9ChO55jBIjatN_YZjSlJw7HL8xaRNxnmo8yk43YGX4p2ug59bTKD13ifJUiwjxU4cLOV4WVJRGL1EcLGBgO73iuQme80CAwEAAaOCAkgwggJEMA8GA1UdEwEB_wQFMAMBAQAwDgYDVR0PAQH_BAQDAgAgMBAGA1UdJQQJMAcGBWeBBQgBMCQGA1UdCQEBAAQaMBgwFgYFZ4EFAhAxDTALDAMyLjACAQACAWcwUAYDVR0RAQH_BEYwRKRCMEAxFjAUBgVngQUCAQwLaWQ6NDk0RTU0NDMxDjAMBgVngQUCAgwDU1BUMRYwFAYFZ4EFAgMMC2lkOjAwMDIwMDAwMB8GA1UdIwQYMBaAFF5zyJqj6QKycrnwdB99hzDj7HJKMFgGA1UdHwRRME8wTaBLoEmGR2h0dHA6Ly91cGdyYWRlcy5pbnRlbC5jb20vY29udGVudC9DUkwvZWtjZXJ0L1NQVEhFUElEUFJPRF9FS19EZXZpY2UuY3JsMHAGCCsGAQUFBwEBBGQwYjBgBggrBgEFBQcwAoZUaHR0cDovL3VwZ3JhZGVzLmludGVsLmNvbS9jb250ZW50L0NSTC9la2NlcnQvU1BUSEVQSURQUk9EX0VLX1BsYXRmb3JtX1B1YmxpY19LZXkuY2VyMIGpBgNVHSAEgaEwgZ4wgZsGCiqGSIb4TQEFAgEwgYwwUgYIKwYBBQUHAgEWRmh0dHA6Ly91cGdyYWRlcy5pbnRlbC5jb20vY29udGVudC9DUkwvZWtjZXJ0L0VLY2VydFBvbGljeVN0YXRlbWVudC5wZGYwNgYIKwYBBQUHAgIwKgwoVENQQSBUcnVzdGVkIFBsYXRmb3JtIE1vZHVsZSBFbmRvcnNlbWVudDAKBggqhkjOPQQDAgNHADBEAiBrQr0ckEoWsrx0971bppP6N8PTb4U6z_hIqpS6o150xAIgNxZNXq7bCqU1b4hGdiSBauowiOVFcaaiTm1p99H_k1Q%3D"}` // base64 encoded response for https://ftpm.amd.com/pki/aia/264D39A23CEB5D5B49D610044EEBD121 amdEKRootMockResponse = `MIIEiDCCA3CgAwIBAgIQJk05ojzrXVtJ1hAETuvRITANBgkqhkiG9w0BAQsFADB2MRQwEgYDVQQLEwtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxEjAQBgNVBAcTCVN1bm55dmFsZTELMAkGA1UECBMCQ0ExHzAdBgNVBAoTFkFkdmFuY2VkIE1pY3JvIERldmljZXMxDzANBgNVBAMTBkFNRFRQTTAeFw0xNDEwMjMxNDM0MzJaFw0zOTEwMjMxNDM0MzJaMHYxFDASBgNVBAsTC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzESMBAGA1UEBxMJU3Vubnl2YWxlMQswCQYDVQQIEwJDQTEfMB0GA1UEChMWQWR2YW5jZWQgTWljcm8gRGV2aWNlczEPMA0GA1UEAxMGQU1EVFBNMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAssnOAYu5nRflQk0bVtsTFcLSAMx9odZ4Ey3n6/MA6FD7DECIE70RGZgaRIID0eb+dyX3znMrp1TS+lD+GJSw7yDJrKeU4it8cMLqFrqGm4SEx/X5GBa11sTmL4i60pJ5nDo2T69OiJ+iqYzgBfYJLqHQaeSRN6bBYyn3w1H4JNzPDNvqKHvkPfYewHjUAFJAI1dShYO8REnNCB8eeolj375nymfAAZzgA8v7zmFX/1tVLCy7Mm6n7zndT452TB1mek9LC5LkwlnyABwaN2Q8LV4NWpIAzTgr55xbU5VvgcIpw+/qcbYHmqL6ZzCSeE1gRKQXlsybK+W4phCtQfMgHQIDAQABo4IBEDCCAQwwDgYDVR0PAQH/BAQDAgEGMCMGCSsGAQQBgjcVKwQWBBRXjFRfeWlRQhIhpKV4rNtfaC+JyDAdBgNVHQ4EFgQUV4xUX3lpUUISIaSleKzbX2gvicgwDwYDVR0TAQH/BAUwAwEB/zA4BggrBgEFBQcBAQQsMCowKAYIKwYBBQUHMAGGHGh0dHA6Ly9mdHBtLmFtZC5jb20vcGtpL29jc3AwLAYDVR0fBCUwIzAhoB+gHYYbaHR0cDovL2Z0cG0uYW1kLmNvbS9wa2kvY3JsMD0GA1UdIAQ2MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL2Z0cG0uYW1kLmNvbS9wa2kvY3BzMA0GCSqGSIb3DQEBCwUAA4IBAQCWB9yAoYYIt5HRY/OqJ5LUacP6rNmsMfPUDTcahXB3iQmY8HpUoGB23lhxbq+kz3vIiGAcUdKHlpB/epXyhABGTcJrNPMfx9akLqhI7WnMCPBbHDDDzKjjMB3Vm65PFbyuqbLujN/sN6kNtc4hL5r5Pr6Mze5H9WXBo2F2Oy+7+9jWMkxNrmUhoUUrF/6YsajTGPeq7r+i6q84W2nJdd+BoQQv4sk5GeuN2j2u4k1a8DkRPsVPc2I9QTtbzekchTK1GCXWki3DKGkZUEuaoaa60Kgw55Q5rt1eK7HKEG5npmR8aEod7BDLWy4CMTNAWR5iabCW/KX28JbJL6Phau9j` ) func TestEK_FingerprintURI(t *testing.T) { signer, err := keyutil.GenerateSigner("RSA", "", 2048) require.NoError(t, err) ek := &EK{ public: signer.Public(), } keyID, err := generateKeyID(signer.Public()) require.NoError(t, err) exp := fmt.Sprintf("urn:ek:sha256:%s", base64.StdEncoding.EncodeToString(keyID)) u, err := ek.FingerprintURI() assert.NoError(t, err) assert.Equal(t, exp, u.String()) invalidEK := &EK{} u, err = invalidEK.FingerprintURI() assert.Error(t, err) assert.Nil(t, u) } func TestEK_PEM(t *testing.T) { ca, err := minica.New( minica.WithGetSignerFunc( func() (crypto.Signer, error) { return keyutil.GenerateSigner("RSA", "", 2048) }, ), ) require.NoError(t, err) signer, err := keyutil.GenerateSigner("RSA", "", 2048) require.NoError(t, err) cr, err := x509util.NewCertificateRequest(signer) require.NoError(t, err) cr.Subject.CommonName = "testek" csr, err := cr.GetCertificateRequest() require.NoError(t, err) cert, err := ca.SignCSR(csr) require.NoError(t, err) pemString := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: cert.Raw, }) type fields struct { public crypto.PublicKey certificate *x509.Certificate certificateURL string } tests := []struct { name string fields fields want string wantErr bool }{ { name: "fail/no-ek-cert", fields: fields{}, wantErr: true, }, { name: "ok", fields: fields{ certificate: cert, }, want: string(pemString), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ek := &EK{ public: tt.fields.public, certificate: tt.fields.certificate, certificateURL: tt.fields.certificateURL, } got, err := ek.PEM() if (err != nil) != tt.wantErr { t.Errorf("EK.PEM() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("EK.PEM() = %v, want %v", got, tt.want) } }) } } crypto-0.57.0/tpm/errors.go000066400000000000000000000007121474156331600156000ustar00rootroot00000000000000package tpm import ( "errors" "go.step.sm/crypto/tpm/storage" ) // ErrNotFound is returned when a Key or AK cannot be found var ErrNotFound = errors.New("not found") // ErrExists is returned when a Key or AK already exists var ErrExists = errors.New("already exists") // ErrNoStorageConfigured is returned when a TPM operation is // performed that requires a storage to have been configured var ErrNoStorageConfigured = storage.ErrNoStorageConfigured crypto-0.57.0/tpm/info.go000066400000000000000000000100341474156331600152150ustar00rootroot00000000000000package tpm import ( "context" "encoding/json" "fmt" "github.com/smallstep/go-attestation/attest" "go.step.sm/crypto/tpm/manufacturer" ) // Info models information about a TPM. It contains the // TPM version, interface, manufacturer, vendor info and // firmware version. type Info struct { Version Version `json:"version"` Interface Interface `json:"interface"` Manufacturer Manufacturer `json:"manufacturer"` VendorInfo string `json:"vendorInfo,omitempty"` FirmwareVersion FirmwareVersion `json:"firmwareVersion,omitempty"` } // Version models the TPM specification version supported // by the TPM. type Version attest.TPMVersion func (v Version) String() string { switch v { case Version(attest.TPMVersion12): return "TPM 1.2" case Version(attest.TPMVersion20): return "TPM 2.0" default: return fmt.Sprintf("unknown (%d)", v) } } // MarshalJSON marshals the version into JSON. func (v Version) MarshalJSON() ([]byte, error) { var s string switch v { case Version(attest.TPMVersion12): s = "1.2" case Version(attest.TPMVersion20): s = "2.0" default: s = fmt.Sprintf("unknown (%d)", v) } return json.Marshal(s) } // Interface models a TPM interface. type Interface attest.TPMInterface // String returns a textual representation of the // TPM interface. func (i Interface) String() string { switch i { case Interface(attest.TPMInterfaceDirect): return "direct" case Interface(attest.TPMInterfaceKernelManaged): return "kernel-managed" case Interface(attest.TPMInterfaceDaemonManaged): return "daemon-managed" case Interface(attest.TPMInterfaceCommandChannel): return "command-channel" default: return fmt.Sprintf("unknown (%d)", i) } } // MarshalJSON marshals the TPM interface into JSON. func (i Interface) MarshalJSON() ([]byte, error) { return json.Marshal(i.String()) } // FirmwareVersion models the TPM firmware version. type FirmwareVersion struct { Major int Minor int } // String returns a textual representation of the // TPM firmware version. func (fv FirmwareVersion) String() string { return fmt.Sprintf("%d.%d", fv.Major, fv.Minor) } // MarshalJSON marshals the TPM firmware version to JSON. func (fv FirmwareVersion) MarshalJSON() ([]byte, error) { // TODO(hs): make empty if major.minor is 0.0? return json.Marshal(fv.String()) } // Manufacturer models a TPM Manufacturer. type Manufacturer struct { ID manufacturer.ID `json:"id"` Name string `json:"name"` ASCII string `json:"ascii"` Hex string `json:"hex"` } // String returns a textual representation of the TPM // manufacturer. An example looks like this: // // ST Microelectronics (, 53544D20, 1398033696) func (m Manufacturer) String() string { return fmt.Sprintf("%s (<%s>, %s, %d)", m.Name, m.ASCII, m.Hex, m.ID) } // GetManufacturerByID returns a Manufacturer based on its Manufacturer ID // code. func GetManufacturerByID(id manufacturer.ID) (m Manufacturer) { m.ID = id m.ASCII, m.Hex = manufacturer.GetEncodings(id) // the FIDO Alliance fake TPM vendor ID doesn't conform to the standard ASCII lookup if id == 4294963664 { m.ASCII = "FIDO" } m.Name = manufacturer.GetNameByASCII(m.ASCII) return } // Info returns info about the TPM. The info doesn't change, so // it's cached after the first lookup. func (t *TPM) Info(ctx context.Context) (info *Info, err error) { if t.info != nil { return t.info, nil } if err = t.open(ctx); err != nil { return nil, fmt.Errorf("failed opening TPM: %w", err) } defer closeTPM(ctx, t, &err) ainfo, err := t.attestTPM.Info() if err != nil { return nil, fmt.Errorf("failed getting TPM info: %w", err) } // the TPM info won't change, so it's cached for future lookups info = &Info{ FirmwareVersion: FirmwareVersion{ Major: ainfo.FirmwareVersionMajor, Minor: ainfo.FirmwareVersionMinor, }, Interface: Interface(ainfo.Interface), Manufacturer: GetManufacturerByID(manufacturer.ID(ainfo.Manufacturer)), VendorInfo: ainfo.VendorInfo, Version: Version(ainfo.Version), } t.info = info return } crypto-0.57.0/tpm/info_test.go000066400000000000000000000061341474156331600162620ustar00rootroot00000000000000package tpm import ( "encoding/json" "testing" "github.com/smallstep/go-attestation/attest" "github.com/stretchr/testify/require" "go.step.sm/crypto/tpm/manufacturer" ) func TestInterface_MarshalJSON(t *testing.T) { b, err := json.Marshal(Interface(attest.TPMInterfaceDirect)) require.NoError(t, err) require.JSONEq(t, `"direct"`, string(b)) b, err = json.Marshal(Interface(attest.TPMInterfaceKernelManaged)) require.NoError(t, err) require.JSONEq(t, `"kernel-managed"`, string(b)) b, err = json.Marshal(Interface(attest.TPMInterfaceDaemonManaged)) require.NoError(t, err) require.JSONEq(t, `"daemon-managed"`, string(b)) b, err = json.Marshal(Interface(attest.TPMInterfaceCommandChannel)) require.NoError(t, err) require.JSONEq(t, `"command-channel"`, string(b)) b, err = json.Marshal(Interface(255)) require.NoError(t, err) require.JSONEq(t, `"unknown (255)"`, string(b)) } func TestFirmwareVersion_MarshalJSON(t *testing.T) { b, err := json.Marshal(FirmwareVersion{Major: 0, Minor: 0}) require.NoError(t, err) require.JSONEq(t, `"0.0"`, string(b)) b, err = json.Marshal(FirmwareVersion{Major: 13, Minor: 37}) require.NoError(t, err) require.JSONEq(t, `"13.37"`, string(b)) } func TestVersion_MarshalJSON(t *testing.T) { b, err := json.Marshal(Version(attest.TPMVersion12)) require.NoError(t, err) require.JSONEq(t, `"1.2"`, string(b)) b, err = json.Marshal(Version(attest.TPMVersion20)) require.NoError(t, err) require.JSONEq(t, `"2.0"`, string(b)) b, err = json.Marshal(Version(0)) require.NoError(t, err) require.JSONEq(t, `"unknown (0)"`, string(b)) } func TestVersion_String(t *testing.T) { require.Equal(t, "TPM 1.2", Version(attest.TPMVersion12).String()) require.Equal(t, "TPM 2.0", Version(attest.TPMVersion20).String()) require.Equal(t, "unknown (0)", Version(0).String()) } func Test_GetManufacturerByID(t *testing.T) { tests := []struct { name string id manufacturer.ID want Manufacturer }{ {"infineon", 1229346816, Manufacturer{1229346816, "Infineon", "IFX", "49465800"}}, {"intel", 1229870147, Manufacturer{1229870147, "Intel", "INTC", "494E5443"}}, {"fido", 4294963664, Manufacturer{4294963664, "FIDO Alliance", "FIDO", "FFFFF1D0"}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := GetManufacturerByID(tt.id); got != tt.want { t.Errorf("getManufacturerByID() = %v, want %v", got, tt.want) } }) } } func TestManufacturer_String(t *testing.T) { m := Manufacturer{ Name: "ST Microelectronics", ASCII: "STM ", ID: 1398033696, Hex: "53544D20", } want := "ST Microelectronics (, 53544D20, 1398033696)" if got := m.String(); got != want { t.Errorf("Manufacturer.String() = %v, want %v", got, want) } } func TestID_MarshalJSON(t *testing.T) { e := Manufacturer{} b, err := json.Marshal(e) require.NoError(t, err) require.JSONEq(t, `{"ascii":"", "hex":"", "id":"0", "name":""}`, string(b)) m := Manufacturer{1229346816, "Infineon", "IFX", "49465800"} b, err = json.Marshal(m) require.NoError(t, err) require.JSONEq(t, `{"id":"1229346816", "name":"Infineon", "ascii":"IFX", "hex":"49465800"}`, string(b)) } crypto-0.57.0/tpm/internal/000077500000000000000000000000001474156331600155515ustar00rootroot00000000000000crypto-0.57.0/tpm/internal/close/000077500000000000000000000000001474156331600166565ustar00rootroot00000000000000crypto-0.57.0/tpm/internal/close/close.go000066400000000000000000000003511474156331600203110ustar00rootroot00000000000000package closer import ( "io" "github.com/smallstep/go-attestation/attest" ) func RWC(rwc io.ReadWriteCloser) error { return closeRWC(rwc) } func AttestTPM(t *attest.TPM, c *attest.OpenConfig) error { return attestTPM(t, c) } crypto-0.57.0/tpm/internal/close/close_others.go000066400000000000000000000012171474156331600216770ustar00rootroot00000000000000//go:build !windows // +build !windows package closer import ( "io" "github.com/google/go-tpm/tpmutil" "github.com/smallstep/go-attestation/attest" "go.step.sm/crypto/tpm/internal/socket" ) func closeRWC(rwc io.ReadWriteCloser) error { if _, ok := rwc.(*tpmutil.EmulatorReadWriteCloser); ok { return nil // EmulatorReadWriteCloser automatically closes on every write/read cycle } return rwc.Close() } func attestTPM(t *attest.TPM, c *attest.OpenConfig) error { if _, ok := c.CommandChannel.(*socket.CommandChannelWithoutMeasurementLog); ok { return nil // backed by tpmutil.EmulatorReadWriteCloser; already closed } return t.Close() } crypto-0.57.0/tpm/internal/close/close_windows.go000066400000000000000000000004141474156331600220630ustar00rootroot00000000000000//go:build windows // +build windows package closer import ( "io" "github.com/smallstep/go-attestation/attest" ) func closeRWC(rwc io.ReadWriteCloser) error { return rwc.Close() } func attestTPM(t *attest.TPM, _ *attest.OpenConfig) error { return t.Close() } crypto-0.57.0/tpm/internal/key/000077500000000000000000000000001474156331600163415ustar00rootroot00000000000000crypto-0.57.0/tpm/internal/key/create.go000066400000000000000000000016761474156331600201450ustar00rootroot00000000000000package key import "io" // Create creates a new TPM key without attesting it and returns a // serialized representation of it. The serialized format is compatible // with the `go-attestation` format. Most of the code in this package // is in fact copied from `go-attestation`, as large parts of its code // are not publicly available at the moment. The code is useful, as it // allows keys to be created in exactly the same way `go-attestation` // creates them, except without attesting them. Both types of keys can // be used for similar purposes, but only keys attested by an AK can be // proved to be actually only resident in a TPM. // // TODO: it might be an option to make some more things public in the // `go-attestation` package, or to change some of the logic of the // `NewKey` function that makes the AK optional. func Create(rwc io.ReadWriteCloser, keyName string, config CreateConfig) ([]byte, error) { return create(rwc, keyName, config) } crypto-0.57.0/tpm/internal/key/key.go000066400000000000000000000213711474156331600174640ustar00rootroot00000000000000// CODE COPIED FROM github.com/google/go-attestation; DO NOT EDIT! // // Copyright 2019 Google Inc. // // 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. //nolint:errorlint,revive // copied code from github.com/google/go-attestation package key import ( "encoding/json" "fmt" "io" "github.com/google/go-tpm/legacy/tpm2" "github.com/google/go-tpm/tpmutil" ) const ( // Defined in "Registry of reserved TPM 2.0 handles and localities". nvramCertIndex = 0x1c00002 nvramEkNonceIndex = 0x1c00003 // Defined in "Registry of reserved TPM 2.0 handles and localities", and checked on a glinux machine. commonSrkEquivalentHandle = tpmutil.Handle(0x81000001) commonEkEquivalentHandle = tpmutil.Handle(0x81010001) ) // Key encodings const ( keyEncodingInvalid keyEncoding = iota // Managed by the OS but loadable by name. keyEncodingOSManaged // Key fully represented but in encrypted form. keyEncodingEncrypted // Parameters stored, but key must be regenerated before use. keyEncodingParameterized ) // keyEncoding indicates how an exported TPM key is represented. type keyEncoding uint8 func (e keyEncoding) String() string { switch e { case keyEncodingInvalid: return "invalid" case keyEncodingOSManaged: return "os-managed" case keyEncodingEncrypted: return "encrypted" case keyEncodingParameterized: return "parameterized" default: return fmt.Sprintf("keyEncoding<%d>", int(e)) } } // serializedKey represents a loadable, TPM-backed key. type serializedKey struct { // Encoding describes the strategy by which the key should be // loaded/unloaded. Encoding keyEncoding `json:"KeyEncoding"` // TPMVersion describes the version of the TPM which the key was generated // on. deserializeKey() returns an error if it attempts to deserialize a key // which is from a different TPM version to the currently opened TPM. TPMVersion uint8 // Public represents the public key, in a TPM-specific format. This // field is populated on all platforms and TPM versions. Public []byte // The following fields are only valid for TPM 2.0 hardware, holding // information returned as the result to a TPM2_CertifyCreation command. // These are stored alongside the key for later use, as the certification // can only be obtained immediately after the key is generated. CreateData []byte CreateAttestation []byte CreateSignature []byte // Name is only valid for KeyEncodingOSManaged, which is only used // on Windows. Name string // Blob represents the key material for KeyEncodingEncrypted keys. This // is only used on Linux. Blob []byte `json:"KeyBlob"` } // Serialize represents the key in a persistent format which may be // loaded at a later time using deserializeKey(). func (k *serializedKey) Serialize() ([]byte, error) { return json.Marshal(k) } type CreateConfig struct { // Algorithm to be used, either RSA or ECDSA. Algorithm string // Size is used to specify the bit size of the key or elliptic curve. For // example, '256' is used to specify curve P-256. Size int } func (c *CreateConfig) Validate() error { switch c.Algorithm { case "RSA": if c.Size > 2048 { return fmt.Errorf("%d bits RSA keys are (currently) not supported in go.step.sm/crypto; maximum is 2048", c.Size) } case "ECDSA": break default: return fmt.Errorf("unsupported algorithm %q", c.Algorithm) } return nil } var tpmEkTemplate *tpm2.Public func ekTemplate(rwc io.ReadWriteCloser) (tpm2.Public, error) { if tpmEkTemplate != nil { return *tpmEkTemplate, nil } nonce, err := tpm2.NVReadEx(rwc, nvramEkNonceIndex, tpm2.HandleOwner, "", 0) if err != nil { tpmEkTemplate = &defaultEKTemplate // No nonce, use the default template } else { template := defaultEKTemplate copy(template.RSAParameters.ModulusRaw, nonce) tpmEkTemplate = &template } return *tpmEkTemplate, nil } // Return value: handle, whether we generated a new one, error func getPrimaryKeyHandle(rwc io.ReadWriteCloser, pHnd tpmutil.Handle) (tpmutil.Handle, bool, error) { _, _, _, err := tpm2.ReadPublic(rwc, pHnd) if err == nil { // Found the persistent handle, assume it's the key we want. return pHnd, false, nil } rerr := err // Preserve this failure for later logging, if needed var keyHnd tpmutil.Handle switch pHnd { case commonSrkEquivalentHandle: keyHnd, _, err = tpm2.CreatePrimary(rwc, tpm2.HandleOwner, tpm2.PCRSelection{}, "", "", defaultSRKTemplate) case commonEkEquivalentHandle: var tmpl tpm2.Public if tmpl, err = ekTemplate(rwc); err != nil { return 0, false, fmt.Errorf("ek template: %v", err) } keyHnd, _, err = tpm2.CreatePrimary(rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", tmpl) } if err != nil { return 0, false, fmt.Errorf("ReadPublic failed (%v), and then CreatePrimary failed: %v", rerr, err) } defer tpm2.FlushContext(rwc, keyHnd) err = tpm2.EvictControl(rwc, "", tpm2.HandleOwner, keyHnd, pHnd) if err != nil { return 0, false, fmt.Errorf("EvictControl failed: %v", err) } return pHnd, true, nil } // Algorithm indicates an asymmetric algorithm to be used. type Algorithm string // Algorithm types supported. const ( ECDSA Algorithm = "ECDSA" RSA Algorithm = "RSA" ) type KeyConfig struct { Algorithm Algorithm Size int } var ( defaultSRKTemplate = tpm2.Public{ Type: tpm2.AlgRSA, NameAlg: tpm2.AlgSHA256, Attributes: tpm2.FlagStorageDefault | tpm2.FlagNoDA, RSAParameters: &tpm2.RSAParams{ Symmetric: &tpm2.SymScheme{ Alg: tpm2.AlgAES, KeyBits: 128, Mode: tpm2.AlgCFB, }, ModulusRaw: make([]byte, 256), KeyBits: 2048, }, } // Default EK template defined in: // https://trustedcomputinggroup.org/wp-content/uploads/Credential_Profile_EK_V2.0_R14_published.pdf defaultEKTemplate = tpm2.Public{ Type: tpm2.AlgRSA, NameAlg: tpm2.AlgSHA256, Attributes: tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin | tpm2.FlagAdminWithPolicy | tpm2.FlagRestricted | tpm2.FlagDecrypt, AuthPolicy: []byte{ 0x83, 0x71, 0x97, 0x67, 0x44, 0x84, 0xB3, 0xF8, 0x1A, 0x90, 0xCC, 0x8D, 0x46, 0xA5, 0xD7, 0x24, 0xFD, 0x52, 0xD7, 0x6E, 0x06, 0x52, 0x0B, 0x64, 0xF2, 0xA1, 0xDA, 0x1B, 0x33, 0x14, 0x69, 0xAA, }, RSAParameters: &tpm2.RSAParams{ Symmetric: &tpm2.SymScheme{ Alg: tpm2.AlgAES, KeyBits: 128, Mode: tpm2.AlgCFB, }, KeyBits: 2048, ModulusRaw: make([]byte, 256), }, } // Basic template for an ECDSA key signing outside-TPM objects. Other // fields are populated depending on the key creation options. ecdsaKeyTemplate = tpm2.Public{ Type: tpm2.AlgECC, Attributes: tpm2.FlagSignerDefault ^ tpm2.FlagRestricted, ECCParameters: &tpm2.ECCParams{ Sign: &tpm2.SigScheme{ Alg: tpm2.AlgECDSA, }, }, } // Basic template for an RSA key signing outside-TPM objects. Other // fields are populated depending on the key creation options. rsaKeyTemplate = tpm2.Public{ Type: tpm2.AlgRSA, NameAlg: tpm2.AlgSHA256, Attributes: tpm2.FlagSignerDefault ^ tpm2.FlagRestricted, RSAParameters: &tpm2.RSAParams{}, } ) func templateFromConfig(opts *KeyConfig) (tpm2.Public, error) { var tmpl tpm2.Public switch opts.Algorithm { case RSA: tmpl = rsaKeyTemplate if opts.Size < 0 || opts.Size > 65535 { // basic sanity check return tmpl, fmt.Errorf("incorrect size parameter") } tmpl.RSAParameters.KeyBits = uint16(opts.Size) case ECDSA: tmpl = ecdsaKeyTemplate switch opts.Size { case 256: tmpl.NameAlg = tpm2.AlgSHA256 tmpl.ECCParameters.Sign.Hash = tpm2.AlgSHA256 tmpl.ECCParameters.CurveID = tpm2.CurveNISTP256 tmpl.ECCParameters.Point = tpm2.ECPoint{ XRaw: make([]byte, 32), YRaw: make([]byte, 32), } case 384: tmpl.NameAlg = tpm2.AlgSHA384 tmpl.ECCParameters.Sign.Hash = tpm2.AlgSHA384 tmpl.ECCParameters.CurveID = tpm2.CurveNISTP384 tmpl.ECCParameters.Point = tpm2.ECPoint{ XRaw: make([]byte, 48), YRaw: make([]byte, 48), } case 521: tmpl.NameAlg = tpm2.AlgSHA512 tmpl.ECCParameters.Sign.Hash = tpm2.AlgSHA512 tmpl.ECCParameters.CurveID = tpm2.CurveNISTP521 tmpl.ECCParameters.Point = tpm2.ECPoint{ XRaw: make([]byte, 66), YRaw: make([]byte, 66), } default: return tmpl, fmt.Errorf("unsupported key size: %v", opts.Size) } default: return tmpl, fmt.Errorf("unsupported algorithm type: %q", opts.Algorithm) } return tmpl, nil } crypto-0.57.0/tpm/internal/key/key_others.go000066400000000000000000000017371474156331600210540ustar00rootroot00000000000000//go:build !windows // +build !windows package key import ( "fmt" "io" "github.com/google/go-tpm/legacy/tpm2" ) func create(rwc io.ReadWriteCloser, keyName string, config CreateConfig) ([]byte, error) { srk, _, err := getPrimaryKeyHandle(rwc, commonSrkEquivalentHandle) if err != nil { return nil, fmt.Errorf("failed to get SRK handle: %w", err) } tmpl, err := templateFromConfig(&KeyConfig{Algorithm: Algorithm(config.Algorithm), Size: config.Size}) if err != nil { return nil, fmt.Errorf("incorrect key options: %w", err) } blob, pub, creationData, _, _, err := tpm2.CreateKey(rwc, srk, tpm2.PCRSelection{}, "", "", tmpl) if err != nil { return nil, fmt.Errorf("CreateKey() failed: %w", err) } out := serializedKey{ Encoding: keyEncodingEncrypted, TPMVersion: uint8(2), // hardcoded to not import github.com/google/go-attestation/attest Name: keyName, Public: pub, Blob: blob, CreateData: creationData, } return out.Serialize() } crypto-0.57.0/tpm/internal/key/key_windows.go000066400000000000000000000013051474156331600212310ustar00rootroot00000000000000//go:build windows // +build windows package key import ( "fmt" "io" ) func create(_ io.ReadWriteCloser, keyName string, config CreateConfig) ([]byte, error) { pcp, err := openPCP() if err != nil { return nil, fmt.Errorf("failed to open PCP: %w", err) } defer pcp.Close() _, pub, _, err := pcp.NewKey(keyName, &KeyConfig{Algorithm: Algorithm(config.Algorithm), Size: config.Size}) if err != nil { return nil, fmt.Errorf("pcp failed to mint application key: %w", err) } out := serializedKey{ Encoding: keyEncodingOSManaged, TPMVersion: uint8(2), // hardcoded to not import github.com/google/go-attestation/attest Name: keyName, Public: pub, } return out.Serialize() } crypto-0.57.0/tpm/internal/key/pcp_windows.go000066400000000000000000000437601474156331600212360ustar00rootroot00000000000000// CODE COPIED FROM github.com/google/go-attestation; DO NOT EDIT! // // Copyright 2019 Google Inc. // // 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. //go:build windows // +build windows //nolint:errorlint,revive // copied package https://github.com/google/go-attestation/blob/master/attest/pcp_windows.go package key import ( "bytes" "encoding/binary" "fmt" "unsafe" "golang.org/x/sys/windows" ) const ( pcpProviderName = "Microsoft Platform Crypto Provider" cryptENotFound = 0x80092004 // From winerror.h. // The below is documented in this Microsoft whitepaper: // https://github.com/Microsoft/TSS.MSR/blob/master/PCPTool.v11/Using%20the%20Windows%208%20Platform%20Crypto%20Provider%20and%20Associated%20TPM%20Functionality.pdf ncryptOverwriteKeyFlag = 0x80 // Key usage value for generic keys nCryptPropertyPCPKeyUsagePolicyGeneric = 0x3 // Key usage value for AKs. nCryptPropertyPCPKeyUsagePolicyIdentity = 0x8 // PCP key magic pcpKeyMagic = 0x4D504350 // TPM types from PCP_KEY_BLOB header data tpm12 = 0x1 tpm20 = 0x2 ) // DLL references. var ( nCrypt = windows.MustLoadDLL("ncrypt.dll") nCryptOpenStorageProvider = nCrypt.MustFindProc("NCryptOpenStorageProvider") nCryptFreeObject = nCrypt.MustFindProc("NCryptFreeObject") nCryptGetProperty = nCrypt.MustFindProc("NCryptGetProperty") nCryptSetProperty = nCrypt.MustFindProc("NCryptSetProperty") nCryptOpenKey = nCrypt.MustFindProc("NCryptOpenKey") nCryptCreatePersistedKey = nCrypt.MustFindProc("NCryptCreatePersistedKey") nCryptFinalizeKey = nCrypt.MustFindProc("NCryptFinalizeKey") nCryptDeleteKey = nCrypt.MustFindProc("NCryptDeleteKey") nCryptExportKey = nCrypt.MustFindProc("NCryptExportKey") crypt32 = windows.MustLoadDLL("crypt32.dll") crypt32CertEnumCertificatesInStore = crypt32.MustFindProc("CertEnumCertificatesInStore") crypt32CertCloseStore = crypt32.MustFindProc("CertCloseStore") tbs *windows.DLL tbsGetDeviceInfo *windows.Proc ) // Error codes. var ( isReadyErrors = map[uint32]string{ 0x00000002: "Platform restart is required (shutdown).", 0x00000004: "Platform restart is required (reboot).", 0x00000008: "The TPM is already owned.", 0x00000010: "Physical presence is required to provision the TPM.", 0x00000020: "The TPM is disabled or deactivated.", 0x00000040: "TPM ownership was taken.", 0x00000080: "An endorsement key exists in the TPM.", 0x00000100: "The TPM owner authorization is not properly stored in the registry.", 0x00000200: "The Storage Root Key (SRK) authorization value is not all zeros.", 0x00000800: "The operating system's registry information about the TPMโ€™s Storage Root Key does not match the TPM Storage Root Key.", 0x00001000: "The TPM permanent flag to allow reading of the Storage Root Key public value is not set.", 0x00002000: "The monotonic counter incremented during boot has not been created.", 0x00020000: "Windows Group Policy is configured to not store any TPM owner authorization so the TPM cannot be fully ready.", 0x00040000: "The EK Certificate was not read from the TPM NV Ram and stored in the registry.", 0x00080000: "The TCG event log is empty or cannot be read.", 0x00100000: "The TPM is not owned.", 0x00200000: "An error occurred, but not specific to a particular task.", 0x00400000: "The device lock counter has not been created.", 0x00800000: "The device identifier has not been created.", } tpmErrNums = map[uint32]string{ 0x80280001: "TPM_E_AUTHFAIL", 0x80280002: "TPM_E_BADINDEX", 0x80280003: "TPM_E_BAD_PARAMETER", 0x80280004: "TPM_E_AUDITFAILURE", 0x80280005: "TPM_E_CLEAR_DISABLED", 0x80280006: "TPM_E_DEACTIVATED", 0x80280007: "TPM_E_DISABLED", 0x80280008: "TPM_E_DISABLED_CMD", 0x80280009: "TPM_E_FAIL", 0x8028000A: "TPM_E_BAD_ORDINAL", 0x8028000B: "TPM_E_INSTALL_DISABLED", 0x8028000C: "TPM_E_INVALID_KEYHANDLE", 0x8028000D: "TPM_E_KEYNOTFOUND", 0x8028000E: "TPM_E_INAPPROPRIATE_ENC", 0x8028000F: "TPM_E_MIGRATEFAIL", 0x80280010: "TPM_E_INVALID_PCR_INFO", 0x80280011: "TPM_E_NOSPACE", 0x80280012: "TPM_E_NOSRK", 0x80280013: "TPM_E_NOTSEALED_BLOB", 0x80280014: "TPM_E_OWNER_SET", 0x80280015: "TPM_E_RESOURCES", 0x80280016: "TPM_E_SHORTRANDOM", 0x80280017: "TPM_E_SIZE", 0x80280018: "TPM_E_WRONGPCRVAL", 0x80280019: "TPM_E_BAD_PARAM_SIZE", 0x8028001A: "TPM_E_SHA_THREAD", 0x8028001B: "TPM_E_SHA_ERROR", 0x8028001C: "TPM_E_FAILEDSELFTEST", 0x8028001D: "TPM_E_AUTH2FAIL", 0x8028001E: "TPM_E_BADTAG", 0x8028001F: "TPM_E_IOERROR", 0x80280020: "TPM_E_ENCRYPT_ERROR", 0x80280021: "TPM_E_DECRYPT_ERROR", 0x80280022: "TPM_E_INVALID_AUTHHANDLE", 0x80280023: "TPM_E_NO_ENDORSEMENT", 0x80280024: "TPM_E_INVALID_KEYUSAGE", 0x80280025: "TPM_E_WRONG_ENTITYTYPE", 0x80280026: "TPM_E_INVALID_POSTINIT", 0x80280027: "TPM_E_INAPPROPRIATE_SIG", 0x80280028: "TPM_E_BAD_KEY_PROPERTY", 0x80280029: "TPM_E_BAD_MIGRATION", 0x8028002A: "TPM_E_BAD_SCHEME", 0x8028002B: "TPM_E_BAD_DATASIZE", 0x8028002C: "TPM_E_BAD_MODE", 0x8028002D: "TPM_E_BAD_PRESENCE", 0x8028002E: "TPM_E_BAD_VERSION", 0x8028002F: "TPM_E_NO_WRAP_TRANSPORT", 0x80280030: "TPM_E_AUDITFAIL_UNSUCCESSFUL", 0x80280031: "TPM_E_AUDITFAIL_SUCCESSFUL", 0x80280032: "TPM_E_NOTRESETABLE", 0x80280033: "TPM_E_NOTLOCAL", 0x80280034: "TPM_E_BAD_TYPE", 0x80280035: "TPM_E_INVALID_RESOURCE", 0x80280036: "TPM_E_NOTFIPS", 0x80280037: "TPM_E_INVALID_FAMILY", 0x80280038: "TPM_E_NO_NV_PERMISSION", 0x80280039: "TPM_E_REQUIRES_SIGN", 0x8028003A: "TPM_E_KEY_NOTSUPPORTED", 0x8028003B: "TPM_E_AUTH_CONFLICT", 0x8028003C: "TPM_E_AREA_LOCKED", // TODO: Finish NVRAM error codes. 0x80280049: "TPM_E_NOOPERATOR", 0x8028004A: "TPM_E_RESOURCEMISSING", 0x8028004B: "TPM_E_DELEGATE_LOCK", 0x8028004C: "TPM_E_DELEGATE_FAMILY", 0x8028004D: "TPM_E_DELEGATE_ADMIN", 0x8028004E: "TPM_E_TRANSPORT_NOTEXCLUSIVE", 0x8028004F: "TPM_E_OWNER_CONTROL", 0x80280050: "TPM_E_DAA_RESOURCES", // TODO: Finish DAA error codes. 0x80280058: "TPM_E_BAD_HANDLE", 0x80280059: "TPM_E_BAD_DELEGATE", 0x8028005A: "TPM_E_BADCONTEXT", 0x8028005B: "TPM_E_TOOMANYCONTEXTS", 0x8028005C: "TPM_E_MA_TICKET_SIGNATURE", 0x8028005D: "TPM_E_MA_DESTINATION", 0x8028005E: "TPM_E_MA_SOURCE", 0x8028005F: "TPM_E_MA_AUTHORITY", 0x80280061: "TPM_E_PERMANENTEK", 0x80280062: "TPM_E_BAD_SIGNATURE", 0x80280063: "TPM_E_NOCONTEXTSPACE", 0x80280400: "TPM_E_COMMAND_BLOCKED", 0x80280401: "TPM_E_INVALID_HANDLE", 0x80280402: "TPM_E_DUPLICATE_VHANDLE", 0x80280403: "TPM_E_EMBEDDED_COMMAND_BLOCKED", 0x80280404: "TPM_E_EMBEDDED_COMMAND_UNSUPPORTED", 0x80280800: "TPM_E_RETRY", 0x80280801: "TPM_E_NEEDS_SELFTEST", 0x80280802: "TPM_E_DOING_SELFTEST", 0x80280803: "TPM_E_DEFEND_LOCK_RUNNING", 0x80284001: "TBS_E_INTERNAL_ERROR", 0x80284002: "TBS_E_BAD_PARAMETER", 0x80284003: "TBS_E_INVALID_OUTPUT_POINTER", 0x80284004: "TBS_E_INVALID_CONTEXT", 0x80284005: "TBS_E_INSUFFICIENT_BUFFER", 0x80284006: "TBS_E_IOERROR", 0x80284007: "TBS_E_INVALID_CONTEXT_PARAM", 0x80284008: "TBS_E_SERVICE_NOT_RUNNING", 0x80284009: "TBS_E_TOO_MANY_TBS_CONTEXTS", 0x8028400A: "TBS_E_TOO_MANY_RESOURCES", 0x8028400B: "TBS_E_SERVICE_START_PENDING", 0x8028400C: "TBS_E_PPI_NOT_SUPPORTED", 0x8028400D: "TBS_E_COMMAND_CANCELED", 0x8028400E: "TBS_E_BUFFER_TOO_LARGE", 0x8028400F: "TBS_E_TPM_NOT_FOUND", 0x80284010: "TBS_E_SERVICE_DISABLED", 0x80284011: "TBS_E_NO_EVENT_LOG", 0x80284012: "TBS_E_ACCESS_DENIED", 0x80284013: "TBS_E_PROVISIONING_NOT_ALLOWED", 0x80284014: "TBS_E_PPI_FUNCTION_UNSUPPORTED", 0x80284015: "TBS_E_OWNERAUTH_NOT_FOUND", 0x80284016: "TBS_E_PROVISIONING_INCOMPLETE", // TODO: TPMAPI & TPMSIMP error codes. 0x80290401: "TPM_E_PCP_DEVICE_NOT_READY", 0x80290402: "TPM_E_PCP_INVALID_HANDLE", 0x80290403: "TPM_E_PCP_INVALID_PARAMETER", 0x80290404: "TPM_E_PCP_FLAG_NOT_SUPPORTED", 0x80290405: "TPM_E_PCP_NOT_SUPPORTED", 0x80290406: "TPM_E_PCP_BUFFER_TOO_SMALL", 0x80290407: "TPM_E_PCP_INTERNAL_ERROR", 0x80290408: "TPM_E_PCP_AUTHENTICATION_FAILED", 0x80290409: "TPM_E_PCP_AUTHENTICATION_IGNORED", 0x8029040A: "TPM_E_PCP_POLICY_NOT_FOUND", 0x8029040B: "TPM_E_PCP_PROFILE_NOT_FOUND", 0x8029040C: "TPM_E_PCP_VALIDATION_FAILED", 0x80090009: "NTE_BAD_FLAGS", 0x80090026: "NTE_INVALID_HANDLE", 0x80090027: "NTE_INVALID_PARAMETER", 0x80090029: "NTE_NOT_SUPPORTED", } ) func maybeWinErr(errNo uintptr) error { if code, known := tpmErrNums[uint32(errNo)]; known { return fmt.Errorf("tpm or subsystem failure: %s", code) } return nil } func utf16ToString(buf []byte) (string, error) { b := make([]uint16, len(buf)/2) // LPCSTR (Windows' representation of utf16) is always little endian. if err := binary.Read(bytes.NewReader(buf), binary.LittleEndian, &b); err != nil { return "", err } return windows.UTF16ToString(b), nil } // closeNCryptoObject is a helper to call NCryptFreeObject on a given handle. func closeNCryptObject(hnd uintptr) error { r, _, msg := nCryptFreeObject.Call(hnd) if r != 0 { if tpmErr := maybeWinErr(r); tpmErr != nil { return tpmErr } return fmt.Errorf("NCryptFreeObject returned %X: %v", r, msg) } return nil } // getNCryptBufferProperty is a helper to read a byte slice from a NCrypt handle property // using NCryptGetProperty. func getNCryptBufferProperty(hnd uintptr, field string) ([]byte, error) { var size uint32 wideField, err := windows.UTF16FromString(field) if err != nil { return nil, err } r, _, msg := nCryptGetProperty.Call(hnd, uintptr(unsafe.Pointer(&wideField[0])), 0, 0, uintptr(unsafe.Pointer(&size)), 0) if r != 0 { if tpmErr := maybeWinErr(r); tpmErr != nil { msg = tpmErr } return nil, fmt.Errorf("NCryptGetProperty returned %d,%X (%v) for key %q on size read", size, r, msg, field) } buff := make([]byte, size) r, _, msg = nCryptGetProperty.Call(hnd, uintptr(unsafe.Pointer(&wideField[0])), uintptr(unsafe.Pointer(&buff[0])), uintptr(size), uintptr(unsafe.Pointer(&size)), 0) if r != 0 { if tpmErr := maybeWinErr(r); tpmErr != nil { msg = tpmErr } return nil, fmt.Errorf("NCryptGetProperty returned %X (%v) for key %q on data read", r, msg, field) } return buff, nil } // winPCP represents a reference to the Platform Crypto Provider. type winPCP struct { hProv uintptr } // Close releases all resources managed by the Handle. func (h *winPCP) Close() error { return closeNCryptObject(h.hProv) } func (h *winPCP) newKey(name string, alg string, length uint32, policy uint32) (uintptr, []byte, []byte, error) { var kh uintptr utf16Name, err := windows.UTF16FromString(name) if err != nil { return 0, nil, nil, err } utf16RSA, err := windows.UTF16FromString(alg) if err != nil { return 0, nil, nil, err } // Create a persistent RSA key of the specified name. r, _, msg := nCryptCreatePersistedKey.Call(h.hProv, uintptr(unsafe.Pointer(&kh)), uintptr(unsafe.Pointer(&utf16RSA[0])), uintptr(unsafe.Pointer(&utf16Name[0])), 0, 0) if r != 0 { if tpmErr := maybeWinErr(r); tpmErr != nil { msg = tpmErr } return 0, nil, nil, fmt.Errorf("NCryptCreatePersistedKey returned %X: %v", r, msg) } // Set the length if provided if length != 0 { utf16Length, err := windows.UTF16FromString("Length") if err != nil { return 0, nil, nil, err } r, _, msg = nCryptSetProperty.Call(kh, uintptr(unsafe.Pointer(&utf16Length[0])), uintptr(unsafe.Pointer(&length)), unsafe.Sizeof(length), 0) if r != 0 { if tpmErr := maybeWinErr(r); tpmErr != nil { msg = tpmErr } return 0, nil, nil, fmt.Errorf("NCryptSetProperty (Length) returned %X: %v", r, msg) } } // Specify the generated key usage policy if appropriate if policy != 0 { utf16KeyPolicy, err := windows.UTF16FromString("PCP_KEY_USAGE_POLICY") if err != nil { return 0, nil, nil, err } r, _, msg = nCryptSetProperty.Call(kh, uintptr(unsafe.Pointer(&utf16KeyPolicy[0])), uintptr(unsafe.Pointer(&policy)), unsafe.Sizeof(policy), 0) if r != 0 { if tpmErr := maybeWinErr(r); tpmErr != nil { msg = tpmErr } return 0, nil, nil, fmt.Errorf("NCryptSetProperty (PCP KeyUsage Policy) returned %X: %v", r, msg) } } // Finalize (create) the key. r, _, msg = nCryptFinalizeKey.Call(kh, 0) if r != 0 { if tpmErr := maybeWinErr(r); tpmErr != nil { msg = tpmErr } return 0, nil, nil, fmt.Errorf("NCryptFinalizeKey returned %X: %v", r, msg) } // Obtain the key blob. var sz uint32 typeString, err := windows.UTF16FromString("OpaqueKeyBlob") if err != nil { return 0, nil, nil, err } if r, _, err := nCryptExportKey.Call(kh, 0, uintptr(unsafe.Pointer(&typeString[0])), 0, 0, 0, uintptr(unsafe.Pointer(&sz)), 0); r != 0 { if tpmErr := maybeWinErr(r); tpmErr != nil { err = tpmErr } return 0, nil, nil, fmt.Errorf("NCryptGetProperty for hKey blob original query returned %X (%v)", r, err) } keyBlob := make([]byte, sz) if r, _, err := nCryptExportKey.Call(kh, 0, uintptr(unsafe.Pointer(&typeString[0])), 0, uintptr(unsafe.Pointer(&keyBlob[0])), uintptr(sz), uintptr(unsafe.Pointer(&sz)), 0); r != 0 { if tpmErr := maybeWinErr(r); tpmErr != nil { err = tpmErr } return 0, nil, nil, fmt.Errorf("NCryptGetProperty for hKey blob returned %X (%v)", r, err) } pubBlob, privBlob, err := decodeKeyBlob(keyBlob) if err != nil { return 0, nil, nil, fmt.Errorf("decodeKeyBlob failed: %v", err) } return kh, pubBlob, privBlob, nil } // NewKey creates a persistent application key of the specified name. func (h *winPCP) NewKey(name string, config *KeyConfig) (uintptr, []byte, []byte, error) { if config.Algorithm == RSA { return h.newKey(name, "RSA", uint32(config.Size), 0) } else if config.Algorithm == ECDSA { switch config.Size { case 256: return h.newKey(name, "ECDSA_P256", 0, 0) case 384: return h.newKey(name, "ECDSA_P384", 0, 0) case 521: return h.newKey(name, "ECDSA_P521", 0, 0) default: return 0, nil, nil, fmt.Errorf("unsupported ECDSA key size: %v", config.Size) } } return 0, nil, nil, fmt.Errorf("unsupported algorithm type: %q", config.Algorithm) } func decodeKeyBlob(keyBlob []byte) ([]byte, []byte, error) { r := bytes.NewReader(keyBlob) var magic uint32 if err := binary.Read(r, binary.LittleEndian, &magic); err != nil { return nil, nil, fmt.Errorf("failed to read header magic: %v", err) } if magic != pcpKeyMagic { return nil, nil, fmt.Errorf("invalid header magic %X", magic) } var headerSize uint32 if err := binary.Read(r, binary.LittleEndian, &headerSize); err != nil { return nil, nil, fmt.Errorf("failed to read header size: %v", err) } var tpmType uint32 if err := binary.Read(r, binary.LittleEndian, &tpmType); err != nil { return nil, nil, fmt.Errorf("failed to read tpm type: %v", err) } if tpmType == tpm12 { return nil, nil, fmt.Errorf("TPM 1.2 currently unsupported") } var flags uint32 if err := binary.Read(r, binary.LittleEndian, &flags); err != nil { return nil, nil, fmt.Errorf("failed to read key flags: %v", err) } var pubLen uint32 if err := binary.Read(r, binary.LittleEndian, &pubLen); err != nil { return nil, nil, fmt.Errorf("failed to read length of public key: %v", err) } var privLen uint32 if err := binary.Read(r, binary.LittleEndian, &privLen); err != nil { return nil, nil, fmt.Errorf("failed to read length of private blob: %v", err) } var pubMigrationLen uint32 if err := binary.Read(r, binary.LittleEndian, &pubMigrationLen); err != nil { return nil, nil, fmt.Errorf("failed to read length of public migration blob: %v", err) } var privMigrationLen uint32 if err := binary.Read(r, binary.LittleEndian, &privMigrationLen); err != nil { return nil, nil, fmt.Errorf("failed to read length of private migration blob: %v", err) } var policyDigestLen uint32 if err := binary.Read(r, binary.LittleEndian, &policyDigestLen); err != nil { return nil, nil, fmt.Errorf("failed to read length of policy digest: %v", err) } var pcrBindingLen uint32 if err := binary.Read(r, binary.LittleEndian, &pcrBindingLen); err != nil { return nil, nil, fmt.Errorf("failed to read length of PCR binding: %v", err) } var pcrDigestLen uint32 if err := binary.Read(r, binary.LittleEndian, &pcrDigestLen); err != nil { return nil, nil, fmt.Errorf("failed to read length of PCR digest: %v", err) } var encryptedSecretLen uint32 if err := binary.Read(r, binary.LittleEndian, &encryptedSecretLen); err != nil { return nil, nil, fmt.Errorf("failed to read length of hostage import symmetric key: %v", err) } var tpm12HostageLen uint32 if err := binary.Read(r, binary.LittleEndian, &tpm12HostageLen); err != nil { return nil, nil, fmt.Errorf("failed to read length of hostage import private key: %v", err) } // Skip over any padding r.Seek(int64(headerSize), 0) pubKey := make([]byte, pubLen) if err := binary.Read(r, binary.BigEndian, &pubKey); err != nil { return nil, nil, fmt.Errorf("failed to read public key: %v", err) } privBlob := make([]byte, privLen) if err := binary.Read(r, binary.BigEndian, &privBlob); err != nil { return nil, nil, fmt.Errorf("failed to read private blob: %v", err) } return pubKey[2:], privBlob[2:], nil } // openPCP initializes a reference to the Microsoft PCP provider. // The Caller is expected to call Close() when they are done. func openPCP() (*winPCP, error) { var err error var h winPCP pname, err := windows.UTF16FromString(pcpProviderName) if err != nil { return nil, err } r, _, err := nCryptOpenStorageProvider.Call(uintptr(unsafe.Pointer(&h.hProv)), uintptr(unsafe.Pointer(&pname[0])), 0) if r != 0 { // r is non-zero on error, err is always populated in this case. if tpmErr := maybeWinErr(r); tpmErr != nil { return nil, tpmErr } return nil, err } return &h, nil } crypto-0.57.0/tpm/internal/open/000077500000000000000000000000001474156331600165125ustar00rootroot00000000000000crypto-0.57.0/tpm/internal/open/open.go000066400000000000000000000001651474156331600200040ustar00rootroot00000000000000package open import ( "io" ) func TPM(deviceName string) (io.ReadWriteCloser, error) { return open(deviceName) } crypto-0.57.0/tpm/internal/open/open_others.go000066400000000000000000000004021474156331600213620ustar00rootroot00000000000000//go:build !windows // +build !windows package open import ( "io" "github.com/google/go-tpm/legacy/tpm2" ) func open(deviceName string) (io.ReadWriteCloser, error) { if deviceName == "" { return tpm2.OpenTPM() } return tpm2.OpenTPM(deviceName) } crypto-0.57.0/tpm/internal/open/open_windows.go000066400000000000000000000002721474156331600215550ustar00rootroot00000000000000//go:build windows // +build windows package open import ( "io" "github.com/google/go-tpm/legacy/tpm2" ) func open(_ string) (io.ReadWriteCloser, error) { return tpm2.OpenTPM() } crypto-0.57.0/tpm/internal/socket/000077500000000000000000000000001474156331600170415ustar00rootroot00000000000000crypto-0.57.0/tpm/internal/socket/socket.go000066400000000000000000000007141474156331600206620ustar00rootroot00000000000000package socket import ( "errors" "io" ) var ( ErrNotAvailable = errors.New("socket not available") ErrNotSupported = errors.New("connecting to a TPM using a UNIX socket is not supported on Windows") ) func New(path string) (io.ReadWriteCloser, error) { return newSocket(path) } type CommandChannelWithoutMeasurementLog struct { io.ReadWriteCloser } func (c *CommandChannelWithoutMeasurementLog) MeasurementLog() ([]byte, error) { return nil, nil } crypto-0.57.0/tpm/internal/socket/socket_others.go000066400000000000000000000007071474156331600222500ustar00rootroot00000000000000//go:build !windows // +build !windows package socket import ( "io" "os" "github.com/google/go-tpm/tpmutil" ) func newSocket(path string) (io.ReadWriteCloser, error) { if path == "" { return nil, ErrNotAvailable } fi, err := os.Stat(path) if err != nil { // TODO(hs): handle specific errors here? return nil, err } if fi.Mode()&os.ModeSocket != 0 { return tpmutil.NewEmulatorReadWriteCloser(path), nil } return nil, ErrNotAvailable } crypto-0.57.0/tpm/internal/socket/socket_windows.go000066400000000000000000000002361474156331600224330ustar00rootroot00000000000000//go:build windows // +build windows package socket import ( "io" ) func newSocket(_ string) (io.ReadWriteCloser, error) { return nil, ErrNotSupported } crypto-0.57.0/tpm/key.go000066400000000000000000000334041474156331600150600ustar00rootroot00000000000000package tpm import ( "context" "crypto" "crypto/x509" "encoding/json" "errors" "fmt" "time" "github.com/smallstep/go-attestation/attest" internalkey "go.step.sm/crypto/tpm/internal/key" "go.step.sm/crypto/tpm/storage" ) // Key models a TPM 2.0 Key. A Key can be used // to sign data. When a Key is created, it can be // attested by an AK, to be able to prove that it // was created by a specific TPM. type Key struct { name string data []byte attestedBy string chain []*x509.Certificate createdAt time.Time blobs *Blobs tpm *TPM } // Name returns the Key name. The name uniquely // identifies the Key if a TPM with persistent // storage is used. func (k *Key) Name() string { return k.name } // Data returns the Key data blob. The data blob // contains all information required for the Key // to be loaded into the TPM that created it again, // so that it can be used for signing data. func (k *Key) Data() []byte { return k.data } // AttestedBy returns the name of the AK the Key was // attested (certified) by at creation time. func (k *Key) AttestedBy() string { return k.attestedBy } // WasAttested returns whether or not the Key was // attested (certified) by an AK at creation time. func (k *Key) WasAttested() bool { return k.attestedBy != "" } // WasAttestedBy returns whether or not the Key // was attested (certified) by the provided AK // at creation time. func (k *Key) WasAttestedBy(ak *AK) bool { return k.attestedBy == ak.name } // Certificate returns the certificate for the Key, if set. // Will return nil in case no AK certificate is available. func (k *Key) Certificate() *x509.Certificate { if len(k.chain) == 0 { return nil } return k.chain[0] } // CertificateChain returns the certificate chain for the Key. // It can return an empty chain. func (k *Key) CertificateChain() []*x509.Certificate { return k.chain } // CreatedAt returns the the creation time of the Key. func (k *Key) CreatedAt() time.Time { return k.createdAt.Truncate(time.Second) } // MarshalJSON marshals the Key to JSON. func (k *Key) MarshalJSON() ([]byte, error) { chain := make([][]byte, len(k.chain)) for i, cert := range k.chain { chain[i] = cert.Raw } o := struct { Name string `json:"name"` Data []byte `json:"data"` AttestedBy string `json:"attestedBy,omitempty"` Chain [][]byte `json:"chain,omitempty"` CreatedAt time.Time `json:"createdAt"` }{ Name: k.name, Data: k.data, AttestedBy: k.attestedBy, Chain: chain, CreatedAt: k.createdAt, } return json.Marshal(o) } // comparablePublicKey is an interface that allows a crypto.PublicKey to be // compared to another crypto.PublicKey. type comparablePublicKey interface { Equal(crypto.PublicKey) bool } // CreateKeyConfig is used to pass configuration // when creating Keys. type CreateKeyConfig struct { // Algorithm to be used, either RSA or ECDSA. Algorithm string // Size is used to specify the bit size of the key or elliptic curve. For // example, '256' is used to specify curve P-256. Size int // TODO(hs): move key name to this struct? } // AttestKeyConfig is used to pass configuration // when creating Keys that are to be attested by // an AK. type AttestKeyConfig struct { // Algorithm to be used, either RSA or ECDSA. Algorithm string // Size is used to specify the bit size of the key or elliptic curve. For // example, '256' is used to specify curve P-256. Size int // QualifyingData is additional data that is passed to the TPM. // It can be used as a nonce to ensure freshness of an attestation. // When used with ACME `device-attest-01`, this contains a hash of // the key authorization. QualifyingData []byte // TODO(hs): add akName and key name to this struct? } // CreateKey creates a new Key identified by `name`. If no name is provided, // a random 10 character name is generated. If a Key with the same name exists, // `ErrExists` is returned. The Key won't be attested by an AK. func (t *TPM) CreateKey(ctx context.Context, name string, config CreateKeyConfig) (key *Key, err error) { if err = t.open(goTPMCall(ctx)); err != nil { return nil, fmt.Errorf("failed opening TPM: %w", err) } defer closeTPM(ctx, t, &err) now := time.Now() if name, err = processName(name); err != nil { return nil, err } _, err = t.store.GetKey(name) switch { case err == nil: return nil, fmt.Errorf("failed creating key %q: %w", name, ErrExists) case errors.Is(err, storage.ErrNoStorageConfigured): return nil, fmt.Errorf("failed creating key %q: %w", name, err) } createConfig := internalkey.CreateConfig{ Algorithm: config.Algorithm, Size: config.Size, } if err := t.validate(&createConfig); err != nil { return nil, fmt.Errorf("invalid key creation parameters: %w", err) } data, err := internalkey.Create(t.rwc, prefixKey(name), createConfig) if err != nil { return nil, fmt.Errorf("failed creating key %q: %w", name, err) } key = &Key{ name: name, data: data, createdAt: now, tpm: t, } if err := t.store.AddKey(key.toStorage()); err != nil { return nil, fmt.Errorf("failed adding key %q to storage: %w", name, err) } if err := t.store.Persist(); err != nil { return nil, fmt.Errorf("failed persisting key %q to storage: %w", name, err) } return } type attestValidationWrapper attest.KeyConfig func (w attestValidationWrapper) Validate() error { switch w.Algorithm { case "RSA": if w.Size > 2048 { return fmt.Errorf("%d bits RSA keys are (currently) not supported in go.step.sm/crypto; maximum is 2048", w.Size) } case "ECDSA": break default: return fmt.Errorf("unsupported algorithm %q", w.Algorithm) } return nil } // AttestKey creates a new Key identified by `name` and attested by the AK // identified by `akName`. If no name is provided, a random 10 character // name is generated. If a Key with the same name exists, `ErrExists` is // returned. func (t *TPM) AttestKey(ctx context.Context, akName, name string, config AttestKeyConfig) (key *Key, err error) { if err = t.open(ctx); err != nil { return nil, fmt.Errorf("failed opening TPM: %w", err) } defer closeTPM(ctx, t, &err) now := time.Now() if name, err = processName(name); err != nil { return nil, err } _, err = t.store.GetKey(name) switch { case err == nil: return nil, fmt.Errorf("failed creating key %q: %w", name, ErrExists) case errors.Is(err, storage.ErrNoStorageConfigured): return nil, fmt.Errorf("failed creating key %q: %w", name, err) } ak, err := t.store.GetAK(akName) if err != nil { if errors.Is(err, storage.ErrNotFound) { return nil, fmt.Errorf("failed getting AK %q: %w", akName, ErrNotFound) } return nil, fmt.Errorf("failed getting AK %q: %w", akName, err) } loadedAK, err := t.attestTPM.LoadAK(ak.Data) if err != nil { return nil, fmt.Errorf("failed loading AK %q: %w", akName, err) } defer loadedAK.Close(t.attestTPM) keyConfig := attest.KeyConfig{ Algorithm: attest.Algorithm(config.Algorithm), Size: config.Size, QualifyingData: config.QualifyingData, Name: prefixKey(name), } if err := t.validate(attestValidationWrapper(keyConfig)); err != nil { return nil, fmt.Errorf("invalid key attestation parameters: %w", err) } akey, err := t.attestTPM.NewKey(loadedAK, &keyConfig) if err != nil { return nil, fmt.Errorf("failed creating key %q: %w", name, err) } defer akey.Close() data, err := akey.Marshal() if err != nil { return nil, fmt.Errorf("failed marshaling key %q: %w", name, err) } key = &Key{ name: name, data: data, attestedBy: akName, createdAt: now, tpm: t, } if err := t.store.AddKey(key.toStorage()); err != nil { return nil, fmt.Errorf("failed adding key %q to storage: %w", name, err) } if err := t.store.Persist(); err != nil { return nil, fmt.Errorf("failed persisting key %q: %w", name, err) } return } // GetKey returns the Key identified by `name`. It returns `ErrNotfound` // if it doesn't exist. func (t *TPM) GetKey(ctx context.Context, name string) (key *Key, err error) { if err = t.open(ctx); err != nil { return nil, fmt.Errorf("failed opening TPM: %w", err) } defer closeTPM(ctx, t, &err) skey, err := t.store.GetKey(name) if err != nil { if errors.Is(err, storage.ErrNotFound) { return nil, fmt.Errorf("failed getting key %q: %w", name, ErrNotFound) } return nil, fmt.Errorf("failed getting key %q: %w", name, err) } return keyFromStorage(skey, t), nil } // ListKeys returns a slice of Keys. The result is (currently) // not ordered. func (t *TPM) ListKeys(ctx context.Context) (keys []*Key, err error) { if err = t.open(ctx); err != nil { return nil, fmt.Errorf("failed opening TPM: %w", err) } defer closeTPM(ctx, t, &err) skeys, err := t.store.ListKeys() if err != nil { return nil, fmt.Errorf("failed listing keys: %w", err) } keys = make([]*Key, 0, len(skeys)) for _, skey := range skeys { keys = append(keys, keyFromStorage(skey, t)) } return } // GetKeysAttestedBy returns a slice of Keys attested by the AK // identified by `akName`. The result is (currently) not ordered. func (t *TPM) GetKeysAttestedBy(ctx context.Context, akName string) (keys []*Key, err error) { if err = t.open(ctx); err != nil { return nil, fmt.Errorf("failed opening TPM: %w", err) } defer closeTPM(ctx, t, &err) skeys, err := t.store.ListKeys() if err != nil { return nil, fmt.Errorf("failed listing keys: %w", err) } keys = make([]*Key, 0, len(skeys)) for _, skey := range skeys { if skey.AttestedBy == akName { keys = append(keys, keyFromStorage(skey, t)) } } return } // DeleteKey removes the Key identified by `name`. It returns `ErrNotfound` // if it doesn't exist. func (t *TPM) DeleteKey(ctx context.Context, name string) (err error) { if err := t.open(ctx); err != nil { return fmt.Errorf("failed opening TPM: %w", err) } defer closeTPM(ctx, t, &err) key, err := t.store.GetKey(name) if err != nil { if errors.Is(err, storage.ErrNotFound) { return fmt.Errorf("failed getting key %q: %w", name, ErrNotFound) } return fmt.Errorf("failed getting key %q: %w", name, err) } if err := t.attestTPM.DeleteKey(key.Data); err != nil { return fmt.Errorf("failed deleting key %q: %w", name, err) } if err := t.store.DeleteKey(name); err != nil { return fmt.Errorf("failed deleting key %q from storage: %w", name, err) } if err := t.store.Persist(); err != nil { return fmt.Errorf("failed persisting storage: %w", err) } return } // Signer returns a crypto.Signer backed by the Key. func (k *Key) Signer(ctx context.Context) (crypto.Signer, error) { return k.tpm.GetSigner(ctx, k.name) } // CertificationParameters returns information about the key that can be used to // verify key certification. func (k *Key) CertificationParameters(ctx context.Context) (params attest.CertificationParameters, err error) { if err = k.tpm.open(ctx); err != nil { return params, fmt.Errorf("failed opening TPM: %w", err) } defer closeTPM(ctx, k.tpm, &err) loadedKey, err := k.tpm.attestTPM.LoadKey(k.data) if err != nil { return attest.CertificationParameters{}, fmt.Errorf("failed loading key %q: %w", k.name, err) } defer loadedKey.Close() params = loadedKey.CertificationParameters() return } // Blobs returns a container for the private and public key blobs. // The resulting blobs are compatible with tpm2-tools, so can be used // like this (after having been written to key.priv and key.pub): // // tpm2_load -C 0x81000001 -u key.pub -r key.priv -c key.ctx func (k *Key) Blobs(ctx context.Context) (blobs *Blobs, err error) { if k.blobs != nil { return k.blobs, nil } if err = k.tpm.open(ctx); err != nil { return nil, fmt.Errorf("failed opening TPM: %w", err) } defer closeTPM(ctx, k.tpm, &err) key, err := k.tpm.attestTPM.LoadKey(k.data) if err != nil { return nil, fmt.Errorf("failed loading key: %w", err) } defer key.Close() public, private, err := key.Blobs() if err != nil { return nil, fmt.Errorf("failed getting key blobs: %w", err) } k.setBlobs(private, public) return k.blobs, nil } // SetCertificateChain associates an X.509 certificate chain with the Key. // If the public key doesn't match the public key in the first certificate // in the chain (the leaf), an error is returned. func (k *Key) SetCertificateChain(ctx context.Context, chain []*x509.Certificate) (err error) { if err = k.tpm.open(ctx); err != nil { return fmt.Errorf("failed opening TPM: %w", err) } defer closeTPM(ctx, k.tpm, &err) signer, err := k.Signer(internalCall(ctx)) // TODO: cache the crypto.PublicKey after its first load instead? if err != nil { return fmt.Errorf("failed getting signer for key: %w", err) } if len(chain) > 0 { leaf := chain[0] leafPK, ok := leaf.PublicKey.(crypto.PublicKey) if !ok { return fmt.Errorf("unexpected type for certificate public key: %T", leaf.PublicKey) } publicKey, ok := leafPK.(comparablePublicKey) if !ok { return errors.New("certificate public key can't be compared to a crypto.PublicKey") } if !publicKey.Equal(signer.Public()) { return errors.New("public key does not match the leaf certificate public key") } } k.chain = chain // TODO(hs): deep copy, so that certs can't be changed by pointer? if err := k.tpm.store.UpdateKey(k.toStorage()); err != nil { return fmt.Errorf("failed updating key %q: %w", k.name, err) } return } // toStorage transforms the Key to the struct used for // persisting Keys. func (k *Key) toStorage() *storage.Key { return &storage.Key{ Name: k.name, Data: k.data, AttestedBy: k.attestedBy, Chain: k.chain, CreatedAt: k.createdAt.UTC(), } } // keyFromStorage recreates a Key from the struct used for // persisting Keys. func keyFromStorage(sk *storage.Key, t *TPM) *Key { return &Key{ name: sk.Name, data: sk.Data, attestedBy: sk.AttestedBy, chain: sk.Chain, createdAt: sk.CreatedAt.Local(), tpm: t, } } crypto-0.57.0/tpm/key_test.go000066400000000000000000000037301474156331600161160ustar00rootroot00000000000000package tpm import ( "crypto" "crypto/x509" "encoding/base64" "encoding/json" "testing" "time" "github.com/stretchr/testify/require" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/minica" "go.step.sm/crypto/x509util" ) func TestKey_MarshalJSON(t *testing.T) { ca, err := minica.New( minica.WithGetSignerFunc( func() (crypto.Signer, error) { return keyutil.GenerateSigner("RSA", "", 2048) }, ), ) require.NoError(t, err) signer, err := keyutil.GenerateSigner("RSA", "", 2048) require.NoError(t, err) cr, err := x509util.NewCertificateRequest(signer) require.NoError(t, err) cr.Subject.CommonName = "testkey" csr, err := cr.GetCertificateRequest() require.NoError(t, err) cert, err := ca.SignCSR(csr) require.NoError(t, err) key := &Key{ name: "key1", data: []byte{1, 2, 3, 4}, attestedBy: "ak1", createdAt: time.Time{}, } data, err := json.Marshal(key) require.NoError(t, err) m := map[string]any{} err = json.Unmarshal(data, &m) require.NoError(t, err) require.Equal(t, m["name"], key.name) require.Equal(t, m["data"], base64.StdEncoding.EncodeToString(key.data)) require.Equal(t, m["attestedBy"], key.attestedBy) require.Equal(t, m["chain"], nil) require.Equal(t, m["createdAt"], key.createdAt.Format("2006-01-02T15:04:05Z")) key = &Key{ name: "key2", data: []byte{1, 2, 3, 4}, attestedBy: "ak1", chain: []*x509.Certificate{cert, ca.Intermediate}, createdAt: time.Time{}, } data, err = json.Marshal(key) require.NoError(t, err) m = map[string]any{} err = json.Unmarshal(data, &m) require.NoError(t, err) require.Equal(t, m["name"], key.name) require.Equal(t, m["data"], base64.StdEncoding.EncodeToString(key.data)) require.Equal(t, m["attestedBy"], key.attestedBy) require.Equal(t, m["chain"], []any{base64.StdEncoding.EncodeToString(cert.Raw), base64.StdEncoding.EncodeToString(ca.Intermediate.Raw)}) require.Equal(t, m["createdAt"], key.createdAt.Format("2006-01-02T15:04:05Z")) } crypto-0.57.0/tpm/manufacturer/000077500000000000000000000000001474156331600164315ustar00rootroot00000000000000crypto-0.57.0/tpm/manufacturer/manufacturers.go000066400000000000000000000063261474156331600216460ustar00rootroot00000000000000package manufacturer import ( "encoding/binary" "encoding/hex" "encoding/json" "regexp" "strconv" "strings" ) var ( manufacturerByASCII map[string]string validChars *regexp.Regexp ) // ID models a TPM Manufacturer (or Vendor) ID. type ID uint32 // MarshalJSON marshals the (numeric) TPM Manufacturer ID to // a JSON string representation, including quotes. func (id ID) MarshalJSON() ([]byte, error) { return json.Marshal(strconv.FormatUint(uint64(id), 10)) } // GetEncodings returns the ASCII and hexadecimal representations // of the manufacturer ID. func GetEncodings(id ID) (ascii, hexa string) { b := [4]byte{} binary.BigEndian.PutUint32(b[:], uint32(id)) ascii = string(b[:]) ascii = validChars.ReplaceAllString(ascii, "") // NOTE: strips \x00 characters (a.o) hexa = strings.ToUpper(hex.EncodeToString(b[:])) return } // GetNameByASCII returns the manufacturer name based on its // ASCII identifier. func GetNameByASCII(ascii string) string { if name, ok := manufacturerByASCII[strings.TrimSpace(ascii)]; ok { return name } return "unknown" } func init() { // manufacturerByASCII contains a mapping of TPM manufacturer // ASCII names to full manufacturer names. It is mainly based on the data // provided on https://trustedcomputinggroup.org/resource/vendor-id-registry/, // e.g. https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-Vendor-ID-Registry-Family-1.2-and-2.0-Version-1.07-Revision-0.02_pub.pdf // Some additional known manufacturers that are not on the list are provided too. manufacturerByASCII = map[string]string{ // 4.1 Product Implementations "AMD": "AMD", "ANT": "Ant Group", "ATML": "Atmel", "BRCM": "Broadcom", "CSCO": "Cisco", "FLYS": "Flyslice Technologies", "ROCC": "Fuzhou Rockchip", "GOOG": "Google", "HPI": "HPI", "HPE": "HPE", "HISI": "Huawei", "IBM": "IBM", "IFX": "Infineon", "INTC": "Intel", "LEN": "Lenovo", "MSFT": "Microsoft", "NSM": "National Semiconductor", "NTZ": "Nationz", "NSG": "NSING", "NTC": "Nuvoton Technology", "QCOM": "Qualcomm", "SMSN": "Samsung", "SECE": "SecEdge", "SNS": "Sinosun", "SMSC": "SMSC", //"": "Solidigm", // TODO: Solidigm was added to the list of Vendor ID interfaces, but not in the ASCII identifier list; haven't found ASCII identifier yet "STM": "ST Microelectronics", "TXN": "Texas Instruments", "WEC": "Winbond", "SEAL": "WiseKey", // 4.2 Simulator and Testing Implementations "SIM0": "Simulator 0", "SIM1": "Simulator 1", "SIM2": "Simulator 2", "SIM3": "Simulator 3", "SIM4": "Simulator 4", "SIM5": "Simulator 5", "SIM6": "Simulator 6", "SIM7": "Simulator 7", "TST0": "Test 0", "TST1": "Test 1", "TST2": "Test 2", "TST3": "Test 3", "TST4": "Test 4", "TST5": "Test 5", "TST6": "Test 6", "TST7": "Test 7", // FIDO Alliance; 0xFFFFF1D0; does not conform to the ASCII naming scheme // Also see https://github.com/fido-alliance/conformance-test-tools-resources/issues/537 "FIDO": "FIDO Alliance", // NOTE: FIDO is not the official ASCII representation // Amazon Web Services; NitroTPM "AMZN": "Amazon Web Services", // Others "PRLS": "Parallels Desktop", "VMW": "VMWare", } validChars = regexp.MustCompile(`[^a-zA-Z0-9 ]+`) } crypto-0.57.0/tpm/manufacturer/manufacturers_test.go000066400000000000000000000021201474156331600226710ustar00rootroot00000000000000package manufacturer import ( "encoding/json" "testing" "github.com/stretchr/testify/require" ) func Test_GetEncodings(t *testing.T) { tests := []struct { name string id ID want string }{ {"infineon", 1229346816, "IFX"}, {"intel", 1229870147, "INTC"}, {"stm", 1398033696, "STM "}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got, _ := GetEncodings(tt.id); got != tt.want { t.Errorf("GetEncodings() = %v, want %v", got, tt.want) } }) } } func Test_GetNameByASCII(t *testing.T) { tests := []struct { name string ascii string want string }{ {"infineon", "IFX", "Infineon"}, {"intel", "INTC", "Intel"}, {"stm", "STM ", "ST Microelectronics"}, {"unknown", "0000", "unknown"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := GetNameByASCII(tt.ascii); got != tt.want { t.Errorf("GetNameByASCII() = %v, want %v", got, tt.want) } }) } } func TestID_MarshalJSON(t *testing.T) { b, err := json.Marshal(ID(12345678)) require.NoError(t, err) require.JSONEq(t, `"12345678"`, string(b)) } crypto-0.57.0/tpm/names.go000066400000000000000000000021341474156331600153670ustar00rootroot00000000000000package tpm import ( "crypto/rand" "fmt" ) // processName creates a random 10 character name if the provided // one is empty. func processName(name string) (string, error) { if name == "" { // TODO: decouple the TPM key name from the name recorded in the storage? This might // make it easier to work with the key names as a user; the TPM key name would be abstracted // away. The key name in the storage can be different from the key stored with the key (which, // to be far, isn't even used on Linux TPMs) nameHex := make([]byte, 5) if n, err := rand.Read(nameHex); err != nil || n != len(nameHex) { return "", fmt.Errorf("failed reading from CSPRNG: %w", err) } name = fmt.Sprintf("%x", nameHex) } return name, nil } // prefixAK prefixes `ak-` to the provided name. // // `ak-` is the default go-attestation uses for AKs. func prefixAK(name string) string { return fmt.Sprintf("ak-%s", name) } // prefixKey prefixes `app-` to the provided name. // // `app-` is the default that go-attestation uses for Keys. func prefixKey(name string) string { return fmt.Sprintf("app-%s", name) } crypto-0.57.0/tpm/names_test.go000066400000000000000000000007351474156331600164330ustar00rootroot00000000000000package tpm import ( "testing" "github.com/stretchr/testify/require" ) func Test_processName(t *testing.T) { name := "name1" name, err := processName(name) require.NoError(t, err) require.Equal(t, "name1", name) name, err = processName("") require.NoError(t, err) require.Len(t, name, 10) } func Test_prefixAK(t *testing.T) { require.Equal(t, "ak-name", prefixAK("name")) } func Test_prefixKey(t *testing.T) { require.Equal(t, "app-name", prefixKey("name")) } crypto-0.57.0/tpm/rand/000077500000000000000000000000001474156331600146615ustar00rootroot00000000000000crypto-0.57.0/tpm/rand/rand.go000066400000000000000000000004061474156331600161340ustar00rootroot00000000000000package rand import ( "fmt" "io" "go.step.sm/crypto/tpm" ) func New(opts ...tpm.NewTPMOption) (io.Reader, error) { t, err := tpm.New(opts...) if err != nil { return nil, fmt.Errorf("failed creating TPM instance: %w", err) } return t.RandomReader() } crypto-0.57.0/tpm/rand/rand_simulator_test.go000066400000000000000000000021001474156331600212630ustar00rootroot00000000000000//go:build tpmsimulator // +build tpmsimulator package rand import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.step.sm/crypto/tpm" "go.step.sm/crypto/tpm/simulator" ) func withSimulator(t *testing.T) tpm.NewTPMOption { t.Helper() var sim simulator.Simulator t.Cleanup(func() { if sim == nil { return } err := sim.Close() require.NoError(t, err) }) sim, err := simulator.New() require.NoError(t, err) err = sim.Open() require.NoError(t, err) return tpm.WithSimulator(sim) } func TestNew(t *testing.T) { r, err := New(withSimulator(t)) require.NoError(t, err) require.NotNil(t, r) ecdsaKey, err := ecdsa.GenerateKey(elliptic.P256(), r) require.NoError(t, err) if assert.NotNil(t, ecdsaKey) { size := (ecdsaKey.D.BitLen() + 7) / 8 require.Equal(t, 32, size) } rsaKey, err := rsa.GenerateKey(r, 2048) require.NoError(t, err) if assert.NotNil(t, rsaKey) { require.Equal(t, 256, rsaKey.Size()) // 2048 bits; 256 bytes expected to have been read } } crypto-0.57.0/tpm/random.go000066400000000000000000000045731474156331600155550ustar00rootroot00000000000000package tpm import ( "context" "errors" "fmt" "io" "math" "github.com/google/go-tpm/legacy/tpm2" ) type ShortRandomReadError struct { Requested int Generated int } func (s ShortRandomReadError) Error() string { return fmt.Sprintf("generated %d random bytes instead of the requested %d", s.Generated, s.Requested) } // GenerateRandom returns `size` number of random bytes generated by the TPM. func (t *TPM) GenerateRandom(ctx context.Context, size uint16) (random []byte, err error) { if err = t.open(goTPMCall(ctx)); err != nil { return nil, fmt.Errorf("failed opening TPM: %w", err) } defer closeTPM(ctx, t, &err) return t.generateRandom(ctx, size) } func (t *TPM) generateRandom(_ context.Context, size uint16) (random []byte, err error) { random, err = tpm2.GetRandom(t.rwc, size) if err != nil { return nil, fmt.Errorf("failed generating random data: %w", err) } if len(random) != int(size) { return nil, ShortRandomReadError{Requested: int(size), Generated: len(random)} } return } type generator struct { t *TPM readError error } func (t *TPM) RandomReader() (io.Reader, error) { return &generator{ t: t, }, nil } func (g *generator) Read(p []byte) (n int, err error) { if g.readError != nil { errMsg := g.readError.Error() // multiple wrapped errors not (yet) allowed return 0, fmt.Errorf("failed generating random bytes in previous call to Read: %s: %w", errMsg, io.EOF) } if len(p) > math.MaxUint16 { p = p[:math.MaxUint16] } ctx := context.Background() if err = g.t.open(goTPMCall(ctx)); err != nil { return 0, fmt.Errorf("failed opening TPM: %w", err) } defer closeTPM(ctx, g.t, &err) var result []byte requestedLength := len(p) singleRequestLength := uint16(requestedLength) for len(result) < requestedLength { if r, err := g.t.generateRandom(ctx, singleRequestLength); err == nil { result = append(result, r...) } else { var s ShortRandomReadError if errors.As(err, &s) && s.Generated > 0 { // adjust number of bytes to request if at least some data was read and continue loop singleRequestLength = uint16(s.Generated) result = append(result, r...) } else { g.readError = err // store the error to be returned for future calls to Read n = copy(p, result) return n, nil // return the result recorded so far and no error } } } n = copy(p, result) return } var _ io.Reader = (*generator)(nil) crypto-0.57.0/tpm/signer.go000066400000000000000000000066361474156331600155660ustar00rootroot00000000000000package tpm import ( "context" "crypto" "errors" "fmt" "io" "go.step.sm/crypto/tpm/storage" "go.step.sm/crypto/tpm/tss2" ) // signer implements crypto.Signer backed by a TPM key. type signer struct { tpm *TPM key Key public crypto.PublicKey } // Public returns the signers public key. func (s *signer) Public() crypto.PublicKey { return s.public } // Sign implements crypto.Signer. It is backed by a TPM key. // The TPM key is loaded lazily, meaning that every call to Sign() // will reload the TPM key to be used. func (s *signer) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { ctx := context.Background() if err = s.tpm.open(ctx); err != nil { return nil, fmt.Errorf("failed opening TPM: %w", err) } defer closeTPM(ctx, s.tpm, &err) loadedKey, err := s.tpm.attestTPM.LoadKey(s.key.data) if err != nil { return nil, err } defer loadedKey.Close() priv, err := loadedKey.Private(s.public) if err != nil { return nil, fmt.Errorf("failed getting TPM private key %q: %w", s.key.name, err) } var signer crypto.Signer var ok bool if signer, ok = priv.(crypto.Signer); !ok { return nil, fmt.Errorf("failed getting TPM private key %q as crypto.Signer", s.key.name) } return signer.Sign(rand, digest, opts) } // GetSigner returns a crypto.Signer for a TPM Key identified by `name`. func (t *TPM) GetSigner(ctx context.Context, name string) (csigner crypto.Signer, err error) { if err = t.open(ctx); err != nil { return nil, fmt.Errorf("failed opening TPM: %w", err) } defer closeTPM(ctx, t, &err) key, err := t.store.GetKey(name) if err != nil { if errors.Is(err, storage.ErrNotFound) { return nil, fmt.Errorf("failed getting signer for key %q: %w", name, ErrNotFound) } return nil, fmt.Errorf("failed getting signer for key %q: %w", name, err) } loadedKey, err := t.attestTPM.LoadKey(key.Data) if err != nil { return nil, err } defer loadedKey.Close() priv, err := loadedKey.Private(loadedKey.Public()) if err != nil { return nil, fmt.Errorf("failed getting TPM private key %q: %w", name, err) } if _, ok := priv.(crypto.Signer); !ok { return nil, fmt.Errorf("failed getting TPM private key %q as crypto.Signer", name) } csigner = &signer{ tpm: t, key: Key{name: name, data: key.Data, attestedBy: key.AttestedBy, createdAt: key.CreatedAt, tpm: t}, public: loadedKey.Public(), } return } // tss2Signer is a wrapper on top of [*tss2.Signer] that opens and closes the // tpm on each sign call. type tss2Signer struct { *tss2.Signer tpm *TPM } func (s *tss2Signer) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { ctx := context.Background() if err = s.tpm.open(goTPMCall(ctx)); err != nil { return nil, fmt.Errorf("failed opening TPM: %w", err) } defer closeTPM(ctx, s.tpm, &err) s.SetCommandChannel(s.tpm.rwc) signature, err = s.Signer.Sign(rand, digest, opts) return } // CreateTSS2Signer returns a crypto.Signer using the given [TPM] and [tss2.TPMKey]. func CreateTSS2Signer(ctx context.Context, t *TPM, key *tss2.TPMKey) (csigner crypto.Signer, err error) { if err := t.open(goTPMCall(ctx)); err != nil { return nil, fmt.Errorf("failed opening TPM: %w", err) } defer closeTPM(ctx, t, &err) s, err := tss2.CreateSigner(t.rwc, key) if err != nil { return nil, fmt.Errorf("failed creating TSS2 signer: %w", err) } csigner = &tss2Signer{ Signer: s, tpm: t, } return } crypto-0.57.0/tpm/simulator/000077500000000000000000000000001474156331600157545ustar00rootroot00000000000000crypto-0.57.0/tpm/simulator/simulator.go000066400000000000000000000002011474156331600203130ustar00rootroot00000000000000package simulator import "io" type Simulator interface { io.ReadWriteCloser Open() error MeasurementLog() ([]byte, error) } crypto-0.57.0/tpm/simulator/simulator_disabled.go000066400000000000000000000015531474156331600221550ustar00rootroot00000000000000//go:build !tpmsimulator // +build !tpmsimulator package simulator import ( "errors" "io" ) type NoSimulator struct { } func New() (Simulator, error) { return &NoSimulator{}, errors.New("no simulator available") } func (s *NoSimulator) Open() error { return errors.New("cannot open: no simulator available") } func (s *NoSimulator) Close() error { return errors.New("cannot close: no simulator available") } func (s *NoSimulator) MeasurementLog() ([]byte, error) { return nil, errors.New("cannot get measurement log: no simulator available") } func (s *NoSimulator) Read([]byte) (int, error) { return -1, errors.New("cannot read: no simulator available") } func (s *NoSimulator) Write([]byte) (int, error) { return -1, errors.New("cannot write: no simulator available") } var _ Simulator = (*NoSimulator)(nil) var _ io.ReadWriteCloser = (*NoSimulator)(nil) crypto-0.57.0/tpm/simulator/simulator_enabled.go000066400000000000000000000037011474156331600217750ustar00rootroot00000000000000//go:build tpmsimulator // +build tpmsimulator package simulator import ( "bytes" "encoding/binary" "encoding/hex" "fmt" "io" gotpm "github.com/google/go-tpm-tools/simulator" ) type WrappingSimulator struct { wrapped *gotpm.Simulator seed *int64 } type NewSimulatorOption func(ws *WrappingSimulator) error func WithSeed(seed string) NewSimulatorOption { return func(ws *WrappingSimulator) error { b, err := hex.DecodeString(seed) if err != nil { return fmt.Errorf("failed decoding %q: %w", seed, err) } if len(b) != 8 { return fmt.Errorf("%q has wrong number of bytes (%d)", seed, len(b)) } var intSeed int64 buf := bytes.NewBuffer(b) if err := binary.Read(buf, binary.BigEndian, &intSeed); err != nil { return fmt.Errorf("failed reading %q into int64: %w", seed, err) } ws.seed = &intSeed return nil } } func New(opts ...NewSimulatorOption) (Simulator, error) { ws := &WrappingSimulator{} for _, applyTo := range opts { if err := applyTo(ws); err != nil { return nil, fmt.Errorf("failed initializing TPM simulator: %w", err) } } return ws, nil } func (s *WrappingSimulator) Open() error { var sim *gotpm.Simulator var err error if s.wrapped == nil { if s.seed == nil { sim, err = gotpm.Get() } else { sim, err = gotpm.GetWithFixedSeedInsecure(*s.seed) } if err != nil { return err } } s.wrapped = sim return nil } func (s *WrappingSimulator) Close() error { if s.wrapped == nil { return nil } if s.wrapped.IsClosed() { return nil } if err := s.wrapped.Close(); err != nil { return fmt.Errorf("failed closing TPM simulator: %w", err) } s.wrapped = nil return nil } func (s *WrappingSimulator) MeasurementLog() ([]byte, error) { return nil, nil } func (s *WrappingSimulator) Read(p []byte) (int, error) { return s.wrapped.Read(p) } func (s *WrappingSimulator) Write(p []byte) (int, error) { return s.wrapped.Write(p) } var _ io.ReadWriteCloser = (*WrappingSimulator)(nil) crypto-0.57.0/tpm/skae/000077500000000000000000000000001474156331600146605ustar00rootroot00000000000000crypto-0.57.0/tpm/skae/extension.go000066400000000000000000000133541474156331600172310ustar00rootroot00000000000000//nolint:unused // ignore unused types for now package skae import ( "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "errors" "math/big" "github.com/smallstep/go-attestation/attest" ) var ( oidSubjectKeyAttestationEvidence = asn1.ObjectIdentifier{2, 23, 133, 6, 1, 1} // SKAE (Subject Key Attestation Evidence) OID: 2.23.133.6.1.1 oidAuthorityInfoAccessOcsp = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 48, 1} oidAuthorityInfoAccessIssuers = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 48, 2} ) func CreateSubjectKeyAttestationEvidenceExtension(_ *x509.Certificate, _ attest.CertificationParameters, _ bool) (pkix.Extension, error) { return pkix.Extension{}, errors.New("not implemented yet") // return early; not verified to be working as expected yet /* asn1Issuer, err := asn1.Marshal(akCert.Issuer.ToRDNSequence()) //nolint:govet // intentionally breaking early if err != nil { return pkix.Extension{}, fmt.Errorf("error marshaling issuer: %w", err) } skaeExtension := asn1SKAE{ TCGSpecVersion: asn1TCGSpecVersion{Major: 2, Minor: 0}, KeyAttestationEvidence: asn1KeyAttestationEvidence{}, } attestationEvidence := asn1AttestationEvidence{ TPMCertifyInfo: asn1TPMCertifyInfo{ CertifyInfo: asn1.BitString{ // TODO: check if setting the values like this is correct Bytes: params.CreateAttestation, BitLength: len(params.CreateAttestation) * 8, }, Signature: asn1.BitString{ Bytes: params.CreateSignature, BitLength: len(params.CreateSignature) * 8, }, }, TPMIdentityCredAccessInfo: asn1TPMIdentityCredentialAccessInfo{ AuthorityInfoAccess: createAIA(akCert), IssuerSerial: issuerAndSerial{ IssuerName: asn1.RawValue{FullBytes: asn1Issuer}, SerialNumber: akCert.SerialNumber, }, }, } aeb, err := asn1.Marshal(attestationEvidence) if err != nil { return pkix.Extension{}, fmt.Errorf("error marshaling attestation evidence: %w", err) } if !shouldEncrypt { //skaeExtension.KeyAttestationEvidence.AttestEvidence = attestationEvidence skaeExtension.KeyAttestationEvidence.Evidence = asn1.RawValue{ Class: asn1.ClassContextSpecific, IsCompound: true, Tag: 0, // CHOICE "0" Bytes: aeb, } } else { // TODO: encrypt the AttestEvidence to (right) recipient; set it as the EnvelopedAttestEvidence encryptedAEB := aeb eae := asn1EnvelopedAttestationEvidence{ RecipientInfos: []recipientInfo{}, // TODO: fill recipient(s) EncryptedAttestInfo: asn1EncryptedAttestationInfo{ EncryptionAlgorithm: pkix.AlgorithmIdentifier{ Algorithm: nil, // TODO: select and fill }, EncryptedAttestEvidence: encryptedAEB, }, } eaeBytes, err := asn1.Marshal(eae) if err != nil { return pkix.Extension{}, errors.New("error marshaling EnvelopedAttestationEvidence") } skaeExtension.KeyAttestationEvidence.Evidence = asn1.RawValue{ Class: asn1.ClassContextSpecific, IsCompound: true, Tag: 1, // CHOICE "1" Bytes: eaeBytes, } return pkix.Extension{}, errors.New("encrypting the AttestEvidence is not yet supported") } skaeExtensionBytes, err := asn1.Marshal(skaeExtension) if err != nil { return pkix.Extension{}, fmt.Errorf("creating SKAE extension failed: %w", err) } result := pkix.Extension{ Id: oidSubjectKeyAttestationEvidence, Critical: false, // non standard extension; don't break clients Value: skaeExtensionBytes, } b, err := asn1.Marshal(result) if err != nil { return result, err } _ = b return result, nil */ } func createAIA(ak *x509.Certificate) []asn1AuthorityInfoAccessSyntax { var aiaValues []asn1AuthorityInfoAccessSyntax for _, server := range ak.OCSPServer { aiaValues = append(aiaValues, asn1AuthorityInfoAccessSyntax{ Method: oidAuthorityInfoAccessOcsp, Location: asn1.RawValue{Tag: asn1.TagOID, Class: asn1.ClassContextSpecific, Bytes: []byte(server)}, }) } for _, url := range ak.IssuingCertificateURL { aiaValues = append(aiaValues, asn1AuthorityInfoAccessSyntax{ Method: oidAuthorityInfoAccessIssuers, Location: asn1.RawValue{Tag: asn1.TagOID, Class: asn1.ClassContextSpecific, Bytes: []byte(url)}, }) } return aiaValues } type asn1SKAE struct { TCGSpecVersion asn1TCGSpecVersion KeyAttestationEvidence asn1KeyAttestationEvidence } type asn1TCGSpecVersion struct { Major int Minor int } type asn1KeyAttestationEvidence struct { // AttestEvidence asn1AttestationEvidence // TODO: ASN1 CHOICE between those two // EnvelopedAttestEvidence asn1EnvelopedAttestationEvidence Evidence asn1.RawValue } type asn1AttestationEvidence struct { TPMCertifyInfo asn1TPMCertifyInfo TPMIdentityCredAccessInfo asn1TPMIdentityCredentialAccessInfo } type asn1TPMCertifyInfo struct { CertifyInfo asn1.BitString Signature asn1.BitString } type asn1TPMIdentityCredentialAccessInfo struct { AuthorityInfoAccess []asn1AuthorityInfoAccessSyntax IssuerSerial issuerAndSerial } type asn1AuthorityInfoAccessSyntax struct { Method asn1.ObjectIdentifier Location asn1.RawValue } type issuerAndSerial struct { IssuerName asn1.RawValue SerialNumber *big.Int } type asn1EnvelopedAttestationEvidence struct { RecipientInfos []recipientInfo `asn1:"set"` EncryptedAttestInfo asn1EncryptedAttestationInfo } type recipientInfo struct { Version int IssuerAndSerialNumber issuerAndSerial KeyEncryptionAlgorithm pkix.AlgorithmIdentifier EncryptedKey []byte } type asn1EncryptedAttestationInfo struct { EncryptionAlgorithm pkix.AlgorithmIdentifier EncryptedAttestEvidence []byte // -- The ciphertext resulting from the encryption of DER-encoded AttestationEvidence (against public key in recipientinfo; something like that) } crypto-0.57.0/tpm/skae/extension_test.go000066400000000000000000000042351474156331600202660ustar00rootroot00000000000000package skae import ( "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "math/big" "reflect" "testing" "github.com/smallstep/go-attestation/attest" ) func TestCreateSubjectKeyAttestationEvidenceExtension(t *testing.T) { t.Skip("skipping because SKAE is not complete yet") akCert := &x509.Certificate{ Issuer: pkix.Name{ CommonName: "AK Test Issuer", }, SerialNumber: big.NewInt(1337), OCSPServer: []string{ "https://www.example.com/ocsp/1", "https://www.example.com/ocsp/2", }, IssuingCertificateURL: []string{ "https://www.example.com/issuing/cert1", }, } type args struct { akCert *x509.Certificate params attest.CertificationParameters shouldEncrypt bool } tests := []struct { name string args args want pkix.Extension wantErr bool }{ { name: "attest", args: args{ akCert: akCert, params: attest.CertificationParameters{ CreateAttestation: []byte("test-fake-create-attestation"), CreateSignature: []byte("test-fake-create-signature"), }, shouldEncrypt: false, }, want: pkix.Extension{ Id: asn1.ObjectIdentifier{2, 23, 133, 6, 1, 1}, Critical: false, Value: []byte{}, }, wantErr: false, }, { name: "enveloped-attest", args: args{ akCert: akCert, params: attest.CertificationParameters{ CreateAttestation: []byte("test-fake-create-attestation"), CreateSignature: []byte("test-fake-create-signature"), }, shouldEncrypt: true, }, // want: pkix.Extension{ // Id: asn1.ObjectIdentifier{2, 23, 133, 6, 1, 1}, // Critical: false, // Value: []byte{}, // }, want: pkix.Extension{}, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := CreateSubjectKeyAttestationEvidenceExtension(tt.args.akCert, tt.args.params, tt.args.shouldEncrypt) if (err != nil) != tt.wantErr { t.Errorf("CreateSubjectKeyAttestationEvidenceExtension() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("CreateSubjectKeyAttestationEvidenceExtension() = %v, want %v", got, tt.want) } }) } } crypto-0.57.0/tpm/storage/000077500000000000000000000000001474156331600154015ustar00rootroot00000000000000crypto-0.57.0/tpm/storage/blackhole.go000066400000000000000000000011031474156331600176470ustar00rootroot00000000000000package storage import "context" // BlackHole returns a [FeedthroughStore] without a backing // storage, effectively resulting in no persistence. Note // that some operations do require persistence, in which // case [ErrNoStorageConfigured] will be returned by the // [FeedthroughStore]. func BlackHole() TPMStore { return NewFeedthroughStore(nil) } // BlackholeContext adds a new BlackHole storage to the context. func BlackHoleContext(ctx context.Context) context.Context { if ctx == nil { ctx = context.Background() } ctx = NewContext(ctx, BlackHole()) return ctx } crypto-0.57.0/tpm/storage/blackhole_test.go000066400000000000000000000020401474156331600207070ustar00rootroot00000000000000package storage import ( "context" "testing" "github.com/stretchr/testify/require" ) func TestBlackHoleContext(t *testing.T) { t.Parallel() got := BlackHoleContext(nil) //nolint:staticcheck // nil context for testing require.NotNil(t, got) require.NotNil(t, FromContext(got)) got = BlackHoleContext(context.TODO()) require.NotNil(t, got) require.NotNil(t, FromContext(got)) } func TestBlackHole(t *testing.T) { t.Parallel() store := BlackHole() err := store.AddAK(&AK{Name: "ak"}) require.ErrorIs(t, err, ErrNoStorageConfigured) k, err := store.GetAK("ak") require.ErrorIs(t, err, ErrNoStorageConfigured) require.Nil(t, k) err = store.UpdateAK(k) require.ErrorIs(t, err, ErrNoStorageConfigured) names := store.ListAKNames() require.Empty(t, names) aks, err := store.ListAKs() require.ErrorIs(t, err, ErrNoStorageConfigured) require.Empty(t, aks) err = store.DeleteAK("ak") require.ErrorIs(t, err, ErrNoStorageConfigured) err = store.Persist() require.NoError(t, err) err = store.Load() require.NoError(t, err) } crypto-0.57.0/tpm/storage/dirstore.go000066400000000000000000000133421474156331600175660ustar00rootroot00000000000000package storage import ( "bytes" "encoding/json" "fmt" "path/filepath" "strings" "github.com/peterbourgon/diskv/v3" ) // Dirstore is a concrete implementation of the [TPMStore] interface that // stores TPM objects in a directory. Each object will be stored in a // separate file in the directory. The name of the file is constructed // by prefixing the name of the object with its type. type Dirstore struct { store *diskv.Diskv directory string } const tpmExtension = ".tpmobj" func advancedTransform(key string) *diskv.PathKey { path := strings.Split(key, "/") last := len(path) - 1 return &diskv.PathKey{ Path: path[:last], FileName: path[last] + tpmExtension, } } func inverseTransform(pathKey *diskv.PathKey) (key string) { ext := filepath.Ext(pathKey.FileName) if ext != tpmExtension { // skipping return "" } filename := pathKey.FileName[:len(pathKey.FileName)-len(tpmExtension)] key = filepath.Join(filepath.Join(pathKey.Path...), filename) if len(pathKey.Path) > 0 && pathKey.Path[0] == "" { // absolute path at "/" key = filepath.Join(string(filepath.Separator), key) } return } // NewDirstore creates a new instance of a Direstore. func NewDirstore(directory string) *Dirstore { return &Dirstore{ store: diskv.New(diskv.Options{ BasePath: directory, AdvancedTransform: advancedTransform, InverseTransform: inverseTransform, CacheSizeMax: 1024 * 1024, // TODO(hs): add TempDir for atomic operations? }), directory: directory, } } func (s *Dirstore) ListKeys() ([]*Key, error) { var result = make([]*Key, 0) c := s.store.KeysPrefix(keyPrefix, nil) for k := range c { data, err := s.store.Read(k) if err != nil { return nil, fmt.Errorf("failed reading key from store: %w", err) } key := &Key{} if err := json.Unmarshal(data, key); err != nil { return nil, fmt.Errorf("failed unmarshaling key: %w", err) } result = append(result, key) } return result, nil } func (s *Dirstore) ListKeyNames() []string { var result = make([]string, 0) c := s.store.KeysPrefix(keyPrefix, nil) for k := range c { result = append(result, strings.TrimPrefix(k, keyPrefix)) } return result } func (s *Dirstore) GetKey(name string) (*Key, error) { kk := keyForKey(name) if !s.store.Has(kk) { return nil, ErrNotFound } data, err := s.store.Read(kk) if err != nil { return nil, fmt.Errorf("failed reading key from store: %w", err) } key := &Key{} if err := json.Unmarshal(data, key); err != nil { return nil, fmt.Errorf("failed unmarshaling key: %w", err) } return key, nil } func (s *Dirstore) AddKey(key *Key) error { kk := keyForKey(key.Name) if s.store.Has(kk) { return ErrExists } data, err := json.Marshal(key) if err != nil { return fmt.Errorf("failed serializing key: %w", err) } if err := s.store.WriteStream(kk, bytes.NewBuffer(data), true); err != nil { return fmt.Errorf("failed writing key to disk: %w", err) } return nil } func (s *Dirstore) UpdateKey(key *Key) error { kk := keyForKey(key.Name) if !s.store.Has(kk) { return ErrNotFound } data, err := json.Marshal(key) if err != nil { return fmt.Errorf("failed serializing key: %w", err) } if err := s.store.WriteStream(kk, bytes.NewBuffer(data), true); err != nil { return fmt.Errorf("failed writing key to disk: %w", err) } return nil } func (s *Dirstore) DeleteKey(name string) error { key := keyForKey(name) if !s.store.Has(key) { return ErrNotFound } if err := s.store.Erase(key); err != nil { return fmt.Errorf("failed deleting key from disk: %w", err) } return nil } func (s *Dirstore) ListAKs() ([]*AK, error) { var result = make([]*AK, 0) c := s.store.KeysPrefix(akPrefix, nil) for k := range c { data, err := s.store.Read(k) if err != nil { return nil, fmt.Errorf("failed reading AK from store: %w", err) } ak := &AK{} if err := json.Unmarshal(data, ak); err != nil { return nil, fmt.Errorf("failed unmarshaling AK: %w", err) } result = append(result, ak) } return result, nil } func (s *Dirstore) ListAKNames() []string { var result = make([]string, 0) c := s.store.KeysPrefix(akPrefix, nil) for k := range c { result = append(result, strings.TrimPrefix(k, akPrefix)) } return result } func (s *Dirstore) GetAK(name string) (*AK, error) { akKey := keyForAK(name) if !s.store.Has(akKey) { return nil, ErrNotFound } data, err := s.store.Read(akKey) if err != nil { return nil, fmt.Errorf("failed reading AK from store: %w", err) } ak := &AK{} if err := json.Unmarshal(data, ak); err != nil { return nil, fmt.Errorf("failed unmarshaling AK: %w", err) } return ak, nil } func (s *Dirstore) AddAK(ak *AK) error { akKey := keyForAK(ak.Name) if s.store.Has(akKey) { return ErrExists } data, err := json.Marshal(ak) if err != nil { return fmt.Errorf("failed serializing AK: %w", err) } if err := s.store.WriteStream(akKey, bytes.NewBuffer(data), true); err != nil { return fmt.Errorf("failed writing AK to disk: %w", err) } return nil } func (s *Dirstore) UpdateAK(ak *AK) error { akKey := keyForAK(ak.Name) if !s.store.Has(akKey) { return ErrNotFound } data, err := json.Marshal(ak) if err != nil { return fmt.Errorf("failed serializing AK: %w", err) } if err := s.store.WriteStream(akKey, bytes.NewBuffer(data), true); err != nil { return fmt.Errorf("failed writing AK to disk: %w", err) } return nil } func (s *Dirstore) DeleteAK(name string) error { key := keyForAK(name) if !s.store.Has(key) { return ErrNotFound } if err := s.store.Erase(key); err != nil { return fmt.Errorf("failed deleting AK from disk: %w", err) } return nil } func (s *Dirstore) Persist() error { // writes are persisted directly return nil } func (s *Dirstore) Load() error { // reads are performed directly return nil } var _ TPMStore = (*Dirstore)(nil) crypto-0.57.0/tpm/storage/dirstore_test.go000066400000000000000000000060121474156331600206210ustar00rootroot00000000000000package storage import ( "testing" "github.com/peterbourgon/diskv/v3" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func Test_transform(t *testing.T) { t.Parallel() got := advancedTransform("/path/to/file") assert.Equal(t, &diskv.PathKey{Path: []string{"", "path", "to"}, FileName: "file.tpmobj"}, got) got = advancedTransform("path/to/file") assert.Equal(t, &diskv.PathKey{Path: []string{"path", "to"}, FileName: "file.tpmobj"}, got) got = advancedTransform("file.txt") assert.Equal(t, &diskv.PathKey{Path: []string{}, FileName: "file.txt.tpmobj"}, got) } func Test_inverseTransform(t *testing.T) { t.Parallel() got := inverseTransform(&diskv.PathKey{FileName: "file.txt.notpmkey"}) assert.Equal(t, "", got) got = inverseTransform(&diskv.PathKey{FileName: "file.txt.tpmobj"}) assert.Equal(t, "file.txt", got) got = inverseTransform(&diskv.PathKey{Path: []string{"path", "to"}, FileName: "file.tpmobj"}) assert.Equal(t, "path/to/file", got) got = inverseTransform(&diskv.PathKey{Path: []string{"", "path", "to"}, FileName: "file.tpmobj"}) assert.Equal(t, "/path/to/file", got) } func TestDirstore_KeyOperations(t *testing.T) { t.Parallel() tempDir := t.TempDir() store := NewDirstore(tempDir) key1 := &Key{Name: "1st-key"} key2 := &Key{Name: "2nd-key"} err := store.AddKey(key1) require.NoError(t, err) err = store.AddKey(key2) require.NoError(t, err) err = store.AddKey(&Key{Name: "1st-key"}) require.EqualError(t, err, "already exists") k, err := store.GetKey("1st-key") require.NoError(t, err) require.Equal(t, key1, k) k, err = store.GetKey("3rd-key") require.EqualError(t, err, "not found") require.Nil(t, k) names := store.ListKeyNames() require.Equal(t, []string{"1st-key", "2nd-key"}, names) keys, err := store.ListKeys() require.NoError(t, err) require.ElementsMatch(t, []*Key{key1, key2}, keys) err = store.DeleteKey("3rd-key") require.EqualError(t, err, "not found") err = store.DeleteKey("1st-key") require.NoError(t, err) keys, err = store.ListKeys() require.NoError(t, err) require.ElementsMatch(t, []*Key{key2}, keys) } func TestDirstore_AKOperations(t *testing.T) { t.Parallel() tempDir := t.TempDir() store := NewDirstore(tempDir) ak1 := &AK{Name: "1st-ak"} ak2 := &AK{Name: "2nd-ak"} err := store.AddAK(ak1) require.NoError(t, err) err = store.AddAK(ak2) require.NoError(t, err) err = store.AddAK(ak1) require.EqualError(t, err, "already exists") k, err := store.GetAK("1st-ak") require.NoError(t, err) require.Equal(t, ak1, k) k, err = store.GetAK("3rd-ak") require.EqualError(t, err, "not found") require.Nil(t, k) names := store.ListAKNames() require.Equal(t, []string{"1st-ak", "2nd-ak"}, names) aks, err := store.ListAKs() require.NoError(t, err) require.ElementsMatch(t, []*AK{ak1, ak2}, aks) err = store.DeleteAK("3rd-ak") require.EqualError(t, err, "not found") err = store.DeleteAK("1st-ak") require.NoError(t, err) aks, err = store.ListAKs() require.NoError(t, err) require.ElementsMatch(t, []*AK{ak2}, aks) } crypto-0.57.0/tpm/storage/errors.go000066400000000000000000000007561474156331600172540ustar00rootroot00000000000000package storage import "errors" // TODO: provide key/ak name in the error? // ErrNotFound is returned when a Key or AK cannot be found in storage var ErrNotFound = errors.New("not found") // ErrExists is returned when a Key or AK already exists in storage var ErrExists = errors.New("already exists") // ErrNoStorageConfigured is returned when a TPM operation is // performed that requires a storage to have been configured var ErrNoStorageConfigured = errors.New("no storage configured") crypto-0.57.0/tpm/storage/feedthrough.go000066400000000000000000000044531474156331600202420ustar00rootroot00000000000000package storage // FeedthroughStore is a TPMStore that feeds through storage operations // to the underlying TPMStore. If no backing TPMStore is set, but the // operation requires one, [ErrNoStorageConfigured] will be returned. type FeedthroughStore struct { store TPMStore } func NewFeedthroughStore(store TPMStore) *FeedthroughStore { return &FeedthroughStore{ store: store, } } func (f *FeedthroughStore) ListKeys() ([]*Key, error) { if f.store == nil { return nil, ErrNoStorageConfigured } return f.store.ListKeys() } func (f *FeedthroughStore) ListKeyNames() []string { if f.store == nil { return nil } return f.store.ListKeyNames() } func (f *FeedthroughStore) GetKey(name string) (*Key, error) { if f.store == nil { return nil, ErrNoStorageConfigured } return f.store.GetKey(name) } func (f *FeedthroughStore) AddKey(key *Key) error { if f.store == nil { return ErrNoStorageConfigured } return f.store.AddKey(key) } func (f *FeedthroughStore) UpdateKey(key *Key) error { if f.store == nil { return ErrNoStorageConfigured } return f.store.UpdateKey(key) } func (f *FeedthroughStore) DeleteKey(name string) error { if f.store == nil { return ErrNoStorageConfigured } return f.store.DeleteKey(name) } func (f *FeedthroughStore) ListAKs() ([]*AK, error) { if f.store == nil { return nil, ErrNoStorageConfigured } return f.store.ListAKs() } func (f *FeedthroughStore) ListAKNames() []string { if f.store == nil { return nil } return f.store.ListAKNames() } func (f *FeedthroughStore) GetAK(name string) (*AK, error) { if f.store == nil { return nil, ErrNoStorageConfigured } return f.store.GetAK(name) } func (f *FeedthroughStore) AddAK(ak *AK) error { if f.store == nil { return ErrNoStorageConfigured } return f.store.AddAK(ak) } func (f *FeedthroughStore) UpdateAK(ak *AK) error { if f.store == nil { return ErrNoStorageConfigured } return f.store.UpdateAK(ak) } func (f *FeedthroughStore) DeleteAK(name string) error { if f.store == nil { return ErrNoStorageConfigured } return f.store.DeleteAK(name) } func (f *FeedthroughStore) Persist() error { if f.store == nil { return nil } return f.store.Persist() } func (f *FeedthroughStore) Load() error { if f.store == nil { return nil } return f.store.Load() } var _ TPMStore = (*FeedthroughStore)(nil) crypto-0.57.0/tpm/storage/feedthrough_test.go000066400000000000000000000116041474156331600212750ustar00rootroot00000000000000package storage import ( "testing" "github.com/stretchr/testify/require" ) func TestFeedthroughStore_NilKeyOperations(t *testing.T) { t.Parallel() store := NewFeedthroughStore(nil) key1 := &Key{Name: "1st-key"} key2 := &Key{Name: "2nd-key"} err := store.AddKey(key1) require.ErrorIs(t, err, ErrNoStorageConfigured) err = store.AddKey(key2) require.ErrorIs(t, err, ErrNoStorageConfigured) err = store.AddKey(&Key{Name: "1st-key"}) require.ErrorIs(t, err, ErrNoStorageConfigured) k, err := store.GetKey("1st-key") require.ErrorIs(t, err, ErrNoStorageConfigured) require.Nil(t, k) err = store.UpdateKey(k) require.ErrorIs(t, err, ErrNoStorageConfigured) k, err = store.GetKey("3rd-key") require.ErrorIs(t, err, ErrNoStorageConfigured) require.Nil(t, k) names := store.ListKeyNames() require.Empty(t, names) keys, err := store.ListKeys() require.ErrorIs(t, err, ErrNoStorageConfigured) require.Empty(t, keys) err = store.DeleteKey("3rd-key") require.ErrorIs(t, err, ErrNoStorageConfigured) err = store.DeleteKey("1st-key") require.ErrorIs(t, err, ErrNoStorageConfigured) keys, err = store.ListKeys() require.ErrorIs(t, err, ErrNoStorageConfigured) require.Empty(t, keys) err = store.Persist() require.NoError(t, err) err = store.Load() require.NoError(t, err) } func TestFeedthroughStore_KeyOperations(t *testing.T) { t.Parallel() tempDir := t.TempDir() store := NewFeedthroughStore(NewDirstore(tempDir)) key1 := &Key{Name: "1st-key"} key2 := &Key{Name: "2nd-key"} err := store.AddKey(key1) require.NoError(t, err) err = store.AddKey(key2) require.NoError(t, err) err = store.AddKey(&Key{Name: "1st-key"}) require.EqualError(t, err, "already exists") k, err := store.GetKey("1st-key") require.NoError(t, err) require.Equal(t, key1, k) k.AttestedBy = "ak1" err = store.UpdateKey(k) require.NoError(t, err) k.AttestedBy = "" err = store.UpdateKey(k) require.NoError(t, err) k, err = store.GetKey("3rd-key") require.EqualError(t, err, "not found") require.Nil(t, k) names := store.ListKeyNames() require.Equal(t, []string{"1st-key", "2nd-key"}, names) keys, err := store.ListKeys() require.NoError(t, err) require.ElementsMatch(t, []*Key{key1, key2}, keys) err = store.DeleteKey("3rd-key") require.EqualError(t, err, "not found") err = store.DeleteKey("1st-key") require.NoError(t, err) keys, err = store.ListKeys() require.NoError(t, err) require.ElementsMatch(t, []*Key{key2}, keys) err = store.Persist() require.NoError(t, err) err = store.Load() require.NoError(t, err) } func TestFeedthroughStore_NilAKOperations(t *testing.T) { t.Parallel() store := NewFeedthroughStore(nil) ak1 := &AK{Name: "1st-ak"} ak2 := &AK{Name: "2nd-ak"} err := store.AddAK(ak1) require.ErrorIs(t, err, ErrNoStorageConfigured) err = store.AddAK(ak2) require.ErrorIs(t, err, ErrNoStorageConfigured) err = store.AddAK(&AK{Name: "1st-ak"}) require.ErrorIs(t, err, ErrNoStorageConfigured) k, err := store.GetAK("1st-ak") require.ErrorIs(t, err, ErrNoStorageConfigured) require.Nil(t, k) err = store.UpdateAK(k) require.ErrorIs(t, err, ErrNoStorageConfigured) k, err = store.GetAK("3rd-ak") require.ErrorIs(t, err, ErrNoStorageConfigured) require.Nil(t, k) names := store.ListAKNames() require.Empty(t, names) aks, err := store.ListAKs() require.ErrorIs(t, err, ErrNoStorageConfigured) require.Empty(t, aks) err = store.DeleteAK("3rd-ak") require.ErrorIs(t, err, ErrNoStorageConfigured) err = store.DeleteAK("1st-ak") require.ErrorIs(t, err, ErrNoStorageConfigured) aks, err = store.ListAKs() require.ErrorIs(t, err, ErrNoStorageConfigured) require.Empty(t, aks) err = store.Persist() require.NoError(t, err) err = store.Load() require.NoError(t, err) } func TestFeedthroughStore_AKOperations(t *testing.T) { t.Parallel() tempDir := t.TempDir() store := NewFeedthroughStore(NewDirstore(tempDir)) ak1 := &AK{Name: "1st-ak"} ak2 := &AK{Name: "2nd-ak"} err := store.AddAK(ak1) require.NoError(t, err) err = store.AddAK(ak2) require.NoError(t, err) err = store.AddAK(ak1) require.EqualError(t, err, "already exists") k, err := store.GetAK("1st-ak") require.NoError(t, err) require.Equal(t, ak1, k) k.Data = []byte{1, 2, 3, 4} err = store.UpdateAK(k) require.NoError(t, err) k.Data = nil err = store.UpdateAK(k) require.NoError(t, err) k, err = store.GetAK("3rd-ak") require.EqualError(t, err, "not found") require.Nil(t, k) names := store.ListAKNames() require.Equal(t, []string{"1st-ak", "2nd-ak"}, names) aks, err := store.ListAKs() require.NoError(t, err) require.ElementsMatch(t, []*AK{ak1, ak2}, aks) err = store.DeleteAK("3rd-ak") require.EqualError(t, err, "not found") err = store.DeleteAK("1st-ak") require.NoError(t, err) aks, err = store.ListAKs() require.NoError(t, err) require.ElementsMatch(t, []*AK{ak2}, aks) err = store.Persist() require.NoError(t, err) err = store.Load() require.NoError(t, err) } crypto-0.57.0/tpm/storage/filestore.go000066400000000000000000000103541474156331600177270ustar00rootroot00000000000000package storage import ( "encoding/json" "errors" "fmt" "regexp" "strings" "github.com/schollz/jsonstore" ) // Filestore is a concrete implementation of the TPMStore interface that // keeps an in-memory map of AKs and TPM Keys. The current state of the // in-memory storage can be persisted to a file. type Filestore struct { store *jsonstore.JSONStore filepath string } // NewFilestore creates a new instance of a Filestore // // TODO: provide options for filepath (with default), // gzip, persistence en-/disabled, ... ? func NewFilestore(filepath string) *Filestore { return &Filestore{ store: new(jsonstore.JSONStore), filepath: filepath, } } func (s *Filestore) AddKey(k *Key) error { kk := keyForKey(k.Name) if err := s.store.Get(kk, nil); err != nil { nsk := &jsonstore.NoSuchKeyError{} if errors.As(err, nsk) { return s.store.Set(kk, k) } return err } return ErrExists } func (s *Filestore) AddAK(ak *AK) error { akKey := keyForAK(ak.Name) if err := s.store.Get(akKey, nil); err != nil { nsk := &jsonstore.NoSuchKeyError{} if errors.As(err, nsk) { return s.store.Set(akKey, ak) } return err } return ErrExists } func (s *Filestore) GetKey(name string) (*Key, error) { key := &Key{} if err := s.store.Get(keyForKey(name), key); err != nil { nsk := &jsonstore.NoSuchKeyError{} if errors.As(err, nsk) { return nil, ErrNotFound } return nil, err } return key, nil } func (s *Filestore) GetAK(name string) (*AK, error) { ak := &AK{} if err := s.store.Get(keyForAK(name), ak); err != nil { nsk := &jsonstore.NoSuchKeyError{} if errors.As(err, nsk) { return nil, ErrNotFound } return nil, err } return ak, nil } func (s *Filestore) UpdateKey(k *Key) error { kk := keyForKey(k.Name) if err := s.store.Get(kk, nil); err != nil { nsk := &jsonstore.NoSuchKeyError{} if errors.As(err, nsk) { return ErrNotFound } return err } return s.store.Set(kk, k) } func (s *Filestore) UpdateAK(ak *AK) error { akKey := keyForAK(ak.Name) if err := s.store.Get(akKey, nil); err != nil { nsk := &jsonstore.NoSuchKeyError{} if errors.As(err, nsk) { return ErrNotFound } return err } return s.store.Set(akKey, ak) } func (s *Filestore) DeleteKey(name string) error { kk := keyForKey(name) if err := s.store.Get(kk, nil); err != nil { nsk := &jsonstore.NoSuchKeyError{} if errors.As(err, nsk) { return ErrNotFound } return err } s.store.Delete(kk) return nil } func (s *Filestore) DeleteAK(name string) error { ka := keyForAK(name) if err := s.store.Get(ka, nil); err != nil { nsk := &jsonstore.NoSuchKeyError{} if errors.As(err, nsk) { return ErrNotFound } return err } s.store.Delete(ka) return nil } func (s *Filestore) ListKeys() ([]*Key, error) { keys := s.store.GetAll(regexp.MustCompile(keyPrefix)) var result = make([]*Key, 0, len(keys)) for _, v := range keys { key := &Key{} err := json.Unmarshal(v, key) if err != nil { return nil, fmt.Errorf("failed unmarshaling key: %w", err) } result = append(result, key) } return result, nil } func (s *Filestore) ListAKs() ([]*AK, error) { aks := s.store.GetAll(regexp.MustCompile(akPrefix)) var result = make([]*AK, 0, len(aks)) for _, v := range aks { ak := &AK{} err := json.Unmarshal(v, ak) if err != nil { return nil, fmt.Errorf("failed unmarshaling AK: %w", err) } result = append(result, ak) } return result, nil } func (s *Filestore) ListKeyNames() []string { keys := s.store.Keys() var result = make([]string, 0, len(keys)) for _, k := range keys { if strings.HasPrefix(k, keyPrefix) { result = append(result, strings.TrimPrefix(k, keyPrefix)) } } return result } func (s *Filestore) ListAKNames() []string { keys := s.store.Keys() var result = make([]string, 0, len(keys)) for _, k := range keys { if strings.HasPrefix(k, akPrefix) { result = append(result, strings.TrimPrefix(k, akPrefix)) } } return result } func (s *Filestore) Persist() error { return jsonstore.Save(s.store, s.filepath) } func (s *Filestore) Load() error { store, err := jsonstore.Open(s.filepath) if err != nil { // TODO: handle different types of errors related to file system? store = new(jsonstore.JSONStore) } s.store = store return nil } var _ TPMStore = (*Filestore)(nil) crypto-0.57.0/tpm/storage/filestore_test.go000066400000000000000000000317041474156331600207700ustar00rootroot00000000000000package storage import ( "errors" "testing" "time" "github.com/schollz/jsonstore" "github.com/stretchr/testify/assert" ) func TestFilestore_AddKey(t *testing.T) { t.Parallel() t0 := time.Time{} // we're hit by https://github.com/stretchr/testify/issues/950 store := new(jsonstore.JSONStore) store.Set("key-1st-key", serializedKey{Name: "1st-key", Type: typeKey, Data: []byte{1, 2, 3, 4}, AttestedBy: "1st-ak", CreatedAt: t0}) store.Data["key-bad-storage"] = nil tests := []struct { name string key *Key expErr error }{ { name: "already-exists", key: &Key{Name: "1st-key"}, expErr: errors.New("already exists"), }, { name: "an-error", key: &Key{Name: "bad-storage"}, expErr: errors.New("unexpected end of JSON input"), }, { name: "ok", key: &Key{Name: "2nd-key"}, expErr: nil, }, } for _, tt := range tests { tc := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() s := &Filestore{ store: store, } err := s.AddKey(tc.key) if tc.expErr != nil { assert.Error(t, err) assert.EqualError(t, err, tc.expErr.Error()) return } assert.NoError(t, err) }) } } func TestFilestore_GetKey(t *testing.T) { t.Parallel() t0 := time.Time{} // we're hit by https://github.com/stretchr/testify/issues/950 store := new(jsonstore.JSONStore) store.Set("key-1st-key", serializedKey{Name: "1st-key", Type: typeKey, Data: []byte{1, 2, 3, 4}, AttestedBy: "1st-ak", CreatedAt: t0}) store.Data["key-bad-storage"] = nil tests := []struct { name string keyName string want *Key expErr error }{ { name: "not-found", keyName: "non-existing-key", want: nil, expErr: errors.New("not found"), }, { name: "an-error", keyName: "bad-storage", want: nil, expErr: errors.New("unexpected end of JSON input"), }, { name: "ok", keyName: "1st-key", want: &Key{Name: "1st-key", Data: []byte{0x1, 0x2, 0x3, 0x4}, AttestedBy: "1st-ak", CreatedAt: t0}, expErr: nil, }, } for _, tt := range tests { tc := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() s := &Filestore{ store: store, } got, err := s.GetKey(tc.keyName) if tc.expErr != nil { assert.Nil(t, got) assert.Error(t, err) assert.EqualError(t, err, tc.expErr.Error()) return } assert.NoError(t, err) assert.Equal(t, tc.want, got) }) } } func TestFilestore_UpdateKey(t *testing.T) { t.Parallel() t0 := time.Time{} // we're hit by https://github.com/stretchr/testify/issues/950 store := new(jsonstore.JSONStore) store.Set("key-1st-key", serializedKey{Name: "1st-key", Type: typeKey, Data: []byte{1, 2, 3, 4}, AttestedBy: "1st-ak", CreatedAt: t0}) store.Data["key-bad-storage"] = nil tests := []struct { name string key *Key expErr error }{ { name: "not-found", key: &Key{Name: "non-existing-key"}, expErr: errors.New("not found"), }, { name: "an-error", key: &Key{Name: "bad-storage"}, expErr: errors.New("unexpected end of JSON input"), }, { name: "ok", key: &Key{Name: "1st-key"}, expErr: nil, }, } for _, tt := range tests { tc := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() s := &Filestore{ store: store, } err := s.UpdateKey(tc.key) if tc.expErr != nil { assert.Error(t, err) assert.EqualError(t, err, tc.expErr.Error()) return } assert.NoError(t, err) }) } } func TestFilestore_DeleteKey(t *testing.T) { t.Parallel() t0 := time.Time{} // we're hit by https://github.com/stretchr/testify/issues/950 store := new(jsonstore.JSONStore) store.Set("key-1st-key", serializedKey{Name: "1st-key", Type: typeKey, Data: []byte{1, 2, 3, 4}, AttestedBy: "1st-ak", CreatedAt: t0}) store.Data["key-bad-storage"] = nil tests := []struct { name string keyName string expErr error }{ { name: "not-found", keyName: "non-existing-key", expErr: errors.New("not found"), }, { name: "an-error", keyName: "bad-storage", expErr: errors.New("unexpected end of JSON input"), }, { name: "ok", keyName: "1st-key", expErr: nil, }, } for _, tt := range tests { tc := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() s := &Filestore{ store: store, } err := s.DeleteKey(tc.keyName) if tc.expErr != nil { assert.Error(t, err) assert.EqualError(t, err, tc.expErr.Error()) return } assert.NoError(t, err) }) } } func TestFilestore_ListKeys(t *testing.T) { t.Parallel() t0 := time.Time{} // we're hit by https://github.com/stretchr/testify/issues/950 okStore := new(jsonstore.JSONStore) okStore.Set("key-1st-key", serializedKey{Name: "1st-key", Type: typeKey, Data: []byte{1, 2, 3, 4}, AttestedBy: "1st-ak", CreatedAt: t0}) okStore.Set("key-2nd-key", serializedKey{Name: "2nd-key", Type: typeKey, Data: []byte{1, 2, 3, 4}, AttestedBy: "1st-ak", CreatedAt: t0}) failStore := new(jsonstore.JSONStore) failStore.Set("key-1st-key", serializedKey{Name: "1st-key", Type: typeKey, Data: []byte{1, 2, 3, 4}, AttestedBy: "1st-ak", CreatedAt: t0}) failStore.Set("key-2nd-key", serializedKey{Name: "2nd-key", Type: typeKey, Data: []byte{1, 2, 3, 4}, AttestedBy: "1st-ak", CreatedAt: t0}) failStore.Data["key-bad-storage"] = nil tests := []struct { name string store *jsonstore.JSONStore want []*Key expErr error }{ { name: "fail", store: failStore, want: []*Key{}, expErr: errors.New("failed unmarshaling key: unexpected end of JSON input"), }, { name: "ok", store: okStore, want: []*Key{ { Name: "1st-key", Data: []byte{1, 2, 3, 4}, AttestedBy: "1st-ak", CreatedAt: t0, }, { Name: "2nd-key", Data: []byte{1, 2, 3, 4}, AttestedBy: "1st-ak", CreatedAt: t0, }, }, expErr: nil, }, } for _, tt := range tests { tc := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() s := &Filestore{ store: tc.store, } got, err := s.ListKeys() if tc.expErr != nil { assert.Error(t, err) assert.EqualError(t, err, tc.expErr.Error()) assert.Empty(t, got) return } assert.NoError(t, err) assert.ElementsMatch(t, tc.want, got) }) } } func TestFilestore_ListKeyNames(t *testing.T) { t.Parallel() t0 := time.Time{} // we're hit by https://github.com/stretchr/testify/issues/950 store := new(jsonstore.JSONStore) store.Set("key-1st-key", serializedKey{Name: "1st-key", Type: typeKey, Data: []byte{1, 2, 3, 4}, AttestedBy: "1st-ak", CreatedAt: t0}) store.Set("key-2nd-key", serializedKey{Name: "2nd-key", Type: typeKey, Data: []byte{1, 2, 3, 4}, AttestedBy: "1st-ak", CreatedAt: t0}) expected := []string{"1st-key", "2nd-key"} s := &Filestore{store: store} got := s.ListKeyNames() assert.ElementsMatch(t, expected, got) } func TestFilestore_AddAK(t *testing.T) { t.Parallel() t0 := time.Time{} // we're hit by https://github.com/stretchr/testify/issues/950 store := new(jsonstore.JSONStore) store.Set("ak-1st-ak", serializedAK{Name: "1st-ak", Type: typeKey, Data: []byte{1, 2, 3, 4}, CreatedAt: t0}) store.Data["ak-bad-storage"] = nil tests := []struct { name string ak *AK expErr error }{ { name: "already-exists", ak: &AK{Name: "1st-ak"}, expErr: errors.New("already exists"), }, { name: "an-error", ak: &AK{Name: "bad-storage"}, expErr: errors.New("unexpected end of JSON input"), }, { name: "ok", ak: &AK{Name: "2nd-ak"}, expErr: nil, }, } for _, tt := range tests { tc := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() s := &Filestore{ store: store, } err := s.AddAK(tc.ak) if tc.expErr != nil { assert.Error(t, err) assert.EqualError(t, err, tc.expErr.Error()) return } assert.NoError(t, err) }) } } func TestFilestore_GetAK(t *testing.T) { t.Parallel() t0 := time.Time{} // we're hit by https://github.com/stretchr/testify/issues/950 store := new(jsonstore.JSONStore) store.Set("ak-1st-ak", serializedAK{Name: "1st-ak", Type: typeAK, Data: []byte{1, 2, 3, 4}, CreatedAt: t0}) store.Data["ak-bad-storage"] = nil tests := []struct { name string akName string want *AK expErr error }{ { name: "not-found", akName: "non-existing-ak", want: nil, expErr: errors.New("not found"), }, { name: "an-error", akName: "bad-storage", want: nil, expErr: errors.New("unexpected end of JSON input"), }, { name: "ok", akName: "1st-ak", want: &AK{Name: "1st-ak", Data: []byte{0x1, 0x2, 0x3, 0x4}, CreatedAt: t0}, expErr: nil, }, } for _, tt := range tests { tc := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() s := &Filestore{ store: store, } got, err := s.GetAK(tc.akName) if tc.expErr != nil { assert.Nil(t, got) assert.Error(t, err) assert.EqualError(t, err, tc.expErr.Error()) return } assert.NoError(t, err) assert.Equal(t, tc.want, got) }) } } func TestFilestore_UpdateAK(t *testing.T) { t.Parallel() t0 := time.Time{} // we're hit by https://github.com/stretchr/testify/issues/950 store := new(jsonstore.JSONStore) store.Set("ak-1st-ak", serializedAK{Name: "1st-ak", Type: typeKey, Data: []byte{1, 2, 3, 4}, CreatedAt: t0}) store.Data["ak-bad-storage"] = nil tests := []struct { name string ak *AK expErr error }{ { name: "not-found", ak: &AK{Name: "non-existing-key"}, expErr: errors.New("not found"), }, { name: "an-error", ak: &AK{Name: "bad-storage"}, expErr: errors.New("unexpected end of JSON input"), }, { name: "ok", ak: &AK{Name: "1st-ak"}, expErr: nil, }, } for _, tt := range tests { tc := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() s := &Filestore{ store: store, } err := s.UpdateAK(tc.ak) if tc.expErr != nil { assert.Error(t, err) assert.EqualError(t, err, tc.expErr.Error()) return } assert.NoError(t, err) }) } } func TestFilestore_DeleteAK(t *testing.T) { t.Parallel() t0 := time.Time{} // we're hit by https://github.com/stretchr/testify/issues/950 store := new(jsonstore.JSONStore) store.Set("ak-1st-ak", serializedAK{Name: "1st-ak", Type: typeAK, Data: []byte{1, 2, 3, 4}, CreatedAt: t0}) store.Data["ak-bad-storage"] = nil tests := []struct { name string akName string expErr error }{ { name: "not-found", akName: "non-existing-key", expErr: errors.New("not found"), }, { name: "an-error", akName: "bad-storage", expErr: errors.New("unexpected end of JSON input"), }, { name: "ok", akName: "1st-ak", expErr: nil, }, } for _, tt := range tests { tc := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() s := &Filestore{ store: store, } err := s.DeleteAK(tc.akName) if tc.expErr != nil { assert.Error(t, err) assert.EqualError(t, err, tc.expErr.Error()) return } assert.NoError(t, err) }) } } func TestFilestore_ListAKs(t *testing.T) { t.Parallel() t0 := time.Time{} // we're hit by https://github.com/stretchr/testify/issues/950 okStore := new(jsonstore.JSONStore) okStore.Set("ak-1st-ak", serializedAK{Name: "1st-ak", Type: typeAK, Data: []byte{1, 2, 3, 4}, CreatedAt: t0}) okStore.Set("ak-2nd-ak", serializedAK{Name: "2nd-ak", Type: typeAK, Data: []byte{1, 2, 3, 4}, CreatedAt: t0}) failStore := new(jsonstore.JSONStore) failStore.Set("ak-1st-ak", serializedAK{Name: "1st-ak", Type: typeAK, Data: []byte{1, 2, 3, 4}, CreatedAt: t0}) failStore.Set("ak-2nd-ak", serializedAK{Name: "2nd-ak", Type: typeAK, Data: []byte{1, 2, 3, 4}, CreatedAt: t0}) failStore.Data["ak-bad-storage"] = nil tests := []struct { name string store *jsonstore.JSONStore want []*AK expErr error }{ { name: "fail", store: failStore, want: []*AK{}, expErr: errors.New("failed unmarshaling AK: unexpected end of JSON input"), }, { name: "ok", store: okStore, want: []*AK{ { Name: "1st-ak", Data: []byte{1, 2, 3, 4}, CreatedAt: t0, }, { Name: "2nd-ak", Data: []byte{1, 2, 3, 4}, CreatedAt: t0, }, }, expErr: nil, }, } for _, tt := range tests { tc := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() s := &Filestore{ store: tc.store, } got, err := s.ListAKs() if tc.expErr != nil { assert.Error(t, err) assert.EqualError(t, err, tc.expErr.Error()) assert.Empty(t, got) return } assert.NoError(t, err) assert.ElementsMatch(t, tc.want, got) }) } } func TestFilestore_ListAKNames(t *testing.T) { t.Parallel() t0 := time.Time{} // we're hit by https://github.com/stretchr/testify/issues/950 store := new(jsonstore.JSONStore) store.Set("ak-1st-ak", serializedAK{Name: "1st-ak", Type: typeKey, Data: []byte{1, 2, 3, 4}, CreatedAt: t0}) store.Set("ak-2nd-ak", serializedAK{Name: "2nd-ak", Type: typeKey, Data: []byte{1, 2, 3, 4}, CreatedAt: t0}) expected := []string{"1st-ak", "2nd-ak"} s := &Filestore{store: store} got := s.ListAKNames() assert.ElementsMatch(t, expected, got) } crypto-0.57.0/tpm/storage/tpmstore.go000066400000000000000000000016011474156331600176030ustar00rootroot00000000000000package storage import "context" type contextKey struct{} // NewContext adds TPMStore `t` to the context. func NewContext(ctx context.Context, t TPMStore) context.Context { return context.WithValue(ctx, contextKey{}, t) } // FromContext retrieves a TPMStore from the context. // // It panics when there's no TPMStore present. func FromContext(ctx context.Context) TPMStore { return ctx.Value(contextKey{}).(TPMStore) } // TPMStore is the interface that TPM storage implementations // need to implement. type TPMStore interface { ListKeys() ([]*Key, error) ListKeyNames() []string GetKey(name string) (*Key, error) AddKey(key *Key) error UpdateKey(key *Key) error DeleteKey(name string) error ListAKs() ([]*AK, error) ListAKNames() []string GetAK(name string) (*AK, error) AddAK(ak *AK) error UpdateAK(ak *AK) error DeleteAK(name string) error Persist() error Load() error } crypto-0.57.0/tpm/storage/tpmstore_test.go000066400000000000000000000005701474156331600206460ustar00rootroot00000000000000package storage import ( "context" "testing" "github.com/stretchr/testify/assert" ) func TestFromContext(t *testing.T) { t.Parallel() exp := new(Dirstore) got := FromContext(NewContext(context.Background(), exp)) assert.Same(t, exp, got) } func TestFromContextPanics(t *testing.T) { t.Parallel() assert.Panics(t, func() { FromContext(context.Background()) }) } crypto-0.57.0/tpm/storage/types.go000066400000000000000000000072611474156331600171020ustar00rootroot00000000000000package storage import ( "crypto/x509" "encoding/json" "fmt" "time" ) // AK is the type used to store AKs. type AK struct { Name string Data []byte Chain []*x509.Certificate CreatedAt time.Time } // MarshalJSON marshals the AK into JSON. func (ak *AK) MarshalJSON() ([]byte, error) { chain := make([][]byte, len(ak.Chain)) for i, cert := range ak.Chain { chain[i] = cert.Raw } sak := serializedAK{ Name: ak.Name, Type: typeAK, Data: ak.Data, CreatedAt: ak.CreatedAt, } if len(chain) > 0 { sak.Chain = chain } return json.Marshal(sak) } // UnmarshalJSON unmarshals `data` into an AK. func (ak *AK) UnmarshalJSON(data []byte) error { sak := &serializedAK{} if err := json.Unmarshal(data, sak); err != nil { return fmt.Errorf("failed unmarshaling serialized AK: %w", err) } if sak.Type != typeAK { return fmt.Errorf("unexpected serialized data type %q", sak.Type) } ak.Name = sak.Name ak.Data = sak.Data ak.CreatedAt = sak.CreatedAt if len(sak.Chain) > 0 { chain := make([]*x509.Certificate, len(sak.Chain)) for i, certBytes := range sak.Chain { cert, err := x509.ParseCertificate(certBytes) if err != nil { return fmt.Errorf("failed parsing certificate: %w", err) } chain[i] = cert } ak.Chain = chain } return nil } // Key is the type used to store Keys. type Key struct { Name string Data []byte AttestedBy string Chain []*x509.Certificate CreatedAt time.Time } // MarshalJSON marshals the Key into JSON. func (key *Key) MarshalJSON() ([]byte, error) { chain := make([][]byte, len(key.Chain)) for i, cert := range key.Chain { chain[i] = cert.Raw } sk := serializedKey{ Name: key.Name, Type: typeKey, Data: key.Data, AttestedBy: key.AttestedBy, CreatedAt: key.CreatedAt, } if len(chain) > 0 { sk.Chain = chain } return json.Marshal(sk) } // UnmarshalJSON unmarshals `data` into a Key. func (key *Key) UnmarshalJSON(data []byte) error { sk := &serializedKey{} if err := json.Unmarshal(data, sk); err != nil { return fmt.Errorf("failed unmarshaling serialized key: %w", err) } if sk.Type != typeKey { return fmt.Errorf("unexpected serialized data type %q", sk.Type) } key.Name = sk.Name key.Data = sk.Data key.AttestedBy = sk.AttestedBy key.CreatedAt = sk.CreatedAt if len(sk.Chain) > 0 { chain := make([]*x509.Certificate, len(sk.Chain)) for i, certBytes := range sk.Chain { cert, err := x509.ParseCertificate(certBytes) if err != nil { return fmt.Errorf("failed parsing certificate: %w", err) } chain[i] = cert } key.Chain = chain } return nil } const ( akPrefix = "ak-" keyPrefix = "key-" ) type tpmObjectType string const ( typeAK tpmObjectType = "AK" typeKey tpmObjectType = "KEY" ) // serializedAK is the struct used when marshaling // a storage AK to JSON. type serializedAK struct { Name string `json:"name"` Type tpmObjectType `json:"type"` Data []byte `json:"data"` Chain [][]byte `json:"chain"` CreatedAt time.Time `json:"createdAt"` } // serializedKey is the struct used when marshaling // a storage Key to JSON. type serializedKey struct { Name string `json:"name"` Type tpmObjectType `json:"type"` Data []byte `json:"data"` AttestedBy string `json:"attestedBy"` Chain [][]byte `json:"chain"` CreatedAt time.Time `json:"createdAt"` } // keyForAK returns the key to use when storing an AK. func keyForAK(name string) string { return fmt.Sprintf("%s%s", akPrefix, name) } // keyForKey returns the key to use when storing a Key. func keyForKey(name string) string { return fmt.Sprintf("%s%s", keyPrefix, name) } crypto-0.57.0/tpm/storage/types_test.go000066400000000000000000000032351474156331600201360ustar00rootroot00000000000000package storage import ( "crypto/x509" "encoding/json" "testing" "time" "github.com/stretchr/testify/require" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/minica" "go.step.sm/crypto/x509util" ) func TestAK_MarshalUnmarshal(t *testing.T) { ca, err := minica.New() require.NoError(t, err) signer, err := keyutil.GenerateSigner("RSA", "", 2048) require.NoError(t, err) cr, err := x509util.NewCertificateRequest(signer) require.NoError(t, err) cr.Subject.CommonName = "testak" csr, err := cr.GetCertificateRequest() require.NoError(t, err) cert, err := ca.SignCSR(csr) require.NoError(t, err) ak := &AK{ Name: "ak1", Data: []byte{1, 2, 3, 4}, Chain: []*x509.Certificate{cert, ca.Intermediate}, CreatedAt: time.Time{}, } data, err := json.Marshal(ak) require.NoError(t, err) var rak = &AK{} err = json.Unmarshal(data, rak) require.NoError(t, err) require.Equal(t, ak, rak) } func TestKey_MarshalUnmarshal(t *testing.T) { ca, err := minica.New() require.NoError(t, err) signer, err := keyutil.GenerateSigner("RSA", "", 2048) require.NoError(t, err) cr, err := x509util.NewCertificateRequest(signer) require.NoError(t, err) cr.Subject.CommonName = "testkey" csr, err := cr.GetCertificateRequest() require.NoError(t, err) cert, err := ca.SignCSR(csr) require.NoError(t, err) key := &Key{ Name: "key1", Data: []byte{1, 2, 3, 4}, AttestedBy: "ak1", Chain: []*x509.Certificate{cert, ca.Intermediate}, CreatedAt: time.Time{}, } data, err := json.Marshal(key) require.NoError(t, err) var rkey = &Key{} err = json.Unmarshal(data, rkey) require.NoError(t, err) require.Equal(t, key, rkey) } crypto-0.57.0/tpm/tpm.go000066400000000000000000000255461474156331600151000ustar00rootroot00000000000000package tpm import ( "context" "errors" "fmt" "io" "net/http" "sync" "github.com/smallstep/go-attestation/attest" closer "go.step.sm/crypto/tpm/internal/close" "go.step.sm/crypto/tpm/internal/open" "go.step.sm/crypto/tpm/internal/socket" "go.step.sm/crypto/tpm/simulator" "go.step.sm/crypto/tpm/storage" ) // TPM models a Trusted Platform Module. It provides an abstraction // over the google/go-tpm and google/go-attestation packages, allowing // functionalities of these packages to be performed in a uniform manner. // Besides that, it provides a transparent method for persisting TPM // objects, so that referencing and using these is simplified. type TPM struct { deviceName string attestConfig *attest.OpenConfig attestTPM *attest.TPM rwc io.ReadWriteCloser lock sync.RWMutex store storage.TPMStore simulator simulator.Simulator commandChannel CommandChannel downloader *downloader options *options initCommandChannelOnce sync.Once info *Info caps *Capabilities eks []*EK } // NewTPMOption is used to provide options when instantiating a new // instance of TPM. type NewTPMOption func(o *options) error // WithDeviceName is used to provide the `name` or path to the TPM // device. func WithDeviceName(name string) NewTPMOption { return func(o *options) error { if name != "" { o.deviceName = name } return nil } } // WithStore is used to set the TPMStore implementation to use for // persisting TPM objects, including AKs and Keys. func WithStore(store storage.TPMStore) NewTPMOption { return func(o *options) error { if store == nil { store = storage.BlackHole() // prevent nil storage; no persistence } o.store = store return nil } } // WithDisableDownload disables EK certificates from being downloaded // from online hosts. func WithDisableDownload() NewTPMOption { return func(o *options) error { o.downloader.enabled = false return nil } } // WithSimulator is used to configure a TPM simulator implementation // that simulates TPM operations instead of interacting with an actual // TPM. func WithSimulator(sim simulator.Simulator) NewTPMOption { return func(o *options) error { o.simulator = sim return nil } } type CommandChannel attest.CommandChannelTPM20 // WithCommandChannel is used to configure a [CommandChannel] as the // interface to interact with instead of with an actual TPM. func WithCommandChannel(commandChannel CommandChannel) NewTPMOption { return func(o *options) error { o.commandChannel = commandChannel return nil } } // WithCapabilities explicitly sets the capabilities rather // than acquiring them from the TPM directly. The primary use // for this option is to ease testing different TPM capabilities. // // # Experimental // // Notice: This option is EXPERIMENTAL and may be changed or removed // in a later release. func WithCapabilities(caps *Capabilities) NewTPMOption { return func(o *options) error { o.caps = caps return nil } } type options struct { deviceName string attestConfig *attest.OpenConfig simulator simulator.Simulator commandChannel CommandChannel store storage.TPMStore downloader *downloader caps *Capabilities } func (o *options) validate() error { if o.simulator != nil && o.commandChannel != nil { return errors.New("WithSimulator and WithCommandChannel options are mutually exclusive") } return nil } // New creates a new TPM instance. It takes `opts` to configure // the instance. By default it uses a blackhole storage, meaning // that there's no actual persistence. Some operations require // an actual persistence mechanism, and will return an error if // none is configured. func New(opts ...NewTPMOption) (*TPM, error) { tpmOptions := options{ attestConfig: &attest.OpenConfig{TPMVersion: attest.TPMVersion20}, // default configuration for TPM attestation use cases store: storage.BlackHole(), // default storage doesn't persist anything // TODO(hs): make this in-memory storage instead? downloader: &downloader{enabled: true, maxDownloads: 10, client: http.DefaultClient}, // EK certificate download (if required) is enabled by default } for _, o := range opts { if err := o(&tpmOptions); err != nil { return nil, err } } if err := tpmOptions.validate(); err != nil { return nil, fmt.Errorf("invalid TPM options provided: %w", err) } return &TPM{ deviceName: tpmOptions.deviceName, attestConfig: tpmOptions.attestConfig, store: tpmOptions.store, downloader: tpmOptions.downloader, simulator: tpmOptions.simulator, commandChannel: tpmOptions.commandChannel, caps: tpmOptions.caps, options: &tpmOptions, }, nil } // Open readies the TPM for usage and marks it as being // in use. This makes using the instance safe for // concurrent use. func (t *TPM) open(ctx context.Context) (err error) { // prevent opening the TPM multiple times if Open is called // within the package multiple times. if isInternalCall(ctx) { return } // lock the TPM instance; it's in use now t.lock.Lock() defer func() { if err != nil { t.lock.Unlock() } }() if err := t.store.Load(); err != nil { // TODO(hs): load this once? Or abstract this away. return fmt.Errorf("failed loading from TPM storage: %w", err) } // initialize the command channel t.initCommandChannelOnce.Do(func() { err = t.initializeCommandChannel() }) if err != nil { return fmt.Errorf("failed initializing command channel: %w", err) } // if a simulator was set, use it as the backing TPM device. // The simulator is currently only used for testing. if t.simulator != nil { if t.attestTPM == nil { at, err := attest.OpenTPM(t.attestConfig) if err != nil { return fmt.Errorf("failed opening attest.TPM: %w", err) } t.attestTPM = at } t.rwc = t.simulator } else { // TODO(hs): when an internal call to open is performed, but when // switching the "TPM implementation" to use between the two types, // there's a possibility of a nil pointer exception. At the moment, // the only "go-tpm" call is for GetRandom(), but this could change // in the future. if isGoTPMCall(ctx) { rwc, err := open.TPM(t.deviceName) if err != nil { return fmt.Errorf("failed opening TPM: %w", err) } t.rwc = rwc } else { // TODO(hs): attest.OpenTPM doesn't currently take into account the // device name provided. This doesn't seem to be an available option // to filter on currently? at, err := attest.OpenTPM(t.attestConfig) if err != nil { return fmt.Errorf("failed opening TPM: %w", err) } t.attestTPM = at } } return nil } // initializeCommandChannel initializes the TPM's command channel based on // configuration provided when creating the TPM instance. The method is // primarily used to be able to use a TPM simulator in lieu of a real TPM // being available or when the real TPM should not be used. There's a special // case for a TPM exposed using a UNIX socket, which also is used primarily // for interacting with a TPM simulator. func (t *TPM) initializeCommandChannel() error { // return early with the complete `attestConfig` set if // command channel was provided before. if t.commandChannel != nil { t.attestConfig = &attest.OpenConfig{ TPMVersion: t.options.attestConfig.TPMVersion, CommandChannel: t.commandChannel, } return nil } if t.options.simulator != nil { t.commandChannel = t.simulator } if t.options.commandChannel != nil { t.commandChannel = t.options.commandChannel } // finally, check if the device name points to a UNIX socket, and use that // as the command channel, if available. if t.commandChannel == nil { if socketCommandChannel, err := trySocketCommandChannel(t.deviceName); err != nil { switch { case errors.Is(err, socket.ErrNotSupported): // don't try to use socket command channel if not supported. No need to return // an error, because the code should still rely on the default TPM command channel. case !errors.Is(err, socket.ErrNotAvailable): return err } } else { t.commandChannel = socketCommandChannel } } // update `attestConfig` with the command channel, so that it is used whenever // attestation operations are being performed. Note that the command channel can // still be nil. It simply won't be used (wrapped) by `go-attestation` in that case. t.attestConfig = &attest.OpenConfig{ TPMVersion: t.options.attestConfig.TPMVersion, CommandChannel: t.commandChannel, } return nil } // trySocketCommandChannel tries func trySocketCommandChannel(path string) (*socket.CommandChannelWithoutMeasurementLog, error) { rwc, err := socket.New(path) if err != nil { return nil, err } return &socket.CommandChannelWithoutMeasurementLog{ReadWriteCloser: rwc}, nil } // Close closes the TPM instance, cleaning up resources and // marking it ready to be use again. func (t *TPM) close(ctx context.Context) error { // prevent closing the TPM multiple times if Open is called // within the package multiple times. if isInternalCall(ctx) { return nil } // if simulation is enabled, closing the TPM simulator must not // happen, because re-opening it will result in a different instance, // resulting in issues when running multiple test operations in // sequence. Closing a simulator has to be done in the calling code, // meaning it has to happen at the end of the test. if t.simulator != nil { t.lock.Unlock() return nil // return early, so that simulator remains usable. } // mark the TPM as ready to be used again when returning defer t.lock.Unlock() // clean up the attest.TPM if t.attestTPM != nil { defer func() { t.attestTPM = nil }() if err := closer.AttestTPM(t.attestTPM, t.attestConfig); err != nil { return fmt.Errorf("failed closing attest.TPM: %w", err) } } // clean up the go-tpm rwc if t.rwc != nil { defer func() { t.rwc = nil }() if err := closer.RWC(t.rwc); err != nil { return fmt.Errorf("failed closing rwc: %w", err) } } return nil } func (t *TPM) Available() (err error) { _, err = t.Info(context.Background()) return } type validatableConfig interface { Validate() error } func (t *TPM) validate(config validatableConfig) error { return config.Validate() } // closeTPM closes TPM `t`. It must be called as a deferred function // every time TPM `t` is opened. If `ep` is nil and closing the TPM // returned an error, `ep` will be pointed to the latter. In practice // this means that errors originating from main-line logic will have // precedence over errors returned from closing the TPM. func closeTPM(ctx context.Context, t *TPM, ep *error) { //nolint:gocritic // pointer to error required to be able to point it to an error if err := t.close(ctx); err != nil && *ep == nil { *ep = err } } crypto-0.57.0/tpm/tpm_simulator_test.go000066400000000000000000000734471474156331600202410ustar00rootroot00000000000000//go:build tpmsimulator // +build tpmsimulator package tpm import ( "context" "crypto" "crypto/ecdsa" "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/base64" "encoding/binary" "errors" "fmt" "io" "math" "strings" "testing" "github.com/smallstep/go-attestation/attest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/minica" "go.step.sm/crypto/tpm/algorithm" "go.step.sm/crypto/tpm/simulator" "go.step.sm/crypto/tpm/storage" "go.step.sm/crypto/tpm/tss2" "go.step.sm/crypto/x509util" ) func newSimulatedTPM(t *testing.T) *TPM { t.Helper() tmpDir := t.TempDir() tpm, err := New(withSimulator(t), WithStore(storage.NewDirstore(tmpDir))) // TODO: provide in-memory storage implementation instead require.NoError(t, err) return tpm } func withSimulator(t *testing.T) NewTPMOption { t.Helper() var sim simulator.Simulator t.Cleanup(func() { if sim == nil { return } err := sim.Close() require.NoError(t, err) }) sim, err := simulator.New() require.NoError(t, err) err = sim.Open() require.NoError(t, err) return WithSimulator(sim) } func TestTPM_Info(t *testing.T) { tpm := newSimulatedTPM(t) info, err := tpm.Info(context.Background()) require.NoError(t, err) // expected TPM info for the Microsoft TPM simulator expected := &Info{ Version: Version(2), Interface: Interface(3), Manufacturer: GetManufacturerByID(1297303124), VendorInfo: "xCG fTPM", FirmwareVersion: FirmwareVersion{ Major: 8215, Minor: 1561, }, } require.Equal(t, expected, info) } func TestTPM_GetCapabilities(t *testing.T) { tpm := newSimulatedTPM(t) info, err := tpm.GetCapabilities(context.Background()) require.NoError(t, err) // expected TPM capabilities for the Microsoft TPM simulator expected := &Capabilities{ Algorithms: []algorithm.Algorithm{ algorithm.AlgorithmRSA, algorithm.AlgorithmSHA1, algorithm.AlgorithmHMAC, algorithm.AlgorithmAES, algorithm.AlgorithmMGF1, algorithm.AlgorithmKeyedHash, algorithm.AlgorithmXOR, algorithm.AlgorithmSHA256, algorithm.AlgorithmSHA384, algorithm.AlgorithmSHA512, algorithm.AlgorithmRSASSA, algorithm.AlgorithmRSAES, algorithm.AlgorithmRSAPSS, algorithm.AlgorithmOAEP, algorithm.AlgorithmECDSA, algorithm.AlgorithmECDH, algorithm.AlgorithmECDAA, algorithm.AlgorithmECSchnorr, algorithm.AlgorithmKDF1_56A, algorithm.AlgorithmKDF1_108, algorithm.AlgorithmECC, algorithm.AlgorithmSymCipher, algorithm.AlgorithmCMAC, algorithm.AlgorithmCTR, algorithm.AlgorithmOFB, algorithm.AlgorithmCBC, algorithm.AlgorithmCFB, algorithm.AlgorithmECB, }, } require.Equal(t, expected, info) } func TestTPM_GenerateRandom(t *testing.T) { tpm := newSimulatedTPM(t) b, err := tpm.GenerateRandom(context.Background(), 16) require.NoError(t, err) require.Len(t, b, 16) b, err = tpm.GenerateRandom(context.Background(), 10) require.NoError(t, err) require.Len(t, b, 10) } func newErrorTPM(t *testing.T) *TPM { t.Helper() tmpDir := t.TempDir() tpm, err := New(withWriteErrorSimulator(t), WithStore(storage.NewDirstore(tmpDir))) // TODO: provide in-memory storage implementation instead require.NoError(t, err) return tpm } func withWriteErrorSimulator(t *testing.T) NewTPMOption { t.Helper() var sim simulator.Simulator t.Cleanup(func() { if sim == nil { return } err := sim.Close() require.NoError(t, err) }) sim = &writeErrorSimulator{} err := sim.Open() require.NoError(t, err) return WithSimulator(sim) } type writeErrorSimulator struct { } func (s *writeErrorSimulator) Open() error { return nil } func (s *writeErrorSimulator) Close() error { return nil } func (s *writeErrorSimulator) Read([]byte) (int, error) { return -1, nil } func (s *writeErrorSimulator) Write([]byte) (int, error) { return 0, errors.New("forced write error") // writing command fails } func (s *writeErrorSimulator) MeasurementLog() ([]byte, error) { return nil, nil } var _ io.ReadWriteCloser = (*writeErrorSimulator)(nil) func Test_generator_Read(t *testing.T) { tpm := newSimulatedTPM(t) errorTPM := newErrorTPM(t) type fields struct { t *TPM } type args struct { data []byte } short := make([]byte, 8) long := make([]byte, 32) tooLongForSimulator := make([]byte, 256) // I've observed the simulator to return 64 at most in one go; we loop through it, so we can get more than 64 random bytes maximumLength := make([]byte, math.MaxUint16) longerThanMax := make([]byte, math.MaxUint16+1) readError := make([]byte, 32) tests := []struct { name string fields fields args args want int expErr error }{ {"ok/short", fields{tpm}, args{data: short}, 8, nil}, {"ok/long", fields{tpm}, args{data: long}, 32, nil}, {"ok/tooLongForSimulator", fields{tpm}, args{data: tooLongForSimulator}, 256, nil}, {"ok/max", fields{tpm}, args{data: maximumLength}, math.MaxUint16, nil}, {"ok/readError", fields{errorTPM}, args{data: readError}, 0, nil}, {"ok/longerThanMax", fields{tpm}, args{data: longerThanMax}, math.MaxUint16, nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g, err := tt.fields.t.RandomReader() require.NoError(t, err) got, err := g.Read(tt.args.data) if tt.expErr != nil { assert.EqualError(t, err, tt.expErr.Error()) assert.Equal(t, 0, got) return } assert.NoError(t, err) assert.Equal(t, tt.want, got) // for the test cases that use the errorTPM, check that trying // to read (again) from the same generator fails with the previous // error. if tt.fields.t == errorTPM { newShort := make([]byte, 8) n, err := g.Read(newShort) assert.Zero(t, n) assert.EqualError(t, err, "failed generating random bytes in previous call to Read: failed generating random data: forced write error: EOF") assert.ErrorIs(t, err, io.EOF) } }) } } func TestTPM_GetEKs(t *testing.T) { tpm := newSimulatedTPM(t) eks, err := tpm.GetEKs(context.Background()) require.NoError(t, err) require.Len(t, eks, 1) require.IsType(t, &rsa.PublicKey{}, eks[0].Public()) require.Nil(t, eks[0].Certificate()) require.Equal(t, "", eks[0].CertificateURL()) fp, err := eks[0].Fingerprint() require.NoError(t, err) b, err := base64.StdEncoding.DecodeString(strings.Split(fp, ":")[1]) require.NoError(t, err) require.Len(t, b, 32) } func TestTPM_CreateAK(t *testing.T) { tpm := newSimulatedTPM(t) ak, err := tpm.CreateAK(context.Background(), "first-ak") require.NoError(t, err) require.Equal(t, "first-ak", ak.Name()) require.NotEqual(t, 0, len(ak.Data())) require.Same(t, tpm, ak.tpm) } func TestTPM_GetAK(t *testing.T) { tpm := newSimulatedTPM(t) ak, err := tpm.CreateAK(context.Background(), "") require.NoError(t, err) require.NotNil(t, ak) require.Same(t, tpm, ak.tpm) r, err := tpm.GetAK(context.Background(), ak.Name()) require.NoError(t, err) require.Equal(t, ak.Name(), r.Name()) require.Same(t, tpm, r.tpm) require.Equal(t, ak.CreatedAt(), r.CreatedAt()) require.Equal(t, ak.Data(), r.Data()) r, err = tpm.GetAK(context.Background(), "non-existing-key") require.EqualError(t, err, `failed getting AK "non-existing-key": not found`) require.Nil(t, r) } func TestTPM_ListAKs(t *testing.T) { tpm := newSimulatedTPM(t) ak1, err := tpm.CreateAK(context.Background(), "") require.NoError(t, err) require.NotNil(t, ak1) require.Same(t, tpm, ak1.tpm) ak2, err := tpm.CreateAK(context.Background(), "") require.NoError(t, err) require.NotNil(t, ak2) require.Same(t, tpm, ak2.tpm) ak3, err := tpm.CreateAK(context.Background(), "") require.NoError(t, err) require.NotNil(t, ak3) require.Same(t, tpm, ak3.tpm) aks, err := tpm.ListAKs(context.Background()) require.NoError(t, err) require.Len(t, aks, 3) for _, ak := range aks { require.Same(t, tpm, ak.tpm) } } func TestTPM_DeleteAK(t *testing.T) { tpm := newSimulatedTPM(t) ak, err := tpm.CreateAK(context.Background(), "first-ak") require.NoError(t, err) require.NotNil(t, ak) require.Same(t, tpm, ak.tpm) err = tpm.DeleteAK(context.Background(), "first-ak") require.NoError(t, err) ak, err = tpm.CreateAK(context.Background(), "second-ak") require.NoError(t, err) require.NotNil(t, ak) config := AttestKeyConfig{ Algorithm: "RSA", Size: 2048, } key, err := tpm.AttestKey(context.Background(), "second-ak", "first-key", config) require.NoError(t, err) require.NotNil(t, key) err = tpm.DeleteAK(context.Background(), "second-ak") require.EqualError(t, err, `failed deleting AK "second-ak" because 1 key(s) exist that were attested by it`) err = tpm.DeleteAK(context.Background(), "non-existing-ak") require.EqualError(t, err, `failed getting AK "non-existing-ak": not found`) } func TestAK_AttestationParameters(t *testing.T) { tpm := newSimulatedTPM(t) ak, err := tpm.CreateAK(context.Background(), "first-ak") require.NoError(t, err) require.NotNil(t, ak) require.Same(t, tpm, ak.tpm) params, err := ak.AttestationParameters(context.Background()) require.NoError(t, err) require.NotNil(t, params) } func TestAK_ActivateCredential(t *testing.T) { tpm := newSimulatedTPM(t) ak, err := tpm.CreateAK(context.Background(), "first-ak") require.NoError(t, err) require.NotNil(t, ak) require.Same(t, tpm, ak.tpm) eks, err := tpm.GetEKs(context.Background()) require.NoError(t, err) require.Len(t, eks, 1) params, err := ak.AttestationParameters(context.Background()) require.NoError(t, err) require.NotNil(t, params) // prepare parameters for activation as performed by an attestor activation := attest.ActivationParameters{ TPMVersion: attest.TPMVersion20, EK: eks[0].Public(), AK: params, } // generate the encrypted challenge for the TPM expectedSecret, encryptedCredentials, err := activation.Generate() require.NoError(t, err) // activate the credential and verify secret is equal to attestor's value secret, err := ak.ActivateCredential(context.Background(), EncryptedCredential(*encryptedCredentials)) require.NoError(t, err) require.Equal(t, expectedSecret, secret) } func TestAK_Blobs(t *testing.T) { tpm := newSimulatedTPM(t) ak, err := tpm.CreateAK(context.Background(), "first-ak") require.NoError(t, err) require.NotNil(t, ak) require.Same(t, tpm, ak.tpm) blobs, err := ak.Blobs(context.Background()) require.NoError(t, err) require.NotNil(t, blobs) // check private bytes and its (encoded) length private, err := blobs.Private() require.NoError(t, err) require.NotEmpty(t, private) size := binary.BigEndian.Uint16(private[0:2]) require.Len(t, private, int(size)+2) // check public bytes and its (encoded) length public, err := blobs.Public() require.NoError(t, err) require.NotEmpty(t, public) size = binary.BigEndian.Uint16(public[0:2]) require.Len(t, public, int(size)+2) } func TestAK_ToTSS2(t *testing.T) { ctx := context.Background() tpm := newSimulatedTPM(t) ak, err := tpm.CreateAK(ctx, "first-ak") require.NoError(t, err) blobs, err := ak.Blobs(ctx) require.NoError(t, err) tss2Key, err := ak.ToTSS2(ctx) require.NoError(t, err) assert.Equal(t, blobs.public, tss2Key.PublicKey[2:]) assert.Equal(t, blobs.private, tss2Key.PrivateKey[2:]) assert.Equal(t, len(blobs.public), int(binary.BigEndian.Uint16(tss2Key.PublicKey[:2]))) assert.Equal(t, len(blobs.private), int(binary.BigEndian.Uint16(tss2Key.PrivateKey[:2]))) assert.Equal(t, 0x81000001, tss2Key.Parent) akPub := ak.Public() tssPub, err := tss2Key.Public() require.NoError(t, err) assert.Equal(t, akPub, tssPub) } func TestAK_Public(t *testing.T) { tpm := newSimulatedTPM(t) ak, err := tpm.CreateAK(context.Background(), "first-ak") require.NoError(t, err) require.NotNil(t, ak) require.Same(t, tpm, ak.tpm) akPub := ak.Public() require.NoError(t, err) require.NotNil(t, akPub) require.Implements(t, (*crypto.PublicKey)(nil), ak) _, ok := akPub.(crypto.Signer) require.False(t, ok) newAK := &AK{ tpm: tpm, name: "second-ak", // non-existent AK; results in error } newAKPub := newAK.Public() require.Nil(t, newAKPub) } func TestAK_CertificateOperations(t *testing.T) { tpm := newSimulatedTPM(t) ak, err := tpm.CreateAK(context.Background(), "first-ak") require.NoError(t, err) require.NotNil(t, ak) require.Same(t, tpm, ak.tpm) akPub := ak.Public() require.NoError(t, err) require.NotNil(t, akPub) ca, err := minica.New( minica.WithGetSignerFunc( func() (crypto.Signer, error) { return keyutil.GenerateSigner("RSA", "", 2048) }, ), ) require.NoError(t, err) template := &x509.Certificate{ Subject: pkix.Name{ CommonName: "testkey", }, PublicKey: akPub, } cert, err := ca.Sign(template) require.NoError(t, err) require.NotNil(t, cert) akCert := ak.Certificate() require.Nil(t, akCert) akChain := ak.CertificateChain() require.Empty(t, akChain) chain := []*x509.Certificate{cert, ca.Intermediate} err = ak.SetCertificateChain(context.TODO(), chain) require.NoError(t, err) akCert = ak.Certificate() require.Equal(t, cert, akCert) akChain = ak.CertificateChain() require.Equal(t, chain, akChain) } func TestTPM_CreateKey(t *testing.T) { tpm := newSimulatedTPM(t) config := CreateKeyConfig{ Algorithm: "RSA", Size: 2048, } key, err := tpm.CreateKey(context.Background(), "first-key", config) require.NoError(t, err) require.Equal(t, "first-key", key.Name()) require.Equal(t, "", key.AttestedBy()) require.NotEqual(t, 0, len(key.Data())) require.Same(t, tpm, key.tpm) require.False(t, key.WasAttested()) config = CreateKeyConfig{ Algorithm: "RSA", Size: 1024, } key, err = tpm.CreateKey(context.Background(), "1024", config) require.NoError(t, err) config = CreateKeyConfig{ Algorithm: "RSA", Size: 3072, } key, err = tpm.CreateKey(context.Background(), "3072", config) assert.EqualError(t, err, "invalid key creation parameters: 3072 bits RSA keys are (currently) not supported in go.step.sm/crypto; maximum is 2048") assert.Nil(t, key) config = CreateKeyConfig{ Algorithm: "RSA", Size: 4096, } key, err = tpm.CreateKey(context.Background(), "4096", config) assert.EqualError(t, err, "invalid key creation parameters: 4096 bits RSA keys are (currently) not supported in go.step.sm/crypto; maximum is 2048") assert.Nil(t, key) } func TestTPM_AttestKey(t *testing.T) { tpm := newSimulatedTPM(t) ak, err := tpm.CreateAK(context.Background(), "first-ak") require.NoError(t, err) require.NotNil(t, ak) require.Same(t, tpm, ak.tpm) config := AttestKeyConfig{ Algorithm: "RSA", Size: 2048, } key, err := tpm.AttestKey(context.Background(), "first-ak", "first-key", config) require.NoError(t, err) require.NotNil(t, key) require.Equal(t, "first-key", key.Name()) require.NotEqual(t, 0, len(key.Data())) require.Equal(t, "first-ak", key.AttestedBy()) require.Same(t, tpm, key.tpm) require.True(t, key.WasAttested()) require.True(t, key.WasAttestedBy(ak)) config = AttestKeyConfig{ Algorithm: "RSA", Size: 3072, } key, err = tpm.AttestKey(context.Background(), "first-ak", "3072", config) assert.EqualError(t, err, "invalid key attestation parameters: 3072 bits RSA keys are (currently) not supported in go.step.sm/crypto; maximum is 2048") assert.Nil(t, key) } func TestTPM_GetKey(t *testing.T) { tpm := newSimulatedTPM(t) config := CreateKeyConfig{ Algorithm: "RSA", Size: 2048, } key, err := tpm.CreateKey(context.Background(), "first-key", config) require.NoError(t, err) require.NotNil(t, key) require.Equal(t, "", key.AttestedBy()) require.Same(t, tpm, key.tpm) r, err := tpm.GetKey(context.Background(), key.Name()) require.NoError(t, err) require.Equal(t, key.Name(), r.Name()) require.Same(t, tpm, r.tpm) require.Equal(t, key.CreatedAt(), r.CreatedAt()) require.Equal(t, key.Data(), r.Data()) require.Equal(t, "", r.AttestedBy()) r, err = tpm.GetKey(context.Background(), "non-existing-key") require.EqualError(t, err, `failed getting key "non-existing-key": not found`) require.Nil(t, r) } func TestTPM_GetKeys(t *testing.T) { tpm := newSimulatedTPM(t) config := CreateKeyConfig{ Algorithm: "RSA", Size: 2048, } key1, err := tpm.CreateKey(context.Background(), "", config) require.NoError(t, err) require.NotNil(t, key1) require.Equal(t, "", key1.AttestedBy()) require.Same(t, tpm, key1.tpm) key2, err := tpm.CreateKey(context.Background(), "", config) require.NoError(t, err) require.NotNil(t, key2) require.Equal(t, "", key2.AttestedBy()) require.Same(t, tpm, key2.tpm) key3, err := tpm.CreateKey(context.Background(), "", config) require.NoError(t, err) require.NotNil(t, key3) require.Equal(t, "", key3.AttestedBy()) require.Same(t, tpm, key3.tpm) keys, err := tpm.ListKeys(context.Background()) require.NoError(t, err) require.Len(t, keys, 3) for _, key := range keys { require.NotEqual(t, 0, len(key.Data())) require.Same(t, tpm, key.tpm) } } func TestTPM_DeleteKey(t *testing.T) { tpm := newSimulatedTPM(t) config := CreateKeyConfig{ Algorithm: "RSA", Size: 2048, } key, err := tpm.CreateKey(context.Background(), "first-key", config) require.NoError(t, err) require.NotNil(t, key) require.Equal(t, "", key.AttestedBy()) require.Same(t, tpm, key.tpm) err = tpm.DeleteKey(context.Background(), "first-key") require.NoError(t, err) err = tpm.DeleteKey(context.Background(), "non-existing-key") require.EqualError(t, err, `failed getting key "non-existing-key": not found`) } func TestKey_CertificationParameters(t *testing.T) { tpm := newSimulatedTPM(t) ak, err := tpm.CreateAK(context.Background(), "first-ak") require.NoError(t, err) require.NotNil(t, ak) require.Same(t, tpm, ak.tpm) config := AttestKeyConfig{ Algorithm: "RSA", Size: 2048, } key, err := tpm.AttestKey(context.Background(), "first-ak", "first-key", config) require.NoError(t, err) require.NotNil(t, key) require.Equal(t, "first-key", key.Name()) require.NotEqual(t, 0, len(key.Data())) require.Equal(t, "first-ak", key.AttestedBy()) require.Same(t, tpm, key.tpm) require.True(t, key.WasAttested()) require.True(t, key.WasAttestedBy(ak)) params, err := key.CertificationParameters(context.Background()) require.NoError(t, err) require.NotNil(t, params) require.NotEqual(t, 0, len(params.CreateAttestation)) require.NotEqual(t, 0, len(params.CreateSignature)) akParams, err := ak.AttestationParameters(context.Background()) require.NoError(t, err) require.NotNil(t, akParams) akPublic, err := attest.ParseAKPublic(attest.TPMVersion20, akParams.Public) require.NoError(t, err) require.NotNil(t, akPublic) opts := attest.VerifyOpts{ Public: akPublic.Public, Hash: akPublic.Hash, } err = params.Verify(opts) require.NoError(t, err) } func TestKey_Blobs(t *testing.T) { tpm := newSimulatedTPM(t) config := CreateKeyConfig{ Algorithm: "RSA", Size: 2048, } key, err := tpm.CreateKey(context.Background(), "first-key", config) require.NoError(t, err) require.NotNil(t, key) require.Equal(t, "", key.AttestedBy()) require.Same(t, tpm, key.tpm) blobs, err := key.Blobs(context.Background()) require.NoError(t, err) require.NotNil(t, blobs) // check private bytes and its (encoded) length private, err := blobs.Private() require.NoError(t, err) require.NotEmpty(t, private) size := binary.BigEndian.Uint16(private[0:2]) require.Len(t, private, int(size)+2) // check public bytes and its (encoded) length public, err := blobs.Public() require.NoError(t, err) require.NotEmpty(t, public) size = binary.BigEndian.Uint16(public[0:2]) require.Len(t, public, int(size)+2) } func TestKey_ToTSS2(t *testing.T) { ctx := context.Background() tpm := newSimulatedTPM(t) config := CreateKeyConfig{ Algorithm: "ECDSA", Size: 256, } key, err := tpm.CreateKey(ctx, "ecdsa-key", config) require.NoError(t, err) blobs, err := key.Blobs(ctx) require.NoError(t, err) tss2Key, err := key.ToTSS2(ctx) require.NoError(t, err) assert.Equal(t, blobs.public, tss2Key.PublicKey[2:]) assert.Equal(t, blobs.private, tss2Key.PrivateKey[2:]) assert.Equal(t, len(blobs.public), int(binary.BigEndian.Uint16(tss2Key.PublicKey[:2]))) assert.Equal(t, len(blobs.private), int(binary.BigEndian.Uint16(tss2Key.PrivateKey[:2]))) assert.Equal(t, 0x81000001, tss2Key.Parent) signer, err := key.Signer(ctx) require.NoError(t, err) tssPub, err := tss2Key.Public() require.NoError(t, err) assert.Equal(t, signer.Public(), tssPub) } func TestKey_ToTSS2_RSA(t *testing.T) { ctx := context.Background() tpm := newSimulatedTPM(t) config := CreateKeyConfig{ Algorithm: "RSA", Size: 2048, } key, err := tpm.CreateKey(ctx, "rsa-key", config) require.NoError(t, err) blobs, err := key.Blobs(ctx) require.NoError(t, err) tss2Key, err := key.ToTSS2(ctx) require.NoError(t, err) assert.Equal(t, blobs.public, tss2Key.PublicKey[2:]) assert.Equal(t, blobs.private, tss2Key.PrivateKey[2:]) assert.Equal(t, len(blobs.public), int(binary.BigEndian.Uint16(tss2Key.PublicKey[:2]))) assert.Equal(t, len(blobs.private), int(binary.BigEndian.Uint16(tss2Key.PrivateKey[:2]))) assert.Equal(t, 0x81000001, tss2Key.Parent) signer, err := key.Signer(ctx) require.NoError(t, err) tssPub, err := tss2Key.Public() require.NoError(t, err) assert.Equal(t, signer.Public(), tssPub) } func TestTPM_AttestKey_ToTSS2(t *testing.T) { tpm := newSimulatedTPM(t) ak, err := tpm.CreateAK(context.Background(), "first-ak") require.NoError(t, err) require.NotNil(t, ak) require.Same(t, tpm, ak.tpm) config := AttestKeyConfig{ Algorithm: "RSA", Size: 2048, } key, err := tpm.AttestKey(context.Background(), "first-ak", "first-key", config) require.NoError(t, err) require.NotNil(t, key) require.Equal(t, "first-key", key.Name()) require.NotEqual(t, 0, len(key.Data())) require.Equal(t, "first-ak", key.AttestedBy()) require.Same(t, tpm, key.tpm) require.True(t, key.WasAttested()) require.True(t, key.WasAttestedBy(ak)) ctx := context.Background() blobs, err := key.Blobs(ctx) require.NoError(t, err) tss2Key, err := key.ToTSS2(ctx) require.NoError(t, err) assert.Equal(t, blobs.public, tss2Key.PublicKey[2:]) assert.Equal(t, blobs.private, tss2Key.PrivateKey[2:]) assert.Equal(t, len(blobs.public), int(binary.BigEndian.Uint16(tss2Key.PublicKey[:2]))) assert.Equal(t, len(blobs.private), int(binary.BigEndian.Uint16(tss2Key.PrivateKey[:2]))) assert.Equal(t, 0x81000001, tss2Key.Parent) } func TestKey_SetCertificateChain(t *testing.T) { tpm := newSimulatedTPM(t) config := CreateKeyConfig{ Algorithm: "RSA", Size: 2048, } key, err := tpm.CreateKey(context.Background(), "first-key", config) require.NoError(t, err) require.NotNil(t, key) require.Equal(t, "", key.AttestedBy()) require.Same(t, tpm, key.tpm) ca, err := minica.New( minica.WithGetSignerFunc( func() (crypto.Signer, error) { return keyutil.GenerateSigner("RSA", "", 2048) }, ), ) require.NoError(t, err) signer, err := key.Signer(context.Background()) require.NoError(t, err) cr, err := x509util.NewCertificateRequest(signer) require.NoError(t, err) cr.Subject.CommonName = "testkey" csr, err := cr.GetCertificateRequest() require.NoError(t, err) cert, err := ca.SignCSR(csr) require.NoError(t, err) keyCert := key.Certificate() require.Nil(t, keyCert) keyChain := key.CertificateChain() require.Empty(t, keyChain) chain := []*x509.Certificate{cert, ca.Intermediate} err = key.SetCertificateChain(context.TODO(), chain) require.NoError(t, err) keyCert = key.Certificate() require.Equal(t, cert, keyCert) keyChain = key.CertificateChain() require.Equal(t, chain, keyChain) } func TestTPM_GetSigner(t *testing.T) { tpm := newSimulatedTPM(t) signer, err := tpm.GetSigner(context.Background(), "non-existing-key") require.EqualError(t, err, `failed getting signer for key "non-existing-key": not found`) require.Nil(t, signer) } func TestKey_Signer(t *testing.T) { tpm := newSimulatedTPM(t) config := CreateKeyConfig{ Algorithm: "RSA", Size: 2048, } key, err := tpm.CreateKey(context.Background(), "first-key", config) require.NoError(t, err) require.NotNil(t, key) require.Equal(t, "", key.AttestedBy()) require.Same(t, tpm, key.tpm) signer, err := key.Signer(context.Background()) require.NoError(t, err) require.NotNil(t, signer) } func Test_signer_Sign(t *testing.T) { tpm := newSimulatedTPM(t) config := CreateKeyConfig{ Algorithm: "RSA", Size: 2048, } key, err := tpm.CreateKey(context.Background(), "first-key", config) require.NoError(t, err) require.NotNil(t, key) require.Equal(t, "", key.AttestedBy()) require.Same(t, tpm, key.tpm) signer, err := key.Signer(context.Background()) require.NoError(t, err) require.NotNil(t, signer) pub, ok := signer.Public().(*rsa.PublicKey) require.True(t, ok) random := make([]byte, 32) n, err := rand.Read(random) require.NoError(t, err) require.Equal(t, 32, n) // PKCS #1 v1.5 signing signature, err := signer.Sign(rand.Reader, random, crypto.SHA256) require.NoError(t, err) require.NotNil(t, signature) err = rsa.VerifyPKCS1v15(pub, crypto.SHA256, random, signature) assert.NoError(t, err) // PSS signing for _, saltLength := range []int{rsa.PSSSaltLengthAuto, rsa.PSSSaltLengthEqualsHash, 32} { t.Run(fmt.Sprintf("saltLength: %d", saltLength), func(t *testing.T) { opts := &rsa.PSSOptions{ SaltLength: saltLength, Hash: crypto.SHA256, } signature, err := signer.Sign(rand.Reader, random, opts) require.NoError(t, err) assert.NoError(t, rsa.VerifyPSS(pub, crypto.SHA256, random, signature, opts)) assert.NoError(t, rsa.VerifyPSS(pub, crypto.SHA256, random, signature, nil)) }) } } func TestCreateTSS2Signer(t *testing.T) { ctx := context.Background() tpm := newSimulatedTPM(t) config := CreateKeyConfig{ Algorithm: "ECDSA", Size: 256, } key, err := tpm.CreateKey(ctx, "ecdsa-key", config) require.NoError(t, err) tss2Key, err := key.ToTSS2(ctx) require.NoError(t, err) type args struct { ctx context.Context key *tss2.TPMKey } tests := []struct { name string tpm *TPM args args assertion assert.ErrorAssertionFunc }{ {"ok", tpm, args{ctx, tss2Key}, assert.NoError}, {"fail createSigner", tpm, args{ctx, nil}, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := CreateTSS2Signer(tt.args.ctx, tt.tpm, tt.args.key) tt.assertion(t, err) }) } } func Test_tss2Signer_Sign_ECDSA(t *testing.T) { ctx := context.Background() tpm := newSimulatedTPM(t) config := CreateKeyConfig{ Algorithm: "ECDSA", Size: 256, } key, err := tpm.CreateKey(ctx, "ecdsa-key", config) require.NoError(t, err) tss2Key, err := key.ToTSS2(ctx) require.NoError(t, err) signer, err := CreateTSS2Signer(ctx, tpm, tss2Key) require.NoError(t, err) hash := crypto.SHA256.New() hash.Write([]byte("emplaced-Coraciiformes-thick-rusting-synarchy-adenocarcinomata-radiographically")) sum := hash.Sum(nil) sig, err := signer.Sign(rand.Reader, sum, crypto.SHA256) require.NoError(t, err) pub, ok := signer.Public().(*ecdsa.PublicKey) require.True(t, ok) assert.True(t, ecdsa.VerifyASN1(pub, sum, sig)) } func Test_tss2Signer_Sign_RSA(t *testing.T) { ctx := context.Background() tpm := newSimulatedTPM(t) config := CreateKeyConfig{ Algorithm: "RSA", Size: 2048, } key, err := tpm.CreateKey(ctx, "rsa-key", config) require.NoError(t, err) tss2Key, err := key.ToTSS2(ctx) require.NoError(t, err) signer, err := CreateTSS2Signer(ctx, tpm, tss2Key) require.NoError(t, err) hash := crypto.SHA256.New() hash.Write([]byte("emplaced-Coraciiformes-thick-rusting-synarchy-adenocarcinomata-radiographically")) sum := hash.Sum(nil) sig, err := signer.Sign(rand.Reader, sum, crypto.SHA256) require.NoError(t, err) pub, ok := signer.Public().(*rsa.PublicKey) require.True(t, ok) assert.NoError(t, rsa.VerifyPKCS1v15(pub, crypto.SHA256, sum, sig)) } func Test_tss2Signer_Sign_RSAPSS(t *testing.T) { ctx := context.Background() tpm := newSimulatedTPM(t) config := CreateKeyConfig{ Algorithm: "RSA", Size: 2048, } key, err := tpm.CreateKey(ctx, "rsa-key", config) require.NoError(t, err) tss2Key, err := key.ToTSS2(ctx) require.NoError(t, err) signer, err := CreateTSS2Signer(ctx, tpm, tss2Key) require.NoError(t, err) hash := crypto.SHA256.New() hash.Write([]byte("emplaced-Coraciiformes-thick-rusting-synarchy-adenocarcinomata-radiographically")) sum := hash.Sum(nil) sig, err := signer.Sign(rand.Reader, sum, &rsa.PSSOptions{ Hash: crypto.SHA256, }) require.NoError(t, err) pub, ok := signer.Public().(*rsa.PublicKey) require.True(t, ok) assert.NoError(t, rsa.VerifyPSS(pub, crypto.SHA256, sum, sig, &rsa.PSSOptions{ Hash: crypto.SHA256, })) } func TestTPMOnlyFailsWithoutStorageWhenRequired(t *testing.T) { tpm, err := New(withSimulator(t)) // defaults to blackhole; no storage require.NoError(t, err) ctx := context.Background() // some operations do not require storage, so must not fail _, err = tpm.Info(ctx) require.NoError(t, err) _, err = tpm.GenerateRandom(ctx, uint16(8)) require.NoError(t, err) err = tpm.Available() require.NoError(t, err) // most operations require storage, so must fail _, err = tpm.CreateKey(ctx, "key", CreateKeyConfig{Algorithm: "RSA", Size: 3072}) require.ErrorIs(t, err, ErrNoStorageConfigured) _, err = tpm.AttestKey(ctx, "ak", "key", AttestKeyConfig{Algorithm: "RSA", Size: 3072, QualifyingData: nil}) require.ErrorIs(t, err, ErrNoStorageConfigured) _, err = tpm.GetKey(ctx, "key") require.ErrorIs(t, err, ErrNoStorageConfigured) _, err = tpm.ListKeys(ctx) require.ErrorIs(t, err, ErrNoStorageConfigured) err = tpm.DeleteKey(ctx, "key") require.ErrorIs(t, err, ErrNoStorageConfigured) _, err = tpm.GetKeysAttestedBy(ctx, "ak") require.ErrorIs(t, err, ErrNoStorageConfigured) _, err = tpm.CreateAK(ctx, "ak") require.ErrorIs(t, err, ErrNoStorageConfigured) _, err = tpm.GetAK(ctx, "ak") require.ErrorIs(t, err, ErrNoStorageConfigured) _, err = tpm.ListAKs(ctx) require.ErrorIs(t, err, ErrNoStorageConfigured) err = tpm.DeleteAK(ctx, "ak") require.ErrorIs(t, err, ErrNoStorageConfigured) _, err = tpm.GetAKByPermanentIdentifier(ctx, "permanent-identifier") require.ErrorIs(t, err, ErrNoStorageConfigured) } crypto-0.57.0/tpm/tpm_test.go000066400000000000000000000034231474156331600161250ustar00rootroot00000000000000package tpm import ( "context" "errors" "io" "testing" "github.com/stretchr/testify/require" "go.step.sm/crypto/tpm/storage" ) type closeSimulator struct { closeErr error } func (s *closeSimulator) Open() error { return nil } func (s *closeSimulator) Close() error { return s.closeErr } func (s *closeSimulator) Read([]byte) (int, error) { return -1, nil } func (s *closeSimulator) Write([]byte) (int, error) { return -1, nil } func (s *closeSimulator) MeasurementLog() ([]byte, error) { return nil, nil } var _ io.ReadWriteCloser = (*closeSimulator)(nil) func newOpenedTPM(t *testing.T) *TPM { t.Helper() tpm, err := New(WithSimulator(&closeSimulator{})) require.NoError(t, err) err = tpm.open(context.Background()) require.NoError(t, err) return tpm } func newCloseErrorTPM(t *testing.T) *TPM { t.Helper() tpm, err := New(WithSimulator(&closeSimulator{ closeErr: errors.New("closeErr"), })) require.NoError(t, err) err = tpm.open(context.Background()) require.NoError(t, err) tpm.simulator = nil // required to skip returning when similator is configured return tpm } func Test_close(t *testing.T) { var emptyErr error anErr := errors.New("anErr") var closeErr error tpm := newOpenedTPM(t) closeTPM(context.Background(), tpm, &emptyErr) require.NoError(t, emptyErr) tpm = newOpenedTPM(t) closeTPM(context.Background(), tpm, &anErr) require.EqualError(t, anErr, "anErr") tpm = newCloseErrorTPM(t) require.Nil(t, tpm.simulator) closeTPM(context.Background(), newCloseErrorTPM(t), &closeErr) require.EqualError(t, closeErr, "failed closing attest.TPM: closeErr") // attest.TPM is backed by the closeSimulator } func TestTPMNoStorageConfiguredError(t *testing.T) { err := ErrNoStorageConfigured require.ErrorIs(t, err, storage.ErrNoStorageConfigured) } crypto-0.57.0/tpm/tss2.go000066400000000000000000000022001474156331600151510ustar00rootroot00000000000000package tpm import ( "context" "github.com/google/go-tpm/tpmutil" "go.step.sm/crypto/tpm/tss2" ) const ( // Defined in "Registry of reserved TPM 2.0 handles and localities", // and checked on a glinux machine. This is the default parent handle // used by go-tpm and go-attestation, and thus also the default handle // set when marshaling to the TSS2 format. commonSrkEquivalentHandle = tpmutil.Handle(0x81000001) ) // ToTSS2 gets the public and private blobs and returns a [*tss2.TPMKey]. func (ak *AK) ToTSS2(ctx context.Context) (*tss2.TPMKey, error) { blobs, err := ak.Blobs(ctx) if err != nil { return nil, err } return tss2.New( blobs.public, blobs.private, tss2.WithParent(commonSrkEquivalentHandle), // default parent used by go-tpm/go-attestation ), nil } // ToTSS2 gets the public and private blobs and returns a [*tss2.TPMKey]. func (k *Key) ToTSS2(ctx context.Context) (*tss2.TPMKey, error) { blobs, err := k.Blobs(ctx) if err != nil { return nil, err } return tss2.New( blobs.public, blobs.private, tss2.WithParent(commonSrkEquivalentHandle), // default parent used by go-tpm/go-attestation ), nil } crypto-0.57.0/tpm/tss2/000077500000000000000000000000001474156331600146305ustar00rootroot00000000000000crypto-0.57.0/tpm/tss2/encode.go000066400000000000000000000033731474156331600164220ustar00rootroot00000000000000package tss2 import ( "encoding/pem" "github.com/google/go-tpm/tpmutil" ) // handleOwner is the reserved handle TPM_RH_OWNER. const handleOwner = tpmutil.Handle(0x40000001) // TPMOption is the type used to modify a [TPMKey]. type TPMOption func(*TPMKey) // WithParent sets the [TPMKey] parent handle. func WithParent(parent tpmutil.Handle) TPMOption { return func(t *TPMKey) { t.Parent = int(parent) } } // New creates a new [TPMKey] with the given public and private keys. func New(pub, priv []byte, opts ...TPMOption) *TPMKey { key := &TPMKey{ Type: oidLoadableKey, EmptyAuth: true, Parent: int(handleOwner), PublicKey: addPrefixLength(pub), PrivateKey: addPrefixLength(priv), } for _, fn := range opts { fn(key) } return key } // Encode encodes the [TPMKey] returns a [*pem.Block]. func (k *TPMKey) Encode() (*pem.Block, error) { b, err := MarshalPrivateKey(k) if err != nil { return nil, err } return &pem.Block{ Type: "TSS2 PRIVATE KEY", Bytes: b, }, nil } // EncodeToMemory encodes the [TPMKey] and returns an encoded PEM block. func (k *TPMKey) EncodeToMemory() ([]byte, error) { block, err := k.Encode() if err != nil { return nil, err } return pem.EncodeToMemory(block), nil } // Encode encodes the given public and private key and returns a [*pem.Block]. func Encode(pub, priv []byte, opts ...TPMOption) (*pem.Block, error) { return New(pub, priv, opts...).Encode() } // EncodeToMemory encodes the given public and private key and returns an // encoded PEM block. func EncodeToMemory(pub, priv []byte, opts ...TPMOption) ([]byte, error) { return New(pub, priv, opts...).EncodeToMemory() } func addPrefixLength(b []byte) []byte { s := len(b) return append([]byte{byte(s >> 8 & 0xFF), byte(s & 0xFF)}, b...) } crypto-0.57.0/tpm/tss2/encode_test.go000066400000000000000000000077331474156331600174650ustar00rootroot00000000000000package tss2 import ( "encoding/pem" "testing" "github.com/stretchr/testify/assert" ) func TestNew(t *testing.T) { type args struct { pub []byte priv []byte opts []TPMOption } tests := []struct { name string args args want *TPMKey }{ {"ok", args{[]byte("public"), []byte("private"), nil}, &TPMKey{ Type: oidLoadableKey, EmptyAuth: true, Parent: 0x40000001, PublicKey: append([]byte{0, 6}, []byte("public")...), PrivateKey: append([]byte{0, 7}, []byte("private")...), }}, {"ok with options", args{[]byte("public"), []byte("private"), []TPMOption{ func(k *TPMKey) { k.EmptyAuth = false }, func(k *TPMKey) { k.Policy = append(k.Policy, TPMPolicy{CommandCode: 1, CommandPolicy: []byte("command-policy")}) }, }}, &TPMKey{ Type: oidLoadableKey, EmptyAuth: false, Policy: []TPMPolicy{{CommandCode: 1, CommandPolicy: []byte("command-policy")}}, Parent: 0x40000001, PublicKey: append([]byte{0, 6}, []byte("public")...), PrivateKey: append([]byte{0, 7}, []byte("private")...), }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert.Equal(t, tt.want, New(tt.args.pub, tt.args.priv, tt.args.opts...)) }) } } func TestTPMKey_Encode(t *testing.T) { tests := []struct { name string tpmKey *TPMKey want *pem.Block assertion assert.ErrorAssertionFunc }{ {"ok", New([]byte("public"), []byte("private")), &pem.Block{ Type: "TSS2 PRIVATE KEY", Bytes: []byte{ 0x30, 0x28, 0x6, 0x6, 0x67, 0x81, 0x5, 0xa, 0x1, 0x3, 0xa0, 0x3, 0x1, 0x1, 0xff, 0x2, 0x4, 0x40, 0x0, 0x0, 0x1, 0x4, 0x8, 0x0, 0x6, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4, 0x9, 0x0, 0x7, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, }, }, assert.NoError}, {"fail", nil, nil, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.tpmKey.Encode() tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } } func TestTPMKey_EncodeToMemory(t *testing.T) { tests := []struct { name string tpmKey *TPMKey want []byte assertion assert.ErrorAssertionFunc }{ {"ok", New([]byte("public"), []byte("private")), []byte(`-----BEGIN TSS2 PRIVATE KEY----- MCgGBmeBBQoBA6ADAQH/AgRAAAABBAgABnB1YmxpYwQJAAdwcml2YXRl -----END TSS2 PRIVATE KEY----- `), assert.NoError}, {"fail", nil, nil, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.tpmKey.EncodeToMemory() tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } } func TestEncode(t *testing.T) { type args struct { pub []byte priv []byte opts []TPMOption } tests := []struct { name string args args want *pem.Block assertion assert.ErrorAssertionFunc }{ {"ok", args{[]byte("public"), []byte("private"), nil}, &pem.Block{ Type: "TSS2 PRIVATE KEY", Bytes: []byte{ 0x30, 0x28, 0x6, 0x6, 0x67, 0x81, 0x5, 0xa, 0x1, 0x3, 0xa0, 0x3, 0x1, 0x1, 0xff, 0x2, 0x4, 0x40, 0x0, 0x0, 0x1, 0x4, 0x8, 0x0, 0x6, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4, 0x9, 0x0, 0x7, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, }, }, assert.NoError}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := Encode(tt.args.pub, tt.args.priv, tt.args.opts...) tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } } func TestEncodeToMemory(t *testing.T) { type args struct { pub []byte priv []byte opts []TPMOption } tests := []struct { name string args args want []byte assertion assert.ErrorAssertionFunc }{ {"ok", args{[]byte("public"), []byte("private"), nil}, []byte(`-----BEGIN TSS2 PRIVATE KEY----- MCgGBmeBBQoBA6ADAQH/AgRAAAABBAgABnB1YmxpYwQJAAdwcml2YXRl -----END TSS2 PRIVATE KEY----- `), assert.NoError}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := EncodeToMemory(tt.args.pub, tt.args.priv, tt.args.opts...) tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } } crypto-0.57.0/tpm/tss2/other_test.go000066400000000000000000000002771474156331600173450ustar00rootroot00000000000000//go:build !tpm && !tpmsimulator package tss2 import ( "io" "testing" ) func openTPM(t *testing.T) io.ReadWriteCloser { t.Helper() t.Skip("Use tags tpm or tpmsimulator") return nil } crypto-0.57.0/tpm/tss2/signer.go000066400000000000000000000201671474156331600164540ustar00rootroot00000000000000// Copyright 2023 Smallstep Labs, Inc // Copyright 2023 David Woodhouse, @dwmw2 // Copyright 2023 @google/go-tpm-admin // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tss2 import ( "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" "encoding/asn1" "errors" "fmt" "io" "math/big" "sync" "github.com/google/go-tpm/legacy/tpm2" "github.com/google/go-tpm/tpmutil" ) var ( // ECCSRKTemplate contains the TCG reference ECC-P256 SRK template. // https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf ECCSRKTemplate = tpm2.Public{ Type: tpm2.AlgECC, NameAlg: tpm2.AlgSHA256, Attributes: tpm2.FlagStorageDefault | tpm2.FlagNoDA, ECCParameters: &tpm2.ECCParams{ Symmetric: &tpm2.SymScheme{ Alg: tpm2.AlgAES, KeyBits: 128, Mode: tpm2.AlgCFB, }, Sign: &tpm2.SigScheme{ Alg: tpm2.AlgNull, }, CurveID: tpm2.CurveNISTP256, }, } // RSASRKTemplate contains the TCG reference RSA-2048 SRK template. // https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf RSASRKTemplate = tpm2.Public{ Type: tpm2.AlgRSA, NameAlg: tpm2.AlgSHA256, Attributes: tpm2.FlagStorageDefault | tpm2.FlagNoDA, RSAParameters: &tpm2.RSAParams{ Symmetric: &tpm2.SymScheme{ Alg: tpm2.AlgAES, KeyBits: 128, Mode: tpm2.AlgCFB, }, ModulusRaw: make([]byte, 256), KeyBits: 2048, }, } ) // Public returns the Go version of the public key. func (k *TPMKey) Public() (crypto.PublicKey, error) { public, err := tpm2.DecodePublic(k.PublicKey[2:]) if err != nil { return nil, err } return public.Key() } // Signer implements [crypto.Signer] using a [TPMKey]. type Signer struct { m sync.Mutex rw io.ReadWriter publicKey crypto.PublicKey tpmKey *TPMKey srkTemplate tpm2.Public } // CreateSigner creates a new [crypto.Signer] with the given TPM (rw) and // [TPMKey]. The caller is responsible for opening and closing the TPM. func CreateSigner(rw io.ReadWriter, key *TPMKey) (*Signer, error) { switch { case rw == nil: return nil, fmt.Errorf("invalid TPM channel: rw cannot be nil") case key == nil: return nil, fmt.Errorf("invalid TPM key: key cannot be nil") case !key.Type.Equal(oidLoadableKey): return nil, fmt.Errorf("invalid TSS2 key: type %q is not valid", key.Type.String()) case len(key.Policy) != 0: return nil, errors.New("invalid TSS2 key: policy is not implemented") case len(key.AuthPolicy) != 0: return nil, errors.New("invalid TSS2 key: auth policy is not implemented") case len(key.Secret) > 0: return nil, errors.New("invalid TSS2 key: secret should not be set") case !validateParent(key.Parent): return nil, fmt.Errorf("invalid TSS2 key: parent '%d' is not valid", key.Parent) case !validateKey(key.PublicKey): return nil, errors.New("invalid TSS2 key: public key is invalid") case !validateKey(key.PrivateKey): return nil, errors.New("invalid TSS2 key: private key key is invalid") } publicKey, err := key.Public() if err != nil { return nil, fmt.Errorf("error decoding TSS2 public key: %w", err) } return &Signer{ rw: rw, publicKey: publicKey, tpmKey: key, srkTemplate: RSASRKTemplate, }, nil } // SetSRKTemplate allows to change the Storage Root Key (SRK) template used // to load the the public/private blobs into an object in the TPM. // // It currently defaults to [RSASRKTemplate], the same used as the default in the // [go.step.sm/crypto/tpm] package. // // # Experimental // // Notice: This API is EXPERIMENTAL and may be changed or removed in a later // release. func (s *Signer) SetSRKTemplate(p tpm2.Public) { s.m.Lock() s.srkTemplate = p s.m.Unlock() } // SetCommandChannel allows to change the TPM channel. This operation is useful // if the channel set in [CreateSigner] is closed and opened again before // calling [Signer.Sign]. // // # Experimental // // Notice: This API is EXPERIMENTAL and may be changed or removed in a later // release. func (s *Signer) SetCommandChannel(rw io.ReadWriter) { s.m.Lock() s.rw = rw s.m.Unlock() } // Public implements the [crypto.Signer] interface. func (s *Signer) Public() crypto.PublicKey { return s.publicKey } // Sign implements the [crypto.Signer] interface. func (s *Signer) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { parentHandle := tpmutil.Handle(s.tpmKey.Parent) if !handleIsPersistent(s.tpmKey.Parent) { parentHandle, _, err = tpm2.CreatePrimary(s.rw, parentHandle, tpm2.PCRSelection{}, "", "", s.srkTemplate) if err != nil { return nil, fmt.Errorf("error creating primary: %w", err) } defer tpm2.FlushContext(s.rw, parentHandle) } keyHandle, _, err := tpm2.Load(s.rw, parentHandle, "", s.tpmKey.PublicKey[2:], s.tpmKey.PrivateKey[2:]) if err != nil { return nil, fmt.Errorf("error loading key handle: %w", err) } defer tpm2.FlushContext(s.rw, keyHandle) switch p := s.publicKey.(type) { case *ecdsa.PublicKey: return signECDSA(s.rw, keyHandle, digest, p.Curve) case *rsa.PublicKey: return signRSA(s.rw, keyHandle, digest, opts) default: return nil, fmt.Errorf("unsupported signing key type %T", s.publicKey) } } // https://github.com/smallstep/go-attestation/blob/f5480326fb6d63859537ec89fbea7c62485bc4da/attest/wrapped_tpm20.go#L513 func signECDSA(rw io.ReadWriter, key tpmutil.Handle, digest []byte, curve elliptic.Curve) ([]byte, error) { scheme, err := curveSigScheme(curve) if err != nil { return nil, err } sig, err := tpm2.Sign(rw, key, "", digest, nil, scheme) if err != nil { return nil, fmt.Errorf("error creating ECDSA signature: %w", err) } if sig.ECC == nil { return nil, fmt.Errorf("expected ECDSA signature, got: %v", sig.Alg) } return asn1.Marshal(struct { R *big.Int S *big.Int }{sig.ECC.R, sig.ECC.S}) } // https://github.com/smallstep/go-attestation/blob/f5480326fb6d63859537ec89fbea7c62485bc4da/attest/wrapped_tpm20.go#L527 func signRSA(rw io.ReadWriter, key tpmutil.Handle, digest []byte, opts crypto.SignerOpts) ([]byte, error) { h, err := tpm2.HashToAlgorithm(opts.HashFunc()) if err != nil { return nil, fmt.Errorf("error getting algorithm: %w", err) } scheme := &tpm2.SigScheme{ Alg: tpm2.AlgRSASSA, Hash: h, } if pss, ok := opts.(*rsa.PSSOptions); ok { if pss.SaltLength != rsa.PSSSaltLengthAuto && pss.SaltLength != rsa.PSSSaltLengthEqualsHash && pss.SaltLength != len(digest) { return nil, fmt.Errorf("invalid PSS salt length %d, expected rsa.PSSSaltLengthAuto, rsa.PSSSaltLengthEqualsHash or %d", pss.SaltLength, len(digest)) } scheme.Alg = tpm2.AlgRSAPSS } sig, err := tpm2.Sign(rw, key, "", digest, nil, scheme) if err != nil { return nil, fmt.Errorf("error creating RSA signature: %w", err) } if sig.RSA == nil { return nil, fmt.Errorf("unexpected signature scheme %v", sig.Alg) } return sig.RSA.Signature, nil } func curveSigScheme(curve elliptic.Curve) (*tpm2.SigScheme, error) { scheme := &tpm2.SigScheme{ Alg: tpm2.AlgECDSA, } switch curve { case elliptic.P256(): scheme.Hash = tpm2.AlgSHA256 case elliptic.P384(): scheme.Hash = tpm2.AlgSHA384 case elliptic.P521(): scheme.Hash = tpm2.AlgSHA512 default: return nil, fmt.Errorf("unsupported curve %s", curve.Params().Name) } return scheme, nil } func handleIsPersistent(h int) bool { return (h >> 24) == int(tpm2.HandleTypePersistent) } func validateParent(parent int) bool { return handleIsPersistent(parent) || parent == int(tpm2.HandleOwner) || parent == int(tpm2.HandleNull) || parent == int(tpm2.HandleEndorsement) || parent == int(tpm2.HandlePlatform) } func validateKey(b []byte) bool { return len(b) >= 2 && len(b)-2 == (int(b[0])<<8)+int(b[1]) } crypto-0.57.0/tpm/tss2/signer_test.go000066400000000000000000000250011474156331600175030ustar00rootroot00000000000000package tss2 import ( "bytes" "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "io" "testing" "github.com/google/go-tpm/legacy/tpm2" "github.com/google/go-tpm/tpmutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var defaultKeyParamsEC = tpm2.Public{ Type: tpm2.AlgECC, NameAlg: tpm2.AlgSHA256, Attributes: tpm2.FlagSignerDefault ^ tpm2.FlagRestricted, ECCParameters: &tpm2.ECCParams{ Sign: &tpm2.SigScheme{ Alg: tpm2.AlgECDSA, Hash: tpm2.AlgSHA256, }, CurveID: tpm2.CurveNISTP256, }, } var defaultKeyParamsRSA = tpm2.Public{ Type: tpm2.AlgRSA, NameAlg: tpm2.AlgSHA256, Attributes: tpm2.FlagSignerDefault ^ tpm2.FlagRestricted, RSAParameters: &tpm2.RSAParams{ Sign: &tpm2.SigScheme{ Alg: tpm2.AlgRSASSA, Hash: tpm2.AlgSHA256, }, KeyBits: 2048, }, } var defaultKeyParamsRSAPSS = tpm2.Public{ Type: tpm2.AlgRSA, NameAlg: tpm2.AlgSHA256, Attributes: tpm2.FlagSignerDefault ^ tpm2.FlagRestricted, RSAParameters: &tpm2.RSAParams{ Sign: &tpm2.SigScheme{ Alg: tpm2.AlgRSAPSS, Hash: tpm2.AlgSHA256, }, KeyBits: 2048, }, } func assertMaybeError(t assert.TestingT, err error, msgAndArgs ...any) bool { return true } func TestSign(t *testing.T) { rw := openTPM(t) t.Cleanup(func() { assert.NoError(t, rw.Close()) }) keyHnd, _, err := tpm2.CreatePrimary(rw, tpm2.HandleOwner, tpm2.PCRSelection{}, "", "", ECCSRKTemplate) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, tpm2.FlushContext(rw, keyHnd)) }) tests := []struct { name string params tpm2.Public opts crypto.SignerOpts assertion assert.ErrorAssertionFunc }{ {"ok ECDSA", defaultKeyParamsEC, crypto.SHA256, assert.NoError}, {"ok RSA", defaultKeyParamsRSA, crypto.SHA256, assert.NoError}, {"ok RSAPSS PSSSaltLengthAuto", defaultKeyParamsRSAPSS, &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthAuto, Hash: crypto.SHA256, }, assert.NoError}, {"ok RSAPSS PSSSaltLengthEqualsHash", defaultKeyParamsRSAPSS, &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: crypto.SHA256, }, assert.NoError}, {"ok RSAPSS SaltLength=32", defaultKeyParamsRSAPSS, &rsa.PSSOptions{ SaltLength: 32, Hash: crypto.SHA256, }, assert.NoError}, // 222 is the largest salt possible when signing with a 2048 bit key. Go // crypto will use this value when rsa.PSSSaltLengthAuto is set. // // TPM 2.0's TPM_ALG_RSAPSS algorithm, uses the maximum possible salt // length. However, as of TPM revision 1.16, TPMs which follow FIPS // 186-4 will interpret TPM_ALG_RSAPSS using salt length equal to the // digest length. {"RSAPSS SaltLength=222", defaultKeyParamsRSAPSS, &rsa.PSSOptions{ SaltLength: 222, Hash: crypto.SHA256, }, assertMaybeError}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { priv, pub, _, _, _, err := tpm2.CreateKey(rw, keyHnd, tpm2.PCRSelection{}, "", "", tt.params) require.NoError(t, err) signer, err := CreateSigner(rw, New(pub, priv)) require.NoError(t, err) // Set the ECC SRK template used for testing signer.SetSRKTemplate(ECCSRKTemplate) hash := crypto.SHA256.New() hash.Write([]byte("rulingly-quailed-cloacal-indifferentist-roughhoused-self-mad")) sum := hash.Sum(nil) sig, err := signer.Sign(rand.Reader, sum, tt.opts) tt.assertion(t, err) if err != nil { return } // Signature validation using Go crypto switch pub := signer.Public().(type) { case *ecdsa.PublicKey: assert.Equal(t, tpm2.AlgECC, tt.params.Type) assert.True(t, ecdsa.VerifyASN1(pub, sum, sig)) case *rsa.PublicKey: assert.Equal(t, tpm2.AlgRSA, tt.params.Type) switch tt.params.RSAParameters.Sign.Alg { case tpm2.AlgRSASSA: assert.NoError(t, rsa.VerifyPKCS1v15(pub, tt.opts.HashFunc(), sum, sig)) case tpm2.AlgRSAPSS: opts, ok := tt.opts.(*rsa.PSSOptions) require.True(t, ok) assert.NoError(t, rsa.VerifyPSS(pub, opts.Hash, sum, sig, opts)) default: t.Errorf("unexpected RSAParameters.Sign.Alg %v", tt.params.RSAParameters.Sign.Alg) } default: t.Errorf("unexpected PublicKey type %T", pub) } }) } } func TestSign_SetTPM(t *testing.T) { var signer *Signer if t.Run("Setup", func(t *testing.T) { rw := openTPM(t) t.Cleanup(func() { assert.NoError(t, rw.Close()) }) keyHnd, _, err := tpm2.CreatePrimary(rw, tpm2.HandleOwner, tpm2.PCRSelection{}, "", "", ECCSRKTemplate) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, tpm2.FlushContext(rw, keyHnd)) }) priv, pub, _, _, _, err := tpm2.CreateKey(rw, keyHnd, tpm2.PCRSelection{}, "", "", defaultKeyParamsEC) require.NoError(t, err) signer, err = CreateSigner(rw, New(pub, priv)) require.NoError(t, err) }) { rw := openTPM(t) t.Cleanup(func() { assert.NoError(t, rw.Close()) }) require.NotNil(t, signer) // Set new tpm channel signer.SetCommandChannel(rw) // Set the ECC SRK template used for testing signer.SetSRKTemplate(ECCSRKTemplate) hash := crypto.SHA256.New() hash.Write([]byte("ungymnastic-theirn-cotwin-Summer-pemphigous-propagate")) sum := hash.Sum(nil) sig, err := signer.Sign(rand.Reader, sum, crypto.SHA256) require.NoError(t, err) publicKey, ok := signer.Public().(*ecdsa.PublicKey) require.True(t, ok) assert.True(t, ecdsa.VerifyASN1(publicKey, sum, sig)) } } func TestCreateSigner(t *testing.T) { var rw bytes.Buffer key, err := ParsePrivateKey(parsePEM(p256TSS2PEM)) require.NoError(t, err) pub, err := tpm2.DecodePublic(key.PublicKey[2:]) require.NoError(t, err) publicKey, err := pub.Key() require.NoError(t, err) modKey := func(fn TPMOption) *TPMKey { return New(key.PublicKey[2:], key.PrivateKey[2:], fn) } type args struct { rw io.ReadWriter key *TPMKey } tests := []struct { name string args args want *Signer assertion assert.ErrorAssertionFunc }{ {"ok", args{&rw, key}, &Signer{ rw: &rw, publicKey: publicKey, tpmKey: key, srkTemplate: RSASRKTemplate, }, assert.NoError}, {"fail rw", args{nil, key}, nil, assert.Error}, {"fail key", args{&rw, nil}, nil, assert.Error}, {"fail type", args{&rw, modKey(func(k *TPMKey) { k.Type = oidSealedKey })}, nil, assert.Error}, {"fail policy", args{&rw, modKey(func(k *TPMKey) { k.Policy = []TPMPolicy{{CommandCode: 1, CommandPolicy: []byte("command-policy")}} })}, nil, assert.Error}, {"fail authPolicy", args{&rw, modKey(func(k *TPMKey) { k.AuthPolicy = []TPMAuthPolicy{{Name: "auth", Policy: []TPMPolicy{{CommandCode: 1, CommandPolicy: []byte("command-policy")}}}} })}, nil, assert.Error}, {"fail secret", args{&rw, modKey(func(k *TPMKey) { k.Secret = []byte("secret") })}, nil, assert.Error}, {"fail parent", args{&rw, modKey(func(k *TPMKey) { k.Parent = 0 })}, nil, assert.Error}, {"fail publicKey", args{&rw, modKey(func(k *TPMKey) { k.PublicKey = key.PublicKey[2:] })}, nil, assert.Error}, {"fail privateKey", args{&rw, modKey(func(k *TPMKey) { k.PrivateKey = key.PrivateKey[2:] })}, nil, assert.Error}, {"fail decodePublic", args{&rw, modKey(func(k *TPMKey) { k.PublicKey = append([]byte{0, 6}, []byte("public")...) })}, nil, assert.Error}, {"fail type", args{&rw, modKey(func(k *TPMKey) { p := tpm2.Public{ Type: tpm2.AlgSymCipher, NameAlg: tpm2.AlgAES, Attributes: tpm2.FlagSignerDefault ^ tpm2.FlagRestricted, SymCipherParameters: &tpm2.SymCipherParams{ Symmetric: &tpm2.SymScheme{ Alg: tpm2.AlgAES, KeyBits: 128, Mode: tpm2.AlgCFB, }, Unique: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, }, } b, err := p.Encode() if assert.NoError(t, err) { k.PublicKey = addPrefixLength(b) } })}, nil, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := CreateSigner(tt.args.rw, tt.args.key) tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } } func Test_curveSigScheme(t *testing.T) { type args struct { curve elliptic.Curve } tests := []struct { name string args args want *tpm2.SigScheme assertion assert.ErrorAssertionFunc }{ {"ok P-256", args{elliptic.P256()}, &tpm2.SigScheme{ Alg: tpm2.AlgECDSA, Hash: tpm2.AlgSHA256, }, assert.NoError}, {"ok P-2384", args{elliptic.P384()}, &tpm2.SigScheme{ Alg: tpm2.AlgECDSA, Hash: tpm2.AlgSHA384, }, assert.NoError}, {"ok P-521", args{elliptic.P521()}, &tpm2.SigScheme{ Alg: tpm2.AlgECDSA, Hash: tpm2.AlgSHA512, }, assert.NoError}, {"fail P-224", args{elliptic.P224()}, nil, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := curveSigScheme(tt.args.curve) tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } } func Test_signECDSA_fail(t *testing.T) { rw := openTPM(t) t.Cleanup(func() { assert.NoError(t, rw.Close()) }) digest := func(h crypto.Hash) []byte { hh := h.New() hh.Write([]byte("Subotica-chronique-radiancy-inspirationally-transuming-Melbeta")) return hh.Sum(nil) } type args struct { rw io.ReadWriter key tpmutil.Handle digest []byte curve elliptic.Curve } tests := []struct { name string args args want []byte assertion assert.ErrorAssertionFunc }{ {"fail curve", args{rw, handleOwner, digest(crypto.SHA224), elliptic.P224()}, nil, assert.Error}, {"fail sign", args{nil, handleOwner, digest(crypto.SHA256), elliptic.P256()}, nil, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := signECDSA(tt.args.rw, tt.args.key, tt.args.digest, tt.args.curve) tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } } func Test_signRSA_fail(t *testing.T) { rw := openTPM(t) t.Cleanup(func() { assert.NoError(t, rw.Close()) }) h := crypto.SHA256.New() h.Write([]byte("murmur-squinance-hoghide-jubilation-enteraden-samadh")) digest := h.Sum(nil) type args struct { rw io.ReadWriter key tpmutil.Handle digest []byte opts crypto.SignerOpts } tests := []struct { name string args args want []byte assertion assert.ErrorAssertionFunc }{ {"fail HashToAlgorithm", args{rw, handleOwner, digest, crypto.SHA224}, nil, assert.Error}, {"fail PSSOptions", args{rw, handleOwner, digest, &rsa.PSSOptions{ Hash: crypto.SHA256, SaltLength: 222, }}, nil, assert.Error}, {"fail sign", args{nil, handleOwner, digest, crypto.SHA256}, nil, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := signRSA(tt.args.rw, tt.args.key, tt.args.digest, tt.args.opts) tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } } crypto-0.57.0/tpm/tss2/simulator_test.go000066400000000000000000000010051474156331600202310ustar00rootroot00000000000000//go:build tpmsimulator package tss2 import ( "crypto/rand" "encoding/hex" "io" "testing" "github.com/stretchr/testify/require" "go.step.sm/crypto/tpm/simulator" ) var seed string func init() { b := make([]byte, 8) if _, err := io.ReadFull(rand.Reader, b); err != nil { panic(err) } seed = hex.EncodeToString(b) } func openTPM(t *testing.T) io.ReadWriteCloser { t.Helper() sim, err := simulator.New(simulator.WithSeed(seed)) require.NoError(t, err) require.NoError(t, sim.Open()) return sim } crypto-0.57.0/tpm/tss2/tpm_test.go000066400000000000000000000004131474156331600170140ustar00rootroot00000000000000//go:build tpm package tss2 import ( "io" "testing" "github.com/google/go-tpm/legacy/tpm2" "github.com/stretchr/testify/require" ) func openTPM(t *testing.T) io.ReadWriteCloser { t.Helper() rwc, err := tpm2.OpenTPM() require.NoError(t, err) return rwc } crypto-0.57.0/tpm/tss2/tss2.go000066400000000000000000000152571474156331600160640ustar00rootroot00000000000000package tss2 import ( "encoding/asn1" "errors" "golang.org/x/crypto/cryptobyte" cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1" ) var ( oidLoadableKey = asn1.ObjectIdentifier{2, 23, 133, 10, 1, 3} oidImportableKey = asn1.ObjectIdentifier{2, 23, 133, 10, 1, 4} oidSealedKey = asn1.ObjectIdentifier{2, 23, 133, 10, 1, 5} ) // TPMKey is defined in https://www.hansenpartnership.com/draft-bottomley-tpm2-keys.html#section-3.1: // // TPMKey ::= SEQUENCE { // type OBJECT IDENTIFIER, // emptyAuth [0] EXPLICIT BOOLEAN OPTIONAL, // policy [1] EXPLICIT SEQUENCE OF TPMPolicy OPTIONAL, // secret [2] EXPLICIT OCTET STRING OPTIONAL, // authPolicy [3] EXPLICIT SEQUENCE OF TPMAuthPolicy OPTIONAL, // parent INTEGER, // pubkey OCTET STRING, // privkey OCTET STRING // } type TPMKey struct { Type asn1.ObjectIdentifier EmptyAuth bool `asn1:"optional,explicit,tag:0"` Policy []TPMPolicy `asn1:"optional,explicit,tag:1"` Secret []byte `asn1:"optional,explicit,tag:2"` AuthPolicy []TPMAuthPolicy `asn1:"optional,explicit,tag:3"` Parent int PublicKey []byte PrivateKey []byte } // TPMPolicy is defined in https://www.hansenpartnership.com/draft-bottomley-tpm2-keys.html#section-4.1: // // TPMPolicy ::= SEQUENCE { // commandCode [0] EXPLICIT INTEGER, // commandPolicy [1] EXPLICIT OCTET STRING // } type TPMPolicy struct { CommandCode int `asn1:"explicit,tag:0"` CommandPolicy []byte `asn1:"explicit,tag:1"` } // TPMAuthPolicy is defined in https://www.hansenpartnership.com/draft-bottomley-tpm2-keys.html#section-5.1 // // TPMAuthPolicy ::= SEQUENCE { // name [0] EXPLICIT UTF8String OPTIONAL, // policy [1] EXPLICIT SEQUENCE OF TPMPolicy // } type TPMAuthPolicy struct { Name string `asn1:"utf8,optional,explicit,tag:0"` Policy []TPMPolicy `asn1:"explicit,tag:1"` } // ParsePrivateKey parses a single TPM key from the given ASN.1 DER data. // // # Experimental // // Notice: This API is EXPERIMENTAL and may be changed or removed in a later // release. func ParsePrivateKey(derBytes []byte) (*TPMKey, error) { var err error input := cryptobyte.String(derBytes) if !input.ReadASN1(&input, cryptobyte_asn1.SEQUENCE) { return nil, errors.New("malformed TSS2 key") } key := new(TPMKey) if !input.ReadASN1ObjectIdentifier(&key.Type) { return nil, errors.New("malformed TSS2 type") } if tag, ok := readOptionalTag(&input, 0); ok { if !readASN1Boolean(&tag, &key.EmptyAuth) { return nil, errors.New("malformed TSS2 emptyAuth") } } // TODO(mariano): generate key with policy if tag, ok := readOptionalTag(&input, 1); ok { var policy cryptobyte.String if !tag.ReadASN1(&policy, cryptobyte_asn1.SEQUENCE) { return nil, errors.New("malformed TSS2 policy") } key.Policy, err = readTPMPolicySequence(&policy) if err != nil { return nil, err } } // TODO(mariano): generate key with secret if tag, ok := readOptionalTag(&input, 2); ok { if key.Secret, ok = readOctetString(&tag); !ok { return nil, errors.New("malformed TSS2 secret") } } // TODO(mariano): generate key with authPolicy if tag, ok := readOptionalTag(&input, 3); ok { if key.AuthPolicy, err = readTPMAuthPolicy(&tag); err != nil { return nil, err } } if !input.ReadASN1Integer(&key.Parent) { return nil, errors.New("malformed TSS2 parent") } var ok bool if key.PublicKey, ok = readOctetString(&input); !ok { return nil, errors.New("malformed TSS2 pubkey") } if key.PrivateKey, ok = readOctetString(&input); !ok { return nil, errors.New("malformed TSS2 privkey") } return key, nil } // MarshalPrivateKey converts the given key to a TSS2 ASN.1 DER form. // // # Experimental // // Notice: This API is EXPERIMENTAL and may be changed or removed in a later // release. func MarshalPrivateKey(key *TPMKey) ([]byte, error) { if key == nil { return nil, errors.New("tpmKey cannot be nil") } return asn1.Marshal(*key) } func readOptionalTag(input *cryptobyte.String, tag int) (cryptobyte.String, bool) { var isPresent bool var output cryptobyte.String if !input.ReadOptionalASN1(&output, &isPresent, cryptobyte_asn1.Tag(tag).Constructed().ContextSpecific()) { return nil, false } return output, isPresent } func readOctetString(input *cryptobyte.String) ([]byte, bool) { var os cryptobyte.String if !input.ReadASN1(&os, cryptobyte_asn1.OCTET_STRING) { return nil, false } return os, true } // readASN1Boolean accepts 0x01 as a TRUE value for a boolean type. OpenSSL // seems to confuse DER with BER encoding and encodes the BOOLEAN TRUE as 0x01 // instead of 0xff. func readASN1Boolean(input *cryptobyte.String, out *bool) bool { var bytes cryptobyte.String if !input.ReadASN1(&bytes, cryptobyte_asn1.BOOLEAN) || len(bytes) != 1 { return false } switch bytes[0] { case 0: *out = false case 1, 0xff: *out = true default: return false } return true } func readTPMPolicySequence(input *cryptobyte.String) ([]TPMPolicy, error) { var policies []TPMPolicy for !input.Empty() { var p TPMPolicy var seq cryptobyte.String if !input.ReadASN1(&seq, cryptobyte_asn1.SEQUENCE) { return nil, errors.New("malformed TSS2 policy") } tag, ok := readOptionalTag(&seq, 0) if !ok || !tag.ReadASN1Integer(&p.CommandCode) { return nil, errors.New("malformed TSS2 policy commandCode") } tag, ok = readOptionalTag(&seq, 1) if !ok { return nil, errors.New("malformed TSS2 policy commandPolicy") } if p.CommandPolicy, ok = readOctetString(&tag); !ok { return nil, errors.New("malformed TSS2 policy commandPolicy") } policies = append(policies, p) } return policies, nil } func readTPMAuthPolicy(input *cryptobyte.String) ([]TPMAuthPolicy, error) { var ( err error authPolicy cryptobyte.String authPolicies []TPMAuthPolicy ) if !input.ReadASN1(&authPolicy, cryptobyte_asn1.SEQUENCE) { return nil, errors.New("malformed TSS2 authPolicy") } for !authPolicy.Empty() { var ap TPMAuthPolicy var seq cryptobyte.String if !authPolicy.ReadASN1(&seq, cryptobyte_asn1.SEQUENCE) { return nil, errors.New("malformed TSS2 authPolicy") } var name cryptobyte.String if tag, ok := readOptionalTag(&seq, 0); ok { if !tag.ReadASN1(&name, cryptobyte_asn1.UTF8String) { return nil, errors.New("malformed TSS2 authPolicy name") } ap.Name = string(name) } var policySeq cryptobyte.String if tag, ok := readOptionalTag(&seq, 1); ok { if !tag.ReadASN1(&policySeq, cryptobyte_asn1.SEQUENCE) { return nil, errors.New("malformed TSS2 authPolicy policy") } if ap.Policy, err = readTPMPolicySequence(&policySeq); err != nil { return nil, errors.New("malformed TSS2 authPolicy policy") } } authPolicies = append(authPolicies, ap) } return authPolicies, nil } crypto-0.57.0/tpm/tss2/tss2_test.go000066400000000000000000000343721474156331600171220ustar00rootroot00000000000000package tss2 import ( "encoding/asn1" "encoding/pem" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // key generated using: // // openssl genpkey -provider tpm2 -algorithm RSA -out rsa-key.pem var rsaTSS2PEM = `-----BEGIN TSS2 PRIVATE KEY----- MIICEgYGZ4EFCgEDoAMBAQECBEAAAAEEggEYARYAAQALAAYAcgAAABAAEAgAAAAA AAEAxaLvqN+HkUsj83CQxxKwjB/OODnMrLzCly3917Fv0iSehV0sjkrW+9kpksYL 5nAdhT/EhfOGupmq8hpEghEFUvocarGf/CtT7ra/FE1d6JT8F6bPe9lIUNcUhObx Msw8JU3f/uPNurlA/yXb8ZTlGSHoXUrYbiDF36EJUKm2nc/QkdB4SyXrAaUjTFAC 8ifqb3cvtx1KrLGhbP+dQ1A2ytK8FaMauhOGp3gIlAOOVwcRg/DvaxzVBlKRW75u 21r6/w5Wo71IdeEPB/qlCfY3CGvk3in6Wf27gjS+vVa/gx9N9sV7Sq/QX5rWnyol x5cb6miCxLAWrchU80bGi0h+BwSB4ADeACCMbeh27b01dSmGrtftJnU9ZJaXr6tr us/XbpEh+QY/NQAQZlRBv8rA2Gicwnf5gbZcBGVAOxp/EWZH+MVEKQOk4EVM+ACa EQRWNdlr/A/9lPyOZIWw4tajkfKZgfr0ia0kAK4Cfb3cTnXnsAp59/kLG35JM2gQ /Xvm/CAxtvmVNPF6OrmWhNhoOHbveEok2nVKyr0WM5WQSCnnZH4DpyHtA3bXa2rb aPU0KW6T7K0wYsGI0FHC/3arTjlVmXC+mze86n32TkO0rhQiAyn8jS6tbUe6b3jW x7YfKP6g -----END TSS2 PRIVATE KEY-----` // key generated using: // // openssl ecparam -provider tpm2 -name prime256v1 -genkey -noout -out ec-key.pem var p256TSS2PEM = `-----BEGIN TSS2 PRIVATE KEY----- MIHwBgZngQUKAQOgAwEBAQIEQAAAAQRYAFYAIwALAAYAcgAAABAAEAADABAAIO7L aur7h2XAyrZTA8g6QMksNNoUvkMZ4xnjVUSn3k+bACDHuNRZDInoD5Nts7WUos0k Oe0/tF/HfhfSQTqsHo/rKgSBgAB+ACBr9xn6R2V13ErShb75o+EyMqtFsTysp24f VNZ7IWfEBAAQebW0tKoqBmNr/ZGTt2jAEO/gdLEuZ+TkiWYf3h8Jcc0bUrj6lA9I W6fVV4B/ZtnADx9/YGB9FBY8Bu07W7m+PorVTCbXFfOAFmSUg3eB0bgb2TRtFevZ izcX -----END TSS2 PRIVATE KEY-----` // key generated using: // // tpm2_createprimary -c primary.ctx // tpm2_create -C primary.ctx -G ecc -u obj.pub -r obj.priv // tpm2_encodeobject -C primary.ctx -u obj.pub -r obj.priv -o obj.pem var p256EmptyAuthFalse = `-----BEGIN TSS2 PRIVATE KEY----- MIHwBgZngQUKAQOgAwEBAAIEQAAAAQRYAFYAIwALAAYAcgAAABAAEAADABAAIIgq 1VllQRCT45GbLlp1Wud0jiSfojBwp1MYljWMw1T7ACAblgTFkwvSMnzpArA8GjVP ULHy7pJubvS2W7TxmzclRQSBgAB+ACDVXoK8RpE5XxjBcfeHpip9Dz2j7AUj0oE1 RKlDg/+dYgAQd9c9mgioJc8wFL1zaU4viH1fq3fObbfZF/L8oLrLv6u3Pg8qeGzf ePVypgEUeJGw68er7UZb4ZSVfoGId6KLX9JE7IwyBkRWLhBU3sLANdgjTqlXUhAD mnYo -----END TSS2 PRIVATE KEY-----` func parsePEM(s string) []byte { block, _ := pem.Decode([]byte(s)) return block.Bytes } func TestParsePrivateKey(t *testing.T) { type args struct { derBytes []byte } tests := []struct { name string args args want *TPMKey assertion assert.ErrorAssertionFunc }{ {"ok rsa", args{parsePEM(rsaTSS2PEM)}, &TPMKey{ Type: oidLoadableKey, EmptyAuth: true, Parent: 0x40000001, PublicKey: []byte{ 0x01, 0x16, 0x00, 0x01, 0x00, 0x0b, 0x00, 0x06, 0x00, 0x72, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xc5, 0xa2, 0xef, 0xa8, 0xdf, 0x87, 0x91, 0x4b, 0x23, 0xf3, 0x70, 0x90, 0xc7, 0x12, 0xb0, 0x8c, 0x1f, 0xce, 0x38, 0x39, 0xcc, 0xac, 0xbc, 0xc2, 0x97, 0x2d, 0xfd, 0xd7, 0xb1, 0x6f, 0xd2, 0x24, 0x9e, 0x85, 0x5d, 0x2c, 0x8e, 0x4a, 0xd6, 0xfb, 0xd9, 0x29, 0x92, 0xc6, 0x0b, 0xe6, 0x70, 0x1d, 0x85, 0x3f, 0xc4, 0x85, 0xf3, 0x86, 0xba, 0x99, 0xaa, 0xf2, 0x1a, 0x44, 0x82, 0x11, 0x05, 0x52, 0xfa, 0x1c, 0x6a, 0xb1, 0x9f, 0xfc, 0x2b, 0x53, 0xee, 0xb6, 0xbf, 0x14, 0x4d, 0x5d, 0xe8, 0x94, 0xfc, 0x17, 0xa6, 0xcf, 0x7b, 0xd9, 0x48, 0x50, 0xd7, 0x14, 0x84, 0xe6, 0xf1, 0x32, 0xcc, 0x3c, 0x25, 0x4d, 0xdf, 0xfe, 0xe3, 0xcd, 0xba, 0xb9, 0x40, 0xff, 0x25, 0xdb, 0xf1, 0x94, 0xe5, 0x19, 0x21, 0xe8, 0x5d, 0x4a, 0xd8, 0x6e, 0x20, 0xc5, 0xdf, 0xa1, 0x09, 0x50, 0xa9, 0xb6, 0x9d, 0xcf, 0xd0, 0x91, 0xd0, 0x78, 0x4b, 0x25, 0xeb, 0x01, 0xa5, 0x23, 0x4c, 0x50, 0x02, 0xf2, 0x27, 0xea, 0x6f, 0x77, 0x2f, 0xb7, 0x1d, 0x4a, 0xac, 0xb1, 0xa1, 0x6c, 0xff, 0x9d, 0x43, 0x50, 0x36, 0xca, 0xd2, 0xbc, 0x15, 0xa3, 0x1a, 0xba, 0x13, 0x86, 0xa7, 0x78, 0x08, 0x94, 0x03, 0x8e, 0x57, 0x07, 0x11, 0x83, 0xf0, 0xef, 0x6b, 0x1c, 0xd5, 0x06, 0x52, 0x91, 0x5b, 0xbe, 0x6e, 0xdb, 0x5a, 0xfa, 0xff, 0x0e, 0x56, 0xa3, 0xbd, 0x48, 0x75, 0xe1, 0x0f, 0x07, 0xfa, 0xa5, 0x09, 0xf6, 0x37, 0x08, 0x6b, 0xe4, 0xde, 0x29, 0xfa, 0x59, 0xfd, 0xbb, 0x82, 0x34, 0xbe, 0xbd, 0x56, 0xbf, 0x83, 0x1f, 0x4d, 0xf6, 0xc5, 0x7b, 0x4a, 0xaf, 0xd0, 0x5f, 0x9a, 0xd6, 0x9f, 0x2a, 0x25, 0xc7, 0x97, 0x1b, 0xea, 0x68, 0x82, 0xc4, 0xb0, 0x16, 0xad, 0xc8, 0x54, 0xf3, 0x46, 0xc6, 0x8b, 0x48, 0x7e, 0x07, }, PrivateKey: []byte{ 0x00, 0xde, 0x00, 0x20, 0x8c, 0x6d, 0xe8, 0x76, 0xed, 0xbd, 0x35, 0x75, 0x29, 0x86, 0xae, 0xd7, 0xed, 0x26, 0x75, 0x3d, 0x64, 0x96, 0x97, 0xaf, 0xab, 0x6b, 0xba, 0xcf, 0xd7, 0x6e, 0x91, 0x21, 0xf9, 0x06, 0x3f, 0x35, 0x00, 0x10, 0x66, 0x54, 0x41, 0xbf, 0xca, 0xc0, 0xd8, 0x68, 0x9c, 0xc2, 0x77, 0xf9, 0x81, 0xb6, 0x5c, 0x04, 0x65, 0x40, 0x3b, 0x1a, 0x7f, 0x11, 0x66, 0x47, 0xf8, 0xc5, 0x44, 0x29, 0x03, 0xa4, 0xe0, 0x45, 0x4c, 0xf8, 0x00, 0x9a, 0x11, 0x04, 0x56, 0x35, 0xd9, 0x6b, 0xfc, 0x0f, 0xfd, 0x94, 0xfc, 0x8e, 0x64, 0x85, 0xb0, 0xe2, 0xd6, 0xa3, 0x91, 0xf2, 0x99, 0x81, 0xfa, 0xf4, 0x89, 0xad, 0x24, 0x00, 0xae, 0x02, 0x7d, 0xbd, 0xdc, 0x4e, 0x75, 0xe7, 0xb0, 0x0a, 0x79, 0xf7, 0xf9, 0x0b, 0x1b, 0x7e, 0x49, 0x33, 0x68, 0x10, 0xfd, 0x7b, 0xe6, 0xfc, 0x20, 0x31, 0xb6, 0xf9, 0x95, 0x34, 0xf1, 0x7a, 0x3a, 0xb9, 0x96, 0x84, 0xd8, 0x68, 0x38, 0x76, 0xef, 0x78, 0x4a, 0x24, 0xda, 0x75, 0x4a, 0xca, 0xbd, 0x16, 0x33, 0x95, 0x90, 0x48, 0x29, 0xe7, 0x64, 0x7e, 0x03, 0xa7, 0x21, 0xed, 0x03, 0x76, 0xd7, 0x6b, 0x6a, 0xdb, 0x68, 0xf5, 0x34, 0x29, 0x6e, 0x93, 0xec, 0xad, 0x30, 0x62, 0xc1, 0x88, 0xd0, 0x51, 0xc2, 0xff, 0x76, 0xab, 0x4e, 0x39, 0x55, 0x99, 0x70, 0xbe, 0x9b, 0x37, 0xbc, 0xea, 0x7d, 0xf6, 0x4e, 0x43, 0xb4, 0xae, 0x14, 0x22, 0x03, 0x29, 0xfc, 0x8d, 0x2e, 0xad, 0x6d, 0x47, 0xba, 0x6f, 0x78, 0xd6, 0xc7, 0xb6, 0x1f, 0x28, 0xfe, 0xa0, }, }, assert.NoError}, {"ok ec", args{parsePEM(p256TSS2PEM)}, &TPMKey{ Type: oidLoadableKey, EmptyAuth: true, Parent: 0x40000001, PublicKey: []byte{ 0x00, 0x56, 0x00, 0x23, 0x00, 0x0b, 0x00, 0x06, 0x00, 0x72, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x03, 0x00, 0x10, 0x00, 0x20, 0xee, 0xcb, 0x6a, 0xea, 0xfb, 0x87, 0x65, 0xc0, 0xca, 0xb6, 0x53, 0x03, 0xc8, 0x3a, 0x40, 0xc9, 0x2c, 0x34, 0xda, 0x14, 0xbe, 0x43, 0x19, 0xe3, 0x19, 0xe3, 0x55, 0x44, 0xa7, 0xde, 0x4f, 0x9b, 0x00, 0x20, 0xc7, 0xb8, 0xd4, 0x59, 0x0c, 0x89, 0xe8, 0x0f, 0x93, 0x6d, 0xb3, 0xb5, 0x94, 0xa2, 0xcd, 0x24, 0x39, 0xed, 0x3f, 0xb4, 0x5f, 0xc7, 0x7e, 0x17, 0xd2, 0x41, 0x3a, 0xac, 0x1e, 0x8f, 0xeb, 0x2a, }, PrivateKey: []byte{ 0x00, 0x7e, 0x00, 0x20, 0x6b, 0xf7, 0x19, 0xfa, 0x47, 0x65, 0x75, 0xdc, 0x4a, 0xd2, 0x85, 0xbe, 0xf9, 0xa3, 0xe1, 0x32, 0x32, 0xab, 0x45, 0xb1, 0x3c, 0xac, 0xa7, 0x6e, 0x1f, 0x54, 0xd6, 0x7b, 0x21, 0x67, 0xc4, 0x04, 0x00, 0x10, 0x79, 0xb5, 0xb4, 0xb4, 0xaa, 0x2a, 0x06, 0x63, 0x6b, 0xfd, 0x91, 0x93, 0xb7, 0x68, 0xc0, 0x10, 0xef, 0xe0, 0x74, 0xb1, 0x2e, 0x67, 0xe4, 0xe4, 0x89, 0x66, 0x1f, 0xde, 0x1f, 0x09, 0x71, 0xcd, 0x1b, 0x52, 0xb8, 0xfa, 0x94, 0x0f, 0x48, 0x5b, 0xa7, 0xd5, 0x57, 0x80, 0x7f, 0x66, 0xd9, 0xc0, 0x0f, 0x1f, 0x7f, 0x60, 0x60, 0x7d, 0x14, 0x16, 0x3c, 0x06, 0xed, 0x3b, 0x5b, 0xb9, 0xbe, 0x3e, 0x8a, 0xd5, 0x4c, 0x26, 0xd7, 0x15, 0xf3, 0x80, 0x16, 0x64, 0x94, 0x83, 0x77, 0x81, 0xd1, 0xb8, 0x1b, 0xd9, 0x34, 0x6d, 0x15, 0xeb, 0xd9, 0x8b, 0x37, 0x17, }, }, assert.NoError}, {"ok emptyAuth false", args{parsePEM(p256EmptyAuthFalse)}, &TPMKey{ Type: oidLoadableKey, EmptyAuth: false, Parent: 0x40000001, PublicKey: []byte{ 0x00, 0x56, 0x00, 0x23, 0x00, 0x0b, 0x00, 0x06, 0x00, 0x72, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x03, 0x00, 0x10, 0x00, 0x20, 0x88, 0x2a, 0xd5, 0x59, 0x65, 0x41, 0x10, 0x93, 0xe3, 0x91, 0x9b, 0x2e, 0x5a, 0x75, 0x5a, 0xe7, 0x74, 0x8e, 0x24, 0x9f, 0xa2, 0x30, 0x70, 0xa7, 0x53, 0x18, 0x96, 0x35, 0x8c, 0xc3, 0x54, 0xfb, 0x00, 0x20, 0x1b, 0x96, 0x04, 0xc5, 0x93, 0x0b, 0xd2, 0x32, 0x7c, 0xe9, 0x02, 0xb0, 0x3c, 0x1a, 0x35, 0x4f, 0x50, 0xb1, 0xf2, 0xee, 0x92, 0x6e, 0x6e, 0xf4, 0xb6, 0x5b, 0xb4, 0xf1, 0x9b, 0x37, 0x25, 0x45, }, PrivateKey: []byte{ 0x00, 0x7e, 0x00, 0x20, 0xd5, 0x5e, 0x82, 0xbc, 0x46, 0x91, 0x39, 0x5f, 0x18, 0xc1, 0x71, 0xf7, 0x87, 0xa6, 0x2a, 0x7d, 0x0f, 0x3d, 0xa3, 0xec, 0x05, 0x23, 0xd2, 0x81, 0x35, 0x44, 0xa9, 0x43, 0x83, 0xff, 0x9d, 0x62, 0x00, 0x10, 0x77, 0xd7, 0x3d, 0x9a, 0x08, 0xa8, 0x25, 0xcf, 0x30, 0x14, 0xbd, 0x73, 0x69, 0x4e, 0x2f, 0x88, 0x7d, 0x5f, 0xab, 0x77, 0xce, 0x6d, 0xb7, 0xd9, 0x17, 0xf2, 0xfc, 0xa0, 0xba, 0xcb, 0xbf, 0xab, 0xb7, 0x3e, 0x0f, 0x2a, 0x78, 0x6c, 0xdf, 0x78, 0xf5, 0x72, 0xa6, 0x01, 0x14, 0x78, 0x91, 0xb0, 0xeb, 0xc7, 0xab, 0xed, 0x46, 0x5b, 0xe1, 0x94, 0x95, 0x7e, 0x81, 0x88, 0x77, 0xa2, 0x8b, 0x5f, 0xd2, 0x44, 0xec, 0x8c, 0x32, 0x06, 0x44, 0x56, 0x2e, 0x10, 0x54, 0xde, 0xc2, 0xc0, 0x35, 0xd8, 0x23, 0x4e, 0xa9, 0x57, 0x52, 0x10, 0x03, 0x9a, 0x76, 0x28, }, }, assert.NoError}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ParsePrivateKey(tt.args.derBytes) tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } } func TestParsePrivateKey_marshal(t *testing.T) { modKey := func(fn func(key *TPMKey)) *TPMKey { fakeKey := &TPMKey{ Type: oidLoadableKey, EmptyAuth: true, Parent: 1234, PublicKey: []byte("pubkey"), PrivateKey: []byte("privkey"), } fn(fakeKey) return fakeKey } fakePolicy1 := TPMPolicy{CommandCode: 1, CommandPolicy: []byte("fake-policy-1")} fakePolicy2 := TPMPolicy{CommandCode: 2, CommandPolicy: []byte("fake-policy-2")} type args struct { key *TPMKey } tests := []struct { name string args args assertion assert.ErrorAssertionFunc }{ {"ok", args{&TPMKey{ Type: oidLoadableKey, EmptyAuth: true, Parent: 1234, PublicKey: []byte("pubkey"), PrivateKey: []byte("privkey"), }}, assert.NoError}, {"ok importable key", args{modKey(func(key *TPMKey) { key.Type = oidImportableKey })}, assert.NoError}, {"ok sealed key", args{modKey(func(key *TPMKey) { key.Type = oidSealedKey })}, assert.NoError}, {"ok emptyAuth false", args{modKey(func(key *TPMKey) { key.EmptyAuth = false })}, assert.NoError}, {"ok policy", args{modKey(func(key *TPMKey) { key.Policy = []TPMPolicy{fakePolicy1} })}, assert.NoError}, {"ok policies", args{modKey(func(key *TPMKey) { key.Policy = []TPMPolicy{fakePolicy1, fakePolicy2} })}, assert.NoError}, {"ok secret", args{modKey(func(key *TPMKey) { key.Secret = []byte("secret") })}, assert.NoError}, {"ok authPolicy", args{modKey(func(key *TPMKey) { key.AuthPolicy = []TPMAuthPolicy{ {Name: "auth", Policy: []TPMPolicy{fakePolicy1}}, } })}, assert.NoError}, {"ok authPolicies", args{modKey(func(key *TPMKey) { key.AuthPolicy = []TPMAuthPolicy{ {Name: "auth-1", Policy: []TPMPolicy{fakePolicy1}}, {Name: "auth-2", Policy: []TPMPolicy{fakePolicy1, fakePolicy2}}, } })}, assert.NoError}, {"ok all", args{&TPMKey{ Type: oidLoadableKey, EmptyAuth: true, Policy: []TPMPolicy{fakePolicy1, fakePolicy2}, Secret: []byte("secret"), AuthPolicy: []TPMAuthPolicy{ {Name: "auth-1", Policy: []TPMPolicy{fakePolicy1, fakePolicy2}}, {Name: "auth-2", Policy: []TPMPolicy{fakePolicy1, fakePolicy2}}, }, Parent: 1234, PublicKey: []byte("pubkey"), PrivateKey: []byte("privkey"), }}, assert.NoError}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { derBytes, err := MarshalPrivateKey(tt.args.key) if err != nil { t.Errorf("MarshalPrivateKey() error = %v", err) return } got, err := ParsePrivateKey(derBytes) tt.assertion(t, err) assert.Equal(t, tt.args.key, got) }) } } func TestParsePrivateKey_noEmptyAuth(t *testing.T) { type tpmKeyTest struct { Type asn1.ObjectIdentifier EmptyAuth *bool `asn1:"optional,explicit,tag:0"` Policy []TPMPolicy `asn1:"optional,explicit,tag:1"` Secret []byte `asn1:"optional,explicit,tag:2"` AuthPolicy []TPMAuthPolicy `asn1:"optional,explicit,tag:3"` Parent int PublicKey []byte PrivateKey []byte } type args struct { key tpmKeyTest } tests := []struct { name string args args wantPrefix []byte assertion assert.ErrorAssertionFunc }{ {"ok", args{tpmKeyTest{ Type: oidLoadableKey, Parent: 123, PublicKey: []byte("public key"), PrivateKey: []byte("private key"), }}, []byte{0x30, 0x24, 0x6, 0x6, 0x67, 0x81, 0x5, 0xa, 0x1, 0x3, 0x2, 0x01, 0x7b}, assert.NoError}, {"ok full", args{tpmKeyTest{ Type: oidImportableKey, Policy: []TPMPolicy{ {CommandCode: 1, CommandPolicy: []byte("fake-policy")}, }, Secret: []byte("secret"), AuthPolicy: []TPMAuthPolicy{{ Name: "auth", Policy: []TPMPolicy{{CommandCode: 1, CommandPolicy: []byte("fake-policy")}}, }}, Parent: 123, PublicKey: []byte("public key"), PrivateKey: []byte("private key"), }}, []byte{0x30, 0x70, 0x6, 0x6, 0x67, 0x81, 0x5, 0xa, 0x1, 0x4, 0xa1, 0x18, 0x30}, assert.NoError}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { key := tt.args.key derBytes, err := asn1.Marshal(key) require.NoError(t, err) prefix := derBytes[:len(tt.wantPrefix)] assert.Equal(t, tt.wantPrefix, prefix) got, err := ParsePrivateKey(derBytes) require.NoError(t, err) assert.Equal(t, &TPMKey{ Type: key.Type, EmptyAuth: false, Policy: key.Policy, Secret: key.Secret, AuthPolicy: key.AuthPolicy, Parent: key.Parent, PublicKey: key.PublicKey, PrivateKey: key.PrivateKey, }, got) }) } } func TestMarshalPrivateKey(t *testing.T) { type args struct { key *TPMKey } tests := []struct { name string args args want []byte assertion assert.ErrorAssertionFunc }{ {"ok", args{&TPMKey{ Type: oidLoadableKey, EmptyAuth: true, Parent: 1234, PublicKey: []byte("pubkey"), PrivateKey: []byte("privkey"), }}, []byte{ 0x30, 0x22, 0x6, 0x6, 0x67, 0x81, 0x5, 0xa, 0x1, 0x3, 0xa0, 0x3, 0x1, 0x1, 0xff, 0x2, 0x2, 0x4, 0xd2, 0x4, 0x6, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x4, 0x7, 0x70, 0x72, 0x69, 0x76, 0x6b, 0x65, 0x79, }, assert.NoError}, {"fail nil", args{nil}, nil, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := MarshalPrivateKey(tt.args.key) tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } } crypto-0.57.0/x25519/000077500000000000000000000000001474156331600140125ustar00rootroot00000000000000crypto-0.57.0/x25519/x25519.go000066400000000000000000000203571474156331600152250ustar00rootroot00000000000000package x25519 import ( "bytes" "crypto" "crypto/ed25519" "crypto/sha512" "crypto/subtle" "errors" "io" "strconv" "filippo.io/edwards25519" "filippo.io/edwards25519/field" "golang.org/x/crypto/curve25519" ) const ( // PrivateKeySize is the size in bytes of a X25519 private key. PrivateKeySize = 32 // PublicKeySize is the size in bytes of a X25519 public key. PublicKeySize = 32 SignatureSize = 64 ) var one = (&field.Element{}).One() // PrivateKey is the type used to represent a X25519 private key. type PrivateKey []byte // PublicKey is the type used to represent a X25519 public key. type PublicKey []byte // GenerateKey generates a public/private key pair using entropy from rand. func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) { priv := make([]byte, PrivateKeySize) if _, err := io.ReadFull(rand, priv); err != nil { return nil, nil, err } pub, err := curve25519.X25519(priv, curve25519.Basepoint) if err != nil { return nil, nil, err } return pub, priv, err } // ToEd25519 converts the public key p into a ed25519 key. // // (x, y) = (sqrt(-486664)*u/v, (u-1)/(u+1)) func (p PublicKey) ToEd25519() (ed25519.PublicKey, error) { a, err := convertMont(p) if err != nil { return nil, err } return a.Bytes(), nil } // Equal reports whether p and x have the same value. func (p PublicKey) Equal(x crypto.PublicKey) bool { xx, ok := x.(PublicKey) if !ok { return false } return bytes.Equal(p, xx) } // Public returns the public key using scalar multiplication (scalar * point) // using the Curve25519 basepoint. It will return nil if the private key is not // a valid one. func (p PrivateKey) Public() crypto.PublicKey { pub, _ := p.PublicKey() return pub } // Equal reports whether p and x have the same value. func (p PrivateKey) Equal(x crypto.PrivateKey) bool { xx, ok := x.(PrivateKey) if !ok { return false } return bytes.Equal(p, xx) } // Public returns the public key using scalar multiplication (scalar * point) // using the Curve25519 basepoint. func (p PrivateKey) PublicKey() (PublicKey, error) { pub, err := curve25519.X25519(p, curve25519.Basepoint) if err != nil { return nil, err } return pub, nil } // SharedKey returns the result of the scalar multiplication (scalar * point), // using the PrivateKey as the scalar value and the given key as the point. Both // scalar and point must be slices of 32 bytes. func (p PrivateKey) SharedKey(peerPublicKey []byte) ([]byte, error) { sharedKey, err := curve25519.X25519(p, peerPublicKey) if err != nil { return nil, err } return sharedKey, nil } // Sign signs the given message with the private key p and returns a signature. // // It implements the XEdDSA sign method defined in // https://signal.org/docs/specifications/xeddsa/#xeddsa // // XEdDSA performs two passes over messages to be signed and therefore cannot // handle pre-hashed messages. Thus opts.HashFunc() must return zero to indicate // the message hasn't been hashed. This can be achieved by passing // crypto.Hash(0) as the value for opts. func (p PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) { if opts.HashFunc() != crypto.Hash(0) { return nil, errors.New("x25519: cannot sign hashed message") } return Sign(rand, p, message) } // Sign signs the message with privateKey and returns a signature. It will panic // if len(privateKey) is not PrivateKeySize. // // It implements the XEdDSA sign method defined in // https://signal.org/docs/specifications/xeddsa/#xeddsa // // xeddsa_sign(k, M, Z): // A, a = calculate_key_pair(k) // r = hash1(a || M || Z) (mod q) // R = rB // h = hash(R || A || M) (mod q) // s = r + ha (mod q) // return R || s func Sign(rand io.Reader, p PrivateKey, message []byte) (signature []byte, err error) { if l := len(p); l != PrivateKeySize { panic("x25519: bad private key length: " + strconv.Itoa(l)) } pub, priv, err := p.calculateKeyPair() if err != nil { return nil, err } random := make([]byte, 64) if _, err := io.ReadFull(rand, random); err != nil { return nil, err } // Using same prefix in libsignal-protocol-c implementation, but can be any // 32 byte prefix. Golang's ed25519 implementation uses: // // ph := sha512.Sum512(a.Bytes()) // prefix := ph[32:] prefix := [32]byte{ 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, } rh := sha512.New() rh.Write(prefix[:]) rh.Write(priv.Bytes()) rh.Write(message) rh.Write(random) rDigest := make([]byte, 0, sha512.Size) rDigest = rh.Sum(rDigest) r, err := edwards25519.NewScalar().SetUniformBytes(rDigest) if err != nil { return nil, err } R := (&edwards25519.Point{}).ScalarBaseMult(r) //nolint:gocritic // variable names match crypto formulae docs hh := sha512.New() hh.Write(R.Bytes()) hh.Write(pub) hh.Write(message) hDigest := make([]byte, 0, sha512.Size) hDigest = hh.Sum(hDigest) h, err := edwards25519.NewScalar().SetUniformBytes(hDigest) if err != nil { return nil, err } s := (&edwards25519.Scalar{}).Add(r, h.Multiply(h, priv)) sig := make([]byte, 64) copy(sig[:32], R.Bytes()) copy(sig[32:], s.Bytes()) return sig, nil } // Verify reports whether sig is a valid signature of message by publicKey. It // will panic if len(publicKey) is not PublicKeySize. // // It implements the XEdDSA verify method defined in // https://signal.org/docs/specifications/xeddsa/#xeddsa // // xeddsa_verify(u, M, (R || s)): // if u >= 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) } crypto-0.57.0/x25519/x25519_test.go000066400000000000000000000470221474156331600162620ustar00rootroot00000000000000package 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) } }) } } func TestPublicKey_Equal(t *testing.T) { pub1, _, err := GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } pub2, _, err := GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } type args struct { x crypto.PublicKey } tests := []struct { name string p PublicKey args args want bool }{ {"true", pub1, args{pub1}, true}, {"false", pub1, args{pub2}, false}, {"false type", pub1, args{[]byte(pub1)}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.p.Equal(tt.args.x); got != tt.want { t.Errorf("PublicKey.Equal() = %v, want %v", got, tt.want) } }) } } func TestPrivateKey_Equal(t *testing.T) { _, priv1, err := GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } _, priv2, err := GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } type args struct { x crypto.PrivateKey } tests := []struct { name string p PrivateKey args args want bool }{ {"true", priv1, args{priv1}, true}, {"false", priv1, args{priv2}, false}, {"false type", priv1, args{[]byte(priv1)}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.p.Equal(tt.args.x); got != tt.want { t.Errorf("PrivateKey.Equal() = %v, want %v", got, tt.want) } }) } } crypto-0.57.0/x509util/000077500000000000000000000000001474156331600145405ustar00rootroot00000000000000crypto-0.57.0/x509util/algorithms.go000066400000000000000000000114451474156331600172450ustar00rootroot00000000000000package x509util import ( "crypto" "crypto/x509" "encoding/asn1" "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 ( oidSignatureMD2WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 2} oidSignatureMD5WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 4} oidSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5} oidSignatureSHA256WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11} oidSignatureSHA384WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12} oidSignatureSHA512WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13} oidSignatureRSAPSS = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10} oidSignatureDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 3} oidSignatureDSAWithSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 3, 2} oidSignatureECDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1} oidSignatureECDSAWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2} oidSignatureECDSAWithSHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3} oidSignatureECDSAWithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4} oidSignatureEd25519 = asn1.ObjectIdentifier{1, 3, 101, 112} // oidISOSignatureSHA1WithRSA means the same as oidSignatureSHA1WithRSA but // it's specified by ISO. Microsoft's makecert.exe has been known to produce // certificates with this OID. oidISOSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 29} ) var signatureAlgorithmMapping = []struct { name string value x509.SignatureAlgorithm oid asn1.ObjectIdentifier hash crypto.Hash }{ {"", x509.UnknownSignatureAlgorithm, nil, crypto.Hash(0)}, {MD2WithRSA, x509.MD2WithRSA, oidSignatureMD2WithRSA, crypto.Hash(0) /* no value for MD2 */}, {MD5WithRSA, x509.MD5WithRSA, oidSignatureMD5WithRSA, crypto.MD5}, {SHA1WithRSA, x509.SHA1WithRSA, oidSignatureSHA1WithRSA, crypto.SHA1}, {SHA1WithRSA, x509.SHA1WithRSA, oidISOSignatureSHA1WithRSA, crypto.SHA1}, {SHA256WithRSA, x509.SHA256WithRSA, oidSignatureSHA256WithRSA, crypto.SHA256}, {SHA384WithRSA, x509.SHA384WithRSA, oidSignatureSHA384WithRSA, crypto.SHA384}, {SHA512WithRSA, x509.SHA512WithRSA, oidSignatureSHA512WithRSA, crypto.SHA512}, {SHA256WithRSAPSS, x509.SHA256WithRSAPSS, oidSignatureRSAPSS, crypto.SHA256}, {SHA384WithRSAPSS, x509.SHA384WithRSAPSS, oidSignatureRSAPSS, crypto.SHA384}, {SHA512WithRSAPSS, x509.SHA512WithRSAPSS, oidSignatureRSAPSS, crypto.SHA512}, {DSAWithSHA1, x509.DSAWithSHA1, oidSignatureDSAWithSHA1, crypto.SHA1}, {DSAWithSHA256, x509.DSAWithSHA256, oidSignatureDSAWithSHA256, crypto.SHA256}, {ECDSAWithSHA1, x509.ECDSAWithSHA1, oidSignatureECDSAWithSHA1, crypto.SHA1}, {ECDSAWithSHA256, x509.ECDSAWithSHA256, oidSignatureECDSAWithSHA256, crypto.SHA256}, {ECDSAWithSHA384, x509.ECDSAWithSHA384, oidSignatureECDSAWithSHA384, crypto.SHA384}, {ECDSAWithSHA512, x509.ECDSAWithSHA512, oidSignatureECDSAWithSHA512, crypto.SHA512}, {PureEd25519, x509.PureEd25519, oidSignatureEd25519, crypto.Hash(0) /* no pre-hashing */}, } // 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) { switch s { case SignatureAlgorithm(x509.UnknownSignatureAlgorithm): return []byte(`""`), nil case SignatureAlgorithm(x509.MD2WithRSA): // removed from stdlib in https://github.com/golang/go/commit/c96159c25217c84a252be5d74d48861af715ecf8 return []byte(`"` + MD2WithRSA + `"`), nil default: 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) } crypto-0.57.0/x509util/algorithms_test.go000066400000000000000000000130621474156331600203010ustar00rootroot00000000000000package 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) } }) } } crypto-0.57.0/x509util/certificate.go000066400000000000000000000213061474156331600173530ustar00rootroot00000000000000// Package x509util implements utilities to build X.509 certificates based on // JSON templates. package x509util import ( "crypto" "crypto/rand" "crypto/x509" "encoding/json" "time" "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"` RawSubject []byte `json:"rawSubject"` 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"` NotBefore time.Time `json:"notBefore"` NotAfter time.Time `json:"notAfter"` 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.CertificateRequest and // will apply 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 } return newCertificateWithOptions(cr, o) } // NewCertificateFromX509 creates a new Certificate from an x509.Certificate and // will apply template options. A new (unsigned) x509.CertificateRequest is created, // with data from the x509.Certificate template. This function is primarily useful // when signing a certificate for a key that can't sign a CSR or when the private // key is not available. func NewCertificateFromX509(template *x509.Certificate, opts ...Option) (*Certificate, error) { // Copy data from the template to a new, unsigned CSR. csr := &x509.CertificateRequest{ PublicKey: template.PublicKey, PublicKeyAlgorithm: template.PublicKeyAlgorithm, Subject: template.Subject, DNSNames: template.DNSNames, EmailAddresses: template.EmailAddresses, IPAddresses: template.IPAddresses, URIs: template.URIs, Extensions: template.ExtraExtensions, } o, err := new(Options).apply(csr, opts) if err != nil { return nil, err } return newCertificateWithOptions(csr, o) } // newCertificateWithOptions creates a new Certificate from an x509.CertificateRequest // with options applied. If no template was applied, the data from the x509.CertificateRequest // will simply be copied over and returned with the default leaf key usages. Otherwise, the // data from the template will be filled in. func newCertificateWithOptions(csr *x509.CertificateRequest, o *Options) (*Certificate, error) { // If no template is set, use only the certificate request with the // default leaf key usages. if o.CertBuffer == nil { return NewCertificateRequestFromX509(csr).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") } // Enforce the public key cert.PublicKey = csr.PublicKey cert.PublicKeyAlgorithm = csr.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 cert.RawSubject = c.RawSubject // 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) } // Validity bounds. cert.NotBefore = c.NotBefore cert.NotAfter = c.NotAfter // 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 } crypto-0.57.0/x509util/certificate_request.go000066400000000000000000000275571474156331600211410ustar00rootroot00000000000000package x509util import ( "crypto" "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/json" "github.com/pkg/errors" "golang.org/x/crypto/cryptobyte" cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1" ) var ( oidExtensionSubjectAltName = []int{2, 5, 29, 17} oidChallengePassword = []int{1, 2, 840, 113549, 1, 9, 7} ) type publicKeyInfo struct { Raw asn1.RawContent Algorithm pkix.AlgorithmIdentifier PublicKey asn1.BitString } type tbsCertificateRequest struct { Raw asn1.RawContent Version int Subject asn1.RawValue PublicKey publicKeyInfo RawAttributes []asn1.RawValue `asn1:"tag:0"` } type certificateRequest struct { Raw asn1.RawContent TBSCSR tbsCertificateRequest SignatureAlgorithm pkix.AlgorithmIdentifier SignatureValue asn1.BitString } // 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"` RawSubject []byte `json:"rawSubject"` 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"` ChallengePassword string `json:"-"` 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), RawSubject: cr.RawSubject, 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 signed x509.CertificateRequest. func (c *CertificateRequest) GetCertificateRequest() (*x509.CertificateRequest, error) { cert := c.GetCertificate().GetCertificate() asn1Data, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{ Subject: cert.Subject, RawSubject: cert.RawSubject, 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") } // If a challenge password is provided, encode and prepend it as a challenge // password attribute. // // The challengePassword attribute doesn't follow the ASN.1 encoding of // [pkix.AttributeTypeAndValueSET] used in the deprecated // [x509.CertificateRequest.Attributes], so this requires some low-level // ASN.1 operations. if c.ChallengePassword != "" { asn1Data, err = c.addChallengePassword(asn1Data) if err != nil { return nil, err } } // This should not fail return x509.ParseCertificateRequest(asn1Data) } // addChallengePassword unmarshals the asn1Data into a certificateRequest and // creates a new one with the challengePassword. func (c *CertificateRequest) addChallengePassword(asn1Data []byte) ([]byte, error) { // Build challengePassword attribute (RFC 2985 section-5.4). The resulting // bytes will be added as an asn1.RawValue in the RawAttributes. var builder cryptobyte.Builder builder.AddASN1(cryptobyte_asn1.SEQUENCE, func(child *cryptobyte.Builder) { child.AddASN1ObjectIdentifier(oidChallengePassword) child.AddASN1(cryptobyte_asn1.SET, func(value *cryptobyte.Builder) { switch { case isPrintableString(c.ChallengePassword, true, true): value.AddASN1(cryptobyte_asn1.PrintableString, func(s *cryptobyte.Builder) { s.AddBytes([]byte(c.ChallengePassword)) }) case isUTF8String(c.ChallengePassword): value.AddASN1(cryptobyte_asn1.UTF8String, func(s *cryptobyte.Builder) { s.AddBytes([]byte(c.ChallengePassword)) }) default: value.SetError(errors.New("error marshaling challenge password: password is not valid")) } }) }) b, err := builder.Bytes() if err != nil { return nil, errors.Wrap(err, "error marshaling challenge password") } challengePasswordAttr := asn1.RawValue{ FullBytes: b, } // Parse certificate request var csr certificateRequest rest, err := asn1.Unmarshal(asn1Data, &csr) if err != nil { return nil, err } else if len(rest) != 0 { return nil, errors.New("error unmarshalling certificate request: trailing data") } sigAlgo := csr.SignatureAlgorithm tbsCSR := tbsCertificateRequest{ Version: csr.TBSCSR.Version, Subject: csr.TBSCSR.Subject, PublicKey: csr.TBSCSR.PublicKey, RawAttributes: csr.TBSCSR.RawAttributes, } // Prepend challengePassword attribute tbsCSR.RawAttributes = append([]asn1.RawValue{challengePasswordAttr}, tbsCSR.RawAttributes...) // Marshal tbsCertificateRequest tbsCSRContents, err := asn1.Marshal(tbsCSR) if err != nil { return nil, errors.Wrap(err, "error creating certificate request") } tbsCSR.Raw = tbsCSRContents // Get the hash used previously var hashFunc crypto.Hash found := false sigAlgoOID := sigAlgo.Algorithm for _, m := range signatureAlgorithmMapping { if sigAlgoOID.Equal(m.oid) { hashFunc = m.hash found = true break } } if !found { return nil, errors.Errorf("error creating certificate request: unsupported signature algorithm %s", sigAlgoOID) } // Sign tbsCertificateRequest signed := tbsCSRContents if hashFunc != 0 { h := hashFunc.New() h.Write(signed) signed = h.Sum(nil) } var signature []byte signature, err = c.Signer.Sign(rand.Reader, signed, hashFunc) if err != nil { return nil, errors.Wrap(err, "error creating certificate request") } // Build new certificate request and marshal asn1Data, err = asn1.Marshal(certificateRequest{ TBSCSR: tbsCSR, SignatureAlgorithm: sigAlgo, SignatureValue: asn1.BitString{ Bytes: signature, BitLength: len(signature) * 8, }, }) if err != nil { return nil, errors.Wrap(err, "error creating certificate request") } return asn1Data, nil } // 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 } } } } crypto-0.57.0/x509util/certificate_request_test.go000066400000000000000000000664111474156331600221700ustar00rootroot00000000000000package x509util import ( "crypto" "crypto/ed25519" "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/base64" "encoding/pem" "fmt" "net" "net/url" "os" "reflect" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) 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_GetCertificateRequest_challengePassword(t *testing.T) { rsaPEM, err := os.ReadFile("testdata/rsa.key") require.NoError(t, err) block, _ := pem.Decode(rsaPEM) rsaKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) require.NoError(t, err) expectedPrintableString, err := os.ReadFile("testdata/challengePassword.csr") require.NoError(t, err) expectedUTF8String, err := os.ReadFile("testdata/challengePasswordUTF8.csr") require.NoError(t, err) tests := []struct { name string cr *CertificateRequest want []byte assertion assert.ErrorAssertionFunc }{ {"ok", &CertificateRequest{ 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"}}, ChallengePassword: "challengePassword", Extensions: []Extension{{ID: []int{1, 2, 3, 4}, Critical: true, Value: []byte("foobar")}}, SignatureAlgorithm: SignatureAlgorithm(x509.SHA256WithRSA), Signer: rsaKey, }, expectedPrintableString, assert.NoError}, {"ok UTF8String", &CertificateRequest{ 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"}}, ChallengePassword: "๐Ÿ”", Extensions: []Extension{{ID: []int{1, 2, 3, 4}, Critical: true, Value: []byte("foobar")}}, SignatureAlgorithm: SignatureAlgorithm(x509.SHA256WithRSA), Signer: rsaKey, }, expectedUTF8String, assert.NoError}, {"fail challengePassword", &CertificateRequest{ 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"}}, ChallengePassword: "\x91\x80\x80\x80", Extensions: []Extension{{ID: []int{1, 2, 3, 4}, Critical: true, Value: []byte("foobar")}}, SignatureAlgorithm: SignatureAlgorithm(x509.SHA256WithRSA), Signer: rsaKey, }, nil, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { csr, err := tt.cr.GetCertificateRequest() tt.assertion(t, err) if tt.want == nil { assert.Nil(t, csr) } else { assert.Equal(t, tt.want, pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE REQUEST", Bytes: csr.Raw, })) } }) } } func TestCertificateRequest_addChallengePassword(t *testing.T) { rsaPEM, err := os.ReadFile("testdata/rsa.key") require.NoError(t, err) block, _ := pem.Decode(rsaPEM) rsaKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) require.NoError(t, err) base := &CertificateRequest{ 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.SHA256WithRSA), Signer: rsaKey, } csr, err := base.GetCertificateRequest() require.NoError(t, err) var cr certificateRequest _, err = asn1.Unmarshal(csr.Raw, &cr) require.NoError(t, err) cr.Raw = nil cr.TBSCSR.Raw = nil cr.SignatureAlgorithm = pkix.AlgorithmIdentifier{ Algorithm: []int{1, 2, 3, 4}, } failSignatureAlgorithm, err := asn1.Marshal(cr) require.NoError(t, err) b, err := os.ReadFile("testdata/challengePassword.csr") require.NoError(t, err) block, _ = pem.Decode(b) expectedPrintableString := block.Bytes b, err = os.ReadFile("testdata/challengePasswordUTF8.csr") require.NoError(t, err) block, _ = pem.Decode(b) expectedUTF8String := block.Bytes type args struct { asn1Data []byte } tests := []struct { name string cr *CertificateRequest args args want []byte assertion assert.ErrorAssertionFunc }{ {"ok", &CertificateRequest{ Subject: base.Subject, DNSNames: base.DNSNames, EmailAddresses: base.EmailAddresses, IPAddresses: base.IPAddresses, URIs: base.URIs, SANs: base.SANs, ChallengePassword: "challengePassword", Extensions: base.Extensions, SignatureAlgorithm: base.SignatureAlgorithm, Signer: base.Signer, }, args{csr.Raw}, expectedPrintableString, assert.NoError}, {"ok UTF8String", &CertificateRequest{ Subject: base.Subject, DNSNames: base.DNSNames, EmailAddresses: base.EmailAddresses, IPAddresses: base.IPAddresses, URIs: base.URIs, SANs: base.SANs, ChallengePassword: "๐Ÿ”", Extensions: base.Extensions, SignatureAlgorithm: base.SignatureAlgorithm, Signer: base.Signer, }, args{csr.Raw}, expectedUTF8String, assert.NoError}, {"fail challengePassword", &CertificateRequest{ Subject: base.Subject, DNSNames: base.DNSNames, EmailAddresses: base.EmailAddresses, IPAddresses: base.IPAddresses, URIs: base.URIs, SANs: base.SANs, ChallengePassword: "\x91\x80\x80\x80", Extensions: base.Extensions, SignatureAlgorithm: base.SignatureAlgorithm, Signer: base.Signer, }, args{csr.Raw}, nil, assert.Error}, {"fail unmarshal", &CertificateRequest{ Subject: base.Subject, DNSNames: base.DNSNames, EmailAddresses: base.EmailAddresses, IPAddresses: base.IPAddresses, URIs: base.URIs, SANs: base.SANs, ChallengePassword: "challengePassword", Extensions: base.Extensions, SignatureAlgorithm: base.SignatureAlgorithm, Signer: base.Signer, }, args{[]byte("not ans1")}, nil, assert.Error}, {"fail unmarshal rest", &CertificateRequest{ Subject: base.Subject, DNSNames: base.DNSNames, EmailAddresses: base.EmailAddresses, IPAddresses: base.IPAddresses, URIs: base.URIs, SANs: base.SANs, ChallengePassword: "challengePassword", Extensions: base.Extensions, SignatureAlgorithm: base.SignatureAlgorithm, Signer: base.Signer, }, args{append(csr.Raw, []byte("some extra data")...)}, nil, assert.Error}, {"fail signatureAlgorithm", &CertificateRequest{ Subject: base.Subject, DNSNames: base.DNSNames, EmailAddresses: base.EmailAddresses, IPAddresses: base.IPAddresses, URIs: base.URIs, SANs: base.SANs, ChallengePassword: "challengePassword", Extensions: base.Extensions, SignatureAlgorithm: base.SignatureAlgorithm, Signer: base.Signer, }, args{failSignatureAlgorithm}, nil, assert.Error}, {"fail sign", &CertificateRequest{ Subject: base.Subject, DNSNames: base.DNSNames, EmailAddresses: base.EmailAddresses, IPAddresses: base.IPAddresses, URIs: base.URIs, SANs: base.SANs, ChallengePassword: "challengePassword", Extensions: base.Extensions, SignatureAlgorithm: base.SignatureAlgorithm, Signer: &badSigner{}, }, args{csr.Raw}, nil, assert.Error}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.cr.addChallengePassword(tt.args.asn1Data) tt.assertion(t, err) assert.Equal(t, tt.want, got) }) } } 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) } }) } } crypto-0.57.0/x509util/certificate_test.go000066400000000000000000001127621474156331600204210ustar00rootroot00000000000000package x509util import ( "bytes" "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/pem" "fmt" "io" "math/big" "net" "net/url" "reflect" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.step.sm/crypto/pemutil" ) 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 readCertificateRequest(t *testing.T, filename, keyFilename string) (*x509.CertificateRequest, crypto.Signer) { t.Helper() cr, err := pemutil.ReadCertificateRequest(filename) require.NoError(t, err) key, err := pemutil.Read(keyFilename) require.NoError(t, err) signer, ok := key.(crypto.Signer) require.True(t, ok) return cr, signer } 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(_ io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { return nil, fmt.Errorf("๐Ÿ’ฅ") } func TestNewCertificate(t *testing.T) { now := time.Now().UTC().Truncate(time.Second) 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 } rawSubjectCR, rawSubjectKey := readCertificateRequest(t, "testdata/rawSubject.csr", "testdata/rawSubject.key") 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", "nbf": now.Unix(), }, WebhooksKey: map[string]interface{}{ "Test": map[string]interface{}{ "notAfter": now.Add(10 * time.Hour).Format(time.RFC3339), }, }, })}}, &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"}}, NotBefore: now, NotAfter: now.Add(10 * time.Hour), 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"}}, NotBefore: time.Unix(1234567890, 0).UTC(), NotAfter: time.Unix(1234654290, 0).UTC(), 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}, {"okRawSubject", args{rawSubjectCR, []Option{WithTemplateFile("./testdata/rawSubject.tpl", TemplateData{ SANsKey: []SubjectAlternativeName{ {Type: "dns", Value: "foo.com"}, }, CertificateRequestKey: NewCertificateRequestFromX509(rawSubjectCR), })}}, &Certificate{ Subject: Subject{}, RawSubject: []byte{ 0x30, 0x68, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72, 0x6e, 0x69, 0x61, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x0d, 0x53, 0x61, 0x6e, 0x20, 0x46, 0x72, 0x61, 0x6e, 0x63, 0x69, 0x73, 0x63, 0x6f, 0x31, 0x1d, 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x14, 0x53, 0x6d, 0x61, 0x6c, 0x6c, 0x73, 0x74, 0x65, 0x70, 0x20, 0x4c, 0x61, 0x62, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x04, 0x54, 0x65, 0x73, 0x74, }, SANs: []SubjectAlternativeName{{Type: DNSType, Value: "foo.com"}}, KeyUsage: KeyUsage(x509.KeyUsageDigitalSignature), ExtKeyUsage: ExtKeyUsage([]x509.ExtKeyUsage{ x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth, }), PublicKey: rawSubjectKey.Public(), PublicKeyAlgorithm: x509.ECDSA, }, 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 TestNewCertificateTemplate(t *testing.T) { marshal := func(t *testing.T, value interface{}, params string) []byte { t.Helper() b, err := asn1.MarshalWithParams(value, params) assert.NoError(t, err) return b } tpl := `{ "subject": {{ set (toJson .Subject | fromJson) "extraNames" (list (dict "type" "1.2.840.113556.1.4.656" "value" .Token.upn )) | toJson }}, "sans": {{ concat .SANs (list (dict "type" "dn" "value" ` + "`" + `{"country":"US","organization":"ACME","commonName":"rocket"}` + "`" + `) (dict "type" "permanentIdentifier" "value" .Token.pi) (dict "type" "hardwareModuleName" "value" .Insecure.User.hmn) (dict "type" "userPrincipalName" "value" .Token.upn) (dict "type" "1.2.3.4" "value" (printf "int:%s" .Insecure.User.id)) ) | toJson }}, "notBefore": "{{ .Token.nbf | formatTime }}", "notAfter": {{ now | dateModify "24h" | toJson }}, {{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }} "keyUsage": ["keyEncipherment", "digitalSignature"], {{- else }} "keyUsage": ["digitalSignature"], {{- end }} "extKeyUsage": ["serverAuth", "clientAuth"], "extensions": [ {"id": "1.2.3.4", "value": {{ asn1Enc (first .Insecure.CR.DNSNames) | toJson }}}, {"id": "1.2.3.5", "value": {{ asn1Marshal (first .Insecure.CR.DNSNames) | toJson }}}, {"id": "1.2.3.6", "value": {{ asn1Seq (asn1Enc (first .Insecure.CR.DNSNames)) (asn1Enc "int:123456") | toJson }}}, {"id": "1.2.3.7", "value": {{ asn1Set (asn1Marshal (first .Insecure.CR.DNSNames) "utf8") (asn1Enc "int:123456") | toJson }}} ] }` // Regular sans sans := []string{"foo.com", "www.foo.com", "root@foo.com"} // Template data data := CreateTemplateData("commonName", sans) data.SetUserData(map[string]any{ "id": "123456", "hmn": `{"type":"1.2.3.1", "serialNumber": "MTIzNDU2"}`, }) data.SetToken(map[string]any{ "upn": "foo@upn.com", "pi": "0123456789", "nbf": time.Now().Unix(), }) iss, issPriv := createIssuerCertificate(t, "issuer") cr, priv := createCertificateRequest(t, "commonName", sans) now := time.Now().Truncate(time.Second) cert, err := NewCertificate(cr, WithTemplate(tpl, data)) require.NoError(t, err) crt, err := CreateCertificate(cert.GetCertificate(), iss, priv.Public(), issPriv) require.NoError(t, err) // Create expected subject assert.Equal(t, pkix.Name{ CommonName: "commonName", Names: []pkix.AttributeTypeAndValue{ {Type: asn1.ObjectIdentifier{2, 5, 4, 3}, Value: "commonName"}, {Type: asn1.ObjectIdentifier{1, 2, 840, 113556, 1, 4, 656}, Value: "foo@upn.com"}, }, }, crt.Subject) assert.WithinDuration(t, now, crt.NotBefore, 2*time.Second) assert.WithinDuration(t, now.Add(24*time.Hour), crt.NotAfter, 2*time.Second) // Create expected SAN extension var rawValues []asn1.RawValue for _, san := range []SubjectAlternativeName{ {Type: DNSType, Value: "foo.com"}, {Type: DNSType, Value: "www.foo.com"}, {Type: EmailType, Value: "root@foo.com"}, {Type: DirectoryNameType, ASN1Value: []byte(`{"country":"US","organization":"ACME","commonName":"rocket"}`)}, {Type: PermanentIdentifierType, Value: "0123456789"}, {Type: HardwareModuleNameType, ASN1Value: []byte(`{"type":"1.2.3.1", "serialNumber": "MTIzNDU2"}`)}, {Type: UserPrincipalNameType, Value: "foo@upn.com"}, {Type: "1.2.3.4", Value: "int:123456"}, } { rawValue, err := san.RawValue() require.NoError(t, err) rawValues = append(rawValues, rawValue) } rawBytes, err := asn1.Marshal(rawValues) require.NoError(t, err) var found int for _, ext := range crt.Extensions { switch { case ext.Id.Equal(oidExtensionSubjectAltName): assert.Equal(t, pkix.Extension{ Id: oidExtensionSubjectAltName, Value: rawBytes, }, ext) case ext.Id.Equal([]int{1, 2, 3, 4}): assert.Equal(t, pkix.Extension{ Id: ext.Id, Value: marshal(t, "foo.com", "printable"), }, ext) case ext.Id.Equal([]int{1, 2, 3, 5}): assert.Equal(t, pkix.Extension{ Id: ext.Id, Value: marshal(t, "foo.com", ""), }, ext) case ext.Id.Equal([]int{1, 2, 3, 6}): assert.Equal(t, pkix.Extension{ Id: ext.Id, Value: marshal(t, []any{"foo.com", 123456}, ""), }, ext) case ext.Id.Equal([]int{1, 2, 3, 7}): assert.Equal(t, pkix.Extension{ Id: ext.Id, Value: marshal(t, struct { String string `asn1:"utf8"` Int int }{"foo.com", 123456}, "set"), }, ext) default: continue } found++ } assert.Equal(t, 5, found, "some of the expected extension where not found") } func TestNewCertificateFromX509(t *testing.T) { now := time.Now().UTC().Truncate(time.Second) priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) template := &x509.Certificate{ // similar template as the certificate request for TestNewCertificate PublicKey: priv.Public(), PublicKeyAlgorithm: x509.ECDSA, Subject: pkix.Name{CommonName: "commonName"}, DNSNames: []string{"foo.com"}, EmailAddresses: []string{"root@foo.com"}, } 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) require.NoError(t, err) return ipNet } type args struct { template *x509.Certificate opts []Option } tests := []struct { name string args args want *Certificate wantErr bool }{ {"okSimple", args{template, 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(template.Extensions), PublicKey: priv.Public(), PublicKeyAlgorithm: x509.ECDSA, SignatureAlgorithm: SignatureAlgorithm(x509.UnknownSignatureAlgorithm), }, false}, {"okDefaultTemplate", args{template, []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.ECDSA, }, false}, {"okCustomSANs", args{template, []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.ECDSA, }, false}, {"okExample", args{template, []Option{WithTemplateFile("./testdata/example.tpl", TemplateData{ SANsKey: []SubjectAlternativeName{ {Type: "dns", Value: "foo.com"}, }, TokenKey: map[string]interface{}{ "iss": "https://iss", "sub": "sub", }, WebhooksKey: map[string]interface{}{ "Test": map[string]interface{}{ "notAfter": now.Add(10 * time.Hour).Format(time.RFC3339), }, }, })}}, &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"}}, NotBefore: now, NotAfter: now.Add(10 * time.Hour), KeyUsage: KeyUsage(x509.KeyUsageDigitalSignature), ExtKeyUsage: ExtKeyUsage([]x509.ExtKeyUsage{ x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth, }), PublicKey: priv.Public(), PublicKeyAlgorithm: x509.ECDSA, }, false}, {"okFullSimple", args{template, []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"}}, NotBefore: time.Unix(1234567890, 0).UTC(), NotAfter: time.Unix(1234654290, 0).UTC(), 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.ECDSA, }, false}, {"failTemplate", args{template, []Option{WithTemplate(`{{ fail "fatal error }}`, CreateTemplateData("commonName", []string{"foo.com"}))}}, nil, true}, {"missingTemplate", args{template, []Option{WithTemplateFile("./testdata/missing.tpl", CreateTemplateData("commonName", []string{"foo.com"}))}}, nil, true}, {"badJson", args{template, []Option{WithTemplate(`"this is not a json object"`, CreateTemplateData("commonName", []string{"foo.com"}))}}, nil, true}, {"failCustomSANs", args{template, []Option{WithTemplate(DefaultLeafTemplate, badCustomSANsData)}}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := NewCertificateFromX509(tt.args.template, tt.args.opts...) if tt.wantErr { assert.Error(t, err) return } assert.NoError(t, err) assert.Equal(t, tt.want, got) }) } } func TestCertificate_GetCertificate(t *testing.T) { now := time.Now() type fields struct { Version int Subject Subject Issuer Issuer SerialNumber SerialNumber DNSNames MultiString EmailAddresses MultiString IPAddresses MultiIP URIs MultiURL SANs []SubjectAlternativeName NotBefore time.Time NotAfter time.Time 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"}, }, NotBefore: now, NotAfter: time.Time{}, 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), NotBefore: now, NotAfter: time.Time{}, 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, NotBefore: tt.fields.NotBefore, NotAfter: tt.fields.NotAfter, 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, })) }) } } crypto-0.57.0/x509util/certpool.go000066400000000000000000000022421474156331600167160ustar00rootroot00000000000000package 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 } crypto-0.57.0/x509util/certpool_test.go000066400000000000000000000033221474156331600177550ustar00rootroot00000000000000package 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) } } }) } } crypto-0.57.0/x509util/extensions.go000066400000000000000000001221571474156331600172760ustar00rootroot00000000000000package x509util import ( "bytes" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/base64" "encoding/json" "fmt" "math/big" "net" "net/url" "strconv" "strings" "time" "github.com/pkg/errors" ) func convertName(s string) string { return strings.ReplaceAll(strings.ToLower(s), "_", "") } // Names used for key usages. const ( KeyUsageDigitalSignature = "digitalSignature" KeyUsageContentCommitment = "contentCommitment" KeyUsageKeyEncipherment = "keyEncipherment" KeyUsageDataEncipherment = "dataEncipherment" KeyUsageKeyAgreement = "keyAgreement" KeyUsageCertSign = "certSign" KeyUsageCRLSign = "crlSign" KeyUsageEncipherOnly = "encipherOnly" KeyUsageDecipherOnly = "decipherOnly" ) // Names used for extended key usages. const ( ExtKeyUsageAny = "any" ExtKeyUsageServerAuth = "serverAuth" ExtKeyUsageClientAuth = "clientAuth" ExtKeyUsageCodeSigning = "codeSigning" ExtKeyUsageEmailProtection = "emailProtection" ExtKeyUsageIPSECEndSystem = "ipsecEndSystem" ExtKeyUsageIPSECTunnel = "ipsecTunnel" ExtKeyUsageIPSECUser = "ipsecUser" ExtKeyUsageTimeStamping = "timeStamping" ExtKeyUsageOCSPSigning = "ocspSigning" ExtKeyUsageMicrosoftServerGatedCrypto = "microsoftServerGatedCrypto" ExtKeyUsageNetscapeServerGatedCrypto = "netscapeServerGatedCrypto" ExtKeyUsageMicrosoftCommercialCodeSigning = "microsoftCommercialCodeSigning" ExtKeyUsageMicrosoftKernelCodeSigning = "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" UserPrincipalNameType = "userPrincipalName" ) //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 = ":" // User Principal Name or UPN is a subject alternative name used for smart card // logon. This OID is associated with Microsoft cryptography and has the // internal name of szOID_NT_PRINCIPAL_NAME. // // The UPN is defined in Microsoft Open Specifications and Windows client // documentation for IT Pros: // - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/ea9ef420-4cbf-44bc-b093-c4175139f90f // - https://learn.microsoft.com/en-us/windows/security/identity-protection/smart-cards/smart-card-certificate-requirements-and-enumeration var oidUserPrincipalName = []int{1, 3, 6, 1, 4, 1, 311, 20, 2, 3} // 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) asn1Type() 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) asn1Type() 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: empty 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.asn1Type()) if err != nil { return zero, errors.Wrap(err, "error marshaling PermanentIdentifier SAN") } return otherName, nil case HardwareModuleNameType: var data []byte switch { case len(s.ASN1Value) != 0: data = s.ASN1Value case s.Value != "": data = []byte(s.Value) default: return zero, errors.New("error parsing HardwareModuleName SAN: empty value or asn1Value is not allowed") } var v HardwareModuleName if err := json.Unmarshal(data, &v); err != nil { return zero, errors.Wrap(err, "error unmarshaling HardwareModuleName SAN") } otherName, err := marshalOtherName(oidHardwareModuleNameIdentifier, v.asn1Type()) if err != nil { return zero, errors.Wrap(err, "error marshaling HardwareModuleName SAN") } return otherName, nil case DirectoryNameType: var data []byte switch { case len(s.ASN1Value) != 0: data = s.ASN1Value case s.Value != "": data = []byte(s.Value) default: return zero, errors.New("error parsing DirectoryName SAN: empty value or asn1Value is not allowed") } var dn Name if err := json.Unmarshal(data, &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 asn1Value") } return asn1.RawValue{ Class: asn1.ClassContextSpecific, Tag: nameTypeDirectoryName, IsCompound: true, Bytes: rdn, }, nil case UserPrincipalNameType: if s.Value == "" { return zero, errors.New("error parsing UserPrincipalName SAN: empty value is not allowed") } rawBytes, err := marshalExplicitValue(s.Value, "utf8") if err != nil { return zero, errors.Wrapf(err, "error marshaling ASN1 value %q", s.Value) } upnBytes, err := asn1.MarshalWithParams(otherName{ TypeID: oidUserPrincipalName, Value: asn1.RawValue{FullBytes: rawBytes}, }, "tag:0") if err != nil { return zero, errors.Wrap(err, "error marshaling UserPrincipalName SAN") } return asn1.RawValue{FullBytes: upnBytes}, 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. 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, "error marshaling 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 } type asn1Params struct { Type string Params string } func parseFieldParameters(str string) (p asn1Params) { var part string var params []string for str != "" { part, str, _ = strings.Cut(str, ",") switch part { // string types case "utf8", "ia5", "numeric", "printable": p.Type = part params = append(params, part) // types that are parsed from the string. // int, oid, and bool are not a type that can be set in a tag. case "int", "oid", "bool", "boolean": p.Type = part // types parsed from the string as a time case "utc", "generalized": p.Type = part params = append(params, part) // base64 encoded asn1 value case "raw": p.Type = part case "": // skip default: params = append(params, part) } } p.Params = strings.Join(params, ",") return p } // marshalValue marshals the given value with the given params. // // The return value value can be any type depending on the OID. ASN supports a // great number of formats, but Golang's asn1 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 marshalValue(value, params string) ([]byte, error) { p := parseFieldParameters(params) // Marshal types without a tag support. switch p.Type { case "int": i, err := strconv.Atoi(value) if err != nil { return nil, errors.Wrap(err, "invalid int value") } return asn1.MarshalWithParams(i, p.Params) case "oid": oid, err := parseObjectIdentifier(value) if err != nil { return nil, errors.Wrap(err, "invalid oid value") } return asn1.MarshalWithParams(oid, p.Params) 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, p.Params) case "ia5": if !isIA5String(value) { return nil, fmt.Errorf("invalid ia5 value") } return asn1.MarshalWithParams(value, p.Params) case "numeric": if !isNumericString(value) { return nil, fmt.Errorf("invalid numeric value") } return asn1.MarshalWithParams(value, p.Params) case "printable": if !isPrintableString(value, true, true) { return nil, fmt.Errorf("invalid printable value") } return asn1.MarshalWithParams(value, p.Params) case "utc", "generalized": // This is the layout of Time.String() function const defaultLayout = "2006-01-02 15:04:05.999999999 -0700 MST" t, err := time.Parse(defaultLayout, value) if err != nil { var err2 error if t, err2 = time.Parse(time.RFC3339, value); err2 != nil { return nil, errors.Wrapf(err, "invalid %s value", p.Type) } } return asn1.MarshalWithParams(t, p.Params) case "bool", "boolean": b, err := strconv.ParseBool(value) if err != nil { return nil, errors.Wrap(err, "invalid bool value") } return asn1.MarshalWithParams(b, p.Params) default: // if it's an unknown type, default to printable if !isPrintableString(value, true, true) { return nil, fmt.Errorf("invalid printable value") } return asn1.MarshalWithParams(value, p.Params) } } // marshalExplicitValue marshals the given value with given type and returns the // raw bytes to use. It will add the explicit tag to the final parameters. func marshalExplicitValue(value, typ string) ([]byte, error) { return marshalValue(value, "explicit,"+typ) } // 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 convertName(KeyUsageDigitalSignature): ku = x509.KeyUsageDigitalSignature case convertName(KeyUsageContentCommitment): ku = x509.KeyUsageContentCommitment case convertName(KeyUsageKeyEncipherment): ku = x509.KeyUsageKeyEncipherment case convertName(KeyUsageDataEncipherment): ku = x509.KeyUsageDataEncipherment case convertName(KeyUsageKeyAgreement): ku = x509.KeyUsageKeyAgreement case convertName(KeyUsageCertSign): ku = x509.KeyUsageCertSign case convertName(KeyUsageCRLSign): ku = x509.KeyUsageCRLSign case convertName(KeyUsageEncipherOnly): ku = x509.KeyUsageEncipherOnly case convertName(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 convertName(ExtKeyUsageAny): ku = x509.ExtKeyUsageAny case convertName(ExtKeyUsageServerAuth): ku = x509.ExtKeyUsageServerAuth case convertName(ExtKeyUsageClientAuth): ku = x509.ExtKeyUsageClientAuth case convertName(ExtKeyUsageCodeSigning): ku = x509.ExtKeyUsageCodeSigning case convertName(ExtKeyUsageEmailProtection): ku = x509.ExtKeyUsageEmailProtection case convertName(ExtKeyUsageIPSECEndSystem): ku = x509.ExtKeyUsageIPSECEndSystem case convertName(ExtKeyUsageIPSECTunnel): ku = x509.ExtKeyUsageIPSECTunnel case convertName(ExtKeyUsageIPSECUser): ku = x509.ExtKeyUsageIPSECUser case convertName(ExtKeyUsageTimeStamping): ku = x509.ExtKeyUsageTimeStamping case convertName(ExtKeyUsageOCSPSigning): ku = x509.ExtKeyUsageOCSPSigning case convertName(ExtKeyUsageMicrosoftServerGatedCrypto): ku = x509.ExtKeyUsageMicrosoftServerGatedCrypto case convertName(ExtKeyUsageNetscapeServerGatedCrypto): ku = x509.ExtKeyUsageNetscapeServerGatedCrypto case convertName(ExtKeyUsageMicrosoftCommercialCodeSigning): ku = x509.ExtKeyUsageMicrosoftCommercialCodeSigning case convertName(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 } // SubjectAlternativeNames is a container for names extracted // from the X.509 Subject Alternative Names extension. type SubjectAlternativeNames struct { DNSNames []string EmailAddresses []string IPAddresses []net.IP URIs []*url.URL PermanentIdentifiers []PermanentIdentifier HardwareModuleNames []HardwareModuleName TPMHardwareDetails TPMHardwareDetails // OtherNames []OtherName // TODO(hs): unused at the moment; do we need it? what type definition to use? } // TPMHardwareDetails is a container for some details // for TPM hardware. type TPMHardwareDetails struct { Manufacturer string // TODO(hs): use Manufacturer from TPM package? Need to fix import cycle, though Model string Version string } var ( oidTPMManufacturer = asn1.ObjectIdentifier{2, 23, 133, 2, 1} oidTPMModel = asn1.ObjectIdentifier{2, 23, 133, 2, 2} oidTPMVersion = asn1.ObjectIdentifier{2, 23, 133, 2, 3} ) // ParseSubjectAlternativeNames parses the Subject Alternative Names // from the X.509 certificate `c`. SAN types supported by the Go stdlib, // including DNS names, IP addresses, email addresses and URLs, are copied // to the result first. After that, the raw extension bytes are parsed to // extract PermanentIdentifiers and HardwareModuleNames SANs. func ParseSubjectAlternativeNames(c *x509.Certificate) (sans SubjectAlternativeNames, err error) { // the Certificate c is expected to have been processed before, so the // SANs known by the Go stdlib are expected to have been populated already. // These SANs are copied over to the result. sans.DNSNames = c.DNSNames sans.IPAddresses = c.IPAddresses sans.EmailAddresses = c.EmailAddresses sans.URIs = c.URIs var sanExtension pkix.Extension for _, ext := range c.Extensions { if ext.Id.Equal(oidExtensionSubjectAltName) { sanExtension = ext break } } if sanExtension.Value == nil { return } directoryNames, otherNames, err := parseSubjectAltName(sanExtension) if err != nil { return sans, fmt.Errorf("failed parsing SubjectAltName extension: %w", err) } for _, otherName := range otherNames { switch { case otherName.TypeID.Equal(oidPermanentIdentifier): permanentIdentifier, err := parsePermanentIdentifier(otherName.Value.FullBytes) if err != nil { return sans, fmt.Errorf("failed parsing PermanentIdentifier: %w", err) } sans.PermanentIdentifiers = append(sans.PermanentIdentifiers, permanentIdentifier) case otherName.TypeID.Equal(oidHardwareModuleNameIdentifier): hardwareModuleName, err := parseHardwareModuleName(otherName.Value.FullBytes) if err != nil { return sans, fmt.Errorf("failed parsing HardwareModuleName: %w", err) } sans.HardwareModuleNames = append(sans.HardwareModuleNames, hardwareModuleName) default: // TODO(hs): handle other types; defaulting to otherName? } } tpmDetails := TPMHardwareDetails{} for _, directoryName := range directoryNames { for _, name := range directoryName.Names { switch { case name.Type.Equal(oidTPMManufacturer): tpmDetails.Manufacturer = name.Value.(string) case name.Type.Equal(oidTPMModel): tpmDetails.Model = name.Value.(string) case name.Type.Equal(oidTPMVersion): tpmDetails.Version = name.Value.(string) default: // TODO(hs): handle other directoryNames? } } } sans.TPMHardwareDetails = tpmDetails return } // https://datatracker.ietf.org/doc/html/rfc5280#page-35 func parseSubjectAltName(ext pkix.Extension) (dirNames []pkix.Name, otherNames []otherName, err error) { err = forEachSAN(ext.Value, func(generalName asn1.RawValue) error { switch generalName.Tag { case 0: // otherName var on otherName if _, err := asn1.UnmarshalWithParams(generalName.FullBytes, &on, "tag:0"); err != nil { return fmt.Errorf("failed unmarshaling otherName: %w", err) } otherNames = append(otherNames, on) case 4: // directoryName var rdns pkix.RDNSequence if _, err := asn1.Unmarshal(generalName.Bytes, &rdns); err != nil { return fmt.Errorf("failed unmarshaling directoryName: %w", err) } var dirName pkix.Name dirName.FillFromRDNSequence(&rdns) dirNames = append(dirNames, dirName) default: // skipping the other tag values intentionally } return nil }) return } func parsePermanentIdentifier(der []byte) (PermanentIdentifier, error) { var permID asn1PermanentIdentifier if _, err := asn1.UnmarshalWithParams(der, &permID, "explicit,tag:0"); err != nil { return PermanentIdentifier{}, fmt.Errorf("failed unmarshaling der data: %w", err) } return PermanentIdentifier{Identifier: permID.IdentifierValue, Assigner: ObjectIdentifier(permID.Assigner)}, nil } func parseHardwareModuleName(der []byte) (HardwareModuleName, error) { var hardwareModuleName asn1HardwareModuleName if _, err := asn1.UnmarshalWithParams(der, &hardwareModuleName, "explicit,tag:0"); err != nil { return HardwareModuleName{}, fmt.Errorf("failed unmarshaling der data: %w", err) } return HardwareModuleName{Type: ObjectIdentifier(hardwareModuleName.Type), SerialNumber: hardwareModuleName.SerialNumber}, nil } // Borrowed from the x509 package. func forEachSAN(extension []byte, callback func(ext asn1.RawValue) error) error { var seq asn1.RawValue rest, err := asn1.Unmarshal(extension, &seq) if err != nil { return err } else if len(rest) != 0 { return errors.New("x509: trailing data after X.509 extension") } if !seq.IsCompound || seq.Tag != 16 || seq.Class != 0 { return asn1.StructuralError{Msg: "bad SAN sequence"} } rest = seq.Bytes for len(rest) > 0 { var v asn1.RawValue rest, err = asn1.Unmarshal(rest, &v) if err != nil { return err } if err := callback(v); err != nil { return err } } return nil } crypto-0.57.0/x509util/extensions_test.go000066400000000000000000001651621474156331600203400ustar00rootroot00000000000000package x509util import ( "bytes" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/json" "math/big" "net" "net/url" "reflect" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) 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}, {"userPrincipalName", fields{"userPrincipalName", "foo@bar.com", nil}, asn1.RawValue{ FullBytes: []byte{160, 27, 6, 10, 43, 6, 1, 4, 1, 130, 55, 20, 2, 3, 160, 13, 12, 11, 102, 111, 111, 64, 98, 97, 114, 46, 99, 111, 109}, }, 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 utc", fields{"1.2.3.4", "utc:2023-03-29T02:03:57Z", nil}, asn1.RawValue{ FullBytes: append([]byte{160, 22, 6, 3, 42, 3, 4, 160, 15, 23, 13}, []byte("230329020357Z")...), }, false}, {"otherName generalizd", fields{"1.2.3.4", "generalized:2023-03-29T02:03:57Z", nil}, asn1.RawValue{ FullBytes: append([]byte{160, 24, 6, 3, 42, 3, 4, 160, 17, 24, 15}, []byte("20230329020357Z")...), }, 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}, {"otherName whitespaces", 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 bool:true", fields{"1.2.3.4", "bool:true", nil}, asn1.RawValue{ FullBytes: []byte{160, 10, 6, 3, 42, 3, 4, 160, 3, 1, 1, 255}, }, false}, {"otherName boolean:false", fields{"1.2.3.4", "boolean:false", nil}, asn1.RawValue{ FullBytes: []byte{160, 10, 6, 3, 42, 3, 4, 160, 3, 1, 1, 0}, }, 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 userPrincipalName empty", fields{"userPrincipalName", "", nil}, asn1.RawValue{}, true}, {"fail userPrincipalName value", fields{"userPrincipalName", "foo\xff@mail.com", 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 utc", fields{"1.2.3.4", "utc:2023", nil}, asn1.RawValue{}, true}, {"fail otherName generalized", fields{"1.2.3.4", "generalized:2023-12-12", 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 if err := unmarshaled.UnmarshalJSON(got); err != nil { t.Errorf("KeyUsage.UnmarshalJSON() error = %v", err) } 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 if err := unmarshaled.UnmarshalJSON(got); err != nil { t.Errorf("ExtKeyUsage.UnmarshalJSON() error = %v", err) } 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) } }) } } func mustParseURL(t *testing.T, s string) *url.URL { t.Helper() u, err := url.Parse(s) require.NoError(t, err) return u } func TestParseSubjectAlternativeNames(t *testing.T) { permanentIdentifierSAN := SubjectAlternativeName{ Type: PermanentIdentifierType, Value: "12345", } permanentIdentifierSANExtension, err := createSubjectAltNameExtension([]string{"test"}, nil, nil, nil, []SubjectAlternativeName{permanentIdentifierSAN}, true) require.NoError(t, err) hardwareModuleNameSAN := SubjectAlternativeName{ Type: HardwareModuleNameType, ASN1Value: []byte(`{"type": "1.2.3.4", "serialNumber": "MTIzNDU2Nzg="}`), } hardwareModuleNameSANExtension, err := createSubjectAltNameExtension(nil, nil, nil, nil, []SubjectAlternativeName{hardwareModuleNameSAN}, true) require.NoError(t, err) tests := []struct { name string cert *x509.Certificate wantSans SubjectAlternativeNames expErr error }{ { name: "ok/stdlib", cert: &x509.Certificate{ DNSNames: []string{"example.com"}, IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, EmailAddresses: []string{"test@example.com"}, URIs: []*url.URL{mustParseURL(t, "https://127.0.0.1")}, }, wantSans: SubjectAlternativeNames{ DNSNames: []string{"example.com"}, IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, EmailAddresses: []string{"test@example.com"}, URIs: []*url.URL{mustParseURL(t, "https://127.0.0.1")}, }, }, { name: "ok/permanent-identifier", cert: &x509.Certificate{ DNSNames: []string{"example.com"}, Extensions: []pkix.Extension{ { Id: asn1.ObjectIdentifier(permanentIdentifierSANExtension.ID), Critical: permanentIdentifierSANExtension.Critical, Value: permanentIdentifierSANExtension.Value, }, }, }, wantSans: SubjectAlternativeNames{ DNSNames: []string{"example.com"}, PermanentIdentifiers: []PermanentIdentifier{ { Identifier: "12345", }, }, }, }, { name: "ok/hardware-module-name", cert: &x509.Certificate{ DNSNames: []string{"example.com"}, Extensions: []pkix.Extension{ { Id: asn1.ObjectIdentifier(hardwareModuleNameSANExtension.ID), Critical: hardwareModuleNameSANExtension.Critical, Value: hardwareModuleNameSANExtension.Value, }, }, }, wantSans: SubjectAlternativeNames{ DNSNames: []string{"example.com"}, HardwareModuleNames: []HardwareModuleName{ { Type: ObjectIdentifier([]int{1, 2, 3, 4}), SerialNumber: []byte("12345678"), }, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotSans, err := ParseSubjectAlternativeNames(tt.cert) if tt.expErr != nil { if assert.Error(t, err) { assert.EqualError(t, err, tt.expErr.Error()) } assert.Empty(t, gotSans) } assert.NoError(t, err) assert.Equal(t, tt.wantSans, gotSans) }) } } crypto-0.57.0/x509util/fingerprint.go000066400000000000000000000034241474156331600174210ustar00rootroot00000000000000package 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) } } crypto-0.57.0/x509util/fingerprint_test.go000066400000000000000000000043721474156331600204630ustar00rootroot00000000000000package 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) } }) } } crypto-0.57.0/x509util/marshal_utils.go000066400000000000000000000115251474156331600177420ustar00rootroot00000000000000package 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 } crypto-0.57.0/x509util/marshal_utils_test.go000066400000000000000000000212221474156331600207740ustar00rootroot00000000000000package 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) } }) } } crypto-0.57.0/x509util/name.go000066400000000000000000000135061474156331600160140ustar00rootroot00000000000000package 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 } crypto-0.57.0/x509util/name_test.go000066400000000000000000000507361474156331600170610ustar00rootroot00000000000000package 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) } }) } } crypto-0.57.0/x509util/options.go000066400000000000000000000113231474156331600165620ustar00rootroot00000000000000package x509util import ( "bytes" "crypto/x509" encoding_asn1 "encoding/asn1" "encoding/base64" "os" "strings" "text/template" "github.com/pkg/errors" "golang.org/x/crypto/cryptobyte" "golang.org/x/crypto/cryptobyte/asn1" "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 // GetFuncMap returns the list of functions used by the templates. It will // return all the functions supported by "sprig.TxtFuncMap()" but exclude "env" // and "expandenv", removed to avoid the leak of information. It will also add // the following functions to encode data using ASN.1: // // - asn1Enc: encodes the given string to ASN.1. By default, it will use the // PrintableString format but it can be change using the suffix ":". // Supported formats are: "printable", "utf8", "ia5", "numeric", "int", "oid", // "utc", "generalized", and "raw". // - asn1Marshal: encodes the given string with the given params using Go's // asn1.MarshalWithParams. // - asn1Seq: encodes a sequence of the given ASN.1 data. // - asn1Set: encodes a set of the given ASN.1 data. func GetFuncMap() template.FuncMap { return getFuncMap(new(TemplateError)) } func getFuncMap(err *TemplateError) template.FuncMap { funcMap := templates.GetFuncMap(&err.Message) // asn1 methods funcMap["asn1Enc"] = asn1Encode funcMap["asn1Marshal"] = asn1Marshal funcMap["asn1Seq"] = asn1Sequence funcMap["asn1Set"] = asn1Set return funcMap } // 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 := getFuncMap(terr) // Parse template 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 { b, err := os.ReadFile(path) if err != nil { return errors.Wrapf(err, "error reading %s", path) } fn := WithTemplate(string(b), data) return fn(cr, o) } } func asn1Encode(str string) (string, error) { value, params := str, "printable" if strings.Contains(value, sanTypeSeparator) { params = strings.SplitN(value, sanTypeSeparator, 2)[0] value = value[len(params)+1:] } b, err := marshalValue(value, params) if err != nil { return "", err } return base64.StdEncoding.EncodeToString(b), nil } func asn1Marshal(v interface{}, params ...string) (string, error) { b, err := encoding_asn1.MarshalWithParams(v, strings.Join(params, ",")) if err != nil { return "", err } return base64.StdEncoding.EncodeToString(b), nil } func asn1Sequence(b64enc ...string) (string, error) { var builder cryptobyte.Builder builder.AddASN1(asn1.SEQUENCE, func(child *cryptobyte.Builder) { for _, s := range b64enc { b, err := base64.StdEncoding.DecodeString(s) if err != nil { child.SetError(err) return } child.AddBytes(b) } }) b, err := builder.Bytes() if err != nil { return "", err } return base64.StdEncoding.EncodeToString(b), nil } func asn1Set(b64enc ...string) (string, error) { var builder cryptobyte.Builder builder.AddASN1(asn1.SET, func(child *cryptobyte.Builder) { for _, s := range b64enc { b, err := base64.StdEncoding.DecodeString(s) if err != nil { child.SetError(err) return } child.AddBytes(b) } }) b, err := builder.Bytes() if err != nil { return "", err } return base64.StdEncoding.EncodeToString(b), nil } crypto-0.57.0/x509util/options_test.go000066400000000000000000000376101474156331600176300ustar00rootroot00000000000000package x509util import ( "bytes" "crypto" "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/base64" "reflect" "testing" "time" "github.com/stretchr/testify/require" ) 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 TestGetFuncMap(t *testing.T) { ok := []string{ "fail", "contains", "split", // generic sprig functions "asn1Enc", "asn1Marshal", "asn1Seq", "asn1Set", // custom functions } fail := []string{"env", "expandenv"} funcMap := GetFuncMap() for _, name := range ok { if _, ok := funcMap[name]; !ok { t.Errorf("GetFuncMap() does not contain the function %s", name) } } for _, name := range fail { if _, ok := funcMap[name]; ok { t.Errorf("GetFuncMap() contains the function %s", name) } } } 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"}) templateWithExtensions := `{ "subject": {{ toJson .Subject }}, "sans": {{ toJson .SANs }}, {{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }} "keyUsage": ["keyEncipherment", "digitalSignature"], {{- else }} "keyUsage": ["digitalSignature"], {{- end }} "extKeyUsage": ["serverAuth", "clientAuth"], "extensions": [ {"id": "1.2.3.4", "value": {{ asn1Enc (first .Insecure.CR.DNSNames) | toJson }}}, {"id": "1.2.3.5", "value": {{ asn1Marshal (first .Insecure.CR.DNSNames) | toJson }}}, {"id": "1.2.3.6", "value": {{ asn1Seq (asn1Enc (first .Insecure.CR.DNSNames)) (asn1Enc "int:123456") | toJson }}}, {"id": "1.2.3.7", "value": {{ asn1Set (asn1Marshal (first .Insecure.CR.DNSNames) "utf8") (asn1Enc "bool:true") | toJson }}} ] }` 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}, {"extensions", args{templateWithExtensions, 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"], "extensions": [ {"id": "1.2.3.4", "value": "Ewdmb28uY29t"}, {"id": "1.2.3.5", "value": "Ewdmb28uY29t"}, {"id": "1.2.3.6", "value": "MA4TB2Zvby5jb20CAwHiQA=="}, {"id": "1.2.3.7", "value": "MQwMB2Zvby5jb20BAf8="} ] }`), }, 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"}) now := time.Now().UTC().Truncate(time.Second) 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", "nbf": now.Unix(), }, WebhooksKey: map[string]interface{}{ "Test": map[string]interface{}{ "notAfter": now.Add(10 * time.Hour).Format(time.RFC3339), }, }, } 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", "notBefore": "` + now.Format(time.RFC3339) + `", "notAfter": "` + now.Add(10*time.Hour).Format(time.RFC3339) + `", "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", "notBefore": "` + now.Format(time.RFC3339) + `", "notAfter": "` + now.Add(10*time.Hour).Format(time.RFC3339) + `", "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) } }) } } func mustMarshal(t *testing.T, value interface{}, params string) string { t.Helper() b, err := asn1.MarshalWithParams(value, params) if err != nil { t.Fatal(err) } return base64.StdEncoding.EncodeToString(b) } func Test_asn1Encode(t *testing.T) { now := time.Now().UTC() type args struct { str string } tests := []struct { name string args args want string wantErr bool }{ {"ok", args{"string"}, mustMarshal(t, "string", "printable"), false}, {"ok explicit", args{"explicit:string"}, mustMarshal(t, "string", "printable,explicit"), false}, {"ok printable", args{"printable:string"}, mustMarshal(t, "string", "printable"), false}, {"ok printable explicit", args{"printable,explicit:string"}, mustMarshal(t, "string", "printable,explicit"), false}, {"ok ia5", args{"ia5:string"}, mustMarshal(t, "string", "ia5"), false}, {"ok utf8", args{"utf8:string"}, mustMarshal(t, "string", "utf8"), false}, {"ok utc", args{"utc:" + now.String()}, mustMarshal(t, now, "utc"), false}, {"ok generalized", args{"generalized:" + now.Format(time.RFC3339)}, mustMarshal(t, now, "generalized"), false}, {"ok int", args{"int:1234"}, mustMarshal(t, 1234, ""), false}, {"ok numeric", args{"numeric:1234"}, mustMarshal(t, "1234", "numeric"), false}, {"ok bool", args{"bool:true"}, mustMarshal(t, true, ""), false}, {"ok raw", args{"raw:" + mustMarshal(t, 1234, "")}, mustMarshal(t, 1234, ""), false}, {"fail numeric", args{"numeric:not-a-number"}, "", true}, {"fail time", args{"utc:not-a-time"}, "", true}, {"fail bool", args{"bool:untrue"}, "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := asn1Encode(tt.args.str) if (err != nil) != tt.wantErr { t.Errorf("asn1Encode() error = %v, wantErr %v", err, tt.wantErr) } if got != tt.want { t.Errorf("asn1Encode() = %v, want %v", got, tt.want) } }) } } func Test_asn1Marshal(t *testing.T) { now := time.Now() type args struct { v interface{} params []string } tests := []struct { name string args args want string wantErr bool }{ {"ok printable", args{"string", nil}, mustMarshal(t, "string", "printable"), false}, {"ok utf8", args{"string", []string{"utf8"}}, mustMarshal(t, "string", "utf8"), false}, {"ok int", args{1234, nil}, mustMarshal(t, 1234, ""), false}, {"ok time", args{now, nil}, mustMarshal(t, now, "utc"), false}, {"ok seq", args{[]any{"string", 1234}, nil}, mustMarshal(t, []any{"string", 1234}, ""), false}, {"ok set", args{[]any{"string", 1234}, []string{"set"}}, mustMarshal(t, []any{"string", 1234}, "set"), false}, {"ok bool", args{false, nil}, mustMarshal(t, false, ""), false}, {"fail numeric", args{"string", []string{"numeric"}}, "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := asn1Marshal(tt.args.v, tt.args.params...) if (err != nil) != tt.wantErr { t.Errorf("asn1Marshal() error = %v, wantErr %v", err, tt.wantErr) } if got != tt.want { t.Errorf("asn1Marshal() = %v, want %v", got, tt.want) } }) } } func Test_asn1Sequence(t *testing.T) { now := time.Now().UTC() type set struct { Int int Time time.Time `asn1:"utc"` } type complexWithSet struct { String string Set set `asn1:"set"` } b64String, err := asn1Encode("string") require.NoError(t, err) b64Int, err := asn1Encode("int:1234") require.NoError(t, err) b64Time, err := asn1Encode("utc:" + now.String()) require.NoError(t, err) b64Set, err := asn1Set(b64Int, b64Time) require.NoError(t, err) type args struct { b64enc []string } tests := []struct { name string args args want string wantErr bool }{ {"ok", args{[]string{b64String, b64Int}}, mustMarshal(t, []any{"string", 1234}, "sequence"), false}, {"ok complex", args{[]string{b64String, b64Set}}, mustMarshal(t, complexWithSet{"string", set{1234, now}}, ""), false}, {"fail", args{[]string{b64String, "bad-base-64"}}, "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := asn1Sequence(tt.args.b64enc...) if (err != nil) != tt.wantErr { t.Errorf("asn1Sequence() error = %v, wantErr %v", err, tt.wantErr) } if got != tt.want { t.Errorf("asn1Sequence() = %v, want %v", got, tt.want) } }) } } func Test_asn1Set(t *testing.T) { now := time.Now().UTC() type complexWithSequence struct { String string Sequence []any } b64String, err := asn1Encode("string") require.NoError(t, err) b64Int, err := asn1Encode("int:1234") require.NoError(t, err) b64Time, err := asn1Encode("utc:" + now.String()) require.NoError(t, err) b64Sequence, err := asn1Sequence(b64Int, b64Time) require.NoError(t, err) type args struct { b64enc []string } tests := []struct { name string args args want string wantErr bool }{ {"ok", args{[]string{b64Int, b64String}}, mustMarshal(t, []any{1234, "string"}, "set"), false}, {"ok complex", args{[]string{b64String, b64Sequence}}, mustMarshal(t, complexWithSequence{"string", []any{1234, now}}, "set"), false}, {"fail", args{[]string{b64String, "bad-base-64"}}, "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := asn1Set(tt.args.b64enc...) if (err != nil) != tt.wantErr { t.Errorf("asn1Set() error = %v, wantErr %v", err, tt.wantErr) } if got != tt.want { t.Errorf("asn1Set() = %v, want %v", got, tt.want) } }) } } crypto-0.57.0/x509util/templates.go000066400000000000000000000166521474156331600170770ustar00rootroot00000000000000package 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, GetFuncMap()) } // 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"] }` crypto-0.57.0/x509util/templates_test.go000066400000000000000000000310411474156331600201230ustar00rootroot00000000000000package 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) } }) } } crypto-0.57.0/x509util/testdata/000077500000000000000000000000001474156331600163515ustar00rootroot00000000000000crypto-0.57.0/x509util/testdata/capath/000077500000000000000000000000001474156331600176115ustar00rootroot00000000000000crypto-0.57.0/x509util/testdata/capath/cert.pem000066400000000000000000000021531474156331600212520ustar00rootroot00000000000000-----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-----crypto-0.57.0/x509util/testdata/capath2/000077500000000000000000000000001474156331600176735ustar00rootroot00000000000000crypto-0.57.0/x509util/testdata/capath2/root1.crt000066400000000000000000000010651474156331600214530ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBdTCCARygAwIBAgIRAMzIwp8pYDKH01yvNcwKLqYwCgYIKoZIzj0EAwIwGTEX MBUGA1UEAxMOU21hbGxzdGVwIENBIDEwHhcNMjEwNDE0MDEyNzMwWhcNMzEwNDEy MDEyNzMwWjAZMRcwFQYDVQQDEw5TbWFsbHN0ZXAgQ0EgMTBZMBMGByqGSM49AgEG CCqGSM49AwEHA0IABPKwI/NGpH5uWYH9KYrusC3aszBIxin+mDFSt7sJiPjfkHps l8HPUWrUvjz0BuHm0ic52zCHwftQc1EXFDuDxpajRTBDMA4GA1UdDwEB/wQEAwIB BjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBTxlV+DRZS5m5HrFGf83OsG W4SYNzAKBggqhkjOPQQDAgNHADBEAiBdVZTcpwwZpfgmU6jqpO8Amn1O3ZZSHXog XWDjTaH/4gIgTaGEuNVMXoMQl/nr8lEcQtaP/p+0woHKL9JGr13ZYNI= -----END CERTIFICATE-----crypto-0.57.0/x509util/testdata/capath2/root2.crt000066400000000000000000000010651474156331600214540ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBdjCCARygAwIBAgIRAMhq6mlSxCy4ZfXyvlOabZEwCgYIKoZIzj0EAwIwGTEX MBUGA1UEAxMOU21hbGxzdGVwIENBIDIwHhcNMjEwNDE0MDEyNzM5WhcNMzEwNDEy MDEyNzM5WjAZMRcwFQYDVQQDEw5TbWFsbHN0ZXAgQ0EgMjBZMBMGByqGSM49AgEG CCqGSM49AwEHA0IABDPFYafdCLBSLtjXZev0PEHJpRyYhLiFUjOGx/Dxbie0LGmU fWlU8AIh7e9jjvl+ugdhco3CoWEUoBCuD9S99DujRTBDMA4GA1UdDwEB/wQEAwIB BjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBQj++HJHgFcVIvah8WOTvMd UboxGDAKBggqhkjOPQQDAgNIADBFAiBeMmKo4n3LvQCgGF609rH/fjJbGI00ZrrS Pe9qXRrxWwIhAKBurnttPIYbOzidmrFA8fBF4w24lu0WmDOr5Cz8IfP9 -----END CERTIFICATE-----crypto-0.57.0/x509util/testdata/challengePassword.csr000066400000000000000000000021121474156331600225230ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- MIIC8jCCAdoCAQAwFTETMBEGA1UEAxMKY29tbW9uTmFtZTCCASIwDQYJKoZIhvcN AQEBBQADggEPADCCAQoCggEBAMpfsoCGJnHQWgd98laaURBT/P/CF3lKkTiilu4M HZq12AbgKe5K4daFg/X4l1vm837wMkQX10WX9lExyBPXQ6gMCtbwOUyaNbhhpWBT 3x4pBVZW6ZTsr95FTWjMCsZbT4sKAI9Do4SgYzZoRN/Z1rD95ia46qJtr94nJK5p JCifopn0n4iZl0cz63NO5ed2fFusfTd7/uXcerLIt/LBCVSH3Os0XeFBzVvj5HmA HrYzjuIMM7LNcC80lyDSTjB5dNyncX5//KFlBcpjYbTDf7iX+fNi9gqWpAQkblJA lokUczDkVAM8vlPOL+B0pEgbi5lq6mmSaUwWg0GyZv6YVSECAwEAAaCBlzAgBgkq hkiG9w0BCQcxExMRY2hhbGxlbmdlUGFzc3dvcmQwcwYJKoZIhvcNAQkOMWYwZDBQ BgNVHREESTBHggdmb28uY29tggdiYXIuY29tgQxyb290QGZvby5jb22HEAAAAAAA AAAAAAAAAAAAAAGGE21haWx0bzpyb290QGZvby5jb20wEAYDKgMEAQH/BAZmb29i YXIwDQYJKoZIhvcNAQELBQADggEBAJ/MjfJ4D9p2Uf+ZJxs1sfQgdbAIWTTiP/EO J3RlBpKgFoII4sU/XvuXiOcwjNZxQuVmxgx0JBsX0iTx6AgKwr34IXVFHNFcKD2+ 582/pB9TqmWQve62GVEQArelgZa/hvYnDUeVwTtnjre95Zp1a7n/2rOZTk2pnxD4 T5dBlKDTDcppKqzlNlhnsXfDbpOt8g6thTovVwHk0fcUif3ODdH/TyZUGdUyiZ13 rhpFuWJnClj+J9SGDRjiEvRP3RzLraDuTGZbqs9UeFkDBIk4s0f0kcwMC8n6an4S GAV0PCzyJ6mmTaK0ylsue4nVuf7svv/ST/cHmKc+z561joMMAjc= -----END CERTIFICATE REQUEST----- crypto-0.57.0/x509util/testdata/challengePasswordUTF8.csr000066400000000000000000000020721474156331600231770ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- MIIC5TCCAc0CAQAwFTETMBEGA1UEAxMKY29tbW9uTmFtZTCCASIwDQYJKoZIhvcN AQEBBQADggEPADCCAQoCggEBAMpfsoCGJnHQWgd98laaURBT/P/CF3lKkTiilu4M HZq12AbgKe5K4daFg/X4l1vm837wMkQX10WX9lExyBPXQ6gMCtbwOUyaNbhhpWBT 3x4pBVZW6ZTsr95FTWjMCsZbT4sKAI9Do4SgYzZoRN/Z1rD95ia46qJtr94nJK5p JCifopn0n4iZl0cz63NO5ed2fFusfTd7/uXcerLIt/LBCVSH3Os0XeFBzVvj5HmA HrYzjuIMM7LNcC80lyDSTjB5dNyncX5//KFlBcpjYbTDf7iX+fNi9gqWpAQkblJA lokUczDkVAM8vlPOL+B0pEgbi5lq6mmSaUwWg0GyZv6YVSECAwEAAaCBijATBgkq hkiG9w0BCQcxBgwE8J+UkDBzBgkqhkiG9w0BCQ4xZjBkMFAGA1UdEQRJMEeCB2Zv by5jb22CB2Jhci5jb22BDHJvb3RAZm9vLmNvbYcQAAAAAAAAAAAAAAAAAAAAAYYT bWFpbHRvOnJvb3RAZm9vLmNvbTAQBgMqAwQBAf8EBmZvb2JhcjANBgkqhkiG9w0B AQsFAAOCAQEALf4qgcXqS9LONJJcS5LvgMNJF18/rwOnZuL1xBx+ml9gHH9kPp8/ SOoGjsXCE6GSZ7FZ7ZZDEf/jb5aurEO4jzZ0RbFRpC16yrzZaRkuh6Tj91lc45HY mc1WwAwXA373OJ4Xzc2FznGVBVh089gvodltZqjbGkBWIlziLf1PdPG/ig89eXCp JPCJdAB3BBBJLKLMJV5lSaaXHUq81dDm/hliksywmg5GS3pfYPEP9PkF2lRbmbj7 vkosVv1fqLRIdC9rE25izwlEeHTomdLKXY2asueBdrRnSDN4h8QN5wpzI/wENkpI Crf8d9KpmQH4yVFs1afRqyRBlhW2haOlbw== -----END CERTIFICATE REQUEST----- crypto-0.57.0/x509util/testdata/ed25519.crt000066400000000000000000000010211474156331600200530ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBWzCCAQ2gAwIBAgIUDIPYISuCyyOYI2Pi95eKQ1vzvZIwBQYDK2VwMCMxITAf BgNVBAMMGEVkMjU1MTkgdGVzdCBjZXJ0aWZpY2F0ZTAeFw0xOTA1MDYxNzI3MTZa Fw0xOTA2MDUxNzI3MTZaMCMxITAfBgNVBAMMGEVkMjU1MTkgdGVzdCBjZXJ0aWZp Y2F0ZTAqMAUGAytlcAMhADYpxWwNTxRsgdD/ddNqcF9pzQ9NZtXamH6CSYmjijz6 o1MwUTAdBgNVHQ4EFgQUCTs6nUop2JX/aL57Q1Ry4K2i464wHwYDVR0jBBgwFoAU CTs6nUop2JX/aL57Q1Ry4K2i464wDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQBT pVgcLDsqnqydTqUdX11tprUI3hKC85cgrvrYmPQagzJrkfUkHcQgfyziTdoTO21U GtKoKNxgudT0eEs8HJEA -----END CERTIFICATE-----crypto-0.57.0/x509util/testdata/example.tpl000066400000000000000000000014151474156331600205260ustar00rootroot00000000000000{ "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 }} "notBefore": "{{ dateInZone "2006-01-02T15:04:05Z07:00" .Token.nbf "UTC" }}", "notAfter": "{{ .Webhooks.Test.notAfter }}", {{- 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"] }crypto-0.57.0/x509util/testdata/fullsimple.tpl000066400000000000000000000026501474156331600212510ustar00rootroot00000000000000{ "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"}], "notBefore": "2009-02-13T23:31:30Z", "notAfter": "2009-02-14T23:31:30Z", "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" }crypto-0.57.0/x509util/testdata/google.crt000066400000000000000000000032631474156331600203430ustar00rootroot00000000000000-----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-----crypto-0.57.0/x509util/testdata/opcua.tpl000066400000000000000000000004261474156331600202030ustar00rootroot00000000000000{ "subject": {{ toJson .Subject }}, "sans": {{ toJson .SANs }}, "basicConstraints": { "isCA": false }, "keyUsage": ["digitalSignature", "keyEncipherment", "keyAgreement", "certSign"], "extKeyUsage": ["serverAuth", "clientAuth"] } crypto-0.57.0/x509util/testdata/rawSubject.csr000066400000000000000000000007241474156331600211760ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- MIIBIjCBygIBADBoMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEW MBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEdMBsGA1UECgwUU21hbGxzdGVwIExhYnMs IEluYy4xDTALBgNVBAMMBFRlc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATG NLZtzv5dcl2Nz66XBV+tq5jKCH9WGPlsuBEq48NNUb1LzecjaVTZsEwe+ZpQbnzk wTdYXFI68HbovxWc9fDxoAAwCgYIKoZIzj0EAwIDRwAwRAIgODOqVqdeZzSwZOFB VZuSLoMVvCVoiFng8/2hXfJWJi4CIBNHEt9batonI4Z6Z0kij/qNTeTTWFd5yawX 2DP2+6EP -----END CERTIFICATE REQUEST-----crypto-0.57.0/x509util/testdata/rawSubject.key000066400000000000000000000003431474156331600211740ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MHcCAQEEIK9FBgwqr6Zjsbf2uPmvnDz341AhiJbPLTuq8+/BLbd/oAoGCCqGSM49 AwEHoUQDQgAExjS2bc7+XXJdjc+ulwVfrauYygh/Vhj5bLgRKuPDTVG9S83nI2lU 2bBMHvmaUG585ME3WFxSOvB26L8VnPXw8Q== -----END EC PRIVATE KEY----- crypto-0.57.0/x509util/testdata/rawSubject.tpl000066400000000000000000000004531474156331600212050ustar00rootroot00000000000000{ "rawSubject": {{ toJson .Insecure.CR.RawSubject }}, "sans": {{ toJson .SANs }}, {{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }} "keyUsage": ["keyEncipherment", "digitalSignature"], {{- else }} "keyUsage": ["digitalSignature"], {{- end }} "extKeyUsage": ["serverAuth", "clientAuth"] }crypto-0.57.0/x509util/testdata/rsa.key000066400000000000000000000032161474156331600176520ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAyl+ygIYmcdBaB33yVppREFP8/8IXeUqROKKW7gwdmrXYBuAp 7krh1oWD9fiXW+bzfvAyRBfXRZf2UTHIE9dDqAwK1vA5TJo1uGGlYFPfHikFVlbp lOyv3kVNaMwKxltPiwoAj0OjhKBjNmhE39nWsP3mJrjqom2v3ickrmkkKJ+imfSf iJmXRzPrc07l53Z8W6x9N3v+5dx6ssi38sEJVIfc6zRd4UHNW+PkeYAetjOO4gwz ss1wLzSXINJOMHl03Kdxfn/8oWUFymNhtMN/uJf582L2CpakBCRuUkCWiRRzMORU Azy+U84v4HSkSBuLmWrqaZJpTBaDQbJm/phVIQIDAQABAoIBACmOsUc3RRnGIVa8 hFFIazEhLikPIGbl6yBoMvo7PrEQeoYe7j1i3zGARXLzU5GpwYLlrEzTYH+IDyrp QB7i65/MqUUuG3YhjLqKQr2fO4gkAFaUjm7ok8zfCmBk1imZgqIYsbi5uRB5JyZ7 Dvmw8Cd0XBjDjFtCjwTdoOi36pb0ozSkukfYHYWbEErdBvllaMdsdtrilV/RBamn uHMGjC9le/SlmJ2bm/KfixCU9y9erqzWN1COiI9ofVt6w54jH60YPZ6LnWqqQ6iZ ry03TQtU5myuEnHbZ0e6eCVdHNNKBRvR+HO1Z/0+JiG8z1vZcOoTU2YNhTNds0Ob DIFtAWkCgYEAzt6apG5FLpQPUUWhpbgVqJsJ6agVXeUZCRnFU6cvQshpdpDEAeqO XZl5zArkG0eNeg2p0ZbUSDhl5mwh+Fq7Mf25IlK3SkQNMb0JHMr/GpqgMH7CUDlC NBBNdv0W2EglFPA55fuOYcdsb37cPWv6mIKyZ0IEMlwyk9xvesb3yKsCgYEA+m/C PiBB0UAzNpRyg3B29zGkF8bEGUqZkR+gK5hS50Q4qaawLGxn2dxLUtPh+zVRi82i Lj1+QHc5lIP9ZGOFKQjbdrjd41w+8PMJ2QpcrmqXFT9uotoBvtW2oYIICEe6l9rM +AXR0xT/CdjO5f3IXjFPI8BS6vIeK0CeasgXMWMCgYEAl6InrDt/zmKMz7/Aex5y EcKDA5hHASo6racMMwMq60LHuzbl1Zm+fPFQi7W3+h76nuqA/bYXKFDesIW2aLro 1/3oxDXpUDCNk1XC5i4Ny8xneuSPPgqacls4zO0635ISTETBy8gWV6m73AYbHZKW f0/dwT5soRVfpW5A8LgDTD0CgYEA8Beg00q9jszLzHG470Ys54PR23GVouGgJD97 oQ/huxHueMbzc0NitLy8AeZcaieNzEgSSd5uYYNr0nRPw6Sdjt+M9174avaO6RU3 xbpnbFeIDLjJapsyvS/KbUBqTvXlb5w2kTmTFZi1gU033crWA2emhZHmbJQ+VB0Z QyodAh0CgYBIgdjqRMbHzxJMk6vo2NnYaAOe2iJUxfgpaAbxuvQtAy29RMQNdtk0 vaNlxDr0NqFjHGA5c0VR5P0zu4ov8mPE6UL+2f797Gfgq9XGuinx7kUaP/U0yLbw zMzpXyfJQhNuy2nPfpdxvtSvlpu252B6+uPle6VS0cqaT/uqgtFsvA== -----END RSA PRIVATE KEY-----crypto-0.57.0/x509util/testdata/secrets/000077500000000000000000000000001474156331600200215ustar00rootroot00000000000000crypto-0.57.0/x509util/testdata/secrets/example.key000066400000000000000000000003601474156331600221650ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgel8C9Xs8cZgTSIHw w7wEM7P2u3q2P0N+lvfQsjG4BWWhRANCAAQzxWGn3QiwUi7Y12Xr9DxByaUcmIS4 hVIzhsfw8W4ntCxplH1pVPACIe3vY475froHYXKNwqFhFKAQrg/UvfQ7 -----END PRIVATE KEY-----crypto-0.57.0/x509util/testdata/smallstep.crt000066400000000000000000000036121474156331600210710ustar00rootroot00000000000000-----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-----crypto-0.57.0/x509util/utils.go000066400000000000000000000133021474156331600162260ustar00rootroot00000000000000package 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 } crypto-0.57.0/x509util/utils_test.go000066400000000000000000000161561474156331600172770ustar00rootroot00000000000000package 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) } }) } }