Połączenia do usługi sieciowej SIO-KReM

Połączenia do usługi sieciowej SIO-KReM

Powrót do listy

Instrukcja połączenia do usługi sieciowej SIO-KReM na środowisku produkcyjnym

Spis treści:

1. System SIO-Krem 4

2. Wykorzystane technologie i standardy przy tworzeniu serwera usług sieciowych 6

3. Dodanie klienta usługi sieciowej w aplikacji SIO-KReM 7

4. Uwierzytelnianie: 12

5. Podpis elektroniczny: 13

5.1 Algorytm podpisywania wiadomości 14

6. Interfejsy: 16

6.1 GetStudentResultSet 16

7. Dodatkowe uwagi do zapytań SOAP 19

8. Przykładowa klasa JAVA do obsługi kluczy RSA SHA512: 19

9. Przykładowa klasa JAVA do obsługi kluczy OpenPGP: 22

System SIO-Krem

Aplikacja SIO-KREM jest częścią SIO (Systemu Informacji Oświatowej) i jest przeznaczona do wsparcia procesu rekrutacji kandydatów na uczelnie wyższe poprzez udostępnienie wyników egzaminów maturalnych oraz innych związanych z tym informacji uczelniom, które posiadają aktywny dostęp do SIO i podadzą PESEL kandydata.

Podstawą prawną działania aplikacji SIO-KREM jest art. 62 ustawy o SIO (pozyskiwanie danych przez uczelnie).

Do bazy danych SIO-KREM mają dostęp uczelnie publiczne, niepubliczne i kościelne zarejestrowane w bazie danych SIO z typem podmiotu S001 id = 142, 145, 162, które posiadają aktywny dostęp do SIO.

W bazie danych SIO-KREM znajdują się informacje o wynikach zdanego egzaminu maturalnego poszczególnych osób (w przyszłości również wyniki egzaminu zawodowego lub egzaminu potwierdzającego kwalifikacje w zawodzie). Wyniki egzaminów przekazywane są przez OKE do SIO-KREM na podstawie art. 20 ustawy o SIO.

Na informacje o kandydacie składają się dane o:

• procentowych wynikach z poszczególnych egzaminów maturalnych

• wskazanie centyla, w którym dany wynik się znajduje

• lista numerów dokumentów wydanych danemu kandydatów

• informację o ukończonej szkole

Zalogowana uczelnia ma możliwość zarejestrowania PESEL kandydata – wpisanego ręcznie lub zaimportowanego z pliku.

Aplikacja przyjmuje jedynie PESELe o poprawnej strukturze oraz informuje jakie PESELe z załączonego pliku są błędne (tj. mają nieprawidłową strukturę).

Po zarejestrowaniu danych na liście kandydatów danej uczelni uczelnia ma możliwość pobrania plików csv z danymi:

• wyników

• numerów dokumentów

• centyli

• szkół

Pliki podzielone są na pliki z:

• nowymi danymi – czyli takie dane, które nigdy nie były pobrane przez dany podmiot z danego zakresu

• aktualizacjami danych – czyli takie dane, które już były pobrane a w międzyczasie do bazy danych SIO-KReM wpłynęła aktualizacja danych

• wszystkimi danymi – czyli pełen komplet danych z danego zakresu

• plik z brakami – czyli lista PESELi, dla których nie ma danych

Do bazy danych SIO-KREM zostają przesłane dane maturzystów, którzy w deklaracji przystąpienia do matury wyrazili zgodę na przekazywanie danych do KReM. W przypadku, gdy kandydat wycofa zgodę na przekazywanie danych do SIO-KREM w aplikacji zostaną usunięte dane o wynikach ucznia oraz zostaje zaktualizowany plik z wycofanymi zgodami.

Uczelnie mogą przeglądać listę kandydatów i zakładać filtry na wyszukiwanie danych oraz pobierać pliki z danymi dla wyszukanej grupy kandydatów.

