UNPKG

anvil-connect-jwt

Version:
1,301 lines (979 loc) 61.3 kB
# 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)