text:
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.microsoft.azure.keyvault.test;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import com.microsoft.azure.management.resources.fluentcore.utils.SdkContext;
import org.apache.commons.codec.binary.Base64;
import org.joda.time.DateTime;
import org.junit.Assert;
import org.junit.Test;

import com.microsoft.azure.PagedList;
import com.microsoft.azure.keyvault.CertificateIdentifier;
import com.microsoft.azure.keyvault.KeyIdentifier;
import com.microsoft.azure.keyvault.SecretIdentifier;
import com.microsoft.azure.keyvault.models.AdministratorDetails;
import com.microsoft.azure.keyvault.models.Attributes;
import com.microsoft.azure.keyvault.models.CertificateAttributes;
import com.microsoft.azure.keyvault.models.CertificateBundle;
import com.microsoft.azure.keyvault.models.Contact;
import com.microsoft.azure.keyvault.models.Contacts;
import com.microsoft.azure.keyvault.models.IssuerBundle;
import com.microsoft.azure.keyvault.models.IssuerCredentials;
import com.microsoft.azure.keyvault.models.IssuerParameters;
import com.microsoft.azure.keyvault.models.KeyBundle;
import com.microsoft.azure.keyvault.models.KeyVaultErrorException;
import com.microsoft.azure.keyvault.models.OrganizationDetails;
import com.microsoft.azure.keyvault.models.CertificateItem;
import com.microsoft.azure.keyvault.models.CertificateOperation;
import com.microsoft.azure.keyvault.models.CertificatePolicy;
import com.microsoft.azure.keyvault.models.SecretBundle;
import com.microsoft.azure.keyvault.models.SecretProperties;
import com.microsoft.azure.keyvault.models.X509CertificateProperties;
import com.microsoft.azure.keyvault.requests.CreateCertificateRequest;
import com.microsoft.azure.keyvault.requests.ImportCertificateRequest;
import com.microsoft.azure.keyvault.requests.SetCertificateIssuerRequest;
import com.microsoft.azure.keyvault.requests.UpdateCertificateIssuerRequest;
import com.microsoft.azure.keyvault.requests.UpdateCertificateOperationRequest;
import com.microsoft.azure.keyvault.requests.UpdateCertificatePolicyRequest;
import com.microsoft.azure.keyvault.requests.UpdateCertificateRequest;

public class CertificateOperationsTest extends KeyVaultClientIntegrationTestBase {

    static final String ALGO_RSA = "RSA";

    static final String X509 = "X.509";

    static final String PKCS12 = "PKCS12";

    static final String MIME_PKCS12 = "application/x-pkcs12";

    static final String MIME_PEM = "application/x-pem-file";

    static final String ISSUER_SELF = "Self";

    static final String ISSUER_TEST = "Test";

    static final String ISSUER_UNKNOWN = "Unknown";

    static final String STATUS_IN_PROGRESS = "inProgress";

    static final String STATUS_COMPLETED = "Completed";

    static final Base64 BASE_64 = new Base64(-1, null, true);

    static final Pattern PRIVATE_KEY = Pattern.compile("-{5}BEGIN PRIVATE KEY-{5}(?:\\s|\\r|\\n)+"
            + "([a-zA-Z0-9+/=\r\n]+)" + "-{5}END PRIVATE KEY-{5}(?:\\s|\\r|\\n)+");

    static final Pattern CERTIFICATE = Pattern.compile("-{5}BEGIN CERTIFICATE-{5}(?:\\s|\\r|\\n)+"
            + "([a-zA-Z0-9+/=\r\n]+)" + "-{5}END CERTIFICATE-{5}(?:\\s|\\r|\\n)+");

    private static final int MAX_CERTS = 4;
    private static final int PAGELIST_MAX_CERTS = 3;

    private static final Map<String, String> TAGS = new HashMap<String, String>();

    /**
     * Create a self-signed certificate in PKCS12 format (which includes the
     * private key) certificate.
     *
     * @throws Exception
     */
    @Test
    public void createSelfSignedCertificatePkcs12ForCertificateOperationsTest() throws Exception {
        // Set content type to indicate the certificate is PKCS12 format.
        SecretProperties secretProperties = new SecretProperties()
                                        .withContentType(MIME_PKCS12);

        String subjectName = "CN=SelfSignedJavaPkcs12";
        X509CertificateProperties x509Properties = new X509CertificateProperties()
                    .withSubject(subjectName)
                    .withValidityInMonths(12);

        // Set issuer to "Self"
        IssuerParameters issuerParameters = new IssuerParameters()
                    .withName(ISSUER_SELF);

        CertificatePolicy certificatePolicy = new CertificatePolicy()
                    .withSecretProperties(secretProperties)
                    .withIssuerParameters(issuerParameters)
                    .withX509CertificateProperties(x509Properties);

        Attributes attribute = new CertificateAttributes()
                .withEnabled(true)
                .withExpires(new DateTime().withYear(2050).withMonthOfYear(1))
                .withNotBefore(new DateTime().withYear(2000).withMonthOfYear(1));

        String vaultUri = getVaultUri();
        String certificateName = "createSelfSignedJavaPkcs12";

        CreateCertificateRequest createCertificateRequest =
                new CreateCertificateRequest
                    .Builder(vaultUri, certificateName)
                        .withPolicy(certificatePolicy)
                        .withAttributes(attribute)
                        .withTags(TAGS)
                        .build();

        CertificateOperation certificateOperation = keyVaultClient.createCertificate(createCertificateRequest);

        Assert.assertNotNull(certificateOperation);
        Assert.assertTrue(certificateOperation.status().equalsIgnoreCase(STATUS_IN_PROGRESS));

        CertificateBundle certificateBundle = pollOnCertificateOperation(certificateOperation);
        validateCertificateBundle(certificateBundle, certificatePolicy);
        compareAttributes(attribute, createCertificateRequest.certificateAttributes());

        // Load the CER part into X509Certificate object
        X509Certificate x509Certificate = loadCerToX509Certificate(certificateBundle);

        Assert.assertTrue(x509Certificate.getSubjectX500Principal().getName().equals(subjectName));
        Assert.assertTrue(x509Certificate.getIssuerX500Principal().getName().equals(subjectName));

        // Retrieve the secret backing the certificate
        SecretIdentifier secretIdentifier = certificateBundle.secretIdentifier();
        SecretBundle secret = keyVaultClient.getSecret(secretIdentifier.baseIdentifier());
        Assert.assertTrue(secret.managed());

        // Retrieve the key backing the certificate
        KeyIdentifier keyIdentifier = certificateBundle.keyIdentifier();
        KeyBundle keyBundle = keyVaultClient.getKey(keyIdentifier.baseIdentifier());
        Assert.assertTrue(keyBundle.managed());

        // Load the secret into a KeyStore
        String secretPassword = "";
        KeyStore keyStore = loadSecretToKeyStore(secret, secretPassword);

        // Validate the certificate and key in the KeyStore
        validateCertificateKeyInKeyStore(keyStore, x509Certificate, secretPassword);

        CertificateBundle deletedCertificateBundle = keyVaultClient.deleteCertificate(getVaultUri(), certificateName);
        Assert.assertNotNull(deletedCertificateBundle);

        pollOnCertificateDeletion(getVaultUri(), certificateName);
        try {
            keyVaultClient.getCertificate(deletedCertificateBundle.certificateIdentifier().baseIdentifier());
        } catch (KeyVaultErrorException e) {
            Assert.assertNotNull(e.body().error());
            Assert.assertEquals("CertificateNotFound", e.body().error().code());
        }

        keyVaultClient.purgeDeletedCertificate(getVaultUri(), certificateName);
        SdkContext.sleep(20000);
    }

    /**
     * Create a self-signed certificate in PEM format (which includes the
     * private key) certificate.
     *
     * @throws Exception
     */
    @Test
    public void createSelfSignedCertificatePemForCertificateOperationsTest() throws Exception {
        // Set content type to indicate the certificate is PKCS12 format.
        SecretProperties secretProperties = new SecretProperties()
                    .withContentType(MIME_PEM);

        String subjectName = "CN=SelfSignedJavaPem";
        X509CertificateProperties x509Properties = new X509CertificateProperties()
                    .withSubject(subjectName)
                    .withValidityInMonths(12);

        // Set issuer to "Self"
        IssuerParameters issuerParameters = new IssuerParameters()
                    .withName(ISSUER_SELF);

        CertificatePolicy certificatePolicy = new CertificatePolicy()
                    .withSecretProperties(secretProperties)
                    .withIssuerParameters(issuerParameters)
                    .withX509CertificateProperties(x509Properties);

        String vaultUri = getVaultUri();
        String certificateName = "SelfSignedJavaPem2";
        CertificateOperation certificateOperation = keyVaultClient.createCertificate(
                new CreateCertificateRequest
                    .Builder(vaultUri, certificateName)
                    .withPolicy(certificatePolicy)
                    .build());

        Assert.assertNotNull(certificateOperation);
        Assert.assertTrue(certificateOperation.status().equalsIgnoreCase(STATUS_IN_PROGRESS));

        CertificateBundle certificateBundle = pollOnCertificateOperation(certificateOperation);
        validateCertificateBundle(certificateBundle, certificatePolicy);

        validatePem(certificateBundle, subjectName);

        CertificateBundle deletedCertificateBundle = keyVaultClient.deleteCertificate(getVaultUri(), certificateName);
        Assert.assertNotNull(deletedCertificateBundle);

        try {
            keyVaultClient.getCertificate(deletedCertificateBundle.certificateIdentifier().baseIdentifier());
        } catch (KeyVaultErrorException e) {
            Assert.assertNotNull(e.body().error());
            Assert.assertEquals("CertificateNotFound", e.body().error().code());
        }

        pollOnCertificateDeletion(getVaultUri(), certificateName);

        keyVaultClient.purgeDeletedCertificate(getVaultUri(), certificateName);
        SdkContext.sleep(20000);
    }

