Compare commits

..

205 Commits

Author SHA1 Message Date
github-actions[bot] a85723d9f8
Merge pull request #1897 from smallstep/dependabot/go_modules/google.golang.org/api-0.185.0
Bump google.golang.org/api from 0.184.0 to 0.185.0
20 hours ago
Max c3beeaf44c
Merge branch 'master' into dependabot/go_modules/google.golang.org/api-0.185.0 20 hours ago
github-actions[bot] 367d90d0cc
Merge pull request #1898 from smallstep/dependabot/go_modules/github.com/go-chi/chi/v5-5.0.14
Bump github.com/go-chi/chi/v5 from 5.0.12 to 5.0.14
20 hours ago
dependabot[bot] 27bea359cb
Bump github.com/go-chi/chi/v5 from 5.0.12 to 5.0.14
Bumps [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) from 5.0.12 to 5.0.14.
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.0.12...v5.0.14)

---
updated-dependencies:
- dependency-name: github.com/go-chi/chi/v5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
20 hours ago
github-actions[bot] 5eb1849a8f
Merge pull request #1896 from smallstep/dependabot/github_actions/softprops/action-gh-release-2.0.6
Bump softprops/action-gh-release from 2.0.5 to 2.0.6
20 hours ago
Max ee69818c27
Merge branch 'master' into dependabot/go_modules/google.golang.org/api-0.185.0 20 hours ago
github-actions[bot] 7ff52f6e6c
Merge pull request #1899 from smallstep/dependabot/go_modules/github.com/fxamacker/cbor/v2-2.7.0
Bump github.com/fxamacker/cbor/v2 from 2.6.0 to 2.7.0
20 hours ago
Carl Tashian 12c2e75db6
Update goreleaser YML version (#1901)
* Update goreleaser YML version

* Update http-retryable go pkg

---------

Co-authored-by: max furman <mx.furman@gmail.com>
20 hours ago
dependabot[bot] d12d866f84
Bump github.com/fxamacker/cbor/v2 from 2.6.0 to 2.7.0
Bumps [github.com/fxamacker/cbor/v2](https://github.com/fxamacker/cbor) from 2.6.0 to 2.7.0.
- [Release notes](https://github.com/fxamacker/cbor/releases)
- [Commits](https://github.com/fxamacker/cbor/compare/v2.6.0...v2.7.0)

---
updated-dependencies:
- dependency-name: github.com/fxamacker/cbor/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2 days ago
dependabot[bot] 43bf6b5a46
Bump google.golang.org/api from 0.184.0 to 0.185.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.184.0 to 0.185.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.184.0...v0.185.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2 days ago
dependabot[bot] fffffc60a8
Bump softprops/action-gh-release from 2.0.5 to 2.0.6
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.0.5 to 2.0.6.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](69320dbe05...a74c6b72af)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2 days ago
github-actions[bot] 9bf6a8369f
Merge pull request #1891 from smallstep/dependabot/go_modules/google.golang.org/protobuf-1.34.2
Bump google.golang.org/protobuf from 1.34.1 to 1.34.2
1 week ago
dependabot[bot] 43bdd61803
Bump google.golang.org/protobuf from 1.34.1 to 1.34.2
Bumps google.golang.org/protobuf from 1.34.1 to 1.34.2.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
1 week ago
github-actions[bot] 1563c264bd
Merge pull request #1890 from smallstep/dependabot/go_modules/google.golang.org/api-0.184.0
Bump google.golang.org/api from 0.183.0 to 0.184.0
1 week ago
github-actions[bot] 14230d8696
Merge pull request #1889 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.47.1
Bump go.step.sm/crypto from 0.47.0 to 0.47.1
1 week ago
dependabot[bot] 958c344f18
Bump google.golang.org/api from 0.183.0 to 0.184.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.183.0 to 0.184.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.183.0...v0.184.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
1 week ago
dependabot[bot] 8a7892463d
Bump go.step.sm/crypto from 0.47.0 to 0.47.1
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.47.0 to 0.47.1.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.47.0...v0.47.1)

---
updated-dependencies:
- dependency-name: go.step.sm/crypto
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
1 week ago
Max 890e81cb66
[actions] dependabot to common workflow | move to new release action (#1887) 2 weeks ago
github-actions[bot] 2d4bc95460
Merge pull request #1883 from smallstep/dependabot/go_modules/github.com/Azure/azure-sdk-for-go/sdk/azidentity-1.6.0
Bump github.com/Azure/azure-sdk-for-go/sdk/azidentity from 1.5.2 to 1.6.0
2 weeks ago
Max 93ca1e21c1
Merge branch 'master' into dependabot/go_modules/github.com/Azure/azure-sdk-for-go/sdk/azidentity-1.6.0 2 weeks ago
Max d6973c979e
Set date for 0.26.2 release in changelog (#1886) 2 weeks ago
Max d4b2916650
Changelog update for 0.26.2 (#1885) 2 weeks ago
Mariano Cano f9e5971978
Merge pull request #1884 from smallstep/mariano/linkedca
Upgrade linkedca
2 weeks ago
Mariano Cano c8e65abfb1
Fix linter warnings 2 weeks ago
Mariano Cano b4616ee8f1
Upgrade linkedca
This commit upgrades go.step.sm/linkedca with dependencies required by
smallstep/certificates#1802. It also fixes some linter warnings.
2 weeks ago
Mariano Cano 634ece4489
Merge pull request #1802 from jdoupe/AuthParams
add AuthParams to OIDC struct
2 weeks ago
Mariano Cano a017c0e3fb
Merge branch 'master' into AuthParams 2 weeks ago
dependabot[bot] 7bc9d15849
Bump github.com/Azure/azure-sdk-for-go/sdk/azidentity
Bumps [github.com/Azure/azure-sdk-for-go/sdk/azidentity](https://github.com/Azure/azure-sdk-for-go) from 1.5.2 to 1.6.0.
- [Release notes](https://github.com/Azure/azure-sdk-for-go/releases)
- [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/release.md)
- [Commits](https://github.com/Azure/azure-sdk-for-go/compare/sdk/internal/v1.5.2...sdk/azcore/v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/Azure/azure-sdk-for-go/sdk/azidentity
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2 weeks ago
github-actions[bot] 8b36f7bc11
Merge pull request #1878 from smallstep/dependabot/go_modules/google.golang.org/api-0.183.0
Bump google.golang.org/api from 0.182.0 to 0.183.0
2 weeks ago
Max 30b2cd1e5c
Merge branch 'master' into dependabot/go_modules/google.golang.org/api-0.183.0 2 weeks ago
github-actions[bot] a0b9360725
Merge pull request #1879 from smallstep/dependabot/go_modules/golang.org/x/crypto-0.24.0
Bump golang.org/x/crypto from 0.23.0 to 0.24.0
2 weeks ago
Max d5171be998
Merge branch 'master' into dependabot/go_modules/golang.org/x/crypto-0.24.0 2 weeks ago
github-actions[bot] 6e12cfa46c
Merge pull request #1880 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.47.0
Bump go.step.sm/crypto from 0.46.0 to 0.47.0
2 weeks ago
Max d1de1ad8df
Merge branch 'master' into dependabot/go_modules/google.golang.org/api-0.183.0 2 weeks ago
Max 0ce8fb6db5
Merge branch 'master' into dependabot/go_modules/golang.org/x/crypto-0.24.0 2 weeks ago
Max 3b9631b883
Merge branch 'master' into dependabot/go_modules/go.step.sm/crypto-0.47.0 2 weeks ago
Max 474f5d28f4
Update hardcoded AWS certs (#1881) 2 weeks ago
dependabot[bot] 7ab8391070
Bump go.step.sm/crypto from 0.46.0 to 0.47.0
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.46.0 to 0.47.0.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.46.0...v0.47.0)

---
updated-dependencies:
- dependency-name: go.step.sm/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2 weeks ago
dependabot[bot] 23f120e9db
Bump golang.org/x/crypto from 0.23.0 to 0.24.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.23.0 to 0.24.0.
- [Commits](https://github.com/golang/crypto/compare/v0.23.0...v0.24.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2 weeks ago
dependabot[bot] e3444c0736
Bump google.golang.org/api from 0.182.0 to 0.183.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.182.0 to 0.183.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.182.0...v0.183.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2 weeks ago
github-actions[bot] 669d992d59
Merge pull request #1870 from smallstep/dependabot/go_modules/google.golang.org/api-0.182.0
Bump google.golang.org/api from 0.181.0 to 0.182.0
3 weeks ago
github-actions[bot] 68c5238fe7
Merge pull request #1869 from smallstep/dependabot/go_modules/github.com/hashicorp/vault/api/auth/approle-0.7.0
Bump github.com/hashicorp/vault/api/auth/approle from 0.6.0 to 0.7.0
3 weeks ago
github-actions[bot] 4884379710
Merge pull request #1868 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.46.0
Bump go.step.sm/crypto from 0.45.1 to 0.46.0
3 weeks ago
dependabot[bot] 437154d70e
Bump google.golang.org/api from 0.181.0 to 0.182.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.181.0 to 0.182.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.181.0...v0.182.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
3 weeks ago
dependabot[bot] 2a9bbff8ce
Bump github.com/hashicorp/vault/api/auth/approle from 0.6.0 to 0.7.0
Bumps [github.com/hashicorp/vault/api/auth/approle](https://github.com/hashicorp/vault) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/hashicorp/vault/releases)
- [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG-v0.md)
- [Commits](https://github.com/hashicorp/vault/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/vault/api/auth/approle
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
3 weeks ago
dependabot[bot] 4d7ca9d672
Bump go.step.sm/crypto from 0.45.1 to 0.46.0
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.45.1 to 0.46.0.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.45.1...v0.46.0)

---
updated-dependencies:
- dependency-name: go.step.sm/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
3 weeks ago
github-actions[bot] 587d0d5a27
Merge pull request #1858 from smallstep/dependabot/go_modules/cloud.google.com/go/security-1.17.0
Bump cloud.google.com/go/security from 1.16.1 to 1.17.0
4 weeks ago
dependabot[bot] 34fde59927
Bump cloud.google.com/go/security from 1.16.1 to 1.17.0
Bumps [cloud.google.com/go/security](https://github.com/googleapis/google-cloud-go) from 1.16.1 to 1.17.0.
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/documentai/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/video/v1.16.1...kms/v1.17.0)

---
updated-dependencies:
- dependency-name: cloud.google.com/go/security
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
4 weeks ago
github-actions[bot] fe8c3d3ee8
Merge pull request #1859 from smallstep/dependabot/go_modules/github.com/hashicorp/vault/api/auth/kubernetes-0.7.0
Bump github.com/hashicorp/vault/api/auth/kubernetes from 0.6.0 to 0.7.0
4 weeks ago
dependabot[bot] 013c2f2f04
Bump github.com/hashicorp/vault/api/auth/kubernetes from 0.6.0 to 0.7.0
Bumps [github.com/hashicorp/vault/api/auth/kubernetes](https://github.com/hashicorp/vault) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/hashicorp/vault/releases)
- [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG-v0.md)
- [Commits](https://github.com/hashicorp/vault/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/vault/api/auth/kubernetes
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
4 weeks ago
github-actions[bot] 4208b0a63f
Merge pull request #1860 from smallstep/dependabot/go_modules/github.com/hashicorp/vault/api-1.14.0
Bump github.com/hashicorp/vault/api from 1.13.0 to 1.14.0
4 weeks ago
github-actions[bot] 6de7aa97dc
Merge pull request #1861 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.45.1
Bump go.step.sm/crypto from 0.45.0 to 0.45.1
1 month ago
dependabot[bot] f3e4f0ae64
Bump go.step.sm/crypto from 0.45.0 to 0.45.1
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.45.0 to 0.45.1.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.45.0...v0.45.1)

---
updated-dependencies:
- dependency-name: go.step.sm/crypto
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
1 month ago
dependabot[bot] 2b8f3e7080
Bump github.com/hashicorp/vault/api from 1.13.0 to 1.14.0
Bumps [github.com/hashicorp/vault/api](https://github.com/hashicorp/vault) from 1.13.0 to 1.14.0.
- [Release notes](https://github.com/hashicorp/vault/releases)
- [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/vault/compare/v1.13.0...v1.14.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/vault/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
1 month ago
Mariano Cano 47b5048d82
Merge pull request #1850 from smallstep/mariano/signer
Add GetX509Signer method
1 month ago
github-actions[bot] 7d6eea0ff0
Merge pull request #1853 from smallstep/dependabot/go_modules/google.golang.org/grpc-1.64.0
Bump google.golang.org/grpc from 1.63.2 to 1.64.0
1 month ago
max furman 99ce13a4ea
Fix linter warnings 1 month ago
dependabot[bot] 5cdfc2c972
Bump google.golang.org/grpc from 1.63.2 to 1.64.0
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.63.2 to 1.64.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.63.2...v1.64.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
1 month ago
github-actions[bot] 980687bc0e
Merge pull request #1854 from smallstep/dependabot/go_modules/google.golang.org/api-0.181.0
Bump google.golang.org/api from 0.180.0 to 0.181.0
1 month ago
dependabot[bot] 8121a05c55
Bump google.golang.org/api from 0.180.0 to 0.181.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.180.0 to 0.181.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.180.0...v0.181.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
1 month ago
Mariano Cano ad0ac55b41
Merge pull request #1844 from smallstep/mariano/account-provisioner
Verify provisioner with id if available
1 month ago
Mariano Cano 192e90eea7
Merge branch 'master' into mariano/account-provisioner 1 month ago
Mariano Cano 812ffd3c40
Reverse assert statements 1 month ago
Mariano Cano d0548f9ec9
Use %q instead of '%s'
Co-authored-by: Herman Slatman <hslatman@users.noreply.github.com>
1 month ago
Mariano Cano 14959dbb2e
Merge pull request #1849 from smallstep/mariano/log-errors
Log errors using slog.Logger
1 month ago
Mariano Cano c0b7c33a58
Use a function as the error logger
This commit addresses comment in the code review. Now, instead of
injecting an slog.Logger we can inject any method that can use a more
flexible implementation.
1 month ago
Mariano Cano 9e8087fbb1
Add GetX509Signer method
This commit adds a method to the Authority type that returns the signer
used to sign X509 certificates.
1 month ago
Mariano Cano 8673818980
Split provisioner check in two cases 1 month ago
Mariano Cano f3f484cee2
Log errors using slog.Logger
This commit allows logging errors in a slog.Logger injected in the
context. This type of logger is not currently used directly in step-ca,
but this will change in the future.
1 month ago
Mariano Cano fdb0cf03f6
Merge pull request #1848 from smallstep/mariano/intermediates
Add methods to get the intermediate certificates
1 month ago
Mariano Cano d4862a2520
Add methods to get the intermediate certificates
This commit adds new methods to get the intermediate certificates.
1 month ago
Mariano Cano e08b27708b
Merge pull request #1847 from smallstep/mariano/x5c-insecure
Upgrade go.step.sm/crypto to v0.45.0
1 month ago
Mariano Cano b6afed3be7
Upgrade go.step.sm/crypto to v0.45.0
This commit upgrades go.step.sm/crypto to a version that fixes
`jose.ParseX5cInsecure`. It should require a certificate with ClientAuth
instead of the default ServerAuth.
1 month ago
github-actions[bot] 9355923799
Merge pull request #1839 from smallstep/dependabot/go_modules/google.golang.org/api-0.180.0
Bump google.golang.org/api from 0.177.0 to 0.180.0
1 month ago
dependabot[bot] a8e9a18bcc
Bump google.golang.org/api from 0.177.0 to 0.180.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.177.0 to 0.180.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.177.0...v0.180.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
1 month ago
github-actions[bot] 803d3d3981
Merge pull request #1840 from smallstep/dependabot/go_modules/google.golang.org/protobuf-1.34.1
Bump google.golang.org/protobuf from 1.34.0 to 1.34.1
1 month ago
github-actions[bot] e0e7ae6c89
Merge pull request #1841 from smallstep/dependabot/go_modules/golang.org/x/net-0.25.0
Bump golang.org/x/net from 0.24.0 to 0.25.0
1 month ago
github-actions[bot] 72a8bb3d39
Merge pull request #1842 from smallstep/dependabot/go_modules/github.com/prometheus/client_golang-1.19.1
Bump github.com/prometheus/client_golang from 1.19.0 to 1.19.1
1 month ago
Mariano Cano 5fa5a63de8
Verify provisioner with id if available
This commit allows verifying the account provisioner with the request
provisioner using the ID instead of the name. When the provisioner are
stored in the DB, the provisioner ID is not mutable but the name is.
1 month ago
dependabot[bot] 9cbdc73809
Bump github.com/prometheus/client_golang from 1.19.0 to 1.19.1
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.19.0 to 1.19.1.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.19.0...v1.19.1)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
1 month ago
dependabot[bot] 42341c7a39
Bump golang.org/x/net from 0.24.0 to 0.25.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.24.0 to 0.25.0.
- [Commits](https://github.com/golang/net/compare/v0.24.0...v0.25.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
1 month ago
dependabot[bot] 0dff5c4b84
Bump google.golang.org/protobuf from 1.34.0 to 1.34.1
Bumps google.golang.org/protobuf from 1.34.0 to 1.34.1.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
1 month ago
github-actions[bot] e3ba702811
Merge pull request #1827 from smallstep/dependabot/go_modules/golang.org/x/crypto-0.23.0
Bump golang.org/x/crypto from 0.22.0 to 0.23.0
2 months ago
github-actions[bot] fe29ccaee3
Merge pull request #1828 from smallstep/dependabot/go_modules/github.com/newrelic/go-agent/v3-3.33.0
Bump github.com/newrelic/go-agent/v3 from 3.32.0 to 3.33.0
2 months ago
github-actions[bot] 8cf5e3c2c6
Merge pull request #1829 from smallstep/dependabot/go_modules/cloud.google.com/go/longrunning-0.5.7
Bump cloud.google.com/go/longrunning from 0.5.6 to 0.5.7
2 months ago
dependabot[bot] 928d446e93
Bump golang.org/x/crypto from 0.22.0 to 0.23.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.22.0 to 0.23.0.
- [Commits](https://github.com/golang/crypto/compare/v0.22.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
dependabot[bot] e11833ef7a
Bump cloud.google.com/go/longrunning from 0.5.6 to 0.5.7
Bumps [cloud.google.com/go/longrunning](https://github.com/googleapis/google-cloud-go) from 0.5.6 to 0.5.7.
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/longrunning/v0.5.6...longrunning/v0.5.7)

---
updated-dependencies:
- dependency-name: cloud.google.com/go/longrunning
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
github-actions[bot] 591b9f743d
Merge pull request #1826 from smallstep/dependabot/go_modules/cloud.google.com/go/security-1.16.1
Bump cloud.google.com/go/security from 1.16.0 to 1.16.1
2 months ago
Mariano Cano a2f2332848
Merge pull request #1831 from smallstep/mariano/err-not-found
Use always acme.IsErrNotFound
2 months ago
Mariano Cano b1e31b1726
Use always acme.IsErrNotFound
This commit replaces the comparisons with acme.ErrNotFound and always
uses acme.IsErrNotFound.
2 months ago
Mariano Cano cca6f6d01b
Merge pull request #1830 from smallstep/mariano/provisioner-id
Add provisioner id to acme accounts
2 months ago
Mariano Cano d037ed6ff2
Add provisioner id to acme accounts
This commit adds a new field that allows to have a reference to
a provisioner id in the acme account.
2 months ago
dependabot[bot] 9b2566556e
Bump github.com/newrelic/go-agent/v3 from 3.32.0 to 3.33.0
Bumps [github.com/newrelic/go-agent/v3](https://github.com/newrelic/go-agent) from 3.32.0 to 3.33.0.
- [Release notes](https://github.com/newrelic/go-agent/releases)
- [Changelog](https://github.com/newrelic/go-agent/blob/master/CHANGELOG.md)
- [Commits](https://github.com/newrelic/go-agent/compare/v3.32.0...v3.33.0)

---
updated-dependencies:
- dependency-name: github.com/newrelic/go-agent/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
dependabot[bot] 8933a2e514
Bump cloud.google.com/go/security from 1.16.0 to 1.16.1
Bumps [cloud.google.com/go/security](https://github.com/googleapis/google-cloud-go) from 1.16.0 to 1.16.1.
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/documentai/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/video/v1.16.0...video/v1.16.1)

---
updated-dependencies:
- dependency-name: cloud.google.com/go/security
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
github-actions[bot] 2c71543d33
Merge pull request #1817 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.44.8
Bump go.step.sm/crypto from 0.44.6 to 0.44.8
2 months ago
Herman Slatman 949e2fdb1c
Fix test error expectation in `TestAuthorityNew` 2 months ago
dependabot[bot] 281efbb949
Bump go.step.sm/crypto from 0.44.6 to 0.44.8
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.44.6 to 0.44.8.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.44.6...v0.44.8)

---
updated-dependencies:
- dependency-name: go.step.sm/crypto
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
github-actions[bot] 14b1211e7a
Merge pull request #1815 from smallstep/dependabot/go_modules/github.com/urfave/cli-1.22.15
Bump github.com/urfave/cli from 1.22.14 to 1.22.15
2 months ago
github-actions[bot] 0b894a0d4a
Merge pull request #1816 from smallstep/dependabot/go_modules/google.golang.org/api-0.176.1
Bump google.golang.org/api from 0.176.0 to 0.176.1
2 months ago
Mariano Cano 20e315bac0
Merge pull request #1819 from smallstep/mariano/not-found
Make IsErrNotFound more flexible
2 months ago
Mariano Cano 296ac4e207
Make ISErrNotFound more flexible
This commit allows to use the standard error sql.ErrNoRows for not found
errors.
2 months ago
github-actions[bot] 28a87bbaa6
Merge pull request #1818 from smallstep/dependabot/github_actions/dependabot/fetch-metadata-2.1.0
Bump dependabot/fetch-metadata from 2.0.0 to 2.1.0
2 months ago
dependabot[bot] bf03d56ae4
Bump dependabot/fetch-metadata from 2.0.0 to 2.1.0
Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/dependabot/fetch-metadata/releases)
- [Commits](https://github.com/dependabot/fetch-metadata/compare/v2.0.0...v2.1.0)

---
updated-dependencies:
- dependency-name: dependabot/fetch-metadata
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
dependabot[bot] 6715c653ca
Bump google.golang.org/api from 0.176.0 to 0.176.1
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.176.0 to 0.176.1.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.176.0...v0.176.1)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
dependabot[bot] 798e190621
Bump github.com/urfave/cli from 1.22.14 to 1.22.15
Bumps [github.com/urfave/cli](https://github.com/urfave/cli) from 1.22.14 to 1.22.15.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v1.22.14...v1.22.15)

---
updated-dependencies:
- dependency-name: github.com/urfave/cli
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
findnature 5072d7a58f
chore: fix function names in comment (#1813)
Signed-off-by: findnature <cricis@aliyun.com>
2 months ago
Max 9cbab5a5b3
Add changelog for 0.26.1 (#1812) 2 months ago
Jeremy Doupe 03c3cf5790 fixed Scopes and AuthParams assignment 2 months ago
Herman Slatman d6bf551f87
Merge pull request #1803 from smallstep/herman/fix-scep-vault-ra
Fix CA startup with Vault RA configuration
2 months ago
github-actions[bot] f4d506f659
Merge pull request #1811 from smallstep/dependabot/go_modules/github.com/hashicorp/vault/api-1.13.0
Bump github.com/hashicorp/vault/api from 1.12.2 to 1.13.0
2 months ago
Herman Slatman 1e5e267b2b
Remove leftover debug print 2 months ago
max furman 760014c64b
go mod tidy 2 months ago
Herman Slatman 2561a7271e
Dedupe CA and SCEP client creation logic 2 months ago
dependabot[bot] 39653050dc
Bump github.com/hashicorp/vault/api from 1.12.2 to 1.13.0
Bumps [github.com/hashicorp/vault/api](https://github.com/hashicorp/vault) from 1.12.2 to 1.13.0.
- [Release notes](https://github.com/hashicorp/vault/releases)
- [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/vault/compare/v1.12.2...v1.13.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/vault/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
github-actions[bot] 65cfee56df
Merge pull request #1810 from smallstep/dependabot/go_modules/google.golang.org/api-0.176.0
Bump google.golang.org/api from 0.172.0 to 0.176.0
2 months ago
dependabot[bot] 8d4effcce8
Bump google.golang.org/api from 0.172.0 to 0.176.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.172.0 to 0.176.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.172.0...v0.176.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
github-actions[bot] 4a375592d3
Merge pull request #1809 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.44.6
Bump go.step.sm/crypto from 0.44.4 to 0.44.6
2 months ago
github-actions[bot] d7ed03124f
Merge pull request #1808 from smallstep/dependabot/go_modules/cloud.google.com/go/security-1.16.0
Bump cloud.google.com/go/security from 1.15.6 to 1.16.0
2 months ago
Herman Slatman 87202001a8
Rewrite SCEP integration tests to only use the HTTPS endpoint 2 months ago
dependabot[bot] 57a6b85388
Bump go.step.sm/crypto from 0.44.4 to 0.44.6
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.44.4 to 0.44.6.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.44.4...v0.44.6)

---
updated-dependencies:
- dependency-name: go.step.sm/crypto
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
dependabot[bot] 0ba61c58e8
Bump cloud.google.com/go/security from 1.15.6 to 1.16.0
Bumps [cloud.google.com/go/security](https://github.com/googleapis/google-cloud-go) from 1.15.6 to 1.16.0.
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/documentai/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/kms/v1.15.6...asset/v1.16.0)

---
updated-dependencies:
- dependency-name: cloud.google.com/go/security
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
Herman Slatman b0fabe1346
Add some SCEP integration tests 2 months ago
Herman Slatman 113a6dd8ab
Remove reporting the CA mode from startup logs 2 months ago
Herman Slatman 6bc0a86207
Fix CA startup with Vault RA configuration 2 months ago
Jeremy Doupe aa543a335a
add Scopes to OIDC struct 2 months ago
Jeremy Doupe 4879376138
add AuthParams and Scopes to linkedca OIDC structures 2 months ago
Jeremy Doupe 2fcf34066b
add AuthParams to OIDC struct 2 months ago
Herman Slatman 07279dd248
Merge pull request #1801 from smallstep/herman/upgrade-crypto-v0.44.4
Upgrade `go.step.sm/crypto` to `v0.44.4`
2 months ago
Herman Slatman 4c6b0b318e
Upgrade `go.step.sm/crypto` to `v0.44.4` 2 months ago
Herman Slatman f1a2c68f43
Merge pull request #1798 from smallstep/herman/fix-instrumented-key-manager
Add support for `kmsapi.Decrypter` to instrumented key manager
2 months ago
Mariano Cano 7df3ad05ed
Merge pull request #1797 from smallstep/mariano/init-scep
Allow custom SCEP key manager
3 months ago
Mariano Cano 4202d6673c
Remove debug statement 3 months ago
Herman Slatman d6bbe5b06b
Add support for `kmsapi.Decrypter` to instrumented key manager 3 months ago
Herman Slatman 721345eea6
Merge pull request #1793 from verytrap/master
chore: fix function names in comment
3 months ago
verytrap db92404342 chore: fix function names in comment
Signed-off-by: verytrap <wangqiuyue@outlook.com>
3 months ago
Mariano Cano 725a913f66
Allow custom SCEP key manager
This commit allows to inject a custom key manger for SCEP.
3 months ago
Herman Slatman 397877a7b6
Merge pull request #1795 from smallstep/herman/fix-scep-failinfo-oid
Prevent exposing any internal details in SCEP failure message
3 months ago
Herman Slatman b226b6eb4c
Prevent exposing any internal details in SCEP failure message
To be on the safe side, block errors from signing operations from
being returned to the client. We should revisit, and make it return
a more informative error, but with high assurance that no sensitive
information is added to the message.
3 months ago
Herman Slatman 02956ad0e3
Merge pull request #1794 from smallstep/herman/fix-scep-failinfo-oid
Fix the `id-scep-failInfoText` OID
3 months ago
Herman Slatman 037554e774
Fix the `id-scep-failInfoText` OID 3 months ago
github-actions[bot] 1513152cb2
Merge pull request #1791 from smallstep/dependabot/go_modules/github.com/newrelic/go-agent/v3-3.32.0
Bump github.com/newrelic/go-agent/v3 from 3.31.0 to 3.32.0
3 months ago
dependabot[bot] c9ba31ae61
Bump github.com/newrelic/go-agent/v3 from 3.31.0 to 3.32.0
Bumps [github.com/newrelic/go-agent/v3](https://github.com/newrelic/go-agent) from 3.31.0 to 3.32.0.
- [Release notes](https://github.com/newrelic/go-agent/releases)
- [Changelog](https://github.com/newrelic/go-agent/blob/master/CHANGELOG.md)
- [Commits](https://github.com/newrelic/go-agent/compare/v3.31.0...v3.32.0)

---
updated-dependencies:
- dependency-name: github.com/newrelic/go-agent/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
github-actions[bot] 1f69ff84a1
Merge pull request #1792 from smallstep/dependabot/go_modules/google.golang.org/grpc-1.63.2
Bump google.golang.org/grpc from 1.62.1 to 1.63.2
3 months ago
dependabot[bot] a76f07143b
Bump google.golang.org/grpc from 1.62.1 to 1.63.2
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.62.1 to 1.63.2.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.62.1...v1.63.2)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
github-actions[bot] 08ef9fe245
Merge pull request #1789 from smallstep/dependabot/go_modules/golang.org/x/net-0.24.0
Bump golang.org/x/net from 0.22.0 to 0.24.0
3 months ago
dependabot[bot] 57d628513b
Bump golang.org/x/net from 0.22.0 to 0.24.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.22.0 to 0.24.0.
- [Commits](https://github.com/golang/net/compare/v0.22.0...v0.24.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
github-actions[bot] d5758ba3a3
Merge pull request #1784 from smallstep/dependabot/go_modules/github.com/newrelic/go-agent/v3-3.31.0
Bump github.com/newrelic/go-agent/v3 from 3.30.0 to 3.31.0
3 months ago
github-actions[bot] 166c4968cf
Merge pull request #1785 from smallstep/dependabot/go_modules/google.golang.org/api-0.172.0
Bump google.golang.org/api from 0.171.0 to 0.172.0
3 months ago
Carl Tashian 1be0932a0f
Merge pull request #1786 from smallstep/carl/winget-fix
Fix winget release notes URL
3 months ago
Carl Tashian f04a5e39c4
Fix winget release URL 3 months ago
dependabot[bot] d1523c93bc
Bump google.golang.org/api from 0.171.0 to 0.172.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.171.0 to 0.172.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.171.0...v0.172.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
dependabot[bot] 44c48a7494
Bump github.com/newrelic/go-agent/v3 from 3.30.0 to 3.31.0
Bumps [github.com/newrelic/go-agent/v3](https://github.com/newrelic/go-agent) from 3.30.0 to 3.31.0.
- [Release notes](https://github.com/newrelic/go-agent/releases)
- [Changelog](https://github.com/newrelic/go-agent/blob/master/CHANGELOG.md)
- [Commits](https://github.com/newrelic/go-agent/compare/v3.30.0...v3.31.0)

---
updated-dependencies:
- dependency-name: github.com/newrelic/go-agent/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
Carl Tashian 188e4e3ff3
Add version number to winget branch name (#1783) 3 months ago
Max 395a3eeb93
Update go.step.sm/crypto (#1781) 3 months ago
Herman Slatman 4772d7cc28
Merge pull request #1780 from smallstep/herman/update-changelog-20240328
Update changelog for `v0.26.0` release
3 months ago
Herman Slatman 854288a0cb
Update changelog for `v0.26.0` release 3 months ago
github-actions[bot] 4016b69b28
Merge pull request #1776 from smallstep/dependabot/go_modules/github.com/hashicorp/vault/api-1.12.2
Bump github.com/hashicorp/vault/api from 1.12.1 to 1.12.2
3 months ago
github-actions[bot] b5b723e835
Merge pull request #1775 from smallstep/dependabot/go_modules/google.golang.org/api-0.171.0
Bump google.golang.org/api from 0.169.0 to 0.171.0
3 months ago
github-actions[bot] 0a6e79a745
Merge pull request #1778 from smallstep/dependabot/github_actions/dependabot/fetch-metadata-2.0.0
Bump dependabot/fetch-metadata from 1.6.0 to 2.0.0
3 months ago
dependabot[bot] 9d86361ae3
Bump github.com/hashicorp/vault/api from 1.12.1 to 1.12.2
Bumps [github.com/hashicorp/vault/api](https://github.com/hashicorp/vault) from 1.12.1 to 1.12.2.
- [Release notes](https://github.com/hashicorp/vault/releases)
- [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/vault/compare/v1.12.1...v1.12.2)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/vault/api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
github-actions[bot] 7e053437b1
Merge pull request #1774 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.44.1
Bump go.step.sm/crypto from 0.43.1 to 0.44.1
3 months ago
dependabot[bot] 014b4ef2c0
Bump dependabot/fetch-metadata from 1.6.0 to 2.0.0
Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 1.6.0 to 2.0.0.
- [Release notes](https://github.com/dependabot/fetch-metadata/releases)
- [Commits](https://github.com/dependabot/fetch-metadata/compare/v1.6.0...v2.0.0)

---
updated-dependencies:
- dependency-name: dependabot/fetch-metadata
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
dependabot[bot] 21734f7742
Bump google.golang.org/api from 0.169.0 to 0.171.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.169.0 to 0.171.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.169.0...v0.171.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
dependabot[bot] 927cd97bd5
Bump go.step.sm/crypto from 0.43.1 to 0.44.1
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.43.1 to 0.44.1.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.43.1...v0.44.1)

---
updated-dependencies:
- dependency-name: go.step.sm/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
Herman Slatman 2650944d65
Merge pull request #1773 from smallstep/herman/cosign-2.x
Use `--yes` to acknowledge user prompts for `cosign` signing
3 months ago
Herman Slatman 7888d868ba
Use `--yes` to acknowledge user prompts for `cosign` signing 3 months ago
Joe Doss 56c4f3bdd5
Merge pull request #1772 from smallstep/jdoss/Enable_tpmkms
Enable tpmkms
3 months ago
Joe Doss 14c9de2570
Enable tpmkms. 3 months ago
github-actions[bot] 44f44e34e4
Merge pull request #1767 from smallstep/dependabot/go_modules/cloud.google.com/go/longrunning-0.5.6
Bump cloud.google.com/go/longrunning from 0.5.5 to 0.5.6
3 months ago
github-actions[bot] 73c827c7fa
Merge pull request #1768 from smallstep/dependabot/go_modules/github.com/hashicorp/vault/api-1.12.1
Bump github.com/hashicorp/vault/api from 1.12.0 to 1.12.1
3 months ago
dependabot[bot] 9874214669
Bump cloud.google.com/go/longrunning from 0.5.5 to 0.5.6
Bumps [cloud.google.com/go/longrunning](https://github.com/googleapis/google-cloud-go) from 0.5.5 to 0.5.6.
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/longrunning/v0.5.5...longrunning/v0.5.6)

---
updated-dependencies:
- dependency-name: cloud.google.com/go/longrunning
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
github-actions[bot] c5a13d86ba
Merge pull request #1766 from smallstep/dependabot/go_modules/golang.org/x/net-0.22.0
Bump golang.org/x/net from 0.21.0 to 0.22.0
3 months ago
github-actions[bot] 70085fcfeb
Merge pull request #1769 from smallstep/dependabot/go_modules/cloud.google.com/go/security-1.15.6
Bump cloud.google.com/go/security from 1.15.5 to 1.15.6
3 months ago
dependabot[bot] 1a768ad522
Bump cloud.google.com/go/security from 1.15.5 to 1.15.6
Bumps [cloud.google.com/go/security](https://github.com/googleapis/google-cloud-go) from 1.15.5 to 1.15.6.
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/kms/v1.15.5...kms/v1.15.6)

---
updated-dependencies:
- dependency-name: cloud.google.com/go/security
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
dependabot[bot] 4dfade10b8
Bump github.com/hashicorp/vault/api from 1.12.0 to 1.12.1
Bumps [github.com/hashicorp/vault/api](https://github.com/hashicorp/vault) from 1.12.0 to 1.12.1.
- [Release notes](https://github.com/hashicorp/vault/releases)
- [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/vault/compare/v1.12.0...v1.12.1)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/vault/api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
dependabot[bot] 9a75f93250
Bump golang.org/x/net from 0.21.0 to 0.22.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.21.0 to 0.22.0.
- [Commits](https://github.com/golang/net/compare/v0.21.0...v0.22.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
Mariano Cano fe364d5081
Merge pull request #1764 from smallstep/mariano/ra-lifetime
Let the CA determine the RA lifetime
4 months ago
Mariano Cano 0ac9023590
Fix typo in error message and comment 4 months ago
Mariano Cano 10f6a901ec
Let the CA determine the RA lifetime
When the RA mode with StepCAS is used, let the CA decide which lifetime
the RA should get instead of requiring always 24h.

This commit also fixes linter warnings.

Related to #1094
4 months ago
Herman Slatman ef1631b00d
Merge pull request #1762 from smallstep/herman/upgrade-pgx-v4.18.3
Upgrade `pgx` to `v4.18.3`
4 months ago
Herman Slatman 6204a1441e
Upgrade `pgx` to `v4.18.3` 4 months ago
github-actions[bot] d03700b155
Merge pull request #1761 from smallstep/dependabot/go_modules/google.golang.org/grpc-1.62.1
Bump google.golang.org/grpc from 1.62.0 to 1.62.1
4 months ago
github-actions[bot] 1362d4dea6
Merge pull request #1759 from smallstep/dependabot/go_modules/go.step.sm/cli-utils-0.9.0
Bump go.step.sm/cli-utils from 0.8.0 to 0.9.0
4 months ago
dependabot[bot] cc7ac97c51
Bump go.step.sm/cli-utils from 0.8.0 to 0.9.0
Bumps [go.step.sm/cli-utils](https://github.com/smallstep/cli-utils) from 0.8.0 to 0.9.0.
- [Release notes](https://github.com/smallstep/cli-utils/releases)
- [Commits](https://github.com/smallstep/cli-utils/compare/v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: go.step.sm/cli-utils
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
dependabot[bot] 22781b8460
Bump google.golang.org/grpc from 1.62.0 to 1.62.1
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.62.0 to 1.62.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.62.0...v1.62.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
github-actions[bot] 9033120401
Merge pull request #1758 from smallstep/dependabot/go_modules/google.golang.org/api-0.169.0
Bump google.golang.org/api from 0.167.0 to 0.169.0
4 months ago
github-actions[bot] 87506d738c
Merge pull request #1760 from smallstep/dependabot/go_modules/golang.org/x/crypto-0.21.0
Bump golang.org/x/crypto from 0.19.0 to 0.21.0
4 months ago
dependabot[bot] 4940556234
Bump golang.org/x/crypto from 0.19.0 to 0.21.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.19.0 to 0.21.0.
- [Commits](https://github.com/golang/crypto/compare/v0.19.0...v0.21.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
dependabot[bot] 299102955b
Bump google.golang.org/api from 0.167.0 to 0.169.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.167.0 to 0.169.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.167.0...v0.169.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
Herman Slatman 5254702130
Merge pull request #1755 from smallstep/herman/export-requestid-middleware
Export `requestid` middleware
4 months ago
Herman Slatman b7a6b93717
Add package comment 4 months ago
github-actions[bot] 9b1dfcf5d0
Merge pull request #1756 from smallstep/dependabot/go_modules/github.com/go-jose/go-jose/v3-3.0.3
Bump github.com/go-jose/go-jose/v3 from 3.0.2 to 3.0.3
4 months ago
dependabot[bot] 9327859f55
Bump github.com/go-jose/go-jose/v3 from 3.0.2 to 3.0.3
Bumps [github.com/go-jose/go-jose/v3](https://github.com/go-jose/go-jose) from 3.0.2 to 3.0.3.
- [Release notes](https://github.com/go-jose/go-jose/releases)
- [Changelog](https://github.com/go-jose/go-jose/blob/v3.0.3/CHANGELOG.md)
- [Commits](https://github.com/go-jose/go-jose/compare/v3.0.2...v3.0.3)

---
updated-dependencies:
- dependency-name: github.com/go-jose/go-jose/v3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
Carl Tashian 26b7761a9c
Merge pull request #1719 from smallstep/carl/fix-ra-install
Update RA installer step-ca package URL
4 months ago
Herman Slatman b8510dd5b2
Make the `requestid` an exported middleware 4 months ago
Herman Slatman 4cae19f94f
Merge pull request #1753 from smallstep/fix-peak-typo
Fix `peak` -> `peek`
4 months ago
Herman Slatman af76ebdc1d
Fix `peak` -> `peek` 4 months ago
github-actions[bot] 2e9c5bbc1a
Merge pull request #1747 from smallstep/dependabot/go_modules/github.com/go-chi/chi/v5-5.0.12
Bump github.com/go-chi/chi/v5 from 5.0.11 to 5.0.12
4 months ago
Herman Slatman a7a4b47ead
Merge pull request #1751 from smallstep/herman/handle-server-startup-errors
Handle CA server startup errors
4 months ago
Herman Slatman 01de10d447
Merge pull request #1752 from smallstep/herman/upgrade-protobuf-v1.33.0
Upgrade `google.golang.org/protobuf` to `v1.33.0`
4 months ago
Herman Slatman fa5117adaa
Upgrade `google.golang.org/protobuf` to `v1.33.0` 4 months ago
Herman Slatman bbb80cde16
Add startup error shutdown message to log 4 months ago
Herman Slatman f02d4546a9
Handle CA server startup errors 4 months ago
dependabot[bot] 3656b458ea
Bump github.com/go-chi/chi/v5 from 5.0.11 to 5.0.12
Bumps [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) from 5.0.11 to 5.0.12.
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.0.11...v5.0.12)

---
updated-dependencies:
- dependency-name: github.com/go-chi/chi/v5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
4 months ago
Carl Tashian e542a26d4b
Fix RA installer step-ca package URL 4 months ago

@ -6,17 +6,6 @@ permissions:
pull-requests: write
jobs:
dependabot:
runs-on: ubuntu-latest
if: ${{ github.actor == 'dependabot[bot]' }}
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v1.6.0
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Enable auto-merge for Dependabot PRs
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
dependabot-auto-merge:
uses: smallstep/workflows/.github/workflows/dependabot-auto-merge.yml@main
secrets: inherit

@ -45,12 +45,12 @@ jobs:
echo "DOCKER_TAGS_HSM=${{ env.DOCKER_TAGS_HSM }},${{ env.DOCKER_IMAGE }}:hsm" >> "${GITHUB_ENV}"
- name: Create Release
id: create_release
uses: actions/create-release@v1
uses: softprops/action-gh-release@a74c6b72af54cfa997e81df42d94703d6313a2d0 # v2.0.6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
name: Release ${{ github.ref }}
draft: false
prerelease: ${{ steps.is_prerelease.outputs.IS_PRERELEASE }}

@ -1,6 +1,6 @@
# This is an example .goreleaser.yml file with some sane defaults.
# Make sure to check the documentation at http://goreleaser.com
# Documentation: https://goreleaser.com/customization/
project_name: step-ca
version: 2
before:
hooks:
@ -98,7 +98,7 @@ signs:
- cmd: cosign
signature: "${artifact}.sig"
certificate: "${artifact}.pem"
args: ["sign-blob", "--oidc-issuer=https://token.actions.githubusercontent.com", "--output-certificate=${certificate}", "--output-signature=${signature}", "${artifact}"]
args: ["sign-blob", "--oidc-issuer=https://token.actions.githubusercontent.com", "--output-certificate=${certificate}", "--output-signature=${signature}", "${artifact}", "--yes"]
artifacts: all
snapshot:
@ -180,7 +180,7 @@ release:
Those were the changes on {{ .Tag }}!
Come join us on [Discord](https://discord.gg/X2RKGwEbV9) to ask questions, chat about PKI, or get a sneak peak at the freshest PKI memes.
Come join us on [Discord](https://discord.gg/X2RKGwEbV9) to ask questions, chat about PKI, or get a sneak peek at the freshest PKI memes.
# You can disable this pipe in order to not upload any artifacts.
# Defaults to false.
@ -268,7 +268,7 @@ winget:
# Release notes URL.
#
# Templates: allowed
release_notes_url: "https://github.com/smallstep/certificates/releases/tag/{{.Version}}"
release_notes_url: "https://github.com/smallstep/certificates/releases/tag/{{ .Tag }}"
# Create the PR - for testing
skip_upload: auto
@ -283,7 +283,7 @@ winget:
repository:
owner: smallstep
name: winget-pkgs
branch: step
branch: "step-ca-{{.Version}}"
# Optionally a token can be provided, if it differs from the token
# provided to GoReleaser

@ -25,6 +25,63 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
---
## [0.26.2] - 2024-06-13
### Added
- Add provisionerID to ACME accounts (smallstep/certificates#1830)
- Enable verifying ACME provisioner using provisionerID if available (smallstep/certificates#1844)
- Add methods to Authority to get intermediate certificates (smallstep/certificates#1848)
- Add GetX509Signer method (smallstep/certificates#1850)
### Changed
- Make ISErrNotFound more flexible (smallstep/certificates#1819)
- Log errors using slog.Logger (smallstep/certificates#1849)
- Update hardcoded AWS certificates (smallstep/certificates#1881)
## [0.26.1] - 2024-04-22
### Added
- Allow configuration of a custom SCEP key manager (smallstep/certificates#1797)
### Fixed
- id-scep-failInfoText OID (smallstep/certificates#1794)
- CA startup with Vault RA configuration (smallstep/certificates#1803)
## [0.26.0] - 2024-03-28
### Added
- [TPM KMS](https://github.com/smallstep/crypto/tree/master/kms/tpmkms) support for CA keys (smallstep/certificates#1772)
- Propagation of HTTP request identifier using X-Request-Id header (smallstep/certificates#1743, smallstep/certificates#1542)
- Expires header in CRL response (smallstep/certificates#1708)
- Support for providing TLS configuration programmatically (smallstep/certificates#1685)
- Support for providing external CAS implementation (smallstep/certificates#1684)
- AWS `ca-west-1` identity document root certificate (smallstep/certificates#1715)
- [COSE RS1](https://www.rfc-editor.org/rfc/rfc8812.html#section-2) as a supported algorithm with ACME `device-attest-01` challenge (smallstep/certificates#1663)
### Changed
- In an RA setup, let the CA decide the RA certificate lifetime (smallstep/certificates#1764)
- Use Debian Bookworm in Docker containers (smallstep/certificates#1615)
- Error message for CSR validation (smallstep/certificates#1665)
- Updated dependencies
### Fixed
- Stop CA when any of the required servers fails to start (smallstep/certificates#1751). Before the fix, the CA would continue running and only log the server failure when stopped.
- Configuration loading errors when not using context were not returned. Fixed in [cli-utils/109](https://github.com/smallstep/cli-utils/pull/109).
- HTTP_PROXY and HTTPS_PROXY support for ACME validation client (smallstep/certificates#1658).
### Security
- Upgrade to using cosign v2 for signing artifacts
## [0.25.1] - 2023-11-28
### Added

@ -21,6 +21,7 @@ type Account struct {
OrdersURL string `json:"orders"`
ExternalAccountBinding interface{} `json:"externalAccountBinding,omitempty"`
LocationPrefix string `json:"-"`
ProvisionerID string `json:"-"`
ProvisionerName string `json:"-"`
}

@ -82,23 +82,23 @@ func NewAccount(w http.ResponseWriter, r *http.Request) {
payload, err := payloadFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
var nar NewAccountRequest
if err := json.Unmarshal(payload.value, &nar); err != nil {
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err,
render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err,
"failed to unmarshal new-account request payload"))
return
}
if err := nar.Validate(); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
prov, err := acmeProvisionerFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -108,26 +108,26 @@ func NewAccount(w http.ResponseWriter, r *http.Request) {
var acmeErr *acme.Error
if !errors.As(err, &acmeErr) || acmeErr.Status != http.StatusBadRequest {
// Something went wrong ...
render.Error(w, err)
render.Error(w, r, err)
return
}
// Account does not exist //
if nar.OnlyReturnExisting {
render.Error(w, acme.NewError(acme.ErrorAccountDoesNotExistType,
render.Error(w, r, acme.NewError(acme.ErrorAccountDoesNotExistType,
"account does not exist"))
return
}
jwk, err := jwkFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
eak, err := validateExternalAccountBinding(ctx, &nar)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -136,20 +136,21 @@ func NewAccount(w http.ResponseWriter, r *http.Request) {
Contact: nar.Contact,
Status: acme.StatusValid,
LocationPrefix: getAccountLocationPath(ctx, linker, ""),
ProvisionerName: prov.GetName(),
ProvisionerID: prov.ID,
ProvisionerName: prov.Name,
}
if err := db.CreateAccount(ctx, acc); err != nil {
render.Error(w, acme.WrapErrorISE(err, "error creating account"))
render.Error(w, r, acme.WrapErrorISE(err, "error creating account"))
return
}
if eak != nil { // means that we have a (valid) External Account Binding key that should be bound, updated and sent in the response
if err := eak.BindTo(acc); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
if err := db.UpdateExternalAccountKey(ctx, prov.ID, eak); err != nil {
render.Error(w, acme.WrapErrorISE(err, "error updating external account binding key"))
render.Error(w, r, acme.WrapErrorISE(err, "error updating external account binding key"))
return
}
acc.ExternalAccountBinding = nar.ExternalAccountBinding
@ -162,7 +163,7 @@ func NewAccount(w http.ResponseWriter, r *http.Request) {
linker.LinkAccount(ctx, acc)
w.Header().Set("Location", getAccountLocationPath(ctx, linker, acc.ID))
render.JSONStatus(w, acc, httpStatus)
render.JSONStatus(w, r, acc, httpStatus)
}
// GetOrUpdateAccount is the api for updating an ACME account.
@ -173,12 +174,12 @@ func GetOrUpdateAccount(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
payload, err := payloadFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -187,12 +188,12 @@ func GetOrUpdateAccount(w http.ResponseWriter, r *http.Request) {
if !payload.isPostAsGet {
var uar UpdateAccountRequest
if err := json.Unmarshal(payload.value, &uar); err != nil {
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err,
render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err,
"failed to unmarshal new-account request payload"))
return
}
if err := uar.Validate(); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
if len(uar.Status) > 0 || len(uar.Contact) > 0 {
@ -203,7 +204,7 @@ func GetOrUpdateAccount(w http.ResponseWriter, r *http.Request) {
}
if err := db.UpdateAccount(ctx, acc); err != nil {
render.Error(w, acme.WrapErrorISE(err, "error updating account"))
render.Error(w, r, acme.WrapErrorISE(err, "error updating account"))
return
}
}
@ -212,7 +213,7 @@ func GetOrUpdateAccount(w http.ResponseWriter, r *http.Request) {
linker.LinkAccount(ctx, acc)
w.Header().Set("Location", linker.GetLink(ctx, acme.AccountLinkType, acc.ID))
render.JSON(w, acc)
render.JSON(w, r, acc)
}
func logOrdersByAccount(w http.ResponseWriter, oids []string) {
@ -232,23 +233,23 @@ func GetOrdersByAccountID(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
accID := chi.URLParam(r, "accID")
if acc.ID != accID {
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, "account ID '%s' does not match url param '%s'", acc.ID, accID))
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType, "account ID '%s' does not match url param '%s'", acc.ID, accID))
return
}
orders, err := db.GetOrdersByAccountID(ctx, acc.ID)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
linker.LinkOrdersByAccountID(ctx, orders)
render.JSON(w, orders)
render.JSON(w, r, orders)
logOrdersByAccount(w, orders)
}

@ -14,6 +14,7 @@ import (
"time"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/pkg/errors"
"go.step.sm/crypto/jose"
@ -24,14 +25,12 @@ import (
)
var (
defaultDisableRenewal = false
defaultDisableSmallstepExtensions = false
globalProvisionerClaims = provisioner.Claims{
MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute},
MaxTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
DefaultTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
DisableRenewal: &defaultDisableRenewal,
DisableSmallstepExtensions: &defaultDisableSmallstepExtensions,
defaultDisableRenewal = false
globalProvisionerClaims = provisioner.Claims{
MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute},
MaxTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
DefaultTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
DisableRenewal: &defaultDisableRenewal,
}
)
@ -68,6 +67,19 @@ func newProv() acme.Provisioner {
return p
}
func newProvWithID() acme.Provisioner {
// Initialize provisioners
p := &provisioner.ACME{
ID: uuid.NewString(),
Type: "ACME",
Name: "test@acme-<test>provisioner.com",
}
if err := p.Init(provisioner.Config{Claims: globalProvisionerClaims}); err != nil {
fmt.Printf("%v", err)
}
return p
}
func newProvWithOptions(options *provisioner.Options) acme.Provisioner {
// Initialize provisioners
p := &provisioner.ACME{

@ -223,13 +223,13 @@ func GetDirectory(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
acmeProv, err := acmeProvisionerFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
linker := acme.MustLinkerFromContext(ctx)
render.JSON(w, &Directory{
render.JSON(w, r, &Directory{
NewNonce: linker.GetLink(ctx, acme.NewNonceLinkType),
NewAccount: linker.GetLink(ctx, acme.NewAccountLinkType),
NewOrder: linker.GetLink(ctx, acme.NewOrderLinkType),
@ -273,8 +273,8 @@ func shouldAddMetaObject(p *provisioner.ACME) bool {
// NotImplemented returns a 501 and is generally a placeholder for functionality which
// MAY be added at some point in the future but is not in any way a guarantee of such.
func NotImplemented(w http.ResponseWriter, _ *http.Request) {
render.Error(w, acme.NewError(acme.ErrorNotImplementedType, "this API is not implemented"))
func NotImplemented(w http.ResponseWriter, r *http.Request) {
render.Error(w, r, acme.NewError(acme.ErrorNotImplementedType, "this API is not implemented"))
}
// GetAuthorization ACME api for retrieving an Authz.
@ -285,28 +285,28 @@ func GetAuthorization(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
az, err := db.GetAuthorization(ctx, chi.URLParam(r, "authzID"))
if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error retrieving authorization"))
render.Error(w, r, acme.WrapErrorISE(err, "error retrieving authorization"))
return
}
if acc.ID != az.AccountID {
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
"account '%s' does not own authorization '%s'", acc.ID, az.ID))
return
}
if err = az.UpdateStatus(ctx, db); err != nil {
render.Error(w, acme.WrapErrorISE(err, "error updating authorization status"))
render.Error(w, r, acme.WrapErrorISE(err, "error updating authorization status"))
return
}
linker.LinkAuthorization(ctx, az)
w.Header().Set("Location", linker.GetLink(ctx, acme.AuthzLinkType, az.ID))
render.JSON(w, az)
render.JSON(w, r, az)
}
// GetChallenge ACME api for retrieving a Challenge.
@ -317,13 +317,13 @@ func GetChallenge(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
payload, err := payloadFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -336,22 +336,22 @@ func GetChallenge(w http.ResponseWriter, r *http.Request) {
azID := chi.URLParam(r, "authzID")
ch, err := db.GetChallenge(ctx, chi.URLParam(r, "chID"), azID)
if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error retrieving challenge"))
render.Error(w, r, acme.WrapErrorISE(err, "error retrieving challenge"))
return
}
ch.AuthorizationID = azID
if acc.ID != ch.AccountID {
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
"account '%s' does not own challenge '%s'", acc.ID, ch.ID))
return
}
jwk, err := jwkFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
if err = ch.Validate(ctx, db, jwk, payload.value); err != nil {
render.Error(w, acme.WrapErrorISE(err, "error validating challenge"))
render.Error(w, r, acme.WrapErrorISE(err, "error validating challenge"))
return
}
@ -359,7 +359,7 @@ func GetChallenge(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Link", link(linker.GetLink(ctx, acme.AuthzLinkType, azID), "up"))
w.Header().Set("Location", linker.GetLink(ctx, acme.ChallengeLinkType, azID, ch.ID))
render.JSON(w, ch)
render.JSON(w, r, ch)
}
// GetCertificate ACME api for retrieving a Certificate.
@ -369,18 +369,18 @@ func GetCertificate(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
certID := chi.URLParam(r, "certID")
cert, err := db.GetCertificate(ctx, certID)
if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error retrieving certificate"))
render.Error(w, r, acme.WrapErrorISE(err, "error retrieving certificate"))
return
}
if cert.AccountID != acc.ID {
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
"account '%s' does not own certificate '%s'", acc.ID, certID))
return
}

@ -36,7 +36,7 @@ func addNonce(next nextHTTP) nextHTTP {
db := acme.MustDatabaseFromContext(r.Context())
nonce, err := db.CreateNonce(r.Context())
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
w.Header().Set("Replay-Nonce", string(nonce))
@ -64,7 +64,7 @@ func verifyContentType(next nextHTTP) nextHTTP {
return func(w http.ResponseWriter, r *http.Request) {
p, err := provisionerFromContext(r.Context())
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -88,7 +88,7 @@ func verifyContentType(next nextHTTP) nextHTTP {
return
}
}
render.Error(w, acme.NewError(acme.ErrorMalformedType,
render.Error(w, r, acme.NewError(acme.ErrorMalformedType,
"expected content-type to be in %s, but got %s", expected, ct))
}
}
@ -98,12 +98,12 @@ func parseJWS(next nextHTTP) nextHTTP {
return func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
render.Error(w, acme.WrapErrorISE(err, "failed to read request body"))
render.Error(w, r, acme.WrapErrorISE(err, "failed to read request body"))
return
}
jws, err := jose.ParseJWS(string(body))
if err != nil {
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, "failed to parse JWS from request body"))
render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "failed to parse JWS from request body"))
return
}
ctx := context.WithValue(r.Context(), jwsContextKey, jws)
@ -133,26 +133,26 @@ func validateJWS(next nextHTTP) nextHTTP {
jws, err := jwsFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
if len(jws.Signatures) == 0 {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "request body does not contain a signature"))
render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "request body does not contain a signature"))
return
}
if len(jws.Signatures) > 1 {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "request body contains more than one signature"))
render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "request body contains more than one signature"))
return
}
sig := jws.Signatures[0]
uh := sig.Unprotected
if len(uh.KeyID) > 0 ||
if uh.KeyID != "" ||
uh.JSONWebKey != nil ||
len(uh.Algorithm) > 0 ||
len(uh.Nonce) > 0 ||
uh.Algorithm != "" ||
uh.Nonce != "" ||
len(uh.ExtraHeaders) > 0 {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "unprotected header must not be used"))
render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "unprotected header must not be used"))
return
}
hdr := sig.Protected
@ -162,13 +162,13 @@ func validateJWS(next nextHTTP) nextHTTP {
switch k := hdr.JSONWebKey.Key.(type) {
case *rsa.PublicKey:
if k.Size() < keyutil.MinRSAKeyBytes {
render.Error(w, acme.NewError(acme.ErrorMalformedType,
render.Error(w, r, acme.NewError(acme.ErrorMalformedType,
"rsa keys must be at least %d bits (%d bytes) in size",
8*keyutil.MinRSAKeyBytes, keyutil.MinRSAKeyBytes))
return
}
default:
render.Error(w, acme.NewError(acme.ErrorMalformedType,
render.Error(w, r, acme.NewError(acme.ErrorMalformedType,
"jws key type and algorithm do not match"))
return
}
@ -176,35 +176,35 @@ func validateJWS(next nextHTTP) nextHTTP {
case jose.ES256, jose.ES384, jose.ES512, jose.EdDSA:
// we good
default:
render.Error(w, acme.NewError(acme.ErrorBadSignatureAlgorithmType, "unsuitable algorithm: %s", hdr.Algorithm))
render.Error(w, r, acme.NewError(acme.ErrorBadSignatureAlgorithmType, "unsuitable algorithm: %s", hdr.Algorithm))
return
}
// Check the validity/freshness of the Nonce.
if err := db.DeleteNonce(ctx, acme.Nonce(hdr.Nonce)); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
// Check that the JWS url matches the requested url.
jwsURL, ok := hdr.ExtraHeaders["url"].(string)
if !ok {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "jws missing url protected header"))
render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "jws missing url protected header"))
return
}
reqURL := &url.URL{Scheme: "https", Host: r.Host, Path: r.URL.Path}
if jwsURL != reqURL.String() {
render.Error(w, acme.NewError(acme.ErrorMalformedType,
render.Error(w, r, acme.NewError(acme.ErrorMalformedType,
"url header in JWS (%s) does not match request url (%s)", jwsURL, reqURL))
return
}
if hdr.JSONWebKey != nil && len(hdr.KeyID) > 0 {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "jwk and kid are mutually exclusive"))
if hdr.JSONWebKey != nil && hdr.KeyID != "" {
render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "jwk and kid are mutually exclusive"))
return
}
if hdr.JSONWebKey == nil && hdr.KeyID == "" {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "either jwk or kid must be defined in jws protected header"))
render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "either jwk or kid must be defined in jws protected header"))
return
}
next(w, r)
@ -221,23 +221,23 @@ func extractJWK(next nextHTTP) nextHTTP {
jws, err := jwsFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
jwk := jws.Signatures[0].Protected.JSONWebKey
if jwk == nil {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "jwk expected in protected header"))
render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "jwk expected in protected header"))
return
}
if !jwk.Valid() {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "invalid jwk in protected header"))
render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "invalid jwk in protected header"))
return
}
// Overwrite KeyID with the JWK thumbprint.
jwk.KeyID, err = acme.KeyToID(jwk)
if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error getting KeyID from JWK"))
render.Error(w, r, acme.WrapErrorISE(err, "error getting KeyID from JWK"))
return
}
@ -247,15 +247,15 @@ func extractJWK(next nextHTTP) nextHTTP {
// Get Account OR continue to generate a new one OR continue Revoke with certificate private key
acc, err := db.GetAccountByKeyID(ctx, jwk.KeyID)
switch {
case errors.Is(err, acme.ErrNotFound):
case acme.IsErrNotFound(err):
// For NewAccount and Revoke requests ...
break
case err != nil:
render.Error(w, err)
render.Error(w, r, err)
return
default:
if !acc.IsValid() {
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, "account is not active"))
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType, "account is not active"))
return
}
ctx = context.WithValue(ctx, accContextKey, acc)
@ -274,11 +274,11 @@ func checkPrerequisites(next nextHTTP) nextHTTP {
if ok {
ok, err := checkFunc(ctx)
if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error checking acme provisioner prerequisites"))
render.Error(w, r, acme.WrapErrorISE(err, "error checking acme provisioner prerequisites"))
return
}
if !ok {
render.Error(w, acme.NewError(acme.ErrorNotImplementedType, "acme provisioner configuration lacks prerequisites"))
render.Error(w, r, acme.NewError(acme.ErrorNotImplementedType, "acme provisioner configuration lacks prerequisites"))
return
}
}
@ -296,13 +296,13 @@ func lookupJWK(next nextHTTP) nextHTTP {
jws, err := jwsFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
kid := jws.Signatures[0].Protected.KeyID
if kid == "" {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "signature missing 'kid'"))
render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "signature missing 'kid'"))
return
}
@ -310,14 +310,14 @@ func lookupJWK(next nextHTTP) nextHTTP {
acc, err := db.GetAccount(ctx, accID)
switch {
case acme.IsErrNotFound(err):
render.Error(w, acme.NewError(acme.ErrorAccountDoesNotExistType, "account with ID '%s' not found", accID))
render.Error(w, r, acme.NewError(acme.ErrorAccountDoesNotExistType, "account with ID '%s' not found", accID))
return
case err != nil:
render.Error(w, err)
render.Error(w, r, err)
return
default:
if !acc.IsValid() {
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, "account is not active"))
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType, "account is not active"))
return
}
@ -325,7 +325,7 @@ func lookupJWK(next nextHTTP) nextHTTP {
if kid != storedLocation {
// ACME accounts should have a stored location equivalent to the
// kid in the ACME request.
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
"kid does not match stored account location; expected %s, but got %s",
storedLocation, kid))
return
@ -334,14 +334,16 @@ func lookupJWK(next nextHTTP) nextHTTP {
// Verify that the provisioner with which the account was created
// matches the provisioner in the request URL.
reqProv := acme.MustProvisionerFromContext(ctx)
reqProvName := reqProv.GetName()
accProvName := acc.ProvisionerName
if reqProvName != accProvName {
// Provisioner in the URL must match the provisioner with
// which the account was created.
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
switch {
case acc.ProvisionerID == "" && acc.ProvisionerName != reqProv.GetName():
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
"account provisioner does not match requested provisioner; account provisioner = %s, requested provisioner = %s",
acc.ProvisionerName, reqProv.GetName()))
return
case acc.ProvisionerID != "" && acc.ProvisionerID != reqProv.GetID():
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
"account provisioner does not match requested provisioner; account provisioner = %s, requested provisioner = %s",
accProvName, reqProvName))
acc.ProvisionerID, reqProv.GetID()))
return
}
} else {
@ -353,7 +355,7 @@ func lookupJWK(next nextHTTP) nextHTTP {
linker := acme.MustLinkerFromContext(ctx)
kidPrefix := linker.GetLink(ctx, acme.AccountLinkType, "")
if !strings.HasPrefix(kid, kidPrefix) {
render.Error(w, acme.NewError(acme.ErrorMalformedType,
render.Error(w, r, acme.NewError(acme.ErrorMalformedType,
"kid does not have required prefix; expected %s, but got %s",
kidPrefix, kid))
return
@ -374,7 +376,7 @@ func extractOrLookupJWK(next nextHTTP) nextHTTP {
ctx := r.Context()
jws, err := jwsFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -410,16 +412,16 @@ func verifyAndExtractJWSPayload(next nextHTTP) nextHTTP {
ctx := r.Context()
jws, err := jwsFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
jwk, err := jwkFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
if jwk.Algorithm != "" && jwk.Algorithm != jws.Signatures[0].Protected.Algorithm {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "verifier and signature algorithm do not match"))
render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "verifier and signature algorithm do not match"))
return
}
@ -428,11 +430,11 @@ func verifyAndExtractJWSPayload(next nextHTTP) nextHTTP {
case errors.Is(err, jose.ErrCryptoFailure):
payload, err = retryVerificationWithPatchedSignatures(jws, jwk)
if err != nil {
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, "error verifying jws with patched signature(s)"))
render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "error verifying jws with patched signature(s)"))
return
}
case err != nil:
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, "error verifying jws"))
render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "error verifying jws"))
return
}
@ -549,11 +551,11 @@ func isPostAsGet(next nextHTTP) nextHTTP {
return func(w http.ResponseWriter, r *http.Request) {
payload, err := payloadFromContext(r.Context())
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
if !payload.isPostAsGet {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "expected POST-as-GET"))
render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "expected POST-as-GET"))
return
}
next(w, r)

@ -15,6 +15,7 @@ import (
"strings"
"testing"
"github.com/google/uuid"
"github.com/smallstep/assert"
"github.com/smallstep/certificates/acme"
tassert "github.com/stretchr/testify/assert"
@ -831,8 +832,37 @@ func TestHandler_lookupJWK(t *testing.T) {
},
statusCode: http.StatusUnauthorized,
err: acme.NewError(acme.ErrorUnauthorizedType,
"account provisioner does not match requested provisioner; account provisioner = %s, reqested provisioner = %s",
prov.GetName(), "other"),
"account provisioner does not match requested provisioner; account provisioner = %s, requested provisioner = %s",
"other", prov.GetName()),
}
},
"fail/account-with-location-prefix/bad-provisioner-id": func(t *testing.T) test {
p := newProvWithID()
acc := &acme.Account{LocationPrefix: prefix + accID, Status: "valid", Key: jwk, ProvisionerID: uuid.NewString()}
ctx := acme.NewProvisionerContext(context.Background(), p)
ctx = context.WithValue(ctx, jwsContextKey, parsedJWS)
return test{
linker: acme.NewLinker("test.ca.smallstep.com", "acme"),
db: &acme.MockDB{
MockGetAccount: func(ctx context.Context, id string) (*acme.Account, error) {
assert.Equals(t, id, accID)
return acc, nil
},
},
ctx: ctx,
next: func(w http.ResponseWriter, r *http.Request) {
_acc, err := accountFromContext(r.Context())
assert.FatalError(t, err)
assert.Equals(t, _acc, acc)
_jwk, err := jwkFromContext(r.Context())
assert.FatalError(t, err)
assert.Equals(t, _jwk, jwk)
w.Write(testBody)
},
statusCode: http.StatusUnauthorized,
err: acme.NewError(acme.ErrorUnauthorizedType,
"account provisioner does not match requested provisioner; account provisioner = %s, requested provisioner = %s",
acc.ProvisionerID, p.GetID()),
}
},
"ok/account-with-location-prefix": func(t *testing.T) test {
@ -885,6 +915,32 @@ func TestHandler_lookupJWK(t *testing.T) {
statusCode: 200,
}
},
"ok/account-with-provisioner-id": func(t *testing.T) test {
p := newProvWithID()
acc := &acme.Account{LocationPrefix: prefix + accID, Status: "valid", Key: jwk, ProvisionerID: p.GetID()}
ctx := acme.NewProvisionerContext(context.Background(), p)
ctx = context.WithValue(ctx, jwsContextKey, parsedJWS)
return test{
linker: acme.NewLinker("test.ca.smallstep.com", "acme"),
db: &acme.MockDB{
MockGetAccount: func(ctx context.Context, id string) (*acme.Account, error) {
assert.Equals(t, id, accID)
return acc, nil
},
},
ctx: ctx,
next: func(w http.ResponseWriter, r *http.Request) {
_acc, err := accountFromContext(r.Context())
assert.FatalError(t, err)
assert.Equals(t, _acc, acc)
_jwk, err := jwkFromContext(r.Context())
assert.FatalError(t, err)
assert.Equals(t, _jwk, jwk)
w.Write(testBody)
},
statusCode: 200,
}
},
}
for name, run := range tests {
tc := run(t)

@ -5,7 +5,6 @@ import (
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"net"
"net/http"
"strings"
@ -17,7 +16,6 @@ import (
"go.step.sm/crypto/x509util"
"github.com/smallstep/certificates/acme"
"github.com/smallstep/certificates/acme/wire"
"github.com/smallstep/certificates/api/render"
"github.com/smallstep/certificates/authority/policy"
"github.com/smallstep/certificates/authority/provisioner"
@ -50,86 +48,16 @@ func (n *NewOrderRequest) Validate() error {
if id.Value == "" {
return acme.NewError(acme.ErrorMalformedType, "permanent identifier cannot be empty")
}
case acme.WireUser, acme.WireDevice:
// validation of Wire identifiers is performed in `validateWireIdentifiers`, but
// marked here as known and supported types.
continue
default:
return acme.NewError(acme.ErrorMalformedType, "identifier type unsupported: %s", id.Type)
}
}
if err := n.validateWireIdentifiers(); err != nil {
return acme.WrapError(acme.ErrorMalformedType, err, "failed validating Wire identifiers")
// TODO(hs): add some validations for DNS domains?
// TODO(hs): combine the errors from this with allow/deny policy, like example error in https://datatracker.ietf.org/doc/html/rfc8555#section-6.7.1
}
// TODO(hs): add some validations for DNS domains?
// TODO(hs): combine the errors from this with allow/deny policy, like example error in https://datatracker.ietf.org/doc/html/rfc8555#section-6.7.1
return nil
}
func (n *NewOrderRequest) validateWireIdentifiers() error {
if !n.hasWireIdentifiers() {
return nil
}
userIdentifiers := identifiersOfType(acme.WireUser, n.Identifiers)
deviceIdentifiers := identifiersOfType(acme.WireDevice, n.Identifiers)
if len(userIdentifiers) != 1 {
return fmt.Errorf("expected exactly one Wire UserID identifier; got %d", len(userIdentifiers))
}
if len(deviceIdentifiers) != 1 {
return fmt.Errorf("expected exactly one Wire DeviceID identifier, got %d", len(deviceIdentifiers))
}
wireUserID, err := wire.ParseUserID(userIdentifiers[0].Value)
if err != nil {
return fmt.Errorf("failed parsing Wire UserID: %w", err)
}
wireDeviceID, err := wire.ParseDeviceID(deviceIdentifiers[0].Value)
if err != nil {
return fmt.Errorf("failed parsing Wire DeviceID: %w", err)
}
if _, err := wire.ParseClientID(wireDeviceID.ClientID); err != nil {
return fmt.Errorf("invalid Wire client ID %q: %w", wireDeviceID.ClientID, err)
}
switch {
case wireUserID.Domain != wireDeviceID.Domain:
return fmt.Errorf("UserID domain %q does not match DeviceID domain %q", wireUserID.Domain, wireDeviceID.Domain)
case wireUserID.Name != wireDeviceID.Name:
return fmt.Errorf("UserID name %q does not match DeviceID name %q", wireUserID.Name, wireDeviceID.Name)
case wireUserID.Handle != wireDeviceID.Handle:
return fmt.Errorf("UserID handle %q does not match DeviceID handle %q", wireUserID.Handle, wireDeviceID.Handle)
}
return nil
}
// hasWireIdentifiers returns whether the [NewOrderRequest] contains
// Wire identifiers.
func (n *NewOrderRequest) hasWireIdentifiers() bool {
for _, i := range n.Identifiers {
if i.Type == acme.WireUser || i.Type == acme.WireDevice {
return true
}
}
return false
}
// identifiersOfType returns the Identifiers that are of type typ.
func identifiersOfType(typ acme.IdentifierType, ids []acme.Identifier) (result []acme.Identifier) {
for _, id := range ids {
if id.Type == typ {
result = append(result, id)
}
}
return
}
// FinalizeRequest captures the body for a Finalize order request.
type FinalizeRequest struct {
CSR string `json:"csr"`
@ -171,29 +99,29 @@ func NewOrder(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
prov, err := provisionerFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
payload, err := payloadFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
var nor NewOrderRequest
if err := json.Unmarshal(payload.value, &nor); err != nil {
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err,
render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err,
"failed to unmarshal new-order request payload"))
return
}
if err := nor.Validate(); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -202,39 +130,39 @@ func NewOrder(w http.ResponseWriter, r *http.Request) {
acmeProv, err := acmeProvisionerFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
var eak *acme.ExternalAccountKey
if acmeProv.RequireEAB {
if eak, err = db.GetExternalAccountKeyByAccountID(ctx, prov.GetID(), acc.ID); err != nil {
render.Error(w, acme.WrapErrorISE(err, "error retrieving external account binding key"))
render.Error(w, r, acme.WrapErrorISE(err, "error retrieving external account binding key"))
return
}
}
acmePolicy, err := newACMEPolicyEngine(eak)
if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error creating ACME policy engine"))
render.Error(w, r, acme.WrapErrorISE(err, "error creating ACME policy engine"))
return
}
for _, identifier := range nor.Identifiers {
// evaluate the ACME account level policy
if err = isIdentifierAllowed(acmePolicy, identifier); err != nil {
render.Error(w, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized"))
render.Error(w, r, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized"))
return
}
// evaluate the provisioner level policy
orderIdentifier := provisioner.ACMEIdentifier{Type: provisioner.ACMEIdentifierType(identifier.Type), Value: identifier.Value}
if err = prov.AuthorizeOrderIdentifier(ctx, orderIdentifier); err != nil {
render.Error(w, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized"))
render.Error(w, r, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized"))
return
}
// evaluate the authority level policy
if err = ca.AreSANsAllowed(ctx, []string{identifier.Value}); err != nil {
render.Error(w, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized"))
render.Error(w, r, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized"))
return
}
}
@ -260,7 +188,7 @@ func NewOrder(w http.ResponseWriter, r *http.Request) {
Status: acme.StatusPending,
}
if err := newAuthorization(ctx, az); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
o.AuthorizationIDs[i] = az.ID
@ -279,14 +207,14 @@ func NewOrder(w http.ResponseWriter, r *http.Request) {
}
if err := db.CreateOrder(ctx, o); err != nil {
render.Error(w, acme.WrapErrorISE(err, "error creating order"))
render.Error(w, r, acme.WrapErrorISE(err, "error creating order"))
return
}
linker.LinkOrder(ctx, o)
w.Header().Set("Location", linker.GetLink(ctx, acme.OrderLinkType, o.ID))
render.JSONStatus(w, o, http.StatusCreated)
render.JSONStatus(w, r, o, http.StatusCreated)
}
func isIdentifierAllowed(acmePolicy policy.X509Policy, identifier acme.Identifier) error {
@ -298,6 +226,7 @@ func isIdentifierAllowed(acmePolicy policy.X509Policy, identifier acme.Identifie
func newACMEPolicyEngine(eak *acme.ExternalAccountKey) (policy.X509Policy, error) {
if eak == nil {
//nolint:nilnil,nolintlint // expected values
return nil, nil
}
return policy.NewX509PolicyEngine(eak.Policy)
@ -334,43 +263,12 @@ func newAuthorization(ctx context.Context, az *acme.Authorization) error {
continue
}
var target string
switch az.Identifier.Type {
case acme.WireUser:
wireOptions, err := prov.GetOptions().GetWireOptions()
if err != nil {
return acme.WrapErrorISE(err, "failed getting Wire options")
}
target, err = wireOptions.GetOIDCOptions().EvaluateTarget("") // TODO(hs): determine if required by Wire
if err != nil {
return acme.WrapError(acme.ErrorMalformedType, err, "invalid Go template registered for 'target'")
}
case acme.WireDevice:
wireID, err := wire.ParseDeviceID(az.Identifier.Value)
if err != nil {
return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing WireDevice")
}
clientID, err := wire.ParseClientID(wireID.ClientID)
if err != nil {
return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing ClientID")
}
wireOptions, err := prov.GetOptions().GetWireOptions()
if err != nil {
return acme.WrapErrorISE(err, "failed getting Wire options")
}
target, err = wireOptions.GetDPOPOptions().EvaluateTarget(clientID.DeviceID)
if err != nil {
return acme.WrapError(acme.ErrorMalformedType, err, "invalid Go template registered for 'target'")
}
}
ch := &acme.Challenge{
AccountID: az.AccountID,
Value: az.Identifier.Value,
Type: typ,
Token: az.Token,
Status: acme.StatusPending,
Target: target,
}
if err := db.CreateChallenge(ctx, ch); err != nil {
return acme.WrapErrorISE(err, "error creating challenge")
@ -391,39 +289,39 @@ func GetOrder(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
prov, err := provisionerFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
o, err := db.GetOrder(ctx, chi.URLParam(r, "ordID"))
if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error retrieving order"))
render.Error(w, r, acme.WrapErrorISE(err, "error retrieving order"))
return
}
if acc.ID != o.AccountID {
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
"account '%s' does not own order '%s'", acc.ID, o.ID))
return
}
if prov.GetID() != o.ProvisionerID {
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
"provisioner '%s' does not own order '%s'", prov.GetID(), o.ID))
return
}
if err = o.UpdateStatus(ctx, db); err != nil {
render.Error(w, acme.WrapErrorISE(err, "error updating order status"))
render.Error(w, r, acme.WrapErrorISE(err, "error updating order status"))
return
}
linker.LinkOrder(ctx, o)
w.Header().Set("Location", linker.GetLink(ctx, acme.OrderLinkType, o.ID))
render.JSON(w, o)
render.JSON(w, r, o)
}
// FinalizeOrder attempts to finalize an order and create a certificate.
@ -434,56 +332,56 @@ func FinalizeOrder(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
prov, err := provisionerFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
payload, err := payloadFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
var fr FinalizeRequest
if err := json.Unmarshal(payload.value, &fr); err != nil {
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err,
render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err,
"failed to unmarshal finalize-order request payload"))
return
}
if err := fr.Validate(); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
o, err := db.GetOrder(ctx, chi.URLParam(r, "ordID"))
if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error retrieving order"))
render.Error(w, r, acme.WrapErrorISE(err, "error retrieving order"))
return
}
if acc.ID != o.AccountID {
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
"account '%s' does not own order '%s'", acc.ID, o.ID))
return
}
if prov.GetID() != o.ProvisionerID {
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
"provisioner '%s' does not own order '%s'", prov.GetID(), o.ID))
return
}
ca := mustAuthority(ctx)
if err = o.Finalize(ctx, db, fr.csr, ca, prov); err != nil {
render.Error(w, acme.WrapErrorISE(err, "error finalizing order"))
render.Error(w, r, acme.WrapErrorISE(err, "error finalizing order"))
return
}
linker.LinkOrder(ctx, o)
w.Header().Set("Location", linker.GetLink(ctx, acme.OrderLinkType, o.ID))
render.JSON(w, o)
render.JSON(w, r, o)
}
// challengeTypes determines the types of challenges that should be used
@ -502,10 +400,6 @@ func challengeTypes(az *acme.Authorization) []acme.ChallengeType {
}
case acme.PermanentIdentifier:
chTypes = []acme.ChallengeType{acme.DEVICEATTEST01}
case acme.WireUser:
chTypes = []acme.ChallengeType{acme.WIREOIDC01}
case acme.WireDevice:
chTypes = []acme.ChallengeType{acme.WIREDPOP01}
default:
chTypes = []acme.ChallengeType{}
}

@ -24,10 +24,6 @@ import (
"github.com/smallstep/certificates/acme"
"github.com/smallstep/certificates/authority/policy"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/authority/provisioner/wire"
sassert "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewOrderRequest_Validate(t *testing.T) {
@ -84,7 +80,7 @@ func TestNewOrderRequest_Validate(t *testing.T) {
err: acme.NewError(acme.ErrorMalformedType, "invalid DNS name: *.example.com:8080"),
}
},
"fail/bad-identifier/ip": func(t *testing.T) test {
"fail/bad-ip": func(t *testing.T) test {
nbf := time.Now().UTC().Add(time.Minute)
naf := time.Now().UTC().Add(5 * time.Minute)
return test{
@ -100,39 +96,6 @@ func TestNewOrderRequest_Validate(t *testing.T) {
err: acme.NewError(acme.ErrorMalformedType, "invalid IP address: %s", "192.168.42.1000"),
}
},
"fail/bad-identifier/wireapp-invalid-uri": func(t *testing.T) test {
return test{
nor: &NewOrderRequest{
Identifiers: []acme.Identifier{
{Type: "wireapp-user", Value: `{"name": "Alice Smith", "domain": "wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`},
{Type: "wireapp-device", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`},
},
},
err: acme.NewError(acme.ErrorMalformedType, `failed validating Wire identifiers: invalid Wire client ID "example.com": invalid Wire client ID URI "example.com": error parsing example.com: scheme is missing`),
}
},
"fail/bad-identifier/wireapp-wrong-scheme": func(t *testing.T) test {
return test{
nor: &NewOrderRequest{
Identifiers: []acme.Identifier{
{Type: "wireapp-user", Value: `{"name": "Alice Smith", "domain": "wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`},
{Type: "wireapp-device", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "nowireapp://example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`},
},
},
err: acme.NewError(acme.ErrorMalformedType, `failed validating Wire identifiers: invalid Wire client ID "nowireapp://example.com": invalid Wire client ID scheme "nowireapp"; expected "wireapp"`),
}
},
"fail/bad-identifier/wireapp-invalid-user-parts": func(t *testing.T) test {
return test{
nor: &NewOrderRequest{
Identifiers: []acme.Identifier{
{Type: "wireapp-user", Value: `{"name": "Alice Smith", "domain": "wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`},
{Type: "wireapp-device", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://user-device@example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`},
},
},
err: acme.NewError(acme.ErrorMalformedType, `failed validating Wire identifiers: invalid Wire client ID "wireapp://user-device@example.com": invalid Wire client ID username "user-device"`),
}
},
"ok": func(t *testing.T) test {
nbf := time.Now().UTC().Add(time.Minute)
naf := time.Now().UTC().Add(5 * time.Minute)
@ -211,50 +174,34 @@ func TestNewOrderRequest_Validate(t *testing.T) {
naf: naf,
}
},
"ok/wireapp": func(t *testing.T) test {
nbf := time.Now().UTC().Add(time.Minute)
naf := time.Now().UTC().Add(5 * time.Minute)
return test{
nor: &NewOrderRequest{
Identifiers: []acme.Identifier{
{Type: "wireapp-user", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`},
{Type: "wireapp-device", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`},
},
NotAfter: naf,
NotBefore: nbf,
},
nbf: nbf,
naf: naf,
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
err := tc.nor.Validate()
if tc.err != nil {
assert.Error(t, err)
var ae *acme.Error
if assert.True(t, errors.As(err, &ae)) {
assert.HasPrefix(t, ae.Error(), tc.err.Error())
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
assert.Equals(t, ae.Type, tc.err.Type)
if err := tc.nor.Validate(); err != nil {
if assert.NotNil(t, err) {
var ae *acme.Error
if assert.True(t, errors.As(err, &ae)) {
assert.HasPrefix(t, ae.Error(), tc.err.Error())
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
assert.Equals(t, ae.Type, tc.err.Type)
}
}
return
}
assert.NoError(t, err)
if tc.nbf.IsZero() {
assert.True(t, tc.nor.NotBefore.Before(time.Now().Add(time.Minute)))
assert.True(t, tc.nor.NotBefore.After(time.Now().Add(-time.Minute)))
} else {
assert.Equals(t, tc.nor.NotBefore, tc.nbf)
}
if tc.naf.IsZero() {
assert.True(t, tc.nor.NotAfter.Before(time.Now().Add(24*time.Hour)))
assert.True(t, tc.nor.NotAfter.After(time.Now().Add(24*time.Hour-time.Minute)))
} else {
assert.Equals(t, tc.nor.NotAfter, tc.naf)
if assert.Nil(t, tc.err) {
if tc.nbf.IsZero() {
assert.True(t, tc.nor.NotBefore.Before(time.Now().Add(time.Minute)))
assert.True(t, tc.nor.NotBefore.After(time.Now().Add(-time.Minute)))
} else {
assert.Equals(t, tc.nor.NotBefore, tc.nbf)
}
if tc.naf.IsZero() {
assert.True(t, tc.nor.NotAfter.Before(time.Now().Add(24*time.Hour)))
assert.True(t, tc.nor.NotAfter.After(time.Now().Add(24*time.Hour-time.Minute)))
} else {
assert.Equals(t, tc.nor.NotAfter, tc.naf)
}
}
}
})
}
@ -556,37 +503,6 @@ func TestHandler_GetOrder(t *testing.T) {
func TestHandler_newAuthorization(t *testing.T) {
defaultProvisioner := newProv()
fakeKey := `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
-----END PUBLIC KEY-----`
wireProvisioner := newWireProvisionerWithOptions(t, &provisioner.Options{
Wire: &wire.Options{
OIDC: &wire.OIDCOptions{
Provider: &wire.Provider{
IssuerURL: "https://issuer.example.com",
Algorithms: []string{"ES256"},
},
Config: &wire.Config{
ClientID: "test",
SignatureAlgorithms: []string{"ES256"},
Now: time.Now,
},
TransformTemplate: "",
},
DPOP: &wire.DPOPOptions{
SigningKey: []byte(fakeKey),
},
},
})
wireProvisionerFailOptions := &provisioner.ACME{
Type: "ACME",
Name: "test@acme-<test>provisioner.com",
Options: &provisioner.Options{},
Challenges: []provisioner.ACMEChallenge{
provisioner.WIREOIDC_01,
provisioner.WIREDPOP_01,
},
}
type test struct {
az *acme.Authorization
prov acme.Provisioner
@ -614,13 +530,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
return errors.New("force")
},
},
az: az,
err: &acme.Error{
Type: "urn:ietf:params:acme:error:serverInternal",
Err: errors.New("error creating challenge: force"),
Detail: "The server experienced an internal error",
Status: 500,
},
az: az,
err: acme.NewErrorISE("error creating challenge: force"),
}
},
"fail/error-db.CreateAuthorization": func(t *testing.T) test {
@ -674,101 +585,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
return errors.New("force")
},
},
az: az,
err: &acme.Error{
Type: "urn:ietf:params:acme:error:serverInternal",
Err: errors.New("error creating authorization: force"),
Detail: "The server experienced an internal error",
Status: 500,
},
}
},
"fail/wireapp-user-options": func(t *testing.T) test {
az := &acme.Authorization{
AccountID: "accID",
Identifier: acme.Identifier{
Type: "wireapp-user",
Value: "wireapp://%40alice.smith.qa@example.com",
},
Status: acme.StatusPending,
ExpiresAt: clock.Now(),
}
return test{
prov: wireProvisionerFailOptions,
db: &acme.MockDB{},
az: az,
err: &acme.Error{
Type: "urn:ietf:params:acme:error:serverInternal",
Err: errors.New("failed getting Wire options: no Wire options available"),
Detail: "The server experienced an internal error",
Status: 500,
},
}
},
"fail/wireapp-device-parse-id": func(t *testing.T) test {
az := &acme.Authorization{
AccountID: "accID",
Identifier: acme.Identifier{
Type: "wireapp-device",
Value: `{"name}`,
},
Status: acme.StatusPending,
ExpiresAt: clock.Now(),
}
return test{
prov: wireProvisioner,
db: &acme.MockDB{},
az: az,
err: &acme.Error{
Type: "urn:ietf:params:acme:error:malformed",
Err: errors.New("failed parsing WireDevice: unexpected end of JSON input"),
Detail: "The request message was malformed",
Status: 400,
},
}
},
"fail/wireapp-device-parse-client-id": func(t *testing.T) test {
az := &acme.Authorization{
AccountID: "accID",
Identifier: acme.Identifier{
Type: "wireapp-device",
Value: `{"name": "device", "domain": "wire.com", "client-id": "CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`,
},
Status: acme.StatusPending,
ExpiresAt: clock.Now(),
}
return test{
prov: wireProvisioner,
db: &acme.MockDB{},
az: az,
err: &acme.Error{
Type: "urn:ietf:params:acme:error:malformed",
Err: errors.New("failed parsing ClientID: invalid Wire client ID URI \"CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com\": error parsing CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com: scheme is missing"),
Detail: "The request message was malformed",
Status: 400,
},
}
},
"fail/wireapp-device-options": func(t *testing.T) test {
az := &acme.Authorization{
AccountID: "accID",
Identifier: acme.Identifier{
Type: "wireapp-device",
Value: `{"name": "device", "domain": "wire.com", "client-id": "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`,
},
Status: acme.StatusPending,
ExpiresAt: clock.Now(),
}
return test{
prov: wireProvisionerFailOptions,
db: &acme.MockDB{},
az: az,
err: &acme.Error{
Type: "urn:ietf:params:acme:error:serverInternal",
Err: errors.New("failed getting Wire options: no Wire options available"),
Detail: "The server experienced an internal error",
Status: 500,
},
az: az,
err: acme.NewErrorISE("error creating authorization: force"),
}
},
"ok/no-wildcard": func(t *testing.T) test {
@ -937,121 +755,33 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
az: az,
}
},
"ok/wireapp-user": func(t *testing.T) test {
az := &acme.Authorization{
AccountID: "accID",
Identifier: acme.Identifier{
Type: "wireapp-user",
Value: "wireapp://%40alice.smith.qa@example.com",
},
Status: acme.StatusPending,
ExpiresAt: clock.Now(),
}
count := 0
var ch1 **acme.Challenge
return test{
prov: wireProvisioner,
db: &acme.MockDB{
MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error {
switch count {
case 0:
ch.ID = "wireapp-user"
assert.Equals(t, ch.Type, acme.WIREOIDC01)
ch1 = &ch
default:
assert.FatalError(t, errors.New("test logic error"))
return errors.New("force")
}
count++
assert.Equals(t, ch.AccountID, az.AccountID)
assert.Equals(t, ch.Token, az.Token)
assert.Equals(t, ch.Status, acme.StatusPending)
assert.Equals(t, ch.Value, az.Identifier.Value)
return nil
},
MockCreateAuthorization: func(ctx context.Context, _az *acme.Authorization) error {
assert.Equals(t, _az.AccountID, az.AccountID)
assert.Equals(t, _az.Token, az.Token)
assert.Equals(t, _az.Status, acme.StatusPending)
assert.Equals(t, _az.Identifier, az.Identifier)
assert.Equals(t, _az.ExpiresAt, az.ExpiresAt)
_ = ch1
// assert.Equals(t, _az.Challenges, []*acme.Challenge{*ch1})
assert.Equals(t, _az.Wildcard, false)
return nil
},
},
az: az,
}
},
"ok/wireapp-device": func(t *testing.T) test {
az := &acme.Authorization{
AccountID: "accID",
Identifier: acme.Identifier{
Type: "wireapp-device",
Value: `{"name": "device", "domain": "wire.com", "client-id": "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`,
},
Status: acme.StatusPending,
ExpiresAt: clock.Now(),
}
count := 0
var ch1 **acme.Challenge
return test{
prov: wireProvisioner,
db: &acme.MockDB{
MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error {
switch count {
case 0:
ch.ID = "wireapp-device"
assert.Equals(t, ch.Type, acme.WIREDPOP01)
ch1 = &ch
default:
assert.FatalError(t, errors.New("test logic error"))
return errors.New("force")
}
count++
assert.Equals(t, ch.AccountID, az.AccountID)
assert.Equals(t, ch.Token, az.Token)
assert.Equals(t, ch.Status, acme.StatusPending)
assert.Equals(t, ch.Value, az.Identifier.Value)
return nil
},
MockCreateAuthorization: func(ctx context.Context, _az *acme.Authorization) error {
assert.Equals(t, _az.AccountID, az.AccountID)
assert.Equals(t, _az.Token, az.Token)
assert.Equals(t, _az.Status, acme.StatusPending)
assert.Equals(t, _az.Identifier, az.Identifier)
assert.Equals(t, _az.ExpiresAt, az.ExpiresAt)
_ = ch1
// assert.Equals(t, _az.Challenges, []*acme.Challenge{*ch1})
assert.Equals(t, _az.Wildcard, false)
return nil
},
},
az: az,
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
if name == "ok/permanent-identifier-enabled" {
println(1)
}
tc := run(t)
ctx := newBaseContext(context.Background(), tc.db)
ctx = acme.NewProvisionerContext(ctx, tc.prov)
err := newAuthorization(ctx, tc.az)
if tc.err != nil {
sassert.Error(t, err)
var k *acme.Error
if sassert.True(t, errors.As(err, &k)) {
sassert.Equal(t, tc.err.Type, k.Type)
sassert.Equal(t, tc.err.Detail, k.Detail)
sassert.Equal(t, tc.err.Status, k.Status)
sassert.EqualError(t, k.Err, tc.err.Error())
if err := newAuthorization(ctx, tc.az); err != nil {
if assert.NotNil(t, tc.err) {
var k *acme.Error
if assert.True(t, errors.As(err, &k)) {
assert.Equals(t, k.Type, tc.err.Type)
assert.Equals(t, k.Detail, tc.err.Detail)
assert.Equals(t, k.Status, tc.err.Status)
assert.Equals(t, k.Err.Error(), tc.err.Err.Error())
assert.Equals(t, k.Detail, tc.err.Detail)
} else {
assert.FatalError(t, errors.New("unexpected error type"))
}
}
return
} else {
assert.Nil(t, tc.err)
}
sassert.NoError(t, err)
})
}
}
@ -1063,10 +793,6 @@ func TestHandler_NewOrder(t *testing.T) {
u := fmt.Sprintf("%s/acme/%s/order/ordID",
baseURL.String(), escProvName)
fakeWireSigningKey := `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
-----END PUBLIC KEY-----`
type test struct {
ca acme.CertificateAuthority
db acme.DB
@ -1897,141 +1623,6 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
},
}
},
"ok/default-naf-nbf-wireapp": func(t *testing.T) test {
acmeWireProv := newWireProvisionerWithOptions(t, &provisioner.Options{
Wire: &wire.Options{
OIDC: &wire.OIDCOptions{
Provider: &wire.Provider{
IssuerURL: "https://issuer.example.com",
AuthURL: "",
TokenURL: "",
JWKSURL: "",
UserInfoURL: "",
Algorithms: []string{"ES256"},
},
Config: &wire.Config{
ClientID: "integration test",
SignatureAlgorithms: []string{"ES256"},
SkipClientIDCheck: true,
SkipExpiryCheck: true,
SkipIssuerCheck: true,
InsecureSkipSignatureCheck: true,
Now: time.Now,
},
},
DPOP: &wire.DPOPOptions{
SigningKey: []byte(fakeWireSigningKey),
},
},
})
acc := &acme.Account{ID: "accID"}
nor := &NewOrderRequest{
Identifiers: []acme.Identifier{
{Type: "wireapp-user", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "handle": "wireapp://%40alice_wire@wire.com"}`},
{Type: "wireapp-device", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com", "handle": "wireapp://%40alice_wire@wire.com"}`},
},
}
b, err := json.Marshal(nor)
assert.FatalError(t, err)
ctx := acme.NewProvisionerContext(context.Background(), acmeWireProv)
ctx = context.WithValue(ctx, accContextKey, acc)
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b})
var (
ch1, ch2 **acme.Challenge
az1ID, az2ID *string
chCount, azCount = 0, 0
)
return test{
ctx: ctx,
statusCode: 201,
nor: nor,
ca: &mockCA{},
db: &acme.MockDB{
MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error {
switch chCount {
case 0:
assert.Equals(t, ch.Type, acme.WIREOIDC01)
assert.Equals(t, ch.Value, `{"name": "Smith, Alice M (QA)", "domain": "example.com", "handle": "wireapp://%40alice_wire@wire.com"}`)
ch.ID = "wireapp-oidc"
ch1 = &ch
case 1:
assert.Equals(t, ch.Type, acme.WIREDPOP01)
assert.Equals(t, ch.Value, `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com", "handle": "wireapp://%40alice_wire@wire.com"}`)
ch.ID = "wireapp-dpop"
ch2 = &ch
default:
require.Fail(t, "test logic error")
}
chCount++
assert.Equals(t, ch.AccountID, "accID")
assert.NotEquals(t, ch.Token, "")
assert.Equals(t, ch.Status, acme.StatusPending)
_, _ = ch1, ch2
return nil
},
MockCreateAuthorization: func(ctx context.Context, az *acme.Authorization) error {
switch azCount {
case 0:
az.ID = "az1ID"
az1ID = &az.ID
assert.Equals(t, az.Identifier, nor.Identifiers[0])
assert.Equals(t, az.Challenges, []*acme.Challenge{*ch1})
case 1:
az.ID = "az2ID"
az2ID = &az.ID
assert.Equals(t, az.Identifier, nor.Identifiers[1])
assert.Equals(t, az.Challenges, []*acme.Challenge{*ch2})
default:
require.Fail(t, "test logic error")
}
azCount++
assert.Equals(t, az.AccountID, "accID")
assert.NotEquals(t, az.Token, "")
assert.Equals(t, az.Status, acme.StatusPending)
assert.Equals(t, az.Wildcard, false)
return nil
},
MockCreateOrder: func(ctx context.Context, o *acme.Order) error {
o.ID = "ordID"
assert.Equals(t, o.AccountID, "accID")
assert.Equals(t, o.ProvisionerID, prov.GetID())
assert.Equals(t, o.Status, acme.StatusPending)
assert.Equals(t, o.Identifiers, nor.Identifiers)
assert.Equals(t, o.AuthorizationIDs, []string{*az1ID, *az2ID})
return nil
},
MockGetExternalAccountKeyByAccountID: func(ctx context.Context, provisionerID, accountID string) (*acme.ExternalAccountKey, error) {
assert.Equals(t, prov.GetID(), provisionerID)
assert.Equals(t, "accID", accountID)
return nil, nil
},
},
vr: func(t *testing.T, o *acme.Order) {
now := clock.Now()
testBufferDur := 5 * time.Second
orderExpiry := now.Add(defaultOrderExpiry)
expNbf := now.Add(-defaultOrderBackdate)
expNaf := now.Add(prov.DefaultTLSCertDuration())
assert.Equals(t, o.ID, "ordID")
assert.Equals(t, o.Status, acme.StatusPending)
assert.Equals(t, o.Identifiers, nor.Identifiers)
assert.Equals(t, o.AuthorizationURLs, []string{
fmt.Sprintf("%s/acme/%s/authz/az1ID", baseURL.String(), escProvName),
fmt.Sprintf("%s/acme/%s/authz/az2ID", baseURL.String(), escProvName),
})
assert.True(t, o.NotBefore.Add(-testBufferDur).Before(expNbf))
assert.True(t, o.NotBefore.Add(testBufferDur).After(expNbf))
assert.True(t, o.NotAfter.Add(-testBufferDur).Before(expNaf))
assert.True(t, o.NotAfter.Add(testBufferDur).After(expNaf))
assert.True(t, o.ExpiresAt.Add(-testBufferDur).Before(orderExpiry))
assert.True(t, o.ExpiresAt.Add(testBufferDur).After(orderExpiry))
},
}
},
"ok/naf-nbf": func(t *testing.T) test {
now := clock.Now()
expNbf := now.Add(5 * time.Minute)

@ -33,65 +33,65 @@ func RevokeCert(w http.ResponseWriter, r *http.Request) {
jws, err := jwsFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
prov, err := provisionerFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
payload, err := payloadFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
var p revokePayload
err = json.Unmarshal(payload.value, &p)
if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error unmarshaling payload"))
render.Error(w, r, acme.WrapErrorISE(err, "error unmarshaling payload"))
return
}
certBytes, err := base64.RawURLEncoding.DecodeString(p.Certificate)
if err != nil {
// in this case the most likely cause is a client that didn't properly encode the certificate
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, "error base64url decoding payload certificate property"))
render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "error base64url decoding payload certificate property"))
return
}
certToBeRevoked, err := x509.ParseCertificate(certBytes)
if err != nil {
// in this case a client may have encoded something different than a certificate
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, "error parsing certificate"))
render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "error parsing certificate"))
return
}
serial := certToBeRevoked.SerialNumber.String()
dbCert, err := db.GetCertificateBySerial(ctx, serial)
if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error retrieving certificate by serial"))
render.Error(w, r, acme.WrapErrorISE(err, "error retrieving certificate by serial"))
return
}
if !bytes.Equal(dbCert.Leaf.Raw, certToBeRevoked.Raw) {
// this should never happen
render.Error(w, acme.NewErrorISE("certificate raw bytes are not equal"))
render.Error(w, r, acme.NewErrorISE("certificate raw bytes are not equal"))
return
}
if shouldCheckAccountFrom(jws) {
account, err := accountFromContext(ctx)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
acmeErr := isAccountAuthorized(ctx, dbCert, certToBeRevoked, account)
if acmeErr != nil {
render.Error(w, acmeErr)
render.Error(w, r, acmeErr)
return
}
} else {
@ -100,7 +100,7 @@ func RevokeCert(w http.ResponseWriter, r *http.Request) {
_, err := jws.Verify(certToBeRevoked.PublicKey)
if err != nil {
// TODO(hs): possible to determine an error vs. unauthorized and thus provide an ISE vs. Unauthorized?
render.Error(w, wrapUnauthorizedError(certToBeRevoked, nil, "verification of jws using certificate public key failed", err))
render.Error(w, r, wrapUnauthorizedError(certToBeRevoked, nil, "verification of jws using certificate public key failed", err))
return
}
}
@ -108,19 +108,19 @@ func RevokeCert(w http.ResponseWriter, r *http.Request) {
ca := mustAuthority(ctx)
hasBeenRevokedBefore, err := ca.IsRevoked(serial)
if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error retrieving revocation status of certificate"))
render.Error(w, r, acme.WrapErrorISE(err, "error retrieving revocation status of certificate"))
return
}
if hasBeenRevokedBefore {
render.Error(w, acme.NewError(acme.ErrorAlreadyRevokedType, "certificate was already revoked"))
render.Error(w, r, acme.NewError(acme.ErrorAlreadyRevokedType, "certificate was already revoked"))
return
}
reasonCode := p.ReasonCode
acmeErr := validateReasonCode(reasonCode)
if acmeErr != nil {
render.Error(w, acmeErr)
render.Error(w, r, acmeErr)
return
}
@ -128,14 +128,14 @@ func RevokeCert(w http.ResponseWriter, r *http.Request) {
ctx = provisioner.NewContextWithMethod(ctx, provisioner.RevokeMethod)
err = prov.AuthorizeRevoke(ctx, "")
if err != nil {
render.Error(w, acme.WrapErrorISE(err, "error authorizing revocation on provisioner"))
render.Error(w, r, acme.WrapErrorISE(err, "error authorizing revocation on provisioner"))
return
}
options := revokeOptions(serial, certToBeRevoked, reasonCode)
err = ca.Revoke(ctx, options)
if err != nil {
render.Error(w, wrapRevokeErr(err))
render.Error(w, r, wrapRevokeErr(err))
return
}

@ -1,615 +0,0 @@
package api
import (
"bytes"
"context"
"crypto/ed25519"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"strings"
"testing"
"time"
"github.com/go-chi/chi/v5"
"github.com/smallstep/certificates/acme"
"github.com/smallstep/certificates/acme/db/nosql"
"github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/authority/provisioner/wire"
nosqlDB "github.com/smallstep/nosql"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/minica"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util"
)
const (
baseURL = "test.ca.smallstep.com"
linkerPrefix = "acme"
)
func newWireProvisionerWithOptions(t *testing.T, options *provisioner.Options) *provisioner.ACME {
t.Helper()
prov := &provisioner.ACME{
Type: "ACME",
Name: "test@acme-<test>provisioner.com",
Options: options,
Challenges: []provisioner.ACMEChallenge{
provisioner.WIREOIDC_01,
provisioner.WIREDPOP_01,
},
}
err := prov.Init(provisioner.Config{
Claims: config.GlobalProvisionerClaims,
})
require.NoError(t, err)
return prov
}
// TODO(hs): replace with test CA server + acmez based test client for
// more realistic integration test?
func TestWireIntegration(t *testing.T) {
accessTokenSignerJWK, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
require.NoError(t, err)
accessTokenSignerPEMBlock, err := pemutil.Serialize(accessTokenSignerJWK.Public().Key)
require.NoError(t, err)
accessTokenSignerPEMBytes := pem.EncodeToMemory(accessTokenSignerPEMBlock)
accessTokenSigner, err := jose.NewSigner(jose.SigningKey{
Algorithm: jose.SignatureAlgorithm(accessTokenSignerJWK.Algorithm),
Key: accessTokenSignerJWK,
}, new(jose.SignerOptions))
require.NoError(t, err)
oidcTokenSignerJWK, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
require.NoError(t, err)
oidcTokenSigner, err := jose.NewSigner(jose.SigningKey{
Algorithm: jose.SignatureAlgorithm(oidcTokenSignerJWK.Algorithm),
Key: oidcTokenSignerJWK,
}, new(jose.SignerOptions))
require.NoError(t, err)
prov := newWireProvisionerWithOptions(t, &provisioner.Options{
X509: &provisioner.X509Options{
Template: `{
"subject": {
"organization": "WireTest",
"commonName": {{ toJson .Oidc.name }}
},
"uris": [{{ toJson .Oidc.preferred_username }}, {{ toJson .Dpop.sub }}],
"keyUsage": ["digitalSignature"],
"extKeyUsage": ["clientAuth"]
}`,
},
Wire: &wire.Options{
OIDC: &wire.OIDCOptions{
Provider: &wire.Provider{
IssuerURL: "https://issuer.example.com",
AuthURL: "",
TokenURL: "",
JWKSURL: "",
UserInfoURL: "",
Algorithms: []string{"ES256"},
},
Config: &wire.Config{
ClientID: "integration test",
SignatureAlgorithms: []string{"ES256"},
SkipClientIDCheck: true,
SkipExpiryCheck: true,
SkipIssuerCheck: true,
InsecureSkipSignatureCheck: true, // NOTE: this skips actual token verification
Now: time.Now,
},
TransformTemplate: "",
},
DPOP: &wire.DPOPOptions{
SigningKey: accessTokenSignerPEMBytes,
},
},
})
// mock provisioner and linker
ctx := context.Background()
ctx = acme.NewProvisionerContext(ctx, prov)
ctx = acme.NewLinkerContext(ctx, acme.NewLinker(baseURL, linkerPrefix))
// create temporary BoltDB file
file, err := os.CreateTemp(os.TempDir(), "integration-db-")
require.NoError(t, err)
t.Log("database file name:", file.Name())
dbFn := file.Name()
err = file.Close()
require.NoError(t, err)
// open BoltDB
rawDB, err := nosqlDB.New(nosqlDB.BBoltDriver, dbFn)
require.NoError(t, err)
// create tables
db, err := nosql.New(rawDB)
require.NoError(t, err)
// make DB available to handlers
ctx = acme.NewDatabaseContext(ctx, db)
// simulate signed payloads by making the signing key available in ctx
jwk, err := jose.GenerateJWK("OKP", "", "EdDSA", "sig", "", 0)
require.NoError(t, err)
ed25519PrivKey, ok := jwk.Key.(ed25519.PrivateKey)
require.True(t, ok)
dpopSigner, err := jose.NewSigner(jose.SigningKey{
Algorithm: jose.SignatureAlgorithm(jwk.Algorithm),
Key: jwk,
}, new(jose.SignerOptions))
require.NoError(t, err)
ed25519PubKey, ok := ed25519PrivKey.Public().(ed25519.PublicKey)
require.True(t, ok)
jwk.Key = ed25519PubKey
ctx = context.WithValue(ctx, jwkContextKey, jwk)
// get directory
dir := func(ctx context.Context) (dir Directory) {
req := httptest.NewRequest(http.MethodGet, "/foo/bar", http.NoBody)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
GetDirectory(w, req)
res := w.Result()
require.Equal(t, http.StatusOK, res.StatusCode)
body, err := io.ReadAll(res.Body)
require.NoError(t, err)
err = json.Unmarshal(bytes.TrimSpace(body), &dir)
require.NoError(t, err)
return
}(ctx)
t.Log("directory:", dir)
// get nonce
nonce := func(ctx context.Context) (nonce string) {
req := httptest.NewRequest(http.MethodGet, dir.NewNonce, http.NoBody).WithContext(ctx)
w := httptest.NewRecorder()
addNonce(GetNonce)(w, req)
res := w.Result()
require.Equal(t, http.StatusNoContent, res.StatusCode)
nonce = res.Header["Replay-Nonce"][0]
return
}(ctx)
t.Log("nonce:", nonce)
// create new account
acc := func(ctx context.Context) (acc *acme.Account) {
// create payload
nar := &NewAccountRequest{
Contact: []string{"foo", "bar"},
}
rawNar, err := json.Marshal(nar)
require.NoError(t, err)
// create account
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: rawNar})
req := httptest.NewRequest(http.MethodGet, dir.NewAccount, http.NoBody).WithContext(ctx)
w := httptest.NewRecorder()
NewAccount(w, req)
res := w.Result()
require.Equal(t, http.StatusCreated, res.StatusCode)
body, err := io.ReadAll(res.Body)
defer res.Body.Close()
require.NoError(t, err)
err = json.Unmarshal(bytes.TrimSpace(body), &acc)
require.NoError(t, err)
locationParts := strings.Split(res.Header["Location"][0], "/")
acc, err = db.GetAccount(ctx, locationParts[len(locationParts)-1])
require.NoError(t, err)
return
}(ctx)
ctx = context.WithValue(ctx, accContextKey, acc)
t.Log("account ID:", acc.ID)
// new order
order := func(ctx context.Context) (order *acme.Order) {
mockMustAuthority(t, &mockCA{})
nor := &NewOrderRequest{
Identifiers: []acme.Identifier{
{
Type: "wireapp-user",
Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`,
},
{
Type: "wireapp-device",
Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`,
},
},
}
b, err := json.Marshal(nor)
require.NoError(t, err)
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b})
req := httptest.NewRequest("POST", "https://random.local/", http.NoBody)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
NewOrder(w, req)
res := w.Result()
require.Equal(t, http.StatusCreated, res.StatusCode)
body, err := io.ReadAll(res.Body)
defer res.Body.Close()
require.NoError(t, err)
err = json.Unmarshal(bytes.TrimSpace(body), &order)
require.NoError(t, err)
order, err = db.GetOrder(ctx, order.ID)
require.NoError(t, err)
return
}(ctx)
t.Log("authzs IDs:", order.AuthorizationIDs)
// get authorization
getAuthz := func(ctx context.Context, authzID string) (az *acme.Authorization) {
chiCtx := chi.NewRouteContext()
chiCtx.URLParams.Add("authzID", authzID)
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
req := httptest.NewRequest(http.MethodGet, "https://random.local/", http.NoBody).WithContext(ctx)
w := httptest.NewRecorder()
GetAuthorization(w, req)
res := w.Result()
require.Equal(t, http.StatusOK, res.StatusCode)
body, err := io.ReadAll(res.Body)
defer res.Body.Close()
require.NoError(t, err)
err = json.Unmarshal(bytes.TrimSpace(body), &az)
require.NoError(t, err)
az, err = db.GetAuthorization(ctx, authzID)
require.NoError(t, err)
return
}
var azs []*acme.Authorization
for _, azID := range order.AuthorizationIDs {
az := getAuthz(ctx, azID)
azs = append(azs, az)
for _, challenge := range az.Challenges {
chiCtx := chi.NewRouteContext()
chiCtx.URLParams.Add("chID", challenge.ID)
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
var payload []byte
switch challenge.Type {
case acme.WIREDPOP01:
dpopBytes, err := json.Marshal(struct {
jose.Claims
Challenge string `json:"chal,omitempty"`
Handle string `json:"handle,omitempty"`
Nonce string `json:"nonce,omitempty"`
HTU string `json:"htu,omitempty"`
}{
Claims: jose.Claims{
Subject: "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com",
},
Challenge: "token",
Handle: "wireapp://%40alice.smith.qa@example.com",
Nonce: "nonce",
HTU: "http://issuer.example.com",
})
require.NoError(t, err)
dpop, err := dpopSigner.Sign(dpopBytes)
require.NoError(t, err)
proof, err := dpop.CompactSerialize()
require.NoError(t, err)
tokenBytes, err := json.Marshal(struct {
jose.Claims
Challenge string `json:"chal,omitempty"`
Cnf struct {
Kid string `json:"kid,omitempty"`
} `json:"cnf"`
Proof string `json:"proof,omitempty"`
ClientID string `json:"client_id"`
APIVersion int `json:"api_version"`
Scope string `json:"scope"`
}{
Claims: jose.Claims{
Issuer: "http://issuer.example.com",
Audience: []string{"test"},
Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)),
},
Challenge: "token",
Cnf: struct {
Kid string `json:"kid,omitempty"`
}{
Kid: jwk.KeyID,
},
Proof: proof,
ClientID: "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com",
APIVersion: 5,
Scope: "wire_client_id",
})
require.NoError(t, err)
signed, err := accessTokenSigner.Sign(tokenBytes)
require.NoError(t, err)
accessToken, err := signed.CompactSerialize()
require.NoError(t, err)
p, err := json.Marshal(struct {
AccessToken string `json:"access_token"`
}{
AccessToken: accessToken,
})
require.NoError(t, err)
payload = p
case acme.WIREOIDC01:
keyAuth, err := acme.KeyAuthorization("token", jwk)
require.NoError(t, err)
tokenBytes, err := json.Marshal(struct {
jose.Claims
Name string `json:"name,omitempty"`
PreferredUsername string `json:"preferred_username,omitempty"`
KeyAuth string `json:"keyauth"`
}{
Claims: jose.Claims{
Issuer: "https://issuer.example.com",
Audience: []string{"test"},
Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)),
},
Name: "Alice Smith",
PreferredUsername: "wireapp://%40alice_wire@wire.com",
KeyAuth: keyAuth,
})
require.NoError(t, err)
signed, err := oidcTokenSigner.Sign(tokenBytes)
require.NoError(t, err)
idToken, err := signed.CompactSerialize()
require.NoError(t, err)
p, err := json.Marshal(struct {
IDToken string `json:"id_token"`
}{
IDToken: idToken,
})
require.NoError(t, err)
payload = p
default:
require.Fail(t, "unexpected challenge payload type")
}
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: payload})
req := httptest.NewRequest(http.MethodGet, "https://random.local/", http.NoBody).WithContext(ctx)
w := httptest.NewRecorder()
GetChallenge(w, req)
res := w.Result()
require.Equal(t, http.StatusOK, res.StatusCode)
body, err := io.ReadAll(res.Body)
defer res.Body.Close() //nolint:gocritic // close the body
require.NoError(t, err)
err = json.Unmarshal(bytes.TrimSpace(body), &challenge)
require.NoError(t, err)
t.Log("challenge:", challenge.ID, challenge.Status)
}
}
// get/validate challenge simulation
updateAz := func(ctx context.Context, az *acme.Authorization) (updatedAz *acme.Authorization) {
now := clock.Now().Format(time.RFC3339)
for _, challenge := range az.Challenges {
challenge.Status = acme.StatusValid
challenge.ValidatedAt = now
err := db.UpdateChallenge(ctx, challenge)
if err != nil {
t.Error("updating challenge", challenge.ID, ":", err)
}
}
updatedAz, err = db.GetAuthorization(ctx, az.ID)
require.NoError(t, err)
return
}
for _, az := range azs {
updatedAz := updateAz(ctx, az)
for _, challenge := range updatedAz.Challenges {
t.Log("updated challenge:", challenge.ID, challenge.Status)
switch challenge.Type {
case acme.WIREOIDC01:
err = db.CreateOidcToken(ctx, order.ID, map[string]any{"name": "Smith, Alice M (QA)", "preferred_username": "wireapp://%40alice.smith.qa@example.com"})
require.NoError(t, err)
case acme.WIREDPOP01:
err = db.CreateDpopToken(ctx, order.ID, map[string]any{"sub": "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com"})
require.NoError(t, err)
default:
require.Fail(t, "unexpected challenge type")
}
}
}
// get order
updatedOrder := func(ctx context.Context) (updatedOrder *acme.Order) {
chiCtx := chi.NewRouteContext()
chiCtx.URLParams.Add("ordID", order.ID)
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
req := httptest.NewRequest(http.MethodGet, "https://random.local/", http.NoBody).WithContext(ctx)
w := httptest.NewRecorder()
GetOrder(w, req)
res := w.Result()
require.Equal(t, http.StatusOK, res.StatusCode)
body, err := io.ReadAll(res.Body)
defer res.Body.Close()
require.NoError(t, err)
err = json.Unmarshal(bytes.TrimSpace(body), &updatedOrder)
require.NoError(t, err)
require.Equal(t, acme.StatusReady, updatedOrder.Status)
return
}(ctx)
t.Log("updated order status:", updatedOrder.Status)
// finalize order
finalizedOrder := func(ctx context.Context) (finalizedOrder *acme.Order) {
ca, err := minica.New(minica.WithName("WireTestCA"))
require.NoError(t, err)
mockMustAuthority(t, &mockCASigner{
signer: func(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
var (
certOptions []x509util.Option
)
for _, op := range extraOpts {
if k, ok := op.(provisioner.CertificateOptions); ok {
certOptions = append(certOptions, k.Options(signOpts)...)
}
}
x509utilTemplate, err := x509util.NewCertificate(csr, certOptions...)
require.NoError(t, err)
template := x509utilTemplate.GetCertificate()
require.NotNil(t, template)
cert, err := ca.Sign(template)
require.NoError(t, err)
u1, err := url.Parse("wireapp://%40alice.smith.qa@example.com")
require.NoError(t, err)
u2, err := url.Parse("wireapp://lJGYPz0ZRq2kvc_XpdaDlA%21ed416ce8ecdd9fad@example.com")
require.NoError(t, err)
assert.Equal(t, []*url.URL{u1, u2}, cert.URIs)
assert.Equal(t, "Smith, Alice M (QA)", cert.Subject.CommonName)
return []*x509.Certificate{cert, ca.Intermediate}, nil
},
})
qUserID, err := url.Parse("wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com")
require.NoError(t, err)
qUserName, err := url.Parse("wireapp://%40alice.smith.qa@example.com")
require.NoError(t, err)
_, priv, err := ed25519.GenerateKey(rand.Reader)
require.NoError(t, err)
csrTemplate := &x509.CertificateRequest{
Subject: pkix.Name{
Organization: []string{"example.com"},
ExtraNames: []pkix.AttributeTypeAndValue{
{
Type: asn1.ObjectIdentifier{2, 16, 840, 1, 113730, 3, 1, 241},
Value: "Smith, Alice M (QA)",
},
},
},
URIs: []*url.URL{
qUserName,
qUserID,
},
SignatureAlgorithm: x509.PureEd25519,
}
csr, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, priv)
require.NoError(t, err)
fr := FinalizeRequest{CSR: base64.RawURLEncoding.EncodeToString(csr)}
frRaw, err := json.Marshal(fr)
require.NoError(t, err)
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: frRaw})
chiCtx := chi.NewRouteContext()
chiCtx.URLParams.Add("ordID", order.ID)
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
req := httptest.NewRequest(http.MethodGet, "https://random.local/", http.NoBody).WithContext(ctx)
w := httptest.NewRecorder()
FinalizeOrder(w, req)
res := w.Result()
require.Equal(t, http.StatusOK, res.StatusCode)
body, err := io.ReadAll(res.Body)
defer res.Body.Close()
require.NoError(t, err)
err = json.Unmarshal(bytes.TrimSpace(body), &finalizedOrder)
require.NoError(t, err)
require.Equal(t, acme.StatusValid, finalizedOrder.Status)
finalizedOrder, err = db.GetOrder(ctx, order.ID)
require.NoError(t, err)
return
}(ctx)
t.Log("finalized order status:", finalizedOrder.Status)
}
type mockCASigner struct {
signer func(*x509.CertificateRequest, provisioner.SignOptions, ...provisioner.SignOption) ([]*x509.Certificate, error)
}
func (m *mockCASigner) SignWithContext(_ context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
if m.signer == nil {
return nil, errors.New("unimplemented")
}
return m.signer(cr, opts, signOpts...)
}
func (m *mockCASigner) AreSANsAllowed(ctx context.Context, sans []string) error {
return nil
}
func (m *mockCASigner) IsRevoked(sn string) (bool, error) {
return false, nil
}
func (m *mockCASigner) Revoke(ctx context.Context, opts *authority.RevokeOptions) error {
return nil
}
func (m *mockCASigner) LoadProvisionerByName(string) (provisioner.Interface, error) {
return nil, nil
}

@ -25,19 +25,18 @@ import (
"strings"
"time"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/fxamacker/cbor/v2"
"github.com/google/go-tpm/legacy/tpm2"
"golang.org/x/exp/slices"
"github.com/smallstep/go-attestation/attest"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util"
"golang.org/x/exp/slices"
"github.com/smallstep/certificates/acme/wire"
"github.com/smallstep/certificates/authority/provisioner"
wireprovisioner "github.com/smallstep/certificates/authority/provisioner/wire"
)
type ChallengeType string
@ -51,10 +50,6 @@ const (
TLSALPN01 ChallengeType = "tls-alpn-01"
// DEVICEATTEST01 is the device-attest-01 ACME challenge type
DEVICEATTEST01 ChallengeType = "device-attest-01"
// WIREOIDC01 is the Wire OIDC challenge type
WIREOIDC01 ChallengeType = "wire-oidc-01"
// WIREDPOP01 is the Wire DPoP challenge type
WIREDPOP01 ChallengeType = "wire-dpop-01"
)
var (
@ -80,7 +75,6 @@ type Challenge struct {
Token string `json:"token"`
ValidatedAt string `json:"validated,omitempty"`
URL string `json:"url"`
Target string `json:"target,omitempty"`
Error *Error `json:"error,omitempty"`
}
@ -110,12 +104,8 @@ func (ch *Challenge) Validate(ctx context.Context, db DB, jwk *jose.JSONWebKey,
return tlsalpn01Validate(ctx, ch, db, jwk)
case DEVICEATTEST01:
return deviceAttest01Validate(ctx, ch, db, jwk, payload)
case WIREOIDC01:
return wireOIDC01Validate(ctx, ch, db, jwk, payload)
case WIREDPOP01:
return wireDPOP01Validate(ctx, ch, db, jwk, payload)
default:
return NewErrorISE("unexpected challenge type %q", ch.Type)
return NewErrorISE("unexpected challenge type '%s'", ch.Type)
}
}
@ -352,387 +342,6 @@ func dns01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebK
return nil
}
type wireOidcPayload struct {
// IDToken contains the OIDC identity token
IDToken string `json:"id_token"`
}
func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, payload []byte) error {
prov, ok := ProvisionerFromContext(ctx)
if !ok {
return NewErrorISE("missing provisioner")
}
wireOptions, err := prov.GetOptions().GetWireOptions()
if err != nil {
return WrapErrorISE(err, "failed getting Wire options")
}
linker, ok := LinkerFromContext(ctx)
if !ok {
return NewErrorISE("missing linker")
}
var oidcPayload wireOidcPayload
if err := json.Unmarshal(payload, &oidcPayload); err != nil {
return WrapError(ErrorMalformedType, err, "error unmarshalling Wire OIDC challenge payload")
}
wireID, err := wire.ParseUserID(ch.Value)
if err != nil {
return WrapErrorISE(err, "error unmarshalling challenge data")
}
oidcOptions := wireOptions.GetOIDCOptions()
verifier, err := oidcOptions.GetVerifier(ctx)
if err != nil {
return WrapErrorISE(err, "no OIDC verifier available")
}
idToken, err := verifier.Verify(ctx, oidcPayload.IDToken)
if err != nil {
return storeError(ctx, db, ch, true, WrapError(ErrorRejectedIdentifierType, err,
"error verifying ID token signature"))
}
var claims struct {
Name string `json:"preferred_username,omitempty"`
Handle string `json:"name"`
Issuer string `json:"iss,omitempty"`
GivenName string `json:"given_name,omitempty"`
KeyAuth string `json:"keyauth"`
ACMEAudience string `json:"acme_aud,omitempty"`
}
if err := idToken.Claims(&claims); err != nil {
return storeError(ctx, db, ch, true, WrapError(ErrorRejectedIdentifierType, err,
"error retrieving claims from ID token"))
}
// TODO(hs): move this into validation below?
expectedKeyAuth, err := KeyAuthorization(ch.Token, jwk)
if err != nil {
return WrapErrorISE(err, "error determining key authorization")
}
if expectedKeyAuth != claims.KeyAuth {
return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType,
"keyAuthorization does not match; expected %q, but got %q", expectedKeyAuth, claims.KeyAuth))
}
// audience is the full URL to the challenge
acmeAudience := linker.GetLink(ctx, ChallengeLinkType, ch.AuthorizationID, ch.ID)
if claims.ACMEAudience != acmeAudience {
return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType,
"invalid 'acme_aud' %q", claims.ACMEAudience))
}
transformedIDToken, err := validateWireOIDCClaims(oidcOptions, idToken, wireID)
if err != nil {
return storeError(ctx, db, ch, true, WrapError(ErrorRejectedIdentifierType, err, "claims in OIDC ID token don't match"))
}
// Update and store the challenge.
ch.Status = StatusValid
ch.Error = nil
ch.ValidatedAt = clock.Now().Format(time.RFC3339)
if err = db.UpdateChallenge(ctx, ch); err != nil {
return WrapErrorISE(err, "error updating challenge")
}
orders, err := db.GetAllOrdersByAccountID(ctx, ch.AccountID)
if err != nil {
return WrapErrorISE(err, "could not retrieve current order by account id")
}
if len(orders) == 0 {
return NewErrorISE("there are not enough orders for this account for this custom OIDC challenge")
}
order := orders[len(orders)-1]
if err := db.CreateOidcToken(ctx, order, transformedIDToken); err != nil {
return WrapErrorISE(err, "failed storing OIDC id token")
}
return nil
}
func validateWireOIDCClaims(o *wireprovisioner.OIDCOptions, token *oidc.IDToken, wireID wire.UserID) (map[string]any, error) {
var m map[string]any
if err := token.Claims(&m); err != nil {
return nil, fmt.Errorf("failed extracting OIDC ID token claims: %w", err)
}
transformed, err := o.Transform(m)
if err != nil {
return nil, fmt.Errorf("failed transforming OIDC ID token: %w", err)
}
name, ok := transformed["name"]
if !ok {
return nil, fmt.Errorf("transformed OIDC ID token does not contain 'name'")
}
if wireID.Name != name {
return nil, fmt.Errorf("invalid 'name' %q after transformation", name)
}
preferredUsername, ok := transformed["preferred_username"]
if !ok {
return nil, fmt.Errorf("transformed OIDC ID token does not contain 'preferred_username'")
}
if wireID.Handle != preferredUsername {
return nil, fmt.Errorf("invalid 'preferred_username' %q after transformation", preferredUsername)
}
return transformed, nil
}
type wireDpopPayload struct {
// AccessToken is the token generated by wire-server
AccessToken string `json:"access_token"`
}
func wireDPOP01Validate(ctx context.Context, ch *Challenge, db DB, accountJWK *jose.JSONWebKey, payload []byte) error {
prov, ok := ProvisionerFromContext(ctx)
if !ok {
return NewErrorISE("missing provisioner")
}
wireOptions, err := prov.GetOptions().GetWireOptions()
if err != nil {
return WrapErrorISE(err, "failed getting Wire options")
}
linker, ok := LinkerFromContext(ctx)
if !ok {
return NewErrorISE("missing linker")
}
var dpopPayload wireDpopPayload
if err := json.Unmarshal(payload, &dpopPayload); err != nil {
return WrapError(ErrorMalformedType, err, "error unmarshalling Wire DPoP challenge payload")
}
wireID, err := wire.ParseDeviceID(ch.Value)
if err != nil {
return WrapErrorISE(err, "error unmarshalling challenge data")
}
clientID, err := wire.ParseClientID(wireID.ClientID)
if err != nil {
return WrapErrorISE(err, "error parsing device id")
}
dpopOptions := wireOptions.GetDPOPOptions()
issuer, err := dpopOptions.EvaluateTarget(clientID.DeviceID)
if err != nil {
return WrapErrorISE(err, "invalid Go template registered for 'target'")
}
// audience is the full URL to the challenge
audience := linker.GetLink(ctx, ChallengeLinkType, ch.AuthorizationID, ch.ID)
params := wireVerifyParams{
token: dpopPayload.AccessToken,
tokenKey: dpopOptions.GetSigningKey(),
dpopKey: accountJWK.Public(),
dpopKeyID: accountJWK.KeyID,
issuer: issuer,
audience: audience,
wireID: wireID,
chToken: ch.Token,
t: clock.Now().UTC(),
}
_, dpop, err := parseAndVerifyWireAccessToken(params)
if err != nil {
return storeError(ctx, db, ch, true, WrapError(ErrorRejectedIdentifierType, err,
"failed validating Wire access token"))
}
// Update and store the challenge.
ch.Status = StatusValid
ch.Error = nil
ch.ValidatedAt = clock.Now().Format(time.RFC3339)
if err = db.UpdateChallenge(ctx, ch); err != nil {
return WrapErrorISE(err, "error updating challenge")
}
orders, err := db.GetAllOrdersByAccountID(ctx, ch.AccountID)
if err != nil {
return WrapErrorISE(err, "could not find current order by account id")
}
if len(orders) == 0 {
return NewErrorISE("there are not enough orders for this account for this custom OIDC challenge")
}
order := orders[len(orders)-1]
if err := db.CreateDpopToken(ctx, order, map[string]any(*dpop)); err != nil {
return WrapErrorISE(err, "failed storing DPoP token")
}
return nil
}
type wireCnf struct {
Kid string `json:"kid"`
}
type wireAccessToken struct {
jose.Claims
Challenge string `json:"chal"`
Nonce string `json:"nonce"`
Cnf wireCnf `json:"cnf"`
Proof string `json:"proof"`
ClientID string `json:"client_id"`
APIVersion int `json:"api_version"`
Scope string `json:"scope"`
}
type wireDpopJwt struct {
jose.Claims
ClientID string `json:"client_id"`
Challenge string `json:"chal"`
Nonce string `json:"nonce"`
HTU string `json:"htu"`
}
type wireDpopToken map[string]any
type wireVerifyParams struct {
token string
tokenKey crypto.PublicKey
dpopKey crypto.PublicKey
dpopKeyID string
issuer string
audience string
wireID wire.DeviceID
chToken string
t time.Time
}
func parseAndVerifyWireAccessToken(v wireVerifyParams) (*wireAccessToken, *wireDpopToken, error) {
jwt, err := jose.ParseSigned(v.token)
if err != nil {
return nil, nil, fmt.Errorf("failed parsing token: %w", err)
}
if len(jwt.Headers) != 1 {
return nil, nil, fmt.Errorf("token has wrong number of headers %d", len(jwt.Headers))
}
keyID, err := KeyToID(&jose.JSONWebKey{Key: v.tokenKey})
if err != nil {
return nil, nil, fmt.Errorf("failed calculating token key ID: %w", err)
}
jwtKeyID := jwt.Headers[0].KeyID
if jwtKeyID == "" {
if jwtKeyID, err = KeyToID(jwt.Headers[0].JSONWebKey); err != nil {
return nil, nil, fmt.Errorf("failed extracting token key ID: %w", err)
}
}
if jwtKeyID != keyID {
return nil, nil, fmt.Errorf("invalid token key ID %q", jwtKeyID)
}
var accessToken wireAccessToken
if err = jwt.Claims(v.tokenKey, &accessToken); err != nil {
return nil, nil, fmt.Errorf("failed validating Wire DPoP token claims: %w", err)
}
if err := accessToken.ValidateWithLeeway(jose.Expected{
Time: v.t,
Issuer: v.issuer,
Audience: jose.Audience{v.audience},
}, 1*time.Minute); err != nil {
return nil, nil, fmt.Errorf("failed validation: %w", err)
}
if accessToken.Challenge == "" {
return nil, nil, errors.New("access token challenge must not be empty")
}
if accessToken.Cnf.Kid == "" || accessToken.Cnf.Kid != v.dpopKeyID {
return nil, nil, fmt.Errorf("expected kid %q; got %q", v.dpopKeyID, accessToken.Cnf.Kid)
}
if accessToken.ClientID != v.wireID.ClientID {
return nil, nil, fmt.Errorf("invalid Wire client ID %q", accessToken.ClientID)
}
if accessToken.Expiry.Time().After(v.t.Add(time.Hour)) {
return nil, nil, fmt.Errorf("'exp' %s is too far into the future", accessToken.Expiry.Time().String())
}
if accessToken.Scope != "wire_client_id" {
return nil, nil, fmt.Errorf("invalid Wire scope %q", accessToken.Scope)
}
dpopJWT, err := jose.ParseSigned(accessToken.Proof)
if err != nil {
return nil, nil, fmt.Errorf("invalid Wire DPoP token: %w", err)
}
if len(dpopJWT.Headers) != 1 {
return nil, nil, fmt.Errorf("DPoP token has wrong number of headers %d", len(jwt.Headers))
}
dpopJwtKeyID := dpopJWT.Headers[0].KeyID
if dpopJwtKeyID == "" {
if dpopJwtKeyID, err = KeyToID(dpopJWT.Headers[0].JSONWebKey); err != nil {
return nil, nil, fmt.Errorf("failed extracting DPoP token key ID: %w", err)
}
}
if dpopJwtKeyID != v.dpopKeyID {
return nil, nil, fmt.Errorf("invalid DPoP token key ID %q", dpopJWT.Headers[0].KeyID)
}
var wireDpop wireDpopJwt
if err := dpopJWT.Claims(v.dpopKey, &wireDpop); err != nil {
return nil, nil, fmt.Errorf("failed validating Wire DPoP token claims: %w", err)
}
if err := wireDpop.ValidateWithLeeway(jose.Expected{
Time: v.t,
Audience: jose.Audience{v.audience},
}, 1*time.Minute); err != nil {
return nil, nil, fmt.Errorf("failed DPoP validation: %w", err)
}
if wireDpop.HTU == "" || wireDpop.HTU != v.issuer { // DPoP doesn't contains "iss" claim, but has it in the "htu" claim
return nil, nil, fmt.Errorf("DPoP contains invalid issuer (htu) %q", wireDpop.HTU)
}
if wireDpop.Expiry.Time().After(v.t.Add(time.Hour)) {
return nil, nil, fmt.Errorf("'exp' %s is too far into the future", wireDpop.Expiry.Time().String())
}
if wireDpop.Subject != v.wireID.ClientID {
return nil, nil, fmt.Errorf("DPoP contains invalid Wire client ID %q", wireDpop.ClientID)
}
if wireDpop.Nonce == "" || wireDpop.Nonce != accessToken.Nonce {
return nil, nil, fmt.Errorf("DPoP contains invalid nonce %q", wireDpop.Nonce)
}
if wireDpop.Challenge == "" || wireDpop.Challenge != accessToken.Challenge {
return nil, nil, fmt.Errorf("DPoP contains invalid challenge %q", wireDpop.Challenge)
}
// TODO(hs): can we use the wireDpopJwt and map that instead of doing Claims() twice?
var dpopToken wireDpopToken
if err := dpopJWT.Claims(v.dpopKey, &dpopToken); err != nil {
return nil, nil, fmt.Errorf("failed validating Wire DPoP token claims: %w", err)
}
challenge, ok := dpopToken["chal"].(string)
if !ok {
return nil, nil, fmt.Errorf("invalid challenge in Wire DPoP token")
}
if challenge == "" || challenge != v.chToken {
return nil, nil, fmt.Errorf("invalid Wire DPoP challenge %q", challenge)
}
handle, ok := dpopToken["handle"].(string)
if !ok {
return nil, nil, fmt.Errorf("invalid handle in Wire DPoP token")
}
if handle == "" || handle != v.wireID.Handle {
return nil, nil, fmt.Errorf("invalid Wire client handle %q", handle)
}
name, ok := dpopToken["name"].(string)
if !ok {
return nil, nil, fmt.Errorf("invalid display name in Wire DPoP token")
}
if name == "" || name != v.wireID.Name {
return nil, nil, fmt.Errorf("invalid Wire client display name %q", name)
}
return &accessToken, &dpopToken, nil
}
type payloadType struct {
AttObj string `json:"attObj"`
Error string `json:"error"`
@ -1117,7 +726,7 @@ var (
oidTCGKpAIKCertificate = asn1.ObjectIdentifier{2, 23, 133, 8, 3}
)
// validateAKCertifiate validates the X.509 AK certificate to be
// validateAKCertificate validates the X.509 AK certificate to be
// in accordance with the required properties. The requirements come from:
// https://www.w3.org/TR/webauthn-2/#sctn-tpm-cert-requirements.
//
@ -1126,7 +735,7 @@ var (
// - The Subject Alternative Name extension MUST be set as defined
// in [TPMv2-EK-Profile] section 3.2.9.
// - The Extended Key Usage extension MUST contain the OID 2.23.133.8.3
// ("joint-iso-itu-t(2) internationalorganizations(23) 133 tcg-kp(8) tcg-kp-AIKCertificate(3)").
// ("joint-iso-itu-t(2) international-organizations(23) 133 tcg-kp(8) tcg-kp-AIKCertificate(3)").
// - The Basic Constraints extension MUST have the CA component set to false.
// - An Authority Information Access (AIA) extension with entry id-ad-ocsp
// and a CRL Distribution Point extension [RFC5280] are both OPTIONAL as

@ -33,13 +33,11 @@ import (
"github.com/fxamacker/cbor/v2"
"github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/provisioner"
wireprovisioner "github.com/smallstep/certificates/authority/provisioner/wire"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/minica"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util"
)
@ -198,25 +196,6 @@ func mustAttestYubikey(t *testing.T, _, keyAuthorization string, serial int) ([]
return payload, leaf, ca.Root
}
func newWireProvisionerWithOptions(t *testing.T, options *provisioner.Options) *provisioner.ACME {
t.Helper()
prov := &provisioner.ACME{
Type: "ACME",
Name: "wire",
Options: options,
Challenges: []provisioner.ACMEChallenge{
provisioner.WIREOIDC_01,
provisioner.WIREDPOP_01,
},
}
if err := prov.Init(provisioner.Config{
Claims: config.GlobalProvisionerClaims,
}); err != nil {
t.Fatal(err)
}
return prov
}
func Test_storeError(t *testing.T) {
type test struct {
ch *Challenge
@ -417,9 +396,6 @@ func TestKeyAuthorization(t *testing.T) {
}
func TestChallenge_Validate(t *testing.T) {
fakeKey := `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
-----END PUBLIC KEY-----`
type test struct {
ch *Challenge
vc Client
@ -454,7 +430,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
}
return test{
ch: ch,
err: NewErrorISE(`unexpected challenge type "foo"`),
err: NewErrorISE("unexpected challenge type 'foo'"),
}
},
"fail/http-01": func(t *testing.T) test {
@ -877,263 +853,6 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
},
}
},
"ok/wire-oidc-01": func(t *testing.T) test {
jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
signerJWK, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
require.NoError(t, err)
signer, err := jose.NewSigner(jose.SigningKey{
Algorithm: jose.SignatureAlgorithm(signerJWK.Algorithm),
Key: signerJWK,
}, new(jose.SignerOptions))
require.NoError(t, err)
srv := mustJWKServer(t, signerJWK.Public())
tokenBytes, err := json.Marshal(struct {
jose.Claims
Name string `json:"name,omitempty"`
PreferredUsername string `json:"preferred_username,omitempty"`
KeyAuth string `json:"keyauth"`
ACMEAudience string `json:"acme_aud"`
}{
Claims: jose.Claims{
Issuer: srv.URL,
Audience: []string{"test"},
Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)),
},
Name: "Alice Smith",
PreferredUsername: "wireapp://%40alice_wire@wire.com",
KeyAuth: keyAuth,
ACMEAudience: "https://ca.example.com/acme/wire/challenge/azID/chID",
})
require.NoError(t, err)
signed, err := signer.Sign(tokenBytes)
require.NoError(t, err)
idToken, err := signed.CompactSerialize()
require.NoError(t, err)
payload, err := json.Marshal(struct {
IDToken string `json:"id_token"`
}{
IDToken: idToken,
})
require.NoError(t, err)
valueBytes, err := json.Marshal(struct {
Name string `json:"name,omitempty"`
Domain string `json:"domain,omitempty"`
ClientID string `json:"client-id,omitempty"`
Handle string `json:"handle,omitempty"`
}{
Name: "Alice Smith",
Domain: "wire.com",
ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com",
Handle: "wireapp://%40alice_wire@wire.com",
})
require.NoError(t, err)
ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{
Wire: &wireprovisioner.Options{
OIDC: &wireprovisioner.OIDCOptions{
Provider: &wireprovisioner.Provider{
IssuerURL: srv.URL,
JWKSURL: srv.URL + "/keys",
Algorithms: []string{"ES256"},
},
Config: &wireprovisioner.Config{
ClientID: "test",
SignatureAlgorithms: []string{"ES256"},
Now: time.Now,
},
TransformTemplate: "",
},
DPOP: &wireprovisioner.DPOPOptions{
SigningKey: []byte(fakeKey),
},
},
}))
ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme"))
return test{
ch: &Challenge{
ID: "chID",
AuthorizationID: "azID",
AccountID: "accID",
Token: "token",
Type: "wire-oidc-01",
Status: StatusPending,
Value: string(valueBytes),
},
srv: srv,
payload: payload,
ctx: ctx,
jwk: jwk,
db: &MockDB{
MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
assert.Equal(t, "chID", updch.ID)
assert.Equal(t, "token", updch.Token)
assert.Equal(t, StatusValid, updch.Status)
assert.Equal(t, ChallengeType("wire-oidc-01"), updch.Type)
assert.Equal(t, string(valueBytes), updch.Value)
return nil
},
MockGetAllOrdersByAccountID: func(ctx context.Context, accountID string) ([]string, error) {
assert.Equal(t, "accID", accountID)
return []string{"orderID"}, nil
},
MockCreateOidcToken: func(ctx context.Context, orderID string, idToken map[string]interface{}) error {
assert.Equal(t, "orderID", orderID)
assert.Equal(t, "Alice Smith", idToken["name"].(string))
assert.Equal(t, "wireapp://%40alice_wire@wire.com", idToken["preferred_username"].(string))
return nil
},
},
}
},
"ok/wire-dpop-01": func(t *testing.T) test {
jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
_ = keyAuth // TODO(hs): keyAuth (not) required for DPoP? Or needs to be added to validation?
dpopSigner, err := jose.NewSigner(jose.SigningKey{
Algorithm: jose.SignatureAlgorithm(jwk.Algorithm),
Key: jwk,
}, new(jose.SignerOptions))
require.NoError(t, err)
signerJWK, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
require.NoError(t, err)
signer, err := jose.NewSigner(jose.SigningKey{
Algorithm: jose.SignatureAlgorithm(signerJWK.Algorithm),
Key: signerJWK,
}, new(jose.SignerOptions))
require.NoError(t, err)
signerPEMBlock, err := pemutil.Serialize(signerJWK.Public().Key)
require.NoError(t, err)
signerPEMBytes := pem.EncodeToMemory(signerPEMBlock)
dpopBytes, err := json.Marshal(struct {
jose.Claims
Challenge string `json:"chal,omitempty"`
Handle string `json:"handle,omitempty"`
Nonce string `json:"nonce,omitempty"`
HTU string `json:"htu,omitempty"`
Name string `json:"name,omitempty"`
}{
Claims: jose.Claims{
Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com",
Audience: jose.Audience{"https://ca.example.com/acme/wire/challenge/azID/chID"},
},
Challenge: "token",
Handle: "wireapp://%40alice_wire@wire.com",
Nonce: "nonce",
HTU: "http://issuer.example.com",
Name: "Alice Smith",
})
require.NoError(t, err)
dpop, err := dpopSigner.Sign(dpopBytes)
require.NoError(t, err)
proof, err := dpop.CompactSerialize()
require.NoError(t, err)
tokenBytes, err := json.Marshal(struct {
jose.Claims
Challenge string `json:"chal,omitempty"`
Nonce string `json:"nonce,omitempty"`
Cnf struct {
Kid string `json:"kid,omitempty"`
} `json:"cnf"`
Proof string `json:"proof,omitempty"`
ClientID string `json:"client_id"`
APIVersion int `json:"api_version"`
Scope string `json:"scope"`
}{
Claims: jose.Claims{
Issuer: "http://issuer.example.com",
Audience: jose.Audience{"https://ca.example.com/acme/wire/challenge/azID/chID"},
Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)),
},
Challenge: "token",
Nonce: "nonce",
Cnf: struct {
Kid string `json:"kid,omitempty"`
}{
Kid: jwk.KeyID,
},
Proof: proof,
ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com",
APIVersion: 5,
Scope: "wire_client_id",
})
require.NoError(t, err)
signed, err := signer.Sign(tokenBytes)
require.NoError(t, err)
accessToken, err := signed.CompactSerialize()
require.NoError(t, err)
payload, err := json.Marshal(struct {
AccessToken string `json:"access_token"`
}{
AccessToken: accessToken,
})
require.NoError(t, err)
valueBytes, err := json.Marshal(struct {
Name string `json:"name,omitempty"`
Domain string `json:"domain,omitempty"`
ClientID string `json:"client-id,omitempty"`
Handle string `json:"handle,omitempty"`
}{
Name: "Alice Smith",
Domain: "wire.com",
ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com",
Handle: "wireapp://%40alice_wire@wire.com",
})
require.NoError(t, err)
ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{
Wire: &wireprovisioner.Options{
OIDC: &wireprovisioner.OIDCOptions{
Provider: &wireprovisioner.Provider{
IssuerURL: "http://issuerexample.com",
Algorithms: []string{"ES256"},
},
Config: &wireprovisioner.Config{
ClientID: "test",
SignatureAlgorithms: []string{"ES256"},
Now: time.Now,
},
TransformTemplate: "",
},
DPOP: &wireprovisioner.DPOPOptions{
Target: "http://issuer.example.com",
SigningKey: signerPEMBytes,
},
},
}))
ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme"))
return test{
ch: &Challenge{
ID: "chID",
AuthorizationID: "azID",
AccountID: "accID",
Token: "token",
Type: "wire-dpop-01",
Status: StatusPending,
Value: string(valueBytes),
},
payload: payload,
ctx: ctx,
jwk: jwk,
db: &MockDB{
MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
assert.Equal(t, "chID", updch.ID)
assert.Equal(t, "token", updch.Token)
assert.Equal(t, StatusValid, updch.Status)
assert.Equal(t, ChallengeType("wire-dpop-01"), updch.Type)
assert.Equal(t, string(valueBytes), updch.Value)
return nil
},
MockGetAllOrdersByAccountID: func(ctx context.Context, accountID string) ([]string, error) {
assert.Equal(t, "accID", accountID)
return []string{"orderID"}, nil
},
MockCreateDpopToken: func(ctx context.Context, orderID string, dpop map[string]interface{}) error {
assert.Equal(t, "orderID", orderID)
assert.Equal(t, "token", dpop["chal"].(string))
assert.Equal(t, "wireapp://%40alice_wire@wire.com", dpop["handle"].(string))
assert.Equal(t, "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", dpop["sub"].(string))
return nil
},
},
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
@ -1148,63 +867,25 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
ctx = context.Background()
}
ctx = NewClientContext(ctx, tc.vc)
err := tc.ch.Validate(ctx, tc.db, tc.jwk, tc.payload)
if tc.err != nil {
var k *Error
if errors.As(err, &k) {
assert.Equal(t, tc.err.Type, k.Type)
assert.Equal(t, tc.err.Detail, k.Detail)
assert.Equal(t, tc.err.Status, k.Status)
assert.Equal(t, tc.err.Err.Error(), k.Err.Error())
} else {
assert.Fail(t, "unexpected error type")
if err := tc.ch.Validate(ctx, tc.db, tc.jwk, tc.payload); err != nil {
if assert.Error(t, tc.err) {
var k *Error
if errors.As(err, &k) {
assert.Equal(t, tc.err.Type, k.Type)
assert.Equal(t, tc.err.Detail, k.Detail)
assert.Equal(t, tc.err.Status, k.Status)
assert.Equal(t, tc.err.Err.Error(), k.Err.Error())
} else {
assert.Fail(t, "unexpected error type")
}
}
return
} else {
assert.Nil(t, tc.err)
}
assert.NoError(t, err)
})
}
}
func mustJWKServer(t *testing.T, pub jose.JSONWebKey) *httptest.Server {
t.Helper()
mux := http.NewServeMux()
server := httptest.NewServer(mux)
b, err := json.Marshal(struct {
Keys []jose.JSONWebKey `json:"keys,omitempty"`
}{
Keys: []jose.JSONWebKey{pub},
})
require.NoError(t, err)
jwks := string(b)
wellKnown := fmt.Sprintf(`{
"issuer": "%[1]s",
"authorization_endpoint": "%[1]s/auth",
"token_endpoint": "%[1]s/token",
"jwks_uri": "%[1]s/keys",
"userinfo_endpoint": "%[1]s/userinfo",
"id_token_signing_alg_values_supported": ["ES256"]
}`, server.URL)
mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, req *http.Request) {
_, err := io.WriteString(w, wellKnown)
if err != nil {
w.WriteHeader(500)
}
})
mux.HandleFunc("/keys", func(w http.ResponseWriter, req *http.Request) {
_, err := io.WriteString(w, jwks)
if err != nil {
w.WriteHeader(500)
}
})
t.Cleanup(server.Close)
return server
}
type errReader int
func (errReader) Read([]byte) (int, error) {

File diff suppressed because it is too large Load Diff

@ -130,7 +130,7 @@ func (m *MockProvisioner) GetName() string {
return m.Mret1.(string)
}
// AuthorizeOrderIdentifiers mock
// AuthorizeOrderIdentifier mock
func (m *MockProvisioner) AuthorizeOrderIdentifier(ctx context.Context, identifier provisioner.ACMEIdentifier) error {
if m.MauthorizeOrderIdentifier != nil {
return m.MauthorizeOrderIdentifier(ctx, identifier)

@ -2,6 +2,7 @@ package acme
import (
"context"
"database/sql"
"github.com/pkg/errors"
)
@ -15,7 +16,7 @@ var ErrNotFound = errors.New("not found")
// IsErrNotFound returns true if the error is a "not found" error. Returns false
// otherwise.
func IsErrNotFound(err error) bool {
return errors.Is(err, ErrNotFound)
return errors.Is(err, ErrNotFound) || errors.Is(err, sql.ErrNoRows)
}
// DB is the DB interface expected by the step-ca ACME API.
@ -53,13 +54,6 @@ type DB interface {
GetOrder(ctx context.Context, id string) (*Order, error)
GetOrdersByAccountID(ctx context.Context, accountID string) ([]string, error)
UpdateOrder(ctx context.Context, o *Order) error
// TODO(hs): put in a different interface
GetAllOrdersByAccountID(ctx context.Context, accountID string) ([]string, error)
CreateDpopToken(ctx context.Context, orderID string, dpop map[string]interface{}) error
GetDpopToken(ctx context.Context, orderID string) (map[string]interface{}, error)
CreateOidcToken(ctx context.Context, orderID string, idToken map[string]interface{}) error
GetOidcToken(ctx context.Context, orderID string) (map[string]interface{}, error)
}
type dbKey struct{}
@ -125,12 +119,6 @@ type MockDB struct {
MockGetOrdersByAccountID func(ctx context.Context, accountID string) ([]string, error)
MockUpdateOrder func(ctx context.Context, o *Order) error
MockGetAllOrdersByAccountID func(ctx context.Context, accountID string) ([]string, error)
MockGetDpopToken func(ctx context.Context, orderID string) (map[string]interface{}, error)
MockCreateDpopToken func(ctx context.Context, orderID string, dpop map[string]interface{}) error
MockGetOidcToken func(ctx context.Context, orderID string) (map[string]interface{}, error)
MockCreateOidcToken func(ctx context.Context, orderID string, idToken map[string]interface{}) error
MockRet1 interface{}
MockError error
}
@ -404,49 +392,3 @@ func (m *MockDB) GetOrdersByAccountID(ctx context.Context, accID string) ([]stri
}
return m.MockRet1.([]string), m.MockError
}
// GetAllOrdersByAccountID returns a list of any order IDs owned by the account.
func (m *MockDB) GetAllOrdersByAccountID(ctx context.Context, accountID string) ([]string, error) {
if m.MockGetAllOrdersByAccountID != nil {
return m.MockGetAllOrdersByAccountID(ctx, accountID)
} else if m.MockError != nil {
return nil, m.MockError
}
return m.MockRet1.([]string), m.MockError
}
// GetDpop retrieves a DPoP from the database.
func (m *MockDB) GetDpopToken(ctx context.Context, orderID string) (map[string]any, error) {
if m.MockGetDpopToken != nil {
return m.MockGetDpopToken(ctx, orderID)
} else if m.MockError != nil {
return nil, m.MockError
}
return m.MockRet1.(map[string]any), m.MockError
}
// CreateDpop creates DPoP resources and saves them to the DB.
func (m *MockDB) CreateDpopToken(ctx context.Context, orderID string, dpop map[string]any) error {
if m.MockCreateDpopToken != nil {
return m.MockCreateDpopToken(ctx, orderID, dpop)
}
return m.MockError
}
// GetOidcToken retrieves an oidc token from the database.
func (m *MockDB) GetOidcToken(ctx context.Context, orderID string) (map[string]any, error) {
if m.MockGetOidcToken != nil {
return m.MockGetOidcToken(ctx, orderID)
} else if m.MockError != nil {
return nil, m.MockError
}
return m.MockRet1.(map[string]any), m.MockError
}
// CreateOidcToken creates oidc token resources and saves them to the DB.
func (m *MockDB) CreateOidcToken(ctx context.Context, orderID string, idToken map[string]any) error {
if m.MockCreateOidcToken != nil {
return m.MockCreateOidcToken(ctx, orderID, idToken)
}
return m.MockError
}

@ -18,6 +18,7 @@ type dbAccount struct {
Contact []string `json:"contact,omitempty"`
Status acme.Status `json:"status"`
LocationPrefix string `json:"locationPrefix"`
ProvisionerID string `json:"provisionerID,omitempty"`
ProvisionerName string `json:"provisionerName"`
CreatedAt time.Time `json:"createdAt"`
DeactivatedAt time.Time `json:"deactivatedAt"`
@ -69,6 +70,7 @@ func (db *DB) GetAccount(ctx context.Context, id string) (*acme.Account, error)
Key: dbacc.Key,
ID: dbacc.ID,
LocationPrefix: dbacc.LocationPrefix,
ProvisionerID: dbacc.ProvisionerID,
ProvisionerName: dbacc.ProvisionerName,
}, nil
}
@ -97,6 +99,7 @@ func (db *DB) CreateAccount(ctx context.Context, acc *acme.Account) error {
Status: acc.Status,
CreatedAt: clock.Now(),
LocationPrefix: acc.LocationPrefix,
ProvisionerID: acc.ProvisionerID,
ProvisionerName: acc.ProvisionerName,
}

@ -68,12 +68,14 @@ func TestDB_getDBAccount(t *testing.T) {
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
dbacc := &dbAccount{
ID: accID,
Status: acme.StatusDeactivated,
CreatedAt: now,
DeactivatedAt: now,
Contact: []string{"foo", "bar"},
Key: jwk,
ID: accID,
Status: acme.StatusDeactivated,
CreatedAt: now,
DeactivatedAt: now,
Contact: []string{"foo", "bar"},
Key: jwk,
ProvisionerID: "73d2c0f1-9753-448b-9b48-bf00fe434681",
ProvisionerName: "acme",
}
b, err := json.Marshal(dbacc)
assert.FatalError(t, err)

@ -19,7 +19,6 @@ type dbChallenge struct {
Status acme.Status `json:"status"`
Token string `json:"token"`
Value string `json:"value"`
Target string `json:"target,omitempty"`
ValidatedAt string `json:"validatedAt"`
CreatedAt time.Time `json:"createdAt"`
Error *acme.Error `json:"error"` // TODO(hs): a bit dangerous; should become db-specific type
@ -62,7 +61,6 @@ func (db *DB) CreateChallenge(ctx context.Context, ch *acme.Challenge) error {
Token: ch.Token,
CreatedAt: clock.Now(),
Type: ch.Type,
Target: ch.Target,
}
return db.save(ctx, ch.ID, dbch, nil, "challenge", challengeTable)
@ -86,7 +84,6 @@ func (db *DB) GetChallenge(ctx context.Context, id, authzID string) (*acme.Chall
Token: dbch.Token,
Error: dbch.Error,
ValidatedAt: dbch.ValidatedAt,
Target: dbch.Target,
}
return ch, nil
}

@ -23,8 +23,6 @@ var (
externalAccountKeyTable = []byte("acme_external_account_keys")
externalAccountKeyIDsByReferenceTable = []byte("acme_external_account_keyID_reference_index")
externalAccountKeyIDsByProvisionerIDTable = []byte("acme_external_account_keyID_provisionerID_index")
wireDpopTokenTable = []byte("wire_acme_dpop_token")
wireOidcTokenTable = []byte("wire_acme_oidc_token")
)
// DB is a struct that implements the AcmeDB interface.
@ -38,11 +36,11 @@ func New(db nosqlDB.DB) (*DB, error) {
challengeTable, nonceTable, orderTable, ordersByAccountIDTable,
certTable, certBySerialTable, externalAccountKeyTable,
externalAccountKeyIDsByReferenceTable, externalAccountKeyIDsByProvisionerIDTable,
wireDpopTokenTable, wireOidcTokenTable,
}
for _, b := range tables {
if err := db.CreateTable(b); err != nil {
return nil, errors.Wrapf(err, "error creating table %s", string(b))
return nil, errors.Wrapf(err, "error creating table %s",
string(b))
}
}
return &DB{db}, nil

@ -98,7 +98,7 @@ func (db *DB) CreateOrder(ctx context.Context, o *acme.Order) error {
return err
}
_, err = db.updateAddOrderIDs(ctx, o.AccountID, false, o.ID)
_, err = db.updateAddOrderIDs(ctx, o.AccountID, o.ID)
if err != nil {
return err
}
@ -117,11 +117,10 @@ func (db *DB) UpdateOrder(ctx context.Context, o *acme.Order) error {
nu.Status = o.Status
nu.Error = o.Error
nu.CertificateID = o.CertificateID
return db.save(ctx, old.ID, nu, old, "order", orderTable)
}
func (db *DB) updateAddOrderIDs(ctx context.Context, accID string, includeReadyOrders bool, addOids ...string) ([]string, error) {
func (db *DB) updateAddOrderIDs(ctx context.Context, accID string, addOids ...string) ([]string, error) {
ordersByAccountMux.Lock()
defer ordersByAccountMux.Unlock()
@ -152,8 +151,7 @@ func (db *DB) updateAddOrderIDs(ctx context.Context, accID string, includeReadyO
if err = o.UpdateStatus(ctx, db); err != nil {
return nil, acme.WrapErrorISE(err, "error updating order %s for account %s", oid, accID)
}
if o.Status == acme.StatusPending || (o.Status == acme.StatusReady && includeReadyOrders) {
if o.Status == acme.StatusPending {
pendOids = append(pendOids, oid)
}
}
@ -185,10 +183,5 @@ func (db *DB) updateAddOrderIDs(ctx context.Context, accID string, includeReadyO
// GetOrdersByAccountID returns a list of order IDs owned by the account.
func (db *DB) GetOrdersByAccountID(ctx context.Context, accID string) ([]string, error) {
return db.updateAddOrderIDs(ctx, accID, false)
}
// GetAllOrdersByAccountID returns a list of any order IDs owned by the account.
func (db *DB) GetAllOrdersByAccountID(ctx context.Context, accID string) ([]string, error) {
return db.updateAddOrderIDs(ctx, accID, true)
return db.updateAddOrderIDs(ctx, accID)
}

@ -997,9 +997,9 @@ func TestDB_updateAddOrderIDs(t *testing.T) {
err error
)
if tc.addOids == nil {
res, err = d.updateAddOrderIDs(context.Background(), accID, false)
res, err = d.updateAddOrderIDs(context.Background(), accID)
} else {
res, err = d.updateAddOrderIDs(context.Background(), accID, false, tc.addOids...)
res, err = d.updateAddOrderIDs(context.Background(), accID, tc.addOids...)
}
if err != nil {

@ -1,121 +0,0 @@
package nosql
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/smallstep/certificates/acme"
"github.com/smallstep/nosql"
)
type dbDpopToken struct {
ID string `json:"id"`
Content []byte `json:"content"`
CreatedAt time.Time `json:"createdAt"`
}
// getDBDpopToken retrieves and unmarshals an DPoP type from the database.
func (db *DB) getDBDpopToken(_ context.Context, orderID string) (*dbDpopToken, error) {
b, err := db.db.Get(wireDpopTokenTable, []byte(orderID))
if err != nil {
if nosql.IsErrNotFound(err) {
return nil, acme.NewError(acme.ErrorMalformedType, "dpop token %q not found", orderID)
}
return nil, fmt.Errorf("failed loading dpop token %q: %w", orderID, err)
}
d := new(dbDpopToken)
if err := json.Unmarshal(b, d); err != nil {
return nil, fmt.Errorf("failed unmarshaling dpop token %q into dbDpopToken: %w", orderID, err)
}
return d, nil
}
// GetDpopToken retrieves an DPoP from the database.
func (db *DB) GetDpopToken(ctx context.Context, orderID string) (map[string]any, error) {
dbDpop, err := db.getDBDpopToken(ctx, orderID)
if err != nil {
return nil, err
}
dpop := make(map[string]any)
err = json.Unmarshal(dbDpop.Content, &dpop)
return dpop, err
}
// CreateDpopToken creates DPoP resources and saves them to the DB.
func (db *DB) CreateDpopToken(ctx context.Context, orderID string, dpop map[string]any) error {
content, err := json.Marshal(dpop)
if err != nil {
return fmt.Errorf("failed marshaling dpop token: %w", err)
}
now := clock.Now()
dbDpop := &dbDpopToken{
ID: orderID,
Content: content,
CreatedAt: now,
}
if err := db.save(ctx, orderID, dbDpop, nil, "dpop", wireDpopTokenTable); err != nil {
return fmt.Errorf("failed saving dpop token: %w", err)
}
return nil
}
type dbOidcToken struct {
ID string `json:"id"`
Content []byte `json:"content"`
CreatedAt time.Time `json:"createdAt"`
}
// getDBOidcToken retrieves and unmarshals an OIDC id token type from the database.
func (db *DB) getDBOidcToken(_ context.Context, orderID string) (*dbOidcToken, error) {
b, err := db.db.Get(wireOidcTokenTable, []byte(orderID))
if err != nil {
if nosql.IsErrNotFound(err) {
return nil, acme.NewError(acme.ErrorMalformedType, "oidc token %q not found", orderID)
}
return nil, fmt.Errorf("failed loading oidc token %q: %w", orderID, err)
}
o := new(dbOidcToken)
if err := json.Unmarshal(b, o); err != nil {
return nil, fmt.Errorf("failed unmarshaling oidc token %q into dbOidcToken: %w", orderID, err)
}
return o, nil
}
// GetOidcToken retrieves an oidc token from the database.
func (db *DB) GetOidcToken(ctx context.Context, orderID string) (map[string]any, error) {
dbOidc, err := db.getDBOidcToken(ctx, orderID)
if err != nil {
return nil, err
}
idToken := make(map[string]any)
err = json.Unmarshal(dbOidc.Content, &idToken)
return idToken, err
}
// CreateOidcToken creates oidc token resources and saves them to the DB.
func (db *DB) CreateOidcToken(ctx context.Context, orderID string, idToken map[string]any) error {
content, err := json.Marshal(idToken)
if err != nil {
return fmt.Errorf("failed marshaling oidc token: %w", err)
}
now := clock.Now()
dbOidc := &dbOidcToken{
ID: orderID,
Content: content,
CreatedAt: now,
}
if err := db.save(ctx, orderID, dbOidc, nil, "oidc", wireOidcTokenTable); err != nil {
return fmt.Errorf("failed saving oidc token: %w", err)
}
return nil
}

@ -1,394 +0,0 @@
package nosql
import (
"context"
"encoding/json"
"errors"
"testing"
"time"
"github.com/smallstep/certificates/acme"
certificatesdb "github.com/smallstep/certificates/db"
"github.com/smallstep/nosql"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDB_GetDpopToken(t *testing.T) {
type test struct {
db *DB
orderID string
expected map[string]any
expectedErr error
}
var tests = map[string]func(t *testing.T) test{
"fail/acme-not-found": func(t *testing.T) test {
dir := t.TempDir()
db, err := nosql.New("badgerv2", dir)
require.NoError(t, err)
return test{
db: &DB{
db: db,
},
orderID: "orderID",
expectedErr: &acme.Error{
Type: "urn:ietf:params:acme:error:malformed",
Status: 400,
Detail: "The request message was malformed",
Err: errors.New(`dpop token "orderID" not found`),
},
}
},
"fail/unmarshal-error": func(t *testing.T) test {
dir := t.TempDir()
db, err := nosql.New("badgerv2", dir)
require.NoError(t, err)
token := dbDpopToken{
ID: "orderID",
Content: []byte("{}"),
CreatedAt: time.Now(),
}
b, err := json.Marshal(token)
require.NoError(t, err)
err = db.Set(wireDpopTokenTable, []byte("orderID"), b[1:]) // start at index 1; corrupt JSON data
require.NoError(t, err)
return test{
db: &DB{
db: db,
},
orderID: "orderID",
expectedErr: errors.New(`failed unmarshaling dpop token "orderID" into dbDpopToken: invalid character ':' after top-level value`),
}
},
"fail/db.Get": func(t *testing.T) test {
db := &certificatesdb.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equal(t, wireDpopTokenTable, bucket)
assert.Equal(t, []byte("orderID"), key)
return nil, errors.New("fail")
},
}
return test{
db: &DB{
db: db,
},
orderID: "orderID",
expectedErr: errors.New(`failed loading dpop token "orderID": fail`),
}
},
"ok": func(t *testing.T) test {
dir := t.TempDir()
db, err := nosql.New("badgerv2", dir)
require.NoError(t, err)
token := dbDpopToken{
ID: "orderID",
Content: []byte(`{"sub": "wireapp://guVX5xeFS3eTatmXBIyA4A!7a41cf5b79683410@wire.com"}`),
CreatedAt: time.Now(),
}
b, err := json.Marshal(token)
require.NoError(t, err)
err = db.Set(wireDpopTokenTable, []byte("orderID"), b)
require.NoError(t, err)
return test{
db: &DB{
db: db,
},
orderID: "orderID",
expected: map[string]any{
"sub": "wireapp://guVX5xeFS3eTatmXBIyA4A!7a41cf5b79683410@wire.com",
},
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
got, err := tc.db.GetDpopToken(context.Background(), tc.orderID)
if tc.expectedErr != nil {
assert.EqualError(t, err, tc.expectedErr.Error())
ae := &acme.Error{}
if errors.As(err, &ae) {
ee := &acme.Error{}
require.True(t, errors.As(tc.expectedErr, &ee))
assert.Equal(t, ee.Detail, ae.Detail)
assert.Equal(t, ee.Type, ae.Type)
assert.Equal(t, ee.Status, ae.Status)
}
assert.Nil(t, got)
return
}
assert.NoError(t, err)
assert.Equal(t, tc.expected, got)
})
}
}
func TestDB_CreateDpopToken(t *testing.T) {
type test struct {
db *DB
orderID string
dpop map[string]any
expectedErr error
}
var tests = map[string]func(t *testing.T) test{
"fail/db.Save": func(t *testing.T) test {
db := &certificatesdb.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
assert.Equal(t, wireDpopTokenTable, bucket)
assert.Equal(t, []byte("orderID"), key)
return nil, false, errors.New("fail")
},
}
return test{
db: &DB{
db: db,
},
orderID: "orderID",
dpop: map[string]any{
"sub": "wireapp://guVX5xeFS3eTatmXBIyA4A!7a41cf5b79683410@wire.com",
},
expectedErr: errors.New("failed saving dpop token: error saving acme dpop: fail"),
}
},
"ok": func(t *testing.T) test {
dir := t.TempDir()
db, err := nosql.New("badgerv2", dir)
require.NoError(t, err)
return test{
db: &DB{
db: db,
},
orderID: "orderID",
dpop: map[string]any{
"sub": "wireapp://guVX5xeFS3eTatmXBIyA4A!7a41cf5b79683410@wire.com",
},
}
},
"ok/nil": func(t *testing.T) test {
dir := t.TempDir()
db, err := nosql.New("badgerv2", dir)
require.NoError(t, err)
return test{
db: &DB{
db: db,
},
orderID: "orderID",
dpop: nil,
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
err := tc.db.CreateDpopToken(context.Background(), tc.orderID, tc.dpop)
if tc.expectedErr != nil {
assert.EqualError(t, err, tc.expectedErr.Error())
return
}
assert.NoError(t, err)
dpop, err := tc.db.getDBDpopToken(context.Background(), tc.orderID)
require.NoError(t, err)
assert.Equal(t, tc.orderID, dpop.ID)
var m map[string]any
err = json.Unmarshal(dpop.Content, &m)
require.NoError(t, err)
assert.Equal(t, tc.dpop, m)
})
}
}
func TestDB_GetOidcToken(t *testing.T) {
type test struct {
db *DB
orderID string
expected map[string]any
expectedErr error
}
var tests = map[string]func(t *testing.T) test{
"fail/acme-not-found": func(t *testing.T) test {
dir := t.TempDir()
db, err := nosql.New("badgerv2", dir)
require.NoError(t, err)
return test{
db: &DB{
db: db,
},
orderID: "orderID",
expectedErr: &acme.Error{
Type: "urn:ietf:params:acme:error:malformed",
Status: 400,
Detail: "The request message was malformed",
Err: errors.New(`oidc token "orderID" not found`),
},
}
},
"fail/unmarshal-error": func(t *testing.T) test {
dir := t.TempDir()
db, err := nosql.New("badgerv2", dir)
require.NoError(t, err)
token := dbOidcToken{
ID: "orderID",
Content: []byte("{}"),
CreatedAt: time.Now(),
}
b, err := json.Marshal(token)
require.NoError(t, err)
err = db.Set(wireOidcTokenTable, []byte("orderID"), b[1:]) // start at index 1; corrupt JSON data
require.NoError(t, err)
return test{
db: &DB{
db: db,
},
orderID: "orderID",
expectedErr: errors.New(`failed unmarshaling oidc token "orderID" into dbOidcToken: invalid character ':' after top-level value`),
}
},
"fail/db.Get": func(t *testing.T) test {
db := &certificatesdb.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equal(t, wireOidcTokenTable, bucket)
assert.Equal(t, []byte("orderID"), key)
return nil, errors.New("fail")
},
}
return test{
db: &DB{
db: db,
},
orderID: "orderID",
expectedErr: errors.New(`failed loading oidc token "orderID": fail`),
}
},
"ok": func(t *testing.T) test {
dir := t.TempDir()
db, err := nosql.New("badgerv2", dir)
require.NoError(t, err)
token := dbOidcToken{
ID: "orderID",
Content: []byte(`{"name": "Alice Smith", "preferred_username": "@alice.smith"}`),
CreatedAt: time.Now(),
}
b, err := json.Marshal(token)
require.NoError(t, err)
err = db.Set(wireOidcTokenTable, []byte("orderID"), b)
require.NoError(t, err)
return test{
db: &DB{
db: db,
},
orderID: "orderID",
expected: map[string]any{
"name": "Alice Smith",
"preferred_username": "@alice.smith",
},
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
got, err := tc.db.GetOidcToken(context.Background(), tc.orderID)
if tc.expectedErr != nil {
assert.EqualError(t, err, tc.expectedErr.Error())
ae := &acme.Error{}
if errors.As(err, &ae) {
ee := &acme.Error{}
require.True(t, errors.As(tc.expectedErr, &ee))
assert.Equal(t, ee.Detail, ae.Detail)
assert.Equal(t, ee.Type, ae.Type)
assert.Equal(t, ee.Status, ae.Status)
}
assert.Nil(t, got)
return
}
assert.NoError(t, err)
assert.Equal(t, tc.expected, got)
})
}
}
func TestDB_CreateOidcToken(t *testing.T) {
type test struct {
db *DB
orderID string
oidc map[string]any
expectedErr error
}
var tests = map[string]func(t *testing.T) test{
"fail/db.Save": func(t *testing.T) test {
db := &certificatesdb.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
assert.Equal(t, wireOidcTokenTable, bucket)
assert.Equal(t, []byte("orderID"), key)
return nil, false, errors.New("fail")
},
}
return test{
db: &DB{
db: db,
},
orderID: "orderID",
oidc: map[string]any{
"name": "Alice Smith",
"preferred_username": "@alice.smith",
},
expectedErr: errors.New("failed saving oidc token: error saving acme oidc: fail"),
}
},
"ok": func(t *testing.T) test {
dir := t.TempDir()
db, err := nosql.New("badgerv2", dir)
require.NoError(t, err)
return test{
db: &DB{
db: db,
},
orderID: "orderID",
oidc: map[string]any{
"name": "Alice Smith",
"preferred_username": "@alice.smith",
},
}
},
"ok/nil": func(t *testing.T) test {
dir := t.TempDir()
db, err := nosql.New("badgerv2", dir)
require.NoError(t, err)
return test{
db: &DB{
db: db,
},
orderID: "orderID",
oidc: nil,
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
err := tc.db.CreateOidcToken(context.Background(), tc.orderID, tc.oidc)
if tc.expectedErr != nil {
assert.EqualError(t, err, tc.expectedErr.Error())
return
}
assert.NoError(t, err)
oidc, err := tc.db.getDBOidcToken(context.Background(), tc.orderID)
require.NoError(t, err)
assert.Equal(t, tc.orderID, oidc.ID)
var m map[string]any
err = json.Unmarshal(oidc.Content, &m)
require.NoError(t, err)
assert.Equal(t, tc.oidc, m)
})
}
}

@ -0,0 +1,32 @@
package acme
import (
"database/sql"
"errors"
"fmt"
"testing"
)
func TestIsErrNotFound(t *testing.T) {
type args struct {
err error
}
tests := []struct {
name string
args args
want bool
}{
{"true ErrNotFound", args{ErrNotFound}, true},
{"true sql.ErrNoRows", args{sql.ErrNoRows}, true},
{"true wrapped ErrNotFound", args{fmt.Errorf("something failed: %w", ErrNotFound)}, true},
{"true wrapped sql.ErrNoRows", args{fmt.Errorf("something failed: %w", sql.ErrNoRows)}, true},
{"false other", args{errors.New("not found")}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsErrNotFound(tt.args.err); got != tt.want {
t.Errorf("IsErrNotFound() = %v, want %v", got, tt.want)
}
})
}
}

@ -424,7 +424,7 @@ func (e *Error) ToLog() (interface{}, error) {
}
// Render implements render.RenderableError for Error.
func (e *Error) Render(w http.ResponseWriter) {
func (e *Error) Render(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/problem+json")
render.JSONStatus(w, e, e.StatusCode())
render.JSONStatus(w, r, e, e.StatusCode())
}

@ -186,19 +186,19 @@ func (l *linker) Middleware(next http.Handler) http.Handler {
nameEscaped := chi.URLParam(r, "provisionerID")
name, err := url.PathUnescape(nameEscaped)
if err != nil {
render.Error(w, WrapErrorISE(err, "error url unescaping provisioner name '%s'", nameEscaped))
render.Error(w, r, WrapErrorISE(err, "error url unescaping provisioner name '%s'", nameEscaped))
return
}
p, err := authority.MustFromContext(ctx).LoadProvisionerByName(name)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
acmeProv, ok := p.(*provisioner.ACME)
if !ok {
render.Error(w, NewError(ErrorAccountDoesNotExistType, "provisioner must be of type ACME"))
render.Error(w, r, NewError(ErrorAccountDoesNotExistType, "provisioner must be of type ACME"))
return
}

@ -5,20 +5,15 @@ import (
"context"
"crypto/subtle"
"crypto/x509"
"encoding/asn1"
"encoding/json"
"fmt"
"net"
"net/url"
"sort"
"strings"
"time"
"github.com/smallstep/certificates/authority/provisioner"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/x509util"
"github.com/smallstep/certificates/acme/wire"
"github.com/smallstep/certificates/authority/provisioner"
)
type IdentifierType string
@ -31,10 +26,6 @@ const (
// PermanentIdentifier is the ACME permanent-identifier identifier type
// defined in https://datatracker.ietf.org/doc/html/draft-bweeks-acme-device-attest-00
PermanentIdentifier IdentifierType = "permanent-identifier"
// WireUser is the Wire user identifier type
WireUser IdentifierType = "wireapp-user"
// WireDevice is the Wire device identifier type
WireDevice IdentifierType = "wireapp-device"
)
// Identifier encodes the type that an order pertains to.
@ -130,15 +121,13 @@ func (o *Order) UpdateStatus(ctx context.Context, db DB) error {
default:
return NewErrorISE("unrecognized order status: %s", o.Status)
}
if err := db.UpdateOrder(ctx, o); err != nil {
return WrapErrorISE(err, "error updating order")
}
return nil
}
// getKeyFingerprint returns a fingerprint from the list of authorizations. This
// getAuthorizationFingerprint returns a fingerprint from the list of authorizations. This
// fingerprint is used on the device-attest-01 flow to verify the attestation
// certificate public key with the CSR public key.
//
@ -207,28 +196,7 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques
// Template data
data := x509util.NewTemplateData()
if o.containsWireIdentifiers() {
subject, err := createWireSubject(o, csr)
if err != nil {
return fmt.Errorf("failed creating Wire subject: %w", err)
}
data.SetSubject(subject)
// Inject Wire's custom challenges into the template once they have been validated
dpop, err := db.GetDpopToken(ctx, o.ID)
if err != nil {
return fmt.Errorf("failed getting Wire DPoP token: %w", err)
}
data.Set("Dpop", dpop)
oidc, err := db.GetOidcToken(ctx, o.ID)
if err != nil {
return fmt.Errorf("failed getting Wire OIDC token: %w", err)
}
data.Set("Oidc", oidc)
} else {
data.SetCommonName(csr.Subject.CommonName)
}
data.SetCommonName(csr.Subject.CommonName)
// Custom sign options passed to authority.Sign
var extraOptions []provisioner.SignOption
@ -315,76 +283,15 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques
o.CertificateID = cert.ID
o.Status = StatusValid
if err = db.UpdateOrder(ctx, o); err != nil {
return WrapErrorISE(err, "error updating order %s", o.ID)
}
return nil
}
// containsWireIdentifiers checks if [Order] contains ACME
// identifiers for the WireUser or WireDevice types.
func (o *Order) containsWireIdentifiers() bool {
for _, i := range o.Identifiers {
if i.Type == WireUser || i.Type == WireDevice {
return true
}
}
return false
}
// createWireSubject creates the subject for an [Order] with WireUser identifiers.
func createWireSubject(o *Order, csr *x509.CertificateRequest) (subject x509util.Subject, err error) {
wireUserIDs, wireDeviceIDs, otherIDs := 0, 0, 0
for _, identifier := range o.Identifiers {
switch identifier.Type {
case WireUser:
wireID, err := wire.ParseUserID(identifier.Value)
if err != nil {
return subject, NewErrorISE("unmarshal wireID: %s", err)
}
// TODO: temporarily using a custom OIDC for carrying the display name without having it listed as a DNS SAN.
// reusing LDAP's OID for diplay name see http://oid-info.com/get/2.16.840.1.113730.3.1.241
displayNameOid := asn1.ObjectIdentifier{2, 16, 840, 1, 113730, 3, 1, 241}
var foundDisplayName = false
for _, entry := range csr.Subject.Names {
if entry.Type.Equal(displayNameOid) {
foundDisplayName = true
displayName := entry.Value.(string)
if displayName != wireID.Name {
return subject, NewErrorISE("expected displayName %v, found %v", wireID.Name, displayName)
}
}
}
if !foundDisplayName {
return subject, NewErrorISE("CSR must contain the display name in '2.16.840.1.113730.3.1.241' OID")
}
if len(csr.Subject.Organization) == 0 || !strings.EqualFold(csr.Subject.Organization[0], wireID.Domain) {
return subject, NewErrorISE("expected Organization [%s], found %v", wireID.Domain, csr.Subject.Organization)
}
subject.CommonName = wireID.Name
subject.Organization = []string{wireID.Domain}
wireUserIDs++
case WireDevice:
wireDeviceIDs++
default:
otherIDs++
}
}
if otherIDs > 0 || wireUserIDs != 1 && wireDeviceIDs != 1 {
return subject, NewErrorISE("order must have exactly one WireUser and WireDevice identifier")
}
return
}
func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativeName, error) {
var sans []x509util.SubjectAlternativeName
if len(csr.EmailAddresses) > 0 {
if len(csr.EmailAddresses) > 0 || len(csr.URIs) > 0 {
return sans, NewError(ErrorBadCSRType, "Only DNS names and IP addresses are allowed")
}
@ -392,8 +299,7 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ
orderNames := make([]string, numberOfIdentifierType(DNS, o.Identifiers))
orderIPs := make([]net.IP, numberOfIdentifierType(IP, o.Identifiers))
orderPIDs := make([]string, numberOfIdentifierType(PermanentIdentifier, o.Identifiers))
tmpOrderURIs := make([]*url.URL, numberOfIdentifierType(WireUser, o.Identifiers)+numberOfIdentifierType(WireDevice, o.Identifiers))
indexDNS, indexIP, indexPID, indexURI := 0, 0, 0, 0
indexDNS, indexIP, indexPID := 0, 0, 0
for _, n := range o.Identifiers {
switch n.Type {
case DNS:
@ -405,37 +311,14 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ
case PermanentIdentifier:
orderPIDs[indexPID] = n.Value
indexPID++
case WireUser:
wireID, err := wire.ParseUserID(n.Value)
if err != nil {
return sans, NewErrorISE("unsupported identifier value in order: %s", n.Value)
}
handle, err := url.Parse(wireID.Handle)
if err != nil {
return sans, NewErrorISE("handle must be a URI: %s", wireID.Handle)
}
tmpOrderURIs[indexURI] = handle
indexURI++
case WireDevice:
wireID, err := wire.ParseDeviceID(n.Value)
if err != nil {
return sans, NewErrorISE("unsupported identifier value in order: %s", n.Value)
}
clientID, err := url.Parse(wireID.ClientID)
if err != nil {
return sans, NewErrorISE("clientId must be a URI: %s", wireID.ClientID)
}
tmpOrderURIs[indexURI] = clientID
indexURI++
default:
return sans, NewErrorISE("unsupported identifier type in order: %s", n.Type)
}
}
orderNames = uniqueSortedLowerNames(orderNames)
orderIPs = uniqueSortedIPs(orderIPs)
orderURIs := uniqueSortedURIStrings(tmpOrderURIs)
totalNumberOfSANs := len(csr.DNSNames) + len(csr.IPAddresses) + len(csr.URIs)
totalNumberOfSANs := len(csr.DNSNames) + len(csr.IPAddresses)
sans = make([]x509util.SubjectAlternativeName, totalNumberOfSANs)
index := 0
@ -478,26 +361,6 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ
index++
}
if len(csr.URIs) != len(tmpOrderURIs) {
return sans, NewError(ErrorBadCSRType, "CSR URIs do not match identifiers exactly: "+
"CSR URIs = %v, Order URIs = %v", csr.URIs, tmpOrderURIs)
}
// sort URI list
csrURIs := uniqueSortedURIStrings(csr.URIs)
for i := range csrURIs {
if csrURIs[i] != orderURIs[i] {
return sans, NewError(ErrorBadCSRType, "CSR URIs do not match identifiers exactly: "+
"CSR URIs = %v, Order URIs = %v", csr.URIs, tmpOrderURIs)
}
sans[index] = x509util.SubjectAlternativeName{
Type: x509util.URIType,
Value: orderURIs[i],
}
index++
}
return sans, nil
}
@ -567,21 +430,6 @@ func uniqueSortedLowerNames(names []string) (unique []string) {
}
unique = make([]string, 0, len(nameMap))
for name := range nameMap {
if len(name) > 0 {
unique = append(unique, name)
}
}
sort.Strings(unique)
return
}
func uniqueSortedURIStrings(uris []*url.URL) (unique []string) {
uriMap := make(map[string]struct{}, len(uris))
for _, name := range uris {
uriMap[name.String()] = struct{}{}
}
unique = make([]string, 0, len(uriMap))
for name := range uriMap {
unique = append(unique, name)
}
sort.Strings(unique)

@ -9,6 +9,7 @@ import (
"encoding/json"
"fmt"
"net"
"net/url"
"reflect"
"testing"
"time"
@ -1701,6 +1702,25 @@ func TestOrder_sans(t *testing.T) {
want: []x509util.SubjectAlternativeName{},
err: NewError(ErrorBadCSRType, "Only DNS names and IP addresses are allowed"),
},
{
name: "fail/invalid-alternative-name-uri",
fields: fields{
Identifiers: []Identifier{},
},
csr: &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: "foo.internal",
},
URIs: []*url.URL{
{
Scheme: "https://",
Host: "smallstep.com",
},
},
},
want: []x509util.SubjectAlternativeName{},
err: NewError(ErrorBadCSRType, "Only DNS names and IP addresses are allowed"),
},
{
name: "fail/error-names-length-mismatch",
fields: fields{

@ -1,92 +0,0 @@
package wire
import (
"encoding/json"
"errors"
"fmt"
"strings"
"go.step.sm/crypto/kms/uri"
)
type UserID struct {
Name string `json:"name,omitempty"`
Domain string `json:"domain,omitempty"`
Handle string `json:"handle,omitempty"`
}
type DeviceID struct {
Name string `json:"name,omitempty"`
Domain string `json:"domain,omitempty"`
ClientID string `json:"client-id,omitempty"`
Handle string `json:"handle,omitempty"`
}
func ParseUserID(value string) (id UserID, err error) {
if err = json.Unmarshal([]byte(value), &id); err != nil {
return
}
switch {
case id.Handle == "":
err = errors.New("handle must not be empty")
case id.Name == "":
err = errors.New("name must not be empty")
case id.Domain == "":
err = errors.New("domain must not be empty")
}
return
}
func ParseDeviceID(value string) (id DeviceID, err error) {
if err = json.Unmarshal([]byte(value), &id); err != nil {
return
}
switch {
case id.Handle == "":
err = errors.New("handle must not be empty")
case id.Name == "":
err = errors.New("name must not be empty")
case id.Domain == "":
err = errors.New("domain must not be empty")
case id.ClientID == "":
err = errors.New("client-id must not be empty")
}
return
}
type ClientID struct {
Scheme string
Username string
DeviceID string
Domain string
}
// ParseClientID parses a Wire clientID. The ClientID format is as follows:
//
// "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com",
//
// where '!' is used as a separator between the user id & device id.
func ParseClientID(clientID string) (ClientID, error) {
clientIDURI, err := uri.Parse(clientID)
if err != nil {
return ClientID{}, fmt.Errorf("invalid Wire client ID URI %q: %w", clientID, err)
}
if clientIDURI.Scheme != "wireapp" {
return ClientID{}, fmt.Errorf("invalid Wire client ID scheme %q; expected \"wireapp\"", clientIDURI.Scheme)
}
fullUsername := clientIDURI.User.Username()
parts := strings.SplitN(fullUsername, "!", 2)
if len(parts) != 2 {
return ClientID{}, fmt.Errorf("invalid Wire client ID username %q", fullUsername)
}
return ClientID{
Scheme: clientIDURI.Scheme,
Username: parts[0],
DeviceID: parts[1],
Domain: clientIDURI.Host,
}, nil
}

@ -1,100 +0,0 @@
package wire
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseUserID(t *testing.T) {
ok := `{"name": "Alice Smith", "domain": "wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`
failJSON := `{"name": }`
emptyHandle := `{"name": "Alice Smith", "domain": "wire.com", "handle": ""}`
emptyName := `{"name": "", "domain": "wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`
emptyDomain := `{"name": "Alice Smith", "domain": "", "handle": "wireapp://%40alice_wire@wire.com"}`
tests := []struct {
name string
value string
wantWireID UserID
wantErr bool
}{
{name: "ok", value: ok, wantWireID: UserID{Name: "Alice Smith", Domain: "wire.com", Handle: "wireapp://%40alice_wire@wire.com"}},
{name: "fail/json", value: failJSON, wantErr: true},
{name: "fail/empty-handle", value: emptyHandle, wantErr: true},
{name: "fail/empty-name", value: emptyName, wantErr: true},
{name: "fail/empty-domain", value: emptyDomain, wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotWireID, err := ParseUserID(tt.value)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.wantWireID, gotWireID)
})
}
}
func TestParseDeviceID(t *testing.T) {
ok := `{"name": "device", "domain": "wire.com", "client-id": "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`
failJSON := `{"name": }`
emptyHandle := `{"name": "device", "domain": "wire.com", "client-id": "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": ""}`
emptyName := `{"name": "", "domain": "wire.com", "client-id": "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`
emptyDomain := `{"name": "device", "domain": "", "client-id": "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`
emptyClientID := `{"name": "device", "domain": "wire.com", "client-id": "", "handle": "wireapp://%40alice_wire@wire.com"}`
tests := []struct {
name string
value string
wantWireID DeviceID
wantErr bool
}{
{name: "ok", value: ok, wantWireID: DeviceID{Name: "device", Domain: "wire.com", ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", Handle: "wireapp://%40alice_wire@wire.com"}},
{name: "fail/json", value: failJSON, wantErr: true},
{name: "fail/empty-handle", value: emptyHandle, wantErr: true},
{name: "fail/empty-name", value: emptyName, wantErr: true},
{name: "fail/empty-domain", value: emptyDomain, wantErr: true},
{name: "fail/empty-client-id", value: emptyClientID, wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotWireID, err := ParseDeviceID(tt.value)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.wantWireID, gotWireID)
})
}
}
func TestParseClientID(t *testing.T) {
tests := []struct {
name string
clientID string
want ClientID
expectedErr error
}{
{name: "ok", clientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", want: ClientID{Scheme: "wireapp", Username: "CzbfFjDOQrenCbDxVmgnFw", DeviceID: "594930e9d50bb175", Domain: "wire.com"}},
{name: "fail/uri", clientID: "bla", expectedErr: errors.New(`invalid Wire client ID URI "bla": error parsing bla: scheme is missing`)},
{name: "fail/scheme", clientID: "not-wireapp://bla.com", expectedErr: errors.New(`invalid Wire client ID scheme "not-wireapp"; expected "wireapp"`)},
{name: "fail/username", clientID: "wireapp://user@wire.com", expectedErr: errors.New(`invalid Wire client ID username "user"`)},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseClientID(tt.clientID)
if tt.expectedErr != nil {
assert.EqualError(t, err, tt.expectedErr.Error())
return
}
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}

@ -353,15 +353,15 @@ func Route(r Router) {
// Version is an HTTP handler that returns the version of the server.
func Version(w http.ResponseWriter, r *http.Request) {
v := mustAuthority(r.Context()).Version()
render.JSON(w, VersionResponse{
render.JSON(w, r, VersionResponse{
Version: v.Version,
RequireClientAuthentication: v.RequireClientAuthentication,
})
}
// Health is an HTTP handler that returns the status of the server.
func Health(w http.ResponseWriter, _ *http.Request) {
render.JSON(w, HealthResponse{Status: "ok"})
func Health(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, HealthResponse{Status: "ok"})
}
// Root is an HTTP handler that using the SHA256 from the URL, returns the root
@ -372,11 +372,11 @@ func Root(w http.ResponseWriter, r *http.Request) {
// Load root certificate with the
cert, err := mustAuthority(r.Context()).Root(sum)
if err != nil {
render.Error(w, errs.Wrapf(http.StatusNotFound, err, "%s was not found", r.RequestURI))
render.Error(w, r, errs.Wrapf(http.StatusNotFound, err, "%s was not found", r.RequestURI))
return
}
render.JSON(w, &RootResponse{RootPEM: Certificate{cert}})
render.JSON(w, r, &RootResponse{RootPEM: Certificate{cert}})
}
func certChainToPEM(certChain []*x509.Certificate) []Certificate {
@ -391,17 +391,17 @@ func certChainToPEM(certChain []*x509.Certificate) []Certificate {
func Provisioners(w http.ResponseWriter, r *http.Request) {
cursor, limit, err := ParseCursor(r)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
p, next, err := mustAuthority(r.Context()).GetProvisioners(cursor, limit)
if err != nil {
render.Error(w, errs.InternalServerErr(err))
render.Error(w, r, errs.InternalServerErr(err))
return
}
render.JSON(w, &ProvisionersResponse{
render.JSON(w, r, &ProvisionersResponse{
Provisioners: p,
NextCursor: next,
})
@ -412,18 +412,18 @@ func ProvisionerKey(w http.ResponseWriter, r *http.Request) {
kid := chi.URLParam(r, "kid")
key, err := mustAuthority(r.Context()).GetEncryptedKey(kid)
if err != nil {
render.Error(w, errs.NotFoundErr(err))
render.Error(w, r, errs.NotFoundErr(err))
return
}
render.JSON(w, &ProvisionerKeyResponse{key})
render.JSON(w, r, &ProvisionerKeyResponse{key})
}
// Roots returns all the root certificates for the CA.
func Roots(w http.ResponseWriter, r *http.Request) {
roots, err := mustAuthority(r.Context()).GetRoots()
if err != nil {
render.Error(w, errs.ForbiddenErr(err, "error getting roots"))
render.Error(w, r, errs.ForbiddenErr(err, "error getting roots"))
return
}
@ -432,7 +432,7 @@ func Roots(w http.ResponseWriter, r *http.Request) {
certs[i] = Certificate{roots[i]}
}
render.JSONStatus(w, &RootsResponse{
render.JSONStatus(w, r, &RootsResponse{
Certificates: certs,
}, http.StatusCreated)
}
@ -441,7 +441,7 @@ func Roots(w http.ResponseWriter, r *http.Request) {
func RootsPEM(w http.ResponseWriter, r *http.Request) {
roots, err := mustAuthority(r.Context()).GetRoots()
if err != nil {
render.Error(w, errs.InternalServerErr(err))
render.Error(w, r, errs.InternalServerErr(err))
return
}
@ -454,7 +454,7 @@ func RootsPEM(w http.ResponseWriter, r *http.Request) {
})
if _, err := w.Write(block); err != nil {
log.Error(w, err)
log.Error(w, r, err)
return
}
}
@ -464,7 +464,7 @@ func RootsPEM(w http.ResponseWriter, r *http.Request) {
func Federation(w http.ResponseWriter, r *http.Request) {
federated, err := mustAuthority(r.Context()).GetFederation()
if err != nil {
render.Error(w, errs.ForbiddenErr(err, "error getting federated roots"))
render.Error(w, r, errs.ForbiddenErr(err, "error getting federated roots"))
return
}
@ -473,7 +473,7 @@ func Federation(w http.ResponseWriter, r *http.Request) {
certs[i] = Certificate{federated[i]}
}
render.JSONStatus(w, &FederationResponse{
render.JSONStatus(w, r, &FederationResponse{
Certificates: certs,
}, http.StatusCreated)
}
@ -565,7 +565,7 @@ func LogSSHCertificate(w http.ResponseWriter, cert *ssh.Certificate) {
func ParseCursor(r *http.Request) (cursor string, limit int, err error) {
q := r.URL.Query()
cursor = q.Get("cursor")
if v := q.Get("limit"); len(v) > 0 {
if v := q.Get("limit"); v != "" {
limit, err = strconv.Atoi(v)
if err != nil {
return "", 0, errs.BadRequestErr(err, "limit '%s' is not an integer", v)

@ -13,12 +13,12 @@ import (
func CRL(w http.ResponseWriter, r *http.Request) {
crlInfo, err := mustAuthority(r.Context()).GetCertificateRevocationList()
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
if crlInfo == nil {
render.Error(w, errs.New(http.StatusNotFound, "no CRL available"))
render.Error(w, r, errs.New(http.StatusNotFound, "no CRL available"))
return
}

@ -2,6 +2,7 @@
package log
import (
"context"
"fmt"
"net/http"
"os"
@ -9,6 +10,29 @@ import (
"github.com/pkg/errors"
)
type errorLoggerKey struct{}
// ErrorLogger is the function type used to log errors.
type ErrorLogger func(http.ResponseWriter, *http.Request, error)
func (fn ErrorLogger) call(w http.ResponseWriter, r *http.Request, err error) {
if fn == nil {
return
}
fn(w, r, err)
}
// WithErrorLogger returns a new context with the given error logger.
func WithErrorLogger(ctx context.Context, fn ErrorLogger) context.Context {
return context.WithValue(ctx, errorLoggerKey{}, fn)
}
// ErrorLoggerFromContext returns an error logger from the context.
func ErrorLoggerFromContext(ctx context.Context) (fn ErrorLogger) {
fn, _ = ctx.Value(errorLoggerKey{}).(ErrorLogger)
return
}
// StackTracedError is the set of errors implementing the StackTrace function.
//
// Errors implementing this interface have their stack traces logged when passed
@ -27,8 +51,10 @@ type fieldCarrier interface {
// Error adds to the response writer the given error if it implements
// logging.ResponseLogger. If it does not implement it, then writes the error
// using the log package.
func Error(rw http.ResponseWriter, err error) {
fc, ok := rw.(fieldCarrier)
func Error(w http.ResponseWriter, r *http.Request, err error) {
ErrorLoggerFromContext(r.Context()).call(w, r, err)
fc, ok := w.(fieldCarrier)
if !ok {
return
}
@ -51,7 +77,7 @@ func Error(rw http.ResponseWriter, err error) {
// EnabledResponse log the response object if it implements the EnableLogger
// interface.
func EnabledResponse(rw http.ResponseWriter, v any) {
func EnabledResponse(rw http.ResponseWriter, r *http.Request, v any) {
type enableLogger interface {
ToLog() (any, error)
}
@ -59,7 +85,7 @@ func EnabledResponse(rw http.ResponseWriter, v any) {
if el, ok := v.(enableLogger); ok {
out, err := el.ToLog()
if err != nil {
Error(rw, err)
Error(rw, r, err)
return
}

@ -1,6 +1,9 @@
package log
import (
"bytes"
"encoding/json"
"log/slog"
"net/http"
"net/http/httptest"
"testing"
@ -27,21 +30,34 @@ func (stackTracedError) StackTrace() pkgerrors.StackTrace {
}
func TestError(t *testing.T) {
var buf bytes.Buffer
logger := slog.New(slog.NewJSONHandler(&buf, &slog.HandlerOptions{}))
req := httptest.NewRequest("GET", "/test", http.NoBody)
reqWithLogger := req.WithContext(WithErrorLogger(req.Context(), func(w http.ResponseWriter, r *http.Request, err error) {
if err != nil {
logger.ErrorContext(r.Context(), "request failed", slog.Any("error", err))
}
}))
tests := []struct {
name string
error
rw http.ResponseWriter
r *http.Request
isFieldCarrier bool
isSlogLogger bool
stepDebug bool
expectStackTrace bool
}{
{"noLogger", nil, nil, false, false, false},
{"noError", nil, logging.NewResponseLogger(httptest.NewRecorder()), true, false, false},
{"noErrorDebug", nil, logging.NewResponseLogger(httptest.NewRecorder()), true, true, false},
{"anError", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), true, false, false},
{"anErrorDebug", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), true, true, false},
{"stackTracedError", new(stackTracedError), logging.NewResponseLogger(httptest.NewRecorder()), true, true, true},
{"stackTracedErrorDebug", new(stackTracedError), logging.NewResponseLogger(httptest.NewRecorder()), true, true, true},
{"noLogger", nil, nil, req, false, false, false, false},
{"noError", nil, logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, false, false},
{"noErrorDebug", nil, logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, true, false},
{"anError", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, false, false},
{"anErrorDebug", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, true, false},
{"stackTracedError", new(stackTracedError), logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, true, true},
{"stackTracedErrorDebug", new(stackTracedError), logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, true, true},
{"slogWithNoError", nil, logging.NewResponseLogger(httptest.NewRecorder()), reqWithLogger, true, true, false, false},
{"slogWithError", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), reqWithLogger, true, true, false, false},
}
for _, tt := range tests {
@ -52,27 +68,41 @@ func TestError(t *testing.T) {
t.Setenv("STEPDEBUG", "0")
}
Error(tt.rw, tt.error)
Error(tt.rw, tt.r, tt.error)
// return early if test case doesn't use logger
if !tt.isFieldCarrier {
if !tt.isFieldCarrier && !tt.isSlogLogger {
return
}
fields := tt.rw.(logging.ResponseLogger).Fields()
if tt.isFieldCarrier {
fields := tt.rw.(logging.ResponseLogger).Fields()
// expect the error field to be (not) set and to be the same error that was fed to Error
if tt.error == nil {
assert.Nil(t, fields["error"])
} else {
assert.Same(t, tt.error, fields["error"])
// expect the error field to be (not) set and to be the same error that was fed to Error
if tt.error == nil {
assert.Nil(t, fields["error"])
} else {
assert.Same(t, tt.error, fields["error"])
}
// check if stack-trace is set when expected
if _, hasStackTrace := fields["stack-trace"]; tt.expectStackTrace && !hasStackTrace {
t.Error(`ResponseLogger["stack-trace"] not set`)
} else if !tt.expectStackTrace && hasStackTrace {
t.Error(`ResponseLogger["stack-trace"] was set`)
}
}
// check if stack-trace is set when expected
if _, hasStackTrace := fields["stack-trace"]; tt.expectStackTrace && !hasStackTrace {
t.Error(`ResponseLogger["stack-trace"] not set`)
} else if !tt.expectStackTrace && hasStackTrace {
t.Error(`ResponseLogger["stack-trace"] was set`)
if tt.isSlogLogger {
b := buf.Bytes()
if tt.error == nil {
assert.Empty(t, b)
} else if assert.NotEmpty(t, b) {
var m map[string]any
assert.NoError(t, json.Unmarshal(b, &m))
assert.Equal(t, tt.error.Error(), m["error"])
}
buf.Reset()
}
})
}

@ -97,7 +97,7 @@ func (s *SCEP) AuthorizeSSHSign(context.Context, string) ([]provisioner.SignOpti
return nil, errDummyImplementation
}
// AuthorizeRevoke returns an unimplemented error. Provisioners should overwrite
// AuthorizeSSHRevoke returns an unimplemented error. Provisioners should overwrite
// this method if they will support authorizing tokens for revoking SSH Certificates.
func (s *SCEP) AuthorizeSSHRevoke(context.Context, string) error {
return errDummyImplementation

@ -51,7 +51,7 @@ func (e badProtoJSONError) Error() string {
}
// Render implements render.RenderableError for badProtoJSONError
func (e badProtoJSONError) Render(w http.ResponseWriter) {
func (e badProtoJSONError) Render(w http.ResponseWriter, r *http.Request) {
v := struct {
Type string `json:"type"`
Detail string `json:"detail"`
@ -62,5 +62,5 @@ func (e badProtoJSONError) Render(w http.ResponseWriter) {
// trim the proto prefix for the message
Message: strings.TrimSpace(strings.TrimPrefix(e.Error(), "proto:")),
}
render.JSONStatus(w, v, http.StatusBadRequest)
render.JSONStatus(w, r, v, http.StatusBadRequest)
}

@ -142,7 +142,8 @@ func Test_badProtoJSONError_Render(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
w := httptest.NewRecorder()
tt.e.Render(w)
r := httptest.NewRequest("POST", "/test", http.NoBody)
tt.e.Render(w, r)
res := w.Result()
defer res.Body.Close()

@ -29,25 +29,25 @@ func (s *RekeyRequest) Validate() error {
// Rekey is similar to renew except that the certificate will be renewed with new key from csr.
func Rekey(w http.ResponseWriter, r *http.Request) {
if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 {
render.Error(w, errs.BadRequest("missing client certificate"))
render.Error(w, r, errs.BadRequest("missing client certificate"))
return
}
var body RekeyRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, errs.BadRequestErr(err, "error reading request body"))
render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
return
}
if err := body.Validate(); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
a := mustAuthority(r.Context())
certChain, err := a.Rekey(r.TLS.PeerCertificates[0], body.CsrPEM.CertificateRequest.PublicKey)
if err != nil {
render.Error(w, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Rekey"))
render.Error(w, r, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Rekey"))
return
}
certChainPEM := certChainToPEM(certChain)
@ -57,7 +57,7 @@ func Rekey(w http.ResponseWriter, r *http.Request) {
}
LogCertificate(w, certChain[0])
render.JSONStatus(w, &SignResponse{
render.JSONStatus(w, r, &SignResponse{
ServerPEM: certChainPEM[0],
CaPEM: caPEM,
CertChainPEM: certChainPEM,

@ -13,8 +13,8 @@ import (
)
// JSON is shorthand for JSONStatus(w, v, http.StatusOK).
func JSON(w http.ResponseWriter, v interface{}) {
JSONStatus(w, v, http.StatusOK)
func JSON(w http.ResponseWriter, r *http.Request, v interface{}) {
JSONStatus(w, r, v, http.StatusOK)
}
// JSONStatus marshals v into w. It additionally sets the status code of
@ -22,7 +22,7 @@ func JSON(w http.ResponseWriter, v interface{}) {
//
// JSONStatus sets the Content-Type of w to application/json unless one is
// specified.
func JSONStatus(w http.ResponseWriter, v interface{}, status int) {
func JSONStatus(w http.ResponseWriter, r *http.Request, v interface{}, status int) {
setContentTypeUnlessPresent(w, "application/json")
w.WriteHeader(status)
@ -43,7 +43,7 @@ func JSONStatus(w http.ResponseWriter, v interface{}, status int) {
}
}
log.EnabledResponse(w, v)
log.EnabledResponse(w, r, v)
}
// ProtoJSON is shorthand for ProtoJSONStatus(w, m, http.StatusOK).
@ -80,22 +80,22 @@ func setContentTypeUnlessPresent(w http.ResponseWriter, contentType string) {
type RenderableError interface {
error
Render(http.ResponseWriter)
Render(http.ResponseWriter, *http.Request)
}
// Error marshals the JSON representation of err to w. In case err implements
// RenderableError its own Render method will be called instead.
func Error(w http.ResponseWriter, err error) {
log.Error(w, err)
func Error(rw http.ResponseWriter, r *http.Request, err error) {
log.Error(rw, r, err)
var r RenderableError
if errors.As(err, &r) {
r.Render(w)
var re RenderableError
if errors.As(err, &re) {
re.Render(rw, r)
return
}
JSONStatus(w, err, statusCodeFromError(err))
JSONStatus(rw, r, err, statusCodeFromError(err))
}
// StatusCodedError is the set of errors that implement the basic StatusCode

@ -18,8 +18,8 @@ import (
func TestJSON(t *testing.T) {
rec := httptest.NewRecorder()
rw := logging.NewResponseLogger(rec)
JSON(rw, map[string]interface{}{"foo": "bar"})
r := httptest.NewRequest("POST", "/test", http.NoBody)
JSON(rw, r, map[string]interface{}{"foo": "bar"})
assert.Equal(t, http.StatusOK, rec.Result().StatusCode)
assert.Equal(t, "application/json", rec.Header().Get("Content-Type"))
@ -64,7 +64,8 @@ func jsonPanicTest[T json.UnsupportedTypeError | json.UnsupportedValueError | js
assert.ErrorAs(t, err, &e)
}()
JSON(httptest.NewRecorder(), v)
r := httptest.NewRequest("POST", "/test", http.NoBody)
JSON(httptest.NewRecorder(), r, v)
}
type renderableError struct {
@ -76,10 +77,9 @@ func (err renderableError) Error() string {
return err.Message
}
func (err renderableError) Render(w http.ResponseWriter) {
func (err renderableError) Render(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "something/custom")
JSONStatus(w, err, err.Code)
JSONStatus(w, r, err, err.Code)
}
type statusedError struct {
@ -116,8 +116,8 @@ func TestError(t *testing.T) {
t.Run(strconv.Itoa(caseIndex), func(t *testing.T) {
rec := httptest.NewRecorder()
Error(rec, kase.err)
r := httptest.NewRequest("POST", "/test", http.NoBody)
Error(rec, r, kase.err)
assert.Equal(t, kase.code, rec.Result().StatusCode)
assert.Equal(t, kase.body, rec.Body.String())

@ -23,19 +23,20 @@ func Renew(w http.ResponseWriter, r *http.Request) {
// Get the leaf certificate from the peer or the token.
cert, token, err := getPeerCertificate(r)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
// The token can be used by RAs to renew a certificate.
if token != "" {
ctx = authority.NewTokenContext(ctx, token)
logOtt(w, token)
}
a := mustAuthority(ctx)
certChain, err := a.RenewContext(ctx, cert, nil)
if err != nil {
render.Error(w, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Renew"))
render.Error(w, r, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Renew"))
return
}
certChainPEM := certChainToPEM(certChain)
@ -45,7 +46,7 @@ func Renew(w http.ResponseWriter, r *http.Request) {
}
LogCertificate(w, certChain[0])
render.JSONStatus(w, &SignResponse{
render.JSONStatus(w, r, &SignResponse{
ServerPEM: certChainPEM[0],
CaPEM: caPEM,
CertChainPEM: certChainPEM,

@ -57,12 +57,12 @@ func (r *RevokeRequest) Validate() (err error) {
func Revoke(w http.ResponseWriter, r *http.Request) {
var body RevokeRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, errs.BadRequestErr(err, "error reading request body"))
render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
return
}
if err := body.Validate(); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -78,10 +78,10 @@ func Revoke(w http.ResponseWriter, r *http.Request) {
// A token indicates that we are using the api via a provisioner token,
// otherwise it is assumed that the certificate is revoking itself over mTLS.
if len(body.OTT) > 0 {
if body.OTT != "" {
logOtt(w, body.OTT)
if _, err := a.Authorize(ctx, body.OTT); err != nil {
render.Error(w, errs.UnauthorizedErr(err))
render.Error(w, r, errs.UnauthorizedErr(err))
return
}
opts.OTT = body.OTT
@ -90,12 +90,12 @@ func Revoke(w http.ResponseWriter, r *http.Request) {
// the client certificate Serial Number must match the serial number
// being revoked.
if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 {
render.Error(w, errs.BadRequest("missing ott or client certificate"))
render.Error(w, r, errs.BadRequest("missing ott or client certificate"))
return
}
opts.Crt = r.TLS.PeerCertificates[0]
if opts.Crt.SerialNumber.String() != opts.Serial {
render.Error(w, errs.BadRequest("serial number in client certificate different than body"))
render.Error(w, r, errs.BadRequest("serial number in client certificate different than body"))
return
}
// TODO: should probably be checking if the certificate was revoked here.
@ -106,12 +106,12 @@ func Revoke(w http.ResponseWriter, r *http.Request) {
}
if err := a.Revoke(ctx, opts); err != nil {
render.Error(w, errs.ForbiddenErr(err, "error revoking certificate"))
render.Error(w, r, errs.ForbiddenErr(err, "error revoking certificate"))
return
}
logRevoke(w, opts)
render.JSON(w, &RevokeResponse{Status: "ok"})
render.JSON(w, r, &RevokeResponse{Status: "ok"})
}
func logRevoke(w http.ResponseWriter, ri *authority.RevokeOptions) {

@ -52,13 +52,13 @@ type SignResponse struct {
func Sign(w http.ResponseWriter, r *http.Request) {
var body SignRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, errs.BadRequestErr(err, "error reading request body"))
render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
return
}
logOtt(w, body.OTT)
if err := body.Validate(); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -74,13 +74,13 @@ func Sign(w http.ResponseWriter, r *http.Request) {
ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod)
signOpts, err := a.Authorize(ctx, body.OTT)
if err != nil {
render.Error(w, errs.UnauthorizedErr(err))
render.Error(w, r, errs.UnauthorizedErr(err))
return
}
certChain, err := a.SignWithContext(ctx, body.CsrPEM.CertificateRequest, opts, signOpts...)
if err != nil {
render.Error(w, errs.ForbiddenErr(err, "error signing certificate"))
render.Error(w, r, errs.ForbiddenErr(err, "error signing certificate"))
return
}
certChainPEM := certChainToPEM(certChain)
@ -90,7 +90,7 @@ func Sign(w http.ResponseWriter, r *http.Request) {
}
LogCertificate(w, certChain[0])
render.JSONStatus(w, &SignResponse{
render.JSONStatus(w, r, &SignResponse{
ServerPEM: certChainPEM[0],
CaPEM: caPEM,
CertChainPEM: certChainPEM,

@ -253,19 +253,19 @@ type SSHBastionResponse struct {
func SSHSign(w http.ResponseWriter, r *http.Request) {
var body SSHSignRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, errs.BadRequestErr(err, "error reading request body"))
render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
return
}
logOtt(w, body.OTT)
if err := body.Validate(); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
publicKey, err := ssh.ParsePublicKey(body.PublicKey)
if err != nil {
render.Error(w, errs.BadRequestErr(err, "error parsing publicKey"))
render.Error(w, r, errs.BadRequestErr(err, "error parsing publicKey"))
return
}
@ -273,7 +273,7 @@ func SSHSign(w http.ResponseWriter, r *http.Request) {
if body.AddUserPublicKey != nil {
addUserPublicKey, err = ssh.ParsePublicKey(body.AddUserPublicKey)
if err != nil {
render.Error(w, errs.BadRequestErr(err, "error parsing addUserPublicKey"))
render.Error(w, r, errs.BadRequestErr(err, "error parsing addUserPublicKey"))
return
}
}
@ -293,13 +293,13 @@ func SSHSign(w http.ResponseWriter, r *http.Request) {
a := mustAuthority(ctx)
signOpts, err := a.Authorize(ctx, body.OTT)
if err != nil {
render.Error(w, errs.UnauthorizedErr(err))
render.Error(w, r, errs.UnauthorizedErr(err))
return
}
cert, err := a.SignSSH(ctx, publicKey, opts, signOpts...)
if err != nil {
render.Error(w, errs.ForbiddenErr(err, "error signing ssh certificate"))
render.Error(w, r, errs.ForbiddenErr(err, "error signing ssh certificate"))
return
}
@ -307,7 +307,7 @@ func SSHSign(w http.ResponseWriter, r *http.Request) {
if addUserPublicKey != nil && authority.IsValidForAddUser(cert) == nil {
addUserCert, err := a.SignSSHAddUser(ctx, addUserPublicKey, cert)
if err != nil {
render.Error(w, errs.ForbiddenErr(err, "error signing ssh certificate"))
render.Error(w, r, errs.ForbiddenErr(err, "error signing ssh certificate"))
return
}
addUserCertificate = &SSHCertificate{addUserCert}
@ -320,7 +320,7 @@ func SSHSign(w http.ResponseWriter, r *http.Request) {
ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignIdentityMethod)
signOpts, err := a.Authorize(ctx, body.OTT)
if err != nil {
render.Error(w, errs.UnauthorizedErr(err))
render.Error(w, r, errs.UnauthorizedErr(err))
return
}
@ -332,14 +332,14 @@ func SSHSign(w http.ResponseWriter, r *http.Request) {
certChain, err := a.SignWithContext(ctx, cr, provisioner.SignOptions{}, signOpts...)
if err != nil {
render.Error(w, errs.ForbiddenErr(err, "error signing identity certificate"))
render.Error(w, r, errs.ForbiddenErr(err, "error signing identity certificate"))
return
}
identityCertificate = certChainToPEM(certChain)
}
LogSSHCertificate(w, cert)
render.JSONStatus(w, &SSHSignResponse{
render.JSONStatus(w, r, &SSHSignResponse{
Certificate: SSHCertificate{cert},
AddUserCertificate: addUserCertificate,
IdentityCertificate: identityCertificate,
@ -352,12 +352,12 @@ func SSHRoots(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
keys, err := mustAuthority(ctx).GetSSHRoots(ctx)
if err != nil {
render.Error(w, errs.InternalServerErr(err))
render.Error(w, r, errs.InternalServerErr(err))
return
}
if len(keys.HostKeys) == 0 && len(keys.UserKeys) == 0 {
render.Error(w, errs.NotFound("no keys found"))
render.Error(w, r, errs.NotFound("no keys found"))
return
}
@ -369,7 +369,7 @@ func SSHRoots(w http.ResponseWriter, r *http.Request) {
resp.UserKeys = append(resp.UserKeys, SSHPublicKey{PublicKey: k})
}
render.JSON(w, resp)
render.JSON(w, r, resp)
}
// SSHFederation is an HTTP handler that returns the federated SSH public keys
@ -378,12 +378,12 @@ func SSHFederation(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
keys, err := mustAuthority(ctx).GetSSHFederation(ctx)
if err != nil {
render.Error(w, errs.InternalServerErr(err))
render.Error(w, r, errs.InternalServerErr(err))
return
}
if len(keys.HostKeys) == 0 && len(keys.UserKeys) == 0 {
render.Error(w, errs.NotFound("no keys found"))
render.Error(w, r, errs.NotFound("no keys found"))
return
}
@ -395,7 +395,7 @@ func SSHFederation(w http.ResponseWriter, r *http.Request) {
resp.UserKeys = append(resp.UserKeys, SSHPublicKey{PublicKey: k})
}
render.JSON(w, resp)
render.JSON(w, r, resp)
}
// SSHConfig is an HTTP handler that returns rendered templates for ssh clients
@ -403,18 +403,18 @@ func SSHFederation(w http.ResponseWriter, r *http.Request) {
func SSHConfig(w http.ResponseWriter, r *http.Request) {
var body SSHConfigRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, errs.BadRequestErr(err, "error reading request body"))
render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
return
}
if err := body.Validate(); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
ctx := r.Context()
ts, err := mustAuthority(ctx).GetSSHConfig(ctx, body.Type, body.Data)
if err != nil {
render.Error(w, errs.InternalServerErr(err))
render.Error(w, r, errs.InternalServerErr(err))
return
}
@ -425,32 +425,32 @@ func SSHConfig(w http.ResponseWriter, r *http.Request) {
case provisioner.SSHHostCert:
cfg.HostTemplates = ts
default:
render.Error(w, errs.InternalServer("it should hot get here"))
render.Error(w, r, errs.InternalServer("it should hot get here"))
return
}
render.JSON(w, cfg)
render.JSON(w, r, cfg)
}
// SSHCheckHost is the HTTP handler that returns if a hosts certificate exists or not.
func SSHCheckHost(w http.ResponseWriter, r *http.Request) {
var body SSHCheckPrincipalRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, errs.BadRequestErr(err, "error reading request body"))
render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
return
}
if err := body.Validate(); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
ctx := r.Context()
exists, err := mustAuthority(ctx).CheckSSHHost(ctx, body.Principal, body.Token)
if err != nil {
render.Error(w, errs.InternalServerErr(err))
render.Error(w, r, errs.InternalServerErr(err))
return
}
render.JSON(w, &SSHCheckPrincipalResponse{
render.JSON(w, r, &SSHCheckPrincipalResponse{
Exists: exists,
})
}
@ -465,10 +465,10 @@ func SSHGetHosts(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
hosts, err := mustAuthority(ctx).GetSSHHosts(ctx, cert)
if err != nil {
render.Error(w, errs.InternalServerErr(err))
render.Error(w, r, errs.InternalServerErr(err))
return
}
render.JSON(w, &SSHGetHostsResponse{
render.JSON(w, r, &SSHGetHostsResponse{
Hosts: hosts,
})
}
@ -477,22 +477,22 @@ func SSHGetHosts(w http.ResponseWriter, r *http.Request) {
func SSHBastion(w http.ResponseWriter, r *http.Request) {
var body SSHBastionRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, errs.BadRequestErr(err, "error reading request body"))
render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
return
}
if err := body.Validate(); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
ctx := r.Context()
bastion, err := mustAuthority(ctx).GetSSHBastion(ctx, body.User, body.Hostname)
if err != nil {
render.Error(w, errs.InternalServerErr(err))
render.Error(w, r, errs.InternalServerErr(err))
return
}
render.JSON(w, &SSHBastionResponse{
render.JSON(w, r, &SSHBastionResponse{
Hostname: body.Hostname,
Bastion: bastion,
})

@ -42,19 +42,19 @@ type SSHRekeyResponse struct {
func SSHRekey(w http.ResponseWriter, r *http.Request) {
var body SSHRekeyRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, errs.BadRequestErr(err, "error reading request body"))
render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
return
}
logOtt(w, body.OTT)
if err := body.Validate(); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
publicKey, err := ssh.ParsePublicKey(body.PublicKey)
if err != nil {
render.Error(w, errs.BadRequestErr(err, "error parsing publicKey"))
render.Error(w, r, errs.BadRequestErr(err, "error parsing publicKey"))
return
}
@ -64,18 +64,18 @@ func SSHRekey(w http.ResponseWriter, r *http.Request) {
a := mustAuthority(ctx)
signOpts, err := a.Authorize(ctx, body.OTT)
if err != nil {
render.Error(w, errs.UnauthorizedErr(err))
render.Error(w, r, errs.UnauthorizedErr(err))
return
}
oldCert, _, err := provisioner.ExtractSSHPOPCert(body.OTT)
if err != nil {
render.Error(w, errs.InternalServerErr(err))
render.Error(w, r, errs.InternalServerErr(err))
return
}
newCert, err := a.RekeySSH(ctx, oldCert, publicKey, signOpts...)
if err != nil {
render.Error(w, errs.ForbiddenErr(err, "error rekeying ssh certificate"))
render.Error(w, r, errs.ForbiddenErr(err, "error rekeying ssh certificate"))
return
}
@ -85,12 +85,12 @@ func SSHRekey(w http.ResponseWriter, r *http.Request) {
identity, err := renewIdentityCertificate(r, notBefore, notAfter)
if err != nil {
render.Error(w, errs.ForbiddenErr(err, "error renewing identity certificate"))
render.Error(w, r, errs.ForbiddenErr(err, "error renewing identity certificate"))
return
}
LogSSHCertificate(w, newCert)
render.JSONStatus(w, &SSHRekeyResponse{
render.JSONStatus(w, r, &SSHRekeyResponse{
Certificate: SSHCertificate{newCert},
IdentityCertificate: identity,
}, http.StatusCreated)

@ -40,13 +40,13 @@ type SSHRenewResponse struct {
func SSHRenew(w http.ResponseWriter, r *http.Request) {
var body SSHRenewRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, errs.BadRequestErr(err, "error reading request body"))
render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
return
}
logOtt(w, body.OTT)
if err := body.Validate(); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -56,18 +56,18 @@ func SSHRenew(w http.ResponseWriter, r *http.Request) {
a := mustAuthority(ctx)
_, err := a.Authorize(ctx, body.OTT)
if err != nil {
render.Error(w, errs.UnauthorizedErr(err))
render.Error(w, r, errs.UnauthorizedErr(err))
return
}
oldCert, _, err := provisioner.ExtractSSHPOPCert(body.OTT)
if err != nil {
render.Error(w, errs.InternalServerErr(err))
render.Error(w, r, errs.InternalServerErr(err))
return
}
newCert, err := a.RenewSSH(ctx, oldCert)
if err != nil {
render.Error(w, errs.ForbiddenErr(err, "error renewing ssh certificate"))
render.Error(w, r, errs.ForbiddenErr(err, "error renewing ssh certificate"))
return
}
@ -77,12 +77,12 @@ func SSHRenew(w http.ResponseWriter, r *http.Request) {
identity, err := renewIdentityCertificate(r, notBefore, notAfter)
if err != nil {
render.Error(w, errs.ForbiddenErr(err, "error renewing identity certificate"))
render.Error(w, r, errs.ForbiddenErr(err, "error renewing identity certificate"))
return
}
LogSSHCertificate(w, newCert)
render.JSONStatus(w, &SSHSignResponse{
render.JSONStatus(w, r, &SSHSignResponse{
Certificate: SSHCertificate{newCert},
IdentityCertificate: identity,
}, http.StatusCreated)

@ -51,12 +51,12 @@ func (r *SSHRevokeRequest) Validate() (err error) {
func SSHRevoke(w http.ResponseWriter, r *http.Request) {
var body SSHRevokeRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, errs.BadRequestErr(err, "error reading request body"))
render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
return
}
if err := body.Validate(); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -75,18 +75,18 @@ func SSHRevoke(w http.ResponseWriter, r *http.Request) {
logOtt(w, body.OTT)
if _, err := a.Authorize(ctx, body.OTT); err != nil {
render.Error(w, errs.UnauthorizedErr(err))
render.Error(w, r, errs.UnauthorizedErr(err))
return
}
opts.OTT = body.OTT
if err := a.Revoke(ctx, opts); err != nil {
render.Error(w, errs.ForbiddenErr(err, "error revoking ssh certificate"))
render.Error(w, r, errs.ForbiddenErr(err, "error revoking ssh certificate"))
return
}
logSSHRevoke(w, opts)
render.JSON(w, &SSHRevokeResponse{Status: "ok"})
render.JSON(w, r, &SSHRevokeResponse{Status: "ok"})
}
func logSSHRevoke(w http.ResponseWriter, ri *authority.RevokeOptions) {

@ -40,12 +40,12 @@ func requireEABEnabled(next http.HandlerFunc) http.HandlerFunc {
acmeProvisioner := prov.GetDetails().GetACME()
if acmeProvisioner == nil {
render.Error(w, admin.NewErrorISE("error getting ACME details for provisioner '%s'", prov.GetName()))
render.Error(w, r, admin.NewErrorISE("error getting ACME details for provisioner '%s'", prov.GetName()))
return
}
if !acmeProvisioner.RequireEab {
render.Error(w, admin.NewError(admin.ErrorBadRequestType, "ACME EAB not enabled for provisioner '%s'", prov.GetName()))
render.Error(w, r, admin.NewError(admin.ErrorBadRequestType, "ACME EAB not enabled for provisioner '%s'", prov.GetName()))
return
}
@ -69,18 +69,18 @@ func NewACMEAdminResponder() ACMEAdminResponder {
}
// GetExternalAccountKeys writes the response for the EAB keys GET endpoint
func (h *acmeAdminResponder) GetExternalAccountKeys(w http.ResponseWriter, _ *http.Request) {
render.Error(w, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm"))
func (h *acmeAdminResponder) GetExternalAccountKeys(w http.ResponseWriter, r *http.Request) {
render.Error(w, r, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm"))
}
// CreateExternalAccountKey writes the response for the EAB key POST endpoint
func (h *acmeAdminResponder) CreateExternalAccountKey(w http.ResponseWriter, _ *http.Request) {
render.Error(w, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm"))
func (h *acmeAdminResponder) CreateExternalAccountKey(w http.ResponseWriter, r *http.Request) {
render.Error(w, r, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm"))
}
// DeleteExternalAccountKey writes the response for the EAB key DELETE endpoint
func (h *acmeAdminResponder) DeleteExternalAccountKey(w http.ResponseWriter, _ *http.Request) {
render.Error(w, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm"))
func (h *acmeAdminResponder) DeleteExternalAccountKey(w http.ResponseWriter, r *http.Request) {
render.Error(w, r, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm"))
}
func eakToLinked(k *acme.ExternalAccountKey) *linkedca.EABKey {

@ -90,7 +90,7 @@ func GetAdmin(w http.ResponseWriter, r *http.Request) {
adm, ok := mustAuthority(r.Context()).LoadAdminByID(id)
if !ok {
render.Error(w, admin.NewError(admin.ErrorNotFoundType,
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType,
"admin %s not found", id))
return
}
@ -101,17 +101,17 @@ func GetAdmin(w http.ResponseWriter, r *http.Request) {
func GetAdmins(w http.ResponseWriter, r *http.Request) {
cursor, limit, err := api.ParseCursor(r)
if err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err,
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err,
"error parsing cursor and limit from query params"))
return
}
admins, nextCursor, err := mustAuthority(r.Context()).GetAdmins(cursor, limit)
if err != nil {
render.Error(w, admin.WrapErrorISE(err, "error retrieving paginated admins"))
render.Error(w, r, admin.WrapErrorISE(err, "error retrieving paginated admins"))
return
}
render.JSON(w, &GetAdminsResponse{
render.JSON(w, r, &GetAdminsResponse{
Admins: admins,
NextCursor: nextCursor,
})
@ -121,19 +121,19 @@ func GetAdmins(w http.ResponseWriter, r *http.Request) {
func CreateAdmin(w http.ResponseWriter, r *http.Request) {
var body CreateAdminRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body"))
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body"))
return
}
if err := body.Validate(); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
auth := mustAuthority(r.Context())
p, err := auth.LoadProvisionerByName(body.Provisioner)
if err != nil {
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", body.Provisioner))
render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", body.Provisioner))
return
}
adm := &linkedca.Admin{
@ -143,7 +143,7 @@ func CreateAdmin(w http.ResponseWriter, r *http.Request) {
}
// Store to authority collection.
if err := auth.StoreAdmin(r.Context(), adm, p); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error storing admin"))
render.Error(w, r, admin.WrapErrorISE(err, "error storing admin"))
return
}
@ -155,23 +155,23 @@ func DeleteAdmin(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
if err := mustAuthority(r.Context()).RemoveAdmin(r.Context(), id); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error deleting admin %s", id))
render.Error(w, r, admin.WrapErrorISE(err, "error deleting admin %s", id))
return
}
render.JSON(w, &DeleteResponse{Status: "ok"})
render.JSON(w, r, &DeleteResponse{Status: "ok"})
}
// UpdateAdmin updates an existing admin.
func UpdateAdmin(w http.ResponseWriter, r *http.Request) {
var body UpdateAdminRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body"))
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body"))
return
}
if err := body.Validate(); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -179,7 +179,7 @@ func UpdateAdmin(w http.ResponseWriter, r *http.Request) {
auth := mustAuthority(r.Context())
adm, err := auth.UpdateAdmin(r.Context(), id, &linkedca.Admin{Type: body.Type})
if err != nil {
render.Error(w, admin.WrapErrorISE(err, "error updating admin %s", id))
render.Error(w, r, admin.WrapErrorISE(err, "error updating admin %s", id))
return
}

@ -1,7 +1,6 @@
package api
import (
"errors"
"net/http"
"github.com/go-chi/chi/v5"
@ -20,7 +19,7 @@ import (
func requireAPIEnabled(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !mustAuthority(r.Context()).IsAdminAPIEnabled() {
render.Error(w, admin.NewError(admin.ErrorNotImplementedType, "administration API not enabled"))
render.Error(w, r, admin.NewError(admin.ErrorNotImplementedType, "administration API not enabled"))
return
}
next(w, r)
@ -32,7 +31,7 @@ func extractAuthorizeTokenAdmin(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
tok := r.Header.Get("Authorization")
if tok == "" {
render.Error(w, admin.NewError(admin.ErrorUnauthorizedType,
render.Error(w, r, admin.NewError(admin.ErrorUnauthorizedType,
"missing authorization header token"))
return
}
@ -40,7 +39,7 @@ func extractAuthorizeTokenAdmin(next http.HandlerFunc) http.HandlerFunc {
ctx := r.Context()
adm, err := mustAuthority(ctx).AuthorizeAdminToken(r, tok)
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -65,13 +64,13 @@ func loadProvisionerByName(next http.HandlerFunc) http.HandlerFunc {
// TODO(hs): distinguish 404 vs. 500
if p, err = auth.LoadProvisionerByName(name); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", name))
render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", name))
return
}
prov, err := adminDB.GetProvisioner(ctx, p.GetID())
if err != nil {
render.Error(w, admin.WrapErrorISE(err, "error retrieving provisioner %s", name))
render.Error(w, r, admin.WrapErrorISE(err, "error retrieving provisioner %s", name))
return
}
@ -92,7 +91,7 @@ func checkAction(next http.HandlerFunc, supportedInStandalone bool) http.Handler
// when an action is not supported in standalone mode and when
// using a nosql.DB backend, actions are not supported
if _, ok := admin.MustFromContext(r.Context()).(*nosql.DB); ok {
render.Error(w, admin.NewError(admin.ErrorNotImplementedType,
render.Error(w, r, admin.NewError(admin.ErrorNotImplementedType,
"operation not supported in standalone mode"))
return
}
@ -125,16 +124,16 @@ func loadExternalAccountKey(next http.HandlerFunc) http.HandlerFunc {
}
if err != nil {
if errors.Is(err, acme.ErrNotFound) {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME External Account Key not found"))
if acme.IsErrNotFound(err) {
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "ACME External Account Key not found"))
return
}
render.Error(w, admin.WrapErrorISE(err, "error retrieving ACME External Account Key"))
render.Error(w, r, admin.WrapErrorISE(err, "error retrieving ACME External Account Key"))
return
}
if eak == nil {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME External Account Key not found"))
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "ACME External Account Key not found"))
return
}

@ -35,7 +35,7 @@ type PolicyAdminResponder interface {
// policyAdminResponder implements PolicyAdminResponder.
type policyAdminResponder struct{}
// NewACMEAdminResponder returns a new PolicyAdminResponder.
// NewPolicyAdminResponder returns a new PolicyAdminResponder.
func NewPolicyAdminResponder() PolicyAdminResponder {
return &policyAdminResponder{}
}
@ -44,7 +44,7 @@ func NewPolicyAdminResponder() PolicyAdminResponder {
func (par *policyAdminResponder) GetAuthorityPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -52,12 +52,12 @@ func (par *policyAdminResponder) GetAuthorityPolicy(w http.ResponseWriter, r *ht
authorityPolicy, err := auth.GetAuthorityPolicy(r.Context())
var ae *admin.Error
if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) {
render.Error(w, admin.WrapErrorISE(ae, "error retrieving authority policy"))
render.Error(w, r, admin.WrapErrorISE(ae, "error retrieving authority policy"))
return
}
if authorityPolicy == nil {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist"))
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist"))
return
}
@ -68,7 +68,7 @@ func (par *policyAdminResponder) GetAuthorityPolicy(w http.ResponseWriter, r *ht
func (par *policyAdminResponder) CreateAuthorityPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -77,26 +77,26 @@ func (par *policyAdminResponder) CreateAuthorityPolicy(w http.ResponseWriter, r
var ae *admin.Error
if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) {
render.Error(w, admin.WrapErrorISE(err, "error retrieving authority policy"))
render.Error(w, r, admin.WrapErrorISE(err, "error retrieving authority policy"))
return
}
if authorityPolicy != nil {
adminErr := admin.NewError(admin.ErrorConflictType, "authority already has a policy")
render.Error(w, adminErr)
render.Error(w, r, adminErr)
return
}
var newPolicy = new(linkedca.Policy)
if err := read.ProtoJSON(r.Body, newPolicy); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating authority policy"))
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating authority policy"))
return
}
@ -105,11 +105,11 @@ func (par *policyAdminResponder) CreateAuthorityPolicy(w http.ResponseWriter, r
var createdPolicy *linkedca.Policy
if createdPolicy, err = auth.CreateAuthorityPolicy(ctx, adm, newPolicy); err != nil {
if isBadRequest(err) {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error storing authority policy"))
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error storing authority policy"))
return
}
render.Error(w, admin.WrapErrorISE(err, "error storing authority policy"))
render.Error(w, r, admin.WrapErrorISE(err, "error storing authority policy"))
return
}
@ -120,7 +120,7 @@ func (par *policyAdminResponder) CreateAuthorityPolicy(w http.ResponseWriter, r
func (par *policyAdminResponder) UpdateAuthorityPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -129,25 +129,25 @@ func (par *policyAdminResponder) UpdateAuthorityPolicy(w http.ResponseWriter, r
var ae *admin.Error
if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) {
render.Error(w, admin.WrapErrorISE(err, "error retrieving authority policy"))
render.Error(w, r, admin.WrapErrorISE(err, "error retrieving authority policy"))
return
}
if authorityPolicy == nil {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist"))
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist"))
return
}
var newPolicy = new(linkedca.Policy)
if err := read.ProtoJSON(r.Body, newPolicy); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating authority policy"))
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating authority policy"))
return
}
@ -156,11 +156,11 @@ func (par *policyAdminResponder) UpdateAuthorityPolicy(w http.ResponseWriter, r
var updatedPolicy *linkedca.Policy
if updatedPolicy, err = auth.UpdateAuthorityPolicy(ctx, adm, newPolicy); err != nil {
if isBadRequest(err) {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error updating authority policy"))
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error updating authority policy"))
return
}
render.Error(w, admin.WrapErrorISE(err, "error updating authority policy"))
render.Error(w, r, admin.WrapErrorISE(err, "error updating authority policy"))
return
}
@ -171,7 +171,7 @@ func (par *policyAdminResponder) UpdateAuthorityPolicy(w http.ResponseWriter, r
func (par *policyAdminResponder) DeleteAuthorityPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -180,35 +180,35 @@ func (par *policyAdminResponder) DeleteAuthorityPolicy(w http.ResponseWriter, r
var ae *admin.Error
if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) {
render.Error(w, admin.WrapErrorISE(ae, "error retrieving authority policy"))
render.Error(w, r, admin.WrapErrorISE(ae, "error retrieving authority policy"))
return
}
if authorityPolicy == nil {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist"))
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist"))
return
}
if err := auth.RemoveAuthorityPolicy(ctx); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error deleting authority policy"))
render.Error(w, r, admin.WrapErrorISE(err, "error deleting authority policy"))
return
}
render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK)
render.JSONStatus(w, r, DeleteResponse{Status: "ok"}, http.StatusOK)
}
// GetProvisionerPolicy handles the GET /admin/provisioners/{name}/policy request
func (par *policyAdminResponder) GetProvisionerPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
prov := linkedca.MustProvisionerFromContext(ctx)
provisionerPolicy := prov.GetPolicy()
if provisionerPolicy == nil {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist"))
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist"))
return
}
@ -219,7 +219,7 @@ func (par *policyAdminResponder) GetProvisionerPolicy(w http.ResponseWriter, r *
func (par *policyAdminResponder) CreateProvisionerPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -227,20 +227,20 @@ func (par *policyAdminResponder) CreateProvisionerPolicy(w http.ResponseWriter,
provisionerPolicy := prov.GetPolicy()
if provisionerPolicy != nil {
adminErr := admin.NewError(admin.ErrorConflictType, "provisioner %s already has a policy", prov.Name)
render.Error(w, adminErr)
render.Error(w, r, adminErr)
return
}
var newPolicy = new(linkedca.Policy)
if err := read.ProtoJSON(r.Body, newPolicy); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating provisioner policy"))
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating provisioner policy"))
return
}
@ -248,11 +248,11 @@ func (par *policyAdminResponder) CreateProvisionerPolicy(w http.ResponseWriter,
auth := mustAuthority(ctx)
if err := auth.UpdateProvisioner(ctx, prov); err != nil {
if isBadRequest(err) {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error creating provisioner policy"))
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error creating provisioner policy"))
return
}
render.Error(w, admin.WrapErrorISE(err, "error creating provisioner policy"))
render.Error(w, r, admin.WrapErrorISE(err, "error creating provisioner policy"))
return
}
@ -263,27 +263,27 @@ func (par *policyAdminResponder) CreateProvisionerPolicy(w http.ResponseWriter,
func (par *policyAdminResponder) UpdateProvisionerPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
prov := linkedca.MustProvisionerFromContext(ctx)
provisionerPolicy := prov.GetPolicy()
if provisionerPolicy == nil {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist"))
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist"))
return
}
var newPolicy = new(linkedca.Policy)
if err := read.ProtoJSON(r.Body, newPolicy); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating provisioner policy"))
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating provisioner policy"))
return
}
@ -291,11 +291,11 @@ func (par *policyAdminResponder) UpdateProvisionerPolicy(w http.ResponseWriter,
auth := mustAuthority(ctx)
if err := auth.UpdateProvisioner(ctx, prov); err != nil {
if isBadRequest(err) {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error updating provisioner policy"))
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error updating provisioner policy"))
return
}
render.Error(w, admin.WrapErrorISE(err, "error updating provisioner policy"))
render.Error(w, r, admin.WrapErrorISE(err, "error updating provisioner policy"))
return
}
@ -306,13 +306,13 @@ func (par *policyAdminResponder) UpdateProvisionerPolicy(w http.ResponseWriter,
func (par *policyAdminResponder) DeleteProvisionerPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
prov := linkedca.MustProvisionerFromContext(ctx)
if prov.Policy == nil {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist"))
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist"))
return
}
@ -321,24 +321,24 @@ func (par *policyAdminResponder) DeleteProvisionerPolicy(w http.ResponseWriter,
auth := mustAuthority(ctx)
if err := auth.UpdateProvisioner(ctx, prov); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error deleting provisioner policy"))
render.Error(w, r, admin.WrapErrorISE(err, "error deleting provisioner policy"))
return
}
render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK)
render.JSONStatus(w, r, DeleteResponse{Status: "ok"}, http.StatusOK)
}
func (par *policyAdminResponder) GetACMEAccountPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
eak := linkedca.MustExternalAccountKeyFromContext(ctx)
eakPolicy := eak.GetPolicy()
if eakPolicy == nil {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist"))
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist"))
return
}
@ -348,7 +348,7 @@ func (par *policyAdminResponder) GetACMEAccountPolicy(w http.ResponseWriter, r *
func (par *policyAdminResponder) CreateACMEAccountPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -357,20 +357,20 @@ func (par *policyAdminResponder) CreateACMEAccountPolicy(w http.ResponseWriter,
eakPolicy := eak.GetPolicy()
if eakPolicy != nil {
adminErr := admin.NewError(admin.ErrorConflictType, "ACME EAK %s already has a policy", eak.Id)
render.Error(w, adminErr)
render.Error(w, r, adminErr)
return
}
var newPolicy = new(linkedca.Policy)
if err := read.ProtoJSON(r.Body, newPolicy); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating ACME EAK policy"))
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating ACME EAK policy"))
return
}
@ -379,7 +379,7 @@ func (par *policyAdminResponder) CreateACMEAccountPolicy(w http.ResponseWriter,
acmeEAK := linkedEAKToCertificates(eak)
acmeDB := acme.MustDatabaseFromContext(ctx)
if err := acmeDB.UpdateExternalAccountKey(ctx, prov.GetId(), acmeEAK); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error creating ACME EAK policy"))
render.Error(w, r, admin.WrapErrorISE(err, "error creating ACME EAK policy"))
return
}
@ -389,7 +389,7 @@ func (par *policyAdminResponder) CreateACMEAccountPolicy(w http.ResponseWriter,
func (par *policyAdminResponder) UpdateACMEAccountPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -397,20 +397,20 @@ func (par *policyAdminResponder) UpdateACMEAccountPolicy(w http.ResponseWriter,
eak := linkedca.MustExternalAccountKeyFromContext(ctx)
eakPolicy := eak.GetPolicy()
if eakPolicy == nil {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist"))
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist"))
return
}
var newPolicy = new(linkedca.Policy)
if err := read.ProtoJSON(r.Body, newPolicy); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating ACME EAK policy"))
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating ACME EAK policy"))
return
}
@ -418,7 +418,7 @@ func (par *policyAdminResponder) UpdateACMEAccountPolicy(w http.ResponseWriter,
acmeEAK := linkedEAKToCertificates(eak)
acmeDB := acme.MustDatabaseFromContext(ctx)
if err := acmeDB.UpdateExternalAccountKey(ctx, prov.GetId(), acmeEAK); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error updating ACME EAK policy"))
render.Error(w, r, admin.WrapErrorISE(err, "error updating ACME EAK policy"))
return
}
@ -428,7 +428,7 @@ func (par *policyAdminResponder) UpdateACMEAccountPolicy(w http.ResponseWriter,
func (par *policyAdminResponder) DeleteACMEAccountPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -436,7 +436,7 @@ func (par *policyAdminResponder) DeleteACMEAccountPolicy(w http.ResponseWriter,
eak := linkedca.MustExternalAccountKeyFromContext(ctx)
eakPolicy := eak.GetPolicy()
if eakPolicy == nil {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist"))
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist"))
return
}
@ -446,11 +446,11 @@ func (par *policyAdminResponder) DeleteACMEAccountPolicy(w http.ResponseWriter,
acmeEAK := linkedEAKToCertificates(eak)
acmeDB := acme.MustDatabaseFromContext(ctx)
if err := acmeDB.UpdateExternalAccountKey(ctx, prov.GetId(), acmeEAK); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error deleting ACME EAK policy"))
render.Error(w, r, admin.WrapErrorISE(err, "error deleting ACME EAK policy"))
return
}
render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK)
render.JSONStatus(w, r, DeleteResponse{Status: "ok"}, http.StatusOK)
}
// blockLinkedCA blocks all API operations on linked deployments

@ -38,21 +38,21 @@ func GetProvisioner(w http.ResponseWriter, r *http.Request) {
auth := mustAuthority(ctx)
db := admin.MustFromContext(ctx)
if len(id) > 0 {
if id != "" {
if p, err = auth.LoadProvisionerByID(id); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", id))
render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", id))
return
}
} else {
if p, err = auth.LoadProvisionerByName(name); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", name))
render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", name))
return
}
}
prov, err := db.GetProvisioner(ctx, p.GetID())
if err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
render.ProtoJSON(w, prov)
@ -62,17 +62,17 @@ func GetProvisioner(w http.ResponseWriter, r *http.Request) {
func GetProvisioners(w http.ResponseWriter, r *http.Request) {
cursor, limit, err := api.ParseCursor(r)
if err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err,
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err,
"error parsing cursor and limit from query params"))
return
}
p, next, err := mustAuthority(r.Context()).GetProvisioners(cursor, limit)
if err != nil {
render.Error(w, errs.InternalServerErr(err))
render.Error(w, r, errs.InternalServerErr(err))
return
}
render.JSON(w, &GetProvisionersResponse{
render.JSON(w, r, &GetProvisionersResponse{
Provisioners: p,
NextCursor: next,
})
@ -82,24 +82,24 @@ func GetProvisioners(w http.ResponseWriter, r *http.Request) {
func CreateProvisioner(w http.ResponseWriter, r *http.Request) {
var prov = new(linkedca.Provisioner)
if err := read.ProtoJSON(r.Body, prov); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
// TODO: Validate inputs
if err := authority.ValidateClaims(prov.Claims); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
// validate the templates and template data
if err := validateTemplates(prov.X509Template, prov.SshTemplate); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "invalid template"))
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "invalid template"))
return
}
if err := mustAuthority(r.Context()).StoreProvisioner(r.Context(), prov); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error storing provisioner %s", prov.Name))
render.Error(w, r, admin.WrapErrorISE(err, "error storing provisioner %s", prov.Name))
return
}
render.ProtoJSONStatus(w, prov, http.StatusCreated)
@ -116,31 +116,31 @@ func DeleteProvisioner(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
auth := mustAuthority(r.Context())
if len(id) > 0 {
if id != "" {
if p, err = auth.LoadProvisionerByID(id); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", id))
render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", id))
return
}
} else {
if p, err = auth.LoadProvisionerByName(name); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", name))
render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", name))
return
}
}
if err := auth.RemoveProvisioner(r.Context(), p.GetID()); err != nil {
render.Error(w, admin.WrapErrorISE(err, "error removing provisioner %s", p.GetName()))
render.Error(w, r, admin.WrapErrorISE(err, "error removing provisioner %s", p.GetName()))
return
}
render.JSON(w, &DeleteResponse{Status: "ok"})
render.JSON(w, r, &DeleteResponse{Status: "ok"})
}
// UpdateProvisioner updates an existing prov.
func UpdateProvisioner(w http.ResponseWriter, r *http.Request) {
var nu = new(linkedca.Provisioner)
if err := read.ProtoJSON(r.Body, nu); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -151,51 +151,51 @@ func UpdateProvisioner(w http.ResponseWriter, r *http.Request) {
p, err := auth.LoadProvisionerByName(name)
if err != nil {
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner from cached configuration '%s'", name))
render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner from cached configuration '%s'", name))
return
}
old, err := db.GetProvisioner(r.Context(), p.GetID())
if err != nil {
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner from db '%s'", p.GetID()))
render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner from db '%s'", p.GetID()))
return
}
if nu.Id != old.Id {
render.Error(w, admin.NewErrorISE("cannot change provisioner ID"))
render.Error(w, r, admin.NewErrorISE("cannot change provisioner ID"))
return
}
if nu.Type != old.Type {
render.Error(w, admin.NewErrorISE("cannot change provisioner type"))
render.Error(w, r, admin.NewErrorISE("cannot change provisioner type"))
return
}
if nu.AuthorityId != old.AuthorityId {
render.Error(w, admin.NewErrorISE("cannot change provisioner authorityID"))
render.Error(w, r, admin.NewErrorISE("cannot change provisioner authorityID"))
return
}
if !nu.CreatedAt.AsTime().Equal(old.CreatedAt.AsTime()) {
render.Error(w, admin.NewErrorISE("cannot change provisioner createdAt"))
render.Error(w, r, admin.NewErrorISE("cannot change provisioner createdAt"))
return
}
if !nu.DeletedAt.AsTime().Equal(old.DeletedAt.AsTime()) {
render.Error(w, admin.NewErrorISE("cannot change provisioner deletedAt"))
render.Error(w, r, admin.NewErrorISE("cannot change provisioner deletedAt"))
return
}
// TODO: Validate inputs
if err := authority.ValidateClaims(nu.Claims); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
// validate the templates and template data
if err := validateTemplates(nu.X509Template, nu.SshTemplate); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "invalid template"))
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "invalid template"))
return
}
if err := auth.UpdateProvisioner(r.Context(), nu); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
render.ProtoJSON(w, nu)

@ -71,28 +71,28 @@ func (war *webhookAdminResponder) CreateProvisionerWebhook(w http.ResponseWriter
var newWebhook = new(linkedca.Webhook)
if err := read.ProtoJSON(r.Body, newWebhook); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
if err := validateWebhook(newWebhook); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
if newWebhook.Secret != "" {
err := admin.NewError(admin.ErrorBadRequestType, "webhook secret must not be set")
render.Error(w, err)
render.Error(w, r, err)
return
}
if newWebhook.Id != "" {
err := admin.NewError(admin.ErrorBadRequestType, "webhook ID must not be set")
render.Error(w, err)
render.Error(w, r, err)
return
}
id, err := randutil.UUIDv4()
if err != nil {
render.Error(w, admin.WrapErrorISE(err, "error generating webhook id"))
render.Error(w, r, admin.WrapErrorISE(err, "error generating webhook id"))
return
}
newWebhook.Id = id
@ -101,14 +101,14 @@ func (war *webhookAdminResponder) CreateProvisionerWebhook(w http.ResponseWriter
for _, wh := range prov.Webhooks {
if wh.Name == newWebhook.Name {
err := admin.NewError(admin.ErrorConflictType, "provisioner %q already has a webhook with the name %q", prov.Name, newWebhook.Name)
render.Error(w, err)
render.Error(w, r, err)
return
}
}
secret, err := randutil.Bytes(64)
if err != nil {
render.Error(w, admin.WrapErrorISE(err, "error generating webhook secret"))
render.Error(w, r, admin.WrapErrorISE(err, "error generating webhook secret"))
return
}
newWebhook.Secret = base64.StdEncoding.EncodeToString(secret)
@ -117,11 +117,11 @@ func (war *webhookAdminResponder) CreateProvisionerWebhook(w http.ResponseWriter
if err := auth.UpdateProvisioner(ctx, prov); err != nil {
if isBadRequest(err) {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error creating provisioner webhook"))
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error creating provisioner webhook"))
return
}
render.Error(w, admin.WrapErrorISE(err, "error creating provisioner webhook"))
render.Error(w, r, admin.WrapErrorISE(err, "error creating provisioner webhook"))
return
}
@ -145,21 +145,21 @@ func (war *webhookAdminResponder) DeleteProvisionerWebhook(w http.ResponseWriter
}
}
if !found {
render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK)
render.JSONStatus(w, r, DeleteResponse{Status: "ok"}, http.StatusOK)
return
}
if err := auth.UpdateProvisioner(ctx, prov); err != nil {
if isBadRequest(err) {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error deleting provisioner webhook"))
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error deleting provisioner webhook"))
return
}
render.Error(w, admin.WrapErrorISE(err, "error deleting provisioner webhook"))
render.Error(w, r, admin.WrapErrorISE(err, "error deleting provisioner webhook"))
return
}
render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK)
render.JSONStatus(w, r, DeleteResponse{Status: "ok"}, http.StatusOK)
}
func (war *webhookAdminResponder) UpdateProvisionerWebhook(w http.ResponseWriter, r *http.Request) {
@ -170,12 +170,12 @@ func (war *webhookAdminResponder) UpdateProvisionerWebhook(w http.ResponseWriter
var newWebhook = new(linkedca.Webhook)
if err := read.ProtoJSON(r.Body, newWebhook); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
if err := validateWebhook(newWebhook); err != nil {
render.Error(w, err)
render.Error(w, r, err)
return
}
@ -186,13 +186,13 @@ func (war *webhookAdminResponder) UpdateProvisionerWebhook(w http.ResponseWriter
}
if newWebhook.Secret != "" && newWebhook.Secret != wh.Secret {
err := admin.NewError(admin.ErrorBadRequestType, "webhook secret cannot be updated")
render.Error(w, err)
render.Error(w, r, err)
return
}
newWebhook.Secret = wh.Secret
if newWebhook.Id != "" && newWebhook.Id != wh.Id {
err := admin.NewError(admin.ErrorBadRequestType, "webhook ID cannot be updated")
render.Error(w, err)
render.Error(w, r, err)
return
}
newWebhook.Id = wh.Id
@ -203,17 +203,17 @@ func (war *webhookAdminResponder) UpdateProvisionerWebhook(w http.ResponseWriter
if !found {
msg := fmt.Sprintf("provisioner %q has no webhook with the name %q", prov.Name, newWebhook.Name)
err := admin.NewError(admin.ErrorNotFoundType, msg)
render.Error(w, err)
render.Error(w, r, err)
return
}
if err := auth.UpdateProvisioner(ctx, prov); err != nil {
if isBadRequest(err) {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error updating provisioner webhook"))
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error updating provisioner webhook"))
return
}
render.Error(w, admin.WrapErrorISE(err, "error updating provisioner webhook"))
render.Error(w, r, admin.WrapErrorISE(err, "error updating provisioner webhook"))
return
}

@ -857,7 +857,7 @@ func TestDB_CreateAdmin(t *testing.T) {
var _dba = new(dbAdmin)
assert.FatalError(t, json.Unmarshal(nu, _dba))
assert.True(t, len(_dba.ID) > 0 && _dba.ID == string(key))
assert.True(t, _dba.ID != "" && _dba.ID == string(key))
assert.Equals(t, _dba.AuthorityID, adm.AuthorityId)
assert.Equals(t, _dba.ProvisionerID, adm.ProvisionerId)
assert.Equals(t, _dba.Subject, adm.Subject)
@ -890,7 +890,7 @@ func TestDB_CreateAdmin(t *testing.T) {
var _dba = new(dbAdmin)
assert.FatalError(t, json.Unmarshal(nu, _dba))
assert.True(t, len(_dba.ID) > 0 && _dba.ID == string(key))
assert.True(t, _dba.ID != "" && _dba.ID == string(key))
assert.Equals(t, _dba.AuthorityID, adm.AuthorityId)
assert.Equals(t, _dba.ProvisionerID, adm.ProvisionerId)
assert.Equals(t, _dba.Subject, adm.Subject)

@ -906,7 +906,7 @@ func TestDB_CreateProvisioner(t *testing.T) {
var _dbp = new(dbProvisioner)
assert.FatalError(t, json.Unmarshal(nu, _dbp))
assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key))
assert.True(t, _dbp.ID != "" && _dbp.ID == string(key))
assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId)
assert.Equals(t, _dbp.Type, prov.Type)
assert.Equals(t, _dbp.Name, prov.Name)
@ -944,7 +944,7 @@ func TestDB_CreateProvisioner(t *testing.T) {
var _dbp = new(dbProvisioner)
assert.FatalError(t, json.Unmarshal(nu, _dbp))
assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key))
assert.True(t, _dbp.ID != "" && _dbp.ID == string(key))
assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId)
assert.Equals(t, _dbp.Type, prov.Type)
assert.Equals(t, _dbp.Name, prov.Name)
@ -1093,7 +1093,7 @@ func TestDB_UpdateProvisioner(t *testing.T) {
var _dbp = new(dbProvisioner)
assert.FatalError(t, json.Unmarshal(nu, _dbp))
assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key))
assert.True(t, _dbp.ID != "" && _dbp.ID == string(key))
assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId)
assert.Equals(t, _dbp.Type, prov.Type)
assert.Equals(t, _dbp.Name, prov.Name)
@ -1188,7 +1188,7 @@ func TestDB_UpdateProvisioner(t *testing.T) {
var _dbp = new(dbProvisioner)
assert.FatalError(t, json.Unmarshal(nu, _dbp))
assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key))
assert.True(t, _dbp.ID != "" && _dbp.ID == string(key))
assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId)
assert.Equals(t, _dbp.Type, prov.Type)
assert.Equals(t, _dbp.Name, prov.Name)

@ -205,8 +205,8 @@ func (e *Error) ToLog() (interface{}, error) {
}
// Render implements render.RenderableError for Error.
func (e *Error) Render(w http.ResponseWriter) {
func (e *Error) Render(w http.ResponseWriter, r *http.Request) {
e.Message = e.Err.Error()
render.JSONStatus(w, e, e.StatusCode())
render.JSONStatus(w, r, e, e.StatusCode())
}

@ -62,9 +62,10 @@ type Authority struct {
x509Enforcers []provisioner.CertificateEnforcer
// SCEP CA
scepOptions *scep.Options
validateSCEP bool
scepAuthority *scep.Authority
scepOptions *scep.Options
validateSCEP bool
scepAuthority *scep.Authority
scepKeyManager provisioner.SCEPKeyManager
// SSH CA
sshHostPassword []byte
@ -139,7 +140,7 @@ func New(cfg *config.Config, opts ...Option) (*Authority, error) {
}
}
if a.keyManager != nil {
a.keyManager = &instrumentedKeyManager{a.keyManager, a.meter}
a.keyManager = newInstrumentedKeyManager(a.keyManager, a.meter)
}
if !a.skipInit {
@ -168,7 +169,7 @@ func NewEmbedded(opts ...Option) (*Authority, error) {
}
}
if a.keyManager != nil {
a.keyManager = &instrumentedKeyManager{a.keyManager, a.meter}
a.keyManager = newInstrumentedKeyManager(a.keyManager, a.meter)
}
// Validate required options
@ -349,7 +350,7 @@ func (a *Authority) init() error {
return err
}
a.keyManager = &instrumentedKeyManager{a.keyManager, a.meter}
a.keyManager = newInstrumentedKeyManager(a.keyManager, a.meter)
}
// Initialize linkedca client if necessary. On a linked RA, the issuer
@ -446,6 +447,7 @@ func (a *Authority) init() error {
return err
}
a.rootX509Certs = append(a.rootX509Certs, resp.RootCertificate)
a.intermediateX509Certs = append(a.intermediateX509Certs, resp.IntermediateCertificates...)
}
}
@ -694,32 +696,42 @@ func (a *Authority) init() error {
options := &scep.Options{
Roots: a.rootX509Certs,
Intermediates: a.intermediateX509Certs,
SignerCert: a.intermediateX509Certs[0],
}
if options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.IntermediateKey,
Password: a.password,
}); err != nil {
return err
// intermediate certificates can be empty in RA mode
if len(a.intermediateX509Certs) > 0 {
options.SignerCert = a.intermediateX509Certs[0]
}
// TODO(hs): instead of creating the decrypter here, pass the
// intermediate key + chain down to the SCEP authority,
// and only instantiate it when required there. Is that possible?
// Also with entering passwords?
// TODO(hs): if moving the logic, try improving the logic for the
// decrypter password too? Right now it needs to be entered multiple
// times; I've observed it to be three times maximum, every time
// the intermediate key is read.
_, isRSA := options.Signer.Public().(*rsa.PublicKey)
if km, ok := a.keyManager.(kmsapi.Decrypter); ok && isRSA {
if decrypter, err := km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKey: a.config.IntermediateKey,
Password: a.password,
}); err == nil {
// only pass the decrypter down when it was successfully created,
// meaning it's an RSA key, and `CreateDecrypter` did not fail.
options.Decrypter = decrypter
options.DecrypterCert = options.Intermediates[0]
// attempt to create the (default) SCEP signer if the intermediate
// key is configured.
if a.config.IntermediateKey != "" {
if options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.IntermediateKey,
Password: a.password,
}); err != nil {
return err
}
// TODO(hs): instead of creating the decrypter here, pass the
// intermediate key + chain down to the SCEP authority,
// and only instantiate it when required there. Is that possible?
// Also with entering passwords?
// TODO(hs): if moving the logic, try improving the logic for the
// decrypter password too? Right now it needs to be entered multiple
// times; I've observed it to be three times maximum, every time
// the intermediate key is read.
_, isRSAKey := options.Signer.Public().(*rsa.PublicKey)
if km, ok := a.keyManager.(kmsapi.Decrypter); ok && isRSAKey {
if decrypter, err := km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKey: a.config.IntermediateKey,
Password: a.password,
}); err == nil {
// only pass the decrypter down when it was successfully created,
// meaning it's an RSA key, and `CreateDecrypter` did not fail.
options.Decrypter = decrypter
options.DecrypterCert = options.Intermediates[0]
}
}
}

@ -113,7 +113,7 @@ func TestAuthorityNew(t *testing.T) {
c.Root = []string{"foo"}
return &newTest{
config: c,
err: errors.New("error reading foo: no such file or directory"),
err: errors.New(`error reading "foo": no such file or directory`),
}
},
"fail bad password": func(t *testing.T) *newTest {
@ -131,7 +131,7 @@ func TestAuthorityNew(t *testing.T) {
c.IntermediateCert = "wrong"
return &newTest{
config: c,
err: errors.New("error reading wrong: no such file or directory"),
err: errors.New(`error reading "wrong": no such file or directory`),
}
},
}

@ -203,7 +203,7 @@ func matchURIConstraint(uri *url.URL, constraint string) (bool, error) {
// domainToReverseLabels converts a textual domain name like foo.example.com to
// the list of labels in reverse order, e.g. ["com", "example", "foo"].
func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) {
for len(domain) > 0 {
for domain != "" {
if i := strings.LastIndexByte(domain, '.'); i == -1 {
reverseLabels = append(reverseLabels, domain)
domain = ""
@ -316,7 +316,7 @@ func parseRFC2821Mailbox(in string) (mailbox rfc2821Mailbox, ok bool) {
} else {
// Atom ("." Atom)*
NextChar:
for len(in) > 0 {
for in != "" {
// atext from RFC 2822, Section 3.2.4
c := in[0]

@ -110,7 +110,7 @@ func newLinkedCAClient(token string) (*linkedCaClient, error) {
tlsConfig.GetClientCertificate = renewer.GetClientCertificate
// Start mTLS client
conn, err := grpc.Dial(u.Host, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
conn, err := grpc.NewClient(u.Host, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
if err != nil {
return nil, errors.Wrapf(err, "error connecting %s", u.Host)
}
@ -478,10 +478,7 @@ func getAuthority(sans []string) (string, error) {
// getRootCertificate creates an insecure majordomo client and returns the
// verified root certificate.
func getRootCertificate(endpoint, fingerprint string) (*x509.Certificate, error) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
conn, err := grpc.DialContext(ctx, endpoint, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
conn, err := grpc.NewClient(endpoint, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
//nolint:gosec // used in bootstrap protocol
InsecureSkipVerify: true, // lgtm[go/disabled-certificate-check]
})))
@ -489,7 +486,7 @@ func getRootCertificate(endpoint, fingerprint string) (*x509.Certificate, error)
return nil, errors.Wrapf(err, "error connecting %s", endpoint)
}
ctx, cancel = context.WithTimeout(context.Background(), 15*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
client := linkedca.NewMajordomoClient(conn)
@ -531,11 +528,7 @@ func getRootCertificate(endpoint, fingerprint string) (*x509.Certificate, error)
// login creates a new majordomo client with just the root ca pool and returns
// the signed certificate and tls configuration.
func login(authority, token string, csr *x509.CertificateRequest, signer crypto.PrivateKey, endpoint string, rootCAs *x509.CertPool) (*tls.Certificate, *tls.Config, error) {
// Connect to majordomo
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
conn, err := grpc.DialContext(ctx, endpoint, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
conn, err := grpc.NewClient(endpoint, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
MinVersion: tls.VersionTLS12,
RootCAs: rootCAs,
})))
@ -544,7 +537,7 @@ func login(authority, token string, csr *x509.CertificateRequest, signer crypto.
}
// Login to get the signed certificate
ctx, cancel = context.WithTimeout(context.Background(), 15*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
client := linkedca.NewMajordomoClient(conn)

@ -66,6 +66,22 @@ type instrumentedKeyManager struct {
meter Meter
}
type instrumentedKeyAndDecrypterManager struct {
kms.KeyManager
decrypter kmsapi.Decrypter
meter Meter
}
func newInstrumentedKeyManager(k kms.KeyManager, m Meter) kms.KeyManager {
decrypter, isDecrypter := k.(kmsapi.Decrypter)
switch {
case isDecrypter:
return &instrumentedKeyAndDecrypterManager{&instrumentedKeyManager{k, m}, decrypter, m}
default:
return &instrumentedKeyManager{k, m}
}
}
func (i *instrumentedKeyManager) CreateSigner(req *kmsapi.CreateSignerRequest) (s crypto.Signer, err error) {
if s, err = i.KeyManager.CreateSigner(req); err == nil {
s = &instrumentedKMSSigner{s, i.meter}
@ -74,6 +90,10 @@ func (i *instrumentedKeyManager) CreateSigner(req *kmsapi.CreateSignerRequest) (
return
}
func (i *instrumentedKeyAndDecrypterManager) CreateDecrypter(req *kmsapi.CreateDecrypterRequest) (s crypto.Decrypter, err error) {
return i.decrypter.CreateDecrypter(req)
}
type instrumentedKMSSigner struct {
crypto.Signer
meter Meter
@ -85,3 +105,7 @@ func (i *instrumentedKMSSigner) Sign(rand io.Reader, digest []byte, opts crypto.
return
}
var _ kms.KeyManager = (*instrumentedKeyManager)(nil)
var _ kms.KeyManager = (*instrumentedKeyAndDecrypterManager)(nil)
var _ kmsapi.Decrypter = (*instrumentedKeyAndDecrypterManager)(nil)

@ -226,6 +226,16 @@ func WithFullSCEPOptions(options *scep.Options) Option {
}
}
// WithSCEPKeyManager defines the key manager used on SCEP provisioners.
//
// This feature is EXPERIMENTAL and might change at any time.
func WithSCEPKeyManager(skm provisioner.SCEPKeyManager) Option {
return func(a *Authority) error {
a.scepKeyManager = skm
return nil
}
}
// WithSSHUserSigner defines the signer used to sign SSH user certificates.
func WithSSHUserSigner(s crypto.Signer) Option {
return func(a *Authority) error {

@ -21,6 +21,7 @@ type HostPolicy policy.SSHNamePolicyEngine
func NewX509PolicyEngine(policyOptions X509PolicyOptionsInterface) (X509Policy, error) {
// return early if no policy engine options to configure
if policyOptions == nil {
//nolint:nilnil,nolintlint // expected values
return nil, nil
}
@ -50,6 +51,7 @@ func NewX509PolicyEngine(policyOptions X509PolicyOptionsInterface) (X509Policy,
// ensure no policy engine is returned when no name options were provided
if len(options) == 0 {
//nolint:nilnil,nolintlint // expected values
return nil, nil
}
@ -93,6 +95,7 @@ func NewSSHHostPolicyEngine(policyOptions SSHPolicyOptionsInterface) (HostPolicy
func newSSHPolicyEngine(policyOptions SSHPolicyOptionsInterface, typ sshPolicyEngineType) (policy.SSHNamePolicyEngine, error) {
// return early if no policy engine options to configure
if policyOptions == nil {
//nolint:nilnil,nolintlint // expected values
return nil, nil
}
@ -134,6 +137,7 @@ func newSSHPolicyEngine(policyOptions SSHPolicyOptionsInterface, typ sshPolicyEn
// ensure no policy engine is returned when no name options were provided
if len(options) == 0 {
//nolint:nilnil,nolintlint // expected values
return nil, nil
}

@ -10,7 +10,6 @@ import (
"time"
"github.com/pkg/errors"
"github.com/smallstep/certificates/acme/wire"
"go.step.sm/linkedca"
)
@ -27,10 +26,6 @@ const (
TLS_ALPN_01 ACMEChallenge = "tls-alpn-01"
// DEVICE_ATTEST_01 is the device-attest-01 ACME challenge.
DEVICE_ATTEST_01 ACMEChallenge = "device-attest-01"
// WIREOIDC_01 is the Wire OIDC challenge.
WIREOIDC_01 ACMEChallenge = "wire-oidc-01"
// WIREDPOP_01 is the Wire DPoP challenge.
WIREDPOP_01 ACMEChallenge = "wire-dpop-01"
)
// String returns a normalized version of the challenge.
@ -41,7 +36,7 @@ func (c ACMEChallenge) String() string {
// Validate returns an error if the acme challenge is not a valid one.
func (c ACMEChallenge) Validate() error {
switch ACMEChallenge(c.String()) {
case HTTP_01, DNS_01, TLS_ALPN_01, DEVICE_ATTEST_01, WIREOIDC_01, WIREDPOP_01:
case HTTP_01, DNS_01, TLS_ALPN_01, DEVICE_ATTEST_01:
return nil
default:
return fmt.Errorf("acme challenge %q is not supported", c)
@ -107,8 +102,7 @@ type ACME struct {
RequireEAB bool `json:"requireEAB,omitempty"`
// Challenges contains the enabled challenges for this provisioner. If this
// value is not set the default http-01, dns-01 and tls-alpn-01 challenges
// will be enabled, device-attest-01, wire-oidc-01 and wire-dpop-01 will be
// disabled.
// will be enabled, device-attest-01 will be disabled.
Challenges []ACMEChallenge `json:"challenges,omitempty"`
// AttestationFormats contains the enabled attestation formats for this
// provisioner. If this value is not set the default apple, step and tpm
@ -212,50 +206,10 @@ func (p *ACME) Init(config Config) (err error) {
}
}
if err := p.initializeWireOptions(); err != nil {
return fmt.Errorf("failed initializing Wire options: %w", err)
}
p.ctl, err = NewController(p, p.Claims, config, p.Options)
return
}
// initializeWireOptions initializes the options for the ACME Wire
// integration. It'll return early if no Wire challenge types are
// enabled.
func (p *ACME) initializeWireOptions() error {
hasWireChallenges := false
for _, c := range p.Challenges {
if c == WIREOIDC_01 || c == WIREDPOP_01 {
hasWireChallenges = true
break
}
}
if !hasWireChallenges {
return nil
}
w, err := p.GetOptions().GetWireOptions()
if err != nil {
return fmt.Errorf("failed getting Wire options: %w", err)
}
if err := w.Validate(); err != nil {
return fmt.Errorf("failed validating Wire options: %w", err)
}
// at this point the Wire options have been validated, and (mostly)
// initialized. Remote keys will be loaded upon the first verification,
// currently.
// TODO(hs): can/should we "prime" the underlying remote keyset, to verify
// auto discovery works as expected? Because of the current way provisioners
// are initialized, doing that as part of the initialization isn't the best
// time to do it, because it could result in operations not resulting in the
// expected result in all cases.
return nil
}
// ACMEIdentifierType encodes ACME Identifier types
type ACMEIdentifierType string
@ -264,10 +218,6 @@ const (
IP ACMEIdentifierType = "ip"
// DNS is the ACME dns identifier type
DNS ACMEIdentifierType = "dns"
// WireUser is the Wire user identifier type
WireUser ACMEIdentifierType = "wireapp-user"
// WireDevice is the Wire device identifier type
WireDevice ACMEIdentifierType = "wireapp-device"
)
// ACMEIdentifier encodes ACME Order Identifiers
@ -293,18 +243,6 @@ func (p *ACME) AuthorizeOrderIdentifier(_ context.Context, identifier ACMEIdenti
err = x509Policy.IsIPAllowed(net.ParseIP(identifier.Value))
case DNS:
err = x509Policy.IsDNSAllowed(identifier.Value)
case WireUser:
var wireID wire.UserID
if wireID, err = wire.ParseUserID(identifier.Value); err != nil {
return fmt.Errorf("failed parsing Wire SANs: %w", err)
}
err = x509Policy.AreSANsAllowed([]string{wireID.Handle})
case WireDevice:
var wireID wire.DeviceID
if wireID, err = wire.ParseDeviceID(identifier.Value); err != nil {
return fmt.Errorf("failed parsing Wire SANs: %w", err)
}
err = x509Policy.AreSANsAllowed([]string{wireID.ClientID})
default:
err = fmt.Errorf("invalid ACME identifier type '%s' provided", identifier.Type)
}

@ -1,3 +1,6 @@
//go:build !go1.18
// +build !go1.18
package provisioner
import (
@ -11,10 +14,8 @@ import (
"testing"
"time"
"github.com/smallstep/assert"
"github.com/smallstep/certificates/api/render"
"github.com/smallstep/certificates/authority/provisioner/wire"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestACMEChallenge_Validate(t *testing.T) {
@ -27,20 +28,14 @@ func TestACMEChallenge_Validate(t *testing.T) {
{"dns-01", DNS_01, false},
{"tls-alpn-01", TLS_ALPN_01, false},
{"device-attest-01", DEVICE_ATTEST_01, false},
{"wire-oidc-01", DEVICE_ATTEST_01, false},
{"wire-dpop-01", DEVICE_ATTEST_01, false},
{"uppercase", "HTTP-01", false},
{"fail", "http-02", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.c.Validate()
if tt.wantErr {
assert.Error(t, err)
return
if err := tt.c.Validate(); (err != nil) != tt.wantErr {
t.Errorf("ACMEChallenge.Validate() error = %v, wantErr %v", err, tt.wantErr)
}
assert.NoError(t, err)
})
}
}
@ -59,24 +54,26 @@ func TestACMEAttestationFormat_Validate(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.f.Validate()
if tt.wantErr {
assert.Error(t, err)
return
if err := tt.f.Validate(); (err != nil) != tt.wantErr {
t.Errorf("ACMEAttestationFormat.Validate() error = %v, wantErr %v", err, tt.wantErr)
}
assert.NoError(t, err)
})
}
}
func TestACME_Getters(t *testing.T) {
p, err := generateACME()
require.NoError(t, err)
id := "acme/test@acme-provisioner.com"
assert.Equal(t, id, p.GetID())
assert.Equal(t, "test@acme-provisioner.com", p.GetName())
assert.Equal(t, TypeACME, p.GetType())
assert.FatalError(t, err)
id := "acme/" + p.Name
if got := p.GetID(); got != id {
t.Errorf("ACME.GetID() = %v, want %v", got, id)
}
if got := p.GetName(); got != p.Name {
t.Errorf("ACME.GetName() = %v, want %v", got, p.Name)
}
if got := p.GetType(); got != TypeACME {
t.Errorf("ACME.GetType() = %v, want %v", got, TypeACME)
}
kid, key, ok := p.GetEncryptedKey()
if kid != "" || key != "" || ok == true {
t.Errorf("ACME.GetEncryptedKey() = (%v, %v, %v), want (%v, %v, %v)",
@ -86,25 +83,26 @@ func TestACME_Getters(t *testing.T) {
func TestACME_Init(t *testing.T) {
appleCA, err := os.ReadFile("testdata/certs/apple-att-ca.crt")
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
yubicoCA, err := os.ReadFile("testdata/certs/yubico-piv-ca.crt")
require.NoError(t, err)
fakeWireDPoPKey := []byte(`-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
-----END PUBLIC KEY-----`)
if err != nil {
t.Fatal(err)
}
type ProvisionerValidateTest struct {
p *ACME
err error
}
tests := map[string]func(*testing.T) ProvisionerValidateTest{
"fail/empty": func(t *testing.T) ProvisionerValidateTest {
"fail-empty": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{},
err: errors.New("provisioner type cannot be empty"),
}
},
"fail/empty-name": func(t *testing.T) ProvisionerValidateTest {
"fail-empty-name": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{
Type: "ACME",
@ -112,119 +110,60 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
err: errors.New("provisioner name cannot be empty"),
}
},
"fail/empty-type": func(t *testing.T) ProvisionerValidateTest {
"fail-empty-type": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{Name: "foo"},
err: errors.New("provisioner type cannot be empty"),
}
},
"fail/bad-claims": func(t *testing.T) ProvisionerValidateTest {
"fail-bad-claims": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{Name: "foo", Type: "ACME", Claims: &Claims{DefaultTLSDur: &Duration{0}}},
p: &ACME{Name: "foo", Type: "bar", Claims: &Claims{DefaultTLSDur: &Duration{0}}},
err: errors.New("claims: MinTLSCertDuration must be greater than 0"),
}
},
"fail/bad-challenge": func(t *testing.T) ProvisionerValidateTest {
"fail-bad-challenge": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{Name: "foo", Type: "ACME", Challenges: []ACMEChallenge{HTTP_01, "zar"}},
p: &ACME{Name: "foo", Type: "bar", Challenges: []ACMEChallenge{HTTP_01, "zar"}},
err: errors.New("acme challenge \"zar\" is not supported"),
}
},
"fail/bad-attestation-format": func(t *testing.T) ProvisionerValidateTest {
"fail-bad-attestation-format": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{Name: "foo", Type: "ACME", AttestationFormats: []ACMEAttestationFormat{APPLE, "zar"}},
p: &ACME{Name: "foo", Type: "bar", AttestationFormats: []ACMEAttestationFormat{APPLE, "zar"}},
err: errors.New("acme attestation format \"zar\" is not supported"),
}
},
"fail/parse-attestation-roots": func(t *testing.T) ProvisionerValidateTest {
"fail-parse-attestation-roots": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{Name: "foo", Type: "ACME", AttestationRoots: []byte("-----BEGIN CERTIFICATE-----\nZm9v\n-----END CERTIFICATE-----")},
p: &ACME{Name: "foo", Type: "bar", AttestationRoots: []byte("-----BEGIN CERTIFICATE-----\nZm9v\n-----END CERTIFICATE-----")},
err: errors.New("error parsing attestationRoots: malformed certificate"),
}
},
"fail/empty-attestation-roots": func(t *testing.T) ProvisionerValidateTest {
"fail-empty-attestation-roots": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{Name: "foo", Type: "ACME", AttestationRoots: []byte("\n")},
p: &ACME{Name: "foo", Type: "bar", AttestationRoots: []byte("\n")},
err: errors.New("error parsing attestationRoots: no certificates found"),
}
},
"fail/wire-missing-options": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{
Name: "foo",
Type: "ACME",
Challenges: []ACMEChallenge{WIREOIDC_01, WIREDPOP_01},
},
err: errors.New("failed initializing Wire options: failed getting Wire options: no options available"),
}
},
"fail/wire-missing-wire-options": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{
Name: "foo",
Type: "ACME",
Challenges: []ACMEChallenge{WIREOIDC_01, WIREDPOP_01},
Options: &Options{},
},
err: errors.New("failed initializing Wire options: failed getting Wire options: no Wire options available"),
}
},
"fail/wire-validate-options": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{
Name: "foo",
Type: "ACME",
Challenges: []ACMEChallenge{WIREOIDC_01, WIREDPOP_01},
Options: &Options{
Wire: &wire.Options{
OIDC: &wire.OIDCOptions{},
DPOP: &wire.DPOPOptions{
SigningKey: fakeWireDPoPKey,
},
},
},
},
err: errors.New("failed initializing Wire options: failed validating Wire options: failed initializing OIDC options: provider not set"),
}
},
"ok": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{Name: "foo", Type: "ACME"},
p: &ACME{Name: "foo", Type: "bar"},
}
},
"ok/attestation": func(t *testing.T) ProvisionerValidateTest {
"ok attestation": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{
Name: "foo",
Type: "ACME",
Type: "bar",
Challenges: []ACMEChallenge{DNS_01, DEVICE_ATTEST_01},
AttestationFormats: []ACMEAttestationFormat{APPLE, STEP},
AttestationRoots: bytes.Join([][]byte{appleCA, yubicoCA}, []byte("\n")),
},
}
},
"ok/wire": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{
Name: "foo",
Type: "ACME",
Challenges: []ACMEChallenge{WIREOIDC_01, WIREDPOP_01},
Options: &Options{
Wire: &wire.Options{
OIDC: &wire.OIDCOptions{
Provider: &wire.Provider{
IssuerURL: "https://issuer.example.com",
},
},
DPOP: &wire.DPOPOptions{
SigningKey: fakeWireDPoPKey,
},
},
},
},
}
},
}
config := Config{
Claims: globalProvisionerClaims,
Audiences: testAudiences,
@ -234,12 +173,13 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
tc := get(t)
t.Log(string(tc.p.AttestationRoots))
err := tc.p.Init(config)
if tc.err != nil {
assert.EqualError(t, err, tc.err.Error())
return
if err != nil {
if assert.NotNil(t, tc.err) {
assert.Equals(t, tc.err.Error(), err.Error())
}
} else {
assert.Nil(t, tc.err)
}
assert.NoError(t, err)
})
}
}
@ -255,12 +195,12 @@ func TestACME_AuthorizeRenew(t *testing.T) {
tests := map[string]func(*testing.T) test{
"fail/renew-disabled": func(t *testing.T) test {
p, err := generateACME()
require.NoError(t, err)
assert.FatalError(t, err)
// disable renewal
disable := true
p.Claims = &Claims{DisableRenewal: &disable}
p.ctl.Claimer, err = NewClaimer(p.Claims, globalProvisionerClaims)
require.NoError(t, err)
assert.FatalError(t, err)
return test{
p: p,
cert: &x509.Certificate{
@ -273,7 +213,7 @@ func TestACME_AuthorizeRenew(t *testing.T) {
},
"ok": func(t *testing.T) test {
p, err := generateACME()
require.NoError(t, err)
assert.FatalError(t, err)
return test{
p: p,
cert: &x509.Certificate{
@ -286,19 +226,16 @@ func TestACME_AuthorizeRenew(t *testing.T) {
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
tc := tt(t)
err := tc.p.AuthorizeRenew(context.Background(), tc.cert)
if tc.err != nil {
if assert.Implements(t, (*render.StatusCodedError)(nil), err) {
var sc render.StatusCodedError
if errors.As(err, &sc) {
assert.Equal(t, tc.code, sc.StatusCode())
}
if err := tc.p.AuthorizeRenew(context.Background(), tc.cert); err != nil {
sc, ok := err.(render.StatusCodedError)
assert.Fatal(t, ok, "error does not implement StatusCodedError interface")
assert.Equals(t, sc.StatusCode(), tc.code)
if assert.NotNil(t, tc.err) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
}
assert.EqualError(t, err, tc.err.Error())
return
} else {
assert.Nil(t, tc.err)
}
assert.NoError(t, err)
})
}
}
@ -313,7 +250,7 @@ func TestACME_AuthorizeSign(t *testing.T) {
tests := map[string]func(*testing.T) test{
"ok": func(t *testing.T) test {
p, err := generateACME()
require.NoError(t, err)
assert.FatalError(t, err)
return test{
p: p,
token: "foo",
@ -323,43 +260,39 @@ func TestACME_AuthorizeSign(t *testing.T) {
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
tc := tt(t)
opts, err := tc.p.AuthorizeSign(context.Background(), tc.token)
if tc.err != nil {
if assert.Implements(t, (*render.StatusCodedError)(nil), err) {
var sc render.StatusCodedError
if errors.As(err, &sc) {
assert.Equal(t, tc.code, sc.StatusCode())
}
if opts, err := tc.p.AuthorizeSign(context.Background(), tc.token); err != nil {
if assert.NotNil(t, tc.err) {
sc, ok := err.(render.StatusCodedError)
assert.Fatal(t, ok, "error does not implement StatusCodedError interface")
assert.Equals(t, sc.StatusCode(), tc.code)
assert.HasPrefix(t, err.Error(), tc.err.Error())
}
assert.EqualError(t, err, tc.err.Error())
return
}
assert.NoError(t, err)
if assert.NotNil(t, opts) {
assert.Len(t, opts, 8) // number of SignOptions returned
for _, o := range opts {
switch v := o.(type) {
case *ACME:
case *provisionerExtensionOption:
assert.Equal(t, v.Type, TypeACME)
assert.Equal(t, v.Name, tc.p.GetName())
assert.Equal(t, v.CredentialID, "")
assert.Len(t, v.KeyValuePairs, 0)
case *forceCNOption:
assert.Equal(t, v.ForceCN, tc.p.ForceCN)
case profileDefaultDuration:
assert.Equal(t, time.Duration(v), tc.p.ctl.Claimer.DefaultTLSCertDuration())
case defaultPublicKeyValidator:
case *validityValidator:
assert.Equal(t, v.min, tc.p.ctl.Claimer.MinTLSCertDuration())
assert.Equal(t, v.max, tc.p.ctl.Claimer.MaxTLSCertDuration())
case *x509NamePolicyValidator:
assert.Equal(t, nil, v.policyEngine)
case *WebhookController:
assert.Len(t, v.webhooks, 0)
default:
require.NoError(t, fmt.Errorf("unexpected sign option of type %T", v))
} else {
if assert.Nil(t, tc.err) && assert.NotNil(t, opts) {
assert.Equals(t, 8, len(opts)) // number of SignOptions returned
for _, o := range opts {
switch v := o.(type) {
case *ACME:
case *provisionerExtensionOption:
assert.Equals(t, v.Type, TypeACME)
assert.Equals(t, v.Name, tc.p.GetName())
assert.Equals(t, v.CredentialID, "")
assert.Len(t, 0, v.KeyValuePairs)
case *forceCNOption:
assert.Equals(t, v.ForceCN, tc.p.ForceCN)
case profileDefaultDuration:
assert.Equals(t, time.Duration(v), tc.p.ctl.Claimer.DefaultTLSCertDuration())
case defaultPublicKeyValidator:
case *validityValidator:
assert.Equals(t, v.min, tc.p.ctl.Claimer.MinTLSCertDuration())
assert.Equals(t, v.max, tc.p.ctl.Claimer.MaxTLSCertDuration())
case *x509NamePolicyValidator:
assert.Equals(t, nil, v.policyEngine)
case *WebhookController:
assert.Len(t, 0, v.webhooks)
default:
assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v))
}
}
}
}
@ -390,14 +323,10 @@ func TestACME_IsChallengeEnabled(t *testing.T) {
{"ok dns-01 enabled", fields{[]ACMEChallenge{"http-01", "dns-01"}}, args{ctx, DNS_01}, true},
{"ok tls-alpn-01 enabled", fields{[]ACMEChallenge{"http-01", "dns-01", "tls-alpn-01"}}, args{ctx, TLS_ALPN_01}, true},
{"ok device-attest-01 enabled", fields{[]ACMEChallenge{"device-attest-01", "dns-01"}}, args{ctx, DEVICE_ATTEST_01}, true},
{"ok wire-oidc-01 enabled", fields{[]ACMEChallenge{"wire-oidc-01"}}, args{ctx, WIREOIDC_01}, true},
{"ok wire-dpop-01 enabled", fields{[]ACMEChallenge{"wire-dpop-01"}}, args{ctx, WIREDPOP_01}, true},
{"fail http-01", fields{[]ACMEChallenge{"dns-01"}}, args{ctx, "http-01"}, false},
{"fail dns-01", fields{[]ACMEChallenge{"http-01", "tls-alpn-01"}}, args{ctx, "dns-01"}, false},
{"fail tls-alpn-01", fields{[]ACMEChallenge{"http-01", "dns-01", "device-attest-01"}}, args{ctx, "tls-alpn-01"}, false},
{"fail device-attest-01", fields{[]ACMEChallenge{"http-01", "dns-01"}}, args{ctx, "device-attest-01"}, false},
{"fail wire-oidc-01", fields{[]ACMEChallenge{"http-01", "dns-01"}}, args{ctx, "wire-oidc-01"}, false},
{"fail wire-dpop-01", fields{[]ACMEChallenge{"http-01", "dns-01"}}, args{ctx, "wire-dpop-01"}, false},
{"fail unknown", fields{[]ACMEChallenge{"http-01", "dns-01", "tls-alpn-01", "device-attest-01"}}, args{ctx, "unknown"}, false},
}
for _, tt := range tests {
@ -405,8 +334,9 @@ func TestACME_IsChallengeEnabled(t *testing.T) {
p := &ACME{
Challenges: tt.fields.Challenges,
}
got := p.IsChallengeEnabled(tt.args.ctx, tt.args.challenge)
assert.Equal(t, tt.want, got)
if got := p.IsChallengeEnabled(tt.args.ctx, tt.args.challenge); got != tt.want {
t.Errorf("ACME.AuthorizeChallenge() = %v, want %v", got, tt.want)
}
})
}
}
@ -440,8 +370,9 @@ func TestACME_IsAttestationFormatEnabled(t *testing.T) {
p := &ACME{
AttestationFormats: tt.fields.AttestationFormats,
}
got := p.IsAttestationFormatEnabled(tt.args.ctx, tt.args.format)
assert.Equal(t, tt.want, got)
if got := p.IsAttestationFormatEnabled(tt.args.ctx, tt.args.format); got != tt.want {
t.Errorf("ACME.IsAttestationFormatEnabled() = %v, want %v", got, tt.want)
}
})
}
}

@ -1,25 +1,89 @@
# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/verify-signature.html
# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/regions-certs.html use RSA format
# default certificate for "other regions"
-----BEGIN CERTIFICATE-----
MIIDIjCCAougAwIBAgIJAKnL4UEDMN/FMA0GCSqGSIb3DQEBBQUAMGoxCzAJBgNV
BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdTZWF0dGxlMRgw
FgYDVQQKEw9BbWF6b24uY29tIEluYy4xGjAYBgNVBAMTEWVjMi5hbWF6b25hd3Mu
Y29tMB4XDTE0MDYwNTE0MjgwMloXDTI0MDYwNTE0MjgwMlowajELMAkGA1UEBhMC
VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1NlYXR0bGUxGDAWBgNV
BAoTD0FtYXpvbi5jb20gSW5jLjEaMBgGA1UEAxMRZWMyLmFtYXpvbmF3cy5jb20w
gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAIe9GN//SRK2knbjySG0ho3yqQM3
e2TDhWO8D2e8+XZqck754gFSo99AbT2RmXClambI7xsYHZFapbELC4H91ycihvrD
jbST1ZjkLQgga0NE1q43eS68ZeTDccScXQSNivSlzJZS8HJZjgqzBlXjZftjtdJL
XeE4hwvo0sD4f3j9AgMBAAGjgc8wgcwwHQYDVR0OBBYEFCXWzAgVyrbwnFncFFIs
77VBdlE4MIGcBgNVHSMEgZQwgZGAFCXWzAgVyrbwnFncFFIs77VBdlE4oW6kbDBq
MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHU2Vh
dHRsZTEYMBYGA1UEChMPQW1hem9uLmNvbSBJbmMuMRowGAYDVQQDExFlYzIuYW1h
em9uYXdzLmNvbYIJAKnL4UEDMN/FMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF
BQADgYEAFYcz1OgEhQBXIwIdsgCOS8vEtiJYF+j9uO6jz7VOmJqO+pRlAbRlvY8T
C1haGgSI/A1uZUKs/Zfnph0oEI0/hu1IIJ/SKBDtN5lvmZ/IzbOPIJWirlsllQIQ
7zvWbGd9c9+Rm3p04oTvhup99la7kZqevJK0QRdD/6NpCKsqP/0=
# certificate for us-east-2
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUVJTc+hOU+8Gk3JlqsX438Dk5c58wDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE3MTE0OVoXDTI5MDQyODE3MTE0OVowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUVJTc+hOU
+8Gk3JlqsX438Dk5c58wEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQAywJQaVNWJqW0R0T0xVOSoN1GLk9x9kKEuN67RN9CLin4dA97qa7Mr5W4P
FZ6vnh5CjOhQBRXV9xJUeYSdqVItNAUFK/fEzDdjf1nUfPlQ3OJ49u6CV01NoJ9m
usvY9kWcV46dqn2bk2MyfTTgvmeqP8fiMRPxxnVRkSzlldP5Fg==
-----END CERTIFICATE-----
# certificate for us-east-1
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUE1y2NIKCU+Rg4uu4u32koG9QEYIwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE3MzQwMVoXDTI5MDQyODE3MzQwMVowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUE1y2NIKC
U+Rg4uu4u32koG9QEYIwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQAlxSmwcWnhT4uAeSinJuz+1BTcKhVSWb5jT8pYjQb8ZoZkXXRGb09mvYeU
NeqOBr27rvRAnaQ/9LUQf72+SahDFuS4CMI8nwowytqbmwquqFr4dxA/SDADyRiF
ea1UoMuNHTY49J/1vPomqsVn7mugTp+TbjqCfOJTpu0temHcFA==
-----END CERTIFICATE-----
# certificate for us-west-1
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUK2zmY9PUSTR7rc1k2OwPYu4+g7wwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE3MDI0M1oXDTI5MDQyODE3MDI0M1owXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUK2zmY9PU
STR7rc1k2OwPYu4+g7wwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQA1Ng4QmN4n7iPh5CnadSOc0ZfM7by0dBePwZJyGvOHdaw6P6E/vEk76KsC
Q8p+akuzVzVPkU4kBK/TRqLp19wEWoVwhhTaxHjQ1tTRHqXIVlrkw4JrtFbeNM21
GlkSLonuzmNZdivn9WuQYeGe7nUD4w3q9GgiF3CPorJe+UxtbA==
-----END CERTIFICATE-----
# certificate for us-west-2
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUFx8PxCkbHwpD31bOyCtyz3GclbgwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE3MjM1OVoXDTI5MDQyODE3MjM1OVowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUFx8PxCkb
HwpD31bOyCtyz3GclbgwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQBzOl+9Xy1+UsbUBI95HO9mbbdnuX+aMJXgG9uFZNjgNEbMcvx+h8P9IMko
z7PzFdheQQ1NLjsHH9mSR1SyC4m9ja6BsejH5nLBWyCdjfdP3muZM4O5+r7vUa1O
dWU+hP/T7DUrPAIVMOE7mpYa+WPWJrN6BlRwQkKQ7twm9kDalA==
-----END CERTIFICATE-----
# certificate for eu-south-1
@ -93,7 +157,7 @@ NTpxxcXmUKquX+pHmIkK1LKDO8rNE84jqxrxRsfDi6by82fjVYf2pgjJW8R1FAw+
mL5WQRFexbfB5aXhcMo0AA==
-----END CERTIFICATE-----
# certificate for cn-north-1, cn-northwest-1
# certificate for cn-north-1
-----BEGIN CERTIFICATE-----
MIIDCzCCAnSgAwIBAgIJALSOMbOoU2svMA0GCSqGSIb3DQEBCwUAMFwxCzAJBgNV
BAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0
@ -114,6 +178,48 @@ oADS0ph+YUz5P/bUCm61wFjlxaTfwKcuTR3ytj7bFLoW5Bm7Sa+TCl3lOGb2taon
SUDlRyNy1jJFstEZjOhs
-----END CERTIFICATE-----
# certificate for cn-northwest-1
-----BEGIN CERTIFICATE-----
MIIDCzCCAnSgAwIBAgIJALSOMbOoU2svMA0GCSqGSIb3DQEBCwUAMFwxCzAJBgNV
BAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0
dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0yMzA3MDQw
ODM1MzlaFw0yODA3MDIwODM1MzlaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBX
YXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6
b24gV2ViIFNlcnZpY2VzIExMQzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
uhhUNlqAZdcWWB/OSDVDGk3OA99EFzOn/mJlmciQ/Xwu2dFJWmSCqEAE6gjufCjQ
q3voxAhC2CF+elKtJW/C0Sz/LYo60PUqd6iXF4h+upB9HkOOGuWHXsHBTsvgkgGA
1CGgel4U0Cdq+23eANr8N8m28UzljjSnTlrYCHtzN4sCAwEAAaOB1DCB0TALBgNV
HQ8EBAMCB4AwHQYDVR0OBBYEFBkZu3wT27NnYgrfH+xJz4HJaNJoMIGOBgNVHSME
gYYwgYOAFBkZu3wT27NnYgrfH+xJz4HJaNJooWCkXjBcMQswCQYDVQQGEwJVUzEZ
MBcGA1UECBMQV2FzaGluZ3RvbiBTdGF0ZTEQMA4GA1UEBxMHU2VhdHRsZTEgMB4G
A1UEChMXQW1hem9uIFdlYiBTZXJ2aWNlcyBMTEOCCQC0jjGzqFNrLzASBgNVHRMB
Af8ECDAGAQH/AgEAMA0GCSqGSIb3DQEBCwUAA4GBAECji43p+oPkYqmzll7e8Hgb
oADS0ph+YUz5P/bUCm61wFjlxaTfwKcuTR3ytj7bFLoW5Bm7Sa+TCl3lOGb2taon
2h+9NirRK6JYk87LMNvbS40HGPFumJL2NzEsGUeK+MRiWu+Oh5/lJGii3qw4YByx
SUDlRyNy1jJFstEZjOhs
-----END CERTIFICATE-----
# certificate for eu-central-1
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUFD5GsmkxRuecttwsCG763m3u63UwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE1NTUyOVoXDTI5MDQyODE1NTUyOVowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUFD5Gsmkx
RuecttwsCG763m3u63UwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQBBh0WaXlBsW56Hqk588MmJxsOrvcKfDjF57RgEDgnGnQaJcStCVWDO9UYO
JX2tdsPw+E7AjDqjsuxYaotLn3Mr3mK0sNOXq9BljBnWD4pARg89KZnZI8FN35HQ
O/LYOVHCknuPL123VmVRNs51qQA9hkPjvw21UzpDLxaUxt9Z/w==
-----END CERTIFICATE-----
# certificate for eu-central-2
-----BEGIN CERTIFICATE-----
MIICMzCCAZygAwIBAgIGAXjSGFGiMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNVBAYT
@ -130,6 +236,27 @@ NBElvPCDKFvTJl4QQhToy056llO5GvdS9RK+H8xrP2mrqngApoKTApv93vHBixgF
Sn5KrczRO0YSm3OjkqbydU7DFlmkXXR7GYE+5jbHvQHYiT1J5sMu
-----END CERTIFICATE-----
# certificate for ap-south-1
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUDLA+x6tTAP3LRTr0z6nOxfsozdMwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE0MTMwMVoXDTI5MDQyODE0MTMwMVowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUDLA+x6tT
AP3LRTr0z6nOxfsozdMwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQAZ7rYKoAwwiiH1M5GJbrT/BEk3OO2VrEPw8ZxgpqQ/EKlzMlOs/0Cyrmp7
UYyUgYFQe5nq37Z94rOUSeMgv/WRxaMwrLlLqD78cuF9DSkXaZIX/kECtVaUnjk8
BZx0QhoIHOpQocJUSlm/dLeMuE0+0A3HNR6JVktGsUdv9ulmKw==
-----END CERTIFICATE-----
# certificate for ap-south-2
-----BEGIN CERTIFICATE-----
MIICMzCCAZygAwIBAgIGAXjwLj9CMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNVBAYT
@ -146,6 +273,48 @@ ETwUZ9mTq2vxlV0KvuetCDNS5u4cJsxe/TGGbYP0yP2qfMl0cCImzRI5W0gn8gog
dervfeT7nH5ih0TWEy/QDWfkQ601L4erm4yh4YQq8vcqAPSkf04N
-----END CERTIFICATE-----
# certificate for ap-southeast-1
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUSqP6ih+++5KF07NXngrWf26mhSUwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE0MzAxNFoXDTI5MDQyODE0MzAxNFowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUSqP6ih++
+5KF07NXngrWf26mhSUwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQAw13BxW11U/JL58j//Fmk7qqtrZTqXmaz1qm2WlIpJpW750MOcP4ux1uPy
eM0RdVZ4jHSMv5gtLAv/PjExBfw9n6vNCk+5GZG4Xec5DoapBZHXmfMo93sjxBFP
4x9rWn0GuwAVO9ukjYPevq2Rerilrq5VvppHtbATVNY2qecXDA==
-----END CERTIFICATE-----
# certificate for ap-southeast-2
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUFxWyAdk4oiXIOC9PxcgjYYh71mwwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE1MjE0M1oXDTI5MDQyODE1MjE0M1owXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUFxWyAdk4
oiXIOC9PxcgjYYh71mwwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQByjeQe6lr7fiIhoGdjBXYzDfkX0lGGvMIhRh57G1bbceQfaYdZd7Ptc0jl
bpycKGaTvhUdkpMOiV2Hi9dOOYawkdhyJDstmDNKu6P9+b6Kak8He5z3NU1tUR2Y
uTwcz7Ye8Nldx//ws3raErfTI7D6s9m63OX8cAJ/f8bNgikwpw==
-----END CERTIFICATE-----
# certificate for ap-southeast-3
-----BEGIN CERTIFICATE-----
MIICMzCCAZygAwIBAgIGAXbVDG2yMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNVBAYT
@ -226,25 +395,46 @@ WX00FTEj4hRVjameE1nENoO8Z7fUVloAFDlDo69fhkJeSvn51D1WRrPnoWGgEfr1
+OfK1bAcKTtfkkkP9r4RdwSjKzO5Zu/B+Wqm3kVEz/QNcz6npmA6
-----END CERTIFICATE-----
# certificate for us-gov-east-1 and us-gov-west-1
# certificate for us-gov-east-1
-----BEGIN CERTIFICATE-----
MIIDCzCCAnSgAwIBAgIJAIe9Hnq82O7UMA0GCSqGSIb3DQEBCwUAMFwxCzAJBgNV
BAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0
dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0yMTA3MTQx
NDI3NTdaFw0yNDA3MTMxNDI3NTdaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBX
YXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6
b24gV2ViIFNlcnZpY2VzIExMQzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
qaIcGFFTx/SO1W5G91jHvyQdGP25n1Y91aXCuOOWAUTvSvNGpXrI4AXNrQF+CmIO
C4beBASnHCx082jYudWBBl9Wiza0psYc9flrczSzVLMmN8w/c78F/95NfiQdnUQP
pvgqcMeJo82cgHkLR7XoFWgMrZJqrcUK0gnsQcb6kakCAwEAAaOB1DCB0TALBgNV
HQ8EBAMCB4AwHQYDVR0OBBYEFNWV53gWJz72F5B1ZVY4O/dfFYBPMIGOBgNVHSME
gYYwgYOAFNWV53gWJz72F5B1ZVY4O/dfFYBPoWCkXjBcMQswCQYDVQQGEwJVUzEZ
MBcGA1UECBMQV2FzaGluZ3RvbiBTdGF0ZTEQMA4GA1UEBxMHU2VhdHRsZTEgMB4G
A1UEChMXQW1hem9uIFdlYiBTZXJ2aWNlcyBMTEOCCQCHvR56vNju1DASBgNVHRMB
Af8ECDAGAQH/AgEAMA0GCSqGSIb3DQEBCwUAA4GBACrKjWj460GUPZCGm3/z0dIz
M2BPuH769wcOsqfFZcMKEysSFK91tVtUb1soFwH4/Lb/T0PqNrvtEwD1Nva5k0h2
xZhNNRmDuhOhW1K9wCcnHGRBwY5t4lYL6hNV6hcrqYwGMjTjcAjBG2yMgznSNFle
Rwi/S3BFXISixNx9cILu
MIIDITCCAoqgAwIBAgIULVyrqjjwZ461qelPCiShB1KCCj4wDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDUwNzE1MjIzNloXDTI5MDUwNjE1MjIzNlowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCpohwYUVPH9I7Vbkb3WMe/JB0Y/bmfVj3VpcK445YBRO9K80al
esjgBc2tAX4KYg4Lht4EBKccLHTzaNi51YEGX1aLNrSmxhz1+WtzNLNUsyY3zD9z
vwX/3k1+JB2dRA+m+Cpwx4mjzZyAeQtHtegVaAytkmqtxQrSCexBxvqRqQIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQU1ZXneBYnPvYXkHVlVjg7918V
gE8wgZkGA1UdIwSBkTCBjoAU1ZXneBYnPvYXkHVlVjg7918VgE+hYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IULVyrqjjw
Z461qelPCiShB1KCCj4wEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQBfAL/YZv0y3zmVbXjyxQCsDloeDCJjFKIu3ameEckeIWJbST9LMto0zViZ
puIAf05x6GQiEqfBMk+YMxJfcTmJB4Ebaj4egFlslJPSHyC2xuydHlr3B04INOH5
Z2oCM68u6GGbj0jZjg7GJonkReG9N72kDva/ukwZKgq8zErQVQ==
-----END CERTIFICATE-----
# certificate for us-gov-west-1
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUe5wGF3jfb7lUHzvDxmM/ktGCLwwwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDUwNzE3MzAzMloXDTI5MDUwNjE3MzAzMlowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCpohwYUVPH9I7Vbkb3WMe/JB0Y/bmfVj3VpcK445YBRO9K80al
esjgBc2tAX4KYg4Lht4EBKccLHTzaNi51YEGX1aLNrSmxhz1+WtzNLNUsyY3zD9z
vwX/3k1+JB2dRA+m+Cpwx4mjzZyAeQtHtegVaAytkmqtxQrSCexBxvqRqQIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQU1ZXneBYnPvYXkHVlVjg7918V
gE8wgZkGA1UdIwSBkTCBjoAU1ZXneBYnPvYXkHVlVjg7918VgE+hYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUe5wGF3jf
b7lUHzvDxmM/ktGCLwwwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQCbTdpx1Iob9SwUReY4exMnlwQlmkTLyA8tYGWzchCJOJJEPfsW0ryy1A0H
YIuvyUty3rJdp9ib8h3GZR71BkZnNddHhy06kPs4p8ewF8+d8OWtOJQcI+ZnFfG4
KyM4rUsBrljpG2aOCm12iACEyrvgJJrS8VZwUDZS6mZEnn/lhA==
-----END CERTIFICATE-----
# certificate for ca-west-1
@ -261,4 +451,188 @@ RZWaBDBJy9x8C2hW+w9lMQjFHkJ7Jy/PHCJ69EzebQIDAQABMA0GCSqGSIb3DQEB
BQUAA4GBAGe9Snkz1A6rHBH6/5kDtYvtPYwhx2sXNxztbhkXErFk40Nw5l459NZx
EeudxJBLoCkkSgYjhRcOZ/gvDVtWG7qyb6fAqgoisyAbk8K9LzxSim2S1nmT9vD8
4B/t/VvwQBylc+ej8kRxMH7fquZLp7IXfmtBzyUqu6Dpbne+chG2
-----END CERTIFICATE-----
-----END CERTIFICATE-----
# certificate for ap-northeast-1
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIULgwDh7TiDrPPBJwscqDwiBHkEFQwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTEyMjMxMFoXDTI5MDQyODEyMjMxMFowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IULgwDh7Ti
DrPPBJwscqDwiBHkEFQwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQBtjAglBde1t4F9EHCZOj4qnY6Gigy07Ou54i+lR77MhbpzE8V28Li9l+YT
QMIn6SzJqU3/fIycIro1OVY1lHmaKYgPGSEZxBenSBHfzwDLRmC9oRp4QMe0BjOC
gepj1lUoiN7OA6PtA+ycNlsP0oJvdBjhvayLiuM3tUfLTrgHbw==
-----END CERTIFICATE-----
# certificate for ap-northeast-2
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUbBSn2UIO6vYk4iNWV0RPxJJtHlgwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTEzMzg0NloXDTI5MDQyODEzMzg0NlowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUbBSn2UIO
6vYk4iNWV0RPxJJtHlgwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQAmjTjalG8MGLqWTC2uYqEM8nzI3px1eo0ArvFRsyqQ3fgmWcQpxExqUqRy
l3+2134Kv8dFab04Gut5wlfRtc2OwPKKicmv/IXGN+9bKFnQFjTqif08NIzrDZch
aFT/uvxrIiM+oN2YsHq66GUhO2+xVRXDXVxM/VObFgPERbJpyA==
-----END CERTIFICATE-----
# certificate for ap-northeast-3
-----BEGIN CERTIFICATE-----
MIICMzCCAZygAwIBAgIGAYPou9weMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNVBAYT
AlVTMRkwFwYDVQQIDBBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHDAdTZWF0dGxl
MSAwHgYDVQQKDBdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAgFw0yMjEwMTgwMTM2
MDlaGA8yMjAxMTAxODAxMzYwOVowXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgMEFdh
c2hpbmd0b24gU3RhdGUxEDAOBgNVBAcMB1NlYXR0bGUxIDAeBgNVBAoMF0FtYXpv
biBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDK
1kIcG5Q6adBXQM75GldfTSiXl7tn54p10TnspI0ErDdb2B6q2Ji/v4XBVH13ZCMg
qlRHMqV8AWI5iO6gFn2A9sN3AZXTMqwtZeiDdebq3k6Wt7ieYvpXTg0qvgsjQIov
RZWaBDBJy9x8C2hW+w9lMQjFHkJ7Jy/PHCJ69EzebQIDAQABMA0GCSqGSIb3DQEB
BQUAA4GBAGe9Snkz1A6rHBH6/5kDtYvtPYwhx2sXNxztbhkXErFk40Nw5l459NZx
EeudxJBLoCkkSgYjhRcOZ/gvDVtWG7qyb6fAqgoisyAbk8K9LzxSim2S1nmT9vD8
4B/t/VvwQBylc+ej8kRxMH7fquZLp7IXfmtBzyUqu6Dpbne+chG2
-----END CERTIFICATE-----
# certificate for ca-central-1
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUIrLgixJJB5C4G8z6pZ5rB0JU2aQwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE1MzU0M1oXDTI5MDQyODE1MzU0M1owXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUIrLgixJJ
B5C4G8z6pZ5rB0JU2aQwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQBHiQJmzyFAaSYs8SpiRijIDZW2RIo7qBKb/pI3rqK6yOWDlPuMr6yNI81D
IrKGGftg4Z+2KETYU4x76HSf0s//vfH3QA57qFaAwddhKYy4BhteFQl/Wex3xTlX
LiwI07kwJvJy3mS6UfQ4HcvZy219tY+0iyOWrz/jVxwq7TOkCw==
-----END CERTIFICATE-----
# certificate for eu-west-1
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUakDaQ1Zqy87Hy9ESXA1pFC116HkwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE2MTgxMFoXDTI5MDQyODE2MTgxMFowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUakDaQ1Zq
y87Hy9ESXA1pFC116HkwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQADIkn/MqaLGPuK5+prZZ5Ox4bBZLPtreO2C7r0pqU2kPM2lVPyYYydkvP0
lgSmmsErGu/oL9JNztDe2oCA+kNy17ehcsf8cw0uP861czNFKCeU8b7FgBbL+sIm
qi33rAq6owWGi/5uEcfCR+JP7W+oSYVir5r/yDmWzx+BVH5S/g==
-----END CERTIFICATE-----
# certificate for eu-west-2
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUCgCV/DPxYNND/swDgEKGiC5I+EwwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE2MjkxNFoXDTI5MDQyODE2MjkxNFowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUCgCV/DPx
YNND/swDgEKGiC5I+EwwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQATPu/sOE2esNa4+XPEGKlEJSgqzyBSQLQc+VWo6FAJhGG9fp7D97jhHeLC
5vwfmtTAfnGBxadfAOT3ASkxnOZhXtnRna460LtnNHm7ArCVgXKJo7uBn6ViXtFh
uEEw4y6p9YaLQna+VC8Xtgw6WKq2JXuKzuhuNKSFaGGw9vRcHg==
-----END CERTIFICATE-----
# certificate for eu-west-3
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUaC9fX57UDr6u1vBvsCsECKBZQyIwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE2MzczOFoXDTI5MDQyODE2MzczOFowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUaC9fX57U
Dr6u1vBvsCsECKBZQyIwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQCARv1bQEDaMEzYI0nPlu8GHcMXgmgA94HyrXhMMcaIlQwocGBs6VILGVhM
TXP2r3JFaPEpmXSQNQHvGA13clKwAZbni8wtzv6qXb4L4muF34iQRHF0nYrEDoK7
mMPR8+oXKKuPO/mv/XKo6XAV5DDERdSYHX5kkA2R9wtvyZjPnQ==
-----END CERTIFICATE-----
# certificate for eu-north-1
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUN1c9U6U/xiVDFgJcYKZB4NkH1QEwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE2MDYwM1oXDTI5MDQyODE2MDYwM1owXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUN1c9U6U/
xiVDFgJcYKZB4NkH1QEwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQBTIQdoFSDRHkpqNPUbZ9WXR2O5v/9bpmHojMYZb3Hw46wsaRso7STiGGX/
tRqjIkPUIXsdhZ3+7S/RmhFznmZc8e0bjU4n5vi9CJtQSt+1u4E17+V2bF+D3h/7
wcfE0l3414Q8JaTDtfEf/aF3F0uyBvr4MDMd7mFvAMmDmBPSlA==
-----END CERTIFICATE-----
# certificate for sa-east-1
-----BEGIN CERTIFICATE-----
MIIDITCCAoqgAwIBAgIUX4Bh4MQ86Roh37VDRRX1MNOB3TcwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO
BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD
MB4XDTI0MDQyOTE2NDYwOVoXDTI5MDQyODE2NDYwOVowXDELMAkGA1UEBhMCVVMx
GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe
BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB
UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku
vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB
o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2
UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ
BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT
ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUX4Bh4MQ8
6Roh37VDRRX1MNOB3TcwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF
AAOBgQBnhocfH6ZIX6F5K9+Y9V4HFk8vSaaKL5ytw/P5td1h9ej94KF3xkZ5fyjN
URvGQv3kNmNJBoNarcP9I7JIMjsNPmVzqWawyCEGCZImoARxSS3Fc5EAs2PyBfcD
9nCtzMTaKO09Xyq0wqXVYn1xJsE5d5yBDsGrzaTHKjxo61+ezQ==
-----END CERTIFICATE-----

@ -896,5 +896,5 @@ func TestAWS_HardcodedCertificates(t *testing.T) {
assert.True(t, cert.NotAfter.After(time.Now()))
certs = append(certs, cert)
}
assert.Len(t, 15, certs, "expected 15 certificates in aws_certificates.pem")
assert.Len(t, 33, certs, "expected 33 certificates in aws_certificates.pem, but got %d", len(certs))
}

@ -234,24 +234,24 @@ func (c *Claimer) IsSSHCAEnabled() bool {
// Validate validates and modifies the Claims with default values.
func (c *Claimer) Validate() error {
var (
min = c.MinTLSCertDuration()
max = c.MaxTLSCertDuration()
def = c.DefaultTLSCertDuration()
minDur = c.MinTLSCertDuration()
maxDur = c.MaxTLSCertDuration()
defDur = c.DefaultTLSCertDuration()
)
switch {
case min <= 0:
case minDur <= 0:
return errors.Errorf("claims: MinTLSCertDuration must be greater than 0")
case max <= 0:
case maxDur <= 0:
return errors.Errorf("claims: MaxTLSCertDuration must be greater than 0")
case def <= 0:
case defDur <= 0:
return errors.Errorf("claims: DefaultTLSCertDuration must be greater than 0")
case max < min:
case maxDur < minDur:
return errors.Errorf("claims: MaxCertDuration cannot be less "+
"than MinCertDuration: MaxCertDuration - %v, MinCertDuration - %v", max, min)
case def < min:
return errors.Errorf("claims: DefaultCertDuration cannot be less than MinCertDuration: DefaultCertDuration - %v, MinCertDuration - %v", def, min)
case max < def:
return errors.Errorf("claims: MaxCertDuration cannot be less than DefaultCertDuration: MaxCertDuration - %v, DefaultCertDuration - %v", max, def)
"than MinCertDuration: MaxCertDuration - %v, MinCertDuration - %v", maxDur, minDur)
case defDur < minDur:
return errors.Errorf("claims: DefaultCertDuration cannot be less than MinCertDuration: DefaultCertDuration - %v, MinCertDuration - %v", defDur, minDur)
case maxDur < defDur:
return errors.Errorf("claims: MaxCertDuration cannot be less than DefaultCertDuration: MaxCertDuration - %v, DefaultCertDuration - %v", maxDur, defDur)
default:
return nil
}

@ -125,7 +125,7 @@ func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims)
}
// Try with azp (OIDC)
if len(payload.AuthorizedParty) > 0 {
if payload.AuthorizedParty != "" {
if p, ok := c.LoadByTokenID(payload.AuthorizedParty); ok {
return p, ok
}

@ -87,7 +87,7 @@ func (p *JWK) GetType() Type {
// GetEncryptedKey returns the base provisioner encrypted key if it's defined.
func (p *JWK) GetEncryptedKey() (string, string, bool) {
return p.Key.KeyID, p.EncryptedKey, len(p.EncryptedKey) > 0
return p.Key.KeyID, p.EncryptedKey, p.EncryptedKey != ""
}
// Init initializes and validates the fields of a JWK type.

@ -105,7 +105,7 @@ func getKeysFromJWKsURI(uri string) (jose.JSONWebKeySet, time.Duration, error) {
func getCacheAge(cacheControl string) time.Duration {
age := defaultCacheAge
if len(cacheControl) > 0 {
if cacheControl != "" {
match := maxAgeRegex.FindAllStringSubmatch(cacheControl, -1)
if len(match) > 0 {
if len(match[0]) == 2 {

@ -93,6 +93,8 @@ type OIDC struct {
ListenAddress string `json:"listenAddress,omitempty"`
Claims *Claims `json:"claims,omitempty"`
Options *Options `json:"options,omitempty"`
Scopes []string `json:"scopes,omitempty"`
AuthParams []string `json:"authParams,omitempty"`
configuration openIDConfiguration
keyStore *keyStore
ctl *Controller

@ -11,7 +11,6 @@ import (
"go.step.sm/crypto/x509util"
"github.com/smallstep/certificates/authority/policy"
"github.com/smallstep/certificates/authority/provisioner/wire"
)
// CertificateOptions is an interface that returns a list of options passed when
@ -31,10 +30,9 @@ func (fn certificateOptionsFunc) Options(so SignOptions) []x509util.Option {
type Options struct {
X509 *X509Options `json:"x509,omitempty"`
SSH *SSHOptions `json:"ssh,omitempty"`
// Webhooks is a list of webhooks that can augment template data
Webhooks []*Webhook `json:"webhooks,omitempty"`
// Wire holds the options used for the ACME Wire integration
Wire *wire.Options `json:"wire,omitempty"`
}
// GetX509Options returns the X.509 options.
@ -53,18 +51,6 @@ func (o *Options) GetSSHOptions() *SSHOptions {
return o.SSH
}
// GetWireOptions returns the Wire options if available. It
// returns an error if they're not available.
func (o *Options) GetWireOptions() (*wire.Options, error) {
if o == nil {
return nil, errors.New("no options available")
}
if o.Wire == nil {
return nil, errors.New("no Wire options available")
}
return o.Wire, nil
}
// GetWebhooks returns the webhooks options.
func (o *Options) GetWebhooks() []*Webhook {
if o == nil {

@ -10,6 +10,7 @@ import (
"strings"
"github.com/pkg/errors"
kmsapi "go.step.sm/crypto/kms/apiv1"
"golang.org/x/crypto/ssh"
"github.com/smallstep/certificates/errs"
@ -206,6 +207,13 @@ type SSHKeys struct {
HostKeys []ssh.PublicKey
}
// SCEPKeyManager is a KMS interface that combines a KeyManager with a
// Decrypter.
type SCEPKeyManager interface {
kmsapi.KeyManager
kmsapi.Decrypter
}
// Config defines the default parameters used in the initialization of
// provisioners.
type Config struct {
@ -226,6 +234,8 @@ type Config struct {
AuthorizeSSHRenewFunc AuthorizeSSHRenewFunc
// WebhookClient is an http client to use in webhook request
WebhookClient *http.Client
// SCEPKeyManager, if defined, is the interface used by SCEP provisioners.
SCEPKeyManager SCEPKeyManager
}
type provisioner struct {
@ -320,7 +330,7 @@ func (b *base) AuthorizeSSHSign(context.Context, string) ([]SignOption, error) {
return nil, errs.Unauthorized("provisioner.AuthorizeSSHSign not implemented")
}
// AuthorizeRevoke returns an unimplemented error. Provisioners should overwrite
// AuthorizeSSHRevoke returns an unimplemented error. Provisioners should overwrite
// this method if they will support authorizing tokens for revoking SSH Certificates.
func (b *base) AuthorizeSSHRevoke(context.Context, string) error {
return errs.Unauthorized("provisioner.AuthorizeSSHRevoke not implemented")

@ -15,7 +15,6 @@ import (
"go.step.sm/crypto/kms"
kmsapi "go.step.sm/crypto/kms/apiv1"
"go.step.sm/crypto/kms/uri"
"go.step.sm/linkedca"
"github.com/smallstep/certificates/webhook"
@ -59,7 +58,7 @@ type SCEP struct {
encryptionAlgorithm int
challengeValidationController *challengeValidationController
notificationController *notificationController
keyManager kmsapi.KeyManager
keyManager SCEPKeyManager
decrypter crypto.Decrypter
decrypterCertificate *x509.Certificate
signer crypto.Signer
@ -269,34 +268,38 @@ func (s *SCEP) Init(config Config) (err error) {
)
// parse the decrypter key PEM contents if available
if decryptionKeyPEM := s.DecrypterKeyPEM; len(decryptionKeyPEM) > 0 {
if len(s.DecrypterKeyPEM) > 0 {
// try reading the PEM for validation
block, rest := pem.Decode(decryptionKeyPEM)
block, rest := pem.Decode(s.DecrypterKeyPEM)
if len(rest) > 0 {
return errors.New("failed parsing decrypter key: trailing data")
}
if block == nil {
return errors.New("failed parsing decrypter key: no PEM block found")
}
opts := kms.Options{
Type: kmsapi.SoftKMS,
}
if s.keyManager, err = kms.New(context.Background(), opts); err != nil {
km, err := kms.New(context.Background(), opts)
if err != nil {
return fmt.Errorf("failed initializing kms: %w", err)
}
kmsDecrypter, ok := s.keyManager.(kmsapi.Decrypter)
scepKeyManager, ok := km.(SCEPKeyManager)
if !ok {
return fmt.Errorf("%q is not a kmsapi.Decrypter", opts.Type)
}
if s.decrypter, err = kmsDecrypter.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKeyPEM: decryptionKeyPEM,
s.keyManager = scepKeyManager
if s.decrypter, err = s.keyManager.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKeyPEM: s.DecrypterKeyPEM,
Password: []byte(s.DecrypterKeyPassword),
PasswordPrompter: kmsapi.NonInteractivePasswordPrompter,
}); err != nil {
return fmt.Errorf("failed creating decrypter: %w", err)
}
if s.signer, err = s.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKeyPEM: decryptionKeyPEM, // TODO(hs): support distinct signer key in the future?
SigningKeyPEM: s.DecrypterKeyPEM, // TODO(hs): support distinct signer key in the future?
Password: []byte(s.DecrypterKeyPassword),
PasswordPrompter: kmsapi.NonInteractivePasswordPrompter,
}); err != nil {
@ -304,41 +307,44 @@ func (s *SCEP) Init(config Config) (err error) {
}
}
if decryptionKeyURI := s.DecrypterKeyURI; len(decryptionKeyURI) > 0 {
u, err := uri.Parse(s.DecrypterKeyURI)
if s.DecrypterKeyURI != "" {
kmsType, err := kmsapi.TypeOf(s.DecrypterKeyURI)
if err != nil {
return fmt.Errorf("failed parsing decrypter key: %w", err)
}
var kmsType kmsapi.Type
switch {
case u.Scheme != "":
kmsType = kms.Type(u.Scheme)
default:
kmsType = kmsapi.SoftKMS
}
opts := kms.Options{
Type: kmsType,
URI: s.DecrypterKeyURI,
}
if s.keyManager, err = kms.New(context.Background(), opts); err != nil {
return fmt.Errorf("failed initializing kms: %w", err)
}
kmsDecrypter, ok := s.keyManager.(kmsapi.Decrypter)
if !ok {
return fmt.Errorf("%q is not a kmsapi.Decrypter", opts.Type)
}
if kmsType != "softkms" { // TODO(hs): this should likely become more transparent?
decryptionKeyURI = u.Opaque
}
if s.decrypter, err = kmsDecrypter.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKey: decryptionKeyURI,
if config.SCEPKeyManager != nil {
s.keyManager = config.SCEPKeyManager
} else {
if kmsType == kmsapi.DefaultKMS {
kmsType = kmsapi.SoftKMS
}
opts := kms.Options{
Type: kmsType,
URI: s.DecrypterKeyURI,
}
km, err := kms.New(context.Background(), opts)
if err != nil {
return fmt.Errorf("failed initializing kms: %w", err)
}
scepKeyManager, ok := km.(SCEPKeyManager)
if !ok {
return fmt.Errorf("%q is not a kmsapi.Decrypter", opts.Type)
}
s.keyManager = scepKeyManager
}
// Create decrypter and signer with the same key:
// TODO(hs): support distinct signer key in the future?
if s.decrypter, err = s.keyManager.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKey: s.DecrypterKeyURI,
Password: []byte(s.DecrypterKeyPassword),
PasswordPrompter: kmsapi.NonInteractivePasswordPrompter,
}); err != nil {
return fmt.Errorf("failed creating decrypter: %w", err)
}
if s.signer, err = s.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: decryptionKeyURI, // TODO(hs): support distinct signer key in the future?
SigningKey: s.DecrypterKeyURI,
Password: []byte(s.DecrypterKeyPassword),
PasswordPrompter: kmsapi.NonInteractivePasswordPrompter,
}); err != nil {

@ -2,19 +2,27 @@ package provisioner
import (
"context"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"encoding/pem"
"errors"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"github.com/smallstep/certificates/webhook"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.step.sm/crypto/kms/softkms"
"go.step.sm/crypto/minica"
"go.step.sm/crypto/pemutil"
"go.step.sm/linkedca"
"github.com/smallstep/certificates/webhook"
)
func Test_challengeValidationController_Validate(t *testing.T) {
@ -366,3 +374,270 @@ func TestSCEP_ValidateChallenge(t *testing.T) {
})
}
}
func TestSCEP_Init(t *testing.T) {
serialize := func(key crypto.PrivateKey, password string) []byte {
var opts []pemutil.Options
if password == "" {
opts = append(opts, pemutil.WithPasswordPrompt("no password", func(s string) ([]byte, error) {
return nil, nil
}))
} else {
opts = append(opts, pemutil.WithPassword([]byte("password")))
}
block, err := pemutil.Serialize(key, opts...)
require.NoError(t, err)
return pem.EncodeToMemory(block)
}
ca, err := minica.New()
require.NoError(t, err)
key, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
badKey, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
cert, err := ca.Sign(&x509.Certificate{
Subject: pkix.Name{CommonName: "SCEP decryptor"},
PublicKey: key.Public(),
})
require.NoError(t, err)
certPEM := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE", Bytes: cert.Raw,
})
certPEMWithIntermediate := append(pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE", Bytes: cert.Raw,
}), pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE", Bytes: ca.Intermediate.Raw,
})...)
keyPEM := serialize(key, "password")
keyPEMNoPassword := serialize(key, "")
badKeyPEM := serialize(badKey, "password")
tmp := t.TempDir()
path := filepath.Join(tmp, "rsa.priv")
pathNoPassword := filepath.Join(tmp, "rsa.key")
require.NoError(t, os.WriteFile(path, keyPEM, 0600))
require.NoError(t, os.WriteFile(pathNoPassword, keyPEMNoPassword, 0600))
type args struct {
config Config
}
tests := []struct {
name string
s *SCEP
args args
wantErr bool
}{
{"ok", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, false},
{"ok no password", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEMNoPassword,
DecrypterKeyPassword: "",
EncryptionAlgorithmIdentifier: 1,
}, args{Config{Claims: globalProvisionerClaims}}, false},
{"ok with uri", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 1024,
DecrypterCertificate: certPEM,
DecrypterKeyURI: "softkms:path=" + path,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 2,
}, args{Config{Claims: globalProvisionerClaims}}, false},
{"ok with uri no password", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 2048,
DecrypterCertificate: certPEM,
DecrypterKeyURI: "softkms:path=" + pathNoPassword,
DecrypterKeyPassword: "",
EncryptionAlgorithmIdentifier: 3,
}, args{Config{Claims: globalProvisionerClaims}}, false},
{"ok with SCEPKeyManager", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 2048,
DecrypterCertificate: certPEM,
DecrypterKeyURI: "softkms:path=" + pathNoPassword,
DecrypterKeyPassword: "",
EncryptionAlgorithmIdentifier: 4,
}, args{Config{Claims: globalProvisionerClaims, SCEPKeyManager: &softkms.SoftKMS{}}}, false},
{"ok intermediate", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: nil,
DecrypterKeyPEM: nil,
DecrypterKeyPassword: "",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, false},
{"fail type", &SCEP{
Type: "",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail name", &SCEP{
Type: "SCEP",
Name: "",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail minimumPublicKeyLength", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 2001,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail encryptionAlgorithmIdentifier", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 5,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail negative encryptionAlgorithmIdentifier", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: -1,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail key decode", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: []byte("not a pem"),
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail certificate decode", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: []byte("not a pem"),
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail certificate with intermediate", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEMWithIntermediate,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail decrypter password", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "badpassword",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail uri", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyURI: "softkms:path=missing.key",
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail uri password", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyURI: "softkms:path=" + path,
DecrypterKeyPassword: "badpassword",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail uri type", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyURI: "foo:path=" + path,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail missing certificate", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: nil,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail key match", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: badKeyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.s.Init(tt.args.config); (err != nil) != tt.wantErr {
t.Errorf("SCEP.Init() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

@ -369,8 +369,8 @@ type validityValidator struct {
}
// newValidityValidator return a new validity validator.
func newValidityValidator(min, max time.Duration) *validityValidator {
return &validityValidator{min: min, max: max}
func newValidityValidator(minDur, maxDur time.Duration) *validityValidator {
return &validityValidator{min: minDur, max: maxDur}
}
// Valid validates the certificate validity settings (notBefore/notAfter) and

@ -276,14 +276,14 @@ func (v *sshCertValidityValidator) Valid(cert *ssh.Certificate, opts SignSSHOpti
return errs.BadRequest("ssh certificate validBefore cannot be before validAfter")
}
var min, max time.Duration
var minDur, maxDur time.Duration
switch cert.CertType {
case ssh.UserCert:
min = v.MinUserSSHCertDuration()
max = v.MaxUserSSHCertDuration()
minDur = v.MinUserSSHCertDuration()
maxDur = v.MaxUserSSHCertDuration()
case ssh.HostCert:
min = v.MinHostSSHCertDuration()
max = v.MaxHostSSHCertDuration()
minDur = v.MinHostSSHCertDuration()
maxDur = v.MaxHostSSHCertDuration()
case 0:
return errs.BadRequest("ssh certificate type has not been set")
default:
@ -295,10 +295,10 @@ func (v *sshCertValidityValidator) Valid(cert *ssh.Certificate, opts SignSSHOpti
dur := time.Duration(cert.ValidBefore-cert.ValidAfter) * time.Second
switch {
case dur < min:
return errs.Forbidden("requested duration of %s is less than minimum accepted duration for selected provisioner of %s", dur, min)
case dur > max+opts.Backdate:
return errs.Forbidden("requested duration of %s is greater than maximum accepted duration for selected provisioner of %s", dur, max+opts.Backdate)
case dur < minDur:
return errs.Forbidden("requested duration of %s is less than minimum accepted duration for selected provisioner of %s", dur, minDur)
case dur > maxDur+opts.Backdate:
return errs.Forbidden("requested duration of %s is greater than maximum accepted duration for selected provisioner of %s", dur, maxDur+opts.Backdate)
default:
return nil
}

@ -15,7 +15,7 @@ import (
"time"
"github.com/pkg/errors"
"github.com/smallstep/certificates/internal/requestid"
"github.com/smallstep/certificates/middleware/requestid"
"github.com/smallstep/certificates/templates"
"github.com/smallstep/certificates/webhook"
"go.step.sm/linkedca"

@ -24,7 +24,7 @@ import (
"go.step.sm/crypto/x509util"
"go.step.sm/linkedca"
"github.com/smallstep/certificates/internal/requestid"
"github.com/smallstep/certificates/middleware/requestid"
"github.com/smallstep/certificates/webhook"
)

@ -1,49 +0,0 @@
package wire
import (
"bytes"
"crypto"
"errors"
"fmt"
"text/template"
"go.step.sm/crypto/pemutil"
)
type DPOPOptions struct {
// Public part of the signing key for DPoP access token in PEM format
SigningKey []byte `json:"key"`
// URI template for the URI the ACME client must call to fetch the DPoP challenge proof (an access token from wire-server)
Target string `json:"target"`
signingKey crypto.PublicKey
target *template.Template
}
func (o *DPOPOptions) GetSigningKey() crypto.PublicKey {
return o.signingKey
}
func (o *DPOPOptions) EvaluateTarget(deviceID string) (string, error) {
if deviceID == "" {
return "", errors.New("deviceID must not be empty")
}
buf := new(bytes.Buffer)
if err := o.target.Execute(buf, struct{ DeviceID string }{DeviceID: deviceID}); err != nil {
return "", fmt.Errorf("failed executing DPoP template: %w", err)
}
return buf.String(), nil
}
func (o *DPOPOptions) validateAndInitialize() (err error) {
o.signingKey, err = pemutil.Parse(o.SigningKey)
if err != nil {
return fmt.Errorf("failed parsing key: %w", err)
}
o.target, err = template.New("DeviceID").Parse(o.Target)
if err != nil {
return fmt.Errorf("failed parsing DPoP template: %w", err)
}
return nil
}

@ -1,58 +0,0 @@
package wire
import (
"errors"
"testing"
"text/template"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDPOPOptions_EvaluateTarget(t *testing.T) {
tu := "http://wire.com:15958/clients/{{.DeviceID}}/access-token"
target, err := template.New("DeviceID").Parse(tu)
require.NoError(t, err)
fail := "https:/wire.com:15958/clients/{{.DeviceId}}/access-token"
failTarget, err := template.New("DeviceID").Parse(fail)
require.NoError(t, err)
type fields struct {
target *template.Template
}
type args struct {
deviceID string
}
tests := []struct {
name string
fields fields
args args
want string
expectedErr error
}{
{
name: "ok", fields: fields{target: target}, args: args{deviceID: "deviceID"}, want: "http://wire.com:15958/clients/deviceID/access-token",
},
{
name: "fail/empty", fields: fields{target: target}, args: args{deviceID: ""}, expectedErr: errors.New("deviceID must not be empty"),
},
{
name: "fail/template", fields: fields{target: failTarget}, args: args{deviceID: "bla"}, expectedErr: errors.New(`failed executing DPoP template: template: DeviceID:1:32: executing "DeviceID" at <.DeviceId>: can't evaluate field DeviceId in type struct { DeviceID string }`),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := &DPOPOptions{
target: tt.fields.target,
}
got, err := o.EvaluateTarget(tt.args.deviceID)
if tt.expectedErr != nil {
assert.EqualError(t, err, tt.expectedErr.Error())
assert.Empty(t, got)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}

@ -1,179 +0,0 @@
package wire
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
"text/template"
"time"
"github.com/coreos/go-oidc/v3/oidc"
"go.step.sm/crypto/x509util"
)
type Provider struct {
DiscoveryBaseURL string `json:"discoveryBaseUrl,omitempty"`
IssuerURL string `json:"issuerUrl,omitempty"`
AuthURL string `json:"authorizationUrl,omitempty"`
TokenURL string `json:"tokenUrl,omitempty"`
JWKSURL string `json:"jwksUrl,omitempty"`
UserInfoURL string `json:"userInfoUrl,omitempty"`
Algorithms []string `json:"signatureAlgorithms,omitempty"`
}
type Config struct {
ClientID string `json:"clientId,omitempty"`
SignatureAlgorithms []string `json:"signatureAlgorithms,omitempty"`
// the properties below are only used for testing
SkipClientIDCheck bool `json:"-"`
SkipExpiryCheck bool `json:"-"`
SkipIssuerCheck bool `json:"-"`
InsecureSkipSignatureCheck bool `json:"-"`
Now func() time.Time `json:"-"`
}
type OIDCOptions struct {
Provider *Provider `json:"provider,omitempty"`
Config *Config `json:"config,omitempty"`
TransformTemplate string `json:"transform,omitempty"`
target *template.Template
transform *template.Template
oidcProviderConfig *oidc.ProviderConfig
provider *oidc.Provider
verifier *oidc.IDTokenVerifier
}
func (o *OIDCOptions) GetVerifier(ctx context.Context) (*oidc.IDTokenVerifier, error) {
if o.verifier == nil {
switch {
case o.Provider.DiscoveryBaseURL != "":
// creates a new OIDC provider using automatic discovery and the default HTTP client
provider, err := oidc.NewProvider(ctx, o.Provider.DiscoveryBaseURL)
if err != nil {
return nil, fmt.Errorf("failed creating new OIDC provider using discovery: %w", err)
}
o.provider = provider
default:
o.provider = o.oidcProviderConfig.NewProvider(ctx)
}
if o.provider == nil {
return nil, errors.New("no OIDC provider available")
}
o.verifier = o.provider.Verifier(o.getConfig())
}
return o.verifier, nil
}
func (o *OIDCOptions) getConfig() *oidc.Config {
if o == nil || o.Config == nil {
return &oidc.Config{}
}
return &oidc.Config{
ClientID: o.Config.ClientID,
SupportedSigningAlgs: o.Config.SignatureAlgorithms,
SkipClientIDCheck: o.Config.SkipClientIDCheck,
SkipExpiryCheck: o.Config.SkipExpiryCheck,
SkipIssuerCheck: o.Config.SkipIssuerCheck,
Now: o.Config.Now,
InsecureSkipSignatureCheck: o.Config.InsecureSkipSignatureCheck,
}
}
const defaultTemplate = `{"name": "{{ .name }}", "preferred_username": "{{ .preferred_username }}"}`
func (o *OIDCOptions) validateAndInitialize() (err error) {
if o.Provider == nil {
return errors.New("provider not set")
}
if o.Provider.IssuerURL == "" && o.Provider.DiscoveryBaseURL == "" {
return errors.New("either OIDC discovery or issuer URL must be set")
}
if o.Provider.DiscoveryBaseURL == "" {
o.oidcProviderConfig, err = toOIDCProviderConfig(o.Provider)
if err != nil {
return fmt.Errorf("failed creationg OIDC provider config: %w", err)
}
}
o.target, err = template.New("DeviceID").Parse(o.Provider.IssuerURL)
if err != nil {
return fmt.Errorf("failed parsing OIDC template: %w", err)
}
o.transform, err = parseTransform(o.TransformTemplate)
if err != nil {
return fmt.Errorf("failed parsing OIDC transformation template: %w", err)
}
return nil
}
func parseTransform(transformTemplate string) (*template.Template, error) {
if transformTemplate == "" {
transformTemplate = defaultTemplate
}
return template.New("transform").Funcs(x509util.GetFuncMap()).Parse(transformTemplate)
}
func (o *OIDCOptions) EvaluateTarget(deviceID string) (string, error) {
buf := new(bytes.Buffer)
if err := o.target.Execute(buf, struct{ DeviceID string }{DeviceID: deviceID}); err != nil {
return "", fmt.Errorf("failed executing OIDC template: %w", err)
}
return buf.String(), nil
}
func (o *OIDCOptions) Transform(v map[string]any) (map[string]any, error) {
if o.transform == nil || v == nil {
return v, nil
}
// TODO(hs): add support for extracting error message from template "fail" function?
buf := new(bytes.Buffer)
if err := o.transform.Execute(buf, v); err != nil {
return nil, fmt.Errorf("failed executing OIDC transformation: %w", err)
}
var r map[string]any
if err := json.Unmarshal(buf.Bytes(), &r); err != nil {
return nil, fmt.Errorf("failed unmarshaling transformed OIDC token: %w", err)
}
// add original claims if not yet in the transformed result
for key, value := range v {
if _, ok := r[key]; !ok {
r[key] = value
}
}
return r, nil
}
func toOIDCProviderConfig(in *Provider) (*oidc.ProviderConfig, error) {
issuerURL, err := url.Parse(in.IssuerURL)
if err != nil {
return nil, fmt.Errorf("failed parsing issuer URL: %w", err)
}
// Removes query params from the URL because we use it as a way to notify client about the actual OAuth ClientId
// for this provisioner.
// This URL is going to look like: "https://idp:5556/dex?clientid=foo"
// If we don't trim the query params here i.e. 'clientid' then the idToken verification is going to fail because
// the 'iss' claim of the idToken will be "https://idp:5556/dex"
issuerURL.RawQuery = ""
issuerURL.Fragment = ""
return &oidc.ProviderConfig{
IssuerURL: issuerURL.String(),
AuthURL: in.AuthURL,
TokenURL: in.TokenURL,
UserInfoURL: in.UserInfoURL,
JWKSURL: in.JWKSURL,
Algorithms: in.Algorithms,
}, nil
}

@ -1,305 +0,0 @@
package wire
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"text/template"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.step.sm/crypto/jose"
)
func TestOIDCOptions_Transform(t *testing.T) {
defaultTransform, err := parseTransform(``)
require.NoError(t, err)
swapTransform, err := parseTransform(`{"name": "{{ .preferred_username }}", "preferred_username": "{{ .name }}"}`)
require.NoError(t, err)
funcTransform, err := parseTransform(`{"name": "{{ .name }}", "preferred_username": "{{ first .usernames }}"}`)
require.NoError(t, err)
type fields struct {
transform *template.Template
}
type args struct {
v map[string]any
}
tests := []struct {
name string
fields fields
args args
want map[string]any
expectedErr error
}{
{
name: "ok/no-transform",
fields: fields{
transform: nil,
},
args: args{
v: map[string]any{
"name": "Example",
"preferred_username": "Preferred",
},
},
want: map[string]any{
"name": "Example",
"preferred_username": "Preferred",
},
},
{
name: "ok/empty-data",
fields: fields{
transform: nil,
},
args: args{
v: map[string]any{},
},
want: map[string]any{},
},
{
name: "ok/default-transform",
fields: fields{
transform: defaultTransform,
},
args: args{
v: map[string]any{
"name": "Example",
"preferred_username": "Preferred",
},
},
want: map[string]any{
"name": "Example",
"preferred_username": "Preferred",
},
},
{
name: "ok/swap-transform",
fields: fields{
transform: swapTransform,
},
args: args{
v: map[string]any{
"name": "Example",
"preferred_username": "Preferred",
},
},
want: map[string]any{
"name": "Preferred",
"preferred_username": "Example",
},
},
{
name: "ok/transform-with-functions",
fields: fields{
transform: funcTransform,
},
args: args{
v: map[string]any{
"name": "Example",
"usernames": []string{"name-1", "name-2", "name-3"},
},
},
want: map[string]any{
"name": "Example",
"preferred_username": "name-1",
"usernames": []string{"name-1", "name-2", "name-3"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := &OIDCOptions{
transform: tt.fields.transform,
}
got, err := o.Transform(tt.args.v)
if tt.expectedErr != nil {
assert.Error(t, err)
return
}
assert.Equal(t, tt.want, got)
})
}
}
func TestOIDCOptions_EvaluateTarget(t *testing.T) {
tu := "http://target.example.com/{{.DeviceID}}"
target, err := template.New("DeviceID").Parse(tu)
require.NoError(t, err)
empty := "http://target.example.com"
emptyTarget, err := template.New("DeviceID").Parse(empty)
require.NoError(t, err)
fail := "https:/wire.com:15958/clients/{{.DeviceId}}/access-token"
failTarget, err := template.New("DeviceID").Parse(fail)
require.NoError(t, err)
type fields struct {
target *template.Template
}
type args struct {
deviceID string
}
tests := []struct {
name string
fields fields
args args
want string
expectedErr error
}{
{
name: "ok", fields: fields{target: target}, args: args{deviceID: "deviceID"}, want: "http://target.example.com/deviceID",
},
{
name: "ok/empty", fields: fields{target: emptyTarget}, args: args{deviceID: ""}, want: "http://target.example.com",
},
{
name: "fail/template", fields: fields{target: failTarget}, args: args{deviceID: "bla"}, expectedErr: errors.New(`failed executing OIDC template: template: DeviceID:1:32: executing "DeviceID" at <.DeviceId>: can't evaluate field DeviceId in type struct { DeviceID string }`),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := &OIDCOptions{
target: tt.fields.target,
}
got, err := o.EvaluateTarget(tt.args.deviceID)
if tt.expectedErr != nil {
assert.EqualError(t, err, tt.expectedErr.Error())
assert.Empty(t, got)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
func TestOIDCOptions_GetVerifier(t *testing.T) {
signerJWK, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
require.NoError(t, err)
require.NoError(t, err)
srv := mustDiscoveryServer(t, signerJWK.Public())
defer srv.Close()
type fields struct {
Provider *Provider
Config *Config
TransformTemplate string
}
tests := []struct {
name string
fields fields
ctx context.Context
want *oidc.IDTokenVerifier
wantErr bool
}{
{
name: "fail/invalid-discovery-url",
fields: fields{
Provider: &Provider{
DiscoveryBaseURL: "http://invalid.example.com",
},
Config: &Config{
ClientID: "client-id",
},
TransformTemplate: "http://target.example.com/{{.DeviceID}}",
},
ctx: context.Background(),
wantErr: true,
},
{
name: "ok/auto",
fields: fields{
Provider: &Provider{
DiscoveryBaseURL: srv.URL,
},
Config: &Config{
ClientID: "client-id",
},
TransformTemplate: "http://target.example.com/{{.DeviceID}}",
},
ctx: context.Background(),
},
{
name: "ok/fixed",
fields: fields{
Provider: &Provider{
IssuerURL: "http://issuer.example.com",
},
Config: &Config{
ClientID: "client-id",
},
TransformTemplate: "http://target.example.com/{{.DeviceID}}",
},
ctx: context.Background(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := &OIDCOptions{
Provider: tt.fields.Provider,
Config: tt.fields.Config,
TransformTemplate: tt.fields.TransformTemplate,
}
err := o.validateAndInitialize()
require.NoError(t, err)
verifier, err := o.GetVerifier(tt.ctx)
if tt.wantErr {
assert.Error(t, err)
assert.Nil(t, verifier)
return
}
assert.NoError(t, err)
assert.NotNil(t, verifier)
if assert.NotNil(t, o.provider) {
assert.NotNil(t, o.provider.Endpoint())
}
})
}
}
func mustDiscoveryServer(t *testing.T, pub jose.JSONWebKey) *httptest.Server {
t.Helper()
mux := http.NewServeMux()
server := httptest.NewServer(mux)
b, err := json.Marshal(struct {
Keys []jose.JSONWebKey `json:"keys,omitempty"`
}{
Keys: []jose.JSONWebKey{pub},
})
require.NoError(t, err)
jwks := string(b)
wellKnown := fmt.Sprintf(`{
"issuer": "%[1]s",
"authorization_endpoint": "%[1]s/auth",
"token_endpoint": "%[1]s/token",
"jwks_uri": "%[1]s/keys",
"userinfo_endpoint": "%[1]s/userinfo",
"id_token_signing_alg_values_supported": ["ES256"]
}`, server.URL)
mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, req *http.Request) {
_, err := io.WriteString(w, wellKnown)
if err != nil {
w.WriteHeader(500)
}
})
mux.HandleFunc("/keys", func(w http.ResponseWriter, req *http.Request) {
_, err := io.WriteString(w, jwks)
if err != nil {
w.WriteHeader(500)
}
})
t.Cleanup(server.Close)
return server
}

@ -1,51 +0,0 @@
package wire
import (
"errors"
"fmt"
)
// Options holds the Wire ACME extension options
type Options struct {
OIDC *OIDCOptions `json:"oidc,omitempty"`
DPOP *DPOPOptions `json:"dpop,omitempty"`
}
// GetOIDCOptions returns the OIDC options.
func (o *Options) GetOIDCOptions() *OIDCOptions {
if o == nil {
return nil
}
return o.OIDC
}
// GetDPOPOptions returns the DPoP options.
func (o *Options) GetDPOPOptions() *DPOPOptions {
if o == nil {
return nil
}
return o.DPOP
}
// Validate validates and initializes the Wire OIDC and DPoP options.
//
// TODO(hs): find a good way to perform this only once.
func (o *Options) Validate() error {
if oidc := o.GetOIDCOptions(); oidc != nil {
if err := oidc.validateAndInitialize(); err != nil {
return fmt.Errorf("failed initializing OIDC options: %w", err)
}
} else {
return errors.New("no OIDC options available")
}
if dpop := o.GetDPOPOptions(); dpop != nil {
if err := dpop.validateAndInitialize(); err != nil {
return fmt.Errorf("failed initializing DPoP options: %w", err)
}
} else {
return errors.New("no DPoP options available")
}
return nil
}

@ -1,163 +0,0 @@
package wire
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestOptions_Validate(t *testing.T) {
key := []byte(`-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
-----END PUBLIC KEY-----`)
type fields struct {
OIDC *OIDCOptions
DPOP *DPOPOptions
}
tests := []struct {
name string
fields fields
expectedErr error
}{
{
name: "ok",
fields: fields{
OIDC: &OIDCOptions{
Provider: &Provider{
IssuerURL: "https://example.com",
},
Config: &Config{},
},
DPOP: &DPOPOptions{
SigningKey: key,
},
},
expectedErr: nil,
},
{
name: "fail/no-oidc-options",
fields: fields{
OIDC: nil,
DPOP: &DPOPOptions{},
},
expectedErr: errors.New("no OIDC options available"),
},
{
name: "fail/empty-issuer-url",
fields: fields{
OIDC: &OIDCOptions{
Provider: &Provider{
IssuerURL: "",
},
Config: &Config{},
},
DPOP: &DPOPOptions{},
},
expectedErr: errors.New("failed initializing OIDC options: either OIDC discovery or issuer URL must be set"),
},
{
name: "fail/invalid-issuer-url",
fields: fields{
OIDC: &OIDCOptions{
Provider: &Provider{
IssuerURL: "\x00",
},
Config: &Config{},
},
DPOP: &DPOPOptions{},
},
expectedErr: errors.New(`failed initializing OIDC options: failed creationg OIDC provider config: failed parsing issuer URL: parse "\x00": net/url: invalid control character in URL`),
},
{
name: "fail/issuer-url-template",
fields: fields{
OIDC: &OIDCOptions{
Provider: &Provider{
IssuerURL: "https://issuer.example.com/{{}",
},
Config: &Config{},
},
DPOP: &DPOPOptions{},
},
expectedErr: errors.New(`failed initializing OIDC options: failed parsing OIDC template: template: DeviceID:1: unexpected "}" in command`),
},
{
name: "fail/invalid-transform-template",
fields: fields{
OIDC: &OIDCOptions{
Provider: &Provider{
IssuerURL: "https://example.com",
},
Config: &Config{},
TransformTemplate: "{{}",
},
DPOP: &DPOPOptions{
SigningKey: key,
},
},
expectedErr: errors.New(`failed initializing OIDC options: failed parsing OIDC transformation template: template: transform:1: unexpected "}" in command`),
},
{
name: "fail/no-dpop-options",
fields: fields{
OIDC: &OIDCOptions{
Provider: &Provider{
IssuerURL: "https://example.com",
},
Config: &Config{},
},
DPOP: nil,
},
expectedErr: errors.New("no DPoP options available"),
},
{
name: "fail/invalid-key",
fields: fields{
OIDC: &OIDCOptions{
Provider: &Provider{
IssuerURL: "https://example.com",
},
Config: &Config{},
},
DPOP: &DPOPOptions{
SigningKey: []byte{0x00},
Target: "",
},
},
expectedErr: errors.New(`failed initializing DPoP options: failed parsing key: error decoding PEM: not a valid PEM encoded block`),
},
{
name: "fail/target-template",
fields: fields{
OIDC: &OIDCOptions{
Provider: &Provider{
IssuerURL: "https://example.com",
},
Config: &Config{},
},
DPOP: &DPOPOptions{
SigningKey: key,
Target: "{{}",
},
},
expectedErr: errors.New(`failed initializing DPoP options: failed parsing DPoP template: template: DeviceID:1: unexpected "}" in command`),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := &Options{
OIDC: tt.fields.OIDC,
DPOP: tt.fields.DPOP,
}
err := o.Validate()
if tt.expectedErr != nil {
assert.EqualError(t, err, tt.expectedErr.Error())
return
}
assert.NoError(t, err)
})
}
}

@ -813,7 +813,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) {
}
tot++
}
if len(tc.claims.Step.SSH.CertType) > 0 {
if tc.claims.Step.SSH.CertType != "" {
assert.Equals(t, tot, 12)
} else {
assert.Equals(t, tot, 10)

@ -201,6 +201,7 @@ func (a *Authority) generateProvisionerConfig(ctx context.Context) (provisioner.
AuthorizeRenewFunc: a.authorizeRenewFunc,
AuthorizeSSHRenewFunc: a.authorizeSSHRenewFunc,
WebhookClient: a.webhookClient,
SCEPKeyManager: a.scepKeyManager,
}, nil
}
@ -425,25 +426,25 @@ func ValidateClaims(c *linkedca.Claims) error {
// ValidateDurations validates the Durations type.
func ValidateDurations(d *linkedca.Durations) error {
var (
err error
min, max, def *provisioner.Duration
err error
minDur, maxDur, def *provisioner.Duration
)
if d.Min != "" {
min, err = provisioner.NewDuration(d.Min)
minDur, err = provisioner.NewDuration(d.Min)
if err != nil {
return admin.WrapError(admin.ErrorBadRequestType, err, "min duration '%s' is invalid", d.Min)
}
if min.Value() < 0 {
if minDur.Value() < 0 {
return admin.WrapError(admin.ErrorBadRequestType, err, "min duration '%s' cannot be less than 0", d.Min)
}
}
if d.Max != "" {
max, err = provisioner.NewDuration(d.Max)
maxDur, err = provisioner.NewDuration(d.Max)
if err != nil {
return admin.WrapError(admin.ErrorBadRequestType, err, "max duration '%s' is invalid", d.Max)
}
if max.Value() < 0 {
if maxDur.Value() < 0 {
return admin.WrapError(admin.ErrorBadRequestType, err, "max duration '%s' cannot be less than 0", d.Max)
}
}
@ -456,15 +457,15 @@ func ValidateDurations(d *linkedca.Durations) error {
return admin.WrapError(admin.ErrorBadRequestType, err, "default duration '%s' cannot be less than 0", d.Default)
}
}
if d.Min != "" && d.Max != "" && min.Value() > max.Value() {
if d.Min != "" && d.Max != "" && minDur.Value() > maxDur.Value() {
return admin.NewError(admin.ErrorBadRequestType,
"min duration '%s' cannot be greater than max duration '%s'", d.Min, d.Max)
}
if d.Min != "" && d.Default != "" && min.Value() > def.Value() {
if d.Min != "" && d.Default != "" && minDur.Value() > def.Value() {
return admin.NewError(admin.ErrorBadRequestType,
"min duration '%s' cannot be greater than default duration '%s'", d.Min, d.Default)
}
if d.Default != "" && d.Max != "" && min.Value() > def.Value() {
if d.Default != "" && d.Max != "" && minDur.Value() > def.Value() {
return admin.NewError(admin.ErrorBadRequestType,
"default duration '%s' cannot be greater than max duration '%s'", d.Default, d.Max)
}
@ -607,20 +608,20 @@ func provisionerWebhookToLinkedca(pwh *provisioner.Webhook) *linkedca.Webhook {
return lwh
}
func durationsToCertificates(d *linkedca.Durations) (min, max, def *provisioner.Duration, err error) {
if len(d.Min) > 0 {
min, err = provisioner.NewDuration(d.Min)
func durationsToCertificates(d *linkedca.Durations) (minDur, maxDur, def *provisioner.Duration, err error) {
if d.Min != "" {
minDur, err = provisioner.NewDuration(d.Min)
if err != nil {
return nil, nil, nil, admin.WrapErrorISE(err, "error parsing minimum duration '%s'", d.Min)
}
}
if len(d.Max) > 0 {
max, err = provisioner.NewDuration(d.Max)
if d.Max != "" {
maxDur, err = provisioner.NewDuration(d.Max)
if err != nil {
return nil, nil, nil, admin.WrapErrorISE(err, "error parsing maximum duration '%s'", d.Max)
}
}
if len(d.Default) > 0 {
if d.Default != "" {
def, err = provisioner.NewDuration(d.Default)
if err != nil {
return nil, nil, nil, admin.WrapErrorISE(err, "error parsing default duration '%s'", d.Default)
@ -917,6 +918,8 @@ func ProvisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface,
Domains: cfg.Domains,
Groups: cfg.Groups,
ListenAddress: cfg.ListenAddress,
Scopes: cfg.Scopes,
AuthParams: cfg.AuthParams,
Claims: claims,
Options: options,
}, nil
@ -1065,6 +1068,8 @@ func ProvisionerToLinkedca(p provisioner.Interface) (*linkedca.Provisioner, erro
Groups: p.Groups,
ListenAddress: p.ListenAddress,
TenantId: p.TenantID,
Scopes: p.Scopes,
AuthParams: p.AuthParams,
},
},
},

@ -45,7 +45,7 @@ func (a *Authority) GetRoots() ([]*x509.Certificate, error) {
// GetFederation returns all the root certificates in the federation.
// This method implements the Authority interface.
func (a *Authority) GetFederation() (federation []*x509.Certificate, err error) {
a.certificates.Range(func(k, v interface{}) bool {
a.certificates.Range(func(_, v interface{}) bool {
crt, ok := v.(*x509.Certificate)
if !ok {
federation = nil
@ -57,3 +57,26 @@ func (a *Authority) GetFederation() (federation []*x509.Certificate, err error)
})
return
}
// GetIntermediateCertificate return the intermediate certificate that issues
// the leaf certificates in the CA.
//
// This method can return nil if the CA is configured with a Certificate
// Authority Service (CAS) that does not implement the
// CertificateAuthorityGetter interface.
func (a *Authority) GetIntermediateCertificate() *x509.Certificate {
if len(a.intermediateX509Certs) > 0 {
return a.intermediateX509Certs[0]
}
return nil
}
// GetIntermediateCertificates returns a list of all intermediate certificates
// configured. The first certificate in the list will be the issuer certificate.
//
// This method can return an empty list or nil if the CA is configured with a
// Certificate Authority Service (CAS) that does not implement the
// CertificateAuthorityGetter interface.
func (a *Authority) GetIntermediateCertificates() []*x509.Certificate {
return a.intermediateX509Certs
}

@ -2,15 +2,18 @@ package authority
import (
"crypto/x509"
"crypto/x509/pkix"
"errors"
"net/http"
"reflect"
"testing"
"go.step.sm/crypto/pemutil"
"github.com/smallstep/assert"
"github.com/smallstep/certificates/api/render"
"github.com/stretchr/testify/require"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/minica"
"go.step.sm/crypto/pemutil"
)
func TestRoot(t *testing.T) {
@ -152,3 +155,63 @@ func TestAuthority_GetFederation(t *testing.T) {
})
}
}
func TestAuthority_GetIntermediateCertificate(t *testing.T) {
ca, err := minica.New(minica.WithRootTemplate(`{
"subject": {{ toJson .Subject }},
"issuer": {{ toJson .Subject }},
"keyUsage": ["certSign", "crlSign"],
"basicConstraints": {
"isCA": true,
"maxPathLen": -1
}
}`), minica.WithIntermediateTemplate(`{
"subject": {{ toJson .Subject }},
"keyUsage": ["certSign", "crlSign"],
"basicConstraints": {
"isCA": true,
"maxPathLen": 1
}
}`))
require.NoError(t, err)
signer, err := keyutil.GenerateDefaultSigner()
require.NoError(t, err)
cert, err := ca.Sign(&x509.Certificate{
Subject: pkix.Name{CommonName: "MiniCA Intermediate CA 0"},
PublicKey: signer.Public(),
BasicConstraintsValid: true,
IsCA: true,
MaxPathLen: 0,
})
require.NoError(t, err)
type fields struct {
intermediateX509Certs []*x509.Certificate
}
tests := []struct {
name string
fields fields
want *x509.Certificate
wantSlice []*x509.Certificate
}{
{"ok one", fields{[]*x509.Certificate{ca.Intermediate}}, ca.Intermediate, []*x509.Certificate{ca.Intermediate}},
{"ok multiple", fields{[]*x509.Certificate{cert, ca.Intermediate}}, cert, []*x509.Certificate{cert, ca.Intermediate}},
{"ok empty", fields{[]*x509.Certificate{}}, nil, []*x509.Certificate{}},
{"ok nil", fields{nil}, nil, nil},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &Authority{
intermediateX509Certs: tt.fields.intermediateX509Certs,
}
if got := a.GetIntermediateCertificate(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("Authority.GetIntermediateCertificate() = %v, want %v", got, tt.want)
}
if got := a.GetIntermediateCertificates(); !reflect.DeepEqual(got, tt.wantSlice) {
t.Errorf("Authority.GetIntermediateCertificates() = %v, want %v", got, tt.wantSlice)
}
})
}
}

@ -59,7 +59,7 @@ var (
)
func withDefaultASN1DN(def *config.ASN1DN) provisioner.CertificateModifierFunc {
return func(crt *x509.Certificate, opts provisioner.SignOptions) error {
return func(crt *x509.Certificate, _ provisioner.SignOptions) error {
if def == nil {
return errors.New("default ASN1DN template cannot be nil")
}
@ -91,6 +91,21 @@ func withDefaultASN1DN(def *config.ASN1DN) provisioner.CertificateModifierFunc {
}
}
// GetX509Signer returns a [crypto.Signer] implementation using the intermediate
// key.
//
// This method can return a [NotImplementedError] if the CA is configured with a
// Certificate Authority Service (CAS) that does not implement the
// CertificateAuthoritySigner interface.
//
// [NotImplementedError]: https://pkg.go.dev/github.com/smallstep/certificates/cas/apiv1#NotImplementedError
func (a *Authority) GetX509Signer() (crypto.Signer, error) {
if s, ok := a.x509CAService.(casapi.CertificateAuthoritySigner); ok {
return s.GetSigner()
}
return nil, casapi.NotImplementedError{}
}
// Sign creates a signed certificate from a certificate signing request. It
// creates a new context.Context, and calls into SignWithContext.
//
@ -913,10 +928,19 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) {
return fatal(err)
}
// Set the cert lifetime as follows:
// i) If the CA is not a StepCAS RA use 24h, else
// ii) if the CA is a StepCAS RA, leave the lifetime empty and
// let the provisioner of the CA decide the lifetime of the RA cert.
var lifetime time.Duration
if casapi.TypeOf(a.x509CAService) != casapi.StepCAS {
lifetime = 24 * time.Hour
}
resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{
Template: certTpl,
CSR: cr,
Lifetime: 24 * time.Hour,
Lifetime: lifetime,
Backdate: 1 * time.Minute,
IsCAServerCert: true,
})

@ -15,6 +15,7 @@ import (
"fmt"
"net/http"
"reflect"
"strings"
"testing"
"time"
@ -24,11 +25,11 @@ import (
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util"
sassert "github.com/smallstep/assert"
"github.com/smallstep/certificates/api/render"
"github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/policy"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/cas/apiv1"
"github.com/smallstep/certificates/cas/softcas"
"github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/errs"
@ -223,6 +224,15 @@ func generateSubjectKeyID(pub crypto.PublicKey) ([]byte, error) {
return hash[:], nil
}
func assertHasPrefix(t *testing.T, s, p string) bool {
if strings.HasPrefix(s, p) {
return true
}
t.Helper()
t.Errorf("%q is not a prefix of %q", p, s)
return false
}
type basicConstraints struct {
IsCA bool `asn1:"optional"`
MaxPathLen int `asn1:"optional,default:-1"`
@ -418,7 +428,7 @@ ZYtQ9Ot36qc=
require.NoError(t, err)
testAuthority.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
sassert.Equals(t, crt.Subject.CommonName, "smallstep test")
assert.Equal(t, "smallstep test", crt.Subject.CommonName)
return nil
},
}
@ -447,7 +457,7 @@ ZYtQ9Ot36qc=
require.NoError(t, err)
testAuthority.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
sassert.Equals(t, crt.Subject.CommonName, "smallstep test")
assert.Equal(t, "smallstep test", crt.Subject.CommonName)
return nil
},
}
@ -476,7 +486,7 @@ ZYtQ9Ot36qc=
require.NoError(t, err)
testAuthority.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
sassert.Equals(t, crt.Subject.CommonName, "smallstep test")
assert.Equal(t, "smallstep test", crt.Subject.CommonName)
return nil
},
}
@ -494,7 +504,7 @@ ZYtQ9Ot36qc=
aa := testAuthority(t)
aa.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
sassert.Equals(t, crt.Subject.CommonName, "smallstep test")
assert.Equal(t, "smallstep test", crt.Subject.CommonName)
return nil
},
}
@ -519,7 +529,7 @@ ZYtQ9Ot36qc=
}))
aa.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
sassert.Equals(t, crt.Subject.CommonName, "smallstep test")
assert.Equal(t, "smallstep test", crt.Subject.CommonName)
return nil
},
}
@ -539,7 +549,7 @@ ZYtQ9Ot36qc=
aa.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
fmt.Println(crt.Subject)
sassert.Equals(t, crt.Subject.CommonName, "smallstep test")
assert.Equal(t, "smallstep test", crt.Subject.CommonName)
return nil
},
}
@ -600,7 +610,7 @@ ZYtQ9Ot36qc=
_a := testAuthority(t)
_a.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
sassert.Equals(t, crt.Subject.CommonName, "smallstep test")
assert.Equal(t, "smallstep test", crt.Subject.CommonName)
return nil
},
}
@ -634,7 +644,7 @@ ZYtQ9Ot36qc=
_a := testAuthority(t)
_a.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
sassert.Equals(t, crt.Subject.CommonName, "smallstep test")
assert.Equal(t, "smallstep test", crt.Subject.CommonName)
return nil
},
}
@ -668,7 +678,7 @@ ZYtQ9Ot36qc=
require.NoError(t, err)
testAuthority.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
sassert.Equals(t, crt.Subject.CommonName, "smallstep test")
assert.Equal(t, "smallstep test", crt.Subject.CommonName)
return nil
},
}
@ -702,7 +712,7 @@ ZYtQ9Ot36qc=
require.NoError(t, err)
testAuthority.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
sassert.Equals(t, crt.Subject.CommonName, "smallstep test")
assert.Equal(t, "smallstep test", crt.Subject.CommonName)
return nil
},
}
@ -739,7 +749,7 @@ ZYtQ9Ot36qc=
_a.config.AuthorityConfig.Template = &ASN1DN{}
_a.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
sassert.Equals(t, crt.Subject, pkix.Name{})
assert.Equal(t, pkix.Name{}, crt.Subject)
return nil
},
}
@ -764,8 +774,8 @@ ZYtQ9Ot36qc=
aa.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template
aa.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
sassert.Equals(t, crt.Subject.CommonName, "smallstep test")
sassert.Equals(t, crt.CRLDistributionPoints, []string{"http://ca.example.org/leaf.crl"})
assert.Equal(t, "smallstep test", crt.Subject.CommonName)
assert.Equal(t, []string{"http://ca.example.org/leaf.crl"}, crt.CRLDistributionPoints)
return nil
},
}
@ -785,7 +795,7 @@ ZYtQ9Ot36qc=
aa.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template
aa.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
sassert.Equals(t, crt.Subject.CommonName, "smallstep test")
assert.Equal(t, crt.Subject.CommonName, "smallstep test")
return nil
},
}
@ -818,13 +828,13 @@ ZYtQ9Ot36qc=
MStoreCertificateChain: func(prov provisioner.Interface, certs ...*x509.Certificate) error {
p, ok := prov.(attProvisioner)
if assert.True(t, ok) {
sassert.Equals(t, &provisioner.AttestationData{
assert.Equal(t, &provisioner.AttestationData{
PermanentIdentifier: "1234567890",
}, p.AttestationData())
}
if assert.Len(t, certs, 2) {
sassert.Equals(t, certs[0].Subject.CommonName, "smallstep test")
sassert.Equals(t, certs[1].Subject.CommonName, "smallstep Intermediate CA")
assert.Equal(t, "smallstep test", certs[0].Subject.CommonName)
assert.Equal(t, "smallstep Intermediate CA", certs[1].Subject.CommonName)
}
return nil
},
@ -853,46 +863,45 @@ ZYtQ9Ot36qc=
if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) {
assert.Nil(t, certChain)
var sc render.StatusCodedError
sassert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface")
sassert.Equals(t, sc.StatusCode(), tc.code)
sassert.HasPrefix(t, err.Error(), tc.err.Error())
require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface")
assert.Equal(t, tc.code, sc.StatusCode())
assertHasPrefix(t, err.Error(), tc.err.Error())
var ctxErr *errs.Error
sassert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error")
sassert.Equals(t, ctxErr.Details["csr"], tc.csr)
sassert.Equals(t, ctxErr.Details["signOptions"], tc.signOpts)
require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error")
assert.Equal(t, tc.csr, ctxErr.Details["csr"])
assert.Equal(t, tc.signOpts, ctxErr.Details["signOptions"])
}
} else {
leaf := certChain[0]
intermediate := certChain[1]
if assert.Nil(t, tc.err) {
sassert.Equals(t, leaf.NotBefore, tc.notBefore)
sassert.Equals(t, leaf.NotAfter, tc.notAfter)
assert.Equal(t, tc.notBefore, leaf.NotBefore)
assert.Equal(t, tc.notAfter, leaf.NotAfter)
tmplt := a.config.AuthorityConfig.Template
if tc.csr.Subject.CommonName == "" {
sassert.Equals(t, leaf.Subject, pkix.Name{})
assert.Equal(t, pkix.Name{}, leaf.Subject)
} else {
sassert.Equals(t, leaf.Subject.String(),
pkix.Name{
Country: []string{tmplt.Country},
Organization: []string{tmplt.Organization},
Locality: []string{tmplt.Locality},
StreetAddress: []string{tmplt.StreetAddress},
Province: []string{tmplt.Province},
CommonName: "smallstep test",
}.String())
sassert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com"})
assert.Equal(t, pkix.Name{
Country: []string{tmplt.Country},
Organization: []string{tmplt.Organization},
Locality: []string{tmplt.Locality},
StreetAddress: []string{tmplt.StreetAddress},
Province: []string{tmplt.Province},
CommonName: "smallstep test",
}.String(), leaf.Subject.String())
assert.Equal(t, []string{"test.smallstep.com"}, leaf.DNSNames)
}
sassert.Equals(t, leaf.Issuer, intermediate.Subject)
sassert.Equals(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256)
sassert.Equals(t, leaf.PublicKeyAlgorithm, x509.ECDSA)
sassert.Equals(t, leaf.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
assert.Equal(t, intermediate.Subject, leaf.Issuer)
assert.Equal(t, x509.ECDSAWithSHA256, leaf.SignatureAlgorithm)
assert.Equal(t, x509.ECDSA, leaf.PublicKeyAlgorithm)
assert.Equal(t, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, leaf.ExtKeyUsage)
issuer := getDefaultIssuer(a)
subjectKeyID, err := generateSubjectKeyID(pub)
require.NoError(t, err)
sassert.Equals(t, leaf.SubjectKeyId, subjectKeyID)
sassert.Equals(t, leaf.AuthorityKeyId, issuer.SubjectKeyId)
assert.Equal(t, subjectKeyID, leaf.SubjectKeyId)
assert.Equal(t, issuer.SubjectKeyId, leaf.AuthorityKeyId)
// Verify Provisioner OID
found := 0
@ -903,9 +912,9 @@ ZYtQ9Ot36qc=
val := stepProvisionerASN1{}
_, err := asn1.Unmarshal(ext.Value, &val)
require.NoError(t, err)
sassert.Equals(t, val.Type, provisionerTypeJWK)
sassert.Equals(t, val.Name, []byte(p.Name))
sassert.Equals(t, val.CredentialID, []byte(p.Key.KeyID))
assert.Equal(t, provisionerTypeJWK, val.Type)
assert.Equal(t, []byte(p.Name), val.Name)
assert.Equal(t, []byte(p.Key.KeyID), val.CredentialID)
// Basic Constraints
case ext.Id.Equal(asn1.ObjectIdentifier([]int{2, 5, 29, 19})):
@ -913,7 +922,7 @@ ZYtQ9Ot36qc=
_, err := asn1.Unmarshal(ext.Value, &val)
require.NoError(t, err)
assert.False(t, val.IsCA, false)
sassert.Equals(t, val.MaxPathLen, 0)
assert.Equal(t, val.MaxPathLen, 0)
// SAN extension
case ext.Id.Equal(asn1.ObjectIdentifier([]int{2, 5, 29, 17})):
@ -924,10 +933,10 @@ ZYtQ9Ot36qc=
}
}
}
sassert.Equals(t, found, 1)
assert.Equal(t, found, 1)
realIntermediate, err := x509.ParseCertificate(issuer.Raw)
require.NoError(t, err)
sassert.Equals(t, intermediate, realIntermediate)
assert.Equal(t, realIntermediate, intermediate)
assert.Len(t, leaf.Extensions, tc.extensionsCount)
}
}
@ -1070,19 +1079,19 @@ func TestAuthority_Renew(t *testing.T) {
if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) {
assert.Nil(t, certChain)
var sc render.StatusCodedError
sassert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface")
sassert.Equals(t, sc.StatusCode(), tc.code)
sassert.HasPrefix(t, err.Error(), tc.err.Error())
require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface")
assert.Equal(t, tc.code, sc.StatusCode())
assertHasPrefix(t, err.Error(), tc.err.Error())
var ctxErr *errs.Error
sassert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error")
sassert.Equals(t, ctxErr.Details["serialNumber"], tc.cert.SerialNumber.String())
require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error")
assert.Equal(t, tc.cert.SerialNumber.String(), ctxErr.Details["serialNumber"])
}
} else {
leaf := certChain[0]
intermediate := certChain[1]
if assert.Nil(t, tc.err) {
sassert.Equals(t, leaf.NotAfter.Sub(leaf.NotBefore), tc.cert.NotAfter.Sub(cert.NotBefore))
assert.Equal(t, tc.cert.NotAfter.Sub(cert.NotBefore), leaf.NotAfter.Sub(leaf.NotBefore))
assert.True(t, leaf.NotBefore.After(now.Add(-2*time.Minute)))
assert.True(t, leaf.NotBefore.Before(now.Add(time.Minute)))
@ -1092,30 +1101,29 @@ func TestAuthority_Renew(t *testing.T) {
assert.True(t, leaf.NotAfter.Before(expiry.Add(time.Hour)))
tmplt := a.config.AuthorityConfig.Template
sassert.Equals(t, leaf.RawSubject, tc.cert.RawSubject)
sassert.Equals(t, leaf.Subject.Country, []string{tmplt.Country})
sassert.Equals(t, leaf.Subject.Organization, []string{tmplt.Organization})
sassert.Equals(t, leaf.Subject.Locality, []string{tmplt.Locality})
sassert.Equals(t, leaf.Subject.StreetAddress, []string{tmplt.StreetAddress})
sassert.Equals(t, leaf.Subject.Province, []string{tmplt.Province})
sassert.Equals(t, leaf.Subject.CommonName, tmplt.CommonName)
sassert.Equals(t, leaf.Issuer, intermediate.Subject)
sassert.Equals(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256)
sassert.Equals(t, leaf.PublicKeyAlgorithm, x509.ECDSA)
sassert.Equals(t, leaf.ExtKeyUsage,
[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
sassert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com", "test"})
assert.Equal(t, tc.cert.RawSubject, leaf.RawSubject)
assert.Equal(t, []string{tmplt.Country}, leaf.Subject.Country)
assert.Equal(t, []string{tmplt.Organization}, leaf.Subject.Organization)
assert.Equal(t, []string{tmplt.Locality}, leaf.Subject.Locality)
assert.Equal(t, []string{tmplt.StreetAddress}, leaf.Subject.StreetAddress)
assert.Equal(t, []string{tmplt.Province}, leaf.Subject.Province)
assert.Equal(t, tmplt.CommonName, leaf.Subject.CommonName)
assert.Equal(t, intermediate.Subject, leaf.Issuer)
assert.Equal(t, x509.ECDSAWithSHA256, leaf.SignatureAlgorithm)
assert.Equal(t, x509.ECDSA, leaf.PublicKeyAlgorithm)
assert.Equal(t, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, leaf.ExtKeyUsage)
assert.Equal(t, []string{"test.smallstep.com", "test"}, leaf.DNSNames)
subjectKeyID, err := generateSubjectKeyID(leaf.PublicKey)
require.NoError(t, err)
sassert.Equals(t, leaf.SubjectKeyId, subjectKeyID)
assert.Equal(t, subjectKeyID, leaf.SubjectKeyId)
// We did not change the intermediate before renewing.
authIssuer := getDefaultIssuer(tc.auth)
if issuer.SerialNumber == authIssuer.SerialNumber {
sassert.Equals(t, leaf.AuthorityKeyId, issuer.SubjectKeyId)
assert.Equal(t, issuer.SubjectKeyId, leaf.AuthorityKeyId)
// Compare extensions: they can be in a different order
for _, ext1 := range tc.cert.Extensions {
//skip SubjectKeyIdentifier
@ -1135,7 +1143,7 @@ func TestAuthority_Renew(t *testing.T) {
}
} else {
// We did change the intermediate before renewing.
sassert.Equals(t, leaf.AuthorityKeyId, authIssuer.SubjectKeyId)
assert.Equal(t, authIssuer.SubjectKeyId, leaf.AuthorityKeyId)
// Compare extensions: they can be in a different order
for _, ext1 := range tc.cert.Extensions {
//skip SubjectKeyIdentifier
@ -1164,7 +1172,7 @@ func TestAuthority_Renew(t *testing.T) {
realIntermediate, err := x509.ParseCertificate(authIssuer.Raw)
require.NoError(t, err)
sassert.Equals(t, intermediate, realIntermediate)
assert.Equal(t, realIntermediate, intermediate)
}
}
})
@ -1275,19 +1283,19 @@ func TestAuthority_Rekey(t *testing.T) {
if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) {
assert.Nil(t, certChain)
var sc render.StatusCodedError
sassert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface")
sassert.Equals(t, sc.StatusCode(), tc.code)
sassert.HasPrefix(t, err.Error(), tc.err.Error())
require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface")
assert.Equal(t, tc.code, sc.StatusCode())
assertHasPrefix(t, err.Error(), tc.err.Error())
var ctxErr *errs.Error
sassert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error")
sassert.Equals(t, ctxErr.Details["serialNumber"], tc.cert.SerialNumber.String())
require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error")
assert.Equal(t, tc.cert.SerialNumber.String(), ctxErr.Details["serialNumber"])
}
} else {
leaf := certChain[0]
intermediate := certChain[1]
if assert.Nil(t, tc.err) {
sassert.Equals(t, leaf.NotAfter.Sub(leaf.NotBefore), tc.cert.NotAfter.Sub(cert.NotBefore))
assert.Equal(t, tc.cert.NotAfter.Sub(cert.NotBefore), leaf.NotAfter.Sub(leaf.NotBefore))
assert.True(t, leaf.NotBefore.After(now.Add(-2*time.Minute)))
assert.True(t, leaf.NotBefore.Before(now.Add(time.Minute)))
@ -1297,41 +1305,39 @@ func TestAuthority_Rekey(t *testing.T) {
assert.True(t, leaf.NotAfter.Before(expiry.Add(time.Hour)))
tmplt := a.config.AuthorityConfig.Template
sassert.Equals(t, leaf.Subject.String(),
pkix.Name{
Country: []string{tmplt.Country},
Organization: []string{tmplt.Organization},
Locality: []string{tmplt.Locality},
StreetAddress: []string{tmplt.StreetAddress},
Province: []string{tmplt.Province},
CommonName: tmplt.CommonName,
}.String())
sassert.Equals(t, leaf.Issuer, intermediate.Subject)
sassert.Equals(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256)
sassert.Equals(t, leaf.PublicKeyAlgorithm, x509.ECDSA)
sassert.Equals(t, leaf.ExtKeyUsage,
[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
sassert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com", "test"})
assert.Equal(t, pkix.Name{
Country: []string{tmplt.Country},
Organization: []string{tmplt.Organization},
Locality: []string{tmplt.Locality},
StreetAddress: []string{tmplt.StreetAddress},
Province: []string{tmplt.Province},
CommonName: tmplt.CommonName,
}.String(), leaf.Subject.String())
assert.Equal(t, intermediate.Subject, leaf.Issuer)
assert.Equal(t, x509.ECDSAWithSHA256, leaf.SignatureAlgorithm)
assert.Equal(t, x509.ECDSA, leaf.PublicKeyAlgorithm)
assert.Equal(t, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, leaf.ExtKeyUsage)
assert.Equal(t, []string{"test.smallstep.com", "test"}, leaf.DNSNames)
// Test Public Key and SubjectKeyId
expectedPK := tc.pk
if tc.pk == nil {
expectedPK = cert.PublicKey
}
sassert.Equals(t, leaf.PublicKey, expectedPK)
assert.Equal(t, expectedPK, leaf.PublicKey)
subjectKeyID, err := generateSubjectKeyID(expectedPK)
require.NoError(t, err)
sassert.Equals(t, leaf.SubjectKeyId, subjectKeyID)
assert.Equal(t, subjectKeyID, leaf.SubjectKeyId)
if tc.pk == nil {
sassert.Equals(t, leaf.SubjectKeyId, cert.SubjectKeyId)
assert.Equal(t, cert.SubjectKeyId, leaf.SubjectKeyId)
}
// We did not change the intermediate before renewing.
authIssuer := getDefaultIssuer(tc.auth)
if issuer.SerialNumber == authIssuer.SerialNumber {
sassert.Equals(t, leaf.AuthorityKeyId, issuer.SubjectKeyId)
assert.Equal(t, issuer.SubjectKeyId, leaf.AuthorityKeyId)
// Compare extensions: they can be in a different order
for _, ext1 := range tc.cert.Extensions {
//skip SubjectKeyIdentifier
@ -1351,7 +1357,7 @@ func TestAuthority_Rekey(t *testing.T) {
}
} else {
// We did change the intermediate before renewing.
sassert.Equals(t, leaf.AuthorityKeyId, authIssuer.SubjectKeyId)
assert.Equal(t, authIssuer.SubjectKeyId, leaf.AuthorityKeyId)
// Compare extensions: they can be in a different order
for _, ext1 := range tc.cert.Extensions {
//skip SubjectKeyIdentifier
@ -1380,7 +1386,7 @@ func TestAuthority_Rekey(t *testing.T) {
realIntermediate, err := x509.ParseCertificate(authIssuer.Raw)
require.NoError(t, err)
sassert.Equals(t, intermediate, realIntermediate)
assert.Equal(t, realIntermediate, intermediate)
}
}
})
@ -1418,7 +1424,7 @@ func TestAuthority_GetTLSOptions(t *testing.T) {
require.NoError(t, err)
opts := tc.auth.GetTLSOptions()
sassert.Equals(t, opts, tc.opts)
assert.Equal(t, tc.opts, opts)
})
}
}
@ -1488,9 +1494,9 @@ func TestAuthority_Revoke(t *testing.T) {
err: errors.New("authority.Revoke; no persistence layer configured"),
code: http.StatusNotImplemented,
checkErrDetails: func(err *errs.Error) {
sassert.Equals(t, err.Details["token"], raw)
sassert.Equals(t, err.Details["tokenID"], "44")
sassert.Equals(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc")
assert.Equal(t, raw, err.Details["token"])
assert.Equal(t, "44", err.Details["tokenID"])
assert.Equal(t, "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc", err.Details["provisionerID"])
},
}
},
@ -1528,9 +1534,9 @@ func TestAuthority_Revoke(t *testing.T) {
err: errors.New("authority.Revoke: force"),
code: http.StatusInternalServerError,
checkErrDetails: func(err *errs.Error) {
sassert.Equals(t, err.Details["token"], raw)
sassert.Equals(t, err.Details["tokenID"], "44")
sassert.Equals(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc")
assert.Equal(t, raw, err.Details["token"])
assert.Equal(t, "44", err.Details["tokenID"])
assert.Equal(t, "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc", err.Details["provisionerID"])
},
}
},
@ -1568,9 +1574,9 @@ func TestAuthority_Revoke(t *testing.T) {
err: errors.New("certificate with serial number 'sn' is already revoked"),
code: http.StatusBadRequest,
checkErrDetails: func(err *errs.Error) {
sassert.Equals(t, err.Details["token"], raw)
sassert.Equals(t, err.Details["tokenID"], "44")
sassert.Equals(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc")
assert.Equal(t, raw, err.Details["token"])
assert.Equal(t, "44", err.Details["tokenID"])
assert.Equal(t, "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc", err.Details["provisionerID"])
},
}
},
@ -1704,17 +1710,17 @@ func TestAuthority_Revoke(t *testing.T) {
if err := tc.auth.Revoke(tc.ctx, tc.opts); err != nil {
if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) {
var sc render.StatusCodedError
sassert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface")
sassert.Equals(t, sc.StatusCode(), tc.code)
sassert.HasPrefix(t, err.Error(), tc.err.Error())
require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface")
assert.Equal(t, tc.code, sc.StatusCode())
assertHasPrefix(t, err.Error(), tc.err.Error())
var ctxErr *errs.Error
sassert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error")
sassert.Equals(t, ctxErr.Details["serialNumber"], tc.opts.Serial)
sassert.Equals(t, ctxErr.Details["reasonCode"], tc.opts.ReasonCode)
sassert.Equals(t, ctxErr.Details["reason"], tc.opts.Reason)
sassert.Equals(t, ctxErr.Details["MTLS"], tc.opts.MTLS)
sassert.Equals(t, ctxErr.Details["context"], provisioner.RevokeMethod.String())
require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error")
assert.Equal(t, tc.opts.Serial, ctxErr.Details["serialNumber"])
assert.Equal(t, tc.opts.ReasonCode, ctxErr.Details["reasonCode"])
assert.Equal(t, tc.opts.Reason, ctxErr.Details["reason"])
assert.Equal(t, tc.opts.MTLS, ctxErr.Details["MTLS"])
assert.Equal(t, provisioner.RevokeMethod.String(), ctxErr.Details["context"])
if tc.checkErrDetails != nil {
tc.checkErrDetails(ctxErr)
@ -1952,3 +1958,39 @@ func TestAuthority_CRL(t *testing.T) {
})
}
}
type notImplementedCAS struct{}
func (notImplementedCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) {
return nil, apiv1.NotImplementedError{}
}
func (notImplementedCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) {
return nil, apiv1.NotImplementedError{}
}
func (notImplementedCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) {
return nil, apiv1.NotImplementedError{}
}
func TestAuthority_GetX509Signer(t *testing.T) {
auth := testAuthority(t)
require.IsType(t, &softcas.SoftCAS{}, auth.x509CAService)
signer := auth.x509CAService.(*softcas.SoftCAS).Signer
require.NotNil(t, signer)
tests := []struct {
name string
authority *Authority
want crypto.Signer
assertion assert.ErrorAssertionFunc
}{
{"ok", auth, signer, assert.NoError},
{"fail", testAuthority(t, WithX509CAService(notImplementedCAS{})), nil, assert.Error},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.authority.GetX509Signer()
tt.assertion(t, err)
assert.Equal(t, tt.want, got)
})
}
}

@ -108,19 +108,19 @@ func TestNewACMEClient(t *testing.T) {
tc := run(t)
i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
switch {
case i == 0:
render.JSONStatus(w, tc.r1, tc.rc1)
render.JSONStatus(w, r, tc.r1, tc.rc1)
i++
case i == 1:
w.Header().Set("Replay-Nonce", "abc123")
render.JSONStatus(w, []byte{}, 200)
render.JSONStatus(w, r, []byte{}, 200)
i++
default:
w.Header().Set("Location", accLocation)
render.JSONStatus(w, tc.r2, tc.rc2)
render.JSONStatus(w, r, tc.r2, tc.rc2)
}
})
@ -203,10 +203,10 @@ func TestACMEClient_GetNonce(t *testing.T) {
t.Run(name, func(t *testing.T) {
tc := run(t)
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce)
render.JSONStatus(w, tc.r1, tc.rc1)
render.JSONStatus(w, r, tc.r1, tc.rc1)
})
if nonce, err := ac.GetNonce(); err != nil {
@ -310,18 +310,18 @@ func TestACMEClient_post(t *testing.T) {
tc := run(t)
i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 {
render.JSONStatus(w, tc.r1, tc.rc1)
render.JSONStatus(w, r, tc.r1, tc.rc1)
i++
return
}
// validate jws request protected headers and body
body, err := io.ReadAll(req.Body)
body, err := io.ReadAll(r.Body)
assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err)
@ -338,7 +338,7 @@ func TestACMEClient_post(t *testing.T) {
assert.Equals(t, hdr.KeyID, ac.kid)
}
render.JSONStatus(w, tc.r2, tc.rc2)
render.JSONStatus(w, r, tc.r2, tc.rc2)
})
if resp, err := tc.client.post(tc.payload, url, tc.ops...); err != nil {
@ -450,18 +450,18 @@ func TestACMEClient_NewOrder(t *testing.T) {
tc := run(t)
i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 {
render.JSONStatus(w, tc.r1, tc.rc1)
render.JSONStatus(w, r, tc.r1, tc.rc1)
i++
return
}
// validate jws request protected headers and body
body, err := io.ReadAll(req.Body)
body, err := io.ReadAll(r.Body)
assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err)
@ -477,7 +477,7 @@ func TestACMEClient_NewOrder(t *testing.T) {
assert.FatalError(t, err)
assert.Equals(t, payload, norb)
render.JSONStatus(w, tc.r2, tc.rc2)
render.JSONStatus(w, r, tc.r2, tc.rc2)
})
if res, err := ac.NewOrder(norb); err != nil {
@ -572,18 +572,18 @@ func TestACMEClient_GetOrder(t *testing.T) {
tc := run(t)
i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 {
render.JSONStatus(w, tc.r1, tc.rc1)
render.JSONStatus(w, r, tc.r1, tc.rc1)
i++
return
}
// validate jws request protected headers and body
body, err := io.ReadAll(req.Body)
body, err := io.ReadAll(r.Body)
assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err)
@ -599,7 +599,7 @@ func TestACMEClient_GetOrder(t *testing.T) {
assert.FatalError(t, err)
assert.Equals(t, len(payload), 0)
render.JSONStatus(w, tc.r2, tc.rc2)
render.JSONStatus(w, r, tc.r2, tc.rc2)
})
if res, err := ac.GetOrder(url); err != nil {
@ -694,18 +694,18 @@ func TestACMEClient_GetAuthz(t *testing.T) {
tc := run(t)
i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 {
render.JSONStatus(w, tc.r1, tc.rc1)
render.JSONStatus(w, r, tc.r1, tc.rc1)
i++
return
}
// validate jws request protected headers and body
body, err := io.ReadAll(req.Body)
body, err := io.ReadAll(r.Body)
assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err)
@ -721,7 +721,7 @@ func TestACMEClient_GetAuthz(t *testing.T) {
assert.FatalError(t, err)
assert.Equals(t, len(payload), 0)
render.JSONStatus(w, tc.r2, tc.rc2)
render.JSONStatus(w, r, tc.r2, tc.rc2)
})
if res, err := ac.GetAuthz(url); err != nil {
@ -816,18 +816,18 @@ func TestACMEClient_GetChallenge(t *testing.T) {
tc := run(t)
i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 {
render.JSONStatus(w, tc.r1, tc.rc1)
render.JSONStatus(w, r, tc.r1, tc.rc1)
i++
return
}
// validate jws request protected headers and body
body, err := io.ReadAll(req.Body)
body, err := io.ReadAll(r.Body)
assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err)
@ -844,7 +844,7 @@ func TestACMEClient_GetChallenge(t *testing.T) {
assert.Equals(t, len(payload), 0)
render.JSONStatus(w, tc.r2, tc.rc2)
render.JSONStatus(w, r, tc.r2, tc.rc2)
})
if res, err := ac.GetChallenge(url); err != nil {
@ -939,18 +939,18 @@ func TestACMEClient_ValidateChallenge(t *testing.T) {
tc := run(t)
i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 {
render.JSONStatus(w, tc.r1, tc.rc1)
render.JSONStatus(w, r, tc.r1, tc.rc1)
i++
return
}
// validate jws request protected headers and body
body, err := io.ReadAll(req.Body)
body, err := io.ReadAll(r.Body)
assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err)
@ -967,7 +967,7 @@ func TestACMEClient_ValidateChallenge(t *testing.T) {
assert.Equals(t, payload, []byte("{}"))
render.JSONStatus(w, tc.r2, tc.rc2)
render.JSONStatus(w, r, tc.r2, tc.rc2)
})
if err := ac.ValidateChallenge(url); err != nil {
@ -983,22 +983,22 @@ func TestACMEClient_ValidateWithPayload(t *testing.T) {
key, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
t.Log(req.RequestURI)
t.Log(r.RequestURI)
w.Header().Set("Replay-Nonce", "nonce")
switch req.RequestURI {
switch r.RequestURI {
case "/nonce":
render.JSONStatus(w, []byte{}, 200)
render.JSONStatus(w, r, []byte{}, 200)
return
case "/fail-nonce":
render.JSONStatus(w, acme.NewError(acme.ErrorMalformedType, "malformed request"), 400)
render.JSONStatus(w, r, acme.NewError(acme.ErrorMalformedType, "malformed request"), 400)
return
}
// validate jws request protected headers and body
body, err := io.ReadAll(req.Body)
body, err := io.ReadAll(r.Body)
assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body))
@ -1015,15 +1015,15 @@ func TestACMEClient_ValidateWithPayload(t *testing.T) {
assert.FatalError(t, err)
assert.Equals(t, payload, []byte("the-payload"))
switch req.RequestURI {
switch r.RequestURI {
case "/ok":
render.JSONStatus(w, acme.Challenge{
render.JSONStatus(w, r, acme.Challenge{
Type: "device-attestation-01",
Status: "valid",
Token: "foo",
}, 200)
case "/fail":
render.JSONStatus(w, acme.NewError(acme.ErrorMalformedType, "malformed request"), 400)
render.JSONStatus(w, r, acme.NewError(acme.ErrorMalformedType, "malformed request"), 400)
}
}))
defer srv.Close()
@ -1160,18 +1160,18 @@ func TestACMEClient_FinalizeOrder(t *testing.T) {
tc := run(t)
i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 {
render.JSONStatus(w, tc.r1, tc.rc1)
render.JSONStatus(w, r, tc.r1, tc.rc1)
i++
return
}
// validate jws request protected headers and body
body, err := io.ReadAll(req.Body)
body, err := io.ReadAll(r.Body)
assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err)
@ -1187,7 +1187,7 @@ func TestACMEClient_FinalizeOrder(t *testing.T) {
assert.FatalError(t, err)
assert.Equals(t, payload, frb)
render.JSONStatus(w, tc.r2, tc.rc2)
render.JSONStatus(w, r, tc.r2, tc.rc2)
})
if err := ac.FinalizeOrder(url, csr); err != nil {
@ -1289,18 +1289,18 @@ func TestACMEClient_GetAccountOrders(t *testing.T) {
tc := run(t)
i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 {
render.JSONStatus(w, tc.r1, tc.rc1)
render.JSONStatus(w, r, tc.r1, tc.rc1)
i++
return
}
// validate jws request protected headers and body
body, err := io.ReadAll(req.Body)
body, err := io.ReadAll(r.Body)
assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err)
@ -1316,7 +1316,7 @@ func TestACMEClient_GetAccountOrders(t *testing.T) {
assert.FatalError(t, err)
assert.Equals(t, len(payload), 0)
render.JSONStatus(w, tc.r2, tc.rc2)
render.JSONStatus(w, r, tc.r2, tc.rc2)
})
if res, err := tc.client.GetAccountOrders(); err != nil {
@ -1420,18 +1420,18 @@ func TestACMEClient_GetCertificate(t *testing.T) {
tc := run(t)
i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 {
render.JSONStatus(w, tc.r1, tc.rc1)
render.JSONStatus(w, r, tc.r1, tc.rc1)
i++
return
}
// validate jws request protected headers and body
body, err := io.ReadAll(req.Body)
body, err := io.ReadAll(r.Body)
assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err)
@ -1450,7 +1450,7 @@ func TestACMEClient_GetCertificate(t *testing.T) {
if tc.certBytes != nil {
w.Write(tc.certBytes)
} else {
render.JSONStatus(w, tc.r2, tc.rc2)
render.JSONStatus(w, r, tc.r2, tc.rc2)
}
})

@ -204,7 +204,7 @@ func (o *adminOptions) apply(opts []AdminOption) (err error) {
func (o *adminOptions) rawQuery() string {
v := url.Values{}
if len(o.cursor) > 0 {
if o.cursor != "" {
v.Set("cursor", o.cursor)
}
if o.limit > 0 {

@ -87,7 +87,7 @@ func startCAServer(configFile string) (*CA, string, error) {
func mTLSMiddleware(next http.Handler, nonAuthenticatedPaths ...string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/version" {
render.JSON(w, api.VersionResponse{
render.JSON(w, r, api.VersionResponse{
Version: "test",
RequireClientAuthentication: true,
})
@ -102,7 +102,7 @@ func mTLSMiddleware(next http.Handler, nonAuthenticatedPaths ...string) http.Han
}
isMTLS := r.TLS != nil && len(r.TLS.PeerCertificates) > 0
if !isMTLS {
render.Error(w, errs.Unauthorized("missing peer certificate"))
render.Error(w, r, errs.Unauthorized("missing peer certificate"))
} else {
next.ServeHTTP(w, r)
}
@ -412,7 +412,7 @@ func TestBootstrapClientServerRotation(t *testing.T) {
//nolint:gosec // insecure test server
server, err := BootstrapServer(context.Background(), token, &http.Server{
Addr: ":0",
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
}),
}, RequireAndVerifyClientCert())
@ -531,7 +531,7 @@ func TestBootstrapClientServerFederation(t *testing.T) {
//nolint:gosec // insecure test server
server, err := BootstrapServer(context.Background(), token, &http.Server{
Addr: ":0",
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
}),
}, RequireAndVerifyClientCert(), AddFederationToClientCAs())

@ -29,8 +29,8 @@ import (
"github.com/smallstep/certificates/cas/apiv1"
"github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/internal/metrix"
"github.com/smallstep/certificates/internal/requestid"
"github.com/smallstep/certificates/logging"
"github.com/smallstep/certificates/middleware/requestid"
"github.com/smallstep/certificates/monitoring"
"github.com/smallstep/certificates/scep"
scepAPI "github.com/smallstep/certificates/scep/api"
@ -476,6 +476,20 @@ func (ca *CA) Run() error {
// wait till error occurs; ensures the servers keep listening
err := <-errs
// if the error is not the usual HTTP server closed error, it is
// highly likely that an error occurred when starting one of the
// CA servers, possibly because of a port already being in use or
// some part of the configuration not being correct. This case is
// handled by stopping the CA in its entirety.
if !errors.Is(err, http.ErrServerClosed) {
log.Println("shutting down due to startup error ...")
if stopErr := ca.Stop(); stopErr != nil {
err = fmt.Errorf("failed stopping CA after error occurred: %w: %w", err, stopErr)
} else {
err = fmt.Errorf("stopped CA after error occurred: %w", err)
}
}
wg.Wait()
return err
@ -664,7 +678,7 @@ func (ca *CA) shouldServeSCEPEndpoints() bool {
//nolint:unused // useful for debugging
func dumpRoutes(mux chi.Routes) {
// helpful routine for logging all routes
walkFunc := func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
walkFunc := func(method string, route string, _ http.Handler, _ ...func(http.Handler) http.Handler) error {
fmt.Printf("%s %s\n", method, route)
return nil
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save