All Downloads are FREE. Search and download functionalities are using the official Maven repository.

oracle.nosql.driver.iam.Utils Maven / Gradle / Ivy

There is a newer version: 5.4.16
Show newest version
/*-
 * Copyright (c) 2011, 2020 Oracle and/or its affiliates.  All rights reserved.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 *  https://oss.oracle.com/licenses/upl/
 */

package oracle.nosql.driver.iam;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAPublicKeySpec;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import oracle.nosql.driver.Region;
import oracle.nosql.driver.iam.CertificateSupplier.X509CertificateKeyPair;
import oracle.nosql.driver.util.CheckNull;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;

/**
 * @hidden
 * Internal use only
 */
class Utils {
    private static final JsonFactory factory = new JsonFactory();
    /* Signing algorithm only rsa-sha256 is allowed */
    static final String RSA = "rsa-sha256";
    static final String RSA_JVM_NAME = "SHA256withRSA";

    /* OCI signature version only version 1 is allowed*/
    static final int SINGATURE_VERSION = 1;

    /* Constants used to build signature */
    static final String HEADER_DELIMITER = ": ";
    static String SIGNATURE_HEADER_FORMAT =
        "Signature headers=\"%s\",keyId=\"%s\",algorithm=\"%s\"," +
        "signature=\"%s\",version=\"%s\"";
    static final String DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss z";

    /*
     * ... (.future-extensibility)
     * .
     * pattern is relaxed other than the required  and
     * 
     */
    private static final Pattern OCID_PATTERN = Pattern.compile(
        "^([0-9a-zA-Z-_]+[.:])([0-9a-zA-Z-_]*[.:]){3,}([0-9a-zA-Z-_]+)$");

    /* HEX chars used to compute certificate fingerprint */
    private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();

    /* 4k bytes */
    private static final int BUF_SIZE = 0x800;

    /* Map used to lookup IAM URI */
    private static final Map IAM_URI = new HashMap<>();
    private final static MessageFormat OC1_EP_BASE = new MessageFormat(
        "https://auth.{0}.oraclecloud.com");
    private final static MessageFormat GOV_EP_BASE = new MessageFormat(
        "https://auth.{0}.oraclegovcloud.com");
    private final static MessageFormat OC4_EP_BASE = new MessageFormat(
        "https://auth.{0}.oraclegovcloud.uk");
    private final static MessageFormat OC8_EP_BASE = new MessageFormat(
        "https://auth.{0}.oraclecloud8.com");