    /**
     * Create a test-issuer issued certificate in PKCS12 format (which includes
     * the private key) certificate.
     *
     * @throws Exception
     */
    @Test
    public void createCertificatePkcs12ForCertificateOperationsTest() throws Exception {
        // Construct organization administrator details
        AdministratorDetails administratorDetails = new AdministratorDetails()
                    .withFirstName("John")
                    .withLastName("Doe")
                    .withEmailAddress("john.doe@contoso.com")
                    .withPhone("fakePhoneNumberPlaceholder");

        // Construct organization details
        List<AdministratorDetails> administratorsDetails = new ArrayList<AdministratorDetails>();
        administratorsDetails.add(administratorDetails);
        OrganizationDetails organizationDetails = new OrganizationDetails()
                    .withAdminDetails(administratorsDetails);

        // Construct certificate issuer credentials
        IssuerCredentials credentials = new IssuerCredentials()
                    .withAccountId("account1")
                    .withPassword("Pa$$w0rd");

        String certificateIssuerName = "createCertificateJavaPkcs12Issuer01";
        IssuerBundle createdCertificateIssuer = keyVaultClient.setCertificateIssuer(
                new SetCertificateIssuerRequest
                    .Builder(getVaultUri(), certificateIssuerName, ISSUER_TEST)
                    .withCredentials(credentials)
                    .withOrganizationDetails(organizationDetails)
                    .build());

        validateCertificateIssuer(createdCertificateIssuer, certificateIssuerName);

        // Set content type to indicate the certificate is PKCS12 format.
        SecretProperties secretProperties = new SecretProperties()
                    .withContentType(MIME_PKCS12);

        String subjectName = "CN=TestJavaPkcs12";
        X509CertificateProperties x509Properties = new X509CertificateProperties()
                    .withSubject(subjectName)
                    .withValidityInMonths(12);

        // Set issuer reference to the created issuer
        IssuerParameters issuerParameters = new IssuerParameters();
        issuerParameters.withName(createdCertificateIssuer.issuerIdentifier().name());

        CertificatePolicy certificatePolicy = new CertificatePolicy()
                    .withSecretProperties(secretProperties)
                    .withIssuerParameters(issuerParameters)
                    .withX509CertificateProperties(x509Properties);

        String vaultUri = getVaultUri();
        String certificateName = "createTestJavaPkcs12";
        CertificateOperation certificateOperation = keyVaultClient.createCertificate(
                new CreateCertificateRequest
                    .Builder(vaultUri, certificateName)
                    .withPolicy(certificatePolicy)
                    .build());

        Assert.assertNotNull(certificateOperation);
        Assert.assertTrue(certificateOperation.status().equalsIgnoreCase(STATUS_IN_PROGRESS));

        CertificateBundle certificateBundle = pollOnCertificateOperation(certificateOperation);
        validateCertificateBundle(certificateBundle, certificatePolicy);

        // Load the CER part into X509Certificate object
        X509Certificate x509Certificate = loadCerToX509Certificate(certificateBundle);

        Assert.assertTrue(x509Certificate.getSubjectX500Principal().getName().equals(subjectName));
        Assert.assertTrue(x509Certificate.getIssuerX500Principal().getName().equals(subjectName));

        // Retrieve the secret backing the certificate
        SecretIdentifier secretIdentifier = certificateBundle.secretIdentifier();
        SecretBundle secret = keyVaultClient.getSecret(secretIdentifier.baseIdentifier());
        Assert.assertTrue(secret.managed());

        // Load the secret into a KeyStore
        String secretPassword = "";
        KeyStore keyStore = loadSecretToKeyStore(secret, secretPassword);

        // Validate the certificate and key in the KeyStore
        validateCertificateKeyInKeyStore(keyStore, x509Certificate, secretPassword);

        CertificateBundle deletedCertificateBundle = keyVaultClient.deleteCertificate(getVaultUri(), certificateName);
        Assert.assertNotNull(deletedCertificateBundle);

        try {
            keyVaultClient.getCertificate(deletedCertificateBundle.certificateIdentifier().baseIdentifier());
        } catch (KeyVaultErrorException e) {
            Assert.assertNotNull(e.body().error());
            Assert.assertEquals("CertificateNotFound", e.body().error().code());
        }

        pollOnCertificateDeletion(getVaultUri(), certificateName);

        keyVaultClient.purgeDeletedCertificate(getVaultUri(), certificateName);
        SdkContext.sleep(20000);
    }

    /**
     * Create a test-issuer certificate in PEM format (which includes the
     * private key) certificate.
     *
     * @throws Exception
     */
    @Test
    public void createCertificatePemForCertificateOperationsTest() throws Exception {
        // Construct organization administrator details
        AdministratorDetails administratorDetails = new AdministratorDetails()
                    .withFirstName("John")
                    .withLastName("Doe")
                    .withEmailAddress("john.doe@contoso.com")
                    .withPhone("fakePhoneNumberPlaceholder");

        // Construct organization details
        OrganizationDetails organizationDetails = new OrganizationDetails();
        List<AdministratorDetails> administratorsDetails = new ArrayList<AdministratorDetails>();
        administratorsDetails.add(administratorDetails);
        organizationDetails.withAdminDetails(administratorsDetails);

        // Construct certificate issuer credentials
        IssuerCredentials credentials = new IssuerCredentials()
                    .withAccountId("account1")
                    .withPassword("Pa$$w0rd");

        String certificateIssuerName = "createCertificateJavaPemIssuer01";
        IssuerBundle createdCertificateIssuer = keyVaultClient.setCertificateIssuer(
                new SetCertificateIssuerRequest
                    .Builder(getVaultUri(),    certificateIssuerName, ISSUER_TEST)
                    .withCredentials(credentials)
                    .withOrganizationDetails(organizationDetails)
                    .build());
        validateCertificateIssuer(createdCertificateIssuer, certificateIssuerName);

        // Set content type to indicate the certificate is PEM format.
        SecretProperties secretProperties = new SecretProperties();
        secretProperties.withContentType(MIME_PEM);

        X509CertificateProperties x509Properties = new X509CertificateProperties();
        String subjectName = "CN=TestJavaPem";
        x509Properties.withSubject(subjectName);
        x509Properties.withValidityInMonths(12);

        // Set issuer reference to the created issuer
        IssuerParameters issuerParameters = new IssuerParameters();
        issuerParameters.withName(createdCertificateIssuer.issuerIdentifier().name());

        CertificatePolicy certificatePolicy = new CertificatePolicy()
                    .withSecretProperties(secretProperties)
                    .withIssuerParameters(issuerParameters)
                    .withX509CertificateProperties(x509Properties);

        String vaultUri = getVaultUri();
        String certificateName = "createTestJavaPem";
        CertificateOperation certificateOperation = keyVaultClient.createCertificate(
                new CreateCertificateRequest
                    .Builder(vaultUri, certificateName)
                    .withPolicy(certificatePolicy)
                    .build());

        Assert.assertNotNull(certificateOperation);
        Assert.assertTrue(certificateOperation.status().equalsIgnoreCase(STATUS_IN_PROGRESS));

        CertificateBundle certificateBundle = pollOnCertificateOperation(certificateOperation);
        validateCertificateBundle(certificateBundle, certificatePolicy);

        validatePem(certificateBundle, subjectName);

        CertificateBundle deletedCertificateBundle = keyVaultClient.deleteCertificate(getVaultUri(), certificateName);
        Assert.assertNotNull(deletedCertificateBundle);

        try {
            keyVaultClient.getCertificate(deletedCertificateBundle.certificateIdentifier().baseIdentifier());
        } catch (KeyVaultErrorException e) {
            Assert.assertNotNull(e.body().error());
            Assert.assertEquals("CertificateNotFound", e.body().error().code());
        }

        pollOnCertificateDeletion(getVaultUri(), certificateName);

        keyVaultClient.purgeDeletedCertificate(getVaultUri(), certificateName);
        SdkContext.sleep(20000);
    }

    /**
     * Create a certificate signing request with key in Key Vault.
     * @throws Exception
     */
    @Test
    public void createCsrForCertificateOperationsTest() throws Exception {
        SecretProperties secretProperties = new SecretProperties();
        secretProperties.withContentType(MIME_PKCS12);

        X509CertificateProperties x509Properties = new X509CertificateProperties();
        String subjectName = "CN=ManualEnrollmentJava";
        x509Properties.withSubject(subjectName);
        x509Properties.withValidityInMonths(12);

        // Set issuer to "Unknown"
        IssuerParameters issuerParameters = new IssuerParameters();
        issuerParameters.withName(ISSUER_UNKNOWN);

        CertificatePolicy certificatePolicy = new CertificatePolicy()
                    .withSecretProperties(secretProperties)
                    .withIssuerParameters(issuerParameters)
                    .withX509CertificateProperties(x509Properties);

        String vaultUri = getVaultUri();
        String certificateName = "createManualEnrollmentJava";
        CertificateOperation certificateOperation = keyVaultClient.createCertificate(
                new CreateCertificateRequest
                    .Builder(vaultUri, certificateName)
                    .withPolicy(certificatePolicy)
                    .build());

        Assert.assertNotNull(certificateOperation);
        Assert.assertTrue(certificateOperation.status().equalsIgnoreCase(STATUS_IN_PROGRESS));
        Assert.assertNotNull(certificateOperation.csr());

        String csr = keyVaultClient.getPendingCertificateSigningRequest(vaultUri, certificateName);
        Assert.assertNotNull(csr);

        CertificateBundle deletedCertificateBundle = keyVaultClient.deleteCertificate(getVaultUri(), certificateName);
        Assert.assertNotNull(deletedCertificateBundle);

        pollOnCertificateDeletion(getVaultUri(), certificateName);

        try {
            keyVaultClient.getCertificate(deletedCertificateBundle.certificateIdentifier().baseIdentifier());
        } catch (KeyVaultErrorException e) {
            Assert.assertNotNull(e.body().error());
            Assert.assertEquals("CertificateNotFound", e.body().error().code());
        }

        keyVaultClient.purgeDeletedCertificate(getVaultUri(), certificateName);
        SdkContext.sleep(20000);
    }

    /**
     * Cancel the certificate create asynchronously
     * @throws Exception
     *
     */
    @Test
    public void certificateAsyncRequestCancellationForCertificateOperationsTest() throws Exception {
        // Set content type to indicate the certificate is PKCS12 format.
        SecretProperties secretProperties = new SecretProperties()
                                        .withContentType(MIME_PKCS12);

        String subjectName = "CN=SelfSignedJavaPkcs12";
        X509CertificateProperties x509Properties = new X509CertificateProperties()
                                    .withSubject(subjectName)
                                    .withValidityInMonths(12);

        // Set issuer to "Self"
        IssuerParameters issuerParameters = new IssuerParameters()
                                    .withName(ISSUER_SELF);

        CertificatePolicy certificatePolicy = new CertificatePolicy()
                    .withSecretProperties(secretProperties)
                    .withIssuerParameters(issuerParameters)
                    .withX509CertificateProperties(x509Properties);

        String vaultUri = getVaultUri();
        String certificateName = "cancellationRequestedCertJava";
        keyVaultClient.createCertificate(
                new CreateCertificateRequest
                    .Builder(vaultUri, certificateName)
                    .withPolicy(certificatePolicy)
                    .build());

        CertificateOperation cancelledCertificateOperation = keyVaultClient.updateCertificateOperation(
                new UpdateCertificateOperationRequest
                    .Builder(vaultUri, certificateName, true)
                    .build());

        Assert.assertNotNull(cancelledCertificateOperation);
        Assert.assertTrue(cancelledCertificateOperation.cancellationRequested());

        keyVaultClient.deleteCertificateOperation(getVaultUri(), certificateName);
        keyVaultClient.deleteCertificate(getVaultUri(), certificateName);
        pollOnCertificateDeletion(getVaultUri(), certificateName);
        keyVaultClient.purgeDeletedCertificate(getVaultUri(), certificateName);
        SdkContext.sleep(20000);
    }

