OSCoreCtx.java
/*******************************************************************************
* Copyright (c) 2019 RISE SICS and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v20.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.html.
*
* Contributors:
* Joakim Brorsson
* Ludwig Seitz (RISE SICS)
* Tobias Andersson (RISE SICS)
* Rikard Höglund (RISE SICS)
*
******************************************************************************/
package org.eclipse.californium.oscore;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.eclipse.californium.core.coap.CoAP.Code;
import org.eclipse.californium.core.config.CoapConfig;
import org.eclipse.californium.cose.AlgorithmID;
import org.eclipse.californium.cose.CoseException;
import org.eclipse.californium.cose.EncryptCommon;
import org.eclipse.californium.elements.config.Configuration;
import org.eclipse.californium.elements.util.Bytes;
import org.eclipse.californium.elements.util.StringUtil;
import org.eclipse.californium.oscore.group.GroupRecipientCtx;
import org.eclipse.californium.oscore.group.GroupSenderCtx;
import org.eclipse.californium.scandium.dtls.cipher.CCMBlockCipher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.upokecenter.cbor.CBORObject;
/**
*
* Represents the Security Context and its parameters. At initiation derives the
* keys and IVs. Also maintains replay window.
*
*/
public class OSCoreCtx {
/**
* The logger
*/
private static final Logger LOGGER = LoggerFactory.getLogger(OSCoreCtx.class);
private static final byte ZERO = 0;
private static final byte ONE = 1;
private AlgorithmID common_alg;
private byte[] common_master_secret;
private byte[] common_master_salt;
private byte[] common_iv;
private byte[] context_id;
protected byte[] sender_id;
protected byte[] sender_key;
protected int sender_seq;
protected byte[] recipient_id;
protected byte[] recipient_key;
protected int recipient_seq;
protected int internal_recipient_seq;
protected int recipient_replay_window_size;
protected int recipient_replay_window;
private AlgorithmID kdf;
private int rollback_recipient_seq = -1;
private int rollback_recipient_replay = -1;
private byte[] last_block_tag = null;
private int seqMax = Integer.MAX_VALUE;
private int id_length;
private int iv_length;
private int key_length;
private Code CoAPCode = null;
/**
* Include the context id in messages generated using this context. This is
* generally optional and can be controlled by the application.
*
* Default value is false.
*/
private boolean includeContextId;
/**
* Generate a new partial IV for outgoing Response messages. If this
* variable is false the same nonce from the original request will be used.
* Otherwise a new partial IV will be generated by the sender and included
* in the Response. This affects the calculation of the nonce.
*
* See https://tools.ietf.org/html/rfc8613#section-5.2
*
* This variable will control the behaviour when sending Response messages
* with this context. Note that Observe notifications will always include a
* new partial IV.
*
* Default value is false.
*/
private boolean responsesIncludePartialIV;
/**
* Indicates if this client/server shall support the context re-derivation
* procedure.
*
* See https://tools.ietf.org/html/rfc8613#appendix-B.2
*/
private boolean contextRederivationEnabled;
/**
* When using outer block-wise with OSCORE a proxy can maliciously inject
* block fragments. To protect against this a message with size exceeding
* this parameter should never be sent without inner block-wise. Likewise
* when receiving a message using outer block-wise it should be discarded if
* the cumulated size exceeds this parameter.
*
* See https://tools.ietf.org/html/rfc8613#section-4.1.3.4.2
*/
private int maxUnfragmentedSize;
/**
* URI this Context is associated with if any.
*
* That is what URI it is associated and stored under in the HashMapCtxDB.
*/
private String uri;
/**
* String versions of the context ID, sender ID and recipient ID.
*
* These are set when the context is created (rather than for every message)
* to be used later when adding information about the messages to the
* EndpointContext on sending or receiving a message.
*/
private final String contextIdString;
private final String senderIdString;
private final String recipientIdString;
/**
* Key that is used during the context re-derivation process.
*/
private byte[] contextRederivationKey;
/**
* Makes it possible to override the Context ID to include in messages.
* Typically this would be the Context ID this context was generated with
* but that is not the case for the context re-derivation procedure.
*/
private byte[] overrideContextId;
/**
* Indicate which phase the context re-derivation procedure is in,
*/
private ContextRederivation.PHASE contextRederivationPhase;
/**
* Constructor. Generates the context from the base parameters with the
* minimal input.
*
* @param master_secret the master secret
* @param client is this originally the client's context
* @throws OSException if the default KDF is not supported
*/
public OSCoreCtx(byte[] master_secret, boolean client) throws OSException {
this(master_secret, client, Configuration.getStandard());
}
/**
* Constructor. Generates the context from the base parameters with the
* minimal input.
*
* @param master_secret the master secret
* @param client is this originally the client's context
* @param configuration configuration to be used by this context
* @throws OSException if the default KDF is not supported
* @since 3.0
*/
public OSCoreCtx(byte[] master_secret, boolean client, Configuration configuration) throws OSException {
this(master_secret, client, null, null, null, null, null, null, null,
configuration.get(CoapConfig.MAX_RESOURCE_BODY_SIZE));
}
/**
* Constructor. Generates the context from the base parameters.
*
* @param master_secret the master secret
* @param alg the encryption algorithm as defined in COSE
* @param client is this originally the client's context
* @param sender_id the sender id or null for default
* @param recipient_id the recipient id or null for default
* @param kdf the COSE algorithm abbreviation of the kdf or null for the
* default
* @param replay_size the replay window size or null for the default
* @param master_salt the optional master salt, can be null
* @param contextId the context id, can be null
* @param maxUnfragmentedSize maximum unfragmented size
*
* @throws OSException if the KDF is not supported
* @since 3.0 (added parameter maxUnfragmentedSize)
*/
public OSCoreCtx(byte[] master_secret, boolean client, AlgorithmID alg, byte[] sender_id, byte[] recipient_id,
AlgorithmID kdf, Integer replay_size, byte[] master_salt, byte[] contextId, int maxUnfragmentedSize) throws OSException {
this(master_secret, client, alg, sender_id, recipient_id, kdf, replay_size, master_salt, contextId,
maxUnfragmentedSize, false);
}
/**
* Constructor. Generates the context from the base parameters.
*
* @param master_secret the master secret
* @param alg the encryption algorithm as defined in COSE
* @param client is this originally the client's context
* @param sender_id the sender id or null for default
* @param recipient_id the recipient id or null for default
* @param kdf the COSE algorithm abbreviation of the kdf or null for the
* default
* @param replay_size the replay window size or null for the default
* @param master_salt the optional master salt, can be null
* @param contextId the context id, can be null
* @param maxUnfragmentedSize maximum unfragmented size
* @param appB1Enabled usage of Appendix B.1
*
* @throws OSException if the KDF is not supported
* @since 3.0 (added parameter maxUnfragmentedSize)
*/
public OSCoreCtx(byte[] master_secret, boolean client, AlgorithmID alg, byte[] sender_id, byte[] recipient_id,
AlgorithmID kdf, Integer replay_size, byte[] master_salt, byte[] contextId, int maxUnfragmentedSize,
boolean appB1Enabled) throws OSException {
if (alg == null) {
this.common_alg = AlgorithmID.AES_CCM_16_64_128;
} else {
this.common_alg = alg;
}
setLengths();
this.sender_seq = 0;
this.recipient_seq = -1;
this.internal_recipient_seq = 0;
if (master_secret != null) {
this.common_master_secret = master_secret.clone();
} else {
LOGGER.error("Input master secret is null");
throw new NullPointerException("Input master secret is null");
}
if (sender_id == null || sender_id.length > this.id_length) {
this.sender_id = createByteArray(client ? ZERO : ONE);
} else {
this.sender_id = sender_id.clone();
}
if (recipient_id == null || recipient_id.length > this.id_length) {
this.recipient_id = createByteArray(client ? ONE : ZERO);
} else {
this.recipient_id = recipient_id.clone();
}
if (kdf == null) {
this.kdf = AlgorithmID.HKDF_HMAC_SHA_256;
} else {
this.kdf = kdf;
}
if (replay_size == null) {
this.recipient_replay_window_size = 32;
} else {
this.recipient_replay_window_size = replay_size.intValue();
}
this.recipient_replay_window = 0;
if (master_salt == null) {
// Default value. Automatically initialized with 0-es.
this.common_master_salt = new byte[this.kdf.getKeySize() / Byte.SIZE];
} else {
this.common_master_salt = master_salt.clone();
}
if (contextId != null) {
this.context_id = contextId.clone();
} else {
this.context_id = null;
}
// Set default values for these flags
//They can be set by the application using their setters
includeContextId = false;
responsesIncludePartialIV = false;
contextRederivationEnabled = false;
//Set string versions of sender ID, recipient ID and Context ID
contextIdString = toHex(this.context_id);
senderIdString = toHex(this.sender_id);
recipientIdString = toHex(this.recipient_id);
//Initialize the URI associated with the context
//It will be overwritten if this context is added to a HashMapCtxDB
uri = "";
overrideContextId = null;
contextRederivationPhase = ContextRederivation.PHASE.INACTIVE;
// Set default value of MAX_UNFRAGMENTED_SIZE
this.maxUnfragmentedSize = maxUnfragmentedSize;
//Set digest value depending on HKDF
String digest = null;
switch (this.kdf) {
case HKDF_HMAC_SHA_256:
digest = "SHA256";
break;
case HKDF_HMAC_SHA_512:
digest = "SHA512";
break;
case HKDF_HMAC_AES_128:
case HKDF_HMAC_AES_256:
default:
LOGGER.error("Requested HKDF algorithm is not supported: " + this.kdf.toString());
throw new OSException("HKDF algorithm not supported");
}
// Derive sender_key
CBORObject info = CBORObject.NewArray();
info.Add(this.sender_id);
info.Add(this.context_id);
info.Add(this.common_alg.AsCBOR());
info.Add(CBORObject.FromObject("Key"));
info.Add(this.key_length);
try {
this.sender_key = deriveKey(this.common_master_secret, this.common_master_salt, this.key_length, digest,
info.EncodeToBytes());
} catch (CoseException e) {
LOGGER.error(e.getMessage());
throw new OSException(e.getMessage());
}
// Derive recipient_key
info = CBORObject.NewArray();
info.Add(this.recipient_id);
info.Add(this.context_id);
info.Add(this.common_alg.AsCBOR());
info.Add(CBORObject.FromObject("Key"));
info.Add(this.key_length);
try {
this.recipient_key = deriveKey(this.common_master_secret, this.common_master_salt, this.key_length, digest,
info.EncodeToBytes());
} catch (CoseException e) {
LOGGER.error(e.getMessage());
throw new OSException(e.getMessage());
}
// Derive common_iv
info = CBORObject.NewArray();
info.Add(Bytes.EMPTY);
info.Add(this.context_id);
info.Add(this.common_alg.AsCBOR());
info.Add(CBORObject.FromObject("IV"));
info.Add(this.iv_length);
try {
this.common_iv = deriveKey(this.common_master_secret, this.common_master_salt, this.iv_length, digest,
info.EncodeToBytes());
} catch (CoseException e) {
LOGGER.error(e.getMessage());
throw new OSException(e.getMessage());
}
// Initialize cipher object
initializeCipher(common_alg);
// Attempt to restore SSN from previous execution (Appendix B.1)
this.useAppB1 = appB1Enabled;
if (useAppB1) {
int resumeSsn = readSsn();
if (resumeSsn != -1) {
this.sender_seq = resumeSsn;
writeSsn();
System.out.println("Resuming from SSN: " + resumeSsn);
}
}
}
/**
* Overrides hasCode to provide a functional implementation for this class.
*/
@Override
public int hashCode() {
return 31 * Arrays.hashCode(sender_id) + Arrays.hashCode(recipient_id);
}
/**
* Overrides equals to provide a functional implementation for this class.
*/
@Override
public boolean equals(Object o) {
if (!(o instanceof OSCoreCtx)) {
return false;
}
OSCoreCtx other = (OSCoreCtx) o;
return Arrays.equals(other.sender_id, sender_id) && Arrays.equals(other.recipient_id, recipient_id);
}
/**
* @return the sender key
*/
public byte[] getSenderKey() {
return sender_key;
}
/**
* @return the recipient key
*/
public byte[] getRecipientKey() {
return recipient_key;
}
/**
* @return the encryption algorithm
*/
public AlgorithmID getAlg() {
return this.common_alg;
}
/**
* @return the sender sequence number
*/
public synchronized int getSenderSeq() {
return sender_seq;
}
/**
* @return the receiver sequence number
*/
public synchronized int getReceiverSeq() {
return recipient_seq;
}
/**
* @return the internal receiver sequence number
*/
public synchronized int getInternalReceiverSeq() {
return internal_recipient_seq;
}
/**
* @return the tag of the last block processed with this context
*/
public byte[] getLastBlockTag() {
return last_block_tag;
}
/**
* @return the sender's identifier
*/
public byte[] getSenderId() {
return sender_id;
}
/**
* @return the repipient's identifier
*/
public byte[] getRecipientId() {
return recipient_id;
}
/**
* @return the common_iv
*/
public byte[] getCommonIV() {
return common_iv;
}
/**
* @return the set length of IV:s
*/
public int getIVLength() {
return iv_length;
}
/**
* @return size of recipient replay window
*/
public int getRecipientReplaySize() {
return recipient_replay_window_size;
}
/**
* @return recipient replay window
*/
public int getRecipientReplayWindow() {
return recipient_replay_window;
}
public byte[] getMasterSecret() {
return common_master_secret;
}
public byte[] getSalt() {
return common_master_salt;
}
public AlgorithmID getKdf() {
return kdf;
}
/**
* Enables getting the ID Context
*
* @return Byte array with ID Context
*/
public byte[] getIdContext() {
return context_id;
}
/**
* Enables getting the ID Context to put in an outgoing message.
*
* Typically this will be the Context ID this context was generated with but
* it may be different when performing the context re-derivation procedure.
*
* @return Byte array with ID Context
*/
public byte[] getMessageIdContext() {
if (overrideContextId != null) {
return overrideContextId;
} else {
return context_id;
}
}
/**
* Get the flag controlling whether or not to include the Context ID in
* messages generated using this context.
*
* @return the includeContextId
*/
public boolean getIncludeContextId() {
return includeContextId;
}
/**
* Set the flag controlling whether or not to include the Context ID in
* messages generated using this context.
*
* Note that this flag should never be set to true in a context without a Context ID set.
*
* @param includeContextId the includeContextId to set
*
* @throws IllegalStateException if a Context ID has not been set for this context
*/
public void setIncludeContextId(boolean includeContextId) {
if (context_id == null && overrideContextId == null) {
LOGGER.error("Context ID cannot be included for a context without one set.");
throw new IllegalStateException("Context ID cannot be included for a context without one set.");
}
// If Context ID is not to be included clear the overriding Context ID
// possibly set to be included in messages
if (!includeContextId) {
this.overrideContextId = null;
}
this.includeContextId = includeContextId;
}
/**
* Indicate as a parameter exactly what Context ID should be included.
* Normally that would be the Context ID this context was generated with but
* that is not the case for the context re-derivation procedure.
*
* @param overrideContextId the Context ID to include in messages
*/
public void setIncludeContextId(byte[] overrideContextId) {
this.overrideContextId = overrideContextId.clone();
this.setIncludeContextId(true);
}
/**
* Get the flag controlling whether or not to generate a new partial IV for
* outgoing Response messages using this context.
*
* @return the responsesIncludePartialIV
*/
public boolean getResponsesIncludePartialIV() {
return responsesIncludePartialIV;
}
/**
* Set the flag controlling whether or not to generate a new partial IV for
* outgoing Response messages using this context.
*
* @param responsesIncludePartialIV the responsesIncludePartialIV to set
*/
public void setResponsesIncludePartialIV(boolean responsesIncludePartialIV) {
this.responsesIncludePartialIV = responsesIncludePartialIV;
}
/**
* Get the flag controlling whether or not this context supports the context
* re-derivation procedure.
*
* @return the contextRederivationEnabled
*/
public boolean getContextRederivationEnabled() {
return contextRederivationEnabled;
}
/**
* Set the flag controlling whether or not this context supports the context
* re-derivation procedure.
*
* @param contextRederivationEnabled the contextRederivationEnabled to set
*/
public void setContextRederivationEnabled(boolean contextRederivationEnabled) {
this.contextRederivationEnabled = contextRederivationEnabled;
}
/**
* Gets the current value of the MAX_UNFRAGMENTED_SIZE parameter. It is used
* to prevent malicious behaviour by a proxy when using block-wise.
*
* @return the current value of MAX_UNFRAGMENTED_SIZE
*/
public int getMaxUnfragmentedSize() {
return maxUnfragmentedSize;
}
/**
* Sets the current value of the MAX_UNFRAGMENTED_SIZE parameter. It is used
* to prevent malicious behaviour by a proxy when using block-wise.
*
* @param maxUnfragmentedSize the desired value of MAX_UNFRAGMENTED_SIZE
*/
public void setMaxUnfragmentedSize(int maxUnfragmentedSize) {
this.maxUnfragmentedSize = maxUnfragmentedSize;
}
/**
* Get a string representation of the context ID. (A string showing the
* hexadecimal bytes.)
*
* @return the contextIdString
*/
public String getContextIdString() {
return contextIdString;
}
/**
* Get a string representation of the sender ID. (A string showing the
* hexadecimal bytes.)
*
* @return the senderIdString
*/
public String getSenderIdString() {
return senderIdString;
}
/**
* Get a string representation of the recipient ID. (A string showing the
* hexadecimal bytes.)
*
* @return the recipientIdString
*/
public String getRecipientIdString() {
return recipientIdString;
}
public int rollbackRecipientSeq() {
return rollback_recipient_seq;
}
public int rollbackRecipientReplay() {
return rollback_recipient_replay;
}
/**
* @param seq the sender sequence number to set
*/
public synchronized void setSenderSeq(int seq) {
sender_seq = seq;
}
/**
* @param seq the recipient sequence number to set
*/
public synchronized void setReceiverSeq(int seq) {
recipient_seq = seq;
}
/**
* Save the tag of the last processed block
*
* @param tag the tag
*/
public void setLastBlockTag(byte[] tag) {
last_block_tag = tag.clone();
}
/**
* Enables setting the sender key
*
* @param senderKey the sender key to set
*/
public void setSenderKey(byte[] senderKey) {
this.sender_key = senderKey.clone();
}
/**
* Enables setting the recipient key
*
* @param recipientKey the recipient key to set
*/
public void setRecipientKey(byte[] recipientKey) {
this.recipient_key = recipientKey.clone();
}
/**
* Set the maximum sequence number.
*
* @param seqMax the maximum sequence number.
*/
public void setSeqMax(int seqMax) {
this.seqMax = seqMax;
}
/**
* Sets the valid lengths, in bytes, of constrained variables(ids, IVs and
* keys).
*
* @throws RuntimeException if not this.common_alg has been initiated
*/
private void setLengths() {
if (common_alg != null) {
iv_length = EncryptCommon.ivLength(common_alg);
if (iv_length > 0) {
id_length = iv_length - 6; // RFC section 5.2
key_length = common_alg.getKeySize() / 8;
} else {
LOGGER.error("Unable to set lengths, since algorithm");
throw new RuntimeException("Unable to set lengths, since algorithm");
}
} else {
LOGGER.error("Common_alg has not yet been initiated.");
throw new RuntimeException("Common_alg has not yet been initiated.");
}
}
/**
* @return the URI this context is associated with if any.
*/
public String getUri() {
return uri;
}
/**
* Sets the URI this context is associated with.
* (The URI it is saved under in the HashMapCtxDB.)
*
* This will be set when added to the HashMapCtxDB.
*
* @param uri the URI this OSCORE context is associated with
*/
protected void setUri(String uri) {
this.uri = uri;
}
/**
* Get the context re-derivation key.
*
* @return the context re-derivation key
*/
protected byte[] getContextRederivationKey() {
return contextRederivationKey;
}
/**
* Sets the context re-derivation key.
*
* @param contextRederivationKey the context re-derivation key to set
*/
protected void setContextRederivationKey(byte[] contextRederivationKey) {
this.contextRederivationKey = contextRederivationKey;
}
/**
* Check the phase of the context re-derivation process.
*
* @return the contextRederivationOngoing
*/
public ContextRederivation.PHASE getContextRederivationPhase() {
return contextRederivationPhase;
}
/**
* Set the phase of the context re-derivation process.
*
* @param contextRederivationPhase the contextRederivationPhase to set
*/
public void setContextRederivationPhase(ContextRederivation.PHASE contextRederivationPhase) {
this.contextRederivationPhase = contextRederivationPhase;
}
/**
* Increase the sender's sequence number by one
*
* @throws OSException if the sequence number wraps
*/
public synchronized void increaseSenderSeq() throws OSException {
if (sender_seq >= seqMax) {
LOGGER.error("Sequence number wrapped, get a new OSCore context");
throw new OSException("Sequence number wrapped");
}
if (useAppB1 && sender_seq % K == 0) {
writeSsn();
}
sender_seq++;
}
// TODO: For interop testing
public static boolean DISABLE_REPLAY_CHECKS = false;
/**
* Checks and sets the sequence number for incoming messages.
*
* @param seq the incoming sequence number
*
* @throws OSException if the sequence number wraps or if it is a replay
*/
public synchronized void checkIncomingSeq(int seq) throws OSException {
if (DISABLE_REPLAY_CHECKS) {
return;
}
if (seq >= seqMax) {
LOGGER.error("Sequence number wrapped, get new OSCore context");
throw new OSException(ErrorDescriptions.REPLAY_DETECT);
}
if (seq < internal_recipient_seq) {
LOGGER.error("Message too old");
throw new OSException(ErrorDescriptions.REPLAY_DETECT);
}
boolean valid = ((recipient_replay_window >> (seq - internal_recipient_seq)) & 1) == 0;
if (seq >= internal_recipient_seq + recipient_replay_window_size) {
valid = true;
}
if (!valid) {
LOGGER.error("Replayed message detected");
throw new OSException(ErrorDescriptions.REPLAY_DETECT);
}
recipient_seq = seq;
// Update window
int shift = seq - (internal_recipient_seq + recipient_replay_window_size - 1);
if (shift > 0) {
recipient_replay_window >>= shift;
internal_recipient_seq += shift;
}
recipient_replay_window |= 1 << (seq - internal_recipient_seq);
}
public static byte[] deriveKey(byte[] secret, byte[] salt, int cbitKey, String digest, byte[] rgbContext)
throws CoseException {
final String HMAC_ALG_NAME = "Hmac" + digest;
try {
Mac hmac = Mac.getInstance(HMAC_ALG_NAME);
int hashLen = hmac.getMacLength();
// Perform extract
hmac.init(new SecretKeySpec(salt, HMAC_ALG_NAME));
byte[] rgbExtract = hmac.doFinal(secret);
// Perform expand
hmac.init(new SecretKeySpec(rgbExtract, HMAC_ALG_NAME));
int c = (cbitKey / hashLen) + 1;
byte[] rgbOut = new byte[cbitKey];
int maxLen = (hashLen * c > cbitKey) ? hashLen * c : cbitKey;
byte[] T = new byte[maxLen];
byte[] last = new byte[0];
for (int i = 0; i < c; i++) {
hmac.reset();
hmac.update(last);
hmac.update(rgbContext);
hmac.update((byte) (i + 1));
last = hmac.doFinal();
System.arraycopy(last, 0, T, i * hashLen, hashLen);
}
System.arraycopy(T, 0, rgbOut, 0, cbitKey);
return rgbOut;
} catch (NoSuchAlgorithmException | InvalidKeyException ex) {
throw new CoseException("Algorithm not supported", ex);
} catch (Exception ex) {
throw new CoseException("Derivation failure", ex);
}
}
/**
* Converts a byte array to a hexadecimal string representation.
*
* @param bytes the byte array to convert
* @return the string representation
*/
private String toHex(byte[] bytes) {
if(bytes == null || bytes.length == 0) {
return "";
} else {
return StringUtil.byteArray2Hex(bytes);
}
}
/**
* Returns this CoAPCode.
*
* @return the coap code
*/
public Code getCoAPCode() {
return CoAPCode;
}
/**
* Sets this CoAPCode to CoAPCode
*
* @param coapCode coap code.
*/
public void setCoAPCode(Code coapCode) {
if (coapCode != null) {
this.CoAPCode = coapCode;
}
}
/**
* Initializes the cipher object by calling CCMBlockCipher.encrypt with
* dummy data. Doing this at creation of the OSCORE context reduces the
* latency for the first request since it would otherwise happen then.
*
* @param alg the encryption algorithm used
*/
private void initializeCipher(AlgorithmID alg) {
switch (alg) {
case AES_CCM_16_64_128:
case AES_CCM_16_128_128:
case AES_CCM_64_64_128:
case AES_CCM_64_128_128:
byte[] key = { (byte) 0xEB, (byte) 0xDE, (byte) 0xBC, (byte) 0x51, (byte) 0xF1, (byte) 0x03,
(byte) 0x79, (byte) 0x14, (byte) 0x14, (byte) 0x4F, (byte) 0xC3, (byte) 0xAC, (byte) 0x40,
(byte) 0x14, (byte) 0xD2, (byte) 0x4C };
byte[] nonce = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
try {
CCMBlockCipher.encrypt(new SecretKeySpec(key, "AES"), nonce, Bytes.EMPTY,
Bytes.EMPTY, 0);
} catch (GeneralSecurityException e) {
LOGGER.error("Failed to initialize cipher.");
throw new RuntimeException("Failed to initialize cipher.");
}
break;
default:
break;
}
}
/**
* Create byte array from values.
*
* @param values bytes for byte array
* @return initialized byte array
*/
private static byte[] createByteArray(byte... values) {
return values;
}
/**
* Provides a method to check if this context is used for Group OSCORE.
*
* @return if this is a group (Group OSCORE) context
*/
public boolean isGroupContext() {
return this instanceof GroupSenderCtx || this instanceof GroupRecipientCtx;
}
// TODO: Remove?
protected GroupSenderCtx getSenderCtx() {
return null;
}
/**
* Save SSN for resumption (Appendix B.1)
*/
private void writeSsn() {
String fileName = getSsnFilePath();
// Create file and folder if it does not exist
File targetFile = new File(fileName);
File parent = targetFile.getParentFile();
parent.mkdirs();
// Write file content
if (fileName != null) {
try (PrintWriter writer = new PrintWriter(fileName, "UTF-8")) {
writer.println(String.valueOf(sender_seq));
writer.close();
} catch (FileNotFoundException | UnsupportedEncodingException e) {
// Failed to write SSN for resumption
}
}
}
/**
* Control the usage of Appendix B.1
*
*/
private final boolean useAppB1;
/**
* Get filename to use for writing/reading SSN for resumption (Appendix B.1)
*
* @return the path to the SSN file
*/
private String getSsnFilePath() {
// Build unique filename to use
String jarFile = System.getProperty("java.class.path");
if (jarFile.length() > 4) {
jarFile = jarFile.substring(0, jarFile.length() - 4);
}
String recipientId = toHex(recipient_id);
String senderId = toHex(sender_id);
String contextId = toHex(context_id);
String fileName;
if (jarFile.contains(":")) {
fileName = "." + senderId + "-" + recipientId + "-" + contextId;
} else {
fileName = "." + jarFile + "-" + senderId + "-" + recipientId + "-" + contextId;
}
return ".resume/" + fileName;
}
/**
* Parameter K for Appendix B.1
*/
private static int K = 12;
/**
* Parameter F for Appendix B.1
*/
private static int F = 3;
/**
* Read SSN for resumption (Appendix B.1)
*
* @return the SSN to resume from
*/
private int readSsn() {
String filePath = getSsnFilePath();
// Attempt to read SSN from file
int resumeSsn = -1;
if (filePath == null) {
return resumeSsn;
}
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line = reader.readLine();
reader.close();
resumeSsn = Integer.parseInt(line);
} catch (IOException e) {
// Failed to read or parse from file
return resumeSsn;
}
return resumeSsn + K + F;
}
}