Compare commits

...

350 commits

Author SHA1 Message Date
b13ba41053 [#2] Add NNS context
Some checks failed
Dependabot auto-merge / dependabot (pull_request) Has been skipped
Add Issues and PRs to Triage / triage (pull_request) Failing after 0s
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
2023-08-03 15:46:59 +03:00
f4dfed3bf3 [#1] Add NNS Challenge support
All checks were successful
Dependabot auto-merge / dependabot (pull_request) Has been skipped
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
2023-07-29 14:18:31 +03:00
Josh Drake
a1350b14fb
Merge pull request #1489 from smallstep/josh/authorization-principal-in-webhook
Include authorization principal in provisioner webhooks.
2023-07-24 21:22:46 -05:00
Mariano Cano
c9df65ebae
Merge pull request #1490 from smallstep/dry-run-migration
Add option to dry-run the migration
2023-07-24 16:39:39 -07:00
Mariano Cano
d9d7c52997
Add option to dry-run the migration
This commit adds an option that runs the migration on a virtual database
that doesn't do anything. This option can be used to see how many rows
there are.
2023-07-24 16:35:22 -07:00
Josh Drake
ff424fa944
Fix tests. 2023-07-24 15:27:49 -05:00
github-actions[bot]
7282245e88
Merge pull request #1488 from smallstep/dependabot/go_modules/go.step.sm/linkedca-0.20.0
Bump go.step.sm/linkedca from 0.19.1 to 0.20.0
2023-07-24 18:21:34 +02:00
github-actions[bot]
9a7582d1d3
Merge pull request #1487 from smallstep/dependabot/go_modules/google.golang.org/api-0.132.0
Bump google.golang.org/api from 0.131.0 to 0.132.0
2023-07-24 18:20:32 +02:00
dependabot[bot]
7796ad8f90
Bump go.step.sm/linkedca from 0.19.1 to 0.20.0
Bumps [go.step.sm/linkedca](https://github.com/smallstep/linkedca) from 0.19.1 to 0.20.0.
- [Release notes](https://github.com/smallstep/linkedca/releases)
- [Commits](https://github.com/smallstep/linkedca/compare/v0.19.1...v0.20.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 15:30:23 +00:00
dependabot[bot]
2d666cfc4f
Bump google.golang.org/api from 0.131.0 to 0.132.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.131.0 to 0.132.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.131.0...v0.132.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>
2023-07-24 15:30:12 +00:00
Josh Drake
904f416d20
Include authorization principal in provisioner webhooks. 2023-07-24 00:30:05 -05:00
Mariano Cano
d89c3a942e
Merge pull request #1486 from smallstep/migrate-admindb
Add to the migration script the admin tables
2023-07-20 20:55:35 -07:00
Mariano Cano
aa30c2c73c
Add to the migration script the admin tables 2023-07-20 18:07:28 -07:00
Mariano Cano
31533c4a15
Merge pull request #1485 from smallstep/webhooks-x5c
Send X5C leaf certificate to webhooks
2023-07-20 14:02:59 -07:00
Mariano Cano
5bfe96d8c7
Send X5C leaf certificate to webhooks
This commit adds a new property that will be sent to authorizing and
enriching webhooks when signing certificates using the X5C provisioner.
2023-07-20 13:03:45 -07:00
Mariano Cano
d604a900ed
Merge pull request #1482 from smallstep/fix-reload-tests
Wait for Accept in TestBootstrapClientServerRotation
2023-07-19 15:03:52 -07:00
Mariano Cano
0c3a1aea38
Wait for Accept in TestBootstrapClientServerRotation
The TestBootstrapClientServerRotation often fails because the reload
returns once the Server loop gets the new listener, but the server
hasn't really started yet. This commit makes the test pass, adding a
small sleep after the reload.

A proper fix might require a wrapper over the listener and an ACK
callback on a sync.Once on a custom Accept.
2023-07-19 14:56:09 -07:00
Mariano Cano
cbc46d11e5
Merge pull request #1477 from smallstep/badger-migration
Add tool to migrate data from badger to mysql or postgresql
2023-07-18 14:36:06 -07:00
Mariano Cano
1755c8d60f
Fix typo in comment 2023-07-18 14:21:55 -07:00
Mariano Cano
f7da9a6f30
Allow to resume badger migration using a given key 2023-07-18 13:11:19 -07:00
Mariano Cano
f7c33d0878
Fix typos in badger migration script 2023-07-18 10:27:36 -07:00
Mariano Cano
7bca0c2349
Add tool to migrate data from badger to mysql or postgresql 2023-07-17 17:40:43 -07:00
Mariano Cano
90bac46a00
Merge pull request #1476 from smallstep/fix-1463
Upgrade go.step.sm/crypto with yubikey fix
2023-07-17 09:53:58 -07:00
Mariano Cano
9edf43b188
Upgrade go.step.sm/crypto with yubikey fix
This commit upgrades the go.step.sm/crypto with a version that includes
a mutex on YubiKey sign and decrypt operations.

Fixes #1463
2023-07-17 09:45:40 -07:00
github-actions[bot]
f998b19bb3
Merge pull request #1474 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.32.3
Bump go.step.sm/crypto from 0.32.2 to 0.32.3
2023-07-17 18:35:23 +02:00
github-actions[bot]
41ff437a6b
Merge pull request #1475 from smallstep/dependabot/go_modules/google.golang.org/api-0.131.0
Bump google.golang.org/api from 0.130.0 to 0.131.0
2023-07-17 18:25:38 +02:00
dependabot[bot]
d1607e460d
Bump google.golang.org/api from 0.130.0 to 0.131.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.130.0 to 0.131.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.130.0...v0.131.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>
2023-07-17 15:19:47 +00:00
dependabot[bot]
b9a3031b84
Bump go.step.sm/crypto from 0.32.2 to 0.32.3
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.32.2 to 0.32.3.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.32.2...v0.32.3)

---
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>
2023-07-17 15:19:33 +00:00
Carl Tashian
4059184b43
Merge pull request #1446 from smallstep/carl/check-cgo-deps
Address Makefile issues around the cgo build
2023-07-13 16:07:00 -07:00
Herman Slatman
0607027412
Merge branch 'master' into carl/check-cgo-deps 2023-07-11 11:26:07 +02:00
Herman Slatman
d39a28535d
Merge pull request #1462 from testwill/ioutil
chore: log error
2023-07-11 11:21:58 +02:00
github-actions[bot]
a6c6df9283
Merge pull request #1469 from smallstep/dependabot/go_modules/github.com/googleapis/gax-go/v2-2.12.0
Bump github.com/googleapis/gax-go/v2 from 2.11.0 to 2.12.0
2023-07-11 11:21:18 +02:00
dependabot[bot]
a6dd12675c
Bump github.com/googleapis/gax-go/v2 from 2.11.0 to 2.12.0
Bumps [github.com/googleapis/gax-go/v2](https://github.com/googleapis/gax-go) from 2.11.0 to 2.12.0.
- [Release notes](https://github.com/googleapis/gax-go/releases)
- [Commits](https://github.com/googleapis/gax-go/compare/v2.11.0...v2.12.0)

---
updated-dependencies:
- dependency-name: github.com/googleapis/gax-go/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-11 09:15:38 +00:00
github-actions[bot]
e1db416c7d
Merge pull request #1470 from smallstep/dependabot/go_modules/google.golang.org/api-0.130.0
Bump google.golang.org/api from 0.129.0 to 0.130.0
2023-07-11 10:52:33 +02:00
dependabot[bot]
2b3bf88001
Bump google.golang.org/api from 0.129.0 to 0.130.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.129.0 to 0.130.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.129.0...v0.130.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>
2023-07-11 08:06:22 +00:00
github-actions[bot]
df20d8fcb4
Merge pull request #1468 from smallstep/dependabot/go_modules/google.golang.org/grpc-1.56.2
Bump google.golang.org/grpc from 1.56.1 to 1.56.2
2023-07-11 10:05:10 +02:00
dependabot[bot]
49d1ca0a49
Bump google.golang.org/grpc from 1.56.1 to 1.56.2
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.56.1 to 1.56.2.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.56.1...v1.56.2)

---
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>
2023-07-10 17:23:27 +00:00
github-actions[bot]
0723987573
Merge pull request #1466 from smallstep/dependabot/go_modules/golang.org/x/net-0.12.0
Bump golang.org/x/net from 0.11.0 to 0.12.0
2023-07-10 19:21:59 +02:00
dependabot[bot]
ef0cd093e3
Bump golang.org/x/net from 0.11.0 to 0.12.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.11.0 to 0.12.0.
- [Commits](https://github.com/golang/net/compare/v0.11.0...v0.12.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>
2023-07-10 16:55:49 +00:00
Herman Slatman
10b7280e17
Merge pull request #1464 from smallstep/herman/fix-tpm-simulator-tests
Fix TPM simulator initialization for tests
2023-07-10 18:51:15 +02:00
Herman Slatman
a5801b3c74
Fix TPM simulator initialization for tests 2023-07-10 13:07:58 +02:00
Carl Tashian
18bc0f333b
Update README.md
Co-authored-by: Herman Slatman <hslatman@users.noreply.github.com>
2023-07-08 02:50:05 -07:00
Carl Tashian
44f3b97e61
Update Makefile
Co-authored-by: Herman Slatman <hslatman@users.noreply.github.com>
2023-07-08 02:49:58 -07:00
guoguangwu
4c70abcd62 chore: log error 2023-07-08 17:20:18 +08:00
github-actions[bot]
9a60734504
Merge pull request #1458 from smallstep/dependabot/go_modules/google.golang.org/api-0.129.0
Bump google.golang.org/api from 0.128.0 to 0.129.0
2023-07-04 11:03:35 +02:00
github-actions[bot]
dc03edbc27
Merge pull request #1460 from smallstep/dependabot/go_modules/github.com/newrelic/go-agent/v3-3.23.1
Bump github.com/newrelic/go-agent/v3 from 3.23.0 to 3.23.1
2023-07-03 20:18:15 +02:00
dependabot[bot]
9cb2c4365d
Bump google.golang.org/api from 0.128.0 to 0.129.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.128.0 to 0.129.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.128.0...v0.129.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>
2023-07-03 17:01:01 +00:00
github-actions[bot]
03011660a8
Merge pull request #1459 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.32.2
Bump go.step.sm/crypto from 0.32.1 to 0.32.2
2023-07-03 18:58:57 +02:00
github-actions[bot]
701a0ea3f4
Merge pull request #1456 from smallstep/dependabot/go_modules/cloud.google.com/go/longrunning-0.5.1
Bump cloud.google.com/go/longrunning from 0.5.0 to 0.5.1
2023-07-03 18:58:39 +02:00
github-actions[bot]
62212d23bf
Merge pull request #1457 from smallstep/dependabot/go_modules/google.golang.org/grpc-1.56.1
Bump google.golang.org/grpc from 1.56.0 to 1.56.1
2023-07-03 18:58:18 +02:00
dependabot[bot]
eae423ed14
Bump github.com/newrelic/go-agent/v3 from 3.23.0 to 3.23.1
Bumps [github.com/newrelic/go-agent/v3](https://github.com/newrelic/go-agent) from 3.23.0 to 3.23.1.
- [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.23.0...v3.23.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-03 15:04:59 +00:00
dependabot[bot]
d59b16cb2a
Bump go.step.sm/crypto from 0.32.1 to 0.32.2
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.32.1 to 0.32.2.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.32.1...v0.32.2)

---
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>
2023-07-03 15:04:49 +00:00
dependabot[bot]
f7f66ad3ed
Bump google.golang.org/grpc from 1.56.0 to 1.56.1
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.56.0 to 1.56.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.56.0...v1.56.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>
2023-07-03 15:04:27 +00:00
dependabot[bot]
898bd6a0f4
Bump cloud.google.com/go/longrunning from 0.5.0 to 0.5.1
Bumps [cloud.google.com/go/longrunning](https://github.com/googleapis/google-cloud-go) from 0.5.0 to 0.5.1.
- [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/v0.5.0...dataflow/v0.5.1)

---
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>
2023-07-03 15:04:17 +00:00
github-actions[bot]
4f21d0c256
Merge pull request #1453 from smallstep/dependabot/go_modules/github.com/newrelic/go-agent/v3-3.23.0
Bump github.com/newrelic/go-agent/v3 from 3.22.1 to 3.23.0
2023-06-26 18:55:58 +02:00
dependabot[bot]
e5c46d4264
Bump github.com/newrelic/go-agent/v3 from 3.22.1 to 3.23.0
Bumps [github.com/newrelic/go-agent/v3](https://github.com/newrelic/go-agent) from 3.22.1 to 3.23.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.22.1...v3.23.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>
2023-06-26 16:44:57 +00:00
github-actions[bot]
4ce88e579e
Merge pull request #1451 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.32.1
Bump go.step.sm/crypto from 0.32.0 to 0.32.1
2023-06-26 18:33:27 +02:00
dependabot[bot]
48855080ff
Bump go.step.sm/crypto from 0.32.0 to 0.32.1
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.32.0 to 0.32.1.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.32.0...v0.32.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>
2023-06-26 16:28:06 +00:00
github-actions[bot]
b029359e78
Merge pull request #1450 from smallstep/dependabot/go_modules/google.golang.org/protobuf-1.31.0
Bump google.golang.org/protobuf from 1.30.0 to 1.31.0
2023-06-26 18:27:21 +02:00
github-actions[bot]
a577ee9699
Merge pull request #1449 from smallstep/dependabot/go_modules/cloud.google.com/go/security-1.15.1
Bump cloud.google.com/go/security from 1.15.0 to 1.15.1
2023-06-26 18:27:00 +02:00
dependabot[bot]
fb7b299110
Bump google.golang.org/protobuf from 1.30.0 to 1.31.0
Bumps google.golang.org/protobuf from 1.30.0 to 1.31.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-26 16:00:32 +00:00
dependabot[bot]
ce89c09031
Bump cloud.google.com/go/security from 1.15.0 to 1.15.1
Bumps [cloud.google.com/go/security](https://github.com/googleapis/google-cloud-go) from 1.15.0 to 1.15.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.15.0...speech/v1.15.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>
2023-06-26 16:00:22 +00:00
Carl Tashian
e38e632dca
Post-review fixes 2023-06-22 15:39:25 -07:00
Carl Tashian
f8b318bb90
Post-review fixes 2023-06-22 15:35:13 -07:00
Carl Tashian
73cb04318a
Trying a different approach 2023-06-21 14:44:16 -07:00
Carl Tashian
b2b8b48949
Trying a different approach 2023-06-21 14:34:29 -07:00
Carl Tashian
de52aee9b1
Trying a different approach 2023-06-21 14:16:29 -07:00
Carl Tashian
5e68a6d49a
Check for gcc and pkg-config before building with cgo enabled 2023-06-21 13:09:58 -07:00
github-actions[bot]
1df9419212
Merge pull request #1440 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.32.0
Bump go.step.sm/crypto from 0.31.2 to 0.32.0
2023-06-20 11:20:36 +02:00
github-actions[bot]
f7d1376327
Merge pull request #1441 from smallstep/dependabot/go_modules/google.golang.org/api-0.128.0
Bump google.golang.org/api from 0.126.0 to 0.128.0
2023-06-20 11:15:56 +02:00
dependabot[bot]
6c4825b149
Bump google.golang.org/api from 0.126.0 to 0.128.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.126.0 to 0.128.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.126.0...v0.128.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>
2023-06-20 08:50:45 +00:00
github-actions[bot]
648e60ac1a
Merge pull request #1442 from smallstep/dependabot/go_modules/google.golang.org/grpc-1.56.0
Bump google.golang.org/grpc from 1.55.0 to 1.56.0
2023-06-20 10:49:38 +02:00
dependabot[bot]
6aa00b3c89
Bump google.golang.org/grpc from 1.55.0 to 1.56.0
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.55.0 to 1.56.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.55.0...v1.56.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>
2023-06-20 08:40:46 +00:00
dependabot[bot]
81228b481f
Bump go.step.sm/crypto from 0.31.2 to 0.32.0
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.31.2 to 0.32.0.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.31.2...v0.32.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>
2023-06-20 08:40:32 +00:00
github-actions[bot]
f4cee836bf
Merge pull request #1443 from smallstep/dependabot/go_modules/golang.org/x/net-0.11.0
Bump golang.org/x/net from 0.10.0 to 0.11.0
2023-06-20 10:39:41 +02:00
github-actions[bot]
83fc176a87
Merge pull request #1444 from smallstep/dependabot/go_modules/golang.org/x/crypto-0.10.0
Bump golang.org/x/crypto from 0.9.0 to 0.10.0
2023-06-20 10:39:23 +02:00
dependabot[bot]
c74bab5161
Bump golang.org/x/crypto from 0.9.0 to 0.10.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.9.0 to 0.10.0.
- [Commits](https://github.com/golang/crypto/compare/v0.9.0...v0.10.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>
2023-06-19 16:00:10 +00:00
dependabot[bot]
d78c9f831b
Bump golang.org/x/net from 0.10.0 to 0.11.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.10.0 to 0.11.0.
- [Commits](https://github.com/golang/net/compare/v0.10.0...v0.11.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>
2023-06-19 16:00:05 +00:00
Carl Tashian
05d6f81ee9
Merge pull request #1435 from smallstep/unversioned-releases
Add unversioned filenames to GitHub assets
2023-06-14 18:29:54 -07:00
Carl Tashian
eeb912e025
Add unversioned filenames to GitHub assets 2023-06-14 17:18:28 -07:00
Max
67d32685c7
[action] updated goCI workflow API (#1429) 2023-06-13 11:13:42 -07:00
github-actions[bot]
56a2a17ff8
Merge pull request #1432 from smallstep/dependabot/go_modules/github.com/newrelic/go-agent/v3-3.22.1
Bump github.com/newrelic/go-agent/v3 from 3.21.1 to 3.22.1
2023-06-13 06:44:04 +02:00
github-actions[bot]
a3301bf65b
Merge pull request #1433 from smallstep/dependabot/go_modules/cloud.google.com/go/security-1.15.0
Bump cloud.google.com/go/security from 1.14.1 to 1.15.0
2023-06-13 06:41:26 +02:00
dependabot[bot]
e400294238
Bump github.com/newrelic/go-agent/v3 from 3.21.1 to 3.22.1
Bumps [github.com/newrelic/go-agent/v3](https://github.com/newrelic/go-agent) from 3.21.1 to 3.22.1.
- [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.21.1...v3.22.1)

---
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>
2023-06-13 03:01:08 +00:00
github-actions[bot]
c02d4adcca
Merge pull request #1431 from smallstep/dependabot/go_modules/google.golang.org/api-0.126.0
Bump google.golang.org/api from 0.125.0 to 0.126.0
2023-06-12 20:00:15 -07:00
github-actions[bot]
02d4657ee6
Merge pull request #1434 from smallstep/dependabot/go_modules/github.com/urfave/cli-1.22.14
Bump github.com/urfave/cli from 1.22.13 to 1.22.14
2023-06-12 19:59:26 -07:00
github-actions[bot]
4bd89a8e5f
Merge pull request #1430 from smallstep/dependabot/go_modules/github.com/sirupsen/logrus-1.9.3
Bump github.com/sirupsen/logrus from 1.9.2 to 1.9.3
2023-06-12 19:58:43 -07:00
dependabot[bot]
d97b254a1d
Bump github.com/urfave/cli from 1.22.13 to 1.22.14
Bumps [github.com/urfave/cli](https://github.com/urfave/cli) from 1.22.13 to 1.22.14.
- [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.13...v1.22.14)

---
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>
2023-06-12 16:02:20 +00:00
dependabot[bot]
f3555ee0e7
Bump cloud.google.com/go/security from 1.14.1 to 1.15.0
Bumps [cloud.google.com/go/security](https://github.com/googleapis/google-cloud-go) from 1.14.1 to 1.15.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/speech/v1.14.1...video/v1.15.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>
2023-06-12 16:02:15 +00:00
dependabot[bot]
3d29316d0b
Bump google.golang.org/api from 0.125.0 to 0.126.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.125.0 to 0.126.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.125.0...v0.126.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>
2023-06-12 16:01:05 +00:00
dependabot[bot]
7ac0e4d21d
Bump github.com/sirupsen/logrus from 1.9.2 to 1.9.3
Bumps [github.com/sirupsen/logrus](https://github.com/sirupsen/logrus) from 1.9.2 to 1.9.3.
- [Release notes](https://github.com/sirupsen/logrus/releases)
- [Changelog](https://github.com/sirupsen/logrus/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sirupsen/logrus/compare/v1.9.2...v1.9.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-12 16:00:44 +00:00
Max
53c7774d3c
[action] use common goreleaser workflow (#1427) 2023-06-09 16:28:44 -07:00
Max
825c5dd754
Update changelog for ACME kid / provisioner name change (#1426) 2023-06-09 12:03:00 -07:00
Mariano Cano
ccb248394b
Merge pull request #1424 from spieglt/vault-namespaces
Add namespace field to VaultCAS JSON config
2023-06-07 14:22:32 -07:00
Theron
9d7dff6995 Add namespace field to VaultCAS JSON config 2023-06-07 13:25:47 -05:00
Max
7731edd816
Store and verify Acme account location (#1386)
* Store and verify account location on acme requests

Co-authored-by: Herman Slatman <hslatman@users.noreply.github.com>
Co-authored-by: Mariano Cano <mariano@smallstep.com>
2023-06-06 23:37:51 -07:00
github-actions[bot]
cbbc54e980
Merge pull request #1417 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.31.2
Bump go.step.sm/crypto from 0.31.0 to 0.31.2
2023-06-05 14:26:25 -07:00
dependabot[bot]
e5bd90918d
Bump go.step.sm/crypto from 0.31.0 to 0.31.2
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.31.0 to 0.31.2.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.31.0...v0.31.2)

---
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>
2023-06-05 21:13:05 +00:00
github-actions[bot]
20c05ed592
Merge pull request #1419 from smallstep/dependabot/go_modules/cloud.google.com/go/longrunning-0.5.0
Bump cloud.google.com/go/longrunning from 0.4.2 to 0.5.0
2023-06-05 14:11:43 -07:00
github-actions[bot]
1fe8b70190
Merge pull request #1416 from smallstep/dependabot/go_modules/github.com/stretchr/testify-1.8.4
Bump github.com/stretchr/testify from 1.8.3 to 1.8.4
2023-06-05 14:10:56 -07:00
dependabot[bot]
561328de05
Bump cloud.google.com/go/longrunning from 0.4.2 to 0.5.0
Bumps [cloud.google.com/go/longrunning](https://github.com/googleapis/google-cloud-go) from 0.4.2 to 0.5.0.
- [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.4.2...v0.5.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-05 15:59:56 +00:00
dependabot[bot]
8e22402190
Bump github.com/stretchr/testify from 1.8.3 to 1.8.4
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.3 to 1.8.4.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.3...v1.8.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-05 15:58:59 +00:00
github-actions[bot]
ff40459035
Merge pull request #1409 from smallstep/dependabot/go_modules/github.com/hashicorp/vault/api/auth/kubernetes-0.4.1
Bump github.com/hashicorp/vault/api/auth/kubernetes from 0.4.0 to 0.4.1
2023-05-30 13:35:15 -07:00
dependabot[bot]
f38ea204d1
Bump github.com/hashicorp/vault/api/auth/kubernetes from 0.4.0 to 0.4.1
Bumps [github.com/hashicorp/vault/api/auth/kubernetes](https://github.com/hashicorp/vault) from 0.4.0 to 0.4.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/v0.4.0...v0.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-30 08:10:01 +00:00
github-actions[bot]
5d069f6b94
Merge pull request #1411 from smallstep/dependabot/go_modules/github.com/googleapis/gax-go/v2-2.9.1
Bump github.com/googleapis/gax-go/v2 from 2.8.0 to 2.9.1
2023-05-30 10:08:51 +02:00
dependabot[bot]
bee6bf7069
Bump github.com/googleapis/gax-go/v2 from 2.8.0 to 2.9.1
Bumps [github.com/googleapis/gax-go/v2](https://github.com/googleapis/gax-go) from 2.8.0 to 2.9.1.
- [Release notes](https://github.com/googleapis/gax-go/releases)
- [Commits](https://github.com/googleapis/gax-go/compare/v2.8.0...v2.9.1)

---
updated-dependencies:
- dependency-name: github.com/googleapis/gax-go/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-30 07:31:17 +00:00
github-actions[bot]
c1be2761bb
Merge pull request #1410 from smallstep/dependabot/go_modules/github.com/hashicorp/vault/api/auth/approle-0.4.1
Bump github.com/hashicorp/vault/api/auth/approle from 0.4.0 to 0.4.1
2023-05-30 09:30:15 +02:00
dependabot[bot]
b4d532fd1f
Bump github.com/hashicorp/vault/api/auth/approle from 0.4.0 to 0.4.1
Bumps [github.com/hashicorp/vault/api/auth/approle](https://github.com/hashicorp/vault) from 0.4.0 to 0.4.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/v0.4.0...v0.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-30 07:24:26 +00:00
github-actions[bot]
ef839a5932
Merge pull request #1413 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.31.0
Bump go.step.sm/crypto from 0.30.0 to 0.31.0
2023-05-30 09:23:20 +02:00
dependabot[bot]
6a9241fb8e
Bump go.step.sm/crypto from 0.30.0 to 0.31.0
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.30.0 to 0.31.0.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.30.0...v0.31.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>
2023-05-29 15:59:05 +00:00
Mariano Cano
96c8cd812a
Merge pull request #1406 from smallstep/fix-1404
Fix linter errors from #1404
2023-05-26 07:13:20 -07:00
Mariano Cano
71fcdf8a0a
Fix linter errors from #1404 2023-05-25 16:55:00 -07:00
Mariano Cano
fffeeec74e
Merge pull request #1404 from rnugmanov/patch-1
add AWS public certificates for me-central-1 and ap-southeast-3
2023-05-25 16:51:27 -07:00
Mariano Cano
1f0a2a8443
Merge pull request #1405 from smallstep/helm-spaces
Fix tabs instead of spaces in helm chart
2023-05-25 14:32:28 -07:00
Mariano Cano
ce4fd3d514
Fix tabs instead of spaces in helm chart 2023-05-25 14:22:56 -07:00
Ruslan Nugmanov
1031324273
add AWS public certificates for me-central-1 and ap-southeast-3
As per https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/verify-signature.html
2023-05-25 13:47:13 +01:00
github-actions[bot]
101120b977
Merge pull request #1398 from smallstep/dependabot/go_modules/google.golang.org/api-0.123.0
Bump google.golang.org/api from 0.122.0 to 0.123.0
2023-05-22 09:15:12 -07:00
github-actions[bot]
a477e41915
Merge pull request #1399 from smallstep/dependabot/go_modules/github.com/sirupsen/logrus-1.9.2
Bump github.com/sirupsen/logrus from 1.9.0 to 1.9.2
2023-05-22 09:14:27 -07:00
github-actions[bot]
ed9a1275cd
Merge pull request #1396 from smallstep/dependabot/go_modules/github.com/stretchr/testify-1.8.3
Bump github.com/stretchr/testify from 1.8.2 to 1.8.3
2023-05-22 09:13:24 -07:00
dependabot[bot]
cdf55a410b
Bump github.com/sirupsen/logrus from 1.9.0 to 1.9.2
Bumps [github.com/sirupsen/logrus](https://github.com/sirupsen/logrus) from 1.9.0 to 1.9.2.
- [Release notes](https://github.com/sirupsen/logrus/releases)
- [Changelog](https://github.com/sirupsen/logrus/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sirupsen/logrus/compare/v1.9.0...v1.9.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-22 16:01:44 +00:00
dependabot[bot]
19896dc04b
Bump google.golang.org/api from 0.122.0 to 0.123.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.122.0 to 0.123.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.122.0...v0.123.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>
2023-05-22 16:01:27 +00:00
dependabot[bot]
9efc47ae42
Bump github.com/stretchr/testify from 1.8.2 to 1.8.3
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.2 to 1.8.3.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.2...v1.8.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-22 16:00:29 +00:00
github-actions[bot]
4f7cdfbaa2
Merge pull request #1391 from smallstep/dependabot/go_modules/google.golang.org/api-0.122.0
Bump google.golang.org/api from 0.121.0 to 0.122.0
2023-05-17 12:00:27 +02:00
dependabot[bot]
ffb6d1c0f2
Bump google.golang.org/api from 0.121.0 to 0.122.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.121.0 to 0.122.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.121.0...v0.122.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>
2023-05-17 09:43:37 +00:00
github-actions[bot]
b4d536db57
Merge pull request #1389 from smallstep/dependabot/go_modules/golang.org/x/crypto-0.9.0
Bump golang.org/x/crypto from 0.8.0 to 0.9.0
2023-05-17 11:42:17 +02:00
github-actions[bot]
57865b399b
Merge pull request #1388 from smallstep/dependabot/go_modules/cloud.google.com/go/longrunning-0.4.2
Bump cloud.google.com/go/longrunning from 0.4.1 to 0.4.2
2023-05-17 11:35:09 +02:00
Carl Tashian
96dcab88ac
Merge pull request #1394 from smallstep/carl/update-shortlinks
Update download URLs
2023-05-16 22:03:22 -07:00
Carl Tashian
5e05d6ec2e
Update download URLs 2023-05-16 19:58:54 -07:00
dependabot[bot]
ca6b7049e5
Bump cloud.google.com/go/longrunning from 0.4.1 to 0.4.2
Bumps [cloud.google.com/go/longrunning](https://github.com/googleapis/google-cloud-go) from 0.4.1 to 0.4.2.
- [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/batch/v0.4.1...longrunning/v0.4.2)

---
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>
2023-05-15 19:07:45 +00:00
github-actions[bot]
2ef45a204f
Merge pull request #1392 from smallstep/dependabot/go_modules/cloud.google.com/go/security-1.14.1
Bump cloud.google.com/go/security from 1.14.0 to 1.14.1
2023-05-15 21:02:55 +02:00
dependabot[bot]
36cb5bf1d4
Bump cloud.google.com/go/security from 1.14.0 to 1.14.1
Bumps [cloud.google.com/go/security](https://github.com/googleapis/google-cloud-go) from 1.14.0 to 1.14.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.14.0...speech/v1.14.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>
2023-05-15 16:01:36 +00:00
dependabot[bot]
f06d22a138
Bump golang.org/x/crypto from 0.8.0 to 0.9.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.8.0 to 0.9.0.
- [Commits](https://github.com/golang/crypto/compare/v0.8.0...v0.9.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>
2023-05-15 16:00:38 +00:00
Herman Slatman
6db3f5f093
Merge pull request #1385 from smallstep/update-changelog-v0.24.2
Update changelog for v0.24.2 release
2023-05-11 14:54:22 +02:00
Herman Slatman
40b69ea95c
Merge pull request #1382 from smallstep/herman/update-crypto-v0.29.4
Depend on our fork of `go-attestation`
2023-05-11 00:06:13 +02:00
Herman Slatman
e71b62e95c
Merge branch 'master' into herman/update-crypto-v0.29.4 2023-05-10 22:28:35 +02:00
Herman Slatman
a49ee2c03d
Add entry for DOCKER_STEPCA_INIT_PASSWORD_FILE 2023-05-10 22:26:12 +02:00
Herman Slatman
b96831ee45
Merge pull request #1384 from francescocapuano/master
Add DOCKER_STEPCA_INIT_PASSWORD_FILE variable for Docker secrets
2023-05-10 22:23:44 +02:00
Max
df13351586
Merge pull request #1381 from smallstep/max/go-1.19
Bump go.mod golang version to 1.19
2023-05-10 10:34:24 -07:00
Herman Slatman
4a60f8f71f
Add UNRELEASED back 2023-05-10 14:39:26 +02:00
francescocapuano
7f54153a1b Add DOCKER_STEPCA_INIT_PASSWORD_FILE variable for docker secrets
Add the management of the DOCKER_STEPCA_INIT_PASSWORD_FILE variable.  over DOCKER_STEPCA_INIT_PASSWORD.
If both are used only DOCKER_STEPCA_INIT_PASSWORD_FILE will be used.
2023-05-10 14:11:41 +02:00
Herman Slatman
e52e79f745
Update changelog for v0.24.2 release 2023-05-10 13:31:31 +02:00
Herman Slatman
8abb511f64
Merge branch 'master' into herman/update-crypto-v0.29.4 2023-05-10 10:44:36 +02:00
max furman
8b256f0351
address linter warning for go 1.19 2023-05-09 23:47:28 -07:00
Mariano Cano
0b832e389d
Merge pull request #1383 from smallstep/azidentity
Upgrade go.step.sm/crypto
2023-05-09 15:59:32 -07:00
Mariano Cano
2b209b94e8
Upgrade go.step.sm/crypto with new version of azidentity 2023-05-09 15:51:36 -07:00
Herman Slatman
0c2b00f6a1
Depend on our fork of go-attestation 2023-05-10 00:38:40 +02:00
Herman Slatman
3c7b247712
Upgrade to go.step.sm/crypto@v0.29.4 2023-05-10 00:35:43 +02:00
Herman Slatman
017c3273ef
Merge pull request #1374 from smallstep/herman/log-ssh-certificate
Log SSH certificates
2023-05-09 17:21:03 +02:00
github-actions[bot]
f93548df40
Merge pull request #1379 from smallstep/dependabot/go_modules/google.golang.org/api-0.121.0
Bump google.golang.org/api from 0.120.0 to 0.121.0
2023-05-08 09:43:27 -07:00
max furman
5735d1d354
Bump go.mod golang version to 1.19 2023-05-08 09:24:29 -07:00
Max
6bf7943a1b
Merge branch 'master' into dependabot/go_modules/google.golang.org/api-0.121.0 2023-05-08 09:22:00 -07:00
github-actions[bot]
f4c6a72967
Merge pull request #1380 from smallstep/dependabot/go_modules/google.golang.org/grpc-1.55.0
Bump google.golang.org/grpc from 1.54.0 to 1.55.0
2023-05-08 18:15:34 +02:00
dependabot[bot]
93426d72a9
Bump google.golang.org/grpc from 1.54.0 to 1.55.0
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.54.0 to 1.55.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.54.0...v1.55.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>
2023-05-08 16:00:38 +00:00
dependabot[bot]
570b10b8e8
Bump google.golang.org/api from 0.120.0 to 0.121.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.120.0 to 0.121.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.120.0...v0.121.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>
2023-05-08 16:00:05 +00:00
Herman Slatman
f17bfdf57d
Reformat the SSH certificate logging output for read- and parsability 2023-05-08 13:46:20 +02:00
Herman Slatman
4c56877d97
Add SSH certificate logging to renew and rekey too 2023-05-05 11:06:01 +02:00
Mariano Cano
1180e33228
Merge pull request #1376 from smallstep/fix-1375
Use vaultcas ttl as a duration string
2023-05-04 20:42:28 -07:00
Mariano Cano
afd5d46a90
Use vaultcas ttl as a duration string
According to docs at thttps://developer.hashicorp.com/vault/api-docs/secret/pki#ttl
the ttl can be sent as a time.Duration string.

Fixes #1375
2023-05-04 18:36:08 -07:00
Panagiotis Siatras
2139121683
optimized render.JSON (#929)
* api/render: render JSON directly to the underlying writer

* also consider json.MarshalerError a panic
2023-05-04 22:16:12 +03:00
Herman Slatman
81140f859c
Fix valid-from and valid-to times 2023-05-04 16:15:03 +02:00
Herman Slatman
39e658b527
Add test for LogSSHCertificate 2023-05-04 15:52:49 +02:00
Herman Slatman
922f702da3
Add logging for SSH certificate issuance 2023-05-04 15:33:06 +02:00
Herman Slatman
ef951f2075
Merge pull request #1204 from smallstep/herman/improve-scep-marshaling
Improve SCEP provisioner marshaling
2023-05-04 11:55:05 +02:00
Herman Slatman
8c53dc9029
Use require.NoError where appropriate in provisioner tests 2023-05-04 11:44:22 +02:00
Herman Slatman
0153ff4377
Remove superfluous GetChallengePassword 2023-05-04 11:43:57 +02:00
Herman Slatman
f9ec62f46c
Merge branch 'master' into herman/improve-scep-marshaling 2023-05-04 10:47:53 +02:00
Herman Slatman
eba93da6d6
Merge pull request #1372 from smallstep/herman/crl-on-http 2023-05-04 07:31:33 +02:00
Panagiotis Siatras
d797941137
do not render CRLs in memory (#1373) 2023-05-03 23:49:26 +03:00
Herman Slatman
5e35aca29c
Use CRLConfig.IsEnabled 2023-05-02 15:17:50 +02:00
Herman Slatman
60a4512abe
Add /crl and /1.0/crl to the insecure HTTP handler 2023-05-02 14:58:32 +02:00
Herman Slatman
cb1dc8055d
Merge pull request #1366 from smallstep/herman/dynamic-scep-webhook
Dynamic SCEP challenge validation using webhooks
2023-05-02 00:53:39 +02:00
Herman Slatman
c73f157ea4
Remove unused error from challenge validation controller creator 2023-05-02 00:52:11 +02:00
Herman Slatman
4f7a4f63f7
Merge branch 'master' into herman/dynamic-scep-webhook 2023-05-02 00:01:03 +02:00
Herman Slatman
4bb88adf63
Move SCEP checks after reload of provisioners in CA initialization 2023-05-01 23:59:48 +02:00
Herman Slatman
e8c1e8719d
Refactor SCEP webhook validation 2023-05-01 22:09:42 +02:00
github-actions[bot]
19d72c9905
Merge pull request #1369 from smallstep/dependabot/go_modules/google.golang.org/api-0.120.0
Bump google.golang.org/api from 0.119.0 to 0.120.0
2023-05-01 09:16:48 -07:00
github-actions[bot]
3a2e60a139
Merge pull request #1370 from smallstep/dependabot/go_modules/github.com/urfave/cli-1.22.13
Bump github.com/urfave/cli from 1.22.12 to 1.22.13
2023-05-01 09:16:11 -07:00
github-actions[bot]
5ea72a2432
Merge pull request #1368 from smallstep/dependabot/go_modules/github.com/newrelic/go-agent/v3-3.21.1
Bump github.com/newrelic/go-agent/v3 from 3.21.0 to 3.21.1
2023-05-01 18:14:44 +02:00
github-actions[bot]
eec7d1ee6a
Merge pull request #1371 from smallstep/dependabot/go_modules/go.step.sm/linkedca-0.19.1
Bump go.step.sm/linkedca from 0.19.0 to 0.19.1
2023-05-01 18:12:17 +02:00
dependabot[bot]
047bb6a826
Bump go.step.sm/linkedca from 0.19.0 to 0.19.1
Bumps [go.step.sm/linkedca](https://github.com/smallstep/linkedca) from 0.19.0 to 0.19.1.
- [Release notes](https://github.com/smallstep/linkedca/releases)
- [Commits](https://github.com/smallstep/linkedca/compare/v0.19.0...v0.19.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-01 16:02:29 +00:00
dependabot[bot]
d19c77795e
Bump github.com/urfave/cli from 1.22.12 to 1.22.13
Bumps [github.com/urfave/cli](https://github.com/urfave/cli) from 1.22.12 to 1.22.13.
- [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.12...v1.22.13)

---
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>
2023-05-01 16:02:14 +00:00
dependabot[bot]
3a50a2fa28
Bump google.golang.org/api from 0.119.0 to 0.120.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.119.0 to 0.120.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.119.0...v0.120.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>
2023-05-01 16:01:57 +00:00
dependabot[bot]
bb33134f8a
Bump github.com/newrelic/go-agent/v3 from 3.21.0 to 3.21.1
Bumps [github.com/newrelic/go-agent/v3](https://github.com/newrelic/go-agent) from 3.21.0 to 3.21.1.
- [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.21.0...v3.21.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-01 16:01:31 +00:00
Herman Slatman
668ff9b515
Cleanup some comments and tests 2023-05-01 11:55:05 +02:00
Herman Slatman
5f0f0f4bcc
Add SCEP webhook validation tests 2023-05-01 11:14:50 +02:00
Herman Slatman
ad4d8e6c68
Add SCEPCHALLENGE as valid webhook type in admin API 2023-04-29 01:40:03 +02:00
Herman Slatman
419478d1e5
Make SCEP webhook validation look better 2023-04-29 01:15:39 +02:00
Herman Slatman
27cdcaf5ee
Integrate the SCEP webhook with the existing webhook logic 2023-04-28 17:15:05 +02:00
Herman Slatman
05f7ab979f
Create basic webhook for SCEP challenge validation 2023-04-28 15:47:22 +02:00
Mariano Cano
1420c762e0
Merge pull request #1362 from smallstep/fix-1358
Upgrades azure-sdk-for-go to the version used in crypto
2023-04-24 15:45:43 -07:00
Mariano Cano
26afd6c932
Upgrades azure-sdk-for-go to the version used in crypto
This PR upgrades package sdk/keyvault/azkeys to v0.10.0, the same
version used in crypto.

This package wasn't upgraded in certificates and for some reason it
causes an authentication error if a client-id/client-secret is used for
authenticating with KeyVault. Managed identities or CLI authentication
works as expected.

Fixes #1358
2023-04-24 15:36:53 -07:00
github-actions[bot]
6bc2164ea1
Merge pull request #1361 from smallstep/dependabot/go_modules/github.com/hashicorp/vault/api-1.9.1
Bump github.com/hashicorp/vault/api from 1.9.0 to 1.9.1
2023-04-24 10:30:37 -07:00
github-actions[bot]
91f51252c5
Merge pull request #1360 from smallstep/dependabot/go_modules/google.golang.org/api-0.119.0
Bump google.golang.org/api from 0.118.0 to 0.119.0
2023-04-24 10:29:36 -07:00
dependabot[bot]
a56b112216
Bump github.com/hashicorp/vault/api from 1.9.0 to 1.9.1
Bumps [github.com/hashicorp/vault/api](https://github.com/hashicorp/vault) from 1.9.0 to 1.9.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.9.0...v1.9.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>
2023-04-24 16:01:08 +00:00
dependabot[bot]
f2fda93cad
Bump google.golang.org/api from 0.118.0 to 0.119.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.118.0 to 0.119.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.118.0...v0.119.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>
2023-04-24 16:00:06 +00:00
github-actions[bot]
4dedbf7678
Merge pull request #1356 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.29.3
Bump go.step.sm/crypto from 0.29.1 to 0.29.3
2023-04-17 09:48:15 -07:00
dependabot[bot]
74414e530b
Bump go.step.sm/crypto from 0.29.1 to 0.29.3
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.29.1 to 0.29.3.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.29.1...v0.29.3)

---
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>
2023-04-17 16:43:02 +00:00
github-actions[bot]
a784038025
Merge pull request #1355 from smallstep/dependabot/go_modules/go.step.sm/cli-utils-0.7.6
Bump go.step.sm/cli-utils from 0.7.5 to 0.7.6
2023-04-17 09:41:31 -07:00
dependabot[bot]
9e198b0e4a
Bump go.step.sm/cli-utils from 0.7.5 to 0.7.6
Bumps [go.step.sm/cli-utils](https://github.com/smallstep/cli-utils) from 0.7.5 to 0.7.6.
- [Release notes](https://github.com/smallstep/cli-utils/releases)
- [Commits](https://github.com/smallstep/cli-utils/compare/v0.7.5...v0.7.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-17 16:30:29 +00:00
github-actions[bot]
631b773257
Merge pull request #1354 from smallstep/dependabot/go_modules/google.golang.org/api-0.118.0
Bump google.golang.org/api from 0.116.0 to 0.118.0
2023-04-17 09:29:26 -07:00
github-actions[bot]
b59a8f0a9f
Merge pull request #1353 from smallstep/dependabot/go_modules/cloud.google.com/go/security-1.14.0
Bump cloud.google.com/go/security from 1.13.0 to 1.14.0
2023-04-17 09:28:18 -07:00
github-actions[bot]
0099ec7244
Merge pull request #1352 from smallstep/dependabot/go_modules/github.com/rs/xid-1.5.0
Bump github.com/rs/xid from 1.4.0 to 1.5.0
2023-04-17 09:27:52 -07:00
dependabot[bot]
0c49d119d5
Bump google.golang.org/api from 0.116.0 to 0.118.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.116.0 to 0.118.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.116.0...v0.118.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>
2023-04-17 15:59:41 +00:00
dependabot[bot]
a7480ebe4f
Bump cloud.google.com/go/security from 1.13.0 to 1.14.0
Bumps [cloud.google.com/go/security](https://github.com/googleapis/google-cloud-go) from 1.13.0 to 1.14.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/asset/v1.13.0...video/v1.14.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>
2023-04-17 15:59:30 +00:00
dependabot[bot]
c2f2c7176c
Bump github.com/rs/xid from 1.4.0 to 1.5.0
Bumps [github.com/rs/xid](https://github.com/rs/xid) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/rs/xid/releases)
- [Commits](https://github.com/rs/xid/compare/v1.4.0...v1.5.0)

---
updated-dependencies:
- dependency-name: github.com/rs/xid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-17 15:59:21 +00:00
Max
7ad81a6c54
Merge pull request #1348 from smallstep/max/release-error
[action] Fix docker image name in release workflow
2023-04-12 18:35:43 -07:00
Max
99f9b2fb3e
Update .github/workflows/release.yml
Co-authored-by: Mariano Cano <mariano@smallstep.com>
2023-04-12 18:31:35 -07:00
max furman
574351a8f7
[action] Fix docker image name in release workflow 2023-04-12 18:26:19 -07:00
Mariano Cano
ef337f5285
Merge pull request #1347 from smallstep/v0.24.0-changelog
Add changelog for v0.24.0
2023-04-12 16:06:49 -07:00
Mariano Cano
b5dbeefcc6
Add changelog for v0.24.0 2023-04-12 16:02:31 -07:00
Max
5ec9e761ca
Merge pull request #1299 from smallstep/docker-hsm-glibc
Update Dockerfile.hsm to use debian:bullseye base image
2023-04-12 14:32:11 -07:00
Carl Tashian
3665616015
Deprecate the step-ca-hsm image in favor of step-ca:hsm 2023-04-11 09:40:49 -07:00
Mariano Cano
848e44e5c8
Merge pull request #1345 from smallstep/asn1-functions
Upgrade go.step.sm/crypto with new ASN.1 functions
2023-04-10 14:56:23 -07:00
Mariano Cano
b034c06ac8
Upgrade go.step.sm/crypto with new ASN.1 functions
This commit upgrades go.step.sm/crypto to v0.29.1. This version adds the
following template functions:
- asn1Enc
- asn1Marshal
- asn1Seq
- asn1Set
2023-04-10 14:42:20 -07:00
github-actions[bot]
38c715ca46
Merge pull request #1342 from smallstep/dependabot/go_modules/google.golang.org/api-0.116.0
Bump google.golang.org/api from 0.114.0 to 0.116.0
2023-04-10 12:06:11 -07:00
dependabot[bot]
4133e7d069
Bump google.golang.org/api from 0.114.0 to 0.116.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.114.0 to 0.116.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.114.0...v0.116.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>
2023-04-10 17:27:01 +00:00
github-actions[bot]
2e1e529731
Merge pull request #1343 from smallstep/dependabot/go_modules/golang.org/x/crypto-0.8.0
Bump golang.org/x/crypto from 0.7.0 to 0.8.0
2023-04-10 10:25:43 -07:00
dependabot[bot]
f3bd1d3dbd
Bump golang.org/x/crypto from 0.7.0 to 0.8.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/compare/v0.7.0...v0.8.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>
2023-04-10 17:15:51 +00:00
github-actions[bot]
432dd7ce10
Merge pull request #1344 from smallstep/dependabot/go_modules/golang.org/x/net-0.9.0
Bump golang.org/x/net from 0.8.0 to 0.9.0
2023-04-10 10:14:19 -07:00
dependabot[bot]
aeb02e280e
Bump golang.org/x/net from 0.8.0 to 0.9.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.8.0 to 0.9.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.8.0...v0.9.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>
2023-04-10 16:02:00 +00:00
Herman Slatman
64e39cb0c9
Merge pull request #1063 from smallstep/herman/acme-da-tpm
Add ACME DA TPM attestation
2023-04-07 00:17:03 +02:00
Herman Slatman
cfd65484fc
Update to v0.29.0 of go.step.sm/crypto 2023-04-06 23:41:39 +02:00
Herman Slatman
d9aa2c110f
Increase test coverage for AK certificate properties 2023-04-06 14:35:48 +02:00
Carl Tashian
a815039283
Merge pull request #1340 from smallstep/carl/cosign-flags-update
Update cosign usage note
2023-04-05 14:19:08 -07:00
Carl Tashian
b5baa55a60
Update cosign usage note 2023-04-05 13:09:58 -07:00
Herman Slatman
ed1a62206e
Add additional verification of AK certificate 2023-04-05 01:02:44 +02:00
Herman Slatman
1c38e252a6
Cast alg to a valid COSEAlgorithmIdentifier 2023-04-04 12:22:58 +02:00
Herman Slatman
e25acff13c
Simplify alg validity check 2023-04-03 22:32:26 +02:00
Herman Slatman
dfc56f21b8
Merge branch 'master' into herman/acme-da-tpm 2023-04-03 22:22:53 +02:00
Herman Slatman
9cd4b362f7
Extract the ParseSubjectAlternativeNames function 2023-04-03 22:21:29 +02:00
github-actions[bot]
b4da554aa6
Merge pull request #1337 from smallstep/dependabot/go_modules/github.com/newrelic/go-agent/v3-3.21.0
Bump github.com/newrelic/go-agent/v3 from 3.20.4 to 3.21.0
2023-04-03 10:53:50 -07:00
dependabot[bot]
6905979537
Bump github.com/newrelic/go-agent/v3 from 3.20.4 to 3.21.0
Bumps [github.com/newrelic/go-agent/v3](https://github.com/newrelic/go-agent) from 3.20.4 to 3.21.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.20.4...v3.21.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>
2023-04-03 16:00:22 +00:00
Herman Slatman
827fcb6a06
Fix the go.mod
- Add comment to why we use fork of `github.com/google/go-attestation`
- Fix require and ordering of imports
2023-04-03 11:56:16 +02:00
Herman Slatman
b6957358fc
Fix PR remarks
- Root CA error message improved
- Looping through intermediate certs
- Change checking unhandled extensions to using `if`
2023-04-03 11:54:22 +02:00
Mariano Cano
023491bcf2
Merge pull request #1336 from smallstep/pr-1051
Remove unused certificate validators and modifiers
2023-04-02 12:15:20 -07:00
Mariano Cano
ac35f3489c
Remove unused certificate validators and modifiers
With the introduction of certificate templates some certificate
validators and modifiers are not used anymore. This commit deletes the
ones that are not used.
2023-03-31 14:54:49 -07:00
Herman Slatman
09bd7705cd
Fix linting issues 2023-03-31 17:41:43 +02:00
Herman Slatman
f88ef6621f
Add PermanentIdentifier SAN parsing and tests 2023-03-31 17:39:18 +02:00
Herman Slatman
79cd42527e
Use newer version of forked github.com/google/go-attestation 2023-03-31 15:06:38 +02:00
Herman Slatman
52023d6083
Add tests for doTPMAttestationFormat 2023-03-31 14:57:25 +02:00
Mariano Cano
390acab7d0
Merge pull request #1335 from smallstep/fix-typo
Fix typo in flag usage
2023-03-30 15:42:12 -07:00
Mariano Cano
57a704f008
Fix typo in flag usage 2023-03-30 15:23:21 -07:00
Mariano Cano
21f14e5708
Merge pull request #1309 from rvichery/azure-envs-identity-token
Add identity token issuance for all Azure cloud environments
2023-03-30 10:16:42 -07:00
Herman Slatman
ae30f6e96b
Add failing TPM simulator test 2023-03-30 13:02:04 +02:00
Herman Slatman
1cc3ad27a5
Run TPM simulator tests 2023-03-30 11:39:24 +02:00
Herman Slatman
bf53b394a1
Add tpm format test with simulated TPM 2023-03-29 18:58:50 +02:00
Herman Slatman
720cafb69c
Merge branch 'master' into herman/acme-da-tpm 2023-03-29 16:21:11 +02:00
github-actions[bot]
a3018d9db5
Merge pull request #1331 from smallstep/dependabot/go_modules/google.golang.org/grpc-1.54.0
Bump google.golang.org/grpc from 1.53.0 to 1.54.0
2023-03-27 09:37:42 -07:00
github-actions[bot]
ca108564ff
Merge pull request #1330 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.28.0
Bump go.step.sm/crypto from 0.27.0 to 0.28.0
2023-03-27 09:36:31 -07:00
dependabot[bot]
0cb5acd01c
Bump google.golang.org/grpc from 1.53.0 to 1.54.0
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.53.0 to 1.54.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.53.0...v1.54.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>
2023-03-27 16:02:37 +00:00
dependabot[bot]
8ed523ea67
Bump go.step.sm/crypto from 0.27.0 to 0.28.0
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.27.0 to 0.28.0.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.27.0...v0.28.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>
2023-03-27 16:02:15 +00:00
Remi Vichery
09cbe8ba65
fixup! Add identity token for all Azure cloud environments 2023-03-26 11:11:57 -07:00
Herman Slatman
094f0521e2
Remove check for PermanentIdentifier from tpm format validation 2023-03-24 12:55:42 +01:00
Herman Slatman
f91a31f9b6
Merge branch 'master' into herman/acme-da-tpm 2023-03-24 11:18:25 +01:00
Carl Tashian
df2909e712
Further docker simplifications 2023-03-21 15:01:02 -07:00
Carl Tashian
25e35aa0ad
Small dockerfile refactor 2023-03-21 14:58:03 -07:00
Carl Tashian
f874e31fff
Merge pull request #1329 from smallstep/carl/make-clean
Clean up Makefile and fix goreleaser deprecation
2023-03-21 11:27:47 -07:00
Carl Tashian
b92f37a61d
Use cloud tag on step-kms-plugin 2023-03-21 09:59:16 -07:00
Carl Tashian
2b76d11631
Clean up Makefile and fix goreleaser deprecation 2023-03-20 21:03:37 -07:00
github-actions[bot]
897f4711df
Merge pull request #1326 from smallstep/dependabot/go_modules/cloud.google.com/go/security-1.13.0
Bump cloud.google.com/go/security from 1.12.0 to 1.13.0
2023-03-20 10:07:15 -07:00
github-actions[bot]
1b1df26864
Merge pull request #1327 from smallstep/dependabot/go_modules/google.golang.org/protobuf-1.30.0
Bump google.golang.org/protobuf from 1.29.1 to 1.30.0
2023-03-20 10:06:20 -07:00
github-actions[bot]
92e25f0f7f
Merge pull request #1328 from smallstep/dependabot/go_modules/github.com/googleapis/gax-go/v2-2.8.0
Bump github.com/googleapis/gax-go/v2 from 2.7.1 to 2.8.0
2023-03-20 09:48:02 -07:00
dependabot[bot]
1859ed2666
Bump cloud.google.com/go/security from 1.12.0 to 1.13.0
Bumps [cloud.google.com/go/security](https://github.com/googleapis/google-cloud-go) from 1.12.0 to 1.13.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/asset/v1.12.0...video/v1.13.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>
2023-03-20 16:47:50 +00:00
dependabot[bot]
1420f441d5
Bump google.golang.org/protobuf from 1.29.1 to 1.30.0
Bumps [google.golang.org/protobuf](https://github.com/protocolbuffers/protobuf-go) from 1.29.1 to 1.30.0.
- [Release notes](https://github.com/protocolbuffers/protobuf-go/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf-go/blob/master/release.bash)
- [Commits](https://github.com/protocolbuffers/protobuf-go/compare/v1.29.1...v1.30.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-20 16:47:49 +00:00
github-actions[bot]
bdd4d0004e
Merge pull request #1325 from smallstep/dependabot/go_modules/google.golang.org/api-0.114.0
Bump google.golang.org/api from 0.112.0 to 0.114.0
2023-03-20 09:46:23 -07:00
dependabot[bot]
d8a2839955
Bump github.com/googleapis/gax-go/v2 from 2.7.1 to 2.8.0
Bumps [github.com/googleapis/gax-go/v2](https://github.com/googleapis/gax-go) from 2.7.1 to 2.8.0.
- [Release notes](https://github.com/googleapis/gax-go/releases)
- [Commits](https://github.com/googleapis/gax-go/compare/v2.7.1...v2.8.0)

---
updated-dependencies:
- dependency-name: github.com/googleapis/gax-go/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-20 16:02:21 +00:00
dependabot[bot]
e6339a3761
Bump google.golang.org/api from 0.112.0 to 0.114.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.112.0 to 0.114.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.112.0...v0.114.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>
2023-03-20 16:01:37 +00:00
Mariano Cano
6e0644beb2
Merge pull request #1323 from smallstep/upgrade-crypto
Upgrade go.step.sm/crypto to improved azurekms support
2023-03-15 17:38:21 -07:00
Mariano Cano
334bc81694
Upgrade go.step.sm/crypto to improved azurekms support
This commit upgrades go.step.sm/crypto to the latest version which
includes support for sovereign clouds and HSM keys.

Fixes #1276
2023-03-15 17:11:45 -07:00
Carl Tashian
7ad1ecf518
Merge pull request #1322 from smallstep/goreleaser-clean
Replace deprecated GoReleaser --rm-dist flag with --clean
2023-03-15 11:49:38 -07:00
Carl Tashian
7a3989e7f2
Replace deprecated GoReleaser --rm-dist flag with --clean
See d18adfb57e
2023-03-15 10:52:00 -07:00
github-actions[bot]
7b26ef72a0
Merge pull request #1320 from smallstep/dependabot/go_modules/google.golang.org/protobuf-1.29.1
Bump google.golang.org/protobuf from 1.29.0 to 1.29.1
2023-03-14 17:47:24 -07:00
dependabot[bot]
942f8bfc9f
Bump google.golang.org/protobuf from 1.29.0 to 1.29.1
Bumps [google.golang.org/protobuf](https://github.com/protocolbuffers/protobuf-go) from 1.29.0 to 1.29.1.
- [Release notes](https://github.com/protocolbuffers/protobuf-go/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf-go/blob/master/release.bash)
- [Commits](https://github.com/protocolbuffers/protobuf-go/compare/v1.29.0...v1.29.1)

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-14 23:09:56 +00:00
Herman Slatman
589a62df74
Make validation of tpm format stricter 2023-03-14 13:59:16 +01:00
Herman Slatman
213b31bc2c
Simplify processing logic for unhandled critical extension 2023-03-14 09:48:44 +01:00
Herman Slatman
e1c7e8f00b
Return the CSR public key fingerprint for tpm format 2023-03-13 23:30:39 +01:00
Herman Slatman
6297bace1a
Merge branch 'master' into herman/acme-da-tpm 2023-03-13 17:27:40 +01:00
Herman Slatman
69489480ab
Add more complete tpm format validation 2023-03-13 17:21:09 +01:00
github-actions[bot]
b3a0769778
Merge pull request #1316 from smallstep/dependabot/go_modules/google.golang.org/api-0.112.0
Bump google.golang.org/api from 0.111.0 to 0.112.0
2023-03-13 09:18:58 -07:00
dependabot[bot]
6588efdb01
Bump google.golang.org/api from 0.111.0 to 0.112.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.111.0 to 0.112.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.111.0...v0.112.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>
2023-03-13 16:18:15 +00:00
github-actions[bot]
19a91671a6
Merge pull request #1315 from smallstep/dependabot/go_modules/github.com/newrelic/go-agent/v3-3.20.4
Bump github.com/newrelic/go-agent/v3 from 3.20.3 to 3.20.4
2023-03-13 09:12:45 -07:00
github-actions[bot]
745c1cc130
Merge pull request #1318 from smallstep/dependabot/go_modules/github.com/googleapis/gax-go/v2-2.7.1
Bump github.com/googleapis/gax-go/v2 from 2.7.0 to 2.7.1
2023-03-13 09:11:26 -07:00
github-actions[bot]
c72826a690
Merge pull request #1317 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.26.0
Bump go.step.sm/crypto from 0.25.2 to 0.26.0
2023-03-13 09:10:51 -07:00
github-actions[bot]
124b1e6273
Merge pull request #1319 from smallstep/dependabot/go_modules/google.golang.org/protobuf-1.29.0
Bump google.golang.org/protobuf from 1.28.1 to 1.29.0
2023-03-13 09:10:30 -07:00
dependabot[bot]
bb3cddd6f1
Bump google.golang.org/protobuf from 1.28.1 to 1.29.0
Bumps [google.golang.org/protobuf](https://github.com/protocolbuffers/protobuf-go) from 1.28.1 to 1.29.0.
- [Release notes](https://github.com/protocolbuffers/protobuf-go/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf-go/blob/master/release.bash)
- [Commits](https://github.com/protocolbuffers/protobuf-go/compare/v1.28.1...v1.29.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-13 16:02:43 +00:00
dependabot[bot]
5943c3955e
Bump github.com/googleapis/gax-go/v2 from 2.7.0 to 2.7.1
Bumps [github.com/googleapis/gax-go/v2](https://github.com/googleapis/gax-go) from 2.7.0 to 2.7.1.
- [Release notes](https://github.com/googleapis/gax-go/releases)
- [Commits](https://github.com/googleapis/gax-go/compare/v2.7.0...v2.7.1)

---
updated-dependencies:
- dependency-name: github.com/googleapis/gax-go/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-13 16:02:31 +00:00
dependabot[bot]
8747156bcc
Bump go.step.sm/crypto from 0.25.2 to 0.26.0
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.25.2 to 0.26.0.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.25.2...v0.26.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>
2023-03-13 16:02:21 +00:00
dependabot[bot]
442f2fe5f9
Bump github.com/newrelic/go-agent/v3 from 3.20.3 to 3.20.4
Bumps [github.com/newrelic/go-agent/v3](https://github.com/newrelic/go-agent) from 3.20.3 to 3.20.4.
- [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.20.3...v3.20.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-13 16:02:01 +00:00
Remi Vichery
b2c2eec76b
Add identity token for all Azure cloud environments
* Azure Public Cloud (default)
* Azure China Cloud
* Azure US Gov Cloud
* Azure German Cloud
2023-03-08 08:18:55 -08:00
Carl Tashian
4378300c80
Update cache before installing packages 2023-03-06 09:40:50 -08:00
github-actions[bot]
b8ee206f71
Merge pull request #1305 from smallstep/dependabot/go_modules/google.golang.org/api-0.111.0
Bump google.golang.org/api from 0.110.0 to 0.111.0
2023-03-06 09:36:27 -08:00
github-actions[bot]
201be0891f
Merge pull request #1307 from smallstep/dependabot/go_modules/go.step.sm/crypto-0.25.2
Bump go.step.sm/crypto from 0.25.0 to 0.25.2
2023-03-06 09:32:46 -08:00
Carl Tashian
79b3924322
Fix docker tags 2023-03-06 09:25:43 -08:00
dependabot[bot]
dd43e9e09f
Bump google.golang.org/api from 0.110.0 to 0.111.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.110.0 to 0.111.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.110.0...v0.111.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>
2023-03-06 17:15:23 +00:00
dependabot[bot]
152a0a2f3e
Bump go.step.sm/crypto from 0.25.0 to 0.25.2
Bumps [go.step.sm/crypto](https://github.com/smallstep/crypto) from 0.25.0 to 0.25.2.
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.25.0...v0.25.2)

---
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>
2023-03-06 17:15:03 +00:00
github-actions[bot]
4fb00940c6
Merge pull request #1308 from smallstep/dependabot/go_modules/golang.org/x/crypto-0.7.0
Bump golang.org/x/crypto from 0.6.0 to 0.7.0
2023-03-06 09:07:35 -08:00
dependabot[bot]
6452afc45c
Bump golang.org/x/crypto from 0.6.0 to 0.7.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/compare/v0.6.0...v0.7.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>
2023-03-06 16:08:09 +00:00
Herman Slatman
7c54154013
Merge pull request #1300 from smallstep/herman/pkcs7-rsa-oaep 2023-03-03 20:30:15 +01:00
Herman Slatman
702f844fa2
Add RSA-OAEP decryption support to changelog 2023-03-03 13:39:38 +01:00
Herman Slatman
4d6ecf9a48
Upgrade to latest smallstep/pkcs7 to fix RSA OAEP decryption 2023-03-03 13:33:44 +01:00
Carl Tashian
12d8ca526a
Update Dockerfile.hsm to use debian:bullseye base image 2023-03-02 15:42:09 -05:00
Mariano Cano
2eb90bf45e
Merge pull request #1298 from smallstep/badger-gc
Add support for compacting the badger db
2023-03-02 10:42:26 -08:00
Mariano Cano
7700bb77da
Remove old call to compact 2023-03-01 17:37:56 -08:00
Mariano Cano
831a1e35ea
Add support for compating the badger db
This commit adds a job that will compact the badger db periodically.
In the nosql package, when Compact is called, it will run badger's
RunValueLogGC method.
2023-03-01 17:16:34 -08:00
github-actions[bot]
f8adb0a51c
Merge pull request #1295 from smallstep/dependabot/go_modules/github.com/stretchr/testify-1.8.2
Bump github.com/stretchr/testify from 1.8.1 to 1.8.2
2023-02-27 09:20:50 -08:00
dependabot[bot]
fe63f3e832
Bump github.com/stretchr/testify from 1.8.1 to 1.8.2
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.1 to 1.8.2.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.1...v1.8.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-27 16:05:54 +00:00
Mariano Cano
060a2f186c
Merge pull request #1294 from smallstep/fix-1292
Disable database if WithNoDB() option is passed
2023-02-24 15:48:05 -08:00
Mariano Cano
4fd9a9b92b
Disable database if WithNoDB() option is passed
This commit removes the database from the configuration if the ca was
initialized with the "--no-db" flag.

Fixes #1292
2023-02-24 15:40:48 -08:00
Mariano Cano
23423814d3
Merge pull request #1293 from LarsBingBong/patch-1
Mark the IDP critical in the generated CRL data.
2023-02-24 14:58:32 -08:00
LarsBingBong
0d5c40e059
Mark the IDP critical in the generated CRL data.
Trying to get CRL to work on my environment I've been reading up on [RFC5280](https://www.rfc-editor.org/rfc/rfc5280#section-5.2.5) ... and the IDP to be marked as `Critical`. I hope I'm correct and that my understanding on how to mark the IDP is critical.
Looking at e.g. `3470b1ec57/x509util/extensions_test.go (L48)` makes me think so.

---

Hopefully the above change - if accepted - can get CRL's to work on my environment. If not we're at least one step closer.
2023-02-24 20:32:49 +01:00
Herman Slatman
176cf30a6f
Merge pull request #1290 from smallstep/herman/email-domain-ssh-sign-error
Add email address to error message returned for OIDC validation
2023-02-23 21:13:54 +01:00
Herman Slatman
59462e826c
Improve testing errors for OIDC authorizeToken function 2023-02-23 13:43:13 +01:00
Herman Slatman
10958a124b
Add email address to error message returned for OIDC validation 2023-02-23 13:24:09 +01:00
github-actions[bot]
b02c43cf8e
Merge pull request #1280 from smallstep/dependabot/go_modules/cloud.google.com/go/security-1.12.0
Bump cloud.google.com/go/security from 1.11.0 to 1.12.0
2023-02-21 22:16:06 -08:00
github-actions[bot]
81b1d2ede6
Merge pull request #1279 from smallstep/dependabot/go_modules/github.com/hashicorp/vault/api/auth/approle-0.4.0
Bump github.com/hashicorp/vault/api/auth/approle from 0.3.0 to 0.4.0
2023-02-21 21:57:12 -08:00
dependabot[bot]
e0b9f3960c
Bump cloud.google.com/go/security from 1.11.0 to 1.12.0
Bumps [cloud.google.com/go/security](https://github.com/googleapis/google-cloud-go) from 1.11.0 to 1.12.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/asset/v1.11.0...video/v1.12.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>
2023-02-22 05:49:03 +00:00
dependabot[bot]
b4f8100c72
Bump github.com/hashicorp/vault/api/auth/approle from 0.3.0 to 0.4.0
Bumps [github.com/hashicorp/vault/api/auth/approle](https://github.com/hashicorp/vault) from 0.3.0 to 0.4.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/v0.3.0...v0.4.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>
2023-02-22 05:49:03 +00:00
github-actions[bot]
ae0be0acbd
Merge pull request #1282 from smallstep/dependabot/go_modules/cloud.google.com/go/longrunning-0.4.1
Bump cloud.google.com/go/longrunning from 0.4.0 to 0.4.1
2023-02-21 21:47:44 -08:00
dependabot[bot]
5f835dc808
Bump cloud.google.com/go/longrunning from 0.4.0 to 0.4.1
Bumps [cloud.google.com/go/longrunning](https://github.com/googleapis/google-cloud-go) from 0.4.0 to 0.4.1.
- [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/v0.4.0...batch/v0.4.1)

---
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>
2023-02-21 21:14:50 +00:00
Max
6915feaae9
Merge pull request #1287 from smallstep/max/linting-errors
Fix linting errors
2023-02-21 11:30:47 -08:00
max furman
7c1c32d86b
Fix linting errors 2023-02-21 11:26:33 -08:00
github-actions[bot]
8e47f05dba
Merge pull request #1283 from smallstep/dependabot/go_modules/golang.org/x/crypto-0.6.0
Bump golang.org/x/crypto from 0.5.0 to 0.6.0
2023-02-21 09:20:11 -08:00
dependabot[bot]
790139d5a7
Bump golang.org/x/crypto from 0.5.0 to 0.6.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.5.0 to 0.6.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/compare/v0.5.0...v0.6.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>
2023-02-20 16:06:45 +00:00
Carl Tashian
ca9f8dc576
Merge pull request #1278 from smallstep/policy-help
Clarify policy lockout error message
2023-02-17 22:07:36 -08:00
Carl Tashian
cfcc95de93
Update policy test 2023-02-16 15:58:36 -08:00
Carl Tashian
96c6613739
Clarify policy lockout error message 2023-02-16 15:56:57 -08:00
github-actions[bot]
effe729d53
Merge pull request #1267 from smallstep/dependabot/go_modules/google.golang.org/grpc-1.53.0
Bump google.golang.org/grpc from 1.52.3 to 1.53.0
2023-02-15 15:44:49 -08:00
dependabot[bot]
bb068f8280
Bump google.golang.org/grpc from 1.52.3 to 1.53.0
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.52.3 to 1.53.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.52.3...v1.53.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>
2023-02-15 23:38:31 +00:00
github-actions[bot]
d59d6c414f
Merge pull request #1268 from smallstep/dependabot/go_modules/github.com/hashicorp/vault/api/auth/kubernetes-0.4.0
Bump github.com/hashicorp/vault/api/auth/kubernetes from 0.3.0 to 0.4.0
2023-02-15 15:36:56 -08:00
Max
25599f8ad5
Merge pull request #1255 from zyzyx03/fix-step-ca-path
There is an error during RA installation which shows.
2023-02-14 23:02:56 -08:00
dependabot[bot]
2f2e3dea0f
Bump github.com/hashicorp/vault/api/auth/kubernetes from 0.3.0 to 0.4.0
Bumps [github.com/hashicorp/vault/api/auth/kubernetes](https://github.com/hashicorp/vault) from 0.3.0 to 0.4.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/v0.3.0...v0.4.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>
2023-02-14 22:04:07 +00:00
Mariano Cano
5fbee3d3ef
Merge pull request #1275 from smallstep/net-update
Upgrade golang.org/x/net
2023-02-14 14:00:28 -08:00
Mariano Cano
0d80473157
Upgrade golang.org/x/net
When the Go client is configured with an http2.Transport we need to
upgrade x/net due to:
  - net/http: avoid quadratic complexity in HPACK decoding (CVE-2022-41723)
2023-02-14 13:11:25 -08:00
github-actions[bot]
12d905be3e
Merge pull request #1270 from smallstep/dependabot/go_modules/golang.org/x/net-0.6.0
Bump golang.org/x/net from 0.5.0 to 0.6.0
2023-02-14 09:51:49 -08:00
dependabot[bot]
045ae52452
Bump golang.org/x/net from 0.5.0 to 0.6.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.5.0 to 0.6.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.5.0...v0.6.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>
2023-02-14 17:36:34 +00:00
Mariano Cano
c2c246b062
Merge pull request #1265 from smallstep/check-csr-acme-da
Verify CSR key fingerprint with attestation certificate key
2023-02-14 09:34:48 -08:00
Max
ff7b8830fe
Merge pull request #1273 from smallstep/max/dependabot-auto-merge
enable auto merge for dependabot PRs
2023-02-13 22:56:50 -08:00
max furman
74e6245e90
enable auto merge for dependabot PRs 2023-02-13 17:06:00 -08:00
Mariano Cano
5ff0dde819
Remove json tag in acme.Authorization fingerprint 2023-02-10 13:58:52 -08:00
Mariano Cano
da95c44943
Fix lint issue with Go 1.20 2023-02-09 17:02:35 -08:00
Mariano Cano
6ba20209c2
Verify CSR key fingerprint with attestation certificate key
This commit makes sure that the attestation certificate key matches the
key used on the CSR on an ACME device attestation flow.
2023-02-09 16:48:43 -08:00
Carl Tashian
ec3be2359a
Merge pull request #1262 from micheljung/patch-1
Add DOCKER_STEPCA_INIT_ADDRESS
2023-02-08 16:46:36 -08:00
Michel Jung
ebe7e5d019
Add DOCKER_STEPCA_INIT_ADDRESS
This allows configuring "--address" instead of using hard-coded :9000
2023-02-08 22:22:45 +01:00
zyzyx
2c57415657 There is an error during installation which shows.
"install: cannot stat 'step-ca_0.23.2/bin/step-ca': No such file or directory"
Upon checking the is no bin directory after step-ca_linux_0.23.2_amd64.tar.gz
is extracted so by simply changing from step-ca_${CA_VERSION:1}/bin/step-ca to step-ca_${CA_VERSION:1}/step-ca the installation succeed.
2023-02-04 01:57:42 +08:00
Herman Slatman
f1724ea8c5
Merge branch 'master' into herman/acme-da-tpm 2023-01-23 22:52:56 +01:00
Herman Slatman
c365d8580e
Move provisioner marshaling logic to api package 2022-12-13 10:26:34 +01:00
Herman Slatman
f2e1c56c6c
Improve SCEP provisioner marshaling 2022-12-13 09:33:31 +01:00
Herman Slatman
85cd9a1277
Fix some ACME DA doc strings (slightly) 2022-11-08 12:10:05 +01:00
Herman Slatman
4cf25ede24
Merge branch 'master' into herman/acme-da-tpm 2022-11-08 12:07:46 +01:00
Herman Slatman
a8125846dd
Add TPM attestation 2022-09-21 14:58:03 +02:00
142 changed files with 6308 additions and 1892 deletions

View file

@ -20,7 +20,8 @@ jobs:
ci:
uses: smallstep/workflows/.github/workflows/goCI.yml@main
with:
os-dependencies: "libpcsclite-dev"
run-gitleaks: true
only-latest-golang: false
os-dependencies: 'libpcsclite-dev'
run-codeql: true
test-command: 'V=1 make test'
secrets: inherit

View file

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

View file

@ -17,7 +17,6 @@ jobs:
runs-on: ubuntu-latest
env:
DOCKER_IMAGE: smallstep/step-ca
DOCKER_IMAGE_HSM: smallstep/step-ca-hsm
outputs:
version: ${{ steps.extract-tag.outputs.VERSION }}
is_prerelease: ${{ steps.is_prerelease.outputs.IS_PRERELEASE }}
@ -38,12 +37,12 @@ jobs:
VERSION=${GITHUB_REF#refs/tags/v}
echo "VERSION=${VERSION}" >> ${GITHUB_OUTPUT}
echo "DOCKER_TAGS=${{ env.DOCKER_IMAGE }}:${VERSION}" >> ${GITHUB_ENV}
echo "DOCKER_TAGS_HSM=${{ env.DOCKER_IMAGE_HSM }}:${VERSION}" >> ${GITHUB_ENV}
echo "DOCKER_TAGS_HSM=${{ env.DOCKER_IMAGE }}:${VERSION}-hsm" >> ${GITHUB_ENV}
- name: Add Latest Tag
if: steps.is_prerelease.outputs.IS_PRERELEASE == 'false'
run: |
echo "DOCKER_TAGS=${{ env.DOCKER_TAGS }},${{ env.DOCKER_IMAGE }}:latest" >> ${GITHUB_ENV}
echo "DOCKER_TAGS_HSM=${{ env.DOCKER_TAGS_HSM }},${{ env.DOCKER_IMAGE_HSM }}:latest" >> ${GITHUB_ENV}
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
@ -56,38 +55,12 @@ jobs:
prerelease: ${{ steps.is_prerelease.outputs.IS_PRERELEASE }}
goreleaser:
name: Upload Assets To Github w/ goreleaser
runs-on: ubuntu-latest
needs: create_release
permissions:
id-token: write
contents: write
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
check-latest: true
- name: Install cosign
uses: sigstore/cosign-installer@v2
with:
cosign-release: 'v1.13.1'
- name: Get Release Date
id: release_date
run: |
RELEASE_DATE=$(date +"%y-%m-%d")
echo "RELEASE_DATE=${RELEASE_DATE}" >> ${GITHUB_ENV}
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v3
with:
version: 'latest'
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GORELEASER_PAT }}
RELEASE_DATE: ${{ env.RELEASE_DATE }}
COSIGN_EXPERIMENTAL: 1
uses: smallstep/workflows/.github/workflows/goreleaser.yml@main
secrets: inherit
build_upload_docker:
name: Build & Upload Docker Images
@ -113,6 +86,6 @@ jobs:
with:
platforms: linux/amd64,linux/386,linux/arm,linux/arm64
tags: ${{ needs.create_release.outputs.docker_tags_hsm }}
docker_image: smallstep/step-ca-hsm
docker_image: smallstep/step-ca
docker_file: docker/Dockerfile.hsm
secrets: inherit

1
.gitignore vendored
View file

@ -24,3 +24,4 @@ output
vendor
.idea
.envrc
.vscode

View file

@ -31,11 +31,12 @@ builds:
- -w -X main.Version={{.Version}} -X main.BuildTime={{.Date}}
archives:
-
- &ARCHIVE
# Can be used to change the archive formats for specific GOOSs.
# Most common use case is to archive as zip on Windows.
# Default is empty.
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Version }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
rlcp: true
format_overrides:
- goos: windows
format: zip
@ -44,6 +45,11 @@ archives:
- README.md
- LICENSE
allow_different_binary_count: true
-
<< : *ARCHIVE
id: unversioned
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
nfpms:
# Configure nFPM for .deb and .rpm releases
@ -55,7 +61,7 @@ nfpms:
# List file contents: dpkg -c dist/step_...deb
# Package metadata: dpkg --info dist/step_....deb
#
-
- &NFPM
builds:
- step-ca
package_name: step-ca
@ -75,9 +81,14 @@ nfpms:
contents:
- src: debian/copyright
dst: /usr/share/doc/step-ca/copyright
-
<< : *NFPM
id: unversioned
file_name_template: "{{ .PackageName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
source:
enabled: true
rlcp: true
name_template: '{{ .ProjectName }}_{{ .Version }}'
checksum:
@ -130,17 +141,17 @@ release:
#### Linux
- 📦 [step-ca_linux_{{ .Version }}_amd64.tar.gz](https://dl.step.sm/gh-release/certificates/gh-release-header/{{ .Tag }}/step-ca_linux_{{ .Version }}_amd64.tar.gz)
- 📦 [step-ca_{{ .Version }}_amd64.deb](https://dl.step.sm/gh-release/certificates/gh-release-header/{{ .Tag }}/step-ca_{{ .Version }}_amd64.deb)
- 📦 [step-ca_linux_{{ .Version }}_amd64.tar.gz](https://dl.smallstep.com/gh-release/certificates/gh-release-header/{{ .Tag }}/step-ca_linux_{{ .Version }}_amd64.tar.gz)
- 📦 [step-ca_{{ .Version }}_amd64.deb](https://dl.smallstep.com/gh-release/certificates/gh-release-header/{{ .Tag }}/step-ca_{{ .Version }}_amd64.deb)
#### OSX Darwin
- 📦 [step-ca_darwin_{{ .Version }}_amd64.tar.gz](https://dl.step.sm/gh-release/certificates/gh-release-header/{{ .Tag }}/step-ca_darwin_{{ .Version }}_amd64.tar.gz)
- 📦 [step-ca_darwin_{{ .Version }}_arm64.tar.gz](https://dl.step.sm/gh-release/certificates/gh-release-header/{{ .Tag }}/step-ca_darwin_{{ .Version }}_arm64.tar.gz)
- 📦 [step-ca_darwin_{{ .Version }}_amd64.tar.gz](https://dl.smallstep.com/gh-release/certificates/gh-release-header/{{ .Tag }}/step-ca_darwin_{{ .Version }}_amd64.tar.gz)
- 📦 [step-ca_darwin_{{ .Version }}_arm64.tar.gz](https://dl.smallstep.com/gh-release/certificates/gh-release-header/{{ .Tag }}/step-ca_darwin_{{ .Version }}_arm64.tar.gz)
#### Windows
- 📦 [step-ca_windows_{{ .Version }}_amd64.zip](https://dl.step.sm/gh-release/certificates/gh-release-header/{{ .Tag }}/step-ca_windows_{{ .Version }}_amd64.zip)
- 📦 [step-ca_windows_{{ .Version }}_amd64.zip](https://dl.smallstep.com/gh-release/certificates/gh-release-header/{{ .Tag }}/step-ca_windows_{{ .Version }}_amd64.zip)
For more builds across platforms and architectures, see the `Assets` section below.
And for packaged versions (Docker, k8s, Homebrew), see our [installation docs](https://smallstep.com/docs/step-ca/installation).
@ -154,9 +165,11 @@ release:
Below is an example using `cosign` to verify a release artifact:
```
COSIGN_EXPERIMENTAL=1 cosign verify-blob \
cosign verify-blob \
--certificate ~/Downloads/step-ca_darwin_{{ .Version }}_amd64.tar.gz.sig.pem \
--signature ~/Downloads/step-ca_darwin_{{ .Version }}_amd64.tar.gz.sig \
--certificate-identity-regexp "https://github\.com/smallstep/certificates/.*" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
~/Downloads/step-ca_darwin_{{ .Version }}_amd64.tar.gz
```
@ -186,39 +199,40 @@ release:
# - glob: ./glob/**/to/**/file/**/*
# - glob: ./glob/foo/to/bar/file/foobar/override_from_previous
scoop:
# Template for the url which is determined by the given Token (github or gitlab)
# Default for github is "https://github.com/<repo_owner>/<repo_name>/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
# Default for gitlab is "https://gitlab.com/<repo_owner>/<repo_name>/uploads/{{ .ArtifactUploadHash }}/{{ .ArtifactName }}"
# Default for gitea is "https://gitea.com/<repo_owner>/<repo_name>/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
url_template: "http://github.com/smallstep/certificates/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
scoops:
-
ids: [ default ]
# Template for the url which is determined by the given Token (github or gitlab)
# Default for github is "https://github.com/<repo_owner>/<repo_name>/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
# Default for gitlab is "https://gitlab.com/<repo_owner>/<repo_name>/uploads/{{ .ArtifactUploadHash }}/{{ .ArtifactName }}"
# Default for gitea is "https://gitea.com/<repo_owner>/<repo_name>/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
url_template: "http://github.com/smallstep/certificates/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
# Repository to push the app manifest to.
bucket:
owner: smallstep
name: scoop-bucket
# Repository to push the app manifest to.
bucket:
owner: smallstep
name: scoop-bucket
# Git author used to commit to the repository.
# Defaults are shown.
commit_author:
name: goreleaserbot
email: goreleaser@smallstep.com
# Git author used to commit to the repository.
# Defaults are shown.
commit_author:
name: goreleaserbot
email: goreleaser@smallstep.com
# The project name and current git tag are used in the format string.
commit_msg_template: "Scoop update for {{ .ProjectName }} version {{ .Tag }}"
# The project name and current git tag are used in the format string.
commit_msg_template: "Scoop update for {{ .ProjectName }} version {{ .Tag }}"
# Your app's homepage.
# Default is empty.
homepage: "https://smallstep.com/docs/step-ca"
# Your app's homepage.
# Default is empty.
homepage: "https://smallstep.com/docs/step-ca"
# Skip uploads for prerelease.
skip_upload: auto
# Skip uploads for prerelease.
skip_upload: auto
# Your app's description.
# Default is empty.
description: "A private certificate authority (X.509 & SSH) & ACME server for secure automated certificate management, so you can use TLS everywhere & SSO for SSH."
# Your app's description.
# Default is empty.
description: "A private certificate authority (X.509 & SSH) & ACME server for secure automated certificate management, so you can use TLS everywhere & SSO for SSH."
# Your app's license
# Default is empty.
license: "Apache-2.0"
# Your app's license
# Default is empty.
license: "Apache-2.0"

View file

@ -27,6 +27,70 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]
### Fixed
- Improved authentication for ACME requests using kid and provisioner name
(smallstep/certificates#1386).
## [v0.24.2] - 2023-05-11
### Added
- Log SSH certificates (smallstep/certificates#1374)
- CRL endpoints on the HTTP server (smallstep/certificates#1372)
- Dynamic SCEP challenge validation using webhooks (smallstep/certificates#1366)
- For Docker deployments, added DOCKER_STEPCA_INIT_PASSWORD_FILE. Useful for pointing to a Docker Secret in the container (smallstep/certificates#1384)
### Changed
- Depend on [smallstep/go-attestation](https://github.com/smallstep/go-attestation) instead of [google/go-attestation](https://github.com/google/go-attestation)
- Render CRLs into http.ResponseWriter instead of memory (smallstep/certificates#1373)
- Redaction of SCEP static challenge when listing provisioners (smallstep/certificates#1204)
### Fixed
- VaultCAS certificate lifetime (smallstep/certificates#1376)
## [v0.24.1] - 2023-04-14
### Fixed
- Docker image name for HSM support (smallstep/certificates#1348)
## [v0.24.0] - 2023-04-12
### Added
- Add ACME `device-attest-01` support with TPM 2.0
(smallstep/certificates#1063).
- Add support for new Azure SDK, sovereign clouds, and HSM keys on Azure KMS
(smallstep/crypto#192, smallstep/crypto#197, smallstep/crypto#198,
smallstep/certificates#1323, smallstep/certificates#1309).
- Add support for ASN.1 functions on certificate templates
(smallstep/crypto#208, smallstep/certificates#1345)
- Add `DOCKER_STEPCA_INIT_ADDRESS` to configure the address to use in a docker
container (smallstep/certificates#1262).
- Make sure that the CSR used matches the attested key when using AME
`device-attest-01` challenge (smallstep/certificates#1265).
- Add support for compacting the Badger DB (smallstep/certificates#1298).
- Build and release cleanups (smallstep/certificates#1322,
smallstep/certificates#1329, smallstep/certificates#1340).
### Fixed
- Fix support for PKCS #7 RSA-OAEP decryption through
[smallstep/pkcs7#4](https://github.com/smallstep/pkcs7/pull/4), as used in
SCEP.
- Fix RA installation using `scripts/install-step-ra.sh`
(smallstep/certificates#1255).
- Clarify error messages on policy errors (smallstep/certificates#1287,
smallstep/certificates#1278).
- Clarify error message on OIDC email validation (smallstep/certificates#1290).
- Mark the IDP critical in the generated CRL data (smallstep/certificates#1293).
- Disable database if CA is initialized with the `--no-db` flag
(smallstep/certificates#1294).
## [v0.23.2] - 2023-02-02
### Added

View file

@ -74,7 +74,7 @@ sudo yum install pcsc-lite-devel
To build `step-ca`, clone this repository and run the following:
```shell
make bootstrap && make build GOFLAGS=""
make bootstrap && make build GO_ENVS="CGO_ENABLED=1"
```
When the build is complete, you will find binaries in `bin/`.

View file

@ -6,8 +6,6 @@ Q=$(if $V,,@)
PREFIX?=
SRC=$(shell find . -type f -name '*.go' -not -path "./vendor/*")
GOOS_OVERRIDE ?=
OUTPUT_ROOT=output/
RELEASE=./.releases
all: lint test build
@ -23,6 +21,8 @@ bootstra%:
$Q curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $$(go env GOPATH)/bin latest
$Q go install golang.org/x/vuln/cmd/govulncheck@latest
$Q go install gotest.tools/gotestsum@latest
$Q go install github.com/goreleaser/goreleaser@latest
$Q go install github.com/sigstore/cosign/v2/cmd/cosign@latest
.PHONY: bootstra%
@ -30,17 +30,8 @@ bootstra%:
# Determine the type of `push` and `version`
#################################################
# If TRAVIS_TAG is set then we know this ref has been tagged.
ifdef TRAVIS_TAG
VERSION ?= $(TRAVIS_TAG)
NOT_RC := $(shell echo $(VERSION) | grep -v -e -rc)
ifeq ($(NOT_RC),)
PUSHTYPE := release-candidate
else
PUSHTYPE := release
endif
# GITHUB Actions
else ifdef GITHUB_REF
ifdef GITHUB_REF
VERSION ?= $(shell echo $(GITHUB_REF) | sed 's/^refs\/tags\///')
NOT_RC := $(shell echo $(VERSION) | grep -v -e -rc)
ifeq ($(NOT_RC),)
@ -53,21 +44,14 @@ VERSION ?= $(shell [ -d .git ] && git describe --tags --always --dirty="-dev")
# If we are not in an active git dir then try reading the version from .VERSION.
# .VERSION contains a slug populated by `git archive`.
VERSION := $(or $(VERSION),$(shell ./.version.sh .VERSION))
ifeq ($(TRAVIS_BRANCH),master)
PUSHTYPE := master
else
PUSHTYPE := branch
endif
endif
VERSION := $(shell echo $(VERSION) | sed 's/^v//')
DEB_VERSION := $(shell echo $(VERSION) | sed 's/-/./g')
ifdef V
$(info TRAVIS_TAG is $(TRAVIS_TAG))
$(info GITHUB_REF is $(GITHUB_REF))
$(info VERSION is $(VERSION))
$(info DEB_VERSION is $(DEB_VERSION))
$(info PUSHTYPE is $(PUSHTYPE))
endif
@ -77,7 +61,23 @@ endif
DATE := $(shell date -u '+%Y-%m-%d %H:%M UTC')
LDFLAGS := -ldflags='-w -X "main.Version=$(VERSION)" -X "main.BuildTime=$(DATE)"'
GOFLAGS := CGO_ENABLED=0
# Always explicitly enable or disable cgo,
# so that go doesn't silently fall back on
# non-cgo when gcc is not found.
ifeq (,$(findstring CGO_ENABLED,$(GO_ENVS)))
ifneq ($(origin GOFLAGS),undefined)
# This section is for backward compatibility with
#
# $ make build GOFLAGS=""
#
# which is how we recommended building step-ca with cgo support
# until June 2023.
GO_ENVS := $(GO_ENVS) CGO_ENABLED=1
else
GO_ENVS := $(GO_ENVS) CGO_ENABLED=0
endif
endif
download:
$Q go mod download
@ -87,7 +87,7 @@ build: $(PREFIX)bin/$(BINNAME)
$(PREFIX)bin/$(BINNAME): download $(call rwildcard,*.go)
$Q mkdir -p $(@D)
$Q $(GOOS_OVERRIDE) $(GOFLAGS) go build -v -o $(PREFIX)bin/$(BINNAME) $(LDFLAGS) $(PKG)
$Q $(GOOS_OVERRIDE) GOFLAGS="$(GOFLAGS)" $(GO_ENVS) go build -v -o $(PREFIX)bin/$(BINNAME) $(LDFLAGS) $(PKG)
# Target to force a build of step-ca without running tests
simple: build
@ -106,18 +106,26 @@ generate:
#########################################
# Test
#########################################
test:
$Q $(GOFLAGS) gotestsum -- -coverprofile=coverage.out -short -covermode=atomic ./...
test: testdefault testtpmsimulator combinecoverage
testdefault:
$Q $(GO_ENVS) gotestsum -- -coverprofile=defaultcoverage.out -short -covermode=atomic ./...
testtpmsimulator:
$Q CGO_ENABLED=1 gotestsum -- -coverprofile=tpmsimulatorcoverage.out -short -covermode=atomic -tags tpmsimulator ./acme
testcgo:
$Q gotestsum -- -coverprofile=coverage.out -short -covermode=atomic ./...
.PHONY: test testcgo
combinecoverage:
cat defaultcoverage.out tpmsimulatorcoverage.out > coverage.out
.PHONY: test testdefault testtpmsimulator testcgo combinecoverage
integrate: integration
integration: bin/$(BINNAME)
$Q $(GOFLAGS) gotestsum -- -tags=integration ./integration/...
$Q $(GO_ENVS) gotestsum -- -tags=integration ./integration/...
.PHONY: integrate integration
@ -169,23 +177,3 @@ run:
.PHONY: run
#########################################
# Debian
#########################################
changelog:
$Q echo "step-ca ($(DEB_VERSION)) unstable; urgency=medium" > debian/changelog
$Q echo >> debian/changelog
$Q echo " * See https://github.com/smallstep/certificates/releases" >> debian/changelog
$Q echo >> debian/changelog
$Q echo " -- Smallstep Labs, Inc. <techadmin@smallstep.com> $(shell date -uR)" >> debian/changelog
debian: changelog
$Q mkdir -p $(RELEASE); \
OUTPUT=../step-ca*.deb; \
rm $$OUTPUT; \
dpkg-buildpackage -b -rfakeroot -us -uc && cp $$OUTPUT $(RELEASE)/
distclean: clean
.PHONY: changelog debian distclean

View file

@ -119,18 +119,12 @@ See our installation docs [here](https://smallstep.com/docs/step-ca/installation
## Documentation
Documentation can be found in a handful of different places:
1. On the web at https://smallstep.com/docs/step-ca.
2. On the command line with `step help ca xxx` where `xxx` is the subcommand
you are interested in. Ex: `step help ca provisioner list`.
3. In your browser, by running `step help --http=:8080 ca` from the command line
* [Official documentation](https://smallstep.com/docs/step-ca) is on smallstep.com
* The `step` command reference is available via `step help`,
[on smallstep.com](https://smallstep.com/docs/step-cli/reference/),
or by running `step help --http=:8080` from the command line
and visiting http://localhost:8080.
4. The [docs](./docs/README.md) folder is being deprecated, but it still has some documentation and tutorials.
## Feedback?
* Tell us what you like and don't like about managing your PKI - we're eager to help solve problems in this space.

View file

@ -20,6 +20,16 @@ type Account struct {
Status Status `json:"status"`
OrdersURL string `json:"orders"`
ExternalAccountBinding interface{} `json:"externalAccountBinding,omitempty"`
LocationPrefix string `json:"-"`
ProvisionerName string `json:"-"`
}
// GetLocation returns the URL location of the given account.
func (a *Account) GetLocation() string {
if a.LocationPrefix == "" {
return ""
}
return a.LocationPrefix + a.ID
}
// ToLog enables response logging.
@ -72,6 +82,7 @@ func (p *Policy) GetAllowedNameOptions() *policy.X509NameOptions {
IPRanges: p.X509.Allowed.IPRanges,
}
}
func (p *Policy) GetDeniedNameOptions() *policy.X509NameOptions {
if p == nil {
return nil

View file

@ -66,6 +66,23 @@ func TestKeyToID(t *testing.T) {
}
}
func TestAccount_GetLocation(t *testing.T) {
locationPrefix := "https://test.ca.smallstep.com/acme/foo/account/"
type test struct {
acc *Account
exp string
}
tests := map[string]test{
"empty": {acc: &Account{LocationPrefix: ""}, exp: ""},
"not-empty": {acc: &Account{ID: "bar", LocationPrefix: locationPrefix}, exp: locationPrefix + "bar"},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
assert.Equals(t, tc.acc.GetLocation(), tc.exp)
})
}
}
func TestAccount_IsValid(t *testing.T) {
type test struct {
acc *Account

View file

@ -1,6 +1,7 @@
package api
import (
"context"
"encoding/json"
"errors"
"net/http"
@ -67,6 +68,12 @@ func (u *UpdateAccountRequest) Validate() error {
}
}
// getAccountLocationPath returns the current account URL location.
// Returned location will be of the form: https://<ca-url>/acme/<provisioner>/account/<accID>
func getAccountLocationPath(ctx context.Context, linker acme.Linker, accID string) string {
return linker.GetLink(ctx, acme.AccountLinkType, accID)
}
// NewAccount is the handler resource for creating new ACME accounts.
func NewAccount(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
@ -125,9 +132,11 @@ func NewAccount(w http.ResponseWriter, r *http.Request) {
}
acc = &acme.Account{
Key: jwk,
Contact: nar.Contact,
Status: acme.StatusValid,
Key: jwk,
Contact: nar.Contact,
Status: acme.StatusValid,
LocationPrefix: getAccountLocationPath(ctx, linker, ""),
ProvisionerName: prov.GetName(),
}
if err := db.CreateAccount(ctx, acc); err != nil {
render.Error(w, acme.WrapErrorISE(err, "error creating account"))
@ -152,7 +161,7 @@ func NewAccount(w http.ResponseWriter, r *http.Request) {
linker.LinkAccount(ctx, acc)
w.Header().Set("Location", linker.GetLink(r.Context(), acme.AccountLinkType, acc.ID))
w.Header().Set("Location", getAccountLocationPath(ctx, linker, acc.ID))
render.JSONStatus(w, acc, httpStatus)
}

View file

@ -34,31 +34,24 @@ var (
type fakeProvisioner struct{}
func (*fakeProvisioner) AuthorizeOrderIdentifier(ctx context.Context, identifier provisioner.ACMEIdentifier) error {
func (*fakeProvisioner) AuthorizeOrderIdentifier(context.Context, provisioner.ACMEIdentifier) error {
return nil
}
func (*fakeProvisioner) AuthorizeSign(ctx context.Context, token string) ([]provisioner.SignOption, error) {
func (*fakeProvisioner) AuthorizeSign(context.Context, string) ([]provisioner.SignOption, error) {
return nil, nil
}
func (*fakeProvisioner) IsChallengeEnabled(ctx context.Context, challenge provisioner.ACMEChallenge) bool {
func (*fakeProvisioner) IsChallengeEnabled(context.Context, provisioner.ACMEChallenge) bool {
return true
}
func (*fakeProvisioner) IsAttestationFormatEnabled(ctx context.Context, format provisioner.ACMEAttestationFormat) bool {
func (*fakeProvisioner) IsAttestationFormatEnabled(context.Context, provisioner.ACMEAttestationFormat) bool {
return true
}
func (*fakeProvisioner) GetAttestationRoots() (*x509.CertPool, bool) {
return nil, false
}
func (*fakeProvisioner) AuthorizeRevoke(ctx context.Context, token string) error { return nil }
func (*fakeProvisioner) GetID() string { return "" }
func (*fakeProvisioner) GetName() string { return "" }
func (*fakeProvisioner) DefaultTLSCertDuration() time.Duration { return 0 }
func (*fakeProvisioner) GetOptions() *provisioner.Options { return nil }
func (*fakeProvisioner) GetAttestationRoots() (*x509.CertPool, bool) { return nil, false }
func (*fakeProvisioner) AuthorizeRevoke(context.Context, string) error { return nil }
func (*fakeProvisioner) GetID() string { return "" }
func (*fakeProvisioner) GetName() string { return "" }
func (*fakeProvisioner) DefaultTLSCertDuration() time.Duration { return 0 }
func (*fakeProvisioner) GetOptions() *provisioner.Options { return nil }
func newProv() acme.Provisioner {
// Initialize provisioners
@ -369,7 +362,7 @@ func TestHandler_GetOrdersByAccountID(t *testing.T) {
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
ctx := acme.NewContext(tc.ctx, tc.db, nil, acme.NewLinker("test.ca.smallstep.com", "acme"), nil)
ctx := acme.NewContext(tc.ctx, tc.db, nil, acme.NewLinker("test.ca.smallstep.com", "acme"), nil, "")
req := httptest.NewRequest("GET", u, nil)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
@ -808,7 +801,7 @@ func TestHandler_NewAccount(t *testing.T) {
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
ctx := acme.NewContext(tc.ctx, tc.db, nil, acme.NewLinker("test.ca.smallstep.com", "acme"), nil)
ctx := acme.NewContext(tc.ctx, tc.db, nil, acme.NewLinker("test.ca.smallstep.com", "acme"), nil, "")
req := httptest.NewRequest("GET", "/foo/bar", nil)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
@ -1011,7 +1004,7 @@ func TestHandler_GetOrUpdateAccount(t *testing.T) {
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
ctx := acme.NewContext(tc.ctx, tc.db, nil, acme.NewLinker("test.ca.smallstep.com", "acme"), nil)
ctx := acme.NewContext(tc.ctx, tc.db, nil, acme.NewLinker("test.ca.smallstep.com", "acme"), nil, "")
req := httptest.NewRequest("GET", "/foo/bar", nil)
req = req.WithContext(ctx)
w := httptest.NewRecorder()

View file

@ -95,7 +95,7 @@ func (h *handler) Route(r api.Router) {
if ca, ok := h.opts.CA.(*authority.Authority); ok && ca != nil {
ctx = authority.NewContext(ctx, ca)
}
ctx = acme.NewContext(ctx, h.opts.DB, client, linker, h.opts.PrerequisitesChecker)
ctx = acme.NewContext(ctx, h.opts.DB, client, linker, h.opts.PrerequisitesChecker, "")
next(w, r.WithContext(ctx))
}
})
@ -273,7 +273,7 @@ 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, r *http.Request) {
func NotImplemented(w http.ResponseWriter, _ *http.Request) {
render.Error(w, acme.NewError(acme.ErrorNotImplementedType, "this API is not implemented"))
}

View file

@ -346,7 +346,7 @@ func TestHandler_GetAuthorization(t *testing.T) {
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
ctx := acme.NewContext(tc.ctx, tc.db, nil, acme.NewLinker("test.ca.smallstep.com", "acme"), nil)
ctx := acme.NewContext(tc.ctx, tc.db, nil, acme.NewLinker("test.ca.smallstep.com", "acme"), nil, "")
req := httptest.NewRequest("GET", "/foo/bar", nil)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
@ -746,7 +746,7 @@ func TestHandler_GetChallenge(t *testing.T) {
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
ctx := acme.NewContext(tc.ctx, tc.db, nil, acme.NewLinker("test.ca.smallstep.com", "acme"), nil)
ctx := acme.NewContext(tc.ctx, tc.db, nil, acme.NewLinker("test.ca.smallstep.com", "acme"), nil, "")
req := httptest.NewRequest("GET", u, nil)
req = req.WithContext(ctx)
w := httptest.NewRecorder()

View file

@ -7,6 +7,7 @@ import (
"io"
"net/http"
"net/url"
"path"
"strings"
"go.step.sm/crypto/jose"
@ -16,7 +17,6 @@ import (
"github.com/smallstep/certificates/api/render"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/logging"
"github.com/smallstep/nosql"
)
type nextHTTP = func(http.ResponseWriter, *http.Request)
@ -293,7 +293,6 @@ func lookupJWK(next nextHTTP) nextHTTP {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
db := acme.MustDatabaseFromContext(ctx)
linker := acme.MustLinkerFromContext(ctx)
jws, err := jwsFromContext(ctx)
if err != nil {
@ -301,19 +300,16 @@ func lookupJWK(next nextHTTP) nextHTTP {
return
}
kidPrefix := linker.GetLink(ctx, acme.AccountLinkType, "")
kid := jws.Signatures[0].Protected.KeyID
if !strings.HasPrefix(kid, kidPrefix) {
render.Error(w, acme.NewError(acme.ErrorMalformedType,
"kid does not have required prefix; expected %s, but got %s",
kidPrefix, kid))
if kid == "" {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "signature missing 'kid'"))
return
}
accID := strings.TrimPrefix(kid, kidPrefix)
accID := path.Base(kid)
acc, err := db.GetAccount(ctx, accID)
switch {
case nosql.IsErrNotFound(err):
case acme.IsErrNotFound(err):
render.Error(w, acme.NewError(acme.ErrorAccountDoesNotExistType, "account with ID '%s' not found", accID))
return
case err != nil:
@ -324,6 +320,45 @@ func lookupJWK(next nextHTTP) nextHTTP {
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, "account is not active"))
return
}
if storedLocation := acc.GetLocation(); storedLocation != "" {
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,
"kid does not match stored account location; expected %s, but got %s",
storedLocation, kid))
return
}
// 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,
"account provisioner does not match requested provisioner; account provisioner = %s, requested provisioner = %s",
accProvName, reqProvName))
return
}
} else {
// This code will only execute for old ACME accounts that do
// not have a cached location. The following validation was
// the original implementation of the `kid` check which has
// since been deprecated. However, the code will remain to
// ensure consistent behavior for old ACME accounts.
linker := acme.MustLinkerFromContext(ctx)
kidPrefix := linker.GetLink(ctx, acme.AccountLinkType, "")
if !strings.HasPrefix(kid, kidPrefix) {
render.Error(w, acme.NewError(acme.ErrorMalformedType,
"kid does not have required prefix; expected %s, but got %s",
kidPrefix, kid))
return
}
}
ctx = context.WithValue(ctx, accContextKey, acc)
ctx = context.WithValue(ctx, jwkContextKey, acc.Key)
next(w, r.WithContext(ctx))

View file

@ -17,14 +17,13 @@ import (
"github.com/pkg/errors"
"github.com/smallstep/assert"
"github.com/smallstep/certificates/acme"
"github.com/smallstep/nosql/database"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil"
)
var testBody = []byte("foo")
func testNext(w http.ResponseWriter, r *http.Request) {
func testNext(w http.ResponseWriter, _ *http.Request) {
w.Write(testBody)
}
@ -328,7 +327,7 @@ func TestHandler_isPostAsGet(t *testing.T) {
type errReader int
func (errReader) Read(p []byte) (n int, err error) {
func (errReader) Read([]byte) (int, error) {
return 0, errors.New("force")
}
func (errReader) Close() error {
@ -678,31 +677,7 @@ func TestHandler_lookupJWK(t *testing.T) {
linker: acme.NewLinker("test.ca.smallstep.com", "acme"),
ctx: ctx,
statusCode: 400,
err: acme.NewError(acme.ErrorMalformedType, "kid does not have required prefix; expected %s, but got ", prefix),
}
},
"fail/bad-kid-prefix": func(t *testing.T) test {
_so := new(jose.SignerOptions)
_so.WithHeader("kid", "foo")
_signer, err := jose.NewSigner(jose.SigningKey{
Algorithm: jose.SignatureAlgorithm(jwk.Algorithm),
Key: jwk.Key,
}, _so)
assert.FatalError(t, err)
_jws, err := _signer.Sign([]byte("baz"))
assert.FatalError(t, err)
_raw, err := _jws.CompactSerialize()
assert.FatalError(t, err)
_parsed, err := jose.ParseJWS(_raw)
assert.FatalError(t, err)
ctx := acme.NewProvisionerContext(context.Background(), prov)
ctx = context.WithValue(ctx, jwsContextKey, _parsed)
return test{
db: &acme.MockDB{},
linker: acme.NewLinker("test.ca.smallstep.com", "acme"),
ctx: ctx,
statusCode: 400,
err: acme.NewError(acme.ErrorMalformedType, "kid does not have required prefix; expected %s, but got foo", prefix),
err: acme.NewError(acme.ErrorMalformedType, "signature missing 'kid'"),
}
},
"fail/account-not-found": func(t *testing.T) test {
@ -713,7 +688,7 @@ func TestHandler_lookupJWK(t *testing.T) {
db: &acme.MockDB{
MockGetAccount: func(ctx context.Context, accID string) (*acme.Account, error) {
assert.Equals(t, accID, accID)
return nil, database.ErrNotFound
return nil, acme.ErrNotFound
},
},
ctx: ctx,
@ -754,7 +729,77 @@ func TestHandler_lookupJWK(t *testing.T) {
err: acme.NewError(acme.ErrorUnauthorizedType, "account is not active"),
}
},
"ok": func(t *testing.T) test {
"fail/account-with-location-prefix/bad-kid": func(t *testing.T) test {
acc := &acme.Account{LocationPrefix: "foobar", Status: "valid"}
ctx := acme.NewProvisionerContext(context.Background(), prov)
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,
statusCode: http.StatusUnauthorized,
err: acme.NewError(acme.ErrorUnauthorizedType, "kid does not match stored account location; expected foobar, but %q", prefix+accID),
}
},
"fail/account-with-location-prefix/bad-provisioner": func(t *testing.T) test {
acc := &acme.Account{LocationPrefix: prefix + accID, Status: "valid", Key: jwk, ProvisionerName: "other"}
ctx := acme.NewProvisionerContext(context.Background(), prov)
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, reqested provisioner = %s",
prov.GetName(), "other"),
}
},
"ok/account-with-location-prefix": func(t *testing.T) test {
acc := &acme.Account{LocationPrefix: prefix + accID, Status: "valid", Key: jwk, ProvisionerName: prov.GetName()}
ctx := acme.NewProvisionerContext(context.Background(), prov)
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.StatusOK,
}
},
"ok/account-without-location-prefix": func(t *testing.T) test {
acc := &acme.Account{Status: "valid", Key: jwk}
ctx := acme.NewProvisionerContext(context.Background(), prov)
ctx = context.WithValue(ctx, jwsContextKey, parsedJWS)

View file

@ -392,7 +392,7 @@ func challengeTypes(az *acme.Authorization) []acme.ChallengeType {
case acme.IP:
chTypes = []acme.ChallengeType{acme.HTTP01, acme.TLSALPN01}
case acme.DNS:
chTypes = []acme.ChallengeType{acme.DNS01}
chTypes = []acme.ChallengeType{acme.DNS01, acme.NNS01}
// HTTP and TLS challenges can only be used for identifiers without wildcards.
if !az.Wildcard {
chTypes = append(chTypes, []acme.ChallengeType{acme.HTTP01, acme.TLSALPN01}...)

View file

@ -151,7 +151,7 @@ func RevokeCert(w http.ResponseWriter, r *http.Request) {
// the identifiers in the certificate are extracted and compared against the (valid) Authorizations
// that are stored for the ACME Account. If these sets match, the Account is considered authorized
// to revoke the certificate. If this check fails, the client will receive an unauthorized error.
func isAccountAuthorized(ctx context.Context, dbCert *acme.Certificate, certToBeRevoked *x509.Certificate, account *acme.Account) *acme.Error {
func isAccountAuthorized(_ context.Context, dbCert *acme.Certificate, certToBeRevoked *x509.Certificate, account *acme.Account) *acme.Error {
if !account.IsValid() {
return wrapUnauthorizedError(certToBeRevoked, nil, fmt.Sprintf("account '%s' has status '%s'", account.ID, account.Status), nil)
}

View file

@ -258,7 +258,7 @@ func jwkEncode(pub crypto.PublicKey) (string, error) {
// jwsFinal constructs the final JWS object.
// Implementation taken from github.com/mholt/acmez, which seems to be based on
// https://github.com/golang/crypto/blob/master/acme/jws.go.
func jwsFinal(sha crypto.Hash, sig []byte, phead, payload string) ([]byte, error) {
func jwsFinal(_ crypto.Hash, sig []byte, phead, payload string) ([]byte, error) {
enc := struct {
Protected string `json:"protected"`
Payload string `json:"payload"`
@ -281,7 +281,7 @@ type mockCA struct {
MockAreSANsallowed func(ctx context.Context, sans []string) error
}
func (m *mockCA) Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
func (m *mockCA) Sign(*x509.CertificateRequest, provisioner.SignOptions, ...provisioner.SignOption) ([]*x509.Certificate, error) {
return nil, nil
}

View file

@ -8,15 +8,16 @@ import (
// Authorization representst an ACME Authorization.
type Authorization struct {
ID string `json:"-"`
AccountID string `json:"-"`
Token string `json:"-"`
Identifier Identifier `json:"identifier"`
Status Status `json:"status"`
Challenges []*Challenge `json:"challenges"`
Wildcard bool `json:"wildcard"`
ExpiresAt time.Time `json:"expires"`
Error *Error `json:"error,omitempty"`
ID string `json:"-"`
AccountID string `json:"-"`
Token string `json:"-"`
Fingerprint string `json:"-"`
Identifier Identifier `json:"identifier"`
Status Status `json:"status"`
Challenges []*Challenge `json:"challenges"`
Wildcard bool `json:"wildcard"`
ExpiresAt time.Time `json:"expires"`
Error *Error `json:"error,omitempty"`
}
// ToLog enables response logging.

View file

@ -26,8 +26,14 @@ import (
"time"
"github.com/fxamacker/cbor/v2"
"github.com/google/go-tpm/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"
"github.com/smallstep/certificates/authority/provisioner"
)
@ -43,6 +49,8 @@ const (
TLSALPN01 ChallengeType = "tls-alpn-01"
// DEVICEATTEST01 is the device-attest-01 ACME challenge type
DEVICEATTEST01 ChallengeType = "device-attest-01"
// NNS01 is the nns-01 ACME challenge type
NNS01 ChallengeType = "nns-01"
)
var (
@ -97,6 +105,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 NNS01:
return nns01Validate(ctx, ch, db, jwk)
default:
return NewErrorISE("unexpected challenge type '%s'", ch.Type)
}
@ -347,6 +357,13 @@ type attestationObject struct {
// TODO(bweeks): move attestation verification to a shared package.
func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, payload []byte) error {
// Load authorization to store the key fingerprint.
az, err := db.GetAuthorization(ctx, ch.AuthorizationID)
if err != nil {
return WrapErrorISE(err, "error loading authorization")
}
// Parse payload.
var p payloadType
if err := json.Unmarshal(payload, &p); err != nil {
return WrapErrorISE(err, "error unmarshalling JSON")
@ -385,7 +402,6 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose
}
return WrapErrorISE(err, "error validating attestation")
}
// Validate nonce with SHA-256 of the token.
if len(data.Nonce) != 0 {
sum := sha256.Sum256([]byte(ch.Token))
@ -401,6 +417,9 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose
if data.UDID != ch.Value && data.SerialNumber != ch.Value {
return storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatementType, "permanent identifier does not match"))
}
// Update attestation key fingerprint to compare against the CSR
az.Fingerprint = data.Fingerprint
case "step":
data, err := doStepAttestationFormat(ctx, prov, ch, jwk, &att)
if err != nil {
@ -426,6 +445,41 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose
)
return storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatementType, "permanent identifier does not match").AddSubproblems(subproblem))
}
// Update attestation key fingerprint to compare against the CSR
az.Fingerprint = data.Fingerprint
case "tpm":
data, err := doTPMAttestationFormat(ctx, prov, ch, jwk, &att)
if err != nil {
// TODO(hs): we should provide more details in the error reported to the client;
// "Attestation statement cannot be verified" is VERY generic. Also holds true for the other formats.
var acmeError *Error
if errors.As(err, &acmeError) {
if acmeError.Status == 500 {
return acmeError
}
return storeError(ctx, db, ch, true, acmeError)
}
return WrapErrorISE(err, "error validating attestation")
}
// TODO(hs): currently this will allow a request for which no PermanentIdentifiers have been
// extracted from the AK certificate. This is currently the case for AK certs from the CLI, as we
// haven't implemented a way for AK certs requested by the CLI to always contain the requested
// PermanentIdentifier. Omitting the check below doesn't allow just any request, as the Order can
// still fail if the challenge value isn't equal to the CSR subject.
if len(data.PermanentIdentifiers) > 0 && !slices.Contains(data.PermanentIdentifiers, ch.Value) { // TODO(hs): add support for HardwareModuleName
subproblem := NewSubproblemWithIdentifier(
ErrorMalformedType,
Identifier{Type: "permanent-identifier", Value: ch.Value},
"challenge identifier %q doesn't match any of the attested hardware identifiers %q", ch.Value, data.PermanentIdentifiers,
)
return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType, "permanent identifier does not match").AddSubproblems(subproblem))
}
// Update attestation key fingerprint to compare against the CSR
az.Fingerprint = data.Fingerprint
default:
return storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatementType, "unexpected attestation object format"))
}
@ -435,12 +489,362 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose
ch.Error = nil
ch.ValidatedAt = clock.Now().Format(time.RFC3339)
// Store the fingerprint in the authorization.
//
// TODO: add method to update authorization and challenge atomically.
if az.Fingerprint != "" {
if err := db.UpdateAuthorization(ctx, az); err != nil {
return WrapErrorISE(err, "error updating authorization")
}
}
if err := db.UpdateChallenge(ctx, ch); err != nil {
return WrapErrorISE(err, "error updating challenge")
}
return nil
}
func nns01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey) error {
domain := strings.TrimPrefix(ch.Value, "*.")
nnsCtx, ok := GetNNSContext(ctx)
if !ok {
return errors.New("error retrieving NNS context")
}
nns := NNS{}
err := nns.Dial(nnsCtx.nnsServer)
if err != nil {
return err
}
defer nns.Close()
txtRecords, err := nns.GetTXTRecords("acme-challenge." + domain)
if err != nil {
return storeError(ctx, db, ch, false, WrapError(ErrorNNSType, err,
"error looking up TXT records for domain %s", domain))
}
expectedKeyAuth, err := KeyAuthorization(ch.Token, jwk)
if err != nil {
return err
}
h := sha256.Sum256([]byte(expectedKeyAuth))
expected := base64.RawURLEncoding.EncodeToString(h[:])
var found bool
for _, r := range txtRecords {
if r == expected {
found = true
break
}
}
if !found {
return storeError(ctx, db, ch, false, NewError(ErrorRejectedIdentifierType,
"keyAuthorization does not match; expected %s, but got %s", expectedKeyAuth, txtRecords))
}
// 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")
}
return nil
}
var (
oidSubjectAlternativeName = asn1.ObjectIdentifier{2, 5, 29, 17}
)
type tpmAttestationData struct {
Certificate *x509.Certificate
VerifiedChains [][]*x509.Certificate
PermanentIdentifiers []string
Fingerprint string
}
// coseAlgorithmIdentifier models a COSEAlgorithmIdentifier.
// Also see https://www.w3.org/TR/webauthn-2/#sctn-alg-identifier.
type coseAlgorithmIdentifier int32
const (
coseAlgES256 coseAlgorithmIdentifier = -7
coseAlgRS256 coseAlgorithmIdentifier = -257
)
func doTPMAttestationFormat(_ context.Context, prov Provisioner, ch *Challenge, jwk *jose.JSONWebKey, att *attestationObject) (*tpmAttestationData, error) {
ver, ok := att.AttStatement["ver"].(string)
if !ok {
return nil, NewError(ErrorBadAttestationStatementType, "ver not present")
}
if ver != "2.0" {
return nil, NewError(ErrorBadAttestationStatementType, "version %q is not supported", ver)
}
x5c, ok := att.AttStatement["x5c"].([]interface{})
if !ok {
return nil, NewError(ErrorBadAttestationStatementType, "x5c not present")
}
if len(x5c) == 0 {
return nil, NewError(ErrorBadAttestationStatementType, "x5c is empty")
}
akCertBytes, ok := x5c[0].([]byte)
if !ok {
return nil, NewError(ErrorBadAttestationStatementType, "x5c is malformed")
}
akCert, err := x509.ParseCertificate(akCertBytes)
if err != nil {
return nil, WrapError(ErrorBadAttestationStatementType, err, "x5c is malformed")
}
intermediates := x509.NewCertPool()
for _, v := range x5c[1:] {
intCertBytes, vok := v.([]byte)
if !vok {
return nil, NewError(ErrorBadAttestationStatementType, "x5c is malformed")
}
intCert, err := x509.ParseCertificate(intCertBytes)
if err != nil {
return nil, WrapError(ErrorBadAttestationStatementType, err, "x5c is malformed")
}
intermediates.AddCert(intCert)
}
// TODO(hs): this can be removed when permanent-identifier/hardware-module-name are handled correctly in
// the stdlib in https://cs.opensource.google/go/go/+/refs/tags/go1.19:src/crypto/x509/parser.go;drc=b5b2cf519fe332891c165077f3723ee74932a647;l=362,
// but I doubt that will happen.
if len(akCert.UnhandledCriticalExtensions) > 0 {
unhandledCriticalExtensions := akCert.UnhandledCriticalExtensions[:0]
for _, extOID := range akCert.UnhandledCriticalExtensions {
if !extOID.Equal(oidSubjectAlternativeName) {
// critical extensions other than the Subject Alternative Name remain unhandled
unhandledCriticalExtensions = append(unhandledCriticalExtensions, extOID)
}
}
akCert.UnhandledCriticalExtensions = unhandledCriticalExtensions
}
roots, ok := prov.GetAttestationRoots()
if !ok {
return nil, NewErrorISE("no root CA bundle available to verify the attestation certificate")
}
// verify that the AK certificate was signed by a trusted root,
// chained to by the intermediates provided by the client. As part
// of building the verified certificate chain, the signature over the
// AK certificate is checked to be a valid signature of one of the
// provided intermediates. Signatures over the intermediates are in
// turn also verified to be valid signatures from one of the trusted
// roots.
verifiedChains, err := akCert.Verify(x509.VerifyOptions{
Roots: roots,
Intermediates: intermediates,
CurrentTime: time.Now().Truncate(time.Second),
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
})
if err != nil {
return nil, WrapError(ErrorBadAttestationStatementType, err, "x5c is not valid")
}
// validate additional AK certificate requirements
if err := validateAKCertificate(akCert); err != nil {
return nil, WrapError(ErrorBadAttestationStatementType, err, "AK certificate is not valid")
}
// TODO(hs): implement revocation check; Verify() doesn't perform CRL check nor OCSP lookup.
sans, err := x509util.ParseSubjectAlternativeNames(akCert)
if err != nil {
return nil, WrapError(ErrorBadAttestationStatementType, err, "failed parsing AK certificate Subject Alternative Names")
}
permanentIdentifiers := make([]string, len(sans.PermanentIdentifiers))
for i, pi := range sans.PermanentIdentifiers {
permanentIdentifiers[i] = pi.Identifier
}
// extract and validate pubArea, sig, certInfo and alg properties from the request body
pubArea, ok := att.AttStatement["pubArea"].([]byte)
if !ok {
return nil, NewError(ErrorBadAttestationStatementType, "invalid pubArea in attestation statement")
}
if len(pubArea) == 0 {
return nil, NewError(ErrorBadAttestationStatementType, "pubArea is empty")
}
sig, ok := att.AttStatement["sig"].([]byte)
if !ok {
return nil, NewError(ErrorBadAttestationStatementType, "invalid sig in attestation statement")
}
if len(sig) == 0 {
return nil, NewError(ErrorBadAttestationStatementType, "sig is empty")
}
certInfo, ok := att.AttStatement["certInfo"].([]byte)
if !ok {
return nil, NewError(ErrorBadAttestationStatementType, "invalid certInfo in attestation statement")
}
if len(certInfo) == 0 {
return nil, NewError(ErrorBadAttestationStatementType, "certInfo is empty")
}
alg, ok := att.AttStatement["alg"].(int64)
if !ok {
return nil, NewError(ErrorBadAttestationStatementType, "invalid alg in attestation statement")
}
// only RS256 and ES256 are allowed
coseAlg := coseAlgorithmIdentifier(alg)
if coseAlg != coseAlgRS256 && coseAlg != coseAlgES256 {
return nil, NewError(ErrorBadAttestationStatementType, "invalid alg %d in attestation statement", alg)
}
// set the hash algorithm to use to SHA256
hash := crypto.SHA256
// recreate the generated key certification parameter values and verify
// the attested key using the public key of the AK.
certificationParameters := &attest.CertificationParameters{
Public: pubArea, // the public key that was attested
CreateAttestation: certInfo, // the attested properties of the key
CreateSignature: sig, // signature over the attested properties
}
verifyOpts := attest.VerifyOpts{
Public: akCert.PublicKey, // public key of the AK that attested the key
Hash: hash,
}
if err = certificationParameters.Verify(verifyOpts); err != nil {
return nil, WrapError(ErrorBadAttestationStatementType, err, "invalid certification parameters")
}
// decode the "certInfo" data. This won't fail, as it's also done as part of Verify().
tpmCertInfo, err := tpm2.DecodeAttestationData(certInfo)
if err != nil {
return nil, WrapError(ErrorBadAttestationStatementType, err, "failed decoding attestation data")
}
keyAuth, err := KeyAuthorization(ch.Token, jwk)
if err != nil {
return nil, WrapError(ErrorBadAttestationStatementType, err, "failed creating key auth digest")
}
hashedKeyAuth := sha256.Sum256([]byte(keyAuth))
// verify the WebAuthn object contains the expect key authorization digest, which is carried
// within the encoded `certInfo` property of the attestation statement.
if subtle.ConstantTimeCompare(hashedKeyAuth[:], []byte(tpmCertInfo.ExtraData)) == 0 {
return nil, NewError(ErrorBadAttestationStatementType, "key authorization does not match")
}
// decode the (attested) public key and determine its fingerprint. This won't fail, as it's also done as part of Verify().
pub, err := tpm2.DecodePublic(pubArea)
if err != nil {
return nil, WrapError(ErrorBadAttestationStatementType, err, "failed decoding pubArea")
}
publicKey, err := pub.Key()
if err != nil {
return nil, WrapError(ErrorBadAttestationStatementType, err, "failed getting public key")
}
data := &tpmAttestationData{
Certificate: akCert,
VerifiedChains: verifiedChains,
PermanentIdentifiers: permanentIdentifiers,
}
if data.Fingerprint, err = keyutil.Fingerprint(publicKey); err != nil {
return nil, WrapErrorISE(err, "error calculating key fingerprint")
}
// TODO(hs): pass more attestation data, so that that can be used/recorded too?
return data, nil
}
var (
oidExtensionExtendedKeyUsage = asn1.ObjectIdentifier{2, 5, 29, 37}
oidTCGKpAIKCertificate = asn1.ObjectIdentifier{2, 23, 133, 8, 3}
)
// validateAKCertifiate 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.
//
// - Version MUST be set to 3.
// - Subject field MUST be set to empty.
// - 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)").
// - 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
// the status of many attestation certificates is available through metadata
// services. See, for example, the FIDO Metadata Service.
func validateAKCertificate(c *x509.Certificate) error {
if c.Version != 3 {
return fmt.Errorf("AK certificate has invalid version %d; only version 3 is allowed", c.Version)
}
if c.Subject.String() != "" {
return fmt.Errorf("AK certificate subject must be empty; got %q", c.Subject)
}
if c.IsCA {
return errors.New("AK certificate must not be a CA")
}
if err := validateAKCertificateExtendedKeyUsage(c); err != nil {
return err
}
return validateAKCertificateSubjectAlternativeNames(c)
}
// validateAKCertificateSubjectAlternativeNames checks if the AK certificate
// has TPM hardware details set.
func validateAKCertificateSubjectAlternativeNames(c *x509.Certificate) error {
sans, err := x509util.ParseSubjectAlternativeNames(c)
if err != nil {
return fmt.Errorf("failed parsing AK certificate Subject Alternative Names: %w", err)
}
details := sans.TPMHardwareDetails
manufacturer, model, version := details.Manufacturer, details.Model, details.Version
switch {
case manufacturer == "":
return errors.New("missing TPM manufacturer")
case model == "":
return errors.New("missing TPM model")
case version == "":
return errors.New("missing TPM version")
}
return nil
}
// validateAKCertificateExtendedKeyUsage checks if the AK certificate
// has the "tcg-kp-AIKCertificate" Extended Key Usage set.
func validateAKCertificateExtendedKeyUsage(c *x509.Certificate) error {
var (
valid = false
ekus []asn1.ObjectIdentifier
)
for _, ext := range c.Extensions {
if ext.Id.Equal(oidExtensionExtendedKeyUsage) {
if _, err := asn1.Unmarshal(ext.Value, &ekus); err != nil || !ekus[0].Equal(oidTCGKpAIKCertificate) {
return errors.New("AK certificate is missing Extended Key Usage value tcg-kp-AIKCertificate (2.23.133.8.3)")
}
valid = true
}
}
if !valid {
return errors.New("AK certificate is missing Extended Key Usage extension")
}
return nil
}
// Apple Enterprise Attestation Root CA from
// https://www.apple.com/certificateauthority/private/
const appleEnterpriseAttestationRootCA = `-----BEGIN CERTIFICATE-----
@ -471,9 +875,10 @@ type appleAttestationData struct {
UDID string
SEPVersion string
Certificate *x509.Certificate
Fingerprint string
}
func doAppleAttestationFormat(ctx context.Context, prov Provisioner, ch *Challenge, att *attestationObject) (*appleAttestationData, error) {
func doAppleAttestationFormat(_ context.Context, prov Provisioner, _ *Challenge, att *attestationObject) (*appleAttestationData, error) {
// Use configured or default attestation roots if none is configured.
roots, ok := prov.GetAttestationRoots()
if !ok {
@ -527,6 +932,9 @@ func doAppleAttestationFormat(ctx context.Context, prov Provisioner, ch *Challen
data := &appleAttestationData{
Certificate: leaf,
}
if data.Fingerprint, err = keyutil.Fingerprint(leaf.PublicKey); err != nil {
return nil, WrapErrorISE(err, "error calculating key fingerprint")
}
for _, ext := range leaf.Extensions {
switch {
case ext.Id.Equal(oidAppleSerialNumber):
@ -572,9 +980,10 @@ var oidYubicoSerialNumber = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 41482, 3, 7}
type stepAttestationData struct {
Certificate *x509.Certificate
SerialNumber string
Fingerprint string
}
func doStepAttestationFormat(ctx context.Context, prov Provisioner, ch *Challenge, jwk *jose.JSONWebKey, att *attestationObject) (*stepAttestationData, error) {
func doStepAttestationFormat(_ context.Context, prov Provisioner, ch *Challenge, jwk *jose.JSONWebKey, att *attestationObject) (*stepAttestationData, error) {
// Use configured or default attestation roots if none is configured.
roots, ok := prov.GetAttestationRoots()
if !ok {
@ -667,6 +1076,9 @@ func doStepAttestationFormat(ctx context.Context, prov Provisioner, ch *Challeng
data := &stepAttestationData{
Certificate: leaf,
}
if data.Fingerprint, err = keyutil.Fingerprint(leaf.PublicKey); err != nil {
return nil, WrapErrorISE(err, "error calculating key fingerprint")
}
for _, ext := range leaf.Extensions {
if !ext.Id.Equal(oidYubicoSerialNumber) {
continue
@ -730,10 +1142,10 @@ func uitoa(val uint) string {
var buf [20]byte // big enough for 64bit value base 10
i := len(buf) - 1
for val >= 10 {
q := val / 10
buf[i] = byte('0' + val - q*10)
v := val / 10
buf[i] = byte('0' + val - v*10)
i--
val = q
val = v
}
// val < 10
buf[i] = byte('0' + val)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,860 @@
//go:build tpmsimulator
// +build tpmsimulator
package acme
import (
"context"
"crypto"
"crypto/sha256"
"crypto/x509"
"encoding/asn1"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"net/url"
"testing"
"github.com/fxamacker/cbor/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/smallstep/go-attestation/attest"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/minica"
"go.step.sm/crypto/tpm"
"go.step.sm/crypto/tpm/simulator"
tpmstorage "go.step.sm/crypto/tpm/storage"
"go.step.sm/crypto/x509util"
)
func newSimulatedTPM(t *testing.T) *tpm.TPM {
t.Helper()
tmpDir := t.TempDir()
tpm, err := tpm.New(withSimulator(t), tpm.WithStore(tpmstorage.NewDirstore(tmpDir))) // TODO: provide in-memory storage implementation instead
require.NoError(t, err)
return tpm
}
func withSimulator(t *testing.T) tpm.NewTPMOption {
t.Helper()
var sim simulator.Simulator
t.Cleanup(func() {
if sim == nil {
return
}
err := sim.Close()
require.NoError(t, err)
})
sim, err := simulator.New()
require.NoError(t, err)
err = sim.Open()
require.NoError(t, err)
return tpm.WithSimulator(sim)
}
func generateKeyID(t *testing.T, pub crypto.PublicKey) []byte {
t.Helper()
b, err := x509.MarshalPKIXPublicKey(pub)
require.NoError(t, err)
hash := sha256.Sum256(b)
return hash[:]
}
func mustAttestTPM(t *testing.T, keyAuthorization string, permanentIdentifiers []string) ([]byte, crypto.Signer, *x509.Certificate) {
t.Helper()
aca, err := minica.New(
minica.WithName("TPM Testing"),
minica.WithGetSignerFunc(
func() (crypto.Signer, error) {
return keyutil.GenerateSigner("RSA", "", 2048)
},
),
)
require.NoError(t, err)
// prepare simulated TPM and create an AK
stpm := newSimulatedTPM(t)
eks, err := stpm.GetEKs(context.Background())
require.NoError(t, err)
ak, err := stpm.CreateAK(context.Background(), "first-ak")
require.NoError(t, err)
require.NotNil(t, ak)
// extract the AK public key // TODO(hs): replace this when there's a simpler method to get the AK public key (e.g. ak.Public())
ap, err := ak.AttestationParameters(context.Background())
require.NoError(t, err)
akp, err := attest.ParseAKPublic(attest.TPMVersion20, ap.Public)
require.NoError(t, err)
// create template and sign certificate for the AK public key
keyID := generateKeyID(t, eks[0].Public())
template := &x509.Certificate{
PublicKey: akp.Public,
IsCA: false,
UnknownExtKeyUsage: []asn1.ObjectIdentifier{oidTCGKpAIKCertificate},
}
sans := []x509util.SubjectAlternativeName{}
uris := []*url.URL{{Scheme: "urn", Opaque: "ek:sha256:" + base64.StdEncoding.EncodeToString(keyID)}}
for _, pi := range permanentIdentifiers {
sans = append(sans, x509util.SubjectAlternativeName{
Type: x509util.PermanentIdentifierType,
Value: pi,
})
}
asn1Value := []byte(fmt.Sprintf(`{"extraNames":[{"type": %q, "value": %q},{"type": %q, "value": %q},{"type": %q, "value": %q}]}`, oidTPMManufacturer, "1414747215", oidTPMModel, "SLB 9670 TPM2.0", oidTPMVersion, "7.55"))
sans = append(sans, x509util.SubjectAlternativeName{
Type: x509util.DirectoryNameType,
ASN1Value: asn1Value,
})
ext, err := createSubjectAltNameExtension(nil, nil, nil, uris, sans, true)
require.NoError(t, err)
ext.Set(template)
akCert, err := aca.Sign(template)
require.NoError(t, err)
require.NotNil(t, akCert)
// create a new key attested by the AK, while including
// the key authorization bytes as qualifying data.
keyAuthSum := sha256.Sum256([]byte(keyAuthorization))
config := tpm.AttestKeyConfig{
Algorithm: "RSA",
Size: 2048,
QualifyingData: keyAuthSum[:],
}
key, err := stpm.AttestKey(context.Background(), "first-ak", "first-key", config)
require.NoError(t, err)
require.NotNil(t, key)
require.Equal(t, "first-key", key.Name())
require.NotEqual(t, 0, len(key.Data()))
require.Equal(t, "first-ak", key.AttestedBy())
require.True(t, key.WasAttested())
require.True(t, key.WasAttestedBy(ak))
signer, err := key.Signer(context.Background())
require.NoError(t, err)
// prepare the attestation object with the AK certificate chain,
// the attested key, its metadata and the signature signed by the
// AK.
params, err := key.CertificationParameters(context.Background())
require.NoError(t, err)
attObj, err := cbor.Marshal(struct {
Format string `json:"fmt"`
AttStatement map[string]interface{} `json:"attStmt,omitempty"`
}{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
"pubArea": params.Public,
},
})
require.NoError(t, err)
// marshal the ACME payload
payload, err := json.Marshal(struct {
AttObj string `json:"attObj"`
}{
AttObj: base64.RawURLEncoding.EncodeToString(attObj),
})
require.NoError(t, err)
return payload, signer, aca.Root
}
func Test_deviceAttest01ValidateWithTPMSimulator(t *testing.T) {
type args struct {
ctx context.Context
ch *Challenge
db DB
jwk *jose.JSONWebKey
payload []byte
}
type test struct {
args args
wantErr *Error
}
tests := map[string]func(t *testing.T) test{
"ok/doTPMAttestationFormat-storeError": func(t *testing.T) test {
jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
payload, _, root := mustAttestTPM(t, keyAuth, nil) // TODO: value(s) for AK cert?
caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: root.Raw})
ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot))
// parse payload, set invalid "ver", remarshal
var p payloadType
err := json.Unmarshal(payload, &p)
require.NoError(t, err)
attObj, err := base64.RawURLEncoding.DecodeString(p.AttObj)
require.NoError(t, err)
att := attestationObject{}
err = cbor.Unmarshal(attObj, &att)
require.NoError(t, err)
att.AttStatement["ver"] = "bogus"
attObj, err = cbor.Marshal(struct {
Format string `json:"fmt"`
AttStatement map[string]interface{} `json:"attStmt,omitempty"`
}{
Format: "tpm",
AttStatement: att.AttStatement,
})
require.NoError(t, err)
payload, err = json.Marshal(struct {
AttObj string `json:"attObj"`
}{
AttObj: base64.RawURLEncoding.EncodeToString(attObj),
})
require.NoError(t, err)
return test{
args: args{
ctx: ctx,
jwk: jwk,
ch: &Challenge{
ID: "chID",
AuthorizationID: "azID",
Token: "token",
Type: "device-attest-01",
Status: StatusPending,
Value: "device.id.12345678",
},
payload: payload,
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
assert.Equal(t, "azID", id)
return &Authorization{ID: "azID"}, nil
},
MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
assert.Equal(t, "chID", updch.ID)
assert.Equal(t, "token", updch.Token)
assert.Equal(t, StatusInvalid, updch.Status)
assert.Equal(t, ChallengeType("device-attest-01"), updch.Type)
assert.Equal(t, "device.id.12345678", updch.Value)
err := NewError(ErrorBadAttestationStatementType, `version "bogus" is not supported`)
assert.EqualError(t, updch.Error.Err, err.Err.Error())
assert.Equal(t, err.Type, updch.Error.Type)
assert.Equal(t, err.Detail, updch.Error.Detail)
assert.Equal(t, err.Status, updch.Error.Status)
assert.Equal(t, err.Subproblems, updch.Error.Subproblems)
return nil
},
},
},
wantErr: nil,
}
},
"ok with invalid PermanentIdentifier SAN": func(t *testing.T) test {
jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
payload, _, root := mustAttestTPM(t, keyAuth, []string{"device.id.12345678"}) // TODO: value(s) for AK cert?
caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: root.Raw})
ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot))
return test{
args: args{
ctx: ctx,
jwk: jwk,
ch: &Challenge{
ID: "chID",
AuthorizationID: "azID",
Token: "token",
Type: "device-attest-01",
Status: StatusPending,
Value: "device.id.99999999",
},
payload: payload,
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
assert.Equal(t, "azID", id)
return &Authorization{ID: "azID"}, nil
},
MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
assert.Equal(t, "chID", updch.ID)
assert.Equal(t, "token", updch.Token)
assert.Equal(t, StatusInvalid, updch.Status)
assert.Equal(t, ChallengeType("device-attest-01"), updch.Type)
assert.Equal(t, "device.id.99999999", updch.Value)
err := NewError(ErrorRejectedIdentifierType, `permanent identifier does not match`).
AddSubproblems(NewSubproblemWithIdentifier(
ErrorMalformedType,
Identifier{Type: "permanent-identifier", Value: "device.id.99999999"},
`challenge identifier "device.id.99999999" doesn't match any of the attested hardware identifiers ["device.id.12345678"]`,
))
assert.EqualError(t, updch.Error.Err, err.Err.Error())
assert.Equal(t, err.Type, updch.Error.Type)
assert.Equal(t, err.Detail, updch.Error.Detail)
assert.Equal(t, err.Status, updch.Error.Status)
assert.Equal(t, err.Subproblems, updch.Error.Subproblems)
return nil
},
},
},
wantErr: nil,
}
},
"ok": func(t *testing.T) test {
jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
payload, signer, root := mustAttestTPM(t, keyAuth, nil) // TODO: value(s) for AK cert?
caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: root.Raw})
ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot))
return test{
args: args{
ctx: ctx,
jwk: jwk,
ch: &Challenge{
ID: "chID",
AuthorizationID: "azID",
Token: "token",
Type: "device-attest-01",
Status: StatusPending,
Value: "device.id.12345678",
},
payload: payload,
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
assert.Equal(t, "azID", id)
return &Authorization{ID: "azID"}, nil
},
MockUpdateAuthorization: func(ctx context.Context, az *Authorization) error {
fingerprint, err := keyutil.Fingerprint(signer.Public())
assert.NoError(t, err)
assert.Equal(t, "azID", az.ID)
assert.Equal(t, fingerprint, az.Fingerprint)
return nil
},
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("device-attest-01"), updch.Type)
assert.Equal(t, "device.id.12345678", updch.Value)
return nil
},
},
},
wantErr: nil,
}
},
"ok with PermanentIdentifier SAN": func(t *testing.T) test {
jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
payload, signer, root := mustAttestTPM(t, keyAuth, []string{"device.id.12345678"}) // TODO: value(s) for AK cert?
caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: root.Raw})
ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot))
return test{
args: args{
ctx: ctx,
jwk: jwk,
ch: &Challenge{
ID: "chID",
AuthorizationID: "azID",
Token: "token",
Type: "device-attest-01",
Status: StatusPending,
Value: "device.id.12345678",
},
payload: payload,
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
assert.Equal(t, "azID", id)
return &Authorization{ID: "azID"}, nil
},
MockUpdateAuthorization: func(ctx context.Context, az *Authorization) error {
fingerprint, err := keyutil.Fingerprint(signer.Public())
assert.NoError(t, err)
assert.Equal(t, "azID", az.ID)
assert.Equal(t, fingerprint, az.Fingerprint)
return nil
},
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("device-attest-01"), updch.Type)
assert.Equal(t, "device.id.12345678", updch.Value)
return nil
},
},
},
wantErr: nil,
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
tc := run(t)
if err := deviceAttest01Validate(tc.args.ctx, tc.args.ch, tc.args.db, tc.args.jwk, tc.args.payload); err != nil {
assert.Error(t, tc.wantErr)
assert.EqualError(t, err, tc.wantErr.Error())
return
}
assert.Nil(t, tc.wantErr)
})
}
}
func newBadAttestationStatementError(msg string) *Error {
return &Error{
Type: "urn:ietf:params:acme:error:badAttestationStatement",
Status: 400,
Err: errors.New(msg),
}
}
func newInternalServerError(msg string) *Error {
return &Error{
Type: "urn:ietf:params:acme:error:serverInternal",
Status: 500,
Err: errors.New(msg),
}
}
var (
oidPermanentIdentifier = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 3}
oidHardwareModuleNameIdentifier = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 4}
)
func Test_doTPMAttestationFormat(t *testing.T) {
ctx := context.Background()
aca, err := minica.New(
minica.WithName("TPM Testing"),
minica.WithGetSignerFunc(
func() (crypto.Signer, error) {
return keyutil.GenerateSigner("RSA", "", 2048)
},
),
)
require.NoError(t, err)
acaRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: aca.Root.Raw})
// prepare simulated TPM and create an AK
stpm := newSimulatedTPM(t)
eks, err := stpm.GetEKs(context.Background())
require.NoError(t, err)
ak, err := stpm.CreateAK(context.Background(), "first-ak")
require.NoError(t, err)
require.NotNil(t, ak)
// extract the AK public key // TODO(hs): replace this when there's a simpler method to get the AK public key (e.g. ak.Public())
ap, err := ak.AttestationParameters(context.Background())
require.NoError(t, err)
akp, err := attest.ParseAKPublic(attest.TPMVersion20, ap.Public)
require.NoError(t, err)
// create template and sign certificate for the AK public key
keyID := generateKeyID(t, eks[0].Public())
template := &x509.Certificate{
PublicKey: akp.Public,
IsCA: false,
UnknownExtKeyUsage: []asn1.ObjectIdentifier{oidTCGKpAIKCertificate},
}
sans := []x509util.SubjectAlternativeName{}
uris := []*url.URL{{Scheme: "urn", Opaque: "ek:sha256:" + base64.StdEncoding.EncodeToString(keyID)}}
asn1Value := []byte(fmt.Sprintf(`{"extraNames":[{"type": %q, "value": %q},{"type": %q, "value": %q},{"type": %q, "value": %q}]}`, oidTPMManufacturer, "1414747215", oidTPMModel, "SLB 9670 TPM2.0", oidTPMVersion, "7.55"))
sans = append(sans, x509util.SubjectAlternativeName{
Type: x509util.DirectoryNameType,
ASN1Value: asn1Value,
})
ext, err := createSubjectAltNameExtension(nil, nil, nil, uris, sans, true)
require.NoError(t, err)
ext.Set(template)
akCert, err := aca.Sign(template)
require.NoError(t, err)
require.NotNil(t, akCert)
invalidTemplate := &x509.Certificate{
PublicKey: akp.Public,
IsCA: false,
UnknownExtKeyUsage: []asn1.ObjectIdentifier{oidTCGKpAIKCertificate},
}
invalidAKCert, err := aca.Sign(invalidTemplate)
require.NoError(t, err)
require.NotNil(t, invalidAKCert)
// generate a JWK and the key authorization value
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
require.NoError(t, err)
keyAuthorization, err := KeyAuthorization("token", jwk)
require.NoError(t, err)
// create a new key attested by the AK, while including
// the key authorization bytes as qualifying data.
keyAuthSum := sha256.Sum256([]byte(keyAuthorization))
config := tpm.AttestKeyConfig{
Algorithm: "RSA",
Size: 2048,
QualifyingData: keyAuthSum[:],
}
key, err := stpm.AttestKey(context.Background(), "first-ak", "first-key", config)
require.NoError(t, err)
require.NotNil(t, key)
params, err := key.CertificationParameters(context.Background())
require.NoError(t, err)
signer, err := key.Signer(context.Background())
require.NoError(t, err)
fingerprint, err := keyutil.Fingerprint(signer.Public())
require.NoError(t, err)
// attest another key and get its certification parameters
anotherKey, err := stpm.AttestKey(context.Background(), "first-ak", "another-key", config)
require.NoError(t, err)
require.NotNil(t, key)
anotherKeyParams, err := anotherKey.CertificationParameters(context.Background())
require.NoError(t, err)
type args struct {
ctx context.Context
prov Provisioner
ch *Challenge
jwk *jose.JSONWebKey
att *attestationObject
}
tests := []struct {
name string
args args
want *tpmAttestationData
expErr *Error
}{
{"ok", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
"pubArea": params.Public,
},
}}, nil, nil},
{"fail ver not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
"pubArea": params.Public,
},
}}, nil, newBadAttestationStatementError("ver not present")},
{"fail ver type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": []interface{}{},
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
"pubArea": params.Public,
},
}}, nil, newBadAttestationStatementError("ver not present")},
{"fail bogus ver", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": "bogus",
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
"pubArea": params.Public,
},
}}, nil, newBadAttestationStatementError(`version "bogus" is not supported`)},
{"fail x5c not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": "2.0",
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
"pubArea": params.Public,
},
}}, nil, newBadAttestationStatementError("x5c not present")},
{"fail x5c type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": [][]byte{akCert.Raw, aca.Intermediate.Raw},
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
"pubArea": params.Public,
},
}}, nil, newBadAttestationStatementError("x5c not present")},
{"fail x5c empty", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{},
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
"pubArea": params.Public,
},
}}, nil, newBadAttestationStatementError("x5c is empty")},
{"fail leaf type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "step",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{"leaf", aca.Intermediate.Raw},
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
"pubArea": params.Public,
},
}}, nil, newBadAttestationStatementError("x5c is malformed")},
{"fail leaf parse", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "step",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{akCert.Raw[:100], aca.Intermediate.Raw},
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
"pubArea": params.Public,
},
}}, nil, newBadAttestationStatementError("x5c is malformed: x509: malformed certificate")},
{"fail intermediate type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "step",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{akCert.Raw, "intermediate"},
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
"pubArea": params.Public,
},
}}, nil, newBadAttestationStatementError("x5c is malformed")},
{"fail intermediate parse", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "step",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw[:100]},
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
"pubArea": params.Public,
},
}}, nil, newBadAttestationStatementError("x5c is malformed: x509: malformed certificate")},
{"fail roots", args{ctx, mustAttestationProvisioner(t, nil), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
"pubArea": params.Public,
},
}}, nil, newInternalServerError("no root CA bundle available to verify the attestation certificate")},
{"fail verify", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "step",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{akCert.Raw},
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
"pubArea": params.Public,
},
}}, nil, newBadAttestationStatementError("x5c is not valid: x509: certificate signed by unknown authority")},
{"fail validateAKCertificate", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{invalidAKCert.Raw, aca.Intermediate.Raw},
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
"pubArea": params.Public,
},
}}, nil, newBadAttestationStatementError("AK certificate is not valid: missing TPM manufacturer")},
{"fail pubArea not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
},
}}, nil, newBadAttestationStatementError("invalid pubArea in attestation statement")},
{"fail pubArea type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
"pubArea": []interface{}{},
},
}}, nil, newBadAttestationStatementError("invalid pubArea in attestation statement")},
{"fail pubArea empty", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
"pubArea": []byte{},
},
}}, nil, newBadAttestationStatementError("pubArea is empty")},
{"fail sig not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
"alg": int64(-257), // RS256
"certInfo": params.CreateAttestation,
"pubArea": params.Public,
},
}}, nil, newBadAttestationStatementError("invalid sig in attestation statement")},
{"fail sig type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
"alg": int64(-257), // RS256
"sig": []interface{}{},
"certInfo": params.CreateAttestation,
"pubArea": params.Public,
},
}}, nil, newBadAttestationStatementError("invalid sig in attestation statement")},
{"fail sig empty", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
"alg": int64(-257), // RS256
"sig": []byte{},
"certInfo": params.CreateAttestation,
"pubArea": params.Public,
},
}}, nil, newBadAttestationStatementError("sig is empty")},
{"fail certInfo not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"pubArea": params.Public,
},
}}, nil, newBadAttestationStatementError("invalid certInfo in attestation statement")},
{"fail certInfo type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"certInfo": []interface{}{},
"pubArea": params.Public,
},
}}, nil, newBadAttestationStatementError("invalid certInfo in attestation statement")},
{"fail certInfo empty", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"certInfo": []byte{},
"pubArea": params.Public,
},
}}, nil, newBadAttestationStatementError("certInfo is empty")},
{"fail alg not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
"pubArea": params.Public,
},
}}, nil, newBadAttestationStatementError("invalid alg in attestation statement")},
{"fail alg type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
"alg": int64(0), // invalid alg
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
"pubArea": params.Public,
},
}}, nil, newBadAttestationStatementError("invalid alg 0 in attestation statement")},
{"fail attestation verification", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
"pubArea": anotherKeyParams.Public,
},
}}, nil, newBadAttestationStatementError("invalid certification parameters: certification refers to a different key")},
{"fail keyAuthorization", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, &jose.JSONWebKey{Key: []byte("not an asymmetric key")}, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
"alg": int64(-257), // RS256
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
"pubArea": params.Public,
},
}}, nil, newInternalServerError("failed creating key auth digest: error generating JWK thumbprint: square/go-jose: unknown key type '[]uint8'")},
{"fail different keyAuthorization", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "aDifferentToken"}, jwk, &attestationObject{
Format: "tpm",
AttStatement: map[string]interface{}{
"ver": "2.0",
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
"alg": int64(-257), //
"sig": params.CreateSignature,
"certInfo": params.CreateAttestation,
"pubArea": params.Public,
},
}}, nil, newBadAttestationStatementError("key authorization does not match")},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := doTPMAttestationFormat(tt.args.ctx, tt.args.prov, tt.args.ch, tt.args.jwk, tt.args.att)
if tt.expErr != nil {
var ae *Error
if assert.True(t, errors.As(err, &ae)) {
assert.EqualError(t, err, tt.expErr.Error())
assert.Equal(t, ae.StatusCode(), tt.expErr.StatusCode())
assert.Equal(t, ae.Type, tt.expErr.Type)
}
assert.Nil(t, got)
return
}
assert.NoError(t, err)
if assert.NotNil(t, got) {
assert.Equal(t, akCert, got.Certificate)
assert.Equal(t, [][]*x509.Certificate{
{
akCert, aca.Intermediate, aca.Root,
},
}, got.VerifiedChains)
assert.Equal(t, fingerprint, got.Fingerprint)
assert.Empty(t, got.PermanentIdentifiers) // currently expected to be always empty
}
})
}
}

View file

@ -29,10 +29,12 @@ type CertificateAuthority interface {
}
// NewContext adds the given acme components to the context.
func NewContext(ctx context.Context, db DB, client Client, linker Linker, fn PrerequisitesChecker) context.Context {
func NewContext(ctx context.Context, db DB, client Client, linker Linker, fn PrerequisitesChecker,
nnsServer string) context.Context {
ctx = NewDatabaseContext(ctx, db)
ctx = NewClientContext(ctx, client)
ctx = NewLinkerContext(ctx, linker)
ctx = NewNNSContext(ctx, nnsServer)
// Prerequisite checker is optional.
if fn != nil {
ctx = NewPrerequisitesCheckerContext(ctx, fn)
@ -46,7 +48,7 @@ type PrerequisitesChecker func(ctx context.Context) (bool, error)
// DefaultPrerequisitesChecker is the default PrerequisiteChecker and returns
// always true.
func DefaultPrerequisitesChecker(ctx context.Context) (bool, error) {
func DefaultPrerequisitesChecker(context.Context) (bool, error) {
return true, nil
}

View file

@ -12,6 +12,12 @@ import (
// account.
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)
}
// DB is the DB interface expected by the step-ca ACME API.
type DB interface {
CreateAccount(ctx context.Context, acc *Account) error

View file

@ -13,12 +13,14 @@ import (
// dbAccount represents an ACME account.
type dbAccount struct {
ID string `json:"id"`
Key *jose.JSONWebKey `json:"key"`
Contact []string `json:"contact,omitempty"`
Status acme.Status `json:"status"`
CreatedAt time.Time `json:"createdAt"`
DeactivatedAt time.Time `json:"deactivatedAt"`
ID string `json:"id"`
Key *jose.JSONWebKey `json:"key"`
Contact []string `json:"contact,omitempty"`
Status acme.Status `json:"status"`
LocationPrefix string `json:"locationPrefix"`
ProvisionerName string `json:"provisionerName"`
CreatedAt time.Time `json:"createdAt"`
DeactivatedAt time.Time `json:"deactivatedAt"`
}
func (dba *dbAccount) clone() *dbAccount {
@ -26,7 +28,7 @@ func (dba *dbAccount) clone() *dbAccount {
return &nu
}
func (db *DB) getAccountIDByKeyID(ctx context.Context, kid string) (string, error) {
func (db *DB) getAccountIDByKeyID(_ context.Context, kid string) (string, error) {
id, err := db.db.Get(accountByKeyIDTable, []byte(kid))
if err != nil {
if nosqlDB.IsErrNotFound(err) {
@ -38,7 +40,7 @@ func (db *DB) getAccountIDByKeyID(ctx context.Context, kid string) (string, erro
}
// getDBAccount retrieves and unmarshals dbAccount.
func (db *DB) getDBAccount(ctx context.Context, id string) (*dbAccount, error) {
func (db *DB) getDBAccount(_ context.Context, id string) (*dbAccount, error) {
data, err := db.db.Get(accountTable, []byte(id))
if err != nil {
if nosqlDB.IsErrNotFound(err) {
@ -62,10 +64,12 @@ func (db *DB) GetAccount(ctx context.Context, id string) (*acme.Account, error)
}
return &acme.Account{
Status: dbacc.Status,
Contact: dbacc.Contact,
Key: dbacc.Key,
ID: dbacc.ID,
Status: dbacc.Status,
Contact: dbacc.Contact,
Key: dbacc.Key,
ID: dbacc.ID,
LocationPrefix: dbacc.LocationPrefix,
ProvisionerName: dbacc.ProvisionerName,
}, nil
}
@ -87,11 +91,13 @@ func (db *DB) CreateAccount(ctx context.Context, acc *acme.Account) error {
}
dba := &dbAccount{
ID: acc.ID,
Key: acc.Key,
Contact: acc.Contact,
Status: acc.Status,
CreatedAt: clock.Now(),
ID: acc.ID,
Key: acc.Key,
Contact: acc.Contact,
Status: acc.Status,
CreatedAt: clock.Now(),
LocationPrefix: acc.LocationPrefix,
ProvisionerName: acc.ProvisionerName,
}
kid, err := acme.KeyToID(dba.Key)

View file

@ -197,6 +197,8 @@ func TestDB_getAccountIDByKeyID(t *testing.T) {
func TestDB_GetAccount(t *testing.T) {
accID := "accID"
locationPrefix := "https://test.ca.smallstep.com/acme/foo/account/"
provisionerName := "foo"
type test struct {
db nosql.DB
err error
@ -222,12 +224,14 @@ func TestDB_GetAccount(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,
LocationPrefix: locationPrefix,
ProvisionerName: provisionerName,
}
b, err := json.Marshal(dbacc)
assert.FatalError(t, err)
@ -266,6 +270,8 @@ func TestDB_GetAccount(t *testing.T) {
assert.Equals(t, acc.ID, tc.dbacc.ID)
assert.Equals(t, acc.Status, tc.dbacc.Status)
assert.Equals(t, acc.Contact, tc.dbacc.Contact)
assert.Equals(t, acc.LocationPrefix, tc.dbacc.LocationPrefix)
assert.Equals(t, acc.ProvisionerName, tc.dbacc.ProvisionerName)
assert.Equals(t, acc.Key.KeyID, tc.dbacc.Key.KeyID)
}
})
@ -379,6 +385,7 @@ func TestDB_GetAccountByKeyID(t *testing.T) {
}
func TestDB_CreateAccount(t *testing.T) {
locationPrefix := "https://test.ca.smallstep.com/acme/foo/account/"
type test struct {
db nosql.DB
acc *acme.Account
@ -390,9 +397,10 @@ func TestDB_CreateAccount(t *testing.T) {
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
acc := &acme.Account{
Status: acme.StatusValid,
Contact: []string{"foo", "bar"},
Key: jwk,
Status: acme.StatusValid,
Contact: []string{"foo", "bar"},
Key: jwk,
LocationPrefix: locationPrefix,
}
return test{
db: &db.MockNoSQLDB{
@ -413,9 +421,10 @@ func TestDB_CreateAccount(t *testing.T) {
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
acc := &acme.Account{
Status: acme.StatusValid,
Contact: []string{"foo", "bar"},
Key: jwk,
Status: acme.StatusValid,
Contact: []string{"foo", "bar"},
Key: jwk,
LocationPrefix: locationPrefix,
}
return test{
db: &db.MockNoSQLDB{
@ -436,9 +445,10 @@ func TestDB_CreateAccount(t *testing.T) {
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
acc := &acme.Account{
Status: acme.StatusValid,
Contact: []string{"foo", "bar"},
Key: jwk,
Status: acme.StatusValid,
Contact: []string{"foo", "bar"},
Key: jwk,
LocationPrefix: locationPrefix,
}
return test{
db: &db.MockNoSQLDB{
@ -456,6 +466,8 @@ func TestDB_CreateAccount(t *testing.T) {
assert.FatalError(t, json.Unmarshal(nu, dbacc))
assert.Equals(t, dbacc.ID, string(key))
assert.Equals(t, dbacc.Contact, acc.Contact)
assert.Equals(t, dbacc.LocationPrefix, acc.LocationPrefix)
assert.Equals(t, dbacc.ProvisionerName, acc.ProvisionerName)
assert.Equals(t, dbacc.Key.KeyID, acc.Key.KeyID)
assert.True(t, clock.Now().Add(-time.Minute).Before(dbacc.CreatedAt))
assert.True(t, clock.Now().Add(time.Minute).After(dbacc.CreatedAt))
@ -479,9 +491,10 @@ func TestDB_CreateAccount(t *testing.T) {
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
acc := &acme.Account{
Status: acme.StatusValid,
Contact: []string{"foo", "bar"},
Key: jwk,
Status: acme.StatusValid,
Contact: []string{"foo", "bar"},
Key: jwk,
LocationPrefix: locationPrefix,
}
return test{
db: &db.MockNoSQLDB{
@ -500,6 +513,8 @@ func TestDB_CreateAccount(t *testing.T) {
assert.FatalError(t, json.Unmarshal(nu, dbacc))
assert.Equals(t, dbacc.ID, string(key))
assert.Equals(t, dbacc.Contact, acc.Contact)
assert.Equals(t, dbacc.LocationPrefix, acc.LocationPrefix)
assert.Equals(t, dbacc.ProvisionerName, acc.ProvisionerName)
assert.Equals(t, dbacc.Key.KeyID, acc.Key.KeyID)
assert.True(t, clock.Now().Add(-time.Minute).Before(dbacc.CreatedAt))
assert.True(t, clock.Now().Add(time.Minute).After(dbacc.CreatedAt))
@ -539,12 +554,14 @@ func TestDB_UpdateAccount(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"},
LocationPrefix: "foo",
ProvisionerName: "alpha",
Key: jwk,
}
b, err := json.Marshal(dbacc)
assert.FatalError(t, err)
@ -644,10 +661,12 @@ func TestDB_UpdateAccount(t *testing.T) {
},
"ok": func(t *testing.T) test {
acc := &acme.Account{
ID: accID,
Status: acme.StatusDeactivated,
Contact: []string{"foo", "bar"},
Key: jwk,
ID: accID,
Status: acme.StatusDeactivated,
Contact: []string{"baz", "zap"},
LocationPrefix: "bar",
ProvisionerName: "beta",
Key: jwk,
}
return test{
acc: acc,
@ -666,7 +685,10 @@ func TestDB_UpdateAccount(t *testing.T) {
assert.FatalError(t, json.Unmarshal(nu, dbNew))
assert.Equals(t, dbNew.ID, dbacc.ID)
assert.Equals(t, dbNew.Status, acc.Status)
assert.Equals(t, dbNew.Contact, dbacc.Contact)
assert.Equals(t, dbNew.Contact, acc.Contact)
// LocationPrefix should not change.
assert.Equals(t, dbNew.LocationPrefix, dbacc.LocationPrefix)
assert.Equals(t, dbNew.ProvisionerName, dbacc.ProvisionerName)
assert.Equals(t, dbNew.Key.KeyID, dbacc.Key.KeyID)
assert.Equals(t, dbNew.CreatedAt, dbacc.CreatedAt)
assert.True(t, dbNew.DeactivatedAt.Add(-time.Minute).Before(now))
@ -686,12 +708,7 @@ func TestDB_UpdateAccount(t *testing.T) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
}
} else {
if assert.Nil(t, tc.err) {
assert.Equals(t, tc.acc.ID, dbacc.ID)
assert.Equals(t, tc.acc.Status, dbacc.Status)
assert.Equals(t, tc.acc.Contact, dbacc.Contact)
assert.Equals(t, tc.acc.Key.KeyID, dbacc.Key.KeyID)
}
assert.Nil(t, tc.err)
}
})
}

View file

@ -17,6 +17,7 @@ type dbAuthz struct {
Identifier acme.Identifier `json:"identifier"`
Status acme.Status `json:"status"`
Token string `json:"token"`
Fingerprint string `json:"fingerprint,omitempty"`
ChallengeIDs []string `json:"challengeIDs"`
Wildcard bool `json:"wildcard"`
CreatedAt time.Time `json:"createdAt"`
@ -31,7 +32,7 @@ func (ba *dbAuthz) clone() *dbAuthz {
// getDBAuthz retrieves and unmarshals a database representation of the
// ACME Authorization type.
func (db *DB) getDBAuthz(ctx context.Context, id string) (*dbAuthz, error) {
func (db *DB) getDBAuthz(_ context.Context, id string) (*dbAuthz, error) {
data, err := db.db.Get(authzTable, []byte(id))
if nosql.IsErrNotFound(err) {
return nil, acme.NewError(acme.ErrorMalformedType, "authz %s not found", id)
@ -61,15 +62,16 @@ func (db *DB) GetAuthorization(ctx context.Context, id string) (*acme.Authorizat
}
}
return &acme.Authorization{
ID: dbaz.ID,
AccountID: dbaz.AccountID,
Identifier: dbaz.Identifier,
Status: dbaz.Status,
Challenges: chs,
Wildcard: dbaz.Wildcard,
ExpiresAt: dbaz.ExpiresAt,
Token: dbaz.Token,
Error: dbaz.Error,
ID: dbaz.ID,
AccountID: dbaz.AccountID,
Identifier: dbaz.Identifier,
Status: dbaz.Status,
Challenges: chs,
Wildcard: dbaz.Wildcard,
ExpiresAt: dbaz.ExpiresAt,
Token: dbaz.Token,
Fingerprint: dbaz.Fingerprint,
Error: dbaz.Error,
}, nil
}
@ -97,6 +99,7 @@ func (db *DB) CreateAuthorization(ctx context.Context, az *acme.Authorization) e
Identifier: az.Identifier,
ChallengeIDs: chIDs,
Token: az.Token,
Fingerprint: az.Fingerprint,
Wildcard: az.Wildcard,
}
@ -111,14 +114,14 @@ func (db *DB) UpdateAuthorization(ctx context.Context, az *acme.Authorization) e
}
nu := old.clone()
nu.Status = az.Status
nu.Fingerprint = az.Fingerprint
nu.Error = az.Error
return db.save(ctx, old.ID, nu, old, "authz", authzTable)
}
// GetAuthorizationsByAccountID retrieves and unmarshals ACME authz types from the database.
func (db *DB) GetAuthorizationsByAccountID(ctx context.Context, accountID string) ([]*acme.Authorization, error) {
func (db *DB) GetAuthorizationsByAccountID(_ context.Context, accountID string) ([]*acme.Authorization, error) {
entries, err := db.db.List(authzTable)
if err != nil {
return nil, errors.Wrapf(err, "error listing authz")
@ -136,15 +139,16 @@ func (db *DB) GetAuthorizationsByAccountID(ctx context.Context, accountID string
continue
}
authzs = append(authzs, &acme.Authorization{
ID: dbaz.ID,
AccountID: dbaz.AccountID,
Identifier: dbaz.Identifier,
Status: dbaz.Status,
Challenges: nil, // challenges not required for current use case
Wildcard: dbaz.Wildcard,
ExpiresAt: dbaz.ExpiresAt,
Token: dbaz.Token,
Error: dbaz.Error,
ID: dbaz.ID,
AccountID: dbaz.AccountID,
Identifier: dbaz.Identifier,
Status: dbaz.Status,
Challenges: nil, // challenges not required for current use case
Wildcard: dbaz.Wildcard,
ExpiresAt: dbaz.ExpiresAt,
Token: dbaz.Token,
Fingerprint: dbaz.Fingerprint,
Error: dbaz.Error,
})
}

View file

@ -473,6 +473,7 @@ func TestDB_UpdateAuthorization(t *testing.T) {
ExpiresAt: now.Add(5 * time.Minute),
ChallengeIDs: []string{"foo", "bar"},
Wildcard: true,
Fingerprint: "fingerprint",
}
b, err := json.Marshal(dbaz)
assert.FatalError(t, err)
@ -549,10 +550,11 @@ func TestDB_UpdateAuthorization(t *testing.T) {
{ID: "foo"},
{ID: "bar"},
},
Token: dbaz.Token,
Wildcard: dbaz.Wildcard,
ExpiresAt: dbaz.ExpiresAt,
Error: acme.NewError(acme.ErrorMalformedType, "malformed"),
Token: dbaz.Token,
Wildcard: dbaz.Wildcard,
ExpiresAt: dbaz.ExpiresAt,
Fingerprint: "fingerprint",
Error: acme.NewError(acme.ErrorMalformedType, "malformed"),
}
return test{
az: updAz,
@ -582,6 +584,7 @@ func TestDB_UpdateAuthorization(t *testing.T) {
assert.Equals(t, dbNew.Wildcard, dbaz.Wildcard)
assert.Equals(t, dbNew.CreatedAt, dbaz.CreatedAt)
assert.Equals(t, dbNew.ExpiresAt, dbaz.ExpiresAt)
assert.Equals(t, dbNew.Fingerprint, dbaz.Fingerprint)
assert.Equals(t, dbNew.Error.Error(), acme.NewError(acme.ErrorMalformedType, "The request message was malformed").Error())
return nu, true, nil
},

View file

@ -69,7 +69,7 @@ func (db *DB) CreateCertificate(ctx context.Context, cert *acme.Certificate) err
// GetCertificate retrieves and unmarshals an ACME certificate type from the
// datastore.
func (db *DB) GetCertificate(ctx context.Context, id string) (*acme.Certificate, error) {
func (db *DB) GetCertificate(_ context.Context, id string) (*acme.Certificate, error) {
b, err := db.db.Get(certTable, []byte(id))
if nosql.IsErrNotFound(err) {
return nil, acme.NewError(acme.ErrorMalformedType, "certificate %s not found", id)

View file

@ -29,7 +29,7 @@ func (dbc *dbChallenge) clone() *dbChallenge {
return &u
}
func (db *DB) getDBChallenge(ctx context.Context, id string) (*dbChallenge, error) {
func (db *DB) getDBChallenge(_ context.Context, id string) (*dbChallenge, error) {
data, err := db.db.Get(challengeTable, []byte(id))
if nosql.IsErrNotFound(err) {
return nil, acme.NewError(acme.ErrorMalformedType, "challenge %s not found", id)
@ -69,6 +69,7 @@ func (db *DB) CreateChallenge(ctx context.Context, ch *acme.Challenge) error {
// GetChallenge retrieves and unmarshals an ACME challenge type from the database.
// Implements the acme.DB GetChallenge interface.
func (db *DB) GetChallenge(ctx context.Context, id, authzID string) (*acme.Challenge, error) {
_ = authzID // unused input
dbch, err := db.getDBChallenge(ctx, id)
if err != nil {
return nil, err

View file

@ -35,7 +35,7 @@ type dbExternalAccountKeyReference struct {
}
// getDBExternalAccountKey retrieves and unmarshals dbExternalAccountKey.
func (db *DB) getDBExternalAccountKey(ctx context.Context, id string) (*dbExternalAccountKey, error) {
func (db *DB) getDBExternalAccountKey(_ context.Context, id string) (*dbExternalAccountKey, error) {
data, err := db.db.Get(externalAccountKeyTable, []byte(id))
if err != nil {
if nosqlDB.IsErrNotFound(err) {
@ -160,6 +160,8 @@ func (db *DB) DeleteExternalAccountKey(ctx context.Context, provisionerID, keyID
// GetExternalAccountKeys retrieves all External Account Binding keys for a provisioner
func (db *DB) GetExternalAccountKeys(ctx context.Context, provisionerID, cursor string, limit int) ([]*acme.ExternalAccountKey, string, error) {
_, _ = cursor, limit // unused input
externalAccountKeyMutex.RLock()
defer externalAccountKeyMutex.RUnlock()
@ -227,7 +229,7 @@ func (db *DB) GetExternalAccountKeyByReference(ctx context.Context, provisionerI
return db.GetExternalAccountKey(ctx, provisionerID, dbExternalAccountKeyReference.ExternalAccountKeyID)
}
func (db *DB) GetExternalAccountKeyByAccountID(ctx context.Context, provisionerID, accountID string) (*acme.ExternalAccountKey, error) {
func (db *DB) GetExternalAccountKeyByAccountID(context.Context, string, string) (*acme.ExternalAccountKey, error) {
//nolint:nilnil // legacy
return nil, nil
}

View file

@ -39,7 +39,7 @@ func (db *DB) CreateNonce(ctx context.Context) (acme.Nonce, error) {
// DeleteNonce verifies that the nonce is valid (by checking if it exists),
// and if so, consumes the nonce resource by deleting it from the database.
func (db *DB) DeleteNonce(ctx context.Context, nonce acme.Nonce) error {
func (db *DB) DeleteNonce(_ context.Context, nonce acme.Nonce) error {
err := db.db.Update(&database.Tx{
Operations: []*database.TxEntry{
{

View file

@ -48,7 +48,7 @@ func New(db nosqlDB.DB) (*DB, error) {
// save writes the new data to the database, overwriting the old data if it
// existed.
func (db *DB) save(ctx context.Context, id string, nu, old interface{}, typ string, table []byte) error {
func (db *DB) save(_ context.Context, id string, nu, old interface{}, typ string, table []byte) error {
var (
err error
newB []byte

View file

@ -35,7 +35,7 @@ func (a *dbOrder) clone() *dbOrder {
}
// getDBOrder retrieves and unmarshals an ACME Order type from the database.
func (db *DB) getDBOrder(ctx context.Context, id string) (*dbOrder, error) {
func (db *DB) getDBOrder(_ context.Context, id string) (*dbOrder, error) {
b, err := db.db.Get(orderTable, []byte(id))
if nosql.IsErrNotFound(err) {
return nil, acme.NewError(acme.ErrorMalformedType, "order %s not found", id)

View file

@ -65,6 +65,8 @@ const (
ErrorUserActionRequiredType
// ErrorNotImplementedType operation is not implemented
ErrorNotImplementedType
// ErrorNNSType was a problem with a NNS query during identifier validation
ErrorNNSType
)
// String returns the string representation of the acme problem type,
@ -121,6 +123,8 @@ func (ap ProblemType) String() string {
return "userActionRequired"
case ErrorNotImplementedType:
return "notImplemented"
case ErrorNNSType:
return "nns"
default:
return fmt.Sprintf("unsupported type ACME error type '%d'", int(ap))
}

122
acme/nns.go Normal file
View file

@ -0,0 +1,122 @@
package acme
import (
"context"
"errors"
"fmt"
"net/url"
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
// multiSchemeClient unites invoker.RPCInvoke and common interface of
// rpcclient.Client and rpcclient.WSClient.
type multiSchemeClient interface {
invoker.RPCInvoke
// Init turns client to "ready-to-work" state.
Init() error
// Close closes connections.
Close()
// GetContractStateByID returns state of the NNS contract on 1 input.
GetContractStateByID(int32) (*state.Contract, error)
}
// NNS is used to interact with NNS contract.
// Before work, the connection to the NNS server must be established using Dial method.
type NNS struct {
nnsContract util.Uint160
client multiSchemeClient
}
// NNSContext is used to store info about NNS server.
type NNSContext struct {
nnsServer string
}
type nnsKey struct{}
// NewNNSContext adds new NNSContext with given params to the context.
func NewNNSContext(ctx context.Context, nnsServer string) context.Context {
return context.WithValue(ctx, nnsKey{}, NNSContext{nnsServer: nnsServer})
}
// GetNNSContext returns NNSContext from the given context.
func GetNNSContext(ctx context.Context) (NNSContext, bool) {
c, ok := ctx.Value(nnsKey{}).(NNSContext)
return c, ok
}
// Dial connects to the address of the NNS server.
// If URL address scheme is 'ws' or 'wss', then WebSocket protocol is used, otherwise HTTP.
func (n *NNS) Dial(address string) error {
var err error
uri, err := url.Parse(address)
if err == nil && (uri.Scheme == "ws" || uri.Scheme == "wss") {
n.client, err = rpcclient.NewWS(context.Background(), address, rpcclient.WSOptions{})
if err != nil {
return fmt.Errorf("create Neo WebSocket client: %w", err)
}
} else {
n.client, err = rpcclient.New(context.Background(), address, rpcclient.Options{})
if err != nil {
return fmt.Errorf("create Neo HTTP client: %w", err)
}
}
if err = n.client.Init(); err != nil {
return fmt.Errorf("initialize Neo client: %w", err)
}
nnsContract, err := n.client.GetContractStateByID(1)
if err != nil {
return fmt.Errorf("get NNS contract state: %w", err)
}
n.nnsContract = nnsContract.Hash
return nil
}
// Close closes connections of multiSchemeClient.
func (n *NNS) Close() {
n.client.Close()
}
// GetTXTRecords returns TXT records of the provided domain by calling `getRecords` method of NNS contract.
func (n *NNS) GetTXTRecords(name string) ([]string, error) {
params, err := smartcontract.NewParametersFromValues(name, int64(nns.TXT))
if err != nil {
return make([]string, 0), fmt.Errorf("create slice of params: %w", err)
}
item, err := unwrap.Item(n.client.InvokeFunction(n.nnsContract, "getRecords", params, nil))
if err != nil {
return make([]string, 0), fmt.Errorf("contract invocation: %w", err)
}
if _, ok := item.(stackitem.Null); !ok {
arr, ok := item.Value().([]stackitem.Item)
if !ok {
return make([]string, 0), errors.New("invalid cast to stack item slice")
}
var result = make([]string, 0, len(arr))
for i := range arr {
recordValue, err := arr[i].TryBytes()
if err != nil {
return make([]string, 0), fmt.Errorf("convert array item to byte slice: %w", err)
}
result = append(result, string(recordValue))
}
return result, nil
}
return make([]string, 0), errors.New("records not found")
}

View file

@ -3,6 +3,7 @@ package acme
import (
"bytes"
"context"
"crypto/subtle"
"crypto/x509"
"encoding/json"
"net"
@ -11,6 +12,7 @@ import (
"time"
"github.com/smallstep/certificates/authority/provisioner"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/x509util"
)
@ -125,6 +127,27 @@ func (o *Order) UpdateStatus(ctx context.Context, db DB) error {
return nil
}
// getKeyFingerprint 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.
//
// There's no point on reading all the authorizations as there will be only one
// for a permanent identifier.
func (o *Order) getAuthorizationFingerprint(ctx context.Context, db DB) (string, error) {
for _, azID := range o.AuthorizationIDs {
az, err := db.GetAuthorization(ctx, azID)
if err != nil {
return "", WrapErrorISE(err, "error getting authorization %q", azID)
}
// There's no point on reading all the authorizations as there will
// be only one for a permanent identifier.
if az.Fingerprint != "" {
return az.Fingerprint, nil
}
}
return "", nil
}
// Finalize signs a certificate if the necessary conditions for Order completion
// have been met.
//
@ -150,6 +173,24 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques
return NewErrorISE("unexpected status %s for order %s", o.Status, o.ID)
}
// Get key fingerprint if any. And then compare it with the CSR fingerprint.
//
// In device-attest-01 challenges we should check that the keys in the CSR
// and the attestation certificate are the same.
fingerprint, err := o.getAuthorizationFingerprint(ctx, db)
if err != nil {
return err
}
if fingerprint != "" {
fp, err := keyutil.Fingerprint(csr.PublicKey)
if err != nil {
return WrapErrorISE(err, "error calculating key fingerprint")
}
if subtle.ConstantTimeCompare([]byte(fingerprint), []byte(fp)) == 0 {
return NewError(ErrorUnauthorizedType, "order %s csr does not match the attested key", o.ID)
}
}
// canonicalize the CSR to allow for comparison
csr = canonicalize(csr)

View file

@ -2,6 +2,7 @@ package acme
import (
"context"
"crypto"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
@ -18,6 +19,7 @@ import (
"github.com/smallstep/assert"
"github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/authority/provisioner"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/x509util"
)
@ -299,7 +301,7 @@ func (m *mockSignAuth) LoadProvisionerByName(name string) (provisioner.Interface
return m.ret1.(provisioner.Interface), m.err
}
func (m *mockSignAuth) IsRevoked(sn string) (bool, error) {
func (m *mockSignAuth) IsRevoked(string) (bool, error) {
return false, nil
}
@ -308,6 +310,14 @@ func (m *mockSignAuth) Revoke(context.Context, *authority.RevokeOptions) error {
}
func TestOrder_Finalize(t *testing.T) {
mustSigner := func(kty, crv string, size int) crypto.Signer {
s, err := keyutil.GenerateSigner(kty, crv, size)
if err != nil {
t.Fatal(err)
}
return s
}
type test struct {
o *Order
err *Error
@ -400,10 +410,18 @@ func TestOrder_Finalize(t *testing.T) {
{Type: "permanent-identifier", Value: "a-permanent-identifier"},
},
}
signer := mustSigner("EC", "P-256", 0)
fingerprint, err := keyutil.Fingerprint(signer.Public())
if err != nil {
t.Fatal(err)
}
csr := &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: "a-different-identifier",
},
PublicKey: signer.Public(),
ExtraExtensions: []pkix.Extension{
{
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 3},
@ -414,6 +432,29 @@ func TestOrder_Finalize(t *testing.T) {
return test{
o: o,
csr: csr,
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
switch id {
case "a":
return &Authorization{
ID: id,
Status: StatusValid,
}, nil
case "b":
return &Authorization{
ID: id,
Fingerprint: fingerprint,
Status: StatusValid,
}, nil
default:
assert.FatalError(t, errors.Errorf("unexpected authorization %s", id))
return nil, errors.New("force")
}
},
MockUpdateOrder: func(ctx context.Context, o *Order) error {
return nil
},
},
err: &Error{
Type: "urn:ietf:params:acme:error:badCSR",
Detail: "The CSR is unacceptable",
@ -452,6 +493,11 @@ func TestOrder_Finalize(t *testing.T) {
return nil, errors.New("force")
},
},
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return &Authorization{ID: id, Status: StatusValid}, nil
},
},
err: NewErrorISE("error retrieving authorization options from ACME provisioner: force"),
}
},
@ -491,6 +537,11 @@ func TestOrder_Finalize(t *testing.T) {
}
},
},
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return &Authorization{ID: id, Status: StatusValid}, nil
},
},
err: NewErrorISE("error creating template options from ACME provisioner: error unmarshaling template data: invalid character 'o' in literal false (expecting 'a')"),
}
},
@ -532,6 +583,11 @@ func TestOrder_Finalize(t *testing.T) {
return nil, errors.New("force")
},
},
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return &Authorization{ID: id, Status: StatusValid}, nil
},
},
err: NewErrorISE("error signing certificate for order oID: force"),
}
},
@ -578,6 +634,9 @@ func TestOrder_Finalize(t *testing.T) {
},
},
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return &Authorization{ID: id, Status: StatusValid}, nil
},
MockCreateCertificate: func(ctx context.Context, cert *Certificate) error {
assert.Equals(t, cert.AccountID, o.AccountID)
assert.Equals(t, cert.OrderID, o.ID)
@ -632,6 +691,9 @@ func TestOrder_Finalize(t *testing.T) {
},
},
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return &Authorization{ID: id, Status: StatusValid}, nil
},
MockCreateCertificate: func(ctx context.Context, cert *Certificate) error {
cert.ID = "certID"
assert.Equals(t, cert.AccountID, o.AccountID)
@ -654,7 +716,7 @@ func TestOrder_Finalize(t *testing.T) {
err: NewErrorISE("error updating order oID: force"),
}
},
"ok/permanent-identifier": func(t *testing.T) test {
"fail/csr-fingerprint": func(t *testing.T) test {
now := clock.Now()
o := &Order{
ID: "oID",
@ -666,10 +728,14 @@ func TestOrder_Finalize(t *testing.T) {
{Type: "permanent-identifier", Value: "a-permanent-identifier"},
},
}
signer := mustSigner("EC", "P-256", 0)
csr := &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: "a-permanent-identifier",
},
PublicKey: signer.Public(),
ExtraExtensions: []pkix.Extension{
{
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 3},
@ -679,7 +745,8 @@ func TestOrder_Finalize(t *testing.T) {
}
leaf := &x509.Certificate{
Subject: pkix.Name{CommonName: "a-permanent-identifier"},
Subject: pkix.Name{CommonName: "a-permanent-identifier"},
PublicKey: signer.Public(),
ExtraExtensions: []pkix.Extension{
{
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 3},
@ -709,6 +776,117 @@ func TestOrder_Finalize(t *testing.T) {
},
},
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return &Authorization{
ID: id,
Fingerprint: "other-fingerprint",
Status: StatusValid,
}, nil
},
MockCreateCertificate: func(ctx context.Context, cert *Certificate) error {
cert.ID = "certID"
assert.Equals(t, cert.AccountID, o.AccountID)
assert.Equals(t, cert.OrderID, o.ID)
assert.Equals(t, cert.Leaf, leaf)
assert.Equals(t, cert.Intermediates, []*x509.Certificate{inter, root})
return nil
},
MockUpdateOrder: func(ctx context.Context, updo *Order) error {
assert.Equals(t, updo.CertificateID, "certID")
assert.Equals(t, updo.Status, StatusValid)
assert.Equals(t, updo.ID, o.ID)
assert.Equals(t, updo.AccountID, o.AccountID)
assert.Equals(t, updo.ExpiresAt, o.ExpiresAt)
assert.Equals(t, updo.AuthorizationIDs, o.AuthorizationIDs)
assert.Equals(t, updo.Identifiers, o.Identifiers)
return nil
},
},
err: NewError(ErrorUnauthorizedType, "order oID csr does not match the attested key"),
}
},
"ok/permanent-identifier": func(t *testing.T) test {
now := clock.Now()
o := &Order{
ID: "oID",
AccountID: "accID",
Status: StatusReady,
ExpiresAt: now.Add(5 * time.Minute),
AuthorizationIDs: []string{"a", "b"},
Identifiers: []Identifier{
{Type: "permanent-identifier", Value: "a-permanent-identifier"},
},
}
signer := mustSigner("EC", "P-256", 0)
fingerprint, err := keyutil.Fingerprint(signer.Public())
if err != nil {
t.Fatal(err)
}
csr := &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: "a-permanent-identifier",
},
PublicKey: signer.Public(),
ExtraExtensions: []pkix.Extension{
{
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 3},
Value: []byte("a-permanent-identifier"),
},
},
}
leaf := &x509.Certificate{
Subject: pkix.Name{CommonName: "a-permanent-identifier"},
PublicKey: signer.Public(),
ExtraExtensions: []pkix.Extension{
{
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 3},
Value: []byte("a-permanent-identifier"),
},
},
}
inter := &x509.Certificate{Subject: pkix.Name{CommonName: "inter"}}
root := &x509.Certificate{Subject: pkix.Name{CommonName: "root"}}
return test{
o: o,
csr: csr,
prov: &MockProvisioner{
MauthorizeSign: func(ctx context.Context, token string) ([]provisioner.SignOption, error) {
assert.Equals(t, token, "")
return nil, nil
},
MgetOptions: func() *provisioner.Options {
return nil
},
},
ca: &mockSignAuth{
sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, _csr, csr)
return []*x509.Certificate{leaf, inter, root}, nil
},
},
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
switch id {
case "a":
return &Authorization{
ID: id,
Status: StatusValid,
}, nil
case "b":
return &Authorization{
ID: id,
Fingerprint: fingerprint,
Status: StatusValid,
}, nil
default:
assert.FatalError(t, errors.Errorf("unexpected authorization %s", id))
return nil, errors.New("force")
}
},
MockCreateCertificate: func(ctx context.Context, cert *Certificate) error {
cert.ID = "certID"
assert.Equals(t, cert.AccountID, o.AccountID)
@ -743,11 +921,19 @@ func TestOrder_Finalize(t *testing.T) {
{Type: "permanent-identifier", Value: "a-permanent-identifier"},
},
}
signer := mustSigner("EC", "P-256", 0)
fingerprint, err := keyutil.Fingerprint(signer.Public())
if err != nil {
t.Fatal(err)
}
csr := &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: "a-permanent-identifier",
},
DNSNames: []string{"foo.internal"},
DNSNames: []string{"foo.internal"},
PublicKey: signer.Public(),
ExtraExtensions: []pkix.Extension{
{
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 3},
@ -757,7 +943,8 @@ func TestOrder_Finalize(t *testing.T) {
}
leaf := &x509.Certificate{
Subject: pkix.Name{CommonName: "a-permanent-identifier"},
Subject: pkix.Name{CommonName: "a-permanent-identifier"},
PublicKey: signer.Public(),
ExtraExtensions: []pkix.Extension{
{
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 3},
@ -792,6 +979,13 @@ func TestOrder_Finalize(t *testing.T) {
},
},
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return &Authorization{
ID: id,
Fingerprint: fingerprint,
Status: StatusValid,
}, nil
},
MockCreateCertificate: func(ctx context.Context, cert *Certificate) error {
cert.ID = "certID"
assert.Equals(t, cert.AccountID, o.AccountID)
@ -856,6 +1050,9 @@ func TestOrder_Finalize(t *testing.T) {
},
},
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return &Authorization{ID: id, Status: StatusValid}, nil
},
MockCreateCertificate: func(ctx context.Context, cert *Certificate) error {
cert.ID = "certID"
assert.Equals(t, cert.AccountID, o.AccountID)
@ -917,6 +1114,9 @@ func TestOrder_Finalize(t *testing.T) {
},
},
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return &Authorization{ID: id, Status: StatusValid}, nil
},
MockCreateCertificate: func(ctx context.Context, cert *Certificate) error {
cert.ID = "certID"
assert.Equals(t, cert.AccountID, o.AccountID)
@ -981,6 +1181,9 @@ func TestOrder_Finalize(t *testing.T) {
},
},
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return &Authorization{ID: id, Status: StatusValid}, nil
},
MockCreateCertificate: func(ctx context.Context, cert *Certificate) error {
cert.ID = "certID"
assert.Equals(t, cert.AccountID, o.AccountID)
@ -1688,3 +1891,55 @@ func TestOrder_sans(t *testing.T) {
})
}
}
func TestOrder_getAuthorizationFingerprint(t *testing.T) {
ctx := context.Background()
type fields struct {
AuthorizationIDs []string
}
type args struct {
ctx context.Context
db DB
}
tests := []struct {
name string
fields fields
args args
want string
wantErr bool
}{
{"ok", fields{[]string{"az1", "az2"}}, args{ctx, &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return &Authorization{ID: id, Status: StatusValid}, nil
},
}}, "", false},
{"ok fingerprint", fields{[]string{"az1", "az2"}}, args{ctx, &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
if id == "az1" {
return &Authorization{ID: id, Status: StatusValid}, nil
}
return &Authorization{ID: id, Fingerprint: "fingerprint", Status: StatusValid}, nil
},
}}, "fingerprint", false},
{"fail", fields{[]string{"az1", "az2"}}, args{ctx, &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return nil, errors.New("force")
},
}}, "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := &Order{
AuthorizationIDs: tt.fields.AuthorizationIDs,
}
got, err := o.getAuthorizationFingerprint(tt.args.ctx, tt.args.db)
if (err != nil) != tt.wantErr {
t.Errorf("Order.getAuthorizationFingerprint() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("Order.getAuthorizationFingerprint() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -1,6 +1,7 @@
package api
import (
"bytes"
"context"
"crypto"
"crypto/dsa" //nolint:staticcheck // support legacy algorithms
@ -20,6 +21,8 @@ import (
"github.com/go-chi/chi"
"github.com/pkg/errors"
"go.step.sm/crypto/sshutil"
"golang.org/x/crypto/ssh"
"github.com/smallstep/certificates/api/log"
"github.com/smallstep/certificates/api/render"
@ -224,8 +227,39 @@ type RootResponse struct {
// ProvisionersResponse is the response object that returns the list of
// provisioners.
type ProvisionersResponse struct {
Provisioners provisioner.List `json:"provisioners"`
NextCursor string `json:"nextCursor"`
Provisioners provisioner.List
NextCursor string
}
// MarshalJSON implements json.Marshaler. It marshals the ProvisionersResponse
// into a byte slice.
//
// Special treatment is given to the SCEP provisioner, as it contains a
// challenge secret that MUST NOT be leaked in (public) HTTP responses. The
// challenge value is thus redacted in HTTP responses.
func (p ProvisionersResponse) MarshalJSON() ([]byte, error) {
for _, item := range p.Provisioners {
scepProv, ok := item.(*provisioner.SCEP)
if !ok {
continue
}
old := scepProv.ChallengePassword
scepProv.ChallengePassword = "*** REDACTED ***"
defer func(p string) { //nolint:gocritic // defer in loop required to restore initial state of provisioners
scepProv.ChallengePassword = p
}(old)
}
var list = struct {
Provisioners []provisioner.Interface `json:"provisioners"`
NextCursor string `json:"nextCursor"`
}{
Provisioners: []provisioner.Interface(p.Provisioners),
NextCursor: p.NextCursor,
}
return json.Marshal(list)
}
// ProvisionerKeyResponse is the response object that returns the encrypted key
@ -257,7 +291,7 @@ func (h *caHandler) Route(r Router) {
// New creates a new RouterHandler with the CA endpoints.
//
// Deprecated: Use api.Route(r Router)
func New(auth Authority) RouterHandler {
func New(Authority) RouterHandler {
return &caHandler{}
}
@ -304,7 +338,7 @@ func Version(w http.ResponseWriter, r *http.Request) {
}
// Health is an HTTP handler that returns the status of the server.
func Health(w http.ResponseWriter, r *http.Request) {
func Health(w http.ResponseWriter, _ *http.Request) {
render.JSON(w, HealthResponse{Status: "ok"})
}
@ -438,7 +472,7 @@ func logOtt(w http.ResponseWriter, token string) {
}
}
// LogCertificate add certificate fields to the log message.
// LogCertificate adds certificate fields to the log message.
func LogCertificate(w http.ResponseWriter, cert *x509.Certificate) {
if rl, ok := w.(logging.ResponseLogger); ok {
m := map[string]interface{}{
@ -470,6 +504,41 @@ func LogCertificate(w http.ResponseWriter, cert *x509.Certificate) {
}
}
// LogSSHCertificate adds SSH certificate fields to the log message.
func LogSSHCertificate(w http.ResponseWriter, cert *ssh.Certificate) {
if rl, ok := w.(logging.ResponseLogger); ok {
mak := bytes.TrimSpace(ssh.MarshalAuthorizedKey(cert))
var certificate string
parts := strings.Split(string(mak), " ")
if len(parts) > 1 {
certificate = parts[1]
}
var userOrHost string
if cert.CertType == ssh.HostCert {
userOrHost = "host"
} else {
userOrHost = "user"
}
certificateType := fmt.Sprintf("%s %s certificate", parts[0], userOrHost) // e.g. ecdsa-sha2-nistp256-cert-v01@openssh.com user certificate
m := map[string]interface{}{
"serial": cert.Serial,
"principals": cert.ValidPrincipals,
"valid-from": time.Unix(int64(cert.ValidAfter), 0).Format(time.RFC3339),
"valid-to": time.Unix(int64(cert.ValidBefore), 0).Format(time.RFC3339),
"certificate": certificate,
"certificate-type": certificateType,
}
fingerprint, err := sshutil.FormatFingerprint(mak, sshutil.DefaultFingerprint)
if err == nil {
fpParts := strings.Split(fingerprint, " ")
if len(fpParts) > 3 {
m["public-key"] = fmt.Sprintf("%s %s", fpParts[1], fpParts[len(fpParts)-1])
}
}
rl.WithFields(m)
}
}
// ParseCursor parses the cursor and limit from the request query params.
func ParseCursor(r *http.Request) (cursor string, limit int, err error) {
q := r.URL.Query()

View file

@ -4,7 +4,7 @@ import (
"bytes"
"context"
"crypto"
"crypto/dsa" //nolint
"crypto/dsa" //nolint:staticcheck // support legacy algorithms
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
@ -28,12 +28,15 @@ import (
"github.com/go-chi/chi"
"github.com/pkg/errors"
"golang.org/x/crypto/ssh"
sassert "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/x509util"
"golang.org/x/crypto/ssh"
squarejose "gopkg.in/square/go-jose.v2"
"github.com/smallstep/assert"
"github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/errs"
@ -1564,3 +1567,122 @@ func mustCertificate(t *testing.T, pub, priv interface{}) *x509.Certificate {
}
return cert
}
func TestProvisionersResponse_MarshalJSON(t *testing.T) {
k := map[string]any{
"use": "sig",
"kty": "EC",
"kid": "4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc",
"crv": "P-256",
"alg": "ES256",
"x": "7ZdAAMZCFU4XwgblI5RfZouBi8lYmF6DlZusNNnsbm8",
"y": "sQr2JdzwD2fgyrymBEXWsxDxFNjjqN64qLLSbLdLZ9Y",
}
key := squarejose.JSONWebKey{}
b, err := json.Marshal(k)
assert.FatalError(t, err)
err = json.Unmarshal(b, &key)
assert.FatalError(t, err)
r := ProvisionersResponse{
Provisioners: provisioner.List{
&provisioner.SCEP{
Name: "scep",
Type: "scep",
ChallengePassword: "not-so-secret",
MinimumPublicKeyLength: 2048,
EncryptionAlgorithmIdentifier: 2,
},
&provisioner.JWK{
EncryptedKey: "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6IlhOdmYxQjgxSUlLMFA2NUkwcmtGTGcifQ.XaN9zcPQeWt49zchUDm34FECUTHfQTn_.tmNHPQDqR3ebsWfd.9WZr3YVdeOyJh36vvx0VlRtluhvYp4K7jJ1KGDr1qypwZ3ziBVSNbYYQ71du7fTtrnfG1wgGTVR39tWSzBU-zwQ5hdV3rpMAaEbod5zeW6SHd95H3Bvcb43YiiqJFNL5sGZzFb7FqzVmpsZ1efiv6sZaGDHtnCAL6r12UG5EZuqGfM0jGCZitUz2m9TUKXJL5DJ7MOYbFfkCEsUBPDm_TInliSVn2kMJhFa0VOe5wZk5YOuYM3lNYW64HGtbf-llN2Xk-4O9TfeSPizBx9ZqGpeu8pz13efUDT2WL9tWo6-0UE-CrG0bScm8lFTncTkHcu49_a5NaUBkYlBjEiw.thPcx3t1AUcWuEygXIY3Fg",
Key: &key,
Name: "step-cli",
Type: "JWK",
},
},
NextCursor: "next",
}
expected := map[string]any{
"provisioners": []map[string]any{
{
"type": "scep",
"name": "scep",
"challenge": "*** REDACTED ***",
"minimumPublicKeyLength": 2048,
"encryptionAlgorithmIdentifier": 2,
},
{
"type": "JWK",
"name": "step-cli",
"key": map[string]any{
"use": "sig",
"kty": "EC",
"kid": "4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc",
"crv": "P-256",
"alg": "ES256",
"x": "7ZdAAMZCFU4XwgblI5RfZouBi8lYmF6DlZusNNnsbm8",
"y": "sQr2JdzwD2fgyrymBEXWsxDxFNjjqN64qLLSbLdLZ9Y",
},
"encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6IlhOdmYxQjgxSUlLMFA2NUkwcmtGTGcifQ.XaN9zcPQeWt49zchUDm34FECUTHfQTn_.tmNHPQDqR3ebsWfd.9WZr3YVdeOyJh36vvx0VlRtluhvYp4K7jJ1KGDr1qypwZ3ziBVSNbYYQ71du7fTtrnfG1wgGTVR39tWSzBU-zwQ5hdV3rpMAaEbod5zeW6SHd95H3Bvcb43YiiqJFNL5sGZzFb7FqzVmpsZ1efiv6sZaGDHtnCAL6r12UG5EZuqGfM0jGCZitUz2m9TUKXJL5DJ7MOYbFfkCEsUBPDm_TInliSVn2kMJhFa0VOe5wZk5YOuYM3lNYW64HGtbf-llN2Xk-4O9TfeSPizBx9ZqGpeu8pz13efUDT2WL9tWo6-0UE-CrG0bScm8lFTncTkHcu49_a5NaUBkYlBjEiw.thPcx3t1AUcWuEygXIY3Fg",
},
},
"nextCursor": "next",
}
expBytes, err := json.Marshal(expected)
sassert.NoError(t, err)
br, err := r.MarshalJSON()
sassert.NoError(t, err)
sassert.JSONEq(t, string(expBytes), string(br))
keyCopy := key
expList := provisioner.List{
&provisioner.SCEP{
Name: "scep",
Type: "scep",
ChallengePassword: "not-so-secret",
MinimumPublicKeyLength: 2048,
EncryptionAlgorithmIdentifier: 2,
},
&provisioner.JWK{
EncryptedKey: "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6IlhOdmYxQjgxSUlLMFA2NUkwcmtGTGcifQ.XaN9zcPQeWt49zchUDm34FECUTHfQTn_.tmNHPQDqR3ebsWfd.9WZr3YVdeOyJh36vvx0VlRtluhvYp4K7jJ1KGDr1qypwZ3ziBVSNbYYQ71du7fTtrnfG1wgGTVR39tWSzBU-zwQ5hdV3rpMAaEbod5zeW6SHd95H3Bvcb43YiiqJFNL5sGZzFb7FqzVmpsZ1efiv6sZaGDHtnCAL6r12UG5EZuqGfM0jGCZitUz2m9TUKXJL5DJ7MOYbFfkCEsUBPDm_TInliSVn2kMJhFa0VOe5wZk5YOuYM3lNYW64HGtbf-llN2Xk-4O9TfeSPizBx9ZqGpeu8pz13efUDT2WL9tWo6-0UE-CrG0bScm8lFTncTkHcu49_a5NaUBkYlBjEiw.thPcx3t1AUcWuEygXIY3Fg",
Key: &keyCopy,
Name: "step-cli",
Type: "JWK",
},
}
// MarshalJSON must not affect the struct properties itself
sassert.Equal(t, expList, r.Provisioners)
}
const (
fixtureECDSACertificate = `ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgLnkvSk4odlo3b1R+RDw+LmorL3RkN354IilCIVFVen4AAAAIbmlzdHAyNTYAAABBBHjKHss8WM2ffMYlavisoLXR0I6UEIU+cidV1ogEH1U6+/SYaFPrlzQo0tGLM5CNkMbhInbyasQsrHzn8F1Rt7nHg5/tcSf9qwAAAAEAAAAGaGVybWFuAAAACgAAAAZoZXJtYW4AAAAAY8kvJwAAAABjyhBjAAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAEEE/ayqpPrZZF5uA1UlDt4FreTf15agztQIzpxnWq/XoxAHzagRSkFGkdgFpjgsfiRpP8URHH3BZScqc0ZDCTxhoQAAAGQAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAEkAAAAhAJuP1wCVwoyrKrEtHGfFXrVbRHySDjvXtS1tVTdHyqymAAAAIBa/CSSzfZb4D2NLP+eEmOOMJwSjYOiNM8fiOoAaqglI herman`
)
func TestLogSSHCertificate(t *testing.T) {
out, _, _, _, err := ssh.ParseAuthorizedKey([]byte(fixtureECDSACertificate))
require.NoError(t, err)
cert, ok := out.(*ssh.Certificate)
require.True(t, ok)
w := httptest.NewRecorder()
rl := logging.NewResponseLogger(w)
LogSSHCertificate(rl, cert)
sassert.Equal(t, 200, w.Result().StatusCode)
fields := rl.Fields()
sassert.Equal(t, uint64(14376510277651266987), fields["serial"])
sassert.Equal(t, []string{"herman"}, fields["principals"])
sassert.Equal(t, "ecdsa-sha2-nistp256-cert-v01@openssh.com user certificate", fields["certificate-type"])
sassert.Equal(t, time.Unix(1674129191, 0).Format(time.RFC3339), fields["valid-from"])
sassert.Equal(t, time.Unix(1674186851, 0).Format(time.RFC3339), fields["valid-to"])
sassert.Equal(t, "AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgLnkvSk4odlo3b1R+RDw+LmorL3RkN354IilCIVFVen4AAAAIbmlzdHAyNTYAAABBBHjKHss8WM2ffMYlavisoLXR0I6UEIU+cidV1ogEH1U6+/SYaFPrlzQo0tGLM5CNkMbhInbyasQsrHzn8F1Rt7nHg5/tcSf9qwAAAAEAAAAGaGVybWFuAAAACgAAAAZoZXJtYW4AAAAAY8kvJwAAAABjyhBjAAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAEEE/ayqpPrZZF5uA1UlDt4FreTf15agztQIzpxnWq/XoxAHzagRSkFGkdgFpjgsfiRpP8URHH3BZScqc0ZDCTxhoQAAAGQAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAEkAAAAhAJuP1wCVwoyrKrEtHGfFXrVbRHySDjvXtS1tVTdHyqymAAAAIBa/CSSzfZb4D2NLP+eEmOOMJwSjYOiNM8fiOoAaqglI", fields["certificate"])
sassert.Equal(t, "SHA256:RvkDPGwl/G9d7LUFm1kmWhvOD9I/moPq4yxcb0STwr0 (ECDSA-CERT)", fields["public-key"])
}

View file

@ -17,13 +17,13 @@ func CRL(w http.ResponseWriter, r *http.Request) {
_, formatAsPEM := r.URL.Query()["pem"]
if formatAsPEM {
pemBytes := pem.EncodeToMemory(&pem.Block{
w.Header().Add("Content-Type", "application/x-pem-file")
w.Header().Add("Content-Disposition", "attachment; filename=\"crl.pem\"")
_ = pem.Encode(w, &pem.Block{
Type: "X509 CRL",
Bytes: crlBytes,
})
w.Header().Add("Content-Type", "application/x-pem-file")
w.Header().Add("Content-Disposition", "attachment; filename=\"crl.pem\"")
w.Write(pemBytes)
} else {
w.Header().Add("Content-Type", "application/pkix-crl")
w.Header().Add("Content-Disposition", "attachment; filename=\"crl.der\"")

View file

@ -2,7 +2,6 @@
package render
import (
"bytes"
"encoding/json"
"errors"
"net/http"
@ -24,14 +23,25 @@ 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) {
var b bytes.Buffer
if err := json.NewEncoder(&b).Encode(v); err != nil {
panic(err)
}
setContentTypeUnlessPresent(w, "application/json")
w.WriteHeader(status)
_, _ = b.WriteTo(w)
if err := json.NewEncoder(w).Encode(v); err != nil {
var errUnsupportedType *json.UnsupportedTypeError
if errors.As(err, &errUnsupportedType) {
panic(err)
}
var errUnsupportedValue *json.UnsupportedValueError
if errors.As(err, &errUnsupportedValue) {
panic(err)
}
var errMarshalError *json.MarshalerError
if errors.As(err, &errMarshalError) {
panic(err)
}
}
log.EnabledResponse(w, v)
}

View file

@ -1,8 +1,10 @@
package render
import (
"encoding/json"
"fmt"
"io"
"math"
"net/http"
"net/http/httptest"
"strconv"
@ -26,10 +28,43 @@ func TestJSON(t *testing.T) {
assert.Empty(t, rw.Fields())
}
func TestJSONPanics(t *testing.T) {
assert.Panics(t, func() {
JSON(httptest.NewRecorder(), make(chan struct{}))
})
func TestJSONPanicsOnUnsupportedType(t *testing.T) {
jsonPanicTest[json.UnsupportedTypeError](t, make(chan struct{}))
}
func TestJSONPanicsOnUnsupportedValue(t *testing.T) {
jsonPanicTest[json.UnsupportedValueError](t, math.NaN())
}
func TestJSONPanicsOnMarshalerError(t *testing.T) {
var v erroneousJSONMarshaler
jsonPanicTest[json.MarshalerError](t, v)
}
type erroneousJSONMarshaler struct{}
func (erroneousJSONMarshaler) MarshalJSON() ([]byte, error) {
return nil, assert.AnError
}
func jsonPanicTest[T json.UnsupportedTypeError | json.UnsupportedValueError | json.MarshalerError](t *testing.T, v any) {
t.Helper()
defer func() {
var err error
if r := recover(); r == nil {
t.Fatal("expected panic")
} else if e, ok := r.(error); !ok {
t.Fatalf("did not panic with an error (%T)", r)
} else {
err = e
}
var e *T
assert.ErrorAs(t, err, &e)
}()
JSON(httptest.NewRecorder(), v)
}
type renderableError struct {

View file

@ -88,6 +88,7 @@ func Sign(w http.ResponseWriter, r *http.Request) {
if len(certChainPEM) > 1 {
caPEM = certChainPEM[1]
}
LogCertificate(w, certChain[0])
render.JSONStatus(w, &SignResponse{
ServerPEM: certChainPEM[0],

View file

@ -338,6 +338,7 @@ func SSHSign(w http.ResponseWriter, r *http.Request) {
identityCertificate = certChainToPEM(certChain)
}
LogSSHCertificate(w, cert)
render.JSONStatus(w, &SSHSignResponse{
Certificate: SSHCertificate{cert},
AddUserCertificate: addUserCertificate,

View file

@ -89,6 +89,7 @@ func SSHRekey(w http.ResponseWriter, r *http.Request) {
return
}
LogSSHCertificate(w, newCert)
render.JSONStatus(w, &SSHRekeyResponse{
Certificate: SSHCertificate{newCert},
IdentityCertificate: identity,

View file

@ -81,6 +81,7 @@ func SSHRenew(w http.ResponseWriter, r *http.Request) {
return
}
LogSSHCertificate(w, newCert)
render.JSONStatus(w, &SSHSignResponse{
Certificate: SSHCertificate{newCert},
IdentityCertificate: identity,

View file

@ -69,17 +69,17 @@ func NewACMEAdminResponder() ACMEAdminResponder {
}
// GetExternalAccountKeys writes the response for the EAB keys GET endpoint
func (h *acmeAdminResponder) GetExternalAccountKeys(w http.ResponseWriter, r *http.Request) {
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"))
}
// CreateExternalAccountKey writes the response for the EAB key POST endpoint
func (h *acmeAdminResponder) CreateExternalAccountKey(w http.ResponseWriter, r *http.Request) {
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"))
}
// DeleteExternalAccountKey writes the response for the EAB key DELETE endpoint
func (h *acmeAdminResponder) DeleteExternalAccountKey(w http.ResponseWriter, r *http.Request) {
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"))
}

View file

@ -57,9 +57,9 @@ func validateWebhook(webhook *linkedca.Webhook) error {
// kind
switch webhook.Kind {
case linkedca.Webhook_ENRICHING, linkedca.Webhook_AUTHORIZING:
case linkedca.Webhook_ENRICHING, linkedca.Webhook_AUTHORIZING, linkedca.Webhook_SCEPCHALLENGE:
default:
return admin.NewError(admin.ErrorBadRequestType, "webhook kind is invalid")
return admin.NewError(admin.ErrorBadRequestType, "webhook kind %q is invalid", webhook.Kind)
}
return nil

View file

@ -180,6 +180,26 @@ func TestWebhookAdminResponder_CreateProvisionerWebhook(t *testing.T) {
statusCode: 400,
}
},
"fail/unsupported-webhook-kind": func(t *testing.T) test {
prov := &linkedca.Provisioner{
Name: "provName",
}
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
adminErr := admin.NewError(admin.ErrorBadRequestType, `(line 5:13): invalid value for enum type: "UNSUPPORTED"`)
adminErr.Message = `(line 5:13): invalid value for enum type: "UNSUPPORTED"`
body := []byte(`
{
"name": "metadata",
"url": "https://example.com",
"kind": "UNSUPPORTED",
}`)
return test{
ctx: ctx,
body: body,
err: adminErr,
statusCode: 400,
}
},
"fail/auth.UpdateProvisioner-error": func(t *testing.T) test {
adm := &linkedca.Admin{
Subject: "step",

View file

@ -40,7 +40,7 @@ func (dba *dbAdmin) clone() *dbAdmin {
return &u
}
func (db *DB) getDBAdminBytes(ctx context.Context, id string) ([]byte, error) {
func (db *DB) getDBAdminBytes(_ context.Context, id string) ([]byte, error) {
data, err := db.db.Get(adminsTable, []byte(id))
if nosql.IsErrNotFound(err) {
return nil, admin.NewError(admin.ErrorNotFoundType, "admin %s not found", id)
@ -102,7 +102,7 @@ func (db *DB) GetAdmin(ctx context.Context, id string) (*linkedca.Admin, error)
// GetAdmins retrieves and unmarshals all active (not deleted) admins
// from the database.
// TODO should we be paginating?
func (db *DB) GetAdmins(ctx context.Context) ([]*linkedca.Admin, error) {
func (db *DB) GetAdmins(context.Context) ([]*linkedca.Admin, error) {
dbEntries, err := db.db.List(adminsTable)
if err != nil {
return nil, errors.Wrap(err, "error loading admins")
@ -115,12 +115,10 @@ func (db *DB) GetAdmins(ctx context.Context) ([]*linkedca.Admin, error) {
if errors.As(err, &ae) {
if ae.IsType(admin.ErrorDeletedType) || ae.IsType(admin.ErrorAuthorityMismatchType) {
continue
} else {
return nil, err
}
} else {
return nil, err
}
return nil, err
}
if adm.AuthorityId != db.authorityID {
continue

View file

@ -36,7 +36,7 @@ func New(db nosqlDB.DB, authorityID string) (*DB, error) {
// save writes the new data to the database, overwriting the old data if it
// existed.
func (db *DB) save(ctx context.Context, id string, nu, old interface{}, typ string, table []byte) error {
func (db *DB) save(_ context.Context, id string, nu, old interface{}, typ string, table []byte) error {
var (
err error
newB []byte

View file

@ -71,7 +71,7 @@ func (dbap *dbAuthorityPolicy) convert() *linkedca.Policy {
return dbToLinked(dbap.Policy)
}
func (db *DB) getDBAuthorityPolicyBytes(ctx context.Context, authorityID string) ([]byte, error) {
func (db *DB) getDBAuthorityPolicyBytes(_ context.Context, authorityID string) ([]byte, error) {
data, err := db.db.Get(authorityPoliciesTable, []byte(authorityID))
if nosql.IsErrNotFound(err) {
return nil, admin.NewError(admin.ErrorNotFoundType, "authority policy not found")

View file

@ -70,7 +70,7 @@ func (dbp *dbProvisioner) convert2linkedca() (*linkedca.Provisioner, error) {
}, nil
}
func (db *DB) getDBProvisionerBytes(ctx context.Context, id string) ([]byte, error) {
func (db *DB) getDBProvisionerBytes(_ context.Context, id string) ([]byte, error) {
data, err := db.db.Get(provisionersTable, []byte(id))
if nosql.IsErrNotFound(err) {
return nil, admin.NewError(admin.ErrorNotFoundType, "provisioner %s not found", id)
@ -132,7 +132,7 @@ func (db *DB) GetProvisioner(ctx context.Context, id string) (*linkedca.Provisio
// GetProvisioners retrieves and unmarshals all active (not deleted) provisioners
// from the database.
func (db *DB) GetProvisioners(ctx context.Context) ([]*linkedca.Provisioner, error) {
func (db *DB) GetProvisioners(_ context.Context) ([]*linkedca.Provisioner, error) {
dbEntries, err := db.db.List(provisionersTable)
if err != nil {
return nil, errors.Wrap(err, "error loading provisioners")
@ -145,12 +145,10 @@ func (db *DB) GetProvisioners(ctx context.Context) ([]*linkedca.Provisioner, err
if errors.As(err, &ae) {
if ae.IsType(admin.ErrorDeletedType) || ae.IsType(admin.ErrorAuthorityMismatchType) {
continue
} else {
return nil, err
}
} else {
return nil, err
}
return nil, err
}
if prov.AuthorityId != db.authorityID {
continue

View file

@ -545,50 +545,6 @@ func (a *Authority) init() error {
tmplVars.SSH.UserFederatedKeys = append(tmplVars.SSH.UserFederatedKeys, a.sshCAUserFederatedCerts...)
}
// Check if a KMS with decryption capability is required and available
if a.requiresDecrypter() {
if _, ok := a.keyManager.(kmsapi.Decrypter); !ok {
return errors.New("keymanager doesn't provide crypto.Decrypter")
}
}
// TODO: decide if this is a good approach for providing the SCEP functionality
// It currently mirrors the logic for the x509CAService
if a.requiresSCEPService() && a.scepService == nil {
var options scep.Options
// Read intermediate and create X509 signer and decrypter for default CAS.
options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert)
if err != nil {
return err
}
options.CertificateChain = append(options.CertificateChain, a.rootX509Certs...)
options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.IntermediateKey,
Password: a.password,
})
if err != nil {
return err
}
if km, ok := a.keyManager.(kmsapi.Decrypter); ok {
options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKey: a.config.IntermediateKey,
Password: a.password,
})
if err != nil {
return err
}
}
a.scepService, err = scep.NewService(ctx, options)
if err != nil {
return err
}
// TODO: mimick the x509CAService GetCertificateAuthority here too?
}
if a.config.AuthorityConfig.EnableAdmin {
// Initialize step-ca Admin Database if it's not already initialized using
// WithAdminDB.
@ -684,6 +640,50 @@ func (a *Authority) init() error {
return err
}
// Check if a KMS with decryption capability is required and available
if a.requiresDecrypter() {
if _, ok := a.keyManager.(kmsapi.Decrypter); !ok {
return errors.New("keymanager doesn't provide crypto.Decrypter")
}
}
// TODO: decide if this is a good approach for providing the SCEP functionality
// It currently mirrors the logic for the x509CAService
if a.requiresSCEPService() && a.scepService == nil {
var options scep.Options
// Read intermediate and create X509 signer and decrypter for default CAS.
options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert)
if err != nil {
return err
}
options.CertificateChain = append(options.CertificateChain, a.rootX509Certs...)
options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.IntermediateKey,
Password: a.password,
})
if err != nil {
return err
}
if km, ok := a.keyManager.(kmsapi.Decrypter); ok {
options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKey: a.config.IntermediateKey,
Password: a.password,
})
if err != nil {
return err
}
}
a.scepService, err = scep.NewService(ctx, options)
if err != nil {
return err
}
// TODO: mimick the x509CAService GetCertificateAuthority here too?
}
// Load X509 constraints engine.
//
// This is currently only available in CA mode.

View file

@ -315,7 +315,7 @@ func (a *Authority) authorizeRenew(ctx context.Context, cert *x509.Certificate)
}
// authorizeSSHCertificate returns an error if the given certificate is revoked.
func (a *Authority) authorizeSSHCertificate(ctx context.Context, cert *ssh.Certificate) error {
func (a *Authority) authorizeSSHCertificate(_ context.Context, cert *ssh.Certificate) error {
var err error
var isRevoked bool
@ -394,7 +394,7 @@ func (a *Authority) authorizeSSHRevoke(ctx context.Context, token string) error
// AuthorizeRenewToken validates the renew token and returns the leaf
// certificate in the x5cInsecure header.
func (a *Authority) AuthorizeRenewToken(ctx context.Context, ott string) (*x509.Certificate, error) {
func (a *Authority) AuthorizeRenewToken(_ context.Context, ott string) (*x509.Certificate, error) {
var claims jose.Claims
jwt, chain, err := jose.ParseX5cInsecure(ott, a.rootX509Certs)
if err != nil {

View file

@ -79,6 +79,7 @@ type Config struct {
CommonName string `json:"commonName,omitempty"`
CRL *CRLConfig `json:"crl,omitempty"`
SkipValidation bool `json:"-"`
NNSServer string `json:"nnsServer,omitempty"`
// Keeps record of the filename the Config is read from
loadedFromFilepath string
@ -182,7 +183,7 @@ func (c *AuthConfig) init() {
}
// Validate validates the authority configuration.
func (c *AuthConfig) Validate(audiences provisioner.Audiences) error {
func (c *AuthConfig) Validate(provisioner.Audiences) error {
if c == nil {
return errors.New("authority cannot be undefined")
}

View file

@ -381,19 +381,19 @@ func (c *linkedCaClient) IsSSHRevoked(serial string) (bool, error) {
return resp.Status != linkedca.RevocationStatus_ACTIVE, nil
}
func (c *linkedCaClient) CreateAuthorityPolicy(ctx context.Context, policy *linkedca.Policy) error {
func (c *linkedCaClient) CreateAuthorityPolicy(_ context.Context, _ *linkedca.Policy) error {
return errors.New("not implemented yet")
}
func (c *linkedCaClient) GetAuthorityPolicy(ctx context.Context) (*linkedca.Policy, error) {
func (c *linkedCaClient) GetAuthorityPolicy(context.Context) (*linkedca.Policy, error) {
return nil, errors.New("not implemented yet")
}
func (c *linkedCaClient) UpdateAuthorityPolicy(ctx context.Context, policy *linkedca.Policy) error {
func (c *linkedCaClient) UpdateAuthorityPolicy(_ context.Context, _ *linkedca.Policy) error {
return errors.New("not implemented yet")
}
func (c *linkedCaClient) DeleteAuthorityPolicy(ctx context.Context) error {
func (c *linkedCaClient) DeleteAuthorityPolicy(context.Context) error {
return errors.New("not implemented yet")
}

View file

@ -154,7 +154,7 @@ func (a *Authority) checkProvisionerPolicy(ctx context.Context, provName string,
// checkPolicy checks if a new or updated policy configuration results in the user
// locking themselves or other admins out of the CA.
func (a *Authority) checkPolicy(ctx context.Context, currentAdmin *linkedca.Admin, otherAdmins []*linkedca.Admin, p *linkedca.Policy) error {
func (a *Authority) checkPolicy(_ context.Context, currentAdmin *linkedca.Admin, otherAdmins []*linkedca.Admin, p *linkedca.Policy) error {
// convert the policy; return early if nil
policyOptions := authPolicy.LinkedToCertificates(p)
if policyOptions == nil {
@ -248,7 +248,7 @@ func isAllowed(engine authPolicy.X509Policy, sans []string) error {
if isNamePolicyError && policyErr.Reason == policy.NotAllowed {
return &PolicyError{
Typ: AdminLockOut,
Err: fmt.Errorf("the provided policy would lock out %s from the CA. Please update your policy to include %s as an allowed name", sans, sans),
Err: fmt.Errorf("the provided policy would lock out %s from the CA. Please create an x509 policy to include %s as an allowed DNS name", sans, sans),
}
}
return &PolicyError{

View file

@ -80,7 +80,7 @@ func TestAuthority_checkPolicy(t *testing.T) {
},
err: &PolicyError{
Typ: AdminLockOut,
Err: errors.New("the provided policy would lock out [step] from the CA. Please update your policy to include [step] as an allowed name"),
Err: errors.New("the provided policy would lock out [step] from the CA. Please create an x509 policy to include [step] as an allowed DNS name"),
},
}
},
@ -127,7 +127,7 @@ func TestAuthority_checkPolicy(t *testing.T) {
},
err: &PolicyError{
Typ: AdminLockOut,
Err: errors.New("the provided policy would lock out [otherAdmin] from the CA. Please update your policy to include [otherAdmin] as an allowed name"),
Err: errors.New("the provided policy would lock out [otherAdmin] from the CA. Please create an x509 policy to include [otherAdmin] as an allowed DNS name"),
},
}
},

View file

@ -26,6 +26,8 @@ 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"
// NNS_01 is the nns-01 ACME challenge.
NNS_01 ACMEChallenge = "nns-01"
)
// String returns a normalized version of the challenge.
@ -36,7 +38,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:
case HTTP_01, DNS_01, TLS_ALPN_01, DEVICE_ATTEST_01, NNS_01:
return nil
default:
return fmt.Errorf("acme challenge %q is not supported", c)
@ -48,7 +50,7 @@ func (c ACMEChallenge) Validate() error {
type ACMEAttestationFormat string
const (
// APPLE is the format used to enable device-attest-01 on apple devices.
// APPLE is the format used to enable device-attest-01 on Apple devices.
APPLE ACMEAttestationFormat = "apple"
// STEP is the format used to enable device-attest-01 on devices that
@ -57,7 +59,7 @@ const (
// TODO(mariano): should we rename this to something else.
STEP ACMEAttestationFormat = "step"
// TPM is the format used to enable device-attest-01 on TPMs.
// TPM is the format used to enable device-attest-01 with TPMs.
TPM ACMEAttestationFormat = "tpm"
)
@ -133,7 +135,7 @@ func (p *ACME) GetIDForToken() string {
}
// GetTokenID returns the identifier of the token.
func (p *ACME) GetTokenID(ott string) (string, error) {
func (p *ACME) GetTokenID(string) (string, error) {
return "", errors.New("acme provisioner does not implement GetTokenID")
}
@ -184,7 +186,7 @@ func (p *ACME) Init(config Config) (err error) {
}
// Parse attestation roots.
// The pool will be nil if the there are not roots.
// The pool will be nil if there are no roots.
if rest := p.AttestationRoots; len(rest) > 0 {
var block *pem.Block
var hasCert bool
@ -228,7 +230,7 @@ type ACMEIdentifier struct {
// AuthorizeOrderIdentifier verifies the provisioner is allowed to issue a
// certificate for an ACME Order Identifier.
func (p *ACME) AuthorizeOrderIdentifier(ctx context.Context, identifier ACMEIdentifier) error {
func (p *ACME) AuthorizeOrderIdentifier(_ context.Context, identifier ACMEIdentifier) error {
x509Policy := p.ctl.getPolicy().getX509()
// identifier is allowed if no policy is configured
@ -253,7 +255,7 @@ func (p *ACME) AuthorizeOrderIdentifier(ctx context.Context, identifier ACMEIden
// AuthorizeSign does not do any validation, because all validation is handled
// in the ACME protocol. This method returns a list of modifiers / constraints
// on the resulting certificate.
func (p *ACME) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) {
func (p *ACME) AuthorizeSign(context.Context, string) ([]SignOption, error) {
opts := []SignOption{
p,
// modifiers / withOptions
@ -274,7 +276,7 @@ func (p *ACME) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e
// the CA. It can be used to authorize revocation of a certificate. With the
// ACME protocol, revocation authorization is specified and performed as part
// of the client/server interaction, so this is a no-op.
func (p *ACME) AuthorizeRevoke(ctx context.Context, token string) error {
func (p *ACME) AuthorizeRevoke(context.Context, string) error {
return nil
}
@ -289,9 +291,9 @@ func (p *ACME) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error
// IsChallengeEnabled checks if the given challenge is enabled. By default
// http-01, dns-01 and tls-alpn-01 are enabled, to disable any of them the
// Challenge provisioner property should have at least one element.
func (p *ACME) IsChallengeEnabled(ctx context.Context, challenge ACMEChallenge) bool {
func (p *ACME) IsChallengeEnabled(_ context.Context, challenge ACMEChallenge) bool {
enabledChallenges := []ACMEChallenge{
HTTP_01, DNS_01, TLS_ALPN_01,
HTTP_01, DNS_01, TLS_ALPN_01, NNS_01,
}
if len(p.Challenges) > 0 {
enabledChallenges = p.Challenges
@ -307,7 +309,7 @@ func (p *ACME) IsChallengeEnabled(ctx context.Context, challenge ACMEChallenge)
// IsAttestationFormatEnabled checks if the given attestation format is enabled.
// By default apple, step and tpm are enabled, to disable any of them the
// AttestationFormat provisioner property should have at least one element.
func (p *ACME) IsAttestationFormatEnabled(ctx context.Context, format ACMEAttestationFormat) bool {
func (p *ACME) IsAttestationFormatEnabled(_ context.Context, format ACMEAttestationFormat) bool {
enabledFormats := []ACMEAttestationFormat{
APPLE, STEP, TPM,
}

View file

@ -24,6 +24,7 @@ import (
"go.step.sm/linkedca"
"github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/webhook"
)
// awsIssuer is the string used as issuer in the generated tokens.
@ -73,6 +74,14 @@ const awsMetadataTokenTTLHeader = "X-aws-ec2-metadata-token-ttl-seconds" //nolin
// The fifth certificate is used in:
//
// me-south-1
//
// The sixth certificate is used in:
//
// me-central-1
//
// The seventh certificate is used in:
//
// ap-southeast-3
const awsCertificate = `-----BEGIN CERTIFICATE-----
MIIDIjCCAougAwIBAgIJAKnL4UEDMN/FMA0GCSqGSIb3DQEBBQUAMGoxCzAJBgNV
BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdTZWF0dGxlMRgw
@ -154,6 +163,34 @@ DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQBhkNTBIFgWFd+ZhC/LhRUY
4OjEiykmbEp6hlzQ79T0Tfbn5A4NYDI2icBP0+hmf6qSnIhwJF6typyd1yPK5Fqt
NTpxxcXmUKquX+pHmIkK1LKDO8rNE84jqxrxRsfDi6by82fjVYf2pgjJW8R1FAw+
mL5WQRFexbfB5aXhcMo0AA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICMzCCAZygAwIBAgIGAXjRrnDjMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNVBAYT
AlVTMRkwFwYDVQQIDBBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHDAdTZWF0dGxl
MSAwHgYDVQQKDBdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAgFw0yMTA0MTQxODM5
MzNaGA8yMjAwMDQxNDE4MzkzM1owXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgMEFdh
c2hpbmd0b24gU3RhdGUxEDAOBgNVBAcMB1NlYXR0bGUxIDAeBgNVBAoMF0FtYXpv
biBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDc
aTgW/KyA6zyruJQrYy00a6wqLA7eeUzk3bMiTkLsTeDQfrkaZMfBAjGaaOymRo1C
3qzE4rIenmahvUplu9ZmLwL1idWXMRX2RlSvIt+d2SeoKOKQWoc2UOFZMHYxDue7
zkyk1CIRaBukTeY13/RIrlc6X61zJ5BBtZXlHwayjQIDAQABMA0GCSqGSIb3DQEB
BQUAA4GBABTqTy3R6RXKPW45FA+cgo7YZEj/Cnz5YaoUivRRdX2A83BHuBTvJE2+
WX00FTEj4hRVjameE1nENoO8Z7fUVloAFDlDo69fhkJeSvn51D1WRrPnoWGgEfr1
+OfK1bAcKTtfkkkP9r4RdwSjKzO5Zu/B+Wqm3kVEz/QNcz6npmA6
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICMzCCAZygAwIBAgIGAXbVDG2yMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNVBAYT
AlVTMRkwFwYDVQQIDBBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHDAdTZWF0dGxl
MSAwHgYDVQQKDBdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAgFw0yMTAxMDYwMDE1
MzBaGA8yMjAwMDEwNjAwMTUzMFowXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgMEFdh
c2hpbmd0b24gU3RhdGUxEDAOBgNVBAcMB1NlYXR0bGUxIDAeBgNVBAoMF0FtYXpv
biBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCn
CS/Vbt0gQ1ebWcur2hSO7PnJifE4OPxQ7RgSAlc4/spJp1sDP+ZrS0LO1ZJfKhXf
1R9S3AUwLnsc7b+IuVXdY5LK9RKqu64nyXP5dx170zoL8loEyCSuRR2fs+04i2Qs
WBVP+KFNAn7P5L1EHRjkgTO8kjNKviwRV+OkP9ab5wIDAQABMA0GCSqGSIb3DQEB
BQUAA4GBAI4WUy6+DKh0JDSzQEZNyBgNlSoSuC2owtMxCwGB6nBfzzfcekWvs6eo
fLTSGovrReX7MtVgrcJBZjmPIentw5dWUs+87w/g9lNwUnUt0ZHYyh2tuBG6hVJu
UEwDJ/z3wDd6wQviLOTF3MITawt9P8siR1hXqLJNxpjRQFZrgHqi
-----END CERTIFICATE-----`
// awsSignatureAlgorithm is the signature algorithm used to verify the identity
@ -435,7 +472,7 @@ func (p *AWS) Init(config Config) (err error) {
// AuthorizeSign validates the given token and returns the sign options that
// will be used on certificate creation.
func (p *AWS) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) {
func (p *AWS) AuthorizeSign(_ context.Context, token string) ([]SignOption, error) {
payload, err := p.authorizeToken(token)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "aws.AuthorizeSign")
@ -485,7 +522,11 @@ func (p *AWS) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
commonNameValidator(payload.Claims.Subject),
newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()),
newX509NamePolicyValidator(p.ctl.getPolicy().getX509()),
p.ctl.newWebhookController(data, linkedca.Webhook_X509),
p.ctl.newWebhookController(
data,
linkedca.Webhook_X509,
webhook.WithAuthorizationPrincipal(doc.InstanceID),
),
), nil
}
@ -708,7 +749,7 @@ func (p *AWS) authorizeToken(token string) (*awsPayload, error) {
}
// AuthorizeSSHSign returns the list of SignOption for a SignSSH request.
func (p *AWS) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
func (p *AWS) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption, error) {
if !p.ctl.Claimer.IsSSHCAEnabled() {
return nil, errs.Unauthorized("aws.AuthorizeSSHSign; ssh ca is disabled for aws provisioner '%s'", p.GetName())
}
@ -768,6 +809,10 @@ func (p *AWS) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
// Ensure that all principal names are allowed
newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), nil),
// Call webhooks
p.ctl.newWebhookController(data, linkedca.Webhook_SSH),
p.ctl.newWebhookController(
data,
linkedca.Webhook_SSH,
webhook.WithAuthorizationPrincipal(doc.InstanceID),
),
), nil
}

View file

@ -20,13 +20,19 @@ import (
"go.step.sm/linkedca"
"github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/webhook"
)
// azureOIDCBaseURL is the base discovery url for Microsoft Azure tokens.
const azureOIDCBaseURL = "https://login.microsoftonline.com"
//nolint:gosec // azureIdentityTokenURL is the URL to get the identity token for an instance.
const azureIdentityTokenURL = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F"
const azureIdentityTokenURL = "http://169.254.169.254/metadata/identity/oauth2/token"
const azureIdentityTokenAPIVersion = "2018-02-01"
// azureInstanceComputeURL is the URL to get the instance compute metadata.
const azureInstanceComputeURL = "http://169.254.169.254/metadata/instance/compute/azEnvironment"
// azureDefaultAudience is the default audience used.
const azureDefaultAudience = "https://management.azure.com/"
@ -35,15 +41,27 @@ const azureDefaultAudience = "https://management.azure.com/"
// Using case insensitive as resourceGroups appears as resourcegroups.
var azureXMSMirIDRegExp = regexp.MustCompile(`(?i)^/subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft.(Compute/virtualMachines|ManagedIdentity/userAssignedIdentities)/([^/]+)$`)
// azureEnvironments is the list of all Azure environments.
var azureEnvironments = map[string]string{
"AzurePublicCloud": "https://management.azure.com/",
"AzureCloud": "https://management.azure.com/",
"AzureUSGovernmentCloud": "https://management.usgovcloudapi.net/",
"AzureUSGovernment": "https://management.usgovcloudapi.net/",
"AzureChinaCloud": "https://management.chinacloudapi.cn/",
"AzureGermanCloud": "https://management.microsoftazure.de/",
}
type azureConfig struct {
oidcDiscoveryURL string
identityTokenURL string
oidcDiscoveryURL string
identityTokenURL string
instanceComputeURL string
}
func newAzureConfig(tenantID string) *azureConfig {
return &azureConfig{
oidcDiscoveryURL: azureOIDCBaseURL + "/" + tenantID + "/.well-known/openid-configuration",
identityTokenURL: azureIdentityTokenURL,
oidcDiscoveryURL: azureOIDCBaseURL + "/" + tenantID + "/.well-known/openid-configuration",
identityTokenURL: azureIdentityTokenURL,
instanceComputeURL: azureInstanceComputeURL,
}
}
@ -103,6 +121,7 @@ type Azure struct {
oidcConfig openIDConfiguration
keyStore *keyStore
ctl *Controller
environment string
}
// GetID returns the provisioner unique identifier.
@ -164,14 +183,35 @@ func (p *Azure) GetEncryptedKey() (kid, key string, ok bool) {
// GetIdentityToken retrieves from the metadata service the identity token and
// returns it.
func (p *Azure) GetIdentityToken(subject, caURL string) (string, error) {
_, _ = subject, caURL // unused input
// Initialize the config if this method is used from the cli.
p.assertConfig()
// default to AzurePublicCloud to keep existing behavior
identityTokenResource := azureEnvironments["AzurePublicCloud"]
var err error
p.environment, err = p.getAzureEnvironment()
if err != nil {
return "", errors.Wrap(err, "error getting azure environment")
}
if resource, ok := azureEnvironments[p.environment]; ok {
identityTokenResource = resource
}
req, err := http.NewRequest("GET", p.config.identityTokenURL, http.NoBody)
if err != nil {
return "", errors.Wrap(err, "error creating request")
}
req.Header.Set("Metadata", "true")
query := req.URL.Query()
query.Add("resource", identityTokenResource)
query.Add("api-version", azureIdentityTokenAPIVersion)
req.URL.RawQuery = query.Encode()
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", errors.Wrap(err, "error getting identity token, are you in a Azure VM?")
@ -276,7 +316,7 @@ func (p *Azure) authorizeToken(token string) (*azurePayload, string, string, str
// AuthorizeSign validates the given token and returns the sign options that
// will be used on certificate creation.
func (p *Azure) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) {
func (p *Azure) AuthorizeSign(_ context.Context, token string) ([]SignOption, error) {
_, name, group, subscription, identityObjectID, err := p.authorizeToken(token)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "azure.AuthorizeSign")
@ -364,7 +404,11 @@ func (p *Azure) AuthorizeSign(ctx context.Context, token string) ([]SignOption,
defaultPublicKeyValidator{},
newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()),
newX509NamePolicyValidator(p.ctl.getPolicy().getX509()),
p.ctl.newWebhookController(data, linkedca.Webhook_X509),
p.ctl.newWebhookController(
data,
linkedca.Webhook_X509,
webhook.WithAuthorizationPrincipal(identityObjectID),
),
), nil
}
@ -377,12 +421,12 @@ func (p *Azure) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) erro
}
// AuthorizeSSHSign returns the list of SignOption for a SignSSH request.
func (p *Azure) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
func (p *Azure) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption, error) {
if !p.ctl.Claimer.IsSSHCAEnabled() {
return nil, errs.Unauthorized("azure.AuthorizeSSHSign; sshCA is disabled for provisioner '%s'", p.GetName())
}
_, name, _, _, _, err := p.authorizeToken(token)
_, name, _, _, identityObjectID, err := p.authorizeToken(token)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "azure.AuthorizeSSHSign")
}
@ -434,7 +478,11 @@ func (p *Azure) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOptio
// Ensure that all principal names are allowed
newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), nil),
// Call webhooks
p.ctl.newWebhookController(data, linkedca.Webhook_SSH),
p.ctl.newWebhookController(
data,
linkedca.Webhook_SSH,
webhook.WithAuthorizationPrincipal(identityObjectID),
),
), nil
}
@ -444,3 +492,37 @@ func (p *Azure) assertConfig() {
p.config = newAzureConfig(p.TenantID)
}
}
// getAzureEnvironment returns the Azure environment for the current instance
func (p *Azure) getAzureEnvironment() (string, error) {
if p.environment != "" {
return p.environment, nil
}
req, err := http.NewRequest("GET", p.config.instanceComputeURL, http.NoBody)
if err != nil {
return "", errors.Wrap(err, "error creating request")
}
req.Header.Add("Metadata", "True")
query := req.URL.Query()
query.Add("format", "text")
query.Add("api-version", "2021-02-01")
req.URL.RawQuery = query.Encode()
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", errors.Wrap(err, "error getting azure instance environment, are you in a Azure VM?")
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
return "", errors.Wrap(err, "error reading azure environment response")
}
if resp.StatusCode >= 400 {
return "", errors.Errorf("error getting azure environment: status=%d, response=%s", resp.StatusCode, b)
}
return string(b), nil
}

View file

@ -100,7 +100,14 @@ func TestAzure_GetIdentityToken(t *testing.T) {
time.Now(), &p1.keyStore.keySet.Keys[0])
assert.FatalError(t, err)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
srvIdentity := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
wantResource := r.URL.Query().Get("want_resource")
resource := r.URL.Query().Get("resource")
if wantResource == "" || resource != wantResource {
http.Error(w, fmt.Sprintf("Azure query param resource = %s, wantResource %s", resource, wantResource), http.StatusBadRequest)
return
}
switch r.URL.Path {
case "/bad-request":
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
@ -111,29 +118,58 @@ func TestAzure_GetIdentityToken(t *testing.T) {
fmt.Fprintf(w, `{"access_token":"%s"}`, t1)
}
}))
defer srv.Close()
defer srvIdentity.Close()
srvInstance := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/bad-request":
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
case "/AzureChinaCloud":
w.Header().Add("Content-Type", "text/plain")
w.Write([]byte("AzureChinaCloud"))
case "/AzureGermanCloud":
w.Header().Add("Content-Type", "text/plain")
w.Write([]byte("AzureGermanCloud"))
case "/AzureUSGovernmentCloud":
w.Header().Add("Content-Type", "text/plain")
w.Write([]byte("AzureUSGovernmentCloud"))
default:
w.Header().Add("Content-Type", "text/plain")
w.Write([]byte("AzurePublicCloud"))
}
}))
defer srvInstance.Close()
type args struct {
subject string
caURL string
}
tests := []struct {
name string
azure *Azure
args args
identityTokenURL string
want string
wantErr bool
name string
azure *Azure
args args
identityTokenURL string
instanceComputeURL string
wantEnvironment string
want string
wantErr bool
}{
{"ok", p1, args{"subject", "caURL"}, srv.URL, t1, false},
{"fail request", p1, args{"subject", "caURL"}, srv.URL + "/bad-request", "", true},
{"fail unmarshal", p1, args{"subject", "caURL"}, srv.URL + "/bad-json", "", true},
{"fail url", p1, args{"subject", "caURL"}, "://ca.smallstep.com", "", true},
{"fail connect", p1, args{"subject", "caURL"}, "foobarzar", "", true},
{"ok", p1, args{"subject", "caURL"}, srvIdentity.URL, srvInstance.URL, "AzurePublicCloud", t1, false},
{"ok azure china", p1, args{"subject", "caURL"}, srvIdentity.URL, srvInstance.URL, "AzurePublicCloud", t1, false},
{"ok azure germany", p1, args{"subject", "caURL"}, srvIdentity.URL, srvInstance.URL, "AzureGermanCloud", t1, false},
{"ok azure us gov", p1, args{"subject", "caURL"}, srvIdentity.URL, srvInstance.URL, "AzureUSGovernmentCloud", t1, false},
{"fail instance request", p1, args{"subject", "caURL"}, srvIdentity.URL + "/bad-request", srvInstance.URL + "/bad-request", "AzurePublicCloud", "", true},
{"fail request", p1, args{"subject", "caURL"}, srvIdentity.URL + "/bad-request", srvInstance.URL, "AzurePublicCloud", "", true},
{"fail unmarshal", p1, args{"subject", "caURL"}, srvIdentity.URL + "/bad-json", srvInstance.URL, "AzurePublicCloud", "", true},
{"fail url", p1, args{"subject", "caURL"}, "://ca.smallstep.com", srvInstance.URL, "AzurePublicCloud", "", true},
{"fail connect", p1, args{"subject", "caURL"}, "foobarzar", srvInstance.URL, "AzurePublicCloud", "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.azure.config.identityTokenURL = tt.identityTokenURL
// reset environment between tests to avoid caching issues
p1.environment = ""
tt.azure.config.identityTokenURL = tt.identityTokenURL + "?want_resource=" + azureEnvironments[tt.wantEnvironment]
tt.azure.config.instanceComputeURL = tt.instanceComputeURL + "/" + tt.wantEnvironment
got, err := tt.azure.GetIdentityToken(tt.args.subject, tt.args.caURL)
if (err != nil) != tt.wantErr {
t.Errorf("Azure.GetIdentityToken() error = %v, wantErr %v", err, tt.wantErr)

View file

@ -10,6 +10,7 @@ import (
"github.com/pkg/errors"
"github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/webhook"
"go.step.sm/linkedca"
"golang.org/x/crypto/ssh"
)
@ -77,7 +78,7 @@ func (c *Controller) AuthorizeSSHRenew(ctx context.Context, cert *ssh.Certificat
return DefaultAuthorizeSSHRenew(ctx, c, cert)
}
func (c *Controller) newWebhookController(templateData WebhookSetter, certType linkedca.Webhook_CertType) *WebhookController {
func (c *Controller) newWebhookController(templateData WebhookSetter, certType linkedca.Webhook_CertType, opts ...webhook.RequestBodyOption) *WebhookController {
client := c.webhookClient
if client == nil {
client = http.DefaultClient
@ -87,6 +88,7 @@ func (c *Controller) newWebhookController(templateData WebhookSetter, certType l
client: client,
webhooks: c.webhooks,
certType: certType,
options: opts,
}
}
@ -111,7 +113,7 @@ type AuthorizeSSHRenewFunc func(ctx context.Context, p *Controller, cert *ssh.Ce
// DefaultIdentityFunc return a default identity depending on the provisioner
// type. For OIDC email is always present and the usernames might
// contain empty strings.
func DefaultIdentityFunc(ctx context.Context, p Interface, email string) (*Identity, error) {
func DefaultIdentityFunc(_ context.Context, p Interface, email string) (*Identity, error) {
switch k := p.(type) {
case *OIDC:
// OIDC principals would be:
@ -140,7 +142,7 @@ func DefaultIdentityFunc(ctx context.Context, p Interface, email string) (*Ident
// will return an error if the provisioner has the renewal disabled, if the
// certificate is not yet valid or if the certificate is expired and renew after
// expiry is disabled.
func DefaultAuthorizeRenew(ctx context.Context, p *Controller, cert *x509.Certificate) error {
func DefaultAuthorizeRenew(_ context.Context, p *Controller, cert *x509.Certificate) error {
if p.Claimer.IsDisableRenewal() {
return errs.Unauthorized("renew is disabled for provisioner '%s'", p.GetName())
}
@ -162,7 +164,7 @@ func DefaultAuthorizeRenew(ctx context.Context, p *Controller, cert *x509.Certif
// will return an error if the provisioner has the renewal disabled, if the
// certificate is not yet valid or if the certificate is expired and renew after
// expiry is disabled.
func DefaultAuthorizeSSHRenew(ctx context.Context, p *Controller, cert *ssh.Certificate) error {
func DefaultAuthorizeSSHRenew(_ context.Context, p *Controller, cert *ssh.Certificate) error {
if p.Claimer.IsDisableRenewal() {
return errs.Unauthorized("renew is disabled for provisioner '%s'", p.GetName())
}

View file

@ -4,15 +4,18 @@ import (
"context"
"crypto/x509"
"fmt"
"net/http"
"reflect"
"testing"
"time"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util"
"go.step.sm/linkedca"
"golang.org/x/crypto/ssh"
"github.com/smallstep/certificates/authority/policy"
"github.com/smallstep/certificates/webhook"
)
var trueValue = true
@ -449,16 +452,39 @@ func TestDefaultAuthorizeSSHRenew(t *testing.T) {
}
func Test_newWebhookController(t *testing.T) {
c := &Controller{}
data := x509util.TemplateData{"foo": "bar"}
ctl := c.newWebhookController(data, linkedca.Webhook_X509)
if !reflect.DeepEqual(ctl.TemplateData, data) {
t.Error("Failed to set templateData")
cert, err := pemutil.ReadCertificate("testdata/certs/x5c-leaf.crt", pemutil.WithFirstBlock())
if err != nil {
t.Fatal(err)
}
if ctl.certType != linkedca.Webhook_X509 {
t.Error("Failed to set certType")
opts := []webhook.RequestBodyOption{webhook.WithX5CCertificate(cert)}
type args struct {
templateData WebhookSetter
certType linkedca.Webhook_CertType
opts []webhook.RequestBodyOption
}
if ctl.client == nil {
t.Error("Failed to set client")
tests := []struct {
name string
args args
want *WebhookController
}{
{"ok", args{x509util.TemplateData{"foo": "bar"}, linkedca.Webhook_X509, nil}, &WebhookController{
TemplateData: x509util.TemplateData{"foo": "bar"},
certType: linkedca.Webhook_X509,
client: http.DefaultClient,
}},
{"ok with options", args{x509util.TemplateData{"foo": "bar"}, linkedca.Webhook_SSH, opts}, &WebhookController{
TemplateData: x509util.TemplateData{"foo": "bar"},
certType: linkedca.Webhook_SSH,
client: http.DefaultClient,
options: opts,
}},
}
for _, tt := range tests {
c := &Controller{}
got := c.newWebhookController(tt.args.templateData, tt.args.certType, tt.args.opts...)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("newWebhookController() = %v, want %v", got, tt.want)
}
}
}

View file

@ -21,6 +21,7 @@ import (
"go.step.sm/linkedca"
"github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/webhook"
)
// gcpCertsURL is the url that serves Google OAuth2 public keys.
@ -169,6 +170,8 @@ func (p *GCP) GetIdentityURL(audience string) string {
// GetIdentityToken does an HTTP request to the identity url.
func (p *GCP) GetIdentityToken(subject, caURL string) (string, error) {
_ = subject // unused input
audience, err := generateSignAudience(caURL, p.GetIDForToken())
if err != nil {
return "", err
@ -220,7 +223,7 @@ func (p *GCP) Init(config Config) (err error) {
// AuthorizeSign validates the given token and returns the sign options that
// will be used on certificate creation.
func (p *GCP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) {
func (p *GCP) AuthorizeSign(_ context.Context, token string) ([]SignOption, error) {
claims, err := p.authorizeToken(token)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "gcp.AuthorizeSign")
@ -273,7 +276,11 @@ func (p *GCP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
defaultPublicKeyValidator{},
newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()),
newX509NamePolicyValidator(p.ctl.getPolicy().getX509()),
p.ctl.newWebhookController(data, linkedca.Webhook_X509),
p.ctl.newWebhookController(
data,
linkedca.Webhook_X509,
webhook.WithAuthorizationPrincipal(ce.InstanceID),
),
), nil
}
@ -380,7 +387,7 @@ func (p *GCP) authorizeToken(token string) (*gcpPayload, error) {
}
// AuthorizeSSHSign returns the list of SignOption for a SignSSH request.
func (p *GCP) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
func (p *GCP) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption, error) {
if !p.ctl.Claimer.IsSSHCAEnabled() {
return nil, errs.Unauthorized("gcp.AuthorizeSSHSign; sshCA is disabled for gcp provisioner '%s'", p.GetName())
}
@ -440,6 +447,10 @@ func (p *GCP) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
// Ensure that all principal names are allowed
newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), nil),
// Call webhooks
p.ctl.newWebhookController(data, linkedca.Webhook_SSH),
p.ctl.newWebhookController(
data,
linkedca.Webhook_SSH,
webhook.WithAuthorizationPrincipal(ce.InstanceID),
),
), nil
}

View file

@ -143,14 +143,14 @@ func (p *JWK) authorizeToken(token string, audiences []string) (*jwtPayload, err
// AuthorizeRevoke returns an error if the provisioner does not have rights to
// revoke the certificate with serial number in the `sub` property.
func (p *JWK) AuthorizeRevoke(ctx context.Context, token string) error {
func (p *JWK) AuthorizeRevoke(_ context.Context, token string) error {
_, err := p.authorizeToken(token, p.ctl.Audiences.Revoke)
// TODO(hs): authorize the SANs using x509 name policy allow/deny rules (also for other provisioners with AuthorizeRevoke)
return errs.Wrap(http.StatusInternalServerError, err, "jwk.AuthorizeRevoke")
}
// AuthorizeSign validates the given token.
func (p *JWK) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) {
func (p *JWK) AuthorizeSign(_ context.Context, token string) ([]SignOption, error) {
claims, err := p.authorizeToken(token, p.ctl.Audiences.Sign)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "jwk.AuthorizeSign")
@ -209,7 +209,7 @@ func (p *JWK) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error
}
// AuthorizeSSHSign returns the list of SignOption for a SignSSH request.
func (p *JWK) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
func (p *JWK) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption, error) {
if !p.ctl.Claimer.IsSSHCAEnabled() {
return nil, errs.Unauthorized("jwk.AuthorizeSSHSign; sshCA is disabled for jwk provisioner '%s'", p.GetName())
}
@ -286,7 +286,7 @@ func (p *JWK) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
}
// AuthorizeSSHRevoke returns nil if the token is valid, false otherwise.
func (p *JWK) AuthorizeSSHRevoke(ctx context.Context, token string) error {
func (p *JWK) AuthorizeSSHRevoke(_ context.Context, token string) error {
_, err := p.authorizeToken(token, p.ctl.Audiences.SSHRevoke)
// TODO(hs): authorize the principals using SSH name policy allow/deny rules (also for other provisioners with AuthorizeSSHRevoke)
return errs.Wrap(http.StatusInternalServerError, err, "jwk.AuthorizeSSHRevoke")

View file

@ -72,7 +72,7 @@ func (p *K8sSA) GetIDForToken() string {
}
// GetTokenID returns an unimplemented error and does not use the input ott.
func (p *K8sSA) GetTokenID(ott string) (string, error) {
func (p *K8sSA) GetTokenID(string) (string, error) {
return "", errors.New("not implemented")
}
@ -148,6 +148,7 @@ func (p *K8sSA) Init(config Config) (err error) {
// claims for case specific downstream parsing.
// e.g. a Sign request will auth/validate different fields than a Revoke request.
func (p *K8sSA) authorizeToken(token string, audiences []string) (*k8sSAPayload, error) {
_ = audiences // unused input
jwt, err := jose.ParseSigned(token)
if err != nil {
return nil, errs.Wrap(http.StatusUnauthorized, err,
@ -207,13 +208,13 @@ func (p *K8sSA) authorizeToken(token string, audiences []string) (*k8sSAPayload,
// AuthorizeRevoke returns an error if the provisioner does not have rights to
// revoke the certificate with serial number in the `sub` property.
func (p *K8sSA) AuthorizeRevoke(ctx context.Context, token string) error {
func (p *K8sSA) AuthorizeRevoke(_ context.Context, token string) error {
_, err := p.authorizeToken(token, p.ctl.Audiences.Revoke)
return errs.Wrap(http.StatusInternalServerError, err, "k8ssa.AuthorizeRevoke")
}
// AuthorizeSign validates the given token.
func (p *K8sSA) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) {
func (p *K8sSA) AuthorizeSign(_ context.Context, token string) ([]SignOption, error) {
claims, err := p.authorizeToken(token, p.ctl.Audiences.Sign)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "k8ssa.AuthorizeSign")
@ -253,7 +254,7 @@ func (p *K8sSA) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) erro
}
// AuthorizeSSHSign validates an request for an SSH certificate.
func (p *K8sSA) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
func (p *K8sSA) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption, error) {
if !p.ctl.Claimer.IsSSHCAEnabled() {
return nil, errs.Unauthorized("k8ssa.AuthorizeSSHSign; sshCA is disabled for k8sSA provisioner '%s'", p.GetName())
}

View file

@ -116,7 +116,7 @@ func (p *Nebula) GetEncryptedKey() (kid, key string, ok bool) {
}
// AuthorizeSign returns the list of SignOption for a Sign request.
func (p *Nebula) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) {
func (p *Nebula) AuthorizeSign(_ context.Context, token string) ([]SignOption, error) {
crt, claims, err := p.authorizeToken(token, p.ctl.Audiences.Sign)
if err != nil {
return nil, err
@ -171,7 +171,7 @@ func (p *Nebula) AuthorizeSign(ctx context.Context, token string) ([]SignOption,
// AuthorizeSSHSign returns the list of SignOption for a SignSSH request.
// Currently the Nebula provisioner only grants host SSH certificates.
func (p *Nebula) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
func (p *Nebula) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption, error) {
if !p.ctl.Claimer.IsSSHCAEnabled() {
return nil, errs.Unauthorized("ssh is disabled for nebula provisioner '%s'", p.Name)
}
@ -275,12 +275,12 @@ func (p *Nebula) AuthorizeRenew(ctx context.Context, crt *x509.Certificate) erro
}
// AuthorizeRevoke returns an error if the token is not valid.
func (p *Nebula) AuthorizeRevoke(ctx context.Context, token string) error {
func (p *Nebula) AuthorizeRevoke(_ context.Context, token string) error {
return p.validateToken(token, p.ctl.Audiences.Revoke)
}
// AuthorizeSSHRevoke returns an error if SSH is disabled or the token is invalid.
func (p *Nebula) AuthorizeSSHRevoke(ctx context.Context, token string) error {
func (p *Nebula) AuthorizeSSHRevoke(_ context.Context, token string) error {
if !p.ctl.Claimer.IsSSHCAEnabled() {
return errs.Unauthorized("ssh is disabled for nebula provisioner '%s'", p.Name)
}
@ -291,12 +291,12 @@ func (p *Nebula) AuthorizeSSHRevoke(ctx context.Context, token string) error {
}
// AuthorizeSSHRenew returns an unauthorized error.
func (p *Nebula) AuthorizeSSHRenew(ctx context.Context, token string) (*ssh.Certificate, error) {
func (p *Nebula) AuthorizeSSHRenew(context.Context, string) (*ssh.Certificate, error) {
return nil, errs.Unauthorized("nebula provisioner does not support SSH renew")
}
// AuthorizeSSHRekey returns an unauthorized error.
func (p *Nebula) AuthorizeSSHRekey(ctx context.Context, token string) (*ssh.Certificate, []SignOption, error) {
func (p *Nebula) AuthorizeSSHRekey(context.Context, string) (*ssh.Certificate, []SignOption, error) {
return nil, nil, errs.Unauthorized("nebula provisioner does not support SSH rekey")
}

View file

@ -18,7 +18,7 @@ func (p *noop) GetIDForToken() string {
return "noop"
}
func (p *noop) GetTokenID(token string) (string, error) {
func (p *noop) GetTokenID(string) (string, error) {
return "", nil
}
@ -33,35 +33,35 @@ func (p *noop) GetEncryptedKey() (kid, key string, ok bool) {
return "", "", false
}
func (p *noop) Init(config Config) error {
func (p *noop) Init(Config) error {
return nil
}
func (p *noop) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) {
func (p *noop) AuthorizeSign(context.Context, string) ([]SignOption, error) {
return []SignOption{p}, nil
}
func (p *noop) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error {
func (p *noop) AuthorizeRenew(context.Context, *x509.Certificate) error {
return nil
}
func (p *noop) AuthorizeRevoke(ctx context.Context, token string) error {
func (p *noop) AuthorizeRevoke(context.Context, string) error {
return nil
}
func (p *noop) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
func (p *noop) AuthorizeSSHSign(context.Context, string) ([]SignOption, error) {
return []SignOption{p}, nil
}
func (p *noop) AuthorizeSSHRenew(ctx context.Context, token string) (*ssh.Certificate, error) {
func (p *noop) AuthorizeSSHRenew(context.Context, string) (*ssh.Certificate, error) {
//nolint:nilnil // fine for noop
return nil, nil
}
func (p *noop) AuthorizeSSHRevoke(ctx context.Context, token string) error {
func (p *noop) AuthorizeSSHRevoke(context.Context, string) error {
return nil
}
func (p *noop) AuthorizeSSHRekey(ctx context.Context, token string) (*ssh.Certificate, []SignOption, error) {
func (p *noop) AuthorizeSSHRekey(context.Context, string) (*ssh.Certificate, []SignOption, error) {
return nil, []SignOption{}, nil
}

View file

@ -230,7 +230,7 @@ func (o *OIDC) ValidatePayload(p openIDPayload) error {
}
}
if !found {
return errs.Unauthorized("validatePayload: failed to validate oidc token payload: email is not allowed")
return errs.Unauthorized("validatePayload: failed to validate oidc token payload: email %q is not allowed", p.Email)
}
}
@ -292,7 +292,7 @@ func (o *OIDC) authorizeToken(token string) (*openIDPayload, error) {
// AuthorizeRevoke returns an error if the provisioner does not have rights to
// revoke the certificate with serial number in the `sub` property.
// Only tokens generated by an admin have the right to revoke a certificate.
func (o *OIDC) AuthorizeRevoke(ctx context.Context, token string) error {
func (o *OIDC) AuthorizeRevoke(_ context.Context, token string) error {
claims, err := o.authorizeToken(token)
if err != nil {
return errs.Wrap(http.StatusInternalServerError, err, "oidc.AuthorizeRevoke")
@ -307,7 +307,7 @@ func (o *OIDC) AuthorizeRevoke(ctx context.Context, token string) error {
}
// AuthorizeSign validates the given token.
func (o *OIDC) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) {
func (o *OIDC) AuthorizeSign(_ context.Context, token string) ([]SignOption, error) {
claims, err := o.authorizeToken(token)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "oidc.AuthorizeSign")
@ -463,7 +463,7 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption
}
// AuthorizeSSHRevoke returns nil if the token is valid, false otherwise.
func (o *OIDC) AuthorizeSSHRevoke(ctx context.Context, token string) error {
func (o *OIDC) AuthorizeSSHRevoke(_ context.Context, token string) error {
claims, err := o.authorizeToken(token)
if err != nil {
return errs.Wrap(http.StatusInternalServerError, err, "oidc.AuthorizeSSHRevoke")

View file

@ -13,6 +13,7 @@ import (
"testing"
"time"
"github.com/stretchr/testify/require"
"go.step.sm/crypto/jose"
"github.com/smallstep/assert"
@ -221,39 +222,37 @@ func TestOIDC_authorizeToken(t *testing.T) {
args args
code int
wantIssuer string
wantErr bool
expErr error
}{
{"ok1", p1, args{t1}, http.StatusOK, issuer, false},
{"ok tenantid", p2, args{t2}, http.StatusOK, tenantIssuer, false},
{"ok admin", p3, args{t3}, http.StatusOK, issuer, false},
{"ok domain", p3, args{t4}, http.StatusOK, issuer, false},
{"ok no email", p3, args{t5}, http.StatusOK, issuer, false},
{"fail-domain", p3, args{failDomain}, http.StatusUnauthorized, "", true},
{"fail-key", p1, args{failKey}, http.StatusUnauthorized, "", true},
{"fail-token", p1, args{failTok}, http.StatusUnauthorized, "", true},
{"fail-claims", p1, args{failClaims}, http.StatusUnauthorized, "", true},
{"fail-issuer", p1, args{failIss}, http.StatusUnauthorized, "", true},
{"fail-audience", p1, args{failAud}, http.StatusUnauthorized, "", true},
{"fail-signature", p1, args{failSig}, http.StatusUnauthorized, "", true},
{"fail-expired", p1, args{failExp}, http.StatusUnauthorized, "", true},
{"fail-not-before", p1, args{failNbf}, http.StatusUnauthorized, "", true},
{"ok1", p1, args{t1}, http.StatusOK, issuer, nil},
{"ok tenantid", p2, args{t2}, http.StatusOK, tenantIssuer, nil},
{"ok admin", p3, args{t3}, http.StatusOK, issuer, nil},
{"ok domain", p3, args{t4}, http.StatusOK, issuer, nil},
{"ok no email", p3, args{t5}, http.StatusOK, issuer, nil},
{"fail-domain", p3, args{failDomain}, http.StatusUnauthorized, "", errors.New(`oidc.AuthorizeToken: validatePayload: failed to validate oidc token payload: email "name@example.com" is not allowed`)},
{"fail-key", p1, args{failKey}, http.StatusUnauthorized, "", errors.New(`oidc.AuthorizeToken; cannot validate oidc token`)},
{"fail-token", p1, args{failTok}, http.StatusUnauthorized, "", errors.New(`oidc.AuthorizeToken; error parsing oidc token: invalid character '~' looking for beginning of value`)},
{"fail-claims", p1, args{failClaims}, http.StatusUnauthorized, "", errors.New(`oidc.AuthorizeToken; error parsing oidc token claims: invalid character '~' looking for beginning of value`)},
{"fail-issuer", p1, args{failIss}, http.StatusUnauthorized, "", errors.New(`oidc.AuthorizeToken: validatePayload: failed to validate oidc token payload: square/go-jose/jwt: validation failed, invalid issuer claim (iss)`)},
{"fail-audience", p1, args{failAud}, http.StatusUnauthorized, "", errors.New(`oidc.AuthorizeToken: validatePayload: failed to validate oidc token payload: square/go-jose/jwt: validation failed, invalid audience claim (aud)`)},
{"fail-signature", p1, args{failSig}, http.StatusUnauthorized, "", errors.New(`oidc.AuthorizeToken; cannot validate oidc token`)},
{"fail-expired", p1, args{failExp}, http.StatusUnauthorized, "", errors.New(`oidc.AuthorizeToken: validatePayload: failed to validate oidc token payload: square/go-jose/jwt: validation failed, token is expired (exp)`)},
{"fail-not-before", p1, args{failNbf}, http.StatusUnauthorized, "", errors.New(`oidc.AuthorizeToken: validatePayload: failed to validate oidc token payload: square/go-jose/jwt: validation failed, token not valid yet (nbf)`)},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.prov.authorizeToken(tt.args.token)
if (err != nil) != tt.wantErr {
fmt.Println(tt)
t.Errorf("OIDC.Authorize() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err != nil {
if tt.expErr != nil {
require.Error(t, err)
require.EqualError(t, err, tt.expErr.Error())
var sc render.StatusCodedError
assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface")
assert.Equals(t, sc.StatusCode(), tt.code)
assert.Nil(t, got)
require.ErrorAs(t, err, &sc, "error does not implement StatusCodedError interface")
require.Equal(t, tt.code, sc.StatusCode())
require.Nil(t, got)
} else {
assert.NotNil(t, got)
assert.Equals(t, got.Issuer, tt.wantIssuer)
require.NotNil(t, got)
require.Equal(t, tt.wantIssuer, got.Issuer)
}
})
}
@ -339,8 +338,6 @@ func TestOIDC_AuthorizeSign(t *testing.T) {
case *validityValidator:
assert.Equals(t, v.min, tt.prov.ctl.Claimer.MinTLSCertDuration())
assert.Equals(t, v.max, tt.prov.ctl.Claimer.MaxTLSCertDuration())
case emailOnlyIdentity:
assert.Equals(t, string(v), "name@smallstep.com")
case *x509NamePolicyValidator:
assert.Equals(t, nil, v.policyEngine)
case *WebhookController:

View file

@ -10,8 +10,9 @@ import (
"strings"
"github.com/pkg/errors"
"github.com/smallstep/certificates/errs"
"golang.org/x/crypto/ssh"
"github.com/smallstep/certificates/errs"
)
// Interface is the interface that all provisioner types must implement.
@ -297,43 +298,43 @@ type base struct{}
// AuthorizeSign returns an unimplemented error. Provisioners should overwrite
// this method if they will support authorizing tokens for signing x509 Certificates.
func (b *base) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) {
func (b *base) AuthorizeSign(context.Context, string) ([]SignOption, error) {
return nil, errs.Unauthorized("provisioner.AuthorizeSign not implemented")
}
// AuthorizeRevoke returns an unimplemented error. Provisioners should overwrite
// this method if they will support authorizing tokens for revoking x509 Certificates.
func (b *base) AuthorizeRevoke(ctx context.Context, token string) error {
func (b *base) AuthorizeRevoke(context.Context, string) error {
return errs.Unauthorized("provisioner.AuthorizeRevoke not implemented")
}
// AuthorizeRenew returns an unimplemented error. Provisioners should overwrite
// this method if they will support authorizing tokens for renewing x509 Certificates.
func (b *base) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error {
func (b *base) AuthorizeRenew(context.Context, *x509.Certificate) error {
return errs.Unauthorized("provisioner.AuthorizeRenew not implemented")
}
// AuthorizeSSHSign returns an unimplemented error. Provisioners should overwrite
// this method if they will support authorizing tokens for signing SSH Certificates.
func (b *base) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
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
// this method if they will support authorizing tokens for revoking SSH Certificates.
func (b *base) AuthorizeSSHRevoke(ctx context.Context, token string) error {
func (b *base) AuthorizeSSHRevoke(context.Context, string) error {
return errs.Unauthorized("provisioner.AuthorizeSSHRevoke not implemented")
}
// AuthorizeSSHRenew returns an unimplemented error. Provisioners should overwrite
// this method if they will support authorizing tokens for renewing SSH Certificates.
func (b *base) AuthorizeSSHRenew(ctx context.Context, token string) (*ssh.Certificate, error) {
func (b *base) AuthorizeSSHRenew(context.Context, string) (*ssh.Certificate, error) {
return nil, errs.Unauthorized("provisioner.AuthorizeSSHRenew not implemented")
}
// AuthorizeSSHRekey returns an unimplemented error. Provisioners should overwrite
// this method if they will support authorizing tokens for rekeying SSH Certificates.
func (b *base) AuthorizeSSHRekey(ctx context.Context, token string) (*ssh.Certificate, []SignOption, error) {
func (b *base) AuthorizeSSHRekey(context.Context, string) (*ssh.Certificate, []SignOption, error) {
return nil, nil, errs.Unauthorized("provisioner.AuthorizeSSHRekey not implemented")
}

View file

@ -2,10 +2,16 @@ package provisioner
import (
"context"
"crypto/subtle"
"fmt"
"net/http"
"time"
"github.com/pkg/errors"
"go.step.sm/linkedca"
"github.com/smallstep/certificates/webhook"
)
// SCEP is the SCEP provisioner type, an entity that can authorize the
@ -33,8 +39,8 @@ type SCEP struct {
Options *Options `json:"options,omitempty"`
Claims *Claims `json:"claims,omitempty"`
ctl *Controller
secretChallengePassword string
encryptionAlgorithm int
challengeValidationController *challengeValidationController
}
// GetID returns the provisioner unique identifier.
@ -67,7 +73,7 @@ func (s *SCEP) GetEncryptedKey() (string, string, bool) {
}
// GetTokenID returns the identifier of the token.
func (s *SCEP) GetTokenID(ott string) (string, error) {
func (s *SCEP) GetTokenID(string) (string, error) {
return "", errors.New("scep provisioner does not implement GetTokenID")
}
@ -82,6 +88,67 @@ func (s *SCEP) DefaultTLSCertDuration() time.Duration {
return s.ctl.Claimer.DefaultTLSCertDuration()
}
type challengeValidationController struct {
client *http.Client
webhooks []*Webhook
}
// newChallengeValidationController creates a new challengeValidationController
// that performs challenge validation through webhooks.
func newChallengeValidationController(client *http.Client, webhooks []*Webhook) *challengeValidationController {
scepHooks := []*Webhook{}
for _, wh := range webhooks {
if wh.Kind != linkedca.Webhook_SCEPCHALLENGE.String() {
continue
}
if !isCertTypeOK(wh) {
continue
}
scepHooks = append(scepHooks, wh)
}
return &challengeValidationController{
client: client,
webhooks: scepHooks,
}
}
var (
ErrSCEPChallengeInvalid = errors.New("webhook server did not allow request")
)
// Validate executes zero or more configured webhooks to
// validate the SCEP challenge. If at least one of them indicates
// the challenge value is accepted, validation succeeds. In
// that case, the other webhooks will be skipped. If none of
// the webhooks indicates the value of the challenge was accepted,
// an error is returned.
func (c *challengeValidationController) Validate(ctx context.Context, challenge, transactionID string) error {
for _, wh := range c.webhooks {
req := &webhook.RequestBody{
SCEPChallenge: challenge,
SCEPTransactionID: transactionID,
}
resp, err := wh.DoWithContext(ctx, c.client, req, nil) // TODO(hs): support templated URL? Requires some refactoring
if err != nil {
return fmt.Errorf("failed executing webhook request: %w", err)
}
if resp.Allow {
return nil // return early when response is positive
}
}
return ErrSCEPChallengeInvalid
}
// isCertTypeOK returns whether or not the webhook can be used
// with the SCEP challenge validation webhook controller.
func isCertTypeOK(wh *Webhook) bool {
if wh.CertType == linkedca.Webhook_ALL.String() || wh.CertType == "" {
return true
}
return linkedca.Webhook_X509.String() == wh.CertType
}
// Init initializes and validates the fields of a SCEP type.
func (s *SCEP) Init(config Config) (err error) {
switch {
@ -91,10 +158,6 @@ func (s *SCEP) Init(config Config) (err error) {
return errors.New("provisioner name cannot be empty")
}
// Mask the actual challenge value, so it won't be marshaled
s.secretChallengePassword = s.ChallengePassword
s.ChallengePassword = "*** redacted ***"
// Default to 2048 bits minimum public key length (for CSRs) if not set
if s.MinimumPublicKeyLength == 0 {
s.MinimumPublicKeyLength = 2048
@ -109,6 +172,11 @@ func (s *SCEP) Init(config Config) (err error) {
return errors.New("only encryption algorithm identifiers from 0 to 4 are valid")
}
s.challengeValidationController = newChallengeValidationController(
config.WebhookClient,
s.GetOptions().GetWebhooks(),
)
// TODO: add other, SCEP specific, options?
s.ctl, err = NewController(s, s.Claims, config, s.Options)
@ -118,7 +186,7 @@ func (s *SCEP) Init(config Config) (err error) {
// AuthorizeSign does not do any verification, because all verification is handled
// in the SCEP protocol. This method returns a list of modifiers / constraints
// on the resulting certificate.
func (s *SCEP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) {
func (s *SCEP) AuthorizeSign(context.Context, string) ([]SignOption, error) {
return []SignOption{
s,
// modifiers / withOptions
@ -133,11 +201,6 @@ func (s *SCEP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e
}, nil
}
// GetChallengePassword returns the challenge password
func (s *SCEP) GetChallengePassword() string {
return s.secretChallengePassword
}
// GetCapabilities returns the CA capabilities
func (s *SCEP) GetCapabilities() []string {
return s.Capabilities
@ -156,3 +219,43 @@ func (s *SCEP) ShouldIncludeRootInChain() bool {
func (s *SCEP) GetContentEncryptionAlgorithm() int {
return s.encryptionAlgorithm
}
// ValidateChallenge validates the provided challenge. It starts by
// selecting the validation method to use, then performs validation
// according to that method.
func (s *SCEP) ValidateChallenge(ctx context.Context, challenge, transactionID string) error {
if s.challengeValidationController == nil {
return fmt.Errorf("provisioner %q wasn't initialized", s.Name)
}
switch s.selectValidationMethod() {
case validationMethodWebhook:
return s.challengeValidationController.Validate(ctx, challenge, transactionID)
default:
if subtle.ConstantTimeCompare([]byte(s.ChallengePassword), []byte(challenge)) == 0 {
return errors.New("invalid challenge password provided")
}
return nil
}
}
type validationMethod string
const (
validationMethodNone validationMethod = "none"
validationMethodStatic validationMethod = "static"
validationMethodWebhook validationMethod = "webhook"
)
// selectValidationMethod returns the method to validate SCEP
// challenges. If a webhook is configured with kind `SCEPCHALLENGE`,
// the webhook method will be used. If a challenge password is set,
// the static method is used. It will default to the `none` method.
func (s *SCEP) selectValidationMethod() validationMethod {
if len(s.challengeValidationController.webhooks) > 0 {
return validationMethodWebhook
}
if s.ChallengePassword != "" {
return validationMethodStatic
}
return validationMethodNone
}

View file

@ -0,0 +1,342 @@
package provisioner
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.step.sm/linkedca"
)
func Test_challengeValidationController_Validate(t *testing.T) {
type request struct {
Challenge string `json:"scepChallenge"`
TransactionID string `json:"scepTransactionID"`
}
type response struct {
Allow bool `json:"allow"`
}
nokServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
req := &request{}
err := json.NewDecoder(r.Body).Decode(req)
require.NoError(t, err)
assert.Equal(t, "not-allowed", req.Challenge)
assert.Equal(t, "transaction-1", req.TransactionID)
b, err := json.Marshal(response{Allow: false})
require.NoError(t, err)
w.WriteHeader(200)
w.Write(b)
}))
okServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
req := &request{}
err := json.NewDecoder(r.Body).Decode(req)
require.NoError(t, err)
assert.Equal(t, "challenge", req.Challenge)
assert.Equal(t, "transaction-1", req.TransactionID)
b, err := json.Marshal(response{Allow: true})
require.NoError(t, err)
w.WriteHeader(200)
w.Write(b)
}))
type fields struct {
client *http.Client
webhooks []*Webhook
}
type args struct {
challenge string
transactionID string
}
tests := []struct {
name string
fields fields
args args
server *httptest.Server
expErr error
}{
{
name: "fail/no-webhook",
fields: fields{http.DefaultClient, nil},
args: args{"no-webhook", "transaction-1"},
expErr: errors.New("webhook server did not allow request"),
},
{
name: "fail/wrong-cert-type",
fields: fields{http.DefaultClient, []*Webhook{
{
Kind: linkedca.Webhook_SCEPCHALLENGE.String(),
CertType: linkedca.Webhook_SSH.String(),
},
}},
args: args{"wrong-cert-type", "transaction-1"},
expErr: errors.New("webhook server did not allow request"),
},
{
name: "fail/wrong-secret-value",
fields: fields{http.DefaultClient, []*Webhook{
{
ID: "webhook-id-1",
Name: "webhook-name-1",
Secret: "{{}}",
Kind: linkedca.Webhook_SCEPCHALLENGE.String(),
CertType: linkedca.Webhook_X509.String(),
URL: okServer.URL,
},
}},
args: args{
challenge: "wrong-secret-value",
transactionID: "transaction-1",
},
expErr: errors.New("failed executing webhook request: illegal base64 data at input byte 0"),
},
{
name: "fail/not-allowed",
fields: fields{http.DefaultClient, []*Webhook{
{
ID: "webhook-id-1",
Name: "webhook-name-1",
Secret: "MTIzNAo=",
Kind: linkedca.Webhook_SCEPCHALLENGE.String(),
CertType: linkedca.Webhook_X509.String(),
URL: nokServer.URL,
},
}},
args: args{
challenge: "not-allowed",
transactionID: "transaction-1",
},
server: nokServer,
expErr: errors.New("webhook server did not allow request"),
},
{
name: "ok",
fields: fields{http.DefaultClient, []*Webhook{
{
ID: "webhook-id-1",
Name: "webhook-name-1",
Secret: "MTIzNAo=",
Kind: linkedca.Webhook_SCEPCHALLENGE.String(),
CertType: linkedca.Webhook_X509.String(),
URL: okServer.URL,
},
}},
args: args{
challenge: "challenge",
transactionID: "transaction-1",
},
server: okServer,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := newChallengeValidationController(tt.fields.client, tt.fields.webhooks)
if tt.server != nil {
defer tt.server.Close()
}
ctx := context.Background()
err := c.Validate(ctx, tt.args.challenge, tt.args.transactionID)
if tt.expErr != nil {
assert.EqualError(t, err, tt.expErr.Error())
return
}
assert.NoError(t, err)
})
}
}
func TestController_isCertTypeOK(t *testing.T) {
assert.True(t, isCertTypeOK(&Webhook{CertType: linkedca.Webhook_X509.String()}))
assert.True(t, isCertTypeOK(&Webhook{CertType: linkedca.Webhook_ALL.String()}))
assert.True(t, isCertTypeOK(&Webhook{CertType: ""}))
assert.False(t, isCertTypeOK(&Webhook{CertType: linkedca.Webhook_SSH.String()}))
}
func Test_selectValidationMethod(t *testing.T) {
tests := []struct {
name string
p *SCEP
want validationMethod
}{
{"webhooks", &SCEP{
Name: "SCEP",
Type: "SCEP",
Options: &Options{
Webhooks: []*Webhook{
{
Kind: linkedca.Webhook_SCEPCHALLENGE.String(),
},
},
},
}, "webhook"},
{"challenge", &SCEP{
Name: "SCEP",
Type: "SCEP",
ChallengePassword: "pass",
}, "static"},
{"challenge-with-different-webhook", &SCEP{
Name: "SCEP",
Type: "SCEP",
Options: &Options{
Webhooks: []*Webhook{
{
Kind: linkedca.Webhook_AUTHORIZING.String(),
},
},
},
ChallengePassword: "pass",
}, "static"},
{"none", &SCEP{
Name: "SCEP",
Type: "SCEP",
}, "none"},
{"none-with-different-webhook", &SCEP{
Name: "SCEP",
Type: "SCEP",
Options: &Options{
Webhooks: []*Webhook{
{
Kind: linkedca.Webhook_AUTHORIZING.String(),
},
},
},
}, "none"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.p.Init(Config{Claims: globalProvisionerClaims})
require.NoError(t, err)
got := tt.p.selectValidationMethod()
assert.Equal(t, tt.want, got)
})
}
}
func TestSCEP_ValidateChallenge(t *testing.T) {
type request struct {
Challenge string `json:"scepChallenge"`
TransactionID string `json:"scepTransactionID"`
}
type response struct {
Allow bool `json:"allow"`
}
okServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
req := &request{}
err := json.NewDecoder(r.Body).Decode(req)
require.NoError(t, err)
assert.Equal(t, "webhook-challenge", req.Challenge)
assert.Equal(t, "webhook-transaction-1", req.TransactionID)
b, err := json.Marshal(response{Allow: true})
require.NoError(t, err)
w.WriteHeader(200)
w.Write(b)
}))
type args struct {
challenge string
transactionID string
}
tests := []struct {
name string
p *SCEP
server *httptest.Server
args args
expErr error
}{
{"ok/webhooks", &SCEP{
Name: "SCEP",
Type: "SCEP",
Options: &Options{
Webhooks: []*Webhook{
{
ID: "webhook-id-1",
Name: "webhook-name-1",
Secret: "MTIzNAo=",
Kind: linkedca.Webhook_SCEPCHALLENGE.String(),
CertType: linkedca.Webhook_X509.String(),
URL: okServer.URL,
},
},
},
}, okServer, args{"webhook-challenge", "webhook-transaction-1"},
nil,
},
{"fail/webhooks-secret-configuration", &SCEP{
Name: "SCEP",
Type: "SCEP",
Options: &Options{
Webhooks: []*Webhook{
{
ID: "webhook-id-1",
Name: "webhook-name-1",
Secret: "{{}}",
Kind: linkedca.Webhook_SCEPCHALLENGE.String(),
CertType: linkedca.Webhook_X509.String(),
URL: okServer.URL,
},
},
},
}, nil, args{"webhook-challenge", "webhook-transaction-1"},
errors.New("failed executing webhook request: illegal base64 data at input byte 0"),
},
{"ok/static-challenge", &SCEP{
Name: "SCEP",
Type: "SCEP",
Options: &Options{},
ChallengePassword: "secret-static-challenge",
}, nil, args{"secret-static-challenge", "static-transaction-1"},
nil,
},
{"fail/wrong-static-challenge", &SCEP{
Name: "SCEP",
Type: "SCEP",
Options: &Options{},
ChallengePassword: "secret-static-challenge",
}, nil, args{"the-wrong-challenge-secret", "static-transaction-1"},
errors.New("invalid challenge password provided"),
},
{"ok/no-challenge", &SCEP{
Name: "SCEP",
Type: "SCEP",
Options: &Options{},
ChallengePassword: "",
}, nil, args{"", "static-transaction-1"},
nil,
},
{"fail/no-challenge-but-provided", &SCEP{
Name: "SCEP",
Type: "SCEP",
Options: &Options{},
ChallengePassword: "",
}, nil, args{"a-challenge-value", "static-transaction-1"},
errors.New("invalid challenge password provided"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.server != nil {
defer tt.server.Close()
}
err := tt.p.Init(Config{Claims: globalProvisionerClaims, WebhookClient: http.DefaultClient})
require.NoError(t, err)
ctx := context.Background()
err = tt.p.ValidateChallenge(ctx, tt.args.challenge, tt.args.transactionID)
if tt.expErr != nil {
assert.EqualError(t, err, tt.expErr.Error())
return
}
assert.NoError(t, err)
})
}
}

View file

@ -83,31 +83,6 @@ type AttestationData struct {
PermanentIdentifier string
}
// emailOnlyIdentity is a CertificateRequestValidator that checks that the only
// SAN provided is the given email address.
type emailOnlyIdentity string
func (e emailOnlyIdentity) Valid(req *x509.CertificateRequest) error {
switch {
case len(req.DNSNames) > 0:
return errs.Forbidden("certificate request cannot contain DNS names")
case len(req.IPAddresses) > 0:
return errs.Forbidden("certificate request cannot contain IP addresses")
case len(req.URIs) > 0:
return errs.Forbidden("certificate request cannot contain URIs")
case len(req.EmailAddresses) == 0:
return errs.Forbidden("certificate request does not contain any email address")
case len(req.EmailAddresses) > 1:
return errs.Forbidden("certificate request contains too many email addresses")
case req.EmailAddresses[0] == "":
return errs.Forbidden("certificate request cannot contain an empty email address")
case req.EmailAddresses[0] != string(e):
return errs.Forbidden("certificate request does not contain the valid email address - got %s, want %s", req.EmailAddresses[0], e)
default:
return nil
}
}
// defaultPublicKeyValidator validates the public key of a certificate request.
type defaultPublicKeyValidator struct{}

View file

@ -16,38 +16,6 @@ import (
"go.step.sm/crypto/pemutil"
)
func Test_emailOnlyIdentity_Valid(t *testing.T) {
uri, err := url.Parse("https://example.com/1.0/getUser")
if err != nil {
t.Fatal(err)
}
type args struct {
req *x509.CertificateRequest
}
tests := []struct {
name string
e emailOnlyIdentity
args args
wantErr bool
}{
{"ok", "name@smallstep.com", args{&x509.CertificateRequest{EmailAddresses: []string{"name@smallstep.com"}}}, false},
{"DNSNames", "name@smallstep.com", args{&x509.CertificateRequest{DNSNames: []string{"foo.bar.zar"}}}, true},
{"IPAddresses", "name@smallstep.com", args{&x509.CertificateRequest{IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)}}}, true},
{"URIs", "name@smallstep.com", args{&x509.CertificateRequest{URIs: []*url.URL{uri}}}, true},
{"no-emails", "name@smallstep.com", args{&x509.CertificateRequest{EmailAddresses: []string{}}}, true},
{"empty-email", "", args{&x509.CertificateRequest{EmailAddresses: []string{""}}}, true},
{"multiple-emails", "name@smallstep.com", args{&x509.CertificateRequest{EmailAddresses: []string{"name@smallstep.com", "foo@smallstep.com"}}}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.e.Valid(tt.args.req); (err != nil) != tt.wantErr {
t.Errorf("emailOnlyIdentity.Valid() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_defaultPublicKeyValidator_Valid(t *testing.T) {
_shortRSA, err := pemutil.Read("./testdata/certs/short-rsa.csr")
assert.FatalError(t, err)

View file

@ -125,35 +125,6 @@ func (o SignSSHOptions) match(got SignSSHOptions) error {
return nil
}
// sshCertPrincipalsModifier is an SSHCertModifier that sets the
// principals to the SSH certificate.
type sshCertPrincipalsModifier []string
// Modify the ValidPrincipals value of the cert.
func (o sshCertPrincipalsModifier) Modify(cert *ssh.Certificate, _ SignSSHOptions) error {
cert.ValidPrincipals = []string(o)
return nil
}
// sshCertKeyIDModifier is an SSHCertModifier that sets the given
// Key ID in the SSH certificate.
type sshCertKeyIDModifier string
func (m sshCertKeyIDModifier) Modify(cert *ssh.Certificate, _ SignSSHOptions) error {
cert.KeyId = string(m)
return nil
}
// sshCertTypeModifier is an SSHCertModifier that sets the
// certificate type.
type sshCertTypeModifier string
// Modify sets the CertType for the ssh certificate.
func (m sshCertTypeModifier) Modify(cert *ssh.Certificate, _ SignSSHOptions) error {
cert.CertType = sshCertTypeUInt32(string(m))
return nil
}
// sshCertValidAfterModifier is an SSHCertModifier that sets the
// ValidAfter in the SSH certificate.
type sshCertValidAfterModifier uint64
@ -172,51 +143,6 @@ func (m sshCertValidBeforeModifier) Modify(cert *ssh.Certificate, _ SignSSHOptio
return nil
}
// sshCertDefaultsModifier implements a SSHCertModifier that
// modifies the certificate with the given options if they are not set.
type sshCertDefaultsModifier SignSSHOptions
// Modify implements the SSHCertModifier interface.
func (m sshCertDefaultsModifier) Modify(cert *ssh.Certificate, _ SignSSHOptions) error {
if cert.CertType == 0 {
cert.CertType = sshCertTypeUInt32(m.CertType)
}
if len(cert.ValidPrincipals) == 0 {
cert.ValidPrincipals = m.Principals
}
if cert.ValidAfter == 0 && !m.ValidAfter.IsZero() {
cert.ValidAfter = uint64(m.ValidAfter.Unix())
}
if cert.ValidBefore == 0 && !m.ValidBefore.IsZero() {
cert.ValidBefore = uint64(m.ValidBefore.Unix())
}
return nil
}
// sshDefaultExtensionModifier implements an SSHCertModifier that sets
// the default extensions in an SSH certificate.
type sshDefaultExtensionModifier struct{}
func (m *sshDefaultExtensionModifier) Modify(cert *ssh.Certificate, _ SignSSHOptions) error {
switch cert.CertType {
// Default to no extensions for HostCert.
case ssh.HostCert:
return nil
case ssh.UserCert:
if cert.Extensions == nil {
cert.Extensions = make(map[string]string)
}
cert.Extensions["permit-X11-forwarding"] = ""
cert.Extensions["permit-agent-forwarding"] = ""
cert.Extensions["permit-port-forwarding"] = ""
cert.Extensions["permit-pty"] = ""
cert.Extensions["permit-user-rc"] = ""
return nil
default:
return errs.BadRequest("ssh certificate has an unknown type '%d'", cert.CertType)
}
}
// sshDefaultDuration is an SSHCertModifier that sets the certificate
// ValidAfter and ValidBefore if they have not been set. It will fail if a
// CertType has not been set or is not valid.
@ -385,7 +311,7 @@ type sshCertDefaultValidator struct{}
// Valid returns an error if the given certificate does not contain the
// necessary fields. We skip ValidPrincipals and Extensions as with custom
// templates you can set them empty.
func (v *sshCertDefaultValidator) Valid(cert *ssh.Certificate, o SignSSHOptions) error {
func (v *sshCertDefaultValidator) Valid(cert *ssh.Certificate, _ SignSSHOptions) error {
switch {
case len(cert.Nonce) == 0:
return errs.Forbidden("ssh certificate nonce cannot be empty")
@ -420,7 +346,7 @@ type sshDefaultPublicKeyValidator struct{}
// TODO: this is the only validator that checks the key type. We should execute
// this before the signing. We should add a new validations interface or extend
// SSHCertOptionsValidator with the key.
func (v sshDefaultPublicKeyValidator) Valid(cert *ssh.Certificate, o SignSSHOptions) error {
func (v sshDefaultPublicKeyValidator) Valid(cert *ssh.Certificate, _ SignSSHOptions) error {
if cert.Key == nil {
return errs.BadRequest("ssh certificate key cannot be nil")
}

View file

@ -202,97 +202,6 @@ func TestSSHOptions_Match(t *testing.T) {
}
}
func Test_sshCertPrincipalsModifier_Modify(t *testing.T) {
type test struct {
modifier sshCertPrincipalsModifier
cert *ssh.Certificate
expected []string
}
tests := map[string]func() test{
"ok": func() test {
a := []string{"foo", "bar"}
return test{
modifier: sshCertPrincipalsModifier(a),
cert: new(ssh.Certificate),
expected: a,
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
tc := run()
if assert.Nil(t, tc.modifier.Modify(tc.cert, SignSSHOptions{})) {
assert.Equals(t, tc.cert.ValidPrincipals, tc.expected)
}
})
}
}
func Test_sshCertKeyIDModifier_Modify(t *testing.T) {
type test struct {
modifier sshCertKeyIDModifier
cert *ssh.Certificate
expected string
}
tests := map[string]func() test{
"ok": func() test {
a := "foo"
return test{
modifier: sshCertKeyIDModifier(a),
cert: new(ssh.Certificate),
expected: a,
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
tc := run()
if assert.Nil(t, tc.modifier.Modify(tc.cert, SignSSHOptions{})) {
assert.Equals(t, tc.cert.KeyId, tc.expected)
}
})
}
}
func Test_sshCertTypeModifier_Modify(t *testing.T) {
type test struct {
modifier sshCertTypeModifier
cert *ssh.Certificate
expected uint32
}
tests := map[string]func() test{
"ok/user": func() test {
return test{
modifier: sshCertTypeModifier("user"),
cert: new(ssh.Certificate),
expected: ssh.UserCert,
}
},
"ok/host": func() test {
return test{
modifier: sshCertTypeModifier("host"),
cert: new(ssh.Certificate),
expected: ssh.HostCert,
}
},
"ok/default": func() test {
return test{
modifier: sshCertTypeModifier("foo"),
cert: new(ssh.Certificate),
expected: 0,
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
tc := run()
if assert.Nil(t, tc.modifier.Modify(tc.cert, SignSSHOptions{})) {
assert.Equals(t, tc.cert.CertType, tc.expected)
}
})
}
}
func Test_sshCertValidAfterModifier_Modify(t *testing.T) {
type test struct {
modifier sshCertValidAfterModifier
@ -318,176 +227,6 @@ func Test_sshCertValidAfterModifier_Modify(t *testing.T) {
}
}
func Test_sshCertDefaultsModifier_Modify(t *testing.T) {
type test struct {
modifier sshCertDefaultsModifier
cert *ssh.Certificate
valid func(*ssh.Certificate)
}
tests := map[string]func() test{
"ok/changes": func() test {
n := time.Now()
va := NewTimeDuration(n.Add(1 * time.Minute))
vb := NewTimeDuration(n.Add(5 * time.Minute))
so := SignSSHOptions{
Principals: []string{"foo", "bar"},
CertType: "host",
ValidAfter: va,
ValidBefore: vb,
}
return test{
modifier: sshCertDefaultsModifier(so),
cert: new(ssh.Certificate),
valid: func(cert *ssh.Certificate) {
assert.Equals(t, cert.ValidPrincipals, so.Principals)
assert.Equals(t, cert.CertType, uint32(ssh.HostCert))
assert.Equals(t, cert.ValidAfter, uint64(so.ValidAfter.RelativeTime(time.Now()).Unix()))
assert.Equals(t, cert.ValidBefore, uint64(so.ValidBefore.RelativeTime(time.Now()).Unix()))
},
}
},
"ok/no-changes": func() test {
n := time.Now()
so := SignSSHOptions{
Principals: []string{"foo", "bar"},
CertType: "host",
ValidAfter: NewTimeDuration(n.Add(15 * time.Minute)),
ValidBefore: NewTimeDuration(n.Add(25 * time.Minute)),
}
return test{
modifier: sshCertDefaultsModifier(so),
cert: &ssh.Certificate{
CertType: uint32(ssh.UserCert),
ValidPrincipals: []string{"zap", "zoop"},
ValidAfter: 15,
ValidBefore: 25,
},
valid: func(cert *ssh.Certificate) {
assert.Equals(t, cert.ValidPrincipals, []string{"zap", "zoop"})
assert.Equals(t, cert.CertType, uint32(ssh.UserCert))
assert.Equals(t, cert.ValidAfter, uint64(15))
assert.Equals(t, cert.ValidBefore, uint64(25))
},
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
tc := run()
if assert.Nil(t, tc.modifier.Modify(tc.cert, SignSSHOptions{})) {
tc.valid(tc.cert)
}
})
}
}
func Test_sshDefaultExtensionModifier_Modify(t *testing.T) {
type test struct {
modifier sshDefaultExtensionModifier
cert *ssh.Certificate
valid func(*ssh.Certificate)
err error
}
tests := map[string]func() test{
"fail/unexpected-cert-type": func() test {
cert := &ssh.Certificate{CertType: 3}
return test{
modifier: sshDefaultExtensionModifier{},
cert: cert,
err: errors.New("ssh certificate has an unknown type '3'"),
}
},
"ok/host": func() test {
cert := &ssh.Certificate{CertType: ssh.HostCert}
return test{
modifier: sshDefaultExtensionModifier{},
cert: cert,
valid: func(cert *ssh.Certificate) {
assert.Len(t, 0, cert.Extensions)
},
}
},
"ok/user/extensions-exists": func() test {
cert := &ssh.Certificate{CertType: ssh.UserCert, Permissions: ssh.Permissions{Extensions: map[string]string{
"foo": "bar",
}}}
return test{
modifier: sshDefaultExtensionModifier{},
cert: cert,
valid: func(cert *ssh.Certificate) {
val, ok := cert.Extensions["foo"]
assert.True(t, ok)
assert.Equals(t, val, "bar")
val, ok = cert.Extensions["permit-X11-forwarding"]
assert.True(t, ok)
assert.Equals(t, val, "")
val, ok = cert.Extensions["permit-agent-forwarding"]
assert.True(t, ok)
assert.Equals(t, val, "")
val, ok = cert.Extensions["permit-port-forwarding"]
assert.True(t, ok)
assert.Equals(t, val, "")
val, ok = cert.Extensions["permit-pty"]
assert.True(t, ok)
assert.Equals(t, val, "")
val, ok = cert.Extensions["permit-user-rc"]
assert.True(t, ok)
assert.Equals(t, val, "")
},
}
},
"ok/user/no-extensions": func() test {
return test{
modifier: sshDefaultExtensionModifier{},
cert: &ssh.Certificate{CertType: ssh.UserCert},
valid: func(cert *ssh.Certificate) {
_, ok := cert.Extensions["foo"]
assert.False(t, ok)
val, ok := cert.Extensions["permit-X11-forwarding"]
assert.True(t, ok)
assert.Equals(t, val, "")
val, ok = cert.Extensions["permit-agent-forwarding"]
assert.True(t, ok)
assert.Equals(t, val, "")
val, ok = cert.Extensions["permit-port-forwarding"]
assert.True(t, ok)
assert.Equals(t, val, "")
val, ok = cert.Extensions["permit-pty"]
assert.True(t, ok)
assert.Equals(t, val, "")
val, ok = cert.Extensions["permit-user-rc"]
assert.True(t, ok)
assert.Equals(t, val, "")
},
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
tc := run()
if err := tc.modifier.Modify(tc.cert, SignSSHOptions{}); err != nil {
if assert.NotNil(t, tc.err) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
}
} else {
if assert.Nil(t, tc.err) {
tc.valid(tc.cert)
}
}
})
}
}
func Test_sshCertDefaultValidator_Valid(t *testing.T) {
pub, _, err := keyutil.GenerateDefaultKeyPair()
assert.FatalError(t, err)

View file

@ -187,7 +187,7 @@ func (p *SSHPOP) authorizeToken(token string, audiences []string, checkValidity
// AuthorizeSSHRevoke validates the authorization token and extracts/validates
// the SSH certificate from the ssh-pop header.
func (p *SSHPOP) AuthorizeSSHRevoke(ctx context.Context, token string) error {
func (p *SSHPOP) AuthorizeSSHRevoke(_ context.Context, token string) error {
claims, err := p.authorizeToken(token, p.ctl.Audiences.SSHRevoke, true)
if err != nil {
return errs.Wrap(http.StatusInternalServerError, err, "sshpop.AuthorizeSSHRevoke")
@ -213,7 +213,7 @@ func (p *SSHPOP) AuthorizeSSHRenew(ctx context.Context, token string) (*ssh.Cert
// AuthorizeSSHRekey validates the authorization token and extracts/validates
// the SSH certificate from the ssh-pop header.
func (p *SSHPOP) AuthorizeSSHRekey(ctx context.Context, token string) (*ssh.Certificate, []SignOption, error) {
func (p *SSHPOP) AuthorizeSSHRekey(_ context.Context, token string) (*ssh.Certificate, []SignOption, error) {
claims, err := p.authorizeToken(token, p.ctl.Audiences.SSHRekey, true)
if err != nil {
return nil, nil, errs.Wrap(http.StatusInternalServerError, err, "sshpop.AuthorizeSSHRekey")

View file

@ -665,6 +665,9 @@ func generateAzureWithServer() (*Azure, *httptest.Server, error) {
AccessToken: tok,
})
}
case "/metadata/instance/compute/azEnvironment":
w.Header().Add("Content-Type", "text/plain")
w.Write([]byte("AzurePublicCloud"))
default:
http.NotFound(w, r)
}
@ -672,6 +675,7 @@ func generateAzureWithServer() (*Azure, *httptest.Server, error) {
srv.Start()
az.config.oidcDiscoveryURL = srv.URL + "/" + az.TenantID + "/.well-known/openid-configuration"
az.config.identityTokenURL = srv.URL + "/metadata/identity/oauth2/token"
az.config.instanceComputeURL = srv.URL + "/metadata/instance/compute/azEnvironment"
return az, srv, nil
}

View file

@ -30,6 +30,7 @@ type WebhookController struct {
client *http.Client
webhooks []*Webhook
certType linkedca.Webhook_CertType
options []webhook.RequestBodyOption
TemplateData WebhookSetter
}
@ -39,6 +40,14 @@ func (wc *WebhookController) Enrich(req *webhook.RequestBody) error {
if wc == nil {
return nil
}
// Apply extra options in the webhook controller
for _, fn := range wc.options {
if err := fn(req); err != nil {
return err
}
}
for _, wh := range wc.webhooks {
if wh.Kind != linkedca.Webhook_ENRICHING.String() {
continue
@ -63,6 +72,14 @@ func (wc *WebhookController) Authorize(req *webhook.RequestBody) error {
if wc == nil {
return nil
}
// Apply extra options in the webhook controller
for _, fn := range wc.options {
if err := fn(req); err != nil {
return err
}
}
for _, wh := range wc.webhooks {
if wh.Kind != linkedca.Webhook_AUTHORIZING.String() {
continue
@ -107,6 +124,13 @@ type Webhook struct {
}
func (w *Webhook) Do(client *http.Client, reqBody *webhook.RequestBody, data any) (*webhook.ResponseBody, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
return w.DoWithContext(ctx, client, reqBody, data)
}
func (w *Webhook) DoWithContext(ctx context.Context, client *http.Client, reqBody *webhook.RequestBody, data any) (*webhook.ResponseBody, error) {
tmpl, err := template.New("url").Funcs(templates.StepFuncMap()).Parse(w.URL)
if err != nil {
return nil, err
@ -129,8 +153,6 @@ func (w *Webhook) Do(client *http.Client, reqBody *webhook.RequestBody, data any
reqBody.Token = tmpl[sshutil.TokenKey]
}
*/
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
reqBody.Timestamp = time.Now()

View file

@ -4,6 +4,7 @@ import (
"crypto/hmac"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
@ -16,6 +17,7 @@ import (
"github.com/pkg/errors"
"github.com/smallstep/assert"
"github.com/smallstep/certificates/webhook"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util"
"go.step.sm/linkedca"
)
@ -96,12 +98,18 @@ func TestWebhookController_isCertTypeOK(t *testing.T) {
}
func TestWebhookController_Enrich(t *testing.T) {
cert, err := pemutil.ReadCertificate("testdata/certs/x5c-leaf.crt", pemutil.WithFirstBlock())
if err != nil {
t.Fatal(err)
}
type test struct {
ctl *WebhookController
req *webhook.RequestBody
responses []*webhook.ResponseBody
expectErr bool
expectTemplateData any
assertRequest func(t *testing.T, req *webhook.RequestBody)
}
tests := map[string]test{
"ok/no enriching webhooks": {
@ -170,6 +178,29 @@ func TestWebhookController_Enrich(t *testing.T) {
},
},
},
"ok/with options": {
ctl: &WebhookController{
client: http.DefaultClient,
webhooks: []*Webhook{{Name: "people", Kind: "ENRICHING"}},
TemplateData: x509util.TemplateData{},
options: []webhook.RequestBodyOption{webhook.WithX5CCertificate(cert)},
},
req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: true, Data: map[string]any{"role": "bar"}}},
expectErr: false,
expectTemplateData: x509util.TemplateData{"Webhooks": map[string]any{"people": map[string]any{"role": "bar"}}},
assertRequest: func(t *testing.T, req *webhook.RequestBody) {
key, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
assert.FatalError(t, err)
assert.Equals(t, &webhook.X5CCertificate{
Raw: cert.Raw,
PublicKey: key,
PublicKeyAlgorithm: cert.PublicKeyAlgorithm.String(),
NotBefore: cert.NotBefore,
NotAfter: cert.NotAfter,
}, req.X5CCertificate)
},
},
"deny": {
ctl: &WebhookController{
client: http.DefaultClient,
@ -181,6 +212,20 @@ func TestWebhookController_Enrich(t *testing.T) {
expectErr: true,
expectTemplateData: x509util.TemplateData{},
},
"fail/with options": {
ctl: &WebhookController{
client: http.DefaultClient,
webhooks: []*Webhook{{Name: "people", Kind: "ENRICHING"}},
TemplateData: x509util.TemplateData{},
options: []webhook.RequestBodyOption{webhook.WithX5CCertificate(&x509.Certificate{
PublicKey: []byte("bad"),
})},
},
req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: false}},
expectErr: true,
expectTemplateData: x509util.TemplateData{},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
@ -200,16 +245,25 @@ func TestWebhookController_Enrich(t *testing.T) {
t.Fatalf("Got err %v, want %v", err, test.expectErr)
}
assert.Equals(t, test.expectTemplateData, test.ctl.TemplateData)
if test.assertRequest != nil {
test.assertRequest(t, test.req)
}
})
}
}
func TestWebhookController_Authorize(t *testing.T) {
cert, err := pemutil.ReadCertificate("testdata/certs/x5c-leaf.crt", pemutil.WithFirstBlock())
if err != nil {
t.Fatal(err)
}
type test struct {
ctl *WebhookController
req *webhook.RequestBody
responses []*webhook.ResponseBody
expectErr bool
ctl *WebhookController
req *webhook.RequestBody
responses []*webhook.ResponseBody
expectErr bool
assertRequest func(t *testing.T, req *webhook.RequestBody)
}
tests := map[string]test{
"ok/no enriching webhooks": {
@ -240,6 +294,27 @@ func TestWebhookController_Authorize(t *testing.T) {
responses: []*webhook.ResponseBody{{Allow: false}},
expectErr: false,
},
"ok/with options": {
ctl: &WebhookController{
client: http.DefaultClient,
webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING"}},
options: []webhook.RequestBodyOption{webhook.WithX5CCertificate(cert)},
},
req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: true}},
expectErr: false,
assertRequest: func(t *testing.T, req *webhook.RequestBody) {
key, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
assert.FatalError(t, err)
assert.Equals(t, &webhook.X5CCertificate{
Raw: cert.Raw,
PublicKey: key,
PublicKeyAlgorithm: cert.PublicKeyAlgorithm.String(),
NotBefore: cert.NotBefore,
NotAfter: cert.NotAfter,
}, req.X5CCertificate)
},
},
"deny": {
ctl: &WebhookController{
client: http.DefaultClient,
@ -249,6 +324,18 @@ func TestWebhookController_Authorize(t *testing.T) {
responses: []*webhook.ResponseBody{{Allow: false}},
expectErr: true,
},
"fail/with options": {
ctl: &WebhookController{
client: http.DefaultClient,
webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING"}},
options: []webhook.RequestBodyOption{webhook.WithX5CCertificate(&x509.Certificate{
PublicKey: []byte("bad"),
})},
},
req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: false}},
expectErr: true,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
@ -267,6 +354,9 @@ func TestWebhookController_Authorize(t *testing.T) {
if (err != nil) != test.expectErr {
t.Fatalf("Got err %v, want %v", err, test.expectErr)
}
if test.assertRequest != nil {
test.assertRequest(t, test.req)
}
})
}
}

View file

@ -15,6 +15,7 @@ import (
"go.step.sm/linkedca"
"github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/webhook"
)
// x5cPayload extends jwt.Claims with step attributes.
@ -187,13 +188,13 @@ func (p *X5C) authorizeToken(token string, audiences []string) (*x5cPayload, err
// AuthorizeRevoke returns an error if the provisioner does not have rights to
// revoke the certificate with serial number in the `sub` property.
func (p *X5C) AuthorizeRevoke(ctx context.Context, token string) error {
func (p *X5C) AuthorizeRevoke(_ context.Context, token string) error {
_, err := p.authorizeToken(token, p.ctl.Audiences.Revoke)
return errs.Wrap(http.StatusInternalServerError, err, "x5c.AuthorizeRevoke")
}
// AuthorizeSign validates the given token.
func (p *X5C) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) {
func (p *X5C) AuthorizeSign(_ context.Context, token string) ([]SignOption, error) {
claims, err := p.authorizeToken(token, p.ctl.Audiences.Sign)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "x5c.AuthorizeSign")
@ -215,7 +216,8 @@ func (p *X5C) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
// The X509 certificate will be available using the template variable
// AuthorizationCrt. For example {{ .AuthorizationCrt.DNSNames }} can be
// used to get all the domains.
data.SetAuthorizationCertificate(claims.chains[0][0])
x5cLeaf := claims.chains[0][0]
data.SetAuthorizationCertificate(x5cLeaf)
templateOptions, err := TemplateOptions(p.Options, data)
if err != nil {
@ -238,7 +240,7 @@ func (p *X5C) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
newProvisionerExtensionOption(TypeX5C, p.Name, ""),
profileLimitDuration{
p.ctl.Claimer.DefaultTLSCertDuration(),
claims.chains[0][0].NotBefore, claims.chains[0][0].NotAfter,
x5cLeaf.NotBefore, x5cLeaf.NotAfter,
},
// validators
commonNameValidator(claims.Subject),
@ -246,7 +248,12 @@ func (p *X5C) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
defaultPublicKeyValidator{},
newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()),
newX509NamePolicyValidator(p.ctl.getPolicy().getX509()),
p.ctl.newWebhookController(data, linkedca.Webhook_X509),
p.ctl.newWebhookController(
data,
linkedca.Webhook_X509,
webhook.WithX5CCertificate(x5cLeaf),
webhook.WithAuthorizationPrincipal(x5cLeaf.Subject.CommonName),
),
}, nil
}
@ -256,7 +263,7 @@ func (p *X5C) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error
}
// AuthorizeSSHSign returns the list of SignOption for a SignSSH request.
func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
func (p *X5C) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption, error) {
if !p.ctl.Claimer.IsSSHCAEnabled() {
return nil, errs.Unauthorized("x5c.AuthorizeSSHSign; sshCA is disabled for x5c provisioner '%s'", p.GetName())
}
@ -305,7 +312,8 @@ func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
// The X509 certificate will be available using the template variable
// AuthorizationCrt. For example {{ .AuthorizationCrt.DNSNames }} can be
// used to get all the domains.
data.SetAuthorizationCertificate(claims.chains[0][0])
x5cLeaf := claims.chains[0][0]
data.SetAuthorizationCertificate(x5cLeaf)
templateOptions, err := TemplateSSHOptions(p.Options, data)
if err != nil {
@ -325,7 +333,7 @@ func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
return append(signOptions,
p,
// Checks the validity bounds, and set the validity if has not been set.
&sshLimitDuration{p.ctl.Claimer, claims.chains[0][0].NotAfter},
&sshLimitDuration{p.ctl.Claimer, x5cLeaf.NotAfter},
// Validate public key.
&sshDefaultPublicKeyValidator{},
// Validate the validity period.
@ -335,6 +343,11 @@ func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
// Ensure that all principal names are allowed
newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), p.ctl.getPolicy().getSSHUser()),
// Call webhooks
p.ctl.newWebhookController(data, linkedca.Webhook_SSH),
p.ctl.newWebhookController(
data,
linkedca.Webhook_SSH,
webhook.WithX5CCertificate(x5cLeaf),
webhook.WithAuthorizationPrincipal(x5cLeaf.Subject.CommonName),
),
), nil
}

View file

@ -12,6 +12,7 @@ import (
"go.step.sm/crypto/jose"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/randutil"
"go.step.sm/linkedca"
"github.com/smallstep/assert"
"github.com/smallstep/certificates/api/render"
@ -497,6 +498,8 @@ func TestX5C_AuthorizeSign(t *testing.T) {
assert.Equals(t, nil, v.policyEngine)
case *WebhookController:
assert.Len(t, 0, v.webhooks)
assert.Equals(t, linkedca.Webhook_X509, v.certType)
assert.Len(t, 2, v.options)
default:
assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v))
}
@ -790,8 +793,6 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) {
assert.Equals(t, int64(v), tc.claims.Step.SSH.ValidAfter.RelativeTime(nw).Unix())
case sshCertValidBeforeModifier:
assert.Equals(t, int64(v), tc.claims.Step.SSH.ValidBefore.RelativeTime(nw).Unix())
case sshCertDefaultsModifier:
assert.Equals(t, SignSSHOptions(v), SignSSHOptions{CertType: SSHUserCert})
case *sshLimitDuration:
assert.Equals(t, v.Claimer, tc.p.ctl.Claimer)
assert.Equals(t, v.NotAfter, x5cCerts[0].NotAfter)
@ -803,6 +804,8 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) {
case *sshDefaultPublicKeyValidator, *sshCertDefaultValidator, sshCertificateOptionsFunc:
case *WebhookController:
assert.Len(t, 0, v.webhooks)
assert.Equals(t, linkedca.Webhook_SSH, v.certType)
assert.Len(t, 2, v.options)
default:
assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v))
}

View file

@ -1223,7 +1223,7 @@ func ProvisionerToLinkedca(p provisioner.Interface) (*linkedca.Provisioner, erro
Data: &linkedca.ProvisionerDetails_SCEP{
SCEP: &linkedca.SCEPProvisioner{
ForceCn: p.ForceCN,
Challenge: p.GetChallengePassword(),
Challenge: p.ChallengePassword,
Capabilities: p.Capabilities,
MinimumPublicKeyLength: int32(p.MinimumPublicKeyLength),
IncludeRoot: p.IncludeRoot,

View file

@ -9,14 +9,17 @@ import (
"testing"
"time"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil"
"go.step.sm/linkedca"
"github.com/stretchr/testify/require"
"github.com/smallstep/assert"
"github.com/smallstep/certificates/api/render"
"github.com/smallstep/certificates/authority/admin"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/db"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil"
"go.step.sm/linkedca"
)
func TestGetEncryptedKey(t *testing.T) {
@ -29,9 +32,9 @@ func TestGetEncryptedKey(t *testing.T) {
tests := map[string]func(t *testing.T) *ek{
"ok": func(t *testing.T) *ek {
c, err := LoadConfiguration("../ca/testdata/ca.json")
assert.FatalError(t, err)
require.NoError(t, err)
a, err := New(c)
assert.FatalError(t, err)
require.NoError(t, err)
return &ek{
a: a,
kid: c.AuthorityConfig.Provisioners[1].(*provisioner.JWK).Key.KeyID,
@ -39,9 +42,9 @@ func TestGetEncryptedKey(t *testing.T) {
},
"fail-not-found": func(t *testing.T) *ek {
c, err := LoadConfiguration("../ca/testdata/ca.json")
assert.FatalError(t, err)
require.NoError(t, err)
a, err := New(c)
assert.FatalError(t, err)
require.NoError(t, err)
return &ek{
a: a,
kid: "foo",
@ -95,9 +98,16 @@ func TestGetProvisioners(t *testing.T) {
tests := map[string]func(t *testing.T) *gp{
"ok": func(t *testing.T) *gp {
c, err := LoadConfiguration("../ca/testdata/ca.json")
assert.FatalError(t, err)
require.NoError(t, err)
a, err := New(c)
assert.FatalError(t, err)
require.NoError(t, err)
return &gp{a: a}
},
"ok/rsa": func(t *testing.T) *gp {
c, err := LoadConfiguration("../ca/testdata/rsaca.json")
require.NoError(t, err)
a, err := New(c)
require.NoError(t, err)
return &gp{a: a}
},
}
@ -111,13 +121,13 @@ func TestGetProvisioners(t *testing.T) {
if assert.NotNil(t, tc.err) {
var sc render.StatusCodedError
if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") {
assert.Equals(t, sc.StatusCode(), tc.code)
assert.Equals(t, tc.code, sc.StatusCode())
}
assert.HasPrefix(t, err.Error(), tc.err.Error())
assert.HasPrefix(t, tc.err.Error(), err.Error())
}
} else {
if assert.Nil(t, tc.err) {
assert.Equals(t, ps, tc.a.config.AuthorityConfig.Provisioners)
assert.Equals(t, tc.a.config.AuthorityConfig.Provisioners, ps)
assert.Equals(t, "", next)
}
}
@ -127,20 +137,20 @@ func TestGetProvisioners(t *testing.T) {
func TestAuthority_LoadProvisionerByCertificate(t *testing.T) {
_, priv, err := keyutil.GenerateDefaultKeyPair()
assert.FatalError(t, err)
require.NoError(t, err)
csr := getCSR(t, priv)
sign := func(a *Authority, extraOpts ...provisioner.SignOption) *x509.Certificate {
key, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
assert.FatalError(t, err)
require.NoError(t, err)
token, err := generateToken("smallstep test", "step-cli", testAudiences.Sign[0], []string{"test.smallstep.com"}, time.Now(), key)
assert.FatalError(t, err)
require.NoError(t, err)
ctx := provisioner.NewContextWithMethod(context.Background(), provisioner.SignMethod)
opts, err := a.Authorize(ctx, token)
assert.FatalError(t, err)
require.NoError(t, err)
opts = append(opts, extraOpts...)
certs, err := a.Sign(csr, provisioner.SignOptions{}, opts...)
assert.FatalError(t, err)
require.NoError(t, err)
return certs[0]
}
getProvisioner := func(a *Authority, name string) provisioner.Interface {
@ -169,9 +179,7 @@ func TestAuthority_LoadProvisionerByCertificate(t *testing.T) {
},
MGetCertificateData: func(serialNumber string) (*db.CertificateData, error) {
p, err := a1.LoadProvisionerByName("dev")
if err != nil {
t.Fatal(err)
}
require.NoError(t, err)
return &db.CertificateData{
Provisioner: &db.ProvisionerData{
ID: p.GetID(),
@ -186,9 +194,7 @@ func TestAuthority_LoadProvisionerByCertificate(t *testing.T) {
a2.adminDB = &mockAdminDB{
MGetCertificateData: (func(s string) (*db.CertificateData, error) {
p, err := a2.LoadProvisionerByName("dev")
if err != nil {
t.Fatal(err)
}
require.NoError(t, err)
return &db.CertificateData{
Provisioner: &db.ProvisionerData{
ID: p.GetID(),

View file

@ -52,7 +52,7 @@ func (a *Authority) GetSSHFederation(context.Context) (*config.SSHKeys, error) {
}
// GetSSHConfig returns rendered templates for clients (user) or servers (host).
func (a *Authority) GetSSHConfig(ctx context.Context, typ string, data map[string]string) ([]templates.Output, error) {
func (a *Authority) GetSSHConfig(_ context.Context, typ string, data map[string]string) ([]templates.Output, error) {
if a.sshCAUserCertSignKey == nil && a.sshCAHostCertSignKey == nil {
return nil, errs.NotFound("getSSHConfig: ssh is not configured")
}
@ -146,7 +146,7 @@ func (a *Authority) GetSSHBastion(ctx context.Context, user, hostname string) (*
}
// SignSSH creates a signed SSH certificate with the given public key and options.
func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) {
func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) {
var (
certOptions []sshutil.Option
mods []provisioner.SSHCertModifier
@ -663,11 +663,7 @@ func callEnrichingWebhooksSSH(webhookCtl webhookController, cr sshutil.Certifica
if err != nil {
return err
}
if err := webhookCtl.Enrich(whEnrichReq); err != nil {
return err
}
return nil
return webhookCtl.Enrich(whEnrichReq)
}
func callAuthorizingWebhooksSSH(webhookCtl webhookController, cert *sshutil.Certificate, certTpl *ssh.Certificate) error {
@ -680,9 +676,5 @@ func callAuthorizingWebhooksSSH(webhookCtl webhookController, cert *sshutil.Cert
if err != nil {
return err
}
if err := webhookCtl.Authorize(whAuthBody); err != nil {
return err
}
return nil
return webhookCtl.Authorize(whAuthBody)
}

View file

@ -55,7 +55,7 @@ func (m sshTestModifier) Modify(cert *ssh.Certificate, _ provisioner.SignSSHOpti
type sshTestCertModifier string
func (m sshTestCertModifier) Modify(cert *ssh.Certificate, opts provisioner.SignSSHOptions) error {
func (m sshTestCertModifier) Modify(*ssh.Certificate, provisioner.SignSSHOptions) error {
if m == "" {
return nil
}
@ -64,7 +64,7 @@ func (m sshTestCertModifier) Modify(cert *ssh.Certificate, opts provisioner.Sign
type sshTestCertValidator string
func (v sshTestCertValidator) Valid(crt *ssh.Certificate, opts provisioner.SignSSHOptions) error {
func (v sshTestCertValidator) Valid(*ssh.Certificate, provisioner.SignSSHOptions) error {
if v == "" {
return nil
}
@ -73,7 +73,7 @@ func (v sshTestCertValidator) Valid(crt *ssh.Certificate, opts provisioner.SignS
type sshTestOptionsValidator string
func (v sshTestOptionsValidator) Valid(opts provisioner.SignSSHOptions) error {
func (v sshTestOptionsValidator) Valid(provisioner.SignSSHOptions) error {
if v == "" {
return nil
}
@ -82,7 +82,7 @@ func (v sshTestOptionsValidator) Valid(opts provisioner.SignSSHOptions) error {
type sshTestOptionsModifier string
func (m sshTestOptionsModifier) Modify(cert *ssh.Certificate, opts provisioner.SignSSHOptions) error {
func (m sshTestOptionsModifier) Modify(*ssh.Certificate, provisioner.SignSSHOptions) error {
if m == "" {
return nil
}

View file

@ -303,7 +303,7 @@ func (a *Authority) isAllowedToSignX509Certificate(cert *x509.Certificate) error
// AreSANsAllowed evaluates the provided sans against the
// authority X.509 policy.
func (a *Authority) AreSANsAllowed(ctx context.Context, sans []string) error {
func (a *Authority) AreSANsAllowed(_ context.Context, sans []string) error {
return a.policyEngine.AreSANsAllowed(sans)
}
@ -786,7 +786,7 @@ func (a *Authority) GenerateCertificateRevocationList() error {
// Note that this is currently using the port 443 by default.
if b, err := marshalDistributionPoint(fullName, false); err == nil {
revocationList.ExtraExtensions = []pkix.Extension{
{Id: oidExtensionIssuingDistributionPoint, Value: b},
{Id: oidExtensionIssuingDistributionPoint, Critical: true, Value: b},
}
}
@ -969,11 +969,7 @@ func callEnrichingWebhooksX509(webhookCtl webhookController, attData *provisione
if err != nil {
return err
}
if err := webhookCtl.Enrich(whEnrichReq); err != nil {
return err
}
return nil
return webhookCtl.Enrich(whEnrichReq)
}
func callAuthorizingWebhooksX509(webhookCtl webhookController, cert *x509util.Certificate, leaf *x509.Certificate, attData *provisioner.AttestationData) error {
@ -993,9 +989,5 @@ func callAuthorizingWebhooksX509(webhookCtl webhookController, cert *x509util.Ce
if err != nil {
return err
}
if err := webhookCtl.Authorize(whAuthBody); err != nil {
return err
}
return nil
return webhookCtl.Authorize(whAuthBody)
}

View file

@ -1146,18 +1146,17 @@ func TestAuthority_Renew(t *testing.T) {
assert.False(t, reflect.DeepEqual(ext1, ext2))
}
continue
} else {
found := false
for _, ext2 := range leaf.Extensions {
if reflect.DeepEqual(ext1, ext2) {
found = true
break
}
}
if !found {
t.Errorf("x509 extension %s not found in renewed certificate", ext1.Id.String())
}
found := false
for _, ext2 := range leaf.Extensions {
if reflect.DeepEqual(ext1, ext2) {
found = true
break
}
}
if !found {
t.Errorf("x509 extension %s not found in renewed certificate", ext1.Id.String())
}
}
}
@ -1363,18 +1362,17 @@ func TestAuthority_Rekey(t *testing.T) {
assert.False(t, reflect.DeepEqual(ext1, ext2))
}
continue
} else {
found := false
for _, ext2 := range leaf.Extensions {
if reflect.DeepEqual(ext1, ext2) {
found = true
break
}
}
if !found {
t.Errorf("x509 extension %s not found in renewed certificate", ext1.Id.String())
}
found := false
for _, ext2 := range leaf.Extensions {
if reflect.DeepEqual(ext1, ext2) {
found = true
break
}
}
if !found {
t.Errorf("x509 extension %s not found in renewed certificate", ext1.Id.String())
}
}
}
@ -1936,14 +1934,14 @@ func TestAuthority_CRL(t *testing.T) {
tc := f()
t.Run(name, func(t *testing.T) {
if crlBytes, err := tc.auth.GetCertificateRevocationList(); err == nil {
crl, parseErr := x509.ParseCRL(crlBytes)
crl, parseErr := x509.ParseRevocationList(crlBytes)
if parseErr != nil {
t.Errorf("x509.ParseCertificateRequest() error = %v, wantErr %v", parseErr, nil)
return
}
var cmpList []string
for _, c := range crl.TBSCertList.RevokedCertificates {
for _, c := range crl.RevokedCertificates {
cmpList = append(cmpList, c.SerialNumber.String())
}

View file

@ -14,7 +14,7 @@ type mockWebhookController struct {
var _ webhookController = &mockWebhookController{}
func (wc *mockWebhookController) Enrich(req *webhook.RequestBody) error {
func (wc *mockWebhookController) Enrich(*webhook.RequestBody) error {
for key, data := range wc.respData {
wc.templateData.SetWebhook(key, data)
}
@ -22,6 +22,6 @@ func (wc *mockWebhookController) Enrich(req *webhook.RequestBody) error {
return wc.enrichErr
}
func (wc *mockWebhookController) Authorize(req *webhook.RequestBody) error {
func (wc *mockWebhookController) Authorize(*webhook.RequestBody) error {
return wc.authorizeErr
}

View file

@ -269,7 +269,7 @@ retry:
}
// GetAdmins returns all admins from the GET /admin/admins request to the CA.
func (c *AdminClient) GetAdmins(opts ...AdminOption) ([]*linkedca.Admin, error) {
func (c *AdminClient) GetAdmins(...AdminOption) ([]*linkedca.Admin, error) {
var (
cursor = ""
admins = []*linkedca.Admin{}
@ -474,7 +474,7 @@ retry:
}
// GetProvisioners returns all admins from the GET /admin/admins request to the CA.
func (c *AdminClient) GetProvisioners(opts ...AdminOption) (provisioner.List, error) {
func (c *AdminClient) GetProvisioners(...AdminOption) (provisioner.List, error) {
var (
cursor = ""
provs = provisioner.List{}

View file

@ -35,7 +35,7 @@ func newLocalListener() net.Listener {
return l
}
func setMinCertDuration(d time.Duration) func() {
func setMinCertDuration(time.Duration) func() {
tmp := minCertDuration
minCertDuration = 1 * time.Second
return func() {
@ -54,7 +54,7 @@ func startCABootstrapServer() *httptest.Server {
if err != nil {
panic(err)
}
baseContext := buildContext(ca.auth, nil, nil, nil)
baseContext := buildContext(ca.auth, nil, nil, nil, "")
srv.Config.Handler = ca.srv.Handler
srv.Config.BaseContext = func(net.Listener) context.Context {
return baseContext
@ -606,7 +606,13 @@ func doReload(ca *CA) error {
}
// Use same address in new server
newCA.srv.Addr = ca.srv.Addr
return ca.srv.Reload(newCA.srv)
if err := ca.srv.Reload(newCA.srv); err != nil {
return err
}
// Wait a few ms until the http server calls listener.Accept()
time.Sleep(100 * time.Millisecond)
return nil
}
func TestBootstrapListener(t *testing.T) {

View file

@ -13,6 +13,7 @@ import (
"reflect"
"strings"
"sync"
"time"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
@ -45,6 +46,7 @@ type options struct {
sshHostPassword []byte
sshUserPassword []byte
database db.AuthDB
nnsServer string
}
func (o *options) apply(opts []Option) {
@ -117,6 +119,12 @@ func WithQuiet(quiet bool) Option {
}
}
func WithNNSServer(nnsServer string) Option {
return func(o *options) {
o.nnsServer = nnsServer
}
}
// CA is the type used to build the complete certificate authority. It builds
// the HTTP server, set ups the middlewares and the HTTP handlers.
type CA struct {
@ -126,13 +134,15 @@ type CA struct {
insecureSrv *server.Server
opts *options
renewer *TLSRenewer
compactStop chan struct{}
}
// New creates and initializes the CA with the given configuration and options.
func New(cfg *config.Config, opts ...Option) (*CA, error) {
ca := &CA{
config: cfg,
opts: new(options),
config: cfg,
opts: new(options),
compactStop: make(chan struct{}),
}
ca.opts.apply(opts)
return ca.Init(cfg)
@ -193,7 +203,11 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) {
api.Route(r)
})
//Add ACME api endpoints in /acme and /1.0/acme
// Mount the CRL to the insecure mux
insecureMux.Get("/crl", api.CRL)
insecureMux.Get("/1.0/crl", api.CRL)
// Add ACME api endpoints in /acme and /1.0/acme
dns := cfg.DNSNames[0]
u, err := url.Parse("https://" + cfg.Address)
if err != nil {
@ -273,6 +287,7 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) {
// helpful routine for logging all routes
//dumpRoutes(mux)
//dumpRoutes(insecureMux)
// Add monitoring if configured
if len(cfg.Monitoring) > 0 {
@ -294,8 +309,16 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) {
insecureHandler = logger.Middleware(insecureHandler)
}
var nnsServer string
if len(ca.opts.nnsServer) > 0 {
nnsServer = ca.opts.nnsServer
} else if len(ca.config.NNSServer) > 0 {
nnsServer = ca.config.NNSServer
} else {
return nil, errors.New("error configuring ACME NNS context: no URL of the NNS server provided")
}
// Create context with all the necessary values.
baseContext := buildContext(auth, scepAuthority, acmeDB, acmeLinker)
baseContext := buildContext(auth, scepAuthority, acmeDB, acmeLinker, nnsServer)
ca.srv = server.New(cfg.Address, handler, tlsConfig)
ca.srv.BaseContext = func(net.Listener) context.Context {
@ -304,7 +327,7 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) {
// only start the insecure server if the insecure address is configured
// and, currently, also only when it should serve SCEP endpoints.
if ca.shouldServeSCEPEndpoints() && cfg.InsecureAddress != "" {
if ca.shouldServeInsecureServer() {
// TODO: instead opt for having a single server.Server but two
// http.Servers handling the HTTP and HTTPS handler? The latter
// will probably introduce more complexity in terms of graceful
@ -318,8 +341,26 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) {
return ca, nil
}
// shouldServeInsecureServer returns whether or not the insecure
// server should also be started. This is (currently) only the case
// if the insecure address has been configured AND when a SCEP
// provisioner is configured or when a CRL is configured.
func (ca *CA) shouldServeInsecureServer() bool {
switch {
case ca.config.InsecureAddress == "":
return false
case ca.shouldServeSCEPEndpoints():
return true
case ca.config.CRL.IsEnabled():
return true
default:
return false
}
}
// buildContext builds the server base context.
func buildContext(a *authority.Authority, scepAuthority *scep.Authority, acmeDB acme.DB, acmeLinker acme.Linker) context.Context {
func buildContext(a *authority.Authority, scepAuthority *scep.Authority, acmeDB acme.DB, acmeLinker acme.Linker,
nnsServer string) context.Context {
ctx := authority.NewContext(context.Background(), a)
if authDB := a.GetDatabase(); authDB != nil {
ctx = db.NewContext(ctx, authDB)
@ -331,7 +372,7 @@ func buildContext(a *authority.Authority, scepAuthority *scep.Authority, acmeDB
ctx = scep.NewContext(ctx, scepAuthority)
}
if acmeDB != nil {
ctx = acme.NewContext(ctx, acmeDB, acme.NewClient(), acmeLinker, nil)
ctx = acme.NewContext(ctx, acmeDB, acme.NewClient(), acmeLinker, nil, nnsServer)
}
return ctx
}
@ -370,6 +411,12 @@ func (ca *CA) Run() error {
}
}
wg.Add(1)
go func() {
defer wg.Done()
ca.runCompactJob()
}()
if ca.insecureSrv != nil {
wg.Add(1)
go func() {
@ -394,6 +441,7 @@ func (ca *CA) Run() error {
// Stop stops the CA calling to the server Shutdown method.
func (ca *CA) Stop() error {
close(ca.compactStop)
ca.renewer.Stop()
if err := ca.auth.Shutdown(); err != nil {
log.Printf("error stopping ca.Authority: %+v\n", err)
@ -440,6 +488,7 @@ func (ca *CA) Reload() error {
WithQuiet(ca.opts.quiet),
WithConfigFile(ca.opts.configFile),
WithDatabase(ca.auth.GetDatabase()),
WithNNSServer(ca.opts.nnsServer),
)
if err != nil {
logContinue("Reload failed because the CA with new configuration could not be initialized.")
@ -576,3 +625,39 @@ func (ca *CA) getConfigFileOutput() string {
}
return "loaded from token"
}
// runCompactJob will run the value log garbage collector if the nosql database
// supports it.
func (ca *CA) runCompactJob() {
caDB, ok := ca.auth.GetDatabase().(*db.DB)
if !ok {
return
}
compactor, ok := caDB.DB.(nosql.Compactor)
if !ok {
return
}
// Compact database at start.
runCompact(compactor)
// Compact database every minute.
ticker := time.NewTicker(time.Minute)
defer ticker.Stop()
for {
select {
case <-ca.compactStop:
return
case <-ticker.C:
runCompact(compactor)
}
}
}
// runCompact executes the compact job until it returns an error.
func runCompact(c nosql.Compactor) {
for err := error(nil); err == nil; {
err = c.Compact(0.7)
}
}

View file

@ -126,7 +126,7 @@ func parseCertificate(data string) *x509.Certificate {
return cert
}
func parseCertificateRequest(data string) *x509.CertificateRequest {
func parseCertificateRequest(string) *x509.CertificateRequest {
block, _ := pem.Decode([]byte(csrPEM))
if block == nil {
panic("failed to parse certificate request PEM")

View file

@ -367,7 +367,7 @@ func (r *renewer) GetRootCAs() *x509.CertPool {
return r.pool
}
func (r *renewer) Renew(tr http.RoundTripper) (*api.SignResponse, error) {
func (r *renewer) Renew(http.RoundTripper) (*api.SignResponse, error) {
return r.sign, r.err
}

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