    /**
     * Import a PKCS12 format (which includes the private key) certificate.
     */
    @Test
    public void importCertificatePkcs12ForCertificateOperationsTest() throws Exception {
        String certificateContent = readCertificate("pkcs12_base64_testdata.cer");
        String certificatePassword = "fakePasswordPlaceholder";

        // Set content type to indicate the certificate is PKCS12 format.
        SecretProperties secretProperties = new SecretProperties().withContentType(MIME_PKCS12);
        CertificatePolicy certificatePolicy = new CertificatePolicy().withSecretProperties(secretProperties);
        Attributes attribute = new CertificateAttributes().withEnabled(true);

        String vaultUri = getVaultUri();
        String certificateName = "importCertPkcs";
        CertificateBundle certificateBundle = keyVaultClient.importCertificate(
                new ImportCertificateRequest
                    .Builder(vaultUri, certificateName, certificateContent)
                    .withPassword(certificatePassword)
                    .withPolicy(certificatePolicy)
                    .withAttributes(attribute)
                    .withTags(TAGS)
                    .build());

        // Validate the certificate bundle created
        validateCertificateBundle(certificateBundle, certificatePolicy);
        Assert.assertTrue(toHexString(certificateBundle.x509Thumbprint()).equalsIgnoreCase("7cb8b7539d87ba7215357b9b9049dff2d3fa59ba"));
        Assert.assertEquals(attribute.enabled(), certificateBundle.attributes().enabled());

        // Load the CER part into X509Certificate object
        X509Certificate x509Certificate = loadCerToX509Certificate(certificateBundle);

        Assert.assertTrue(x509Certificate.getSubjectX500Principal().getName().equals("CN=KeyVaultTest"));
        Assert.assertTrue(x509Certificate.getIssuerX500Principal().getName().equals("CN=Root Agency"));

        // Skip retrieving secret in playback mode because the cert in response body is redacted in the playback file.
        if (interceptorManager.isRecordMode()) {
            // Retrieve the secret backing the certificate
            SecretIdentifier secretIdentifier = certificateBundle.secretIdentifier();
            SecretBundle secret = keyVaultClient.getSecret(secretIdentifier.baseIdentifier());
            Assert.assertTrue(secret.managed());

            // Load the secret into a KeyStore
            String secretPassword = "";
            KeyStore keyStore = loadSecretToKeyStore(secret, secretPassword);

            // Validate the certificate and key in the KeyStore
            validateCertificateKeyInKeyStore(keyStore, x509Certificate, secretPassword);
        }

        CertificateBundle deletedCertificateBundle = keyVaultClient.deleteCertificate(getVaultUri(), certificateName);
        pollOnCertificateDeletion(getVaultUri(), certificateName);

        try {
            keyVaultClient.getCertificate(deletedCertificateBundle.certificateIdentifier().baseIdentifier());
        } catch (KeyVaultErrorException e) {
            Assert.assertNotNull(e.body().error());
            Assert.assertEquals("CertificateNotFound", e.body().error().code());
        }

        keyVaultClient.purgeDeletedCertificate(getVaultUri(), certificateName);
        SdkContext.sleep(10000);
    }

       /**
     * Import a PKCS12 format (which includes the private key) certificate.
     */
    @Test
    public void certificateUpdateForCertificateOperationsTest() throws Exception {
        String certificateContent = readCertificate("pkcs12_base64_testdata.cer");
        String certificatePassword = "fakePasswordPlaceholder";

        // Set content type to indicate the certificate is PKCS12 format.
        SecretProperties secretProperties = new SecretProperties().withContentType(MIME_PKCS12);
        CertificatePolicy certificatePolicy = new CertificatePolicy().withSecretProperties(secretProperties);

        String vaultUri = getVaultUri();
        String certificateName = "updateCertJava";
        keyVaultClient.importCertificate(
                new ImportCertificateRequest
                    .Builder(vaultUri, certificateName, certificateContent)
                    .withPassword(certificatePassword)
                    .withPolicy(certificatePolicy)
                    .build());

        Attributes attribute = new CertificateAttributes()
                .withExpires(new DateTime().withYear(2050).withMonthOfYear(1))
                .withNotBefore(new DateTime().withYear(2000).withMonthOfYear(1));
        CertificateBundle updatedCertBundle = keyVaultClient.updateCertificate(
                new UpdateCertificateRequest
                    .Builder(vaultUri, certificateName)
                    .withAttributes(attribute.withEnabled(false))
                    .withTags(TAGS)
                    .build());
        Assert.assertEquals(attribute.enabled(), updatedCertBundle.attributes().enabled());
        Assert.assertEquals(TAGS.toString(), updatedCertBundle.tags().toString());

        CertificatePolicy certificatePolicyUpdate = certificatePolicy.withIssuerParameters(new IssuerParameters().withName(ISSUER_SELF));
        CertificatePolicy updatedCertificatePolicy = keyVaultClient.updateCertificatePolicy(
                new UpdateCertificatePolicyRequest
                    .Builder(vaultUri, certificateName)
                    .withPolicy(certificatePolicyUpdate)
                    .build());
        Assert.assertEquals(certificatePolicyUpdate.issuerParameters().name(), updatedCertificatePolicy.issuerParameters().name());

        CertificatePolicy policy = keyVaultClient.getCertificatePolicy(vaultUri, certificateName);
        Assert.assertEquals(certificatePolicyUpdate.issuerParameters().name(), policy.issuerParameters().name());

        keyVaultClient.deleteCertificate(getVaultUri(), certificateName);
        pollOnCertificateDeletion(getVaultUri(), certificateName);

        keyVaultClient.purgeDeletedCertificate(getVaultUri(), certificateName);
        SdkContext.sleep(10000);
    }

    /**
     * List certificates in a vault.
     */
    @Test
    public void listCertificatesForCertificateOperationsTest() throws Exception {
        String certificateName = "listCertificate";
        String certificateContent = readCertificate("pkcs12_base64_testdata.cer");
        String certificatePassword = "fakePasswordPlaceholder";

        // Set content type to indicate the certificate is PKCS12 format.
        SecretProperties secretProperties = new SecretProperties();
        secretProperties.withContentType(MIME_PKCS12);
        CertificatePolicy certificatePolicy = new CertificatePolicy();
        certificatePolicy.withSecretProperties(secretProperties);

        HashSet<String> certificates = new HashSet<String>();
        for (int i = 0; i < MAX_CERTS; ++i) {
            int failureCount = 0;
            for (;;) {
                try {
                    CertificateBundle certificateBundle = keyVaultClient.importCertificate(
                            new ImportCertificateRequest
                                .Builder(getVaultUri(), certificateName + i, certificateContent)
                                .withPassword(certificatePassword)
                                .withPolicy(certificatePolicy)
                                .build());
                    CertificateIdentifier id = certificateBundle.certificateIdentifier();
                    certificates.add(id.baseIdentifier());
                    break;
                } catch (KeyVaultErrorException e) {
                    ++failureCount;
                    if (e.body().error().code().equals("Throttled")) {
                        System.out.println("Waiting to avoid throttling");
                        SdkContext.sleep(failureCount * 1500);
                        continue;
                    }
                    throw e;
                }
            }
        }

        PagedList<CertificateItem> listResult = keyVaultClient.listCertificates(getVaultUri(), PAGELIST_MAX_CERTS);
        Assert.assertTrue(PAGELIST_MAX_CERTS >= listResult.currentPage().items().size());

        HashSet<String> toDelete = new HashSet<String>();

        for (CertificateItem item : listResult) {
            if (item != null) {
                CertificateIdentifier id = new CertificateIdentifier(item.id());
                toDelete.add(id.name());
                certificates.remove(item.id());
            }
        }

        Assert.assertEquals(0, certificates.size());

        for (String toDeleteCertificateName : toDelete) {
            keyVaultClient.deleteCertificate(getVaultUri(), toDeleteCertificateName);
            pollOnCertificateDeletion(getVaultUri(), toDeleteCertificateName);
            keyVaultClient.purgeDeletedCertificate(getVaultUri(), toDeleteCertificateName);
            SdkContext.sleep(10000);
        }
    }

    /**
     * List versions of a certificate in a vault.
     */
    @Test
    public void listCertificateVersionsForCertificateOperationsTest() throws Exception {
        String certificateName = "listCertificateVersions";
        String certificateContent = readCertificate("pkcs12_base64_testdata.cer");
        String certificatePassword = "fakePasswordPlaceholder";

        // Set content type to indicate the certificate is PKCS12 format.
        SecretProperties secretProperties = new SecretProperties();
        secretProperties.withContentType(MIME_PKCS12);
        CertificatePolicy certificatePolicy = new CertificatePolicy();
        certificatePolicy.withSecretProperties(secretProperties);

        HashSet<String> certificates = new HashSet<String>();
        for (int i = 0; i < MAX_CERTS; ++i) {
            int failureCount = 0;
            for (;;) {
                try {
                    CertificateBundle certificateBundle = keyVaultClient.importCertificate(
                            new ImportCertificateRequest
                                .Builder(getVaultUri(), certificateName, certificateContent)
                                .withPassword(certificatePassword)
                                .withPolicy(certificatePolicy)
                                .build());
                    CertificateIdentifier id = certificateBundle.certificateIdentifier();
                    certificates.add(id.identifier());
                    break;
                } catch (KeyVaultErrorException e) {
                    ++failureCount;
                    if (e.body().error().code().equals("Throttled")) {
                        System.out.println("Waiting to avoid throttling");
                        SdkContext.sleep(failureCount * 1500);
                        continue;
                    }
                    throw e;
                }
            }
        }

        PagedList<CertificateItem> listResult = keyVaultClient.listCertificateVersions(getVaultUri(), certificateName, PAGELIST_MAX_CERTS);
        Assert.assertTrue(PAGELIST_MAX_CERTS >= listResult.currentPage().items().size());

        listResult = keyVaultClient.listCertificateVersions(getVaultUri(), certificateName);

        for (CertificateItem item : listResult) {
            if (item != null) {
                certificates.remove(item.id());
            }
        }

        Assert.assertEquals(0, certificates.size());

        keyVaultClient.deleteCertificate(getVaultUri(), certificateName);
        pollOnCertificateDeletion(getVaultUri(), certificateName);
        keyVaultClient.purgeDeletedCertificate(getVaultUri(), certificateName);
    }

