# Deploying a vIdP An OLYMPUS virtual IdP (vIdP) consists of a number of components working together. Some of these are delivered as part of the OLYMPUS framework, whereas others are specific for a concrete application and must therefore be implemented by the application developer. The main components are the following: * Configuration handling * Identity proofing * Cryptomodule and storage * Multifactor authentication * REST interface deployment This document introduces the various components, along with small samples. A full example can be found in the sourcecode as TODO and in the end of this page. ## Configuration handling The vIdP needs a number of parameters, such as key material, lifetime of issued tokens and information on the other servers, as part of the setup procedure. For full functionality of the distributed IdP, a PABCConfiguration should be used. This can either be provided as a custom implementation or using the standard PABCConfigurationImpl class provided by the OLYMPUS framework. The standard PABCConfigurationImpl can be provided using JSON (a full working set of configuration files can be found in test/resources): ``` {"keyStorePath":"src/test/resources/keystore.jks", "keyStorePassword":"server1", "trustStorePath":"src/test/resources/keystore.jks", "trustStorePassword":"server1", "port":9080, "myAuthorizationCookie":"hk5NQgrO9BJlBedHaD8Qro8rc6SGL65H9W90Wt2snpr2cKwnSQ+C2bK55F2sKZRa0gxAkKz6P5mu3Nk9WKLGSg==", "authorizationCookies":{ "5tabBLiLXuD1eWTTliSHvkfVLYr9+lhs0WhPpET3NGBCp3r4onpzRUh2/tyZvdW6fDp1yr2uAePpGPvXnhkwCw==":{"id":"server1", "roles":["SERVER"]}, "WS66b+TWK3XHHLJW3YHJ3KGJk0wEfeTmxouRFVc+/w9hps+OcL2JwxVW5kK6wH1tD3jAmD18yv/6fs3308vYDw==":{"id":"server2", "roles":["SERVER"]}, "8Y9mocwbGZbU0YSNQR46kb6DHYuniHqpXmOjM2uUQ+iEmlX/ka4ZPzBjgrWz9Zw/zeNA4Neq9LSLAaPa6+B0Vg==":{"id":"administrator", "roles":["ADMIN"]}}, "servers":["http://127.0.0.1:9081","http://127.0.0.1:9082"], "tlsPort":9933, "keyMaterial": {"modulus":209145188754454650...4757169531007361728238634367542988141617, "privateKey":1462249078153...0513422592, "publicExponent":65537}, "oprfBlindings":{"1":162683617192684354721534228919969509586,"2":201103837588655148085398197875718785305}, "rsaBlindings":{"1":116250903442045051614123370266866563879,"2":139820269263465496115751481575796568221}, "localKeyShare":"56tnFNMZfBX/...fqJHkH4y4eVEcvadnSv3iqmS6v7plckHPasX5kpxPqbmg07Zkb/WO0hrWWif08o/ceGRW9m9yadC5UG0l4aQuA==", "remoteShares":{"1":"iHOmSPB1DxgImGYJTv1rKk2wPZ4pMRa3...3FpFdBSuRLxtslThgNqn5Bkhhx8y6NXkWL/NXmC2F7yGxgn75+Q==", "2":"nhVAQ6gKlD8XxfjPMLLM+rbW39Iex3...UU5m8FPeYygiYkEJJCchTHn33dA=="}, "oprfKey":2272631655901059652376853502653585612556037362756263395289688822267433693845509915514559918780126188577715083976063300998323278426691654597, "refreshKey":"AAAAAA==", "id":0, "waitTime":1000, "attrDefinitions":[ {"id":"url:PurchaseTime", "shortName":"Time of Purchase", "minDate":"2000-01-01T00:00:00", "maxDate":"2021-02-01T00:00:00", "type":"Date"}, {"id":"url:DateOfBirth", "shortName":"Date of Birth", "minDate":"1950-01-01T00:00:00", "maxDate":"2021-09-01T00:00:00", "granularity":"DAYS", "type":"Date"} {"id":"url:IntegerWithNegatives", "shortName":"Int", "minimumValue":-20, "maximumValue":300, "type":"Integer"}, {"id":"url:HasDrivingPermit", "shortName":"Has Driving Permit", "type":"Boolean"}, {"id":"url:FamilyName", "shortName":"Family Name", "minLength":2, "maxLength":16, "type":"String"} ], "seed":"cmFuZG9tIHZhbHVlIHJhbmRvbSB2YWx1ZSByYW5kb20gdmFsdWUgcmFuZG9tIHZhbHVlIHJhbmRvbQ==", "lifetime":72000000, "allowedTimeDifference":10000} ``` ## Identity proofing Identity proofing is the process of getting user attributes into the vIdP. After a user account has been created, a user may send an IdentityProof to the vIdP. If the vIdP can validate the proof, the attributes in the IdentityProof is then stored as part of the user's account. The IdentityProof is essentially just a key-value mapping of attributes along with some kind of signature. This can be implemented done in many different ways, using JWT, signed XML or many other schemes, and is left up to the application developer to implement. For each identity proofing scheme the application should support, the application develop must implement two components, an implementation of the IdentityProver interface and a matching implementation of the IdentityProof interface. The following is a very basic example, using a key-value map of attributes along with a signature string as an IdentityProof and a matching IdentityProver, that verifies that the signature is "SIGNATURE" and stores the attributes. * IdentityProof: ```java @JsonTypeInfo(use=Id.CLASS, include=As.PROPERTY, property="@class") public class DemoIdentityProof extends IdentityProof { private String signature; @JsonTypeInfo(use=Id.CLASS, include=As.PROPERTY, property="class") private Map attributes; public DemoIdentityProof() { } public DemoIdentityProof(String signature, Map attributes) { super(); this.signature = signature; this.attributes = attributes; } public Object getSignature() { return signature; } public void setSignature(String signature) { this.signature = signature; } public Map getAttributes() { return attributes; } public void setAttributes(Map attributes) { this.attributes = attributes; } } ``` * IdentityProver: ```java public class DemoIdentityProver implements IdentityProver { private Storage storage; public DemoIdentityProver(Storage storage) { this.storage = storage; } //Only validates that the proof is a TokenIdentityProof @Override public boolean isValid(String input, String username) { ObjectMapper mapper = new ObjectMapper(); try { mapper.readValue(input, DemoIdentityProof.class); } catch (IOException e) { return false; } return true; } @Override public void addAttributes(String input, String username) { ObjectMapper mapper = new ObjectMapper(); DemoIdentityProof proof; try { proof = mapper.readValue(input, DemoIdentityProof.class); storage.addAttributes(username, proof.getAttributes()); } catch (IOException e) { } } } ``` ## Cryptomodule and storage In some applications there may be requirements to storing sensitive information in special hardware. In order to support this, the application developer should make an implementation of the relevant storage interface (in order to persist data) and may implement a custom implementation of the ServerCryptoModule interface. The storage interface allows the application developer to use their choice of database. The OLYMPUS framework does contain an InMemory implementation of the relevant interface, however as the name implies, this does not persist data after restarts. The ServerCryptoModule allows integration with HSM modules or other strong cryptographic solutions. The OLYMPUS framework contains a software implementation of the interface and may be used if HSM support is not required. The following code sample will create an InMemoryDatabase and a SoftwareServerCryptoModule: ```java PestoDatabase db = new InMemoryPestoDatabase(); ServerCryptoModule cryptoModule = new SoftwareServerCryptoModule(new SecureRandom()); ``` ## Multifactor authentication Some applications may require the use of multifactor authentication to provide an additional layer of security. Similar to the processes around identity proofing, this can be done in a number of ways and is therefore left for the application developer to implement. Each authenticator implementation must implement the MFAAuthenticator interface, which specifies 4 main methods: * generateTOTP(String secret) - Given some private keymaterial, generate a one-time key. * isValid(String token, String secret) - Validate if the provided token is valid with regard to the key material. * generateSecret() - Generate private key material. * combineSecrets(List secrets) - Given a list of partial key material, combine it into a single key. Note that since the vIdP is distributed, each partial IdP need to generate "trusted" key material, which is then combined to the key material that is actually used. In some cases, this may not be necessary, in which case the combineSecrets method should be implemented accordingly. The following sample code shows how a simple TOTP based Authenticators could be implemented. Note that each type of authenticator should have some unique identifier, so the the clients can specify which type of authenticator is used: ```java public class TOTPAuthenticator implements MFAAuthenticator { public static final String TYPE = "TOTP_AUTHENTICATOR"; private static final int BYTES_IN_SECRET = 20; private final CommonCrypto crypto; private final Base32 base32 = new Base32(); public TOTPAuthenticator(CommonCrypto crypto) { this.crypto = crypto; } @Override public boolean isValid(String token, String secret) { String totp = generateTOTP(secret); return totp.equals(token); } @Override public String generateTOTP(String secret) { byte[] bytes = base32.decode(secret); String hexKey = Hex.encodeHexString(bytes); return TOTP.getOTP(hexKey); } @Override public String generateSecret() { return base32.encodeToString(crypto.getBytes(BYTES_IN_SECRET)); } @Override public String combineSecrets(List secrets) { byte[] combinedSecret = base32.decode(secrets.remove(0)); while(secrets.size() > 0) { combinedSecret = Util.xorArray(combinedSecret, base32.decode(secrets.remove(0))); } Base32 base32 = new Base32(); return base32.encodeToString(combinedSecret); } } ``` ## REST based deployment The OLYMPUS framework offers a basic REST server (RESTIdPServer), allowing for the vIdP functionality to be exposed as a series of REST endpoints. The RESTIdPServer takes an VirtualIdP as argument and is afterwards configured with relevant ports for HTTP/HTTPS communication as well as a trust- and keystores for TLS connectivity. ```java RESTIdPServer restServer = new RESTIdPServer(); restServer.setIdP(idp); List servlets = new ArrayList<>(1); servlets.add(PestoIdPServlet.class.getCanonicalName()); String keyStorePath = "some/path"; restServer.start(8080, servlets, 443, keyStorePath, "keyStorePassword", "trustStorePassword"); ``` ## Complete example The following tutorial code shows how a partial IdP may be started using components included in the framework. Note that it is uses the previously described DemoIdentityProvider and TOTPAuthenticator. While the TOTPAuthenticator is "secure", the DemoIdentityProvider does no validation and should only be used for testing. A slight modified version of the example code may also be found in the source code as the RunOIDCServer or RunCFPServer classes in the oidc-demo-idp and cfp-usecase projects. ```java public class RunTestServer { /** * Main method to start a server. Takes the path to a configuration file as a paramenter. If no paramenters * are given, it looks for the location of the configuration file in the ENV variable CONFIG_FILE */ public static void main(String[] args) throws Exception { ObjectMapper mapper = new ObjectMapper(); PABCConfigurationImpl configuration = null; if (args.length == 0) { String configFile = System.getenv("CONFIG_FILE"); configuration = mapper.readValue(new File(configFile), PABCConfigurationImpl.class); } else { configuration = mapper.readValue(new File(args[0]), PABCConfigurationImpl.class); } List others = new ArrayList(); for (String s: configuration.getServers()) { others.add(new PestoIdP2IdPRESTConnection(s, configuration.getId(), configuration.getKeyStorePath(), configuration.getKeyStorePassword(), configuration.getTrustStorePath(), configuration.getTrustStorePassword(), configuration.getMyAuthorizationCookie())); } //Setup databases PestoDatabase db = new InMemoryPestoDatabase(); //Setup identity provers List identityProvers = new LinkedList(); identityProvers.add(new DemoIdentityProver(db)); ServerCryptoModule cryptoModule = new SoftwareServerCryptoModule(new SecureRandom()); List types = new ArrayList<>(1); types.add(PestoIdPServlet.class.getCanonicalName()); //Setup the IdP. OIDCPestoIdPImpl idp = null; Map authenticators = new HashMap<>(); authenticators.put(TOTPAuthenticator.TYPE, new TOTPAuthenticator()); idp = new OIDCPestoIdPImpl(db, identityProvers, authenticators, cryptoModule); idp.setup("ssid", configuration, others); //And also an in memory database for authorization of servers and admins for(String cookie: configuration.getAuthorizationCookies().keySet()) { Authorization authorization = configuration.getAuthorizationCookies().get(cookie); authorization.setExpiration(System.currentTimeMillis()+604800000); //valid for one week idp.addSession(cookie, authorization); } RESTIdPServer restServer = new RESTIdPServer(); restServer.setIdP(idp); try { restServer.start(configuration.getPort(), types, configuration.getTlsPort(), configuration.getKeyStorePath(), configuration.getKeyStorePassword(), configuration.getTrustStorePassword()); }catch(Exception e) { } } ```