fix: panic on malformed protected field in verifyEabPayload (#12260) (#12365)

* fix: panic on malformed protected field in veryfyEabPayload

* clear comments

* missed a }

* add changelog

* Update changelog/12260.txt



* rename 12260.txt to _12260.txt

* address lint issue

---------

Co-authored-by: Deniz Onur Duzgun <59659739+dduzgun-security@users.noreply.github.com>
Co-authored-by: Steven Clark <steven.clark@hashicorp.com>
This commit is contained in:
Vault Automation 2026-02-16 12:02:21 -05:00 committed by GitHub
parent 64fdc0b877
commit daa03407c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 104 additions and 1 deletions

View file

@ -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 {

View file

@ -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

3
changelog/_12260.txt Normal file
View file

@ -0,0 +1,3 @@
```release-note:bug
secrets/pki: fix panic during acme account creation with malformed protected field.
``