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<SecurityContext> { 068 * private final JWKSource<SecurityContext> delegate; 069 * 070 * public List<JWK> get(final JWKSelector jwkSelector, final SecurityContext context) 071 * throws KeySourceException { 072 * List<JWK> 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}