diff options
Diffstat (limited to 'eaaf_core_utils/src/main')
| -rw-r--r-- | eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/builder/BpkBuilder.java | 446 | 
1 files changed, 446 insertions, 0 deletions
| diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/builder/BpkBuilder.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/builder/BpkBuilder.java new file mode 100644 index 00000000..867f5cfc --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/builder/BpkBuilder.java @@ -0,0 +1,446 @@ +/* + * Copyright 2014 Federal Chancellery Austria MOA-ID has been developed in a cooperation between + * BRZ, the Federal Chancellery Austria - ICT staff unit, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the European + * Commission - subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: http://www.osor.eu/eupl/ + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + * + * This product combines work with different licenses. See the "NOTICE" text file for details on the + * various modules and licenses. The "NOTICE" text file is part of the distribution. Any derivative + * works that you distribute must include a readable copy of the "NOTICE" text file. +*/ + +package at.gv.egiz.eaaf.core.impl.builder; + +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map.Entry; + +import javax.annotation.Nonnull; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.Base64Utils; + +import at.gv.egiz.eaaf.core.api.data.EaafConstants; +import at.gv.egiz.eaaf.core.exceptions.EaafBuilderException; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import lombok.extern.slf4j.Slf4j; + + +/** + * Builder for the bPK, as defined in + * <code>"Ableitung f¨r die bereichsspezifische Personenkennzeichnung"</code> + * version <code>1.0.1</code> from + * <code>"reference.e-government.gv.at"</code>. + * + */ +@Slf4j +public class BpkBuilder { +   +  private static final String ERROR_CODE_33 = "builder.33"; +     +  private static final String ERROR_MSG_WRONG_TARGET_FORMAT = "bPK-target format must be full URI"; +   +   +  /** +   * Calculates an area specific unique person-identifier from a baseID. +   * +   * @param baseID           baseId from user but never null +   * @param targetIdentifier target identifier for area specific identifier +   *                         calculation but never null +   * @return Pair consists of (unique person identifier for this target, +   *         targetArea) but never null +   * @throws EaafBuilderException if some input data are not valid +   */ +  public static Pair<String, String> generateAreaSpecificPersonIdentifier(final String baseID, +      final String targetIdentifier) throws EaafBuilderException { +    return generateAreaSpecificPersonIdentifier(baseID, EaafConstants.URN_PREFIX_BASEID, +        targetIdentifier); + +  } + +  /** +   * Calculates an area specific unique person-identifier from an unique +   * identifier with a specific type. +   * +   * @param baseID           baseId from user but never null +   * @param baseIdType       Type of the baseID but never null +   * @param targetIdentifier target identifier for area specific identifier +   *                         calculation but never null +   * @return Pair consists of (unique person identifier for this target, +   *         targetArea) but never null +   * @throws EaafBuilderException if some input data are not valid +   */ +  public static Pair<String, String> generateAreaSpecificPersonIdentifier(final String baseID, +      final String baseIdType, final String targetIdentifier) throws EaafBuilderException { +    if (StringUtils.isEmpty(baseID)) { +      throw new EaafBuilderException(ERROR_CODE_33, new Object[] { "baseID is empty or null" }, +          "BaseId is empty or null"); +    } + +    if (StringUtils.isEmpty(baseIdType)) { +      throw new EaafBuilderException(ERROR_CODE_33, +          new Object[] { "the type of baseID is empty or null" }, "Type of baseId is empty or null"); +    } + +    if (StringUtils.isEmpty(targetIdentifier)) { +      throw new EaafBuilderException(ERROR_CODE_33, +          new Object[] { "SP specific target identifier is empty or null" }, +          "SP specific target identifier is empty or null"); +    } + +    if (baseIdType.equals(EaafConstants.URN_PREFIX_BASEID)) { +      log.trace("Find baseID. Starting unique identifier caluclation for this target"); + +      if (targetIdentifier.startsWith(EaafConstants.URN_PREFIX_CDID)) { +        log.trace("Calculate bPK identifier for target: " + targetIdentifier); +        return Pair.newInstance(calculatebPKwbPK(baseID + "+" + targetIdentifier), +            targetIdentifier); + +      } else if (targetIdentifier.startsWith(EaafConstants.URN_PREFIX_WBPK)) { +        log.trace("Calculate  wbPK identifier for target: " + targetIdentifier); +        String commonBpkTarget = normalizeBpkTargetIdentifierToCommonFormat(targetIdentifier); +        return Pair.newInstance(calculatebPKwbPK( +            baseID + "+" + normalizeBpkTargetIdentifierToBpkCalculationFormat(commonBpkTarget)), +            commonBpkTarget); +         +      } else if (targetIdentifier.startsWith(EaafConstants.URN_PREFIX_EIDAS)) { +        log.trace("Calculate eIDAS identifier for target: " + targetIdentifier); +        final String[] splittedTarget = targetIdentifier.split("\\+"); +        final String cititzenCountryCode = splittedTarget[1]; +        final String eidasOutboundCountry = splittedTarget[2]; + +        if (cititzenCountryCode.equalsIgnoreCase(eidasOutboundCountry)) { +          log.warn("Suspect configuration FOUND!!! CitizenCountry equals DestinationCountry"); + +        } +        return buildEidasIdentifer(baseID, baseIdType, cititzenCountryCode, eidasOutboundCountry); + +      } else { +        throw new EaafBuilderException(ERROR_CODE_33, +            new Object[] { "Target identifier: " + targetIdentifier + " is NOT allowed or unknown" }, +            "Target identifier: " + targetIdentifier + " is NOT allowed or unknown"); +      } + +    } else { +      log.trace("BaseID is not of type " + EaafConstants.URN_PREFIX_BASEID +          + ". Check type against requested target ..."); +      if (baseIdType.equals(targetIdentifier)) { +        log.debug("Unique identifier is already area specific. Is nothing todo"); +        return Pair.newInstance(baseID, targetIdentifier); + +      } else { +        log.warn("Get unique identifier for target: " + baseIdType + " but target: " +            + targetIdentifier + " is required!"); +        throw new EaafBuilderException(ERROR_CODE_33, +            new Object[] { "Get unique identifier for target: " + baseIdType + " but target: " +                + targetIdentifier + " is required" }, +            "Get unique identifier for target: " + baseIdType + " but target: " + targetIdentifier +                + " is required"); + +      } +    } +  } + + + +  /** +   * Create an encrypted bPK. +   * +   * @param bpk       unencrypted bPK +   * @param target    bPK target in full form +   * @param publicKey Public-Key used for encryption +   * @return encrypted bPK +   * @throws EaafBuilderException In case of an error +   */ +  public static String encryptBpk(final String bpk, String target, final PublicKey publicKey) +      throws EaafBuilderException { +    final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); +     +    if (!target.startsWith(EaafConstants.URN_PREFIX_WITH_COLON)) { +      throw new EaafBuilderException("builder.32",  +          null, ERROR_MSG_WRONG_TARGET_FORMAT); +       +    } +     +    target = normalizeBpkTargetIdentifierToBpkCalculationFormat( +        normalizeBpkTargetIdentifierToCommonFormat(target)); + +    final String input = +        "V1::" + target + "::" + bpk + "::" + sdf.format(new Date()); +    // System.out.println(input); +    byte[] result; +    try { +      final byte[] inputBytes = input.getBytes("ISO-8859-1"); +      result = encrypt(inputBytes, publicKey); +      return new String(Base64Utils.encode(result), "ISO-8859-1").replaceAll("\r\n", ""); +      // return new String(Base64Utils.encode(result, +      // "ISO-8859-1")).replaceAll("\r\n", ""); + +    } catch (final Exception e) { +      throw new EaafBuilderException("bPK encryption FAILED", null, e.getMessage(), e); + +    } +  } + +  /** +   * Decrypt an encrypted bPK. +   * +   * @param encryptedBpk encrypted bPK +   * @param target       bPK target in full form +   * @param privateKey   private-key for decryption +   * @return bPK Pair consists of (unique person identifier for this target, +   *         targetArea) but never null +   * @throws EaafBuilderException In case of an error +   */ +  public static Pair<String, String> decryptBpk(final String encryptedBpk, String target, +      final PrivateKey privateKey) throws EaafBuilderException { +    String decryptedString; +     +    if (!target.startsWith(EaafConstants.URN_PREFIX_WITH_COLON)) { +      throw new EaafBuilderException("builder.32",  +          null, ERROR_MSG_WRONG_TARGET_FORMAT); +       +    } +     +    try { +      final byte[] encryptedBytes = Base64Utils.decode(encryptedBpk.getBytes("ISO-8859-1")); +      final byte[] decryptedBytes = decrypt(encryptedBytes, privateKey); +      decryptedString = new String(decryptedBytes, "ISO-8859-1"); + +    } catch (final Exception e) { +      throw new EaafBuilderException("bPK decryption FAILED", null, e.getMessage(), e); + +    } + +    String[] parts = decryptedString.split("::"); +    if (parts.length != 4) { +      log.trace("Encrypted bPK has value: {}", decryptedString); +      throw new EaafBuilderException("builder.31", new Object[] {parts.length},  +          "encBpk has a suspect format"); +       +    } +     +    final String sector = parts[1]; +    final String bPK = parts[2]; + +    if (target.equals(normalizeBpkTargetIdentifierToCommonFormat(sector))) { +      return Pair.newInstance(bPK, target); +       +    } else { +      throw new EaafBuilderException("builder.30", new Object[] {sector, target},  +          "Decrypted bPK-target does not match"); +       +    } +  } + +  /** +   * Normalize wbPK target identifier for FN, ZVR, and ERSB to XFN, XZVR, and XERSB. +   *  +   * <p>If the target is not of this types the target will be returned as it is</p> +   * @param targetIdentifier bPK input target +   * @return XFN, XZVR, XERSB, or targetIdentfier if no normalization is required  +   */ +  @Nullable +  public static String normalizeBpkTargetIdentifierToCommonFormat(@Nullable String targetIdentifier) { +    if (targetIdentifier != null  +        && !targetIdentifier.startsWith(EaafConstants.URN_PREFIX_WBPK_TARGET_WITH_X)) { +      for (Entry<String, String> mapper : EaafConstants.URN_WBPK_TARGET_X_TO_NONE_MAPPER.entrySet()) { +        if (targetIdentifier.startsWith(mapper.getValue())) { +          String wbpkTarget = mapper.getKey() + targetIdentifier.substring(mapper.getValue().length());  +          log.trace("Normalize wbPK target: {} to {}", targetIdentifier, wbpkTarget); +          return wbpkTarget; +         +        } +      } +    }  +     +    return targetIdentifier; +  } +   +  /** +   * Normalize wbPK target identifier for XFN, XZVR, and XERSB to bPK non-X format like, FN, ZVR, and ERSB. +   *  +   * <p>If the target is not of this types the target will be returned as it is</p> +   *  +   * @param targetIdentifier bPK input target +   * @return FN, ZVR, ERSB, or targetIdentfier if no normalization is required  +   */ +  @Nullable +  public static String normalizeBpkTargetIdentifierToNonXFormat(@Nullable String targetIdentifier) { +    if (targetIdentifier != null && targetIdentifier.startsWith(EaafConstants.URN_PREFIX_WBPK)) { +      for (Entry<String, String> mapper : EaafConstants.URN_WBPK_TARGET_X_TO_NONE_MAPPER.entrySet()) { +        if (targetIdentifier.startsWith(mapper.getKey())) { +          String wbpkTarget = mapper.getValue() + targetIdentifier.substring(mapper.getKey().length());  +          log.trace("Find new wbPK target: {}. Replace it by: {}", targetIdentifier, wbpkTarget); +          return wbpkTarget; +       +        } +      } +    } +     +    return targetIdentifier;     +  } +   +  /** +   * Normalize wbPK target identifier for XFN, XZVR, and XERSB to bPK calculation format like, FN, VR, and ERJ. +   *  +   * <p>If the target is not of this types the target will be returned as it is</p> +   *  +   * @param targetIdentifier bPK input target +   * @return FN, VR, ERJ, or targetIdentfier if no normalization is required  +   */ +  @Nullable +  public static String normalizeBpkTargetIdentifierToBpkCalculationFormat(@Nullable String targetIdentifier) { +    if (targetIdentifier != null && targetIdentifier.startsWith(EaafConstants.URN_PREFIX_WBPK)) { +      for (Entry<String, String> mapper : EaafConstants.URN_WBPK_TARGET_X_TO_CALC_TARGET_MAPPER.entrySet()) { +        if (targetIdentifier.startsWith(mapper.getKey())) { +          String wbpkTarget = mapper.getValue() + targetIdentifier.substring(mapper.getKey().length());  +          log.trace("Find new wbPK target: {}. Replace it by: {}", targetIdentifier, wbpkTarget); +          return wbpkTarget; +       +        } +      } +    } +     +    return targetIdentifier;     +  } +   +  /** +   * Remove prefixes from bPK target identifier and get only the SP specific part. +   *  +   * @param type full qualified bPK target with 'urn:publicid:gv.at:' prefix +   * @return SP specific part, or full type if reduction is not supported  +   */ +  @Nonnull +  public static String removeBpkTypePrefix(@Nonnull final String type) { +    Assert.isTrue(type != null, "bPKType is 'NULL'"); +    if (type.startsWith(EaafConstants.URN_PREFIX_WBPK)) { +      return type.substring(EaafConstants.URN_PREFIX_WBPK.length()); +       +    } else if (type.startsWith(EaafConstants.URN_PREFIX_CDID)) { +      return type.substring(EaafConstants.URN_PREFIX_CDID.length()); +       +    } else if (type.startsWith(EaafConstants.URN_PREFIX_EIDAS)) { +      return type.substring(EaafConstants.URN_PREFIX_EIDAS.length()); +       +    } else { +      return type; +       +    } +  } +   +  /** +   * Builds the eIDAS from the given parameters. +   * +   * @param baseId             baseID of the citizen +   * @param baseIdType         Type of the baseID +   * @param sourceCountry      CountryCode of that country, which build the eIDAs +   *                           ID +   * @param destinationCountry CountryCode of that country, which receives the +   *                           eIDAs ID +   * +   * @return Pair eIDAs/bPKType in a BASE64 encoding +   * @throws EaafBuilderException if some input data are not valid +   */ +  private static Pair<String, String> buildEidasIdentifer(final String baseId, +      final String baseIdType, final String sourceCountry, final String destinationCountry) +      throws EaafBuilderException { +    String bpk = null; +    String bpkType = null; + +    // check if we have been called by public sector application +    if (baseIdType.startsWith(EaafConstants.URN_PREFIX_BASEID)) { +      bpkType = EaafConstants.URN_PREFIX_EIDAS + sourceCountry + "+" + destinationCountry; +      log.debug("Building eIDAS identification from: [identValue]+" + bpkType); +      bpk = calculatebPKwbPK(baseId + "+" + bpkType); + +    } else { // if not, sector identification value is already calculated by BKU +      log.debug("eIDAS eIdentifier already provided by BKU"); +      bpk = baseId; +    } + +    if (StringUtils.isEmpty(bpk) || StringUtils.isEmpty(sourceCountry) +        || StringUtils.isEmpty(destinationCountry)) { +      throw new EaafBuilderException("builder.00", +          new Object[] { "eIDAS-ID", +              "Unvollständige Parameterangaben: identificationValue=" + bpk + ", Zielland=" +                  + destinationCountry + ", Ursprungsland=" + sourceCountry }, +          "eIDAS-ID: Unvollständige Parameterangaben: identificationValue=" + bpk + ", Zielland=" +              + destinationCountry + ", Ursprungsland=" + sourceCountry); +    } + +    log.trace("eIDAS pseudonym generation finished. "); +    final String eIdentifier = sourceCountry + "/" + destinationCountry + "/" + bpk; + +    return Pair.newInstance(eIdentifier, bpkType); +  } +   +  private static String calculatebPKwbPK(final String basisbegriff) throws EaafBuilderException { +    try { +      final MessageDigest md = MessageDigest.getInstance("SHA-1"); +      final byte[] hash = md.digest(basisbegriff.getBytes("ISO-8859-1")); +      final String hashBase64 = +          new String(Base64Utils.encode(hash), "ISO-8859-1").replaceAll("\r\n", ""); // Base64Utils.encode(hash); +      return hashBase64; + +    } catch (final Exception ex) { +      throw new EaafBuilderException(ERROR_CODE_33, new Object[] {ex.toString() }, +          ex.getMessage(), ex); + +    } + +  } + +  private static byte[] encrypt(final byte[] inputBytes, final PublicKey publicKey) +      throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, +      IllegalBlockSizeException, BadPaddingException { +    byte[] result; +    Cipher cipher = null; +    try { +      cipher = Cipher.getInstance("RSA/ECB/OAEPPadding"); // try with bouncycastle + +    } catch (final NoSuchAlgorithmException e) { +      cipher = Cipher.getInstance("RSA/ECB/OAEP"); // try with iaik provider +    } +    cipher.init(Cipher.ENCRYPT_MODE, publicKey); +    result = cipher.doFinal(inputBytes); + +    return result; +  } + +  private static byte[] decrypt(final byte[] encryptedBytes, final PrivateKey privateKey) +      throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, +      IllegalBlockSizeException, BadPaddingException { +    byte[] result; +    Cipher cipher = null; +    try { +      cipher = Cipher.getInstance("RSA/ECB/OAEPPadding"); // try with bouncycastle + +    } catch (final NoSuchAlgorithmException e) { +      cipher = Cipher.getInstance("RSA/ECB/OAEP"); // try with iaik provider + +    } +    cipher.init(Cipher.DECRYPT_MODE, privateKey); +    result = cipher.doFinal(encryptedBytes); +    return result; + +  } +} | 
