diff --git a/builtin/logical/pki/acme_jws.go b/builtin/logical/pki/acme_jws.go index 3ad5e99846..d7207b7fcc 100644 --- a/builtin/logical/pki/acme_jws.go +++ b/builtin/logical/pki/acme_jws.go @@ -190,7 +190,10 @@ func verifyEabPayload(acmeState *acmeState, ac *acmeContext, outer *jwsCtx, expe if !ok { return nil, fmt.Errorf("missing required field 'protected': %w", ErrMalformed) } - jwkBase64 := rawProtectedBase64.(string) + jwkBase64, ok := rawProtectedBase64.(string) + if !ok { + return nil, fmt.Errorf("failed to parse 'protected' field: %w", ErrMalformed) + } jwkBytes, err := base64.RawURLEncoding.DecodeString(jwkBase64) if err != nil { diff --git a/builtin/logical/pki/acme_state_test.go b/builtin/logical/pki/acme_state_test.go index 6f93503157..e940bc7565 100644 --- a/builtin/logical/pki/acme_state_test.go +++ b/builtin/logical/pki/acme_state_test.go @@ -43,6 +43,103 @@ func TestAcmeNonces(t *testing.T) { } } +// TestVerifyEabPayloadProtectedFieldValidation verifies that the verifyEabPayload function +// properly validates and rejects malformed 'protected' fields in ACME External Account Binding +// (EAB) payloads. It ensures that only string values are accepted and that various non-string +// types (object, array, number, boolean) produce appropriate error messages. +func TestVerifyEabPayloadProtectedFieldValidation(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + payload map[string]interface{} + expectError bool + expectMessage string + }{ + { + name: "valid string protected field", + payload: map[string]interface{}{ + "protected": "eyJhbGciOiAiSFMyNTYifQ", + }, + expectError: false, + }, + { + name: "missing protected field", + payload: map[string]interface{}{ + "payload": "test", + "signature": "test", + }, + expectError: true, + expectMessage: "missing required field 'protected'", + }, + { + name: "protected field as object", + payload: map[string]interface{}{ + "protected": map[string]interface{}{ // should be a string, not an object + "alg": "HS256", + }, + "payload": "test", + "signature": "test", + }, + expectError: true, + expectMessage: "failed to parse 'protected' field", + }, + { + name: "protected field as array", + payload: map[string]interface{}{ + "protected": []interface{}{"test"}, // should be a string, not an array + "payload": "test", + "signature": "test", + }, + expectError: true, + expectMessage: "failed to parse 'protected' field", + }, + { + name: "protected field as number", + payload: map[string]interface{}{ + "protected": 12345, // should be a string, not a number + "payload": "test", + "signature": "test", + }, + expectError: true, + expectMessage: "failed to parse 'protected' field", + }, + { + name: "protected field as boolean", + payload: map[string]interface{}{ + "protected": true, // should be a string, not a boolean + "payload": "test", + "signature": "test", + }, + expectError: true, + expectMessage: "failed to parse 'protected' field", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + acmeState := NewACMEState() + acmeCtx := &acmeContext{} + outerJws := &jwsCtx{} + + _, err := verifyEabPayload(acmeState, acmeCtx, outerJws, "/new-account", tc.payload) + + if tc.expectError { + require.Error(t, err, "expected error but got none") + require.Contains(t, err.Error(), tc.expectMessage, "error message does not match") + require.NotEmpty(t, err.Error()) + } else { + // For valid protected field, we expect an error later in the flow + // (since we don't have real base64 data), but it should NOT be from parsing the protected field + if err != nil { + require.NotContains(t, err.Error(), "failed to parse 'protected' field", + "should not fail at protected field parsing stage") + } + } + }) + } +} + // TestErrorResponseNoSubproblems builds the http body that exists in the header of an ACME error response and checks // in a simple case that "type" and "detail" two fields on the body do exist, but that "subproblems" a field which is // optional, is omitted because it does not exist in this case (rather than being included with a value null which can diff --git a/changelog/_12260.txt b/changelog/_12260.txt new file mode 100644 index 0000000000..b0173b670c --- /dev/null +++ b/changelog/_12260.txt @@ -0,0 +1,3 @@ +```release-note:bug +secrets/pki: fix panic during acme account creation with malformed protected field. +``