Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

[libscs] use base64 url-safe encoding

  • Loading branch information...
commit 854da3de481e8156b83820b3beb91777a9bbe373 1 parent 9a8a2d2
@babongo babongo authored
View
55 doc/draft-scs.xml
@@ -19,7 +19,7 @@
<?rfc sortrefs="yes" ?>
<?rfc compact="yes" ?>
<?rfc subcompact="no" ?>
-<rfc category="info" docName="draft-secure-cookie-session-protocol-04"
+<rfc category="info" docName="draft-secure-cookie-session-protocol-05"
ipr="trust200902">
<front>
<title abbrev="">SCS: Secure Cookie Sessions for HTTP</title>
@@ -235,11 +235,11 @@
scs-cookie = scs-cookie-name "=" scs-cookie-value
scs-cookie-name = token
scs-cookie-value = DATA "|" ATIME "|" TID "|" IV "|" AUTHTAG
-DATA = 4*base64-character
-ATIME = 4*base64-character
-TID = 4*base64-character
-IV = 4*base64-character
-AUTHTAG = 4*base64-character
+DATA = 1*base64url-character
+ATIME = 1*base64url-character
+TID = 1*base64url-character
+IV = 1*base64url-character
+AUTHTAG = 1*base64url-character
]]></artwork>
</figure>
</t>
@@ -337,13 +337,13 @@ AUTHTAG = 4*base64-character
<section anchor="sec_scs_authtag" title="AUTHTAG">
<t>Authentication tag based on the concatenation of DATA, ATIME,
- TID and IV fields encoded in Base-64, framed by the "|" separator:
+ TID and IV fields base64url encoded, framed by the "|" separator:
<figure>
<artwork align="left"><![CDATA[
-AUTHTAG = HMAC(base64(DATA) || "|" ||
- base64(ATIME) || "|" ||
- base64(TID) || "|" ||
- base64(IV))
+AUTHTAG = HMAC(base64url(DATA) || "|" ||
+ base64url(ATIME) || "|" ||
+ base64url(TID) || "|" ||
+ base64url(IV))
]]></artwork>
</figure>
Note that, from a cryptographic point of view, the "|" character
@@ -460,7 +460,7 @@ AUTHTAG = HMAC(base64(DATA) || "|" ||
<t>Since the only user of the ATIME field is the server, it is
unnecessary for it to be synchronized with the client -- though
- it needs to be a fairly stable clock. However, if multiple servers
+ it needs to use a fairly stable clock. However, if multiple servers
are active in a load-balancing configuration, clocks SHOULD be
synchronized to avoid errors in the calculation of session expiry.</t>
@@ -474,7 +474,7 @@ AUTHTAG = HMAC(base64(DATA) || "|" ||
<t>Then the authentication tag, which encompasses each SCS field
(along with lengths, and relative positions) is computed by HMAC'ing
- the "|"-separated concatenation of their base64 representations using
+ the "|"-separated concatenation of their base64url representations using
the key-set identified by TID (step 4.).</t>
<t>Finally the SCS cookie-value is created as follows:
@@ -519,8 +519,8 @@ AUTHTAG = HMAC(base64(DATA) || "|" ||
<t>If the cryptographic credentials (encryption and authentication
algorithms and keys identified by TID) are unavailable (step 12.),
- the inbound SCS cookie is discarded as its value has no chance to
- be interpreted correctly.
+ the inbound SCS cookie is discarded since its value has no chance to
+ be interpreted correctly.
This may happen for several reasons: e.g., if a device without
storage has been reset and loses the credentials stored in RAM, if
a server pool node desynchronizes, or in case of a key compromise
@@ -573,12 +573,12 @@ AUTHTAG = HMAC(base64(DATA) || "|" ||
<artwork align="center"><![CDATA[
1. dump-state:
S --> C
- Set-Cookie: ANY_COOKIE_NAME=BO2zHC0tRg76axnguyuK5g==|MTI5...
+ Set-Cookie: ANY_COOKIE_NAME=BO2zHC0tRg76axnguyuK5g|MTI5...
Expires=...; Path=...; Domain=...;
2. restore-state:
C --> S
- Cookie: ANY_COOKIE_NAME=BO2zHC0tRg76axnguyuK5g==|MTI5...
+ Cookie: ANY_COOKIE_NAME=BO2zHC0tRg76axnguyuK5g|MTI5...
]]></artwork>
</figure>
@@ -738,8 +738,9 @@ T ----------------|---------------------| {no longer valid}
<section anchor="sec_security_of_the_crypto_protocol"
title="Security of the Cryptographic Protocol">
<t>From a cryptographic architecture perspective, the described
- mechanism can be easily traced to an Encode-then-EtM scheme described
- in <xref target="Kohno"></xref>.</t>
+ mechanism can be easily traced to an "encode then encrypt then MAC"
+ scheme (Encode-then-EtM) as described in <xref
+ target="Kohno"></xref>.</t>
<t>Given a "provably-secure" encryption scheme and MAC (as for the
algorithms mandated in <xref target="sec_cipher_set"></xref>),
@@ -983,11 +984,11 @@ T ----------------|---------------------| {no longer valid}
<t>produce the following tokens:
<list style="symbols">
- <t>DATA: GJRz3N0cuPKTumCqjtVjgw%3D%3D</t>
- <t>ATIME: MTMyMzg5ODgwMA%3D%3D</t>
+ <t>DATA: GJRz3N0cuPKTumCqjtVjgw</t>
+ <t>ATIME: MTMyMzg5ODgwMA</t>
<t>TID: dGlk</t>
- <t>IV: 0QL8yr8FA7H0Tx_9bRJcZg%3D%3D</t>
- <t>AUTHTAG: ktKOYXnTjrCzXgxGH__dWXUZAJ8%3D</t>
+ <t>IV: 0QL8yr8FA7H0Tx_9bRJcZg</t>
+ <t>AUTHTAG: ktKOYXnTjrCzXgxGH__dWXUZAJ8</t>
</list></t>
</section>
@@ -1003,11 +1004,11 @@ T ----------------|---------------------| {no longer valid}
</list></t>
<t>produce the following tokens:
<list style="symbols">
- <t>DATA: XaLWZDoFmv9vYF8wYYYxeXtCkUYAwbzpCfBWBzAy3Y8%3D</t>
- <t>ATIME: MTMyMzg5OTM4OA%3D%3D</t>
+ <t>DATA: XaLWZDoFmv9vYF8wYYYxeXtCkUYAwbzpCfBWBzAy3Y8</t>
+ <t>ATIME: MTMyMzg5OTM4OA</t>
<t>TID: dGlk</t>
- <t>IV: cm8ALkzzbf3xH5LPEo7niw%3D%3D</t>
- <t>AUTHTAG: K_rig5ZxGz_aGPQkyAb8JRMcTUY%3D</t>
+ <t>IV: cm8ALkzzbf3xH5LPEo7niw</t>
+ <t>AUTHTAG: K_rig5ZxGz_aGPQkyAb8JRMcTUY</t>
</list></t>
<t>In both cases, the resulting SCS cookie is obtained via ordered
concatenation of the produced tokens, as described in
View
145 src/base64.c
@@ -3,40 +3,43 @@
#include <ctype.h>
#include "base64.h"
-//static void bp (void) { puts("BP"); }
-
static const char E[] =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
-/* Compute offsets (+1) in E (also works as a truth table for is_base64().) */
-static const size_t D[256] = {
+/* Compute offsets (+1) in E (also works as truth table for is_base64url_c). */
+static const int D[256] = {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 63, 0, 0, 0, 64, /* +,/ */
+ 0, 0, 0, 0, 0, 63, 0, 0, /* - */
53, 54, 55, 56, 57, 58, 59, 60, /* 0-7 */
61, 62, 0, 0, 0, 0, 0, 0, /* 8,9 */
0, 1, 2, 3, 4, 5, 6, 7, /* A-G */
8, 9, 10, 11, 12, 13, 14, 15, /* H-O */
16, 17, 18, 19, 20, 21, 22, 23, /* P-W */
- 24, 25, 26, 0, 0, 0, 0, 0, /* X-Z */
+ 24, 25, 26, 0, 0, 0, 0, 64, /* X-Z,_ */
0, 27, 28, 29, 30, 31, 32, 33, /* a-g */
34, 35, 36, 37, 38, 39, 40, 41, /* h-o */
42, 43, 44, 45, 46, 47, 48, 49, /* p-w */
50, 51, 52, 0, 0, 0, 0, 0 /* x-z */
};
-static inline size_t val (int c);
-static inline int is_base64 (int c);
-static inline void chunk_encode (uint8_t in[3], char out[4], int len);
-static inline void chunk_decode (char in[4], uint8_t out[3]);
-
-/* IN: binary data, OUT: B64 string (possibly NUL-terminated). */
-int base64_encode (const uint8_t *in, size_t in_sz, char *out, size_t out_sz)
+static inline int val(int c);
+static inline int is_base64url_c(int c);
+static inline size_t chunk_encode(uint8_t in[3], size_t in_sz, char out[4],
+ size_t out_sz);
+static inline int chunk_decode(char in[4], size_t in_sz, uint8_t out[3],
+ size_t out_sz);
+
+/* IN: binary data, OUT: B64 string (*not* NUL-terminated).
+ * 'pout_sz' is a value-result argument carrying the size of 'out' on
+ * input and the number of encoded bytes on output. */
+int base64url_encode(const uint8_t *in, size_t in_sz, char *out,
+ size_t *pout_sz)
{
- size_t i, len;
+ size_t i, len, out_sz = *pout_sz;
uint8_t buf[3];
char *pout;
@@ -50,69 +53,66 @@ int base64_encode (const uint8_t *in, size_t in_sz, char *out, size_t out_sz)
buf[i] = *in++;
++len;
}
- else
- buf[i] = '\0';
}
/* See if we've harvested enough data to call the block encoder. */
if (len)
{
- /* See if there's room at the output buffer to receive. */
- if (out_sz >= 4)
+ size_t outlen = chunk_encode(buf, len, pout, out_sz);
+
+ if (outlen)
{
- chunk_encode(buf, pout, len);
- out_sz -= 4;
- pout += 4;
+ out_sz -= outlen;
+ pout += outlen;
}
else
return -1;
}
}
- /* Possibly NUL-terminate out string. */
- if (out_sz)
- *pout = '\0';
+ /* Update the encoded length counter. */
+ *pout_sz -= out_sz;
return 0;
}
-int base64_decode (const char *in, size_t in_sz, uint8_t *out, size_t *out_sz)
+/* IN: base64url encoded string (not NUL-terminated). OUT: the corresponding
+ * decoded buffer. */
+int base64url_decode(const char *in, size_t in_sz, uint8_t *out,
+ size_t *out_sz)
{
char buf[4];
- size_t i, len, pad, tot_sz = *out_sz;
+ size_t i, len, tot_sz = *out_sz;
uint8_t *pout;
for (pout = out; in_sz; )
{
- for (pad = 0, len = 0, i = 0; i < 4; ++i)
+ /* Get a 4-bytes chunk (may be less than 4, actually). */
+ for (len = 0, i = 0; i < 4; ++i)
{
if (in_sz && in_sz-- > 0) /* Avoid wrapping around in_sz. */
{
buf[i] = *in++;
- if (buf[i] == '=')
- ++pad;
- else if (!is_base64(buf[i]))
+ if (!is_base64url_c(buf[i]))
return -1;
++len;
}
- else
- buf[i] = '\0';
}
+ /* Decode the chunk into its 3-bytes' binary data. */
if (len)
{
- size_t nlen = 3 - pad;
+ int outlen = chunk_decode(buf, len, pout, tot_sz);
- if (tot_sz >= nlen)
+ if (outlen)
{
- chunk_decode(buf, pout);
- tot_sz -= nlen; /* Take care of subtracting pad bytes. */
- pout += nlen;
- }
+ tot_sz -= outlen;
+ pout += outlen;
+ }
else
- return -1; /* Not enough space. */
+ return -1;
}
}
@@ -121,27 +121,74 @@ int base64_decode (const char *in, size_t in_sz, uint8_t *out, size_t *out_sz)
return 0;
}
-static inline int is_base64 (int c)
+static inline int is_base64url_c(int c)
{
return D[c];
}
-static inline size_t val (int c)
+static inline int val(int c)
{
return (D[c] - 1);
}
-static inline void chunk_encode (uint8_t in[3], char out[4], int len)
+/* Encode 'in_sz' bytes to from 'in' to 'out'. Return the number
+ * of encoded bytes (2 to 4) or '0' on error. */
+static inline size_t chunk_encode(uint8_t in[3], size_t in_sz, char out[4],
+ size_t out_sz)
{
+ size_t nenc = 4;
+
+ if (!in_sz || in_sz > 3)
+ return 0;
+
+ /* Make sure unused bytes don't introduce noise. */
+ memset(in + in_sz, 0, 3 - in_sz);
+
out[0] = E[in[0] >> 2];
out[1] = E[((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4)];
- out[2] = len > 1 ? E[((in[1] & 0x0f) << 2) | ((in[2] & 0xc0) >> 6)] : '=';
- out[3] = len > 2 ? E[in[2] & 0x3f] : '=';
+
+ if (in_sz >= 2)
+ out[2] = E[((in[1] & 0x0f) << 2) | ((in[2] & 0xc0) >> 6)];
+ else
+ nenc -= 1;
+
+ if (in_sz == 3)
+ out[3] = E[in[2] & 0x3f];
+ else
+ nenc -= 1;
+
+ return (out_sz >= nenc) ? nenc : 0;
}
-static inline void chunk_decode (char in[4], uint8_t out[3])
-{
+/* Decode a chunk of 'in_sz' (at most 4) base64url encoded chars. */
+static inline int chunk_decode(char in[4], size_t in_sz, uint8_t out[3],
+ size_t out_sz)
+{
+ size_t ndec;
+ int r1, r2;
+
+ if (!in_sz || in_sz > 4)
+ return 0;
+
+ /* Make sure unused bytes don't introduce noise. */
+ memset(in + in_sz, 0, 4 - in_sz);
+
+ /* Compute the right leg of the 2nd and 3rd sextets. */
+ r1 = val(in[1]) & 0x0f;
+ r2 = val(in[2]) & 0x03;
+
+ /* Compute the number of decoded bytes. */
+ switch (in_sz)
+ {
+ case 1: ndec = 1; break;
+ case 2: ndec = r1 ? 2 : 1; break;
+ case 3: ndec = r2 ? 3 : 2; break;
+ case 4: ndec = 3; break;
+ }
+
out[0] = (val(in[0]) << 2) | (val(in[1]) >> 4);
- out[1] = ((val(in[1]) & 0x0f) << 4) | (val(in[2]) >> 2);
- out[2] = ((val(in[2]) & 0x03) << 6) | val(in[3]);
+ out[1] = r1 << 4 | val(in[2]) >> 2;
+ out[2] = r2 << 6 | val(in[3]);
+
+ return (out_sz >= ndec) ? ndec : 0;
}
View
6 src/base64.h
@@ -5,9 +5,11 @@
#include <stdint.h>
/* IN: binary data, OUT: B64 string (possibly NUL-terminated). */
-int base64_encode (const uint8_t *in, size_t in_sz, char *out, size_t out_sz);
+int base64url_encode(const uint8_t *in, size_t in_sz, char *out,
+ size_t *pout_sz);
/* IN: B64 string, OUT: binary data. */
-int base64_decode (const char *in, size_t in_sz, uint8_t *out, size_t *out_sz);
+int base64url_decode (const char *in, size_t in_sz, uint8_t *out,
+ size_t *out_sz);
#endif /* !_B64_H_ */
View
11 src/scs.c
@@ -588,7 +588,8 @@ static int create_tag (scs_t *ctx, scs_keyset_t *ks, int skip_encoding)
{
for (i = 0; i < NUM_ATOMS; ++i)
{
- if (base64_encode(A[i].raw, A[i].raw_sz, A[i].enc, A[i].enc_sz))
+ if (base64url_encode(A[i].raw, A[i].raw_sz,
+ A[i].enc, &A[i].enc_sz))
{
scs_set_error(ctx, SCS_ERR_ENCODE, "%s encode failed", A[i].id);
return -1;
@@ -600,8 +601,10 @@ static int create_tag (scs_t *ctx, scs_keyset_t *ks, int skip_encoding)
if (D.tag(ctx, ks))
return -1;
+ size_t b64_tag = sizeof ats->b64_tag;
+
/* Base-64 encode the auth tag. */
- if (base64_encode(ats->tag, ats->tag_sz, ats->b64_tag, sizeof ats->b64_tag))
+ if (base64url_encode(ats->tag, ats->tag_sz, ats->b64_tag, &b64_tag))
{
scs_set_error(ctx, SCS_ERR_ENCODE, "tag encode failed", A[i].id);
return -1;
@@ -748,7 +751,7 @@ static scs_keyset_t *retr_keyset (scs_t *ctx)
size_t raw_tid_len = sizeof raw_tid - 1;
/* Make sure we have room for the terminating NUL char. */
- if (base64_decode(ats->b64_tid, strlen(ats->b64_tid),
+ if (base64url_decode(ats->b64_tid, strlen(ats->b64_tid),
(uint8_t *) raw_tid, &raw_tid_len))
{
scs_set_error(ctx, SCS_ERR_DECODE, "Base-64 decoding of tid failed");
@@ -881,7 +884,7 @@ static int decode_atoms (scs_t *ctx, scs_keyset_t *ks)
for (i = 0; i < NUM_ATOMS; ++i)
{
- if (base64_decode(A[i].enc, A[i].enc_sz, A[i].raw, A[i].raw_sz))
+ if (base64url_decode(A[i].enc, A[i].enc_sz, A[i].raw, A[i].raw_sz))
{
scs_set_error(ctx, SCS_ERR_DECODE, "%s decoding failed", A[i].id);
return -1;
View
13 test/run-tests.sh
@@ -2,6 +2,9 @@
SCSBIN="`pwd`/scs"
+#OF=/dev/null
+OF=/dev/stdout
+
err ()
{
echo $*
@@ -10,14 +13,14 @@ err ()
test0 ()
{
- "${SCSBIN}" -A > /dev/null
+ "${SCSBIN}" -A > "${OF}"
}
test1 ()
{
"${SCSBIN}" -A \
-s "try to encode and decode my custom state" \
- > /dev/null
+ > "${OF}"
}
test2 ()
@@ -26,7 +29,7 @@ test2 ()
-s "yet another custom state string" \
-k "0123" \
-h "4567" \
- > /dev/null
+ > "${OF}"
}
test3 ()
@@ -36,7 +39,7 @@ test3 ()
-k "0123" \
-h "4567" \
-t my_tid \
- > /dev/null
+ > "${OF}"
}
test4 ()
@@ -46,7 +49,7 @@ test4 ()
-z \
-f book.json \
-o book-decoded.json
- > /dev/null
+ > "${OF}"
diff book.json book-decoded.json
ret=$?
Please sign in to comment.
Something went wrong with that request. Please try again.