001// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
002//
003// SPDX-License-Identifier: Apache-2.0
004
005package org.pgpainless.signature.subpackets;
006
007import java.util.ArrayList;
008import java.util.Arrays;
009import java.util.Date;
010import java.util.LinkedHashSet;
011import java.util.List;
012import java.util.Set;
013
014import javax.annotation.Nonnull;
015import javax.annotation.Nullable;
016
017import org.bouncycastle.bcpg.sig.Exportable;
018import org.bouncycastle.bcpg.sig.Features;
019import org.bouncycastle.bcpg.sig.IntendedRecipientFingerprint;
020import org.bouncycastle.bcpg.sig.IssuerFingerprint;
021import org.bouncycastle.bcpg.sig.IssuerKeyID;
022import org.bouncycastle.bcpg.sig.KeyExpirationTime;
023import org.bouncycastle.bcpg.sig.KeyFlags;
024import org.bouncycastle.bcpg.sig.NotationData;
025import org.bouncycastle.bcpg.sig.PreferredAlgorithms;
026import org.bouncycastle.bcpg.sig.PrimaryUserID;
027import org.bouncycastle.bcpg.sig.Revocable;
028import org.bouncycastle.bcpg.sig.RevocationKey;
029import org.bouncycastle.bcpg.sig.RevocationReason;
030import org.bouncycastle.bcpg.sig.SignatureCreationTime;
031import org.bouncycastle.bcpg.sig.SignatureExpirationTime;
032import org.bouncycastle.bcpg.sig.SignatureTarget;
033import org.bouncycastle.bcpg.sig.SignerUserID;
034import org.bouncycastle.bcpg.sig.TrustSignature;
035import org.bouncycastle.openpgp.PGPException;
036import org.bouncycastle.openpgp.PGPPublicKey;
037import org.bouncycastle.openpgp.PGPSignature;
038import org.bouncycastle.openpgp.PGPSignatureList;
039import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
040import org.bouncycastle.util.encoders.Hex;
041import org.pgpainless.algorithm.CompressionAlgorithm;
042import org.pgpainless.algorithm.Feature;
043import org.pgpainless.algorithm.HashAlgorithm;
044import org.pgpainless.algorithm.KeyFlag;
045import org.pgpainless.algorithm.PublicKeyAlgorithm;
046import org.pgpainless.algorithm.SignatureSubpacket;
047import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
048import org.pgpainless.key.OpenPgpFingerprint;
049import org.pgpainless.key.OpenPgpV4Fingerprint;
050import org.pgpainless.key.generation.type.KeyType;
051import org.pgpainless.signature.SignatureUtils;
052
053/**
054 * Utility class to access signature subpackets from signatures.
055 *
056 * Since rfc4880 is not always clear about where a signature subpacket can be located (hashed/unhashed area),
057 * this class makes some educated guesses as to where the subpacket may be found when necessary.
058 */
059public final class SignatureSubpacketsUtil {
060
061    private SignatureSubpacketsUtil() {
062
063    }
064
065    /**
066     * Return the issuer-fingerprint subpacket of the signature.
067     * Since this packet is self-authenticating, we expect it to be in the unhashed area,
068     * however as it cannot hurt we search for it in the hashed area first.
069     *
070     * @param signature signature
071     * @return issuer fingerprint or null
072     */
073    public static IssuerFingerprint getIssuerFingerprint(PGPSignature signature) {
074        return hashedOrUnhashed(signature, SignatureSubpacket.issuerFingerprint);
075    }
076
077    /**
078     * Return the {@link IssuerFingerprint} subpacket of the signature into a {@link org.pgpainless.key.OpenPgpFingerprint}.
079     * If no v4 issuer fingerprint is present in the signature, return null.
080     *
081     * @param signature signature
082     * @return v4 fingerprint of the issuer, or null
083     */
084    public static OpenPgpFingerprint getIssuerFingerprintAsOpenPgpFingerprint(PGPSignature signature) {
085        IssuerFingerprint subpacket = getIssuerFingerprint(signature);
086        if (subpacket == null) {
087            return null;
088        }
089
090        OpenPgpFingerprint fingerprint = null;
091        if (subpacket.getKeyVersion() == 4) {
092            fingerprint = new OpenPgpV4Fingerprint(Hex.encode(subpacket.getFingerprint()));
093        }
094
095        return fingerprint;
096    }
097
098    /**
099     * Return the issuer key-id subpacket of the signature.
100     * Since this packet is self-authenticating, we expect it to be in the unhashed area,
101     * however as it cannot hurt we search for it in the hashed area first.
102     *
103     * @param signature signature
104     * @return issuer key-id or null
105     */
106    public static IssuerKeyID getIssuerKeyId(PGPSignature signature) {
107        return hashedOrUnhashed(signature, SignatureSubpacket.issuerKeyId);
108    }
109
110    /**
111     * Inspect the given signature's {@link IssuerKeyID} packet to determine the issuer key-id.
112     * If no such packet is present, return null.
113     *
114     * @param signature signature
115     * @return issuer key-id as {@link Long}
116     */
117    public static Long getIssuerKeyIdAsLong(PGPSignature signature) {
118        IssuerKeyID keyID = getIssuerKeyId(signature);
119        if (keyID == null) {
120            return null;
121        }
122        return keyID.getKeyID();
123    }
124
125    /**
126     * Return the revocation reason subpacket of the signature.
127     * Since this packet is rather important for revocations, we only search for it in the
128     * hashed area of the signature.
129     *
130     * @param signature signature
131     * @return revocation reason
132     */
133    public static RevocationReason getRevocationReason(PGPSignature signature) {
134        return hashed(signature, SignatureSubpacket.revocationReason);
135    }
136
137    /**
138     * Return the signature creation time subpacket.
139     * Since this packet is rather important, we only search for it in the hashed area
140     * of the signature.
141     *
142     * @param signature signature
143     * @return signature creation time subpacket
144     */
145    public static SignatureCreationTime getSignatureCreationTime(PGPSignature signature) {
146        return hashed(signature, SignatureSubpacket.signatureCreationTime);
147    }
148
149    /**
150     * Return the signature expiration time subpacket of the signature.
151     * Since this packet is rather important, we only search for it in the hashed area of the signature.
152     *
153     * @param signature signature
154     * @return signature expiration time
155     */
156    public static SignatureExpirationTime getSignatureExpirationTime(PGPSignature signature) {
157        return hashed(signature, SignatureSubpacket.signatureExpirationTime);
158    }
159
160    /**
161     * Return the signatures' expiration time as a date.
162     * The expiration date is computed by adding the expiration time to the signature creation date.
163     * If the signature has no expiration time subpacket, or the expiration time is set to '0', this message returns null.
164     *
165     * @param signature signature
166     * @return expiration time as date
167     */
168    public static Date getSignatureExpirationTimeAsDate(PGPSignature signature) {
169        SignatureExpirationTime subpacket = getSignatureExpirationTime(signature);
170        if (subpacket == null) {
171            return null;
172        }
173        return SignatureUtils.datePlusSeconds(signature.getCreationTime(), subpacket.getTime());
174    }
175
176    /**
177     * Return the key expiration time subpacket of this signature.
178     * We only look for it in the hashed area of the signature.
179     *
180     * @param signature signature
181     * @return key expiration time
182     */
183    public static KeyExpirationTime getKeyExpirationTime(PGPSignature signature) {
184        return hashed(signature, SignatureSubpacket.keyExpirationTime);
185    }
186
187    /**
188     * Return the signatures key-expiration time as a date.
189     * The expiration date is computed by adding the signatures' key-expiration time to the signing keys
190     * creation date.
191     * If the signature does not have a key-expiration time subpacket, or its value is '0', this method returns null.
192     *
193     * @param signature self-signature carrying the key-expiration time subpacket
194     * @param signingKey signature creation key
195     * @return key expiration time as date
196     */
197    public static Date getKeyExpirationTimeAsDate(PGPSignature signature, PGPPublicKey signingKey) {
198        if (signature.getKeyID() != signingKey.getKeyID()) {
199            throw new IllegalArgumentException("Provided key (" + Long.toHexString(signingKey.getKeyID()) + ") did not create the signature (" + Long.toHexString(signature.getKeyID()) + ")");
200        }
201        KeyExpirationTime subpacket = getKeyExpirationTime(signature);
202        if (subpacket == null) {
203            return null;
204        }
205
206        return SignatureUtils.datePlusSeconds(signingKey.getCreationTime(), subpacket.getTime());
207    }
208
209    /**
210     * Calculate the duration in seconds until the key expires after creation.
211     *
212     * @param expirationDate new expiration date
213     * @param creationDate key creation time
214     * @return lifetime of the key in seconds
215     */
216    public static long getKeyLifetimeInSeconds(@Nullable Date expirationDate, @Nonnull Date creationDate) {
217        long secondsToExpire = 0; // 0 means "no expiration"
218        if (expirationDate != null) {
219            if (creationDate.after(expirationDate)) {
220                throw new IllegalArgumentException("Key MUST NOT expire before being created. " +
221                        "(creation: " + creationDate + ", expiration: " + expirationDate + ")");
222            }
223            secondsToExpire = (expirationDate.getTime() - creationDate.getTime()) / 1000;
224        }
225        return secondsToExpire;
226    }
227
228    /**
229     * Return the revocable subpacket of this signature.
230     * We only look for it in the hashed area of the signature.
231     *
232     * @param signature signature
233     * @return revocable subpacket
234     */
235    public static Revocable getRevocable(PGPSignature signature) {
236        return hashed(signature, SignatureSubpacket.revocable);
237    }
238
239    /**
240     * Return the symmetric algorithm preferences from the signatures hashed area.
241     *
242     * @param signature signature
243     * @return symm. algo. prefs
244     */
245    public static PreferredAlgorithms getPreferredSymmetricAlgorithms(PGPSignature signature) {
246        return hashed(signature, SignatureSubpacket.preferredSymmetricAlgorithms);
247    }
248
249    /**
250     * Return the preferred {@link SymmetricKeyAlgorithm SymmetricKeyAlgorithms} as present in the signature.
251     * If no preference is given with regard to symmetric encryption algorithms, return an empty set.
252     *
253     * In any case, the resulting set is ordered by occurrence ({@link LinkedHashSet}).
254     * @param signature signature
255     * @return ordered set of symmetric key algorithm preferences
256     */
257    public static Set<SymmetricKeyAlgorithm> parsePreferredSymmetricKeyAlgorithms(PGPSignature signature) {
258        Set<SymmetricKeyAlgorithm> algorithms = new LinkedHashSet<>();
259        PreferredAlgorithms preferences = getPreferredSymmetricAlgorithms(signature);
260        if (preferences != null) {
261            for (int code : preferences.getPreferences()) {
262                algorithms.add(SymmetricKeyAlgorithm.fromId(code));
263            }
264        }
265        return algorithms;
266    }
267
268    /**
269     * Return the hash algorithm preferences from the signatures hashed area.
270     *
271     * @param signature signature
272     * @return hash algo prefs
273     */
274    public static PreferredAlgorithms getPreferredHashAlgorithms(PGPSignature signature) {
275        return hashed(signature, SignatureSubpacket.preferredHashAlgorithms);
276    }
277
278    /**
279     * Return the preferred {@link HashAlgorithm HashAlgorithms} as present in the signature.
280     * If no preference is given with regard to hash algorithms, return an empty set.
281     *
282     * In any case, the resulting set is ordered by occurrence ({@link LinkedHashSet}).
283     * @param signature signature
284     * @return ordered set of hash algorithm preferences
285     */
286    public static Set<HashAlgorithm> parsePreferredHashAlgorithms(PGPSignature signature) {
287        Set<HashAlgorithm> algorithms = new LinkedHashSet<>();
288        PreferredAlgorithms preferences = getPreferredHashAlgorithms(signature);
289        if (preferences != null) {
290            for (int code : preferences.getPreferences()) {
291                algorithms.add(HashAlgorithm.fromId(code));
292            }
293        }
294        return algorithms;
295    }
296
297    /**
298     * Return the compression algorithm preferences from the signatures hashed area.
299     *
300     * @param signature signature
301     * @return compression algo prefs
302     */
303    public static PreferredAlgorithms getPreferredCompressionAlgorithms(PGPSignature signature) {
304        return hashed(signature, SignatureSubpacket.preferredCompressionAlgorithms);
305    }
306
307    /**
308     * Return the preferred {@link CompressionAlgorithm CompressionAlgorithms} as present in the signature.
309     * If no preference is given with regard to compression algorithms, return an empty set.
310     *
311     * In any case, the resulting set is ordered by occurrence ({@link LinkedHashSet}).
312     * @param signature signature
313     * @return ordered set of compression algorithm preferences
314     */
315    public static Set<CompressionAlgorithm> parsePreferredCompressionAlgorithms(PGPSignature signature) {
316        Set<CompressionAlgorithm> algorithms = new LinkedHashSet<>();
317        PreferredAlgorithms preferences = getPreferredCompressionAlgorithms(signature);
318        if (preferences != null) {
319            for (int code : preferences.getPreferences()) {
320                algorithms.add(CompressionAlgorithm.fromId(code));
321            }
322        }
323        return algorithms;
324    }
325
326    /**
327     * Return the primary user-id subpacket from the signatures hashed area.
328     *
329     * @param signature signature
330     * @return primary user id
331     */
332    public static PrimaryUserID getPrimaryUserId(PGPSignature signature) {
333        return hashed(signature, SignatureSubpacket.primaryUserId);
334    }
335
336    /**
337     * Return the key flags subpacket from the signatures hashed area.
338     *
339     * @param signature signature
340     * @return key flags
341     */
342    public static KeyFlags getKeyFlags(PGPSignature signature) {
343        return hashed(signature, SignatureSubpacket.keyFlags);
344    }
345
346    /**
347     * Return a list of key flags carried by the signature.
348     * If the signature is null, or has no {@link KeyFlags} subpacket, return null.
349     *
350     * @param signature signature
351     * @return list of key flags
352     */
353    public static List<KeyFlag> parseKeyFlags(@Nullable PGPSignature signature) {
354        if (signature == null) {
355            return null;
356        }
357        KeyFlags keyFlags = getKeyFlags(signature);
358        if (keyFlags == null) {
359            return null;
360        }
361        return KeyFlag.fromBitmask(keyFlags.getFlags());
362    }
363
364    /**
365     * Return the features subpacket from the signatures hashed area.
366     *
367     * @param signature signature
368     * @return features subpacket
369     */
370    public static Features getFeatures(PGPSignature signature) {
371        return hashed(signature, SignatureSubpacket.features);
372    }
373
374    /**
375     * Parse out the features subpacket of a signature.
376     * If the signature has no features subpacket, return null.
377     * Otherwise, return the features as a feature set.
378     *
379     * @param signature signature
380     * @return features as set
381     */
382    public static @Nullable Set<Feature> parseFeatures(PGPSignature signature) {
383        Features features = getFeatures(signature);
384        if (features == null) {
385            return null;
386        }
387        return new LinkedHashSet<>(Feature.fromBitmask(features.getData()[0]));
388    }
389
390    /**
391     * Return the signature target subpacket from the signature.
392     * We search for this subpacket in the hashed and unhashed area (in this order).
393     *
394     * @param signature signature
395     * @return signature target
396     */
397    public static SignatureTarget getSignatureTarget(PGPSignature signature) {
398        return hashedOrUnhashed(signature, SignatureSubpacket.signatureTarget);
399    }
400
401    /**
402     * Return the notation data subpackets from the signatures hashed area.
403     *
404     * @param signature signature
405     * @return hashed notations
406     */
407    public static List<NotationData> getHashedNotationData(PGPSignature signature) {
408        NotationData[] notations = signature.getHashedSubPackets().getNotationDataOccurrences();
409        return Arrays.asList(notations);
410    }
411
412    /**
413     * Return a list of all {@link NotationData} objects from the hashed area of the signature that have a
414     * notation name equal to the given notationName argument.
415     *
416     * @param signature signature
417     * @param notationName notation name
418     * @return list of matching notation data objects
419     */
420    public static List<NotationData> getHashedNotationData(PGPSignature signature, String notationName) {
421        List<NotationData> allNotations = getHashedNotationData(signature);
422        List<NotationData> withName = new ArrayList<>();
423        for (NotationData data : allNotations) {
424            if (data.getNotationName().equals(notationName)) {
425                withName.add(data);
426            }
427        }
428        return withName;
429    }
430
431    /**
432     * Return the notation data subpackets from the signatures unhashed area.
433     *
434     * @param signature signture
435     * @return unhashed notations
436     */
437    public static List<NotationData> getUnhashedNotationData(PGPSignature signature) {
438        NotationData[] notations = signature.getUnhashedSubPackets().getNotationDataOccurrences();
439        return Arrays.asList(notations);
440    }
441
442    /**
443     * Return a list of all {@link NotationData} objects from the unhashed area of the signature that have a
444     * notation name equal to the given notationName argument.
445     *
446     * @param signature signature
447     * @param notationName notation name
448     * @return list of matching notation data objects
449     */
450    public static List<NotationData> getUnhashedNotationData(PGPSignature signature, String notationName) {
451        List<NotationData> allNotations = getUnhashedNotationData(signature);
452        List<NotationData> withName = new ArrayList<>();
453        for (NotationData data : allNotations) {
454            if (data.getNotationName().equals(notationName)) {
455                withName.add(data);
456            }
457        }
458        return withName;
459    }
460
461    /**
462     * Return the revocation key subpacket from the signatures hashed area.
463     *
464     * @param signature signature
465     * @return revocation key
466     */
467    public static RevocationKey getRevocationKey(PGPSignature signature) {
468        return hashed(signature, SignatureSubpacket.revocationKey);
469    }
470
471    /**
472     * Return the signers user-id from the hashed area of the signature.
473     * TODO: Can this subpacket also be found in the unhashed area?
474     *
475     * @param signature signature
476     * @return signers user-id
477     */
478    public static SignerUserID getSignerUserID(PGPSignature signature) {
479        return hashed(signature, SignatureSubpacket.signerUserId);
480    }
481
482    /**
483     * Return the intended recipients fingerprint subpackets from the hashed area of this signature.
484     *
485     * @param signature signature
486     * @return intended recipient fingerprint subpackets
487     */
488    public static List<IntendedRecipientFingerprint> getIntendedRecipientFingerprints(PGPSignature signature) {
489        org.bouncycastle.bcpg.SignatureSubpacket[] subpackets = signature.getHashedSubPackets().getSubpackets(SignatureSubpacket.intendedRecipientFingerprint.getCode());
490        List<IntendedRecipientFingerprint> intendedRecipients = new ArrayList<>(subpackets.length);
491        for (org.bouncycastle.bcpg.SignatureSubpacket subpacket : subpackets) {
492            intendedRecipients.add((IntendedRecipientFingerprint) subpacket);
493        }
494        return intendedRecipients;
495    }
496
497    /**
498     * Return the embedded signature subpacket from the signatures hashed area.
499     *
500     * @param signature signature
501     * @return embedded signature
502     */
503    public static PGPSignatureList getEmbeddedSignature(PGPSignature signature) throws PGPException {
504        PGPSignatureList hashed = signature.getHashedSubPackets().getEmbeddedSignatures();
505        if (!hashed.isEmpty()) {
506            return hashed;
507        }
508        return signature.getUnhashedSubPackets().getEmbeddedSignatures();
509    }
510
511    /**
512     * Return the signatures exportable certification subpacket from the hashed area.
513     *
514     * @param signature signature
515     * @return exportable certification subpacket
516     */
517    public static Exportable getExportableCertification(PGPSignature signature) {
518        return hashed(signature, SignatureSubpacket.exportableCertification);
519    }
520
521    /**
522     * Return the trust signature packet from the signatures hashed area.
523     *
524     * @param signature signature
525     * @return trust signature subpacket
526     */
527    public static TrustSignature getTrustSignature(PGPSignature signature) {
528        return hashed(signature, SignatureSubpacket.trustSignature);
529    }
530
531    /**
532     * Select a list of all signature subpackets of the given type, which are present in the hashed area of
533     * the given signature.
534     *
535     * @param signature signature
536     * @param type subpacket type
537     * @param <P> generic subpacket type
538     * @return list of subpackets from the hashed area
539     */
540    private static <P extends org.bouncycastle.bcpg.SignatureSubpacket> P hashed(PGPSignature signature, SignatureSubpacket type) {
541        return getSignatureSubpacket(signature.getHashedSubPackets(), type);
542    }
543
544    /**
545     * Select a list of all signature subpackets of the given type, which are present in the unhashed area of
546     * the given signature.
547     *
548     * @param signature signature
549     * @param type subpacket type
550     * @param <P> generic subpacket type
551     * @return list of subpackets from the unhashed area
552     */
553    private static <P extends org.bouncycastle.bcpg.SignatureSubpacket> P unhashed(PGPSignature signature, SignatureSubpacket type) {
554        return getSignatureSubpacket(signature.getUnhashedSubPackets(), type);
555    }
556
557    /**
558     * Select a list of all signature subpackets of the given type, which are present in either the hashed
559     * or the unhashed area of the given signature.
560     *
561     * @param signature signature
562     * @param type subpacket type
563     * @param <P> generic subpacket type
564     * @return list of subpackets from the hashed/unhashed area
565     */
566    private static <P extends org.bouncycastle.bcpg.SignatureSubpacket> P hashedOrUnhashed(PGPSignature signature, SignatureSubpacket type) {
567        P hashedSubpacket = hashed(signature, type);
568        return hashedSubpacket != null ? hashedSubpacket : unhashed(signature, type);
569    }
570
571    /**
572     * Return the last occurence of a subpacket type in the given signature subpacket vector.
573     *
574     * @param vector subpacket vector (hashed/unhashed)
575     * @param type subpacket type
576     * @param <P> generic return type of the subpacket
577     * @return last occurrence of the subpacket in the vector
578     */
579    public static <P extends org.bouncycastle.bcpg.SignatureSubpacket> P getSignatureSubpacket(PGPSignatureSubpacketVector vector, SignatureSubpacket type) {
580        org.bouncycastle.bcpg.SignatureSubpacket[] allPackets = vector.getSubpackets(type.getCode());
581        if (allPackets.length == 0) {
582            return null;
583        }
584        return (P) allPackets[allPackets.length - 1]; // return last
585    }
586
587    /**
588     * Make sure that the given key type can carry the given key flags.
589     *
590     * @param type key type
591     * @param flags key flags
592     */
593    public static void assureKeyCanCarryFlags(KeyType type, KeyFlag... flags) {
594        final int mask = KeyFlag.toBitmask(flags);
595
596        if (!type.canCertify() && KeyFlag.hasKeyFlag(mask, KeyFlag.CERTIFY_OTHER)) {
597            throw new IllegalArgumentException("KeyType " + type.getName() + " cannot carry key flag CERTIFY_OTHER.");
598        }
599
600        if (!type.canSign() && KeyFlag.hasKeyFlag(mask, KeyFlag.SIGN_DATA)) {
601            throw new IllegalArgumentException("KeyType " + type.getName() + " cannot carry key flag SIGN_DATA.");
602        }
603
604        if (!type.canEncryptCommunication() && KeyFlag.hasKeyFlag(mask, KeyFlag.ENCRYPT_COMMS)) {
605            throw new IllegalArgumentException("KeyType " + type.getName() + " cannot carry key flag ENCRYPT_COMMS.");
606        }
607
608        if (!type.canEncryptStorage() && KeyFlag.hasKeyFlag(mask, KeyFlag.ENCRYPT_STORAGE)) {
609            throw new IllegalArgumentException("KeyType " + type.getName() + " cannot carry key flag ENCRYPT_STORAGE.");
610        }
611
612        if (!type.canAuthenticate() && KeyFlag.hasKeyFlag(mask, KeyFlag.AUTHENTICATION)) {
613            throw new IllegalArgumentException("KeyType " + type.getName() + " cannot carry key flag AUTHENTICATION.");
614        }
615    }
616
617    public static void assureKeyCanCarryFlags(PublicKeyAlgorithm algorithm, KeyFlag... flags) {
618        final int mask = KeyFlag.toBitmask(flags);
619
620        if (!algorithm.isSigningCapable() && KeyFlag.hasKeyFlag(mask, KeyFlag.CERTIFY_OTHER)) {
621            throw new IllegalArgumentException("Algorithm " + algorithm + " cannot be used with key flag CERTIFY_OTHER.");
622        }
623
624        if (!algorithm.isSigningCapable() && KeyFlag.hasKeyFlag(mask, KeyFlag.SIGN_DATA)) {
625            throw new IllegalArgumentException("Algorithm " + algorithm + " cannot be used with key flag SIGN_DATA.");
626        }
627
628        if (!algorithm.isEncryptionCapable() && KeyFlag.hasKeyFlag(mask, KeyFlag.ENCRYPT_COMMS)) {
629            throw new IllegalArgumentException("Algorithm " + algorithm + " cannot be used with key flag ENCRYPT_COMMS.");
630        }
631
632        if (!algorithm.isEncryptionCapable() && KeyFlag.hasKeyFlag(mask, KeyFlag.ENCRYPT_STORAGE)) {
633            throw new IllegalArgumentException("Algorithm " + algorithm + " cannot be used with key flag ENCRYPT_STORAGE.");
634        }
635
636        if (!algorithm.isSigningCapable() && KeyFlag.hasKeyFlag(mask, KeyFlag.AUTHENTICATION)) {
637            throw new IllegalArgumentException("Algorithm " + algorithm + " cannot be used with key flag AUTHENTICATION.");
638        }
639    }
640}