    /**
     * CRUD for Certificate issuers
     */
    @Test
    public void issuerCrudOperationsForCertificateOperationsTest() throws Exception {
        // Construct organization administrator details
        AdministratorDetails administratorDetails = new AdministratorDetails()
                    .withFirstName("John")
                    .withLastName("Doe")
                    .withEmailAddress("john.doe@contoso.com")
                    .withPhone("fakePhoneNumberPlaceholder");

        // Construct organization details
        OrganizationDetails organizationDetails = new OrganizationDetails();
        List<AdministratorDetails> administratorsDetails = new ArrayList<AdministratorDetails>();
        administratorsDetails.add(administratorDetails);
        organizationDetails.withAdminDetails(administratorsDetails);

        // Construct certificate issuer credentials
        IssuerCredentials credentials = new IssuerCredentials()
                    .withAccountId("account1")
                    .withPassword("Pa$$w0rd");

        IssuerBundle certificateIssuer = new IssuerBundle()
                    .withProvider(ISSUER_TEST)
                    .withCredentials(credentials)
                    .withOrganizationDetails(organizationDetails);

        IssuerBundle createdCertificateIssuer = keyVaultClient.setCertificateIssuer(
                new SetCertificateIssuerRequest
                    .Builder(getVaultUri(), "issuer1", certificateIssuer.provider())
                    .withCredentials(certificateIssuer.credentials())
                    .withOrganizationDetails(certificateIssuer.organizationDetails())
                    .build());

        validateCertificateIssuer(certificateIssuer, createdCertificateIssuer);

        String certificateIssuerName = createdCertificateIssuer.issuerIdentifier().name();
        IssuerBundle retrievedCertificateIssuer = keyVaultClient.getCertificateIssuer(getVaultUri(),
                certificateIssuerName);

        validateCertificateIssuer(certificateIssuer, retrievedCertificateIssuer);

        IssuerCredentials updatedCredentials = new IssuerCredentials()
                    .withAccountId("account2")
                    .withPassword("Secur!Ty");

        retrievedCertificateIssuer.withCredentials(updatedCredentials);
        IssuerBundle updatedCertificateIssuer = keyVaultClient.updateCertificateIssuer(
                new UpdateCertificateIssuerRequest
                    .Builder(getVaultUri(), certificateIssuerName)
                    .withProvider(ISSUER_TEST)
                    .withCredentials(updatedCredentials)
                    .withOrganizationDetails(retrievedCertificateIssuer.organizationDetails())
                    .withAttributes(retrievedCertificateIssuer.attributes())
                    .build());

        validateCertificateIssuer(retrievedCertificateIssuer, updatedCertificateIssuer);

        Assert.assertNotNull(updatedCertificateIssuer.organizationDetails());

        IssuerBundle deletedCertificateIssuer = keyVaultClient.deleteCertificateIssuer(getVaultUri(), certificateIssuerName);

        validateCertificateIssuer(updatedCertificateIssuer, deletedCertificateIssuer);

        try {
            keyVaultClient.getCertificateIssuer(getVaultUri(), certificateIssuerName);
        } catch (KeyVaultErrorException e) {
            Assert.assertNotNull(e.body().error());
            Assert.assertEquals("CertificateIssuerNotFound", e.body().error().code());
        }
    }

    /**
     * CRUD for Certificate contacts
     * @throws Exception
     */
    @Test
    public void contactsCrudOperationsForCertificateOperationsTest() throws Exception {
        // Create
        Contact contact1 = new Contact();
        contact1.withName("James");
        contact1.withEmailAddress("james@contoso.com");
        contact1.withPhone("7777777777");

        Contact contact2 = new Contact();
        contact2.withName("Ethan");
        contact2.withEmailAddress("ethan@contoso.com");
        contact2.withPhone("8888888888");

        List<Contact> contacts = new ArrayList<Contact>();
        contacts.add(contact1);
        contacts.add(contact2);

        Contacts certificateContacts = new Contacts();
        certificateContacts.withContactList(contacts);
        Contacts createdCertificateContacts = keyVaultClient.setCertificateContacts(getVaultUri(), certificateContacts);
        Assert.assertNotNull(createdCertificateContacts);
        Assert.assertNotNull(createdCertificateContacts.contactList());
        Assert.assertTrue(createdCertificateContacts.contactList().size() == 2);
        Contact[] createContacts = createdCertificateContacts.contactList().toArray(new Contact[createdCertificateContacts.contactList().size()]);
        Assert.assertTrue(createContacts[0].name().equalsIgnoreCase("James"));
        Assert.assertTrue(createContacts[0].emailAddress().equalsIgnoreCase("james@contoso.com"));
        Assert.assertTrue(createContacts[0].phone().equalsIgnoreCase("7777777777"));
        Assert.assertTrue(createContacts[1].name().equalsIgnoreCase("Ethan"));
        Assert.assertTrue(createContacts[1].emailAddress().equalsIgnoreCase("ethan@contoso.com"));
        Assert.assertTrue(createContacts[1].phone().equalsIgnoreCase("8888888888"));

        // Get
        Contacts retrievedCertificateContacts = keyVaultClient.getCertificateContacts(getVaultUri());
        Assert.assertNotNull(retrievedCertificateContacts);
        Assert.assertNotNull(retrievedCertificateContacts.contactList());
        Assert.assertTrue(retrievedCertificateContacts.contactList().size() == 2);

        // Delete
        Contacts deletedCertificateContacts = keyVaultClient.deleteCertificateContacts(getVaultUri());
        Assert.assertNotNull(deletedCertificateContacts);
        Assert.assertNotNull(deletedCertificateContacts.contactList());
        Assert.assertTrue(deletedCertificateContacts.contactList().size() == 2);

        // Get after delete
        try {
            keyVaultClient.getCertificateContacts(getVaultUri());
        } catch (KeyVaultErrorException e) {
            Assert.assertNotNull(e.body().error());
            Assert.assertEquals("ContactsNotFound", e.body().error().code());
        }
    }

    /**
     * Polls on a certificate operation for completion.
     *
     * @throws Exception
     */
    private static CertificateBundle pollOnCertificateOperation(CertificateOperation certificateOperation)
            throws Exception {

        // Wait for enrollment to complete. We will wait for 200 seconds
        int pendingPollCount = 0;
        while (pendingPollCount < 21) {
            String certificateName = certificateOperation.certificateOperationIdentifier().name();
            CertificateOperation pendingCertificateOperation = keyVaultClient
                    .getCertificateOperation(getVaultUri(), certificateName);
            if (pendingCertificateOperation.status().equalsIgnoreCase(STATUS_IN_PROGRESS)) {
                SdkContext.sleep(10000);
                pendingPollCount += 1;
                continue;
            }

            if (pendingCertificateOperation.status().equalsIgnoreCase(STATUS_COMPLETED)) {
                return keyVaultClient.getCertificate(pendingCertificateOperation.target());
            }

            throw new Exception(String.format(
                    "Polling on pending certificate returned an unexpected result. Error code = %s, Error message = %s",
                    pendingCertificateOperation.error().code(),
                    pendingCertificateOperation.error().message()));
        }

        throw new Exception("Pending certificate processing delayed");
    }

    /**
     * Extracts private key from PEM contents
     *
     * @throws InvalidKeySpecException
     * @throws NoSuchAlgorithmException
     */
    private static PrivateKey extractPrivateKeyFromPemContents(String pemContents)
            throws InvalidKeySpecException, NoSuchAlgorithmException {
        Matcher matcher = PRIVATE_KEY.matcher(pemContents);
        if (!matcher.find()) {
            throw new IllegalArgumentException("No private key found in PEM contents.");
        }

        byte[] privateKeyBytes = BASE_64.decode(matcher.group(1));
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(ALGO_RSA);
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
        return privateKey;
    }

    /**
     * Extracts certificates from PEM contents
     *
     * @throws CertificateException
     * @throws IOException
     */
    private static List<X509Certificate> extractCertificatesFromPemContents(String pemContents)
            throws CertificateException, IOException {
        Matcher matcher = CERTIFICATE.matcher(pemContents);
        if (!matcher.find()) {
            throw new IllegalArgumentException("No certificate found in PEM contents.");
        }

        List<X509Certificate> result = new ArrayList<X509Certificate>();
        int offset = 0;
        while (true) {
            if (!matcher.find(offset)) {
                break;
            }
            byte[] certBytes = BASE_64.decode(matcher.group(1));
            ByteArrayInputStream certStream = new ByteArrayInputStream(certBytes);
            CertificateFactory certificateFactory = CertificateFactory.getInstance(X509);
            X509Certificate x509Certificate = (X509Certificate) certificateFactory.generateCertificate(certStream);
            certStream.close();

            result.add(x509Certificate);
            offset = matcher.end();
        }

        return result;
    }

    /**
     * Verify a RSA key pair with a simple encrypt/decrypt test.
     *
     * @throws NoSuchPaddingException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     */
    private static void verifyRSAKeyPair(KeyPair keyPair) throws NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        // Validate algorithm is RSA
        Assert.assertTrue(keyPair.getPublic().getAlgorithm().equals(ALGO_RSA));
        Assert.assertTrue(keyPair.getPrivate().getAlgorithm().equals(ALGO_RSA));

        // Generate an array of 10 random bytes
        byte[] plainData = new byte[10];
        Random random = new Random();
        random.nextBytes(plainData);

        // Encrypt using the public key
        Cipher encryptCipher = Cipher.getInstance(ALGO_RSA);
        encryptCipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
        byte[] encryptedData = encryptCipher.doFinal(plainData);

        // Decrypt using the private key
        Cipher decryptCipher = Cipher.getInstance(ALGO_RSA);
        decryptCipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
        byte[] decryptedData = decryptCipher.doFinal(encryptedData);

