View Javadoc

1   package com.atlassian.asap.nimbus.parser;
2   
3   import com.atlassian.asap.api.AlgorithmType;
4   import com.atlassian.asap.api.JwsHeader;
5   import com.atlassian.asap.api.Jwt;
6   import com.atlassian.asap.api.JwtBuilder;
7   import com.atlassian.asap.api.JwtClaims;
8   import com.atlassian.asap.api.SigningAlgorithm;
9   import com.atlassian.asap.core.exception.SignatureMismatchException;
10  import com.atlassian.asap.core.exception.UnsupportedAlgorithmException;
11  import com.atlassian.asap.core.parser.VerifiableJwt;
12  import com.google.common.collect.Maps;
13  import com.nimbusds.jose.JOSEException;
14  import com.nimbusds.jose.JWSObject;
15  import com.nimbusds.jose.JWSVerifier;
16  import com.nimbusds.jose.crypto.ECDSAVerifier;
17  import com.nimbusds.jose.crypto.RSASSAVerifier;
18  import com.nimbusds.jwt.JWTClaimsSet;
19  import net.minidev.json.JSONObject;
20  import org.slf4j.Logger;
21  import org.slf4j.LoggerFactory;
22  
23  import javax.json.JsonObject;
24  import java.security.Provider;
25  import java.security.PublicKey;
26  import java.security.interfaces.ECPublicKey;
27  import java.security.interfaces.RSAPublicKey;
28  import java.util.Date;
29  import java.util.Locale;
30  import java.util.Map;
31  import java.util.Optional;
32  import java.util.Set;
33  
34  import static com.google.common.base.Predicates.in;
35  import static com.google.common.base.Predicates.not;
36  
37  public class NimbusVerifiableJwt implements VerifiableJwt {
38      private static final Logger logger = LoggerFactory.getLogger(NimbusVerifiableJwt.class);
39      private static final Set<String> REGISTERED_CLAIM_NAMES = JWTClaimsSet.getRegisteredNames();
40  
41      private final Jwt unverifiedJwt;
42      private final JWSObject jwsObject;
43  
44      private final Provider provider;
45  
46      public NimbusVerifiableJwt(final Jwt unverifiedJwt, final JWSObject jwsObject, final Provider provider) {
47          this.unverifiedJwt = unverifiedJwt;
48          this.jwsObject = jwsObject;
49          this.provider = provider;
50      }
51  
52      /**
53       * Factory method to create a signature verifiable jwt.
54       *
55       * @param jwsObject a json web signature object
56       * @param claims    jwt claims set
57       * @param provider  Java Security provider
58       * @return a signature verifiable jwt
59       * @throws UnsupportedAlgorithmException if the signing algorithm is not supported
60       */
61      public static VerifiableJwt buildVerifiableJwt(final JWSObject jwsObject, final JWTClaimsSet claims, final Provider provider)
62              throws UnsupportedAlgorithmException {
63          Map<String, Object> customClaimsMap = Maps.filterKeys(claims.getClaims(), not(in(REGISTERED_CLAIM_NAMES)));
64          JsonObject customClaims = (JsonObject) NimbusJsr353Translator.nimbusToJsr353(new JSONObject(customClaimsMap));
65  
66          Jwt unverifiedJwt = JwtBuilder.newJwt()
67                  .algorithm(getSigningAlgorithm(jwsObject.getHeader().getAlgorithm().getName()))
68                  .keyId(jwsObject.getHeader().getKeyID())
69                  .issuer(claims.getIssuer())
70                  .jwtId(claims.getJWTID())
71                  .subject(Optional.ofNullable(claims.getSubject()))
72                  .audience(claims.getAudience())
73                  .expirationTime(claims.getExpirationTime().toInstant())
74                  .issuedAt(claims.getIssueTime().toInstant())
75                  .notBefore(Optional.ofNullable(claims.getNotBeforeTime()).map(Date::toInstant))
76                  .customClaims(customClaims)
77                  .build();
78  
79          return new NimbusVerifiableJwt(unverifiedJwt, jwsObject, provider);
80      }
81  
82      @Override
83      public void verifySignature(final PublicKey publicKey) throws SignatureMismatchException, UnsupportedAlgorithmException {
84          try {
85              if (!jwsObject.verify(verifierFor(unverifiedJwt.getHeader().getAlgorithm(), publicKey, provider))) {
86                  // log at debug level because this can be caused by invalid input
87                  logger.debug("Invalid JWT signature");
88                  throw new SignatureMismatchException("Invalid JWT signature");
89              }
90          } catch (JOSEException e) {
91              logger.error("Unexpected error when verifying a JWT signature", e);
92              throw new SignatureMismatchException("Unexpected error when verifying JWT signature");
93          }
94      }
95  
96      private static JWSVerifier verifierFor(final SigningAlgorithm algorithm, final PublicKey publicKey, final Provider provider)
97              throws UnsupportedAlgorithmException {
98          if ((algorithm.type() == AlgorithmType.RSA || algorithm.type() == AlgorithmType.RSASSA_PSS) &&
99                  publicKey instanceof RSAPublicKey) {
100             RSASSAVerifier rsassaVerifier = new RSASSAVerifier((RSAPublicKey) publicKey);
101             rsassaVerifier.getJCAContext().setProvider(provider);
102             return rsassaVerifier;
103         } else if (algorithm.type() == AlgorithmType.ECDSA && publicKey instanceof ECPublicKey) {
104             try {
105                 ECPublicKey ecPublicKey = (ECPublicKey) publicKey;
106                 ECDSAVerifier ecdsaVerifier = new ECDSAVerifier(ecPublicKey);
107                 ecdsaVerifier.getJCAContext().setProvider(provider);
108                 return ecdsaVerifier;
109             } catch (JOSEException e) {
110                 // log at debug level since this can be caused by invalid input
111                 logger.debug("Unsupported signing algorithm {} or public key algorithm {}", algorithm, publicKey.getAlgorithm());
112                 throw new UnsupportedAlgorithmException(algorithm.name(), e);
113             }
114         } else {
115             // log at debug level since this can be caused by invalid input
116             logger.debug("Unsupported signing algorithm {} or public key algorithm {}", algorithm, publicKey.getAlgorithm());
117             throw new UnsupportedAlgorithmException(algorithm.name());
118         }
119     }
120 
121 
122     private static SigningAlgorithm getSigningAlgorithm(String algorithm) throws UnsupportedAlgorithmException {
123         try {
124             return SigningAlgorithm.valueOf(algorithm.toUpperCase(Locale.ROOT));
125         } catch (IllegalArgumentException e) {
126             throw new UnsupportedAlgorithmException(algorithm + " is not a supported asymmetric JWS algorithm");
127         }
128     }
129 
130     @Override
131     public JwsHeader getHeader() {
132         return unverifiedJwt.getHeader();
133     }
134 
135     @Override
136     public JwtClaims getClaims() {
137         return unverifiedJwt.getClaims();
138     }
139 }