Aplikacja SIO-KREM prowadzi również rejestr/historię wysłanych i pobranych z SIO-KReM plików. Dane w SIO-KREM są przechowywane w bazie danych bezterminowo.

Z SIO-KREM można również pobrać słownik arkuszy egzaminacyjnych, słownik szkół oraz słownik olimpiad.

Wykorzystane technologie i standardy przy tworzeniu serwera usług sieciowych

System SIO-KReM udostępnia usługi sieciowe pod adresem https://krem1.sio.gov.pl/ws. Adres pliku WSDL specyfikującego interfejs usług sieciowych to https://krem1.sio.gov.pl/ws?wsdl.

  • Serwer usług sieciowych został napisany zgodnie ze standardami SOAP (Simple Object Access Protocol). Obsługuje ̨ wersje protokołu SOAP 1.1 [SOAP] i SOAP 1.2 [SOAP1.2].
  • Do opisu interfejsu został wykorzystany standard WSDL (Web Services Description Language) [WSDL].
  • Cała komunikacja z serwerem usług sieciowych jest szyfrowana za pomocą TLS/SSL (jest jednostronna – adres https://krem1.sio.gov.pl/ws podpisany jest zaufanym certyfikatem) [RFC2246]
  • Uwierzytelnianie klienta jest przeprowadzane poprzez identyfikator i hasło (ustawiane poprzez interfejs systemu SIO-KReM) zgodnie ze standardem WS-Security [UTP].
  • Do podpisywania komunikatów użyty został standard RSA-SHA512 [RFC8332] i OpenPGP [RFC2440]

Dodanie klienta usługi sieciowej w aplikacji SIO-KReM

Aby skonfigurować połączenie systemu uczelni do usługi SIO-KReM należy zalogować się do aplikacji SIO-KReM dostępnej pod adresem

https://krem.sio.gov.pl

 

  1. Użytkownik po zalogowaniu wybiera „Ustawienia Systemowe” w menu bocznym. Po kliknięciu pojawi się okno do konfiguracji połączenia:

Okno z widokiem na pola do uzupełnienia

Rysunek 1 Okno Konfiguracji połączenia

  1. Użytkownik w module „Dane systemu” wypełnia dane wymagane do połączenia między usługą SIO-KreM a systemem uczelni (wszystkie pola są wymagane) tj. :
  • Imię;
  • Nazwisko;
  • Adres e-mail;
  • Numer telefonu;
  • Identyfikator/Login
  • adres IP – adres serwera z którego będzie wysyłane zapytanie do usługi SIO-KReM.
  • Hasło;
  • Powtórzenie hasła;
  • Klucz publiczny systemu uczelni – wymagany format PEM (zawierający header 'BEGIN PGP PUBLIC KEY BLOCK’ lub 'BEGIN PUBLIC KEY’ – w zależności od wybranego rodzaju klucza). Preferujemy klucz RSA od PGP.

Okno z wypełnionymi danymi

Rysunek 2 Dane systemu

Po uzupełnieniu wszystkich pól, użytkownik zatwierdza konfigurację przyciskiem „Zapisz”. System poinformuje o pomyślnej konfiguracji.

W oknie po prawej stronie klucza publicznego systemu uczelni wyświetli się wygenerowany klucz publiczny usługi SIO-KReM oraz daty ważności obydwu kluczy.

Widok konfiguracji klucza RSA:

okno modyfikacji danych

Rysunek Konfiguracja połączenia z kluczem RSA

W przypadku klucza PGP dodatkowo wyświetlą się pola zawierające odciski palca obydwu kluczy.

Widok konfiguracji klucza PGP:

Widok konfiguracji klucza PGP

Rysunek Konfiguracja połączenia z kluczem PGP

Dostęp do usługi SIO-KReM (https://krem1.sio.gov.pl/ws ) będzie aktywny maksymalnie po 5 minutach.

Jeżeli użytkownik chce edytować dane połączenia, wybiera „Modyfikuj” umieszczony w prawym górnym rogu.

wskazanie na funkcję modyfikacji

Rysunek 5 Modyfikacja połączenia

Uwierzytelnianie:

Uwierzytelnianie klientów usług sieciowych odbywa się podobnie, jak w przypadku stron internetowych, czyli poprzez identyfikator i hasło. Informacje te umieszczane są wewnątrz nagłówka wiadomości SOAP, czyli <Header>, w elemencie <Security>. Identyfikator i hasło są opakowane w element <UsernameToken>, który posiada dwa podelementy: <Username>zawierający identyfikator klienta oraz <Password>zawierający hasło. Dodatkowo element <Password> może zawierać atrybut Type, który określa typ przesyłanego hasła. Są dwie możliwości: PasswordText — hasło przesyłane jest zwykłym tekstem, PasswordDigest — hasło przesyłane jest w postaci skrótu (ang. digest). Jeśli żaden atrybut nie został podany, to domyślnie obowiązuje typ PasswordText. Usługa SIO-KReM akceptuje obie te możliwości, choć zalecane jest przesyłanie hasła w postaci skrótu (w przypadku usługi SIO-KReM musi on zostać wykonany funkcją MD5). Wszystkie elementy muszą być w przestrzeni nazw WS-Security [UTP].

Oto przykład poprawnego nagłówka zawierającego identyfikator user i skrót hasła password wykonany funkcją MD5:

<wsse:Security
xmlns:wsse=”http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-
wssecurity-secext-1.0.xsd”>
<wsse:UsernameToken>
<wsse:Username>
user
</wsse:Username>
<wsse:Password Type=”#PasswordDigest”>
c62d929e7b7e7b6165923a5dfc60cb56
</wsse:Password>
</wsse:UsernameToken>

 

Podpis elektroniczny:

W każdej poprawnej wiadomości SOAP przesyłanej od klienta do serwera lub w odwrotną stronę, musi się̨ znaleźć podpis elektroniczny całego elementu <Body>. Musi być on umieszczony, wewnątrz nagłówka <Security>, w specjalnym elemencie <Signature> (w przypadku użycia prostego klucza RSA) lub <PGPSignature> (w przypadku użycia klucza OpenPGP). Aby utworzyć poprawny podpis elektroniczny zaakceptowany przez system SIO-KReM należy użyć dwóch standardów: Exclusive XML Canonicalization [xml-exc-c14n] oraz jeden ze standardów: RSA-SHA512 [RFC8332] lub OpenPGP [RFC2440].

Pierwszy służy do sprowadzenia dokumentu XML do postaci kanonicznej, natomiast drugi jest używany do wygenerowania podpisu (odłączonego, ang. detached) na postaci kanonicznej odpowiedniej struktury XML (w naszym przypadku elementu <Body>). Konieczność użycia algorytmu sprowadzania do postaci kanonicznej wynika z faktu, że dwa logicznie takie same dokumenty XML mogą̨ się̨ trochę różnić tekstowo (np. inną kolejnością atrybutów, niewypisywaniem atrybutów domyślnych, nadmiarowymi spacjami, sposobem umieszczania informacji o przestrzeniach nazw, różnym traktowaniem znaku końca wiersza itp.) i wtedy mogłoby dojść do niezamierzonej, niepoprawnej weryfikacji podpisu. Dlatego by być pewnym, że do podpisu i weryfikacji będziemy używali tego samego dokumentu, konieczne jest przed wykonaniem tych operacji, przekształcenie dokumentu XML do postaci kanonicznej. Algorytm ten jest elementem standardu XML Signature [XMLDSig]

Algorytm podpisywania wiadomości

Poniżej został przedstawiony dokładny algorytm podpisywania wiadomości wymienianych z serwerem usług sieciowych systemu SIO-KReM:

  • Identyfikacja elementu <Body> w dokumencie,
  • Wykonanie algorytmu kanonizacji na elemencie <Body> wraz zawartością,
  • Stworzenie podpisu odłączonego na wyniku kanonizacji (z użyciem RSA SHA512),
  • Zakodowanie wartości podpisu w postaci Base64 [RFC2045],
  • Stworzenie wewnątrz nagłówka <Security> (zgodnego ze standardem WS-Security) pod-elementu <Signature> (lub <PGPSignature> w przypadku OpenPGP), który będzie zawierał wartość podpisu (w postaci Base64),
  • Umieszczenie wewnątrz elementu <Signature> (lub <PGPSignature> w przypadku OpenPGP) wartości wcześniej wygenerowanego podpisu.

Proces weryfikacji wygląda następująco:

  • Identyfikacja nagłówka <Security> wraz z podelementem <Signature> (lub <PGPSignature> w przypadku OpenPGP),
  • Identyfikacja elementu <Body> w dokumencie,
  • Wykonanie algorytmu kanonizacji na elemencie <Body> wraz zawartością,
  • Odkodowanie wartości podpisu umieszczonego wewnątrz elementu <Signature> z postaci Base64 do binarnej,
  • Weryfikacja czy wartość podpisu (w formacie binarnym) odpowiada podpisowi skanonizowanej postaci elementu <Body> stworzonego przez nadawcę.

Każde zapytanie wysyłane do serwera usług sieciowych systemu SIO-KReM musi zostać podpisane kluczem prywatnym administratora (Uczelni), który jest zarejestrowany w systemie SIO-KREM.

Każda odpowiedź odsyłana przez usługę SIO-KReM jest podpisywana kluczem prywatnym systemu (klucz publiczny usługi do weryfikacji wiadomości udostępniony jest w systemie SIO-KREM w zakładce administracyjnej systemu uczelni).

Podany kod jest przykładem poprawnie podpisanego pliku. Jest to zapytanie dla funkcji GetStudentResultSet podpisane omówionym algorytmem (odpowiednio sformatowane by byłe bardziej czytelne). Zapytanie zawiera także nagłówek autoryzacyjny, w którym identyfikatorem administratora jest user, a hasłem password.

<S:Envelope xmlns:S=”http://schemas.xmlsoap.org/soap/envelope/”>
<S:Header>
<wsse:Security
xmlns:wsse=”http://docs.oasis-open.org/wss/2004/01/oasis-200401-
wss-wssecurity-secext-1.0.xsd”>
<wsse:UsernameToken>
<wsse:Username>user</wsse:Username>
<wsse:Password>password</wsse:Password>
</wsse:UsernameToken>
<siokrem:Signature xmlns:siokrem=”https://krem1.sio.gov.pl”>
iEYEABE…hsv7y
</siokrem:Signature>
</wsse:Security>
</S:Header>
<S:Body>
<GetStudentResultSet xmlns=”https://krem1.sio.gov.pl „>
<pesel>860911xxxxx</pesel>
</GetStudentResultSet>
</S:Body>
</S:Envelope>

Interfejsy:

GetStudentResultSet

Pobranie wyników wielu maturzystów.

Parametry — lista struktura zawierających numery PESEL maturzystów.

Wynik ogólny kod zakończenia funkcji (kod sukcesu lub informacja o błędzie) i lista struktur (dokładnie tyle ile numerów PESEL było w zapytaniu) zawierająca wynik wykonania funkcji (kod sukcesu lub błędu) i oceny maturzysty (w przypadku gdy ocen nie ma w bazie lub zapytanie dla tego maturzysty zakończyło się innym błędem, zbiór ocen jest pusty).

Przykładowe zapytanie:

<?xml version=”1.0″ encoding=”UTF-8″?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV=”http://schemas.xmlsoap.org/soap/envelope/”
xmlns:ns1=”https://krem1.sio.gov.pl”>
<SOAP-ENV:Header>
<wsse:Security
xmlns:wsse=”http://docs.oasis-open.org/wss/2004/01/oasis-200401-
wss-wssecurity-secext-1.0.xsd”>
<wsse:UsernameToken>
<wsse:Username>user_uni</wsse:Username>
<wsse:Password Type=”#PasswordDigest”>
5f4dcc3b5aa765d61d8327deb882cf99
</wsse:Password>
</wsse:UsernameToken>
<ns1:Signature>
iD8DBQBG…HVMhBkJ8xI=
</ns1:Signature>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<ns1:GetStudentResultSet>
<ns1:pesel>111111xxxxx</ns1:pesel>
<ns1:pesel>881212xxxxx</ns1:pesel>
<ns1:pesel>881224xxxxx</ns1:pesel>
</ns1:GetStudentResultSet>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Przykładowa odpowiedź:

<?xml version=”1.0″ encoding=”UTF-8″?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV=”http://schemas.xmlsoap.org/soap/envelope/”
xmlns:ns1=”https://krem.uw.edu.pl”>
<SOAP-ENV:Header>
<wsse:Security
xmlns:wsse=”http://docs.oasis-open.org/wss/2004/01/oasis-200401-
wss-wssecurity-secext-1.0.xsd” SOAP-ENV:mustUnderstand=”1″>
<ns1:PGPSignature>
iD8DBQBG…JxsOT8=
</ns1:PGPSignature>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<ns1:GetStudentResultSetResponse>
<ns1:responseCode>0</ns1:responseCode>
<ns1:response>
<ns1:errorCode>12</ns1:errorCode>
<ns1:studentResult>
<ns1:pesel>111111xxxxx</ns1:pesel>
<ns1:results/>
</ns1:studentResult>
</ns1:response>
<ns1:response>
<ns1:errorCode>11</ns1:errorCode>
<ns1:studentResult>
<ns1:pesel>881212xxxxx</ns1:pesel>
<ns1:results/>
</ns1:studentResult>
</ns1:response>
<ns1:response>
<ns1:errorCode>0</ns1:errorCode>
<ns1:studentResult>
<ns1:pesel>881224xxxxxx</ns1:pesel>
<ns1:results>
<ns1:exam>
<ns1:code>PO_u_N_p_082</ns1:code>
<ns1:points>30</ns1:points>
</ns1:exam>
<ns1:exam>
<ns1:code>PO_p_P_p_082</ns1:code>
<ns1:points>40</ns1:points>
</ns1:exam>
<ns1:exam>
<ns1:code>PO_p_R_p_082</ns1:code>
<ns1:points>30</ns1:points>
</ns1:exam>
</ns1:results>
</ns1:studentResult>
</ns1:response>
</ns1:GetStudentResultSetResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Kody błędu (errorCode):

(’RESULT_OK’,0)
(’RESULT_ERROR_WRONG_PESEL_NO’,12)
(’RESULT_ERROR_NO_DATA’,11)
(’RESULT_ERROR_WRONG_MARK’,25)
(’RESULT_PESEL_WITHDRAWN’,41)
(’RESULT_UNKNOWN_PESEL’,43)

Dodatkowe uwagi do zapytań SOAP

Ze względów wydajnościowych, w usłudze SIO-KReM będzie ustalony limit — określający ile danych różnych maturzystów może znajdować się w jednym zapytaniu (i odpowiedzi SOAP). Obecnie limit wynosi 1000, czyli np. zapytanie dodające 800 wyników maturzystów wykona sį prawidłowo, natomiast prośba o pobranie 2000 maturzystów zakończy się błędem. Gdy klient chce wykonać operacje dla większej ilości danych, to musi wykonać kilka razy tę samą funkcję, w każdym wywołaniu podając inne parametry.

Przykładowa klasa JAVA do obsługi kluczy RSA SHA512:

package pl.men.utils.common;

import java.io.DataInputStream;

import java.io.File;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.StringWriter;

import java.security.InvalidKeyException;

import java.security.KeyFactory;

import java.security.KeyPair;

import java.security.KeyPairGenerator;

import java.security.NoSuchAlgorithmException;

import java.security.PrivateKey;

import java.security.PublicKey;

import java.security.Signature;

import java.security.SignatureException;

import java.security.interfaces.RSAPrivateKey;

import java.security.spec.EncodedKeySpec;

import java.security.spec.InvalidKeySpecException;

import java.security.spec.PKCS8EncodedKeySpec;

import java.security.spec.X509EncodedKeySpec;

import java.util.Base64;

 

import org.bouncycastle.openssl.jcajce.JcaPEMWriter;

 

import javax.crypto.Mac;

import javax.crypto.spec.SecretKeySpec;

 

public class Cryptography {

private static final String PUBLIC_KEY_PATH = „sciezka_do_pliku.pub”;

private static final String PRIVATE_KEY_PATH = „sciezka_do_pliku.key”;

private static final String SIG_ALG = „SHA512withRSA”;

private static final String KEY_ALG = „HmacSHA512”;

 

public static byte[] generateSignature (String content) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, InvalidKeySpecException, IOException {

Signature sign = Signature.getInstance(SIG_ALG);

PrivateKey privateKey = readPrivateKeyFromFile();

sign.initSign(privateKey);

byte[] bytes = content.getBytes();

sign.update(bytes);

 

return sign.sign();

}

public static boolean verifySignature (String publicKey, String content, byte[] signature) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, InvalidKeySpecException, IOException {

Signature sign = Signature.getInstance(SIG_ALG);

sign.initVerify(readPublicKeyFromString(publicKey));

sign.update(content.getBytes());

return sign.verify(signature);

}

 

public static void generateKeyPair() throws NoSuchAlgorithmException, IOException {

KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(„RSA”);

keyPairGen.initialize(2048);

KeyPair pair = keyPairGen.generateKeyPair();

saveKeyPairToFiles(pair);

}

 

private static PublicKey readPublicKeyFromString(String publicKey) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {

publicKey = publicKey.replace(„—–BEGIN PUBLIC KEY—–„, „”).replace(„—–END PUBLIC KEY—–„, „”).replaceAll(System.lineSeparator(), „”).replaceAll(” „, „”).replaceAll(„\\s+”,””);

byte[] publicKeyBytes = Base64.getDecoder().decode(publicKey);

KeyFactory keyFactory = KeyFactory.getInstance(„RSA”);

EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);

return keyFactory.generatePublic(publicKeySpec);

}

private static PublicKey readPublicKeyFromFile() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {

File file = new File(PUBLIC_KEY_PATH);

DataInputStream dis = new DataInputStream(new FileInputStream(file));

byte[] pubKeyBytes = new byte[(int) file.length()];

dis.read(pubKeyBytes);

dis.close();

 

KeyFactory factory = KeyFactory.getInstance(„RSA”);

EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pubKeyBytes);

return factory.generatePublic(publicKeySpec);

}

 

private static PrivateKey readPrivateKeyFromFile() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {

File file = new File(PRIVATE_KEY_PATH);

DataInputStream dis = new DataInputStream(new FileInputStream(file));

byte[] privKeyBytes = new byte[(int) file.length()];

dis.read(privKeyBytes);

dis.close();

 

KeyFactory factory = KeyFactory.getInstance(„RSA”);

PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(privKeyBytes);

return (RSAPrivateKey) factory.generatePrivate(privKeySpec);

}

private static void saveKeyPairToFiles(KeyPair pair) throws IOException {

FileOutputStream fos = new FileOutputStream(PUBLIC_KEY_PATH);

fos.write(pair.getPublic().getEncoded());

fos.close();

 

FileOutputStream out = new FileOutputStream(PRIVATE_KEY_PATH);

out.write(pair.getPrivate().getEncoded());

out.close();

}

public static String mapPublicKeyToString(PublicKey key) {

StringWriter sw = new StringWriter();

JcaPEMWriter writer = new JcaPEMWriter(sw);

try {

writer.writeObject(key);

writer.close();

} catch (IOException e) {

e.printStackTrace();

}

return sw.getBuffer().toString();

}

 

public static String getStringHash(String message, String secret)

throws InvalidKeyException, NoSuchAlgorithmException {

Mac hmacSHA512 = Mac.getInstance(KEY_ALG);

SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(), KEY_ALG);

hmacSHA512.init(secretKey);

return org.apache.commons.codec.binary.Base64.encodeBase64String(hmacSHA512.doFinal(message.getBytes()));

}

}

