{"action":"created","comment":{"url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/pulls/comments/66042705","id":66042705,"diff_hunk":"@@ -0,0 +1,400 @@\n+/*\n+ * The MIT License\n+ *\n+ * Copyright (c) 2016, CloudBees, Inc.\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the \"Software\"), to deal\n+ * in the Software without restriction, including without limitation the rights\n+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n+ * copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n+ * THE SOFTWARE.\n+ */\n+\n+package jenkins.bouncycastle.api;\n+\n+import java.io.File;\n+import java.io.IOException;\n+import java.io.StringReader;\n+import java.io.StringWriter;\n+import java.nio.charset.StandardCharsets;\n+import java.security.Key;\n+import java.security.KeyPair;\n+import java.security.MessageDigest;\n+import java.security.NoSuchAlgorithmException;\n+import java.security.PrivateKey;\n+import java.security.PublicKey;\n+import java.security.cert.Certificate;\n+\n+import javax.annotation.CheckForNull;\n+import javax.annotation.Nonnull;\n+import javax.annotation.Nullable;\n+\n+import org.apache.commons.codec.binary.Hex;\n+import org.apache.commons.io.FileUtils;\n+import org.bouncycastle.openssl.PEMReader;\n+import org.bouncycastle.openssl.PEMWriter;\n+import org.bouncycastle.openssl.PasswordException;\n+import org.bouncycastle.openssl.PasswordFinder;\n+import org.bouncycastle.util.encoders.Base64;\n+\n+/**\n+ * A class that provides an API to manager PEM format, providing additional methods to handle Keys, Certificates,\n+ * Fingerprints, etc The supported algorithms will depend on the underlying version of BouncyCastle\n+ *\n+ * @since 1.0\n+ */\n+public class PEMManager {\n+\n+    /**\n+     * Stores the internal Bouncy Castle or JCA object\n+     */\n+    private Object object;\n+\n+    /**\n+     * Creates a {@link PEMManager} by reading a PEM file\n+     * \n+     * @param pemFile {@link File} pointing to the PEM file to read\n+     * @throws IOException launched if a problem exists reading the PEM information or the {@link File}\n+     * @throws BCPasswordException in case PEM is passphrase protected and none or wrong is provided\n+     */\n+    public PEMManager(@Nonnull File pemFile) throws IOException {\n+        decode(pemFile, null);\n+    }\n+\n+    /**\n+     * Creates a {@link PEMManager} by reading a PEM file\n+     * \n+     * @param pemFile {@link File} pointing to the PEM file to read\n+     * @param passphrase passphrase for the encrypted PEM data. null if PEM data is not passphrase protected\n+     * @throws IOException launched if a problem exists reading the PEM information or the {@link File}\n+     * @throws BCPasswordException in case PEM is passphrase protected and none or wrong is provided\n+     */\n+    public PEMManager(@Nonnull File pemFile, @Nullable String passphrase) throws IOException {\n+        decode(pemFile, passphrase);\n+    }\n+\n+    /**\n+     * Creates a {@link PEMManager} by reading PEM formated data from a {@link String}\n+     * \n+     * @param pem {@link String} with the PEM data\n+     * @throws IOException launched if a problem exists reading the PEM information\n+     * @throws BCPasswordException in case PEM is passphrase protected and none or wrong is provided\n+     */\n+    public PEMManager(@Nonnull String pem) throws IOException {\n+        decode(pem, null);\n+    }\n+\n+    /**\n+     * Creates a {@link PEMManager} by reading PEM formated data from a {@link String}\n+     * \n+     * @param pem {@link String} with the PEM data\n+     * @param passphrase passphrase for the encrypted PEM data. null if PEM data is not passphrase protected\n+     * @throws IOException launched if a problem exists reading the PEM information\n+     * @throws BCPasswordException in case PEM is passphrase protected and none or wrong is provided\n+     */\n+    public PEMManager(@Nonnull String pem, @Nullable String passphrase) throws IOException {\n+        decode(pem, passphrase);\n+    }\n+\n+    /**\n+     *\n+     * Creates a {@link PEMManager} from an {@link Object} that can be of any supported type by Bouncy Castle or JCA:\n+     * {@link Key}, {@link PrivateKey}, {@link KeyPair}, {@link Certificate}, etc.\n+     *\n+     * @param pemObject object to manage\n+     */\n+    public PEMManager(@Nonnull Object pemObject) {\n+        this.object = pemObject;\n+    }\n+\n+    private void decode(@Nonnull File pemFile, @Nullable String passphrase) throws IOException {\n+        decode(FileUtils.readFileToString(pemFile), passphrase);\n+    }\n+\n+    private void decode(@Nonnull String pem, @Nullable final String passphrase) throws IOException {\n+        PasswordFinder pwf = null;\n+        if (passphrase != null) {\n+            pwf = new PasswordFinder() {\n+                @Override\n+                public char[] getPassword() {\n+                    return passphrase.toCharArray();\n+                }\n+            };\n+        }\n+\n+        PEMReader parser = new PEMReader(new StringReader(pem), pwf);\n+        try {\n+            object = parser.readObject();\n+        } catch (PasswordException pwE) {\n+            throw new BCPasswordException(pwE);\n+        } finally {\n+            parser.close();\n+        }\n+    }\n+\n+    /**\n+     * Encodes the current stored information in PEM format and returns it as a {@link String}\n+     * \n+     * @return PEM encoded data\n+     * @throws IOException launched if a problem exists generating the PEM information\n+     */\n+    @Nonnull\n+    public String encode() throws IOException {\n+        StringWriter sw = new StringWriter();\n+        PEMWriter w = new PEMWriter(sw);\n+        try {\n+            w.writeObject(object);\n+        } finally {\n+            w.close();\n+        }\n+        return sw.toString();\n+    }\n+\n+    /**\n+     * Encodes the current stored information in PEM formated {@link File}\n+     * \n+     * @param pemFile PEM {@link File} to read\n+     * \n+     * @throws IOException launched if a problem exists generating the PEM information or writing the {@link File}\n+     */\n+    public void encode(@Nonnull File pemFile) throws IOException {\n+        FileUtils.writeStringToFile(pemFile, encode());\n+    }\n+\n+    /**\n+     * Obtain {@link KeyPair} object with the public and private key from the read PEM. No conversion is performed, the\n+     * read PEM must contain private and public key in order to obtain a {@link KeyPair} object, null will be returned\n+     * in all the other cases.\n+     * \n+     * @return {@link KeyPair} object with public and private keys or null if the read PEM didn't contain private and\n+     * public keys.\n+     */\n+    @CheckForNull\n+    public KeyPair toKeyPair() {\n+        if (object instanceof KeyPair) {\n+            return (KeyPair) object;\n+        } // We will need conversion here on BC 1.54\n+        return null;\n+    }\n+\n+    /**\n+     * Obtain {@link PublicKey} object from the read PEM. If the PEM data contained other object type like\n+     * {@link KeyPair} or {@link Certificate}, the public key will be extracted from them.\n+     * \n+     * @return {@link PublicKey} with the public key, null if a public key could not be obtained from the current data\n+     */\n+    @CheckForNull\n+    public PublicKey toPublicKey() {\n+        if (object instanceof PublicKey) {\n+            return (PublicKey) object;\n+        } else if (object instanceof KeyPair) {\n+            return ((KeyPair) object).getPublic();\n+        } else if (object instanceof Certificate) {\n+            return ((Certificate) object).getPublicKey();\n+        }\n+        return null;\n+    }\n+\n+    /**\n+     * Obtain {@link Certificate} object from the read PEM.\n+     * \n+     * @return {@link Certificate} with the certificate, null if a certificate could not be obtained from the current\n+     * data\n+     */\n+    @CheckForNull\n+    public Certificate toCertificate() {\n+        if (object instanceof Certificate) {\n+            return ((Certificate) object);\n+        }\n+        return null;\n+    }\n+\n+    /**\n+     * Obtain {@link PrivateKey} object from the read PEM. If the PEM data contained other object type like\n+     * {@link KeyPair}, the private key will be extracted from them.\n+     * \n+     * @return {@link PrivateKey} with the private key, null if a private key could not be obtained from the current\n+     * data\n+     */\n+    @CheckForNull\n+    public PrivateKey toPrivateKey() {\n+        if (object instanceof PrivateKey) {\n+            return (PrivateKey) object;\n+        } else if (object instanceof KeyPair) {\n+            return ((KeyPair) object).getPrivate();\n+        }\n+        return null;\n+    }\n+\n+    /**\n+     * Obtains raw JCA or BouncyCastle {@link Object} from the read PEM. Depending on the PEM nature or the object\n+     * passed to the {@link #PEMManager(Object pemObject)}, the returned object can be one of the following (not\n+     * exhaustive list) and any classes that inherit from them:\n+     * <ul>\n+     * <li><strong>JCA</strong>\n+     * <ul>\n+     * <li>{@link Certificate}\n+     * <li>{@link java.security.cert.CRL}\n+     * <li>{@link KeyPair}\n+     * <li>{@link PublicKey}\n+     * <li>{@link PrivateKey}\n+     * </ul>\n+     * </ul>\n+     * <ul>\n+     * <li><strong>Bouncy Castle</strong>\n+     * <ul>\n+     * <li>{@link org.bouncycastle.asn1.cms.ContentInfo}\n+     * <li>{@link org.bouncycastle.jce.spec.ECNamedCurveParameterSpec}\n+     * <li>{@link org.bouncycastle.pkcs.PKCS10CertificationRequest}\n+     * <li>{@link org.bouncycastle.jce.provider.X509CertificateObject}\n+     * <li>{@link org.bouncycastle.x509.X509V2AttributeCertificate}\n+     * </ul>\n+     * </ul>\n+     * \n+     * @return {@link Object} read from the PEM\n+     */\n+    @CheckForNull\n+    public Object getRawObject() {\n+        return object;\n+    }\n+\n+    /**\n+     * Obtains the fingerprint of the private key in the \"ab:cd:ef:...:12\" format, which basically is an SHA1 digest\n+     * from the key, encoded in hex format.\n+     * \n+     * @return private key fingerprint in hex format \"ab:cd:ef:...:12\", null if the private key could not be obtained\n+     * from the current PEM data.\n+     * @throws IOException thrown if a problem exists creating the fingerprint\n+     */\n+    @CheckForNull\n+    public String getPrivateKeyFingerprint() throws IOException {\n+        PrivateKey key = toPrivateKey();\n+        if (key == null) {\n+            return null;\n+        }\n+        return hexEncode(getKeyDigestSHA1(key));\n+    }\n+\n+    /**\n+     * Obtains the fingerprint of the public key in the \"ab:cd:ef:...:12\" format, which basically is an MD5 digest from\n+     * the key, encoded in hex format.\n+     * \n+     * @return public key fingerprint in hex format \"ab:cd:ef:...:12\", null if the public key could not be obtained from\n+     * the current PEM data.\n+     * @throws IOException if a problem exists creating the fingerprint\n+     */\n+    @CheckForNull\n+    public String getPublicKeyFingerprint() throws IOException {\n+        PublicKey key = toPublicKey();\n+        if (key == null) {\n+            return null;\n+        }\n+        return hexEncode(getKeyDigestMD5(key));\n+    }\n+\n+    /**\n+     * Generates an SHA1 digest from a Key object\n+     * \n+     * @param k the key to generate the digest from\n+     * @return the generated digest\n+     * @throws IOException if a problem exists creating the digest\n+     * @see <a href=\"https://docs.oracle.com/javase/7/docs/api/java/security/MessageDigest.html\"> JDK Digest\n+     * Algorithms</a>\n+     */\n+    @Nonnull\n+    public static byte[] getKeyDigestSHA1(@Nonnull Key k) {\n+        try {\n+            return getKeyDigest(k, \"SHA1\");\n+        } catch (NoSuchAlgorithmException e) {\n+            throw new AssertionError(\"SHA1 algorithm not found to create digest\");\n+        }\n+    }\n+\n+    /**\n+     * Generates an MD5 digest from a Key object\n+     * \n+     * @param k the key to generate the digest from\n+     * @return the generated digest\n+     * @throws IOException if a problem exists creating the digest","path":"src/main/java/jenkins/bouncycastle/api/PEMManager.java","position":330,"original_position":330,"commit_id":"f07b7608fa0e33bd56d20038a582859916ae899e","original_commit_id":"f07b7608fa0e33bd56d20038a582859916ae899e","user":{"login":"stephenc","id":209336,"avatar_url":"https://avatars.githubusercontent.com/u/209336?v=3","gravatar_id":"","url":"https://api.github.com/users/stephenc","html_url":"https://github.com/stephenc","followers_url":"https://api.github.com/users/stephenc/followers","following_url":"https://api.github.com/users/stephenc/following{/other_user}","gists_url":"https://api.github.com/users/stephenc/gists{/gist_id}","starred_url":"https://api.github.com/users/stephenc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stephenc/subscriptions","organizations_url":"https://api.github.com/users/stephenc/orgs","repos_url":"https://api.github.com/users/stephenc/repos","events_url":"https://api.github.com/users/stephenc/events{/privacy}","received_events_url":"https://api.github.com/users/stephenc/received_events","type":"User","site_admin":false},"body":"invalid javadoc","created_at":"2016-06-07T10:04:39Z","updated_at":"2016-06-07T10:04:39Z","html_url":"https://github.com/jenkinsci/bouncycastle-api/pull/1#discussion_r66042705","pull_request_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/pulls/1","_links":{"self":{"href":"https://api.github.com/repos/jenkinsci/bouncycastle-api/pulls/comments/66042705"},"html":{"href":"https://github.com/jenkinsci/bouncycastle-api/pull/1#discussion_r66042705"},"pull_request":{"href":"https://api.github.com/repos/jenkinsci/bouncycastle-api/pulls/1"}}},"pull_request":{"url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/pulls/1","id":72685424,"html_url":"https://github.com/jenkinsci/bouncycastle-api/pull/1","diff_url":"https://github.com/jenkinsci/bouncycastle-api/pull/1.diff","patch_url":"https://github.com/jenkinsci/bouncycastle-api/pull/1.patch","issue_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/issues/1","number":1,"state":"open","locked":false,"title":"[JENKINS-35291] Initial API for BC 1.47","user":{"login":"alvarolobato","id":17157380,"avatar_url":"https://avatars.githubusercontent.com/u/17157380?v=3","gravatar_id":"","url":"https://api.github.com/users/alvarolobato","html_url":"https://github.com/alvarolobato","followers_url":"https://api.github.com/users/alvarolobato/followers","following_url":"https://api.github.com/users/alvarolobato/following{/other_user}","gists_url":"https://api.github.com/users/alvarolobato/gists{/gist_id}","starred_url":"https://api.github.com/users/alvarolobato/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/alvarolobato/subscriptions","organizations_url":"https://api.github.com/users/alvarolobato/orgs","repos_url":"https://api.github.com/users/alvarolobato/repos","events_url":"https://api.github.com/users/alvarolobato/events{/privacy}","received_events_url":"https://api.github.com/users/alvarolobato/received_events","type":"User","site_admin":false},"body":"[JENKINS-35291](https://issues.jenkins-ci.org/browse/JENKINS-35291)\r\n\r\nDependency to multiple Bouncy Castle versions from jenkins core and plugins is causing problems due to the binary incompatibility between versions, the different supported algorithms, etc.\r\n\r\nOn Jenkins core 1.648, Bouncy Castle was bumped from version `1.47` to `1.54` as a result of a change in ``instance-identity`` module\r\n* ``instance-identity-module`` bumped Bouncy Castle from `1.47` to `1.54`: [pom.xml (v 1.4)](https://github.com/jenkinsci/instance-identity-module/blob/instance-identity-1.4/pom.xml#L32) -> [pom.xml (v 1.5.1)](https://github.com/jenkinsci/instance-identity-module/blob/instance-identity-1.5.1/pom.xml#L33)\r\n* Jenkins core bumped instance-identity-plugin:  [pom.xml (v 1.647) ](https://github.com/jenkinsci/jenkins/blob/stable-1.647/war/pom.xml#L107)-> [pom.xml (v 1.648)](https://github.com/jenkinsci/jenkins/blob/jenkins-1.648/war/pom.xml#L100)\r\n\r\n**Problems found:**\r\n* Plugins running in Jenkins < 1.648 get 1.47 from the parent class loader (in the default class loading strategy). If they include a later version in their own classpath, only \"new\" classes are seen from their referenced JAR.\r\n* Plugins running in Jenkins >= 1.648 get 1.54 from the parent class loader (in the default class loading strategy). If they include an earlier version in their own classpath, only \"removed\" classes are seen from their referenced JAR.\r\n* If the plugin is using JCA instead of BC classes directly usually the plugin works, but if ran in an BC 1.47 Jenkins core, there will be missing algorithms.\r\n* Plugins are introducing different versions of BC which adds to de mix, potentially producing unpredictable results\r\n\r\n**Some considerations:**\r\n* Many plugins are only introducing BC dependency to do PEM encoding/decoding. This API got changed by the BC bump.\r\n* instance-identity-module is using BC only for PEM encoding/decoding, removing this dependency from the core is possible\r\n\r\n**Proposed solution**\r\nA possible solution to this problem would to create a plugin (this plugin) from which plugins using BC will depend. Responsible for:\r\n* Load BC into uber class loader.\r\n* Register BC as a JVM security provider in order to allow other plugins to use JCA API with BC algorithms. \r\n* Provide an API to do common tasks like PEM Encoding/Decoding ensuring its stability among BC versions.\r\n\r\nThe implementation of this plugin does not expose any classes from BC, only JCA, allowing plugins to not depend on the specifics of BC.\r\n\r\nLater some other actions would have to be taken in order to fully solve this situation, like having one version of the plugin for each version of BC, etc.","created_at":"2016-06-06T10:12:15Z","updated_at":"2016-06-07T10:04:39Z","closed_at":null,"merged_at":null,"merge_commit_sha":"9155b3d7d0bb126ec2d4e2b21f7ee4de9a96e728","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/pulls/1/commits","review_comments_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/pulls/1/comments","review_comment_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/pulls/comments{/number}","comments_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/issues/1/comments","statuses_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/statuses/f07b7608fa0e33bd56d20038a582859916ae899e","head":{"label":"alvarolobato:BC_API_1_47","ref":"BC_API_1_47","sha":"f07b7608fa0e33bd56d20038a582859916ae899e","user":{"login":"alvarolobato","id":17157380,"avatar_url":"https://avatars.githubusercontent.com/u/17157380?v=3","gravatar_id":"","url":"https://api.github.com/users/alvarolobato","html_url":"https://github.com/alvarolobato","followers_url":"https://api.github.com/users/alvarolobato/followers","following_url":"https://api.github.com/users/alvarolobato/following{/other_user}","gists_url":"https://api.github.com/users/alvarolobato/gists{/gist_id}","starred_url":"https://api.github.com/users/alvarolobato/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/alvarolobato/subscriptions","organizations_url":"https://api.github.com/users/alvarolobato/orgs","repos_url":"https://api.github.com/users/alvarolobato/repos","events_url":"https://api.github.com/users/alvarolobato/events{/privacy}","received_events_url":"https://api.github.com/users/alvarolobato/received_events","type":"User","site_admin":false},"repo":{"id":60322120,"name":"bouncycastle-api","full_name":"alvarolobato/bouncycastle-api","owner":{"login":"alvarolobato","id":17157380,"avatar_url":"https://avatars.githubusercontent.com/u/17157380?v=3","gravatar_id":"","url":"https://api.github.com/users/alvarolobato","html_url":"https://github.com/alvarolobato","followers_url":"https://api.github.com/users/alvarolobato/followers","following_url":"https://api.github.com/users/alvarolobato/following{/other_user}","gists_url":"https://api.github.com/users/alvarolobato/gists{/gist_id}","starred_url":"https://api.github.com/users/alvarolobato/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/alvarolobato/subscriptions","organizations_url":"https://api.github.com/users/alvarolobato/orgs","repos_url":"https://api.github.com/users/alvarolobato/repos","events_url":"https://api.github.com/users/alvarolobato/events{/privacy}","received_events_url":"https://api.github.com/users/alvarolobato/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/alvarolobato/bouncycastle-api","description":"Bouncy Castle API Plugin","fork":true,"url":"https://api.github.com/repos/alvarolobato/bouncycastle-api","forks_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/forks","keys_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/keys{/key_id}","collaborators_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/teams","hooks_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/hooks","issue_events_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/issues/events{/number}","events_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/events","assignees_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/assignees{/user}","branches_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/branches{/branch}","tags_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/tags","blobs_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/git/refs{/sha}","trees_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/git/trees{/sha}","statuses_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/statuses/{sha}","languages_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/languages","stargazers_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/stargazers","contributors_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/contributors","subscribers_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/subscribers","subscription_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/subscription","commits_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/commits{/sha}","git_commits_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/git/commits{/sha}","comments_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/comments{/number}","issue_comment_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/issues/comments{/number}","contents_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/contents/{+path}","compare_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/compare/{base}...{head}","merges_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/merges","archive_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/downloads","issues_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/issues{/number}","pulls_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/pulls{/number}","milestones_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/milestones{/number}","notifications_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/labels{/name}","releases_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/releases{/id}","deployments_url":"https://api.github.com/repos/alvarolobato/bouncycastle-api/deployments","created_at":"2016-06-03T05:59:18Z","updated_at":"2016-06-06T10:08:26Z","pushed_at":"2016-06-07T09:51:18Z","git_url":"git://github.com/alvarolobato/bouncycastle-api.git","ssh_url":"git@github.com:alvarolobato/bouncycastle-api.git","clone_url":"https://github.com/alvarolobato/bouncycastle-api.git","svn_url":"https://github.com/alvarolobato/bouncycastle-api","homepage":"","size":48,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"jenkinsci:master","ref":"master","sha":"78fd215cbad48e464cbbd8bf495f9135a41ce22b","user":{"login":"jenkinsci","id":107424,"avatar_url":"https://avatars.githubusercontent.com/u/107424?v=3","gravatar_id":"","url":"https://api.github.com/users/jenkinsci","html_url":"https://github.com/jenkinsci","followers_url":"https://api.github.com/users/jenkinsci/followers","following_url":"https://api.github.com/users/jenkinsci/following{/other_user}","gists_url":"https://api.github.com/users/jenkinsci/gists{/gist_id}","starred_url":"https://api.github.com/users/jenkinsci/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jenkinsci/subscriptions","organizations_url":"https://api.github.com/users/jenkinsci/orgs","repos_url":"https://api.github.com/users/jenkinsci/repos","events_url":"https://api.github.com/users/jenkinsci/events{/privacy}","received_events_url":"https://api.github.com/users/jenkinsci/received_events","type":"Organization","site_admin":false},"repo":{"id":60247502,"name":"bouncycastle-api","full_name":"jenkinsci/bouncycastle-api","owner":{"login":"jenkinsci","id":107424,"avatar_url":"https://avatars.githubusercontent.com/u/107424?v=3","gravatar_id":"","url":"https://api.github.com/users/jenkinsci","html_url":"https://github.com/jenkinsci","followers_url":"https://api.github.com/users/jenkinsci/followers","following_url":"https://api.github.com/users/jenkinsci/following{/other_user}","gists_url":"https://api.github.com/users/jenkinsci/gists{/gist_id}","starred_url":"https://api.github.com/users/jenkinsci/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jenkinsci/subscriptions","organizations_url":"https://api.github.com/users/jenkinsci/orgs","repos_url":"https://api.github.com/users/jenkinsci/repos","events_url":"https://api.github.com/users/jenkinsci/events{/privacy}","received_events_url":"https://api.github.com/users/jenkinsci/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/jenkinsci/bouncycastle-api","description":"","fork":false,"url":"https://api.github.com/repos/jenkinsci/bouncycastle-api","forks_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/forks","keys_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/teams","hooks_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/hooks","issue_events_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/issues/events{/number}","events_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/events","assignees_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/assignees{/user}","branches_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/branches{/branch}","tags_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/tags","blobs_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/git/refs{/sha}","trees_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/statuses/{sha}","languages_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/languages","stargazers_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/stargazers","contributors_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/contributors","subscribers_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/subscribers","subscription_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/subscription","commits_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/commits{/sha}","git_commits_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/git/commits{/sha}","comments_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/comments{/number}","issue_comment_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/issues/comments{/number}","contents_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/contents/{+path}","compare_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/merges","archive_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/downloads","issues_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/issues{/number}","pulls_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/pulls{/number}","milestones_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/milestones{/number}","notifications_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/labels{/name}","releases_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/releases{/id}","deployments_url":"https://api.github.com/repos/jenkinsci/bouncycastle-api/deployments","created_at":"2016-06-02T08:44:12Z","updated_at":"2016-06-03T05:37:12Z","pushed_at":"2016-06-07T09:51:18Z","git_url":"git://github.com/jenkinsci/bouncycastle-api.git","ssh_url":"git@github.com:jenkinsci/bouncycastle-api.git","clone_url":"https://github.com/jenkinsci/bouncycastle-api.git","svn_url":"https://github.com/jenkinsci/bouncycastle-api","homepage":"","size":0,"stargazers_count":1,"watchers_count":1,"language":null,"has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":1,"forks":1,"open_issues":1,"watchers":1,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/jenkinsci/bouncycastle-api/pulls/1"},"html":{"href":"https://github.com/jenkinsci/bouncycastle-api/pull/1"},"issue":{"href":"https://api.github.com/repos/jenkinsci/bouncycastle-api/issues/1"},"comments":{"href":"https://api.github.com/repos/jenkinsci/bouncycastle-api/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/jenkinsci/bouncycastle-api/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/jenkinsci/bouncycastle-api/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/jenkinsci/bouncycastle-api/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/jenkinsci/bouncycastle-api/statuses/f07b7608fa0e33bd56d20038a582859916ae899e"}}}}
{"actor":{"display_login":"stephenc"}}