Skip to content

Zeroize a copy of the card user PIN in the YubiKey decryptor factories, not the provider's array - #2444

Open
Arpan0995 wants to merge 1 commit into
bcgit:mainfrom
Arpan0995:pgsc-userpin-buffer-ownership
Open

Arpan0995 wants to merge 1 commit into
bcgit:mainfrom
Arpan0995:pgsc-userpin-buffer-ownership

Conversation

@Arpan0995

Copy link
Copy Markdown

BcYubikeyPublicKeyDataDecryptorFactory.requireUserPin and JceYubikeyPublicKeyDataDecryptorFactoryBuilder.requireUserPin return the char[] that KeyPassphraseProvider.getKeyPassword handed them, and each of the three private-key operations in each class clears it in a finally block. Both providers BC ships return the application's own array by reference: DefaultKeyPassphraseProvider returns the char[] it has cached for the key, and the anonymous provider in OpenPGPApi.editKey returns its passphrase argument. So the first card operation destroys the caller's PIN, and since the provider keeps handing back that same array, the next private-key operation presents an all-zero PIN, which a card rejects, spending a user PIN retry.

No other getKeyPassword consumer in pg/src/main zeroizes what it is given, and the simulator card in this module clears nothing; where BC clears a char[] after use it clears a copy it made itself, as JPAKEParticipant and ECJPAKEParticipant do, with OpenPGPKeyGenerator.build(char[]) the one exception. The existing YubiKey tests cannot see it, since SmartCardTestProperties.getUserPin() already returns Arrays.clone.

Reproduced against released 1.86 (no pgsc jar was published for it, so pgsc/src/main/java was compiled from the r1rv86 tag): a registered PIN of 123456 does not survive one operation on either factory, the JCE factory printing it back as six NUL characters. Both factory sources are byte-identical in the current 1.87-SNAPSHOT beta.

This change:

  • clones the provider's array in each requireUserPin, so the finally blocks clear a copy the factory owns, and records the borrowing convention in the getKeyPassword javadoc, where the provider contract lives; and
  • adds SmartCardUserPinOwnershipTest, registered in SimulatorTests, which builds each factory with a null card handle so the card call fails immediately while the PIN handling around it runs in full (the fetch before the session is opened, the clear in the finally block after it), asserting that the PIN was fetched and survives, and decrypts two messages in a row through the simulator against one cached PIN. Either factory case fails without the change and both pass with it; the assertions abort at the first failure, so reverting one clone at a time shows each in turn.

#2430 removes both files and moves requireUserPin into OpenPGPSmartCard unchanged, with YubikeyOpenPGPSmartCard still clearing the returned array in three finally blocks, one of them a signing path; the fix there is a single Arrays.clone if you would rather take it on that PR.

Base tree only, no new imports in the touched main sources and no module-info change. Happy to add a release-note entry under whichever version block you prefer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant