pax_global_header00006660000000000000000000000064146453334650014527gustar00rootroot0000000000000052 comment=df25ef8d21722b66f866cdc13f218473648126dc tspclient-go-0.2.0/000077500000000000000000000000001464533346500141365ustar00rootroot00000000000000tspclient-go-0.2.0/.github/000077500000000000000000000000001464533346500154765ustar00rootroot00000000000000tspclient-go-0.2.0/.github/.codecov.yml000066400000000000000000000012101464533346500177130ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. coverage: status: project: default: target: 80%tspclient-go-0.2.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001464533346500176615ustar00rootroot00000000000000tspclient-go-0.2.0/.github/ISSUE_TEMPLATE/bug-or-issue.yaml000066400000000000000000000044231464533346500230710ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: 🐛 Bug or Issue description: Something is not working as expected or not working at all! Report it here! labels: [bug, triage] body: - type: markdown attributes: value: | Thank you for taking the time to fill out this issue report. 🛑 Please check existing issues first before continuing: https://github.com/notaryproject/tspclient-go/issues - type: textarea id: verbatim validations: required: true attributes: label: "What is not working as expected?" description: "In your own words, describe what the issue is." - type: textarea id: expect validations: required: true attributes: label: "What did you expect to happen?" description: "A clear and concise description of what you expected to happen." - type: textarea id: reproduce validations: required: true attributes: label: "How can we reproduce it?" description: "Detailed steps to reproduce the behavior, code snippets are welcome." - type: textarea id: environment validations: required: true attributes: label: Describe your environment description: "OS and Golang version" - type: textarea id: version validations: required: true attributes: label: What is the version of your tspclient-go Library? description: "Check the `go.mod` file for the library version." - type: markdown attributes: value: | If you want to contribute to this project, we will be happy to guide you through the contribution process especially when you already have a good proposal or understanding of how to fix this issue. Join us at https://slack.cncf.io/ and choose #notary-project channel. tspclient-go-0.2.0/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000013411464533346500216500ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. blank_issues_enabled: false contact_links: - name: Ask a question url: https://slack.cncf.io/ about: "Join #notary-project channel on CNCF Slack" tspclient-go-0.2.0/.github/ISSUE_TEMPLATE/feature-request.yaml000066400000000000000000000041111464533346500236630ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: 🚀 Feature Request description: Suggest an idea for this project. labels: [enhancement, triage] body: - type: markdown attributes: value: | Thank you for taking the time to suggest a useful feature for the project! - type: textarea id: problem validations: required: true attributes: label: "Is your feature request related to a problem?" description: "A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]" - type: textarea id: solution validations: required: true attributes: label: "What solution do you propose?" description: "A clear and concise description of what you want to happen." - type: textarea id: alternatives validations: required: true attributes: label: "What alternatives have you considered?" description: "A clear and concise description of any alternative solutions or features you've considered." - type: textarea id: context validations: required: false attributes: label: "Any additional context?" description: "Add any other context or screenshots about the feature request here." - type: markdown attributes: value: | If you want to contribute to this project, we will be happy to guide you through the contribution process especially when you already have a good proposal or understanding of how to improve the functionality. Join us at https://slack.cncf.io/ and choose #notary-project channel. tspclient-go-0.2.0/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000013021464533346500212730ustar00rootroot00000000000000**What this PR does / why we need it**: **Which issue(s) this PR resolves** *(optional, in `resolves #(, resolves #, ...)` format, will close the issue(s) when PR gets merged)*: Resolves # **Please check the following list**: - [ ] Does the affected code have corresponding tests, e.g. unit test? - [ ] Does this introduce breaking changes that would require an announcement or bumping the major version? - [ ] Do all new files have an appropriate license header? tspclient-go-0.2.0/.github/dependabot.yml000066400000000000000000000014241464533346500203270ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. version: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "weekly" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" tspclient-go-0.2.0/.github/licenserc.yml000066400000000000000000000026571464533346500202020ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. header: license: spdx-id: Apache-2.0 content: | Copyright The Notary Project Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. paths-ignore: - '**/*.md' - 'CODEOWNERS' - 'LICENSE' - 'MAINTAINERS' - 'go.mod' - 'go.sum' - '**/testdata/**' comment: on-failure dependency: files: - go.modtspclient-go-0.2.0/.github/workflows/000077500000000000000000000000001464533346500175335ustar00rootroot00000000000000tspclient-go-0.2.0/.github/workflows/build.yml000066400000000000000000000014661464533346500213640ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: build on: push: branches: main pull_request: branches: main jobs: build: uses: notaryproject/notation-core-go/.github/workflows/reusable-build.yml@main secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} tspclient-go-0.2.0/.github/workflows/codeql.yml000066400000000000000000000014421464533346500215260ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: "CodeQL" on: push: branches: main pull_request: branches: main schedule: - cron: '35 13 * * 5' jobs: analyze: uses: notaryproject/notation-core-go/.github/workflows/reusable-codeql.yml@maintspclient-go-0.2.0/.github/workflows/license-checker.yml000066400000000000000000000015111464533346500233000ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: License Checker on: push: branches: main pull_request: branches: main permissions: contents: write pull-requests: write jobs: check-license: uses: notaryproject/notation-core-go/.github/workflows/reusable-license-checker.yml@maintspclient-go-0.2.0/.gitignore000066400000000000000000000017111464533346500161260ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ # Go workspace file go.work # Code Editors .vscode .idea *.sublime-project *.sublime-workspace # Custom coverage.txttspclient-go-0.2.0/CODEOWNERS000066400000000000000000000003111464533346500155240ustar00rootroot00000000000000# Repo-Level Owners (in alphabetical order) # Note: This is only for the notaryproject/tspclient-go repo * @gokarnm @JeyJeyGao @niazfk @priteshbandi @shizhMSFT @toddysm @Two-Hearts @vaninrao10 @yizha1 tspclient-go-0.2.0/LICENSE000066400000000000000000000261351464533346500151520ustar00rootroot00000000000000 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. tspclient-go-0.2.0/MAINTAINERS000066400000000000000000000015751464533346500156430ustar00rootroot00000000000000# Org-Level Maintainers (in alphabetical order) # Pattern: [First Name] [Last Name] <[Email Address]> ([GitHub Handle]) Niaz Khan (@niazfk) Pritesh Bandi (@priteshbandi) Shiwei Zhang (@shizhMSFT) Toddy Mladenov (@toddysm) Vani Rao (@vaninrao10) Yi Zha (@yizha1) # Repo-Level Maintainers (in alphabetical order) # Pattern: [First Name] [Last Name] <[Email Address]> ([GitHub Handle]) # Note: This is for the notaryproject/tspclient-go repo Junjie Gao (@JeyJeyGao) Patrick Zheng (@Two-Hearts) Shiwei Zhang (@shizhMSFT) # Emeritus Org Maintainers (in alphabetical order) Justin Cormack (@justincormack) Steve Lasker (@stevelasker)tspclient-go-0.2.0/Makefile000066400000000000000000000024371464533346500156040ustar00rootroot00000000000000# Copyright The Notary Project Authors. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. .PHONY: help help: @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-25s\033[0m %s\n", $$1, $$2}' .PHONY: all all: test .PHONY: test test: check-line-endings ## run unit tests go test -race -v -coverprofile=coverage.txt -covermode=atomic ./... .PHONY: clean clean: git status --ignored --short | grep '^!! ' | sed 's/!! //' | xargs rm -rf .PHONY: check-line-endings check-line-endings: ## check line endings ! find . -name "*.go" -type f -exec file "{}" ";" | grep CRLF ! find scripts -name "*.sh" -type f -exec file "{}" ";" | grep CRLF .PHONY: fix-line-endings fix-line-endings: ## fix line endings find . -type f -name "*.go" -exec sed -i -e "s/\r//g" {} + tspclient-go-0.2.0/README.md000066400000000000000000000022061464533346500154150ustar00rootroot00000000000000# tspclient-go [![Build Status](https://github.com/notaryproject/tspclient-go/actions/workflows/build.yml/badge.svg?event=push&branch=main)](https://github.com/notaryproject/tspclient-go/actions/workflows/build.yml?query=workflow%3Abuild+event%3Apush+branch%3Amain) [![codecov](https://codecov.io/gh/notaryproject/tspclient-go/branch/main/graph/badge.svg)](https://codecov.io/gh/notaryproject/tspclient-go) [![Go Reference](https://pkg.go.dev/badge/github.com/notaryproject/tspclient-go.svg)](https://pkg.go.dev/github.com/notaryproject/tspclient-go@main) tspclient-go provides implementation of the Time-Stamp Protocol (TSP) client as specified in RFC 3161. ## Table of Contents - [Documentation](#documentation) - [Code of Conduct](#code-of-conduct) - [License](#license) ## Documentation Library documentation is available at [Go Reference](https://pkg.go.dev/github.com/notaryproject/tspclient-go). ## Code of Conduct This project has adopted the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). ## License This project is covered under the Apache 2.0 license. You can read the license [here](LICENSE).tspclient-go-0.2.0/conformance_test.go000066400000000000000000000357201464533346500200250ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tspclient import ( "context" "crypto" "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "math" "math/big" "testing" "time" "github.com/notaryproject/tspclient-go/internal/cms" "github.com/notaryproject/tspclient-go/internal/hashutil" "github.com/notaryproject/tspclient-go/internal/oid" "github.com/notaryproject/tspclient-go/pki" ) // responseRejection is a general response for request rejection. var responseRejection = &Response{ Status: pki.StatusInfo{ Status: pki.StatusRejection, }, } var ( // sha1WithRSA is defined in RFC 8017 C ASN.1 Module; bad algorithm, // for testing purpose only sha1WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5} ) // testTSA is a Timestamping Authority for testing purpose. type testTSA struct { // key is the TSA signing key. key *rsa.PrivateKey // cert is the self-signed certificate by the TSA signing key. cert *x509.Certificate // nowFunc provides the current time. time.Now() is used if nil. nowFunc func() time.Time // test time zone other than UTC malformedTimeZone bool } func TestTSATimestampGranted(t *testing.T) { // prepare TSA now := time.Date(2021, 9, 18, 11, 54, 34, 0, time.UTC) tsa, err := newTestTSA(false, true) if err != nil { t.Fatalf("NewTSA() error = %v", err) } tsa.nowFunc = func() time.Time { return now } // do timestamp message := []byte("notation") nonce, err := generateNonce() if err != nil { t.Fatal("failed to create nonce:", err) } requestOpts := RequestOptions{ Content: message, HashAlgorithm: crypto.SHA256, Nonce: nonce, } req, err := NewRequest(requestOpts) if err != nil { t.Fatalf("NewRequest() error = %v", err) } ctx := context.Background() resp, err := tsa.Timestamp(ctx, req) if err != nil { t.Fatalf("TSA.Timestamp() error = %v", err) } wantStatus := pki.StatusGranted if got := resp.Status.Status; got != wantStatus { t.Fatalf("Response.Status = %v, want %v", got, wantStatus) } // verify timestamp token token, err := resp.SignedToken() if err != nil { t.Fatalf("Response.SignedToken() error = %v", err) } roots := x509.NewCertPool() roots.AddCert(tsa.certificate()) opts := x509.VerifyOptions{ Roots: roots, } if _, err := token.Verify(context.Background(), opts); err != nil { t.Fatal("SignedToken.Verify() error =", err) } info, err := token.Info() if err != nil { t.Fatal("SignedToken.Info() error =", err) } timestamp, err := info.Validate(message) if err != nil { t.Errorf("TSTInfo.Timestamp() error = %v", err) } wantTimestampValue := time.Date(2021, 9, 18, 11, 54, 34, 0, time.UTC) wantTimestampAccuracy := time.Second if timestamp.Value != wantTimestampValue { t.Fatalf("TSTInfo.Timestamp() Timestamp = %v, want %v", wantTimestampValue, timestamp.Value) } if timestamp.Accuracy != wantTimestampAccuracy { t.Fatalf("TSTInfo.Timestamp() Timestamp Accuracy = %v, want %v", wantTimestampAccuracy, timestamp.Accuracy) } } func TestTSATimestampRejection(t *testing.T) { // prepare TSA tsa, err := newTestTSA(false, true) if err != nil { t.Fatalf("NewTSA() error = %v", err) } // do timestamp requestOpts := RequestOptions{ Content: []byte("notation"), HashAlgorithm: crypto.SHA256, } req, err := NewRequest(requestOpts) if err != nil { t.Fatalf("NewRequest() error = %v", err) } req.MessageImprint.HashAlgorithm.Algorithm = sha1WithRSA // set bad algorithm ctx := context.Background() resp, err := tsa.Timestamp(ctx, req) if err != nil { t.Fatalf("TSA.Timestamp() error = %v", err) } wantStatus := pki.StatusRejection if got := resp.Status.Status; got != wantStatus { t.Fatalf("Response.Status = %v, want %v", got, wantStatus) } } func TestTSATimestampMalformedExtKeyUsage(t *testing.T) { // prepare TSA now := time.Date(2021, 9, 18, 11, 54, 34, 0, time.UTC) tsa, err := newTestTSA(true, false) if err != nil { t.Fatalf("NewTSA() error = %v", err) } tsa.nowFunc = func() time.Time { return now } // do timestamp requestOpts := RequestOptions{ Content: []byte("notation"), HashAlgorithm: crypto.SHA256, } req, err := NewRequest(requestOpts) if err != nil { t.Fatalf("NewRequest() error = %v", err) } ctx := context.Background() resp, err := tsa.Timestamp(ctx, req) if err != nil { t.Fatalf("TSA.Timestamp() error = %v", err) } wantStatus := pki.StatusGranted if got := resp.Status.Status; got != wantStatus { t.Fatalf("Response.Status = %v, want %v", got, wantStatus) } // verify timestamp token token, err := resp.SignedToken() if err != nil { t.Fatalf("Response.SignedToken() error = %v", err) } roots := x509.NewCertPool() roots.AddCert(tsa.certificate()) opts := x509.VerifyOptions{ Roots: roots, } expectedErrMsg := "failed to verify signed token: signing certificate must have and only have ExtKeyUsageTimeStamping as extended key usage" if _, err := token.Verify(context.Background(), opts); err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } } func TestTSATimestampNonCriticalExtKeyUsage(t *testing.T) { // prepare TSA now := time.Date(2021, 9, 18, 11, 54, 34, 0, time.UTC) tsa, err := newTestTSA(false, false) if err != nil { t.Fatalf("NewTSA() error = %v", err) } tsa.nowFunc = func() time.Time { return now } // do timestamp requestOpts := RequestOptions{ Content: []byte("notation"), HashAlgorithm: crypto.SHA256, } req, err := NewRequest(requestOpts) if err != nil { t.Fatalf("NewRequest() error = %v", err) } ctx := context.Background() resp, err := tsa.Timestamp(ctx, req) if err != nil { t.Fatalf("TSA.Timestamp() error = %v", err) } wantStatus := pki.StatusGranted if got := resp.Status.Status; got != wantStatus { t.Fatalf("Response.Status = %v, want %v", got, wantStatus) } // verify timestamp token token, err := resp.SignedToken() if err != nil { t.Fatalf("Response.SignedToken() error = %v", err) } roots := x509.NewCertPool() roots.AddCert(tsa.certificate()) opts := x509.VerifyOptions{ Roots: roots, } expectedErrMsg := "failed to verify signed token: signing certificate extended key usage extension must be set as critical" if _, err := token.Verify(context.Background(), opts); err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } } func TestTSATimestampWithoutCertificate(t *testing.T) { // prepare TSA now := time.Date(2021, 9, 18, 11, 54, 34, 0, time.UTC) tsa, err := newTestTSA(false, true) if err != nil { t.Fatalf("NewTSA() error = %v", err) } tsa.nowFunc = func() time.Time { return now } // do timestamp message := []byte("notation") requestOpts := RequestOptions{ Content: message, HashAlgorithm: crypto.SHA256, NoCert: true, } req, err := NewRequest(requestOpts) if err != nil { t.Fatalf("NewRequest() error = %v", err) } ctx := context.Background() resp, err := tsa.Timestamp(ctx, req) if err != nil { t.Fatalf("TSA.Timestamp() error = %v", err) } wantStatus := pki.StatusGranted if got := resp.Status.Status; got != wantStatus { t.Fatalf("Response.Status = %v, want %v", got, wantStatus) } // verify timestamp token token, err := resp.SignedToken() if err != nil { t.Fatalf("Response.SignedToken() error = %v", err) } roots := x509.NewCertPool() roots.AddCert(tsa.certificate()) opts := x509.VerifyOptions{ Roots: roots, } expectedErrMsg := "failed to verify signed token: signing certificate not found in the timestamp token" _, err = token.Verify(context.Background(), opts) if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } } // newTestTSA creates a testTSA with random credentials. func newTestTSA(malformedExtKeyUsage, criticalTimestampingExtKeyUsage bool) (*testTSA, error) { // generate key key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return nil, err } // generate certificate serialNumber, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) if err != nil { return nil, err } var extKeyUsages []x509.ExtKeyUsage if malformedExtKeyUsage { extKeyUsages = append(extKeyUsages, x509.ExtKeyUsageAny) } else { extKeyUsages = append(extKeyUsages, x509.ExtKeyUsageTimeStamping) } now := time.Now() template := x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ CommonName: "timestamp test", }, NotBefore: now, NotAfter: now.Add(365 * 24 * time.Hour), // 1 year KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: extKeyUsages, BasicConstraintsValid: true, } if criticalTimestampingExtKeyUsage { extValue, err := asn1.Marshal([]asn1.ObjectIdentifier{oid.Timestamping}) if err != nil { return nil, err } template.ExtraExtensions = []pkix.Extension{ { Id: oid.ExtKeyUsage, Critical: true, Value: extValue, }, } } certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, key.Public(), key) if err != nil { return nil, err } cert, err := x509.ParseCertificate(certBytes) if err != nil { return nil, err } return &testTSA{ key: key, cert: cert, }, nil } // certificate returns the certificate used by the server. func (tsa *testTSA) certificate() *x509.Certificate { return tsa.cert } // Timestamp stamps the time with the given request. func (tsa *testTSA) Timestamp(_ context.Context, req *Request) (*Response, error) { // validate request if req.Version != 1 { return responseRejection, nil } hash, ok := oid.ToHash(req.MessageImprint.HashAlgorithm.Algorithm) if !ok { return responseRejection, nil } if hashedMessage := req.MessageImprint.HashedMessage; len(hashedMessage) != hash.Size() { return responseRejection, nil } // generate token info policy := asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 4146, 2} // time-stamp-policies switch hash { case crypto.SHA1: policy = append(policy, 2) case crypto.SHA256, crypto.SHA384, crypto.SHA512: policy = append(policy, 3) default: return responseRejection, nil } infoBytes, err := tsa.generateTokenInfo(req, policy) if err != nil { return nil, err } // generate signed data signed, err := tsa.generateSignedData(infoBytes, req.CertReq) if err != nil { return nil, err } content, err := convertToRawASN1(signed, "explicit,tag:0") if err != nil { return nil, err } // generate content info contentInfo := cms.ContentInfo{ ContentType: oid.SignedData, Content: content, } token, err := convertToRawASN1(contentInfo, "") if err != nil { return nil, err } // generate response return &Response{ Status: pki.StatusInfo{ Status: pki.StatusGranted, }, TimestampToken: token, }, nil } // generateTokenInfo generate timestamp token info. func (tsa *testTSA) generateTokenInfo(req *Request, policy asn1.ObjectIdentifier) ([]byte, error) { serialNumber, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) if err != nil { return nil, err } nowFunc := tsa.nowFunc if nowFunc == nil { nowFunc = time.Now } info := TSTInfo{ Version: 1, Policy: policy, MessageImprint: req.MessageImprint, SerialNumber: serialNumber, GenTime: nowFunc().UTC().Truncate(time.Second), Accuracy: Accuracy{ Seconds: 1, }, Nonce: req.Nonce, } if tsa.malformedTimeZone { info.GenTime = nowFunc().Truncate(time.Second) } return asn1.Marshal(info) } // generateSignedData generate signed data according to func (tsa *testTSA) generateSignedData(infoBytes []byte, requestCert bool) (cms.SignedData, error) { var issuer asn1.RawValue _, err := asn1.Unmarshal(tsa.cert.RawIssuer, &issuer) if err != nil { return cms.SignedData{}, err } contentType, err := convertToRawASN1([]interface{}{oid.TSTInfo}, "set") if err != nil { return cms.SignedData{}, err } infoDigest, err := hashutil.ComputeHash(crypto.SHA256, infoBytes) if err != nil { return cms.SignedData{}, err } messageDigest, err := convertToRawASN1([]interface{}{infoDigest}, "set") if err != nil { return cms.SignedData{}, err } signingTime, err := convertToRawASN1([]interface{}{time.Now().UTC()}, "set") if err != nil { return cms.SignedData{}, err } certHash, err := hashutil.ComputeHash(crypto.SHA256, tsa.cert.Raw) if err != nil { return cms.SignedData{}, err } signingCertificateV2 := signingCertificateV2{ Certificates: []eSSCertIDv2{ { CertHash: certHash, }, }, } signingCertificateV2Raw, err := convertToRawASN1([]interface{}{signingCertificateV2}, "set") if err != nil { return cms.SignedData{}, err } signed := cms.SignedData{ Version: 3, DigestAlgorithmIdentifiers: []pkix.AlgorithmIdentifier{ { Algorithm: oid.SHA256, }, }, EncapsulatedContentInfo: cms.EncapsulatedContentInfo{ ContentType: oid.TSTInfo, Content: infoBytes, }, SignerInfos: []cms.SignerInfo{ { Version: 1, SignerIdentifier: cms.IssuerAndSerialNumber{ Issuer: issuer, SerialNumber: tsa.cert.SerialNumber, }, DigestAlgorithm: pkix.AlgorithmIdentifier{ Algorithm: oid.SHA256, }, SignedAttributes: cms.Attributes{ { Type: oid.ContentType, Values: contentType, }, { Type: oid.MessageDigest, Values: messageDigest, }, { Type: oid.SigningTime, Values: signingTime, }, { Type: oid.SigningCertificateV2, Values: signingCertificateV2Raw, }, }, SignatureAlgorithm: pkix.AlgorithmIdentifier{ Algorithm: oid.SHA256WithRSA, }, }, }, } if requestCert { certs, err := convertToRawASN1(tsa.cert.Raw, "tag:0") if err != nil { return cms.SignedData{}, err } signed.Certificates = certs } // sign data signer := &signed.SignerInfos[0] encodedAttributes, err := asn1.MarshalWithParams(signer.SignedAttributes, "set") if err != nil { return cms.SignedData{}, err } hashedAttributes, err := hashutil.ComputeHash(crypto.SHA256, encodedAttributes) if err != nil { return cms.SignedData{}, err } signer.Signature, err = rsa.SignPKCS1v15(rand.Reader, tsa.key, crypto.SHA256, hashedAttributes) if err != nil { return cms.SignedData{}, err } return signed, nil } // convertToRawASN1 convert any data ASN.1 data structure to asn1.RawValue. func convertToRawASN1(val interface{}, params string) (asn1.RawValue, error) { b, err := asn1.MarshalWithParams(val, params) if err != nil { return asn1NullRawValue, err } var raw asn1.RawValue _, err = asn1.UnmarshalWithParams(b, &raw, params) if err != nil { return asn1NullRawValue, err } return raw, nil } tspclient-go-0.2.0/errors.go000066400000000000000000000051341464533346500160040ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tspclient // CertificateNotFoundError is used when identified certificate is not found // in the timestampe token type CertificateNotFoundError error // MalformedRequestError is used when timestamping request is malformed. type MalformedRequestError struct { Msg string Detail error } // Error returns error message. func (e *MalformedRequestError) Error() string { msg := "malformed timestamping request" if e.Msg != "" { msg += ": " + e.Msg } if e.Detail != nil { msg += ": " + e.Detail.Error() } return msg } // Unwrap returns the internal error. func (e *MalformedRequestError) Unwrap() error { return e.Detail } // InvalidResponseError is used when timestamping response is invalid. type InvalidResponseError struct { Msg string Detail error } // Error returns error message. func (e *InvalidResponseError) Error() string { msg := "invalid timestamping response" if e.Msg != "" { msg += ": " + e.Msg } if e.Detail != nil { msg += ": " + e.Detail.Error() } return msg } // Unwrap returns the internal error. func (e *InvalidResponseError) Unwrap() error { return e.Detail } // SignedTokenVerificationError is used when fail to verify signed token. type SignedTokenVerificationError struct { Msg string Detail error } // Error returns error message. func (e *SignedTokenVerificationError) Error() string { msg := "failed to verify signed token" if e.Msg != "" { msg += ": " + e.Msg } if e.Detail != nil { msg += ": " + e.Detail.Error() } return msg } // Unwrap returns the internal error. func (e *SignedTokenVerificationError) Unwrap() error { return e.Detail } // TSTInfoError is used when fail a TSTInfo is invalid. type TSTInfoError struct { Msg string Detail error } // Error returns error message. func (e *TSTInfoError) Error() string { msg := "invalid TSTInfo" if e.Msg != "" { msg += ": " + e.Msg } if e.Detail != nil { msg += ": " + e.Detail.Error() } return msg } // Unwrap returns the internal error. func (e *TSTInfoError) Unwrap() error { return e.Detail } tspclient-go-0.2.0/errors_test.go000066400000000000000000000064331464533346500170460ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tspclient import ( "errors" "testing" ) var errTestInner = errors.New("test inner error") func TestMalformedRequestError(t *testing.T) { newErr := MalformedRequestError{} expectedErrMsg := "malformed timestamping request" if newErr.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, newErr) } newErr = MalformedRequestError{ Detail: errTestInner, } expectedErrMsg = "malformed timestamping request: test inner error" if newErr.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, newErr) } innerErr := newErr.Unwrap() expectedErrMsg = "test inner error" if innerErr.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, innerErr) } } func TestInvalidResponseError(t *testing.T) { newErr := InvalidResponseError{} expectedErrMsg := "invalid timestamping response" if newErr.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, newErr) } newErr = InvalidResponseError{ Detail: errTestInner, } expectedErrMsg = "invalid timestamping response: test inner error" if newErr.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, newErr) } innerErr := newErr.Unwrap() expectedErrMsg = "test inner error" if innerErr.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, innerErr) } } func TestSignedTokenVerificationError(t *testing.T) { newErr := SignedTokenVerificationError{Msg: "test error msg"} expectedErrMsg := "failed to verify signed token: test error msg" if newErr.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, newErr) } newErr = SignedTokenVerificationError{ Detail: errTestInner, } expectedErrMsg = "failed to verify signed token: test inner error" if newErr.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, newErr) } innerErr := newErr.Unwrap() expectedErrMsg = "test inner error" if innerErr.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, newErr) } } func TestTSTInfoError(t *testing.T) { newErr := TSTInfoError{} expectedErrMsg := "invalid TSTInfo" if newErr.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, newErr) } newErr = TSTInfoError{ Detail: errTestInner, } expectedErrMsg = "invalid TSTInfo: test inner error" if newErr.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, newErr) } innerErr := newErr.Unwrap() expectedErrMsg = "test inner error" if innerErr.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, innerErr) } } tspclient-go-0.2.0/go.mod000066400000000000000000000000661464533346500152460ustar00rootroot00000000000000module github.com/notaryproject/tspclient-go go 1.21 tspclient-go-0.2.0/http.go000066400000000000000000000070141464533346500154460ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tspclient import ( "bytes" "context" "fmt" "io" "net/http" "net/url" "time" ) // maxBodyLength specifies the max content can be received from the remote // server. // The legnth of a regular TSA response with certificates is usually less than // 10 KiB. var maxBodyLength = 1 * 1024 * 1024 // 1 MiB // const for MediaTypes defined in RFC 3161 3.4 const ( // MediaTypeTimestampQuery is the content-type of timestamp query. // RFC 3161 3.4 MediaTypeTimestampQuery = "application/timestamp-query" // MediaTypeTimestampReply is the content-type of timestamp reply // RFC 3161 3.4 MediaTypeTimestampReply = "application/timestamp-reply" ) // httpTimestamper is a HTTP-based timestamper. type httpTimestamper struct { httpClient *http.Client endpoint string } // NewHTTPTimestamper creates a HTTP-based timestamper with the endpoint // provided by the TSA. // http.DefaultTransport is used if nil RoundTripper is passed. func NewHTTPTimestamper(httpClient *http.Client, endpoint string) (Timestamper, error) { if httpClient == nil { httpClient = &http.Client{Timeout: 5 * time.Second} } if _, err := url.Parse(endpoint); err != nil { return nil, err } return &httpTimestamper{ httpClient: httpClient, endpoint: endpoint, }, nil } // Timestamp sends the request to the remote TSA server for timestamping. // // Reference: RFC 3161 3.4 Time-Stamp Protocol via HTTP func (ts *httpTimestamper) Timestamp(ctx context.Context, req *Request) (*Response, error) { // sanity check if err := req.Validate(); err != nil { return nil, err } // prepare for http request reqBytes, err := req.MarshalBinary() if err != nil { return nil, err } hReq, err := http.NewRequestWithContext(ctx, http.MethodPost, ts.endpoint, bytes.NewReader(reqBytes)) if err != nil { return nil, err } hReq.Header.Set("Content-Type", MediaTypeTimestampQuery) // send the request to the remote TSA server hResp, err := ts.httpClient.Do(hReq) if err != nil { return nil, err } defer hResp.Body.Close() // verify HTTP response if hResp.StatusCode != http.StatusOK { return nil, fmt.Errorf("%s %q: https response bad status: %s", http.MethodPost, ts.endpoint, hResp.Status) } if contentType := hResp.Header.Get("Content-Type"); contentType != MediaTypeTimestampReply { return nil, fmt.Errorf("%s %q: unexpected response content type: %s", http.MethodPost, ts.endpoint, contentType) } // read TSA response lr := &io.LimitedReader{ R: hResp.Body, N: int64(maxBodyLength), } respBytes, err := io.ReadAll(lr) if err != nil { return nil, err } if lr.N == 0 { return nil, fmt.Errorf("%s %q: unexpected large http response, max response body size allowed is %d MiB", hResp.Request.Method, hResp.Request.URL, maxBodyLength/1024/1024) } var resp Response if err := resp.UnmarshalBinary(respBytes); err != nil { return nil, err } // validate response against RFC 3161 if err := resp.Validate(req); err != nil { return nil, err } return &resp, nil } tspclient-go-0.2.0/http_test.go000066400000000000000000000315071464533346500165110ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tspclient import ( "bytes" "context" "crypto" "crypto/x509" "encoding/asn1" "io" "net/http" "net/http/httptest" "os" "strings" "testing" "time" "github.com/notaryproject/tspclient-go/internal/hashutil" "github.com/notaryproject/tspclient-go/pki" ) func TestHTTPTimestampGranted(t *testing.T) { // setup test server testResp, err := os.ReadFile("testdata/granted.tsq") if err != nil { t.Fatal("failed to read test response:", err) } ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { const wantContentType = MediaTypeTimestampQuery if got := r.Header.Get("Content-Type"); got != wantContentType { t.Fatalf("TimestampRequest.ContentType = %v, want %v", err, wantContentType) } if _, err := io.ReadAll(r.Body); err != nil { t.Fatalf("TimestampRequest.Body read error = %v", err) } // write reply w.Header().Set("Content-Type", MediaTypeTimestampReply) if _, err := w.Write(testResp); err != nil { t.Error("failed to write response:", err) } })) defer ts.Close() // do timestamp tsa, err := NewHTTPTimestamper(nil, ts.URL) if err != nil { t.Fatalf("NewHTTPTimestamper() error = %v", err) } message := []byte("notation") requestOpts := RequestOptions{ Content: message, HashAlgorithm: crypto.SHA256, HashAlgorithmParameters: asn1.RawValue{ Tag: 5, FullBytes: []byte{5, 0}, }, NoNonce: true, } req, err := NewRequest(requestOpts) if err != nil { t.Fatalf("NewRequest() error = %v", err) } ctx := context.Background() resp, err := tsa.Timestamp(ctx, req) if err != nil { t.Fatalf("httpTimestamper.Timestamp() error = %v", err) } wantStatus := pki.StatusGranted if got := resp.Status.Status; got != wantStatus { t.Fatalf("Response.Status = %v, want %v", got, wantStatus) } // verify timestamp token token, err := resp.SignedToken() if err != nil { t.Fatalf("Response.SignedToken() error = %v", err) } roots := x509.NewCertPool() rootCABytes, err := os.ReadFile("testdata/GlobalSignRootCA.crt") if err != nil { t.Fatal("failed to read root CA certificate:", err) } if ok := roots.AppendCertsFromPEM(rootCABytes); !ok { t.Fatal("failed to load root CA certificate") } opts := x509.VerifyOptions{ Roots: roots, CurrentTime: time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC), } certs, err := token.Verify(context.Background(), opts) if err != nil { t.Fatal("SignedToken.Verify() error =", err) } if got := len(certs); got != 4 { t.Fatalf("SignedToken.Verify() len([]*x509.Certificate) = %v, want %v", got, 4) } certThumbprint, err := hashutil.ComputeHash(crypto.SHA256, certs[0].Raw) if err != nil { t.Fatal("failed to compute certificate thumbprint:", err) } wantCertThumbprint := []byte{ 0x13, 0xd6, 0xe9, 0xc4, 0x20, 0xff, 0x6d, 0x4e, 0x27, 0x54, 0x72, 0x8c, 0x68, 0xe7, 0x78, 0x82, 0x65, 0x64, 0x67, 0xdb, 0x9a, 0x19, 0x0f, 0x81, 0x65, 0x97, 0xf6, 0x7f, 0xb6, 0xcc, 0xc6, 0xf9, } if !bytes.Equal(certThumbprint, wantCertThumbprint) { t.Fatalf("SignedToken.Verify() = %v, want %v", certThumbprint, wantCertThumbprint) } info, err := token.Info() if err != nil { t.Fatal("SignedToken.Info() error =", err) } timestamp, err := info.Validate(message) if err != nil { t.Errorf("TSTInfo.Timestamp() error = %v", err) } wantTimestampValue := time.Date(2021, 9, 18, 11, 54, 34, 0, time.UTC) wantTimestampAccuracy := time.Second if timestamp.Value != wantTimestampValue { t.Fatalf("TSTInfo.Timestamp() Timestamp = %v, want %v", wantTimestampValue, timestamp.Value) } if timestamp.Accuracy != wantTimestampAccuracy { t.Fatalf("TSTInfo.Timestamp() Timestamp Accuracy = %v, want %v", wantTimestampAccuracy, timestamp.Accuracy) } } func TestHTTPTimestampRejection(t *testing.T) { // setup test server testResp, err := os.ReadFile("testdata/rejection.tsq") if err != nil { t.Fatal("failed to read test response:", err) } ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { const wantContentType = MediaTypeTimestampQuery if got := r.Header.Get("Content-Type"); got != wantContentType { t.Fatalf("TimestampRequest.ContentType = %v, want %v", err, wantContentType) } if _, err := io.ReadAll(r.Body); err != nil { t.Fatalf("TimestampRequest.Body read error = %v", err) } // write reply w.Header().Set("Content-Type", MediaTypeTimestampReply) if _, err := w.Write(testResp); err != nil { t.Error("failed to write response:", err) } })) defer ts.Close() // do timestamp tsa, err := NewHTTPTimestamper(nil, ts.URL) if err != nil { t.Fatalf("NewHTTPTimestamper() error = %v", err) } requestOpts := RequestOptions{ Content: []byte("notation"), HashAlgorithm: crypto.SHA256, HashAlgorithmParameters: asn1.RawValue{ Tag: 5, FullBytes: []byte{5, 0}, }, } req, err := NewRequest(requestOpts) if err != nil { t.Fatalf("NewRequest() error = %v", err) } ctx := context.Background() expectedErrMsg := "invalid timestamping response: invalid response with status code 2: rejected. Failure info: unrecognized or unsupported Algorithm Identifier" _, err = tsa.Timestamp(ctx, req) if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } } func TestHTTPTimestampBadEndpoint(t *testing.T) { // setup test server ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // write reply w.Header().Set("Content-Type", "application/json; charset=utf-8") if _, err := w.Write([]byte("{}")); err != nil { t.Error("failed to write response:", err) } })) defer ts.Close() // do timestamp tsa, err := NewHTTPTimestamper(nil, ts.URL) if err != nil { t.Fatalf("NewHTTPTimestamper() error = %v", err) } requestOpts := RequestOptions{ Content: []byte("notation"), HashAlgorithm: crypto.SHA256, HashAlgorithmParameters: asn1.RawValue{ Tag: 5, FullBytes: []byte{5, 0}, }, } req, err := NewRequest(requestOpts) if err != nil { t.Fatalf("NewRequest() error = %v", err) } ctx := context.Background() _, err = tsa.Timestamp(ctx, req) if err == nil { t.Fatalf("httpTimestamper.Timestamp() error = %v, wantErr %v", err, true) } } func TestHTTPTimestampEndpointNotFound(t *testing.T) { // setup test server ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) })) defer ts.Close() // do timestamp tsa, err := NewHTTPTimestamper(nil, ts.URL) if err != nil { t.Fatalf("NewHTTPTimestamper() error = %v", err) } requestOpts := RequestOptions{ Content: []byte("notation"), HashAlgorithm: crypto.SHA256, HashAlgorithmParameters: asn1.RawValue{ Tag: 5, FullBytes: []byte{5, 0}, }, } req, err := NewRequest(requestOpts) if err != nil { t.Fatalf("NewRequest() error = %v", err) } ctx := context.Background() _, err = tsa.Timestamp(ctx, req) if err == nil { t.Fatalf("httpTimestamper.Timestamp() error = %v, wantErr %v", err, true) } } func TestNewHTTPTimestamper(t *testing.T) { malformedURL := "http://[::1]/%" expectedErrMsg := `parse "http://[::1]/%": invalid URL escape "%"` if _, err := NewHTTPTimestamper(nil, malformedURL); err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } } func TestHttpTimestamperTimestamp(t *testing.T) { // setup test server testResp, err := os.ReadFile("testdata/granted.tsq") if err != nil { t.Fatal("failed to read test response:", err) } ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { const wantContentType = MediaTypeTimestampQuery if got := r.Header.Get("Content-Type"); got != wantContentType { t.Fatalf("TimestampRequest.ContentType = %v, want %v", err, wantContentType) } if _, err := io.ReadAll(r.Body); err != nil { t.Fatalf("TimestampRequest.Body read error = %v", err) } // write reply w.Header().Set("Content-Type", MediaTypeTimestampReply) if _, err := w.Write(testResp); err != nil { t.Error("failed to write response:", err) } })) defer ts.Close() tsa, err := NewHTTPTimestamper(nil, ts.URL) if err != nil { t.Fatalf("NewHTTPTimestamper() error = %v", err) } expectedErrMsg := "malformed timestamping request: request cannot be nil" if _, err := tsa.Timestamp(context.Background(), nil); err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } requestOpts := RequestOptions{ Content: []byte("notation"), HashAlgorithm: crypto.SHA256, HashAlgorithmParameters: asn1.RawValue{ Tag: 5, FullBytes: []byte{5, 0}, }, } req, err := NewRequest(requestOpts) if err != nil { t.Fatalf("NewRequest() error = %v", err) } expectedErrMsg = "net/http: nil Context" if _, err := tsa.Timestamp(nil, req); err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } ts2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { const wantContentType = MediaTypeTimestampQuery if got := r.Header.Get("Content-Type"); got != wantContentType { t.Fatalf("TimestampRequest.ContentType = %v, want %v", err, wantContentType) } if _, err := io.ReadAll(r.Body); err != nil { t.Fatalf("TimestampRequest.Body read error = %v", err) } // write reply w.Header().Set("Content-Type", MediaTypeTimestampReply) w.WriteHeader(http.StatusInternalServerError) if _, err := w.Write(testResp); err != nil { t.Error("failed to write response:", err) } })) defer ts.Close() tsa, err = NewHTTPTimestamper(nil, ts2.URL) if err != nil { t.Fatalf("NewHTTPTimestamper() error = %v", err) } expectedErrMsg = "https response bad status: 500 Internal Server Error" if _, err := tsa.Timestamp(context.Background(), req); err == nil || !strings.Contains(err.Error(), expectedErrMsg) { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } ts3 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { const wantContentType = MediaTypeTimestampQuery if got := r.Header.Get("Content-Type"); got != wantContentType { t.Fatalf("TimestampRequest.ContentType = %v, want %v", err, wantContentType) } if _, err := io.ReadAll(r.Body); err != nil { t.Fatalf("TimestampRequest.Body read error = %v", err) } // write reply w.Header().Set("Content-Type", "invalid-response-header") if _, err := w.Write(testResp); err != nil { t.Error("failed to write response:", err) } })) defer ts.Close() tsa, err = NewHTTPTimestamper(nil, ts3.URL) if err != nil { t.Fatalf("NewHTTPTimestamper() error = %v", err) } expectedErrMsg = "unexpected response content type: invalid-response-header" if _, err := tsa.Timestamp(context.Background(), req); err == nil || !strings.Contains(err.Error(), expectedErrMsg) { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } malformedResp, err := os.ReadFile("testdata/malformed.tsq") if err != nil { t.Fatal("failed to read test response:", err) } ts4 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { const wantContentType = MediaTypeTimestampQuery if got := r.Header.Get("Content-Type"); got != wantContentType { t.Fatalf("TimestampRequest.ContentType = %v, want %v", err, wantContentType) } if _, err := io.ReadAll(r.Body); err != nil { t.Fatalf("TimestampRequest.Body read error = %v", err) } // write reply w.Header().Set("Content-Type", MediaTypeTimestampReply) if _, err := w.Write(malformedResp); err != nil { t.Error("failed to write response:", err) } })) defer ts.Close() tsa, err = NewHTTPTimestamper(nil, ts4.URL) if err != nil { t.Fatalf("NewHTTPTimestamper() error = %v", err) } expectedErrMsg = "asn1: structure error" if _, err := tsa.Timestamp(context.Background(), req); err == nil || !strings.Contains(err.Error(), expectedErrMsg) { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } maxBodyLength = 0 tsa, err = NewHTTPTimestamper(nil, ts.URL) if err != nil { t.Fatalf("NewHTTPTimestamper() error = %v", err) } expectedErrMsg = "unexpected large http response, max response body size allowed is 0 MiB" if _, err := tsa.Timestamp(context.Background(), req); err == nil || !strings.Contains(err.Error(), expectedErrMsg) { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } } tspclient-go-0.2.0/internal/000077500000000000000000000000001464533346500157525ustar00rootroot00000000000000tspclient-go-0.2.0/internal/cms/000077500000000000000000000000001464533346500165345ustar00rootroot00000000000000tspclient-go-0.2.0/internal/cms/cms.go000066400000000000000000000143421464533346500176510ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package cms verifies Signed-Data defined in RFC 5652 Cryptographic Message // Syntax (CMS) / PKCS7 // // References: // - RFC 5652 Cryptographic Message Syntax (CMS) package cms import ( "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "math/big" ) // ContentInfo struct is used to represent the content of a CMS message, // which can be encrypted, signed, or both. // // References: RFC 5652 3 ContentInfo Type // // ContentInfo ::= SEQUENCE { // contentType ContentType, // content [0] EXPLICIT ANY DEFINED BY contentType } type ContentInfo struct { // ContentType field specifies the type of the content, which can be one of // several predefined types, such as data, signedData, envelopedData, or // encryptedData. Only signedData is supported currently. ContentType asn1.ObjectIdentifier // Content field contains the actual content of the message. Content asn1.RawValue `asn1:"explicit,tag:0"` } // SignedData struct is used to represent a signed CMS message, which contains // one or more signatures that are used to verify the authenticity and integrity // of the message. // // Reference: RFC 5652 5.1 SignedData // // SignedData ::= SEQUENCE { // version CMSVersion, // digestAlgorithms DigestAlgorithmIdentifiers, // encapContentInfo EncapsulatedContentInfo, // certificates [0] IMPLICIT CertificateSet OPTIONAL, // crls [1] IMPLICIT CertificateRevocationLists OPTIONAL, // signerInfos SignerInfos } type SignedData struct { // Version field specifies the syntax version number of the SignedData. Version int // DigestAlgorithmIdentifiers field specifies the digest algorithms used // by one or more signatures in SignerInfos. DigestAlgorithmIdentifiers []pkix.AlgorithmIdentifier `asn1:"set"` // EncapsulatedContentInfo field specifies the content that is signed. EncapsulatedContentInfo EncapsulatedContentInfo // Certificates field contains the certificates that are used to verify the // signatures in SignerInfos. Certificates asn1.RawValue `asn1:"optional,tag:0"` // CRLs field contains the Certificate Revocation Lists that are used to // verify the signatures in SignerInfos. CRLs []x509.RevocationList `asn1:"optional,tag:1"` // SignerInfos field contains one or more signatures. SignerInfos []SignerInfo `asn1:"set"` } // EncapsulatedContentInfo struct is used to represent the content of a CMS // message. // // References: RFC 5652 5.2 EncapsulatedContentInfo // // EncapsulatedContentInfo ::= SEQUENCE { // eContentType ContentType, // eContent [0] EXPLICIT OCTET STRING OPTIONAL } type EncapsulatedContentInfo struct { // ContentType is an object identifier. The object identifier uniquely // specifies the content type. ContentType asn1.ObjectIdentifier // Content field contains the actual content of the message. Content []byte `asn1:"explicit,optional,tag:0"` } // SignerInfo struct is used to represent a signature and related information // that is needed to verify the signature. // // Reference: RFC 5652 5.3 SignerInfo // // SignerInfo ::= SEQUENCE { // version CMSVersion, // sid SignerIdentifier, // digestAlgorithm DigestAlgorithmIdentifier, // signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL, // signatureAlgorithm SignatureAlgorithmIdentifier, // signature SignatureValue, // unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL } // // Only version 1 is supported. As defined in RFC 5652 5.3, SignerIdentifier // is IssuerAndSerialNumber when version is 1. type SignerInfo struct { // Version field specifies the syntax version number of the SignerInfo. Version int // SignerIdentifier field specifies the signer's certificate. Only IssuerAndSerialNumber // is supported currently. SignerIdentifier IssuerAndSerialNumber // DigestAlgorithm field specifies the digest algorithm used by the signer. DigestAlgorithm pkix.AlgorithmIdentifier // SignedAttributes field contains a collection of attributes that are // signed. SignedAttributes Attributes `asn1:"optional,tag:0"` // SignatureAlgorithm field specifies the signature algorithm used by the // signer. SignatureAlgorithm pkix.AlgorithmIdentifier // Signature field contains the actual signature. Signature []byte // UnsignedAttributes field contains a collection of attributes that are // not signed. UnsignedAttributes Attributes `asn1:"optional,tag:1"` } // IssuerAndSerialNumber struct is used to identify a certificate. // // Reference: RFC 5652 5.3 SignerIdentifier // // IssuerAndSerialNumber ::= SEQUENCE { // issuer Name, // serialNumber CertificateSerialNumber } type IssuerAndSerialNumber struct { // Issuer field identifies the certificate issuer. Issuer asn1.RawValue // SerialNumber field identifies the certificate. SerialNumber *big.Int } // Attribute struct is used to represent a attribute with type and values. // // Reference: RFC 5652 5.3 SignerInfo // // Attribute ::= SEQUENCE { // attrType OBJECT IDENTIFIER, // attrValues SET OF AttributeValue } type Attribute struct { // Type field specifies the type of the attribute. Type asn1.ObjectIdentifier // Values field contains the actual value of the attribute. Values asn1.RawValue `asn1:"set"` } // Attributes ::= SET SIZE (0..MAX) OF Attribute type Attributes []Attribute // Get tries to find the attribute by the given identifier, parse and store // the result in the value pointed to by out. func (a Attributes) Get(identifier asn1.ObjectIdentifier, out any) error { for _, attribute := range a { if identifier.Equal(attribute.Type) { _, err := asn1.Unmarshal(attribute.Values.Bytes, out) return err } } return ErrAttributeNotFound } tspclient-go-0.2.0/internal/cms/errors.go000066400000000000000000000037321464533346500204040ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cms import "errors" // ErrNotSignedData is returned if wrong content is provided when signed // data is expected. var ErrNotSignedData = errors.New("cms: content type is not signed-data") // ErrAttributeNotFound is returned if attribute is not found in a given set. var ErrAttributeNotFound = errors.New("attribute not found") // Verification errors var ( ErrSignerInfoNotFound = VerificationError{Message: "signerInfo not found"} ErrCertificateNotFound = VerificationError{Message: "certificate not found"} ) // SyntaxError indicates that the ASN.1 data is invalid. type SyntaxError struct { Message string Detail error } // Error returns error message. func (e SyntaxError) Error() string { msg := "cms: syntax error" if e.Message != "" { msg += ": " + e.Message } if e.Detail != nil { msg += ": " + e.Detail.Error() } return msg } // Unwrap returns the internal error. func (e SyntaxError) Unwrap() error { return e.Detail } // VerificationError indicates verification failures. type VerificationError struct { Message string Detail error } // Error returns error message. func (e VerificationError) Error() string { msg := "cms verification failure" if e.Message != "" { msg += ": " + e.Message } if e.Detail != nil { msg += ": " + e.Detail.Error() } return msg } // Unwrap returns the internal error. func (e VerificationError) Unwrap() error { return e.Detail } tspclient-go-0.2.0/internal/cms/errors_test.go000066400000000000000000000037401464533346500214420ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cms import ( "errors" "testing" ) func TestSyntaxError(t *testing.T) { tests := []struct { name string err SyntaxError wantMsg string }{ {"No detail", SyntaxError{Message: "test"}, "cms: syntax error: test"}, {"With detail", SyntaxError{Message: "test", Detail: errors.New("detail")}, "cms: syntax error: test: detail"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if gotMsg := tt.err.Error(); gotMsg != tt.wantMsg { t.Errorf("SyntaxError.Error() = %v, want %v", gotMsg, tt.wantMsg) } if gotDetail := tt.err.Unwrap(); gotDetail != tt.err.Detail { t.Errorf("SyntaxError.Unwrap() = %v, want %v", gotDetail, tt.err.Detail) } }) } } func TestVerificationError(t *testing.T) { tests := []struct { name string err VerificationError wantMsg string }{ {"No detail", VerificationError{Message: "test"}, "cms verification failure: test"}, {"With detail", VerificationError{Message: "test", Detail: errors.New("detail")}, "cms verification failure: test: detail"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if gotMsg := tt.err.Error(); gotMsg != tt.wantMsg { t.Errorf("VerificationError.Error() = %v, want %v", gotMsg, tt.wantMsg) } if gotDetail := tt.err.Unwrap(); gotDetail != tt.err.Detail { t.Errorf("VerificationError.Unwrap() = %v, want %v", gotDetail, tt.err.Detail) } }) } } tspclient-go-0.2.0/internal/cms/signed.go000066400000000000000000000264471464533346500203510ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cms import ( "bytes" "context" "crypto" "crypto/x509" "encoding/asn1" "encoding/hex" "errors" "fmt" "time" "github.com/notaryproject/tspclient-go/internal/encoding/asn1/ber" "github.com/notaryproject/tspclient-go/internal/hashutil" "github.com/notaryproject/tspclient-go/internal/oid" ) // ParsedSignedData is a parsed SignedData structure for golang friendly types. type ParsedSignedData struct { // Content is the content of the EncapsulatedContentInfo. Content []byte // ContentType is the content type of the EncapsulatedContentInfo. ContentType asn1.ObjectIdentifier // Certificates is the list of certificates in the SignedData. Certificates []*x509.Certificate // CRLs is the list of certificate revocation lists in the SignedData. CRLs []x509.RevocationList // SignerInfos is the list of signer information in the SignedData. SignerInfos []SignerInfo } // ParseSignedData parses ASN.1 BER-encoded SignedData structure to golang // friendly types. // // Only supported SignedData version is 3. func ParseSignedData(berData []byte) (*ParsedSignedData, error) { data, err := ber.ConvertToDER(berData) if err != nil { return nil, SyntaxError{Message: "invalid signed data: failed to convert from BER to DER", Detail: err} } var contentInfo ContentInfo if _, err := asn1.Unmarshal(data, &contentInfo); err != nil { return nil, SyntaxError{Message: "invalid content info: failed to unmarshal DER to ContentInfo", Detail: err} } if !oid.SignedData.Equal(contentInfo.ContentType) { return nil, ErrNotSignedData } var signedData SignedData if _, err := asn1.Unmarshal(contentInfo.Content.Bytes, &signedData); err != nil { return nil, SyntaxError{Message: "invalid signed data", Detail: err} } if signedData.Version != 3 { return nil, SyntaxError{Message: fmt.Sprintf("unsupported signed data version: got %d, want 3", signedData.Version)} } certs, err := x509.ParseCertificates(signedData.Certificates.Bytes) if err != nil { return nil, SyntaxError{Message: "failed to parse X.509 certificates from signed data. Only X.509 certificates are supported", Detail: err} } return &ParsedSignedData{ Content: signedData.EncapsulatedContentInfo.Content, ContentType: signedData.EncapsulatedContentInfo.ContentType, Certificates: certs, CRLs: signedData.CRLs, SignerInfos: signedData.SignerInfos, }, nil } // Verify attempts to verify the content in the parsed signed data against the signer // information. The `Intermediates` in the verify options will be ignored and // re-contrusted using the certificates in the parsed signed data. // If more than one signature is present, the successful validation of any signature // implies that the content in the parsed signed data is valid. // On successful verification, the list of signing certificates that successfully // verify is returned. // If all signatures fail to verify, the last error is returned. // // References: // - RFC 5652 5 Signed-data Content Type // - RFC 5652 5.4 Message Digest Calculation Process // - RFC 5652 5.6 Signature Verification Process // // WARNING: this function doesn't do any revocation checking. func (d *ParsedSignedData) Verify(ctx context.Context, opts x509.VerifyOptions) ([][]*x509.Certificate, error) { if len(d.SignerInfos) == 0 { return nil, ErrSignerInfoNotFound } if len(d.Certificates) == 0 { return nil, ErrCertificateNotFound } intermediates := x509.NewCertPool() for _, cert := range d.Certificates { intermediates.AddCert(cert) } opts.Intermediates = intermediates verifiedSignerMap := map[string][]*x509.Certificate{} var lastErr error for _, signerInfo := range d.SignerInfos { signingCertificate := d.GetCertificate(signerInfo.SignerIdentifier) if signingCertificate == nil { lastErr = ErrCertificateNotFound continue } certChain, err := d.VerifySigner(ctx, &signerInfo, signingCertificate, opts) if err != nil { lastErr = err continue } thumbprint, err := hashutil.ComputeHash(crypto.SHA256, signingCertificate.Raw) if err != nil { lastErr = err continue } verifiedSignerMap[hex.EncodeToString(thumbprint)] = certChain } if len(verifiedSignerMap) == 0 { return nil, lastErr } verifiedSigningCertChains := make([][]*x509.Certificate, 0, len(verifiedSignerMap)) for _, certChain := range verifiedSignerMap { verifiedSigningCertChains = append(verifiedSigningCertChains, certChain) } return verifiedSigningCertChains, nil } // VerifySigner verifies the signerInfo against the user specified signingCertificate. // // This function should be used when: // // 1. The certificates field of d is missing. This function allows the caller to provide // a signing certificate to verify the signerInfo. // // 2. The caller doesn't trust the signer identifier (unsigned field) of signerInfo // to identify signing certificate. This function allows such caller to use their trusted // signing certificate. // // Note: the intermediate certificates (if any) and root certificates in the verify // options MUST be set by the caller. The certificates field of d is not used in this function. // // References: // - RFC 5652 5 Signed-data Content Type // - RFC 5652 5.4 Message Digest Calculation Process // - RFC 5652 5.6 Signature Verification Process // // WARNING: this function doesn't do any revocation checking. func (d *ParsedSignedData) VerifySigner(ctx context.Context, signerInfo *SignerInfo, signingCertificate *x509.Certificate, opts x509.VerifyOptions) ([]*x509.Certificate, error) { if signerInfo == nil { return nil, VerificationError{Message: "VerifySigner failed: signer info is required"} } if signingCertificate == nil { return nil, VerificationError{Message: "VerifySigner failed: signing certificate is required"} } if signerInfo.Version != 1 { // Only IssuerAndSerialNumber is supported currently return nil, VerificationError{Message: fmt.Sprintf("invalid signer info version: only version 1 is supported; got %d", signerInfo.Version)} } return d.verify(signerInfo, signingCertificate, &opts) } // verify verifies the trust in a top-down manner. // // References: // - RFC 5652 5.4 Message Digest Calculation Process // - RFC 5652 5.6 Signature Verification Process func (d *ParsedSignedData) verify(signerInfo *SignerInfo, cert *x509.Certificate, opts *x509.VerifyOptions) ([]*x509.Certificate, error) { // verify signer certificate certChains, err := cert.Verify(*opts) if err != nil { return nil, VerificationError{Detail: err} } // verify signature if err := d.verifySignature(signerInfo, cert); err != nil { return nil, err } // verify attribute return d.verifySignedAttributes(signerInfo, certChains) } // verifySignature verifies the signature with a trusted certificate. // // References: // - RFC 5652 5.4 Message Digest Calculation Process // - RFC 5652 5.6 Signature Verification Process func (d *ParsedSignedData) verifySignature(signerInfo *SignerInfo, cert *x509.Certificate) error { // verify signature algorithm := oid.ToSignatureAlgorithm( signerInfo.DigestAlgorithm.Algorithm, signerInfo.SignatureAlgorithm.Algorithm, ) if algorithm == x509.UnknownSignatureAlgorithm { return VerificationError{Message: "unknown signature algorithm"} } signed := d.Content if len(signerInfo.SignedAttributes) > 0 { encoded, err := asn1.MarshalWithParams(signerInfo.SignedAttributes, "set") if err != nil { return VerificationError{Message: "invalid signed attributes", Detail: err} } signed = encoded } if err := cert.CheckSignature(algorithm, signed, signerInfo.Signature); err != nil { return VerificationError{Detail: err} } return nil } // verifySignedAttributes verifies the signed attributes. // // References: // - RFC 5652 5.3 SignerInfo Type // - RFC 5652 5.6 Signature Verification Process func (d *ParsedSignedData) verifySignedAttributes(signerInfo *SignerInfo, chains [][]*x509.Certificate) ([]*x509.Certificate, error) { if len(chains) == 0 { return nil, VerificationError{Message: "Failed to verify signed attributes because the certificate chain is empty."} } // verify attributes if present if len(signerInfo.SignedAttributes) == 0 { if d.ContentType.Equal(oid.Data) { return nil, nil } // signed attributes MUST be present if the content type of the // EncapsulatedContentInfo value being signed is not id-data. return nil, VerificationError{Message: "missing signed attributes"} } var contentType asn1.ObjectIdentifier if err := signerInfo.SignedAttributes.Get(oid.ContentType, &contentType); err != nil { return nil, VerificationError{Message: "invalid content type", Detail: err} } if !d.ContentType.Equal(contentType) { return nil, VerificationError{Message: fmt.Sprintf("mismatch content type: found %q in signer info, and %q in signed data", contentType, d.ContentType)} } var expectedDigest []byte if err := signerInfo.SignedAttributes.Get(oid.MessageDigest, &expectedDigest); err != nil { return nil, VerificationError{Message: "invalid message digest", Detail: err} } hash, ok := oid.ToHash(signerInfo.DigestAlgorithm.Algorithm) if !ok { return nil, VerificationError{Message: "unsupported digest algorithm"} } actualDigest, err := hashutil.ComputeHash(hash, d.Content) if err != nil { return nil, VerificationError{Message: "hash failure", Detail: err} } if !bytes.Equal(expectedDigest, actualDigest) { return nil, VerificationError{Message: "mismatch message digest"} } // sanity check on signing time var signingTime time.Time if err := signerInfo.SignedAttributes.Get(oid.SigningTime, &signingTime); err != nil { if errors.Is(err, ErrAttributeNotFound) { return chains[0], nil } return nil, VerificationError{Message: "invalid signing time", Detail: err} } // verify signing time is within the validity period of all certificates // in the chain. As long as one chain is valid, the signature is valid. for _, chain := range chains { if isSigningTimeValid(chain, signingTime) { return chain, nil } } return nil, VerificationError{Message: fmt.Sprintf("signing time, %s, is outside certificate's validity period", signingTime)} } // GetCertificate finds the certificate by issuer name and issuer-specific // serial number. // Reference: RFC 5652 5 Signed-data Content Type func (d *ParsedSignedData) GetCertificate(ref IssuerAndSerialNumber) *x509.Certificate { for _, cert := range d.Certificates { if bytes.Equal(cert.RawIssuer, ref.Issuer.FullBytes) && cert.SerialNumber.Cmp(ref.SerialNumber) == 0 { return cert } } return nil } // isSigningTimeValid helpes to check if signingTime is within the validity // period of all certificates in the chain func isSigningTimeValid(chain []*x509.Certificate, signingTime time.Time) bool { for _, cert := range chain { if signingTime.Before(cert.NotBefore) || signingTime.After(cert.NotAfter) { return false } } return true } tspclient-go-0.2.0/internal/cms/signed_test.go000066400000000000000000000302051464533346500213730ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cms import ( "context" "crypto/x509" "os" "reflect" "testing" "time" ) func TestVerifySignedData(t *testing.T) { ctx := context.Background() // parse signed data sigBytes, err := os.ReadFile("testdata/TimeStampToken.p7s") if err != nil { t.Fatal("failed to read test signature:", err) } signed, err := ParseSignedData(sigBytes) if err != nil { t.Fatal("ParseSignedData() error =", err) } // basic check on parsed signed data if got := len(signed.Certificates); got != 4 { t.Fatalf("len(Certificates) = %v, want %v", got, 4) } if got := len(signed.SignerInfos); got != 1 { t.Fatalf("len(Signers) = %v, want %v", got, 1) } // verify with no root CAs and should fail roots := x509.NewCertPool() opts := x509.VerifyOptions{ Roots: roots, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageTimeStamping}, CurrentTime: time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC), } if _, err := signed.Verify(ctx, opts); err == nil { t.Errorf("ParseSignedData.Verify() error = %v, wantErr %v", err, true) } else if vErr, ok := err.(VerificationError); !ok { t.Errorf("ParseSignedData.Verify() error = %v, want VerificationError", err) } else if _, ok := vErr.Detail.(x509.UnknownAuthorityError); !ok { t.Errorf("ParseSignedData.Verify() VerificationError.Detail = %v, want UnknownAuthorityError", err) } // verify with proper root CA rootCABytes, err := os.ReadFile("testdata/GlobalSignRootCA.crt") if err != nil { t.Fatal("failed to read root CA certificate:", err) } if ok := roots.AppendCertsFromPEM(rootCABytes); !ok { t.Fatal("failed to load root CA certificate") } verifiedCertChains, err := signed.Verify(ctx, opts) if err != nil { t.Fatal("ParseSignedData.Verify() error =", err) } if !reflect.DeepEqual(verifiedCertChains[0], signed.Certificates) { t.Fatalf("ParseSignedData.Verify() = %v, want %v", verifiedCertChains, signed.Certificates[:1]) } } func TestParseSignedData(t *testing.T) { t.Run("invalid berData", func(t *testing.T) { _, err := ParseSignedData([]byte("invalid")) if err == nil { t.Fatal("ParseSignedData() error = nil, wantErr true") } }) t.Run("invalid contentInfo", func(t *testing.T) { _, err := ParseSignedData([]byte{0x30, 0x00}) if err == nil { t.Fatal("ParseSignedData() error = nil, wantErr true") } }) t.Run("content type is not signed data", func(t *testing.T) { _, err := ParseSignedData([]byte{ 0x30, 0x12, 0x06, 0x0b, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x01, 0x06, 0xa0, 0x03, 0x04, 0x01, 0x78, }) if err != ErrNotSignedData { t.Errorf("ParseSignedData() error = %v, wantErr %v", err, ErrNotSignedData) } }) t.Run("invalid signed data content", func(t *testing.T) { _, err := ParseSignedData([]byte{ 0x30, 0x10, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02, 0xa0, 0x03, 0x04, 0x01, 0x78, }) if err == nil { t.Fatal("ParseSignedData() error = nil, wantErr true") } }) t.Run("invalid certificate", func(t *testing.T) { // parse signed data sigBytes, err := os.ReadFile("testdata/TimeStampTokenWithInvalidCertificates.p7s") if err != nil { t.Fatal("failed to read test signature:", err) } _, err = ParseSignedData(sigBytes) if err == nil { t.Fatal("ParseSignedData() error = nil, wantErr true") } }) } func TestVerify(t *testing.T) { testData := []struct { name string filePath string wantErr bool }{ { name: "without certificate", filePath: "testdata/TimeStampTokenWithoutCertificate.p7s", wantErr: true, }, { name: "without signer info", filePath: "testdata/TimeStampTokenWithoutSigner.p7s", wantErr: true, }, { name: "signer version is 2", filePath: "testdata/TimeStampTokenWithSignerVersion2.p7s", wantErr: true, }, { name: "unknown signer issuer", filePath: "testdata/TimeStampTokenWithUnknownSignerIssuer.p7s", wantErr: true, }, { name: "sha1 leaf cert", filePath: "testdata/Sha1SignedData.p7s", wantErr: true, }, { name: "invalid signature", filePath: "testdata/TimeStampTokenWithInvalidSignature.p7s", wantErr: true, }, { name: "id-data content type without signed attributes", filePath: "testdata/SignedDataWithoutSignedAttributes.p7s", wantErr: false, }, { name: "an invalid and a valid signer info", filePath: "testdata/TimeStampTokenWithAnInvalidAndAValidSignerInfo.p7s", wantErr: false, }, } for _, testcase := range testData { t.Run(testcase.name, func(t *testing.T) { ctx := context.Background() // parse signed data sigBytes, err := os.ReadFile(testcase.filePath) if err != nil { t.Fatal("failed to read test signature:", err) } signed, err := ParseSignedData(sigBytes) if err != nil { t.Fatal("ParseSignedData() error =", err) } // verify with no root CAs and should fail roots := x509.NewCertPool() certLen := len(signed.Certificates) if certLen > 0 { roots.AddCert(signed.Certificates[certLen-1]) } opts := x509.VerifyOptions{ Roots: roots, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageTimeStamping}, CurrentTime: time.Date(2024, 1, 9, 0, 0, 0, 0, time.UTC), } _, err = signed.Verify(ctx, opts) if testcase.wantErr != (err != nil) { t.Errorf("ParseSignedData.Verify() error = %v, wantErr %v", err, true) } }) } } func TestVerifySignerInvalidSignerInfo(t *testing.T) { ctx := context.Background() testData := []struct { name string filePath string wantErr bool }{ { name: "signer version is not 1", filePath: "testdata/TimeStampTokenWithSignerVersion2.p7s", wantErr: true, }, } for _, testcase := range testData { t.Run(testcase.name, func(t *testing.T) { // parse signed data sigBytes, err := os.ReadFile(testcase.filePath) if err != nil { t.Fatal("failed to read test signature:", err) } signed, err := ParseSignedData(sigBytes) if err != nil { t.Fatal("ParseSignedData() error =", err) } // verify with no root CAs and should fail roots := x509.NewCertPool() certLen := len(signed.Certificates) if certLen > 0 { roots.AddCert(signed.Certificates[certLen-1]) } intermediates := x509.NewCertPool() for _, cert := range signed.Certificates { intermediates.AddCert(cert) } opts := x509.VerifyOptions{ Roots: roots, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageTimeStamping}, CurrentTime: time.Date(2024, 1, 9, 0, 0, 0, 0, time.UTC), Intermediates: intermediates, } _, err = signed.VerifySigner(ctx, &signed.SignerInfos[0], signed.Certificates[0], opts) // err = err , err == nil, false, want error == false if testcase.wantErr != (err != nil) { t.Errorf("ParseSignedData.Verify() error = %v, wantErr %v", err, testcase.wantErr) } }) } } func TestVerifySigner(t *testing.T) { ctx := context.Background() // parse signed data sigBytes, err := os.ReadFile("testdata/TimeStampToken.p7s") if err != nil { t.Fatal("failed to read test signature:", err) } signed, err := ParseSignedData(sigBytes) if err != nil { t.Fatal("ParseSignedData() error =", err) } roots := x509.NewCertPool() certLen := len(signed.Certificates) if certLen > 0 { roots.AddCert(signed.Certificates[certLen-1]) } intermediates := x509.NewCertPool() for _, cert := range signed.Certificates { intermediates.AddCert(cert) } opts := x509.VerifyOptions{ Roots: roots, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageTimeStamping}, CurrentTime: time.Date(2024, 1, 9, 0, 0, 0, 0, time.UTC), Intermediates: intermediates, } t.Run("valid user provided signing certificate", func(t *testing.T) { // verify with no root CAs and should fail _, err = signed.VerifySigner(ctx, &signed.SignerInfos[0], signed.Certificates[0], opts) if err != nil { t.Errorf("ParseSignedData.Verify() error = %v, want nil", err) } }) t.Run("invalid user provided signing certificate", func(t *testing.T) { // verify with no root CAs and should fail _, err = signed.VerifySigner(ctx, &signed.SignerInfos[0], signed.Certificates[1], opts) if err == nil { t.Errorf("ParseSignedData.Verify() error = %v, want error", err) } }) t.Run("signerInfo is nil", func(t *testing.T) { _, err = signed.VerifySigner(ctx, nil, signed.Certificates[0], opts) if err == nil { t.Error("ParseSignedData.Verify() error = nil, want error") } }) t.Run("certificate is nil", func(t *testing.T) { // verify with no root CAs and should fail _, err = signed.VerifySigner(ctx, &signed.SignerInfos[0], nil, opts) if err == nil { t.Error("ParseSignedData.Verify() error = nil, want error") } }) } func TestVerifyAttributes(t *testing.T) { testData := []struct { name string filePath string wantErr bool }{ { name: "without content type", filePath: "testdata/TimeStampTokenWithoutSignedAttributeContentType.p7s", wantErr: true, }, { name: "with invalid content type", filePath: "testdata/TimeStampTokenInvalidSignedAttributeContentType.p7s", wantErr: true, }, { name: "without signed attributes digest", filePath: "testdata/TimeStampTokenWithoutSignedAttributeDigest.p7s", wantErr: true, }, { name: "with SHA1 hash", filePath: "testdata/TimeStampTokenWithSignedAttributeSHA1.p7s", wantErr: true, }, { name: "with invalid signing time", filePath: "testdata/TimeStampTokenWithInvalidSigningTime.p7s", wantErr: true, }, { name: "valid signing time", filePath: "testdata/TimeStampTokenWithSigningTime.p7s", wantErr: false, }, { name: "signing time before expected", filePath: "testdata/TimeStampTokenWithSigningTimeBeforeExpected.p7s", wantErr: true, }, { name: "timestamp token without signed attributes", filePath: "testdata/TimeStampTokenWithoutSignedAttributes.p7s", wantErr: true, }, } for _, testcase := range testData { t.Run(testcase.name, func(t *testing.T) { // parse signed data sigBytes, err := os.ReadFile(testcase.filePath) if err != nil { t.Fatal("failed to read test signature:", err) } signed, err := ParseSignedData(sigBytes) if err != nil { t.Fatal("ParseSignedData() error =", err) } _, err = signed.verifySignedAttributes(&signed.SignerInfos[0], [][]*x509.Certificate{signed.Certificates}) if testcase.wantErr && err == nil { t.Errorf("ParseSignedData.Verify() error = %v, wantErr %v", err, true) } else if !testcase.wantErr && err != nil { t.Errorf("ParseSignedData.Verify() error = %v, wantErr %v", err, false) } }) } } func TestVerifyCorruptedSignedData(t *testing.T) { ctx := context.Background() // parse signed data sigBytes, err := os.ReadFile("testdata/TimeStampToken.p7s") if err != nil { t.Fatal("failed to read test signature:", err) } signed, err := ParseSignedData(sigBytes) if err != nil { t.Fatal("ParseSignedData() error =", err) } // corrupt the content signed.Content = []byte("corrupted data") roots := x509.NewCertPool() rootCABytes, err := os.ReadFile("testdata/GlobalSignRootCA.crt") if err != nil { t.Fatal("failed to read root CA certificate:", err) } if ok := roots.AppendCertsFromPEM(rootCABytes); !ok { t.Fatal("failed to load root CA certificate") } opts := x509.VerifyOptions{ Roots: roots, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageTimeStamping}, CurrentTime: time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC), } if _, err := signed.Verify(ctx, opts); err == nil { t.Errorf("ParseSignedData.Verify() error = %v, wantErr %v", err, true) } else if _, ok := err.(VerificationError); !ok { t.Errorf("ParseSignedData.Verify() error = %v, want VerificationError", err) } } tspclient-go-0.2.0/internal/cms/testdata/000077500000000000000000000000001464533346500203455ustar00rootroot00000000000000tspclient-go-0.2.0/internal/cms/testdata/GlobalSignRootCA.crt000066400000000000000000000023151464533346500241510ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK 6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH WD9f -----END CERTIFICATE----- tspclient-go-0.2.0/internal/cms/testdata/Sha1SignedData.p7s000066400000000000000000000030651464533346500235240ustar00rootroot0000000000000001 *H "01 0 +0  *H y0u0]Z.eYS0  *H 0f1 0 UUS10U California10U San Francisco10U My Company10U mydomain.com0 240107053839Z 340105053839Z0f1 0 UUS10U California10U San Francisco10U My Company10U mydomain.com0"0  *H 0 >*<~xoG/&6NK+$ojM%^q y)o $hz#u&NKwSԔ&##qs\m [`5j\$kt U}"HERݡ$.ٞREfۀ6aM. 4Z  796r{ZL[M:Sr$|Ngyُ򚫗M hƦH+QQEV3Wt0d␗@B&SQҀ[00U0 mydomain.com0  *H VJex^ؼק9|ʿkd˜{S:q w(nՔC!AOQ,50Fuݠ-'Yn1^ 3eO 7'Vkv.RLw `ڨby ݒPV`/uiA51  boGSK&rQ TTEnr'aҺQ㤫YBbܡs]10|0~0f1 0 UUS10U California10U San Francisco10U My Company10U mydomain.comZ.eYS0 +0 *H  1  *H 0 *H  1 240108053839Z0# *H  1i 'v(0y *H  1l0j0  `He*0  `He0  `He0 *H 0*H 0 *H @0+0 *H (0  *H Z/p4@s&ybrޜưcixƧ~ nyTEGJV""(2l LDOv6:ih:pڏ<뗡Y !O U`Rj@ rfq=tp_NhŠm.<_,ɟΡ8G-^f?wЯ ku:oc&pbl`/ .  pѯ)fG̥/}Tntspclient-go-0.2.0/internal/cms/testdata/SignedDataWithoutSignedAttributes.p7s000066400000000000000000000025731464533346500275770ustar00rootroot000000000000000w *H h0d10  `He0  *H Data to be signed|0x0`z86d~|{Q0  *H  0g1 0 UUS10U California10U San Francisco10U My Company10U mycompany.com0 240108060616Z 240118060616Z0g1 0 UUS10U California10U San Francisco10U My Company10U mycompany.com0"0  *H 0 mdgxBQ%ulOQ"jr.Svo=bnjZ(9)q(tu>kJ0F="_.K/h)ѶrArFTi!-)<>P UM ((g!aKX˦t+00U0 mycompany.com0  *H  3K_xzS[š2 "3ͼ:s=VN8HLǺfzaρ|zKܽLv { ZF3=贔f*,Dg(Yܿ*2dE)AN},M+M7=c.C^ j>1I z\MMg;|]lj3?pcqA#OmGuCƌ~-(r<#\wW;60U̩ys.TIR vP1f 6⎼Ol nJ̀\Awtspclient-go-0.2.0/internal/cms/testdata/TimeStampToken.p7s000066400000000000000000000147031464533346500237110ustar00rootroot000000000000000 *H 010  `He0 *H  0 +2010  `He &p@ޠH@@(kQY<&g*Mty20210917140910Z0WU0S1 0 UBE10U GlobalSign nv-sa1)0'U Globalsign TSA for Advanced - G4d0U0=FiPpMA0  *H  0[1 0 UBE10U GlobalSign nv-sa110/U(GlobalSign Timestamping CA - SHA384 - G40 210527095523Z 320628095522Z0S1 0 UBE10U GlobalSign nv-sa1)0'U Globalsign TSA for Advanced - G400  *H 00i{ nT9_dYu"Niott}QF h.eґU;bBz2Lѹ1%Vg22\-bM 7*`ol=1])Uvl{a)3,nZC;0 xT`8dCpȴ ܻn!5~„8~dz& *%RmL0 x-FW ?yP32@ PAN !D2H,K W[dTjC v֣ 0@6Ўx/ ;aGTpCZhᮓ,r2K+%3h*y00U0U% 0 +0U~px )xY̦$0LU E0C0A +20402+&https://www.globalsign.com/repository/0 U00+009+0-http://ocsp.globalsign.com/ca/gstsacasha384g40C+07http://secure.globalsign.com/cacert/gstsacasha384g4.crt0U#0iWE93@ýe0AU:0806420http://crl.globalsign.com/ca/gstsacasha384g4.crl0  *H  bw/BokEY6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%OZ{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%OZ{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,= len(r) { return nil, nil, asn1.SyntaxError{Msg: "decoding BER identifier octets: high-tag-number form with early EOF"} } offset++ } if offset >= len(r) { return nil, nil, asn1.SyntaxError{Msg: "decoding BER identifier octets: early EOF due to missing length and content octets"} } return r[:offset], r[offset:], nil } // decodeLength decodes length octets. // Indefinite length is not supported // // Parameters: // r - The input byte slice from which the length octets are to be decoded. // // Returns: // int - The length decoded from the input byte slice. // []byte - The remaining part of the input byte slice after the length octets. // error - An error that can occur during the decoding process. // // Reference: ISO/IEC 8825-1: 8.1.3 func decodeLength(r []byte) (int, []byte, error) { if len(r) < 1 { return 0, nil, asn1.SyntaxError{Msg: "decoding BER length octets: length octets is empty"} } offset := 0 b := r[offset] offset++ if b < 0x80 { // short form // Reference: ISO/IEC 8825-1: 8.1.3.4 contentLen := int(b) subsequentOctets := r[offset:] if contentLen > len(subsequentOctets) { return 0, nil, asn1.SyntaxError{Msg: "decoding BER length octets: short form length octets value should be less or equal to the subsequent octets length"} } return contentLen, subsequentOctets, nil } if b == 0x80 { // Indefinite-length method is not supported. // Reference: ISO/IEC 8825-1: 8.1.3.6.1 return 0, nil, asn1.StructuralError{Msg: "decoding BER length octets: indefinite length not supported"} } // long form // Reference: ISO/IEC 8825-1: 8.1.3.5 n := int(b & 0x7f) if n > 4 { // length must fit the memory space of the int type (4 bytes). return 0, nil, asn1.StructuralError{Msg: fmt.Sprintf("decoding BER length octets: length of encoded data (%d bytes) cannot exceed 4 bytes", n)} } if offset+n >= len(r) { return 0, nil, asn1.SyntaxError{Msg: "decoding BER length octets: long form length octets with early EOF"} } var length uint64 for i := 0; i < n; i++ { length = (length << 8) | uint64(r[offset]) offset++ } // length must fit the memory space of the int32. if (length >> 31) > 0 { return 0, nil, asn1.StructuralError{Msg: fmt.Sprintf("decoding BER length octets: length %d does not fit the memory space of int32", length)} } contentLen := int(length) subsequentOctets := r[offset:] if contentLen > len(subsequentOctets) { return 0, nil, asn1.SyntaxError{Msg: "decoding BER length octets: long form length octets value should be less or equal to the subsequent octets length"} } return contentLen, subsequentOctets, nil } // isPrimitive returns true if the first identifier octet is marked // as primitive. // Reference: ISO/IEC 8825-1: 8.1.2.5 func isPrimitive(identifier []byte) bool { return identifier[0]&0x20 == 0 } tspclient-go-0.2.0/internal/encoding/asn1/ber/ber_test.go000066400000000000000000000163311464533346500233140ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ber import ( "reflect" "testing" ) func TestConvertToDER(t *testing.T) { var testBytes = make([]byte, 0xFFFFFFFF+8) // primitive identifier testBytes[0] = 0x1f testBytes[1] = 0xa0 testBytes[2] = 0x20 // length testBytes[3] = 0x84 testBytes[4] = 0xFF testBytes[5] = 0xFF testBytes[6] = 0xFF testBytes[7] = 0xFF type data struct { name string ber []byte der []byte expectError bool } testData := []data{ { name: "Constructed value", ber: []byte{ // Constructed value 0x30, // Constructed value length 0x2e, // Type identifier 0x06, // Type length 0x09, // Type content 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, // Value identifier 0x04, // Value length in BER 0x81, 0x20, // Value content 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, }, der: []byte{ // Constructed value 0x30, // Constructed value length 0x2d, // Type identifier 0x06, // Type length 0x09, // Type content 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, // Value identifier 0x04, // Value length in BER 0x20, // Value content 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, }, expectError: false, }, { name: "Primitive value", ber: []byte{ // Primitive value // identifier 0x1f, 0x20, // length 0x81, 0x01, // content 0x01, }, der: []byte{ // Primitive value // identifier 0x1f, 0x20, // length 0x01, // content 0x01, }, expectError: false, }, { name: "Constructed value in constructed value", ber: []byte{ // Constructed value 0x30, // Constructed value length 0x2d, // Constructed value identifier 0x26, // Type length 0x2b, // Value identifier 0x04, // Value length in BER 0x81, 0x28, // Value content 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 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, }, der: []byte{ // Constructed value 0x30, // Constructed value length 0x2c, // Constructed value identifier 0x26, // Type length 0x2a, // Value identifier 0x04, // Value length in BER 0x28, // Value content 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 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, }, expectError: false, }, { name: "empty", ber: []byte{}, der: []byte{}, expectError: true, }, { name: "identifier high tag number form", ber: []byte{ // Primitive value // identifier 0x1f, 0xa0, 0x20, // length 0x81, 0x01, // content 0x01, }, der: []byte{ // Primitive value // identifier 0x1f, 0xa0, 0x20, // length 0x01, // content 0x01, }, expectError: false, }, { name: "EarlyEOF for identifier high tag number form", ber: []byte{ // Primitive value // identifier 0x1f, 0xa0, }, der: []byte{}, expectError: true, }, { name: "EarlyEOF for length", ber: []byte{ // Primitive value // identifier 0x1f, 0xa0, 0x20, }, der: []byte{}, expectError: true, }, { name: "Unsupport indefinite-length", ber: []byte{ // Primitive value // identifier 0x1f, 0xa0, 0x20, // length 0x80, }, der: []byte{}, expectError: true, }, { name: "length greater than 4 bytes", ber: []byte{ // Primitive value // identifier 0x1f, 0xa0, 0x20, // length 0x85, }, der: []byte{}, expectError: true, }, { name: "long form length EarlyEOF ", ber: []byte{ // Primitive value // identifier 0x1f, 0xa0, 0x20, // length 0x84, }, der: []byte{}, expectError: true, }, { name: "length greater than content", ber: []byte{ // Primitive value // identifier 0x1f, 0xa0, 0x20, // length 0x02, }, der: []byte{}, expectError: true, }, { name: "trailing data", ber: []byte{ // Primitive value // identifier 0x1f, 0xa0, 0x20, // length 0x02, // content 0x01, 0x02, 0x03, }, der: []byte{}, expectError: true, }, { name: "EarlyEOF in constructed value", ber: []byte{ // Constructed value 0x30, // Constructed value length 0x2c, // Constructed value identifier 0x26, // Type length 0x2b, // Value identifier 0x04, // Value length in BER 0x81, 0x28, // Value content 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 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, }, expectError: true, }, { name: "length greater > int32", ber: testBytes[:], der: []byte{}, expectError: true, }, { name: "long form length greather than subsequent octets length ", ber: []byte{ // Primitive value // identifier 0x1f, 0xa0, 0x20, // length 0x81, 0x09, // content 0x01, }, der: []byte{}, expectError: true, }, } for _, tt := range testData { der, err := ConvertToDER(tt.ber) if !tt.expectError && err != nil { t.Errorf("ConvertToDER() error = %v, but expect no error", err) return } if tt.expectError && err == nil { t.Errorf("ConvertToDER() error = nil, but expect error") } if !tt.expectError && !reflect.DeepEqual(der, tt.der) { t.Errorf("got = %v, want %v", der, tt.der) } } } func TestDecodeIdentifier(t *testing.T) { t.Run("identifier is empty", func(t *testing.T) { _, _, err := decodeIdentifier([]byte{}) if err == nil { t.Errorf("decodeIdentifier() error = nil, but expect error") } }) } func TestDecodeLength(t *testing.T) { t.Run("length is empty", func(t *testing.T) { _, _, err := decodeLength([]byte{}) if err == nil { t.Errorf("decodeLength() error = nil, but expect error") } }) } tspclient-go-0.2.0/internal/encoding/asn1/ber/common.go000066400000000000000000000034731464533346500230000ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ber import "io" // writer is the interface that wraps the basic Write and WriteByte methods. type writer interface { io.Writer io.ByteWriter } // encodeLength encodes length octets in DER. // Reference: // - ISO/IEC 8825-1: 10.1 // - https://learn.microsoft.com/windows/win32/seccertenroll/about-encoded-length-and-value-bytes func encodeLength(w io.ByteWriter, length int) error { // DER restriction: short form must be used for length less than 128 if length < 0x80 { return w.WriteByte(byte(length)) } // DER restriction: long form must be encoded in the minimum number of octets lengthSize := encodedLengthSize(length) err := w.WriteByte(0x80 | byte(lengthSize-1)) if err != nil { return err } for i := lengthSize - 1; i > 0; i-- { if err = w.WriteByte(byte(length >> (8 * (i - 1)))); err != nil { return err } } return nil } // encodedLengthSize gives the number of octets used for encoding the length // in DER. // Reference: // - ISO/IEC 8825-1: 10.1 // - https://learn.microsoft.com/windows/win32/seccertenroll/about-encoded-length-and-value-bytes func encodedLengthSize(length int) int { if length < 0x80 { return 1 } lengthSize := 1 for length > 0 { length >>= 8 lengthSize++ } return lengthSize } tspclient-go-0.2.0/internal/encoding/asn1/ber/common_test.go000066400000000000000000000051721464533346500240350ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ber import ( "bytes" "errors" "testing" ) func TestEncodeLength(t *testing.T) { tests := []struct { name string length int want []byte wantErr bool }{ { name: "Length less than 128", length: 127, want: []byte{127}, wantErr: false, }, { name: "Length equal to 128", length: 128, want: []byte{0x81, 128}, wantErr: false, }, { name: "Length greater than 128", length: 300, want: []byte{0x82, 0x01, 0x2C}, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { buf := &bytes.Buffer{} err := encodeLength(buf, tt.length) if (err != nil) != tt.wantErr { t.Errorf("encodeLength() error = %v, wantErr %v", err, tt.wantErr) return } if got := buf.Bytes(); !bytes.Equal(got, tt.want) { t.Errorf("encodeLength() = %v, want %v", got, tt.want) } }) } } func TestEncodedLengthSize(t *testing.T) { tests := []struct { name string length int want int }{ { name: "Length less than 128", length: 127, want: 1, }, { name: "Length equal to 128", length: 128, want: 2, }, { name: "Length greater than 128", length: 300, want: 3, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := encodedLengthSize(tt.length); got != tt.want { t.Errorf("encodedLengthSize() = %v, want %v", got, tt.want) } }) } } type secondErrorWriter struct { count int } func (ew *secondErrorWriter) WriteByte(p byte) (err error) { ew.count += 1 if ew.count == 2 { return errors.New("write error") } return nil } func TestEncodeLengthFailed(t *testing.T) { t.Run("byte write error 1", func(t *testing.T) { buf := &errorWriter{} err := encodeLength(buf, 128) if err == nil { t.Error("encodeLength() error = nil, want Error") return } }) t.Run("byte write error 2", func(t *testing.T) { buf := &secondErrorWriter{} err := encodeLength(buf, 128) if err == nil { t.Error("encodeLength() error = nil, want Error") return } }) } tspclient-go-0.2.0/internal/encoding/asn1/ber/constructed.go000066400000000000000000000026141464533346500240410ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ber // constructed represents a value in constructed. type constructed struct { identifier []byte length int // length of this constructed value's memebers in bytes when encoded in DER members []value rawContent []byte // the raw content of BER } // EncodeMetadata encodes the identifier and length octets of constructed // to the value writer in DER. func (v *constructed) EncodeMetadata(w writer) error { _, err := w.Write(v.identifier) if err != nil { return err } return encodeLength(w, v.length) } // EncodedLen returns the length in bytes of the constructed when encoded // in DER. func (v *constructed) EncodedLen() int { return len(v.identifier) + encodedLengthSize(v.length) + v.length } // Content returns the content of the value. func (v *constructed) Content() []byte { return nil } tspclient-go-0.2.0/internal/encoding/asn1/ber/constructed_test.go000066400000000000000000000025311464533346500250760ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ber import ( "errors" "testing" ) type errorWriter struct{} func (ew *errorWriter) Write(p []byte) (n int, err error) { return 0, errors.New("write error") } func (ew *errorWriter) WriteByte(c byte) error { return errors.New("write error") } func TestConstructedEncodeMetadata(t *testing.T) { tests := []struct { name string v constructed wantError bool }{ { name: "Error case", v: constructed{ identifier: []byte{0x30}, length: 5, }, wantError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { w := &errorWriter{} err := tt.v.EncodeMetadata(w) if (err != nil) != tt.wantError { t.Errorf("EncodeMetadata() error = %v, wantError %v", err, tt.wantError) } }) } } tspclient-go-0.2.0/internal/encoding/asn1/ber/primitive.go000066400000000000000000000024201464533346500235070ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ber // primitive represents a value in primitive encoding. type primitive struct { identifier []byte content []byte } // EncodeMetadata encodes the identifier and length octets of primitive to // the value writer in DER. func (v *primitive) EncodeMetadata(w writer) error { _, err := w.Write(v.identifier) if err != nil { return err } return encodeLength(w, len(v.content)) } // EncodedLen returns the length in bytes of the primitive when encoded in DER. func (v *primitive) EncodedLen() int { return len(v.identifier) + encodedLengthSize(len(v.content)) + len(v.content) } // Content returns the content of the value. func (v *primitive) Content() []byte { return v.content } tspclient-go-0.2.0/internal/encoding/asn1/ber/primitive_test.go000066400000000000000000000021411464533346500245460ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ber import ( "testing" ) func TestPrimitiveEncodeMetadata(t *testing.T) { tests := []struct { name string v primitive wantError bool }{ { name: "Error case", v: primitive{ identifier: []byte{0x30}, }, wantError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { w := &errorWriter{} err := tt.v.EncodeMetadata(w) if (err != nil) != tt.wantError { t.Errorf("EncodeMetadata() error = %v, wantError %v", err, tt.wantError) } }) } } tspclient-go-0.2.0/internal/hashutil/000077500000000000000000000000001464533346500175735ustar00rootroot00000000000000tspclient-go-0.2.0/internal/hashutil/hash.go000066400000000000000000000017701464533346500210520ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package hashutil provides utilities for hash. package hashutil import "crypto" // ComputeHash computes the digest of the message with the given hash algorithm. // Callers should check the availability of the hash algorithm before invoking. func ComputeHash(hash crypto.Hash, message []byte) ([]byte, error) { h := hash.New() _, err := h.Write(message) if err != nil { return nil, err } return h.Sum(nil), nil } tspclient-go-0.2.0/internal/hashutil/hash_test.go000066400000000000000000000020751464533346500221100ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package hashutil provides utilities for hash. package hashutil import ( "crypto" "crypto/sha256" "testing" ) func TestComputeHash(t *testing.T) { message := []byte("test message") expectedHash := sha256.Sum256(message) hash, err := ComputeHash(crypto.SHA256, message) if err != nil { t.Fatalf("ComputeHash returned an error: %v", err) } if string(hash) != string(expectedHash[:]) { t.Errorf("ComputeHash returned incorrect hash: got %x, want %x", hash, expectedHash) } } tspclient-go-0.2.0/internal/oid/000077500000000000000000000000001464533346500165255ustar00rootroot00000000000000tspclient-go-0.2.0/internal/oid/algorithm.go000066400000000000000000000033421464533346500210440ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oid import ( "crypto/x509" "encoding/asn1" ) // ToSignatureAlgorithm converts ASN.1 digest and signature algorithm // identifiers to golang signature algorithms. func ToSignatureAlgorithm(digestAlg, sigAlg asn1.ObjectIdentifier) x509.SignatureAlgorithm { switch { case RSA.Equal(sigAlg): switch { case SHA256.Equal(digestAlg): return x509.SHA256WithRSA case SHA384.Equal(digestAlg): return x509.SHA384WithRSA case SHA512.Equal(digestAlg): return x509.SHA512WithRSA } case RSAPSS.Equal(sigAlg): switch { case SHA256.Equal(digestAlg): return x509.SHA256WithRSAPSS case SHA384.Equal(digestAlg): return x509.SHA384WithRSAPSS case SHA512.Equal(digestAlg): return x509.SHA512WithRSAPSS } case SHA256WithRSA.Equal(sigAlg): return x509.SHA256WithRSA case SHA384WithRSA.Equal(sigAlg): return x509.SHA384WithRSA case SHA512WithRSA.Equal(sigAlg): return x509.SHA512WithRSA case ECDSAWithSHA256.Equal(sigAlg): return x509.ECDSAWithSHA256 case ECDSAWithSHA384.Equal(sigAlg): return x509.ECDSAWithSHA384 case ECDSAWithSHA512.Equal(sigAlg): return x509.ECDSAWithSHA512 } return x509.UnknownSignatureAlgorithm } tspclient-go-0.2.0/internal/oid/algorithm_test.go000066400000000000000000000040251464533346500221020ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oid import ( "crypto/x509" "encoding/asn1" "testing" ) func TestToSignatureAlgorithm(t *testing.T) { tests := []struct { name string digestAlg asn1.ObjectIdentifier sigAlg asn1.ObjectIdentifier wantResult x509.SignatureAlgorithm }{ {"SHA256WithRSA", SHA256, RSA, x509.SHA256WithRSA}, {"SHA384WithRSA", SHA384, RSA, x509.SHA384WithRSA}, {"SHA512WithRSA", SHA512, RSA, x509.SHA512WithRSA}, {"SHA256WithRSAPSS", SHA256, RSAPSS, x509.SHA256WithRSAPSS}, {"SHA384WithRSAPSS", SHA384, RSAPSS, x509.SHA384WithRSAPSS}, {"SHA512WithRSAPSS", SHA512, RSAPSS, x509.SHA512WithRSAPSS}, {"SHA256WithRSA direct", SHA256WithRSA, SHA256WithRSA, x509.SHA256WithRSA}, {"SHA384WithRSA direct", SHA384WithRSA, SHA384WithRSA, x509.SHA384WithRSA}, {"SHA512WithRSA direct", SHA512WithRSA, SHA512WithRSA, x509.SHA512WithRSA}, {"ECDSAWithSHA256", ECDSAWithSHA256, ECDSAWithSHA256, x509.ECDSAWithSHA256}, {"ECDSAWithSHA384", ECDSAWithSHA384, ECDSAWithSHA384, x509.ECDSAWithSHA384}, {"ECDSAWithSHA512", ECDSAWithSHA512, ECDSAWithSHA512, x509.ECDSAWithSHA512}, {"UnknownSignatureAlgorithm", asn1.ObjectIdentifier{1, 2, 3}, asn1.ObjectIdentifier{4, 5, 6}, x509.UnknownSignatureAlgorithm}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if gotResult := ToSignatureAlgorithm(tt.digestAlg, tt.sigAlg); gotResult != tt.wantResult { t.Errorf("ToSignatureAlgorithm() = %v, want %v", gotResult, tt.wantResult) } }) } } tspclient-go-0.2.0/internal/oid/hash.go000066400000000000000000000026401464533346500200010ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oid import ( "crypto" "encoding/asn1" "fmt" ) // ToHash converts ASN.1 digest algorithm identifier to golang crypto hash // if it is available. func ToHash(alg asn1.ObjectIdentifier) (crypto.Hash, bool) { var hash crypto.Hash switch { case SHA256.Equal(alg): hash = crypto.SHA256 case SHA384.Equal(alg): hash = crypto.SHA384 case SHA512.Equal(alg): hash = crypto.SHA512 default: return hash, false } return hash, hash.Available() } // FromHash returns corresponding ASN.1 OID for the given Hash algorithm. func FromHash(alg crypto.Hash) (asn1.ObjectIdentifier, error) { var id asn1.ObjectIdentifier switch alg { case crypto.SHA256: id = SHA256 case crypto.SHA384: id = SHA384 case crypto.SHA512: id = SHA512 default: return nil, fmt.Errorf("unsupported hashing algorithm: %v", alg) } return id, nil } tspclient-go-0.2.0/internal/oid/hash_test.go000066400000000000000000000045211464533346500210400ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oid import ( "crypto" "encoding/asn1" "fmt" "testing" ) // sha1 (id-sha1) is defined in RFC 8017 B.1 Hash Functions. // for test purpose only var sha1 = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26} func TestToHash(t *testing.T) { tests := []struct { name string alg asn1.ObjectIdentifier wantHash crypto.Hash wantExists bool }{ {"SHA256", SHA256, crypto.SHA256, true}, {"SHA384", SHA384, crypto.SHA384, true}, {"SHA512", SHA512, crypto.SHA512, true}, {"Unknown", asn1.ObjectIdentifier{1, 2, 3}, crypto.Hash(0), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotHash, gotExists := ToHash(tt.alg) if gotHash != tt.wantHash { t.Errorf("ToHash() gotHash = %v, want %v", gotHash, tt.wantHash) } if gotExists != tt.wantExists { t.Errorf("ToHash() gotExists = %v, want %v", gotExists, tt.wantExists) } }) } } func TestFromHash(t *testing.T) { tests := []struct { name string alg crypto.Hash wantAlg asn1.ObjectIdentifier wantError error }{ {"SHA256", crypto.SHA256, SHA256, nil}, {"SHA384", crypto.SHA384, SHA384, nil}, {"SHA512", crypto.SHA512, SHA512, nil}, {"Unsupported", crypto.SHA1, sha1, fmt.Errorf("unsupported hashing algorithm: %s", crypto.SHA1)}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotAlg, err := FromHash(tt.alg) if err == nil && tt.wantError != nil { t.Fatalf("FromHash() expected %v, but got nil", tt.wantError) } if err != nil && (tt.wantError == nil || err.Error() != tt.wantError.Error()) { t.Fatalf("FromHash() expected %v, but got %v", tt.wantError, err) } if err == nil && !gotAlg.Equal(tt.wantAlg) { t.Errorf("FromHash() gotAlg = %v, want %v", gotAlg, tt.wantAlg) } }) } } tspclient-go-0.2.0/internal/oid/oid.go000066400000000000000000000103311464533346500176250ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package oid collects object identifiers for crypto algorithms. package oid import "encoding/asn1" // OIDs for hash algorithms var ( // SHA256 (id-sha256) is defined in RFC 8017 B.1 Hash Functions SHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1} // SHA384 (id-sha384) is defined in RFC 8017 B.1 Hash Functions SHA384 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2} // SHA512 (id-sha512) is defined in RFC 8017 B.1 Hash Functions SHA512 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3} ) // OIDs for signature algorithms var ( // RSA is defined in RFC 8017 C ASN.1 Module RSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} // SHA256WithRSA is defined in RFC 8017 C ASN.1 Module SHA256WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11} // SHA384WithRSA is defined in RFC 8017 C ASN.1 Module SHA384WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12} // SHA512WithRSA is defined in RFC 8017 C ASN.1 Module SHA512WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13} // RSAPSS is defined in RFC 8017 C ASN.1 Module RSAPSS = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10} // ECDSAWithSHA256 is defined in RFC 5758 3.2 ECDSA Signature Algorithm ECDSAWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2} // ECDSAWithSHA384 is defined in RFC 5758 3.2 ECDSA Signature Algorithm ECDSAWithSHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3} // ECDSAWithSHA512 is defined in RFC 5758 3.2 ECDSA Signature Algorithm ECDSAWithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4} ) // OIDs defined in RFC 5652 Cryptographic Message Syntax (CMS) var ( // Data (id-data) is defined in RFC 5652 4 Data Content Type Data = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1} // SignedData (id-signedData) is defined in RFC 5652 5.1 SignedData Type SignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2} // ContentType (id-ct-contentType) is defined in RFC 5652 3 General Syntax ContentType = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 3} // MessageDigest (id-messageDigest) is defined in RFC 5652 11.2 Message Digest MessageDigest = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 4} // SigningTime (id-signingTime) is defined in RFC 5652 11.3 Signing Time SigningTime = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 5} ) // OIDs for RFC 3161 Timestamping var ( // TSTInfo (id-ct-TSTInfo) is defined in RFC 3161 2.4.2 Response Format TSTInfo = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 1, 4} // SigningCertificateV2 (id-aa-signingCertificate) is defined in RFC 2634 5.4 // // Reference: https://datatracker.ietf.org/doc/html/rfc2634#section-5.4 SigningCertificate = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 12} // SigningCertificateV2 (id-aa-signingCertificateV2) is defined in RFC 5035 3 // // Reference: https://datatracker.ietf.org/doc/html/rfc5035#section-3 SigningCertificateV2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 47} // ExtKeyUsage (id-ce-extKeyUsage) is defined in RFC 5280 // // Reference: https://www.rfc-editor.org/rfc/rfc5280.html#section-4.2.1.12 ExtKeyUsage = asn1.ObjectIdentifier{2, 5, 29, 37} // Timestamping (id-kp-timeStamping) is defined in RFC 3161 2.3 // // Reference: https://datatracker.ietf.org/doc/html/rfc3161#section-2.3 Timestamping = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 8} ) // OIDs for RFC 3628 Policy Requirements for Time-Stamping Authorities (TSAs) var ( // BaselineTimestampPolicy (baseline time-stamp policy) is defined in // RFC 3628 // // Referene: https://datatracker.ietf.org/doc/html/rfc3628#section-5.2 BaselineTimestampPolicy = asn1.ObjectIdentifier{0, 4, 0, 2023, 1, 1} ) tspclient-go-0.2.0/pki/000077500000000000000000000000001464533346500147215ustar00rootroot00000000000000tspclient-go-0.2.0/pki/errors.go000066400000000000000000000022171464533346500165660ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package pki contains Status of a timestamping response defined in RFC 3161. package pki import "strings" // FailureInfoError is error of FailureInfo with a joined internal error type FailureInfoError struct { // Detail is the joined internal error Detail error } // Error prints out the internal error e.Detail split by '; ' func (e *FailureInfoError) Error() string { if e.Detail == nil { return "" } return strings.ReplaceAll(e.Detail.Error(), "\n", "; ") } // Unwrap returns the internal error func (e *FailureInfoError) Unwrap() error { return e.Detail } tspclient-go-0.2.0/pki/errors_test.go000066400000000000000000000044671464533346500176360ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package pki contains Status of a timestamping response defined in RFC 3161. package pki import ( "errors" "testing" ) func TestFailureInfoError(t *testing.T) { testData := []string{ "unrecognized or unsupported Algorithm Identifier", "transaction not permitted or supported", "the data submitted has the wrong format", "the TSA's time source is not available", "the requested TSA policy is not supported by the TSA", "the requested extension is not supported by the TSA", "the additional information requested could not be understood or is not available", "the request cannot be handled due to system failure", } for idx, f := range failureInfos { if f.Error().Error() != testData[idx] { t.Fatalf("expected %s, but got %s", f.Error().Error(), testData[idx]) } } unknown := FailureInfo(1) if unknown.Error().Error() != "unknown PKIFailureInfo 1" { t.Fatalf("expected %s, but got %s", "unknown PKIFailureInfo", unknown.Error().Error()) } failureInfoErr := FailureInfoError{ Detail: errors.Join(FailureInfoBadRequest.Error(), FailureInfoBadDataFormat.Error()), } expectedErrMsg := "transaction not permitted or supported; the data submitted has the wrong format" if failureInfoErr.Error() != expectedErrMsg { t.Fatalf("expected %s, but got %s", expectedErrMsg, failureInfoErr.Error()) } innerErr := failureInfoErr.Unwrap() expectedErrMsg = "transaction not permitted or supported\nthe data submitted has the wrong format" if innerErr.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, innerErr) } failureInfoErr = FailureInfoError{} expectedErrMsg = "" if failureInfoErr.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, innerErr) } } tspclient-go-0.2.0/pki/pki.go000066400000000000000000000132351464533346500160370ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package pki contains Status of a timestamping response defined in RFC 3161. package pki import ( "encoding/asn1" "errors" "fmt" "strconv" ) // ErrUnknownStatus is used when PKIStatus is not supported var ErrUnknownStatus = errors.New("unknown PKIStatus") // ErrUnknownFailureInfo is used when PKIFailureInfo is not supported or does // not exists var ErrUnknownFailureInfo = errors.New("unknown PKIFailureInfo") // Status is PKIStatus defined in RFC 3161 2.4.2. type Status int const ( StatusGranted Status = 0 // you got exactly what you asked for StatusGrantedWithMods Status = 1 // you got something like what you asked for StatusRejection Status = 2 // you don't get it, more information elsewhere in the message StatusWaiting Status = 3 // the request body part has not yet been processed, expect to hear more later StatusRevocationWarning Status = 4 // this message contains a warning that a revocation is imminent StatusRevocationNotification Status = 5 // notification that a revocation has occurred ) // String converts Status to string func (s Status) String() string { switch s { case StatusGranted: return "granted" case StatusGrantedWithMods: return "granted with modifications" case StatusRejection: return "rejected" case StatusWaiting: return "the request body part has not yet been processed, expect to hear more later" case StatusRevocationWarning: return "warning: a revocation is imminent" case StatusRevocationNotification: return "a revocation has occurred" default: return "unknown PKIStatus " + strconv.Itoa(int(s)) } } // FailureInfo is PKIFailureInfo defined in RFC 3161 2.4.2. type FailureInfo int const ( FailureInfoBadAlg FailureInfo = 0 // unrecognized or unsupported Algorithm Identifier FailureInfoBadRequest FailureInfo = 2 // transaction not permitted or supported FailureInfoBadDataFormat FailureInfo = 5 // the data submitted has the wrong format FailureInfoTimeNotAvailable FailureInfo = 14 // the TSA's time source is not available FailureInfoUnacceptedPolicy FailureInfo = 15 // the requested TSA policy is not supported by the TSA. FailureInfoUnacceptedExtension FailureInfo = 16 // the requested extension is not supported by the TSA. FailureInfoAddInfoNotAvailable FailureInfo = 17 // the additional information requested could not be understood or is not available FailureInfoSystemFailure FailureInfo = 25 // the request cannot be handled due to system failure ) // failureInfos is an array of supported PKIFailureInfo var failureInfos = []FailureInfo{ FailureInfoBadAlg, FailureInfoBadRequest, FailureInfoBadDataFormat, FailureInfoTimeNotAvailable, FailureInfoUnacceptedPolicy, FailureInfoUnacceptedExtension, FailureInfoAddInfoNotAvailable, FailureInfoSystemFailure, } // Error converts a FailureInfo to an error func (fi FailureInfo) Error() error { switch fi { case FailureInfoBadAlg: return errors.New("unrecognized or unsupported Algorithm Identifier") case FailureInfoBadRequest: return errors.New("transaction not permitted or supported") case FailureInfoBadDataFormat: return errors.New("the data submitted has the wrong format") case FailureInfoTimeNotAvailable: return errors.New("the TSA's time source is not available") case FailureInfoUnacceptedPolicy: return errors.New("the requested TSA policy is not supported by the TSA") case FailureInfoUnacceptedExtension: return errors.New("the requested extension is not supported by the TSA") case FailureInfoAddInfoNotAvailable: return errors.New("the additional information requested could not be understood or is not available") case FailureInfoSystemFailure: return errors.New("the request cannot be handled due to system failure") default: return errors.New("unknown PKIFailureInfo " + strconv.Itoa(int(fi))) } } // StatusInfo contains status codes and failure information for PKI messages. // // PKIStatusInfo ::= SEQUENCE { // status PKIStatus, // statusString PKIFreeText OPTIONAL, // failInfo PKIFailureInfo OPTIONAL } // // PKIStatus ::= INTEGER // PKIFreeText ::= SEQUENCE SIZE (1..MAX) OF UTF8String // PKIFailureInfo ::= BIT STRING // // Reference: RFC 3161 2.4.2 type StatusInfo struct { Status Status StatusString []string `asn1:"optional,utf8"` FailInfo asn1.BitString `asn1:"optional"` } // Err return nil when si Status is StatusGranted or StatusGrantedWithMods // // Otherwise, Err returns an error with FailInfo if any. func (si StatusInfo) Err() error { if si.Status != StatusGranted && si.Status != StatusGrantedWithMods { var errs []error for _, fi := range failureInfos { if si.FailInfo.At(int(fi)) != 0 { errs = append(errs, fi.Error()) } } if len(errs) != 0 { // there is FailInfo, wrap them into a FailureInfoError return fmt.Errorf("invalid response with status code %d: %s. Failure info: %w", si.Status, si.Status.String(), &FailureInfoError{Detail: errors.Join(errs...)}) } return fmt.Errorf("invalid response with status code %d: %s", si.Status, si.Status.String()) } return nil } tspclient-go-0.2.0/pki/pki_test.go000066400000000000000000000055061464533346500171000ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package pki import ( "encoding/asn1" "testing" ) // statuses is an array of supported PKIStatus var statuses = []Status{ StatusGranted, StatusGrantedWithMods, StatusRejection, StatusWaiting, StatusRevocationWarning, StatusRevocationNotification, } func TestStatusInfo(t *testing.T) { statusInfo := StatusInfo{ Status: StatusGranted, } err := statusInfo.Err() if err != nil { t.Fatalf("expected nil error, but got %s", err) } statusInfo = StatusInfo{ Status: StatusRejection, FailInfo: asn1.BitString{ // unknown FailureInfo Bytes: []byte{0x01}, BitLength: 1, }, } err = statusInfo.Err() expectedErrMsg := "invalid response with status code 2: rejected" if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected %s, but got %s", expectedErrMsg, err) } statusInfo = StatusInfo{ Status: StatusRejection, FailInfo: asn1.BitString{ // FailureInfoBadAlg Bytes: []byte{0x80}, BitLength: 1, }, } err = statusInfo.Err() expectedErrMsg = "invalid response with status code 2: rejected. Failure info: unrecognized or unsupported Algorithm Identifier" if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected %s, but got %s", expectedErrMsg, err) } statusInfo = StatusInfo{ Status: StatusRejection, FailInfo: asn1.BitString{ // FailureInfoBadRequest and FailureInfoBadDataFormat Bytes: []byte{0x24}, BitLength: 8, }, } err = statusInfo.Err() expectedErrMsg = "invalid response with status code 2: rejected. Failure info: transaction not permitted or supported; the data submitted has the wrong format" if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected %s, but got %s", expectedErrMsg, err) } } func TestStatusString(t *testing.T) { testData := []string{ "granted", "granted with modifications", "rejected", "the request body part has not yet been processed, expect to hear more later", "warning: a revocation is imminent", "a revocation has occurred", } for idx, s := range statuses { if s.String() != testData[idx] { t.Fatalf("expected %s, but got %s", s.String(), testData[idx]) } } unknown := Status(6) if unknown.String() != "unknown PKIStatus 6" { t.Fatalf("expected %s, but got %s", "unknown PKIStatus", unknown.String()) } } tspclient-go-0.2.0/request.go000066400000000000000000000157471464533346500161730ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tspclient import ( "bytes" "crypto" "crypto/rand" "crypto/x509/pkix" "encoding/asn1" "errors" "fmt" "math/big" tspclientasn1 "github.com/notaryproject/tspclient-go/internal/encoding/asn1" "github.com/notaryproject/tspclient-go/internal/hashutil" "github.com/notaryproject/tspclient-go/internal/oid" ) // MessageImprint contains the hash of the datum to be time-stamped. // // MessageImprint ::= SEQUENCE { // hashAlgorithm AlgorithmIdentifier, // hashedMessage OCTET STRING } type MessageImprint struct { HashAlgorithm pkix.AlgorithmIdentifier HashedMessage []byte } // Equal compares if m and n are the same MessageImprint // // Reference: RFC 3161 2.4.2 func (m MessageImprint) Equal(n MessageImprint) bool { return m.HashAlgorithm.Algorithm.Equal(n.HashAlgorithm.Algorithm) && tspclientasn1.EqualRawValue(m.HashAlgorithm.Parameters, n.HashAlgorithm.Parameters) && bytes.Equal(m.HashedMessage, n.HashedMessage) } // asn1NullRawValue is the full form of asn1.NullRawValue with its encoded self // in the `FullBytes` field. // // https://pkg.go.dev/encoding/asn1#NullRawValue var asn1NullRawValue = asn1.RawValue{ Tag: asn1.TagNull, FullBytes: []byte{asn1.TagNull, 0}, } // Request is a time-stamping request. // // TimeStampReq ::= SEQUENCE { // version INTEGER { v1(1) }, // messageImprint MessageImprint, // reqPolicy TSAPolicyID OPTIONAL, // nonce INTEGER OPTIONAL, // certReq BOOLEAN DEFAULT FALSE, // extensions [0] IMPLICIT Extensions OPTIONAL } type Request struct { Version int // fixed to 1 as defined in RFC 3161 2.4.1 Request Format MessageImprint MessageImprint ReqPolicy asn1.ObjectIdentifier `asn1:"optional"` Nonce *big.Int `asn1:"optional"` CertReq bool `asn1:"optional,default:false"` Extensions []pkix.Extension `asn1:"optional,tag:0"` } // RequestOptions provides options for caller to create a new timestamp request type RequestOptions struct { // Content is the datum to be time stamped. REQUIRED. Content []byte // HashAlgorithm is the hash algorithm to be used to hash the Content. // REQUIRED and MUST be an available hash algorithm. HashAlgorithm crypto.Hash // HashAlgorithmParameters is the parameters for the HashAlgorithm. // OPTIONAL. // // Reference: https://www.rfc-editor.org/rfc/rfc5280#section-4.1.1.2 HashAlgorithmParameters asn1.RawValue // ReqPolicy specifies the TSA policy ID. OPTIONAL. // // Reference: https://datatracker.ietf.org/doc/html/rfc3161#section-2.4.1 ReqPolicy asn1.ObjectIdentifier // NoNonce disables any Nonce usage. When set to true, the Nonce field is // ignored, and no built-in Nonce will be generated. OPTIONAL. NoNonce bool // Nonce is a large random number with a high probability that the client // generates it only once. The same nonce is included and validated in the // response. It is only used when NoNonce is not set to true. // // When this field is nil, a built-in Nonce will be generated and sent to // the TSA. OPTIONAL. Nonce *big.Int // NoCert tells the TSA to not include any signing certificate in its // response. By default, TSA signing certificate is included in the response. // OPTIONAL. NoCert bool // Extensions is a generic way to add additional information // to the request in the future. OPTIONAL. Extensions []pkix.Extension } // NewRequest creates a timestamp request based on caller provided options. func NewRequest(opts RequestOptions) (*Request, error) { if opts.Content == nil { return nil, &MalformedRequestError{Msg: "content to be time stamped cannot be empty"} } hashAlg, err := oid.FromHash(opts.HashAlgorithm) if err != nil { return nil, &MalformedRequestError{Msg: err.Error()} } digest, err := hashutil.ComputeHash(opts.HashAlgorithm, opts.Content) if err != nil { return nil, &MalformedRequestError{Msg: err.Error()} } hashAlgParameter := opts.HashAlgorithmParameters if tspclientasn1.EqualRawValue(hashAlgParameter, asn1.RawValue{}) || tspclientasn1.EqualRawValue(hashAlgParameter, asn1.NullRawValue) { hashAlgParameter = asn1NullRawValue } var nonce *big.Int if !opts.NoNonce { if opts.Nonce != nil { // user provided Nonce, use it nonce = opts.Nonce } else { // user ignored Nonce, use built-in Nonce var err error nonce, err = generateNonce() if err != nil { return nil, &MalformedRequestError{Msg: err.Error()} } } } return &Request{ Version: 1, MessageImprint: MessageImprint{ HashAlgorithm: pkix.AlgorithmIdentifier{ Algorithm: hashAlg, Parameters: hashAlgParameter, }, HashedMessage: digest, }, ReqPolicy: opts.ReqPolicy, Nonce: nonce, CertReq: !opts.NoCert, Extensions: opts.Extensions, }, nil } // MarshalBinary encodes the request to binary form. // This method implements encoding.BinaryMarshaler. // // Reference: https://pkg.go.dev/encoding#BinaryMarshaler func (r *Request) MarshalBinary() ([]byte, error) { if r == nil { return nil, errors.New("nil request") } return asn1.Marshal(*r) } // UnmarshalBinary decodes the request from binary form. // This method implements encoding.BinaryUnmarshaler. // // Reference: https://pkg.go.dev/encoding#BinaryUnmarshaler func (r *Request) UnmarshalBinary(data []byte) error { _, err := asn1.Unmarshal(data, r) return err } // Validate checks if req is a valid request against RFC 3161. // It is used before a timstamp requestor sending the request to TSA. func (r *Request) Validate() error { if r == nil { return &MalformedRequestError{Msg: "request cannot be nil"} } if r.Version != 1 { return &MalformedRequestError{Msg: fmt.Sprintf("request version must be 1, but got %d", r.Version)} } hashAlg := r.MessageImprint.HashAlgorithm.Algorithm hash, available := oid.ToHash(hashAlg) if !available { return &MalformedRequestError{Msg: fmt.Sprintf("hash algorithm %v is unavailable", hashAlg)} } if hash.Size() != len(r.MessageImprint.HashedMessage) { return &MalformedRequestError{Msg: fmt.Sprintf("hashed message is of incorrect size %d", len(r.MessageImprint.HashedMessage))} } return nil } // generateNonce generates a built-in Nonce for TSA request func generateNonce() (*big.Int, error) { // Pick a random number from 0 to 2^159 nonce, err := rand.Int(rand.Reader, (&big.Int{}).Lsh(big.NewInt(1), 159)) if err != nil { return nil, errors.New("error generating nonce") } return nonce, nil } tspclient-go-0.2.0/request_test.go000066400000000000000000000113351464533346500172170ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tspclient import ( "crypto" "crypto/x509/pkix" "encoding/asn1" "errors" "fmt" "reflect" "testing" "github.com/notaryproject/tspclient-go/internal/hashutil" "github.com/notaryproject/tspclient-go/internal/oid" ) func TestNewRequest(t *testing.T) { message := []byte("test") var malformedRequest *MalformedRequestError opts := RequestOptions{} expectedErrMsg := "malformed timestamping request: content to be time stamped cannot be empty" _, err := NewRequest(opts) if err == nil || !errors.As(err, &malformedRequest) || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } opts = RequestOptions{ Content: message, HashAlgorithm: crypto.SHA1, } expectedErrMsg = fmt.Sprintf("malformed timestamping request: unsupported hashing algorithm: %s", crypto.SHA1) _, err = NewRequest(opts) if err == nil || !errors.As(err, &malformedRequest) || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } opts = RequestOptions{ Content: message, HashAlgorithm: crypto.SHA256, } req, err := NewRequest(opts) if err != nil { t.Fatalf("expected nil error, but got %v", err) } if !reflect.DeepEqual(req.MessageImprint.HashAlgorithm.Parameters, asn1NullRawValue) { t.Fatalf("expected %v, but got %v", asn1NullRawValue, req.MessageImprint.HashAlgorithm.Parameters) } opts = RequestOptions{ Content: message, HashAlgorithm: crypto.SHA256, HashAlgorithmParameters: asn1.NullRawValue, } req, err = NewRequest(opts) if err != nil { t.Fatalf("expected nil error, but got %v", err) } if !reflect.DeepEqual(req.MessageImprint.HashAlgorithm.Parameters, asn1NullRawValue) { t.Fatalf("expected %v, but got %v", asn1NullRawValue, req.MessageImprint.HashAlgorithm.Parameters) } } func TestRequestMarshalBinary(t *testing.T) { var r *Request _, err := r.MarshalBinary() if err == nil || err.Error() != "nil request" { t.Fatalf("expected error 'nil request', but got %v", err) } opts := RequestOptions{ Content: []byte("test"), HashAlgorithm: crypto.SHA256, } req, err := NewRequest(opts) if err != nil { t.Fatalf("NewRequest() error = %v", err) } _, err = req.MarshalBinary() if err != nil { t.Fatal(err) } } func TestRequestUnmarshalBinary(t *testing.T) { var r *Request expectedErrMsg := "asn1: Unmarshal recipient value is nil *tspclient.Request" err := r.UnmarshalBinary([]byte("test")) if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } } func TestValidateRequest(t *testing.T) { var r *Request var malformedRequest *MalformedRequestError expectedErrMsg := "malformed timestamping request: request cannot be nil" err := r.Validate() if err == nil || !errors.As(err, &malformedRequest) || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } r = &Request{ Version: 2, } expectedErrMsg = "malformed timestamping request: request version must be 1, but got 2" err = r.Validate() if err == nil || !errors.As(err, &malformedRequest) || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } r = &Request{ Version: 1, MessageImprint: MessageImprint{ HashAlgorithm: pkix.AlgorithmIdentifier{ Algorithm: asn1.ObjectIdentifier{1}, }, }, } expectedErrMsg = "malformed timestamping request: hash algorithm 1 is unavailable" err = r.Validate() if err == nil || !errors.As(err, &malformedRequest) || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } digest, err := hashutil.ComputeHash(crypto.SHA384, []byte("test")) if err != nil { t.Fatal(err) } r = &Request{ Version: 1, MessageImprint: MessageImprint{ HashAlgorithm: pkix.AlgorithmIdentifier{ Algorithm: oid.SHA256, }, HashedMessage: digest, }, } expectedErrMsg = "malformed timestamping request: hashed message is of incorrect size 48" err = r.Validate() if err == nil || !errors.As(err, &malformedRequest) || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } } tspclient-go-0.2.0/response.go000066400000000000000000000160001464533346500163200ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tspclient import ( "crypto/x509/pkix" "encoding/asn1" "errors" "fmt" "math/big" "time" "github.com/notaryproject/tspclient-go/pki" ) // signingCertificateV2 contains certificate hash and identifier of the // TSA signing certificate. // // Reference: RFC 5035 3 signingCertificateV2 // // signingCertificateV2 ::= SEQUENCE { // certs SEQUENCE OF ESSCertIDv2, // policies SEQUENCE OF PolicyInformation OPTIONAL } type signingCertificateV2 struct { // Certificates contains the list of certificates. The first certificate // MUST be the signing certificate used to verify the timestamp token. Certificates []eSSCertIDv2 // Policies suggests policy values to be used in the certification path // validation. Policies asn1.RawValue `asn1:"optional"` } // eSSCertIDv2 uniquely identifies a certificate. // // Reference: RFC 5035 4 // // eSSCertIDv2 ::= SEQUENCE { // hashAlgorithm AlgorithmIdentifier // DEFAULT {algorithm id-sha256}, // certHash Hash, // issuerSerial IssuerSerial OPTIONAL } type eSSCertIDv2 struct { // HashAlgorithm is the hashing algorithm used to hash certificate. // When it is not present, the default value is SHA256 (id-sha256). // Supported values are SHA256, SHA384, and SHA512 HashAlgorithm pkix.AlgorithmIdentifier `asn1:"optional"` // CertHash is the certificate hash using algorithm specified // by HashAlgorithm. It is computed over the entire DER-encoded // certificate (including the signature) CertHash []byte // IssuerSerial holds the issuer and serialNumber of the certificate. // When it is not present, the SignerIdentifier field in the SignerInfo // will be used. IssuerSerial issuerAndSerial `asn1:"optional"` } // issuerAndSerial holds the issuer name and serialNumber of the certificate // // Reference: RFC 5035 4 // // IssuerSerial ::= SEQUENCE { // issuer GeneralNames, // serialNumber CertificateSerialNumber } type issuerAndSerial struct { IssuerName generalNames SerialNumber *big.Int } // generalNames holds the issuer name of the certificate. // // Reference: RFC 3280 4.2.1.7 // // GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName // // GeneralName ::= CHOICE { // otherName [0] OtherName, // rfc822Name [1] IA5String, // dNSName [2] IA5String, // x400Address [3] ORAddress, // directoryName [4] Name, // ediPartyName [5] EDIPartyName, // uniformResourceIdentifier [6] IA5String, // iPAddress [7] OCTET STRING, // registeredID [8] OBJECT IDENTIFIER } type generalNames struct { Name asn1.RawValue `asn1:"optional,tag:4"` } // Response is a time-stamping response. // // TimeStampResp ::= SEQUENCE { // status PKIStatusInfo, // timeStampToken TimeStampToken OPTIONAL } type Response struct { Status pki.StatusInfo TimestampToken asn1.RawValue `asn1:"optional"` } // MarshalBinary encodes the response to binary form. // This method implements encoding.BinaryMarshaler. // // Reference: https://pkg.go.dev/encoding#BinaryMarshaler func (r *Response) MarshalBinary() ([]byte, error) { if r == nil { return nil, errors.New("nil response") } return asn1.Marshal(*r) } // UnmarshalBinary decodes the response from binary form. // This method implements encoding.BinaryUnmarshaler. // // Reference: https://pkg.go.dev/encoding#BinaryUnmarshaler func (r *Response) UnmarshalBinary(data []byte) error { _, err := asn1.Unmarshal(data, r) return err } // SignedToken returns the timestamp token with signatures. // // Callers should invoke SignedToken.Verify to verify the content before // comsumption. func (r *Response) SignedToken() (*SignedToken, error) { if err := r.validateStatus(); err != nil { return nil, err } return ParseSignedToken(r.TimestampToken.FullBytes) } // Validate checks if resp is a successful timestamp response against // its corresponding request based on RFC 3161. // It is used when a timestamp requestor receives the response from TSA. func (r *Response) Validate(req *Request) error { if req == nil { return &InvalidResponseError{Msg: "missing corresponding request"} } if r == nil { return &InvalidResponseError{Msg: "response cannot be nil"} } if err := r.validateStatus(); err != nil { return err } token, err := r.SignedToken() if err != nil { return &InvalidResponseError{Detail: err} } info, err := token.Info() if err != nil { return &InvalidResponseError{Detail: err} } if info.Version != 1 { return &InvalidResponseError{Msg: fmt.Sprintf("timestamp token info version must be 1, but got %d", info.Version)} } // check policy if req.ReqPolicy != nil && !req.ReqPolicy.Equal(info.Policy) { return &InvalidResponseError{Msg: fmt.Sprintf("policy in response %v does not match policy in request %v", info.Policy, req.ReqPolicy)} } // check MessageImprint if !info.MessageImprint.Equal(req.MessageImprint) { return &InvalidResponseError{Msg: fmt.Sprintf("message imprint in response %+v does not match with request %+v", info.MessageImprint, req.MessageImprint)} } // check gen time to be UTC // reference: https://datatracker.ietf.org/doc/html/rfc3161#section-2.4.2 genTime := info.GenTime if genTime.Location() != time.UTC { return &InvalidResponseError{Msg: "TSTInfo genTime must be in UTC"} } // check nonce if req.Nonce != nil { responseNonce := info.Nonce if responseNonce == nil || responseNonce.Cmp(req.Nonce) != 0 { return &InvalidResponseError{Msg: fmt.Sprintf("nonce in response %s does not match nonce in request %s", responseNonce, req.Nonce)} } } // check certReq if req.CertReq { for _, signerInfo := range token.SignerInfos { if _, err := token.SigningCertificate(&signerInfo); err == nil { // found at least one signing certificate return nil } } // no signing certificate was found return &InvalidResponseError{Msg: "certReq is True in request, but did not find any TSA signing certificate in the response"} } if len(token.Certificates) != 0 { return &InvalidResponseError{Msg: "certReq is False in request, but certificates field is included in the response"} } return nil } // validateStatus validates the response.Status // // Reference: RFC 3161 2.4.2 func (r *Response) validateStatus() error { if err := r.Status.Err(); err != nil { return &InvalidResponseError{Detail: err} } return nil } tspclient-go-0.2.0/response_test.go000066400000000000000000000304151464533346500173650ustar00rootroot00000000000000// Copyright The Notary Project Authors. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tspclient import ( "context" "crypto" "crypto/x509/pkix" "encoding/asn1" "encoding/hex" "errors" "math/big" "os" "testing" "time" "github.com/notaryproject/tspclient-go/internal/oid" "github.com/notaryproject/tspclient-go/pki" ) func TestResponseMarshalBinary(t *testing.T) { var r *Response _, err := r.MarshalBinary() if err == nil || err.Error() != "nil response" { t.Fatalf("expected error nil response, but got %v", err) } testResponse := Response{ Status: pki.StatusInfo{ Status: pki.StatusGranted, }, } _, err = (&testResponse).MarshalBinary() if err != nil { t.Fatal(err) } } func TestResponseUnmarshalBinary(t *testing.T) { var r *Response expectedErrMsg := "asn1: Unmarshal recipient value is nil *tspclient.Response" err := r.UnmarshalBinary([]byte("test")) if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } } func TestValidateStatus(t *testing.T) { badResponse := Response{ Status: pki.StatusInfo{ Status: pki.StatusRejection, }, } expectedErrMsg := "invalid timestamping response: invalid response with status code 2: rejected" err := (&badResponse).validateStatus() if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } badResponse = Response{ Status: pki.StatusInfo{ Status: pki.StatusRejection, FailInfo: asn1.BitString{ Bytes: []byte{0x80}, BitLength: 1, }, }, } expectedErrMsg = "invalid timestamping response: invalid response with status code 2: rejected. Failure info: unrecognized or unsupported Algorithm Identifier" err = (&badResponse).validateStatus() if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } validResponse := Response{ Status: pki.StatusInfo{ Status: pki.StatusGranted, }, } err = (&validResponse).validateStatus() if err != nil { t.Fatal(err) } } func TestSignedToken(t *testing.T) { badResponse := Response{ Status: pki.StatusInfo{ Status: pki.StatusRejection, }, } expectedErrMsg := "invalid timestamping response: invalid response with status code 2: rejected" _, err := (&badResponse).SignedToken() if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } validResponse := Response{ Status: pki.StatusInfo{ Status: pki.StatusGranted, }, } expectedErrMsg = "cms: syntax error: invalid signed data: failed to convert from BER to DER: asn1: syntax error: BER-encoded ASN.1 data structures is empty" _, err = (&validResponse).SignedToken() if err == nil || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } } func TestValidateResponse(t *testing.T) { var invalidResponse *InvalidResponseError var req *Request var resp *Response expectedErrMsg := "invalid timestamping response: missing corresponding request" err := resp.Validate(req) if err == nil || !errors.As(err, &invalidResponse) || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } req = &Request{ Version: 1, } expectedErrMsg = "invalid timestamping response: response cannot be nil" err = resp.Validate(req) if err == nil || !errors.As(err, &invalidResponse) || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } resp = &Response{ Status: pki.StatusInfo{ Status: pki.StatusRejection, }, } expectedErrMsg = "invalid timestamping response: invalid response with status code 2: rejected" err = resp.Validate(req) if err == nil || !errors.As(err, &invalidResponse) || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } resp = &Response{ Status: pki.StatusInfo{ Status: pki.StatusGranted, }, } expectedErrMsg = "invalid timestamping response: cms: syntax error: invalid signed data: failed to convert from BER to DER: asn1: syntax error: BER-encoded ASN.1 data structures is empty" err = resp.Validate(req) if err == nil || !errors.As(err, &invalidResponse) || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } token, err := os.ReadFile("testdata/TimeStampTokenWithInvalidTSTInfo.p7s") if err != nil { t.Fatal("failed to read timestamp token from file:", err) } resp = &Response{ Status: pki.StatusInfo{ Status: pki.StatusGranted, }, TimestampToken: asn1.RawValue{ FullBytes: token, }, } expectedErrMsg = "invalid timestamping response: cannot unmarshal TSTInfo from timestamp token: asn1: structure error: tags don't match (23 vs {class:0 tag:16 length:3 isCompound:true}) {optional:false explicit:false application:false private:false defaultValue: tag: stringType:0 timeType:24 set:false omitEmpty:false} Time @89" err = resp.Validate(req) if err == nil || !errors.As(err, &invalidResponse) || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } req = &Request{} token, err = os.ReadFile("testdata/TimeStampTokenWithTSTInfoVersion2.p7s") if err != nil { t.Fatal("failed to read timestamp token from file:", err) } resp = &Response{ Status: pki.StatusInfo{ Status: pki.StatusGranted, }, TimestampToken: asn1.RawValue{ FullBytes: token, }, } expectedErrMsg = "invalid timestamping response: timestamp token info version must be 1, but got 2" err = resp.Validate(req) if err == nil || !errors.As(err, &invalidResponse) || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } req = &Request{ ReqPolicy: asn1.ObjectIdentifier{1}, } token, err = os.ReadFile("testdata/TimeStampToken.p7s") if err != nil { t.Fatal("failed to read timestamp token from file:", err) } resp = &Response{ Status: pki.StatusInfo{ Status: pki.StatusGranted, }, TimestampToken: asn1.RawValue{ FullBytes: token, }, } expectedErrMsg = "invalid timestamping response: policy in response 1.3.6.1.4.1.4146.2.3 does not match policy in request 1" err = resp.Validate(req) if err == nil || !errors.As(err, &invalidResponse) || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } req = &Request{} token, err = os.ReadFile("testdata/TimeStampToken.p7s") if err != nil { t.Fatal("failed to read timestamp token from file:", err) } resp = &Response{ Status: pki.StatusInfo{ Status: pki.StatusGranted, }, TimestampToken: asn1.RawValue{ FullBytes: token, }, } expectedErrMsg = "invalid timestamping response: message imprint in response {HashAlgorithm:{Algorithm:2.16.840.1.101.3.4.2.1 Parameters:{Class:0 Tag:5 IsCompound:false Bytes:[] FullBytes:[5 0]}} HashedMessage:[131 38 244 112 157 64 29 250 191 167 131 2 251 28 222 160 241 128 72 164 64 64 194 18 189 142 40 218 107 198 81 199]} does not match with request {HashAlgorithm:{Algorithm: Parameters:{Class:0 Tag:0 IsCompound:false Bytes:[] FullBytes:[]}} HashedMessage:[]}" err = resp.Validate(req) if err == nil || !errors.As(err, &invalidResponse) || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } digest, err := hex.DecodeString("8326f4709d401dfabfa78302fb1cdea0f18048a44040c212bd8e28da6bc651c7") if err != nil { t.Fatal(err) } messageImprint := MessageImprint{ HashAlgorithm: pkix.AlgorithmIdentifier{ Algorithm: oid.SHA256, Parameters: asn1.RawValue{ Class: 0, Tag: 5, FullBytes: []byte{5, 0}, }, }, HashedMessage: digest, } req = &Request{ MessageImprint: messageImprint, Nonce: big.NewInt(63456), } token, err = os.ReadFile("testdata/TimeStampToken.p7s") if err != nil { t.Fatal("failed to read timestamp token from file:", err) } resp = &Response{ Status: pki.StatusInfo{ Status: pki.StatusGranted, }, TimestampToken: asn1.RawValue{ FullBytes: token, }, } expectedErrMsg = "invalid timestamping response: nonce in response does not match nonce in request 63456" err = resp.Validate(req) if err == nil || !errors.As(err, &invalidResponse) || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } req = &Request{ MessageImprint: messageImprint, CertReq: true, } token, err = os.ReadFile("testdata/TimeStampTokenWithoutCertificate.p7s") if err != nil { t.Fatal("failed to read timestamp token from file:", err) } resp = &Response{ Status: pki.StatusInfo{ Status: pki.StatusGranted, }, TimestampToken: asn1.RawValue{ FullBytes: token, }, } expectedErrMsg = "invalid timestamping response: certReq is True in request, but did not find any TSA signing certificate in the response" err = resp.Validate(req) if err == nil || !errors.As(err, &invalidResponse) || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } req = &Request{ MessageImprint: messageImprint, CertReq: false, } token, err = os.ReadFile("testdata/TimeStampToken.p7s") if err != nil { t.Fatal("failed to read timestamp token from file:", err) } resp = &Response{ Status: pki.StatusInfo{ Status: pki.StatusGranted, }, TimestampToken: asn1.RawValue{ FullBytes: token, }, } expectedErrMsg = "invalid timestamping response: certReq is False in request, but certificates field is included in the response" err = resp.Validate(req) if err == nil || !errors.As(err, &invalidResponse) || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } req = &Request{ Version: 1, MessageImprint: messageImprint, ReqPolicy: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 4146, 2, 3}, CertReq: true, } token, err = os.ReadFile("testdata/TimeStampToken.p7s") if err != nil { t.Fatal("failed to read timestamp token from file:", err) } resp = &Response{ Status: pki.StatusInfo{ Status: pki.StatusGranted, }, TimestampToken: asn1.RawValue{ FullBytes: token, }, } err = resp.Validate(req) if err != nil { t.Fatalf("expected nil error, but got %v", err) } req = &Request{ Version: 1, MessageImprint: messageImprint, ReqPolicy: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 4146, 2, 3}, CertReq: false, } token, err = os.ReadFile("testdata/TimeStampTokenWithoutCertificate.p7s") if err != nil { t.Fatal("failed to read timestamp token from file:", err) } resp = &Response{ Status: pki.StatusInfo{ Status: pki.StatusGranted, }, TimestampToken: asn1.RawValue{ FullBytes: token, }, } err = resp.Validate(req) if err != nil { t.Fatalf("expected nil error, but got %v", err) } } func TestTSAWithGenTimeNotUTC(t *testing.T) { // prepare TSA loc := time.FixedZone("UTC+8", 8*60*60) now := time.Date(2021, 9, 18, 11, 54, 34, 0, loc) tsa, err := newTestTSA(false, true) if err != nil { t.Fatalf("NewTSA() error = %v", err) } tsa.nowFunc = func() time.Time { return now } tsa.malformedTimeZone = true // do timestamp requestOpts := RequestOptions{ Content: []byte("notation"), HashAlgorithm: crypto.SHA256, } req, err := NewRequest(requestOpts) if err != nil { t.Fatalf("NewRequest() error = %v", err) } ctx := context.Background() resp, err := tsa.Timestamp(ctx, req) if err != nil { t.Fatalf("TSA.Timestamp() error = %v", err) } wantStatus := pki.StatusGranted if got := resp.Status.Status; got != wantStatus { t.Fatalf("Response.Status = %v, want %v", got, wantStatus) } expectedErrMsg := "invalid timestamping response: TSTInfo genTime must be in UTC" var invalidResponse *InvalidResponseError err = resp.Validate(req) if err == nil || !errors.As(err, &invalidResponse) || err.Error() != expectedErrMsg { t.Fatalf("expected error %s, but got %v", expectedErrMsg, err) } } tspclient-go-0.2.0/testdata/000077500000000000000000000000001464533346500157475ustar00rootroot00000000000000tspclient-go-0.2.0/testdata/GlobalSignRootCA.crt000066400000000000000000000023151464533346500215530ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK 6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH WD9f -----END CERTIFICATE----- tspclient-go-0.2.0/testdata/SHA1TimeStampToken.p7s000066400000000000000000000147031464533346500217300ustar00rootroot000000000000000 *H 010  `He0 *H  0 +2010  *H  &p@ޠH@@(kQY<&g*Mty20210917140910Z0WU0S1 0 UBE10U GlobalSign nv-sa1)0'U Globalsign TSA for Advanced - G4d0U0=FiPpMA0  *H  0[1 0 UBE10U GlobalSign nv-sa110/U(GlobalSign Timestamping CA - SHA384 - G40 210527095523Z 320628095522Z0S1 0 UBE10U GlobalSign nv-sa1)0'U Globalsign TSA for Advanced - G400  *H 00i{ nT9_dYu"Niott}QF h.eґU;bBz2Lѹ1%Vg22\-bM 7*`ol=1])Uvl{a)3,nZC;0 xT`8dCpȴ ܻn!5~„8~dz& *%RmL0 x-FW ?yP32@ PAN !D2H,K W[dTjC v֣ 0@6Ўx/ ;aGTpCZhᮓ,r2K+%3h*y00U0U% 0 +0U~px )xY̦$0LU E0C0A +20402+&https://www.globalsign.com/repository/0 U00+009+0-http://ocsp.globalsign.com/ca/gstsacasha384g40C+07http://secure.globalsign.com/cacert/gstsacasha384g4.crt0U#0iWE93@ýe0AU:0806420http://crl.globalsign.com/ca/gstsacasha384g4.crl0  *H  bw/BokEY6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O=!0TU M0K0IU 0A0?+3http://www.microsoft.com/pkiops/Docs/Repository.htm0U% 0 +0 +7  SubCA0U00U#0~j*'POh0U}0{0ywushttp://www.microsoft.com/pkiops/crl/Microsoft%20Identity%20Verification%20Root%20Certificate%20Authority%202020.crl0+00+0uhttp://www.microsoft.com/pkiops/certs/Microsoft%20Identity%20Verification%20Root%20Certificate%20Authority%202020.crt0  *H  _v~mZuLxhND%49; {ċ5c&ynZGB؅2KS"rhIy3mE󲃢ᄝԩgLͽ\!!{T#~=0sc0r5Bz˟Smu%,]͜"dΆ= c5t[|w훼3#4jľTv'}p҆"xjWk`rO Z/ d%fu*Q hHҎ 8ڛ0&ӋhA(t5W)RJpK.1__GKiX~FN\rv!a O|¸x9ǤRD,(xЛ+W+:GJ!嗼B?ͨL`V=;W{z2v6M@o#'tTBXX 3b٧*S+{TOOՑq?T]9SLT Bd['sӇ _#fş-k bƬ@o&2.Z+ڈTBsҽH. I\ :GP ,ಶ|'GGJYkmEeg*;:{Jۤ1 jDq^qzљؿFF#4s;,7: !p؅ u{)'Fbkm, @CF`YCzX3xv3qu| Q뵚ѻ0Ǯ5[K`'г.Tk,NA]z`ꈀ?@V~900U- :X1XGԫ!<+0U#0ki(:5/Hc@{دI>=!0lUe0c0a_][http://www.microsoft.com/pkiops/crl/Microsoft%20Public%20RSA%20Timestamping%20CA%202020.crl0y+m0k0i+0]http://www.microsoft.com/pkiops/certs/Microsoft%20Public%20RSA%20Timestamping%20CA%202020.crt0 U00U% 0 +0U0fU _0]0Q +7L}0A0?+3http://www.microsoft.com/pkiops/Docs/Repository.htm0g 0  *H  $27#IgKŖ8. o? ͭ ./ED\A7rCsX;Jّa0d zyPWIz-%XҝU^2R=BEҶQʊfaFX|eK Kb4l`CÌ+r3厒TwBm{AypDřNaonvwهKL<_^6 k:oDO$3n*v8Z/~jF "$LI(N bY0 S >r]ba0˻@Q&q,~{Gj{8XR,u;N,+QVzVHR55gG9 +L  <\ݷn9H#ЄŲheB9/*&ΧK'VO9㗎`6_ CG 8~n.0"3Y5ǫЗB1E0A0x0a1 0 UUS10U Microsoft Corporation1200U)Microsoft Public RSA Timestamping CA 202035  50  `He0 *H  10 *H  1  *H  0 *H  1 240621071205Z0/ *H  1" U pnVc|1R=qNWC){rx0 *H  /1000 |Sa&Z<Οx-qR][c&uW0|0ec0a1 0 UUS10U Microsoft Corporation1200U)Microsoft Public RSA Timestamping CA 202035  50` *H  1O0KG0C0+0 ᤁ01 0 UUS10U Washington10URedmond10U Microsoft Corporation1%0#U Microsoft America Operations1'0%U nShield TSS ESN:7D00-05E0-D9471503U,Microsoft Public RSA Time Stamping Authority# 0+/컆芣b٠g0ec0a1 0 UUS10U Microsoft Corporation1200U)Microsoft Public RSA Timestamping CA 20200  *H  _0"20240621021014Z20240622021014Z0v0< +Y 1.0,0 _0 (0A0  06 +Y 1(0&0  +Y  0 00  *H  2څ^3"^l V_)z(4 L `gifh7o;eF|v)Vo?X$,Mw+d~U7H.@c3k_%!I #c7ƲX"zq+`#cb NV$Wa|\,څ/)ֵE@i-^At{6@^evj1`ݑ RA\V@@b-b+wzͦު~`~)z|T'ӵݿc"F|g;Y}&t.F^D&/Na(}‡+ W0يRaUtz[w'm 1) sRn84z#"ϐjصۜJL0\HK񤣁s <$tf uCf>첯jf;E8\ԜQUn; tspclient-go-0.2.0/testdata/TimeStampTokenWithInvalidSignature.p7s000066400000000000000000000147031464533346500253400ustar00rootroot000000000000000 *H 010  `He0 *H  0 +2010  `He &p@ޠH@@(kQY<&g*Mty20210917140910Z0WU0S1 0 UBE10U GlobalSign nv-sa1)0'U Globalsign TSA for Advanced - G4d0U0=FiPpMA0  *H  0[1 0 UBE10U GlobalSign nv-sa110/U(GlobalSign Timestamping CA - SHA384 - G40 210527095523Z 320628095522Z0S1 0 UBE10U GlobalSign nv-sa1)0'U Globalsign TSA for Advanced - G400  *H 00i{ nT9_dYu"Niott}QF h.eґU;bBz2Lѹ1%Vg22\-bM 7*`ol=1])Uvl{a)3,nZC;0 xT`8dCpȴ ܻn!5~„8~dz& *%RmL0 x-FW ?yP32@ PAN !D2H,K W[dTjC v֣ 0@6Ўx/ ;aGTpCZhᮓ,r2K+%3h*y00U0U% 0 +0U~px )xY̦$0LU E0C0A +20402+&https://www.globalsign.com/repository/0 U00+009+0-http://ocsp.globalsign.com/ca/gstsacasha384g40C+07http://secure.globalsign.com/cacert/gstsacasha384g4.crt0U#0iWE93@ýe0AU:0806420http://crl.globalsign.com/ca/gstsacasha384g4.crl0  *H  bw/BokEY6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O|hٹvOarG-AwxbB"@:&v{oWQeLF ҭj~%%E xt 74&\-a9eg8,>n] RfOm׿272RfCl_^2u.P.bhxqݎ)Uz 1,lQC|w+6(#Ct>ab|Cs]`4)2Lo#T{ZyF8yɆTl)v= Uo;&(v;7|=A#}OS@ r!u" o+Ș=esVQYq҅|% Nj*/ࠪB wkTX +YF`7dZ TogIp3:ChYIH.xRw(! %'o<Ӵ8+6$H)U-00  0  *H  010U Free TSA10U Root CA10Uwww.freetsa.org1"0  *H  busilezas@gmail.com10U Wuerzburg10 UBayern1 0 UDE0 160313015213Z 410307015213Z010U Free TSA10U Root CA10Uwww.freetsa.org1"0  *H  busilezas@gmail.com10U Wuerzburg10 UBayern1 0 UDE0"0  *H 0 02dͩKx*ͦ[٬~37!pgpQgPr. bCqEsZ1l x3\Evj]lWoO|涡>5 _p93M"h[)a E;.&;as̛I_4` aN.Eޢ"oR w͙S}(zO+q<ـ6fU8TV8kn$wJfRE%fKcSSrs|r* -l87Iȁ`m8$qG:6FtW-o%TwןܫA8mrRD?X)~sr=&f_pl {4K78uX[Q&!;L j7~1ܦqm~݀,rb\R5}Қ?a[;s}j3b)l#N0J0 U00U0UU 4fQCL糧lz椗0U#0U 4fQCL糧lz椗010U Free TSA10U Root CA10Uwww.freetsa.org1"0  *H  busilezas@gmail.com10U Wuerzburg10 UBayern1 0 UDE  03U,0*0(&$"http://www.freetsa.org/root_ca.crl0U 00 +$003+'http://www.freetsa.org/freetsa_cps.html02+&http://www.freetsa.org/freetsa_cps.pdf0G+0;9FreeTSA trusted timestamping Software as a Service (SaaS)07++0)0'+0http://www.freetsa.org:25600  *H  h~bL;X 5gr/=c БLj3‚>oޕ3&"UrU"aJ;x% JYd d&bM )HNDB.29n`:ԥU?vJ Ù/_XqS7} tHik-wFJ O^-]{¢lb$jOݻo Y|w@K;x'gRÙ,%5ZN4!QzP./@5OaUJB /-}۝wSxJu[4ܽ0VH L ?fк xdkl]%wGslő`ͧxjJòO]fXla>n4oAz!:j #c vC8%I}zT<,ڙ1p0l0010U Free TSA10U Root CA10Uwww.freetsa.org1"0  *H  busilezas@gmail.com10U Wuerzburg10 UBayern1 0 UDE  0  `He0 *H  1  *H  0 *H  1 240409052938Z0 *H   100O *H  1B@0 / oLWTpM$@ S_).x^0ѦK[hx>E4nQ6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O|hٹvOarG-AwxbB"@:&v{oWQeLF ҭj~%%E xt 74&\-a9eg8,>n] RfOm׿272RfCl_^2u.P.bhxqݎ)Uz 1,lQC|w+6(#Ct>ab|Cs]`4)2Lo#T{ZyF8yɆTl)v= Uo;&(v;7|=A#}OS@ r!u" o+Ș=esVQYq҅|% Nj*/ࠪB wkTX +YF`7dZ TogIp3:ChYIH.xRw(! %'o<Ӵ8+6$H)U-00  0  *H  010U Free TSA10U Root CA10Uwww.freetsa.org1"0  *H  busilezas@gmail.com10U Wuerzburg10 UBayern1 0 UDE0 160313015213Z 410307015213Z010U Free TSA10U Root CA10Uwww.freetsa.org1"0  *H  busilezas@gmail.com10U Wuerzburg10 UBayern1 0 UDE0"0  *H 0 02dͩKx*ͦ[٬~37!pgpQgPr. bCqEsZ1l x3\Evj]lWoO|涡>5 _p93M"h[)a E;.&;as̛I_4` aN.Eޢ"oR w͙S}(zO+q<ـ6fU8TV8kn$wJfRE%fKcSSrs|r* -l87Iȁ`m8$qG:6FtW-o%TwןܫA8mrRD?X)~sr=&f_pl {4K78uX[Q&!;L j7~1ܦqm~݀,rb\R5}Қ?a[;s}j3b)l#N0J0 U00U0UU 4fQCL糧lz椗0U#0U 4fQCL糧lz椗010U Free TSA10U Root CA10Uwww.freetsa.org1"0  *H  busilezas@gmail.com10U Wuerzburg10 UBayern1 0 UDE  03U,0*0(&$"http://www.freetsa.org/root_ca.crl0U 00 +$003+'http://www.freetsa.org/freetsa_cps.html02+&http://www.freetsa.org/freetsa_cps.pdf0G+0;9FreeTSA trusted timestamping Software as a Service (SaaS)07++0)0'+0http://www.freetsa.org:25600  *H  h~bL;X 5gr/=c БLj3‚>oޕ3&"UrU"aJ;x% JYd d&bM )HNDB.29n`:ԥU?vJ Ù/_XqS7} tHik-wFJ O^-]{¢lb$jOݻo Y|w@K;x'gRÙ,%5ZN4!QzP./@5OaUJB /-}۝wSxJu[4ܽ0VH L ?fк xdkl]%wGslő`ͧxjJòO]fXla>n4oAz!:j #c vC8%I}zT<,ڙ100010U Free TSA10U Root CA10Uwww.freetsa.org1"0  *H  busilezas@gmail.com10U Wuerzburg10 UBayern1 0 UDE  0  `He0 *H  1  *H  0 *H  1 240409052938Z0+ *H   1000m`ʂKŝh_0O *H  1B@0 / oLWTpM$@ S_).x^0ѦK[hx>E4nQ10  `He0 *H  0*0Q0  `He@&JI ?aw.G?Ԕ zO{72_oW(Az20240409052939Z 0 10U Free TSA1 0 U TSA1v0tU mThis certificate digitally signs documents and time stamp requests made using the freetsa.org online services10Uwww.freetsa.org1"0  *H  busilezas@gmail.com10U Wuerzburg1 0 UDE10 UBayern00  0  *H  010U Free TSA10U Root CA10Uwww.freetsa.org1"0  *H  busilezas@gmail.com10U Wuerzburg10 UBayern1 0 UDE0 160313015739Z 260311015739Z0 10U Free TSA1 0 U TSA1v0tU mThis certificate digitally signs documents and time stamp requests made using the freetsa.org online services10Uwww.freetsa.org1"0  *H  busilezas@gmail.com10U Wuerzburg1 0 UDE10 UBayern0"0  *H 0 NHo4b7Qb#i,0Q|8K\etڲb5c4_#"t`&(455?ya e#}`VY-jXN8 P( 9~F@$C|_Bh|hٹvOarG-AwxbB"@:&v{oWQeLF ҭj~%%E xt 74&\-a9eg8,>n] RfOm׿272RfCl_^2u.P.bhxqݎ)Uz 1,lQC|w+6(#Ct>ab|Cs]`4)2Lo#T{ZyF8yɆTl)v= Uo;&(v;7|=A#}OS@ r!u" o+Ș=esVQYq҅|% Nj*/ࠪB wkTX +YF`7dZ TogIp3:ChYIH.xRw(! %'o<Ӵ8+6$H)U-00  0  *H  010U Free TSA10U Root CA10Uwww.freetsa.org1"0  *H  busilezas@gmail.com10U Wuerzburg10 UBayern1 0 UDE0 160313015213Z 410307015213Z010U Free TSA10U Root CA10Uwww.freetsa.org1"0  *H  busilezas@gmail.com10U Wuerzburg10 UBayern1 0 UDE0"0  *H 0 02dͩKx*ͦ[٬~37!pgpQgPr. bCqEsZ1l x3\Evj]lWoO|涡>5 _p93M"h[)a E;.&;as̛I_4` aN.Eޢ"oR w͙S}(zO+q<ـ6fU8TV8kn$wJfRE%fKcSSrs|r* -l87Iȁ`m8$qG:6FtW-o%TwןܫA8mrRD?X)~sr=&f_pl {4K78uX[Q&!;L j7~1ܦqm~݀,rb\R5}Қ?a[;s}j3b)l#N0J0 U00U0UU 4fQCL糧lz椗0U#0U 4fQCL糧lz椗010U Free TSA10U Root CA10Uwww.freetsa.org1"0  *H  busilezas@gmail.com10U Wuerzburg10 UBayern1 0 UDE  03U,0*0(&$"http://www.freetsa.org/root_ca.crl0U 00 +$003+'http://www.freetsa.org/freetsa_cps.html02+&http://www.freetsa.org/freetsa_cps.pdf0G+0;9FreeTSA trusted timestamping Software as a Service (SaaS)07++0)0'+0http://www.freetsa.org:25600  *H  h~bL;X 5gr/=c БLj3‚>oޕ3&"UrU"aJ;x% JYd d&bM )HNDB.29n`:ԥU?vJ Ù/_XqS7} tHik-wFJ O^-]{¢lb$jOݻo Y|w@K;x'gRÙ,%5ZN4!QzP./@5OaUJB /-}۝wSxJu[4ܽ0VH L ?fк xdkl]%wGslő`ͧxjJòO]fXla>n4oAz!:j #c vC8%I}zT<,ڙ1r0n0010U Free TSA10U Root CA10Uwww.freetsa.org1"0  *H  busilezas@gmail.com10U Wuerzburg10 UBayern1 0 UDE  0  `He0 *H  1  *H  0 *H  1 240409052938Z0 *H   1000O *H  1B@0 / oLWTpM$@ S_).x^0ѦK[hx>E4nQ6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%OZ{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke]FiPpMA0  *H  y@I]g{k`.ٗ>Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%OZ{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,Z{Ke] Iw^n2usMula;zZ[\?*[#?3"`kc:nT)@mq ס·=آ_ۏ8rER)I ֟gx0 Ryc13([cHBOԽ;> K^b,-ְTEfa6ēm$7uL0ĕXijŞ n_ \K?考ߩ-f- KteT"<_%O6+fAղ#I"Ӿ_6ZP?NT q؟6<4fJ7]z>=gK붲oE]LYZfsaE6JcCn: F9ts ^B/"l$'Q]Aud鷇 07bY)rwiux:\jr2YxNf+u 7Au?:BٜQz+L%mOH[\r`7q\IL)mW;W.Ba|UbdUJo3X$g& {gXAUnŻLKBo6df!#4DIgZf3ޙ%qn]L`<rBM&wW a&ȲMdE?Tvlj"\ )K7tf4s}|^=PmW~7k)0%0U0U00UiWE93@ýe0U#0lgS0>+2000.+0"http://ocsp2.globalsign.com/rootr606U/0-0+)'%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  Wg+B_ Ļ(VLp :`39k@I%֤ AOἫ#ƅK$D¤вVeނȷaӵFHgzJbг4HEUJ^rqvp7W܍8X©~n&*Hdi/Қr$KtZ}r5WLSLIUOuoJ.jʈ0Ns^֙}K,kY7 ,Eֲnuڍ[/.=}0{|ŬQ0QV`|=" $S'H',y<㪄>J!b0H'|uBkkX4 P{H+Uٍ% QclraJCWŝ<1@l - ƍmqR!>9/z -j<SMGY|CA%,~ } `r@P CD YxƤj"0G0/ @B@"lq0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0"0  *H 0 sf{< E ,H[+2000.+0"http://ocsp2.globalsign.com/rootr306U/0-0+)'%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *H  I^ŃZa*MJ) z5 3mr"NA?m _,;6YytheYBU9&q8!N[`jC} `aݪ^N2l<»ӐvjiܨXO‹2JT8; u  |%'&Sa52݃:hr$7+$N򶹷G a3-<# 40_0G !XS0  *H  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0"0  *H 0 %vyx"(vŭrFCDz_$.K`FR Gpld,= +׶y;wIjb/^h߉'8>&Y sް&[`I(i;(坊aW7tt:r/.л=3+S:sA :O.2`W˹hh8&`uw I@H1a^wdz_b lTin郓qviB0@0U0U00UK.E$MPc0  *H  K@P TEI A(3kt- sgJD{xnlo)39EÎWlS-$lcShgV>5!hS̐]FzX(/7ADmS(~g׊L'Lssvz- ,RHlU&& ǜe Rp{v@ayJi7ٗBJǨR̲z-4`'YQ~iȒ?:ʊU7-%Jk9JHnn(KQ^H}t3Yuj uw}=~AI>s0.6WZ -i/,0l2 <Wc'H/+*v}Y6_,eP"b0((q/Jtspclient-go-0.2.0/testdata/malformed.tsq000066400000000000000000000223231464533346500204500ustar00rootroot000000000000000��00�� *�H�� ���0��10  `�He0�� *�H��  �����0�� +�2010  `�He �&�p�@�����ޠ�H�@@���(�k�Q�6_�#`)Ƕt�e!r��LY�20210918115434Z0�W�U0S1 0 UBE10U GlobalSign nv-sa1)0'U Globalsign TSA for Advanced - G4��d0�U0�=�FiP���p��MA�0  *�H��  0[1 0 UBE10U GlobalSign nv-sa110/U(GlobalSign Timestamping CA - SHA384 - G40 210527095523Z 320628095522Z0S1 0 UBE10U GlobalSign nv-sa1)0'U Globalsign TSA for Advanced - G40��0  *�H�� ~p�x )��x�Y�̦$�0LU E0C0A +�20402+&https://www.globalsign.com/repository/0 U00��+��0��09+0�-http://ocsp.globalsign.com/ca/gstsacasha384g40C+0�7http://secure.globalsign.com/cacert/gstsacasha384g4.crt0U#0���i���WE93��@��ýe�0AU:0806�4�2�0http://crl.globalsign.com/ca/gstsacasha384g4.crl0  *�H��  �b���w/Bok��EY<�uz��o#m-3BrOD�����L��]ͷ�$���h Z�EaӰ,�}��]�������2(6�� F-�~�@"yag����Of,.T*X��N�u�T& h��'�<�m0�&L�,�s܏���dJ{GLڢԷ��Qz�9�!����qp}��4��1� ��>6+f��Aղ#I"Ӿ_�6�Z��P�?NT �� ��q؟6<4������fJ�7��]z�>��=����gK붲�o��E]L�YZ�fsaE6J��cCn: F9�t�s ^B��/"l$'�Q���]����Au�d����鷇 �� 07bY�)rwiux���:\j�r��2�Yx�N��f��+���u�� 7�Au��?:����B�ٜQ��z�+�L%�m���OH���[���\r`��7q��\I�L)m�W;W���.Ba������|U��bd���UJo3���X���$g&��� ������{�g��X���A�U�n�Ż�LK��Bo�6�d������f�!��#��4D���Ig���Z��f�3ޙ%�qn�]L`<���r���BM�&�w�W �a�&��ȲM�d�E���?T��v�lj��"\� �)�K��7t��f�4s�}|^��=���P�m�W�~7k���)0�%0U��0U�0�0U��i���WE93��@��ýe�0U#0��l����������gS�0>+2000.+0�"http://ocsp2.globalsign.com/rootr606U/0-0+�)�'�%http://crl.globalsign.com/root-r6.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *�H��  ���Wg+B_�� �Ļ(V�L��p�� �:`39�k@I%��֤ ����A�OἫ���#�ƅ�K$�D�¤�в���Ve��ނȷ�aӵFH��gz�J�b�г�4HEU���J�^�rqvp����7W���܍�8X©�~�n&��*��Hdi/Қ�r��$KtZ}r�5�W�����L���SL����IUOu��oJ�.j�ʈ�0N�s�^�֙��}K�,�k��Y7� ,�Eֲ�n�u�ڍ�[/�.=}��0��{|�Ŭ�Q��0�QV`�|�="�� $S'���H�',y�<�㪄��>�J�!�b�0����H'|�uBk��k�X4 ��P��{���H�+Uٍ�% Q��c���lraJ��C��Wŝ�<����1���@l �-� ƍm��q�R!�>���9���/z �����-���j�<����SM�GY|��CA�%�,~�� ��}� ��`r@P� �CD����� Y�xƤj�"�0�G0�/� �@B@��"��lq�0  *�H��  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 190220000000Z 290318100000Z0L1 0U GlobalSign Root CA - R610U  GlobalSign10U GlobalSign0�"0  *�H�� �0� ���s�f���{<� �E ,��H��[<���A�3�o�*�ư�kŶ��Ʋ��Q!�J�Z�և�M:�df ���D�s�N�Oxc�PmBf/M�y(MR����~Ċ�dL!Ch�=<�ŲfՐ��1ž�m2���몣����cP�����y��*�p.{缓�mS�H|�8�f�wa~��<�����Jm�����Рaw�Xt��#:�]:ʢ۝ �]D-���W��~�Pc4�k��k6�9�$6���W��޲�ⅷs��5�E���6�oT��rVn.��QBD���8��NNZ G�6Iw0�q7��!u��a?w�ّ�� l�Mt���9���^�� ����n ��af j�:e�Y��5���(��p� ��u�:��ۀ�%���'YLv9[����؃���0���3H��md,zXO�KIŕdcy=����X��BEyn�\T�e������ o�.�gnɋ���p�y����'�72�c<(L���&0�"0U�0U�0�0U�l����������gS�0U#0���K�.E$�MP�c������0>+2000.+0�"http://ocsp2.globalsign.com/rootr306U/0-0+�)�'�%http://crl.globalsign.com/root-r3.crl0GU @0>0<U 0402+&https://www.globalsign.com/repository/0  *�H��  �I�^Ń�Z�a*M�J)���� ��z�5� 3mr�"NA?m ����_���,�;���6Yy��t����h��eYB��U9���&�q8��!�N�[`jC�} �`aݪ�^N2�l�<�»�Ӑvji�ܨ��XO�‹2J�T�8�; u � |%'�&�S����a52��݃�:��h�r�$7+���$�N򶹷G� �a��3�-����<#� 4�0�_0�G� !XS�0  *�H��  0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0 090318100000Z 290318100000Z0L1 0U GlobalSign Root CA - R310U  GlobalSign10U GlobalSign0�"0  *�H�� �0� ��%v�yx"������(��vŭ�r�FCDz��_$�.K�`�F�R� �Gpl�d���,��= +��׶�y�;�w��I�jb/^��h߉'�8��>��&Y sް��&���[��`�I�(�i;���(�坊aW7�t�t�:�r/.��л��=�3�+�S�:s��A :�����O�.2`�W˹�hh�8&`u��w��� I��@H�1a^���w�d�z�_��b� l�Ti��n郓qv�i���B0@0U�0U�0�0U��K�.E$�MP�c������0  *�H��  �K@��P��� ���TEI�� A����(3�k�t��-�� ������sgJ��D{x�nlo)�39EÎ�Wl����S�-�$l��c��ShgV>���5!��h����S�̐���]F���zX(/��7A��Dm�S(�~�g׊����L'�Lssv���z�-� ,�<�U�~6��WI��.-|`��AQ#���2k����,3:;%҆@�;,�x�a/���Uo߄� M�(�r��bPe뒗�1ٳ��GX?_1�M0�I0o0[1 0 UBE10U GlobalSign nv-sa110/U(GlobalSign Timestamping CA - SHA384 - G4FiP���p��MA�0  `�He��/0 *�H��  1  *�H��  0- *�H��  41 00  `�He�  *�H��  0/ *�H��  1" �CwT�����~����#Xw�����}W��{G0�� *�H��  /1��0��0��0�� ��� �mN'Tr�h�x�edgۚ�e������0s0_�]0[1 0 UBE10U GlobalSign nv-sa110/U(GlobalSign Timestamping CA - SHA384 - G4FiP���p��MA�0  *�H��  ���~s�U�N������2a1By�pѦvm��} ��!�W��B���a������z��r���J/|s��b�P,'��ýٽ�#�}3,0m>�R�Hl�U&& ��ǜe���� Rp{v@ayJ�i�7ٗB��JǨR̲�z-�4�`'�����Y��Q~i��Ȓ�?:�ʊ���U�7-%�J���k9JH�nn����(K�����Q�^H�}t�3���Y�u����j u�w}�=��~AI>�s0�<�P�n���$���WE��z'���,r�hxEY:�>.6���WZ �����-i�/�����,�0l2� ���<����W�����c�'���H�����/��+*v}��Y6�_,eP��"�b��0��((q��/Jtspclient-go-0.2.0/testdata/rejection.tsq000066400000000000000000000000611464533346500204570ustar00rootroot000000000000000/0-0$ "request contains unknown algorithmtspclient-go-0.2.0/testdata/validResponseWithSigningCertificateV1.tsr000066400000000000000000000125661464533346500260560ustar00rootroot000000000000000r00i *H Z0V10  `He0 *H  0*0Q0  `He@&JI ?aw.G?Ԕ zO{72_oW(Az20240409052939Z 0 10U Free TSA1 0 U TSA1v0tU mThis certificate digitally signs documents and time stamp requests made using the freetsa.org online services10Uwww.freetsa.org1"0  *H  busilezas@gmail.com10U Wuerzburg1 0 UDE10 UBayern00  0  *H  010U Free TSA10U Root CA10Uwww.freetsa.org1"0  *H  busilezas@gmail.com10U Wuerzburg10 UBayern1 0 UDE0 160313015739Z 260311015739Z0 10U Free TSA1 0 U TSA1v0tU mThis certificate digitally signs documents and time stamp requests made using the freetsa.org online services10Uwww.freetsa.org1"0  *H  busilezas@gmail.com10U Wuerzburg1 0 UDE10 UBayern0"0  *H 0 NHo4b7Qb#i,0Q|8K\etڲb5c4_#"t`&(455?ya e#}`VY-jXN8 P( 9~F@$C|_Bh|hٹvOarG-AwxbB"@:&v{oWQeLF ҭj~%%E xt 74&\-a9eg8,>n] RfOm׿272RfCl_^2u.P.bhxqݎ)Uz 1,lQC|w+6(#Ct>ab|Cs]`4)2Lo#T{ZyF8yɆTl)v= Uo;&(v;7|=A#}OS@ r!u" o+Ș=esVQYq҅|% Nj*/ࠪB wkTX +YF`7dZ TogIp3:ChYIH.xRw(! %'o<Ӵ8+6$H)U-00  0  *H  010U Free TSA10U Root CA10Uwww.freetsa.org1"0  *H  busilezas@gmail.com10U Wuerzburg10 UBayern1 0 UDE0 160313015213Z 410307015213Z010U Free TSA10U Root CA10Uwww.freetsa.org1"0  *H  busilezas@gmail.com10U Wuerzburg10 UBayern1 0 UDE0"0  *H 0 02dͩKx*ͦ[٬~37!pgpQgPr. bCqEsZ1l x3\Evj]lWoO|涡>5 _p93M"h[)a E;.&;as̛I_4` aN.Eޢ"oR w͙S}(zO+q<ـ6fU8TV8kn$wJfRE%fKcSSrs|r* -l87Iȁ`m8$qG:6FtW-o%TwןܫA8mrRD?X)~sr=&f_pl {4K78uX[Q&!;L j7~1ܦqm~݀,rb\R5}Қ?a[;s}j3b)l#N0J0 U00U0UU 4fQCL糧lz椗0U#0U 4fQCL糧lz椗010U Free TSA10U Root CA10Uwww.freetsa.org1"0  *H  busilezas@gmail.com10U Wuerzburg10 UBayern1 0 UDE  03U,0*0(&$"http://www.freetsa.org/root_ca.crl0U 00 +$003+'http://www.freetsa.org/freetsa_cps.html02+&http://www.freetsa.org/freetsa_cps.pdf0G+0;9FreeTSA trusted timestamping Software as a Service (SaaS)07++0)0'+0http://www.freetsa.org:25600  *H  h~bL;X 5gr/=c БLj3‚>oޕ3&"UrU"aJ;x% JYd d&bM )HNDB.29n`:ԥU?vJ Ù/_XqS7} tHik-wFJ O^-]{¢lb$jOݻo Y|w@K;x'gRÙ,%5ZN4!QzP./@5OaUJB /-}۝wSxJu[4ܽ0VH L ?fк xdkl]%wGslő`ͧxjJòO]fXla>n4oAz!:j #c vC8%I}zT<,ڙ100010U Free TSA10U Root CA10Uwww.freetsa.org1"0  *H  busilezas@gmail.com10U Wuerzburg10 UBayern1 0 UDE  0  `He0 *H  1  *H  0 *H  1 240409052938Z0+ *H   1000m`ʂKŝh_0O *H  1B@0 / oLWTpM$@ S_).x^0ѦK[hx>E4nQ