OscoreAS.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.coap.as;
import java.util.Arrays;
import java.util.Base64;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import org.eclipse.californium.core.CoapServer;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.network.CoapEndpoint;
import org.eclipse.californium.oscore.OSCoreCoapStackFactory;
import org.eclipse.californium.oscore.OSCoreCtx;
import org.eclipse.californium.oscore.OSException;
import org.eclipse.californium.scandium.dtls.PskPublicInformation;
import org.eclipse.californium.cose.OneKey;
import se.sics.ace.AceException;
import se.sics.ace.TimeProvider;
import se.sics.ace.as.Introspect;
import se.sics.ace.as.PDP;
import se.sics.ace.as.Token;
import se.sics.ace.coap.rs.oscoreProfile.OscoreCtxDbSingleton;
/**
* An Authorization Server that offers secure connections and authentication via OSCORE.
*
* This server uses the following conventions:
*
* alg = AES_CCM_16_64_128
* salt = null
* kdf = HMAC_SHA_256
* recipient_replay_window_size = 32
* id_context = null
* sender_id = asId
* recipient_id = rs/client id
*
* @author Ludwig Seitz and Marco Tiloca
*
*/
public class OscoreAS extends CoapServer implements AutoCloseable {
/**
* The logger
*/
private static final Logger LOGGER
= Logger.getLogger(OscoreAS.class.getName());
/**
* The token endpoint
*/
Token t = null;
/**
* The introspect endpoint
*/
Introspect i = null;
private OscoreAceEndpoint token;
private OscoreAceEndpoint introspect;
private final static int MAX_UNFRAGMENTED_SIZE = 4096;
/**
* Constructor.
*
* @param asId identifier of the AS
* @param db database connector of the AS
* @param pdp PDP for deciding who gets which token
* @param time time provider, must not be null
* @param asymmetricKey asymmetric key pair of the AS or
* null if it hasn't any
* @param port the port number to run the server on
*
* @throws AceException
* @throws OSException
*
*/
public OscoreAS(String asId, CoapDBConnector db,
PDP pdp, TimeProvider time,
OneKey asymmetricKey, int port,
Map<String, String> peerNamesToIdentities,
Map<String, String> peerIdentitiesToNames,
Map<String, String> myIdentities)
throws AceException, OSException {
this(asId, db, pdp, time, asymmetricKey, "token", "introspect", port,
null, false, (short)0, false, peerNamesToIdentities,
peerIdentitiesToNames, myIdentities);
}
/**
* Constructor.
*
* @param asId identifier of the AS
* @param db database connector of the AS
* @param pdp PDP for deciding who gets which token
* @param time time provider, must not be null
* @param asymmetricKey asymmetric key pair of the AS or
* null if it hasn't any
* @throws AceException
* @throws OSException
*
*/
public OscoreAS(String asId, CoapDBConnector db, PDP pdp, TimeProvider time,
OneKey asymmetricKey, Map<String, String> peerNamesToIdentities,
Map<String, String> peerIdentitiesToNames,
Map<String, String> myIdentities) throws AceException, OSException {
this(asId, db, pdp, time, asymmetricKey, "token", "introspect",
CoAP.DEFAULT_COAP_PORT, null, false, (short)0, false,
peerNamesToIdentities, peerIdentitiesToNames, myIdentities);
}
/**
* Constructor.
*
* @param asId identifier of the AS
* @param db database connector of the AS
* @param pdp PDP for deciding who gets which token
* @param time time provider, must not be null
* @param asymmetricKey asymmetric key pair of the AS or
* null if it hasn't any
* @param tokenName the name of the token endpoint
* (will be converted into the address as well)
* @param introspectName the name of the introspect endpoint
* (will be converted into the address as well), if this is null,
* no introspection endpoint will be offered
* @param port the port number to run the server on
* @param claims the claim types to include in tokens issued by this
* AS, can be null to use default set
* @param setAudHeader insert the AUD as header in the CWT.
* @param masterSaltSize the size in bytes of the Master Salt to provide. It can be 0 to not provide a Master Salt
* @param provideIdContext true if the Id Context has to provided, or false otherwise
* @param peerNamesToIdentities mapping between the names of the peers and their OSCORE identities
* @param myIdentities mapping between the names of the peers and the OSCORE identities that the AS uses with them
*
* @throws AceException
* @throws OSException
*
*/
public OscoreAS(String asId, CoapDBConnector db,
PDP pdp, TimeProvider time, OneKey asymmetricKey, String tokenName,
String introspectName, int port, Set<Short> claims,
boolean setAudHeader, short masterSaltSize, boolean provideIdContext,
Map<String, String> peerNamesToIdentities,
Map<String, String> peerIdentitiesToNames,
Map<String, String> myIdentities) throws AceException, OSException {
this.t = new Token(asId, pdp, db, time, asymmetricKey, claims, setAudHeader,
masterSaltSize, provideIdContext, peerIdentitiesToNames);
this.token = new OscoreAceEndpoint(tokenName, this.t);
add(this.token);
if (introspectName != null) {
if (asymmetricKey == null) {
this.i = new Introspect(pdp, db, time, null, peerIdentitiesToNames);
} else {
this.i = new Introspect(pdp, db, time, asymmetricKey.PublicKey(), peerIdentitiesToNames);
}
this.introspect = new OscoreAceEndpoint(introspectName, this.i);
add(this.introspect);
}
this.addEndpoint(new CoapEndpoint.Builder()
.setCoapStackFactory(new OSCoreCoapStackFactory())
.setPort(port)
.setCustomCoapStackArgument(OscoreCtxDbSingleton.getInstance())
.build());
loadOscoreCtx(db, peerNamesToIdentities, myIdentities);
}
/**
* Load the OSCORE contexts from the database
*
* @param db the database connector
* @param asId the AS identifier
* @param peerNamesToIdentities mapping between the names of the peers and their OSCORE identities
* @param myIdentities mapping between the names of the peers and the OSCORE identities that the AS uses with them
*
* @throws AceException
* @throws OSException
*/
private static void loadOscoreCtx(CoapDBConnector db,
Map<String, String> peerNamesToIdentities,
Map<String, String> myIdentities) throws AceException, OSException {
Set<String> ids = db.getRSS();
ids.addAll(db.getClients());
for (String id : ids) {
byte[] key = db.getKey(new PskPublicInformation(id)).getEncoded();
// The identity that this AS uses with this peer
String identity = myIdentities.get(id);
IdPair idPair = new IdPair(identity);
if (idPair.getSenderId() == null) {
// This identity is malformed; proceed to the next one
}
byte[] senderId = idPair.getSenderId();
byte[] contextId = idPair.getContextId();
// The identity that this peer uses with this AS
identity = peerNamesToIdentities.get(id);
idPair = new IdPair(identity);
if (idPair.getSenderId() == null) {
// This identity is malformed; proceed to the next one
}
byte[] recipientId = idPair.getSenderId();
byte[] contextIdBis = idPair.getContextId();
// This are all error conditions; skipped this peer and proceed to the next one
if (!Arrays.equals(contextId, contextIdBis)) {
continue;
}
OSCoreCtx ctx = new OSCoreCtx(key, false, null, senderId,
recipientId, null, null, null, contextId, MAX_UNFRAGMENTED_SIZE);
OscoreCtxDbSingleton.getInstance().addContext(ctx);
}
LOGGER.finest("Loaded OSCORE contexts");
}
@Override
public void close() throws Exception {
LOGGER.info("Closing down OscoreAS ...");
this.token.close();
this.introspect.close();
}
public static class IdPair {
private byte[] senderId;
private byte[] contextId;
public IdPair(String identity) {
senderId = null;
contextId = null;
int index = identity.indexOf(":");
if (index != -1) {
// The Context ID is present
contextId = Base64.getDecoder().decode(identity.substring(0, index));
}
index++; // This becomes 0 if the Context ID was not present
senderId = Base64.getDecoder().decode(identity.substring(index, identity.length()));
}
public byte[] getSenderId() {
return senderId;
}
public byte[] getContextId() {
return contextId;
}
}
}