Пример #1
0
  /**
   * Create a new user ID token from the provided JSON object. The associated master token must be
   * provided to verify the user ID token.
   *
   * @param ctx MSL context.
   * @param userIdTokenJO user ID token JSON object.
   * @param masterToken the master token.
   * @throws MslEncodingException if there is an error parsing the JSON, the token data is missing
   *     or invalid, or the signature is invalid.
   * @throws MslCryptoException if there is an error verifying the token data.
   * @throws MslException if the user ID token master token serial number does not match the master
   *     token serial number, or the expiration timestamp occurs before the renewal window, or the
   *     user data is missing or invalid, or the user ID token master token serial number is out of
   *     range, or the user ID token serial number is out of range.
   */
  public UserIdToken(
      final MslContext ctx, final JSONObject userIdTokenJO, final MasterToken masterToken)
      throws MslEncodingException, MslCryptoException, MslException {
    this.ctx = ctx;

    // Grab the crypto context.
    final ICryptoContext cryptoContext = ctx.getMslCryptoContext();

    // Verify the JSON representation.
    try {
      try {
        tokendata = Base64.decode(userIdTokenJO.getString(KEY_TOKENDATA));
      } catch (final IllegalArgumentException e) {
        throw new MslEncodingException(
                MslError.USERIDTOKEN_TOKENDATA_INVALID,
                "useridtoken " + userIdTokenJO.toString(),
                e)
            .setMasterToken(masterToken);
      }
      if (tokendata == null || tokendata.length == 0)
        throw new MslEncodingException(
                MslError.USERIDTOKEN_TOKENDATA_MISSING, "useridtoken " + userIdTokenJO.toString())
            .setMasterToken(masterToken);
      try {
        signature = Base64.decode(userIdTokenJO.getString(KEY_SIGNATURE));
      } catch (final IllegalArgumentException e) {
        throw new MslEncodingException(
                MslError.USERIDTOKEN_SIGNATURE_INVALID,
                "useridtoken " + userIdTokenJO.toString(),
                e)
            .setMasterToken(masterToken);
      }
      verified = cryptoContext.verify(tokendata, signature);
    } catch (final JSONException e) {
      throw new MslEncodingException(
              MslError.JSON_PARSE_ERROR, "useridtoken " + userIdTokenJO.toString(), e)
          .setMasterToken(masterToken);
    }

    // Pull the token data.
    final String tokenDataJson = new String(tokendata, MslConstants.DEFAULT_CHARSET);
    try {
      final JSONObject tokenDataJO = new JSONObject(tokenDataJson);
      renewalWindow = tokenDataJO.getLong(KEY_RENEWAL_WINDOW);
      expiration = tokenDataJO.getLong(KEY_EXPIRATION);
      if (expiration < renewalWindow)
        throw new MslException(
                MslError.USERIDTOKEN_EXPIRES_BEFORE_RENEWAL, "usertokendata " + tokenDataJson)
            .setMasterToken(masterToken);
      mtSerialNumber = tokenDataJO.getLong(KEY_MASTER_TOKEN_SERIAL_NUMBER);
      if (mtSerialNumber < 0 || mtSerialNumber > MslConstants.MAX_LONG_VALUE)
        throw new MslException(
                MslError.USERIDTOKEN_MASTERTOKEN_SERIAL_NUMBER_OUT_OF_RANGE,
                "usertokendata " + tokenDataJson)
            .setMasterToken(masterToken);
      serialNumber = tokenDataJO.getLong(KEY_SERIAL_NUMBER);
      if (serialNumber < 0 || serialNumber > MslConstants.MAX_LONG_VALUE)
        throw new MslException(
                MslError.USERIDTOKEN_SERIAL_NUMBER_OUT_OF_RANGE, "usertokendata " + tokenDataJson)
            .setMasterToken(masterToken);
      final byte[] ciphertext;
      try {
        ciphertext = Base64.decode(tokenDataJO.getString(KEY_USERDATA));
      } catch (final IllegalArgumentException e) {
        throw new MslException(
                MslError.USERIDTOKEN_USERDATA_INVALID, tokenDataJO.getString(KEY_USERDATA))
            .setMasterToken(masterToken);
      }
      if (ciphertext == null || ciphertext.length == 0)
        throw new MslException(
                MslError.USERIDTOKEN_USERDATA_MISSING, tokenDataJO.getString(KEY_USERDATA))
            .setMasterToken(masterToken);
      userdata = (verified) ? cryptoContext.decrypt(ciphertext) : null;
    } catch (final JSONException e) {
      throw new MslEncodingException(
              MslError.USERIDTOKEN_TOKENDATA_PARSE_ERROR, "usertokendata " + tokenDataJson, e)
          .setMasterToken(masterToken);
    } catch (final MslCryptoException e) {
      e.setMasterToken(masterToken);
      throw e;
    }

    // Pull the user data.
    if (userdata != null) {
      final String userDataJson = new String(userdata, MslConstants.DEFAULT_CHARSET);
      try {
        final JSONObject userDataJO = new JSONObject(userDataJson);
        issuerData =
            (userDataJO.has(KEY_ISSUER_DATA)) ? userDataJO.getJSONObject(KEY_ISSUER_DATA) : null;
        final String identity = userDataJO.getString(KEY_IDENTITY);
        if (identity == null || identity.length() == 0)
          throw new MslException(MslError.USERIDTOKEN_IDENTITY_INVALID, "userdata " + userDataJson)
              .setMasterToken(masterToken);
        final TokenFactory factory = ctx.getTokenFactory();
        user = factory.createUser(ctx, identity);
        if (user == null)
          throw new MslInternalException(
              "TokenFactory.createUser() returned null in violation of the interface contract.");
      } catch (final JSONException e) {
        throw new MslEncodingException(
                MslError.USERIDTOKEN_USERDATA_PARSE_ERROR, "userdata " + userDataJson, e)
            .setMasterToken(masterToken);
      }
    } else {
      issuerData = null;
      user = null;
    }

    // Verify serial numbers.
    if (masterToken == null || this.mtSerialNumber != masterToken.getSerialNumber())
      throw new MslException(
              MslError.USERIDTOKEN_MASTERTOKEN_MISMATCH,
              "uit mtserialnumber " + this.mtSerialNumber + "; mt " + masterToken)
          .setMasterToken(masterToken);
  }
