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