#---------------------------------------------------------------------- # 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')$entityid]; }else{ $harica_cache_bulk_smime[$bulkid][]='<>'; # non-empty string but not a valid mail address # wenn relevant, Organisationsangabe ergaenzen und alles dem Ergebnisarray hinzufuegen if(!$mails)$result[$one['id']]=[...$one,'entityId'=>$entityid]; } } $harica_cache_bulk_smime_modified=true; } } # Modifizierten Cache neu abspeichern und atomar ersetzen, Fehler stillschweigend ignorieren # Schlimmstenfalls fuehren wir einige unnoetige API-Abfragen durch ... if($harica_cache_bulk_smime_modified){ $data=''; foreach($harica_cache_bulk_smime as $bulkid => $bulkmails)foreach($bulkmails as $one)$data.=$bulkid.' '.$one."\n"; if(file_put_contents('/www/data/it-portal/var/harica_cache_bulk_smime.'.posix_getpid(),$data,LOCK_EX))rename('/www/data/it-portal/var/harica_cache_bulk_smime.'.posix_getpid(),'/www/data/it-portal/var/harica_cache_bulk_smime'); # Nach Fehlern keine Arbeitsdatei hinterlassen @unlink('/www/data/it-portal/var/harica_cache_bulk_smime.'.posix_getpid()); } return [true,$result]; } #---------------------------------------------------------------------- # Als ADMIN: Bulk-S/MIME-Zertifikat beantragen und abholen #---------------------------------------------------------------------- # Stand 2025-03-12 koennen nur drei E-Mail-Adressen aufgenommen # werden #---------------------------------------------------------------------- function harica_request_retrieve_bulk_smime( array $ticket, # Rueckgabewert von harica_login() string $pass, # pickup password (leave empty if CSR is given) string $friendly, # friendly name bool $withname, # identity is checked string $gn, # given name string $sn, # surname array|string $mails, # mails (at least one, at most three) string $csr='', # CSR as PEM data (optional) ):array{ if(($ticket[0]??null)!==true)return [false,html('Not logged in')]; if( strpos($friendly,',')!==false or strpos($pass ,',')!==false or strpos($gn ,',')!==false or strpos($sn ,',')!==false or ($csr!=='' and substr($csr,0,35)!='-----BEGIN CERTIFICATE REQUEST-----') )return [false,html('Invalid input')]; $mails=checkmailaddrlist($mails); if(!$mails or count($mails)>3)return [false,html('Invalid mails')]; $mails=array_values($mails); # HARICA-interne GroupId ermitteln # KA: dde15334-842e-4b39-b733-3cb7af9a6e95 # Uni: 0a60d5a8-da39-4f61-9657-0922440d6eb0 list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/OrganizationAdmin/GetOrganizationsBulk','',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($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')]; $groupid=[]; foreach($mails as $mail){ foreach($data as $one)if(is_array($one)){ if(array_key_exists('organizationId',$one))if(is_string($one['organizationId'])){ if(array_key_exists('groupDomains',$one))if(is_string($one['groupDomains'])){ foreach(explode(',',$one['groupDomains']) as $dom){ if(substr($mail,-strlen($dom))==$dom and in_array(substr($mail,-strlen($dom)-1,1),['@','.'])){ if(!array_key_exists($mail,$groupid)){ $groupid[$mail]=$one['organizationId']; }elseif($groupid[$mail]!=$one['organizationId']){ return [false,html('Conflicting organisation IDs for '.$mail)]; } } } } } } if($groupid[$mail]=='')return [false,html('Cannot find organization ID for '.$mail)]; } if(count(array_unique($groupid))!=1)return [false,html('Conflicting organisation IDs for mails')]; $groupid=reset($groupid); # Request absenden # Parameter muessen als multipart/form-data uebergeben werden $csv='FriendlyName,Email,Email2,Email3,GivenName,Surname,PickupPassword,CertType,CSR' ."\n".$friendly .','.$mails[0] .','.($mails[1]??'') .','.($mails[2]??'') .','.($withname?$gn:'') .','.($withname?$sn:'') .','.($csr?'':$pass) .','.($withname?'natural_legal_lcp':'email_only') .','.($csr?'"'.strtr($csr,['"'=>'""']).'"':'') ."\n"; list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/OrganizationAdmin/CreateBulkCertificatesSMIME',[ 'groupId' => $groupid, 'csv' => new CURLStringFile($csv,'bulk.csv','text/csv'), ],harica_curlopt($ticket['cookiefile'],false,[ #no#'Content-Type: application/json;charset=utf-8', 'Accept: application/zip', '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')]; # Output muss eine ZIP-Datei mit genau einer Datei sein, diese auspacken $raw=false; $fn=tempnam('/www/data/it-portal/run','harica-zip-'); if($fn){ if(file_put_contents($fn,$output)){ $raw=''; $zip=new ZipArchive; if($zip->open($fn,ZipArchive::RDONLY)){ if($zip->count()==1){ $raw=$zip->getFromIndex(0); } $zip->close(); } } unlink($fn); } if($raw===false)return [false,html('Step 2: Internal error')]; if($raw=='')return [false,html('Step 2: Invalid output (zip file format)')]; if($csr){ # Die Datei muss eine PKCS#7-Datei sein # In der Datei muss das erste Zertifikat das Nutzerzertifikat und die weiteren Zertifikate die Chain sein $certs=[]; if( !openssl_pkcs7_read("-----BEGIN PKCS7-----\n".base64_encode($raw)."\n-----END PKCS7-----\n",$certs) or !is_array($certs) or !$certs )return [false,html('Step 2: Invalid output (PKCS#7 format)')]; $cert=array_shift($certs); return [true, 'p7b' => $raw, # PKCS#7 binary 'cert' => $cert, # PEM string 'chain' => $certs, # array of PEM strings ]; }else{ # Die Datei muss eine PKCS#12-Datei sein if( !openssl_pkcs12_read($raw,$data,$pass) or !is_array($data) or !array_key_exists('pkey',$data) or !is_string($data['pkey']) or substr($data['pkey'],0,28)!="-----BEGIN PRIVATE KEY-----\n" or !array_key_exists('cert',$data) or !is_string($data['cert']) or substr($data['cert'],0,28)!="-----BEGIN CERTIFICATE-----\n" )return [false,html('Step 2: Invalid output (PKCS#12 format)')]; if(!array_key_exists('extracerts',$data))$data['extracerts']=[]; if(is_string($data['extracerts']))$data['extracerts']=[$data['extracerts']]; if(!is_array($data['extracerts']))return [false,html('Step 2: Invalid output (PKCS#12 format)')]; foreach($data['extracerts'] as $key => $val){ if( !is_string($val) or substr($val,0,28)!="-----BEGIN CERTIFICATE-----\n" )return [false,html('Step 2: Invalid output (PKCS#12 format)')]; } # Extrahierten privaten Schluessel wieder verschluesseln if(openssl_pkey_export($data['pkey'],$key,$pass) and is_string($key) and substr($key,0,38)=="-----BEGIN ENCRYPTED PRIVATE KEY-----\n" )$data['pkey']=$key; return [true, 'p12' => $raw, # PKCS#12 binary 'key' => $data['pkey'], # PEM string 'cert' => $data['cert'], # PEM string 'chain' => $data['extracerts'], # array of PEM strings ]; } return [false,'THIS CANNOT HAPPEN']; } #---------------------------------------------------------------------- # Als ADMIN: Bulk-S/MIME-Zertifikat widerrufen # ##### NICHT MEHR VERWENDEN !!! ##### # ##### siehe harica_revoke() ##### #---------------------------------------------------------------------- function harica_revoke_bulk_smime( array $ticket, # Rueckgabewert von harica_login() string $certid, # Aus dem Rueckgabewert von harica_list_bulk_smime() 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/OrganizationAdmin/RevokeBulkCertificate',json([ 'transactionId' => $certid, 'name' => '4.9.1.1.1.1', # Unspecified reason 'message' => $reason, ]),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))]; return [true]; } #---------------------------------------------------------------------- # Als ADMIN: Andere S/MIME-Zertifikate auflisten # ##### NICHT MEHR VERWENDEN !!! ##### # ##### siehe harica_list() ##### #---------------------------------------------------------------------- function harica_list_user_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')]; $filter=[]; foreach($mails as $mail)$filter[]=[ 'filterType' => 'Email', 'filterTypeSelection' => 'Is', 'filterValue' => $mail, 'isSeperator' => true, ]; $result=[]; for($startindex=0;;$startindex+=count($data)){ # Parameter muessen als application/json uebergeben werden list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/OrganizationValidatorSmime/GetEnterpriseCertificates',json([ 'startIndex' => $startindex, 'status' => '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 # [ { "transactionId":"79576543-6bfa-4a8f-9c08-fce125a1af11", # "chainedTransactionId":null, # "transactionTypeName":"S/MIME email-only", # "transactionStatus":"Completed", # "transactionStatusMessage":"", # "notes":null, # "organization":null, # "purchaseDuration":0, # "additionalEmails":"", # "userEmail":"rainer.perske@uni-muenster.de", # "user":"Rainer Perske", # "friendlyName":"", # "reviewValue":null, # "reviewMessage":null, # "reviewedBy":null, # "requestedAt":"0001-01-01T00:00:00", # "reviewedAt":null, # "dN":"E=rainer.perske@uni-muenster.de", # "hasReview":false, # "canRenew":false, # "isRevoked":false, # "isPaid":null, # "isEidasValidated":null, # "hasEidasValidation":null, # "isHighRisk":null, # "isShortTerm":null, # "isExpired":null, # "issuedAt":"2024-12-23T10:20:26.939905", # "certificateValidTo":"2026-12-23T10:10:24", # "domains":null, # "validations":null, # "chainedTransactions":null, # "tokenType":null, # "csrType":null, # "acceptanceRetrievalAt":null, # "reviewGetDTOs":null, # "userDescription":null, # "userOrganization":null, # "transactionType":null, # "isPendingP12":null # }, # ... # ] 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')]; # 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; } } # Array certid => data foreach($result as $id => &$one){ # Parameter muessen als application/json uebergeben werden list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/OrganizationValidatorSmime/GetCertificate',json([ 'id' => $id, ]),harica_curlopt($ticket['cookiefile'],false,[ 'Content-Type: application/json;charset=utf-8', 'Accept: application/json', 'RequestVerificationToken: '.$ticket['rvt'], 'Authorization: '.$ticket['jwt'], ])); # Status 200 erwartet # { "pKCS7":"-----BEGIN PKCS7-----\n.....", # "certificate":"-----BEGIN CERTIFICATE-----\n.....", # "pemBundle":null, # "dN":"E=rainer.perske@uni-muenster.de", # "sANS":"email:rainer.perske@uni-muenster.de", # "revocationCode":"07b7ead0dae527e3c04373e6111f001d", # "serial":"66E526F15517B8A139F106237BEC740F", # "isRevoked":false, # "revokedAt":null, # "validFrom":"2024-12-23T10:10:24", # "validTo":"2026-12-23T10:10:24", # "issuerDN":"C=GR,O=Hellenic Academic and Research Institutions CA,CN=HARICA S/MIME RSA", # "authorizationDomains":null, # "keyType":"RSA 4096", # "friendlyName":"", # "approver":{"fullName":null, # "fullNameLocalized":null, # "dateOfBirth":null, # "email":null, # "organization":null, # "organizationLocalized":null, # "organizationDN":null, # "role":null, # "country":null, # "city":null, # "address":null, # "zipCode":null, # "creationDateTime":"0001-01-01T00:00:00", # "groups":null, # "id":null, # "notificationLanguage":null, # "enabled2FA":false, # "isSSO":false, # "isRemoteSignatureEnabled":false, # "phoneNumber":null, # "physicalPresenceAt":null, # "physicalPresenceValidUntil":null, # "viableTransactionTypes":null, # "isEmailConfirmed":false, # "canRequestOV":false, # "canRequestEV":false, # "customTags":null, # "givenName":null, # "surname":null, # "givenNameLocalized":null, # "surnameLocalized":null}, # "approversAddress":null, # "tokenDeviceId":null, # "orders":[], # "needsImportWithFortify":false, # "isTokenCertificate":false, # "issuerCertificate":null, # "transactionId":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')]; $one['']=$data; } unset($one); return [true,$result]; } #---------------------------------------------------------------------- # ACME API Examples #---------------------------------------------------------------------- # POST https://'.harica_server().'/api/OrganizationAdmin/GetOrganizationsBulk # No input # Status: 200 # Output: # [ # { # "organizationId":"f1d0f150-2aa2-4825-b66b-3ec8909da649","domain":"kunstakademie-muenster.de","organization":"Kunstakademie Münster - Stage","organizationLocalized":"", # "country":"DE","state":"Nordrhein-Westfalen","stateLocalized":"","locality":"" ,"localityLocalized":"","organizationalUnit":"","organizationalUnitLocalized":null, # "dn":"O=Kunstakademie Münster - Stage, ST=Nordrhein-Westfalen, C=DE" ,"validity":"2025-06-15T11:12:25.462615","groupId":"55ff4c4e-159b-4510-9d2c-3e1613a13ae7", # "productListId":"f87782fa-be9c-470b-bafd-2391350f29b2","isBaseDomain":true,"isRemoteSignatureEnabled":false,"dSAAccounts":0,"maxDSAAccounts":0,"historyOrganizationHierarchyGetDTOs":null, # "subUnits":[],"disabledAt":"0001-01-01T00:00:00","organizationIdentifier":null,"validityOV":"2025-06-15T11:12:25.462617","validityEV":"2025-06-15T11:12:25.462617","detailsHistory":"", # "jurisdictionCountry":null,"jurisdictionState":null,"jurisdictionLocality":null,"businessCategory":null,"serial":null, # "groupDomains":"kunstakademie-muenster.de", # "groupName":"Kunstakademie Münster - Stage","customTags":null,"nameCustom":null # },{ # "organizationId":"94eb6d49-d092-4ed4-9fc5-83251d3f9f03","domain":"uni-muenster.de" ,"organization":"Uni Münster - Stage" ,"organizationLocalized":"", # "country":"DE","state":"Nordrhein-Westfalen","stateLocalized":"","locality":"Münster","localityLocalized":"","organizationalUnit":"","organizationalUnitLocalized":null, # "dn":"O=Uni Münster - Stage, ST=Nordrhein-Westfalen, L=Münster, C=DE","validity":"2025-06-15T11:12:05.962607","groupId":"8c4cb2ea-6100-4159-aa7d-9322f396ad5f", # "productListId":"f87782fa-be9c-470b-bafd-2391350f29b2","isBaseDomain":true,"isRemoteSignatureEnabled":false,"dSAAccounts":0,"maxDSAAccounts":0,"historyOrganizationHierarchyGetDTOs":null, # "subUnits":[],"disabledAt":"0001-01-01T00:00:00","organizationIdentifier":null,"validityOV":"2025-06-15T11:12:05.962608","validityEV":"2025-06-15T11:12:05.962609", # "detailsHistory":"","jurisdictionCountry":null,"jurisdictionState":null,"jurisdictionLocality":null,"businessCategory":null,"serial":null, # "groupDomains":"uni-muenster.de", # "groupName":"Uni Münster - Stage","customTags":null,"nameCustom":null # } # ] #---------------------------------------------------------------------- # POST https://'.harica_server().'/api/OrganizationAdmin/GetAcmeEntries # Input: # {"key":"","value":""} # Status: 200 # Output: # [ # { # "id":"da111801-0732-4536-a1d0-5212c8854a4d","entityName":"Uni Münster - Stage","createdAt":"2025-06-16T16:18:54.093936","customTags":"Enterprise;","userEmail":"perske@uni-muenster.de", # "keyId":"8xX8xX8xX8xX8xX8xX8x","hmacKey":"8xX8xX8xX8xX8xX8xX8x8xX8xX8xX8xX8xX8xX8xX8x","acmeAccountId":"","transactionTypeName":"SSL OV","lastTimeUsed":"0001-01-01T00:00:00", # "isEnabled":true,"notes":null,"acmeServerUrl":"https://acme-stg-v02.harica.gr/acme/da111801-0732-4536-a1d0-5212c8854a4d/directory","friendlyName":"perfidix.uni-muenster.de by perske" # } # ] #---------------------------------------------------------------------- ########## UMSTELLEN AUF NEUE API ########## # POST https://'.harica_server().'/api/OrganizationAdmin/GetAcmeCertificatesOfEntry # Input: # {"id":"da111801-0732-4536-a1d0-5212c8854a4d"} # Output: # [...???...] #---------------------------------------------------------------------- # POST https://'.harica_server().'/api/OrganizationAdmin/GetGroupDomainsForAcme # Input: 200 # {"id":"da111801-0732-4536-a1d0-5212c8854a4d"} # Output: # [ # { # "organizationId":"94eb6d49-d092-4ed4-9fc5-83251d3f9f03","organizationName":"Uni Münster - Stage", # "domain":"uni-muenster.de", # "validity":"2025-06-15T11:12:05.962607" # } # ] #---------------------------------------------------------------------- # POST https://'.harica_server().'/api/OrganizationAdmin/GetAcmeDomainsOfEntry # Input: # {"id":"da111801-0732-4536-a1d0-5212c8854a4d"} # Status: 200 # Output: # [ # { # "id":"2cc10718-e4ae-4030-8984-e3c7dc2148dd","acmeEntryId":"da111801-0732-4536-a1d0-5212c8854a4d","fqdn":"wwu.de.uni-muenster.de","isEnabled":false,"allowSubdomains":true,"isAllowed":true # },{ # "id":"b941db92-2896-4678-9edc-9a14fd50aecf","acmeEntryId":"da111801-0732-4536-a1d0-5212c8854a4d","fqdn":"abcde.uni-muenster.de","isEnabled":true,"allowSubdomains":false,"isAllowed":true # } # ] #---------------------------------------------------------------------- # POST https://'.harica_server().'/api/OrganizationAdmin/CreateAcmeDomain # Input: # {"acmeEntryId":"da111801-0732-4536-a1d0-5212c8854a4d","baseDomain":"uni-muenster.de","customDomain":"wwu.de.uni-muenster.de","allowSubdomains":false,"isAllowed":true} # Status: 200 # No output #---------------------------------------------------------------------- # POST https://'.harica_server().'/api/OrganizationAdmin/DisableDomainRule # Input: # {"id":"a5b4206d-e8e9-4c44-ab67-529189282dcd"} # Status: 200 # No output #---------------------------------------------------------------------- # POST https://'.harica_server().'/api/OrganizationAdmin/DisableACMEEntry # Input: # {"id":"da111801-0732-4536-a1d0-5212c8854a4d"} # Status: 200 # No output #---------------------------------------------------------------------- # POST https://'.harica_server().'/api/OrganizationAdmin/CreateAcmeEntry # Input: # {"id":"94eb6d49-d092-4ed4-9fc5-83251d3f9f03","transactionType":"SSL OV","friendlyName":"AFriendlyName"} # # id = GetOrganizationsBulk::organizationId # Status: 200 # No Output #---------------------------------------------------------------------- #---------------------------------------------------------------------- # Als ADMIN: ACME-Konten auflisten #---------------------------------------------------------------------- function harica_list_acme( array $ticket, # Rueckgabewert von harica_login() ):array{ if(($ticket[0]??null)!==true)return [false,html('Not logged in')]; # ACME-Konten abrufen # Parameter muessen als application/json uebergeben werden list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/OrganizationAdmin/GetAcmeEntries',json([ 'key' => '', 'value' => '', ]),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":"da111801-0732-4536-a1d0-5212c8854a4d", # "entityName":"Uni Münster - Stage", # "createdAt":"2025-06-16T16:18:54.093936", # "customTags":"Enterprise;", # "userEmail":"perske@uni-muenster.de", # "keyId":"8xX8xX8xX8xX8xX8xX8x", # "hmacKey":"8xX8xX8xX8xX8xX8xX8x8xX8xX8xX8xX8xX8xX8xX8x", # "acmeAccountId":"", # "transactionTypeName":"SSL OV", # "lastTimeUsed":"0001-01-01T00:00:00", # "isEnabled":true, # "notes":null, # "acmeServerUrl":"https://acme-stg-v02.harica.gr/acme/da111801-0732-4536-a1d0-5212c8854a4d/directory", # "friendlyName":"perfidix.uni-muenster.de by perske" # }, # ... # ] 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 id => data $result=[]; foreach($data as $one)if(is_array($one) and ($one['id']??'')!='' and ($one['isEnabled']??false))$result[$one['id']]=$one; return [true,$result]; } #---------------------------------------------------------------------- # Als ADMIN: ACME-Konto einrichten #---------------------------------------------------------------------- function harica_create_acme( array $ticket, # Rueckgabewert von harica_login() string $orgname, # $config[$orgid]['orgname'] = GetOrganizationsBulk::organization string $friendly, # friendly name = FQDN creator date modifier date ):array{ if(($ticket[0]??null)!==true)return [false,html('Not logged in')]; # HARICA-interne OrganizationId ermitteln # Parameter muessen keine uebergeben werden list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/OrganizationAdmin/GetOrganizationsBulk','',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($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')]; $organizationid=[]; foreach($data as $one)if(is_array($one)){ if(array_key_exists('organizationId',$one))if(is_string($one['organizationId'])){ if(array_key_exists('organization',$one))if(is_string($one['organization'])){ if($orgname==$one['organization']){ $organizationid[]=$one['organizationId']; } } } } if(!$organizationid)return [false,html('Cannot find organization ID for '.$orgname)]; if(count(array_unique($organizationid))!=1)return [false,html('Conflicting organisation IDs for '.$orgname)]; $organizationid=reset($organizationid); # ACME-Account erzeugen # Parameter muessen als application/json uebergeben werden list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/OrganizationAdmin/CreateAcmeEntry',json([ 'id' => $organizationid, 'transactionType' => 'SSL OV', 'friendlyName' => $friendly, ]),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($status['http_code']!=200)return [false,html('Step 2: HTTP status code '.$status['http_code']).'
'.nl2br(html($header)).'
'.nl2br(html($output))]; # Kein Output erwartet # ACME-Accounts auflisten # Parameter muessen als application/json uebergeben werden list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/OrganizationAdmin/GetAcmeEntries',json([ 'key' => '', 'value' => '', ]),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":"da111801-0732-4536-a1d0-5212c8854a4d", # "entityName":"Uni Münster - Stage", # "createdAt":"2025-06-16T16:18:54.093936", # "customTags":"Enterprise;", # "userEmail":"perske@uni-muenster.de", # "keyId":"8xX8xX8xX8xX8xX8xX8x", # "hmacKey":"8xX8xX8xX8xX8xX8xX8x8xX8xX8xX8xX8xX8xX8xX8x", # "acmeAccountId":"", # "transactionTypeName":"SSL OV", # "lastTimeUsed":"0001-01-01T00:00:00", # "isEnabled":true, # "notes":null, # "acmeServerUrl":"https://acme-stg-v02.harica.gr/acme/da111801-0732-4536-a1d0-5212c8854a4d/directory", # "friendlyName":"perfidix.uni-muenster.de by perske" # }, # ... # ] if($output===false)return [false,html('Step 3: '.$status)]; if(!array_key_exists('http_code',$status))return [false,html('Step 3: No HTTP status').'
'.nl2br(html($header)).'
'.nl2br(html($output))]; if(!in_array($status['http_code'],[200,202]))return [false,html('Step 3: HTTP status code '.$status['http_code']).'
'.nl2br(html($header)).'
'.nl2br(html($output))]; if($output=='')return [false,html('Step 3: Missing output')]; $data=json_decode($output,true,9999,JSON_BIGINT_AS_STRING); if(!is_array($data))return [false,html('Step 3: Invalid output')]; $acmeid=[]; foreach($data as $one)if(is_array($one))if($one['isEnabled']??false){ if(array_key_exists('id',$one))if(is_string($one['id'])){ if(array_key_exists('friendlyName',$one))if(is_string($one['friendlyName'])){ if($one['friendlyName']==$friendly)$acmeid[]=$one['id']; } } } if(!$acmeid)return [false,html('Cannot find ACME ID for '.$friendly)]; if(count(array_unique($acmeid))!=1)return [false,html('Conflicting ACME IDs for '.$friendly)]; $acmeid=reset($acmeid); return [true,$acmeid]; } #---------------------------------------------------------------------- # Als ADMIN: ACME-Konto-Domains auflisten #---------------------------------------------------------------------- function harica_get_acme_domains( array $ticket, # Rueckgabewert von harica_login() string $acmeid, # GetAcmeEntries::id ):array{ if(($ticket[0]??null)!==true)return [false,html('Not logged in')]; # ACME-Konto-Domains abrufen # Parameter muessen als application/json uebergeben werden list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/OrganizationAdmin/GetAcmeDomainsOfEntry',json([ 'id' => $acmeid, ]),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":"2cc10718-e4ae-4030-8984-e3c7dc2148dd", # "acmeEntryId":"da111801-0732-4536-a1d0-5212c8854a4d", # "fqdn":"wwu.de.uni-muenster.de", # "isEnabled":false, # "allowSubdomains":true, # "isAllowed":true # },... # ] 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 id => data $result=[]; foreach($data as $one)if(is_array($one))if($one['isEnabled']??false)$result[$one['id']]=$one; return [true,$result]; } #---------------------------------------------------------------------- # Als ADMIN: ACME-Konto-Domain hinzufuegen #---------------------------------------------------------------------- function harica_add_acme_domain( array $ticket, # Rueckgabewert von harica_login() string $acmeid, # GetAcmeEntries::id string $fqdn, # FQDN string $base, # Bei HARICA validierte Base-Domain bool $allowsub=false, # Unterdomains erlauben ):array{ if(($ticket[0]??null)!==true)return [false,html('Not logged in')]; # ACME-Konto-Domain hinzufuegen # Parameter muessen als application/json uebergeben werden list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/OrganizationAdmin/CreateAcmeDomain',json([ 'acmeEntryId' => $acmeid, 'baseDomain' => $base, 'customDomain' => $fqdn, 'allowSubdomains' => $allowsub, 'isAllowed' => true, ]),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))]; # Kein Output erwartet return [true]; } #---------------------------------------------------------------------- # Als ADMIN: ACME-Konto-Domain wegnehmen #---------------------------------------------------------------------- function harica_del_acme_domain( array $ticket, # Rueckgabewert von harica_login() string $acmeid, # GetAcmeEntries::id string $domid, # Domain-ID (nicht FQDN!) ):array{ if(($ticket[0]??null)!==true)return [false,html('Not logged in')]; # ACME-Konto-Domain wegnehmen # Parameter muessen als application/json uebergeben werden list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/OrganizationAdmin/DisableDomainRule',json([ 'id' => $domid, ]),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))]; # Kein Output erwartet return [true]; } #---------------------------------------------------------------------- # Als ADMIN: ACME-Konto-Zertifikate auflisten ########## UMSTELLEN AUF NEUE API ########## #---------------------------------------------------------------------- function harica_get_acme_certs( array $ticket, # Rueckgabewert von harica_login() string $acmeid, # GetAcmeEntries::id ):array{ if(($ticket[0]??null)!==true)return [false,html('Not logged in')]; # ACME-Konto-Zertifikate abrufen # Parameter muessen als application/json uebergeben werden ########## UMSTELLEN AUF NEUE API ########## list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/OrganizationAdmin/GetAcmeCertificatesOfEntry',json([ 'id' => $acmeid, ]),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":"a3d578b4-1570-47b3-8df8-a06e11b5a297", # "acmeEntryId":"a4e41192-b385-4fc0-a1bf-0560649cc785", # "statusName":"Valid", # "friendlyName":null, # "duration":0, # "customTags":"", # "revocationMessage":"", # "issuedAt":"2025-07-08T11:18:31.165699", # "validFrom":"2025-07-08T11:08:29", # "validTo":"2026-07-08T11:08:29", # "revocationCode":"", # "isRevoked":false, # "revokedAt":null, # "dn":"C=DE,ST=Nordrhein-Westfalen,L=Münster,O=Universität Münster,CN=ivv1srv35.uni-muenster.de", # "sans":"DNS Name=ivv1srv35.uni-muenster.de, DNS Name=ivv1filemaker2.uni-muenster.de", # "certificate":"-----BEGIN CERTIFICATE-----\nMII.....BA==\n-----END CERTIFICATE-----", # "serial":"3A64454319C739F69E95242EEBF54A49", # "notes":null, # "revokedByEmail":null, # "pemBundle":"subject=...\nissuer=...\n-----BEGIN CERTIFICATE-----\nMII...BA==\n-----END CERTIFICATE-----\nsubject=...\nissuer=...\n-----BEGIN CERTIFICATE-----\nMII...UQGJ\n-----END CERTIFICATE-----\nsubject=...\nissuer=...\n-----BEGIN CERTIFICATE-----\nMII.....ew==\n-----END CERTIFICATE-----\n" # },... # ] 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 id => data $result=[]; foreach($data as $one)if(is_array($one))$result[$one['id']]=$one; return [true,$result]; } #---------------------------------------------------------------------- # Als ADMIN: ACME-Konto loeschen #---------------------------------------------------------------------- function harica_delete_acme( array $ticket, # Rueckgabewert von harica_login() string $acmeid, # GetAcmeEntries::id ):array{ if(($ticket[0]??null)!==true)return [false,html('Not logged in')]; # ACME-Konto-Domain wegnehmen # Parameter muessen als application/json uebergeben werden list($output,$status,$header)=curl_run_post('https://'.harica_server().'/api/OrganizationAdmin/DisableACMEEntry',json([ 'id' => $acmeid, ]),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))]; # Kein Output erwartet return [true]; } #---------------------------------------------------------------------- # Als ADMIN: Alle gueltigen Zertifikate auflisten #---------------------------------------------------------------------- function harica_list( string $apikey, # Kein Ticket, sondern API-Key string $typ='valid', # valid, expired oder revoked ):array{ if(strlen($apikey)!=strspn($apikey,'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/='))return [false,html('Invalid API key')]; if(!in_array($typ,['valid','expired','revoked']))return [false,html('Invalid validity type')]; # Alle Zertifikate auflisten # Keine Parameter list($output,$status,$header)=curl_run_get('https://cm.harica.gr/cm/v1/admin/certificates/list/'.$typ,null,harica_curlopt(null,false,[ 'X-API-Key: '.$apikey, 'Accept: application/json', ])); # Status 200 erwartet # [ { "source": "ACME", # "transactionId": "3a96f059-e045-4661-8a1a-7df33c646cfb", # "transactionTypeName": "SSL OV", # "friendlyName": null, # "userEmail": "itportal3@uni-muenster.de", # "user": "Rainer Perske", # "serial": "4B2B176FC42DF6D77B2C97272688F97E", # "certificate": "-----BEGIN CERTIFICATE-----\n.....\n-----END CERTIFICATE-----", # "dN": "C=DE,ST=.....". # "validFrom": "2025-11-05T21:10:11", # "validTo": "2026-11-05T21:10:11", # "isRevoked": false, # "revokedAt": null # },..... # { "source": "BULK", # "transactionId": "0c3cfc31-0176-4ce5-936c-585dd1a97dbd", # "transactionTypeName": "S/MIME email-only", # "friendlyName": "Vxxxxxx Nxxxxxx", # "userEmail": "itportal3@uni-muenster.de", # "user": "Rainer Perske", # "serial": "1CE66496004F9512E33FEE7A16B041C0", # "certificate": "-----BEGIN CERTIFICATE-----\n.....\n-----END CERTIFICATE-----", # "dN": "CN= ", # "validFrom": "2025-10-15T09:37:54", # "validTo": "2027-10-15T09:37:54", # "isRevoked": false, # "revokedAt": null # },..... # { "source": "Single Request", # "transactionId": "4d842ab0-f3b1-486b-8230-476ba2dbca9d", # "transactionTypeName": "SSL OV", # "friendlyName": "xxxxx.xxxx.xxxxxxxxxx", # "userEmail": "itportal@uni-muenster.de", # "user": "Rainer Perske", # "serial": "25464C4FB2745CA38B7F7881880026E7", # "certificate": "-----BEGIN CERTIFICATE-----\n.....\n-----END CERTIFICATE-----", # "dN": "C=DE,ST=.....", # "validFrom": "2025-01-09T10:38:53", # "validTo": "2026-01-09T10:38:53", # "isRevoked": false, # "revokedAt": null # },..... # ] 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 id => data $result=[]; foreach($data as $one)if(is_array($one))if(($one['serial']??'')!='')$result[$one['serial']]=$one; return [true,$result]; } #---------------------------------------------------------------------- # Als ADMIN: Zertifikate widerrufen #---------------------------------------------------------------------- function harica_revoke( string $apikey, # Kein Ticket, sondern API-Key string $source, # bulk oder single oder acme, aus dem von harica_list() gelieferten source herleiten string $tid, # von harica_list() gelieferte transactionId string $reason, # Freitext ):array{ if(strlen($apikey)!=strspn($apikey,'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/='))return [false,html('Invalid API key')]; if(!in_array($source,['single','bulk','acme']))return [false,html('Invalid certificate source')]; # Angegebenes Zertifikat widerrufen # Keine Parameter list($output,$status,$header)=curl_run_post('https://cm.harica.gr/cm/v1/admin/revoke/'.$source,json([ 'transactionId' => $tid, 'message' => $reason, 'value' => '4.9.1.1.1.1', 'informUser' => true, ]),harica_curlopt(null,false,[ 'X-API-Key: '.$apikey, 'Content-Type: application/json;charset=utf-8', 'Accept: application/json', ])); # 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){ # Status 400 mit Textfragment 'has previously been revoked' ist einfach nur eine Bestaetigung eines frueheren Widerrufs if($status['http_code']!=400 or strpos($output,'has previously been revoked')==false){ return [false,html('HTTP status code '.$status['http_code']).'
'.nl2br(html($header)).'
'.nl2br(html($output))]; } } # Kein Output erwartet return [true]; } #---------------------------------------------------------------------- # Ende #---------------------------------------------------------------------- ?>