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.net.URISyntaxException;
023import java.text.ParseException;
024import java.util.Objects;
025
026import net.jcip.annotations.Immutable;
027
028import com.nimbusds.jose.JOSEException;
029import com.nimbusds.jose.util.Base64URL;
030
031
032/**
033 * JSON Web Key (JWK) thumbprint URI.
034 *
035 * <p>Example SHA-256 thumbprint URI:
036 *
037 * <pre>
038 * urn:ietf:params:oauth:jwk-thumbprint:sha-256:NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs
039 * </pre>
040 *
041 * <p>See draft-ietf-oauth-jwk-thumbprint-uri-01
042 *
043 * @author Vladimir Dzhuvinov
044 * @version 2024-04-20
045 */
046@Immutable
047public class ThumbprintURI {
048        
049        
050        /**
051         * The URI prefix of JWK thumbprints.
052         */
053        public static final String PREFIX = "urn:ietf:params:oauth:jwk-thumbprint:";
054        
055        
056        /**
057         * The hash algorithm.
058         */
059        private final String hashAlg;
060        
061        
062        /**
063         * The thumbprint value.
064         */
065        private final Base64URL thumbprint;
066        
067        
068        /**
069         * Creates a new JWK thumbprint URI.
070         *
071         * @param hashAlg    The hash algorithm. Must not be {@code null}.
072         * @param thumbprint The thumbprint value. Must not be {@code null}.
073         */
074        public ThumbprintURI(final String hashAlg, final Base64URL thumbprint) {
075                if (hashAlg.isEmpty()) {
076                        throw new IllegalArgumentException("The hash algorithm must not be empty");
077                }
078                this.hashAlg = hashAlg;
079                
080                if (thumbprint.toString().isEmpty()) {
081                        throw new IllegalArgumentException("The thumbprint must not be empty");
082                }
083                this.thumbprint = thumbprint;
084        }
085        
086        
087        /**
088         * Returns the hash algorithm string.
089         *
090         * @return The hash algorithm string.
091         */
092        public String getAlgorithmString() {
093                
094                return hashAlg;
095        }
096        
097        
098        /**
099         * Returns the underlying thumbprint value.
100         *
101         * @return The thumbprint value.
102         */
103        public Base64URL getThumbprint() {
104                
105                return thumbprint;
106        }
107        
108        
109        /**
110         * Returns the {@link URI} representation.
111         *
112         * @return The {@link URI} representation.
113         */
114        public URI toURI() {
115                
116                return URI.create(toString());
117        }
118        
119        
120        @Override
121        public String toString() {
122                
123                return PREFIX + hashAlg + ":" + thumbprint;
124        }
125        
126        
127        @Override
128        public boolean equals(Object o) {
129                if (this == o) return true;
130                if (!(o instanceof ThumbprintURI)) return false;
131                ThumbprintURI that = (ThumbprintURI) o;
132                return hashAlg.equals(that.hashAlg) && getThumbprint().equals(that.getThumbprint());
133        }
134        
135        
136        @Override
137        public int hashCode() {
138                return Objects.hash(hashAlg, getThumbprint());
139        }
140        
141        
142        /**
143         * Computes the SHA-256 JWK thumbprint URI for the specified JWK.
144         *
145         * @param jwk The JWK. Must not be {@code null}.
146         *
147         * @return The SHA-256 JWK thumbprint URI.
148         *
149         * @throws JOSEException If the SHA-256 hash algorithm is not
150         *                       supported.
151         */
152        public static ThumbprintURI compute(final JWK jwk)
153                throws JOSEException {
154                
155                return new ThumbprintURI("sha-256", jwk.computeThumbprint());
156        }
157        
158        
159        /**
160         * Parses a JWK thumbprint URI from the specified URI.
161         *
162         * @param uri The URI. Must not be {@code null}.
163         *
164         * @return The JWK thumbprint URI.
165         *
166         * @throws ParseException If the URI is illegal.
167         */
168        public static ThumbprintURI parse(final URI uri)
169                throws ParseException {
170                
171                String uriString = uri.toString();
172                if (! uriString.startsWith(PREFIX)) {
173                        throw new ParseException("Illegal JWK thumbprint prefix", 0);
174                }
175                
176                String valuesString = uriString.substring(PREFIX.length());
177                if (valuesString.isEmpty()) {
178                        throw new ParseException("Illegal JWK thumbprint: Missing value", 0);
179                }
180                
181                String[] values = valuesString.split(":");
182                if (values.length != 2) {
183                        throw new ParseException("Illegal JWK thumbprint: Unexpected number of components", 0);
184                }
185                if (values[0].isEmpty()) {
186                        throw new ParseException("Illegal JWK thumbprint: The hash algorithm must not be empty", 0);
187                }
188                // Empty thumbprint prevented by split method
189                
190                return new ThumbprintURI(values[0], new Base64URL(values[1]));
191        }
192        
193        
194        /**
195         * Parses a JWK thumbprint URI from the specified URI string.
196         *
197         * @param s The URI string. Must not be {@code null}.
198         *
199         * @return The JWK thumbprint URI.
200         *
201         * @throws ParseException If the URI string is illegal.
202         */
203        public static ThumbprintURI parse(final String s)
204                throws ParseException {
205                
206                try {
207                        return parse(new URI(s));
208                } catch (URISyntaxException e) {
209                        throw new ParseException(e.getMessage(), 0);
210                }
211        }
212}