devices/ms/DeviceW8W10.php
<?php
/*
* *****************************************************************************
* Contributions to this work were made on behalf of the GÉANT project, a
* project that has received funding from the European Union’s Framework
* Programme 7 under Grant Agreements No. 238875 (GN3) and No. 605243 (GN3plus),
* Horizon 2020 research and innovation programme under Grant Agreements No.
* 691567 (GN4-1) and No. 731122 (GN4-2).
* On behalf of the aforementioned projects, GEANT Association is the sole owner
* of the copyright in all material which was developed by a member of the GÉANT
* project. GÉANT Vereniging (Association) is registered with the Chamber of
* Commerce in Amsterdam with registration number 40535155 and operates in the
* UK as a branch of GÉANT Vereniging.
*
* Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands.
* UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK
*
* License: see the web/copyright.inc.php file in the file structure or
* <base_url>/copyright.php after deploying the software
*/
/**
* This file creates MS Windows 8 and 10 installers
* It supports EAP-TLS, TTLS (both native and GEANTLink), PEAP.
* Other EAP methods could be added.
*
* The file is an interface between the global CAT system and individual EAP
* methods modules. It also performs global operations like preparing
* and saving certificates and generating the installers.
*
* Adding a new EAP handler requires defining an extension of the MsEapProfile
* class. Such an extension is required to define a public getConfig method
* returning a valid Windows XML <Config> element.
* Extensions to Files/common.inc will also be required.
*
* @author Tomasz Wolniewicz <twoln@umk.pl>
*
* @package ModuleWriting
*/
namespace devices\ms;
use Exception;
class DeviceW8W10 extends \devices\ms\WindowsCommon
{
public function __construct()
{
parent::__construct();
\core\common\Entity::intoThePotatoes();
$this->setSupportedEapMethods([
\core\common\EAP::EAPTYPE_TLS,
\core\common\EAP::EAPTYPE_PEAP_MSCHAP2,
\core\common\EAP::EAPTYPE_TTLS_PAP,
\core\common\EAP::EAPTYPE_TTLS_MSCHAP2,
\core\common\EAP::EAPTYPE_SILVERBULLET
]);
$this->profileNames = [];
$this->specialities['internal:use_anon_outer'][serialize(\core\common\EAP::EAPTYPE_PEAP_MSCHAP2)] = _("Anonymous identities do not use the realm as specified in the profile - it is derived from the suffix of the user's username input instead.");
$this->specialities['media:openroaming'] = _("While OpenRoaming can be configured, it is possible that the Wi-Fi hardware does not support it; then the network definition is ignored.");
$this->specialities['media:consortium_OI'] = _("While Passpoint networks can be configured, it is possible that the Wi-Fi hardware does not support it; then the network definition is ignored.");
\core\common\Entity::outOfThePotatoes();
}
/**
* create the actual installer executable
*
* @return string filename of the generated installer
*
*/
public function writeInstaller()
{
\core\common\Entity::intoThePotatoes();
$this->prepareInstallerLang();
$this->setupEapConfig();
$setWired = isset($this->attributes['media:wired'][0]) && $this->attributes['media:wired'][0] == 'on' ? 1 : 0;
$this->iterator = 0;
$fcontentsProfile = '';
$this->createProfileDir();
foreach ($this->attributes['internal:networks'] as $networkName => $oneNetwork) {
if ($this::separateHS20profiles === true) {
$fcontentsProfile .= $this->saveNetworkProfileSeparateHS($networkName, $oneNetwork);
} else {
$fcontentsProfile .= $this->saveNetworkProfileJoinedHS($networkName, $oneNetwork);
}
}
file_put_contents('profiles.nsh', $fcontentsProfile);
$delSSIDs = $this->attributes['internal:remove_SSID'];
$delProfiles = [];
foreach ($delSSIDs as $ssid => $cipher) {
if ($cipher == 'DEL') {
$delProfiles[] = $ssid;
}
if ($cipher == 'TKIP') {
$delProfiles[] = $ssid.' (TKIP)';
}
}
// the two lines below remove the eduroam® profiles we used to install, this is a temporary hack untill a better solution is implemented
$delProfiles[] = 'eduroam®';
$delProfiles[] = 'eduroam® via partner';
// this removes the profile container that we used in CAT 2.1 and removed in 2.1.1
$delProfiles[] = sprintf('%s Custom Network', \core\CAT::$nomenclature_participant);
$this->writeAdditionalDeletes($delProfiles);
if ($setWired) {
$this->loggerInstance->debug(4, "Saving LAN profile\n");
$windowsProfile = $this->generateLANprofile();
$this->saveProfile($windowsProfile);
}
$this->saveCerts();
if ($this->selectedEap == \core\common\EAP::EAPTYPE_SILVERBULLET) {
$this->writeClientP12File();
}
$this->copyFiles($this->selectedEap);
$this->saveLogo();
$this->writeMainNSH($this->selectedEap, $this->attributes);
$this->compileNSIS();
$installerPath = $this->signInstaller();
\core\common\Entity::outOfThePotatoes();
return $installerPath;
}
private function createProfileDir()
{
if (!is_dir('profiles')) {
mkdir('profiles');
}
}
/**
* If separateHS20profiles is true then we should be saving OID and SSID
* profiles separately. OID profiles should be considered optionl, i.e.
* the installer should not report installation failure (??). If we decide
* that such installation failures should be silent then it is enough if
* these profiles are marked as hs20 and no "nohs" profiles are created
*/
private function saveNetworkProfileSeparateHS($profileName, $network)
{
$out = '';
if (!empty($network['ssid'])) {
if ($this::separateSSIDprofiles === true && !empty($network['condition']) && $network['condition'] === 'locally_defined') {
$out = "";
foreach ($network['ssid'] as $ssid) {
$this->loggerInstance->debug(4, "SSID network: $ssid\n");
$windowsProfileSSID = $this->generateWlanProfile($ssid, [$ssid], 'WPA2', 'AES', [], false);
$this->saveProfile($windowsProfileSSID, $this->iterator, true);
$out .= "!insertmacro define_wlan_profile \"$ssid\" \"AES\" 0 \"0\"\n";
$this->iterator++;
}
} else {
$this->loggerInstance->debug(4, "SSID network: $profileName\n");
$windowsProfileSSID = $this->generateWlanProfile($profileName, $network['ssid'], 'WPA2', 'AES', [], false);
$this->saveProfile($windowsProfileSSID, $this->iterator, true);
$ssids = '';
foreach ($network['ssid'] as $ssid) {
if ($ssid != $profileName) {
$ssids .= '|'.$ssid;
}
}
$out = "!insertmacro define_wlan_profile \"$profileName\" \"AES\" 0 \"$ssids\"\n";
$this->iterator++;
}
$profileName .= " via partner";
}
if (!empty($network['oi'])) {
$this->loggerInstance->debug(4, "RCOI network: $profileName\n");
$windowsProfileHS = $this->generateWlanProfile($profileName, ['cat-passpoint-profile'], 'WPA2', 'AES', $network['oi'], true);
$this->saveProfile($windowsProfileHS, $this->iterator, true);
$out .= "!insertmacro define_wlan_profile \"$profileName\" \"AES\" 1 \"0\"\n";
$this->iterator++;
}
return($out);
}
/**
* If separateHS20profiles is false then we should be saving a hs20 profile
* containing both OIDs and SSIDs. In addition we should also be saving
* a nohs_... profile. When the installer runs it first tries the normal
* profile and if this fails it will try the nohs (if one exists)
*/
private function saveNetworkProfileJoinedHS($profileName, $network)
{
$oiOnly = false;
if ($network['ssid'] == []) {
$oiOnly = true;
$network['ssid'] = ['cat-passpoint-profile'];
}
$windowsProfile = $this->generateWlanProfile($profileName, $network['ssid'], 'WPA2', 'AES', $network['oi'], true);
$this->saveProfile($windowsProfile, $this->iterator, true);
if (!$oiOnly) {
$windowsProfile = $this->generateWlanProfile($profileName, $network['ssid'], 'WPA2', 'AES', [], false);
$this->saveProfile($windowsProfile, $this->iterator, false);
}
$this->iterator++;
return("!insertmacro define_wlan_profile \"$profileName\" \"AES\" 2 \"".implode('|', $network['ssid'])."\"\n");
}
private function saveLogo()
{
$fedLogo = $this->attributes['fed:logo_file'] ?? NULL;
$idpLogo = $this->attributes['internal:logo_file'] ?? NULL;
$this->combineLogo($idpLogo, $fedLogo);
}
private function writeMainNSH($eap, $attr)
{
$this->loggerInstance->debug(4, "writeMainNSH");
$this->loggerInstance->debug(4, $attr);
$this->loggerInstance->debug(4, "Device_id = ".$this->device_id."\n");
$fcontents = "!define W8\n";
if ($this->device_id == 'w10') {
$fcontents .= "!define W10\n";
}
$fcontents .= "Unicode true\n";
if ($this->useGeantLink && $this->selectedEap['OUTER'] == \core\common\EAP::TTLS) {
$eapStr = 'GEANTLink';
} else {
$eapStr = \core\common\EAP::eapDisplayName($this->selectedEap)['OUTER'];
}
if (isset($this->tlsOtherUsername) && $this->tlsOtherUsername == 1) {
$fcontents .= "!define PFX_USERNAME\n";
}
if ($eap == \core\common\EAP::EAPTYPE_SILVERBULLET) {
$fcontents .= "!define SILVERBULLET\n";
}
$fcontents .= '!define '.$eapStr;
$fcontents .= "\n".'!define EXECLEVEL "user"';
$fcontents .= $this->writeNsisDefines($attr);
file_put_contents('main.nsh', $fcontents);
}
private function copyFiles($eap)
{
$this->loggerInstance->debug(4, "copyFiles start\n");
$this->copyBasicFiles();
switch ($eap["OUTER"]) {
case \core\common\EAP::TTLS:
if ($this->useGeantLink) {
$this->copyGeantLinkFiles();
} else {
$this->copyStandardNsi();
}
break;
default:
$this->copyStandardNsi();
}
$this->loggerInstance->debug(4, "copyFiles end\n");
return true;
}
private function copyStandardNsi()
{
if (!$this->translateFile('eap_w8.inc', 'cat.NSI')) {
throw new Exception("Translating needed file eap_w8.inc failed!");
}
}
private function saveCerts()
{
$caArray = $this->saveCertificateFiles('der');
$fcontentsCerts = '';
$fileHandleCerts = fopen('certs.nsh', 'w');
if ($fileHandleCerts === false) {
throw new Exception("Unable to open new certs.nsh file for writing CAs.");
}
foreach ($caArray as $certAuthority) {
$store = $certAuthority['root'] ? "root" : "ca";
$fcontentsCerts .= '!insertmacro install_ca_cert "'.$certAuthority['file'].'" "'.$certAuthority['sha1'].'" "'.$store."\"\n";
}
fwrite($fileHandleCerts, $fcontentsCerts);
fclose($fileHandleCerts);
}
/* saveProvile writes a LAN or WLAN profile
* @param string $profile the XML content to be saved
* @param int $profileNumber the profile index or NULL to indicate a LAN profile
* @param boolean $hs20 for WLAN profiles indicates if use the nohs prefix
*/
private function saveProfile($profile, $profileNumber = NULL, $hs20 = false)
{
if ($hs20) {
$prefix = 'w';
} else {
$prefix = 'nohs_w';
}
if (is_null($profileNumber)) {
$prefix = '';
$suffix = '';
} else {
$suffix = "-$profileNumber";
}
$xmlFname = "profiles/".$prefix."lan_prof".$suffix.".xml";
$this->loggerInstance->debug(4, "Saving profile to ".$xmlFname."\n");
file_put_contents($xmlFname, $profile);
}
/**
* Selects the appropriate handler for a given EAP type and retirns
* an initiated object
*
* @return a profile object
*/
private function setEapObject()
{
switch ($this->selectedEap['OUTER']) {
case \core\common\EAP::TTLS:
if ($this->useGeantLink) {
return(new GeantLinkTtlsProfile());
} else {
return(new MsTtlsProfile());
}
case \core\common\EAP::PEAP:
return(new MsPeapProfile());
case \core\common\EAP::TLS:
return(new MsTlsProfile());
default:
// use Exception here
break;
}
}
private function setupEapConfig() {
$servers = empty($this->attributes['eap:server_name']) ? '' : implode(';', $this->attributes['eap:server_name']);
$outerId = $this->determineOuterIdString();
$nea = (\core\common\Entity::getAttributeValue($this->attributes, 'media:wired', 0) === 'on') ? 'true' : 'false';
$otherTlsName = \core\common\Entity::getAttributeValue($this->attributes, 'eap-specific:tls_use_other_id', 0) === 'on' ? 'true' : 'false';
if (isset(\core\common\Entity::getAttributeValue($this->attributes, 'device-specific:geantlink', $this->device_id)[0]) &&
\core\common\Entity::getAttributeValue($this->attributes, 'device-specific:geantlink', $this->device_id)[0] === 'on') {
$this->useGeantLink = true;
} else {
$this->useGeantLink = false;
}
$eapConfig = $this->setEapObject();
$eapConfig->setInnerType($this->selectedEap['INNER']);
$eapConfig->setInnerTypeDisplay(\core\common\EAP::eapDisplayName($this->selectedEap)['INNER']);
$eapConfig->setCAList($this->getAttribute('internal:CAs')[0]);
$eapConfig->setServerNames($servers);
$eapConfig->setOuterId($outerId);
$eapConfig->setNea($nea);
$eapConfig->setDisplayName($this->translateString($this->attributes['general:instname'][0]));
$eapConfig->setIdPId($this->deviceUUID);
$eapConfig->setOtherTlsName($otherTlsName);
$eapConfig->setConfig();
$this->eapConfigObject = $eapConfig;
}
private function generateWlanProfile($networkName, $ssids, $authentication, $encryption, $ois, $hs20 = false)
{
if (empty($this->attributes['internal:realm'][0])) {
$domainName = \config\ConfAssistant::CONSORTIUM['interworking-domainname-fallback'];
} else {
$domainName = $this->attributes['internal:realm'][0];
}
$wlanProfile = new MsWlanProfile();
$wlanProfile->setName($networkName);
$wlanProfile->setEncryption($authentication, $encryption);
$wlanProfile->setSSIDs($ssids);
$wlanProfile->setHS20($hs20);
$wlanProfile->setOIs($ois);
$wlanProfile->setDomainName($domainName);
$wlanProfile->setEapConfig($this->eapConfigObject);
return($wlanProfile->writeWLANprofile());
}
private function generateLanProfile()
{
$lanProfile = new MsLanProfile();
$lanProfile->setEapConfig($this->eapConfigObject);
return($lanProfile->writeLANprofile());
}
private $eapTypeId;
private $eapAuthorId;
private $eapConfigObject;
private $profileNames;
private $iterator;
}