|
|
|
@@ -94,6 +94,12 @@ data:
|
|
|
|
|
js_content passpoint.install;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Signed Windows provisioning test file (dummy credentials), reached
|
|
|
|
|
# without the gate through the Ingress wlan-setup-provisioning.
|
|
|
|
|
location = /windows/test-profile.xml {
|
|
|
|
|
js_content passpoint.windowsTest;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
location ~ ^/(android|apple)/callback$ {
|
|
|
|
|
# The query carries the one-time authorization code.
|
|
|
|
|
access_log off;
|
|
|
|
@@ -153,7 +159,14 @@ data:
|
|
|
|
|
# identity anonymous, trusted server name radius.renner.uno, username and
|
|
|
|
|
# app password. Fixed PayloadIdentifier/UUIDs so a reinstall replaces the
|
|
|
|
|
# previous profile.
|
|
|
|
|
# Windows: provisioning XML (CarrierProvisioning) for
|
|
|
|
|
# ms-settings:wifi-provisioning, signed with XML-DSig (enveloped, C14N,
|
|
|
|
|
# rsa-sha1, as Microsoft's samples) by the wlan-setup-signing key. The
|
|
|
|
|
# document is written in canonical form, so the digest covers it as sent.
|
|
|
|
|
passpoint.js: |
|
|
|
|
|
import fs from "fs";
|
|
|
|
|
import cr from "crypto";
|
|
|
|
|
|
|
|
|
|
const ISRG_ROOT_X1 = "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZLubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=";
|
|
|
|
|
const USER = /^[A-Za-z0-9._@+-]{1,64}$/;
|
|
|
|
|
|
|
|
|
@@ -229,6 +242,79 @@ data:
|
|
|
|
|
'</dict></plist>\n';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const SIGNING = "/etc/wlan-signing/";
|
|
|
|
|
const DSIG = "http://www.w3.org/2000/09/xmldsig#";
|
|
|
|
|
const CARRIER_ID = "{5423c37a-7530-4ce9-8f87-a2574ea006bc}";
|
|
|
|
|
|
|
|
|
|
function pem(text, label) {
|
|
|
|
|
const re = new RegExp("-----BEGIN " + label + "-----([^-]+)-----END " + label + "-----", "g");
|
|
|
|
|
const out = [];
|
|
|
|
|
let m;
|
|
|
|
|
while ((m = re.exec(text)) !== null) out.push(m[1].replace(/\s+/g, ""));
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Canonical form (C14N 1.0): no self-closing tags, namespaces declared
|
|
|
|
|
// only where they change.
|
|
|
|
|
function windowsProfile(user, password) {
|
|
|
|
|
const common = ' xmlns="http://www.microsoft.com/provisioning/EapCommon"';
|
|
|
|
|
return '<CarrierProvisioning xmlns="http://www.microsoft.com/networking/CarrierControl/v1">' +
|
|
|
|
|
'<Global><CarrierId>' + CARRIER_ID + '</CarrierId><SubscriberId>renner-unifi</SubscriberId></Global>' +
|
|
|
|
|
'<WLANProfiles><WLANProfile xmlns="http://www.microsoft.com/networking/CarrierControl/WLAN/v1">' +
|
|
|
|
|
'<name>Renner UniFi</name>' +
|
|
|
|
|
'<SSIDConfig><SSID><name>Renner UniFi</name></SSID></SSIDConfig>' +
|
|
|
|
|
'<Hotspot2><DomainName>renner.uno</DomainName><NAIRealm><name>renner.uno</name></NAIRealm></Hotspot2>' +
|
|
|
|
|
'<MSM><security>' +
|
|
|
|
|
'<authEncryption><authentication>WPA3ENT</authentication><encryption>AES</encryption><useOneX>true</useOneX></authEncryption>' +
|
|
|
|
|
'<OneX xmlns="http://www.microsoft.com/networking/OneX/v1"><authMode>user</authMode><EAPConfig>' +
|
|
|
|
|
'<EapHostConfig xmlns="http://www.microsoft.com/provisioning/EapHostConfig">' +
|
|
|
|
|
'<EapMethod><Type' + common + '>21</Type><VendorId' + common + '>0</VendorId>' +
|
|
|
|
|
'<VendorType' + common + '>0</VendorType><AuthorId' + common + '>311</AuthorId></EapMethod>' +
|
|
|
|
|
'<Config><EapTtls xmlns="http://www.microsoft.com/provisioning/EapTtlsConnectionPropertiesV1">' +
|
|
|
|
|
'<ServerValidation><ServerNames>radius.renner.uno</ServerNames>' +
|
|
|
|
|
'<TrustedRootCAHash>ca bd 2a 79 a1 07 6a 31 f2 1d 25 36 35 cb 03 9d 43 29 a5 e8</TrustedRootCAHash>' +
|
|
|
|
|
'<DisablePrompt>true</DisablePrompt></ServerValidation>' +
|
|
|
|
|
'<Phase2Authentication><PAPAuthentication></PAPAuthentication></Phase2Authentication>' +
|
|
|
|
|
'<Phase1Identity><IdentityPrivacy>true</IdentityPrivacy><AnonymousIdentity>anonymous</AnonymousIdentity></Phase1Identity>' +
|
|
|
|
|
'</EapTtls></Config></EapHostConfig></EAPConfig></OneX>' +
|
|
|
|
|
'<EapHostUserCredentials xmlns="http://www.microsoft.com/provisioning/EapHostUserCredentials" xmlns:eapCommon="http://www.microsoft.com/provisioning/EapCommon">' +
|
|
|
|
|
'<EapMethod><eapCommon:Type>21</eapCommon:Type><eapCommon:AuthorId>311</eapCommon:AuthorId></EapMethod>' +
|
|
|
|
|
'<Credentials><EapTtls xmlns="http://www.microsoft.com/provisioning/EapTtlsUserPropertiesV1">' +
|
|
|
|
|
'<Username>' + xml(user) + '</Username><Password>' + xml(password) + '</Password>' +
|
|
|
|
|
'</EapTtls></Credentials></EapHostUserCredentials>' +
|
|
|
|
|
'</security></MSM></WLANProfile></WLANProfiles></CarrierProvisioning>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function signed(doc) {
|
|
|
|
|
const digest = cr.createHash("sha1").update(doc).digest("base64");
|
|
|
|
|
const signedInfo = '<SignedInfo xmlns="' + DSIG + '">' +
|
|
|
|
|
'<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></CanonicalizationMethod>' +
|
|
|
|
|
'<SignatureMethod Algorithm="' + DSIG + 'rsa-sha1"></SignatureMethod>' +
|
|
|
|
|
'<Reference URI=""><Transforms><Transform Algorithm="' + DSIG + 'enveloped-signature"></Transform></Transforms>' +
|
|
|
|
|
'<DigestMethod Algorithm="' + DSIG + 'sha1"></DigestMethod><DigestValue>' + digest + '</DigestValue></Reference>' +
|
|
|
|
|
'</SignedInfo>';
|
|
|
|
|
const der = Buffer.from(pem(fs.readFileSync(SIGNING + "tls.key", "utf8"), "PRIVATE KEY")[0], "base64");
|
|
|
|
|
const key = await crypto.subtle.importKey("pkcs8", der, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-1" }, false, ["sign"]);
|
|
|
|
|
const value = Buffer.from(await crypto.subtle.sign({ name: "RSASSA-PKCS1-v1_5" }, key, signedInfo)).toString("base64");
|
|
|
|
|
const certs = pem(fs.readFileSync(SIGNING + "tls.crt", "utf8"), "CERTIFICATE")
|
|
|
|
|
.map(c => "<X509Certificate>" + c + "</X509Certificate>").join("");
|
|
|
|
|
const signature = '<Signature xmlns="' + DSIG + '">' + signedInfo +
|
|
|
|
|
"<SignatureValue>" + value + "</SignatureValue><KeyInfo><X509Data>" + certs + "</X509Data></KeyInfo></Signature>";
|
|
|
|
|
return doc.replace(/<\/CarrierProvisioning>$/, signature + "</CarrierProvisioning>");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Test of the signature only: fixed profile with credentials that exist
|
|
|
|
|
// nowhere.
|
|
|
|
|
async function windowsTest(r) {
|
|
|
|
|
try {
|
|
|
|
|
r.headersOut["Content-Type"] = "text/xml";
|
|
|
|
|
r.return(200, await signed(windowsProfile("wlan-signature-test", "not-a-real-password")));
|
|
|
|
|
} catch (e) {
|
|
|
|
|
r.error("wlan-setup windows test: " + e.message);
|
|
|
|
|
r.return(500, "signing failed\n");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const PLATFORMS = {
|
|
|
|
|
android: { label: "Android" },
|
|
|
|
|
apple: { label: "Apple" },
|
|
|
|
@@ -347,7 +433,7 @@ data:
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default { install, callback };
|
|
|
|
|
export default { install, callback, windowsTest };
|
|
|
|
|
|
|
|
|
|
index.html: |
|
|
|
|
|
<!doctype html>
|
|
|
|
|