        // Validate plainData is equal to decryptedData
        Assert.assertArrayEquals(plainData, decryptedData);
    }

    private String toHexString(byte[] x5t) {
        if (x5t == null) {
            return "";
        }

        StringBuilder hexString = new StringBuilder();
        for (int i = 0; i < x5t.length; i++) {
            String hex = Integer.toHexString(0xFF & x5t[i]);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
        }

        return hexString.toString().replace("-", "");
    }

    private void validateCertificateBundle(CertificateBundle certificateBundle, CertificatePolicy certificatePolicy) {
        Assert.assertNotNull(certificateBundle);
        Assert.assertNotNull(certificateBundle.id());
        Assert.assertNotNull(certificateBundle.keyIdentifier());
        Assert.assertNotNull(certificateBundle.secretIdentifier());
        Assert.assertNotNull(certificateBundle.x509Thumbprint());

        if (certificatePolicy != null) {
            Assert.assertNotNull(certificateBundle.policy());
            Assert.assertNotNull(certificateBundle.policy().issuerParameters());
            Assert.assertNotNull(certificateBundle.policy().issuerParameters().name());
            if (certificatePolicy.issuerParameters() != null) {
                Assert.assertTrue(certificateBundle.policy().issuerParameters().name().equalsIgnoreCase(certificatePolicy.issuerParameters().name()));
            }
        }
    }

    private X509Certificate loadCerToX509Certificate(CertificateBundle certificateBundle) throws CertificateException, IOException {
        Assert.assertNotNull(certificateBundle.cer());
        ByteArrayInputStream cerStream = new ByteArrayInputStream(certificateBundle.cer());
        CertificateFactory certificateFactory = CertificateFactory.getInstance(X509);
        X509Certificate x509Certificate = (X509Certificate) certificateFactory.generateCertificate(cerStream);
        cerStream.close();
        return x509Certificate;
    }

    private void validateCertificateIssuer(IssuerBundle expecred, IssuerBundle actual) {
        Assert.assertNotNull(actual);
        Assert.assertNotNull(actual.provider());
        Assert.assertTrue(actual.provider().equals(expecred.provider()));

        Assert.assertNotNull(actual.credentials());
        Assert.assertNotNull(actual.credentials().accountId());
        Assert.assertTrue(actual.credentials().accountId().equals(expecred.credentials().accountId()));
        Assert.assertNull(actual.credentials().password());

        Assert.assertNotNull(actual.organizationDetails());
    }

    private void validateCertificateKeyInKeyStore(KeyStore keyStore, X509Certificate x509Certificate, String secretPassword) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
        String defaultAlias = Collections.list(keyStore.aliases()).get(0);
        X509Certificate secretCertificate = (X509Certificate) keyStore.getCertificate(defaultAlias);
        Assert.assertNotNull(secretCertificate);
        Assert.assertTrue(secretCertificate.getSubjectX500Principal().getName()
                .equals(x509Certificate.getSubjectX500Principal().getName()));
        Assert.assertTrue(secretCertificate.getIssuerX500Principal().getName()
                .equals(x509Certificate.getIssuerX500Principal().getName()));
        Assert.assertTrue(secretCertificate.getSerialNumber().equals(x509Certificate.getSerialNumber()));

        // Validate the key in the KeyStore
        Key secretKey = keyStore.getKey(defaultAlias, secretPassword.toCharArray());
        Assert.assertNotNull(secretKey);
        Assert.assertTrue(secretKey instanceof PrivateKey);
        PrivateKey secretPrivateKey = (PrivateKey) secretKey;

        // Create a KeyPair with the private key from the KeyStore and public
        // key from the certificate to verify they match
        KeyPair keyPair = new KeyPair(secretCertificate.getPublicKey(), secretPrivateKey);
        Assert.assertNotNull(keyPair);
        verifyRSAKeyPair(keyPair);
    }

    private void validateCertificateIssuer(IssuerBundle issuer, String issuerName) {
        Assert.assertNotNull(issuer);
        Assert.assertNotNull(issuer.issuerIdentifier());
        Assert.assertNotNull(issuer.issuerIdentifier().name());
        Assert.assertTrue(issuer.issuerIdentifier().name().equalsIgnoreCase(issuerName));
    }

    private KeyStore loadSecretToKeyStore(SecretBundle secret, String secretPassword) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
        ByteArrayInputStream secretStream = new ByteArrayInputStream(BASE_64.decode(secret.value()));
        KeyStore keyStore = KeyStore.getInstance(PKCS12);
        keyStore.load(secretStream, secretPassword.toCharArray());
        secretStream.close();
        return keyStore;
    }

    private void validatePem(CertificateBundle certificateBundle, String subjectName) throws CertificateException, IOException, KeyVaultErrorException, IllegalArgumentException, InvalidKeySpecException, NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
        // Load the CER part into X509Certificate object
        X509Certificate x509Certificate = loadCerToX509Certificate(certificateBundle);

        Assert.assertTrue(x509Certificate.getSubjectX500Principal().getName().equals(subjectName));
        Assert.assertTrue(x509Certificate.getIssuerX500Principal().getName().equals(subjectName));

        // Retrieve the secret backing the certificate
        SecretIdentifier secretIdentifier = certificateBundle.secretIdentifier();
        SecretBundle secret = keyVaultClient.getSecret(secretIdentifier.baseIdentifier());
        Assert.assertTrue(secret.managed());
        String secretValue = secret.value();

        // Extract private key from PEM
        PrivateKey secretPrivateKey = extractPrivateKeyFromPemContents(secretValue);
        Assert.assertNotNull(secretPrivateKey);

        // Extract certificates from PEM
        List<X509Certificate> certificates = extractCertificatesFromPemContents(secretValue);
        Assert.assertNotNull(certificates);
        Assert.assertTrue(certificates.size() == 1);

        // has the public key corresponding to the private key.
        X509Certificate secretCertificate = certificates.get(0);
        Assert.assertNotNull(secretCertificate);
        Assert.assertTrue(secretCertificate.getSubjectX500Principal().getName()
                .equals(x509Certificate.getSubjectX500Principal().getName()));
        Assert.assertTrue(secretCertificate.getIssuerX500Principal().getName()
                .equals(x509Certificate.getIssuerX500Principal().getName()));
        Assert.assertTrue(secretCertificate.getSerialNumber().equals(x509Certificate.getSerialNumber()));

        // Create a KeyPair with the private key from the KeyStore and public
        // key from the certificate to verify they match
        KeyPair keyPair = new KeyPair(secretCertificate.getPublicKey(), secretPrivateKey);
        Assert.assertNotNull(keyPair);
        verifyRSAKeyPair(keyPair);
    }

    private String readCertificate(String certName) throws IOException {
        String pemPath = getClass().getClassLoader().getResource(certName).getPath();
        String pemCert = "";
        BufferedReader br = new BufferedReader(new FileReader(pemPath));
        try {
            String line;
            while ((line = br.readLine()) != null) {
                pemCert += line + "\n";
            }
        } finally {
            br.close();
        }
        return pemCert;
    }
}

redpajama_set_name: RedPajamaGithub


text:
Walkout At Fiat Chrysler Windsor Assembly Over Coronavirus Danger
By Shannon Jones, WSWS.org.
| Resist!
Walkout At Fiat Chrysler Windsor Assembly Over Coronavirus Danger2020-03-152020-03-18https://popularresistance-uploads.s3.amazonaws.com/uploads/2017/12/popres-shorter.pngPopularResistance.Orghttps://popularresistance-uploads.s3.amazonaws.com/uploads/2020/03/image-e1584286196401.jpg200px200px
Above photo: Workers at the FCA Windsor plant.
Canada – Workers at the Fiat Chrysler Windsor Assembly Plant walked off the job Thursday over concerns about the spread of coronavirus at their plant and after learning that a worker at the FCA Kokomo Transmission Plant in the United States was diagnosed with the potentially deadly disease. Windsor Assembly is the largest FCA plant in Canada and employs 6,000 workers building the Dodge Caravan and Chrysler Pacifica vans.
Plant management confirmed that one worker at Windsor Assembly was on self-quarantine after their possible exposure to the virus. According to the latest reports, management got production restarted Friday afternoon following the intervention of the federal Ministry of Labour and the Unifor union. The same day workers walked off the job in Windsor it was reported that the wife of Canadian Prime Minister Justin Trudeau tested positive for coronavirus.
A statement issued by FCA management declared, "The Ministry of Labour visited the plant on March 12 to investigate and determined our protocols and work environment to be safe. We worked with the local union and Unifor leadership as well as government officials to address this issue."
The statement did not explain why auto assembly plants with thousands of workers were safe when political jurisdictions all over North America were declaring states of emergencies, closing schools and public corporate offices and banning large public gatherings in an attempt to halt the potentially catastrophic spread of COVID-19 infection.
The action by the Windsor FCA workers reflects growing anger and frustration inside auto plants across North America, where workers are raising strident demands for a suspension of operations to protect the health and safety of workers, their families and the broader community as the scale of the coronavirus pandemic becomes more widely known.
A worker from GM St. Catharines, Ontario wrote to the WSWS Autoworker Newsletter, "Virtually nothing is being done. Both management and the union leadership are asleep at the wheel based upon what I have been told."
Meanwhile, the state of Michigan by executive order closed schools and universities and banned gatherings of more than 250 people over COVID-19 concerns. However, the order exempts factories and offices.
While factory production across North America is continuing full tilt, Ford and GM are telling non-factory employees, including white collar and upper management, to work as much as possible from home to prevent the spread of the virus.
At the massive FCA Sterling Heights Assembly Plant (SHAP) north of Detroit unconfirmed reports circulated Friday about multiple potential cases of COVID-19 among employees at the facility. While there have been no statements from the company or the United Auto Workers union confirming or denying these reports, workers contacted by the World Socialist Web Site Autoworker Newsletter expressed anger and contempt at the disregard by management and the UAW for their health and safety.
One SHAP worker told the Autoworker Newsletter, "They are telling us not to panic. How can you say that when you are not safeguarding our health? You have made unlimited profits for nine years. How much is enough? People are pissed about that.
"We can't believe the union. We are so mad at them. If they aren't going to fight for us, then we don't need them. What's the point?
"(Thursday) at 9:30 or 10:00 at night they had a form that supervisors wanted us to put our name on from upper management saying we should wash our hands; take precautions about the virus. If you didn't sign it, they said you would get written up. They didn't give us a copy of the form. It was on a clipboard.
"We think it was a paper that was designed to relieve them of responsibility in case people get the virus. That's what everyone thinks. Many didn't sign it. Later they came around and wrote up people who didn't sign it.
"We found out that a worker called his cousin who is a union steward at JNAP [FCA Jefferson North Assembly Plant] and he told them there was a confirmed case there. Management knew about it."
He said of reports that workers at SHAP may have contracted the virus, "People are ready to walk out. My whole department said they weren't coming today or tomorrow, but they were told they would be written up.
"They don't give a crap about management; they are for putting their family and kids first. If they contract it and take it home, their babies might get it."
Another SHAP worker wrote, "They've done nothing. I feel as though they shouldn't be running production. There are 2 cases in my plant, and they've done nothing about it. They claim that they'll be setting up hand sanitizing station, but they are yet to be seen.
"They clearly don't care about the line workers if they aren't considering shutting down plants but are talking about shutting down corporate offices."
A worker at the Kokomo Transmission Plant wrote to the Autoworker Newsletter about the confirmation of a case of COVID-19 at FCA in Kokomo. "We have had numerous confirmed cases with workers at the Tipton transmission plant and they are covering it up and making us work. If this doesn't open the peoples' eyes, nothing will.
"This company and union don't give a damn about any of us. We are treated like farm animals. We are a bunch of slaves for the rich and their families and we always will be until the people take back what's rightfully theirs."
A worker at the GM Kansas City Assembly Plant wrote, "We have heard a B shift supervisor has the virus and today a girl thought she had it on C shift, and they couldn't care less. They weren't even going to make her go to medical. I was the one who disinfected her gun and parts rack and trained the double up who did her job the rest of the day. We called our committeeman and told supervisors and called health and safety. They did nothing but tell us there is no way she has it."
Workers at other Ford, Chrysler and GM plants report similar experiences. In a social media post, workers at the GM Flint Assembly Plant asked the UAW shop chairman what would happen if workers at the plant contract COVID-19; would management close the plant?
The UAW is dismissing workers' concerns and making sure production is not disrupted. In response to the workers' question, Local 598 shop chairman Eric Welter said, "decisions are coming out of the 39th floor of the RenCen," referring to CEO Mary Barra's office in GM's headquarters at the Renaissance Center in Detroit. "I expect the policies will continue to change as the spread of the virus continues. It's not a negotiation; it's being driven by medical experts at the (Centers for Disease Control)."
The decision to keep the plants open is not being driven by medical experts, but the profit interests of the corporations and Wall Street shareholders. Far from representing the interests of workers in this dangerous situation, the UAW functions as management's enforcers.
The Autoworker Newsletter advances the demand for a halt to production until the COVID-19 infection is contained. At the same time, workers must receive their full pay and benefits for the duration of the closures. To enforce this, we call for the formation of rank-and-file committees in every factory, neighborhood and workplace to fight for the independent interests of workers. No confidence can be placed in the assurances of management or the UAW about the safety in the workplace. The guiding principle must be that human lives take precedence over corporate profits.
A SHAP worker told the Autoworker Newsletter, "This is an example of capitalism. It's all about profit. They don't give a crap about health. They care about their stockholders and CEOs. Workers are a dime a dozen—[costing] $15 an hour. Once they get rid of the older ones, it's good for them."
A worker at the FCA Jeep complex in Toledo, Ohio wrote, "I'm very scared about the situation… Work has only handed out paperwork for us to sign about the virus…They haven't supplied us with any sanitizer. I have my own… Nor have [they] come to us to speak about [it] personally. I understand they don't want to lose money, but why take the chance of us getting infected by it? God forbid that any of us get it, then nobody will be able to even build the Jeeps."
The Autoworker Newsletter is interested in your experiences. We urge workers to email us at: autoworkers@wsws.org with information about conditions in your plant and to subscribe to our newsletter for regular updates and more information about forming rank-and-file committees to take collective, independent action in the face of the COVID-19 threat.
Canada Coronavirus COVID-19 health Worker Rights

