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

org.jclouds.crypto.Pems Maven / Gradle / Ivy

There is a newer version: 2.6.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jclouds.crypto;

import static com.google.common.base.Charsets.US_ASCII;
import static com.google.common.base.Joiner.on;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Splitter.fixedLength;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.base.Throwables.propagateIfInstanceOf;
import static com.google.common.io.BaseEncoding.base64;
import static org.jclouds.crypto.ASN1Codec.decodeRSAPrivateKey;
import static org.jclouds.crypto.ASN1Codec.decodeRSAPublicKey;
import static org.jclouds.crypto.ASN1Codec.encode;
import static org.jclouds.util.Closeables2.closeQuietly;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;

import org.jclouds.javax.annotation.Nullable;

import com.google.common.annotations.Beta;
import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteProcessor;
import com.google.common.io.ByteSource;

/**
 * Reads and writes PEM encoded Strings and Streams
 */
@Beta
public class Pems {
   public static final String PRIVATE_PKCS1_MARKER = "-----BEGIN RSA PRIVATE KEY-----";
   public static final String PRIVATE_PKCS8_MARKER = "-----BEGIN PRIVATE KEY-----";
   public static final String CERTIFICATE_X509_MARKER = "-----BEGIN CERTIFICATE-----";
   public static final String PUBLIC_X509_MARKER = "-----BEGIN PUBLIC KEY-----";
   public static final String PUBLIC_PKCS1_MARKER = "-----BEGIN RSA PUBLIC KEY-----";
   public static final String PROC_TYPE_ENCRYPTED = "Proc-Type: 4,ENCRYPTED";

   private static class PemProcessor implements ByteProcessor {
      private interface ResultParser {
         T parseResult(byte[] bytes) throws IOException;
      }

      private final ByteArrayOutputStream out = new ByteArrayOutputStream();
      private final Map> parsers;

      private PemProcessor(Map> parsers) {
         this.parsers = checkNotNull(parsers, "parsers");
      }

      @Override
      public boolean processBytes(byte[] buf, int off, int len) {
         out.write(buf, off, len);
         return true;
      }

      @Override
      public T getResult() {
         Pem pem = PemReader.INSTANCE.apply(new String(out.toByteArray(), US_ASCII));
         String beginMarker = "-----BEGIN " + pem.type + "-----";
         checkState(parsers.containsKey(beginMarker), "Invalid PEM: no parsers for marker %s in %s", beginMarker,
               parsers.keySet());
         try {
            return parsers.get(beginMarker).parseResult(pem.content);
         } catch (IOException e) {
            throw new IllegalStateException("Invalid PEM : " + pem, e);
         }
      }
   }