Пример #2
0
  /**
   * Return the crypto context resulting from key response data contained in the provided header.
   *
   * <p>The {@link MslException}s thrown by this method will not have the entity or user set.
   *
   * @param ctx MSL context.
   * @param header header.
   * @param keyRequestData key request data for key exchange.
   * @return the crypto context or null if the header does not contain key response data or is for
   *     an error message.
   * @throws MslKeyExchangeException if there is an error with the key request data or key response
   *     data or the key exchange scheme is not supported.
   * @throws MslCryptoException if the crypto context cannot be created.
   * @throws MslEncodingException if there is an error parsing the JSON.
   * @throws MslMasterTokenException if the master token is not trusted and needs to be.
   * @throws MslEntityAuthException if there is a problem with the master token identity.
   */
  private static ICryptoContext getKeyxCryptoContext(
      final MslContext ctx, final MessageHeader header, final Set<KeyRequestData> keyRequestData)
      throws MslCryptoException, MslKeyExchangeException, MslEncodingException,
          MslMasterTokenException, MslEntityAuthException {
    // Pull the header data.
    final MessageHeader messageHeader = (MessageHeader) header;
    final MasterToken masterToken = messageHeader.getMasterToken();
    final KeyResponseData keyResponse = messageHeader.getKeyResponseData();

    // If there is no key response data then return null.
    if (keyResponse == null) return null;

    // If the key response data master token is decrypted then use the
    // master token keys to create the crypto context.
    final MasterToken keyxMasterToken = keyResponse.getMasterToken();
    if (keyxMasterToken.isDecrypted()) return new SessionCryptoContext(ctx, keyxMasterToken);

    // Perform the key exchange.
    final KeyExchangeScheme responseScheme = keyResponse.getKeyExchangeScheme();
    final KeyExchangeFactory factory = ctx.getKeyExchangeFactory(responseScheme);
    if (factory == null)
      throw new MslKeyExchangeException(MslError.KEYX_FACTORY_NOT_FOUND, responseScheme.name());

    // Attempt the key exchange but if it fails then try with the next
    // key request data before giving up.
    MslException keyxException = null;
    final Iterator<KeyRequestData> keyRequests = keyRequestData.iterator();
    while (keyRequests.hasNext()) {
      final KeyRequestData keyRequest = keyRequests.next();
      final KeyExchangeScheme requestScheme = keyRequest.getKeyExchangeScheme();

      // Skip incompatible key request data.
      if (!responseScheme.equals(requestScheme)) continue;

      try {
        return factory.getCryptoContext(ctx, keyRequest, keyResponse, masterToken);
      } catch (final MslKeyExchangeException e) {
        if (!keyRequests.hasNext()) throw e;
        keyxException = e;
      } catch (final MslEncodingException e) {
        if (!keyRequests.hasNext()) throw e;
        keyxException = e;
      } catch (final MslMasterTokenException e) {
        if (!keyRequests.hasNext()) throw e;
        keyxException = e;
      } catch (final MslEntityAuthException e) {
        if (!keyRequests.hasNext()) throw e;
        keyxException = e;
      }
    }

    // We did not perform a successful key exchange. If we caught an
    // exception then throw that exception now.
    if (keyxException != null) {
      if (keyxException instanceof MslKeyExchangeException)
        throw (MslKeyExchangeException) keyxException;
      if (keyxException instanceof MslEncodingException) throw (MslEncodingException) keyxException;
      if (keyxException instanceof MslMasterTokenException)
        throw (MslMasterTokenException) keyxException;
      if (keyxException instanceof MslEntityAuthException)
        throw (MslEntityAuthException) keyxException;
      throw new MslInternalException(
          "Unexpected exception caught during key exchange.", keyxException);
    }

    // If we did not perform a successful key exchange then the
    // payloads will not decrypt properly. Throw an exception.
    throw new MslKeyExchangeException(
        MslError.KEYX_RESPONSE_REQUEST_MISMATCH, Arrays.toString(keyRequestData.toArray()));
  }