redpajama_set_name: RedPajamaCommonCrawl


text:
« "BOMBSHELL": Fanny patters
"A HIDDEN LIFE": Crisis of conscience »
"CATS": Hissyfit
December 19, 2019 by butlerscinemascene
"CATS" My rating: C+
110 minutes | MPAA rating: PG
It's taken the musical "Cats" nearly 40 years to make its way from the stage to the big screen. Now we know why.
Just as there are some novels that defy dramatization, so there are stage productions that derive their power from the interaction of audience and performer, that work precisely because the viewer realizes that all the magic unfolding in front of him/her is being created by real people in real time.
Tom Hooper's movie version, on the other hand, has been so digitally diddled with that we can't be sure that anything we're seeing — from the settings to the performers' faces — is even remotely real. Characters do impossible flips in the air, cockroaches march in formation…it's all so artificial that the film might as well have been done as pure animation (actually that was the plan, back in the '90s).
That said, the movie "Cats" isn't a total wipeout. The score (the tunes are by Andrew Lloyd Webber, the lyrics derived from T.S. Eliot's book of poems Old Possum's Book of Practical Cats) remains humworthy and at least a couple of the performers manage to transcend their hairy makeup (all too often they look like werewolves from a '60s Hammer film) and establish an emotional connection with the audience.
A big problem is that "Cats" lacks a real story. On stage this wasn't a deal breaker…the show was a musical revue with different "cats" taking center stage to sing and dance their signature numbers. What plot there was dealt with the approaching Jellicle Ball where one lucky feline will be chosen by the ancient Deuteronomy to be reincarnated into a new life (cats get nine of them, after all).
The screenplay by Lee Hall and Hooper centers on Victoria (ballerina Francesca Hayward, who seems capable of expressing only a quizzical attitude), abandoned by her owner in a dirty alley and adopted into the Jellicle tribe. Her guide and guardian is Munkstrap (Robbie Fairchild), who introduces her to various other characters and the rundown corner of London they call home.
To add a bit of tension to the proceedings, the filmmakers have invented a subplot in which Macavity (Idris Elba), the Mack-the-Knifeish criminal mastermind, kidnaps his competitors for top Jellicle Ball honors. It's pretty weak stuff.
Highlights? Well, Jason Derulo brings a big dose of James Brown theatricality to his performance as the bumptious Rum Tum Tugger. Ian McKellan is quite wonderful as Gus the Theatre Cat, delivering a sort of melancholy recitative about his days hanging about the West End. Steven McRae, as Skimbleshanks the Railway Cat, dishes some tapping fury…it's one of the few times I felt I was actually seeing someone dance.
Taylor Swift (who gets top billing despite only a few minutes of screen time) does well as Bombalurina, singing a sort of vampy burlesque tribute to her sinister boss Macavity. She's also credited with writing a new song, "Beautiful Ghosts." Gotta be honest…I don't even recall the number.
As Old Deuteronomy (a role traditionally performed by a man) Judy Dench exudes class even through her bulky Bert Lahr costuming.
And then there's Jennifer Hudson's Grizabella, the once-glamorous, now ragged and despised courtesan cat who delivers the show's big number, "Memory." As with the stage show, the entire film builds to that moment and Hudson doesn't let us down, eschewing vocal pyrotechnics for an almost-hesitant sadness that goes straight for the tear ducts.
The cast also includes Rebel Wilson and James Corden in comedy numbers (both about gluttony); Ray Winstone is wasted as Macavity's battle-scarred henchman Growltiger; Laurie Davison has a few good moments as Mistoffelees the magical cat.
Those who have seen "Cats" on stage at least have some inkling of what to expect; for the uninitiated I fear this film will be simply overbusy, uninvolving confusion.
Posted in Uncategorized | Tagged cats, Ian McKellan, Idris Elba, James Corden, Jennifer Hudson, Judi Dench, Rebel Wilson, T.S. Eliot, Taylor Swift, Tom Hooper | Leave a Comment


Functionality has been added to allow extensive flexibility in adding and managing file attachments, especially with regards to attachment icons. Icons can now be created with both custom and standard images as well as additional text tags, allowing personalization or corporate branding of attachment icons.
Support has been added for Wiki links in rich text, allowing fast and easy authoring assistance inside the IBM Lotus Notes client as well as on the web. In addition, Wiki links can be used to rapidly create link hotspots without coding or manual steps, using an easy to learn syntax.
Support has been added for regular expression matching with a number of methods, enabling intelligent search and replace, data mining with regular expression keywords, highlighting and more. The additional power of the regular expression engine will empower a great many new potential applications.
Extensive improvements have been made in the HTML generation engine and the separate HTML import engine, primarily focused on performance and scalability, but also incorporating bug fixes and reliability enhancements, and improvement in CSS handling. Particular focus has been on use through the Midas C++ API to allow third parties to incorporate HTML coexistence as part of their offerings, but the improvements apply to all three Midas Rich Text products.
Additional properties have been added to allow switching between stored images and native images. Stored images are held in CD Storage attachments and are used for delayed display of images on demand.
While Notes/Domino 7.x has been supported since Version 3.61, additional support for new rich text structures and options have been added to the rich text editing engine, and Version 4.00 is the first version to fully support Notes/Domino 8 for both Midas products.
Version 4.00 has been released for the Windows 95/98/NT/2000/XP client and the Windows NT/2000/2003 server, as well as for AIX 4.3.3 and above. More details on features, as well as enhanced samples and demos on our website, will be available on the Genii Software website.


Own and share your digital credentials and professional achievements to empower your career and life.
Empower yourself with on-demand access to your valuable credentials.
Verify your employment and education to Improve the credibility of your professional qualifications.
See what employers see and run a self background check to kickstart your job or apartment application.
Verify and keep your certificates organized in one place so you can use them wherever you go.
Collect experiences, certificates and accolades throughout your career.
Keep your life's credentials organized in one easy place.


text:
Bridgestone buying TireConnect, online sales tool developer
NASHVILLE, Tenn. (Oct. 22, 2015) — Bridgestone Americas has struck a deal to buy TireConnect Systems, a Canadian software company that's developed an online tire sales tool that that links retailers to wholesalers and provides consumer e-commerce capabilities as well.
The acquisition, expected to close by mid- to late November, will allow Bridgestone, its dealer network and distributors to meet the needs of today's digitally minded consumers more effectively by providing a fully integrated tire-buying experience from online to in-store, Bridgestone said.
Financial terms are not being disclosed.
"At Bridgestone, we constantly look for new ways to grow our business, while also empowering and growing the businesses of our dealers," said John Baratta, president, consumer replacement, Bridgestone Americas Tire Operations.
"As the world's largest tire and rubber company, we know that we need to be active and aggressive in the increasingly digital marketplace. The acquisition of TireConnect will allow Bridgestone to create a direct path to online tire buying that collectively benefits our dealers, distributors and consumers."
The TireConnect software tool allows dealers to get full credit for online tire sales, while also remaining in control of their inventory, pricing and labor/installation costs, Bridgestone said.
TireConnect Systems
George Silagadze, founder, TireConnect Systems
A plug-and-play solution, the TireConnect tool works with Bridgestone dealers' current websites, providing a seamless research and purchasing experience between consumers and their preferred local dealers.
The tool also improves efficiency, saves time and delivers enhanced speed-to-market for dealers and distributors, Bridgestone said.
Founded in 2012 by George Silagadze and Chance Harrington, TireConnect offers a unique B2B and B2C online tire sales solution to more than a dozen customers with nearly 600 points of sale across Canada and the U.S.
Messrs. Silagadze and Harrington will join Bridgestone as part of the acquisition.
Bridgestone did not say whether TireConnect will continue to be based in Richmond Hill, Ontario, or whether it will move to Bridgestone headquarters in Nashville.
"We are excited that Bridgestone chose to acquire TireConnect and establish it as a digital and ecommerce solution for its dealers," Mr. Silagadze said.
"This move demonstrates the company's ongoing commitment to its customers and end-user consumers, and we look forward to working together to further enhance the tire buying experience."
The Bridgestone TireConnect online sales and ecommerce solution will be available to Bridgestone dealers and distributors in the first quarter of 2016.
It did not say what the cost will be to use TireConnect. As the tool is currently structured, dealers pay a $150 start-up fee and $69.95 per month for the online tire quote tool, Mr. Silagadze said in an interview with Tire Business earlier this year.
Bridgestone did not say in its release whether TireConnect will be exclusive to Bridgestone dealers or distributors or continue to be available to other interested parties, including those already using it.
Bridgestone to buy Pep Boys