Przykładowa klasa JAVA do obsługi kluczy OpenPGP:

package pl.men.utils.common;

import org.bouncycastle.bcpg.BCPGOutputStream;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.*;
import org.bouncycastle.util.encoders.Hex;

import java.io.*;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.security.SignatureException;
import java.util.Iterator;

public class Cryptography {

private static final String PGP_SECRET_KEY_RING_PATH = „keys/secring.gpg”;
private static final String PGP_PROVIDER = „BC”;
private static final int PGP_BUFFER_SIZE = 1024;

public static PGPSecretKey readPGPSecretKey(InputStream privateKeyRingInputStream, String publicKeyFingerprint) throws IOException, PGPException {
Security.addProvider(new BouncyCastleProvider());
PGPUtil.setDefaultProvider(PGP_PROVIDER);

privateKeyRingInputStream = PGPUtil.getDecoderStream(privateKeyRingInputStream);

PGPSecretKeyRingCollection secretRingCollection = new PGPSecretKeyRingCollection(privateKeyRingInputStream);

PGPSecretKey secretKey = null;
Iterator<PGPSecretKeyRing> secretKeyRingCollectionIterator = secretRingCollection.getKeyRings();

while (secretKey == null && secretKeyRingCollectionIterator.hasNext()) {
PGPSecretKeyRing secretKeyRing = secretKeyRingCollectionIterator.next();
Iterator<PGPSecretKey> secretKeyRingIterator = secretKeyRing.getSecretKeys();

while (secretKey == null && secretKeyRingIterator.hasNext()) {
PGPSecretKey currentSecretKey = secretKeyRingIterator.next();
PGPPublicKey currentPublicKey = currentSecretKey.getPublicKey();
String keyFingerprint = new String(Hex.encode(currentPublicKey.getFingerprint()));
if (currentSecretKey.isSigningKey() && keyFingerprint.equalsIgnoreCase(publicKeyFingerprint)) {
secretKey = currentSecretKey;
}
}
}

if (secretKey == null) {
throw new IllegalArgumentException(„Can’t find signing key with fingerprint: (” + publicKeyFingerprint + „) in key ring.”);
}

return secretKey;
}

public static PGPPublicKey readPGPPublicKey(InputStream publicKeyRingInputStream, String publicKeyFingerprint) throws IOException, PGPException {
Security.addProvider(new BouncyCastleProvider());
PGPUtil.setDefaultProvider(PGP_PROVIDER);

PGPPublicKey publicKey = null;

PGPPublicKeyRingCollection publicKeyRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(publicKeyRingInputStream));
Iterator<PGPPublicKeyRing> publicKeyRingIterator = publicKeyRingCollection.getKeyRings();

while (publicKey == null && publicKeyRingIterator.hasNext()) {
PGPPublicKeyRing publicKeyRing = publicKeyRingIterator.next();
Iterator<PGPPublicKey> publicKeyIterator = publicKeyRing.getPublicKeys();

while (publicKey == null && publicKeyIterator.hasNext()) {
PGPPublicKey currentPublicKey = publicKeyIterator.next();
String keyFingerprint = new String(Hex.encode(currentPublicKey.getFingerprint()));
if (keyFingerprint.equalsIgnoreCase(publicKeyFingerprint)) {
publicKey = currentPublicKey;
}
}
}

if (publicKey == null) {
throw new IllegalArgumentException(„Can’t find signing key with fingerprint: (” + publicKeyFingerprint + „) in public key ring.”);
}

return publicKey;
}

