001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
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.net.URI;
022import java.security.KeyPair;
023import java.security.KeyStore;
024import java.security.PrivateKey;
025import java.security.PublicKey;
026import java.security.cert.X509Certificate;
027import java.text.ParseException;
028import java.util.*;
029
030import com.nimbusds.jose.Algorithm;
031import com.nimbusds.jose.JOSEException;
032import com.nimbusds.jose.util.Base64;
033import com.nimbusds.jose.util.Base64URL;
034import com.nimbusds.jose.util.ByteUtils;
035import com.nimbusds.jose.util.JSONObjectUtils;
036import net.jcip.annotations.Immutable;
037import net.minidev.json.JSONObject;
038
039
040/**
041 * {@link KeyType#OKP Octet key pair} JSON Web Key (JWK), used to represent
042 * Edwards-curve keys. This class is immutable.
043 *
044 * <p>Supported curves:
045 *
046 * <ul>
047 *     <li>{@link Curve#Ed25519 Ed25519}
048 *     <li>{@link Curve#Ed448 Ed448}
049 *     <li>{@link Curve#X25519 X25519}
050 *     <li>{@link Curve#X448 X448}
051 * </ul>
052 *
053 * <p>Example JSON object representation of a public OKP JWK:
054 *
055 * <pre>
056 * {
057 *   "kty" : "OKP",
058 *   "crv" : "Ed25519",
059 *   "x"   : "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
060 *   "use" : "sig",
061 *   "kid" : "1"
062 * }
063 * </pre>
064 *
065 * <p>Example JSON object representation of a private OKP JWK:
066 *
067 * <pre>
068 * {
069 *   "kty" : "OKP",
070 *   "crv" : "Ed25519",
071 *   "x"   : "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
072 *   "d"   : "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A"
073 *   "use" : "sig",
074 *   "kid" : "1"
075 * }
076 * </pre>
077 *
078 * <p>Use the builder to create a new OKP JWK:
079 *
080 * <pre>
081 * OctetKeyPair key = new OctetKeyPair.Builder(Curve.Ed25519, x)
082 *      .keyUse(KeyUse.SIGNATURE)
083 *      .keyID("1")
084 *      .build();
085 * </pre>
086 *
087 * @author Vladimir Dzhuvinov
088 * @version 2018-02-27
089 */
090@Immutable
091public class OctetKeyPair extends JWK implements AsymmetricJWK, CurveBasedJWK {
092        
093        
094        private static final long serialVersionUID = 1L;
095        
096        
097        /**
098         * Supported Edwards curves.
099         */
100        public static final Set<Curve> SUPPORTED_CURVES = Collections.unmodifiableSet(
101                new HashSet<>(Arrays.asList(Curve.Ed25519, Curve.Ed448, Curve.X25519, Curve.X448))
102        );
103        
104        
105        /**
106         * Builder for constructing Octet Key Pair JWKs.
107         *
108         * <p>Example usage:
109         *
110         * <pre>
111         * OctetKeyPair key = new OctetKeyPair.Builder(Curve.Ed25519, x)
112         *     .d(d)
113         *     .algorithm(JWSAlgorithm.EdDSA)
114         *     .keyID("1")
115         *     .build();
116         * </pre>
117         */
118        public static class Builder {
119                
120                
121                /**
122                 * The curve name.
123                 */
124                private final Curve crv;
125                
126                
127                /**
128                 * The public 'x' parameter.
129                 */
130                private final Base64URL x;
131                
132                
133                /**
134                 * The private 'd' parameter, optional.
135                 */
136                private Base64URL d;
137                
138                
139                /**
140                 * The key use, optional.
141                 */
142                private KeyUse use;
143                
144                
145                /**
146                 * The key operations, optional.
147                 */
148                private Set<KeyOperation> ops;
149                
150                
151                /**
152                 * The intended JOSE algorithm for the key, optional.
153                 */
154                private Algorithm alg;
155                
156                
157                /**
158                 * The key ID, optional.
159                 */
160                private String kid;
161                
162                
163                /**
164                 * X.509 certificate URL, optional.
165                 */
166                private URI x5u;
167                
168                
169                /**
170                 * X.509 certificate SHA-1 thumbprint, optional.
171                 */
172                @Deprecated
173                private Base64URL x5t;
174                
175                
176                /**
177                 * X.509 certificate SHA-256 thumbprint, optional.
178                 */
179                private Base64URL x5t256;
180                
181                
182                /**
183                 * The X.509 certificate chain, optional.
184                 */
185                private List<Base64> x5c;
186                
187                
188                /**
189                 * Reference to the underlying key store, {@code null} if none.
190                 */
191                private KeyStore ks;
192                
193                
194                /**
195                 * Creates a new Octet Key Pair JWK builder.
196                 *
197                 * @param crv The cryptographic curve. Must not be
198                 *            {@code null}.
199                 * @param x   The public 'x' parameter. Must not be 
200                 *            {@code null}.
201                 */
202                public Builder(final Curve crv, final Base64URL x) {
203                        
204                        if (crv == null) {
205                                throw new IllegalArgumentException("The curve must not be null");
206                        }
207                        
208                        this.crv = crv;
209                        
210                        if (x == null) {
211                                throw new IllegalArgumentException("The 'x' coordinate must not be null");
212                        }
213                        
214                        this.x = x;
215                }
216                
217                
218                /**
219                 * Creates a new Octet Key Pair JWK builder.
220                 *
221                 * @param okpJWK The Octet Key Pair to start with. Must not be
222                 *              {@code null}.
223                 */
224                public Builder(final OctetKeyPair okpJWK) {
225                        
226                        crv = okpJWK.crv;
227                        x = okpJWK.x;
228                        d = okpJWK.d;
229                        use = okpJWK.getKeyUse();
230                        ops = okpJWK.getKeyOperations();
231                        alg = okpJWK.getAlgorithm();
232                        kid = okpJWK.getKeyID();
233                        x5u = okpJWK.getX509CertURL();
234                        x5t = okpJWK.getX509CertThumbprint();
235                        x5t256 = okpJWK.getX509CertSHA256Thumbprint();
236                        x5c = okpJWK.getX509CertChain();
237                        ks = okpJWK.getKeyStore();
238                }
239                
240                
241                /**
242                 * Sets the private 'd' parameter.
243                 *
244                 * @param d The private 'd' parameter, {@code null} if not 
245                 *          specified (for a public key).
246                 *
247                 * @return This builder.
248                 */
249                public OctetKeyPair.Builder d(final Base64URL d) {
250                        
251                        this.d = d;
252                        return this;
253                }
254                
255                
256                /**
257                 * Sets the use ({@code use}) of the JWK.
258                 *
259                 * @param use The key use, {@code null} if not specified or if
260                 *            the key is intended for signing as well as
261                 *            encryption.
262                 *
263                 * @return This builder.
264                 */
265                public OctetKeyPair.Builder keyUse(final KeyUse use) {
266                        
267                        this.use = use;
268                        return this;
269                }
270                
271                
272                /**
273                 * Sets the operations ({@code key_ops}) of the JWK.
274                 *
275                 * @param ops The key operations, {@code null} if not
276                 *            specified.
277                 *
278                 * @return This builder.
279                 */
280                public OctetKeyPair.Builder keyOperations(final Set<KeyOperation> ops) {
281                        
282                        this.ops = ops;
283                        return this;
284                }
285                
286                
287                /**
288                 * Sets the intended JOSE algorithm ({@code alg}) for the JWK.
289                 *
290                 * @param alg The intended JOSE algorithm, {@code null} if not
291                 *            specified.
292                 *
293                 * @return This builder.
294                 */
295                public OctetKeyPair.Builder algorithm(final Algorithm alg) {
296                        
297                        this.alg = alg;
298                        return this;
299                }
300                
301                /**
302                 * Sets the ID ({@code kid}) of the JWK. The key ID can be used
303                 * to match a specific key. This can be used, for instance, to
304                 * choose a key within a {@link JWKSet} during key rollover.
305                 * The key ID may also correspond to a JWS/JWE {@code kid}
306                 * header parameter value.
307                 *
308                 * @param kid The key ID, {@code null} if not specified.
309                 *
310                 * @return This builder.
311                 */
312                public OctetKeyPair.Builder keyID(final String kid) {
313                        
314                        this.kid = kid;
315                        return this;
316                }
317                
318                
319                /**
320                 * Sets the ID ({@code kid}) of the JWK to its SHA-256 JWK
321                 * thumbprint (RFC 7638). The key ID can be used to match a
322                 * specific key. This can be used, for instance, to choose a
323                 * key within a {@link JWKSet} during key rollover. The key ID
324                 * may also correspond to a JWS/JWE {@code kid} header
325                 * parameter value.
326                 *
327                 * @return This builder.
328                 *
329                 * @throws JOSEException If the SHA-256 hash algorithm is not
330                 *                       supported.
331                 */
332                public OctetKeyPair.Builder keyIDFromThumbprint()
333                        throws JOSEException {
334                        
335                        return keyIDFromThumbprint("SHA-256");
336                }
337                
338                
339                /**
340                 * Sets the ID ({@code kid}) of the JWK to its JWK thumbprint
341                 * (RFC 7638). The key ID can be used to match a specific key.
342                 * This can be used, for instance, to choose a key within a
343                 * {@link JWKSet} during key rollover. The key ID may also
344                 * correspond to a JWS/JWE {@code kid} header parameter value.
345                 *
346                 * @param hashAlg The hash algorithm for the JWK thumbprint
347                 *                computation. Must not be {@code null}.
348                 *
349                 * @return This builder.
350                 *
351                 * @throws JOSEException If the hash algorithm is not
352                 *                       supported.
353                 */
354                public OctetKeyPair.Builder keyIDFromThumbprint(final String hashAlg)
355                        throws JOSEException {
356                        
357                        // Put mandatory params in sorted order
358                        LinkedHashMap<String,String> requiredParams = new LinkedHashMap<>();
359                        requiredParams.put("crv", crv.toString());
360                        requiredParams.put("kty", KeyType.OKP.getValue());
361                        requiredParams.put("x", x.toString());
362                        this.kid = ThumbprintUtils.compute(hashAlg, requiredParams).toString();
363                        return this;
364                }
365                
366                
367                /**
368                 * Sets the X.509 certificate URL ({@code x5u}) of the JWK.
369                 *
370                 * @param x5u The X.509 certificate URL, {@code null} if not
371                 *            specified.
372                 *
373                 * @return This builder.
374                 */
375                public OctetKeyPair.Builder x509CertURL(final URI x5u) {
376                        
377                        this.x5u = x5u;
378                        return this;
379                }
380                
381                
382                /**
383                 * Sets the X.509 certificate SHA-1 thumbprint ({@code x5t}) of
384                 * the JWK.
385                 *
386                 * @param x5t The X.509 certificate SHA-1 thumbprint,
387                 *            {@code null} if not specified.
388                 *
389                 * @return This builder.
390                 */
391                @Deprecated
392                public OctetKeyPair.Builder x509CertThumbprint(final Base64URL x5t) {
393                        
394                        this.x5t = x5t;
395                        return this;
396                }
397                
398                
399                /**
400                 * Sets the X.509 certificate SHA-256 thumbprint
401                 * ({@code x5t#S256}) of the JWK.
402                 *
403                 * @param x5t256 The X.509 certificate SHA-256 thumbprint,
404                 *               {@code null} if not specified.
405                 *
406                 * @return This builder.
407                 */
408                public OctetKeyPair.Builder x509CertSHA256Thumbprint(final Base64URL x5t256) {
409                        
410                        this.x5t256 = x5t256;
411                        return this;
412                }
413                
414                
415                /**
416                 * Sets the X.509 certificate chain ({@code x5c}) of the JWK.
417                 *
418                 * @param x5c The X.509 certificate chain as a unmodifiable
419                 *            list, {@code null} if not specified.
420                 *
421                 * @return This builder.
422                 */
423                public OctetKeyPair.Builder x509CertChain(final List<Base64> x5c) {
424                        
425                        this.x5c = x5c;
426                        return this;
427                }
428                
429                
430                /**
431                 * Sets the underlying key store.
432                 *
433                 * @param keyStore Reference to the underlying key store,
434                 *                 {@code null} if none.
435                 *
436                 * @return This builder.
437                 */
438                public OctetKeyPair.Builder keyStore(final KeyStore keyStore) {
439                        
440                        this.ks = keyStore;
441                        return this;
442                }
443                
444                
445                /**
446                 * Builds a new Octet Key Pair JWK.
447                 *
448                 * @return The Octet Key Pair JWK.
449                 *
450                 * @throws IllegalStateException If the JWK parameters were
451                 *                               inconsistently specified.
452                 */
453                public OctetKeyPair build() {
454                        
455                        try {
456                                if (d == null) {
457                                        // Public key
458                                        return new OctetKeyPair(crv, x, use, ops, alg, kid, x5u, x5t, x5t256, x5c, ks);
459                                }
460                                
461                                // Public / private key pair with 'd'
462                                return new OctetKeyPair(crv, x, d, use, ops, alg, kid, x5u, x5t, x5t256, x5c, ks);
463                                
464                        } catch (IllegalArgumentException e) {
465                                throw new IllegalStateException(e.getMessage(), e);
466                        }
467                }
468        }
469        
470        
471        /**
472         * The curve name.
473         */
474        private final Curve crv;
475        
476        
477        /**
478         * The public 'x' parameter.
479         */
480        private final Base64URL x;
481        
482        
483        /**
484         * The private 'd' parameter.
485         */
486        private final Base64URL d;
487        
488        
489        /**
490         * Creates a new public Octet Key Pair JSON Web Key (JWK) with the
491         * specified parameters.
492         *
493         * @param crv    The cryptographic curve. Must not be {@code null}.
494         * @param x      The public 'x' parameter. Must not be {@code null}.
495         * @param use    The key use, {@code null} if not specified or if the
496         *               key is intended for signing as well as encryption.
497         * @param ops    The key operations, {@code null} if not specified.
498         * @param alg    The intended JOSE algorithm for the key, {@code null}
499         *               if not specified.
500         * @param kid    The key ID, {@code null} if not specified.
501         * @param x5u    The X.509 certificate URL, {@code null} if not
502         *               specified.
503         * @param x5t    The X.509 certificate SHA-1 thumbprint, {@code null}
504         *               if not specified.
505         * @param x5t256 The X.509 certificate SHA-256 thumbprint, {@code null}
506         *               if not specified.
507         * @param x5c    The X.509 certificate chain, {@code null} if not
508         *               specified.
509         * @param ks     Reference to the underlying key store, {@code null} if
510         *               not specified.
511         */
512        public OctetKeyPair(final Curve crv, final Base64URL x,
513                            final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid,
514                            final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c,
515                            final KeyStore ks) {
516                
517                super(KeyType.OKP, use, ops, alg, kid, x5u, x5t, x5t256, x5c, ks);
518                
519                if (crv == null) {
520                        throw new IllegalArgumentException("The curve must not be null");
521                }
522                
523                if (! SUPPORTED_CURVES.contains(crv)) {
524                        throw new IllegalArgumentException("Unknown / unsupported curve: " + crv);
525                }
526                
527                this.crv = crv;
528                
529                if (x == null) {
530                        throw new IllegalArgumentException("The 'x' parameter must not be null");
531                }
532                
533                this.x = x;
534                
535                d = null;
536        }
537        
538        
539        /**
540         * Creates a new public / private Octet Key Pair JSON Web Key (JWK)
541         * with the specified parameters.
542         *
543         * @param crv    The cryptographic curve. Must not be {@code null}.
544         * @param x      The public 'x' parameter. Must not be {@code null}.
545         * @param d      The private 'd' parameter. Must not be {@code null}.
546         * @param use    The key use, {@code null} if not specified or if the
547         *               key is intended for signing as well as encryption.
548         * @param ops    The key operations, {@code null} if not specified.
549         * @param alg    The intended JOSE algorithm for the key, {@code null}
550         *               if not specified.
551         * @param kid    The key ID, {@code null} if not specified.
552         * @param x5u    The X.509 certificate URL, {@code null} if not
553         *               specified.
554         * @param x5t    The X.509 certificate SHA-1 thumbprint, {@code null}
555         *               if not specified.
556         * @param x5t256 The X.509 certificate SHA-256 thumbprint, {@code null}
557         *               if not specified.
558         * @param x5c    The X.509 certificate chain, {@code null} if not
559         *               specified.
560         * @param ks     Reference to the underlying key store, {@code null} if
561         *               not specified.
562         */
563        public OctetKeyPair(final Curve crv, final Base64URL x, final Base64URL d,
564                            final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid,
565                            final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c,
566                            final KeyStore ks) {
567                
568                super(KeyType.OKP, use, ops, alg, kid, x5u, x5t, x5t256, x5c, ks);
569                
570                if (crv == null) {
571                        throw new IllegalArgumentException("The curve must not be null");
572                }
573                
574                if (! SUPPORTED_CURVES.contains(crv)) {
575                        throw new IllegalArgumentException("Unknown / unsupported curve: " + crv);
576                }
577                
578                this.crv = crv;
579                
580                if (x == null) {
581                        throw new IllegalArgumentException("The 'x' parameter must not be null");
582                }
583                
584                this.x = x;
585                
586                if (d == null) {
587                        throw new IllegalArgumentException("The 'd' parameter must not be null");
588                }
589                
590                this.d = d;
591        }
592        
593        
594        @Override
595        public Curve getCurve() {
596                
597                return crv;
598        }
599        
600        
601        /**
602         * Gets the public 'x' parameter.
603         *
604         * @return The public 'x' parameter.
605         */
606        public Base64URL getX() {
607                
608                return x;
609        }
610        
611        
612        /**
613         * Gets the private 'd' parameter.
614         *
615         * @return The private 'd' coordinate, {@code null} if not specified
616         *         (for a public key).
617         */
618        public Base64URL getD() {
619                
620                return d;
621        }
622        
623        
624        @Override
625        public PublicKey toPublicKey()
626                throws JOSEException {
627                
628                throw new JOSEException("Export to java.security.PublicKey not supported");
629        }
630        
631        
632        @Override
633        public PrivateKey toPrivateKey()
634                throws JOSEException {
635                
636                throw new JOSEException("Export to java.security.PrivateKey not supported");
637        }
638        
639        
640        @Override
641        public KeyPair toKeyPair()
642                throws JOSEException {
643                
644                throw new JOSEException("Export to java.security.KeyPair not supported");
645        }
646        
647        
648        @Override
649        public boolean matches(final X509Certificate cert) {
650                // X.509 certs don't support OKP yet
651                return false;
652        }
653        
654        
655        @Override
656        public LinkedHashMap<String,?> getRequiredParams() {
657                
658                // Put mandatory params in sorted order
659                LinkedHashMap<String,String> requiredParams = new LinkedHashMap<>();
660                requiredParams.put("crv", crv.toString());
661                requiredParams.put("kty", getKeyType().getValue());
662                requiredParams.put("x", x.toString());
663                return requiredParams;
664        }
665        
666        
667        @Override
668        public boolean isPrivate() {
669                
670                return d != null;
671        }
672        
673        
674        /**
675         * Returns a copy of this Octet Key Pair JWK with any private values
676         * removed.
677         *
678         * @return The copied public Octet Key Pair JWK.
679         */
680        @Override
681        public OctetKeyPair toPublicJWK() {
682                
683                return new OctetKeyPair(
684                        getCurve(), getX(),
685                        getKeyUse(), getKeyOperations(), getAlgorithm(), getKeyID(),
686                        getX509CertURL(), getX509CertThumbprint(), getX509CertSHA256Thumbprint(), getX509CertChain(),
687                        getKeyStore());
688        }
689        
690        
691        @Override
692        public JSONObject toJSONObject() {
693                
694                JSONObject o = super.toJSONObject();
695                
696                // Append OKP specific attributes
697                o.put("crv", crv.toString());
698                o.put("x", x.toString());
699                
700                if (d != null) {
701                        o.put("d", d.toString());
702                }
703                
704                return o;
705        }
706        
707        
708        @Override
709        public int size() {
710                
711                return ByteUtils.bitLength(x.decode());
712        }
713        
714        
715        /**
716         * Parses a public / private Octet Key Pair JWK from the specified JSON
717         * object string representation.
718         *
719         * @param s The JSON object string to parse. Must not be {@code null}.
720         *
721         * @return The public / private Octet Key Pair JWK.
722         *
723         * @throws ParseException If the string couldn't be parsed to an Octet
724         *                        Key Pair JWK.
725         */
726        public static OctetKeyPair parse(final String s)
727                throws ParseException {
728                
729                return parse(JSONObjectUtils.parse(s));
730        }
731        
732        
733        /**
734         * Parses a public / private Octet Key Pair JWK from the specified JSON
735         * object representation.
736         *
737         * @param jsonObject The JSON object to parse. Must not be
738         *                   {@code null}.
739         *
740         * @return The public / private Octet Key Pair JWK.
741         *
742         * @throws ParseException If the JSON object couldn't be parsed to an
743         *                        Octet Key Pair JWK.
744         */
745        public static OctetKeyPair parse(final JSONObject jsonObject)
746                throws ParseException {
747                
748                // Parse the mandatory parameters first
749                Curve crv = Curve.parse(JSONObjectUtils.getString(jsonObject, "crv"));
750                Base64URL x = new Base64URL(JSONObjectUtils.getString(jsonObject, "x"));
751                
752                // Check key type
753                KeyType kty = JWKMetadata.parseKeyType(jsonObject);
754                
755                if (kty != KeyType.OKP) {
756                        throw new ParseException("The key type \"kty\" must be OKP", 0);
757                }
758                
759                // Get optional private key
760                Base64URL d = null;
761                if (jsonObject.get("d") != null) {
762                        d = new Base64URL(JSONObjectUtils.getString(jsonObject, "d"));
763                }
764                
765                
766                try {
767                        if (d == null) {
768                                // Public key
769                                return new OctetKeyPair(crv, x,
770                                        JWKMetadata.parseKeyUse(jsonObject),
771                                        JWKMetadata.parseKeyOperations(jsonObject),
772                                        JWKMetadata.parseAlgorithm(jsonObject),
773                                        JWKMetadata.parseKeyID(jsonObject),
774                                        JWKMetadata.parseX509CertURL(jsonObject),
775                                        JWKMetadata.parseX509CertThumbprint(jsonObject),
776                                        JWKMetadata.parseX509CertSHA256Thumbprint(jsonObject),
777                                        JWKMetadata.parseX509CertChain(jsonObject),
778                                        null);
779                                
780                        } else {
781                                // Key pair
782                                return new OctetKeyPair(crv, x, d,
783                                        JWKMetadata.parseKeyUse(jsonObject),
784                                        JWKMetadata.parseKeyOperations(jsonObject),
785                                        JWKMetadata.parseAlgorithm(jsonObject),
786                                        JWKMetadata.parseKeyID(jsonObject),
787                                        JWKMetadata.parseX509CertURL(jsonObject),
788                                        JWKMetadata.parseX509CertThumbprint(jsonObject),
789                                        JWKMetadata.parseX509CertSHA256Thumbprint(jsonObject),
790                                        JWKMetadata.parseX509CertChain(jsonObject),
791                                        null);
792                        }
793                        
794                } catch (IllegalArgumentException ex) {
795                        
796                        // Conflicting 'use' and 'key_ops'
797                        throw new ParseException(ex.getMessage(), 0);
798                }
799        }
800}