text:
Rock is Dead – A solo exhibition by Nanae Mitobe
We at biscuit gallery are pleased to announce that we will holding Rock is Dead, a solo exhibition of works by Nanae Mitobe, from June 24 to July 11, 2021.
The culture of rock music left tracks of dazzling light in the 20th century as a symbol of youthful energy powerful enough to pull down the barriers of dull little worlds. Rock stars stood as champions who spoke for those without a voice, anarchists who spat at the establishment, and revolutionaries who were out to change the world. With such personae, they captivated and led the youth living through those times. As the times changed, however, rock became more commercialized, and the artists who had shot to stardom were consumed like products as their visions became hackneyed and hollow. In the end, all that was left was the facade of style. In the accompanying atmosphere of envy and disappointment, the phrase "rock is dead" was in the air.
For all its provocative, aggressive musicality, glittering style, and rousing messages, rock has a history of turning its artists into idols that are consumed as commodities. Rock stars went on singing while struggling with the contradictions and conflicts they encountered in the gap between their idolized and real selves. Mitobe has been steadily producing new works while overlaying the consumption-expression antinomy that must be confronted by all active in the world of art on the figures of such idols of yesteryear. Even as we enter an age when many are pointing out the limits of capitalism, the art market is nevertheless ballooning. As it caters to the wants of this market, art is losing substance and becoming distorted. It is clear to all concerned that, however disillusioned they may be with this situation, the rules and principles of capital cannot be ignored. With full understanding of this area, Mitobe has applied pigment in a manner that seems aimed at breaking through this impasse, and portrayed rock stars with the words "art is dead" in her head.
This exhibition displays works depicting rock stars of the 1970s. The subjects include David Bowie, the icon of the glam rock scene who made his mark in fashion and cinema as well as in music, and The Runaways, the girl band that smashed the conservative idea of femininity with their risqué appearance and behavior. Today, innocuous, colorful interior items that have a pop flair and carry no statements or messages are sweeping the market as works of art. Under these circumstances, the depiction of stars who lived amid contradiction and conflict is an attempt to shatter the position of paintings as products. Mitobe has steadfastly adhered to her style of ultra-thick impastos ignoring concerns of efficiency and ease of handling. Radiating the raw smell of pigments and bearing up under their weight and mass, the rock stars portrayed in this style flaunt the very antithesis of today's art.
Nanae Mitobe《Sexy Rock Alien》2mx2m Oil painting,linen,pigment,wood panel 2021
Curator Junya Yamamine has written the following comment on this exhibition.
As an antithesis when rock is dead / art has lost its beauty
– Junya Yamamine (Curator)
One thing I have decided never to do is to talk about paintings.
I have worked mainly in the area of videos and photographs, which I consider to be no more than sub-streams in the field of art. I have viewed painting, the mainstream, as sacred ground in which I was not qualified to set foot. Indeed, I may even have felt that I could pride myself on my singularity by doing so and carving out a path for myself as a counterpart to the mainstream. Or perhaps it was because of a meanness of spirit that came from being shown over and over again in this world of art how all sorts of art-related institutions revolve around painting. In any case, there is no doubt that I have avoided squarely facing up to the entity that is painting. It was smack in the middle of this mental milieu that I made the acquaintance of the painter Nanae Mitobe.
I first saw works by Mitobe at Musee Hamaguchi Yozo. With their levels of mass going beyond the conventional realm of paintings, they projected an extraordinary presence that could not be comprehended iconographically. If anything, their massiveness made them seem more like sculptures than paintings. Mitobe, however, insists on calling them paintings. In contrast to my impression of her as a reticent, charming artist, I found an unshakeable belief in the supremacy of painting, and hardheadedness. Her tenacity and the strange pressure generated by her works left an indelible mark on my mind. And yet, I figured they were not compatible with me as one working in the world of contextualism, where artists wield the language and rhetoric of video to manipulate images in the minds of viewers. Nevertheless, this was just a jealousy of her overwhelming aura that arose inside me as I struggled with art in the age dominated by replication technology devoid of all aura. And because of this jealousy, I shut it out. That is how I finally see it now. Yet Mitobe, too, was struggling in the arena of contemporary art that had forgotten reliance on aura and placed supreme value on rhetoric deftly merged with context. She, however, had a unique weapon at her disposal; this was nothing other than the generation of a concrete impact that virtually shouts "this is painting," thanks to the staggering mass of pigment, smell of oil paints, and sheer presence of her works. The art looks almost absurdly single-minded and clumsy. But then the keen viewer realizes that the essence and appeal of Mitobe as an artist lies in the way she takes this line of hers as far as it can go.
Although I am presumably on the opposite pole from her, for some reason, Mitobe often came to my talk sessions and events, and listened closely to the discussion. In this connection, she asked me for some advice. I had had few opportunities to comment on paintings, and was fairly taken aback by this request. At that time, I guess I said something about the iconographical impression made by flat images in the contemporary channels of information distribution, as well as the narrative carried by works and the need for a hook for linkage with the existing context, however excellent the work may be. I believe that's the sort of superficial theory I talked about then. But when I again saw works of hers at her studio, I had to correct the shallow view I had had of her art, which was constructed with a sense of values completely outside the grammar I knew. The presence radiated by the multiple layers of pigment applied by Mitobe in her dialogue with Michael Jackson, David Bowie, and other personages who attracted her speaks to us more eloquently than any words could. As I have endeavored to grasp art conceptually or with a logos (in short, too much with my brain), this realization came as a shock to me. Thanks to her, I became re-acquainted with one of the fundamental aspects of art.
The theme chosen by Mitobe, who prompted this realization of mine, for this group of works is "rock is dead." I imagine that she wants to scream "art is dead," and is projecting her voice on those of the rock stars who bucked the times and shone in their day with a sparkle born of the resulting friction and conflict. I understand this feeling of hers all too well. She says that, when she first saw Van Gogh's Sunflowers, the painting seemed to be shining. Today's art world is accepting as "art" an influx of works that truckle to the market, are shallow, and take aim at a certain clientele. She undoubtedly thinks that it is no longer shining. I can empathize with this outlook. While she and I have different ideas of what is beautiful, we both cannot bear to see the expulsion of art that seems to shine and the pretensions of art that merely imitates styles and modes to the mainstream position. To me, a work is beautiful because it is imbued with the earnest cry of the artist. Art is made art only through the overlay and resonance of the work itself and the cry of the artist. If this is in the process of being lost, then art is heading for the same fate as rock that lost its fire and soul.
Once, things of beauty were called art. But these days, even things that do not appear to merit the term are being called art. The antitheses conceived by Mitobe in her desire to demolish this circumstance ensoul her works. Art that carries on superficial techniques and styles is embraced by the mass media, easy to handle, and geared for commercialization. And the economic principles on its side magnify the power supporting such art. As this trend deepens, the people who have been captivated by the genuine, diverse, profound, and at times unsettling or fearful power animating real art are becoming unable to object. We are entering such an age right now. Even in this age, however, I would like to think that beauty will be born from the internal struggles of people who remain true to their own mode of expression. I would also like to entertain at least a faint hope that it is precisely the works they create that will defeat the status quo. Only existences shining with the glimmer ignited by struggle can truly be artists. Mitobe has proceeded in pursuit of this path. I have a hunch that, with these new works, she is opening the door to her advancement to the next stage. This is because the thickness of her works is permeated with her fervent cry.
Nanae Mitobe《All-girl Rock Band Sings in Lingerie》1.5m x 1m Oil painting,linen,wood panel 2021
Comment by the artist, Nanae Mitobe
I heard the news of David Bowie's death in 2016, and went to see a big retrospective exhibition on him that was held in Japan in the following year. The exhibition was very exciting to me, but the David Bowie in my heart had a perfect beauty, and I was unable to portray him on canvas. The ensuing years went by in a flash, and the art scene in Japan, too, underwent major changes. These changes also influenced my thinking on the art that I had believed in. Amid my doubts about beauty, Bowie's bold message, in effect declaring that he was an alien, brought home to me an individual existence transcending beauty. I was saved by his existence.
Outline of Rock is Dead, a solo exhibition by Nanae Mitobe
Period: June 24 (Thurs.) – July 11 (Sun.), 2021
Hours: 13:00 – 19:00 (12:00 – 18:00 on Saturdays and Sundays)
Closed on Mondays, Tuesdays, and Wednesdays
Venue: biscuit gallery, F1 – F3
Please feel free to contact us if you would like to inquire about the works.


