#!/usr/bin/php
<?php
#=======================================================================
# Configuration
#=======================================================================
# Digital ID of the requester
# This PEM file consists of four parts in this order:
# 1st the encrypted private key of the requester
# 2nd the certificate of the requester
# 3rd the certificate of the DFN-Verein Global Issuing CA
# 4th the certificate of the DFN-Verein Certification Authority 2
$pemfile = '/path/to/digitalID.pem';
# The password for decrypting the private key
$pempass = 'Who-knows-this-can-steal-my-identity';
# RA-ID of the corresponding participant service staff member
# 4930 = university
# 5540 = academy of fine arts
$raid = 4930;
# Requested certificate profile
# For current TLS servers choose: Webserver MustStaple
# (Current TLS servers support OCSP Stapling)
# For old TLS servers choose: Web Server
# For TLS server+clients choose Mail Server
# For Shibboleth servers choose: Shibboleth IdP SP
# Please let the CA advise you
$profil = 'Webserver MustStaple';
# Fully qualified server names
# The "primary name" must be mentioned first
# Do not use uppercase letters!
$fqdnlist = [
'hostname.uni-muenster.de',
'hostname2.uni-muenster.de',
'hostname.wwu.de',
'hostname2.wwu.de',
];
# Revocation password of the requester for the requested certificate
$revocationpass = 'Who-knows-this-can-request-a-revocation';
# Exactly with this German text the request must be confirmed
$confirm = 'Hiermit unterschreibe ich den Zertifikatantrag.';
#=======================================================================
# Current time stamp for file names
#=======================================================================
$now=date('Ymd\\THis');
#=======================================================================
# Compose Subject and Subject Alternative Names
#=======================================================================
# The RA-ID determines the organization name
switch($raid){
case 4930:
$org = 'Westfaelische Wilhelms-Universitaet Muenster';
$unit = 'WWU';
break;
case 5540:
$org = 'Kunstakademie Muenster - Hochschule fuer Bildende Kuenste';
$unit = 'KA';
break;
default:
die("RA-ID unknown\n");
}
# Syntax check of the fully qualified server names
if(!$fqdnlist or !is_array($fqdnlist))
die("FQDN list faulty\n");
foreach($fqdnlist as $fqdn)
if(strlen($fqdn) > 253 or !preg_match(
'#^(?:(?!-)[a-z0-9-]{1,63}(?<!-)\\.)+[a-z]{2,63}$#',
$fqdn
)) die("FQDN faulty: $fqdn\n");
# Compose Subject Alternative Names
$reqsan = [];
foreach($fqdnlist as $fqdn)
$reqsan[] = 'DNS:' . $fqdn;
# Compose Subject Distinguished Name in OpenSSL format
# Slashes must be escaped
$fqdn = reset($fqdnlist);
$reqsub = '/C=DE/ST=Nordrhein-Westfalen/L=Muenster'
. '/O=' . strtr($org, ['/' => '\\/'])
. '/CN=' . strtr($fqdn, ['/' => '\\/']);
#=======================================================================
# Compose revocation password hash
#=======================================================================
# To retrieve request information, request printout and issued
# certificate, the same revocation password hash must be specified as
# when the request was submitted
$reqpin = sha1($revocationpass);
#=======================================================================
# Extract requester details from user certificate
#=======================================================================
# Read digital ID of the requester
$data = file_get_contents($pemfile)
or die("User certificate not found\n");
$x509 = openssl_x509_parse($data)
or die("User certificate faulty\n");
# Extract and check name, email, and purpose of key use
$name = @$x509['subject']['CN'];
$mail = @$x509['subject']['emailAddress'];
$exku = explode(', ',@$x509['extensions']['extendedKeyUsage']);
if( !is_string($name)
or !is_string($mail)
# User certificates have blanks between first and last name
# Server certificate no not have a blank here
or false === strpos($name, ' ')
# Purpose of key use
or !in_array('TLS Web Client Authentication', $exku)
) die("User certificate not usable\n");
#=======================================================================
# Download root CA certificate and intermediate CA certificates
#=======================================================================
# Root CA certificate both for the new digital ID and for verifying
# the SOAP and HTTPS servers
copy('https://www.uni-muenster.de/CA/rootca.pem', "$now.$fqdn.root")
or die("Root certificate not downloadable\n");
# Zwischen-CA-Zertifikate für die neue digitale ID
copy('https://www.uni-muenster.de/CA/chain.pem', "$now.$fqdn.chain")
or die("Intermediate certificates not downloadable\n");
#=======================================================================
# Generate key pair and certificate request file
#=======================================================================
# Using the OpenSSL functions of PHP this would be much more complicated
passthru('/usr/bin/openssl req -new -sha256 -newkey rsa:2048 -nodes'
. ' -keyout ' . escapeshellarg( "$now.$fqdn.key" )
. ' -out ' . escapeshellarg( "$now.$fqdn.req" )
. ' -subj ' . escapeshellarg( $reqsub )
);
# Read created request file
$req = file_get_contents("$now.$fqdn.req")
or die("Errorloading $now.$fqdn.req\n");
#=======================================================================
# Establish SOAP connection
#=======================================================================
# Define as function because it is needed several times
function newsoap($rootcafile){
return new SoapClient(
'https://pki.pca.dfn.de/dfn-ca-global-g2/cgi-bin/pub/soap?wsdl=1',
[
# This script requires these settings
'trace' => false,
'exceptions' => false,
'features' => SOAP_SINGLE_ELEMENT_ARRAYS,
'cache_wsdl' => WSDL_CACHE_NONE,
# The next two lines only if
# the proxy server must be used
'proxy_host' => 'wwwproxy.uni-muenster.de',
'proxy_port' => 3128,
'stream_context' => stream_context_create([
'http' => [
'timeout' => 120,
# The next two lines only if
# the proxy server must be used
'proxy' => 'tcp://wwwproxy.uni-muenster.de:3128',
'request_fulluri' => true,
],
'ssl' => [
# Verification of the server certificate
'verify_peer' => true,
'cafile' => $rootcafile,
'verify_depth' => 3, # RootCA > DFN-PCA > CA > Server
],
]),
]
);
}
# Establish connection to the DFN-PKI certification server
$soap = newsoap("$now.$fqdn.root")
or die("Error establishing the SOAP connection\n");
#=======================================================================
# 1st SOAP function call: Submit the request
#=======================================================================
$soapdata = $soap->newRequest(
$raid, # RA-ID
$req, # Request in PEM format
$reqsan, # Array with Subject Alternative Names
$profil, # Certificate profile
$reqpin, # Revocation PIN hash
$name, # Name of requester
$mail, # Email of requester
$unit, # Organizational unit of requester
true # Mandantory: Publish the certificate
);
if($soapdata == '')
die("Error submitting the request\n");
if(is_soap_fault($soapdata))
die("Error submitting the request:\n"
. "{$soapdata->faultcode}: {$soapdata->faultstring}\n");
# Response is the request number
$reqnum = intval($soapdata);
#=======================================================================
# 2nd SOAP function call: Retrieve request information
#=======================================================================
$soapdata = $soap->getRequestInfo(
$raid, # Same RA-ID as above
$reqnum, # Request number, was assigned above
$reqpin # Same revocation PIN hash as above
);
if($soapdata == '')
die("Error retrieving the request information\n");
if(is_soap_fault($soapdata))
die("Error retrieving the request information:\n"
. "{$soapdata->faultcode}: {$soapdata->faultstring}\n");
# Save and check required details from the respons
# $reqsub and $reqsan are adjusted if necessary
$status = $soapdata->Status;
$reqsub = $soapdata->Parameters->Subject;
$reqsan = $soapdata->Parameters->SubjectAltNames;
$digest = $soapdata->PublicKeyDigest;
if( $status!='NEW'
or !is_string($reqsub)
or !is_array($reqsan)
or !is_string($digest)
) die("Request information faulty\n");
#=======================================================================
# 3rd SOAP function call: Retrieve request printout
#=======================================================================
$soapdata = $soap->getRequestPrintout(
$raid, # Same RA-ID as above
$reqnum, # Request number, was assigned above
'application/pdf', # Mandantory: PDF format
$reqpin # Same revocation PIN hash as above
);
if($soapdata == '')
die("Error retrieving the PDF file\n");
if(is_soap_fault($soapdata))
die("Error retrieving the PDF file:\n"
. "{$soapdata->faultcode}: {$soapdata->faultstring}\n");
# Save request printout
file_put_contents("$now.$fqdn.pdf",$soapdata)
or die("Fehler beim Speichern der PDF-Datei nach $now.$fqdn.pdf\n");
# This printout is no longer required, but the requester should at least
# be able to read which statement he confirms below.
#=======================================================================
# Terminate SOAP connection
#=======================================================================
unset($soap);
#=======================================================================
# Control output
#=======================================================================
# Subject, Subject Alternative Names, Public Key Digest
echo "\nRequest $reqnum to RA-ID $raid:\n$reqsub\n"
. implode("\n", $reqsan)
. "\n$digest\n\n";
#=======================================================================
# HTTPS function call: Submit confirmation to IT portal
#=======================================================================
# Submit HTTPS post request with JSON data and TLS client
# authentification and retrieve JSON response
$data = file_get_contents(
'https://xsso.uni-muenster.de/IT-Portal/dfnpkiconfirm/',
false,
stream_context_create([
'http' => [
'follow_location' => 0,
'ignore_errors' => true,
'method' => 'POST',
'protocol_version' => 1.1,
'timeout' => 120,
'user_agent' => 'None/0.0',
'header' => [
'Content-type: application/json',
'Accept: application/json',
],
# The next two lines only if
# the proxy server must be used
'proxy' => 'tcp://wwwproxy.uni-muenster.de:3128',
'request_fulluri' => true,
# All data in JSON format
'content' => json_encode([
'reqnum' => $reqnum,
'raid' => $raid,
'reqpin' => $reqpin,
'reqsub' => $reqsub,
'reqsan' => $reqsan, # Array!
'digest' => $digest,
'confirm' => $confirm,
],JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_APOS|JSON_HEX_QUOT),
],
'ssl' => [
# Verification of the server certificate
'verify_peer' => true,
'cafile' => "$now.$fqdn.root",
'verify_depth' => 3, # RootCA > DFN-PCA > CA > Server
# Identify the requester with client certificate
'local_cert' => $pemfile,
'passphrase' => $pempass,
],
])
) or die("Error submitting the confirmation\n$data\n");
$json = json_decode($data, true, 9999, JSON_BIGINT_AS_STRING)
or die("Response to confirmation faulty\n");
if(!$json['success'])
die("Confirmation rejected:\n"
. implode("\n", $json['errors']) . "\n");
#=======================================================================
# Loop: At large intervals try to retrieve the new certificate
#=======================================================================
# Initially wait at least 10 minutes, better 20
$nextsleep = 1200;
# Loop until the certificate is successfully fetched
while(true){
#=======================================================================
# Before waiting terminate any open SOAP connection
#=======================================================================
unset($soap);
#=======================================================================
# Wait
#=======================================================================
echo "Wait ...\n";
sleep($nextsleep);
# Between two tries wait at least 30 minutes, better 60
$nextsleep = 3600;
#=======================================================================
# Establish SOAP connection
#=======================================================================
$soap = newsoap("$now.$fqdn.root");
if(!$soap){
echo "SOAP server not reached\n";
continue;
}
#=======================================================================
# 4th SOAP function call: Retrieve certificate
#=======================================================================
$soapdata = $soap->getCertificateByRequestSerial(
$raid, # Same RA-ID as above
$reqnum, # Request number, was assigned above
$reqpin # Same revocation PIN hash as above
);
if($soapdata == ''){
echo "Error retrieving the certificate\n";
continue;
}
if(is_soap_fault($soapdata)){
echo "Error retrieving the certificate:\n"
. "{$soapdata->faultcode}: {$soapdata->faultstring}\n";
continue;
}
# Save certificate
if(!file_put_contents("$now.$fqdn.cert", $soapdata)){
echo "Error saving the certificate to $now.$fqdn.cert\n";
continue;
}
#=======================================================================
# End of loop: We have got the certicate
#=======================================================================
break;
}
#=======================================================================
# Terminate SOAP connection
#=======================================================================
unset($soap);
#=======================================================================
# Control output
#=======================================================================
echo "\n"
. "The digital ID is complete, see the following files:\n"
. "Request file: $now.$fqdn.req\n"
. "Request printout: $now.$fqdn.pdf\n"
. "Private key: $now.$fqdn.key\n"
. "Server certificate: $now.$fqdn.cert\n"
. "Intermediate CA certificates: $now.$fqdn.chain\n"
. "Root CA certificate: $now.$fqdn.root\n"
. "\n";
#=======================================================================
# Example: Install new digital ID into an Apache webserver
#=======================================================================
### # Replace configuration files
### if( !copy("$now.$fqdn.key", '/path/to/server/key.pem')
### or !copy("$now.$fqdn.cert", '/path/to/server/cert.pem')
### or !copy("$now.$fqdn.chain", '/path/to/server/chain.pem')
### ) die("Error updating the server configuration\n");
### # Reload server configuration
### passthru('sudo systemctl reload httpd');
?>