#======================================================================
# Aufrufe der HARICA-API
#======================================================================
# Diese Funktionssammlung ist speziell fuer das IT-Portal der Uni
# Muenster geschrieben.
# Verwendet werden zahlreiche Funktionen aus meiner PHP-Toolbox mit
# zahlreichen eigenen Funktionen, diese gebe ich auf Anfrage gerne an
# andere DFN-Mitglieder weiter.
#----------------------------------------------------------------------
# (C) 2025 Rainer Perske, Universitaet Muenster
# Lizenz: CC BY SA
#----------------------------------------------------------------------
# Fuer diese Aufrufe werden unterschiedliche HARICA-Accounts benoetigt:
# * USER - ohne weitere Berechtigungen (2FA optional)
# * APPROVER - berechtigt als Enterprise Approver (2FA aktiv)
# * ADMIN - berechtigt als Enterprise Admin (2FA aktiv)
# Fuer ADMIN wird auch ein API-Key benoetigt.
#----------------------------------------------------------------------
# Diese Funktionen sind intensiv "work in progress", insbesondere
# auch bei HARICA, und keonnen sich sicherlich kurzfristig aendern,
# das gilt sogar fuer Funktionsnamen, Argumente und Rueckgabewerte
#======================================================================
# Aufrufsequenzen Serverzertifikate
#======================================================================
# Gueltige Serverzertifikate auflisten (nur von USER)
# ##### NICHT MEHR VERWENDEN !!! #####
# ##### siehe harica_list() #####
# 1. harica_login(USER,PASS[,OTPKEY])
# -> User-Ticket
# 2. harica_list_server(User-Ticket,FQDN-Liste)
# -> Liste von Transaktions-IDs
# 3. Schleife ueber alle gelieferten Transaktions-IDs:
# 3.1. harica_retrieve_server(User-Ticket,Transaktions-ID)
# 4. harica_logout(User-Ticket)
#----------------------------------------------------------------------
# Gueltige oder widerrufene Serverzertifikate auflisten
# ##### NICHT MEHR VERWENDEN !!! #####
# ##### siehe harica_list() #####
# 1. harica_login(APPROVER,PASS,OTPKEY)
# -> Aprover-Ticket
# 2. harica_get_all_server(Approver-Ticket[,FQDN-Liste[,Status]])
# -> Liste von Transaktions-IDs
# 3. Schleife ueber alle gelieferten Transaktions-IDs:
# 3.1. harica_get_one_server(Approver-Ticket,Transaktions-ID)
# 4. harica_logout(Approver-Ticket)
#----------------------------------------------------------------------
# Serverzertifikat beantragen und abholen
# 1. harica_login(USER,PASS[,OTPKEY])
# -> User-Ticket
# 2. harica_request_server(User-Ticket,FriendlyName,FQDN-Liste,CSR)
# -> Request-ID
# 3. harica_login(APPROVER,PASS,OTPKEY)
# -> Approver-Ticket
# 4. harica_approve_server(Approver-Ticket,Request-ID)
# 5. harica_logout(Approver-Ticket)
# 6. harica_retrieve_server(User-Ticket,Request-ID)
# -> pemBundle und mehr
# 7. harica_logout(User-Ticket)
#----------------------------------------------------------------------
# Serverzertifikat widerrufen
# ##### NICHT MEHR VERWENDEN !!! #####
# ##### siehe harica_revoke() #####
# 1. harica_login(APPROVER,PASS,OTPKEY)
# -> Approver-Ticket
# 2. harica_revoke_server(Approver-Ticket,Transaktions-ID)
# 3. harica_logout(Approver-Ticket)
#======================================================================
# Aufrufsequenzen S/MIME-Zertifikate (Bulk-Mechanismus)
#======================================================================
# Bulk-S/MIME-Zertifikate auflisten
# ##### NICHT MEHR VERWENDEN !!! #####
# ##### siehe harica_list() #####
# 1. harica_login(ADMIN,PASS,OTPKEY)
# -> Admin-Ticket
# 2. harica_list_bulk_smime(Admin-Ticket,E-Mail-Liste)
# -> alle Zertifikate inkl. PKCS#12 und PEM-Dateien
# 3. harica_logout(Admin-Ticket)
#----------------------------------------------------------------------
# Bulk-S/MIME-Zertifikat beantragen und abholen (CSR optional)
# 1. harica_login(ADMIN,PASS,OTPKEY)
# -> Admin-Ticket
# 2. harica_request_retrieve_bulk_smime(Admin-Ticket,
# PickupPass,FriendlyName,Level,Vorname,Nachname,Mailadressen,CSR)
# -> Array Transaktions-ID => Metadaten und PEM-Dateien
# Transaktions-ID wird zum Widerrufen benoetigt
# 3. harica_logout(Admin-Ticket)
#----------------------------------------------------------------------
# Bulk-S/MIME-Zertifikat widerrufen
# ##### NICHT MEHR VERWENDEN !!! #####
# ##### siehe harica_revoke() #####
# 1. harica_login(APPROVER,PASS,OTPKEY)
# -> Approver-Ticket
# 2. harica_revoke_bulk_smime(Approver-Ticket,Transaktions-ID)
# 3. harica_logout(Approver-Ticket)
#======================================================================
# Aufrufsequenzen S/MIME-Zertifikat (vom Kunden selbst beantragt)
#======================================================================
# Normale S/MIME-Zertifikate auflisten
# ##### NICHT MEHR VERWENDEN !!! #####
# ##### siehe harica_list() #####
# 1. harica_login(ADMIN,PASS,OTPKEY)
# -> Admin-Ticket
# 2. harica_list_user_smime(Admin-Ticket,E-Mail-Liste)
# -> alle Zertifikate inkl. PKCS#12 und PEM-Dateien
# 3. harica_logout(Admin-Ticket)
#----------------------------------------------------------------------
# Normale S/MIME-Zertifikate widerrufen
# - nicht implementiert -
# ##### GEHT JETZT #####
# ##### siehe harica_revoke() #####
#======================================================================
# Aufrufsequenzen ACME
#======================================================================
# ACME-Konten auflisten
# 1. harica_login(ADMIN,PASS,OTPKEY)
# -> Admin-Ticket
# 2. harica_list_acme(Admin-Ticket)
# -> alle ACME-Konten (ACME-ID als Array-Index)
# 3. harica_logout(Admin-Ticket)
#----------------------------------------------------------------------
# ACME-Konto einrichten
# 1. harica_login(ADMIN,PASS,OTPKEY)
# -> Admin-Ticket
# 2. harica_create_acme(Admin-Ticket,OrgName,FriendlyName)
# -> neue ACME-ID
# 3. harica_logout(Admin-Ticket)
#----------------------------------------------------------------------
# ACME-Konto-Domains auflisten
# 1. harica_login(ADMIN,PASS,OTPKEY)
# -> Admin-Ticket
# 2. harica_get_acme_domains(Admin-Ticket,ACME-ID)
# -> alle Domains (Domain-ID als Array-Index)
# 3. harica_logout(Admin-Ticket)
#----------------------------------------------------------------------
# ACME-Konto-Domain hinzufuegen
# 1. harica_login(ADMIN,PASS,OTPKEY)
# -> Admin-Ticket
# 2. harica_add_acme_domain(Admin-Ticket,ACME-ID,FQDN)
# 3. harica_logout(Admin-Ticket)
#----------------------------------------------------------------------
# ACME-Konto-Domain wegnehmen
# 1. harica_login(ADMIN,PASS,OTPKEY)
# -> Admin-Ticket
# 2. harica_del_acme_domain(Admin-Ticket,ACME-ID,Domain-ID)
# 3. harica_logout(Admin-Ticket)
#----------------------------------------------------------------------
# ACME-Konto-Zertifikate auflisten
# 1. harica_login(ADMIN,PASS,OTPKEY)
# -> Admin-Ticket
# 2. harica_get_acme_certs(Admin-Ticket,ACME-ID)
# 3. harica_logout(Admin-Ticket)
#----------------------------------------------------------------------
# ACME-Konto loeschen
# 1. harica_login(ADMIN,PASS,OTPKEY)
# -> Admin-Ticket
# 2. harica_delete_acme(Admin-Ticket,ACME-ID)
# 3. harica_logout(Admin-Ticket)
#======================================================================
# Aufruf zum Auflisten aller Zertifikate
#======================================================================
# Als ADMIN: Alle gueltigen Zertifikate auflisten
# * harica_list(APIKEY,'valid' )
# * harica_list(APIKEY,'expired')
# * harica_list(APIKEY,'revoked')
#======================================================================
# Aufruf zum Widerrufen von Zertifikaten
#======================================================================
# Als ADMIN: Gueltiges Zertifikat widerrufen
# * harica_revoke(APIKEY,'single',Transaktions-ID,Grund)
# * harica_revoke(APIKEY,'bulk' ,Transaktions-ID,Grund)
# * harica_revoke(APIKEY,'acme' ,Transaktions-ID,Grund)
#======================================================================
function harica_server(){
return 'cm.harica.gr';
}
function harica_curlopt($cookiefile,$follow,$headers){
return $cookiefile=='' ? [
CURLOPT_HEADER => true,
CURLOPT_CONNECTTIMEOUT => 300,
CURLOPT_TIMEOUT => 300,
CURLOPT_PROXY => 'wwwproxy2.uni-muenster.de:3128',
CURLOPT_CAINFO => '/www/data/sys/pem/all-rootca.pem',
CURLOPT_FOLLOWLOCATION => $follow,
CURLOPT_HTTPHEADER => $headers,
] : [
CURLOPT_HEADER => true,
CURLOPT_CONNECTTIMEOUT => 300,
CURLOPT_TIMEOUT => 300,
CURLOPT_PROXY => 'wwwproxy2.uni-muenster.de:3128',
CURLOPT_CAINFO => '/www/data/sys/pem/all-rootca.pem',
CURLOPT_COOKIEJAR => $cookiefile,
CURLOPT_COOKIEFILE => $cookiefile,
CURLOPT_FOLLOWLOCATION => $follow,
CURLOPT_HTTPHEADER => $headers,
];
}
#----------------------------------------------------------------------
# Anmeldung bei HARICA ohne oder mit Einmalpasswort
#----------------------------------------------------------------------
function harica_login(
string $user, # HARICA-Loginname
string $pass, # HARICA-Passwort
string|null $otpkey=null, # OTP-Schluessel im Base32-Format
):array{
# Cookie-Datei vorbereiten
$cookiefile=tempnam('/www/data/it-portal/run','harica-cookies-'.$user.'-');
if($cookiefile===false)return [false,html('Cannot setup cookie file')];
register_shutdown_function(function($x){@unlink($x);},$cookiefile);
# RequestValidationToken holen
list($output,$status,$header)=curl_run_get('https://'.harica_server().'/',[],harica_curlopt($cookiefile,true,[
#no#'Content-Type: application/json;charset=utf-8',
#no#'Accept: application/json',
#no#"RequestVerificationToken: $rvt",
#no#"Authorization: $jwt",
]));
# (Nach Redirect) Status 200 erwartet
if($output===false){
unlink($cookiefile);
return [false,html('Step 1: '.$status)];
}
if(!array_key_exists('http_code',$status)){
unlink($cookiefile);
return [false,html('Step 1: No HTTP status').'
'.nl2br(html($header)).'
'.nl2br(html($output))];
}
if($status['http_code']!=200){
unlink($cookiefile);
return [false,html('Step 1: HTTP status code '.$status['http_code']).'
'.nl2br(html($header)).'
'.nl2br(html($output))];
}
if($output==''){
unlink($cookiefile);
return [false,html('Step 1: Missing output')];
}
if(!preg_match('#name\\s*=\\s*"__RequestVerificationToken"[^<>]*value\\s*=\\s*"([^<>"]*)"#',$output,$m)){
unlink($cookiefile);
return [false,html('Step 1: No RequestValidationToken found')];
}
$rvt=$m[1];
# JavaWebToken holen ohne OTP
# Parameter muessen als application/json uebergeben werden
list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/User/Login',json([
'email' => $user,
'password' => $pass,
]),harica_curlopt($cookiefile,false,[
'Content-Type: application/json;charset=utf-8',
#no#'Accept: application/json',
"RequestVerificationToken: $rvt",
#no#"Authorization: $jwt",
]));
# Status 200 (1FA) oder 202 (2FA) erwartet
# Zwei-Faktor-Anmeldung erwartet?
if($otpkey!==null){
if($output===false){
unlink($cookiefile);
return [false,html('Step 2: '.$status)];
}
if(!array_key_exists('http_code',$status)){
unlink($cookiefile);
return [false,html('Step 2: No HTTP status').'
'.nl2br(html($header)).'
'.nl2br(html($output))];
}
if($status['http_code']!=202){
unlink($cookiefile);
return [false,html('Step 2: HTTP status code '.$status['http_code']).'
'.nl2br(html($header)).'
'.nl2br(html($output))];
}
if($output!=''){
unlink($cookiefile);
return [false,html('Step 2: Invalid output')];
}
# JavaWebToken holen mit OTP
# Parameter muessen als application/json uebergeben werden
list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/User/Login2FA',json([
'email' => $user,
'password' => $pass,
'token' => chop(shell_exec('/www/bin/timeout 5 /usr/bin/oathtool --totp --base32 '.shellarg($otpkey))),
]),harica_curlopt($cookiefile,false,[
'Content-Type: application/json;charset=utf-8',
#no#'Accept: application/json',
"RequestVerificationToken: $rvt",
#no#"Authorization: $jwt",
]));
# Status 200 erwartet
}
if($output===false){
unlink($cookiefile);
return [false,html('Step 3: '.$status)];
}
if(!array_key_exists('http_code',$status)){
unlink($cookiefile);
return [false,html('Step 3: No HTTP status').'
'.nl2br(html($header)).'
'.nl2br(html($output))];
}
if($status['http_code']!=200){
unlink($cookiefile);
return [false,html('Step 3: HTTP status code '.$status['http_code']).'
'.nl2br(html($header)).'
'.nl2br(html($output))];
}
if($output==''){
unlink($cookiefile);
return [false,html('Step 3: Missing output')];
}
$jwt=$output;
# Neues RequestValidationToken holen
list($output,$status,$header)=curl_run_get('https://'.harica_server().'/',[],harica_curlopt($cookiefile,false,[
#no#'Content-Type: application/json;charset=utf-8',
#no#'Accept: application/json',
#no#"RequestVerificationToken: $rvt",
"Authorization: $jwt",
]));
# Status 200 erwartet
if($output===false){
unlink($cookiefile);
return [false,html('Step 4: '.$status)];
}
if(!array_key_exists('http_code',$status)){
unlink($cookiefile);
return [false,html('Step 4: No HTTP status').'
'.nl2br(html($header)).'
'.nl2br(html($output))];
}
if($status['http_code']!=200){
unlink($cookiefile);
return [false,html('Step 4: HTTP status code '.$status['http_code']).'
'.nl2br(html($header)).'
'.nl2br(html($output))];
}
if($output==''){
unlink($cookiefile);
return [false,html('Step 4: Missing output')];
}
if(!preg_match('#name\\s*=\\s*"__RequestVerificationToken"[^<>]*value\\s*=\\s*"([^<>"]*)"#',$output,$m)){
unlink($cookiefile);
return [false,html('Step 4: No RequestValidationToken found')];
}
$rvt=$m[1];
return [true,'rvt'=>$rvt,'jwt'=>$jwt,'cookiefile'=>$cookiefile];
}
#----------------------------------------------------------------------
# Abmeldung von HARICA
#----------------------------------------------------------------------
function harica_logout(
array &$ticket, # Rueckgabewert von harica_login()
):array{
if(($ticket[0]??null)!==true)return [false,html('Not logged in')];
# Parameter muessen keine uebergeben werden
list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/User/SignOut','',harica_curlopt($ticket['cookiefile'],false,[
#no#'Content-Type: application/json;charset=utf-8',
'Accept: application/json',
'RequestVerificationToken: '.$ticket['rvt'],
'Authorization: '.$ticket['jwt'],
]));
# Status 200 (Doku) oder 202 (Realitaet) erwartet
if($output===false)return [false,html($status)];
if(!array_key_exists('http_code',$status))return [false,html('No HTTP status').'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if(!in_array($status['http_code'],[200,202]))return [false,html('HTTP status code '.$status['http_code']).'
'.nl2br(html($header)).'
'.nl2br(html($output))];
unlink($ticket['cookiefile']);
$ticket=[false,html('Already logged out')];
return [true];
}
#----------------------------------------------------------------------
# Als USER: Beantragen eines Serverzertifikats
#----------------------------------------------------------------------
function harica_request_server(
array $ticket, # Rueckgabewert von harica_login()
string $name, # Friendly Name, z. B. der primaere FQDN
array $fqdns, # Liste aller FQDNs
string $csr, # CSR im PEM-Format
):array{
if(($ticket[0]??null)!==true)return [false,html('Not logged in')];
# Noch vor der Syntaxpruefung:
# *.sub.dom.ain inkludiert sub.dom.ain, dieses darf nicht getrennt vorkommen
# auch sonst duerfen keine Duplikate vorkommen
$fqdns=array_unique(array_values($fqdns));
foreach($fqdns as $key1 => $fqdn1)if(substr($fqdn1,0,2)=='*.'){
foreach($fqdns as $key2 => $fqdn2)if($fqdn2==substr($fqdn1,2)){
# wenn *.sub.dom.ain und sub.dom.ain vorkommen, beide Eintraege auf *.sub.dom.ain setzen
# der erste wird unten das erneute array_unique() ueberstehen
$fqdns[$key2]=$fqdn1;
break;
}
}
$fqdns=array_unique($fqdns);
$tmparr=[];
foreach($fqdns as $fqdn){
# Korrekt aufgebauter FQDN, ggf. mit vorangestelltem '*.' fuer ein Wildcard-Zertifikat?
if(!preg_match('#^(?:\\*\\.)?'.PATTERNFQDN.'$#',$fqdn))return [false,html('Invalid FQDN: '.$fqdn)];
$tmparr[$fqdn][0]=true;
# Wildcard?
if(substr($fqdn,0,2)=='*.')$tmparr[$fqdn]['isWildcard']=true;
}
$domlist1=[];
$domlist2=[];
foreach($tmparr as $fqdn => $one){
$domlist1[]=[
'domain' => $fqdn,
];
$domlist2[]=[
'domain' => $fqdn,
'isValid' => true,
'includeWWW' => false,
'errorMessage' => '',
'warningMessage' => '',
'isPrevalidated' => true,
'isWildcard' => $one['isWildcard']??false,
'isFreeDomain' => true,
'isFreeDomainDV' => true,
'isFreeDomainEV' => false,
'canRequestOV' => true,
'canRequestEV' => false,
];
}
# HARICA-interne OrganizationId zusammenstellen
# Da wir mehrere Organisationen verwalten, muessen wir unten die interne DN angeben, also einen der Werte:
# 'OrganizationId:907506b7-458a-4c3f-b9bf-4b064fc0b55d&C:DE&ST:Nordrhein-Westfalen&L:Muenster&O:Universitaet Muenster'
# 'OrganizationId:dde15334-842e-4b39-b733-3cb7af9a6e95&C:DE&ST:Nordrhein-Westfalen&O:Kunstakademie Muenster'
list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/ServerCertificate/CheckMachingOrganization',json($domlist1),harica_curlopt($ticket['cookiefile'],false,[
'Content-Type: application/json;charset=utf-8',
'Accept: application/json',
'RequestVerificationToken: '.$ticket['rvt'],
'Authorization: '.$ticket['jwt'],
]));
# Status 200 erwartet
if($output===false)return [false,html('Step 1: '.$status)];
if(!array_key_exists('http_code',$status))return [false,html('Step 1: No HTTP status').'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if($status['http_code']!=200)return [false,html('Step 1: HTTP status code '.$status['http_code']).'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if($output=='')return [false,html('Step 1: Missing output')];
$data=json_decode($output,true,9999,JSON_BIGINT_AS_STRING);
if(!is_array($data))return [false,html('Got no organization ID')];
$tmparr=[];
foreach($data as $one)if(is_array($one))if(array_key_exists('id',$one))if(is_string($one['id'])){
$dn='OrganizationId:'.$one['id'];
if(array_key_exists('country' ,$one))if($one['country' ]!='')$dn.='&C:' .$one['country' ];
if(array_key_exists('state' ,$one))if($one['state' ]!='')$dn.='&ST:'.$one['state' ];
if(array_key_exists('locality' ,$one))if($one['locality' ]!='')$dn.='&L:' .$one['locality' ];
if(array_key_exists('organizationName' ,$one))if($one['organizationName' ]!='')$dn.='&O:' .$one['organizationName' ];
if(array_key_exists('organizationUnitName',$one))if($one['organizationUnitName']!='')$dn.='&OU:'.$one['organizationUnitName'];
$tmparr[$dn]=true;
}
if(!$tmparr)return [false,html('Got no organization ID')];
if(count($tmparr)>1)return [false,html('Got conflicting organization IDs')];
foreach($tmparr as $one => $dummy)$haricaorgdn=$one;
# Request absenden
# Parameter muessen als multipart/form-data uebergeben werden
list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/ServerCertificate/RequestServerCertificate',[
'domains' => json($domlist2),
'domainsString' => json($domlist2),
'csr' => $csr,
'duration' => 1,
'transactionType' => 'OV',
'friendlyName' => $name,
'isManualCSR' => 'true',
'consentSameKey' => 'true',
'organizationDN' => $haricaorgdn,
],harica_curlopt($ticket['cookiefile'],false,[
#no#'Content-Type: application/json;charset=utf-8',
'Accept: application/json',
'RequestVerificationToken: '.$ticket['rvt'],
'Authorization: '.$ticket['jwt'],
]));
# Status 200 erwartet
if($output===false)return [false,html('Step 2: '.$status)];
if(!array_key_exists('http_code',$status))return [false,html('Step 2: No HTTP status').'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if($status['http_code']!=200)return [false,html('Step 2: HTTP status code '.$status['http_code']).'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if($output=='')return [false,html('Step 2: Missing output')];
$data=json_decode($output,true,9999,JSON_BIGINT_AS_STRING);
if(!is_array($data) or !array_key_exists('id',$data))return [false,html('Got no request ID')];
return [true,$data['id']];
}
#----------------------------------------------------------------------
# Als APPROVER: Validieren eines Serverzertifikat-Antrags
#----------------------------------------------------------------------
function harica_approve_server(
array $ticket, # Rueckgabewert von harica_login()
string $requestid, # Teil des Rueckgabewerts von harica_request_server()
):array{
if(($ticket[0]??null)!==true)return [false,html('Not logged in')];
# Reviews holen
$reviews=[];
for($startindex=0;;$startindex+=count($data)){
# Parameter muessen als application/json uebergeben werden
list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/OrganizationValidatorSSL/GetSSLReviewableTransactions',json([
'startIndex' => $startindex,
'status' => 'Pending',
'filterPostDTOs' => [],
]),harica_curlopt($ticket['cookiefile'],false,[
'Content-Type: application/json;charset=utf-8',
'Accept: application/json',
'RequestVerificationToken: '.$ticket['rvt'],
'Authorization: '.$ticket['jwt'],
]));
# Status 200 erwartet
if($output===false)return [false,html('Step 1: '.$status)];
if(!array_key_exists('http_code',$status))return [false,html('Step 1: No HTTP status').'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if($status['http_code']!=200)return [false,html('Step 1: HTTP status code '.$status['http_code']).'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if($output=='')return [false,html('Step 1: Missing output')];
$data=json_decode($output,true,9999,JSON_BIGINT_AS_STRING);
if(!is_array($data))return [false,html('Step 1: Invalid output')];
# Ende der Liste erreicht?
if(!$data)break;
foreach($data as $dat)if(($dat['transactionId']??'')==$requestid)if(is_array($dat['reviewGetDTOs']))foreach($dat['reviewGetDTOs'] as $rev){
if(($rev['isReviewed']??null)===false)if(($rev['reviewId']??null)!='')if(array_key_exists('reviewValue',$rev))$reviews[$rev['reviewId']]=$rev['reviewValue'];
}
}
if(!$reviews)return [false,html('No review requirement found')];
# Reviews durchfuehren
$num=0;
foreach($reviews as $id => $val){
$num++;
# Parameter muessen als multipart/form-data uebergeben werden
list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/OrganizationValidatorSSL/UpdateReviews',[
'reviewId' => $id,
'isValid' => 'true',
'informApplicant' => 'false',
'reviewMessage' => 'Automatic Review by IT-Portal',
'reviewValue' => $val,
],harica_curlopt($ticket['cookiefile'],false,[
#no#'Content-Type: application/json;charset=utf-8',
'Accept: application/json',
'RequestVerificationToken: '.$ticket['rvt'],
'Authorization: '.$ticket['jwt'],
]));
# Status 200 erwartet
if($output===false)return [false,html('Step 1: '.$status)];
if(!array_key_exists('http_code',$status))return [false,html('Step 2.'.$num.': No HTTP status').'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if($status['http_code']!=200)return [false,html('Step 2.'.$num.': HTTP status code '.$status['http_code']).'
'.nl2br(html($header)).'
'.nl2br(html($output))];
}
# Der Aufruf des letzten Reviews kehrt erst zurueck, wenn das Zertifikat fertig ausgestellt ist
# Es muss also nicht mehr weiter auf das Zertifikat gewartet werden
return [true];
}
#----------------------------------------------------------------------
# Als USER: Abholen eines Serverzertifikats
#----------------------------------------------------------------------
function harica_retrieve_server(
array $ticket, # Rueckgabewert von harica_login()
string $requestid, # Teil des Rueckgabewerts von harica_request_server()
):array{
if(($ticket[0]??null)!==true)return [false,html('Not logged in')];
# Parameter muessen als application/json uebergeben werden
list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/Certificate/GetCertificate',json([
'id' => $requestid,
]),harica_curlopt($ticket['cookiefile'],false,[
'Content-Type: application/json;charset=utf-8',
'Accept: application/json',
'RequestVerificationToken: '.$ticket['rvt'],
'Authorization: '.$ticket['jwt'],
]));
# Status 200 erwartet
if($output===false)return [false,html($status)];
if(!array_key_exists('http_code',$status))return [false,html('No HTTP status').'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if($status['http_code']!=200)return [false,html('HTTP status code '.$status['http_code']).'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if($output=='')return [false,html('Missing output')];
$data=json_decode($output,true,9999,JSON_BIGINT_AS_STRING);
if(!is_array($data)
or !array_key_exists('certificate',$data)
or !array_key_exists('pemBundle',$data)
or !array_key_exists('serial',$data)
or substr($data['certificate'],0,27)!='-----BEGIN CERTIFICATE-----'
)return [false,html('Invalid output')];
return [true,$data];
}
#----------------------------------------------------------------------
# Als USER: Eigene Serverzertifikate auflisten
# ##### NICHT MEHR VERWENDEN !!! #####
# ##### siehe harica_list() #####
#----------------------------------------------------------------------
function harica_list_server(
array $ticket, # Rueckgabewert von harica_login()
array $fqdns=[], # leer (fuer alle) oder eigene FQDNs
):array{
if(($ticket[0]??null)!==true)return [false,html('Not logged in')];
# Parameter muessen keine uebergeben werden
list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/ServerCertificate/GetMyTransactions','',harica_curlopt($ticket['cookiefile'],false,[
#no#'Content-Type: application/json;charset=utf-8',
'Accept: application/json',
'RequestVerificationToken: '.$ticket['rvt'],
'Authorization: '.$ticket['jwt'],
]));
# Status 200 erwartet
if($output===false)return [false,html($status)];
if(!array_key_exists('http_code',$status))return [false,html('No HTTP status').'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if(!in_array($status['http_code'],[200,202]))return [false,html('HTTP status code '.$status['http_code']).'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if($output=='')return [false,html('Missing output')];
$data=json_decode($output,true,9999,JSON_BIGINT_AS_STRING);
if(!is_array($data))return [false,html('Invalid output')];
# Array transactionId => Details
$result=[];
foreach($data as $one)if(is_array($one)
and ($one['transactionId' ]??'' )!=''
and ($one['transactionStatus']??'' )=='Completed'
and ($one['isRevoked' ]??false)==false
and ($one['isExpired' ]??false)==false
){
foreach($one['domains']??[] as $two)if(($two['fqdn']??'')!=''){
if(!$fqdns or in_array($two['fqdn'],$fqdns)){
$result[$one['transactionId']]=$one;
break;
}
if($two['includesWWW']??false)if(!$fqdns or in_array('www.'.$two['fqdn'],$fqdns)){
$result[$one['transactionId']]=$one;
break;
}
}
}
return [true,$result];
}
#----------------------------------------------------------------------
# Als APPROVER: Serverzertifikate auflisten
# ##### NICHT MEHR VERWENDEN !!! #####
# ##### siehe harica_list() #####
#----------------------------------------------------------------------
function harica_get_all_server(
array $ticket, # Rueckgabewert von harica_login()
array $fqdns=[], # or a list of FQDNs
bool $revoked=false, # valid or revoked
):array{
if(($ticket[0]??null)!==true)return [false,html('Not logged in')];
# Parameter muessen keine uebergeben werden
list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/OrganizationValidatorSSL/GetSSLTransactionsCount','',harica_curlopt($ticket['cookiefile'],false,[
#no#'Content-Type: application/json;charset=utf-8',
'Accept: application/json',
'RequestVerificationToken: '.$ticket['rvt'],
'Authorization: '.$ticket['jwt'],
]));
# Status 200 erwartet
if($output===false)return [false,html('Step 1: '.$status)];
if(!array_key_exists('http_code',$status))return [false,html('Step 1: No HTTP status').'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if(!in_array($status['http_code'],[200,202]))return [false,html('Step 1: HTTP status code '.$status['http_code']).'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if($output=='')return [false,html('Step 1: Missing output')];
$data=json_decode($output,true,9999,JSON_BIGINT_AS_STRING);
if(!is_array($data) or !array_key_exists('valid',$data))return [false,html('Step 1: Invalid output')];
$validcount=$data['valid'];
$result=[];
for($startindex=0;;$startindex+=count($data)){
# Parameter muessen als application/json uebergeben werden
$filter=[];
foreach($fqdns as $fqdn)$filter[]=[
'filterType' => 'FQDN',
'filterTypeSelection' => 'Is',
'filterValue' => $fqdn,
'isSeperator' => false,
];
list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/OrganizationValidatorSSL/GetSSLTransactions',json([
'startIndex' => $startindex,
'status' => $revoked?'Revoked':'Valid',
'filterPostDTOs' => $filter,
]),harica_curlopt($ticket['cookiefile'],false,[
'Content-Type: application/json;charset=utf-8',
'Accept: application/json',
'RequestVerificationToken: '.$ticket['rvt'],
'Authorization: '.$ticket['jwt'],
]));
# Status 200 erwartet
if($output===false)return [false,html('Step 2: '.$status)];
if(!array_key_exists('http_code',$status))return [false,html('Step 2: No HTTP status').'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if(!in_array($status['http_code'],[200,202]))return [false,html('Step 2: HTTP status code '.$status['http_code']).'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if($output=='')return [false,html('Step 2: Missing output')];
$data=json_decode($output,true,9999,JSON_BIGINT_AS_STRING);
if(!is_array($data))return [false,html('Step 2: Invalid output')];
# Ende der Liste erreicht?
if(!$data)break;
foreach($data as $one)if(is_array($one))if(array_key_exists('transactionId',$one)){
$result[$one['transactionId']]=$one;
}
}
return [true,$result];
}
#----------------------------------------------------------------------
# Unterschiede zwischen der Auflistung als USER (-) oder APPROVER (+)
# - https://'.harica_server().'/api/ServerCertificate/GetMyTransactions
# + https://'.harica_server().'/api/OrganizationValidatorSSL/GetSSLTransactions
#----------------------------------------------------------------------
# [
# 'transactionId' => 'e789d534-29e7-40fc-9d46-297fc301dc5b',
# 'chainedTransactionId' => NULL,
# - 'transactionTypeName' => 'SSL OV',
# + 'transactionTypeName' => 'OV',
# 'transactionStatus' => 'Completed',
# 'transactionStatusMessage' => '',
# 'notes' => NULL,
# - 'organization' => NULL,
# + 'organization' => 'Universitaet Muenster',
# 'purchaseDuration' => 0,
# 'additionalEmails' => '',
# 'userEmail' => 'itportal@uni-muenster.de',
# - 'user' => NULL,
# + 'user' => 'Rainer Perske', # weil das der Name des IT-Portal-Accounts bei HARICA ist
# 'friendlyName' => 'it.uni-muenster.deperske',
# 'reviewValue' => NULL,
# 'reviewMessage' => NULL,
# 'reviewedBy' => NULL,
# - 'requestedAt' => '2025-01-02T16:18:50.532623',
# + 'requestedAt' => '0001-01-01T00:00:00',
# 'reviewedAt' => NULL,
# - 'dN' => NULL,
# + 'dN' => 'C=DE,ST=Nordrhein-Westfalen,L=Muenster,O=Universitaet Muenster,CN=it.uni-muenster.de',
# - 'hasReview' => false,
# + 'hasReview' => true,
# 'canRenew' => false,
# 'isRevoked' => false,
# - 'isPaid' => true,
# + 'isPaid' => NULL,
# 'isEidasValidated' => NULL,
# 'hasEidasValidation' => NULL,
# - 'isHighRisk' => false,
# + 'isHighRisk' => NULL,
# 'isShortTerm' => NULL,
# - 'isExpired' => false,
# + 'isExpired' => NULL,
# - 'issuedAt' => NULL,
# + 'issuedAt' => '2025-01-02T16:08:53',
# 'certificateValidTo' => '2026-01-02T16:08:53',
# 'domains' => [['fqdn' => 'it.uni-muenster.de', 'includesWWW' => true, 'validations' => []], ...],
# 'validations' => NULL,
# - 'chainedTransactions' => [],
# + 'chainedTransactions' => NULL,
# 'tokenType' => NULL,
# 'csrType' => NULL,
# 'acceptanceRetrievalAt' => NULL,
# 'reviewGetDTOs' => NULL,
# 'userDescription' => NULL,
# 'userOrganization' => NULL,
# 'transactionType' => NULL,
# 'isPendingP12' => NULL,
# ]
#----------------------------------------------------------------------
# Als APPROVER: Einzelnes Serverzertifikat holen
#----------------------------------------------------------------------
function harica_get_one_server(
array $ticket, # Rueckgabewert von harica_login()
string $transactionid # Aus dem Rueckgabewert von harica_get_all_server()
):array{
if(($ticket[0]??null)!==true)return [false,html('Not logged in')];
# Parameter muessen als application/json uebergeben werden
list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/OrganizationValidatorSSL/GetSSLCertificate',json([
'id' => $transactionid,
]),harica_curlopt($ticket['cookiefile'],false,[
'Content-Type: application/json;charset=utf-8',
'Accept: application/json',
'RequestVerificationToken: '.$ticket['rvt'],
'Authorization: '.$ticket['jwt'],
]));
# Status 200 erwartet
if($output===false)return [false,html($status)];
if(!array_key_exists('http_code',$status))return [false,html('No HTTP status').'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if(!in_array($status['http_code'],[200,202]))return [false,html('HTTP status code '.$status['http_code']).'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if($output=='')return [false,html('Missing output')];
$data=json_decode($output,true,9999,JSON_BIGINT_AS_STRING);
if(!is_array($data))return [false,html('Invalid output')];
return [true,$data];
}
#----------------------------------------------------------------------
# Als APPROVER: Serverzertifikat widerrufen
# ##### NICHT MEHR VERWENDEN !!! #####
# ##### siehe harica_revoke() #####
#----------------------------------------------------------------------
function harica_revoke_server(
array $ticket, # Rueckgabewert von harica_login()
string $transactionid, # Aus dem Rueckgabewert von harica_get_all_server()
string $reason, # Freitext
):array{
if(($ticket[0]??null)!==true)return [false,html('Not logged in')];
# Parameter muessen als application/json uebergeben werden
list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/OrganizationValidatorSSL/RevokeCertificate',json([
'transactionId' => $transactionid,
'name' => '4.9.1.1.1.1', # Unspecified reason
'notes' => $reason,
'message' => '',
]),harica_curlopt($ticket['cookiefile'],false,[
'Content-Type: application/json;charset=utf-8',
'Accept: application/json',
'RequestVerificationToken: '.$ticket['rvt'],
'Authorization: '.$ticket['jwt'],
]));
# Status 200 erwartet
if($output===false)return [false,html($status)];
if(!array_key_exists('http_code',$status))return [false,html('No HTTP status').'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if(!in_array($status['http_code'],[200,202]))return [false,html('HTTP status code '.$status['http_code']).'
'.nl2br(html($header)).'
'.nl2br(html($output))];
return [true];
}
#----------------------------------------------------------------------
# Als ADMIN: Bulk-S/MIME-Zertifikate auflisten
# ##### NICHT MEHR VERWENDEN !!! #####
# ##### siehe harica_list() #####
#----------------------------------------------------------------------
function harica_list_bulk_smime(
array $ticket, # Rueckgabewert von harica_login()
array $mails, # Liste aller primaeren E-Mail-Adressen
):array{
if(($ticket[0]??null)!==true)return [false,html('Not logged in')];
if(!$mails)return [false,html('No mail addresses given')];
# Parameter muessen keine uebergeben werden
list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/OrganizationAdmin/GetBulkCertificateEntries','',harica_curlopt($ticket['cookiefile'],false,[
#no#'Content-Type: application/json;charset=utf-8',
'Accept: application/json',
'RequestVerificationToken: '.$ticket['rvt'],
'Authorization: '.$ticket['jwt'],
]));
# Status 200 erwartet
# [ { "id": "7733943b-5bc6-4f05-a90f-3efaa47355d8",
# "entityId": "Universität Münster",
# "createdAt": "2025-01-12T00:55:40.126697",
# "customTags": "S/MIME",
# "userEmail": "itportal3@uni-muenster.de"
# },
# ...
# ]
if($output===false)return [false,html('Step 1: '.$status)];
if(!array_key_exists('http_code',$status))return [false,html('Step 1: No HTTP status').'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if(!in_array($status['http_code'],[200,202]))return [false,html('Step 1: HTTP status code '.$status['http_code']).'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if($output=='')return [false,html('Step 1: Missing output')];
$data=json_decode($output,true,9999,JSON_BIGINT_AS_STRING);
if(!is_array($data))return [false,html('Step 1: Invalid output')];
# Array bulkid => true
$bulkidlist=[];
foreach($data as $one)if(is_array($one)
and ($one['id' ]??'')!=''
)$bulkidlist[$one['id']]=$one['entityId']??'';
# Cache einlesen: Array bulkid => [mail, ...]
# Obiger API-Aufruf liefert keine Angaben zu dem im jeweiligen Bulk-Job enthaltenen Zertifikaten.
# Damit wir nicht jedes Mal saemtliche Bulkjobs abrufen muessen, merken wir uns,
# welcher Bulkjob Zertifikate fuer welche primaeren Mailadressen enthaelt
$harica_cache_bulk_smime=[];
$data=file('/www/data/it-portal/var/harica_cache_bulk_smime');
if(is_array($data))foreach($data as $line){
$line=explode(' ',chop($line));
if(count($line)==2)$harica_cache_bulk_smime[$line[0]][]=$line[1];
}
$harica_cache_bulk_smime_modified=false;
# Array certid => data
$result=[];
foreach($bulkidlist as $bulkid => $entityid){
# Wir brauchen die Daten eines Bulk-Requests nur dann bei HARICA nachzufragen, wenn:
# - entweder die Bulk-Job-ID noch nicht im Cache steht (dann wissen wir ja nicht, ob eine relevante E-Mail-Adresse enthalten ist)
# - oder die Bulk-Job-ID im Cache steht und eine relevante primaere E-Mail-Adresse enthaelt (dann koennte inzwischen widerrufen worden sein)
# aber nicht, wenn_
# - die Bulk-Job-ID im Cache steht und keine relevante E-Mail-Adresse enthaelt.
$need=(!$mails or !array_key_exists($bulkid,$harica_cache_bulk_smime));
if(!$need)if(array_intersect($mails,$harica_cache_bulk_smime[$bulkid]))$need=true;
if($need){
# Parameter muessen als application/json uebergeben werden
########## UMSTELLEN AUF NEUE API ##########
list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/OrganizationAdmin/GetBulkCertificatesOfAnEntry',json([
'id' => $bulkid,
]),harica_curlopt($ticket['cookiefile'],false,[
'Content-Type: application/json;charset=utf-8',
'Accept: application/json',
'RequestVerificationToken: '.$ticket['rvt'],
'Authorization: '.$ticket['jwt'],
]));
# Status 200 erwartet
# [ { "id": "efa0d2b9-d319-42aa-a5c6-ff6af774d197",
# "bulkCertificatesEntryId": "7733943b-5bc6-4f05-a90f-3efaa47355d8",
# "statusName": "Completed",
# "friendlyName": "Rainer Perske",
# "duration": 730,
# "customTags": null,
# "revocationMessage": "",
# "pkcs12": "MIIZowIBAzCCGV8GCSqGSIb3DQEHAaCCGVAEghlMMIIZSDCCBTkGCSqGSIb3DQEHAaCCBSoEggUmMIIFIjCCBR4GCyqGSIb3DQEMCgECoIIE9jCCBPIwJAYK
# "issuedAt": "2025-01-12T00:55:40.579098",
# "validFrom": "2025-01-12T00:45:40",
# "validTo": "2027-01-12T00:45:40",
# "revocationCode": "",
# "isRevoked": false,
# "revokedAt": null,
# "commonName": "Rainer Perske",
# "organizationUnit1": "rainer.perske@uni-muenster.de",
# "organizationUnit2": "rainer.perske@exchange.wwu.de",
# "organizationUnit3": "perske@uni-muenster.de",
# "keySpec": "2048",
# "keyAlg": "RSA",
# "serial": "24F9BA60E83EB04A80C28EA5233049DA",
# "certificate": "-----BEGIN CERTIFICATE-----\nMIIGkjCCBHqgAwIBAgIQJPm6YOg+sEqAwo6lIzBJ2jANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQG\nEwJHUjE3
# "notes": null,
# "revokedByEmail": null,
# "pkcs7": null
# },
# ...
# ]
if($output===false)return [false,html('Step 2: '.$status)];
if(!array_key_exists('http_code',$status))return [false,html('Step 2: No HTTP status').'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if(!in_array($status['http_code'],[200,202]))return [false,html('Step 2: HTTP status code '.$status['http_code']).'
'.nl2br(html($header)).'
'.nl2br(html($output))];
if($output=='')return [false,html('Step 2: Missing output')];
$data=json_decode($output,true,9999,JSON_BIGINT_AS_STRING);
if(!is_array($data))return [false,html('Step 2: Invalid output')];
# Das Ergebnis dem Cache hinzufuegen
# Bei nicht mehr gueltigen Zertifikaten nicht die E-Mail-Adresse, sondern eine Markierung hinzufuegen,
# damit der Eintrag fuer die Zukunft ungueltig und nie mehr beruecksichtigt wird.
$harica_cache_bulk_smime[$bulkid]=[];
foreach($data as $one){
# Bugfix: Leerzeile entfernen:
if(array_key_exists('certificate',$one))$one['certificate']=strtr($one['certificate'],["\n\n"=>"\n"]);
if($one['isRevoked']){
$harica_cache_bulk_smime[$bulkid][]='<>'; # non-empty string but not a valid mail address
}elseif(strtotime($one['validTo'].' Z')