What happens to propane released when lighting a cooker burner. It takes a few seconds to light the burner and in that time a little propane is released. It is heavier than air so I guess it ends up in the bilge. Overtime will it accumulate, disperse naturally or decay?
It is only a small amount but over time does it accumulate?
i find no trace of it post ignition, which seems to use all the escaped gas.
I would assume it evaporates after some time as gasoline does.
Good question. LP, "Liquid Propane," evaporates into a gas at the top of the bottle and is drawn off into the hose and through the regulator. It is already "evaporated" after this point. A gas can't evaporate again unless it is first condensed into a liquid again.
Propane gas is heavier than air. It sinks. It can't evaporate, it simply drops down to the lowest place it can settle into. From there, can it slowly mix with the air and atom by atom bounce up and out of the boat eventually?
Like Karen said, I haven't blowed up already.
We have a Fireboy-Xinex LP sensor in our dry bilge as part of the solenoid panel. It goes through a self-test every time it starts up and thereafter monitors the air in the bilge for traces of propane and other dangerous gases. But it isn't quite down at the very bottom of the wet bilge. It wouldn't survive the moisture. Every once in a while I will take my portable gas sniffer and put it down there and check when I'm sniffing around the stove for leaks. It never has shown any trace of explosive gases.
Where does it go? It must migrate out slowly somehow.
Over time propane dissolves in air forming a solution. Between 2.1% and 9.5% propane this solution is explosive. Propane, and solutions of air and propane, are heavier than air and so tend to move downward while being mixed with surrounding air.
But ultimately the bilge is not completely sealed and there is some air exchange over time. The propane diffuses into the incoming air and its concentration gradually drops. Once in solution the propane does not settle out, that is, it does not form a highly concentrated layer at the bottom of the bilge.
Gasoline vapor is both heavier (well, more dense) and more explosive with a wider LEL-HEL range of 1.2%-7.1%. There are far more gasoline-powered boats (with engines below decks) on the water than boats equipped with propane systems. While bilge explosions do occur, they are infrequent considering the enormous number of craft and the comparatively lax standards of maintenance and operation that are applied on so many of them.
While propane is dangerous if handled carelessly, I think the risk is oft times exaggerated.
I would bet that it gets sucked into the engine while you are running and disappears. There is quite an air flow through the bilge, especially with a diesel engine. Even though it is heavier than air, the turbulence will no doubt stir it up and it will get used or blown out.
I think any that 'gets away' before the burner ignites goes up at the same time.. that's why sometimes one gets that 'Woosh' as it lights if the first try fails..
Doubt if any gets to the bilge..
So basically it diffuses as it mixes with the air, but that takes a little time at first and it tends to settle to the bottom of the air in the boat before it can slowly mix evenly with the atmospheric air above it.
Assuming 10 seconds of propane leaking before lighting (which is lots) and a 6000 btu stove top burner, you're looking at 0.00015 pounds of propane. After the burner lights the heat of combustion causes the air around the burner to be drawn towards the burner. More then likely the leaked propane is burned and consumed as air is drawn towards the burner.
Having lived aboard for 13 years now, it's never been an issue.
Never been an issue. However, when sailing with the army in the old days from British Kiel Yacht Club. You had to make a log entry every hour and update you DR on a thing called a paper chart! At the same time the crew had to pump the bilge. The rule was pump dry them 10 strokes for gas! The instructor was wonderful Capt Ken Hazard!!!
I think it is Brownian motion, the random movement of the gas molecules, that eventually diffuses the propane into all the rest of the air in the boat, and since that is hopefully way below the explosive threshold, eventually the "not quite air" gets circulated out of the boat and the danger doesn't build.
With natural gas ashore, there is a distinct odor added to the gas so you can tell if something is leaking. (Methyl mercaptan?) I know I can "smell gas" but have never detected that odor, even in a closed room, after a stove or other gas appliance was lit. So I expect part of this is because very very little gas escapes before the flame, and much of that combusts with the flame.
I press and hold the igniter before I open the valve. Time to combustion measured in microseconds.
Quite comfortable that any gas in the vicinity of the burner is ignited.
To add to the general consensus, keep in mind 100% if the excess propane isn't rocketing into the bilge. Some, if not all, is being diffused and "carried" by the drafts found in your cabin.
Assuming the spark igniter is working properly, I doubt that any measurable amount actually escapes combustion in the moment of ignition.
If you've never tried it, light your burner at night, with the cabin lights off. You may be surprised at the fireball that is invisible in higher ambient light levels. Even the steady burner flame will appear quite a bit higher in the dark.
Ok propane released when you turn on a 6500 btu burner is .184 oz per second. So its really not enough to even think about. Besides it is usually consumed in the initial burn / just enough to fill the burner itself prior to ignition.


text:
Abstracts In Press
The JABFM
ABFM News
abfm
American Board of Family Medicine
JABFM On Twitter
JABFM On YouTube
JABFM On Facebook
Research ArticleOriginal Article
The Prevalence Of Selected Chronic Diseases Among The Mexican-American Elderly: Data From The 1982–1984 Hispanic Health And Nutrition Examination Survey
David V. Espino, Sandra K. Burge and Carlos A. Moreno
The Journal of the American Board of Family Practice July 1991, 4 (4) 217-222; DOI: https://doi.org/10.3122/jabfm.4.4.217
David V. Espino
From the Division of Geriatrics in the Department of Family Practice, University of Texas Health Science Center at San Antonio. Address reprint requests to David V. Espino, M.D., Department of Family Practice, The University of Texas Health Science Center at San Antonio, 7703 Floyd Curl Drive, San Antonio, TX 78284-7795.
Sandra K. Burge
Carlos A. Moreno
Although the Mexican-American elderly represent a large and rapidly growing subgroup of the ethnic aged, national prevalence data of major chronic diseases among this population are sparse. Data based on physical examinations from three older groups of the southwestern portion of the 1982–1984 Hispanic Health and Nutrition Examination Survey (HHANES) were reviewed to determine rates of hypertension, diabetes mellitus, arthritis, and heart disease. The results show a higher prevalence of diabetes and lower prevalence of heart disease and hypertension when compared with the general population, coinciding with data from previous studies. Findings for the prevalence of arthritis varied widely from other studies, however, and proportions were lower than expected. Further research on arthritis in the Mexican-American elderly is necessary to determine whether protective mechanisms that may lead to a lower prevalence of this disease exist in this ethnic subgroup.
The Journal of the American Board of Family Practice
Back Matter (PDF)
Thank you for your interest in spreading the word on American Board of Family Medicine.
You are going to email the following The Prevalence Of Selected Chronic Diseases Among The Mexican-American Elderly: Data From The 1982–1984 Hispanic Health And Nutrition Examination Survey
Message Subject (Your Name) has sent you a message from American Board of Family Medicine
Message Body (Your Name) thought you would like to see the American Board of Family Medicine web site.
David V. Espino, Sandra K. Burge, Carlos A. Moreno
The Journal of the American Board of Family Practice Jul 1991, 4 (4) 217-222; DOI: 10.3122/jabfm.4.4.217
Screening for Bipolar Disorder in Patients Treated for Depression in a Family Medicine Clinic
Screening for Dementia: Family Caregiver Questionnaires Reliably Predict Dementia
Help-Seeking for Insomnia among Adult Patients in Primary Care
Info For Reviewers
Submit A Manuscript/Review
© 2022 American Board of Family Medicine


Original Sensible Seeds flagship strain Blueberry Ghost OG is blended with OG Kush Auto to produce a fast and easy to grow version of this superior hybrid mariuana variety. These feminized autoflowering seeds produce short heavyset plants which give a quick turn around of dank tantalizing juicy fuity weed. In just 70 days from seed these babies are ripe and ready for harvest. Earthy, fruity berry weed with a slight acidic undertone will uplift you instantly delivering great waves of euphoric pleasure which then leads into a tranquil solace of relaxation.
Easy to grow and virtually maintenance free it´s time for the novice gorwer to share and experience what the fuss is about with our Blueberry Ghost OG. Resistant to most of what mother nature can throw at her and producing sensational results you´d be mad not to pop some Auto Blueberry Ghost OG from Original Sensible Seeds into your next grow.


← Commitment: The Gift That Keeps Giving!
We're all aware of the parade of powerful, public figures from Today Show host Matt Lauer to hotel and casino mogul Steve Wynn to White House staffers who have fallen in the #MeToo movement. It has gained momentum as women are deciding that, "We won't take it any more." Mass revelations of sexual misconduct and abuse perpetrated by a who's who of show business, industry and political titans are making headlines almost daily.
This is certainly a positive development for our society, shocking and sad as it is to recognize how pervasive this 'bad behavior' – as many have termed it – seems to be. The fact that even the most powerful exploiters are being toppled is a sign of maturing attitudes.
However, the sheer frequency of allegations of misconduct is beginning to create some backlash. If we are to continue making progress beyond the present moment, we must understand what's behind these episodes. Why do so many people behave this way and what can be done to affect pervasive change in what we're willing to accept as ok?
The answers are not simple, yet in a few words, I feel compelled to distill down the problem and solutions from the psychotherapeutic perspective.
First, while it is common knowledge that men – and in some cases, women – at all levels of society have been engaging in sexual misconduct in the workplace and elsewhere forever, it has never been ok! This bears repeating: Sexual intimidation – for that's what it is – has never, is not, and will never be ok!!
Why does it happen and why have we accepted it for so long? It's about psychological dysfunction and it's about power – or lack thereof.
Why would people who have attained great success, including reaching the pinnacles of their chosen fields, risk it all by engaging in sexual intimidation? Take Matt Lauer who was beloved by audiences and co-workers who were shocked to learn about this aspect of his personality. Also others from Steve Wynn to Harvey Weinstein who were revered or feared? Why? Why do they put it all on the line?
It's tied to character disorders that underlie and have the ability to undermine the positive results of native talent and drive.
It's perhaps easier to understand such dysfunction by first reviewing how emotionally healthy human beings behave. They respect themselves and, therefore, in their relationships whether professional or personal, they respect others. Respect is the underpinning of trust, positive regard and authentic love.
If you dig behind the facades of abusers, you will discover lack of genuine self respect – for any number of reasons based on their life experiences. When these individuals nonetheless achieve success, their personalities often become grandiose, which is a cover-up for unaddressed insecurities. That insecurity – a lack of a sense of genuine power – frequently results in a lack of empathy for others.
This is especially true when connected to sexuality – a drive tied to our very survival. For these individuals, fantasies can become distorted and lead to abuse of those deemed to be weak, vulnerable or subject to control. The impetus for this behavior – a form of bullying – is on a subconscious level.
The difficulty is that abusers often have no capacity for the self-awareness required to change and can continue their aberrant behaviors for decades or a lifetime without remorse.
Should we feel sorry for these individuals? Perhaps on some level. However, we are all responsible for our own lives and the choices we make. It is extremely difficult for some of these abusers to take responsibility as they see their actions as an entitlement. However, it's not impossible.
If abusers cannot or will not recognize their own deviance and find the courage to get help to address the inevitably painful experiences that led to their behavior, then I believe it's a positive outcome that society is taking them down. Perhaps when confronted with severe rejection of their actions and a healthier, alternate world view, they will finally recognize that they must take steps to better understand themselves, so that they can change.
This entry was posted in Abuse, Anger and Control, Anxiety, Attitude, Betrayal, Celebrity, Character, Choices, Ego, Emotional Courage, Emotional Pain, Emotionally Safe Environments, Emotions, Fear, Anxiety and Panic, Grandiosity, Hate, Helplessness, Hopelessness, Horror, Immaturity, LevineCounseling.com, Low Self-Esteem, Lying, Not Good Enough, Personal Boundaries, Personal Change, Personality Disorders, Physical Abuse, Political, Power and Control, Relationships, Setting Limits, Sexual Abuse, Sexual Disfunction, Sexuality, Tragedy, Trust, Uncategorized, Violence and Aggression. Bookmark the permalink.