Hashicorp Vault Privilege Escalation Vulnerability
Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.
Affected versions
0 → fixed in 1.7.51.8.0 → fixed in 1.8.4
Details
HashiCorp Vault and Vault Enterprise through 1.7.4 and 1.8.3 allowed a user with write permission to an entity alias ID sharing a mount accessor with another user to acquire this other user’s policies by merging their identities. Fixed in Vault and Vault Enterprise 1.7.5 and 1.8.4.
The fix
Release delta 1.8.0 → 1.8.4 (contains the fix)
CHANGELOG.md+69 −28
@@ -1,5 +1,24 @@-## 1.8.0-rc2-### July 15th, 2021+## 1.8.1+### August 5th, 2021++CHANGES:++* go: Update go version to 1.16.6 [[GH-12245](https://github.com/hashicorp/vault/pull/12245)]++IMPROVEMENTS:++* serviceregistration: add `external-source: "vault"` metadata value for Consul registration. [[GH-12163](https://github.com/hashicorp/vault/pull/12163)]++BUG FIXES:++* auth/aws: Remove warning stating AWS Token TTL will be capped by the Default Lease TTL. [[GH-12026](https://github.com/hashicorp/vault/pull/12026)]+* auth/jwt: Fixes OIDC auth from the Vault UI when using `form_post` as the `oidc_response_mode`. [[GH-12258](https://github.com/hashicorp/vault/pull/12258)]+* core (enterprise): Disallow autogenerated licenses to be used in diagnose even when config is specified+* core: fix byte printing for diagnose disk checks [[GH-12229](https://github.com/hashicorp/vault/pull/12229)]+* identity: do not allow a role's token_ttl to be longer than the signing key's verification_ttl [[GH-12151](https://github.com/hashicorp/vault/pull/12151)]++## 1.8.0+### July 28th, 2021CHANGES:@@ -12,23 +31,17 @@ be also set to `true`. Otherwise, the template engine will log an error but thenrestart its internal runner. [[GH-11775](https://github.com/hashicorp/vault/pull/11775)]* agent: Update to use IAM Service Account Credentials endpoint for signing JWTswhen using GCP Auto-Auth method [[GH-11473](https://github.com/hashicorp/vault/pull/11473)]-* go: Update to Go 1.16.5 [[GH-11802](https://github.com/hashicorp/vault/pull/11802)]+* core (enterprise): License/EULA changes that ensure the presence of a valid HashiCorp license to+start Vault. More information is available in the [Vault License FAQ](https://www.vaultproject.io/docs/enterprise/license/faqs)FEATURES:+* **GCP Secrets Engine Static Accounts**: Adds ability to use existing service accounts for generation+of service account keys and access tokens. [[GH-12023](https://github.com/hashicorp/vault/pull/12023)]+* **Key Management Secrets Engine (Enterprise)**: Adds general availability for distributing and managing keys in AWS KMS. [[GH-11958](https://github.com/hashicorp/vault/pull/11958)]+* **License Autoloading (Enterprise)**: Licenses may now be automatically loaded from the environment or disk.* **MySQL Database UI**: The UI now supports adding and editing MySQL connections in the database secret engine [[GH-11532](https://github.com/hashicorp/vault/pull/11532)]-* cli/api: Add lease lookup command [[GH-11129](https://github.com/hashicorp/vault/pull/11129)]-* core: Add controlled capabilities to control group policy stanza-* secret/rabbitmq: Add ability to customize dynamic usernames [[GH-11899](https://github.com/hashicorp/vault/pull/11899)]-* secrets/database/elasticsearch: Add ability to customize dynamic usernames [[GH-11957](https://github.com/hashicorp/vault/pull/11957)]-* secrets/database/influxdb: Add ability to customize dynamic usernames [[GH-11796](https://github.com/hashicorp/vault/pull/11796)]-* secrets/database/mongodbatlas: Add ability to customize dynamic usernames [[GH-11956](https://github.com/hashicorp/vault/pull/11956)]-* secrets/database/redshift: Add ability to customize dynamic usernames [[GH-12016](https://github.com/hashicorp/vault/pull/12016)]-* secrets/database/snowflake: Add ability to customize dynamic usernames [[GH-11997](https://github.com/hashicorp/vault/pull/11997)]-* secrets/gcp: Adds ability to use existing service accounts for generation of service account keys and access tokens. [[GH-12023](https://github.com/hashicorp/vault/pull/12023)]-* secrets/keymgmt (enterprise): Adds general availability for distributing and managing keys in AWS KMS. [[GH-11958](https://github.com/hashicorp/vault/pull/11958)]-* ssh: add support for templated values in SSH CA DefaultExtensions [[GH-11495](https://github.com/hashicorp/vault/pull/11495)]-* ui: Add database secret engine support for MSSQL [[GH-11231](https://github.com/hashicorp/vault/pull/11231)]+* **Vault Diagnose**: A new `vault operator` command to detect common issues with vault server setups.IMPROVEMENTS:@@ -36,24 +49,38 @@ IMPROVEMENTS:* agent: Allow Agent auto auth to read symlinked JWT files [[GH-11502](https://github.com/hashicorp/vault/pull/11502)]* api: Allow a leveled logger to be provided to `api.Client` through `SetLogger`. [[GH-11696](https://github.com/hashicorp/vault/pull/11696)]* auth/aws: Underlying error included in validation failure message. [[GH-11638](https://github.com/hashicorp/vault/pull/11638)]+* cli/api: Add lease lookup command [[GH-11129](https://github.com/hashicorp/vault/pull/11129)]* core: Add `prefix_filter` to telemetry config [[GH-12025](https://github.com/hashicorp/vault/pull/12025)]+* core: Add a darwin/arm64 binary release supporting the Apple M1 CPU [[GH-12071](https://github.com/hashicorp/vault/pull/12071)]* core: Add a small (<1s) exponential backoff to failed TCP listener Accept failures. [[GH-11588](https://github.com/hashicorp/vault/pull/11588)]+* core (enterprise): Add controlled capabilities to control group policy stanza* core: Add metrics for standby node forwarding. [[GH-11366](https://github.com/hashicorp/vault/pull/11366)]+* core: Add metrics to report if a node is a perf standby, if a node is a dr secondary or primary, and if a node is a perf secondary or primary. [[GH-11472](https://github.com/hashicorp/vault/pull/11472)]* core: Send notifications to systemd on start, stop, and configuration reload. [[GH-11517](https://github.com/hashicorp/vault/pull/11517)]* core: add irrevocable lease list and count apis [[GH-11607](https://github.com/hashicorp/vault/pull/11607)]* core: allow arbitrary length stack traces upon receiving SIGUSR2 (was 32MB) [[GH-11364](https://github.com/hashicorp/vault/pull/11364)]* db/cassandra: Added tls_server_name to specify server name for TLS validation [[GH-11820](https://github.com/hashicorp/vault/pull/11820)]-* plugins/ad: Added rotate-role endpoint for manual service account password rotations [[GH-11942](https://github.com/hashicorp/vault/pull/11942)]-* raft: Improve raft batch size selection [[GH-11907](https://github.com/hashicorp/vault/pull/11907)]-* raft: change freelist type to map and set nofreelistsync to true [[GH-11895](https://github.com/hashicorp/vault/pull/11895)]-* replication (enterprise): The log shipper is now memory-as well as length bound, and length and size can be-separately configured.+* go: Update to Go 1.16.5 [[GH-11802](https://github.com/hashicorp/vault/pull/11802)]+* replication: Delay evaluation of X-Vault-Index headers until merkle sync completes.+* secrets/rabbitmq: Add ability to customize dynamic usernames [[GH-11899](https://github.com/hashicorp/vault/pull/11899)]+* secrets/ad: Add `rotate-role` endpoint to allow rotations of service accounts. [[GH-11942](https://github.com/hashicorp/vault/pull/11942)]+* secrets/aws: add IAM tagging support for iam_user roles [[GH-10953](https://github.com/hashicorp/vault/pull/10953)]* secrets/aws: add ability to provide a role session name when generating STS credentials [[GH-11345](https://github.com/hashicorp/vault/pull/11345)]+* secrets/database/elasticsearch: Add ability to customize dynamic usernames [[GH-11957](https://github.com/hashicorp/vault/pull/11957)]+* secrets/database/influxdb: Add ability to customize dynamic usernames [[GH-11796](https://github.com/hashicorp/vault/pull/11796)]* secrets/database/mongodb: Add ability to customize `SocketTimeout`, `ConnectTimeout`, and `ServerSelectionTimeout` [[GH-11600](https://github.com/hashicorp/vault/pull/11600)]* secrets/database/mongodb: Increased throughput by allowing for multiple request threads to simultaneously update users in MongoDB [[GH-11600](https://github.com/hashicorp/vault/pull/11600)]+* secrets/database/mongodbatlas: Adds the ability to customize username generation for dynamic users in MongoDB Atlas. [[GH-11956](https://github.com/hashicorp/vault/pull/11956)]+* secrets/database/redshift: Add ability to customize dynamic usernames [[GH-12016](https://github.com/hashicorp/vault/pull/12016)]+* secrets/database/snowflake: Add ability to customize dynamic usernames [[GH-11997](https://github.com/hashicorp/vault/pull/11997)]+* ssh: add support for templated values in SSH CA DefaultExtensions [[GH-11495](https://github.com/hashicorp/vault/pull/11495)]+* storage/raft: Improve raft batch size selection [[GH-11907](https://github.com/hashicorp/vault/pull/11907)]+* storage/raft: change freelist type to map and set nofreelistsync to true [[GH-11895](https://github.com/hashicorp/vault/pull/11895)]+* storage/raft: Switch to shared raft-boltdb library and add boltdb metrics [[GH-11269](https://github.com/hashicorp/vault/pull/11269)]* storage/raft: Support autopilot for HA only raft storage. [[GH-11260](https://github.com/hashicorp/vault/pull/11260)]+* storage/raft (enterprise): Enable Autopilot on DR secondary clusters* ui: Add Validation to KV secret engine [[GH-11785](https://github.com/hashicorp/vault/pull/11785)]+* ui: Add database secret engine support for MSSQL [[GH-11231](https://github.com/hashicorp/vault/pull/11231)]* ui: Add push notification message when selecting okta auth. [[GH-11442](https://github.com/hashicorp/vault/pull/11442)]* ui: Add regex validation to Transform Template pattern input [[GH-11586](https://github.com/hashicorp/vault/pull/11586)]* ui: Add specific error message if unseal fails due to license [[GH-11705](https://github.com/hashicorp/vault/pull/11705)]@@ -67,6 +94,7 @@ separately configured.* ui: Update ember to latest LTS and upgrade UI dependencies [[GH-11447](https://github.com/hashicorp/vault/pull/11447)]* ui: Update partials to components [[GH-11680](https://github.com/hashicorp/vault/pull/11680)]* ui: Updated ivy code mirror component for consistency [[GH-11500](https://github.com/hashicorp/vault/pull/11500)]+* ui: Updated node to v14, latest stable build [[GH-12049](https://github.com/hashicorp/vault/pull/12049)]* ui: Updated search select component styling [[GH-11360](https://github.com/hashicorp/vault/pull/11360)]* ui: add transform secrets engine to features list [[GH-12003](https://github.com/hashicorp/vault/pull/12003)]* ui: add validations for duplicate path kv engine [[GH-11878](https://github.com/hashicorp/vault/pull/11878)]@@ -75,25 +103,27 @@ separately configured.DEPRECATIONS:-* secrets/gcp: Deprecated the `/gcp/token/:roleset` and `/gcp/key/:roleset` paths for generating secrets for rolesets.-Use `/gcp/roleset/:roleset/token` and `/gcp/roleset/:roleset/key` instead. [[GH-12023](https://github.com/hashicorp/vault/pull/12023)]+* secrets/gcp: Deprecated the `/gcp/token/:roleset` and `/gcp/key/:roleset` paths for generating+secrets for rolesets. Use `/gcp/roleset/:roleset/token` and `/gcp/roleset/:roleset/key` instead. [[GH-12023](https://github.com/hashicorp/vault/pull/12023)]BUG FIXES:* activity: Omit wrapping tokens and control groups from client counts [[GH-11826](https://github.com/hashicorp/vault/pull/11826)]* agent/cert: Fix issue where the API client on agent was not honoring certificate-information from the auto-auth config map on renewals or retries. [[GH-11576](https://github.com/hashicorp/vault/pull/11576)]+information from the auto-auth config map on renewals or retries. [[GH-11576](https://github.com/hashicorp/vault/pull/11576)]* agent/template: fix command shell quoting issue [[GH-11838](https://github.com/hashicorp/vault/pull/11838)]* agent: Fixed agent templating to use configured tls servername values [[GH-11288](https://github.com/hashicorp/vault/pull/11288)]* agent: fix timestamp format in log messages from the templating engine [[GH-11838](https://github.com/hashicorp/vault/pull/11838)]* auth/approle: fixing dereference of nil pointer [[GH-11864](https://github.com/hashicorp/vault/pull/11864)]* auth/jwt: Updates the [hashicorp/cap](https://github.com/hashicorp/cap) library to `v0.1.0` to-bring in a verification key caching fix. [[GH-11784](https://github.com/hashicorp/vault/pull/11784)]+bring in a verification key caching fix. [[GH-11784](https://github.com/hashicorp/vault/pull/11784)]+* auth/kubernetes: Fix AliasLookahead to correctly extract ServiceAccount UID when using ephemeral JWTs [[GH-12073](https://github.com/hashicorp/vault/pull/12073)]* auth/ldap: Fix a bug where the LDAP auth method does not return the request_timeout configuration parameter on config read. [[GH-11975](https://github.com/hashicorp/vault/pull/11975)]* cli: Add support for response wrapping in `vault list` and `vault kv list` with output format other than `table`. [[GH-12031](https://github.com/hashicorp/vault/pull/12031)]* cli: vault delete and vault kv delete should support the same output options (e.g. -format) as vault write. [[GH-11992](https://github.com/hashicorp/vault/pull/11992)]* core (enterprise): Fix orphan return value from auth methods executed on performance standby nodes.* core (enterprise): Fix plugins mounted in namespaces being unable to use password policies [[GH-11596](https://github.com/hashicorp/vault/pull/11596)]+* core (enterprise): serialize access to HSM entropy generation to avoid errors in concurrent key generation.* core/metrics: Add generic KV mount support for vault.kv.secret.count telemetry metric [[GH-12020](https://github.com/hashicorp/vault/pull/12020)]* core: Fix cleanup of storage entries from cubbyholes within namespaces. [[GH-11408](https://github.com/hashicorp/vault/pull/11408)]* core: Fix edge cases in the configuration endpoint for barrier key autorotation. [[GH-11541](https://github.com/hashicorp/vault/pull/11541)]@@ -105,6 +135,10 @@ bring in a verification key caching fix. [[GH-11784](https://github.com/hashicor* identity: Use correct mount accessor when refreshing external group memberships. [[GH-11506](https://github.com/hashicorp/vault/pull/11506)]* mongo-db: default username template now strips invalid '.' characters [[GH-11872](https://github.com/hashicorp/vault/pull/11872)]* pki: Only remove revoked entry for certificates during tidy if they are past their NotAfter value [[GH-11367](https://github.com/hashicorp/vault/pull/11367)]+* replication: Fix panic trying to update walState during identity group invalidation.+* replication: Fix: mounts created within a namespace that was part of an Allow+filtering rule would not appear on performance secondary if created after rule+was defined.* secret/pki: use case insensitive domain name comparison as per RFC1035 section 2.3.3* secret: fix the bug where transit encrypt batch doesn't work with key_version [[GH-11628](https://github.com/hashicorp/vault/pull/11628)]* secrets/ad: Forward all creds requests to active node [[GH-76](https://github.com/hashicorp/vault-plugin-secrets-ad/pull/76)] [[GH-11836](https://github.com/hashicorp/vault/pull/11836)]@@ -112,20 +146,29 @@ bring in a verification key caching fix. [[GH-11784](https://github.com/hashicor* secrets/database/cassandra: Fixed issue where the PEM parsing logic of `pem_bundle` and `pem_json` didn't work for CA-only configurations [[GH-11861](https://github.com/hashicorp/vault/pull/11861)]* secrets/database/cassandra: Updated default statement for password rotation to allow for special characters. This applies to root and static credentials. [[GH-11262](https://github.com/hashicorp/vault/pull/11262)]* secrets/database: Fix marshalling to allow providing numeric arguments to external database plugins. [[GH-11451](https://github.com/hashicorp/vault/pull/11451)]+* secrets/database: Fixed an issue that prevented external database plugin processes from restarting after a shutdown. [[GH-12087](https://github.com/hashicorp/vault/pull/12087)]* secrets/database: Fixed minor race condition when rotate-root is called [[GH-11600](https://github.com/hashicorp/vault/pull/11600)]* secrets/database: Fixes issue for V4 database interface where `SetCredentials` wasn't falling back to using `RotateRootCredentials` if `SetCredentials` is `Unimplemented` [[GH-11585](https://github.com/hashicorp/vault/pull/11585)]+* secrets/openldap: Fix bug where schema was not compatible with rotate-root [#24](https://github.com/hashicorp/vault-plugin-secrets-openldap/pull/24) [[GH-12019](https://github.com/hashicorp/vault/pull/12019)]* storage/dynamodb: Handle throttled batch write requests by retrying, without which writes could be lost. [[GH-10181](https://github.com/hashicorp/vault/pull/10181)]* storage/raft: Support cluster address change for nodes in a cluster managed by autopilot [[GH-11247](https://github.com/hashicorp/vault/pull/11247)]* storage/raft: Tweak creation of vault.db file [[GH-12034](https://github.com/hashicorp/vault/pull/12034)]* storage/raft: leader_tls_servername wasn't used unless leader_ca_cert_file and/or mTLS were configured. [[GH-11252](https://github.com/hashicorp/vault/pull/11252)]* tokenutil: Perform the num uses check before token type. [[GH-11647](https://github.com/hashicorp/vault/pull/11647)]* transform (enterprise): Fix an issue with malformed transform configuration-storage when upgrading from 1.5 to 1.6. See Upgrade Notes for 1.6.x.+storage when upgrading from 1.5 to 1.6. See Upgrade Notes for 1.6.x.+* ui: Add role from database connection automatically populates the database for new role [[GH-11119](https://github.com/hashicorp/vault/pull/11119)]* ui: Add root rotation statements support to appropriate database secret engine plugins [[GH-11404](https://github.com/hashicorp/vault/pull/11404)]+* ui: Automatically refresh the page when user logs out [[GH-12035](https://github.com/hashicorp/vault/pull/12035)]+* ui: Fix Version History queryParams on LinkedBlock [[GH-12079](https://github.com/hashicorp/vault/pull/12079)]+* ui: Fix bug where database secret engines with custom names cannot delete connections [[GH-11127](https://github.com/hashicorp/vault/pull/11127)]* ui: Fix bug where the UI does not recognize version 2 KV until refresh, and fix [object Object] error message [[GH-11258](https://github.com/hashicorp/vault/pull/11258)]+* ui: Fix database role CG access [[GH-12111](https://github.com/hashicorp/vault/pull/12111)]+* ui: Fix date display on expired token notice [[GH-11142](https://github.com/hashicorp/vault/pull/11142)]* ui: Fix entity group membership and metadata not showing [[GH-11641](https://github.com/hashicorp/vault/pull/11641)]* ui: Fix error message caused by control group [[GH-11143](https://github.com/hashicorp/vault/pull/11143)]* ui: Fix footer URL linking to the correct version changelog. [[GH-11283](https://github.com/hashicorp/vault/pull/11283)]+* ui: Fix issue where logging in without namespace input causes error [[GH-11094](https://github.com/hashicorp/vault/pull/11094)]* ui: Fix namespace-bug on login [[GH-11182](https://github.com/hashicorp/vault/pull/11182)]* ui: Fix status menu no showing on login [[GH-11213](https://github.com/hashicorp/vault/pull/11213)]* ui: Fix text link URL on database roles list [[GH-11597](https://github.com/hashicorp/vault/pull/11597)]@@ -134,8 +177,6 @@ storage when upgrading from 1.5 to 1.6. See Upgrade Notes for 1.6.x.* ui: fix issue where select-one option was not showing in secrets database role creation [[GH-11294](https://github.com/hashicorp/vault/pull/11294)]* ui: fix oidc login with Safari [[GH-11884](https://github.com/hashicorp/vault/pull/11884)]-## 1.7.3-## 1.7.3### June 16th, 2021to 3.11 in test (#12311) (#12313)plugins/database/cassandra/connection_producer_test.go | 2 +-1 file changed, 1 insertion(+), 1 deletion(-)
ui/tests/acceptance/secrets/backend/kv/secret-test.js+56 −0
@@ -409,6 +409,62 @@ module('Acceptance | secrets/secret/create', function(hooks) {assert.dom('[data-test-secret-undelete]').exists('undelete button shows');});+test('version 2 with path forward slash will show delete button', async function(assert) {+let backend = 'kv-v2';+const V2_POLICY = `+path "kv-v2/delete/forward/slash" {+capabilities = ["update"]+}+path "kv-v2/metadata/*" {+capabilities = ["list","read","create","update"]+}+path "kv-v2/data/forward/slash" {+capabilities = ["create", "read"]+}+`;+await consoleComponent.runCommands([+`write sys/mounts/${backend} type=kv options=version=2`,+`write sys/policies/acl/kv-v2-degrade policy=${btoa(V2_POLICY)}`,+// delete any kv previously written here so that tests can be re-run+'delete kv-v2/metadata/forward/slash',+'write -field=client_token auth/token/create policies=kv-v2-degrade',+]);++let userToken = consoleComponent.lastLogOutput;+await logout.visit();+await authPage.login(userToken);+await writeSecret(backend, 'forward/slash', 'foo', 'bar');+assert.dom('[data-test-secret-v2-delete="true"]').exists('drop down delete shows');+});++test('version 2 with engine with forward slash will show delete button', async function(assert) {+let backend = 'forward/slash';+const V2_POLICY = `+path "forward/slash/delete/secret" {+capabilities = ["update"]+}+path "forward/slash/metadata/*" {+capabilities = ["list","read","create","update"]+}+path "forward/slash/data/*" {+capabilities = ["create", "read"]+}+`;+await consoleComponent.runCommands([+`write sys/mounts/${backend} type=kv options=version=2`,+`write sys/policies/acl/kv-v2-degrade policy=${btoa(V2_POLICY)}`,+// delete any kv previously written here so that tests can be re-run+'delete forward/slash/metadata/secret',+'write -field=client_token auth/token/create policies=kv-v2-degrade',+]);++let userToken = consoleComponent.lastLogOutput;+await logout.visit();+await authPage.login(userToken);+await writeSecret(backend, 'secret', 'foo', 'bar');+assert.dom('[data-test-secret-v2-delete="true"]').exists('drop down delete shows');+});+test('paths are properly encoded', async function(assert) {let backend = 'kv';let paths = [key algorithm. (#12514) (#12548)builtin/logical/pki/backend_test.go | 197 +++++++++++++++++++++++++---builtin/logical/pki/path_root.go | 57 ++++++++changelog/12514.txt | 3 +3 files changed, 240 insertions(+), 17 deletions(-)create mode 100644 changelog/12514.txt
vault/identity_store_oidc_test.go+86 −0
@@ -113,6 +113,48 @@ func TestOIDC_Path_OIDCRoleRole(t *testing.T) {}}+// TestOIDC_Path_OIDCRole_InvalidTokenTTL tests the TokenTTL validation+func TestOIDC_Path_OIDCRole_InvalidTokenTTL(t *testing.T) {+c, _, _ := TestCoreUnsealed(t)+ctx := namespace.RootContext(nil)+storage := &logical.InmemStorage{}++// Create a test key "test-key"+c.identityStore.HandleRequest(ctx, &logical.Request{+Path: "oidc/key/test-key",+Operation: logical.CreateOperation,+Data: map[string]interface{}{+"verification_ttl": int64(60),+},+Storage: storage,+})++// Create a test role "test-role1" with a ttl longer than the+// verification_ttl -- should fail with error+resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{+Path: "oidc/role/test-role1",+Operation: logical.CreateOperation,+Data: map[string]interface{}{+"key": "test-key",+"ttl": int64(3600),+},+Storage: storage,+})+expectError(t, resp, err)++// Read "test-role1"+respReadTestRole1, err3 := c.identityStore.HandleRequest(ctx, &logical.Request{+Path: "oidc/role/test-role1",+Operation: logical.ReadOperation,+Storage: storage,+})+// Ensure that "test-role1" was not created+expectSuccess(t, respReadTestRole1, err3)+if respReadTestRole1 != nil {+t.Fatalf("Expected a nil response but instead got:\n%#v", respReadTestRole1)+}+}+// TestOIDC_Path_OIDCRole tests the List operation for rolesfunc TestOIDC_Path_OIDCRole(t *testing.T) {c, _, _ := TestCoreUnsealed(t)@@ -284,6 +326,49 @@ func TestOIDC_Path_OIDCKeyKey(t *testing.T) {expectSuccess(t, resp, err)}+// TestOIDC_Path_OIDCKey_InvalidTokenTTL tests the TokenTTL validation+func TestOIDC_Path_OIDCKey_InvalidTokenTTL(t *testing.T) {+c, _, _ := TestCoreUnsealed(t)+ctx := namespace.RootContext(nil)+storage := &logical.InmemStorage{}++// Create a test key "test-key" -- should succeed+resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{+Path: "oidc/key/test-key",+Operation: logical.CreateOperation,+Data: map[string]interface{}{+"verification_ttl": "4m",+},+Storage: storage,+})+expectSuccess(t, resp, err)++// Create a role that depends on test key+resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{+Path: "oidc/role/allowed-test-role",+Operation: logical.UpdateOperation,+Data: map[string]interface{}{+"key": "test-key",+"ttl": "4m",+},+Storage: storage,+})+expectSuccess(t, resp, err)++// Update "test-key" -- should fail since allowed-test-role ttl is less than 2m+resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{+Path: "oidc/key/test-key",+Operation: logical.UpdateOperation,+Data: map[string]interface{}{+"rotation_period": "10m",+"verification_ttl": "2m",+"allowed_client_ids": "allowed-test-role",+},+Storage: storage,+})+expectError(t, resp, err)+}+// TestOIDC_Path_OIDCKey tests the List operation for keysfunc TestOIDC_Path_OIDCKey(t *testing.T) {c, _, _ := TestCoreUnsealed(t)@@ -1100,6 +1185,7 @@ func expectSuccess(t *testing.T, resp *logical.Response, err error) {}func expectError(t *testing.T, resp *logical.Response, err error) {+t.Helper()if err == nil {if resp == nil || !resp.IsError() {t.Fatalf("expected error but got success; error:\n%v\nresp: %#v", err, resp)changelog/12229.txt | 3 +++go.mod | 1 +vault/diagnose/os_common.go | 7 ++++---3 files changed, 8 insertions(+), 3 deletions(-)create mode 100644 changelog/12229.txt
builtin/credential/aws/pkcs7/sign.go+399 −0
@@ -0,0 +1,429 @@+package pkcs7++import (+"bytes"+"crypto"+"crypto/dsa"+"crypto/rand"+"crypto/x509"+"crypto/x509/pkix"+"encoding/asn1"+"errors"+"fmt"+"math/big"+"time"+)++// SignedData is an opaque data structure for creating signed data payloads+type SignedData struct {+sd signedData+certs []*x509.Certificate+data, messageDigest []byte+digestOid asn1.ObjectIdentifier+encryptionOid asn1.ObjectIdentifier+}++// NewSignedData takes data and initializes a PKCS7 SignedData struct that is+// ready to be signed via AddSigner. The digest algorithm is set to SHA1 by default+// and can be changed by calling SetDigestAlgorithm.+func NewSignedData(data []byte) (*SignedData, error) {+content, err := asn1.Marshal(data)+if err != nil {+return nil, err+}+ci := contentInfo{+ContentType: OIDData,+Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: content, IsCompound: true},+}+sd := signedData{+ContentInfo: ci,+Version: 1,+}+return &SignedData{sd: sd, data: data, digestOid: OIDDigestAlgorithmSHA1}, nil+}++// SignerInfoConfig are optional values to include when adding a signer+type SignerInfoConfig struct {+ExtraSignedAttributes []Attribute+ExtraUnsignedAttributes []Attribute+}++type signedData struct {+Version int `asn1:"default:1"`+DigestAlgorithmIdentifiers []pkix.AlgorithmIdentifier `asn1:"set"`+ContentInfo contentInfo+Certificates rawCertificates `asn1:"optional,tag:0"`+CRLs []pkix.CertificateList `asn1:"optional,tag:1"`+SignerInfos []signerInfo `asn1:"set"`+}++type signerInfo struct {+Version int `asn1:"default:1"`+IssuerAndSerialNumber issuerAndSerial+DigestAlgorithm pkix.AlgorithmIdentifier+AuthenticatedAttributes []attribute `asn1:"optional,omitempty,tag:0"`+DigestEncryptionAlgorithm pkix.AlgorithmIdentifier+EncryptedDigest []byte+UnauthenticatedAttributes []attribute `asn1:"optional,omitempty,tag:1"`+}++type attribute struct {+Type asn1.ObjectIdentifier+Value asn1.RawValue `asn1:"set"`+}++func marshalAttributes(attrs []attribute) ([]byte, error) {+encodedAttributes, err := asn1.Marshal(struct {+A []attribute `asn1:"set"`+}{A: attrs})+if err != nil {+return nil, err+}++// Remove the leading sequence octets+var raw asn1.RawValue+asn1.Unmarshal(encodedAttributes, &raw)+return raw.Bytes, nil+}++type rawCertificates struct {+Raw asn1.RawContent+}++type issuerAndSerial struct {+IssuerName asn1.RawValue+SerialNumber *big.Int+}++// SetDigestAlgorithm sets the digest algorithm to be used in the signing process.+//+// This should be called before adding signers+func (sd *SignedData) SetDigestAlgorithm(d asn1.ObjectIdentifier) {+sd.digestOid = d+}++// SetEncryptionAlgorithm sets the encryption algorithm to be used in the signing process.+//+// This should be called before adding signers+func (sd *SignedData) SetEncryptionAlgorithm(d asn1.ObjectIdentifier) {+sd.encryptionOid = d+}++// AddSigner is a wrapper around AddSignerChain() that adds a signer without any parent.+func (sd *SignedData) AddSigner(ee *x509.Certificate, pkey crypto.PrivateKey, config SignerInfoConfig) error {+var parents []*x509.Certificate+return sd.AddSignerChain(ee, pkey, parents, config)+}++// AddSignerChain signs attributes about the content and adds certificates+// and signers infos to the Signed Data. The certificate and private key+// of the end-entity signer are used to issue the signature, and any+// parent of that end-entity that need to be added to the list of+// certifications can be specified in the parents slice.+//+// The signature algorithm used to hash the data is the one of the end-entity+// certificate.+func (sd *SignedData) AddSignerChain(ee *x509.Certificate, pkey crypto.PrivateKey, parents []*x509.Certificate, config SignerInfoConfig) error {+// Following RFC 2315, 9.2 SignerInfo type, the distinguished name of+// the issuer of the end-entity signer is stored in the issuerAndSerialNumber+// section of the SignedData.SignerInfo, alongside the serial number of+// the end-entity.+var ias issuerAndSerial+ias.SerialNumber = ee.SerialNumber+if len(parents) == 0 {+// no parent, the issuer is the end-entity cert itself+ias.IssuerName = asn1.RawValue{FullBytes: ee.RawIssuer}+} else {+err := verifyPartialChain(ee, parents)+if err != nil {+return err+}+// the first parent is the issuer+ias.IssuerName = asn1.RawValue{FullBytes: parents[0].RawSubject}+}+sd.sd.DigestAlgorithmIdentifiers = append(sd.sd.DigestAlgorithmIdentifiers,+pkix.AlgorithmIdentifier{Algorithm: sd.digestOid},+)+hash, err := getHashForOID(sd.digestOid)+if err != nil {+return err+}+h := hash.New()+h.Write(sd.data)+sd.messageDigest = h.Sum(nil)+encryptionOid, err := getOIDForEncryptionAlgorithm(pkey, sd.digestOid)+if err != nil {+return err+}+attrs := &attributes{}+attrs.Add(OIDAttributeContentType, sd.sd.ContentInfo.ContentType)+attrs.Add(OIDAttributeMessageDigest, sd.messageDigest)+attrs.Add(OIDAttributeSigningTime, time.Now().UTC())+for _, attr := range config.ExtraSignedAttributes {+attrs.Add(attr.Type, attr.Value)+}+finalAttrs, err := attrs.ForMarshalling()+if err != nil {+return err+}+unsignedAttrs := &attributes{}+for _, attr := range config.ExtraUnsignedAttributes {+unsignedAttrs.Add(attr.Type, attr.Value)+}+finalUnsignedAttrs, err := unsignedAttrs.ForMarshalling()+if err != nil {+return err+}+// create signature of signed attributes+signature, err := signAttributes(finalAttrs, pkey, hash)+if err != nil {+return err+}+signer := signerInfo{+AuthenticatedAttributes: finalAttrs,+UnauthenticatedAttributes: finalUnsignedAttrs,+DigestAlgorithm: pkix.AlgorithmIdentifier{Algorithm: sd.digestOid},+DigestEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: encryptionOid},+IssuerAndSerialNumber: ias,+EncryptedDigest: signature,+Version: 1,+}+sd.certs = append(sd.certs, ee)+if len(parents) > 0 {+sd.certs = append(sd.certs, parents...)+}+sd.sd.SignerInfos = append(sd.sd.SignerInfos, signer)+return nil+}++// SignWithoutAttr issues a signature on the content of the pkcs7 SignedData.+// Unlike AddSigner/AddSignerChain, it calculates the digest on the data alone+// and does not include any signed attributes like timestamp and so on.+//+// This function is needed to sign old Android APKs, something you probably+// shouldn't do unless you're maintaining backward compatibility for old+// applications.+func (sd *SignedData) SignWithoutAttr(ee *x509.Certificate, pkey crypto.PrivateKey, config SignerInfoConfig) error {+var signature []byte+sd.sd.DigestAlgorithmIdentifiers = append(sd.sd.DigestAlgorithmIdentifiers, pkix.AlgorithmIdentifier{Algorithm: sd.digestOid})+hash, err := getHashForOID(sd.digestOid)+if err != nil {+return err+}+h := hash.New()+h.Write(sd.data)+sd.messageDigest = h.Sum(nil)+switch pkey := pkey.(type) {+case *dsa.PrivateKey:+// dsa doesn't implement crypto.Signer so we make a special case+// https://github.com/golang/go/issues/27889+r, s, err := dsa.Sign(rand.Reader, pkey, sd.messageDigest)+if err != nil {+return err+}+signature, err = asn1.Marshal(dsaSignature{r, s})+if err != nil {+return err+}+default:+key, ok := pkey.(crypto.Signer)+if !ok {+return errors.New("pkcs7: private key does not implement crypto.Signer")+}+signature, err = key.Sign(rand.Reader, sd.messageDigest, hash)+if err != nil {+return err+}+}+var ias issuerAndSerial+ias.SerialNumber = ee.SerialNumber+// no parent, the issue is the end-entity cert itself+ias.IssuerName = asn1.RawValue{FullBytes: ee.RawIssuer}+if sd.encryptionOid == nil {+// if the encryption algorithm wasn't set by SetEncryptionAlgorithm,+// infer it from the digest algorithm+sd.encryptionOid, err = getOIDForEncryptionAlgorithm(pkey, sd.digestOid)+}+if err != nil {+return err+}+signer := signerInfo{+DigestAlgorithm: pkix.AlgorithmIdentifier{Algorithm: sd.digestOid},+DigestEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: sd.encryptionOid},+IssuerAndSerialNumber: ias,+EncryptedDigest: signature,+Version: 1,+}+// create signature of signed attributes+sd.certs = append(sd.certs, ee)+sd.sd.SignerInfos = append(sd.sd.SignerInfos, signer)+return nil+}++func (si *signerInfo) SetUnauthenticatedAttributes(extraUnsignedAttrs []Attribute) error {+unsignedAttrs := &attributes{}+for _, attr := range extraUnsignedAttrs {+unsignedAttrs.Add(attr.Type, attr.Value)+}+finalUnsignedAttrs, err := unsignedAttrs.ForMarshalling()+if err != nil {+return err+}++si.UnauthenticatedAttributes = finalUnsignedAttrs++return nil+}++// AddCertificate adds the certificate to the payload. Useful for parent certificates+func (sd *SignedData) AddCertificate(cert *x509.Certificate) {+sd.certs = append(sd.certs, cert)+}++// Detach removes content from the signed data struct to make it a detached signature.+// This must be called right before Finish()+func (sd *SignedData) Detach() {+sd.sd.ContentInfo = contentInfo{ContentType: OIDData}+}++// GetSignedData returns the private Signed Data+func (sd *SignedData) GetSignedData() *signedData {+return &sd.sd+}++// Finish marshals the content and its signers+func (sd *SignedData) Finish() ([]byte, error) {+sd.sd.Certificates = marshalCertificates(sd.certs)+inner, err := asn1.Marshal(sd.sd)+if err != nil {+return nil, err+}+outer := contentInfo{+ContentType: OIDSignedData,+Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: inner, IsCompound: true},+}+return asn1.Marshal(outer)+}++// RemoveAuthenticatedAttributes removes authenticated attributes from signedData+// similar to OpenSSL's PKCS7_NOATTR or -noattr flags+func (sd *SignedData) RemoveAuthenticatedAttributes() {+for i := range sd.sd.SignerInfos {+sd.sd.SignerInfos[i].AuthenticatedAttributes = nil+}+}++// RemoveUnauthenticatedAttributes removes unauthenticated attributes from signedData+func (sd *SignedData) RemoveUnauthenticatedAttributes() {+for i := range sd.sd.SignerInfos {+sd.sd.SignerInfos[i].UnauthenticatedAttributes = nil+}+}++// verifyPartialChain checks that a given cert is issued by the first parent in the list,+// then continue down the path. It doesn't require the last parent to be a root CA,+// or to be trusted in any truststore. It simply verifies that the chain provided, albeit+// partial, makes sense.+func verifyPartialChain(cert *x509.Certificate, parents []*x509.Certificate) error {+if len(parents) == 0 {+return fmt.Errorf("pkcs7: zero parents provided to verify the signature of certificate %q", cert.Subject.CommonName)+}+err := cert.CheckSignatureFrom(parents[0])+if err != nil {+return fmt.Errorf("pkcs7: certificate signature from parent is invalid: %v", err)+}+if len(parents) == 1 {+// there is no more parent to check, return+return nil+}+return verifyPartialChain(parents[0], parents[1:])+}++func cert2issuerAndSerial(cert *x509.Certificate) (issuerAndSerial, error) {+var ias issuerAndSerial+// The issuer RDNSequence has to match exactly the sequence in the certificate+// We cannot use cert.Issuer.ToRDNSequence() here since it mangles the sequence+ias.IssuerName = asn1.RawValue{FullBytes: cert.RawIssuer}+ias.SerialNumber = cert.SerialNumber++return ias, nil+}++// signs the DER encoded form of the attributes with the private key+func signAttributes(attrs []attribute, pkey crypto.PrivateKey, digestAlg crypto.Hash) ([]byte, error) {+attrBytes, err := marshalAttributes(attrs)+if err != nil {+return nil, err+}+h := digestAlg.New()+h.Write(attrBytes)+hash := h.Sum(nil)++// dsa doesn't implement crypto.Signer so we make a special case+// https://github.com/golang/go/issues/27889+switch pkey := pkey.(type) {+case *dsa.PrivateKey:+r, s, err := dsa.Sign(rand.Reader, pkey, hash)+if err != nil {+return nil, err+}+return asn1.Marshal(dsaSignature{r, s})+}++key, ok := pkey.(crypto.Signer)+if !ok {+return nil, errors.New("pkcs7: private key does not implement crypto.Signer")+}+return key.Sign(rand.Reader, hash, digestAlg)+}++type dsaSignature struct {+R, S *big.Int+}++// concats and wraps the certificates in the RawValue structure+func marshalCertificates(certs []*x509.Certificate) rawCertificates {+var buf bytes.Buffer+for _, cert := range certs {+buf.Write(cert.Raw)+}+rawCerts, _ := marshalCertificateBytes(buf.Bytes())+return rawCerts+}++// Even though, the tag & length are stripped out during marshalling the+// RawContent, we have to encode it into the RawContent. If its missing,+// then `asn1.Marshal()` will strip out the certificate wrapper instead.+func marshalCertificateBytes(certs []byte) (rawCertificates, error) {+var val = asn1.RawValue{Bytes: certs, Class: 2, Tag: 0, IsCompound: true}+b, err := asn1.Marshal(val)… diff truncated
builtin/credential/aws/pkcs7/sign_test.go+266 −0
@@ -0,0 +1,266 @@+package pkcs7++import (+"bytes"+"crypto/dsa"+"crypto/x509"+"encoding/asn1"+"encoding/pem"+"fmt"+"io/ioutil"+"log"+"math/big"+"os"+"os/exec"+"testing"+)++func TestSign(t *testing.T) {+content := []byte("Hello World")+sigalgs := []x509.SignatureAlgorithm{+x509.SHA1WithRSA,+x509.SHA256WithRSA,+x509.SHA512WithRSA,+x509.ECDSAWithSHA1,+x509.ECDSAWithSHA256,+x509.ECDSAWithSHA384,+x509.ECDSAWithSHA512,+}+for _, sigalgroot := range sigalgs {+rootCert, err := createTestCertificateByIssuer("PKCS7 Test Root CA", nil, sigalgroot, true)+if err != nil {+t.Fatalf("test %s: cannot generate root cert: %s", sigalgroot, err)+}+truststore := x509.NewCertPool()+truststore.AddCert(rootCert.Certificate)+for _, sigalginter := range sigalgs {+interCert, err := createTestCertificateByIssuer("PKCS7 Test Intermediate Cert", rootCert, sigalginter, true)+if err != nil {+t.Fatalf("test %s/%s: cannot generate intermediate cert: %s", sigalgroot, sigalginter, err)+}+var parents []*x509.Certificate+parents = append(parents, interCert.Certificate)+for _, sigalgsigner := range sigalgs {+signerCert, err := createTestCertificateByIssuer("PKCS7 Test Signer Cert", interCert, sigalgsigner, false)+if err != nil {+t.Fatalf("test %s/%s/%s: cannot generate signer cert: %s", sigalgroot, sigalginter, sigalgsigner, err)+}+for _, testDetach := range []bool{false, true} {+log.Printf("test %s/%s/%s detached %t\n", sigalgroot, sigalginter, sigalgsigner, testDetach)+toBeSigned, err := NewSignedData(content)+if err != nil {+t.Fatalf("test %s/%s/%s: cannot initialize signed data: %s", sigalgroot, sigalginter, sigalgsigner, err)+}++// Set the digest to match the end entity cert+signerDigest, _ := getDigestOIDForSignatureAlgorithm(signerCert.Certificate.SignatureAlgorithm)+toBeSigned.SetDigestAlgorithm(signerDigest)++if err := toBeSigned.AddSignerChain(signerCert.Certificate, *signerCert.PrivateKey, parents, SignerInfoConfig{}); err != nil {+t.Fatalf("test %s/%s/%s: cannot add signer: %s", sigalgroot, sigalginter, sigalgsigner, err)+}+if testDetach {+toBeSigned.Detach()+}+signed, err := toBeSigned.Finish()+if err != nil {+t.Fatalf("test %s/%s/%s: cannot finish signing data: %s", sigalgroot, sigalginter, sigalgsigner, err)+}+pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: signed})+p7, err := Parse(signed)+if err != nil {+t.Fatalf("test %s/%s/%s: cannot parse signed data: %s", sigalgroot, sigalginter, sigalgsigner, err)+}+if testDetach {+p7.Content = content+}+if !bytes.Equal(content, p7.Content) {+t.Errorf("test %s/%s/%s: content was not found in the parsed data:\n\tExpected: %s\n\tActual: %s", sigalgroot, sigalginter, sigalgsigner, content, p7.Content)+}+if err := p7.VerifyWithChain(truststore); err != nil {+t.Errorf("test %s/%s/%s: cannot verify signed data: %s", sigalgroot, sigalginter, sigalgsigner, err)+}+if !signerDigest.Equal(p7.Signers[0].DigestAlgorithm.Algorithm) {+t.Errorf("test %s/%s/%s: expected digest algorithm %q but got %q",+sigalgroot, sigalginter, sigalgsigner, signerDigest, p7.Signers[0].DigestAlgorithm.Algorithm)+}+}+}+}+}+}++func TestDSASignAndVerifyWithOpenSSL(t *testing.T) {+content := []byte("Hello World")+// write the content to a temp file+tmpContentFile, err := ioutil.TempFile("", "TestDSASignAndVerifyWithOpenSSL_content")+if err != nil {+t.Fatal(err)+}+ioutil.WriteFile(tmpContentFile.Name(), content, 0755)++block, _ := pem.Decode([]byte(dsaPublicCert))+if block == nil {+t.Fatal("failed to parse certificate PEM")+}+signerCert, err := x509.ParseCertificate(block.Bytes)+if err != nil {+t.Fatal("failed to parse certificate: " + err.Error())+}++// write the signer cert to a temp file+tmpSignerCertFile, err := ioutil.TempFile("", "TestDSASignAndVerifyWithOpenSSL_signer")+if err != nil {+t.Fatal(err)+}+ioutil.WriteFile(tmpSignerCertFile.Name(), dsaPublicCert, 0755)++priv := dsa.PrivateKey{+PublicKey: dsa.PublicKey{Parameters: dsa.Parameters{P: fromHex("fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31e3f80b6512669455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b801d346ff26660b76b9950a5a49f9fe8047b1022c24fbba9d7feb7c61bf83b57e7c6a8a6150f04fb83f6d3c51ec3023554135a169132f675f3ae2b61d72aeff22203199dd14801c7"),+Q: fromHex("9760508F15230BCCB292B982A2EB840BF0581CF5"),+G: fromHex("F7E1A085D69B3DDECBBCAB5C36B857B97994AFBBFA3AEA82F9574C0B3D0782675159578EBAD4594FE67107108180B449167123E84C281613B7CF09328CC8A6E13C167A8B547C8D28E0A3AE1E2BB3A675916EA37F0BFA213562F1FB627A01243BCCA4F1BEA8519089A883DFE15AE59F06928B665E807B552564014C3BFECF492A"),+},+},+X: fromHex("7D6E1A3DD4019FD809669D8AB8DA73807CEF7EC1"),+}+toBeSigned, err := NewSignedData(content)+if err != nil {+t.Fatalf("test case: cannot initialize signed data: %s", err)+}+if err := toBeSigned.SignWithoutAttr(signerCert, &priv, SignerInfoConfig{}); err != nil {+t.Fatalf("Cannot add signer: %s", err)+}+toBeSigned.Detach()+signed, err := toBeSigned.Finish()+if err != nil {+t.Fatalf("test case: cannot finish signing data: %s", err)+}++// write the signature to a temp file+tmpSignatureFile, err := ioutil.TempFile("", "TestDSASignAndVerifyWithOpenSSL_signature")+if err != nil {+t.Fatal(err)+}+ioutil.WriteFile(tmpSignatureFile.Name(), pem.EncodeToMemory(&pem.Block{Type: "PKCS7", Bytes: signed}), 0755)++// call openssl to verify the signature on the content using the root+opensslCMD := exec.Command("openssl", "smime", "-verify", "-noverify",+"-in", tmpSignatureFile.Name(), "-inform", "PEM",+"-content", tmpContentFile.Name())+out, err := opensslCMD.CombinedOutput()+if err != nil {+t.Fatalf("test case: openssl command failed with %s: %s", err, out)+}+os.Remove(tmpSignatureFile.Name()) // clean up+os.Remove(tmpContentFile.Name()) // clean up+os.Remove(tmpSignerCertFile.Name()) // clean up+}++func ExampleSignedData() {+// generate a signing cert or load a key pair+cert, err := createTestCertificate(x509.SHA256WithRSA)+if err != nil {+fmt.Printf("Cannot create test certificates: %s", err)+}++// Initialize a SignedData struct with content to be signed+signedData, err := NewSignedData([]byte("Example data to be signed"))+if err != nil {+fmt.Printf("Cannot initialize signed data: %s", err)+}++// Add the signing cert and private key+if err := signedData.AddSigner(cert.Certificate, cert.PrivateKey, SignerInfoConfig{}); err != nil {+fmt.Printf("Cannot add signer: %s", err)+}++// Call Detach() is you want to remove content from the signature+// and generate an S/MIME detached signature+signedData.Detach()++// Finish() to obtain the signature bytes+detachedSignature, err := signedData.Finish()+if err != nil {+fmt.Printf("Cannot finish signing data: %s", err)+}+pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: detachedSignature})+}++func TestUnmarshalSignedAttribute(t *testing.T) {+cert, err := createTestCertificate(x509.SHA512WithRSA)+if err != nil {+t.Fatal(err)+}+content := []byte("Hello World")+toBeSigned, err := NewSignedData(content)+if err != nil {+t.Fatalf("Cannot initialize signed data: %s", err)+}+oidTest := asn1.ObjectIdentifier{2, 3, 4, 5, 6, 7}+testValue := "TestValue"+if err := toBeSigned.AddSigner(cert.Certificate, *cert.PrivateKey, SignerInfoConfig{+ExtraSignedAttributes: []Attribute{Attribute{Type: oidTest, Value: testValue}},+}); err != nil {+t.Fatalf("Cannot add signer: %s", err)+}+signed, err := toBeSigned.Finish()+if err != nil {+t.Fatalf("Cannot finish signing data: %s", err)+}+p7, err := Parse(signed)+if err != nil {+t.Fatalf("Cannot parse signed data: %v", err)+}+var actual string+err = p7.UnmarshalSignedAttribute(oidTest, &actual)+if err != nil {+t.Fatalf("Cannot unmarshal test value: %s", err)+}+if testValue != actual {+t.Errorf("Attribute does not match test value\n\tExpected: %s\n\tActual: %s", testValue, actual)+}+}++func TestDegenerateCertificate(t *testing.T) {+cert, err := createTestCertificate(x509.SHA1WithRSA)+if err != nil {+t.Fatal(err)+}+deg, err := DegenerateCertificate(cert.Certificate.Raw)+if err != nil {+t.Fatal(err)+}+testOpenSSLParse(t, deg)+pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: deg})+}++// writes the cert to a temporary file and tests that openssl can read it.+func testOpenSSLParse(t *testing.T, certBytes []byte) {+tmpCertFile, err := ioutil.TempFile("", "testCertificate")+if err != nil {+t.Fatal(err)+}+defer os.Remove(tmpCertFile.Name()) // clean up++if _, err := tmpCertFile.Write(certBytes); err != nil {+t.Fatal(err)+}++opensslCMD := exec.Command("openssl", "pkcs7", "-inform", "der", "-in", tmpCertFile.Name())+_, err = opensslCMD.Output()+if err != nil {+t.Fatal(err)+}++if err := tmpCertFile.Close(); err != nil {+t.Fatal(err)+}++}+func fromHex(s string) *big.Int {+result, ok := new(big.Int).SetString(s, 16)+if !ok {+panic(s)+}+return result+}
builtin/credential/aws/pkcs7/ber.go+251 −0
@@ -0,0 +1,251 @@+package pkcs7++import (+"bytes"+"errors"+)++var encodeIndent = 0++type asn1Object interface {+EncodeTo(writer *bytes.Buffer) error+}++type asn1Structured struct {+tagBytes []byte+content []asn1Object+}++func (s asn1Structured) EncodeTo(out *bytes.Buffer) error {+//fmt.Printf("%s--> tag: % X\n", strings.Repeat("| ", encodeIndent), s.tagBytes)+encodeIndent+++inner := new(bytes.Buffer)+for _, obj := range s.content {+err := obj.EncodeTo(inner)+if err != nil {+return err+}+}+encodeIndent--+out.Write(s.tagBytes)+encodeLength(out, inner.Len())+out.Write(inner.Bytes())+return nil+}++type asn1Primitive struct {+tagBytes []byte+length int+content []byte+}++func (p asn1Primitive) EncodeTo(out *bytes.Buffer) error {+_, err := out.Write(p.tagBytes)+if err != nil {+return err+}+if err = encodeLength(out, p.length); err != nil {+return err+}+//fmt.Printf("%s--> tag: % X length: %d\n", strings.Repeat("| ", encodeIndent), p.tagBytes, p.length)+//fmt.Printf("%s--> content length: %d\n", strings.Repeat("| ", encodeIndent), len(p.content))+out.Write(p.content)++return nil+}++func ber2der(ber []byte) ([]byte, error) {+if len(ber) == 0 {+return nil, errors.New("ber2der: input ber is empty")+}+//fmt.Printf("--> ber2der: Transcoding %d bytes\n", len(ber))+out := new(bytes.Buffer)++obj, _, err := readObject(ber, 0)+if err != nil {+return nil, err+}+obj.EncodeTo(out)++// if offset < len(ber) {+// return nil, fmt.Errorf("ber2der: Content longer than expected. Got %d, expected %d", offset, len(ber))+//}++return out.Bytes(), nil+}++// encodes lengths that are longer than 127 into string of bytes+func marshalLongLength(out *bytes.Buffer, i int) (err error) {+n := lengthLength(i)++for ; n > 0; n-- {+err = out.WriteByte(byte(i >> uint((n-1)*8)))+if err != nil {+return+}+}++return nil+}++// computes the byte length of an encoded length value+func lengthLength(i int) (numBytes int) {+numBytes = 1+for i > 255 {+numBytes+++i >>= 8+}+return+}++// encodes the length in DER format+// If the length fits in 7 bits, the value is encoded directly.+//+// Otherwise, the number of bytes to encode the length is first determined.+// This number is likely to be 4 or less for a 32bit length. This number is+// added to 0x80. The length is encoded in big endian encoding follow after+//+// Examples:+// length | byte 1 | bytes n+// 0 | 0x00 | -+// 120 | 0x78 | -+// 200 | 0x81 | 0xC8+// 500 | 0x82 | 0x01 0xF4+//+func encodeLength(out *bytes.Buffer, length int) (err error) {+if length >= 128 {+l := lengthLength(length)+err = out.WriteByte(0x80 | byte(l))+if err != nil {+return+}+err = marshalLongLength(out, length)+if err != nil {+return+}+} else {+err = out.WriteByte(byte(length))+if err != nil {+return+}+}+return+}++func readObject(ber []byte, offset int) (asn1Object, int, error) {+berLen := len(ber)+if offset >= berLen {+return nil, 0, errors.New("ber2der: offset is after end of ber data")+}+tagStart := offset+b := ber[offset]+offset+++if offset >= berLen {+return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached")+}+tag := b & 0x1F // last 5 bits+if tag == 0x1F {+tag = 0+for ber[offset] >= 0x80 {+tag = tag*128 + ber[offset] - 0x80+offset+++if offset > berLen {+return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached")+}+}+// jvehent 20170227: this doesn't appear to be used anywhere...+//tag = tag*128 + ber[offset] - 0x80+offset+++if offset > berLen {+return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached")+}+}+tagEnd := offset++kind := b & 0x20+if kind == 0 {+debugprint("--> Primitive\n")+} else {+debugprint("--> Constructed\n")+}+// read length+var length int+l := ber[offset]+offset+++if offset > berLen {+return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached")+}+hack := 0+if l > 0x80 {+numberOfBytes := (int)(l & 0x7F)+if numberOfBytes > 4 { // int is only guaranteed to be 32bit+return nil, 0, errors.New("ber2der: BER tag length too long")+}+if numberOfBytes == 4 && (int)(ber[offset]) > 0x7F {+return nil, 0, errors.New("ber2der: BER tag length is negative")+}+if (int)(ber[offset]) == 0x0 {+return nil, 0, errors.New("ber2der: BER tag length has leading zero")+}+debugprint("--> (compute length) indicator byte: %x\n", l)+debugprint("--> (compute length) length bytes: % X\n", ber[offset:offset+numberOfBytes])+for i := 0; i < numberOfBytes; i++ {+length = length*256 + (int)(ber[offset])+offset+++if offset > berLen {+return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached")+}+}+} else if l == 0x80 {+// find length by searching content+markerIndex := bytes.LastIndex(ber[offset:], []byte{0x0, 0x0})+if markerIndex == -1 {+return nil, 0, errors.New("ber2der: Invalid BER format")+}+length = markerIndex+hack = 2+debugprint("--> (compute length) marker found at offset: %d\n", markerIndex+offset)+} else {+length = (int)(l)+}+if length < 0 {+return nil, 0, errors.New("ber2der: invalid negative value found in BER tag length")+}+//fmt.Printf("--> length : %d\n", length)+contentEnd := offset + length+if contentEnd > len(ber) {+return nil, 0, errors.New("ber2der: BER tag length is more than available data")+}+debugprint("--> content start : %d\n", offset)+debugprint("--> content end : %d\n", contentEnd)+debugprint("--> content : % X\n", ber[offset:contentEnd])+var obj asn1Object+if kind == 0 {+obj = asn1Primitive{+tagBytes: ber[tagStart:tagEnd],+length: length,+content: ber[offset:contentEnd],+}+} else {+var subObjects []asn1Object+for offset < contentEnd {+var subObj asn1Object+var err error+subObj, offset, err = readObject(ber[:contentEnd], offset)+if err != nil {+return nil, 0, err+}+subObjects = append(subObjects, subObj)+}+obj = asn1Structured{+tagBytes: ber[tagStart:tagEnd],+content: subObjects,+}+}++return obj, contentEnd + hack, nil+}++func debugprint(format string, a ...interface{}) {+//fmt.Printf(format, a)+}
builtin/credential/aws/pkcs7/verify_test_dsa.go+182 −0
@@ -0,0 +1,182 @@+// +build go1.11 go1.12 go1.13 go1.14 go1.15++package pkcs7++import (+"crypto/x509"+"encoding/pem"+"fmt"+"io/ioutil"+"os"+"os/exec"+"testing"+)++func TestVerifyEC2(t *testing.T) {+fixture := UnmarshalDSATestFixture(EC2IdentityDocumentFixture)+p7, err := Parse(fixture.Input)+if err != nil {+t.Errorf("Parse encountered unexpected error: %v", err)+}+p7.Certificates = []*x509.Certificate{fixture.Certificate}+if err := p7.Verify(); err != nil {+t.Errorf("Verify failed with error: %v", err)+}+}++var EC2IdentityDocumentFixture = `+-----BEGIN PKCS7-----+MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAaCA+JIAEggGmewogICJwcml2YXRlSXAiIDogIjE3Mi4zMC4wLjI1MiIsCiAgImRldnBh+eVByb2R1Y3RDb2RlcyIgOiBudWxsLAogICJhdmFpbGFiaWxpdHlab25lIiA6ICJ1+cy1lYXN0LTFhIiwKICAidmVyc2lvbiIgOiAiMjAxMC0wOC0zMSIsCiAgImluc3Rh+bmNlSWQiIDogImktZjc5ZmU1NmMiLAogICJiaWxsaW5nUHJvZHVjdHMiIDogbnVs+bCwKICAiaW5zdGFuY2VUeXBlIiA6ICJ0Mi5taWNybyIsCiAgImFjY291bnRJZCIg+OiAiMTIxNjU5MDE0MzM0IiwKICAiaW1hZ2VJZCIgOiAiYW1pLWZjZTNjNjk2IiwK+ICAicGVuZGluZ1RpbWUiIDogIjIwMTYtMDQtMDhUMDM6MDE6MzhaIiwKICAiYXJj+aGl0ZWN0dXJlIiA6ICJ4ODZfNjQiLAogICJrZXJuZWxJZCIgOiBudWxsLAogICJy+YW1kaXNrSWQiIDogbnVsbCwKICAicmVnaW9uIiA6ICJ1cy1lYXN0LTEiCn0AAAAA+AAAxggEYMIIBFAIBATBpMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5n+dG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2Vi+IFNlcnZpY2VzIExMQwIJAJa6SNnlXhpnMAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0B+CQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xNjA0MDgwMzAxNDRaMCMG+CSqGSIb3DQEJBDEWBBTuUc28eBXmImAautC+wOjqcFCBVjAJBgcqhkjOOAQDBC8w+LQIVAKA54NxGHWWCz5InboDmY/GHs33nAhQ6O/ZI86NwjA9Vz3RNMUJrUPU5tAAA+AAAAAA==+-----END PKCS7-----+-----BEGIN CERTIFICATE-----+MIIC7TCCAq0CCQCWukjZ5V4aZzAJBgcqhkjOOAQDMFwxCzAJBgNVBAYTAlVTMRkw+FwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYD+VQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0xMjAxMDUxMjU2MTJaFw0z+ODAxMDUxMjU2MTJaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9u+IFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNl+cnZpY2VzIExMQzCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQCjkvcS2bb1VQ4yt/5e+ih5OO6kK/n1Lzllr7D8ZwtQP8fOEpp5E2ng+D6Ud1Z1gYipr58Kj3nssSNpI6bX3+VyIQzK7wLclnd/YozqNNmgIyZecN7EglK9ITHJLP+x8FtUpt3QbyYXJdmVMegN6P+hviYt5JH/nYl4hh3Pa1HJdskgQIVALVJ3ER11+Ko4tP6nwvHwh6+ERYRAoGBAI1j+k+tkqMVHuAFcvAGKocTgsjJem6/5qomzJuKDmbJNu9Qxw3rAotXau8Qe+MBcJl/U+hhy1KHVpCGl9fueQ2s6IL0CaO/buycU1CiYQk40KNHCcHfNiZbdlx1E9rpUp7bnF+lRa2v1ntMX3caRVDdbtPEWmdxSCYsYFDk4mZrOLBA4GEAAKBgEbmeve5f8LIE/Gf+MNmP9CM5eovQOGx5ho8WqD+aTebs+k2tn92BBPqeZqpWRa5P/+jrdKml1qx4llHW+MXrs3IgIb6+hUIB+S8dz8/mmO0bpr76RoZVCXYab2CZedFut7qc3WUH9+EUAH5mw+vSeDCOUMYQR7R9LINYwouHIziqQYMAkGByqGSM44BAMDLwAwLAIUWXBlk40xTwSw+7HX32MxXYruse9ACFBNGmdX2ZBrVNGrN9N2f6ROk0k9K+-----END CERTIFICATE-----`++func TestDSASignWithOpenSSLAndVerify(t *testing.T) {+content := []byte(`+A ship in port is safe,+but that's not what ships are built for.+-- Grace Hopper`)+// write the content to a temp file+tmpContentFile, err := ioutil.TempFile("", "TestDSASignWithOpenSSLAndVerify_content")+if err != nil {+t.Fatal(err)+}+ioutil.WriteFile(tmpContentFile.Name(), content, 0755)++// write the signer cert to a temp file+tmpSignerCertFile, err := ioutil.TempFile("", "TestDSASignWithOpenSSLAndVerify_signer")+if err != nil {+t.Fatal(err)+}+ioutil.WriteFile(tmpSignerCertFile.Name(), dsaPublicCert, 0755)++// write the signer key to a temp file+tmpSignerKeyFile, err := ioutil.TempFile("", "TestDSASignWithOpenSSLAndVerify_key")+if err != nil {+t.Fatal(err)+}+ioutil.WriteFile(tmpSignerKeyFile.Name(), dsaPrivateKey, 0755)++tmpSignedFile, err := ioutil.TempFile("", "TestDSASignWithOpenSSLAndVerify_signature")+if err != nil {+t.Fatal(err)+}+// call openssl to sign the content+opensslCMD := exec.Command("openssl", "smime", "-sign", "-nodetach", "-md", "sha1",+"-in", tmpContentFile.Name(), "-out", tmpSignedFile.Name(),+"-signer", tmpSignerCertFile.Name(), "-inkey", tmpSignerKeyFile.Name(),+"-certfile", tmpSignerCertFile.Name(), "-outform", "PEM")+out, err := opensslCMD.CombinedOutput()+if err != nil {+t.Fatalf("openssl command failed with %s: %s", err, out)+}++// verify the signed content+pemSignature, err := ioutil.ReadFile(tmpSignedFile.Name())+if err != nil {+t.Fatal(err)+}+fmt.Printf("%s\n", pemSignature)+derBlock, _ := pem.Decode(pemSignature)+if derBlock == nil {+t.Fatalf("failed to read DER block from signature PEM %s", tmpSignedFile.Name())+}+p7, err := Parse(derBlock.Bytes)+if err != nil {+t.Fatalf("Parse encountered unexpected error: %v", err)+}+if err := p7.Verify(); err != nil {+t.Fatalf("Verify failed with error: %v", err)+}+os.Remove(tmpSignerCertFile.Name()) // clean up+os.Remove(tmpSignerKeyFile.Name()) // clean up+os.Remove(tmpContentFile.Name()) // clean up+}++var dsaPrivateKey = []byte(`-----BEGIN PRIVATE KEY-----+MIIBSwIBADCCASwGByqGSM44BAEwggEfAoGBAP1/U4EddRIpUt9KnC7s5Of2EbdS+PO9EAMMeP4C2USZpRV1AIlH7WT2NWPq/xfW6MPbLm1Vs14E7gB00b/JmYLdrmVCl+pJ+f6AR7ECLCT7up1/63xhv4O1fnxqimFQ8E+4P208UewwI1VBNaFpEy9nXzrith+1yrv8iIDGZ3RSAHHAhUAl2BQjxUjC8yykrmCouuEC/BYHPUCgYEA9+GghdabPd7L+vKtcNrhXuXmUr7v6OuqC+VdMCz0HgmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3+zwkyjMim4TwWeotUfI0o4KOuHiuzpnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImo+g9/hWuWfBpKLZl6Ae1UlZAFMO/7PSSoEFgIUfW4aPdQBn9gJZp2KuNpzgHzvfsE=+-----END PRIVATE KEY-----`)++var dsaPublicCert = []byte(`-----BEGIN CERTIFICATE-----+MIIDOjCCAvWgAwIBAgIEPCY/UDANBglghkgBZQMEAwIFADBsMRAwDgYDVQQGEwdV+bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD+VQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3du+MB4XDTE4MTAyMjEzNDMwN1oXDTQ2MDMwOTEzNDMwN1owbDEQMA4GA1UEBhMHVW5r+bm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UE+ChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCC+AbgwggEsBgcqhkjOOAQBMIIBHwKBgQD9f1OBHXUSKVLfSpwu7OTn9hG3UjzvRADD+Hj+AtlEmaUVdQCJR+1k9jVj6v8X1ujD2y5tVbNeBO4AdNG/yZmC3a5lQpaSfn+gE+exAiwk+7qdf+t8Yb+DtX58aophUPBPuD9tPFHsMCNVQTWhaRMvZ1864rYdcq7/Ii+Axmd0UgBxwIVAJdgUI8VIwvMspK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4+V7l5lK+7+jrqgvlXTAs9B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozI+puE8FnqLVHyNKOCjrh4rs6Z1kW6jfwv6ITVi8ftiegEkO8yk8b6oUZCJqIPf4Vrl+nwaSi2ZegHtVJWQBTDv+z0kqA4GFAAKBgQDCriMPbEVBoRK4SOUeFwg7+VRf4TTp+rcOQC9IVVoCjXzuWEGrp3ZI7YWJSpFnSch4lk29RH8O0HpI/NOzKnOBtnKr782pt+1k/bJVMH9EaLd6MKnAVjrCDMYBB0MhebZ8QHY2elZZCWoqDYAcIDOsEx+m4NLErT+ypPnjS5M0jm1PKMhMB8wHQYDVR0OBBYEFC0Yt5XdM0Kc95IX8NQ8XRssGPx7MA0G+CWCGSAFlAwQDAgUAAzAAMC0CFQCIgQtrZZ9hdZG1ROhR5hc8nYEmbgIUAIlgC688+qzy/7yePTlhlpj+ahMM=+-----END CERTIFICATE-----`)++type DSATestFixture struct {+Input []byte+Certificate *x509.Certificate+}++func UnmarshalDSATestFixture(testPEMBlock string) DSATestFixture {+var result DSATestFixture+var derBlock *pem.Block+var pemBlock = []byte(testPEMBlock)+for {+derBlock, pemBlock = pem.Decode(pemBlock)+if derBlock == nil {+break+}+switch derBlock.Type {+case "PKCS7":+result.Input = derBlock.Bytes+case "CERTIFICATE":+result.Certificate, _ = x509.ParseCertificate(derBlock.Bytes)+}+}++return result+}
builtin/credential/aws/pkcs7/verify.go+399 −0
@@ -0,0 +1,399 @@+package pkcs7++import (+"crypto"+"crypto/dsa"+"crypto/subtle"+"crypto/x509"+"crypto/x509/pkix"+"encoding/asn1"+"errors"+"fmt"+"time"+)++// Verify is a wrapper around VerifyWithChain() that initializes an empty+// trust store, effectively disabling certificate verification when validating+// a signature.+func (p7 *PKCS7) Verify() (err error) {+return p7.VerifyWithChain(nil)+}++// VerifyWithChain checks the signatures of a PKCS7 object.+//+// If truststore is not nil, it also verifies the chain of trust of+// the end-entity signer cert to one of the roots in the+// truststore. When the PKCS7 object includes the signing time+// authenticated attr verifies the chain at that time and UTC now+// otherwise.+func (p7 *PKCS7) VerifyWithChain(truststore *x509.CertPool) (err error) {+if len(p7.Signers) == 0 {+return errors.New("pkcs7: Message has no signers")+}+for _, signer := range p7.Signers {+if err := verifySignature(p7, signer, truststore); err != nil {+return err+}+}+return nil+}++// VerifyWithChainAtTime checks the signatures of a PKCS7 object.+//+// If truststore is not nil, it also verifies the chain of trust of+// the end-entity signer cert to a root in the truststore at+// currentTime. It does not use the signing time authenticated+// attribute.+func (p7 *PKCS7) VerifyWithChainAtTime(truststore *x509.CertPool, currentTime time.Time) (err error) {+if len(p7.Signers) == 0 {+return errors.New("pkcs7: Message has no signers")+}+for _, signer := range p7.Signers {+if err := verifySignatureAtTime(p7, signer, truststore, currentTime); err != nil {+return err+}+}+return nil+}++func verifySignatureAtTime(p7 *PKCS7, signer signerInfo, truststore *x509.CertPool, currentTime time.Time) (err error) {+signedData := p7.Content+ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber)+if ee == nil {+return errors.New("pkcs7: No certificate for signer")+}+if len(signer.AuthenticatedAttributes) > 0 {+// TODO(fullsailor): First check the content type match+var (+digest []byte+signingTime time.Time+)+err := unmarshalAttribute(signer.AuthenticatedAttributes, OIDAttributeMessageDigest, &digest)+if err != nil {+return err+}+hash, err := getHashForOID(signer.DigestAlgorithm.Algorithm)+if err != nil {+return err+}+h := hash.New()+h.Write(p7.Content)+computed := h.Sum(nil)+if subtle.ConstantTimeCompare(digest, computed) != 1 {+return &MessageDigestMismatchError{+ExpectedDigest: digest,+ActualDigest: computed,+}+}+signedData, err = marshalAttributes(signer.AuthenticatedAttributes)+if err != nil {+return err+}+err = unmarshalAttribute(signer.AuthenticatedAttributes, OIDAttributeSigningTime, &signingTime)+if err == nil {+// signing time found, performing validity check+if signingTime.After(ee.NotAfter) || signingTime.Before(ee.NotBefore) {+return fmt.Errorf("pkcs7: signing time %q is outside of certificate validity %q to %q",+signingTime.Format(time.RFC3339),+ee.NotBefore.Format(time.RFC3339),+ee.NotAfter.Format(time.RFC3339))+}+}+}+if truststore != nil {+_, err = verifyCertChain(ee, p7.Certificates, truststore, currentTime)+if err != nil {+return err+}+}+sigalg, err := getSignatureAlgorithm(signer.DigestEncryptionAlgorithm, signer.DigestAlgorithm)+if err != nil {+return err+}+switch sigalg {+case x509.DSAWithSHA1, x509.DSAWithSHA256:+return dsaCheckSignature(sigalg, signedData, signer.EncryptedDigest, ee.PublicKey)+default:+return ee.CheckSignature(sigalg, signedData, signer.EncryptedDigest)+}+}++// dsaSignature verifies the DSA signature on a PKCS7 document. DSA support was+// removed from Go's crypto/x509 support prior to Go 1.16. This allows+// verifying legacy signatures until affected applications can be migrated off+// of DSA.+func dsaCheckSignature(algo x509.SignatureAlgorithm, signed, signature []byte, publicKey crypto.PublicKey) error {+dsaKey, ok := publicKey.(*dsa.PublicKey)+if !ok {+return ErrUnsupportedAlgorithm+}++var hashType crypto.Hash+switch algo {+case x509.DSAWithSHA1:+hashType = crypto.SHA1+case x509.DSAWithSHA256:+hashType = crypto.SHA256+default:+return ErrUnsupportedAlgorithm+}+h := hashType.New()+h.Write(signed)+signed = h.Sum(nil)++dsaSig := new(dsaSignature)+if rest, err := asn1.Unmarshal(signature, dsaSig); err != nil {+return err+} else if len(rest) != 0 {+return errors.New("x509: trailing data after DSA signature")+}+if dsaSig.R.Sign() <= 0 || dsaSig.S.Sign() <= 0 {+return errors.New("x509: DSA signature contained zero or negative values")+}+// According to FIPS 186-3, section 4.6, the hash must be truncated if it is longer+// than the key length, but crypto/dsa doesn't do it automatically.+if maxHashLen := dsaKey.Q.BitLen() / 8; maxHashLen < len(signed) {+signed = signed[:maxHashLen]+}+if !dsa.Verify(dsaKey, signed, dsaSig.R, dsaSig.S) {+return errors.New("x509: DSA verification failure")+}+return nil+}++func verifySignature(p7 *PKCS7, signer signerInfo, truststore *x509.CertPool) (err error) {+signedData := p7.Content+ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber)+if ee == nil {+return errors.New("pkcs7: No certificate for signer")+}+signingTime := time.Now().UTC()+if len(signer.AuthenticatedAttributes) > 0 {+// TODO(fullsailor): First check the content type match+var digest []byte+err := unmarshalAttribute(signer.AuthenticatedAttributes, OIDAttributeMessageDigest, &digest)+if err != nil {+return err+}+hash, err := getHashForOID(signer.DigestAlgorithm.Algorithm)+if err != nil {+return err+}+h := hash.New()+h.Write(p7.Content)+computed := h.Sum(nil)+if subtle.ConstantTimeCompare(digest, computed) != 1 {+return &MessageDigestMismatchError{+ExpectedDigest: digest,+ActualDigest: computed,+}+}+signedData, err = marshalAttributes(signer.AuthenticatedAttributes)+if err != nil {+return err+}+err = unmarshalAttribute(signer.AuthenticatedAttributes, OIDAttributeSigningTime, &signingTime)+if err == nil {+// signing time found, performing validity check+if signingTime.After(ee.NotAfter) || signingTime.Before(ee.NotBefore) {+return fmt.Errorf("pkcs7: signing time %q is outside of certificate validity %q to %q",+signingTime.Format(time.RFC3339),+ee.NotBefore.Format(time.RFC3339),+ee.NotAfter.Format(time.RFC3339))+}+}+}+if truststore != nil {+_, err = verifyCertChain(ee, p7.Certificates, truststore, signingTime)+if err != nil {+return err+}+}+sigalg, err := getSignatureAlgorithm(signer.DigestEncryptionAlgorithm, signer.DigestAlgorithm)+if err != nil {+return err+}++switch sigalg {+case x509.DSAWithSHA1, x509.DSAWithSHA256:+return dsaCheckSignature(sigalg, signedData, signer.EncryptedDigest, ee.PublicKey)+default:+return ee.CheckSignature(sigalg, signedData, signer.EncryptedDigest)+}+}++// GetOnlySigner returns an x509.Certificate for the first signer of the signed+// data payload. If there are more or less than one signer, nil is returned+func (p7 *PKCS7) GetOnlySigner() *x509.Certificate {+if len(p7.Signers) != 1 {+return nil+}+signer := p7.Signers[0]+return getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber)+}++// UnmarshalSignedAttribute decodes a single attribute from the signer info+func (p7 *PKCS7) UnmarshalSignedAttribute(attributeType asn1.ObjectIdentifier, out interface{}) error {+sd, ok := p7.raw.(signedData)+if !ok {+return errors.New("pkcs7: payload is not signedData content")+}+if len(sd.SignerInfos) < 1 {+return errors.New("pkcs7: payload has no signers")+}+attributes := sd.SignerInfos[0].AuthenticatedAttributes+return unmarshalAttribute(attributes, attributeType, out)+}++func parseSignedData(data []byte) (*PKCS7, error) {+var sd signedData+asn1.Unmarshal(data, &sd)+certs, err := sd.Certificates.Parse()+if err != nil {+return nil, err+}+// fmt.Printf("--> Signed Data Version %d\n", sd.Version)++var compound asn1.RawValue+var content unsignedData++// The Content.Bytes maybe empty on PKI responses.+if len(sd.ContentInfo.Content.Bytes) > 0 {+if _, err := asn1.Unmarshal(sd.ContentInfo.Content.Bytes, &compound); err != nil {+return nil, err+}+}+// Compound octet string+if compound.IsCompound {+if compound.Tag == 4 {+if _, err = asn1.Unmarshal(compound.Bytes, &content); err != nil {+return nil, err+}+} else {+content = compound.Bytes+}+} else {+// assuming this is tag 04+content = compound.Bytes+}+return &PKCS7{+Content: content,+Certificates: certs,+CRLs: sd.CRLs,+Signers: sd.SignerInfos,+raw: sd}, nil+}++// verifyCertChain takes an end-entity certs, a list of potential intermediates and a+// truststore, and built all potential chains between the EE and a trusted root.+//+// When verifying chains that may have expired, currentTime can be set to a past date+// to allow the verification to pass. If unset, currentTime is set to the current UTC time.+func verifyCertChain(ee *x509.Certificate, certs []*x509.Certificate, truststore *x509.CertPool, currentTime time.Time) (chains [][]*x509.Certificate, err error) {+intermediates := x509.NewCertPool()+for _, intermediate := range certs {+intermediates.AddCert(intermediate)+}+verifyOptions := x509.VerifyOptions{+Roots: truststore,+Intermediates: intermediates,+KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},+CurrentTime: currentTime,+}+chains, err = ee.Verify(verifyOptions)+if err != nil {+return chains, fmt.Errorf("pkcs7: failed to verify certificate chain: %v", err)+}+return+}++// MessageDigestMismatchError is returned when the signer data digest does not+// match the computed digest for the contained content+type MessageDigestMismatchError struct {+ExpectedDigest []byte+ActualDigest []byte+}++func (err *MessageDigestMismatchError) Error() string {+return fmt.Sprintf("pkcs7: Message digest mismatch\n\tExpected: %X\n\tActual : %X", err.ExpectedDigest, err.ActualDigest)+}++func getSignatureAlgorithm(digestEncryption, digest pkix.AlgorithmIdentifier) (x509.SignatureAlgorithm, error) {+switch {+case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA1):+return x509.ECDSAWithSHA1, nil+case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA256):+return x509.ECDSAWithSHA256, nil+case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA384):+return x509.ECDSAWithSHA384, nil+case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA512):+return x509.ECDSAWithSHA512, nil+case digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSA),+digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA1),+digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA256),+digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA384),+digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA512):+switch {+case digest.Algorithm.Equal(OIDDigestAlgorithmSHA1):+return x509.SHA1WithRSA, nil+case digest.Algorithm.Equal(OIDDigestAlgorithmSHA256):+return x509.SHA256WithRSA, nil+case digest.Algorithm.Equal(OIDDigestAlgorithmSHA384):+return x509.SHA384WithRSA, nil+case digest.Algorithm.Equal(OIDDigestAlgorithmSHA512):+return x509.SHA512WithRSA, nil+default:+return -1, fmt.Errorf("pkcs7: unsupported digest %q for encryption algorithm %q",+digest.Algorithm.String(), digestEncryption.Algorithm.String())+}+case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmDSA),+digestEncryption.Algorithm.Equal(OIDDigestAlgorithmDSASHA1):+switch {+case digest.Algorithm.Equal(OIDDigestAlgorithmSHA1):+return x509.DSAWithSHA1, nil+case digest.Algorithm.Equal(OIDDigestAlgorithmSHA256):+return x509.DSAWithSHA256, nil+default:+return -1, fmt.Errorf("pkcs7: unsupported digest %q for encryption algorithm %q",+digest.Algorithm.String(), digestEncryption.Algorithm.String())+}+case digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmECDSAP256),+digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmECDSAP384),+digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmECDSAP521):+switch {+case digest.Algorithm.Equal(OIDDigestAlgorithmSHA1):+return x509.ECDSAWithSHA1, nil+case digest.Algorithm.Equal(OIDDigestAlgorithmSHA256):+return x509.ECDSAWithSHA256, nil+case digest.Algorithm.Equal(OIDDigestAlgorithmSHA384):+return x509.ECDSAWithSHA384, nil+case digest.Algorithm.Equal(OIDDigestAlgorithmSHA512):+return x509.ECDSAWithSHA512, nil+default:+return -1, fmt.Errorf("pkcs7: unsupported digest %q for encryption algorithm %q",+digest.Algorithm.String(), digestEncryption.Algorithm.String())+}+default:+return -1, fmt.Errorf("pkcs7: unsupported algorithm %q",+digestEncryption.Algorithm.String())+}+}++func getCertFromCertsByIssuerAndSerial(certs []*x509.Certificate, ias issuerAndSerial) *x509.Certificate {+for _, cert := range certs {+if isCertMatchForIssuerAndSerial(cert, ias) {+return cert+}+}+return nil+}++func unmarshalAttribute(attrs []attribute, attributeType asn1.ObjectIdentifier, out interface{}) error {+for _, attr := range attrs {+if attr.Type.Equal(attributeType) {+_, err := asn1.Unmarshal(attr.Value.Bytes, out)+return err+}+}+return errors.New("pkcs7: attribute type not in attributes")+}
vault/identity_store_oidc.go+99 −32
@@ -11,6 +11,7 @@ import ("errors""fmt""net/url"+"sort""strings""time"@@ -472,6 +473,25 @@ func (i *IdentityStore) pathOIDCCreateUpdateKey(ctx context.Context, req *logicareturn logical.ErrorResponse("verification_ttl cannot be longer than 10x rotation_period"), nil}+if req.Operation == logical.UpdateOperation {+// ensure any roles referencing this key do not already have a token_ttl+// greater than the key's verification_ttl+roles, err := i.rolesReferencingTargetKeyName(ctx, req, name)+if err != nil {+return nil, err+}+for _, role := range roles {+if role.TokenTTL > key.VerificationTTL {+errorMessage := fmt.Sprintf(+"unable to update key %q because it is currently referenced by one or more roles with a token ttl greater than %d seconds",+name,+key.VerificationTTL/time.Second,+)+return logical.ErrorResponse(errorMessage), nil+}+}+}+if allowedClientIDsRaw, ok := d.GetOk("allowed_client_ids"); ok {key.AllowedClientIDs = allowedClientIDsRaw.([]string)} else if req.Operation == logical.CreateOperation {@@ -556,46 +576,70 @@ func (i *IdentityStore) pathOIDCReadKey(ctx context.Context, req *logical.Reques}, nil}-// handleOIDCDeleteKey is used to delete a key-func (i *IdentityStore) pathOIDCDeleteKey(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {-ns, err := namespace.FromContext(ctx)-if err != nil {-return nil, err-}--targetKeyName := d.Get("name").(string)--i.oidcLock.Lock()--// it is an error to delete a key that is actively referenced by a role+// rolesReferencingTargetKeyName returns a map of role names to roles referenced by targetKeyName.+// Note: this is not threadsafe. It is to be called with Lock already held.+func (i *IdentityStore) rolesReferencingTargetKeyName(ctx context.Context, req *logical.Request, targetKeyName string) (map[string]role, error) {roleNames, err := req.Storage.List(ctx, roleConfigPath)if err != nil {-i.oidcLock.Unlock()return nil, err}-var role *role-rolesReferencingTargetKeyName := make([]string, 0)+var tempRole role+roles := make(map[string]role)for _, roleName := range roleNames {entry, err := req.Storage.Get(ctx, roleConfigPath+roleName)if err != nil {-i.oidcLock.Unlock()return nil, err}if entry != nil {-if err := entry.DecodeJSON(&role); err != nil {-i.oidcLock.Unlock()+if err := entry.DecodeJSON(&tempRole); err != nil {return nil, err}-if role.Key == targetKeyName {-rolesReferencingTargetKeyName = append(rolesReferencingTargetKeyName, roleName)+if tempRole.Key == targetKeyName {+roles[roleName] = tempRole}}}-if len(rolesReferencingTargetKeyName) > 0 {+return roles, nil+}++// roleNamesReferencingTargetKeyName returns a slice of strings of role+// names referenced by targetKeyName.+// Note: this is not threadsafe. It is to be called with Lock already held.+func (i *IdentityStore) roleNamesReferencingTargetKeyName(ctx context.Context, req *logical.Request, targetKeyName string) ([]string, error) {+roles, err := i.rolesReferencingTargetKeyName(ctx, req, targetKeyName)+if err != nil {+return nil, err+}++var names []string+for key, _ := range roles {+names = append(names, key)+}+sort.Strings(names)+return names, nil+}++// handleOIDCDeleteKey is used to delete a key+func (i *IdentityStore) pathOIDCDeleteKey(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {+ns, err := namespace.FromContext(ctx)+if err != nil {+return nil, err+}++targetKeyName := d.Get("name").(string)++i.oidcLock.Lock()++roleNames, err := i.roleNamesReferencingTargetKeyName(ctx, req, targetKeyName)+if err != nil {+return nil, err+}++if len(roleNames) > 0 {errorMessage := fmt.Sprintf("unable to delete key %q because it is currently referenced by these roles: %s",-targetKeyName, strings.Join(rolesReferencingTargetKeyName, ", "))+targetKeyName, strings.Join(roleNames, ", "))i.oidcLock.Unlock()return logical.ErrorResponse(errorMessage), logical.ErrInvalidRequest}@@ -747,13 +791,21 @@ func (i *IdentityStore) pathOIDCGenerateToken(ctx context.Context, req *logical.return nil, err}+retResp := &logical.Response{}+expiry := role.TokenTTL+if expiry > key.VerificationTTL {+expiry = key.VerificationTTL+retResp.AddWarning(fmt.Sprintf("a role's token ttl cannot be longer "++"than the verification_ttl of the key it references, setting token ttl to %d", expiry))+}+now := time.Now()idToken := idToken{Issuer: config.effectiveIssuer,Namespace: ns.ID,Subject: req.EntityID,Audience: role.ClientID,-Expiry: now.Add(role.TokenTTL).Unix(),+Expiry: now.Add(expiry).Unix(),IssuedAt: now.Unix(),}@@ -782,13 +834,12 @@ func (i *IdentityStore) pathOIDCGenerateToken(ctx context.Context, req *logical.return nil, fmt.Errorf("error signing OIDC token: %w", err)}-return &logical.Response{-Data: map[string]interface{}{-"token": signedIdToken,-"client_id": role.ClientID,-"ttl": int64(role.TokenTTL.Seconds()),-},-}, nil+retResp.Data = map[string]interface{}{+"token": signedIdToken,+"client_id": role.ClientID,+"ttl": int64(role.TokenTTL.Seconds()),+}+return retResp, nil}func (tok *idToken) generatePayload(logger hclog.Logger, template string, entity *identity.Entity, groups []*identity.Group) ([]byte, error) {@@ -867,7 +918,7 @@ func (i *IdentityStore) pathOIDCRoleExistenceCheck(ctx context.Context, req *logreturn role != nil, nil}-// handleOIDCCreateRole is used to create a new role or update an existing one+// pathOIDCCreateUpdateRole is used to create a new role or update an existing onefunc (i *IdentityStore) pathOIDCCreateUpdateRole(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {ns, err := namespace.FromContext(ctx)if err != nil {@@ -938,6 +989,22 @@ func (i *IdentityStore) pathOIDCCreateUpdateRole(ctx context.Context, req *logicrole.TokenTTL = time.Duration(d.Get("ttl").(int)) * time.Second}+// get the key referenced by this role+var key namedKey+entry, err := req.Storage.Get(ctx, namedKeyConfigPath+role.Key)+if err != nil {+return nil, err+}+if entry != nil {+if err := entry.DecodeJSON(&key); err != nil {+return nil, err+}+}++if role.TokenTTL > key.VerificationTTL {+return logical.ErrorResponse("a role's token ttl cannot be longer than the verification_ttl of the key it references"), nil+}+if clientID, ok := d.GetOk("client_id"); ok {role.ClientID = clientID.(string)}@@ -952,7 +1019,7 @@ func (i *IdentityStore) pathOIDCCreateUpdateRole(ctx context.Context, req *logic}// store role (which was either just created or updated)-entry, err := logical.StorageEntryJSON(roleConfigPath+name, role)+entry, err = logical.StorageEntryJSON(roleConfigPath+name, role)if err != nil {return nil, err}
ui/tests/acceptance/usage-metrics-test.js+35 −0
@@ -2,9 +2,22 @@ import { module, test } from 'qunit';import { visit, currentURL, findAll } from '@ember/test-helpers';import { setupApplicationTest } from 'ember-qunit';import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';+import { create } from 'ember-cli-page-object';import authPage from 'vault/tests/pages/auth';import logout from 'vault/tests/pages/logout';+import consoleClass from 'vault/tests/pages/components/console/ui-panel';++const consoleComponent = create(consoleClass);++const tokenWithPolicy = async function(name, policy) {+await consoleComponent.runCommands([+`write sys/policies/acl/${name} policy=${btoa(policy)}`,+`write -field=client_token auth/token/create policies=${name}`,+]);++return consoleComponent.lastLogOutput;+};module('Acceptance | usage metrics', function(hooks) {setupApplicationTest(hooks);@@ -65,6 +78,28 @@ module('Acceptance | usage metrics', function(hooks) {assert.equal(currentURL(), '/vault/metrics');assert.dom('[data-test-pricing-metrics-form]').exists('Pricing metrics date form exists');+assert.dom('[data-test-configuration-tab]').exists('Metrics config tab exists');+assert.dom('[data-test-tracking-disabled]').doesNotExist('Flash message does not exists');+assert.ok(findAll('.selectable-card').length === 3, 'renders the counts');+});++test('it shows metrics even if config endpoint not allowed', async function(assert) {+server.create('metrics/activity');+const deny_config_policy = `+path "sys/internal/counters/config" {+capabilities = ["deny"]+},+`;++const userToken = await tokenWithPolicy('no-metrics-config', deny_config_policy);+await logout.visit();+await authPage.login(userToken);++await visit('/vault/metrics');++assert.equal(currentURL(), '/vault/metrics');+assert.dom('[data-test-pricing-metrics-form]').exists('Pricing metrics date form exists');+assert.dom('[data-test-configuration-tab]').doesNotExist('Metrics config tab does not exist');assert.dom('[data-test-tracking-disabled]').doesNotExist('Flash message does not exists');assert.ok(findAll('.selectable-card').length === 3, 'renders the counts');});clearing the value (#12409) (#12416)changelog/12409.txt | 3 +++ui/lib/core/addon/components/form-field.js | 2 +-ui/lib/core/addon/components/masked-input.js | 6 ++++++ui/lib/core/addon/templates/components/form-field.hbs | 6 ++----.../core/addon/templates/components/masked-input.hbs | 4 ++++ui/tests/integration/components/masked-input-test.js | 11 ++++++++++-6 files changed, 26 insertions(+), 6 deletions(-)create mode 100644 changelog/12409.txt
CHANGELOG.md+21 −0
@@ -1,3 +1,24 @@+## 1.8.2+### 26 August 2021++CHANGES:++* go: Update go version to 1.16.7 [[GH-12408](https://github.com/hashicorp/vault/pull/12408)]++BUG FIXES:++* auth/aws: Fixes ec2 login no longer supporting DSA signature verification [[GH-12340](https://github.com/hashicorp/vault/pull/12340)]+* cli: vault debug now puts newlines after every captured log line. [[GH-12175](https://github.com/hashicorp/vault/pull/12175)]+* database/couchbase: change default template to truncate username at 128 characters [[GH-12300](https://github.com/hashicorp/vault/pull/12300)]+* identity: Fix a panic on arm64 platform when doing identity I/O. [[GH-12371](https://github.com/hashicorp/vault/pull/12371)]+* physical/raft: Fix safeio.Rename error when restoring snapshots on windows [[GH-12377](https://github.com/hashicorp/vault/pull/12377)]+* plugin/snowflake: Fixed bug where plugin would crash on 32 bit systems [[GH-12378](https://github.com/hashicorp/vault/pull/12378)]+* sdk/database: Fix a DeleteUser error message on the gRPC client. [[GH-12351](https://github.com/hashicorp/vault/pull/12351)]+* secrets/gcp: Fixes a potential panic in the service account policy rollback for rolesets. [[GH-12379](https://github.com/hashicorp/vault/pull/12379)]+* ui: Fixed api explorer routing bug [[GH-12354](https://github.com/hashicorp/vault/pull/12354)]+* ui: Fixes metrics page when read on counter config not allowed [[GH-12348](https://github.com/hashicorp/vault/pull/12348)]+* ui: fix issue where on MaskedInput on auth methods if tab it would clear the value. [[GH-12409](https://github.com/hashicorp/vault/pull/12409)]+## 1.8.1### August 5th, 2021tokens (#12468)changelog/12339.txt | 3 +++vault/auth.go | 10 ++++++----vault/quotas/quotas.go | 5 +++--3 files changed, 12 insertions(+), 6 deletions(-)create mode 100644 changelog/12339.txt
vault/diagnose/os_common.go+4 −3
@@ -7,6 +7,7 @@ import ("fmt""strings"+"github.com/dustin/go-humanize""github.com/shirou/gopsutil/disk")@@ -31,10 +32,10 @@ partLoop:Warn(ctx, fmt.Sprintf("Could not obtain partition usage for %s: %v.", partition.Mountpoint, err))} else {if usage.UsedPercent > 95 {-SpotWarn(ctx, testName, fmt.Sprintf(partition.Mountpoint+" is %d percent full.", usage.UsedPercent),+SpotWarn(ctx, testName, fmt.Sprintf(partition.Mountpoint+" is %.2f percent full.", usage.UsedPercent),Advice("It is recommended to have more than five percent of the partition free."))-} else if usage.Free < 2<<30 {-SpotWarn(ctx, testName, partition.Mountpoint+" has %d bytes full.",+} else if usage.Free < 1<<30 {+SpotWarn(ctx, testName, fmt.Sprintf(partition.Mountpoint+" has %s free.", humanize.Bytes(usage.Free)),Advice("It is recommended to have at least 1 GB of space free per partition."))} else {SpotOk(ctx, testName, partition.Mountpoint+" usage ok.")when config is specified (#12235)changelog/_2071.txt | 3 +++command/operator_diagnose.go | 2 +-vault/core_util.go | 2 +-3 files changed, 5 insertions(+), 2 deletions(-)create mode 100644 changelog/_2071.txt
sdk/database/dbplugin/v5/grpc_client.go+1 −1
@@ -200,7 +200,7 @@ func (c gRPCClient) DeleteUser(ctx context.Context, req DeleteUserRequest) (Deleif c.doneCtx.Err() != nil {return DeleteUserResponse{}, ErrPluginShutdown}-return DeleteUserResponse{}, fmt.Errorf("unable to update user: %w", err)+return DeleteUserResponse{}, fmt.Errorf("unable to delete user: %w", err)}return deleteUserRespFromProto(rpcResp)1.16+arm64. (#12371) (#12375)api/go.sum | 1 +changelog/12371.txt | 3 +++go.sum | 2 ++sdk/go.mod | 2 +-sdk/go.sum | 2 ++5 files changed, 9 insertions(+), 1 deletion(-)create mode 100644 changelog/12371.txt
builtin/credential/aws/backend_test.go+17 −0
@@ -1129,6 +1129,22 @@ func TestBackendAcc_LoginWithInstanceIdentityDocAndAccessListIdentity(t *testing}}+// Configure additional metadata to be returned for ec2 logins.+identity := map[string]interface{}{+"ec2_metadata": []string{"instance_id", "region", "ami_id"},+}++// store the identity+_, err = b.HandleRequest(context.Background(), &logical.Request{+Operation: logical.UpdateOperation,+Storage: storage,+Path: "config/identity",+Data: identity,+})+if err != nil {+t.Fatal(err)+}+loginInput := map[string]interface{}{"pkcs7": pkcs7,"nonce": "vault-client-nonce",@@ -1241,6 +1257,7 @@ func TestBackendAcc_LoginWithInstanceIdentityDocAndAccessListIdentity(t *testingdelete(loginInput, "pkcs7")loginInput["identity"] = identityDocloginInput["signature"] = identityDocSig+resp, err = b.HandleRequest(context.Background(), loginRequest)if err != nil {t.Fatal(err)
vault/identity_store_oidc_test.go+35 −0
@@ -113,6 +113,41 @@ func TestOIDC_Path_OIDCRoleRole(t *testing.T) {}}+// TestOIDC_Path_OIDCRole_NoKey tests that a role can be created with a non-existent key+func TestOIDC_Path_OIDCRole_NoKey(t *testing.T) {+c, _, _ := TestCoreUnsealed(t)+ctx := namespace.RootContext(nil)+storage := &logical.InmemStorage{}++// Create a test role "test-role1" with a non-existent key -- should succeed+resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{+Path: "oidc/role/test-role1",+Operation: logical.CreateOperation,+Data: map[string]interface{}{+"key": "test-key",+},+Storage: storage,+})+expectSuccess(t, resp, err)++// Read "test-role1" and validate+resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{+Path: "oidc/role/test-role1",+Operation: logical.ReadOperation,+Storage: storage,+})+expectSuccess(t, resp, err)+expected := map[string]interface{}{+"key": "test-key",+"ttl": int64(86400),+"template": "",+"client_id": resp.Data["client_id"],+}+if diff := deep.Equal(expected, resp.Data); diff != nil {+t.Fatal(diff)+}+}+// TestOIDC_Path_OIDCRole_InvalidTokenTTL tests the TokenTTL validationfunc TestOIDC_Path_OIDCRole_InvalidTokenTTL(t *testing.T) {c, _, _ := TestCoreUnsealed(t)changelog/12258.txt | 3 +++go.mod | 2 +-go.sum | 4 ++--3 files changed, 6 insertions(+), 3 deletions(-)create mode 100644 changelog/12258.txt
More files changed — see the full commit.