Пример #3
0
  /**
   * Create a new user ID token with the specified user.
   *
   * @param ctx MSL context.
   * @param renewalWindow the renewal window.
   * @param expiration the expiration.
   * @param masterToken the master token.
   * @param serialNumber the user ID token serial number.
   * @param issuerData the issuer data. May be null.
   * @param user the MSL user.
   * @throws MslEncodingException if there is an error encoding the JSON data.
   * @throws MslCryptoException if there is an error encrypting or signing the token data.
   */
  public UserIdToken(
      final MslContext ctx,
      final Date renewalWindow,
      final Date expiration,
      final MasterToken masterToken,
      final long serialNumber,
      final JSONObject issuerData,
      final MslUser user)
      throws MslEncodingException, MslCryptoException {
    // The expiration must appear after the renewal window.
    if (expiration.before(renewalWindow))
      throw new MslInternalException(
          "Cannot construct a user ID token that expires before its renewal window opens.");
    // A master token must be provided.
    if (masterToken == null)
      throw new MslInternalException("Cannot construct a user ID token without a master token.");
    // The serial number must be within range.
    if (serialNumber < 0 || serialNumber > MslConstants.MAX_LONG_VALUE)
      throw new MslInternalException(
          "Serial number " + serialNumber + " is outside the valid range.");

    this.ctx = ctx;
    this.renewalWindow = renewalWindow.getTime() / MILLISECONDS_PER_SECOND;
    this.expiration = expiration.getTime() / MILLISECONDS_PER_SECOND;
    this.mtSerialNumber = masterToken.getSerialNumber();
    this.serialNumber = serialNumber;
    this.issuerData = issuerData;
    this.user = user;

    // Construct the user data.
    final JSONObject userData = new JSONObject();
    try {
      if (this.issuerData != null) userData.put(KEY_ISSUER_DATA, this.issuerData);
      userData.put(KEY_IDENTITY, user.getEncoded());
      this.userdata = userData.toString().getBytes(MslConstants.DEFAULT_CHARSET);
    } catch (final JSONException e) {
      throw new MslEncodingException(MslError.JSON_ENCODE_ERROR, "userdata", e);
    }

    try {
      // Encrypt the user data.
      final ICryptoContext cryptoContext = ctx.getMslCryptoContext();
      final byte[] ciphertext = cryptoContext.encrypt(this.userdata);

      // Construct the token data.
      try {
        final JSONObject tokenDataJO = new JSONObject();
        tokenDataJO.put(KEY_RENEWAL_WINDOW, this.renewalWindow);
        tokenDataJO.put(KEY_EXPIRATION, this.expiration);
        tokenDataJO.put(KEY_MASTER_TOKEN_SERIAL_NUMBER, this.mtSerialNumber);
        tokenDataJO.put(KEY_SERIAL_NUMBER, this.serialNumber);
        tokenDataJO.put(KEY_USERDATA, Base64.encode(ciphertext));
        this.tokendata = tokenDataJO.toString().getBytes(MslConstants.DEFAULT_CHARSET);
      } catch (final JSONException e) {
        throw new MslEncodingException(MslError.JSON_ENCODE_ERROR, "usertokendata", e)
            .setMasterToken(masterToken);
      }

      // Sign the token data.
      this.signature = cryptoContext.sign(this.tokendata);
      this.verified = true;
    } catch (final MslCryptoException e) {
      e.setMasterToken(masterToken);
      throw e;
    }
  }
