View Javadoc

1   package com.atlassian.asap.core.keys;
2   
3   
4   import com.atlassian.asap.api.exception.CannotRetrieveKeyException;
5   import com.atlassian.asap.core.SecurityProvider;
6   import org.apache.commons.io.IOUtils;
7   import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
8   import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
9   import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
10  import org.bouncycastle.openssl.PEMParser;
11  import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
12  
13  import java.io.IOException;
14  import java.io.Reader;
15  import java.io.StringReader;
16  import java.nio.charset.StandardCharsets;
17  import java.security.KeyFactory;
18  import java.security.NoSuchAlgorithmException;
19  import java.security.PrivateKey;
20  import java.security.Provider;
21  import java.security.PublicKey;
22  import java.security.spec.InvalidKeySpecException;
23  import java.security.spec.PKCS8EncodedKeySpec;
24  
25  /**
26   * Reads keys encoded in a data uri. Only Base64 DER encoded key data is supported.
27   * The format of the data uri must be:
28   * <pre>
29   *  data:{key-mediatype};kid={key-identifier};base64,{encoded-key-data}
30   * </pre>
31   * In addition to the media type of the key, the key identifier must be specified using
32   * url escaping as the value of the 'kid' parameter.
33   *
34   * <ul>
35   *    <li> For private keys, only {@code application/pkcs8} media type is supported.
36   *         PKCS#8 object in the data section MUST be DER-ASN.1-encoded {@code PrivateKeyInfo}.</li>
37   *    <li> For public keys, only {@code application/x-pem-file} media type is supported.
38   *         The public key in the PEM must be DER-ASN.1-encoded {@code SubjectPublicKeyInfo}.</li>
39   * </ul>
40   *
41   * @see <a href="https://tools.ietf.org/html/rfc2397">RFC 2397 The "data" uri scheme</a>
42   * @see DataUriUtil
43   */
44  public class DataUriKeyReader implements KeyReader {
45      public static final String DATA_URI_PKCS8_HEADER = "data:application/pkcs8;";
46      public static final String DATA_URI_PEM_HEADER = "data:application/x-pem-file;";
47  
48      private final Provider provider;
49  
50      public DataUriKeyReader() {
51          this(SecurityProvider.getProvider());
52      }
53  
54      public DataUriKeyReader(Provider provider) {
55          this.provider = provider;
56      }
57  
58      @Override
59      public PrivateKey readPrivateKey(Reader reader) throws CannotRetrieveKeyException {
60          try {
61              String dataUri = IOUtils.toString(reader);
62              if (dataUri.startsWith(DATA_URI_PKCS8_HEADER)) {
63                  byte[] keyData = DataUriUtil.getKeyData(dataUri);
64                  AlgorithmIdentifier algorithmIdentifier = PrivateKeyInfo.getInstance(keyData).getPrivateKeyAlgorithm();
65                  KeyFactory keyFactory = KeyFactory.getInstance(algorithmIdentifier.getAlgorithm().getId(), provider);
66                  return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyData));
67              } else {
68                  throw new CannotRetrieveKeyException("Data uri could not be parsed due to unexpected prefix");
69              }
70          } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
71              throw new CannotRetrieveKeyException("Error reading private key, unknown key type", e);
72          } catch (IOException | IllegalArgumentException e) {
73              throw new CannotRetrieveKeyException("Error reading private key", e);
74          }
75      }
76  
77      @Override
78      public PublicKey readPublicKey(Reader reader) throws CannotRetrieveKeyException {
79          try {
80              String dataUri = IOUtils.toString(reader);
81              if (dataUri.startsWith(DATA_URI_PEM_HEADER)) {
82                  byte[] keyData = DataUriUtil.getKeyData(dataUri);
83                  PEMParser pemParser = new PEMParser(new StringReader(new String(keyData, StandardCharsets.US_ASCII)));
84                  SubjectPublicKeyInfo pub = SubjectPublicKeyInfo.getInstance(pemParser.readObject());
85                  JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(provider);
86                  return converter.getPublicKey(pub);
87              } else {
88                  throw new CannotRetrieveKeyException("Data uri could not be parsed due to unexpected prefix");
89              }
90          } catch (IOException | IllegalArgumentException e) {
91              throw new CannotRetrieveKeyException("Error reading Public key", e);
92          }
93      }
94  }