Skip to content

Commit 0c4ae4a

Browse files
committed
Merge pull request #4 from niallmccullagh/master
Adding HMAC support
2 parents a213fd7 + 6605b4a commit 0c4ae4a

File tree

9 files changed

+512
-1
lines changed

9 files changed

+512
-1
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ Overview
3636

3737
* **httpsig-ssh-jsch**: Alternative implementation of SSH key support using the JSch API.
3838

39+
* **httpsig-hmac**: Provides a HMAC implementation with support for SHA-256 and SHA-512.
40+
3941
* **httpsig-test-common**: Provides several public/private SSH key pairs and utility methods for writing unit tests.
4042

4143
API

hmac/README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
Overview
2+
========
3+
4+
Provides a HMAC implementation with support for SHA-256 and SHA-512.
5+
6+
7+
API
8+
===
9+
10+
Signer
11+
------
12+
13+
To create a Signer, you must provide both a HMAC key and a KeyId. For example:
14+
15+
Signer signer = new Signer(
16+
new HmacKey("keyId", "ssh. its a secret"),
17+
new HmacKeyId()
18+
);
19+
20+
Verifier
21+
--------
22+
23+
The Verifier is the mechanism used by a server to verify the signature provided in
24+
the Authorization header against the Request and the Challenge defined for the server.
25+
26+
To create a Verifier, you must provide a HMAC and a KeyId. For example:
27+
28+
Verifier verifier = new Verifier(
29+
new DefaultKeychain(Arrays.asList(new HmacKey("keyId", "ssh. its a secret"))),
30+
new HmacKeyId()
31+
);
32+

