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
020import com.nimbusds.jose.util.JSONObjectUtils;
021import com.nimbusds.jwt.util.DateUtils;
022import net.jcip.annotations.Immutable;
023
024import java.io.Serializable;
025import java.text.ParseException;
026import java.util.Date;
027import java.util.Map;
028import java.util.Objects;
029
030
031/**
032 * Key revocation.
033 *
034 * @author Vladimir Dzhuvinov
035 * @version 2024-04-27
036 */
037@Immutable
038public final class KeyRevocation implements Serializable {
039
040
041        /**
042         * Key revocation reason.
043         */
044        public static class Reason {
045
046
047                /**
048                 * General or unspecified reason for the key status change.
049                 */
050                public static final Reason UNSPECIFIED = new Reason("unspecified");
051
052
053                /**
054                 * The private key is believed to have been compromised.
055                 */
056                public static final Reason COMPROMISED = new Reason("compromised");
057
058
059                /**
060                 * The key is no longer active.
061                 */
062                public static final Reason SUPERSEDED = new Reason("superseded");
063
064
065                /**
066                 * The reason value.
067                 */
068                private final String value;
069
070
071                /**
072                 * Creates a new reason with the specified value.
073                 *
074                 * @param value The reason value. Must not be {@code null}
075                 */
076                public Reason(final String value) {
077                        this.value = Objects.requireNonNull(value);
078                }
079
080
081                /**
082                 * Returns the reason value.
083                 *
084                 * @return The reason value.
085                 */
086                public String getValue() {
087                        return value;
088                }
089
090
091                @Override
092                public String toString() {
093                        return getValue();
094                }
095
096                @Override
097                public boolean equals(Object o) {
098                        if (this == o) return true;
099                        if (!(o instanceof Reason)) return false;
100                        Reason reason = (Reason) o;
101                        return Objects.equals(getValue(), reason.getValue());
102                }
103
104                @Override
105                public int hashCode() {
106                        return Objects.hashCode(getValue());
107                }
108
109
110                /**
111                 * Parses a reason from the specified string.
112                 *
113                 * @param s The string. Must not be {@code null}.
114                 *
115                 * @return The reason.
116                 */
117                public static Reason parse(final String s) {
118                        if (Reason.UNSPECIFIED.getValue().equals(s)) {
119                                return Reason.UNSPECIFIED;
120                        } else if (Reason.COMPROMISED.getValue().equals(s)) {
121                                return Reason.COMPROMISED;
122                        } else if (Reason.SUPERSEDED.getValue().equals(s)) {
123                                return Reason.SUPERSEDED;
124                        } else {
125                                return new Reason(s);
126                        }
127                }
128        }
129
130
131        /**
132         * The revocation time.
133         */
134        private final Date revokedAt;
135
136
137        /**
138         * The reason.
139         */
140        private final Reason reason;
141
142
143        /**
144         * Creates a new key revocation.
145         *
146         * @param revokedAt The revocation time.
147         * @param reason    The reason.
148         */
149        public KeyRevocation(final Date revokedAt, final Reason reason) {
150                this.revokedAt = Objects.requireNonNull(revokedAt);
151                this.reason = reason;
152        }
153
154
155        /**
156         * Returns the revocation time ({@code revoked_at}) parameter.
157         *
158         * @return The revocation time.
159         */
160        public Date getRevocationTime() {
161                return revokedAt;
162        }
163
164
165        /**
166         * Returns the reason ({@code reason}) parameter.
167         *
168         * @return The reason.
169         */
170        public Reason getReason() {
171                return reason;
172        }
173
174
175        @Override
176        public boolean equals(Object o) {
177                if (this == o) return true;
178                if (!(o instanceof KeyRevocation)) return false;
179                KeyRevocation that = (KeyRevocation) o;
180                return Objects.equals(revokedAt, that.revokedAt) && Objects.equals(getReason(), that.getReason());
181        }
182
183
184        @Override
185        public int hashCode() {
186                return Objects.hash(revokedAt, getReason());
187        }
188
189
190        /**
191         * Returns a JSON object representation of this key revocation.
192         *
193         * @return The JSON object representation.
194         */
195        public Map<String, Object> toJSONObject() {
196                Map<String, Object> o = JSONObjectUtils.newJSONObject();
197                o.put("revoked_at", DateUtils.toSecondsSinceEpoch(getRevocationTime()));
198                if (getReason() != null) {
199                        o.put("reason", getReason().getValue());
200                }
201                return o;
202        }
203
204
205        /**
206         * Parses a key revocation from the specified JSON object.
207         *
208         * @param jsonObject The JSON object. Must not be {@code null}.
209         *
210         * @return The key revocation.
211         *
212         * @throws ParseException If parsing failed.
213         */
214        public static KeyRevocation parse(final Map<String, Object> jsonObject)
215                throws ParseException {
216                Date revokedAt = DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getLong(jsonObject, "revoked_at"));
217                Reason reason = null;
218                if (jsonObject.get("reason") != null) {
219                        reason = Reason.parse(JSONObjectUtils.getString(jsonObject, "reason"));
220                }
221                return new KeyRevocation(revokedAt, reason);
222        }
223}