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