    static {
        /* OC1 */
        IAM_URI.put("bom", OC1_EP_BASE.format(new Object[] {"ap-mumbai-1"}));
        IAM_URI.put("icn", OC1_EP_BASE.format(new Object[] {"ap-seoul-1"}));
        IAM_URI.put("syd", OC1_EP_BASE.format(new Object[] {"ap-sydney-1"}));
        IAM_URI.put("nrt", OC1_EP_BASE.format(new Object[] {"ap-tokyo-1"}));
        IAM_URI.put("mel", OC1_EP_BASE.format(new Object[] {"ap-melbourne-1"}));
        IAM_URI.put("kix", OC1_EP_BASE.format(new Object[] {"ap-osaka-1"}));
        IAM_URI.put("hyd", OC1_EP_BASE.format(new Object[] {"ap-hyderabad-1"}));
        IAM_URI.put("yny", OC1_EP_BASE.format(new Object[] {"ap-chuncheon-1"}));

        IAM_URI.put("fra", OC1_EP_BASE.format(new Object[] {"eu-frankfurt-1"}));
        IAM_URI.put("zrh", OC1_EP_BASE.format(new Object[] {"eu-zurich-1"}));
        IAM_URI.put("lhr", OC1_EP_BASE.format(new Object[] {"uk-london-1"}));
        IAM_URI.put("ams", OC1_EP_BASE.format(new Object[] {"eu-amsterdam-1"}));
        IAM_URI.put("jed", OC1_EP_BASE.format(new Object[] {"me-jeddah-1"}));
        IAM_URI.put("cwl", OC1_EP_BASE.format(new Object[] {"uk-cardiff-1"}));
        IAM_URI.put("dxb", OC1_EP_BASE.format(new Object[] {"me-dubai-1"}));

        IAM_URI.put("gru", OC1_EP_BASE.format(new Object[] {"sa-saopaulo-1"}));
        IAM_URI.put("scl", OC1_EP_BASE.format(new Object[] {"sa-santiago-1"}));

        IAM_URI.put("phx", OC1_EP_BASE.format(new Object[] {"us-phoenix-1"}));
        IAM_URI.put("iad", OC1_EP_BASE.format(new Object[] {"us-ashburn-1"}));
        IAM_URI.put("sjc", OC1_EP_BASE.format(new Object[] {"us-sanjose-1"}));
        IAM_URI.put("yyz", OC1_EP_BASE.format(new Object[] {"ca-toronto-1"}));
        IAM_URI.put("yul", OC1_EP_BASE.format(new Object[] {"ca-montreal-1"}));

        /* OC2 */
        IAM_URI.put("lfi", GOV_EP_BASE.format(new Object[] {"us-langley-1"}));
        IAM_URI.put("luf", GOV_EP_BASE.format(new Object[] {"us-luke-1"}));

        /* OC3 */
        IAM_URI.put("ric", GOV_EP_BASE.format(new Object[] {"us-gov-ashburn-1"}));
        IAM_URI.put("pia", GOV_EP_BASE.format(new Object[] {"us-gov-chicago-1"}));
        IAM_URI.put("tus", GOV_EP_BASE.format(new Object[] {"us-gov-phoenix-1"}));

        /* OC4 */
        IAM_URI.put("ltn", OC4_EP_BASE.format(new Object[] {"uk-gov-london-1"}));

        /* OC8 */
        IAM_URI.put("nja", OC8_EP_BASE.format(new Object[] {"ap-chiyoda-1"}));
    }

    static String getIAMURL(String regionIdOrCode) {
        String uri = IAM_URI.get(regionIdOrCode);
        if (uri == null) {
            if (Region.isOC1Region(regionIdOrCode)) {
                return OC1_EP_BASE.format(new Object[] {regionIdOrCode});
            }
            if (Region.isGovRegion(regionIdOrCode)) {
                return GOV_EP_BASE.format(new Object[] {regionIdOrCode});
            }
            if (Region.isOC4Region(regionIdOrCode)) {
                return OC4_EP_BASE.format(new Object[] {regionIdOrCode});
            }
            if (Region.isOC8Region(regionIdOrCode)) {
                return OC8_EP_BASE.format(new Object[] {regionIdOrCode});
            }
        }

        return uri;
    }

    /**
     * Test if the given OCID matches the expected pattern for OCIDs.
     *
     * @param ocid The string to test.
     * @return true if it matches teh pattern, false if not.
     */
    static boolean isValidOcid(String ocid) {
        return OCID_PATTERN.matcher(ocid).matches();
    }

    /**
     * Creates a keyId from the individual components.
     * @param tenantId
     * @param userId
     * @param fingerprint
     * @return The keyId used to sign requests
     */
    static String createKeyId(String tenantId,
                              String userId,
                              String fingerprint) {
        return String.format("%s/%s/%s", tenantId, userId, fingerprint);
    }

    /**
     * Creates a keyId from an {@link AuthenticationDetailsProvider}.
     *
     * @param provider
     * @return The keyId used to sign requests
     */
    static String createKeyId(UserAuthenticationProfileProvider prov) {
        return createKeyId(prov.getTenantId(),
                           prov.getUserId(),
                           prov.getFingerprint());
    }

    /**
     * Attempts to expand paths that may contain unix-style home shorthand.
     */
    static String expandUserHome(final String path) {
        /* If the home (~) shortcut is used, then attempt to determine correct
         * path. Otherwise, leave as is to allow users to always be able to
         * specify a path without modifying it.
         */
        if (path.startsWith("~/") || path.startsWith("~\\")) {
            return System.getProperty("user.home") +
                   correctPath(isWindows(), path.substring(1));
        }
        return path;
    }

