Skip to content

Commit 3afe802

Browse files
committed
refactored Verifier type to be an interface backed by a DefaultVerifier to allow for remote calls and other such things
1 parent 9ef38bd commit 3afe802

File tree

12 files changed

+306
-131
lines changed

12 files changed

+306
-131
lines changed

api/src/main/java/net/adamcin/httpsig/api/Authorization.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929

3030
import java.io.Serializable;
3131
import java.util.ArrayList;
32-
import java.util.Collections;
3332
import java.util.LinkedHashMap;
3433
import java.util.List;
3534
import java.util.Map;

api/src/main/java/net/adamcin/httpsig/api/Constants.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public final class Constants {
113113
*/
114114
public static final Pattern RFC2617_PARAM = Pattern.compile("(\\w+)=\"([^\"]*)\"");
115115

116-
public static final List<String> parseTokens(String tokens) {
116+
public static List<String> parseTokens(String tokens) {
117117
if (tokens == null || tokens.trim().isEmpty()) {
118118
return Collections.emptyList();
119119
} else {
@@ -126,7 +126,7 @@ public static final List<String> parseTokens(String tokens) {
126126
}
127127
}
128128

129-
public static final String constructTokensString(List<String> tokens) {
129+
public static String constructTokensString(List<String> tokens) {
130130
StringBuilder sb = new StringBuilder();
131131
if (tokens != null) {
132132
for (String token : tokens) {
@@ -136,7 +136,7 @@ public static final String constructTokensString(List<String> tokens) {
136136
return sb.toString();
137137
}
138138

139-
public static final Map<String, String> parseRFC2617(String header) {
139+
public static Map<String, String> parseRFC2617(String header) {
140140
Map<String, String> params = new HashMap<String, String>();
141141
if (header != null && header.toLowerCase().startsWith(Constants.SCHEME.toLowerCase())) {
142142
final Matcher matcher = RFC2617_PARAM.matcher(header.substring(Constants.SCHEME.length() + 1));
@@ -147,7 +147,7 @@ public static final Map<String, String> parseRFC2617(String header) {
147147
return Collections.unmodifiableMap(params);
148148
}
149149

150-
public static final String constructRFC2617(Map<String, String> params) {
150+
public static String constructRFC2617(Map<String, String> params) {
151151
StringBuilder sb = new StringBuilder(SCHEME);
152152
if (params != null && !params.isEmpty()) {
153153
for (Map.Entry<String, String> param : params.entrySet()) {

api/src/main/java/net/adamcin/httpsig/api/DefaultKeychain.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -169,12 +169,20 @@ public Keychain filterAlgorithms(Collection<Algorithm> algorithms) {
169169
}
170170

171171
public Map<String, Key> toMap(KeyId keyIdentifier) {
172-
KeyId identifier = keyIdentifier != null ? keyIdentifier : Constants.DEFAULT_KEY_IDENTIFIER;
173172
LinkedHashMap<String, Key> map = new LinkedHashMap<String, Key>(this.size());
174-
for (Key key : this) {
175-
String keyId = identifier.getId(key);
176-
if (keyId != null) {
177-
map.put(keyId, key);
173+
if (keyIdentifier == null) {
174+
for (Key key : this) {
175+
String keyId = Constants.DEFAULT_KEY_IDENTIFIER.getId(key);
176+
if (keyId != null) {
177+
map.put(keyId, key);
178+
}
179+
}
180+
} else {
181+
for (Key key : this) {
182+
String keyId = keyIdentifier.getId(key);
183+
if (keyId != null) {
184+
map.put(keyId, key);
185+
}
178186
}
179187
}
180188
return Collections.unmodifiableMap(map);
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
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.api;
29+
30+
import java.util.Collection;
31+
import java.util.Date;
32+
import java.util.GregorianCalendar;
33+
import java.util.Iterator;
34+
import java.util.Map;
35+
import java.util.Set;
36+
import java.util.TimeZone;
37+
38+
/**
39+
* The Server-Side component of the protocol which verifies {@link Authorization} headers using SSH Public Keys
40+
*/
41+
public final class DefaultVerifier implements Verifier {
42+
public static final long DEFAULT_SKEW = 300000L;
43+
44+
private final Keychain keychain;
45+
private final KeyId keyId;
46+
private final long skew;
47+
48+
public DefaultVerifier(Keychain keychain) {
49+
this(keychain, null, DEFAULT_SKEW);
50+
}
51+
52+
public DefaultVerifier(Keychain keychain, KeyId keyId) {
53+
this(keychain, keyId, DEFAULT_SKEW);
54+
}
55+
56+
public DefaultVerifier(Keychain keychain, KeyId keyId, long skew) {
57+
this.keychain = keychain != null ? new KeychainGuard(keychain) : new KeychainGuard(new DefaultKeychain());
58+
this.keyId = new CanVerifyId(keyId != null ? keyId : Constants.DEFAULT_KEY_IDENTIFIER);
59+
this.skew = skew;
60+
}
61+
62+
public Keychain getKeychain() {
63+
return keychain;
64+
}
65+
66+
/**
67+
* {@inheritDoc}
68+
*/
69+
public long getSkew() {
70+
return skew;
71+
}
72+
73+
/**
74+
* @deprecated will remove to make the class immutable. use constructor overload instead.
75+
* @param skew new server skew in milliseconds
76+
*/
77+
public void setSkew(long skew) {
78+
// this is now a no-op.
79+
}
80+
81+
/**
82+
* {@inheritDoc}
83+
*/
84+
public Key selectKey(Authorization authorization) {
85+
return keychain.toMap(this.keyId).get(authorization.getKeyId());
86+
}
87+
88+
/**
89+
* {@inheritDoc}
90+
*/
91+
public boolean verify(Challenge challenge, RequestContent requestContent, Authorization authorization) {
92+
return verifyWithResult(challenge, requestContent, authorization) == VerifyResult.SUCCESS;
93+
}
94+
95+
/**
96+
* {@inheritDoc}
97+
*/
98+
public VerifyResult verifyWithResult(Challenge challenge, RequestContent requestContent, Authorization authorization) {
99+
if (challenge == null) {
100+
throw new IllegalArgumentException("challenge cannot be null");
101+
}
102+
103+
if (requestContent == null) {
104+
throw new IllegalArgumentException("requestContent cannot be null");
105+
}
106+
107+
if (authorization == null) {
108+
throw new IllegalArgumentException("authorization cannot be null");
109+
}
110+
111+
// verify that all headers required by the challenge are declared by the authorization
112+
for (String header : challenge.getHeaders()) {
113+
if (!header.startsWith(":") && !authorization.getHeaders().contains(header)) {
114+
return VerifyResult.CHALLENGE_NOT_SATISFIED;
115+
}
116+
}
117+
118+
// verify that all headers declared by the authorization are present in the request
119+
for (String header : authorization.getHeaders()) {
120+
if (requestContent.getHeaderValues(header).isEmpty()) {
121+
return VerifyResult.INCOMPLETE_REQUEST;
122+
}
123+
}
124+
125+
// if date is declared by the authorization, verify that its value is within $skew of the current time
126+
if (authorization.getHeaders().contains(Constants.HEADER_DATE) && skew >= 0) {
127+
Date requestTime = requestContent.getDateGMT();
128+
Date currentTime = new GregorianCalendar(TimeZone.getTimeZone("UTC")).getTime();
129+
Date past = new Date(currentTime.getTime() - skew);
130+
Date future = new Date(currentTime.getTime() + skew);
131+
if (requestTime.before(past) || requestTime.after(future)) {
132+
return VerifyResult.EXPIRED_DATE_HEADER;
133+
}
134+
}
135+
136+
Key key = selectKey(authorization);
137+
if (key == null) {
138+
return VerifyResult.KEY_NOT_FOUND;
139+
}
140+
141+
if (key.verify(authorization.getAlgorithm(),
142+
requestContent.getContent(authorization.getHeaders(), Constants.CHARSET),
143+
authorization.getSignatureBytes())) {
144+
return VerifyResult.SUCCESS;
145+
} else {
146+
return VerifyResult.FAILED_KEY_VERIFY;
147+
}
148+
}
149+
150+
private static class CanVerifyId implements KeyId {
151+
private KeyId delegatee;
152+
153+
private CanVerifyId(KeyId delegatee) {
154+
this.delegatee = delegatee;
155+
}
156+
157+
public String getId(Key key) {
158+
if (key != null && key.canVerify()) {
159+
return delegatee.getId(key);
160+
}
161+
return null;
162+
}
163+
}
164+
165+
/**
166+
* Guards a Keychain from modification via reflection (not enough for JAAS?)
167+
*/
168+
private static class KeychainGuard implements Keychain {
169+
private final Keychain keychain;
170+
171+
private KeychainGuard(Keychain keychain) {
172+
this.keychain = keychain;
173+
}
174+
175+
public Set<Algorithm> getAlgorithms() {
176+
return keychain.getAlgorithms();
177+
}
178+
179+
public Keychain filterAlgorithms(Collection<Algorithm> algorithms) {
180+
return new KeychainGuard(keychain.filterAlgorithms(algorithms));
181+
}
182+
183+
public Keychain discard() {
184+
return new KeychainGuard(keychain.discard());
185+
}
186+
187+
public Key currentKey() {
188+
return keychain.currentKey();
189+
}
190+
191+
public Map<String, Key> toMap(KeyId keyId) {
192+
return keychain.toMap(keyId);
193+
}
194+
195+
public boolean isEmpty() {
196+
return keychain.isEmpty();
197+
}
198+
199+
public Iterator<Key> iterator() {
200+
return keychain.iterator();
201+
}
202+
}
203+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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.api;
29+
30+
/**
31+
* Extends the base {@link Key} interface to add a userId field, which is a common use case for authentication.
32+
* @since 1.0.8
33+
*/
34+
public interface UserKey extends Key {
35+
36+
/**
37+
* Get the associated userId
38+
* @return the associated userId
39+
*/
40+
String getUserId();
41+
}

0 commit comments

Comments
 (0)