public static PGPPublicKey readPGPPublicKey(String armoredKeyString, String publicKeyFingerprint) throws IOException, PGPException {
Security.addProvider(new BouncyCastleProvider());
PGPUtil.setDefaultProvider(PGP_PROVIDER);

PGPPublicKey publicKey = null;

PGPPublicKeyRingCollection publicKeyRingCollection;
try (InputStream publicKeyRingInputStream = new ByteArrayInputStream(armoredKeyString.getBytes())) {
publicKeyRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(publicKeyRingInputStream));
}

Iterator<PGPPublicKeyRing> publicKeyRingIterator = publicKeyRingCollection.getKeyRings();

while (publicKey == null && publicKeyRingIterator.hasNext()) {
PGPPublicKeyRing publicKeyRing = publicKeyRingIterator.next();
Iterator<PGPPublicKey> publicKeyIterator = publicKeyRing.getPublicKeys();

while (publicKey == null && publicKeyIterator.hasNext()) {
PGPPublicKey currentPublicKey = publicKeyIterator.next();
String keyFingerprint = new String(Hex.encode(currentPublicKey.getFingerprint()));
if (keyFingerprint.equalsIgnoreCase(publicKeyFingerprint)) {
publicKey = currentPublicKey;
}
}
}

return publicKey;
}