   /**
    * Parsed PEM format
    * 
    * 
    *  -----BEGIN RSA PRIVATE KEY-----
    *  Proc-Type: 4,ENCRYPTED
    *  DEK-Info: DES-EDE3-CBC,3F17F5316E2BAC89
    * 
    *  ...base64 encoded data...
    *  -----END RSA PRIVATE KEY-----
    * 
    * 
* */ private static enum PemReader implements Function { INSTANCE; private static final String BEGIN = "-----BEGIN "; private static final String END = "-----END "; @Override public Pem apply(CharSequence chars) { checkNotNull(chars, "chars"); BufferedReader reader = null; try { reader = new BufferedReader(new StringReader(chars.toString())); Optional begin = skipUntilBegin(reader); checkArgument(begin.isPresent(), "chars %s doesn't contain % line", chars, BEGIN); String line = begin.get().substring(BEGIN.length()); String type = line.substring(0, line.indexOf('-')); StringBuilder encoded = new StringBuilder(); boolean reachedEnd = false; while ((line = reader.readLine()) != null) { if (line.indexOf(':') >= 0) { // skip headers continue; } if (line.indexOf(END + type) != -1) { reachedEnd = true; break; } encoded.append(line.trim()); } checkArgument(reachedEnd, "chars %s doesn't contain % line", chars, END); return new Pem(type, base64().decode(encoded.toString())); } catch (IOException e) { throw new IllegalStateException(String.format("io exception reading %s", chars), e); } finally { closeQuietly(reader); } } private static Optional skipUntilBegin(BufferedReader reader) throws IOException { String line = reader.readLine(); while (line != null && !line.startsWith(BEGIN)) { line = reader.readLine(); } return Optional.fromNullable(line); } } private static final class Pem { private final String type; private final byte[] content; private Pem(String type, byte[] content) { this.type = checkNotNull(type, "type"); this.content = checkNotNull(content, "content"); } } /** * Returns the object of generic type {@code T} that is pem encoded in the supplier. * * @param supplier * the input stream factory * @param marker * header that begins the PEM block * @param processor * how to parser the object from a byte array * @return the object of generic type {@code T} which was PEM encoded in the stream * @throws IOException * if an I/O error occurs */ public static T fromPem(ByteSource supplier, PemProcessor processor) throws IOException { try { return supplier.read(processor); } catch (RuntimeException e) { propagateIfInstanceOf(e.getCause(), IOException.class); propagateIfInstanceOf(e, IOException.class); throw e; } } /** * Returns the {@link RSAPrivateKeySpec} that is pem encoded in the supplier. * * @param supplier * the input stream factory * * @return the {@link RSAPrivateKeySpec} which was PEM encoded in the stream * @throws IOException * if an I/O error occurs */ public static KeySpec privateKeySpec(ByteSource supplier) throws IOException { return fromPem( supplier, new PemProcessor(ImmutableMap.> of( PRIVATE_PKCS1_MARKER, DecodeRSAPrivateCrtKeySpec.INSTANCE, PRIVATE_PKCS8_MARKER, new PemProcessor.ResultParser() { @Override public KeySpec parseResult(byte[] bytes) throws IOException { return new PKCS8EncodedKeySpec(bytes); } }))); } /** * Decode PKCS#1 encoded private key into RSAPrivateCrtKeySpec. * * @param keyBytes * Encoded PKCS#1 rsa key. */ private static enum DecodeRSAPrivateCrtKeySpec implements PemProcessor.ResultParser { INSTANCE; @Override public KeySpec parseResult(byte[] bytes) throws IOException { return decodeRSAPrivateKey(bytes); } } /** * Executes {@link Pems#privateKeySpec(ByteSource)} on the string which contains an encoded private key in PEM * format. * * @param pem * private key in pem encoded format. * @see Pems#privateKeySpec(ByteSource) */ public static KeySpec privateKeySpec(String pem) { try { return privateKeySpec(ByteSource.wrap( pem.getBytes(Charsets.UTF_8))); } catch (IOException e) { throw propagate(e); } } /** * Returns the {@link KeySpec} that is pem encoded in the supplier. * * @param supplier * the input stream factory * * @return the {@link KeySpec} which was PEM encoded in the stream * @throws IOException * if an I/O error occurs */ public static KeySpec publicKeySpec(ByteSource supplier) throws IOException { return fromPem( supplier, new PemProcessor(ImmutableMap.> of(PUBLIC_PKCS1_MARKER, DecodeRSAPublicKeySpec.INSTANCE, PUBLIC_X509_MARKER, new PemProcessor.ResultParser() { @Override public X509EncodedKeySpec parseResult(byte[] bytes) throws IOException { return new X509EncodedKeySpec(bytes); } }))); } /** * Decode PKCS#1 encoded public key into RSAPublicKeySpec. *

* Keys here can be in two different formats. They can have the algorithm encoded, or they can have only the modulus * and the public exponent. *

* The latter is not a valid PEM encoded file, but it is a valid DER encoded RSA key, so this method should also * support it. * * @param keyBytes * Encoded PKCS#1 rsa key. */ private static enum DecodeRSAPublicKeySpec implements PemProcessor.ResultParser { INSTANCE; @Override public KeySpec parseResult(byte[] bytes) throws IOException { return decodeRSAPublicKey(bytes); } } /** * Executes {@link Pems#publicKeySpec(ByteSource)} on the string which contains an encoded public key in PEM * format. * * @param pem * public key in pem encoded format. * @see Pems#publicKeySpec(ByteSource) */ public static KeySpec publicKeySpec(String pem) throws IOException { return publicKeySpec(ByteSource.wrap( pem.getBytes(Charsets.UTF_8))); } /** * Returns the {@link X509EncodedKeySpec} that is pem encoded in the supplier. * * @param supplier * the input stream factory * @param certFactory * or null to use default * * @return the {@link X509EncodedKeySpec} which was PEM encoded in the stream * @throws IOException * if an I/O error occurs * @throws CertificateException */ public static X509Certificate x509Certificate(ByteSource supplier, @Nullable CertificateFactory certFactory) throws IOException, CertificateException { final CertificateFactory certs = certFactory != null ? certFactory : CertificateFactory.getInstance("X.509"); try { return fromPem( supplier, new PemProcessor(ImmutableMap.> of( CERTIFICATE_X509_MARKER, new PemProcessor.ResultParser() { @Override public X509Certificate parseResult(byte[] bytes) throws IOException { try { return (X509Certificate) certs.generateCertificate(new ByteArrayInputStream(bytes)); } catch (CertificateException e) { throw new RuntimeException(e); } } }))); } catch (RuntimeException e) { propagateIfInstanceOf(e.getCause(), CertificateException.class); throw e; } } /** * Executes {@link Pems#x509Certificate(ByteSource, CertificateFactory)} on the string which contains an X.509 * certificate in PEM format. * * @param pem * certificate in pem encoded format. * @see Pems#x509Certificate(ByteSource, CertificateFactory) */ public static X509Certificate x509Certificate(String pem) throws IOException, CertificateException { return x509Certificate(ByteSource.wrap( pem.getBytes(Charsets.UTF_8)), null); } /** * encodes the {@link X509Certificate} to PEM format. * * @param cert * what to encode * @return the PEM encoded certificate * @throws IOException * @throws CertificateEncodingException */ public static String pem(X509Certificate cert) throws CertificateEncodingException { String marker = CERTIFICATE_X509_MARKER; return pem(cert.getEncoded(), marker); } /** * encodes the {@link PublicKey} to PEM format. */ public static String pem(PublicKey key) { String marker = key instanceof RSAPublicKey ? PUBLIC_PKCS1_MARKER : PUBLIC_X509_MARKER; return pem(key.getEncoded(), marker); } /** * encodes the {@link PrivateKey} to PEM format. */ public static String pem(PrivateKey key) { String marker = key instanceof RSAPrivateCrtKey ? PRIVATE_PKCS1_MARKER : PRIVATE_PKCS8_MARKER; return pem(key instanceof RSAPrivateCrtKey ? encode(RSAPrivateCrtKey.class.cast(key)) : key.getEncoded(), marker); } private static String pem(byte[] encoded, String marker) { String ls = System.getProperty("line.separator"); StringBuilder builder = new StringBuilder(); builder.append(marker).append(ls); builder.append(on(ls).join(fixedLength(64).split(base64().encode(encoded)))).append(ls); builder.append(marker.replace("BEGIN", "END")).append(ls); return builder.toString(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy