001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2016, Connect2id Ltd.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.jose.jwk;
019
020
021import java.io.Serializable;
022import java.net.URI;
023import java.security.*;
024import java.security.cert.X509Certificate;
025import java.security.interfaces.ECPrivateKey;
026import java.security.interfaces.ECPublicKey;
027import java.security.interfaces.RSAPrivateKey;
028import java.security.interfaces.RSAPublicKey;
029import java.security.spec.ECParameterSpec;
030import java.text.ParseException;
031import java.util.*;
032
033import net.minidev.json.JSONArray;
034import net.minidev.json.JSONAware;
035import net.minidev.json.JSONObject;
036
037import com.nimbusds.jose.Algorithm;
038import com.nimbusds.jose.JOSEException;
039import com.nimbusds.jose.util.Base64;
040import com.nimbusds.jose.util.*;
041
042
043/**
044 * The base abstract class for JSON Web Keys (JWKs). It serialises to a JSON
045 * object.
046 *
047 * <p>The following JSON object members are common to all JWK types:
048 *
049 * <ul>
050 *     <li>{@link #getKeyType kty} (required)
051 *     <li>{@link #getKeyUse use} (optional)
052 *     <li>{@link #getKeyOperations key_ops} (optional)
053 *     <li>{@link #getKeyID kid} (optional)
054 *     <li>{@link #getX509CertURL()  x5u} (optional)
055 *     <li>{@link #getX509CertThumbprint()  x5t} (optional)
056 *     <li>{@link #getX509CertSHA256Thumbprint()  x5t#S256} (optional)
057 *     <li>{@link #getX509CertChain() x5c} (optional)
058 *     <li>{@link #getKeyStore()}
059 * </ul>
060 *
061 * <p>Example JWK (of the Elliptic Curve type):
062 *
063 * <pre>
064 * {
065 *   "kty" : "EC",
066 *   "crv" : "P-256",
067 *   "x"   : "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
068 *   "y"   : "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
069 *   "use" : "enc",
070 *   "kid" : "1"
071 * }
072 * </pre>
073 *
074 * @author Vladimir Dzhuvinov
075 * @author Justin Richer
076 * @author Stefan Larsson
077 * @version 2020-02-09
078 */
079public abstract class JWK implements JSONAware, Serializable {
080
081
082        private static final long serialVersionUID = 1L;
083
084
085        /**
086         * The MIME type of JWK objects: 
087         * {@code application/jwk+json; charset=UTF-8}
088         */
089        public static final String MIME_TYPE = "application/jwk+json; charset=UTF-8";
090
091
092        /**
093         * The key type, required.
094         */
095        private final KeyType kty;
096
097
098        /**
099         * The key use, optional.
100         */
101        private final KeyUse use;
102
103
104        /**
105         * The key operations, optional.
106         */
107        private final Set<KeyOperation> ops;
108
109
110        /**
111         * The intended JOSE algorithm for the key, optional.
112         */
113        private final Algorithm alg;
114
115
116        /**
117         * The key ID, optional.
118         */
119        private final String kid;
120
121
122        /**
123         * X.509 certificate URL, optional.
124         */
125        private final URI x5u;
126
127
128        /**
129         * X.509 certificate SHA-1 thumbprint, optional.
130         */
131        @Deprecated
132        private final Base64URL x5t;
133        
134        
135        /**
136         * X.509 certificate SHA-256 thumbprint, optional.
137         */
138        private Base64URL x5t256;
139
140
141        /**
142         * The X.509 certificate chain, optional.
143         */
144        private final List<Base64> x5c;
145        
146        
147        /**
148         * The parsed X.509 certificate chain, optional.
149         */
150        private final List<X509Certificate> parsedX5c;
151        
152        
153        /**
154         * Reference to the underlying key store, {@code null} if none.
155         */
156        private final KeyStore keyStore;
157
158
159        /**
160         * Creates a new JSON Web Key (JWK).
161         *
162         * @param kty    The key type. Must not be {@code null}.
163         * @param use    The key use, {@code null} if not specified or if the
164         *               key is intended for signing as well as encryption.
165         * @param ops    The key operations, {@code null} if not specified.
166         * @param alg    The intended JOSE algorithm for the key, {@code null}
167         *               if not specified.
168         * @param kid    The key ID, {@code null} if not specified.
169         * @param x5u    The X.509 certificate URL, {@code null} if not
170         *               specified.
171         * @param x5t    The X.509 certificate thumbprint, {@code null} if not
172         *               specified.
173         * @param x5t256 The X.509 certificate SHA-256 thumbprint, {@code null}
174         *               if not specified.
175         * @param x5c    The X.509 certificate chain, {@code null} if not
176         *               specified.
177         * @param ks     Reference to the underlying key store, {@code null} if
178         *               none.
179         */
180        protected JWK(final KeyType kty,
181                      final KeyUse use,
182                      final Set<KeyOperation> ops,
183                      final Algorithm alg,
184                      final String kid,
185                      final URI x5u,
186                      final Base64URL x5t,
187                      final Base64URL x5t256,
188                      final List<Base64> x5c,
189                      final KeyStore ks) {
190
191                if (kty == null) {
192                        throw new IllegalArgumentException("The key type \"kty\" parameter must not be null");
193                }
194
195                this.kty = kty;
196
197                if (! KeyUseAndOpsConsistency.areConsistent(use, ops)) {
198                        throw new IllegalArgumentException("The key use \"use\" and key options \"key_opts\" parameters are not consistent, " +
199                                "see RFC 7517, section 4.3");
200                }
201
202                this.use = use;
203                this.ops = ops;
204
205                this.alg = alg;
206                this.kid = kid;
207
208                this.x5u = x5u;
209                this.x5t = x5t;
210                this.x5t256 = x5t256;
211                
212                if (x5c != null && x5c.isEmpty()) {
213                        throw new IllegalArgumentException("The X.509 certificate chain \"x5c\" must not be empty");
214                }
215                this.x5c = x5c;
216                
217                try {
218                        parsedX5c = X509CertChainUtils.parse(x5c);
219                } catch (ParseException e) {
220                        throw new IllegalArgumentException("Invalid X.509 certificate chain \"x5c\": " + e.getMessage(), e);
221                }
222                
223                this.keyStore = ks;
224        }
225
226
227        /**
228         * Gets the type ({@code kty}) of this JWK.
229         *
230         * @return The key type.
231         */
232        public KeyType getKeyType() {
233
234                return kty;
235        }
236
237
238        /**
239         * Gets the use ({@code use}) of this JWK.
240         *
241         * @return The key use, {@code null} if not specified or if the key is
242         *         intended for signing as well as encryption.
243         */
244        public KeyUse getKeyUse() {
245
246                return use;
247        }
248
249
250        /**
251         * Gets the operations ({@code key_ops}) for this JWK.
252         *
253         * @return The key operations, {@code null} if not specified.
254         */
255        public Set<KeyOperation> getKeyOperations() {
256
257                return ops;
258        }
259
260
261        /**
262         * Gets the intended JOSE algorithm ({@code alg}) for this JWK.
263         *
264         * @return The intended JOSE algorithm, {@code null} if not specified.
265         */
266        public Algorithm getAlgorithm() {
267
268                return alg;
269        }
270
271
272        /**
273         * Gets the ID ({@code kid}) of this JWK. The key ID can be used to 
274         * match a specific key. This can be used, for instance, to choose a 
275         * key within a {@link JWKSet} during key rollover. The key ID may also 
276         * correspond to a JWS/JWE {@code kid} header parameter value.
277         *
278         * @return The key ID, {@code null} if not specified.
279         */
280        public String getKeyID() {
281
282                return kid;
283        }
284
285
286        /**
287         * Gets the X.509 certificate URL ({@code x5u}) of this JWK.
288         *
289         * @return The X.509 certificate URL, {@code null} if not specified.
290         */
291        public URI getX509CertURL() {
292
293                return x5u;
294        }
295
296
297        /**
298         * Gets the X.509 certificate SHA-1 thumbprint ({@code x5t}) of this
299         * JWK.
300         *
301         * @return The X.509 certificate SHA-1 thumbprint, {@code null} if not
302         *         specified.
303         */
304        @Deprecated
305        public Base64URL getX509CertThumbprint() {
306
307                return x5t;
308        }
309        
310        
311        /**
312         * Gets the X.509 certificate SHA-256 thumbprint ({@code x5t#S256}) of
313         * this JWK.
314         *
315         * @return The X.509 certificate SHA-256 thumbprint, {@code null} if
316         *         not specified.
317         */
318        public Base64URL getX509CertSHA256Thumbprint() {
319                
320                return x5t256;
321        }
322
323
324        /**
325         * Gets the X.509 certificate chain ({@code x5c}) of this JWK.
326         *
327         * @return The X.509 certificate chain as a unmodifiable list,
328         *         {@code null} if not specified.
329         */
330        public List<Base64> getX509CertChain() {
331
332                if (x5c == null) {
333                        return null;
334                }
335
336                return Collections.unmodifiableList(x5c);
337        }
338        
339        
340        /**
341         * Gets the parsed X.509 certificate chain ({@code x5c}) of this JWK.
342         *
343         * @return The X.509 certificate chain as a unmodifiable list,
344         *         {@code null} if not specified.
345         */
346        public List<X509Certificate> getParsedX509CertChain() {
347                
348                if (parsedX5c == null) {
349                        return null;
350                }
351                
352                return Collections.unmodifiableList(parsedX5c);
353        }
354        
355        
356        /**
357         * Returns a reference to the underlying key store.
358         *
359         * @return The underlying key store, {@code null} if none.
360         */
361        public KeyStore getKeyStore() {
362                
363                return keyStore;
364        }
365
366
367        /**
368         * Returns the required JWK parameters. Intended as input for JWK
369         * thumbprint computation. See RFC 7638 for more information.
370         *
371         * @return The required JWK parameters, sorted alphanumerically by key
372         *         name and ready for JSON serialisation.
373         */
374        public abstract LinkedHashMap<String,?> getRequiredParams();
375
376
377        /**
378         * Computes the SHA-256 thumbprint of this JWK. See RFC 7638 for more
379         * information.
380         *
381         * @return The SHA-256 thumbprint.
382         *
383         * @throws JOSEException If the SHA-256 hash algorithm is not
384         *                       supported.
385         */
386        public Base64URL computeThumbprint()
387                throws JOSEException {
388
389                return computeThumbprint("SHA-256");
390        }
391
392
393        /**
394         * Computes the thumbprint of this JWK using the specified hash
395         * algorithm. See RFC 7638 for more information.
396         *
397         * @param hashAlg The hash algorithm. Must not be {@code null}.
398         *
399         * @return The SHA-256 thumbprint.
400         *
401         * @throws JOSEException If the hash algorithm is not supported.
402         */
403        public Base64URL computeThumbprint(final String hashAlg)
404                throws JOSEException {
405
406                return ThumbprintUtils.compute(hashAlg, this);
407        }
408
409
410        /**
411         * Returns {@code true} if this JWK contains private or sensitive
412         * (non-public) parameters.
413         *
414         * @return {@code true} if this JWK contains private parameters, else
415         *         {@code false}.
416         */
417        public abstract boolean isPrivate();
418
419
420        /**
421         * Creates a copy of this JWK with all private or sensitive parameters 
422         * removed.
423         * 
424         * @return The newly created public JWK, or {@code null} if none can be
425         *         created.
426         */
427        public abstract JWK toPublicJWK();
428
429
430        /**
431         * Returns the size of this JWK.
432         *
433         * @return The JWK size, in bits.
434         */
435        public abstract int size();
436        
437        
438        /**
439         * Casts this JWK to an RSA JWK.
440         *
441         * @return The RSA JWK.
442         */
443        public RSAKey toRSAKey() {
444                return (RSAKey)this;
445        }
446        
447        
448        /**
449         * Casts this JWK to an EC JWK.
450         *
451         * @return The EC JWK.
452         */
453        public ECKey toECKey() {
454                return (ECKey)this;
455        }
456        
457        
458        /**
459         * Casts this JWK to an octet sequence JWK.
460         *
461         * @return The octet sequence JWK.
462         */
463        public OctetSequenceKey toOctetSequenceKey() {
464                return (OctetSequenceKey)this;
465        }
466        
467        
468        /**
469         * Casts this JWK to an octet key pair JWK.
470         *
471         * @return The octet key pair JWK.
472         */
473        public OctetKeyPair toOctetKeyPair() {
474                return (OctetKeyPair)this;
475        }
476
477
478        /**
479         * Returns a JSON object representation of this JWK. This method is 
480         * intended to be called from extending classes.
481         *
482         * <p>Example:
483         *
484         * <pre>
485         * {
486         *   "kty" : "RSA",
487         *   "use" : "sig",
488         *   "kid" : "fd28e025-8d24-48bc-a51a-e2ffc8bc274b"
489         * }
490         * </pre>
491         *
492         * @return The JSON object representation.
493         */
494        public JSONObject toJSONObject() {
495
496                JSONObject o = new JSONObject();
497
498                o.put("kty", kty.getValue());
499
500                if (use != null) {
501                        o.put("use", use.identifier());
502                }
503
504                if (ops != null) {
505
506                        List<String> sl = new ArrayList<>(ops.size());
507
508                        for (KeyOperation op: ops) {
509                                sl.add(op.identifier());
510                        }
511
512                        o.put("key_ops", sl);
513                }
514
515                if (alg != null) {
516                        o.put("alg", alg.getName());
517                }
518
519                if (kid != null) {
520                        o.put("kid", kid);
521                }
522
523                if (x5u != null) {
524                        o.put("x5u", x5u.toString());
525                }
526
527                if (x5t != null) {
528                        o.put("x5t", x5t.toString());
529                }
530                
531                if (x5t256 != null) {
532                        o.put("x5t#S256", x5t256.toString());
533                }
534
535                if (x5c != null) {
536                        JSONArray stringValues = new JSONArray();
537                        for (Base64 base64: x5c) {
538                                stringValues.add(base64.toString());
539                        }
540                        o.put("x5c", stringValues);
541                }
542
543                return o;
544        }
545
546
547        /**
548         * Returns the JSON object string representation of this JWK.
549         *
550         * @return The JSON object string representation.
551         */
552        @Override
553        public String toJSONString() {
554
555                return toJSONObject().toString();
556        }
557
558
559        /**
560         * @see #toJSONString
561         */
562        @Override
563        public String toString() {
564
565                return toJSONObject().toString();
566        }
567
568
569        /**
570         * Parses a JWK from the specified JSON object string representation. 
571         * The JWK must be an {@link ECKey}, an {@link RSAKey}, or a 
572         * {@link OctetSequenceKey}.
573         *
574         * @param s The JSON object string to parse. Must not be {@code null}.
575         *
576         * @return The JWK.
577         *
578         * @throws ParseException If the string couldn't be parsed to a
579         *                        supported JWK.
580         */
581        public static JWK parse(final String s)
582                throws ParseException {
583
584                return parse(JSONObjectUtils.parse(s));
585        }
586
587
588        /**
589         * Parses a JWK from the specified JSON object representation. The JWK 
590         * must be an {@link ECKey}, an {@link RSAKey}, or a 
591         * {@link OctetSequenceKey}.
592         *
593         * @param jsonObject The JSON object to parse. Must not be 
594         *                   {@code null}.
595         *
596         * @return The JWK.
597         *
598         * @throws ParseException If the JSON object couldn't be parsed to a 
599         *                        supported JWK.
600         */
601        public static JWK parse(final JSONObject jsonObject)
602                throws ParseException {
603
604                KeyType kty = KeyType.parse(JSONObjectUtils.getString(jsonObject, "kty"));
605
606                if (kty == KeyType.EC) {
607                        
608                        return ECKey.parse(jsonObject);
609
610                } else if (kty == KeyType.RSA) {
611                        
612                        return RSAKey.parse(jsonObject);
613
614                } else if (kty == KeyType.OCT) {
615                        
616                        return OctetSequenceKey.parse(jsonObject);
617                        
618                } else if (kty == KeyType.OKP) {
619                        
620                        return OctetKeyPair.parse(jsonObject);
621
622                } else {
623
624                        throw new ParseException("Unsupported key type \"kty\" parameter: " + kty, 0);
625                }
626        }
627        
628        
629        /**
630         * Parses a public {@link RSAKey RSA} or {@link ECKey EC JWK} from the
631         * specified X.509 certificate. Requires BouncyCastle.
632         *
633         * <p><strong>Important:</strong> The X.509 certificate is not
634         * validated!
635         *
636         * <p>Sets the following JWK parameters:
637         *
638         * <ul>
639         *     <li>For an EC key the curve is obtained from the subject public
640         *         key info algorithm parameters.
641         *     <li>The JWK use inferred by {@link KeyUse#from}.
642         *     <li>The JWK ID from the X.509 serial number (in base 10).
643         *     <li>The JWK X.509 certificate chain (this certificate only).
644         *     <li>The JWK X.509 certificate SHA-256 thumbprint.
645         * </ul>
646         *
647         * @param cert The X.509 certificate. Must not be {@code null}.
648         *
649         * @return The public RSA or EC JWK.
650         *
651         * @throws JOSEException If parsing failed.
652         */
653        public static JWK parse(final X509Certificate cert)
654                throws JOSEException {
655                
656                if (cert.getPublicKey() instanceof RSAPublicKey) {
657                        return RSAKey.parse(cert);
658                } else if (cert.getPublicKey() instanceof ECPublicKey) {
659                        return ECKey.parse(cert);
660                } else {
661                        throw new JOSEException("Unsupported public key algorithm: " + cert.getPublicKey().getAlgorithm());
662                }
663        }
664        
665        
666        /**
667         * Parses a public {@link RSAKey RSA} or {@link ECKey EC JWK} from the
668         * specified PEM-encoded X.509 certificate. Requires BouncyCastle.
669         *
670         * <p><strong>Important:</strong> The X.509 certificate is not
671         * validated!
672         *
673         * <p>Sets the following JWK parameters:
674         *
675         * <ul>
676         *     <li>For an EC key the curve is obtained from the subject public
677         *         key info algorithm parameters.
678         *     <li>The JWK use inferred by {@link KeyUse#from}.
679         *     <li>The JWK ID from the X.509 serial number (in base 10).
680         *     <li>The JWK X.509 certificate chain (this certificate only).
681         *     <li>The JWK X.509 certificate SHA-256 thumbprint.
682         * </ul>
683         *
684         * @param pemEncodedCert The PEM-encoded X.509 certificate. Must not be
685         *                       {@code null}.
686         *
687         * @return The public RSA or EC JWK.
688         *
689         * @throws JOSEException If parsing failed.
690         */
691        public static JWK parseFromPEMEncodedX509Cert(final String pemEncodedCert)
692                throws JOSEException {
693                
694                X509Certificate cert = X509CertUtils.parse(pemEncodedCert);
695                
696                if (cert == null) {
697                        throw new JOSEException("Couldn't parse PEM-encoded X.509 certificate");
698                }
699                
700                return parse(cert);
701        }
702        
703        
704        /**
705         * Loads a JWK from the specified JCE key store. The JWK can be a
706         * public / private {@link RSAKey RSA key}, a public / private
707         * {@link ECKey EC key}, or a {@link OctetSequenceKey secret key}.
708         * Requires BouncyCastle.
709         *
710         * <p><strong>Important:</strong> The X.509 certificate is not
711         * validated!
712         *
713         * @param keyStore The key store. Must not be {@code null}.
714         * @param alias    The alias. Must not be {@code null}.
715         * @param pin      The pin to unlock the private key if any, empty or
716         *                 {@code null} if not required.
717         *
718         * @return The public / private RSA or EC JWK, or secret JWK, or
719         *         {@code null} if no key with the specified alias was found.
720         *
721         * @throws KeyStoreException On a key store exception.
722         * @throws JOSEException     If RSA or EC key loading failed.
723         */
724        public static JWK load(final KeyStore keyStore, final String alias, final char[] pin)
725                throws KeyStoreException, JOSEException {
726                
727                java.security.cert.Certificate cert = keyStore.getCertificate(alias);
728                
729                if (cert == null) {
730                        // Try secret key
731                        return OctetSequenceKey.load(keyStore, alias, pin);
732                }
733                
734                if (cert.getPublicKey() instanceof RSAPublicKey) {
735                        return RSAKey.load(keyStore, alias, pin);
736                } else if (cert.getPublicKey() instanceof ECPublicKey) {
737                        return ECKey.load(keyStore, alias, pin);
738                } else {
739                        throw new JOSEException("Unsupported public key algorithm: " + cert.getPublicKey().getAlgorithm());
740                }
741        }
742
743        /**
744         * Parses an RSA or EC JWK from the specified string of one or more
745         * PEM-encoded object(s):
746         *
747         * <ul>
748         *     <li>X.509 certificate (PEM header: BEGIN CERTIFICATE)
749         *     <li>PKCS#1 RSAPublicKey (PEM header: BEGIN RSA PUBLIC KEY)
750         *     <li>X.509 SubjectPublicKeyInfo (PEM header: BEGIN PUBLIC KEY)
751         *     <li>PKCS#1 RSAPrivateKey (PEM header: BEGIN RSA PRIVATE KEY)
752         *     <li>PKCS#8 PrivateKeyInfo (PEM header: BEGIN PRIVATE KEY)
753         *     <li>matching pair of the above
754         * </ul>
755         *
756         * <p>Requires BouncyCastle.
757         *
758         * @param pemEncodedObjects The string of PEM-encoded object(s).
759         *
760         * @return The public / (private) RSA or EC JWK.
761         *
762         * @throws JOSEException If RSA or EC key parsing failed.
763         */
764        public static JWK parseFromPEMEncodedObjects(final String pemEncodedObjects)
765                throws JOSEException {
766                
767                final List<KeyPair> keys = PEMEncodedKeyParser.parseKeys(pemEncodedObjects);
768                if (keys.isEmpty()) {
769                        throw new JOSEException("No PEM-encoded keys found");
770                }
771
772                final KeyPair pair = mergeKeyPairs(keys);
773
774                final PublicKey publicKey = pair.getPublic();
775                final PrivateKey privateKey = pair.getPrivate();
776                
777                if (publicKey == null) {
778                        // For EC keys, for RSA the public can be reconstructed
779                        throw new JOSEException("Missing PEM-encoded public key to construct JWK");
780                }
781
782                if (publicKey instanceof ECPublicKey) {
783                        final ECPublicKey ecPubKey = (ECPublicKey) publicKey;
784                        final ECParameterSpec pubParams = ecPubKey.getParams();
785
786                        if (privateKey instanceof ECPrivateKey) {
787                                validateEcCurves(ecPubKey, (ECPrivateKey) privateKey);
788                        }
789                        if (privateKey != null && !(privateKey instanceof ECPrivateKey)) {
790                                throw new JOSEException("Unsupported EC private key type: " + privateKey);
791                        }
792
793                        final Curve curve = Curve.forECParameterSpec(pubParams);
794                        final ECKey.Builder builder = new ECKey.Builder(curve, (ECPublicKey) publicKey);
795
796                        if (privateKey != null) {
797                                builder.privateKey((ECPrivateKey) privateKey);
798                        }
799                        return builder.build();
800                }
801
802                if (publicKey instanceof RSAPublicKey) {
803                        final RSAKey.Builder builder = new RSAKey.Builder((RSAPublicKey) publicKey);
804                        if (privateKey instanceof RSAPrivateKey) {
805                                builder.privateKey((RSAPrivateKey) privateKey);
806                        } else if (privateKey != null) {
807                                throw new JOSEException("Unsupported RSA private key type: " + privateKey);
808                        }
809                        return builder.build();
810                }
811
812                throw new JOSEException("Unsupported algorithm of PEM-encoded key: " + publicKey.getAlgorithm());
813        }
814        
815
816        private static void validateEcCurves(ECPublicKey publicKey, ECPrivateKey privateKey) throws JOSEException {
817                final ECParameterSpec pubParams = publicKey.getParams();
818                final ECParameterSpec privParams = privateKey.getParams();
819                if (!pubParams.getCurve().equals(privParams.getCurve())) {
820                        throw new JOSEException("Public/private EC key curve mismatch: " + publicKey);
821                }
822                if (pubParams.getCofactor() != privParams.getCofactor()) {
823                        throw new JOSEException("Public/private EC key cofactor mismatch: " + publicKey);
824                }
825                if (!pubParams.getGenerator().equals(privParams.getGenerator())) {
826                        throw new JOSEException("Public/private EC key generator mismatch: " + publicKey);
827                }
828                if (!pubParams.getOrder().equals(privParams.getOrder())) {
829                        throw new JOSEException("Public/private EC key order mismatch: " + publicKey);
830                }
831        }
832
833        
834        private static KeyPair mergeKeyPairs(final List<KeyPair> keys) throws JOSEException {
835                final KeyPair pair;
836                if (keys.size() == 1) {
837                        // Assume public key, or private key easy to convert to public,
838                        // otherwise not representable as a JWK
839                        pair = keys.get(0);
840                } else if (keys.size() == 2) {
841                        // If two keys, assume public + private keys separated
842                        pair = twoKeysToKeyPair(keys);
843                } else {
844                        throw new JOSEException("Expected key or pair of PEM-encoded keys");
845                }
846                return pair;
847        }
848
849        
850        private static KeyPair twoKeysToKeyPair(final List<? extends KeyPair> keys) throws JOSEException {
851                final KeyPair key1 = keys.get(0);
852                final KeyPair key2 = keys.get(1);
853                if (key1.getPublic() != null && key2.getPrivate() != null) {
854                        return new KeyPair(key1.getPublic(), key2.getPrivate());
855                } else if (key1.getPrivate() != null && key2.getPublic() != null) {
856                        return new KeyPair(key2.getPublic(), key1.getPrivate());
857                } else {
858                        throw new JOSEException("Not a public/private key pair");
859                }
860        }
861
862        
863        @Override
864        public boolean equals(Object o) {
865                if (this == o) return true;
866                if (!(o instanceof JWK)) return false;
867                JWK jwk = (JWK) o;
868                return Objects.equals(kty, jwk.kty) &&
869                                Objects.equals(use, jwk.use) &&
870                                Objects.equals(ops, jwk.ops) &&
871                                Objects.equals(alg, jwk.alg) &&
872                                Objects.equals(kid, jwk.kid) &&
873                                Objects.equals(x5u, jwk.x5u) &&
874                                Objects.equals(x5t, jwk.x5t) &&
875                                Objects.equals(x5t256, jwk.x5t256) &&
876                                Objects.equals(x5c, jwk.x5c) &&
877                                Objects.equals(keyStore, jwk.keyStore);
878        }
879
880        
881        @Override
882        public int hashCode() {
883                return Objects.hash(kty, use, ops, alg, kid, x5u, x5t, x5t256, x5c, keyStore);
884        }
885}