public static byte[] generatePGPSignature(String content, String publicKeyFingerprint, String secretKeyPassword)
throws IOException, NoSuchAlgorithmException, NoSuchProviderException, PGPException, SignatureException {
String propPath = System.getenv(„environment.MulePropPath”);
FileInputStream secretKeyRingInputStream= new FileInputStream(propPath + PGP_SECRET_KEY_RING_PATH);

ByteArrayInputStream dataInputStream = new ByteArrayInputStream(content.getBytes());
ByteArrayOutputStream signatureOutputStream = new ByteArrayOutputStream();

PGPSecretKey mySecretKey = readPGPSecretKey(secretKeyRingInputStream, publicKeyFingerprint);
PGPPrivateKey myPrivateKey = mySecretKey.extractPrivateKey(secretKeyPassword.toCharArray(), PGP_PROVIDER);

PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(mySecretKey.getPublicKey().getAlgorithm(), PGPUtil.SHA1, PGP_PROVIDER);
signatureGenerator.initSign(PGPSignature.BINARY_DOCUMENT, myPrivateKey);

BCPGOutputStream bcpgOutputStream = new BCPGOutputStream(signatureOutputStream);

int bufferSize = PGP_BUFFER_SIZE;
byte[] bytes = new byte[bufferSize];
int length;
while ((length = dataInputStream.read(bytes,0,bufferSize)) > 0) {
signatureGenerator.update(bytes,0,length);
}

signatureGenerator.generate().encode(bcpgOutputStream);
signatureOutputStream.close();

return signatureOutputStream.toByteArray();
}