hmac/pom.xml

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ This is free and unencumbered software released into the public domain.
4+
~
5+
~ Anyone is free to copy, modify, publish, use, compile, sell, or
6+
~ distribute this software, either in source code form or as a compiled
7+
~ binary, for any purpose, commercial or non-commercial, and by any
8+
~ means.
9+
~
10+
~ In jurisdictions that recognize copyright laws, the author or authors
11+
~ of this software dedicate any and all copyright interest in the
12+
~ software to the public domain. We make this dedication for the benefit
13+
~ of the public at large and to the detriment of our heirs and
14+
~ successors. We intend this dedication to be an overt act of
15+
~ relinquishment in perpetuity of all present and future rights to this
16+
~ software under copyright law.
17+
~
18+
~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19+
~ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20+
~ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21+
~ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
22+
~ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
23+
~ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24+
~ OTHER DEALINGS IN THE SOFTWARE.
25+
~
26+
~ For more information, please refer to <http://unlicense.org />
27+
-->
28+
29+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
30+
<modelVersion>4.0.0</modelVersion>
31+
32+
<parent>
33+
<groupId>net.adamcin.httpsig</groupId>
34+
<artifactId>httpsig</artifactId>
35+
<version>1.1.1-SNAPSHOT</version>
36+
<relativePath>..</relativePath>
37+
</parent>
38+
39+
<artifactId>httpsig-hmac</artifactId>
40+
<packaging>jar</packaging>
41+
42+
<name>${project.artifactId}</name>
43+
<description>
44+
Library providing a HMAC based implementation of the httpsig API
45+
</description>
46+
47+
<inceptionYear>2014</inceptionYear>
48+
49+
<scm>
50+
<url>https://github.com/adamcin/httpsig-java</url>
51+
<developerConnection>scm:git:git@github.com:adamcin/httpsig-java.git</developerConnection>
52+
<connection>scm:git://github.com/adamcin/httpsig-java.git</connection>
53+
<tag>HEAD</tag>
54+
</scm>
55+
56+
<build>
57+
<plugins>
58+
<plugin>
59+
<groupId>org.codehaus.mojo</groupId>
60+
<artifactId>templating-maven-plugin</artifactId>
61+
</plugin>
62+
<plugin>
63+
<groupId>org.apache.maven.plugins</groupId>
64+
<artifactId>maven-compiler-plugin</artifactId>
65+
</plugin>
66+
<plugin>
67+
<groupId>com.github.github</groupId>
68+
<artifactId>site-maven-plugin</artifactId>
69+
<configuration>
70+
<message>Creating site for ${project.artifactId} ${project.version}</message>
71+
<noJekyll>true</noJekyll>
72+
<repositoryOwner>adamcin</repositoryOwner>
73+
<repositoryName>httpsig-java</repositoryName>
74+
<path>${project.artifactId}</path>
75+
<merge>true</merge>
76+
</configuration>
77+
</plugin>
78+
</plugins>
79+
</build>
80+
81+
<profiles>
82+
<profile>
83+
<id>itests</id>
84+
<build>
85+
<plugins>
86+
<plugin>
87+
<groupId>org.apache.maven.plugins</groupId>
88+
<artifactId>maven-failsafe-plugin</artifactId>
89+
<version>2.14</version>
90+
<executions>
91+
<execution>
92+
<goals>
93+
<goal>integration-test</goal>
94+
<goal>verify</goal>
95+
</goals>
96+
</execution>
97+
</executions>
98+
</plugin>
99+
</plugins>
100+
</build>
101+
</profile>
102+
</profiles>
103+
104+
<dependencies>
105+
<dependency>
106+
<groupId>net.adamcin.httpsig</groupId>
107+
<artifactId>httpsig-api</artifactId>
108+
</dependency>
109+
<dependency>
110+
<groupId>org.slf4j</groupId>
111+
<artifactId>slf4j-api</artifactId>
112+
<scope>provided</scope>
113+
</dependency>
114+
<dependency>
115+
<groupId>biz.aQute.bnd</groupId>
116+
<artifactId>bndlib</artifactId>
117+
<scope>provided</scope>
118+
</dependency>
119+
120+
<dependency>
121+
<groupId>junit</groupId>
122+
<artifactId>junit</artifactId>
123+
<scope>test</scope>
124+
</dependency>
125+
<dependency>
126+
<groupId>net.adamcin.httpsig</groupId>
127+
<artifactId>httpsig-test-common</artifactId>
128+
</dependency>
129+
<dependency>
130+
<groupId>org.slf4j</groupId>
131+
<artifactId>slf4j-simple</artifactId>
132+
<scope>test</scope>
133+
</dependency>
134+
<dependency>
135+
<groupId>net.adamcin.commons</groupId>
136+
<artifactId>net.adamcin.commons.testing</artifactId>
137+
<scope>test</scope>
138+
</dependency>
139+
<dependency>
140+
<groupId>commons-io</groupId>
141+
<artifactId>commons-io</artifactId>
142+
<scope>test</scope>
143+
</dependency>
144+
<dependency>
145+
<groupId>org.hamcrest</groupId>
146+
<artifactId>hamcrest-all</artifactId>
147+
<version>1.3</version>
148+
<scope>test</scope>
149+
</dependency>
150+
151+
</dependencies>
152+
153+
<developers>
154+
<developer>
155+
<id>niallmccullagh</id>
156+
<name>Niall McCullagh</name>
157+
<url>https://github.com/niallmccullagh</url>
158+
</developer>
159+
</developers>
160+
161+
</project>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* This is free and unencumbered software released into the public domain.
3+
*
4+
* Anyone is free to copy, modify, publish, use, compile, sell, or
5+
* distribute this software, either in source code form or as a compiled
6+
* binary, for any purpose, commercial or non-commercial, and by any
7+
* means.
8+
*
9+
* In jurisdictions that recognize copyright laws, the author or authors
10+
* of this software dedicate any and all copyright interest in the
11+
* software to the public domain. We make this dedication for the benefit
12+
* of the public at large and to the detriment of our heirs and
13+
* successors. We intend this dedication to be an overt act of
14+
* relinquishment in perpetuity of all present and future rights to this
15+
* software under copyright law.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20+
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
21+
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
22+
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23+
* OTHER DEALINGS IN THE SOFTWARE.
24+
*
25+
* For more information, please refer to <http://unlicense.org/>
26+
*/
27+
28+
@Version("${package-info.version}")
29+
package net.adamcin.httpsig.hmac;
30+
31+
import aQute.bnd.annotation.Version;
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/*
2+
* This is free and unencumbered software released into the public domain.
3+
*
4+
* Anyone is free to copy, modify, publish, use, compile, sell, or
5+
* distribute this software, either in source code form or as a compiled
6+
* binary, for any purpose, commercial or non-commercial, and by any
7+
* means.
8+
*
9+
* In jurisdictions that recognize copyright laws, the author or authors
10+
* of this software dedicate any and all copyright interest in the
11+
* software to the public domain. We make this dedication for the benefit
12+
* of the public at large and to the detriment of our heirs and
13+
* successors. We intend this dedication to be an overt act of
14+
* relinquishment in perpetuity of all present and future rights to this
15+
* software under copyright law.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20+
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
21+
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
22+
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23+
* OTHER DEALINGS IN THE SOFTWARE.
24+
*
25+
* For more information, please refer to <http://unlicense.org/>
26+
*/
27+
28+
package net.adamcin.httpsig.hmac;
29+
30+
import net.adamcin.httpsig.api.Algorithm;
31+
import net.adamcin.httpsig.api.Key;
32+
import org.slf4j.Logger;
33+
import org.slf4j.LoggerFactory;
34+
35+
import javax.crypto.Mac;
36+
import javax.crypto.spec.SecretKeySpec;
37+
import java.security.InvalidKeyException;
38+
import java.security.NoSuchAlgorithmException;
39+
import java.util.Arrays;
40+
import java.util.HashSet;
41+
import java.util.Objects;
42+
import java.util.Set;
43+
44+
public class HmacKey implements Key {
45+
46+
private final String keyId;
47+
private final String secret;
48+
49+
private static final byte[] EMPTY_BYTES = new byte[0];
50+
private static final Logger LOGGER = LoggerFactory.getLogger(HmacKey.class);
51+
52+
/**
53+
* Instantiates a new HMAC key with an identifier and a secret used to sign
54+
* @param keyId The keys identifier
55+
* @param secret The secret used to sign
56+
*/
57+
public HmacKey(String keyId, String secret) {
58+
this.keyId = Objects.requireNonNull(keyId, "keyId must not be null");
59+
this.secret = Objects.requireNonNull(secret, "secret must not be null");
60+
}
61+
62+
/**
63+
* @return the {@link net.adamcin.httpsig.api.Key}'s self-identification. This may end up not being unique within a keychain.
64+
*/
65+
public String getId() {
66+
return keyId;
67+
}
68+
69+
/**
70+
* @return the {@link java.util.Set} of Signature {@link net.adamcin.httpsig.api.Algorithm}s supported by this key.
71+
*/
72+
public Set<Algorithm> getAlgorithms() {
73+
Set<Algorithm> algorithms = new HashSet<Algorithm>();
74+
75+
algorithms.add(Algorithm.HMAC_SHA512);
76+
algorithms.add(Algorithm.HMAC_SHA256);
77+
78+
return algorithms;
79+
}
80+
81+
/**
82+
* HMAC Keys can always be used to verify
83+
* @return always true
84+
*/
85+
public boolean canVerify() {
86+
return true;
87+
}
88+
89+
/**
90+
* Verifies the {@code signatureBytes} against the {@code challengeHash} using an underlying public key
91+
* @param algorithm the selected Signature {@link net.adamcin.httpsig.api.Algorithm}
92+
* @param contentBytes the result of {@link net.adamcin.httpsig.api.RequestContent#getContent(java.util.List, java.nio.charset.Charset)}
93+
* @param signatureBytes the result of {@link net.adamcin.httpsig.api.Authorization#getSignatureBytes()}
94+
* @return true if signature is valid
95+
*/
96+
public boolean verify(Algorithm algorithm, byte[] contentBytes, byte[] signatureBytes) {
97+
if(getAlgorithms().contains(algorithm)){
98+
byte[] generatedSig = sign(algorithm, contentBytes);
99+
return Arrays.equals(generatedSig, signatureBytes);
100+
}
101+
102+
return false;
103+
}
104+
105+
/**
106+
* Maps http-signatures spec algorithm names to Java
107+
* @param algorithm http-signature spec algorithm name
108+
* @return Java algorithm name
109+
*/
110+
private String mapAlgorithm(Algorithm algorithm) {
111+
if("hmac-sha512".equalsIgnoreCase(algorithm.getName())){
112+
return "HmacSHA512";
113+
}else if("hmac-sha256".equalsIgnoreCase(algorithm.getName())){
114+
return "HmacSHA256";
115+
}else{
116+
throw new IllegalArgumentException(String.format("Unsupported algorithm: %s", algorithm.getName()));
117+
}
118+
}
119+
120+
/**
121+
* HMAC Keys can always be used to sign
122+
* @return always true
123+
*/
124+
public boolean canSign() {
125+
return true;
126+
}
127+
128+
/**
129+
* Signs the {@code challengeHash} using the specified signature {@link net.adamcin.httpsig.api.Algorithm}
130+
* @param algorithm the selected Signature {@link net.adamcin.httpsig.api.Algorithm}
131+
* @param contentBytes the result of {@link net.adamcin.httpsig.api.RequestContent#getContent(java.util.List, java.nio.charset.Charset)}
132+
* @return byte array containing the challengeHash signature or null if a signature could not be generated.
133+
*/
134+
public byte[] sign(Algorithm algorithm, byte[] contentBytes) {
135+
try{
136+
137+
SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(), mapAlgorithm(algorithm));
138+
Mac mac = Mac.getInstance(mapAlgorithm(algorithm));
139+
mac.init(secretKeySpec);
140+
return mac.doFinal(contentBytes);
141+
142+
} catch (NoSuchAlgorithmException e) {
143+
LOGGER.error("[sign] failed to sign content.", e);
144+
} catch (InvalidKeyException e) {
145+
LOGGER.error("[sign] failed to sign content.", e);
146+
}
147+
148+
return EMPTY_BYTES;
149+
}
150+
}

0 commit comments

Comments
 (0)