anvil-connect-jwt
Version:
Anvil Connect JWT library for Node.js
1,301 lines (979 loc) • 61.3 kB
text/coffeescript
# Test dependencies
cwd = process.cwd()
path = require 'path'
chai = require 'chai'
sinon = require 'sinon'
sinonChai = require 'sinon-chai'
expect = chai.expect
# Assertions
chai.use sinonChai
chai.should()
# Code under test
JWT = require path.join cwd, 'lib/JWT'
base64url = require 'base64url'
# JSON Web Token (JWT)
# draft-ietf-oauth-json-web-token-18
#
# http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-18
#
#
# 3. JSON Web Token (JWT) Overview
#
# JWTs represent a set of claims as a JSON object that is encoded in a
# JWS and/or JWE structure. This JSON object is the JWT Claims Set. As
# per Section 4 of [RFC7158], the JSON object consists of zero or more
# name/value pairs (or members), where the names are strings and the
# values are arbitrary JSON values. These members are the claims
# represented by the JWT.
#
# The member names within the JWT Claims Set are referred to as Claim
# Names. The corresponding values are referred to as Claim Values.
#
# The contents of the JWT Header describe the cryptographic operations
# applied to the JWT Claims Set. If the JWT Header is a JWS Header, the
# JWT is represented as a JWS, and the claims are digitally signed or
# MACed, with the JWT Claims Set being the JWS Payload. If the JWT
# Header is a JWE Header, the JWT is represented as a JWE, and the
# claims are encrypted, with the JWT Claims Set being the input
# Plaintext. A JWT may be enclosed in another JWE or JWS structure to
# create a Nested JWT, enabling nested signing and encryption to be
# performed.
#
# A JWT is represented as a sequence of URL-safe parts separated by
# period ('.') characters. Each part contains a base64url encoded
# value. The number of parts in the JWT is dependent upon the
# representation of the resulting JWS or JWE object using the JWS
# Compact Serialization or the JWE Compact Serialization.
describe 'JWT', ->
describe 'traverse', ->
{keys,schema,operation,descriptorA} = {}
beforeEach ->
keys = ['a','b']
descriptorA = { format: 'String' }
descriptorB = { format: 'String' }
schema =
a: descriptorA
b: descriptorB
operation = sinon.spy()
it 'should not traverse an undefined source', ->
JWT.traverse(keys, schema, undefined, {}, operation, {})
operation.should.not.have.been.called
it 'should not traverse a null source', ->
JWT.traverse(keys, schema, null, {}, operation, {})
operation.should.not.have.been.called
it 'should iterate over a set of keys', ->
JWT.traverse(keys, schema, {}, {}, operation, {})
operation.should.have.been.calledWith 'a'
operation.should.have.been.calledWith 'b'
it 'should fail with unrecognized schema properties', ->
expect(-> JWT.traverse(['x'], {}, {}, {}, (->), {})).to.throw Error
it 'should ignore unrecognized source properties', ->
JWT.traverse(keys, schema, { x: 'x' }, {}, operation, {})
operation.should.not.have.been.calledWith 'x'
it 'should operate on a target object', ->
source = { a: 'a', b: 'b' }
target = {}
options = {}
JWT.traverse(keys, schema, source, target, operation, options)
operation.should.have.been.calledWith 'a', descriptorA, source, target, options
describe 'assignValid', ->
{key,descriptor,source,target,options} = {}
beforeEach ->
key = 'a'
descriptor = format: 'String'
source = { a: 'a' }
target = {}
options = {}
it 'should set a target property from a source object', ->
JWT.assignValid(key, descriptor, source, target, options)
target.a.should.equal source.a
it 'should set a target property from a source object by mapping', ->
descriptor =
format: 'String'
from: 'a'
JWT.assignValid('b', descriptor, source, target, options)
target.b.should.equal source.a
it 'should set a target property from a default value', ->
descriptor =
format: 'String'
default: 'd'
JWT.assignValid('b', descriptor, source, target, options)
target.b.should.equal descriptor.default
it 'should set a target property from a default function', ->
descriptor =
format: 'String'
default: -> 'generated'
JWT.assignValid('b', descriptor, source, target, options)
target.b.should.equal 'generated'
it 'should fail with an invalid format', ->
source = { a: 1 }
expect(-> JWT.assignValid(key, descriptor, source, target, options)).to.throw Error
it 'should fail with an unenumerated value', ->
descriptor =
format: 'String'
enum: ['x','y']
expect(-> JWT.assignValid(key, descriptor, source, target, options)).to.throw Error
it 'should fail with an undefined required property', ->
descriptor =
format: 'String'
required: true
expect(-> JWT.assignValid(key, descriptor, {}, target, options)).to.throw Error
describe 'assertFormat', ->
it 'should verify a value matching a prescribed format', ->
JWT.assertFormat('key', 123456789, { format: 'IntDate' }).should.be.true
it 'should fail with a value not matching a prescribed format', ->
expect(-> JWT.assertFormat('key', false, { format: 'IntDate' })).to.throw Error
it 'should fail with an unrecognized format', ->
expect(-> JWT.assertFormat('key', false, { format: 'Unknown' })).to.throw Error
describe 'StringOrURI', ->
it 'should accept a string', ->
JWT.formats['StringOrURI']('string').should.be.true
it 'should accept a valid URI', ->
JWT.formats['StringOrURI']("http://anvil.io").should.be.true
JWT.formats['StringOrURI']("anvil.io").should.be.true
it 'should not accept an invalid URI', ->
JWT.formats['StringOrURI']("http://an%vil.io").should.be.false
#JWT.formats['StringOrURI']("an%vil.io").should.be.false
it 'should not accept a non-string', ->
JWT.formats['StringOrURI'](true).should.be.false
JWT.formats['StringOrURI'](123).should.be.false
JWT.formats['StringOrURI']([]).should.be.false
JWT.formats['StringOrURI']({}).should.be.false
JWT.formats['StringOrURI'](->).should.be.false
JWT.formats['StringOrURI'](null).should.be.false
describe 'StringOrURI*', ->
describe 'String', ->
it 'should accept a string', ->
JWT.formats['String']('true').should.be.true
it 'should not accept a non-string', ->
JWT.formats['String'](false).should.be.false
describe 'String*', ->
it 'should accept an empty array', ->
JWT.formats['String*']([]).should.be.true
it 'should accept an array of strings', ->
JWT.formats['String*'](['a', 'b', 'c']).should.be.true
it 'should not accept a non-array', ->
JWT.formats['String*']('nope').should.be.false
JWT.formats['String*'](true).should.be.false
JWT.formats['String*']({}).should.be.false
it 'should not accept non-string array elements', ->
JWT.formats['String*']([false, 'string']).should.be.false
describe 'CaseSensitiveString', ->
describe 'IntDate', ->
it 'should accept a valid integer', ->
JWT.formats['IntDate'](Date.now()).should.be.true
it 'should not accept a non-integer', ->
JWT.formats['IntDate'](null).should.be.false
JWT.formats['IntDate'](true).should.be.false
JWT.formats['IntDate'](123.45).should.be.false
JWT.formats['IntDate']('foo').should.be.false
JWT.formats['IntDate'](->).should.be.false
JWT.formats['IntDate']({}).should.be.false
describe 'URI', ->
it 'should accept a valid URI', ->
JWT.formats['URI']("http://anvil.io").should.be.true
it 'should not accept an invalid URI', ->
JWT.formats['URI']("http://an%vil.io").should.be.false
JWT.formats['URI']("an%vil.io").should.be.false
JWT.formats['URI']("anvil.io").should.be.false
describe 'JWK', ->
describe 'CertificateOrChain', ->
describe 'CertificateThumbprint', ->
describe 'ParameterList', ->
describe 'assertEnumerated', ->
it 'should verify an enumerated value', ->
JWT.assertEnumerated('key', 'z', {
format: 'String',
enum: ['x','y','z']
}).should.be.true
it 'should fail with an unenumerated value', ->
expect(-> JWT.assertEnumerated('key', 'z', {
format: 'String'
enum: ['x','y']
})).to.throw Error
describe 'assertPresence', ->
it 'should verify the presence of a required value', ->
JWT.assertPresence('key', 'value', { required: true }).should.be.true
it 'should not fail if a value is not required', ->
JWT.assertPresence('key', undefined, {}).should.be.true
it 'should fail with an absent required value', ->
expect(-> JWT.assertPresence('key', undefined, { required: true })).to.throw Error
describe 'header initialization', ->
{header,constructor,instance} = {}
before ->
header =
alg: 'none'
constructor =
headers: ['alg'],
registeredHeaders: JWT.registeredHeaders
instance = {constructor}
sinon.spy(JWT, 'traverse')
JWT.prototype.initializeHeader.call(instance, { alg: 'none' })
after ->
JWT.traverse.restore()
it 'should assign a valid set of parameters', ->
JWT.traverse.should.have.been.calledWith(
constructor.headers
constructor.registeredHeaders
header
instance.header
JWT.assignValid
)
it 'should base64url encode the header', ->
base64url.decode(instance.headerB64u).should.equal JSON.stringify(header)
describe 'payload initialization', ->
{payload,constructor,instance} = {}
before ->
payload =
iss: 'http://anvil.io'
constructor =
claims: ['iss'],
registeredClaims: JWT.registeredClaims
instance = {constructor}
sinon.spy(JWT, 'traverse')
JWT.prototype.initializePayload.call(instance, payload)
after ->
JWT.traverse.restore()
it 'should assign a valid set of claims', ->
JWT.traverse.should.have.been.calledWith(
constructor.claims
constructor.registeredClaims
payload
instance.payload
JWT.assignValid
)
describe 'constructor', ->
{payload,header} = {}
before ->
payload = { iss: 'http://anvil.io' }
header = { alg: 'none' }
sinon.spy(JWT.prototype, 'initializeHeader')
sinon.spy(JWT.prototype, 'initializePayload')
new JWT(payload, header)
after ->
JWT.prototype.initializeHeader.restore()
JWT.prototype.initializePayload.restore()
it 'should initialize the provided header if provided', ->
JWT.prototype.initializeHeader.should.have.been.calledWith header
it 'should initialize the provided payload', ->
JWT.prototype.initializePayload.should.have.been.calledWith payload
it 'should initialize a signature if provided'
# 3.1. "alg" (Algorithm) Header Parameter Values for JWS
#
# http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-23#section-3.1
#
# The table below is the set of "alg" (algorithm) header parameter
# values defined by this specification for use with JWS, each of which
# is explained in more detail in the following sections:
#
# +---------------+------------------------------+--------------------+
# | alg Parameter | Digital Signature or MAC | Implementation |
# | Value | Algorithm | Requirements |
# +---------------+------------------------------+--------------------+
# | HS256 | HMAC using SHA-256 | Required |
# | HS384 | HMAC using SHA-384 | Optional |
# | HS512 | HMAC using SHA-512 | Optional |
# | RS256 | RSASSA-PKCS-v1_5 using | Recommended |
# | | SHA-256 | |
# | RS384 | RSASSA-PKCS-v1_5 using | Optional |
# | | SHA-384 | |
# | RS512 | RSASSA-PKCS-v1_5 using | Optional |
# | | SHA-512 | |
# | ES256 | ECDSA using P-256 and | Recommended+ |
# | | SHA-256 | |
# | ES384 | ECDSA using P-384 and | Optional |
# | | SHA-384 | |
# | ES512 | ECDSA using P-521 and | Optional |
# | | SHA-512 | |
# | PS256 | RSASSA-PSS using SHA-256 and | Optional |
# | | MGF1 with SHA-256 | |
# | PS384 | RSASSA-PSS using SHA-384 and | Optional |
# | | MGF1 with SHA-384 | |
# | PS512 | RSASSA-PSS using SHA-512 and | Optional |
# | | MGF1 with SHA-512 | |
# | none | No digital signature or MAC | Optional |
# | | performed | |
# +---------------+------------------------------+--------------------+
#
# The use of "+" in the Implementation Requirements indicates that the
# requirement strength is likely to be increased in a future version of
# the specification.
#
# See Appendix A.1 for a table cross-referencing the JWS digital
# signature and MAC "alg" (algorithm) values defined in this
# specification with the equivalent identifiers used by other standards
# and software packages.
describe 'supported algorithms', ->
it 'should be enumerated', ->
JWT.algorithms.should.be.an.array
# 4. JWT Claims
#
# http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-18#section-4
#
# The JWT Claims Set represents a JSON object whose members are the
# claims conveyed by the JWT. The Claim Names within a JWT Claims Set
# MUST be unique; recipients MUST either reject JWTs with duplicate
# Claim Names or use a JSON parser that returns only the lexically last
# duplicate member name, as specified in Section 15.12 (The JSON
# Object) of ECMAScript 5.1 [ECMAScript].
#
# The set of claims that a JWT must contain to be considered valid is
# context-dependent and is outside the scope of this specification.
# Specific applications of JWTs will require implementations to
# understand and process some claims in particular ways. However, in
# the absence of such requirements, all claims that are not understood
# by implementations MUST be ignored.
#
# There are three classes of JWT Claim Names: Registered Claim Names,
# Public Claim Names, and Private Claim Names.
#
# 4.1. Registered Claim Names
#
# The following Claim Names are registered in the IANA JSON Web Token
# Claims registry defined in Section 10.1. None of the claims defined
# below are intended to be mandatory to use or implement in all cases,
# but rather, provide a starting point for a set of useful,
# interoperable claims. Applications using JWTs should define which
# specific claims they use and when they are required or optional. All
# the names are short because a core goal of JWTs is for the
# representation to be compact.
describe 'registered claims', ->
# 4.1.1. "iss" (Issuer) Claim
#
# http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-18#section-4.1.1
#
# The "iss" (issuer) claim identifies the principal that issued the
# JWT. The processing of this claim is generally application specific.
# The "iss" value is a case-sensitive string containing a StringOrURI
# value. Use of this claim is OPTIONAL.
it 'should specify "iss" as a StringOrURI', ->
JWT.registeredClaims.iss.format.should.equal 'StringOrURI'
# 4.1.2. "sub" (Subject) Claim
#
# http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-18#section-4.1.2
#
# The "sub" (subject) claim identifies the principal that is the
# subject of the JWT. The Claims in a JWT are normally statements
# about the subject. The subject value MAY be scoped to be locally
# unique in the context of the issuer or MAY be globally unique. The
# processing of this claim is generally application specific. The
# "sub" value is a case-sensitive string containing a StringOrURI
# value. Use of this claim is OPTIONAL.
it 'should specify "sub" as a StringOrURI', ->
JWT.registeredClaims.sub.format.should.equal 'StringOrURI'
# 4.1.3. "aud" (Audience) Claim
#
# http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-18#section-4.1.3
#
# The "aud" (audience) claim identifies the recipients that the JWT is
# intended for. Each principal intended to process the JWT MUST
# identify itself with a value in the audience claim. If the principal
# processing the claim does not identify itself with a value in the
# "aud" claim when this claim is present, then the JWT MUST be
# rejected. In the general case, the "aud" value is an array of case-
# sensitive strings, each containing a StringOrURI value. In the
# special case when the JWT has one audience, the "aud" value MAY be a
# single case-sensitive string containing a StringOrURI value. The
# interpretation of audience values is generally application specific.
# Use of this claim is OPTIONAL.
it 'should specify "aud" as an array of StringOrURI values', ->
JWT.registeredClaims.aud.format.should.equal 'StringOrURI'
# JWT.registeredClaims.aud.format.should.equal 'StringOrURI*'
# 4.1.4. "exp" (Expiration Time) Claim
#
# http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-18#section-4.1.4
#
# The "exp" (expiration time) claim identifies the expiration time on
# or after which the JWT MUST NOT be accepted for processing. The
# processing of the "exp" claim requires that the current date/time
# MUST be before the expiration date/time listed in the "exp" claim.
# Implementers MAY provide for some small leeway, usually no more than
# a few minutes, to account for clock skew. Its value MUST be a number
# containing an IntDate value. Use of this claim is OPTIONAL.
it 'should specify "exp" as an IntDate', ->
JWT.registeredClaims.exp.format.should.equal 'IntDate'
# 4.1.5. "nbf" (Not Before) Claim
#
# http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-18#section-4.1.5
#
# The "nbf" (not before) claim identifies the time before which the JWT
# MUST NOT be accepted for processing. The processing of the "nbf"
# claim requires that the current date/time MUST be after or equal to
# the not-before date/time listed in the "nbf" claim. Implementers MAY
# provide for some small leeway, usually no more than a few minutes, to
# account for clock skew. Its value MUST be a number containing an
# IntDate value. Use of this claim is OPTIONAL.
it 'should specify "nbf" as an IntDate', ->
JWT.registeredClaims.nbf.format.should.equal 'IntDate'
# 4.1.6. "iat" (Issued At) Claim
#
# http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-18#section-4.1.6
#
# The "iat" (issued at) claim identifies the time at which the JWT was
# issued. This claim can be used to determine the age of the JWT. Its
# value MUST be a number containing an IntDate value. Use of this
# claim is OPTIONAL.
it 'should specify "iat" as an IntDate', ->
JWT.registeredClaims.iat.format.should.equal 'IntDate'
# 4.1.7. "jti" (JWT ID) Claim
#
# http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-18#section-4.1.7
#
# The "jti" (JWT ID) claim provides a unique identifier for the JWT.
# The identifier value MUST be assigned in a manner that ensures that
# there is a negligible probability that the same value will be
# accidentally assigned to a different data object. The "jti" claim
# can be used to prevent the JWT from being replayed. The "jti" value
# is a case-sensitive string. Use of this claim is OPTIONAL.
it 'should specify "jti" as a case-sensitive string', ->
JWT.registeredClaims.jti.format.should.equal 'String'
# JWT.registeredClaims.jti.format.should.equal 'CaseSensitiveString'
# 4.2. Public Claim Names
#
# Claim Names can be defined at will by those using JWTs. However, in
# order to prevent collisions, any new Claim Name should either be
# registered in the IANA JSON Web Token Claims registry defined in
# Section 10.1 or be a Public Name: a value that contains a Collision-
# Resistant Name. In each case, the definer of the name or value needs
# to take reasonable precautions to make sure they are in control of
# the part of the namespace they use to define the Claim Name.
#
# 4.3. Private Claim Names
#
# A producer and consumer of a JWT MAY agree to use Claim Names that
# are Private Names: names that are not Registered Claim Names
# Section 4.1 or Public Claim Names Section 4.2. Unlike Public Claim
# Names, Private Claim Names are subject to collision and should be
# used with caution.
# 5. JWT Header
#
# http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-18#section-5
#
# The members of the JSON object represented by the JWT Header describe
# the cryptographic operations applied to the JWT and optionally,
# additional properties of the JWT. The member names within the JWT
# Header are referred to as Header Parameter Names. These names MUST
# be unique; recipients MUST either reject JWTs with duplicate Header
# Parameter Names or use a JSON parser that returns only the lexically
# last duplicate member name, as specified in Section 15.12 (The JSON
# Object) of ECMAScript 5.1 [ECMAScript]. The corresponding values are
# referred to as Header Parameter Values.
#
# JWS Header Parameters are defined by [JWS]. JWE Header Parameters
# are defined by [JWE]. This specification further specifies the use
# of the following Header Parameters in both the cases where the JWT is
# a JWS and where it is a JWE.
describe 'registered headers', ->
# 4.1.1. "alg" (Algorithm) Header Parameter
#
# http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-23#section-4.1.1
#
# The "alg" (algorithm) Header Parameter identifies the cryptographic
# algorithm used to secure the JWS. The signature, MAC, or plaintext
# value is not valid if the "alg" value does not represent a supported
# algorithm, or if there is not a key for use with that algorithm
# associated with the party that digitally signed or MACed the content.
# "alg" values should either be registered in the IANA JSON Web
# Signature and Encryption Algorithms registry defined in [JWA] or be a
# value that contains a Collision-Resistant Name. The "alg" value is a
# case-sensitive string containing a StringOrURI value. This Header
# Parameter MUST be present and MUST be understood and processed by
# implementations.
# A list of defined "alg" values for this use can be found in the IANA
# JSON Web Signature and Encryption Algorithms registry defined in
# [JWA]; the initial contents of this registry are the values defined
# in Section 3.1 of the JSON Web Algorithms (JWA) [JWA] specification.
it 'should specify "alg" to be required', ->
JWT.registeredHeaders.alg.required.should.equal true
it 'should specify "alg" to be a StringOrURI', ->
JWT.registeredHeaders.alg.format.should.equal 'StringOrURI'
it 'should specify "alg" to require a supported algorithm', ->
JWT.registeredHeaders.alg.enum.should.eql JWT.algorithms
# 5.1. "typ" (Type) Header Parameter
#
# http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-18#section-5.1
#
# The "typ" (type) Header Parameter defined by [JWS] and [JWE] is used
# to declare the MIME Media Type [IANA.MediaTypes] of this complete JWT
# in contexts where this is useful to the application. This parameter
# has no effect upon the JWT processing. If present, it is RECOMMENDED
# that its value be "JWT" to indicate that this object is a JWT. While
# media type names are not case-sensitive, it is RECOMMENDED that "JWT"
# always be spelled using uppercase characters for compatibility with
# legacy implementations. Use of this Header Parameter is OPTIONAL.
# 4.1.8. "typ" (Type) Header Parameter
#
# http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-23#section-4.1.8
#
# The "typ" (type) Header Parameter is used to declare the MIME Media
# Type [IANA.MediaTypes] of this complete JWS object in contexts where
# this is useful to the application. This parameter has no effect upon
# the JWS processing. Use of this Header Parameter is OPTIONAL.
#
# Per [RFC2045], all media type values, subtype values, and parameter
# names are case-insensitive. However, parameter values are case-
# sensitive unless otherwise specified for the specific parameter.
#
# To keep messages compact in common situations, it is RECOMMENDED that
# senders omit an "application/" prefix of a media type value in a
# "typ" Header Parameter when no other '/' appears in the media type
# value. A recipient using the media type value MUST treat it as if
# "application/" were prepended to any "typ" value not containing a
# '/'. For instance, a "typ" value of "example" SHOULD be used to
# represent the "application/example" media type; whereas, the media
# type "application/example;part="1/2"" cannot be shortened to
# "example;part="1/2"".
#
# The "typ" value "JOSE" can be used by applications to indicate that
# this object is a JWS or JWE using the JWS Compact Serialization or
# the JWE Compact Serialization. The "typ" value "JOSE+JSON" can be
# used by applications to indicate that this object is a JWS or JWE
# using the JWS JSON Serialization or the JWE JSON Serialization.
# Other type values can also be used by applications.
it 'should specify "typ" to be a String', ->
JWT.registeredHeaders.typ.format.should.equal 'String'
it 'should specify "typ" to default to "JWT"', ->
JWT.registeredHeaders.typ.default.should.equal 'JWT'
# 5.2. "cty" (Content Type) Header Parameter
#
# http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-18#section-5.2
#
# The "cty" (content type) Header Parameter defined by [JWS] and [JWE]
# is used by this specification to convey structural information about
# the JWT.
#
# In the normal case where nested signing or encryption operations are
# not employed, the use of this Header Parameter is NOT RECOMMENDED.
# In the case that nested signing or encryption is employed, this
# Header Parameter MUST be present; in this case, the value MUST be
# "JWT", to indicate that a Nested JWT is carried in this JWT. While
# media type names are not case-sensitive, it is RECOMMENDED that "JWT"
# always be spelled using uppercase characters for compatibility with
# legacy implementations. See Appendix A.2 for an example of a Nested
# JWT.
# 4.1.9. "cty" (Content Type) Header Parameter
#
# http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-23#section-4.1.9
#
# The "cty" (content type) Header Parameter is used to declare the MIME
# Media Type [IANA.MediaTypes] of the secured content (the payload) in
# contexts where this is useful to the application. This parameter has
# no effect upon the JWS processing. Use of this Header Parameter is
# OPTIONAL.
#
# Per [RFC2045], all media type values, subtype values, and parameter
# names are case-insensitive. However, parameter values are case-
# sensitive unless otherwise specified for the specific parameter.
#
# To keep messages compact in common situations, it is RECOMMENDED that
# senders omit an "application/" prefix of a media type value in a
# "cty" Header Parameter when no other '/' appears in the media type
# value. A recipient using the media type value MUST treat it as if
# "application/" were prepended to any "cty" value not containing a
# '/'. For instance, a "cty" value of "example" SHOULD be used to
# represent the "application/example" media type; whereas, the media
# type "application/example;part="1/2"" cannot be shortened to
# "example;part="1/2"".
it 'should specify "cty" to be a String', ->
JWT.registeredHeaders.cty.format.should.equal 'String'
it 'should specify "cty" to require "JWT" as the value', ->
JWT.registeredHeaders.cty.enum.length.should.equal 1
JWT.registeredHeaders.cty.enum.should.contain 'JWT'
it 'should specify "cty" to be required if the JWT is nested'
# 4.1.2. "jku" (JWK Set URL) Header Parameter
#
# http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-23#section-4.1.2
#
# The "jku" (JWK Set URL) Header Parameter is a URI [RFC3986] that
# refers to a resource for a set of JSON-encoded public keys, one of
# which corresponds to the key used to digitally sign the JWS. The
# keys MUST be encoded as a JSON Web Key Set (JWK Set) [JWK]. The
# protocol used to acquire the resource MUST provide integrity
# protection; an HTTP GET request to retrieve the JWK Set MUST use TLS
# [RFC2818] [RFC5246]; the identity of the server MUST be validated, as
# per Section 3.1 of HTTP Over TLS [RFC2818]. Use of this Header
# Parameter is OPTIONAL.
it 'should specify "jku" to be a URI', ->
JWT.registeredHeaders.jku.format.should.equal 'URI'
# 4.1.3. "jwk" (JSON Web Key) Header Parameter
#
# http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-23#section-4.1.3
#
# The "jwk" (JSON Web Key) Header Parameter is the public key that
# corresponds to the key used to digitally sign the JWS. This key is
# represented as a JSON Web Key [JWK]. Use of this Header Parameter is
# OPTIONAL.
it 'should specify "jwk" to be a JWK', ->
JWT.registeredHeaders.jwk.format.should.equal 'JWK'
# 4.1.4. "kid" (Key ID) Header Parameter
#
# http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-23#section-4.1.4
#
# The "kid" (key ID) Header Parameter is a hint indicating which key
# was used to secure the JWS. This parameter allows originators to
# explicitly signal a change of key to recipients. The structure of
# the "kid" value is unspecified. Its value MUST be a string. Use of
# this Header Parameter is OPTIONAL.
#
# When used with a JWK, the "kid" value is used to match a JWK "kid"
# parameter value.
it 'should specify "kid" to be a String', ->
JWT.registeredHeaders.kid.format.should.equal 'String'
# 4.1.5. "x5u" (X.509 URL) Header Parameter
#
# http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-23#section-4.1.5
#
# The "x5u" (X.509 URL) Header Parameter is a URI [RFC3986] that refers
# to a resource for the X.509 public key certificate or certificate
# chain [RFC5280] corresponding to the key used to digitally sign the
# JWS. The identified resource MUST provide a representation of the
# certificate or certificate chain that conforms to RFC 5280 [RFC5280]
# in PEM encoded form [RFC1421]. The certificate containing the public
# key corresponding to the key used to digitally sign the JWS MUST be
# the first certificate. This MAY be followed by additional
# certificates, with each subsequent certificate being the one used to
# certify the previous one. The protocol used to acquire the resource
# MUST provide integrity protection; an HTTP GET request to retrieve
# the certificate MUST use TLS [RFC2818] [RFC5246]; the identity of the
# server MUST be validated, as per Section 3.1 of HTTP Over TLS
# [RFC2818]. Use of this Header Parameter is OPTIONAL.
it 'should specify "x5u" to be a URI', ->
JWT.registeredHeaders.x5u.format.should.equal 'URI'
# 4.1.6. "x5c" (X.509 Certificate Chain) Header Parameter
#
# http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-23#section-4.1.6
#
# The "x5c" (X.509 Certificate Chain) Header Parameter contains the
# X.509 public key certificate or certificate chain [RFC5280]
# corresponding to the key used to digitally sign the JWS. The
# certificate or certificate chain is represented as a JSON array of
# certificate value strings. Each string in the array is a base64
# encoded ([RFC4648] Section 4 -- not base64url encoded) DER
# [ITU.X690.1994] PKIX certificate value. The certificate containing
# the public key corresponding to the key used to digitally sign the
# JWS MUST be the first certificate. This MAY be followed by
# additional certificates, with each subsequent certificate being the
# one used to certify the previous one. The recipient MUST validate
# the certificate chain according to [RFC5280] and reject the signature
# if any validation failure occurs. Use of this Header Parameter is
# OPTIONAL.
#
# See Appendix B for an example "x5c" value.
it 'should specify "x5c" to be a CertificateOrChain', ->
JWT.registeredHeaders.x5c.format.should.equal 'CertificateOrChain'
# 4.1.7. "x5t" (X.509 Certificate SHA-1 Thumbprint) Header Parameter
#
# http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-23#section-4.1.7
#
# The "x5t" (X.509 Certificate SHA-1 Thumbprint) Header Parameter is a
# base64url encoded SHA-1 thumbprint (a.k.a. digest) of the DER
# encoding of the X.509 certificate [RFC5280] corresponding to the key
# used to digitally sign the JWS. Use of this Header Parameter is
# OPTIONAL.
#
# If, in the future, certificate thumbprints need to be computed using
# hash functions other than SHA-1, it is suggested that additional
# related Header Parameters be defined for that purpose. For example,
# it is suggested that a new "x5t#S256" (X.509 Certificate Thumbprint
# using SHA-256) Header Parameter could be defined by registering it in
# the IANA JSON Web Signature and Encryption Header Parameters registry
# defined in Section 9.1.
it 'should specify "x5t" to be a CertificateThumbprint', ->
JWT.registeredHeaders.x5t.format.should.equal 'CertificateThumbprint'
# 4.1.10. "crit" (Critical) Header Parameter
#
# http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-23#section-4.1.10
#
# The "crit" (critical) Header Parameter indicates that extensions to
# the initial RFC versions of [[ this specification ]] and [JWA] are
# being used that MUST be understood and processed. Its value is an
# array listing the Header Parameter names present in the JWS Header
# that use those extensions. If any of the listed extension Header
# Parameters are not understood and supported by the receiver, it MUST
# reject the JWS. Senders MUST NOT include Header Parameter names
# defined by the initial RFC versions of [[ this specification ]] or
# [JWA] for use with JWS, duplicate names, or names that do not occur
# as Header Parameter names within the JWS Header in the "crit" list.
# Senders MUST NOT use the empty list "[]" as the "crit" value.
# Recipients MAY reject the JWS if the critical list contains any
# Header Parameter names defined by the initial RFC versions of [[ this
# specification ]] or [JWA] for use with JWS, or any other constraints
# on its use are violated. This Header Parameter MUST be integrity
# protected, and therefore MUST occur only within the JWS Protected
# Header, when used. Use of this Header Parameter is OPTIONAL. This
# Header Parameter MUST be understood and processed by implementations.
#
# An example use, along with a hypothetical "exp" (expiration-time)
# field is:
#
# {"alg":"ES256",
# "crit":["exp"],
# "exp":1363284000
# }
it 'should specify "crit" to be a ParameterList', ->
JWT.registeredHeaders.crit.format.should.equal 'ParameterList'
describe 'define', ->
{MyJWT,header,headers,claims} = {}
before ->
registeredHeaders =
alg: { format: 'String', enum: ['RS256'] }
new: { format: 'IntDate' }
registeredClaims =
iss: { format: 'StringOrURI', required: true }
header = { alg: 'RS256' }
headers = ['alg']
claims = ['iss', 'sub', 'aud']
MyJWT = JWT.define {
registeredHeaders
registeredClaims
headers
header
claims
}
it 'should return a subclass of JWT', ->
expect(new MyJWT).to.be.instanceof JWT
it 'should set the correct constructor', ->
MyJWT.prototype.constructor.should.equal MyJWT
it 'should register header definitions', ->
MyJWT.registeredHeaders.alg.enum.length.should.equal 1
#MyJWT.registeredHeaders.alg.required.should.equal true
#MyJWT.registeredHeaders.alg.format.should.equal 'StringOrURI'
MyJWT.registeredHeaders.new.format.should.equal 'IntDate'
it 'should register claim definitions', ->
MyJWT.registeredClaims.iss.required.should.equal true
it 'should select applicable headers', ->
MyJWT.headers.should.equal headers
it 'should select applicable claims', ->
MyJWT.claims.should.equal claims
it 'should initialize a default header', ->
MyJWT.prototype.header.alg.should.equal header.alg
MyJWT.prototype.headerB64u.should.equal base64url(JSON.stringify(header))
# 7. Rules for Creating and Validating a JWT
#
# http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-18#section-7
#
# To create a JWT, the following steps MUST be taken. The order of the
# steps is not significant in cases where there are no dependencies
# between the inputs and outputs of the steps.
#
# 1. Create a JWT Claims Set containing the desired claims. Note that
# white space is explicitly allowed in the representation and no
# canonicalization need be performed before encoding.
#
# 2. Let the Message be the octets of the UTF-8 representation of the
# JWT Claims Set.
#
# 3. Create a JWT Header containing the desired set of Header
# Parameters. The JWT MUST conform to either the [JWS] or [JWE]
# specifications. Note that white space is explicitly allowed in
# the representation and no canonicalization need be performed
# before encoding.
#
# 4. Depending upon whether the JWT is a JWS or JWE, there are two
# cases:
#
# * If the JWT is a JWS, create a JWS using the JWT Header as the
# JWS Header and the Message as the JWS Payload; all steps
# specified in [JWS] for creating a JWS MUST be followed.
#
# * Else, if the JWT is a JWE, create a JWE using the JWT Header
# as the JWE Header and the Message as the JWE Plaintext; all
# steps specified in [JWE] for creating a JWE MUST be followed.
#
# 5. If a nested signing or encryption operation will be performed,
# let the Message be the JWS or JWE, and return to Step 3, using a
# "cty" (content type) value of "JWT" in the new JWT Header created
# in that step.
#
# 6. Otherwise, let the resulting JWT be the JWS or JWE.
describe 'encode', ->
it 'should assert the presence of a payload'
it 'should ensure the presence of a header'
# 4. JWT Claims
#
# http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-18#section-4
#
# [...]
#
# The set of claims that a JWT must contain to be considered valid is
# context-dependent and is outside the scope of this specification.
# Specific applications of JWTs will require implementations to
# understand and process some claims in particular ways. However, in
# the absence of such requirements, all claims that are not understood
# by implementations MUST be ignored.
it 'should ignore unspecified claims'
# LIBRARY SPECIFIC BEHAVIORS
it 'should validate specified claims'
it 'should assign default claims'
# 6. Plaintext JWTs
#
# http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-18#section-6
#
# To support use cases where the JWT content is secured by a means
# other than a signature and/or encryption contained within the JWT
# (such as a signature on a data structure containing the JWT), JWTs
# MAY also be created without a signature or encryption. A plaintext
# JWT is a JWS using the "none" JWS "alg" Header Parameter Value
# defined in JSON Web Algorithms (JWA) [JWA]; it is a JWS with the
# empty string for its JWS Signature value.
#
# 6.1. Example Plaintext JWT
#
# The following example JWT Header declares that the encoded object is
# a Plaintext JWT:
#
# {"alg":"none"}
#
# Base64url encoding the octets of the UTF-8 representation of the JWT
# Header yields this Encoded JWT Header:
#
# eyJhbGciOiJub25lIn0
#
# The following is an example of a JWT Claims Set:
#
# {"iss":"joe",
# "exp":1300819380,
# "http://example.com/is_root":true}
#
# Base64url encoding the octets of the UTF-8 representation of the JWT
# Claims Set yields this encoded JWS Payload (with line breaks for
# display purposes only):
#
# eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt
# cGxlLmNvbS9pc19yb290Ijp0cnVlfQ
#
# The encoded JWS Signature is the empty string.
#
# Concatenating these encoded parts in this order with period ('.')
# characters between the parts yields this complete JWT (with line
# breaks for display purposes only):
#
# eyJhbGciOiJub25lIn0
# .
# eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt
# cGxlLmNvbS9pc19yb290Ijp0cnVlfQ
# .
describe 'plaintext', ->
{jwt,token,header,headerJSON,payload,payloadJSON,signature} = {}
before ->
header = { alg: 'none' }
headerJSON = JSON.stringify(header)
headers = ['alg']
payload = { iss: 'http://anvil.io' }
payloadJSON = JSON.stringify(payload)
MyJWT = JWT.define {header,headers}
jwt = new MyJWT { iss: 'http://anvil.io' }
token = jwt.encode()
it 'should base64url encode the header', ->
base64url.decode(token.split('.')[0]).should.equal headerJSON
it 'should base64url encode the payload', ->
base64url.decode(token.split('.')[1]).should.equal payloadJSON
it 'should append an empty signature', ->
token.split('.')[2].should.equal ''
# 5.1. Message Signature or MAC Computation
#
# http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-23#section-5.1
#
# To create a JWS, one MUST perform these steps. The order of the
# steps is not significant in cases where there are no dependencies
# between the inputs and outputs of the steps.
# 1. Create the content to be used as the JWS Payload.
# 2. Compute the encoded payload value BASE64URL(JWS Payload).
# 3. Create the JSON object(s) containing the desired set of Header
# Parameters, which together comprise the JWS Header: the JWS
# Protected Header, and if the JWS JSON Serialization is being
# used, the JWS Unprotected Header.
# 4. Compute the encoded header value BASE64URL(UTF8(JWS Protected
# Header)). If the JWS Protected Header is not present (which can
# only happen when using the JWS JSON Serialization and no
# "protected" member is present), let this value be the empty
# string.
# 5. Compute the JWS Signature in the manner defined for the
# particular algorithm being used over the JWS Signing Input
# ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.' ||
# BASE64URL(JWS Payload)). The "alg" (algorithm) Header Parameter
# MUST be present in the JWS Header, with the algorithm value
# accurately representing the algorithm used to construct the JWS
# Signature.
# 6. Compute the encoded signature value BASE64URL(JWS Signature).
# 7. These three encoded values are used in both the JWS Compact
# Serialization and the JWS JSON Serialization representations.
# 8. If the JWS JSON Serialization is being used, repeat this process
# (steps 3-7) for each digital signature or MAC operation being
# performed.
# 9. Create the desired serialized output. The JWS Compact
# Serialization of this result is BASE64URL(UTF8(JWS Protected
# Header)) || '.' || BASE64URL(JWS Payload) || '.' || BASE64URL(JWS
# Signature). The JWS JSON Serialization is described in
# Section 7.2.
# 8. Cryptographic Algorithms
#
# http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-18#section-8
#
# JWTs use JSON Web Signature (JWS) [JWS] and JSON Web Encryption (JWE)
# [JWE] to sign and/or encrypt the contents of the JWT.
#
# Of the signature and MAC algorithms specified in JSON Web Algorithms
# (JWA) [JWA], only HMAC SHA-256 ("HS256") and "none" MUST be
# implemented by conforming JWT implementations. It is RECOMMENDED
# that implementations also support RSASSA-PKCS1-V1_5 with the SHA-256
# hash algorithm ("RS256") and ECDSA using the P-256 curve and the SHA-
# 256 hash algorithm ("ES256"). Support for other algorithms and key
# sizes is OPTIONAL.
#
# [...]
describe 'with signature', ->
{MyJWT,header,payload,jwt,token} = {}
describe 'via HS256', ->
before ->
header = { alg: 'HS256' }
headers = ['alg']
payload = { iss: 'http://anvil.io' }
MyJWT = JWT.define {header,headers}
jwt = new MyJWT payload
token = jwt.encode('secret')
it 'should include the base64url encoded header', ->
token.split('.')[0].should.equal MyJWT.prototype.headerB64u
it 'should base64url encode the payload', ->
base64url.decode(token.split('.')[1]).should.equal JSON.stringify(payload)
it 'should append a HMAC SHA256 signature', ->
token.split('.')[2].length.should.equal 43
describe 'via RS256', ->
before ->
header = { alg: 'RS256' }
headers = ['alg']
payload = { iss: 'http://anvil.io' }
privateKey = require('fs')
.readFileSync('test/lib/keys/private.pem')
.toString('ascii')
MyJWT = JWT.define {header,headers}
jwt = new MyJWT payload
token = jwt.encode(privateKey)
it 'should include the base64url encoded header', ->
token.split('.')[0].should.equal MyJWT.prototype.headerB64u
it 'should base64url encode the payload', ->
base64url.decode(token.split('.')[1]).should.equal JSON.stringify(payload)
it 'should append a RSA SHA256 signature', ->
token.split('.')[2].length.should.equal 342
describe 'via ES256', ->
before ->
header = { alg: 'ES256' }
headers = ['alg']
payload = { iss: 'http://anvil.io' }
privateKey = require('fs')
.readFileSync('test/lib/keys/private_ecdsa.pem')
.toString('ascii')
MyJWT = JWT.define {header,headers}
jwt = new MyJWT payload
token = jwt.encode(privateKey)
it 'should include the base64url encoded header', ->
token.split('.')[0].should.equal MyJWT.prototype.headerB64u
it 'should base64url encode the payload', ->
base64url.decode(token.split('.')[1]).should.equal JSON.stringify(payload)
it 'should append a ECDSA SHA256 signature', ->
token.split('.')[2].length.should.equal 86
# 8. Cryptographic Algorithms
#
# http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-18#section-8
#
# JWTs use JSON Web Signature (JWS) [JWS] and JSON Web Encryption (JWE)