/** * 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); }
/** * 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())); }
/** * 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; } }
/** * 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; } }