AuthzInfo.java
- /*******************************************************************************
- * Copyright (c) 2019, RISE AB
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * 3. Neither the name of the copyright holder nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *******************************************************************************/
- package se.sics.ace.rs;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Base64;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- import java.util.logging.Level;
- import java.util.logging.Logger;
- import org.bouncycastle.crypto.InvalidCipherTextException;
- import org.eclipse.californium.oscore.CoapOSException;
- import org.eclipse.californium.oscore.OSCoreCtxDB;
- import com.upokecenter.cbor.CBORObject;
- import com.upokecenter.cbor.CBORType;
- import org.eclipse.californium.cose.CoseException;
- import se.sics.ace.AceException;
- import se.sics.ace.Constants;
- import se.sics.ace.Endpoint;
- import se.sics.ace.Message;
- import se.sics.ace.TimeProvider;
- import se.sics.ace.Util;
- import se.sics.ace.coap.rs.oscoreProfile.OscoreCtxDbSingleton;
- import se.sics.ace.cwt.CWT;
- import se.sics.ace.cwt.CwtCryptoCtx;
- /**
- * This class implements the /authz_info endpoint at the RS that receives
- * access tokens, verifies if they are valid and then stores them.
- *
- * Note this implementation requires the following claims in a CWT:
- * iss, sub, scope, aud.
- *
- * @author Ludwig Seitz and Marco Tiloca
- *
- */
- public class AuthzInfo implements Endpoint, AutoCloseable {
-
- /**
- * The logger
- */
- private static final Logger LOGGER
- = Logger.getLogger(AuthzInfo.class.getName());
- /**
- * The acceptable issuers
- */
- private List<String> issuers;
-
- /**
- * Provides system time
- */
- private TimeProvider time;
-
- /**
- * Handles introspection of tokens
- */
- private IntrospectionHandler intro;
-
- /**
- * Handles audience validation
- */
- private AudienceValidator audience;
-
- /**
- * The crypto context to use with the AS
- */
- private CwtCryptoCtx ctx;
-
- /**
- * Flag to indicate if we need to check cnonces
- */
- private boolean checkCnonce;
-
- /**
- * Each set of the list refers to a different size of Recipient IDs.
- * The element with index 0 includes as elements Recipient IDs with size 1 byte.
- */
- private static List<Set<Integer>> usedRecipientIds = new ArrayList<Set<Integer>>();
-
- /**
- * Constructor. Needs an initialized TokenRepository.
- *
- * @param issuers the list of acceptable issuer of access tokens
- * @param time the time provider
- * @param intro the introspection handler (can be null)
- * @param rsId the identifier of the Resource Server
- * @param audience the audience validator
- * @param ctx the crypto context to use with the As
- * @param keyDerivationKey the key derivation key to use with the As, it can be null
- * @param derivedKeySize the size in bytes of symmetric keys derived with the key derivation key
- * @param tokenFile the file where to save tokens when persisting
- * @param scopeValidator the application specific scope validator
- * @param checkCnonce true if this RS uses cnonces for freshness validation
- * @throws AceException if the token repository is not initialized
- * @throws IOException
- */
- public AuthzInfo(List<String> issuers,
- TimeProvider time, IntrospectionHandler intro, String rsId,
- AudienceValidator audience, CwtCryptoCtx ctx, byte[] keyDerivationKey, int derivedKeySize,
- String tokenFile, ScopeValidator scopeValidator, boolean checkCnonce)
- throws AceException, IOException {
- if (TokenRepository.getInstance()==null) {
- TokenRepository.create(scopeValidator, tokenFile, ctx, keyDerivationKey, derivedKeySize, time, rsId);
- }
- this.issuers = new ArrayList<>();
- this.issuers.addAll(issuers);
- this.time = time;
- this.intro = intro;
- this.audience = audience;
- this.ctx = ctx;
- this.checkCnonce = checkCnonce;
-
- for (int i = 0; i < 4; i++) {
- // Empty sets of assigned Sender IDs; one set for each possible Sender ID size in bytes.
- // The set with index 0 refers to Sender IDs with size 1 byte
- usedRecipientIds.add(new HashSet<Integer>());
- }
-
- }
- @Override
- public synchronized Message processMessage(Message msg) {
- LOGGER.log(Level.INFO, "received message: " + msg);
- CBORObject token = null;
- try {
- token = CBORObject.DecodeFromBytes(msg.getRawPayload());
- } catch (Exception e) {
- LOGGER.info("Invalid payload at authz-info: " + e.getMessage());
- CBORObject map = CBORObject.NewMap();
- map.Add(Constants.ERROR, Constants.INVALID_REQUEST);
- return msg.failReply(Message.FAIL_BAD_REQUEST, map);
- }
- return processToken(token, msg);
- }
-
- protected synchronized Message processToken(CBORObject token, Message msg) {
- Map<Short, CBORObject> claims = null;
-
- byte[] recipientId = null;
- boolean recipientIdFound = false;
- //1. Check whether it is a CWT or REF type
- if (token.getType().equals(CBORType.ByteString)) {
- try {
- claims = processReferenceToken(token);
- } catch (AceException e) {
- LOGGER.severe("Message processing aborted: " + e.getMessage());
- return msg.failReply(Message.FAIL_INTERNAL_SERVER_ERROR, null);
- } catch (IntrospectionException e) {
- LOGGER.info("Introspection error, "
- + "message processing aborted: " + e.getMessage());
- if (e.getMessage().isEmpty()) {
- return msg.failReply(Message.FAIL_INTERNAL_SERVER_ERROR, null);
- }
- CBORObject map = CBORObject.NewMap();
- map.Add(Constants.ERROR, Constants.INVALID_REQUEST);
- map.Add(Constants.ERROR_DESCRIPTION, e.getMessage());
- return msg.failReply(e.getCode(), map);
- }
- } else if (token.getType().equals(CBORType.Array)) {
- try {
- claims = processCWT(token);
- } catch (IntrospectionException e) {
- LOGGER.info("Introspection error, "
- + "message processing aborted: " + e.getMessage());
- if (e.getMessage().isEmpty()) {
- return msg.failReply(Message.FAIL_INTERNAL_SERVER_ERROR, null);
- }
- CBORObject map = CBORObject.NewMap();
- map.Add(Constants.ERROR, Constants.INVALID_REQUEST);
- map.Add(Constants.ERROR_DESCRIPTION, e.getMessage());
- return msg.failReply(e.getCode(), map);
- } catch (AceException | CoseException
- | InvalidCipherTextException e) {
- LOGGER.info("Token invalid: " + e.getMessage());
- CBORObject map = CBORObject.NewMap();
- map.Add(Constants.ERROR, Constants.UNAUTHORIZED_CLIENT);
- map.Add(Constants.ERROR_DESCRIPTION, "Token is invalid");
- return msg.failReply(Message.FAIL_BAD_REQUEST, map);
- } catch (Exception e) {
- LOGGER.severe("Unsupported key wrap algorithm in token: "
- + e.getMessage());
- return msg.failReply(Message.FAIL_NOT_IMPLEMENTED, null);
- }
- } else {
- CBORObject map = CBORObject.NewMap();
- map.Add(Constants.ERROR, Constants.INVALID_REQUEST);
- map.Add(Constants.ERROR_DESCRIPTION, "Unknown token format");
- LOGGER.info("Message processing aborted: invalid request");
- return msg.failReply(Message.FAIL_BAD_REQUEST, map);
- }
-
- //2. Check if the token is active, this will only be present if we
- // did introspect
- CBORObject active = claims.get(Constants.ACTIVE);
- if (active != null && active.isFalse()) {
- CBORObject map = CBORObject.NewMap();
- map.Add(Constants.ERROR, Constants.UNAUTHORIZED_CLIENT);
- map.Add(Constants.ERROR_DESCRIPTION, "Token is not active");
- LOGGER.info("Message processing aborted: Token is not active");
- return msg.failReply(Message.FAIL_UNAUTHORIZED, map);
- }
- //3. Check that the token is not expired (exp)
- CBORObject exp = claims.get(Constants.EXP);
- if (exp != null && exp.AsNumber().ToInt64Checked() < this.time.getCurrentTime()) {
- CBORObject map = CBORObject.NewMap();
- map.Add(Constants.ERROR, Constants.UNAUTHORIZED_CLIENT);
- map.Add(Constants.ERROR_DESCRIPTION, "Token is expired");
- LOGGER.log(Level.INFO, "Message processing aborted: "
- + "Token is expired");
- return msg.failReply(Message.FAIL_UNAUTHORIZED, map);
- }
-
- //4. Check if we accept the issuer (iss)
- CBORObject iss = claims.get(Constants.ISS);
- if (iss != null) {
- if (!this.issuers.contains(iss.AsString())) {
- CBORObject map = CBORObject.NewMap();
- map.Add(Constants.ERROR, Constants.INVALID_REQUEST);
- map.Add(Constants.ERROR_DESCRIPTION, "Token issuer unknown");
- LOGGER.log(Level.INFO, "Message processing aborted: "
- + "Token issuer unknown");
- return msg.failReply(Message.FAIL_UNAUTHORIZED, map);
- }
- }
-
- //5. Check if we are the audience (aud)
- CBORObject audCbor = claims.get(Constants.AUD);
- if (audCbor == null) {
- CBORObject map = CBORObject.NewMap();
- map.Add(Constants.ERROR, Constants.INVALID_REQUEST);
- map.Add(Constants.ERROR_DESCRIPTION, "Token has no audience");
- LOGGER.log(Level.INFO, "Message processing aborted: "
- + "Token has no audience");
- return msg.failReply(Message.FAIL_BAD_REQUEST, map);
- }
- String aud;
- if (audCbor.getType().equals(CBORType.TextString)) {
- aud = audCbor.AsString();
- } else {//Error
- CBORObject map = CBORObject.NewMap();
- map.Add(Constants.ERROR, Constants.INVALID_REQUEST);
- map.Add(Constants.ERROR_DESCRIPTION, "Audience malformed");
- LOGGER.log(Level.INFO, "Message processing aborted: "
- + "audience malformed");
- return msg.failReply(Message.FAIL_BAD_REQUEST, map);
- }
-
- boolean audMatch = false;
- if (this.audience.match(aud)) {
- audMatch = true;
- }
- if (!audMatch) {
- CBORObject map = CBORObject.NewMap();
- map.Add(Constants.ERROR, Constants.UNAUTHORIZED_CLIENT);
- map.Add(Constants.ERROR_DESCRIPTION, "Audience does not apply");
- LOGGER.log(Level.INFO, "Message processing aborted: "
- + "Audience does not apply");
- return msg.failReply(Message.FAIL_FORBIDDEN, map);
- }
- //6. Check if the token has a scope
- CBORObject scope = claims.get(Constants.SCOPE);
- if (scope == null) {
- CBORObject map = CBORObject.NewMap();
- map.Add(Constants.ERROR, Constants.INVALID_SCOPE);
- map.Add(Constants.ERROR_DESCRIPTION, "Token has no scope");
- LOGGER.log(Level.INFO, "Message processing aborted: "
- + "Token has no scope");
- return msg.failReply(Message.FAIL_BAD_REQUEST, map);
- }
-
- //7. Check if any part of the scope is meaningful to us
- boolean meaningful = false;
- try {
- if(scope.getType().equals(CBORType.TextString)) {
- meaningful = TokenRepository.getInstance().checkScope(scope);
- }
- else {
- // The version of checkScope() with two arguments is invoked
- // This is currently expecting a structured scope for joining OSCORE groups
- meaningful = TokenRepository.getInstance().checkScope(scope, aud);
- }
- } catch (AceException e) {
- LOGGER.info("Invalid scope, "
- + "message processing aborted: " + e.getMessage());
- CBORObject map = CBORObject.NewMap();
- map.Add(Constants.ERROR, Constants.INVALID_SCOPE);
- map.Add(Constants.ERROR_DESCRIPTION, "Scope has invalid format");
- return msg.failReply(Message.FAIL_BAD_REQUEST, map);
- }
- if (!meaningful) {
- CBORObject map = CBORObject.NewMap();
- map.Add(Constants.ERROR, Constants.INVALID_SCOPE);
- map.Add(Constants.ERROR_DESCRIPTION, "Scope does not apply");
- LOGGER.log(Level.INFO, "Message processing aborted: "
- + "Token's scope does not apply");
- return msg.failReply(Message.FAIL_BAD_REQUEST, map);
- }
-
- //8. Handle EXI if present
- int exiSeqNum = handleExi(claims);
- if (exiSeqNum < -1) {
- // The 'exi' claim is present, but an error occurs during its processing
- CBORObject map = CBORObject.NewMap();
- String errStr = null;
- switch (exiSeqNum) {
- case -2: // the 'cti' claim is not present in the Access Token as a CBOR byte string
- errStr = "The Access Token includes the 'exi' claim, but the 'cti' claim is not present as a CBOR byte string";
- break;
- case -3: // the 'cti' claim is present but it is not formatted as expected
- errStr = "The Access Token includes the 'exi' claim, but the 'cti' claim is not formatted as expected";
- break;
- case -4: // the Sequence Number encoded in the 'cti' claim is not greater than the stored highest Sequence Number
- errStr = "The Access Token includes the 'exi' claim, but the Sequence Number value is too little";
- }
- map.Add(Constants.ERROR, Constants.INVALID_REQUEST);
- map.Add(Constants.ERROR_DESCRIPTION, errStr);
- LOGGER.log(Level.INFO, "Message processing aborted: " + errStr);
- return msg.failReply(Message.FAIL_BAD_REQUEST, map);
- }
-
- //9. Handle cnonce if required
- try {
- handleCnonce(claims);
- } catch (AceException e) {
- CBORObject map = CBORObject.NewMap();
- map.Add(Constants.ERROR, Constants.INVALID_REQUEST);
- map.Add(Constants.ERROR_DESCRIPTION, e.getMessage());
- LOGGER.log(Level.INFO, "Message processing aborted: "
- + "error while checking cnonce: " + e.getMessage());
- return msg.failReply(Message.FAIL_BAD_REQUEST, map);
- }
-
- CBORObject cnf = claims.get(Constants.CNF);
- try {
- if (cnf == null) {
- LOGGER.severe("Token has not cnf");
- throw new AceException("Token has no cnf");
- }
- }
- catch (Exception e) {
- LOGGER.info("No Recipient ID available to use");
- return msg.failReply(Message.FAIL_INTERNAL_SERVER_ERROR, null);
-
- }
-
- boolean firstOscoreAccessToken = false;
-
- // The OSCORE profile is being used. The Resource Server has to determine
- // an available Recipient ID to offer to the Client.
- if (cnf.getKeys().contains(Constants.OSCORE_Input_Material)) {
-
- if (msg.getSenderId() != null) {
- LOGGER.info("OSCORE Input Material provided over a protected POST request");
- CBORObject map = CBORObject.NewMap();
- map.Add(Constants.ERROR, Constants.INVALID_REQUEST);
- map.Add(Constants.ERROR_DESCRIPTION,
- "OSCORE_Input_Material provided over a protected POST request");
- return msg.failReply(Message.FAIL_BAD_REQUEST, map);
- }
-
- CBORObject osc = cnf.get(Constants.OSCORE_Input_Material);
- try {
- if (osc == null || !osc.getType().equals(CBORType.Map)) {
- LOGGER.info("Missing or invalid parameter type for "
- + "'OSCORE_Input_Material', must be CBOR-map");
- throw new AceException("invalid/missing OSCORE_Input_Material");
- }
- }
- catch (Exception e) {
- LOGGER.info("Invalid/missing OSCORE_Input_Material");
- CBORObject map = CBORObject.NewMap();
- map.Add(Constants.ERROR, Constants.INVALID_REQUEST);
- map.Add(Constants.ERROR_DESCRIPTION,
- "invalid/missing OSCORE_Input_Material");
- return msg.failReply(Message.FAIL_BAD_REQUEST, map);
-
- }
-
- CBORObject cbor = CBORObject.DecodeFromBytes(msg.getRawPayload());
- byte[] senderId = cbor.get(Constants.ACE_CLIENT_RECIPIENTID).GetByteString();
-
- OSCoreCtxDB db = OscoreCtxDbSingleton.getInstance();
-
- // Determine an available Recipient ID to offer to the Client as ID2 (i.e., as Client's Sender ID)
- synchronized(usedRecipientIds) {
- synchronized(db) {
-
- int maxIdValue;
-
- byte[] contextId = new byte[0];
- if (cnf.get(Constants.OSCORE_Input_Material).ContainsKey(Constants.OS_CONTEXTID)) {
- contextId = cnf.get(Constants.OSCORE_Input_Material).get(Constants.OS_CONTEXTID).GetByteString();
- }
-
- // Start with 1 byte as size of Recipient ID; try with up to 4 bytes in size
- for (int idSize = 1; idSize <= 4; idSize++) {
-
- if (idSize == 4)
- maxIdValue = (1 << 31) - 1;
- else
- maxIdValue = (1 << (idSize * 8)) - 1;
-
- for (int j = 0; j <= maxIdValue; j++) {
-
- recipientId = Util.intToBytes(j, idSize);
-
- // The Recipient ID must be different than what offered by the Client in the 'id1' parameter
- if(Arrays.equals(senderId, recipientId))
- continue;
-
- // This Recipient ID is marked as not available to use
- if (usedRecipientIds.get(idSize - 1).contains(j))
- continue;
-
- try {
- // This Recipient ID seems to be available to use
- if (!usedRecipientIds.get(idSize - 1).contains(j)) {
-
- // Double check in the database of OSCORE Security Contexts
- if (db.getContext(recipientId, contextId) != null) {
-
- // A Security Context with this Recipient ID exists and was not tracked!
- // Update the local list of used Recipient IDs, then move on to the next candidate
- usedRecipientIds.get(idSize - 1).add(j);
- continue;
-
- }
- else {
-
- // This Recipient ID is actually available at the moment. Add it to the local list
- usedRecipientIds.get(idSize - 1).add(j);
- recipientIdFound = true;
- break;
- }
-
- }
- }
- catch(RuntimeException e) {
- // Multiple Security Contexts with this Recipient ID exist and it was not tracked!
- // Update the local list of used Recipient IDs, then move on to the next candidate
- usedRecipientIds.get(idSize - 1).add(j);
- continue;
- } catch (CoapOSException e) {
- LOGGER.severe("Error while accessing the database of OSCORE Security Contexts");
- return msg.failReply(Message.FAIL_INTERNAL_SERVER_ERROR, null);
- }
-
- }
-
- if (recipientIdFound)
- break;
-
- }
- }
- }
-
- try {
- if (!recipientIdFound) {
- throw new AceException("No Recipient ID available to use");
- }
- } catch (Exception e) {
- LOGGER.info("No Recipient ID available to use");
- return msg.failReply(Message.FAIL_INTERNAL_SERVER_ERROR, null);
-
- }
-
- claims.get(Constants.CNF).get(Constants.OSCORE_Input_Material).Add(Constants.OS_CLIENTID, CBORObject.FromObject(recipientId));
- claims.get(Constants.CNF).get(Constants.OSCORE_Input_Material).Add(Constants.OS_SERVERID, CBORObject.FromObject(senderId));
-
- // This is not a Token for updating access rights,
- // but rather to establish a new OSCORE Security Context
- firstOscoreAccessToken = true;
-
- }
-
- //9. Extension point for handling other special claims in the future
- processOther(claims);
-
- //10. Store the claims of this token
- CBORObject cti = null;
-
- //Check if we have a sid
- String sid = msg.getSenderId();
- try {
- cti = TokenRepository.getInstance().addToken(token, claims, this.ctx, sid, exiSeqNum);
- } catch (AceException e) {
- LOGGER.severe("Message processing aborted: " + e.getMessage());
- CBORObject map = CBORObject.NewMap();
- map.Add(Constants.ERROR, Constants.INVALID_REQUEST);
- map.Add(Constants.ERROR_DESCRIPTION, e.getMessage());
- return msg.failReply(Message.FAIL_BAD_REQUEST, map);
- }
-
- //11. Create success message
- //Return the cti or the local identifier assigned to the token
- CBORObject rep = CBORObject.NewMap();
- rep.Add(Constants.CTI, cti);
-
- // The following enables this class to return to the specific AuthzInfo instance also the
- // Sender Identifier associated to this Access Token, as 'SUB' parameter of the response.
- String assignedKid = null;
- String assignedSid;
- try {
- String ctiStr = Base64.getEncoder().encodeToString(cti.GetByteString());
-
- cnf = claims.get(Constants.CNF);
-
- // This should really not happen for a previously validated and stored Access Token
- if (cnf == null) {
- LOGGER.severe("Token has not cnf");
- throw new AceException("Token has no cnf");
- }
- // This should really not happen for a previously validated and stored Access Token
- if (!cnf.getType().equals(CBORType.Map)) {
- LOGGER.severe("Malformed cnf in token");
- throw new AceException("cnf claim malformed in token");
- }
-
- // Rebuild the 'kid' leveraging what already happened in the Token Repository
- assignedKid = TokenRepository.getInstance().getKidByCti(ctiStr);
-
- // This should really not happen for a previously validated and stored Access Token
- if (assignedKid == null) {
- LOGGER.severe("kid not found");
- throw new AceException("kid not found");
- }
-
- assignedSid = TokenRepository.getInstance().getSid(assignedKid);
- }
- catch (Exception e) {
- LOGGER.info("Unable to retrieve kid after token addition: " + e.getMessage());
- CBORObject map = CBORObject.NewMap();
- map.Add(Constants.ERROR, Constants.INVALID_REQUEST);
- map.Add(Constants.ERROR_DESCRIPTION, e.getMessage());
- return msg.failReply(Message.FAIL_BAD_REQUEST, map);
- }
- if (assignedSid != null)
- rep.Add(Constants.SUB, assignedSid);
- // If the OSCORE profile is being used, and especially a new OSCORE Security Context is
- // being established, return also the selected Recipient ID to the specific AuthzInfo instance.
- //
- // This is not the case when a Token is posted to update access rights, by superseding an
- // existing Token from which the original 'cnf' claim is inherited and retained in the new Token
- if (firstOscoreAccessToken == true) {
-
- String recipientIdString = Base64.getEncoder().encodeToString(recipientId);
- rep.Add(Constants.CLIENT_ID, recipientIdString);
-
- }
-
- LOGGER.info("Successfully processed token");
- return msg.successReply(Message.CREATED, rep);
- }
-
- /**
- * Extension point for handling other special claims.
- *
- * @param claims all claims
- */
- protected synchronized void processOther(Map<Short, CBORObject> claims) {
- //No processing needed
- }
- /**
- * Process a message containing a CWT.
- *
- * Note: The behavior implemented here is the following:
- * If we have an introspection handler, we try to introspect,
- * if introspection fails we just return the claims from the CWT,
- * otherwise we add the claims returned by introspection
- * to those of the CWT, possibly overwriting CWT claims with
- * "fresher" introspection claim having the same id.
- *
- * @param token the token as CBOR
- *
- * @return the claims of the CWT
- *
- * @throws AceException
- * @throws IntrospectionException
- * @throws CoseException
- *
- * @throws Exception when using a not supported key wrap
- */
- protected synchronized Map<Short,CBORObject> processCWT(CBORObject token)
- throws IntrospectionException, AceException,
- CoseException, Exception {
- CWT cwt = CWT.processCOSE(token.EncodeToBytes(), this.ctx);
- //Check if we can introspect this token
- Map<Short, CBORObject> claims = cwt.getClaims();
- if (this.intro != null) {
- CBORObject cti = claims.get(Constants.CTI);
- if (cti != null && cti.getType().equals(CBORType.ByteString)) {
- Map<Short, CBORObject> introClaims
- = this.intro.getParams(cti.GetByteString());
- if (introClaims != null) {
- claims.putAll(introClaims);
- }
- }
- }
- return claims;
- }
-
- /**
- * Process a message containing a reference token.
- *
- * @param token the token as CBOR
- *
- * @return the claims of the reference token
- * @throws AceException
- * @throws IntrospectionException
- */
- protected synchronized Map<Short, CBORObject> processReferenceToken(CBORObject token)
- throws AceException, IntrospectionException {
- // This should be a CBOR String
- if (token.getType() != CBORType.ByteString) {
- throw new AceException("Reference Token processing error");
- }
-
- // Try to introspect the token
- if (this.intro == null) {
- throw new AceException("Introspection handler not found");
- }
- Map<Short, CBORObject> params
- = this.intro.getParams(token.GetByteString());
- if (params == null) {
- params = new HashMap<>();
- params.put(Constants.ACTIVE, CBORObject.False);
- }
-
- return params;
- }
-
- /**
- * Handle exi claim, if present.
- * This is done by internally translating it to a exp claim in sync with the local time.
- *
- * Additional checks are also performed, to ensure that the Sequence Number
- * encoded in the 'cti' claim is strictly greater than the highest Sequence Number
- * received by this Resource Server in Access Tokens that include the 'exi' claim.
- *
- * @param claims
- *
- * @return It returns a positive integer if the Sequence Number is successfully extracted from the 'cti' claim
- * It returns a negative integer in the following cases:
- * -1 : the 'exi' claim is not present
- * -2 : the 'cti' claim is not present in the Access Token as a CBOR byte string
- * -3 : the 'cti' claim is present but it is not formatted as expected
- * -4 : the Sequence Number encoded in the 'cti' claim is not greater than the stored highest Sequence Number
- */
- private synchronized int handleExi(Map<Short, CBORObject> claims) {
-
- CBORObject exi = claims.get(Constants.EXI);
- if (exi == null) {
- return -1;
- }
-
- // Determine the expiration time and add it to the Access Token as value of an 'exp' claim
- Long now = this.time.getCurrentTime();
- Long exp = now + exi.AsNumber().ToInt64Checked();
- claims.put(Constants.EXP, CBORObject.FromObject(exp));
-
- // Check that the 'cti' claim is also present
- CBORObject cticb = claims.get(Constants.CTI);
- if (cticb == null || cticb.getType() != CBORType.ByteString) {
- // The 'cti' claim is not included in the Access Token as a CBOR byte string.
- return -2;
- }
-
- // Retrieve the Sequence Number from the 'cti' claim
- int seqNum = TokenRepository.getInstance().getExiSeqNumFromCti(cticb.GetByteString());
-
- if (seqNum < 0) {
- // The 'cti' claim is malformed
- return -3;
- }
- if (seqNum <= TokenRepository.getInstance().getTopExiSequenceNumber()) {
- // The Sequence Number encoded in the 'cti' claim is not greater than the stored highest Sequence Number
- return -4;
- }
-
- return seqNum;
-
- }
-
- /**
- * Handle cnonce if required
- * @throws AceException
- */
- private synchronized void handleCnonce(Map<Short, CBORObject> claims) throws AceException {
- if (this.checkCnonce) {
- CnonceHandler.getInstance().checkNonce(claims);
- }
- }
-
- @Override
- public void close() throws AceException {
- if (TokenRepository.getInstance() != null) {
- TokenRepository.getInstance().close();
- }
- }
- }