-
Notifications
You must be signed in to change notification settings - Fork 160
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #140 from jenkinsci/feature/whitelist-hmac
Whitelist and HMAC #139
- Loading branch information
Showing
21 changed files
with
624 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
src/main/java/org/jenkinsci/plugins/gwt/global/CredentialsHelper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package org.jenkinsci.plugins.gwt.global; | ||
|
||
import static com.google.common.base.Optional.absent; | ||
import static com.google.common.base.Optional.fromNullable; | ||
import static com.google.common.base.Strings.isNullOrEmpty; | ||
|
||
import com.cloudbees.plugins.credentials.CredentialsMatcher; | ||
import com.cloudbees.plugins.credentials.CredentialsMatchers; | ||
import com.cloudbees.plugins.credentials.CredentialsProvider; | ||
import com.cloudbees.plugins.credentials.common.StandardListBoxModel; | ||
import com.cloudbees.plugins.credentials.domains.DomainRequirement; | ||
import com.google.common.base.Optional; | ||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; | ||
import hudson.model.Item; | ||
import hudson.model.Queue; | ||
import hudson.model.queue.Tasks; | ||
import hudson.security.ACL; | ||
import hudson.util.FormValidation; | ||
import hudson.util.ListBoxModel; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import jenkins.model.Jenkins; | ||
import org.acegisecurity.Authentication; | ||
import org.jenkinsci.plugins.plaincredentials.StringCredentials; | ||
|
||
public class CredentialsHelper { | ||
|
||
@SuppressFBWarnings("NP_NULL_PARAM_DEREF") | ||
public static ListBoxModel doFillCredentialsIdItems(final Item item, final String credentialsId) { | ||
final StandardListBoxModel result = new StandardListBoxModel(); | ||
if (item == null) { | ||
if (!Jenkins.getInstance().hasPermission(Jenkins.ADMINISTER)) { | ||
return result.includeCurrentValue(credentialsId); | ||
} | ||
} else { | ||
if (!item.hasPermission(Item.EXTENDED_READ) | ||
&& !item.hasPermission(CredentialsProvider.USE_ITEM)) { | ||
return result.includeCurrentValue(credentialsId); | ||
} | ||
} | ||
return result // | ||
.includeEmptyValue() // | ||
.includeMatchingAs( | ||
item instanceof Queue.Task ? Tasks.getAuthenticationOf((Queue.Task) item) : ACL.SYSTEM, | ||
item, | ||
StringCredentials.class, | ||
new ArrayList<DomainRequirement>(), | ||
CredentialsMatchers.anyOf(CredentialsMatchers.instanceOf(StringCredentials.class))) | ||
.includeCurrentValue(credentialsId); | ||
} | ||
|
||
public static FormValidation doCheckFillCredentialsId(final String credentialsId) { | ||
if (isNullOrEmpty(credentialsId)) { | ||
return FormValidation.ok(); | ||
} | ||
if (!findCredentials(credentialsId).isPresent()) { | ||
return FormValidation.error("Cannot find currently selected credentials"); | ||
} | ||
return FormValidation.ok(); | ||
} | ||
|
||
public static Optional<StringCredentials> findCredentials(final String credentialsId) { | ||
if (isNullOrEmpty(credentialsId)) { | ||
return absent(); | ||
} | ||
final Item item2 = null; | ||
final Authentication authentication = null; | ||
final ArrayList<DomainRequirement> domainRequirements = new ArrayList<DomainRequirement>(); | ||
final List<StringCredentials> lookupCredentials = | ||
CredentialsProvider.lookupCredentials( | ||
StringCredentials.class, item2, authentication, domainRequirements); | ||
final CredentialsMatcher allOf = | ||
CredentialsMatchers.allOf( | ||
CredentialsMatchers.withId(credentialsId), | ||
CredentialsMatchers.anyOf(CredentialsMatchers.instanceOf(StringCredentials.class))); | ||
return fromNullable(CredentialsMatchers.firstOrNull(lookupCredentials, allOf)); | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
src/main/java/org/jenkinsci/plugins/gwt/global/Whitelist.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package org.jenkinsci.plugins.gwt.global; | ||
|
||
import com.google.common.annotations.VisibleForTesting; | ||
import hudson.Extension; | ||
import java.io.Serializable; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import jenkins.model.GlobalConfiguration; | ||
import net.sf.json.JSONObject; | ||
import org.kohsuke.stapler.DataBoundSetter; | ||
import org.kohsuke.stapler.StaplerRequest; | ||
|
||
@Extension | ||
public class Whitelist extends GlobalConfiguration implements Serializable { | ||
|
||
private static final long serialVersionUID = -2832851253933848205L; | ||
|
||
public static Whitelist get() { | ||
return GlobalConfiguration.all().get(Whitelist.class); | ||
} | ||
|
||
private boolean enabled; | ||
private List<WhitelistItem> whitelistItems = new ArrayList<>(); | ||
|
||
@VisibleForTesting | ||
public Whitelist(final boolean enabled, final List<WhitelistItem> whitelistItems) { | ||
this.enabled = enabled; | ||
this.whitelistItems = whitelistItems; | ||
} | ||
|
||
public Whitelist() { | ||
load(); | ||
} | ||
|
||
@Override | ||
public boolean configure(final StaplerRequest req, final JSONObject json) throws FormException { | ||
req.bindJSON(this, json); | ||
save(); | ||
return true; | ||
} | ||
|
||
@DataBoundSetter | ||
public void setEnabled(final boolean enabled) { | ||
this.enabled = enabled; | ||
} | ||
|
||
public boolean isEnabled() { | ||
return enabled; | ||
} | ||
|
||
@DataBoundSetter | ||
public void setWhitelistItems(final List<WhitelistItem> whitelistItems) { | ||
this.whitelistItems = whitelistItems; | ||
} | ||
|
||
public List<WhitelistItem> getWhitelistItems() { | ||
return whitelistItems; | ||
} | ||
} |
104 changes: 104 additions & 0 deletions
104
src/main/java/org/jenkinsci/plugins/gwt/global/WhitelistItem.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package org.jenkinsci.plugins.gwt.global; | ||
|
||
import edu.umd.cs.findbugs.annotations.NonNull; | ||
import hudson.Extension; | ||
import hudson.model.AbstractDescribableImpl; | ||
import hudson.model.Descriptor; | ||
import hudson.model.Item; | ||
import hudson.util.FormValidation; | ||
import hudson.util.ListBoxModel; | ||
import java.io.Serializable; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import org.kohsuke.stapler.AncestorInPath; | ||
import org.kohsuke.stapler.DataBoundConstructor; | ||
import org.kohsuke.stapler.DataBoundSetter; | ||
import org.kohsuke.stapler.QueryParameter; | ||
|
||
public class WhitelistItem extends AbstractDescribableImpl<WhitelistItem> implements Serializable { | ||
|
||
public static final String HMAC_MD5 = "HmacMD5"; | ||
public static final String HMAC_SHA1 = "HmacSHA1"; | ||
public static final String HMAC_SHA256 = "HmacSHA256"; | ||
private static final long serialVersionUID = 1176246137502450635L; | ||
private String host; | ||
private boolean hmacEnabled; | ||
private String hmacHeader; | ||
private String hmacCredentialId; | ||
private String hmacAlgorithm; | ||
private static final List<String> MAC_ALGORITHMS = | ||
Arrays.asList(HMAC_MD5, HMAC_SHA1, HMAC_SHA256); | ||
|
||
public WhitelistItem() {} | ||
|
||
@DataBoundConstructor | ||
public WhitelistItem(final String host) { | ||
this.host = host; | ||
} | ||
|
||
public String getHost() { | ||
return host; | ||
} | ||
|
||
public String getHmacAlgorithm() { | ||
return hmacAlgorithm; | ||
} | ||
|
||
@DataBoundSetter | ||
public void setHmacAlgorithm(final String hmacAlgorithm) { | ||
this.hmacAlgorithm = hmacAlgorithm; | ||
} | ||
|
||
public String getHmacCredentialId() { | ||
return hmacCredentialId; | ||
} | ||
|
||
@DataBoundSetter | ||
public void setHmacCredentialId(final String hmacCredentialId) { | ||
this.hmacCredentialId = hmacCredentialId; | ||
} | ||
|
||
public boolean isHmacEnabled() { | ||
return hmacEnabled; | ||
} | ||
|
||
@DataBoundSetter | ||
public void setHmacEnabled(final boolean hmacEnabled) { | ||
this.hmacEnabled = hmacEnabled; | ||
} | ||
|
||
public String getHmacHeader() { | ||
return hmacHeader; | ||
} | ||
|
||
@DataBoundSetter | ||
public void setHmacHeader(final String hmacHeader) { | ||
this.hmacHeader = hmacHeader; | ||
} | ||
|
||
@Extension | ||
public static class DescriptorImpl extends Descriptor<WhitelistItem> { | ||
@NonNull | ||
@Override | ||
public String getDisplayName() { | ||
return "Whitelist item"; | ||
} | ||
|
||
public ListBoxModel doFillHmacAlgorithmItems() { | ||
final ListBoxModel listBoxModel = new ListBoxModel(); | ||
for (final String a : MAC_ALGORITHMS) { | ||
listBoxModel.add(a); | ||
} | ||
return listBoxModel; | ||
} | ||
|
||
public ListBoxModel doFillHmacCredentialIdItems( | ||
@AncestorInPath final Item item, @QueryParameter final String credentialsId) { | ||
return CredentialsHelper.doFillCredentialsIdItems(item, credentialsId); | ||
} | ||
|
||
public FormValidation doCheckHmacCredentialIdItems(@QueryParameter final String value) { | ||
return CredentialsHelper.doCheckFillCredentialsId(value); | ||
} | ||
} | ||
} |
69 changes: 69 additions & 0 deletions
69
src/main/java/org/jenkinsci/plugins/gwt/whitelist/HMACVerifier.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package org.jenkinsci.plugins.gwt.whitelist; | ||
|
||
import static java.nio.charset.StandardCharsets.UTF_8; | ||
|
||
import java.io.UnsupportedEncodingException; | ||
import java.security.InvalidKeyException; | ||
import java.security.NoSuchAlgorithmException; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Map.Entry; | ||
import javax.crypto.Mac; | ||
import javax.crypto.spec.SecretKeySpec; | ||
|
||
public class HMACVerifier { | ||
|
||
public static boolean hmacVerify( | ||
final Map<String, List<String>> headers, | ||
final String postContent, | ||
final String hmacHeader, | ||
final String hmacSecret, | ||
final String algorithm) { | ||
final String headerValue = getHeaderValue(hmacHeader, headers); | ||
final String calculateHmac = getCalculatedHmac(postContent, hmacSecret, algorithm); | ||
return headerValue.equalsIgnoreCase(calculateHmac); | ||
} | ||
|
||
private static String getCalculatedHmac( | ||
final String postContent, final String hmacSecret, final String algorithm) { | ||
try { | ||
final byte[] byteKey = hmacSecret.getBytes("UTF-8"); | ||
final Mac sha512_HMAC = Mac.getInstance(algorithm); | ||
final SecretKeySpec keySpec = new SecretKeySpec(byteKey, algorithm); | ||
sha512_HMAC.init(keySpec); | ||
final byte[] mac_data = sha512_HMAC.doFinal(postContent.getBytes(UTF_8)); | ||
return bytesToHex(mac_data); | ||
} catch (UnsupportedEncodingException | NoSuchAlgorithmException | InvalidKeyException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
private static String bytesToHex(final byte[] bytes) { | ||
final char[] hexArray = "0123456789ABCDEF".toCharArray(); | ||
final char[] hexChars = new char[bytes.length * 2]; | ||
for (int j = 0; j < bytes.length; j++) { | ||
final int v = bytes[j] & 0xFF; | ||
hexChars[j * 2] = hexArray[v >>> 4]; | ||
hexChars[j * 2 + 1] = hexArray[v & 0x0F]; | ||
} | ||
return new String(hexChars); | ||
} | ||
|
||
private static String getHeaderValue( | ||
final String hmacHeader, final Map<String, List<String>> headers) { | ||
for (final Entry<String, List<String>> ck : headers.entrySet()) { | ||
final boolean sameHeader = ck.getKey().equalsIgnoreCase(hmacHeader); | ||
final boolean oneValue = ck.getValue().size() == 1; | ||
if (sameHeader && oneValue) { | ||
final String value = ck.getValue().get(0); | ||
if (value.contains("=")) { | ||
// To handle X-Hub-Signature: sha256=87e3e7... | ||
return value.split("=")[1]; | ||
} | ||
return value; | ||
} | ||
} | ||
throw new RuntimeException( | ||
"Was unable to find header with name \"" + hmacHeader + "\" among " + headers); | ||
} | ||
} |
Oops, something went wrong.