001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2020, 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.oauth2.sdk.dpop;
019
020
021import java.net.URI;
022import java.nio.charset.StandardCharsets;
023import java.security.MessageDigest;
024import java.security.NoSuchAlgorithmException;
025import java.util.Date;
026
027import com.nimbusds.jose.JOSEException;
028import com.nimbusds.jose.util.Base64URL;
029import com.nimbusds.jwt.JWTClaimsSet;
030import com.nimbusds.oauth2.sdk.id.JWTID;
031import com.nimbusds.oauth2.sdk.token.AccessToken;
032import com.nimbusds.oauth2.sdk.util.StringUtils;
033
034
035/**
036 * DPoP utilities.
037 */
038public final class DPoPUtils {
039        
040        
041        /**
042         * Creates a new DPoP JWT claims set.
043         *
044         * @param jti         The JWT ID. Must not be {@code null}.
045         * @param htm         The HTTP request method. Must not be
046         *                    {@code null}.
047         * @param htu         The HTTP URI, without a query or fragment. Must
048         *                    not be {@code null}.
049         * @param iat         The issue time. Must not be {@code null}.
050         * @param accessToken The access token for the access token hash
051         *                    ("ath") claim computation, {@code null} if not
052         *                    specified.
053         *
054         * @return The JWT claims set.
055         *
056         * @throws JOSEException If a cryptographic exception was encountered.
057         */
058        public static JWTClaimsSet createJWTClaimsSet(final JWTID jti,
059                                                      final String htm,
060                                                      final URI htu,
061                                                      final Date iat,
062                                                      final AccessToken accessToken)
063                throws JOSEException {
064                
065                if (StringUtils.isBlank(htm)) {
066                        throw new IllegalArgumentException("The HTTP method (htu) is required");
067                }
068                
069                if (htu.getQuery() != null) {
070                        throw new IllegalArgumentException("The HTTP URI (htu) must not have a query");
071                }
072                
073                if (htu.getFragment() != null) {
074                        throw new IllegalArgumentException("The HTTP URI (htu) must not have a fragment");
075                }
076                
077                if (iat == null) {
078                        throw new IllegalArgumentException("The issue time (iat) is required");
079                }
080                
081                JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder()
082                        .jwtID(jti.getValue())
083                        .claim("htm", htm)
084                        .claim("htu", htu.toString())
085                        .issueTime(iat);
086                
087                if (accessToken != null) {
088                        builder = builder.claim("ath", computeSHA256(accessToken).toString());
089                }
090                
091                return builder.build();
092        }
093        
094        
095        /**
096         * Computes a SHA-256 hash for the specified access token.
097         *
098         * @param accessToken The access token. Must not be {@code null}.
099         *
100         * @return The hash, BASE64 URL encoded.
101         *
102         * @throws JOSEException If hashing failed.
103         */
104        public static Base64URL computeSHA256(final AccessToken accessToken)
105                throws JOSEException {
106                
107                byte[] hash;
108                try {
109                        MessageDigest md = MessageDigest.getInstance("SHA-256");
110                        hash = md.digest(accessToken.getValue().getBytes(StandardCharsets.UTF_8));
111                } catch (NoSuchAlgorithmException e) {
112                        throw new JOSEException(e.getMessage(), e);
113                }
114                
115                return Base64URL.encode(hash);
116        }
117        
118        
119      /**
120       *Prevents public instantiation.
121       */
122      private DPoPUtils() {}
123}