99import java .util .LinkedList ;
1010import java .util .List ;
1111
12+ import ee .cyber .cdoc2 .container .recipients .PBKDF2Recipient ;
1213import org .slf4j .Logger ;
1314import org .slf4j .LoggerFactory ;
1415
2324import ee .cyber .cdoc2 .crypto .SymmetricKeyTools ;
2425import ee .cyber .cdoc2 .util .PasswordValidationUtil ;
2526
27+ import javax .annotation .Nullable ;
28+
2629
2730/**
2831 * Symmetric key usage in CDOC is supported by CDOC format, but its use cases are not finalized.
@@ -38,13 +41,15 @@ public final class SymmetricKeyUtil {
3841 private static final String SYMMETRIC_KEY_DESCRIPTION = "symmetric key with label. "
3942 + "Must have format" ;
4043
44+ private static final String PW_DESCRIPTION = "password with label. Must have format" ;
45+
4146 // --secret format description, used in cdoc <cmd> classes
4247 public static final String SECRET_DESCRIPTION = SYMMETRIC_KEY_DESCRIPTION
4348 + " <label>:<secret>. <secret> is a base64 encoded binary. "
4449 + "It must be prefixed with `base64,`" ;
4550
4651 // --password format description, used in cdoc <cmd> classes
47- public static final String PASSWORD_DESCRIPTION = SYMMETRIC_KEY_DESCRIPTION
52+ public static final String PASSWORD_DESCRIPTION = PW_DESCRIPTION
4853 + " <label>:<password>. <password> can be plain text or base64 "
4954 + "encoded binary. In case of base64, it must be prefixed with `base64,`" ;
5055
@@ -140,23 +145,64 @@ public static FormattedOptionParts splitFormattedOption(
140145 * aejUgxxSQXqiiyrxSGACfMiIRBZq5KjlCwr/xVNY/B0="
141146 * @param formattedPassword formatted as label:password where 2nd param can be base64 encoded
142147 * bytes or regular utf-8 string. Base64 encoded string must be
143- * prefixed with 'base64,', followed by base64 string
148+ * prefixed with 'base64,', followed by base64 string. If "",
149+ * then password and label are asked interactively.
150+ * @param verifyPw if password should be asked twice to verify that they match (encryption).
144151 * @return FormattedOptionParts with extracted password and label
145152 */
146- public static FormattedOptionParts getSplitPasswordAndLabel (String formattedPassword )
153+ public static FormattedOptionParts getSplitPasswordAndLabel (String formattedPassword ,
154+ boolean verifyPw )
147155 throws CDocValidationException {
148156 FormattedOptionParts passwordAndLabel ;
149157 if (formattedPassword .isEmpty ()) {
150- passwordAndLabel = InteractiveCommunicationUtil .readPasswordAndLabelInteractively ();
158+ passwordAndLabel = InteractiveCommunicationUtil .readPasswordAndLabelInteractively (verifyPw );
151159 } else {
152160 passwordAndLabel
153161 = splitFormattedOption (formattedPassword , EncryptionKeyOrigin .PASSWORD );
154- PasswordValidationUtil .validatePassword (passwordAndLabel .optionChars ());
162+
163+ if (verifyPw ) {
164+ PasswordValidationUtil .validatePassword (passwordAndLabel .optionChars ());
165+ }
155166 }
156167
157168 return passwordAndLabel ;
158169 }
159170
171+ /**
172+ * Get password for decryption. Ignore user-entered label when there is only one pbkdf (password) recipient
173+ * in CDOC2 header.
174+ * @param formattedPassword formatted as label:password where
175+ * 2nd param can be base64 encoded bytes or regular utf-8 string.
176+ * Base64 encoded string must be
177+ * prefixed with 'base64,', followed by base64 string. If "",
178+ * then password and label are asked interactively.
179+ * @param pbkdf2Recipients if pbkdf2Recipients.size() == 1 then label is read from CDOC header
180+ * @return
181+ * @throws CDocValidationException
182+ */
183+ public static FormattedOptionParts getSplitPasswordAndLabel (String formattedPassword ,
184+ List <PBKDF2Recipient > pbkdf2Recipients )
185+ throws CDocValidationException {
186+
187+ if (pbkdf2Recipients .size () == 1 ) {
188+ String label = pbkdf2Recipients .get (0 ).getRecipientKeyLabel ();
189+ if (formattedPassword .isEmpty ()) {
190+ char [] pw = InteractiveCommunicationUtil .readPasswordInteractively (System .console (),
191+ InteractiveCommunicationUtil .PROMPT_PASSWORD );
192+
193+ return new FormattedOptionParts (pw , label , EncryptionKeyOrigin .PASSWORD );
194+ } else {
195+ FormattedOptionParts parsed =
196+ splitFormattedOption (formattedPassword , EncryptionKeyOrigin .PASSWORD );
197+ //overwrite label with recipient label to allow entering password without label for
198+ //single pbkdfRecipient eg -pw ":pw"
199+ return new FormattedOptionParts (parsed .optionChars (), label , EncryptionKeyOrigin .PASSWORD );
200+ }
201+ } else {
202+ return getSplitPasswordAndLabel (formattedPassword , false );
203+ }
204+ }
205+
160206 /**
161207 * Extract decryption key material from formatted secret or password "label:topsecret123!"
162208 * or "label123:base64,aejUgxxSQXqiiyrxSGACfMiIRBZq5KjlCwr/xVNY/B0="
@@ -174,20 +220,25 @@ public static FormattedOptionParts getSplitPasswordAndLabel(String formattedPass
174220 * @throws IOException if header parsing has failed
175221 * @throws CDocParseException if recipients deserializing has failed
176222 */
177- public static DecryptionKeyMaterial extractDecryptionKeyMaterialFromSymmetricKey (
223+ public static DecryptionKeyMaterial extractDecryptionKeyMaterial (
178224 Path cDocFilePath ,
179- String formattedPassword ,
180- String formattedSecret
225+ @ Nullable String formattedPassword ,
226+ @ Nullable String formattedSecret
181227 ) throws CDocValidationException ,
182228 GeneralSecurityException ,
183229 IOException ,
184230 CDocParseException {
185231
186232 List <Recipient > recipients = Envelope .parseHeader (Files .newInputStream (cDocFilePath ));
187233
234+
188235 DecryptionKeyMaterial decryptionKm = null ;
189236 if (null != formattedPassword ) {
190- FormattedOptionParts splitPassword = getSplitPasswordAndLabel (formattedPassword );
237+ List <PBKDF2Recipient > pbkdf2Recipients = recipients .stream ()
238+ .filter (recipient -> recipient instanceof PBKDF2Recipient )
239+ .map (r -> (PBKDF2Recipient )r ).toList ();
240+
241+ FormattedOptionParts splitPassword = getSplitPasswordAndLabel (formattedPassword , pbkdf2Recipients );
191242 decryptionKm =
192243 SymmetricKeyTools .extractDecryptionKeyMaterialFromPassword (splitPassword , recipients );
193244 }
0 commit comments