Пример #4
0
  /**
   * Construct a new message input stream. The header is parsed.
   *
   * <p>If key request data is provided and a matching key response data is found in the message
   * header the key exchange will be performed to process the message payloads.
   *
   * <p>Service tokens will be decrypted and verified with the provided crypto contexts identified
   * by token name. A default crypto context may be provided by using the empty string as the token
   * name; if a token name is not explcitly mapped onto a crypto context, the default crypto context
   * will be used.
   *
   * @param ctx MSL context.
   * @param source MSL input stream.
   * @param charset input stream character set encoding.
   * @param keyRequestData key request data to use when processing key response data.
   * @param cryptoContexts the map of service token names onto crypto contexts used to decrypt and
   *     verify service tokens.
   * @throws MslEncodingException if there is an error parsing the message.
   * @throws MslCryptoException if there is an error decrypting or verifying the header or creating
   *     the message payload crypto context.
   * @throws MslEntityAuthException if unable to create the entity authentication data.
   * @throws MslUserAuthException if unable to create the user authentication data.
   * @throws MslMessageException if the message master token is expired and the message is not
   *     renewable.
   * @throws MslMasterTokenException if the master token is not trusted and needs to be or if it has
   *     been revoked.
   * @throws MslUserIdTokenException if the user ID token has been revoked.
   * @throws MslKeyExchangeException if there is an error with the key request data or key response
   *     data or the key exchange scheme is not supported.
   * @throws MslMessageException if the message master token is expired and the message is not
   *     renewable.
   * @throws MslException if the message does not contain an entity authentication data or a master
   *     token, or a token is improperly bound to another token.
   */
  public MessageInputStream(
      final MslContext ctx,
      final InputStream source,
      final Charset charset,
      final Set<KeyRequestData> keyRequestData,
      final Map<String, ICryptoContext> cryptoContexts)
      throws MslEncodingException, MslEntityAuthException, MslCryptoException, MslUserAuthException,
          MslMessageException, MslKeyExchangeException, MslMasterTokenException,
          MslUserIdTokenException, MslMessageException, MslException {
    // Parse the header.
    this.source = source;
    this.tokener = new JSONTokener(new InputStreamReader(source, charset));
    final JSONObject jo;
    try {
      if (!this.tokener.more()) throw new MslEncodingException(MslError.MESSAGE_DATA_MISSING);
      final Object o = this.tokener.nextValue();
      if (!(o instanceof JSONObject)) throw new MslEncodingException(MslError.MESSAGE_FORMAT_ERROR);
      jo = (JSONObject) o;
    } catch (final JSONException e) {
      throw new MslEncodingException(MslError.JSON_PARSE_ERROR, "header", e);
    }
    this.header = Header.parseHeader(ctx, jo, cryptoContexts);

    try {
      // For error messages there are no key exchange or payload crypto
      // contexts.
      if (this.header instanceof ErrorHeader) {
        this.keyxCryptoContext = null;
        this.cryptoContext = null;
        return;
      }

      // Grab the key exchange crypto context, if any.
      final MessageHeader messageHeader = (MessageHeader) this.header;
      this.keyxCryptoContext = getKeyxCryptoContext(ctx, messageHeader, keyRequestData);

      // In peer-to-peer mode or in trusted network mode with no key
      // exchange the payload crypto context equals the header crypto
      // context.
      if (ctx.isPeerToPeer() || this.keyxCryptoContext == null)
        this.cryptoContext = messageHeader.getCryptoContext();

      // Otherwise the payload crypto context equals the key exchange
      // crypto context.
      else this.cryptoContext = this.keyxCryptoContext;

      // If this is a handshake message but it is not renewable or does
      // not contain key request data then reject the message.
      if (messageHeader.isHandshake()
          && (!messageHeader.isRenewable() || messageHeader.getKeyRequestData().isEmpty())) {
        throw new MslMessageException(
            MslError.HANDSHAKE_DATA_MISSING, messageHeader.toJSONString());
      }

      // If I am in peer-to-peer mode or the master token is verified
      // (i.e. issued by the local entity which is therefore a trusted
      // network server) then perform the master token checks.
      final MasterToken masterToken = messageHeader.getMasterToken();
      if (masterToken != null && (ctx.isPeerToPeer() || masterToken.isVerified())) {
        // If the master token has been revoked then reject the
        // message.
        final TokenFactory factory = ctx.getTokenFactory();
        final MslError revoked = factory.isMasterTokenRevoked(ctx, masterToken);
        if (revoked != null) throw new MslMasterTokenException(revoked, masterToken);

        // If the user ID token has been revoked then reject the
        // message. We know the master token is not null and that it is
        // verified so we assume the user ID token is as well.
        final UserIdToken userIdToken = messageHeader.getUserIdToken();
        if (userIdToken != null) {
          final MslError uitRevoked = factory.isUserIdTokenRevoked(ctx, masterToken, userIdToken);
          if (uitRevoked != null) throw new MslUserIdTokenException(uitRevoked, userIdToken);
        }

        // If the master token is expired...
        if (masterToken.isExpired(null)) {
          // If the message is not renewable or does not contain key
          // request data then reject the message.
          if (!messageHeader.isRenewable() || messageHeader.getKeyRequestData().isEmpty())
            throw new MslMessageException(MslError.MESSAGE_EXPIRED, messageHeader.toJSONString());

          // If the master token will not be renewed by the token
          // factory then reject the message.
          //
          // This throws an exception if the master token is not
          // renewable.
          final MslError notRenewable = factory.isMasterTokenRenewable(ctx, masterToken);
          if (notRenewable != null)
            throw new MslMessageException(
                notRenewable, "Master token is expired and not renewable.");
        }
      }

      // TODO: This is the old non-replayable logic for backwards
      // compatibility. It should be removed once all MSL stacks have
      // migrated to the newer non-replayable ID logic.
      //
      // If the message is non-replayable (it is not from a trusted
      // network server).
      if (messageHeader.isNonReplayable()) {
        // ...and not also renewable with key request data and a
        // master token then reject the message.
        if (!messageHeader.isRenewable()
            || messageHeader.getKeyRequestData().isEmpty()
            || masterToken == null) {
          throw new MslMessageException(
              MslError.INCOMPLETE_NONREPLAYABLE_MESSAGE, messageHeader.toJSONString());
        }

        // If the message does not have the newest master token
        // then notify the sender.
        final TokenFactory factory = ctx.getTokenFactory();
        if (!factory.isNewestMasterToken(ctx, masterToken))
          throw new MslMessageException(MslError.MESSAGE_REPLAYED, messageHeader.toJSONString());
      }

      // If the message is non-replayable (it is not from a trusted
      // network server).
      final Long nonReplayableId = messageHeader.getNonReplayableId();
      if (nonReplayableId != null) {
        // ...and does not include a master token then reject the
        // message.
        if (masterToken == null)
          throw new MslMessageException(
              MslError.INCOMPLETE_NONREPLAYABLE_MESSAGE, messageHeader.toJSONString());

        // If the non-replayable ID is not accepted then notify the
        // sender.
        final TokenFactory factory = ctx.getTokenFactory();
        final MslError replayed = factory.acceptNonReplayableId(ctx, masterToken, nonReplayableId);
        if (replayed != null) throw new MslMessageException(replayed, messageHeader.toJSONString());
      }
    } catch (final MslException e) {
      if (this.header instanceof MessageHeader) {
        final MessageHeader messageHeader = (MessageHeader) this.header;
        e.setEntity(messageHeader.getMasterToken());
        e.setEntity(messageHeader.getEntityAuthenticationData());
        e.setUser(messageHeader.getUserIdToken());
        e.setUser(messageHeader.getUserAuthenticationData());
        e.setMessageId(messageHeader.getMessageId());
      } else {
        final ErrorHeader errorHeader = (ErrorHeader) this.header;
        e.setEntity(errorHeader.getEntityAuthenticationData());
        e.setMessageId(errorHeader.getMessageId());
      }
      throw e;
    }
  }