public static boolean verifyPGPSignature(String publicKeyString, String publicKeyFingerprint, String content, byte[] signatureByteArray)
throws Exception {
ByteArrayInputStream dataInputStream = new ByteArrayInputStream(content.getBytes());
ByteArrayInputStream signatureInputStream = new ByteArrayInputStream(signatureByteArray);

PGPObjectFactory factory = new PGPObjectFactory(PGPUtil.getDecoderStream(signatureInputStream));
PGPSignatureList signatureList;

Object object = factory.nextObject();
if (object instanceof PGPCompressedData) {
PGPCompressedData compressedData = (PGPCompressedData) object;
factory = new PGPObjectFactory(compressedData.getDataStream());
signatureList = (PGPSignatureList) factory.nextObject();
}
else {
signatureList = (PGPSignatureList) object;

PGPSignature signature = signatureList.get(0);
PGPPublicKey publicKey = readPGPPublicKey(publicKeyString, publicKeyFingerprint);

signature.initVerify(publicKey, PGP_PROVIDER);

int bufferSize = PGP_BUFFER_SIZE;
byte[] bytes = new byte[bufferSize];
int length;
while ((length = dataInputStream.read(bytes,0,bufferSize)) > 0) {
signature.update(bytes,0,length);
}
return signature.verify();
}
}