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