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