001package com.nimbusds.jose.mint;
002
003
004import java.util.List;
005
006import com.nimbusds.jose.JOSEException;
007import com.nimbusds.jose.JWSHeader;
008import com.nimbusds.jose.JWSObject;
009import com.nimbusds.jose.Payload;
010import com.nimbusds.jose.crypto.factories.DefaultJWSSignerFactory;
011import com.nimbusds.jose.jwk.JWK;
012import com.nimbusds.jose.jwk.JWKMatcher;
013import com.nimbusds.jose.jwk.JWKSelector;
014import com.nimbusds.jose.jwk.JWKSet;
015import com.nimbusds.jose.jwk.source.JWKSource;
016import com.nimbusds.jose.proc.JWKSecurityContext;
017import com.nimbusds.jose.proc.SecurityContext;
018import com.nimbusds.jose.produce.JWSSignerFactory;
019import com.nimbusds.jwt.JWTClaimsSet;
020
021
022/**
023 * Default minter of {@link JWSObject JSON Web Signature (JWS) objects} and
024 * {@link com.nimbusds.jwt.SignedJWT signed JSON Web Tokens} (JWTs).
025 *
026 * <p>Must be configured with the following:
027 *
028 * <ul>
029 *     <li>A {@link #setJWKSource} JSON Web Key (JWK) source} to select a
030 *     signing key. The default key selection procedure is based on the
031 *     {@link JWSHeader}. To customise it pass a suitable
032 *     {@link SecurityContext context}.</li>
033 * </ul>
034 *
035 * <p>An optional {@link SecurityContext context} parameter is available to
036 * facilitate passing of additional data between the caller and the underlying
037 * selector of key candidates (in both directions).
038 *
039 * <p>See sections 6 of RFC 7515 (JWS) for guidelines on key selection.
040 *
041 * <p>This minter adds any key-identifying header based on the JWK that it
042 * selects.
043 *
044 * @author Josh Cummings
045 * @version 2021-01-14
046 */
047public class DefaultJWSMinter<C extends SecurityContext> implements ConfigurableJWSMinter<C> {
048        private JWKSource<C> jwkSource;
049
050        private JWSSignerFactory jwsSignerFactory = new DefaultJWSSignerFactory();
051
052        
053        /**
054         * Creates a new JSON Web Signature (JWS) object using the provided
055         * {@link JWSHeader} and {@link Payload}. To create a signed JSON Web
056         * Token (JWT) use the {@link JWTClaimsSet#toPayload()} method to
057         * obtain a {@link Payload} representation of the JWT claims.
058         *
059         * <p>Derives the signing key from the {@link JWSHeader} as well as any
060         * application-specific {@link SecurityContext context}.
061         *
062         * <p>If multiple keys are matched against the header's criteria, the
063         * first will be used to sign the object. To customise the key
064         * selection you can set a custom {@link JWKSource} like so:
065         *
066         * <pre>
067         * public static class MyJWKSource implements JWKSource&lt;SecurityContext&gt; {
068         *     private final JWKSource&lt;SecurityContext&gt; delegate;
069         *
070         *     public List&lt;JWK&gt; get(final JWKSelector jwkSelector, final SecurityContext context)
071         *         throws KeySourceException {
072         *         List&lt;JWK&gt; jwks = this.delegate.get(jwkSelector, context);
073         *         return jwks.get(jwks.size() - 1); // get last one instead
074         *     }
075         * }
076         *
077         * minter.setJWKSource(new MyJWKSource(jwkSource));
078         * </pre>
079         *
080         * <p>or you can select your own {@link JWK} and do:
081         *
082         * <pre>
083         * JWK jwk = findJWK();
084         * minter.mint(header, claims, new JWKSecurityContext(jwks));
085         * </pre>
086         *
087         * <p>Once the key is discovered, adds any headers related to the
088         * discovered signing key, including {@code kid}, {@code x5u},
089         * {@code x5c}, and {@code x5t#256}.
090         *
091         * <p>All other headers and claims remain as-is. This method expects
092         * the caller to add the {@code typ}, {@code alg}, and any other needed
093         * headers.
094         *
095         * @param header  The {@link JWSHeader} to use, less any
096         *                key-identifying headers, which this method will
097         *                derive.
098         * @param payload The {@link Payload}.
099         * @param context A {@link SecurityContext}, {@code null} if not
100         *                specified.
101         *
102         * @return The signed JWS object.
103         *
104         * @throws JOSEException If the instance is improperly configured, if
105         * no appropriate JWK could be found, or if signing failed.
106         */
107        @Override
108        public JWSObject mint(final JWSHeader header, final Payload payload, final C context)
109                throws JOSEException {
110                
111                List<JWK> jwks = jwks(header, context);
112                if (jwks.isEmpty()) {
113                        throw new JOSEException("No JWKs found for signing");
114                }
115                JWK jwk = jwks.get(0);
116                JWSHeader withJwk = new JWSHeader.Builder(header)
117                                .keyID(jwk.getKeyID())
118                                .x509CertURL(jwk.getX509CertURL())
119                                .x509CertChain(jwk.getX509CertChain())
120                                .x509CertSHA256Thumbprint(jwk.getX509CertSHA256Thumbprint())
121                                .x509CertThumbprint(jwk.getX509CertThumbprint())
122                                .build();
123                JWSObject jws = new JWSObject(withJwk, payload);
124                if (this.jwsSignerFactory == null) {
125                        throw new JOSEException("No JWS signer factory configured");
126                }
127                jws.sign(this.jwsSignerFactory.createJWSSigner(jwk));
128                return jws;
129        }
130
131        
132        private List<JWK> jwks(final JWSHeader header, final C context) throws JOSEException {
133                JWKMatcher matcher = JWKMatcher.forJWSHeader(header);
134                JWKSelector selector = new JWKSelector(matcher);
135                if (context instanceof JWKSecurityContext) {
136                        return selector.select(new JWKSet(((JWKSecurityContext) context).getKeys()));
137                }
138                if (this.jwkSource == null) {
139                        throw new JOSEException("No JWK source configured");
140                }
141                return this.jwkSource.get(selector, context);
142        }
143
144        
145        @Override
146        public JWKSource<C> getJWKSource() {
147                return jwkSource;
148        }
149
150        
151        @Override
152        public void setJWKSource(final JWKSource<C> jwkSource) {
153                this.jwkSource = jwkSource;
154        }
155
156        @Override
157        public JWSSignerFactory getJWSSignerFactory() {
158                return jwsSignerFactory;
159        }
160
161        
162        @Override
163        public void setJWSSignerFactory(final JWSSignerFactory jwsSignerFactory) {
164                this.jwsSignerFactory = jwsSignerFactory;
165        }
166}