    private static boolean isWindows() {
        String os = System.getProperty("os.name");
        return (os.indexOf("Windows") != -1);
    }

    /*
     * Handle the case where somebody is copying the config file
     * between platforms (or copying examples without changing values)
     */
    private static String correctPath(boolean isWindows, String path) {
        if (isWindows) {
            /* https://msdn.microsoft.com/en-us/library/aa365247
             * forward slash is reserved, assume its not supposed to
             * be there and replace with back slash
             */
            path = path.replace('/', '\\');
        }
        /*
         * back slash is not a reserved character on other platforms,
         * so do not attempt to modify it
         */
        return path;
    }

    static SimpleDateFormat createFormatter() {
        SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT,
                                                           Locale.US);
        dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
        return dateFormat;
    }

    static String sign(String signingContent, PrivateKey key)
        throws Exception {

        Signature signature = Signature.getInstance(RSA_JVM_NAME);
        signature.initSign(key);
        signature.update(signingContent.getBytes(StandardCharsets.UTF_8));
        byte[] bytes = signature.sign();
        return new String(Base64.getEncoder().encode(bytes),
                          StandardCharsets.UTF_8);
    }

    /**
     * Converts a private key back to a PEM formatted input stream.
     * @param key The key to convert.
     * @return A new input stream
     */
    static byte[] toByteArray(RSAPrivateKey key) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try (JcaPEMWriter writer = new JcaPEMWriter(
                 new OutputStreamWriter(baos, StandardCharsets.UTF_8))) {

            writer.writeObject(key);
            writer.flush();
        } catch (IOException e) {
            throw new IllegalStateException("Unable to write PEM object", e);
        }

        return baos.toByteArray();
    }

    /**
     * Base64 encodes a public key with no chunking.
     * @param publicKey The public key
     * @return Base64 representation
     */
    static String base64EncodeNoChunking(RSAPublicKey publicKey) {
        return new String(
            Base64.getMimeEncoder().encode(publicKey.getEncoded()),
            StandardCharsets.UTF_8);
    }

   /**
    * Base64 encodes a X509Certificate with no chunking.
    * @param certificate The certificate
    * @return Base64 representation
    */
    static String base64EncodeNoChunking(X509CertificateKeyPair keyPair) {

        return new String(
            Base64.getMimeEncoder().encode(
                getEncodedCertificate(keyPair.getRawCertificate())),
                StandardCharsets.UTF_8);
    }

    /**
     * Gets the fingerprint of a certificate using Sha1.
     * @param certificate the certificate
     * @return Fingerprint of the certificate
     * @throws Error if there is an error
     */
    static String getFingerPrint(X509CertificateKeyPair keyPair) {
        CheckNull.requireNonNull(keyPair.getRawCertificate(),
                                 "Unable to get certificate finger print, " +
                                 "raw certificate is null");

        try {
            String pemCert = keyPair.getRawCertificate();
            byte[] encodedCertificate = getEncodedCertificate(pemCert);
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            md.update(encodedCertificate);
            String fingerprint = getHex(md.digest());

            return formatFingerPrint(fingerprint);
        } catch (NoSuchAlgorithmException  e) {
            throw new IllegalStateException(
                "Unknown error getting certificate fingerprint", e);
        }
    }

    private static byte[] getEncodedCertificate(String pemCertificate) {
        /* strip out header and footer */
        return Base64.getMimeDecoder().decode(pemCertificate
                   .replace("-----BEGIN CERTIFICATE-----", "")
                   .replace("-----END CERTIFICATE-----", ""));
    }

    private static String formatFingerPrint(String fingerprint) {
        int length = fingerprint.length();
        char[] format = new char[length * 3 / 2 - 1];

        int j = 0;
        for (int i = 0; i < length - 2; i += 2) {
            format[j++] = fingerprint.charAt(i);
            format[j++] = fingerprint.charAt(i + 1);
            format[j++] = ':';
        }
        format[j++] = fingerprint.charAt(length - 2);
        format[j] = fingerprint.charAt(length - 1);

        return String.valueOf(format);
    }

    /**
     * Computes the hex representation of a byte array.
     */
    private static String getHex(byte bytes[]) {
        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = HEX_ARRAY[v >>> 4];
            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
        }

        return new String(hexChars);
    }

    /**
     * Get the tenant id from the given X509 certificate.
     * @param certificate the given certificate.
     * @return the tenant id.
     */
    static String getTenantId(X509Certificate certificate) {
        CheckNull.requireNonNull(certificate,
                                 "Unable to get tenant id, certificate is null");

        X500Name name = new X500Name(
            certificate.getSubjectX500Principal().getName());

        String tenantId = getValue(name, BCStyle.OU, "opc-tenant");
        if (tenantId == null) {
            tenantId = getValue(name, BCStyle.O, "opc-identity");
        }

        if (tenantId != null) {
            return tenantId;
        }
        throw new IllegalStateException(
            "The certificate used by instance principal " +
            "does not contain tenant id.");
    }

    private static String getValue(X500Name name,
                                   ASN1ObjectIdentifier id,
                                   String key) {
        String prefix = key + ":";
        for (RDN rdn : name.getRDNs(id)) {
            for (AttributeTypeAndValue typeAndValue : rdn.getTypesAndValues()) {
                String value = typeAndValue.getValue().toString();

                if (value.startsWith(prefix)) {
                    return value.substring(prefix.length());
                }
            }
        }
        return null;
    }

    static String readStream(InputStream inputStream)
        throws IOException {

        InputStreamReader reader = new InputStreamReader(inputStream,
                                                         StandardCharsets.UTF_8);
        StringBuilder sb = new StringBuilder();
        char[] buf = new char[BUF_SIZE];

        int read;
        while ((read = reader.read(buf)) != -1) {
            sb.append(buf, 0, read);
        }
        return sb.toString();
    }

    /**
     * Convert modulus and exponent from JWK to a RSAPublicKey.
     */
    static RSAPublicKey toPublicKey(String modulus, String exponent) {
        try {
            /* modulus and exponent are unsigned, negative big integer should
             * be converted to positive
             */
            Decoder decoder = Base64.getUrlDecoder();
            RSAPublicKey key = (RSAPublicKey) KeyFactory.getInstance("RSA")
                .generatePublic(new RSAPublicKeySpec(
                new BigInteger(1, decoder.decode(modulus.getBytes())),
                new BigInteger(1, decoder.decode(exponent.getBytes()))));
            return key;
        } catch (Exception ex) {
            throw new IllegalStateException(
                "Error build public key from JWK", ex);
        }
    }

    static JsonParser createParser(String json)
        throws IOException {

        return factory.createParser(json);
    }

    static JsonGenerator createGenerator(StringWriter sw)
        throws IOException {

        return factory.createGenerator(sw);
    }

    static String findField(String json, JsonParser parser, String... name)
        throws IOException {

        List fieldsList = Arrays.asList(name);
        JsonToken token;
        while ((token = parser.nextToken()) != null) {
            if (token == JsonToken.START_OBJECT ||
                token == JsonToken.END_OBJECT ||
                token == JsonToken.VALUE_STRING) {
                continue;
            }

            String fieldName = parser.getCurrentName();
            if (fieldName == null) {
                throw new IllegalStateException(
                    "Null token or field name found in JSON " + json);
            }
            if (token == JsonToken.FIELD_NAME) {
                if (fieldsList.stream().anyMatch(
                    str -> str.trim().equals(fieldName))) {
                    return fieldName;
                }
                token = parser.nextToken();
                if (token == JsonToken.START_ARRAY) {
                    while (true) {
                        if (parser.nextToken() == JsonToken.END_ARRAY) {
                            break;
                        }
                    }
                    continue;
                }
            }
        }
        return null;
    }

    static void logTrace(Logger logger, String message) {
        if (logger != null) {
            logger.log(Level.FINE, message);
        }
    }

    static void logError(Logger logger, String message) {
        if (logger != null) {
            logger.log(Level.WARNING, message);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy