Skip to content

fix(keymanager): parse key files strictly, warn on writable files, and pin the unpinned controls - #477

Merged
Jaro-c merged 3 commits into
developfrom
fix/keys-review-round-2
Sep 26, 2026
Merged

Jaro-c merged 3 commits into
developfrom
fix/keys-review-round-2

Conversation

@Jaro-c

@Jaro-c Jaro-c commented Sep 26, 2026

Copy link
Copy Markdown
Member

Closes #476 in three commits, each building and passing on its own.

keymanager

  • decodePEMBlock is the one parser behind both key files and FromPEM: the input must start with a PEM block, the block must be labelled PRIVATE KEY or PUBLIC KEY as expected, carry no headers, and be the only thing in the input apart from whitespace. The refresh-secret error no longer wraps the hex package's error, which quotes a byte of the file. A key file with text before or after its block, or with headers, is refused at startup now. Files written by authcore, by openssl genpkey/pkey -pubout, and by authcore-keygen are unaffected.
  • The permission warning covers write bits (mask 0o066), so a secret file others can replace is reported, and Load warns when KeysDir is group- or world-writable without the sticky bit (a Kubernetes Secret mount is 1777 and is not reported). New still tightens the directory instead.
  • A parent directory that cannot be opened for its fsync (search but no read permission) is a warning on the first run, as it already was on the load path; the test that pinned the refusal is rewritten to the new contract.
  • The two comments claiming exists makes callers "fail closed rather than generating" now say what it feeds.

KeyID: New refuses a KeyStore whose Keys report an empty KeyID, and jwt.New refuses a PreviousPublicKeys entry whose id equals the current key's, with ErrInvalidConfig, instead of letting it replace the current key in the verification set. The Keys.KeyID contract and docs/key-management.md say so. I did not require a custom store to reproduce the built-in derivation: it is an internal detail, and a store with its own stable ids has worked and keeps working.

docs: docs/faq.md no longer tells an operator to delete .authcore; docs/containers.md describes concurrent first start as measured today rather than as "being reworked".

Tests

  • mode_table_test.go: {New, Load} × {private key, refresh secret} × {0640, 0604, 0602, 0620, 0600, 0400}, exactly one warning naming the file for the four open modes and none otherwise; Load on a 0777 and 0733 directory warns, on 1777 (sticky), 0700 and 0750 does not, and the mode is left as found.
  • review_round2_internal_test.go: New on a 0555 directory leaves it at 0500 and 0750 at 0700; recovery with an unrelated staging directory that sorts first still completes from the one whose private key matches; seven non-canonical PEM inputs refused with the reason named and the canonical pair (with surrounding whitespace) accepted; the not-hex error names no byte of the file and a hex file of the same length loads.
  • TestNewWarnsWhenTheParentCannotBeSynced, TestNew_refusesAnEmptyKeyID (with a custom non-empty id accepted), TestNew_refusesAPreviousKeyWithTheCurrentKeyID (with a different key registered).

Sabotage, each edit confirmed to change the file:

Sabotage Result
drop the secret warning on New / the private-key warning on Load the mode table fails on those cells
mask group only / read only the 0604 and 0602/0620 cells fail
Chmod(dir, dirMode) instead of mode&dirMode the tighten test fails
accept any staging directory (|| true) the recovery test fails
drop the type, header, trailing-content or prefix check the matching PEM cases fail
wrap the hex error again the no-echo test fails
refuse the parent sync again / drop the directory warning their tests fail
accept an empty KeyID / a previous key with the current id their tests fail

gofmt, go vet ./..., go test -race -count=1 ./..., gosec and the nine example builds are clean. internal/keymanager is at 90.3%, the root package at 96.2%, auth/jwt at 94.5%. The keymanager figure sits close to the floor because the strict parser and the directory warning add branches; the untested ones are the Windows returns and the non-permission parent-sync failure, which has no fault seam.

Signed-off-by: Jaro-c 75870284+Jaro-c@users.noreply.github.com

… directories, and pin the mode, tighten and recovery controls

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
…e current key's id

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
…ent first start as it works

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
@Jaro-c Jaro-c added type:bug A defect to fix type:docs Documentation type:security Security-relevant change prio:P2 Medium priority status:review In review effort:M Medium area:keymanager Subsystem: keymanager area:docs Area: docs labels Sep 26, 2026
@Jaro-c
Jaro-c merged commit a731652 into develop Sep 26, 2026
19 checks passed
@Jaro-c
Jaro-c deleted the fix/keys-review-round-2 branch September 26, 2026 00:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:docs Area: docs area:keymanager Subsystem: keymanager effort:M Medium prio:P2 Medium priority status:review In review type:bug A defect to fix type:docs Documentation type:security Security-relevant change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant